@rocicorp/zero 1.10.0-canary.2 → 1.10.0-canary.3

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 (178) hide show
  1. package/out/zero/package.js +1 -1
  2. package/out/zero/package.js.map +1 -1
  3. package/out/zero-cache/src/auth/write-authorizer.d.ts.map +1 -1
  4. package/out/zero-cache/src/auth/write-authorizer.js +8 -1
  5. package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
  6. package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
  7. package/out/zero-cache/src/config/normalize.js +7 -2
  8. package/out/zero-cache/src/config/normalize.js.map +1 -1
  9. package/out/zero-cache/src/config/zero-config.d.ts +14 -4
  10. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  11. package/out/zero-cache/src/config/zero-config.js +26 -8
  12. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  13. package/out/zero-cache/src/db/transaction-pool.d.ts +5 -2
  14. package/out/zero-cache/src/db/transaction-pool.d.ts.map +1 -1
  15. package/out/zero-cache/src/db/transaction-pool.js +5 -3
  16. package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
  17. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  18. package/out/zero-cache/src/server/change-streamer.js +24 -13
  19. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  20. package/out/zero-cache/src/server/runner/run-worker.js +1 -1
  21. package/out/zero-cache/src/server/worker-urls.d.ts +0 -1
  22. package/out/zero-cache/src/server/worker-urls.d.ts.map +1 -1
  23. package/out/zero-cache/src/server/worker-urls.js +1 -2
  24. package/out/zero-cache/src/server/worker-urls.js.map +1 -1
  25. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +1 -1
  26. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  27. package/out/zero-cache/src/services/change-source/pg/change-source.js +1 -1
  28. package/out/zero-cache/src/services/change-source/pg/decommission.d.ts.map +1 -1
  29. package/out/zero-cache/src/services/change-source/pg/decommission.js +20 -11
  30. package/out/zero-cache/src/services/change-source/pg/decommission.js.map +1 -1
  31. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  32. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +51 -74
  33. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  34. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts +20 -0
  35. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts.map +1 -1
  36. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +43 -3
  37. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
  38. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +24 -1
  39. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +1 -1
  40. package/out/zero-cache/src/services/change-source/pg/replication-slots.js +35 -5
  41. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
  42. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts +1 -4
  43. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts.map +1 -1
  44. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js +11 -18
  45. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js.map +1 -1
  46. package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts.map +1 -1
  47. package/out/zero-cache/src/services/change-streamer/backup-monitor.js +8 -3
  48. package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
  49. package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.d.ts +34 -0
  50. package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.d.ts.map +1 -0
  51. package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.js +67 -0
  52. package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.js.map +1 -0
  53. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +2 -4
  54. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  55. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +8 -2
  56. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  57. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +34 -13
  58. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  59. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +149 -27
  60. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  61. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +3 -9
  62. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  63. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  64. package/out/zero-cache/src/services/change-streamer/shard-sampling.d.ts +11 -0
  65. package/out/zero-cache/src/services/change-streamer/shard-sampling.d.ts.map +1 -0
  66. package/out/zero-cache/src/services/change-streamer/shard-sampling.js +19 -0
  67. package/out/zero-cache/src/services/change-streamer/shard-sampling.js.map +1 -0
  68. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts +11 -1
  69. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts.map +1 -1
  70. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js +38 -5
  71. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -1
  72. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts +25 -1
  73. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts.map +1 -1
  74. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js +95 -12
  75. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js.map +1 -1
  76. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.d.ts +83 -0
  77. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.d.ts.map +1 -0
  78. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.js +417 -0
  79. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.js.map +1 -0
  80. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.d.ts +73 -0
  81. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.d.ts.map +1 -0
  82. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.js +157 -0
  83. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.js.map +1 -0
  84. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts +1 -1
  85. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts.map +1 -1
  86. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js +8 -3
  87. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js.map +1 -1
  88. package/out/zero-cache/src/services/change-streamer/storer.d.ts +23 -0
  89. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  90. package/out/zero-cache/src/services/change-streamer/storer.js +40 -0
  91. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  92. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.d.ts +29 -0
  93. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.d.ts.map +1 -0
  94. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.js +154 -0
  95. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.js.map +1 -0
  96. package/out/zero-cache/src/services/http-service.d.ts +1 -0
  97. package/out/zero-cache/src/services/http-service.d.ts.map +1 -1
  98. package/out/zero-cache/src/services/http-service.js +7 -3
  99. package/out/zero-cache/src/services/http-service.js.map +1 -1
  100. package/out/zero-cache/src/services/litestream/metrics.d.ts +1 -0
  101. package/out/zero-cache/src/services/litestream/metrics.d.ts.map +1 -1
  102. package/out/zero-cache/src/services/litestream/metrics.js +4 -1
  103. package/out/zero-cache/src/services/litestream/metrics.js.map +1 -1
  104. package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
  105. package/out/zero-cache/src/services/mutagen/mutagen.js +1 -1
  106. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  107. package/out/zero-cache/src/services/replicator/change-log-db.d.ts +10 -0
  108. package/out/zero-cache/src/services/replicator/change-log-db.d.ts.map +1 -1
  109. package/out/zero-cache/src/services/replicator/change-log-db.js +15 -1
  110. package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -1
  111. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts +3 -2
  112. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts.map +1 -1
  113. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js +8 -6
  114. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js.map +1 -1
  115. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts +3 -2
  116. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +1 -1
  117. package/out/zero-cache/src/services/view-syncer/client-handler.js +25 -17
  118. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  119. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
  120. package/out/zero-cache/src/services/view-syncer/cvr-store.js +14 -1
  121. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  122. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +3 -3
  123. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  124. package/out/zero-cache/src/services/view-syncer/view-syncer.js +6 -2
  125. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  126. package/out/zero-cache/src/types/downstream.d.ts +11 -0
  127. package/out/zero-cache/src/types/downstream.d.ts.map +1 -0
  128. package/out/zero-cache/src/types/pg.d.ts.map +1 -1
  129. package/out/zero-cache/src/types/pg.js +14 -4
  130. package/out/zero-cache/src/types/pg.js.map +1 -1
  131. package/out/zero-cache/src/types/poke-chunk.d.ts +18 -0
  132. package/out/zero-cache/src/types/poke-chunk.d.ts.map +1 -0
  133. package/out/zero-cache/src/types/poke-chunk.js +57 -0
  134. package/out/zero-cache/src/types/poke-chunk.js.map +1 -0
  135. package/out/zero-cache/src/types/processes.js +1 -4
  136. package/out/zero-cache/src/types/processes.js.map +1 -1
  137. package/out/zero-cache/src/workers/connection.d.ts +22 -4
  138. package/out/zero-cache/src/workers/connection.d.ts.map +1 -1
  139. package/out/zero-cache/src/workers/connection.js +112 -14
  140. package/out/zero-cache/src/workers/connection.js.map +1 -1
  141. package/out/zero-client/src/client/version.js +1 -1
  142. package/out/zero-client/src/client/zero-poke-handler.d.ts +7 -2
  143. package/out/zero-client/src/client/zero-poke-handler.d.ts.map +1 -1
  144. package/out/zero-client/src/client/zero-poke-handler.js +52 -2
  145. package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
  146. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  147. package/out/zero-client/src/client/zero.js +38 -6
  148. package/out/zero-client/src/client/zero.js.map +1 -1
  149. package/out/zero-protocol/src/down.d.ts +1 -1
  150. package/out/zero-protocol/src/poke.d.ts +89 -7
  151. package/out/zero-protocol/src/poke.d.ts.map +1 -1
  152. package/out/zero-protocol/src/poke.js +16 -8
  153. package/out/zero-protocol/src/poke.js.map +1 -1
  154. package/out/zero-protocol/src/protocol-version.d.ts +12 -7
  155. package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
  156. package/out/zero-protocol/src/protocol-version.js.map +1 -1
  157. package/out/zql/src/ivm/memory-source.d.ts +10 -2
  158. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  159. package/out/zql/src/ivm/memory-source.js +14 -6
  160. package/out/zql/src/ivm/memory-source.js.map +1 -1
  161. package/out/zql/src/ivm/stream.d.ts +0 -1
  162. package/out/zql/src/ivm/stream.d.ts.map +1 -1
  163. package/out/zql/src/ivm/stream.js +1 -7
  164. package/out/zql/src/ivm/stream.js.map +1 -1
  165. package/out/zql/src/ivm/union-fan-in.d.ts +1 -1
  166. package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
  167. package/out/zql/src/ivm/union-fan-in.js +11 -2
  168. package/out/zql/src/ivm/union-fan-in.js.map +1 -1
  169. package/out/zqlite/src/table-source.d.ts.map +1 -1
  170. package/out/zqlite/src/table-source.js +1 -1
  171. package/out/zqlite/src/table-source.js.map +1 -1
  172. package/package.json +1 -1
  173. package/out/zero-cache/src/server/backup-watermark-poller.js +0 -31
  174. package/out/zero-cache/src/server/backup-watermark-poller.js.map +0 -1
  175. package/out/zero-cache/src/services/litestream/vfs-watermark-poller.d.ts +0 -57
  176. package/out/zero-cache/src/services/litestream/vfs-watermark-poller.d.ts.map +0 -1
  177. package/out/zero-cache/src/services/litestream/vfs-watermark-poller.js +0 -137
  178. package/out/zero-cache/src/services/litestream/vfs-watermark-poller.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"zero-config.js","names":[],"sources":["../../../../../zero-cache/src/config/zero-config.ts"],"sourcesContent":["/**\n * These types represent the _compiled_ config whereas `define-config` types represent the _source_ config.\n */\n\nimport {timingSafeEqual} from 'node:crypto';\nimport type {LogContext} from '@rocicorp/logger';\nimport {logOptions} from '../../../otel/src/log-options.ts';\nimport {\n flagToEnv,\n parseOptions,\n type Config,\n type ParseOptions,\n} from '../../../shared/src/options.ts';\nimport * as v from '../../../shared/src/valita.ts';\n// @circular-dep-ignore - importing package.json for version info only\nimport packageJson from '../../../zero/package.json' with {type: 'json'};\nimport {runtimeDebugFlags} from '../../../zql/src/builder/debug-delegate.ts';\nimport {singleProcessMode} from '../types/processes.ts';\nimport {\n ALLOWED_APP_ID_CHARACTERS,\n INVALID_APP_ID_MESSAGE,\n} from '../types/shards.ts';\nimport {DEFAULT_PREFERRED_PREFIXES} from './network.ts';\nimport {\n assertNormalized,\n isDevelopmentMode,\n type NormalizedZeroConfig,\n} from './normalize.ts';\nexport type {LogConfig} from '../../../otel/src/log-options.ts';\n\nexport const ZERO_ENV_VAR_PREFIX = 'ZERO_';\n\nexport const appOptions = {\n id: {\n type: v\n .string()\n .default('zero')\n .assert(id => ALLOWED_APP_ID_CHARACTERS.test(id), INVALID_APP_ID_MESSAGE),\n desc: [\n 'Unique identifier for the app.',\n '',\n 'Multiple zero-cache apps can run on a single upstream database, each of which',\n 'is isolated from the others, with its own permissions, sharding (future feature),',\n 'and change/cvr databases.',\n '',\n 'The metadata of an app is stored in an upstream schema with the same name,',\n 'e.g. \"zero\", and the metadata for each app shard, e.g. client and mutation',\n 'ids, is stored in the \"\\\\{app-id\\\\}_\\\\{#\\\\}\" schema. (Currently there is only a single',\n '\"0\" shard, but this will change with sharding).',\n '',\n 'The CVR and Change data are managed in schemas named \"\\\\{app-id\\\\}_\\\\{shard-num\\\\}/cvr\"',\n 'and \"\\\\{app-id\\\\}_\\\\{shard-num\\\\}/cdc\", respectively, allowing multiple apps and shards',\n 'to share the same database instance (e.g. a Postgres \"cluster\") for CVR and Change management.',\n '',\n 'Due to constraints on replication slot names, an App ID may only consist of',\n 'lower-case letters, numbers, and the underscore character.',\n '',\n 'Note that this option is used by both {bold zero-cache} and {bold zero-deploy-permissions}.',\n ],\n },\n\n publications: {\n type: v.array(v.string()).optional(() => []),\n desc: [\n `Postgres {bold PUBLICATION}s that define the tables and columns to`,\n `replicate. Publication names may not begin with an underscore,`,\n `as zero reserves that prefix for internal use.`,\n ``,\n `If unspecified, zero-cache will create and use an internal publication that`,\n `publishes all tables in the {bold public} schema, i.e.:`,\n ``,\n `CREATE PUBLICATION _\\\\{app-id\\\\}_public_0 FOR TABLES IN SCHEMA public;`,\n ``,\n `Note that changing the set of publications will result in resyncing the replica,`,\n `which may involve downtime (replication lag) while the new replica is initializing.`,\n `To change the set of publications without disrupting an existing app, a new app`,\n `should be created.`,\n ],\n },\n};\n\nexport const shardOptions = {\n id: {\n type: v\n .string()\n .assert(() => {\n throw new Error(\n `ZERO_SHARD_ID is no longer an option. Please use ZERO_APP_ID instead.`,\n // TODO: Link to release / migration notes?\n );\n })\n .optional(),\n hidden: true,\n },\n\n num: {\n type: v.number().default(0),\n desc: [\n `The shard number (from 0 to NUM_SHARDS) of the App. zero will eventually`,\n `support data sharding as a first-class primitive; until then, deploying`,\n `multiple shard-nums creates functionally identical shards. Until sharding is`,\n `actually meaningful, this flag is hidden but available for testing.`,\n ],\n hidden: true,\n },\n};\n\nconst replicaOptions = {\n file: {\n type: v.string().default('zero.db'),\n desc: [\n `File path to the SQLite replica that zero-cache maintains.`,\n `This can be lost, but if it is, zero-cache will have to re-replicate next`,\n `time it starts up.`,\n ],\n },\n\n vacuumIntervalHours: {\n type: v.number().optional(),\n desc: [\n `Performs a VACUUM at server startup if the specified number of hours has elapsed`,\n `since the last VACUUM (or initial-sync). The VACUUM operation is heavyweight`,\n `and requires double the size of the db in disk space. If unspecified, VACUUM`,\n `operations are not performed.`,\n ],\n },\n};\n\nexport type ReplicaOptions = Config<typeof replicaOptions>;\n\nconst perUserMutationLimit = {\n max: {\n type: v.number().optional(),\n desc: [\n `The maximum mutations per user within the specified {bold windowMs}.`,\n `If unset, no rate limiting is enforced.`,\n ],\n },\n windowMs: {\n type: v.number().default(60_000),\n desc: [\n `The sliding window over which the {bold perUserMutationLimitMax} is enforced.`,\n ],\n },\n};\n\nexport type RateLimit = Config<typeof perUserMutationLimit>;\n\nconst authOptions = {\n jwk: {\n type: v.string().optional(),\n desc: [\n `A public key in JWK format used to verify JWTs. Only one of {bold jwk}, {bold jwksUrl} and {bold secret} may be set.`,\n ],\n deprecated: [\n `Use cookie-based authentication or an auth token instead - see https://zero.rocicorp.dev/docs/auth.`,\n ],\n },\n jwksUrl: {\n type: v.string().optional(),\n desc: [\n `A URL that returns a JWK set used to verify JWTs. Only one of {bold jwk}, {bold jwksUrl} and {bold secret} may be set.`,\n ],\n deprecated: [\n `Use cookie-based authentication or an auth token instead - see https://zero.rocicorp.dev/docs/auth.`,\n ],\n },\n secret: {\n type: v.string().optional(),\n desc: [\n `A symmetric key used to verify JWTs. Only one of {bold jwk}, {bold jwksUrl} and {bold secret} may be set.`,\n ],\n deprecated: [\n `Use cookie-based authentication or an auth token instead - see https://zero.rocicorp.dev/docs/auth.`,\n ],\n },\n issuer: {\n type: v.string().optional(),\n desc: [\n `Expected issuer ({bold iss} claim) for JWT validation.`,\n `If set, tokens with a different or missing issuer will be rejected.`,\n ],\n deprecated: [\n `Use cookie-based authentication or an auth token instead - see https://zero.rocicorp.dev/docs/auth.`,\n ],\n },\n audience: {\n type: v.string().optional(),\n desc: [\n `Expected audience ({bold aud} claim) for JWT validation.`,\n `If set, tokens with a different or missing audience will be rejected.`,\n ],\n deprecated: [\n `Use cookie-based authentication or an auth token instead - see https://zero.rocicorp.dev/docs/auth.`,\n ],\n },\n revalidateIntervalSeconds: {\n type: v.number().default(300),\n desc: [\n `The interval in seconds between periodic /query auth revalidation for validated connections.`,\n `If unset, periodic auth revalidation is disabled.`,\n ],\n },\n retransformIntervalSeconds: {\n type: v.number().default(300),\n desc: [\n `The interval in seconds between periodic shared /query retransform work for a client group.`,\n `If unset, periodic shared retransform is disabled.`,\n ],\n },\n};\n\nconst makeDeprecationMessage = (flag: string) =>\n `Use {bold ${flagToEnv(ZERO_ENV_VAR_PREFIX, flag)}} (or {bold --${flag}}) instead.`;\n\nconst makeMutatorQueryOptions = (\n replacement: 'mutate' | 'query' | undefined,\n suffix: string,\n) => ({\n url: {\n type: v.array(v.string()).optional(), // optional until we remove CRUD mutations\n desc: [\n `The URL of the API server to which zero-cache will ${suffix}.`,\n ``,\n `{bold IMPORTANT:} URLs are matched using {bold URLPattern}, a standard Web API.`,\n ``,\n `{bold Pattern Syntax:}`,\n ` URLPattern uses a simple and intuitive syntax similar to Express routes.`,\n ` Wildcards and named parameters make it easy to match multiple URLs.`,\n ``,\n `{bold Basic Examples:}`,\n ` Exact URL match:`,\n ` \"https://api.example.com/mutate\"`,\n ` `,\n ` Any subdomain using wildcard:`,\n ` \"https://*.example.com/mutate\"`,\n ` `,\n ` Multiple subdomain levels:`,\n ` \"https://*.*.example.com/mutate\"`,\n ` `,\n ` Any path under a domain:`,\n ` \"https://api.example.com/*\"`,\n ` `,\n ` Named path parameters:`,\n ` \"https://api.example.com/:version/mutate\"`,\n ` ↳ Matches \"https://api.example.com/v1/mutate\", \"https://api.example.com/v2/mutate\", etc.`,\n ``,\n `{bold Advanced Patterns:}`,\n ` Optional path segments:`,\n ` \"https://api.example.com/:path?\"`,\n ` `,\n ` Regex in segments (for specific patterns):`,\n ` \"https://api.example.com/:version(v\\\\\\\\d+)/mutate\"`,\n ` ↳ Matches only \"v\" followed by digits`,\n ``,\n `{bold Multiple patterns:}`,\n ` [\"https://api1.example.com/mutate\", \"https://api2.example.com/mutate\"]`,\n ``,\n `{bold Note:} Query parameters and URL fragments (#) are automatically ignored during matching.`,\n ``,\n `For full URLPattern syntax, see: https://developer.mozilla.org/en-US/docs/Web/API/URLPattern`,\n ],\n ...(replacement\n ? {deprecated: [makeDeprecationMessage(`${replacement}-url`)]}\n : {}),\n },\n apiKey: {\n type: v.string().optional(),\n desc: [\n `An optional secret used to authorize zero-cache to call the API server handling writes.`,\n ],\n ...(replacement\n ? {deprecated: [makeDeprecationMessage(`${replacement}-api-key`)]}\n : {}),\n },\n forwardCookies: {\n type: v.boolean().default(false),\n desc: [\n `If true, zero-cache will forward cookies from the request.`,\n `This is useful for passing authentication cookies to the API server.`,\n `If false, cookies are not forwarded.`,\n ],\n ...(replacement\n ? {deprecated: [makeDeprecationMessage(`${replacement}-forward-cookies`)]}\n : {}),\n },\n allowedClientHeaders: {\n type: v.array(v.string()).optional(),\n desc: [\n `A list of header names that clients are allowed to set via custom headers.`,\n `If specified, only headers in this list will be forwarded to the ${suffix === 'push mutations' ? 'push' : 'query'} URL.`,\n `Header names are case-insensitive.`,\n `If not specified, no client-provided headers are forwarded.`,\n `Example: ZERO_${replacement ? replacement.toUpperCase() : suffix === 'push mutations' ? 'MUTATE' : 'QUERY'}_ALLOWED_CLIENT_HEADERS=x-request-id,x-correlation-id`,\n ],\n ...(replacement\n ? {\n deprecated: [\n makeDeprecationMessage(`${replacement}-allowed-client-headers`),\n ],\n }\n : {}),\n },\n allowedRequestHeaders: {\n type: v.array(v.string()).optional(),\n desc: [\n `A list of header names to forward from the incoming HTTP request to the ${suffix === 'push mutations' ? 'push' : 'query'} URL.`,\n `Unlike {bold allowed-client-headers} (which forwards headers set by the client), these are taken`,\n `from the HTTP request that established the connection (e.g. headers injected by a proxy or load balancer).`,\n `If a listed header is present on the request, its value is forwarded upstream under the same header name.`,\n `Header names are case-insensitive.`,\n `If not specified, no request headers are forwarded.`,\n `Example: ZERO_${replacement ? replacement.toUpperCase() : suffix === 'push mutations' ? 'MUTATE' : 'QUERY'}_ALLOWED_REQUEST_HEADERS=x-forwarded-for,cf-ray`,\n ],\n ...(replacement\n ? {\n deprecated: [\n makeDeprecationMessage(`${replacement}-allowed-request-headers`),\n ],\n }\n : {}),\n },\n});\n\nconst mutateOptions = makeMutatorQueryOptions(undefined, 'push mutations');\nconst pushOptions = makeMutatorQueryOptions('mutate', 'push mutations');\nconst queryOptions = makeMutatorQueryOptions(undefined, 'send synced queries');\nconst getQueriesOptions = makeMutatorQueryOptions(\n 'query',\n 'send synced queries',\n);\n\nexport type AuthConfig = Config<typeof authOptions>;\n\n/** @deprecated used only by legacy JWT verification helpers */\nexport type LegacyJWTAuthConfig = Pick<\n AuthConfig,\n 'jwk' | 'jwksUrl' | 'secret' | 'issuer' | 'audience'\n>;\n\n// Note: --help will list flags in the order in which they are defined here,\n// so order the fields such that the important (e.g. required) ones are first.\n// (Exported for testing)\nexport const zeroOptions = {\n upstream: {\n db: {\n type: v.string(),\n desc: [\n `The \"upstream\" authoritative postgres database.`,\n `In the future we will support other types of upstream besides PG.`,\n ],\n },\n\n type: {\n type: v.literalUnion('pg', 'custom').default('pg'),\n desc: [\n `The meaning of the {bold upstream-db} depends on the upstream type:`,\n `* {bold pg}: The connection database string, e.g. \"postgres://...\"`,\n `* {bold custom}: The base URI of the change source \"endpoint, e.g.`,\n ` \"https://my-change-source.dev/changes/v0/stream?apiKey=...\"`,\n ],\n hidden: true, // TODO: Unhide when ready to officially support.\n },\n\n maxConns: {\n type: v.number().default(20),\n desc: [\n `The maximum number of connections to open to the upstream database`,\n `for committing mutations. This is divided evenly amongst sync workers.`,\n `In addition to this number, zero-cache uses one connection for the`,\n `replication stream.`,\n ``,\n `Note that this number must allow for at least one connection per`,\n `sync worker, or zero-cache will fail to start. See {bold num-sync-workers}`,\n ],\n },\n\n maxConnsPerWorker: {\n type: v.number().optional(),\n hidden: true, // Passed from main thread to sync workers\n },\n\n pgReplicationSlotFailover: {\n type: v.boolean().optional(),\n desc: [\n `For upstream Postgres versions 17+, creates replication slots with the`,\n `{bold failover} parameter set to {bold true} to enable slot synchronization`,\n `and failover. Note that additional Postgres-level configuration is necessary`,\n `when enabling this option. For details, see:`,\n ``,\n `https://www.postgresql.org/docs/current/logicaldecoding-explanation.html#LOGICALDECODING-REPLICATION-SLOTS-SYNCHRONIZATION`,\n ``,\n `(Note that this option has no effect for Postgres versions before 17.)`,\n ],\n },\n\n pgStreamInboundTimeoutMs: {\n type: v.number().optional(),\n desc: [\n `The time (in milliseconds) without any inbound message from the upstream`,\n `wal sender after which the replication stream is considered unresponsive`,\n `and torn down to force a reconnect.`,\n ``,\n `Defaults to 2x the server's {bold wal_sender_timeout}. That suits idle`,\n `streams, but a busy wal sender can be legitimately silent for longer —`,\n `e.g. while decoding through WAL from unpublished tables, or assembling a`,\n `large transaction that is only sent at commit. If the server's`,\n `{bold wal_sender_timeout} is aggressive (some managed environments`,\n `default it as low as 5 seconds), the resulting teardown aborts and`,\n `replays the in-flight transaction, which can prevent replication from`,\n `ever catching up on a large backlog. Set this option to widen the`,\n `client-side threshold without changing the server setting.`,\n ``,\n `(This option has no effect when {bold wal_sender_timeout} is 0, which`,\n `disables inbound liveness detection entirely.)`,\n ],\n },\n },\n\n /** @deprecated */\n push: pushOptions,\n mutate: mutateOptions,\n /** @deprecated */\n getQueries: getQueriesOptions,\n query: queryOptions,\n\n enableCrudMutations: {\n type: v.boolean().default(true),\n desc: [\n `Enables support for legacy CRUD mutations. When this is {bold false}, no connections`,\n `are made from view-syncers to the upstream db, and push messages with CRUD mutations`,\n `result in an InvalidPush response.`,\n ],\n },\n\n cvr: {\n db: {\n type: v.string().optional(),\n desc: [\n `The Postgres database used to store CVRs. CVRs (client view records) keep track`,\n `of the data synced to clients in order to determine the diff to send on reconnect.`,\n `If unspecified, the {bold upstream-db} will be used.`,\n ],\n },\n\n maxConns: {\n type: v.number().default(30),\n desc: [\n `The maximum number of connections to open to the CVR database.`,\n `This is divided evenly amongst sync workers.`,\n ``,\n `Note that this number must allow for at least one connection per`,\n `sync worker, or zero-cache will fail to start. See {bold num-sync-workers}`,\n ],\n },\n\n maxConnsPerWorker: {\n type: v.number().optional(),\n hidden: true, // Passed from main thread to sync workers\n },\n\n garbageCollectionInactivityThresholdHours: {\n type: v.number().default(48),\n desc: [\n `The duration after which an inactive CVR is eligible for garbage collection.`,\n `Note that garbage collection is an incremental, periodic process which does not`,\n `necessarily purge all eligible CVRs immediately.`,\n ],\n },\n\n garbageCollectionInitialIntervalSeconds: {\n type: v.number().default(60),\n desc: [\n `The initial interval at which to check and garbage collect inactive CVRs.`,\n `This interval is increased exponentially (up to 16 minutes) when there is`,\n `nothing to purge.`,\n ],\n },\n\n garbageCollectionInitialBatchSize: {\n type: v.number().default(25),\n desc: [\n `The initial number of CVRs to purge per garbage collection interval.`,\n `This number is increased linearly if the rate of new CVRs exceeds the rate of`,\n `purged CVRs, in order to reach a steady state.`,\n ``,\n `Setting this to 0 effectively disables CVR garbage collection.`,\n ],\n },\n },\n\n queryHydrationStats: {\n type: v.boolean().optional(),\n desc: [\n `Track and log the number of rows considered by query hydrations which`,\n `take longer than {bold log-slow-hydrate-threshold} milliseconds.`,\n `This is useful for debugging and performance tuning.`,\n ],\n },\n\n sqliteCorruptionChecks: {\n type: v.boolean().default(false),\n desc: [\n `Run SQLite quick_check and integrity_check when corruption is detected.`,\n `These checks scan the replica and can take a long time on large databases.`,\n ],\n hidden: true,\n },\n\n enableQueryPlanner: {\n type: v.boolean().default(true),\n desc: [\n `Enable the query planner for optimizing ZQL queries.`,\n ``,\n `The query planner analyzes and optimizes query execution by determining`,\n `the most efficient join strategies.`,\n ``,\n `You can disable the planner if it is picking bad strategies.`,\n ],\n },\n\n enableQueryCovering: {\n type: v.boolean().default(true),\n desc: [\n `Enable shadow-mode query covering detection during query hydration.`,\n ``,\n `When enabled, view-syncers compare newly hydrated queries against running`,\n `queries with the same root table and log aggregate coverage stats.`,\n ``,\n `You can disable this if covering detection adds too much CPU overhead.`,\n ],\n },\n\n yieldThresholdMs: {\n type: v.number().default(10),\n desc: [\n `The maximum amount of time in milliseconds that a sync worker will`,\n `spend in IVM (processing query hydration and advancement) before yielding`,\n `to the event loop. Lower values increase responsiveness and fairness at`,\n `the cost of reduced throughput.`,\n ],\n },\n\n change: {\n db: {\n type: v.string().optional(),\n desc: [\n `The Postgres database used to store recent replication log entries, in order`,\n `to sync multiple view-syncers without requiring multiple replication slots on`,\n `the upstream database. If unspecified, the {bold upstream-db} will be used.`,\n ],\n },\n\n maxConns: {\n type: v.number().default(5),\n desc: [\n `The maximum number of connections to open to the change database.`,\n `This is used by the {bold change-streamer} for catching up`,\n `{bold zero-cache} replication subscriptions.`,\n ],\n },\n\n statementTimeoutMs: {\n type: v.number().default(20_000),\n desc: [\n `Fail change-log transactions if a statement response from postgres is not received within`,\n `the specified timeout. This differs from a postgres {bold statement_timeout} in that`,\n `it is implemented to handle a pathological case in which Postgres does not return a`,\n `response but otherwise believes the transaction to be idle.`,\n ],\n hidden: true, // make visible if proven to be effective/necessary\n },\n\n logBatchSize: {\n type: v\n .number()\n .assert(\n n => Number.isInteger(n) && n >= 1,\n `change.logBatchSize must be an integer >= 1`,\n )\n .default(2000),\n desc: [\n `The maximum number of change-log rows written per multi-row INSERT to the change`,\n `database. Larger upstream transactions are persisted in batches of this size rather`,\n `than one INSERT per change, which is the dominant cost when replicating large`,\n `transactions (e.g. bulk backfills or migrations). Larger values increase throughput`,\n `at the cost of higher transient memory; the effective batch is internally capped to`,\n `stay within Postgres's bind-parameter limit. Set to {bold 1} to disable batching.`,\n ],\n hidden: true, // make visible if proven to be effective/necessary\n },\n },\n\n replica: replicaOptions,\n\n log: logOptions,\n\n app: appOptions,\n\n shard: shardOptions,\n\n auth: authOptions,\n\n port: {\n type: v.number().default(4848),\n desc: [`The port for sync connections.`],\n },\n\n keepaliveTimeoutMs: {\n type: v.number().optional(),\n desc: [\n `The timeout since the last /keepalive request after which the server will initiate`,\n `a graceful shutdown. This is a workaround for AWS Elastic Container Service, which`,\n `otherwise provides no signal that a target has been deregistered (and should thus begin`,\n `shutdown); the cessation of health checks at /keepalive is instead used as the signal to`,\n `drain. (ECS later sends a SIGTERM before killing the server but only allows a 30-second`,\n `timeout before sending SIGKILL).`,\n ``,\n `Other container runners explicitly send a SIGTERM followed by a configurable drain interval,`,\n `in which case /keepalive logic is not necessary.`,\n ``,\n `When running the server in ECS, this timeout should be set to some multiple of the health`,\n `check interval. If the option is unset, the keepalive timeout is disabled in non-ECS environments,`,\n `and defaults to 20 seconds when run in ECS (determined by the presence of the`,\n `{bold ECS_CONTAINER_METADATA_URI_V4} environment variable as per`,\n `https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-environment-variables.html).`,\n ],\n },\n\n changeStreamer: {\n uri: {\n type: v.string().optional(),\n desc: [\n `When set, connects to the {bold change-streamer} at the given URI.`,\n `In a multi-node setup, this should be specified in {bold view-syncer} options,`,\n `pointing to the {bold replication-manager} URI, which runs a {bold change-streamer}`,\n `on port 4849.`,\n ],\n },\n\n mode: {\n type: v.literalUnion('dedicated', 'discover').default('dedicated'),\n desc: [\n `As an alternative to {bold ZERO_CHANGE_STREAMER_URI}, the {bold ZERO_CHANGE_STREAMER_MODE}`,\n `can be set to \"{bold discover}\" to instruct the {bold view-syncer} to connect to the `,\n `ip address registered by the {bold replication-manager} upon startup.`,\n ``,\n `This may not work in all networking configurations, e.g. certain private `,\n `networking or port forwarding configurations. Using the {bold ZERO_CHANGE_STREAMER_URI}`,\n `with an explicit routable hostname is recommended instead.`,\n ``,\n `Note: This option is ignored if the {bold ZERO_CHANGE_STREAMER_URI} is set.`,\n ],\n },\n\n port: {\n type: v.number().optional(),\n desc: [\n `The port on which the {bold change-streamer} runs. This is an internal`,\n `protocol between the {bold replication-manager} and {bold view-syncers}, which`,\n `runs in the same process tree in local development or a single-node configuration.`,\n ``,\n `If unspecified, defaults to {bold --port} + 1.`,\n ],\n },\n\n /** @deprecated */\n address: {\n type: v.string().optional(),\n deprecated: [\n `Set the {bold ZERO_CHANGE_STREAMER_URI} on view-syncers instead.`,\n ],\n hidden: true,\n },\n\n /** @deprecated */\n protocol: {\n type: v.literalUnion('ws', 'wss').default('ws'),\n deprecated: [\n `Set the {bold ZERO_CHANGE_STREAMER_URI} on view-syncers instead.`,\n ],\n hidden: true,\n },\n\n discoveryInterfacePreferences: {\n type: v.array(v.string()).default([...DEFAULT_PREFERRED_PREFIXES]),\n desc: [\n `The name prefixes to prefer when introspecting the network interfaces to determine`,\n `the externally reachable IP address for change-streamer discovery. This defaults`,\n `to commonly used names for standard ethernet interfaces in order to prevent selecting`,\n `special interfaces such as those for VPNs.`,\n ],\n // More confusing than it's worth to advertise this. The default list should be\n // adjusted to make things work for all environments; it is controlled as a\n // hidden flag as an emergency to unblock people with outlier network configs.\n hidden: true,\n },\n\n startupDelayMs: {\n type: v.number().default(15000),\n desc: [\n `The delay to wait before the change-streamer takes over the replication stream`,\n `(i.e. the handoff during replication-manager updates), to allow loadbalancers to register`,\n `the task as healthy based on healthcheck parameters. Note that if a change stream request`,\n `is received during this interval, the delay will be canceled and the takeover will happen`,\n `immediately, since the incoming request indicates that the task is registered as a target.`,\n ],\n },\n\n sqliteChangeLogMode: {\n type: v.literalUnion('off', 'write', 'compare', 'serve').default('off'),\n desc: [\n `Controls the staged SQLite change-log rollout. Modes are cumulative:`,\n `{bold off}, {bold write}, {bold compare}, and {bold serve}.`,\n ],\n hidden: true,\n },\n\n sqliteChangeLogReadPercent: {\n type: v.number().default(0),\n desc: [\n `The stable percentage of eligible catchup subscriptions served from SQLite.`,\n ],\n hidden: true,\n },\n\n sqliteChangeLogRetentionMs: {\n type: v.number().default(60_000),\n desc: [`The minimum time window retained in the SQLite change log.`],\n hidden: true,\n },\n\n sqliteChangeLogReadBatchRows: {\n type: v.number().default(1000),\n desc: [`The target number of rows in each SQLite catchup read batch.`],\n hidden: true,\n },\n\n sqliteChangeLogPurgeBatchRows: {\n type: v.number().default(1000),\n desc: [`The target number of rows in each SQLite purge batch.`],\n hidden: true,\n },\n\n sqliteChangeLogBarrierTimeoutMs: {\n type: v.number().default(300_000),\n desc: [\n `The maximum wait for the SQLite required-head barrier. This is a`,\n `backstop for a wedged replica on an idle shard, where waiting costs`,\n `nothing and would otherwise go unnoticed. A shard with traffic is`,\n `bounded well before this by the subscriber's backlog reaching its`,\n `high water mark, which is the point at which waiting starts holding`,\n `up replication.`,\n ],\n hidden: true,\n },\n\n backPressureLimitHeapProportion: {\n type: v.number().default(0.04),\n desc: [\n `The percentage of {bold --max-old-space-size} to use as a buffer for absorbing replication`,\n `stream spikes. When the estimated amount of queued data exceeds this threshold, back pressure`,\n `is applied to the replication stream, delaying downstream sync as a result.`,\n ``,\n `The threshold was determined empirically with load testing. Higher thresholds have resulted`,\n `in OOMs. Note also that the byte-counting logic in the queue is strictly an underestimate of`,\n `actual memory usage (but importantly, proportionally correct), so the queue is actually`,\n `using more than what this proportion suggests.`,\n ``,\n `This parameter is exported as an emergency knob to reduce the size of the buffer in the`,\n `event that the server OOMs from back pressure. Resist the urge to {italic increase} this`,\n `proportion, as it is mainly useful for absorbing periodic spikes and does not meaningfully`,\n `affect steady-state replication throughput; the latter is determined by other factors such`,\n `as object serialization and PG throughput`,\n ``,\n `In other words, the back pressure limit does not constrain replication throughput;`,\n `rather, it protects the system when the upstream throughput exceeds the downstream`,\n `throughput.`,\n ],\n },\n\n flowControlConsensusTimeoutProportion: {\n type: v.number().default(2.0),\n desc: [\n `During periodic flow control checks (every 64kb), the amount of time to wait after the majority`,\n `of subscribers have acked, proportional to that interval, after which replication will continue`,\n `even if some subscribers have yet to ack.`,\n ``,\n `This allows a bounded amount of time for backlogged subscribers to catch up on each flush`,\n `without forcing all subscribers to wait for the entire backlog to be processed. It is also`,\n `useful for mitigating the effect of unresponsive subscribers due to severed websocket`,\n `connections or pathological zombie situations (until liveness checks or laggard detection`,\n `disconnects them).`,\n ``,\n `For example, if the majority of subscribers ack a message in 2.5ms, a padding proportion of`,\n `1.0 instructs replication to continue after an additional 2.5ms; for a proportion of 2.0, an`,\n `additional 5.0ms, etc. The default value of 2.0 allows for a subscriber to be 3x slower than the`,\n `majority in the steady state, while similarly bounding the extent to which a temporarily lagging`,\n `subscriber (e.g. due to catchup) slows down the fleet.`,\n ``,\n `Note that subscribers that continually exceed the timeout will eventually be disconnected and`,\n `instructed to shutdown in order to protect the healthy subscribers. It is thus important that`,\n `the proportion account for expected variance in processing speed across subscribers.`,\n ``,\n `Set this to a negative number to disable early flow control releases. (Not recommended, but`,\n `available as an emergency measure.)`,\n ],\n },\n\n flowControlSlowSubscriberGracePeriodSeconds: {\n type: v.number().default(30),\n desc: [\n `The period of time after which a lagging subscriber is disconnected and instructed to`,\n `restart. A subscriber is considered lagging if it {italic continuously} (1) exceeds the`,\n `consensus timeout and (2) fails to exceed the change rate of healthy subscribers. These`,\n `conditions distinguish expected, temporary periods of slowness from pathological`,\n `scenarios, such as zombie tasks, in which the subscriber is unlikely to recover.`,\n ``,\n `In particular, the second condition excludes subscribers that are performing initial`,\n `catchup, as the rate of change for catchup must eventually exceed the rate of upstream`,\n `changes. Note, however, that catchup rate can be legitimately slow during (rare) expensive`,\n `operations such as index creation.`,\n ``,\n `Thus, this grace period caps the amount of time that a lagging subscriber degrades overall`,\n `throughput, but should be long enough to allow for legitimate intervals of slowness.`,\n ``,\n `Note that in the pathological case where a catchup operation exceeds this grace period,`,\n `the system will eventually recover after the next backup/restore cycle, as the expensive`,\n `operation (e.g. index creation) will have been applied to the restored replica and no longer`,\n `executed during catchup.`,\n ``,\n `Set this to 0 or a negative number to disable laggard detection. (Not recommended, but`,\n `available as an emergency measure.)`,\n ],\n },\n },\n\n taskID: {\n type: v.string().optional(),\n desc: [\n `Globally unique identifier for the zero-cache instance.`,\n ``,\n `Setting this to a platform specific task identifier can be useful for debugging.`,\n `If unspecified, zero-cache will attempt to extract the TaskARN if run from within`,\n `an AWS ECS container, and otherwise use a random string.`,\n ],\n },\n\n perUserMutationLimit,\n\n numSyncWorkers: {\n type: v.number().optional(),\n desc: [\n `The number of processes to use for view syncing.`,\n `Leave this unset to use the maximum available parallelism.`,\n `If set to 0, the server runs without sync workers, which is the`,\n `configuration for running the {bold replication-manager}.`,\n ],\n },\n\n autoReset: {\n type: v.boolean().default(true),\n desc: [\n `Automatically wipe and resync the replica when replication is halted.`,\n `This situation can occur for configurations in which the upstream database`,\n `provider prohibits event trigger creation, preventing the zero-cache from`,\n `being able to correctly replicate schema changes. For such configurations,`,\n `an upstream schema change will instead result in halting replication with an`,\n `error indicating that the replica needs to be reset.`,\n ``,\n `When {bold auto-reset} is enabled, zero-cache will respond to such situations`,\n `by shutting down, and when restarted, resetting the replica and all synced `,\n `clients. This is a heavy-weight operation and can result in user-visible`,\n `slowness or downtime if compute resources are scarce.`,\n ],\n },\n\n replicationLag: {\n reportIntervalMs: {\n type: v.number().default(30000),\n desc: [\n `The minimum interval at which replication lag reports are written upstream and`,\n `reported via the {bold zero.replication.total_lag} opentelemetry metric. If`,\n `an expected report is not received before the next interval, Zero retries with`,\n `a new report and increments {bold zero.replication.lag_report_retries}. A`,\n `negative or 0 value disables lag reporting.`,\n ``,\n `This monitoring feature is only support on the postgres upstream type.`,\n ],\n },\n },\n\n adminPassword: {\n type: v.string().optional(),\n desc: [\n `A password used to administer zero-cache server, for example to access the`,\n `/statz endpoint.`,\n '',\n 'A password is optional in development mode but {bold required in production} mode.',\n ],\n },\n\n websocketCompression: {\n type: v.boolean().default(false),\n desc: [\n 'Enable WebSocket per-message deflate compression.',\n '',\n 'Compression can reduce bandwidth usage for sync traffic but',\n 'increases CPU usage on both client and server. Disabled by default.',\n '',\n 'See: https://github.com/websockets/ws#websocket-compression',\n ],\n },\n\n websocketCompressionOptions: {\n type: v.string().optional(),\n desc: [\n 'JSON string containing WebSocket compression options.',\n '',\n 'Only used if websocketCompression is enabled.',\n '',\n 'Example: \\\\{\"zlibDeflateOptions\":\\\\{\"level\":3\\\\},\"threshold\":1024\\\\}',\n '',\n 'See https://github.com/websockets/ws/blob/master/doc/ws.md#new-websocketserveroptions-callback for available options.',\n ],\n },\n\n websocketMaxPayloadBytes: {\n type: v.number().default(10 * 1024 * 1024),\n desc: [\n 'Maximum size of incoming WebSocket messages in bytes.',\n '',\n 'Messages exceeding this limit are rejected before parsing.',\n 'Default: 10MB (10 * 1024 * 1024 = 10485760)',\n ],\n },\n\n litestream: {\n executable: {\n type: v.string().optional(),\n desc: [\n `Path to the {bold litestream} executable. This must be built from the`,\n `{bold rocicorp/litestream} fork. Support for the official binary at v0.5.x`,\n `is planned.`,\n ],\n },\n\n executableV5: {\n type: v.string().optional(),\n desc: [\n `The v0.5.x litestream executable which is used for restoring the backup`,\n `backup when {bold ZERO_LITESTREAM_RESTORE_USING_V5} is specified.`,\n `litestream v0.5.8+ can restore from both v0.3.x and v0.5.x backup formats,`,\n `affording forwards compatibility with a future zero-cache`,\n `version that will use litestream v0.5.x to backup the replica.`,\n ],\n },\n\n restoreUsingV5: {\n type: v.boolean().default(true),\n desc: [\n `Restores the backup using the {bold ZERO_LITESTREAM_EXECUTABLE_V5} if specified.`,\n `This provides a recovery path if rolling back from {bold ZERO_LITESTREAM_BACKUP_USING_V5}`,\n `as v5 restores from both v3 and v5 backups (whichever is more recent).`,\n ],\n },\n\n backupUsingV5: {\n type: v.boolean().default(false),\n desc: [\n `Backs up the replica using Litestream v0.5.x and monitors cleanup`,\n `watermarks by reading the backup through the Litestream SQLite VFS.`,\n `This requires {bold ZERO_LITESTREAM_RESTORE_USING_V5}.`,\n ],\n },\n\n configPath: {\n type: v.string().default('./src/services/litestream/config.yml'),\n desc: [\n `Path to the litestream yaml config file. zero-cache will run this with its`,\n `environment variables, which can be referenced in the file via $\\\\{ENV\\\\}`,\n `substitution, for example:`,\n `* {bold ZERO_REPLICA_FILE} for the db path`,\n `* {bold ZERO_LITESTREAM_BACKUP_LOCATION} for the db replica url`,\n `* {bold ZERO_LITESTREAM_LOG_LEVEL} for the log level`,\n `* {bold ZERO_LOG_FORMAT} for the log type`,\n ],\n },\n\n configPathV5: {\n type: v.string().default('./src/services/litestream/config-v5.yml'),\n desc: [\n `Path to the litestream v5 yaml config file. zero-cache will run this with its`,\n `environment variables, which can be referenced in the file via $\\\\{ENV\\\\}`,\n `substitution, for example:`,\n `* {bold ZERO_REPLICA_FILE} for the db path`,\n `* {bold ZERO_LITESTREAM_BACKUP_LOCATION} for the db replica url`,\n `* {bold ZERO_LITESTREAM_LOG_LEVEL} for the log level`,\n `* {bold ZERO_LOG_FORMAT} for the log type`,\n ],\n },\n\n vfsExtensionPath: {\n type: v.string().default('/usr/local/lib/litestream-vfs.so'),\n desc: [\n `Path to the Litestream v0.5.x SQLite VFS loadable extension used by`,\n `the backup watermark reader to query the backup directly.`,\n ],\n },\n\n vfsProbeIntervalMs: {\n type: v.number().default(15 * 1000),\n desc: [\n `Interval in milliseconds litestream vfs extension polls the backup store (e.g. s3)`,\n `to determine the most recent backup.`,\n ``,\n `This, in turn, influences how quickly new backups are confirmed, allowing the`,\n `change-streamer to ack the upstream change-source (e.g. replication slot).`,\n ],\n },\n\n vfsProbeTimeoutMs: {\n type: v.number().default(30 * 1000),\n desc: [\n `Timeout in milliseconds for requests to the Litestream VFS backup`,\n `watermark reader worker.`,\n ],\n },\n\n vfsLogFile: {\n type: v.string().optional(),\n desc: [\n `Optional file path for logs emitted by the Litestream VFS native`,\n `extension. If unset, the extension writes to stdout.`,\n ],\n },\n\n logLevel: {\n type: v.literalUnion('debug', 'info', 'warn', 'error').default('warn'),\n },\n\n backupURL: {\n type: v.string().optional(),\n desc: [\n `The location of the litestream backup, usually an {bold s3://} URL.`,\n `This is only consulted by the {bold replication-manager}.`,\n `{bold view-syncers} receive this information from the {bold replication-manager}.`,\n ],\n },\n\n endpoint: {\n type: v.string().optional(),\n desc: [\n `The S3-compatible endpoint URL to use for the litestream backup. Only required for non-AWS services.`,\n `The {bold replication-manager} and {bold view-syncers} must have the same endpoint.`,\n ],\n },\n\n region: {\n type: v.string().optional(),\n desc: [\n `The AWS region for the litestream backup bucket. Required for non-standard AWS partitions`,\n `(e.g. GovCloud {bold us-gov-west-1}) where Litestream cannot auto-detect the region.`,\n `The {bold replication-manager} and {bold view-syncers} must have the same region.`,\n ],\n },\n\n port: {\n type: v.number().optional(),\n desc: [\n `Port on which litestream exports metrics, used to determine the replication`,\n `watermark up to which it is safe to purge change log records.`,\n ``,\n `If unspecified, defaults to {bold --port} + 2.`,\n ],\n },\n\n checkpointThresholdMB: {\n type: v.number().default(40),\n desc: [\n `The size of the WAL file at which to perform an SQlite checkpoint to apply`,\n `the writes in the WAL to the main database file. Each checkpoint creates`,\n `a new WAL segment file that will be backed up by litestream (v3). Smaller thresholds`,\n `may improve read performance, at the expense of creating more files to download`,\n `when restoring the replica from the backup.`,\n ``,\n `This setting is only relevant when replicating with litestream v3, and is ignored`,\n `when replicating with litestream v5.`,\n ],\n },\n\n minCheckpointPageCount: {\n type: v.number().optional(),\n desc: [\n `The WAL page count at which SQLite attempts a PASSIVE checkpoint, which`,\n `transfers pages to the main database file without blocking writers.`,\n `Defaults to {bold checkpointThresholdMB * 250} (since SQLite page size is 4KB).`,\n ``,\n `This setting is only relevant when replicating with litestream v3, and is ignored`,\n `when replicating with litestream v5.`,\n ],\n },\n\n maxCheckpointPageCount: {\n type: v.number().optional(),\n desc: [\n `The WAL page count at which SQLite performs a RESTART checkpoint, which`,\n `blocks writers until complete. Defaults to {bold minCheckpointPageCount * 10}.`,\n `Set to {bold 0} to disable RESTART checkpoints entirely.`,\n ``,\n `This setting is only relevant when replicating with litestream v3, and is ignored`,\n `when replicating with litestream v5.`,\n ],\n },\n\n incrementalBackupIntervalMinutes: {\n type: v.number().default(5),\n desc: [\n `The interval between incremental v3 backups of the replica. Shorter intervals`,\n `reduce the amount of change history that needs to be replayed when catching`,\n `up a new view-syncer, at the expense of increasing the number of files needed`,\n `to download for the initial litestream restore.`,\n ``,\n `This option only applies to litestream v3 backups and will be deprecated/removed`,\n `once the zero-cache is transitioned to litestream v5. For configuring v5 backup`,\n `frequency, use {bold ZERO_LITESTREAM_INCREMENTAL_BACKUP_INTERVAL_SECONDS}.`,\n ],\n },\n\n incrementalBackupIntervalSeconds: {\n type: v.number().default(15),\n desc: [\n `The interval between incremental v5 backups of the replica. With litestream v5`,\n `the upstream change source is not ACKed until the corresponding changes have been`,\n `applied to the replica and backed up by litestream. As such, shorter intervals`,\n `incur a higher number of backup storage writes and files managed (e.g. in s3),`,\n `while longer intervals result requiring a larger buffer for changes upstream `,\n `(e.g. per-replication slot wal records). The default value of 15 seconds targets`,\n `an s3 API cost of ~$1/month (not counting storage costs).`,\n ``,\n `This option only applies to litestream v5 backups. For v3 backups, use`,\n `{bold ZERO_LITESTREAM_INCREMENTAL_BACKUP_INTERVAL_MINUTES}.`,\n ],\n },\n\n snapshotBackupIntervalHours: {\n type: v.number().default(4),\n desc: [\n `The interval between snapshot backups of the replica. Snapshot backups`,\n `make a full copy of the database to a new litestream generation. This`,\n `improves restore time at the expense of bandwidth. Applications with a`,\n `large database and low write rate can increase this interval to reduce`,\n `network usage for backups (litestream defaults to 24 hours).`,\n ``,\n `This setting is applied when replicating with either litestream v3 or v5.`,\n `Note, however, that snapshots are generally not needed to improve restore time`,\n `with v5, and so a longer interval (e.g. the litestream default of 24h) is fine.`,\n ],\n },\n\n restoreParallelism: {\n type: v.number().default(48),\n desc: [\n `The number of WAL files to download in parallel when performing the`,\n `initial restore of the replica from the backup.`,\n ],\n },\n\n multipartConcurrency: {\n type: v.number().default(48),\n desc: [\n `The number of parts (of size {bold --litestream-multipart-size} bytes)`,\n `to upload or download in parallel when backing up or restoring the snapshot.`,\n ],\n },\n\n multipartSize: {\n type: v.number().default(16 * 1024 * 1024),\n desc: [\n `The size of each part when uploading or downloading the snapshot with`,\n `{bold --multipart-concurrency}. Note that up to {bold concurrency * size}`,\n `bytes of memory are used when backing up or restoring the snapshot.`,\n ],\n },\n },\n\n storageDBTmpDir: {\n type: v.string().optional(),\n desc: [\n `tmp directory for IVM operator storage. Leave unset to use os.tmpdir()`,\n ],\n },\n\n initialSync: {\n tableCopyWorkers: {\n type: v.number().default(5),\n desc: [\n `The number of parallel workers used to copy tables during initial sync.`,\n `Each worker uses a database connection and will buffer up to (approximately)`,\n `10 MB of table data in memory during initial sync. Increasing the number of`,\n `workers may improve initial sync speed; however, note that local disk throughput`,\n `(i.e. IOPS), upstream CPU, and network bandwidth may also be bottlenecks.`,\n ],\n },\n\n profileCopy: {\n type: v.boolean().optional(),\n hidden: true,\n desc: [\n `Takes a cpu profile during the copy phase initial-sync, storing it as a JSON file`,\n `initial-copy.cpuprofile in the tmp directory.`,\n ],\n },\n\n textCopy: {\n type: v.boolean().default(false),\n desc: [\n `Use text-format COPY instead of binary COPY for initial sync and`,\n `backfill streaming. This is slower but can work around issues with`,\n `binary encoding of certain data types.`,\n ],\n },\n },\n\n shadowSync: {\n enabled: {\n type: v.boolean().default(false),\n desc: [\n `Periodically exercises the initial-sync code path against a sample of`,\n `rows from every published table, writing to a throwaway SQLite database.`,\n `This acts as a canary: if the real initial-sync path ever breaks (schema`,\n `drift, PG version quirks, etc.), the shadow run fails before a customer`,\n `actually needs a full reset.`,\n ],\n },\n\n intervalHours: {\n type: v.number().default(12),\n desc: [\n `The interval between shadow initial-sync runs, in hours. The first`,\n `run fires within [2/3, 1) of this interval after startup, so the`,\n `canary completes at least once per task lifetime (the replication`,\n `manager is restarted every ~24h) while still jittering so a fleet`,\n `restart does not cause all tasks to canary simultaneously.`,\n ],\n },\n\n sampleRate: {\n type: v.number().default(0.1),\n desc: [\n `The BERNOULLI sampling rate for each table (0 < rate <= 1). A value of`,\n `1 disables sampling and copies all rows (still subject to`,\n `{bold max-rows-per-table}).`,\n ],\n },\n\n maxRowsPerTable: {\n type: v.number().default(10000),\n desc: [\n `The hard upper bound on rows copied per table per shadow run. Guards`,\n `against unexpectedly large tables consuming disk / upstream bandwidth.`,\n ],\n },\n },\n\n /** @deprecated */\n targetClientRowCount: {\n type: v.number().default(20_000),\n deprecated: [\n 'This option is no longer used and will be removed in a future version.',\n 'The client-side cache no longer enforces a row limit. Instead, TTL-based expiration',\n 'automatically manages cache size to prevent unbounded growth.',\n ],\n hidden: true,\n },\n\n lazyStartup: {\n type: v.boolean().default(false),\n desc: [\n 'Delay starting the majority of zero-cache until first request.',\n '',\n 'This is mainly intended to avoid connecting to Postgres replication stream',\n 'until the first request is received, which can be useful i.e., for preview instances.',\n '',\n 'Currently only supported in single-node mode.',\n ],\n },\n\n serverVersion: {\n type: v.string().optional(),\n desc: [`The version string outputted to logs when the server starts up.`],\n },\n\n enableTelemetry: {\n type: v.boolean().default(true),\n desc: [\n `Set to false to opt out of telemetry collection.`,\n ``,\n `This helps us improve Zero by collecting anonymous usage data.`,\n `Setting the DO_NOT_TRACK environment variable also disables telemetry.`,\n ],\n },\n\n cloudEvent: {\n sinkEnv: {\n type: v.string().optional(),\n desc: [\n `ENV variable containing a URI to a CloudEvents sink. When set, ZeroEvents`,\n `will be published to the sink as the {bold data} field of CloudEvents.`,\n `The {bold source} field of the CloudEvents will be set to the {bold ZERO_TASK_ID},`,\n `along with any extension attributes specified by the {bold ZERO_CLOUD_EVENT_EXTENSION_OVERRIDES_ENV}.`,\n ``,\n `This configuration is modeled to easily integrate with a knative K_SINK binding,`,\n `(i.e. https://github.com/knative/eventing/blob/main/docs/spec/sources.md#sinkbinding).`,\n `However, any CloudEvents sink can be used.`,\n ],\n },\n\n extensionOverridesEnv: {\n type: v.string().optional(),\n desc: [\n `ENV variable containing a JSON stringified object with an {bold extensions} field`,\n `containing attributes that should be added or overridden on outbound CloudEvents.`,\n ``,\n `This configuration is modeled to easily integrate with a knative K_CE_OVERRIDES binding,`,\n `(i.e. https://github.com/knative/eventing/blob/main/docs/spec/sources.md#sinkbinding).`,\n ],\n },\n },\n};\n\nexport type ZeroConfig = Config<typeof zeroOptions>;\n\nlet loadedConfig: Config<typeof zeroOptions> | undefined;\n\nexport function getZeroConfig(\n opts: Omit<ParseOptions, 'envNamePrefix'> = {},\n): ZeroConfig {\n if (!loadedConfig || singleProcessMode()) {\n loadedConfig = parseOptions(zeroOptions, {\n envNamePrefix: ZERO_ENV_VAR_PREFIX,\n emitDeprecationWarnings: false, // overridden at the top level parse\n ...opts,\n });\n\n if (loadedConfig.queryHydrationStats) {\n runtimeDebugFlags.trackRowCountsVended = true;\n }\n }\n return loadedConfig;\n}\n\n/**\n * Same as {@link getZeroConfig}, with an additional check that the\n * config has already been normalized (i.e. by the top level server/runner).\n */\nexport function getNormalizedZeroConfig(\n opts: Omit<ParseOptions, 'envNamePrefix'> = {},\n): NormalizedZeroConfig {\n const config = getZeroConfig(opts);\n assertNormalized(config);\n return config;\n}\n\n/**\n * Gets the server version from the config if provided. Otherwise it gets it\n * from the Zero package.json.\n */\nexport function getServerVersion(\n config: Pick<ZeroConfig, 'serverVersion'> | undefined,\n): string {\n return config?.serverVersion ?? packageJson.version;\n}\n\nexport function isAdminPasswordValid(\n lc: LogContext,\n config: Pick<NormalizedZeroConfig, 'adminPassword'>,\n password: string | undefined,\n) {\n // If development mode, password is optional\n // We use process.env.NODE_ENV === 'development' as a sign that we're in\n // development mode, rather than a custom env var like ZERO_DEVELOPMENT_MODE,\n // because NODE_ENV is more standard and is already used by many tools.\n // Note that if NODE_ENV is not set, we assume production mode.\n\n if (!password && !config.adminPassword && isDevelopmentMode()) {\n warnOnce(\n lc,\n 'No admin password set; allowing access in development mode only',\n );\n return true;\n }\n\n if (!config.adminPassword) {\n lc.warn?.('No admin password set; denying access');\n return false;\n }\n\n // Use constant-time comparison to prevent timing attacks\n const passwordBuffer = Buffer.from(password ?? '');\n const configBuffer = Buffer.from(config.adminPassword);\n\n // Handle length mismatch in constant time\n if (passwordBuffer.length !== configBuffer.length) {\n // Perform dummy comparison to maintain constant timing\n timingSafeEqual(configBuffer, configBuffer);\n lc.warn?.('Invalid admin password');\n return false;\n }\n\n if (!timingSafeEqual(passwordBuffer, configBuffer)) {\n lc.warn?.('Invalid admin password');\n return false;\n }\n\n lc.debug?.('Admin password accepted');\n return true;\n}\n\nlet hasWarned = false;\n\nfunction warnOnce(lc: LogContext, msg: string) {\n if (!hasWarned) {\n lc.warn?.(msg);\n hasWarned = true;\n }\n}\n\n// For testing purposes - reset the warning state\nexport function resetWarnOnceState() {\n hasWarned = false;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"zero-config.js","names":[],"sources":["../../../../../zero-cache/src/config/zero-config.ts"],"sourcesContent":["/**\n * These types represent the _compiled_ config whereas `define-config` types represent the _source_ config.\n */\n\nimport {timingSafeEqual} from 'node:crypto';\nimport type {LogContext} from '@rocicorp/logger';\nimport {logOptions} from '../../../otel/src/log-options.ts';\nimport {\n flagToEnv,\n parseOptions,\n type Config,\n type ParseOptions,\n} from '../../../shared/src/options.ts';\nimport * as v from '../../../shared/src/valita.ts';\n// @circular-dep-ignore - importing package.json for version info only\nimport packageJson from '../../../zero/package.json' with {type: 'json'};\nimport {runtimeDebugFlags} from '../../../zql/src/builder/debug-delegate.ts';\nimport {singleProcessMode} from '../types/processes.ts';\nimport {\n ALLOWED_APP_ID_CHARACTERS,\n INVALID_APP_ID_MESSAGE,\n} from '../types/shards.ts';\nimport {DEFAULT_PREFERRED_PREFIXES} from './network.ts';\nimport {\n assertNormalized,\n isDevelopmentMode,\n type NormalizedZeroConfig,\n} from './normalize.ts';\nexport type {LogConfig} from '../../../otel/src/log-options.ts';\n\nexport const ZERO_ENV_VAR_PREFIX = 'ZERO_';\n\nexport const appOptions = {\n id: {\n type: v\n .string()\n .default('zero')\n .assert(id => ALLOWED_APP_ID_CHARACTERS.test(id), INVALID_APP_ID_MESSAGE),\n desc: [\n 'Unique identifier for the app.',\n '',\n 'Multiple zero-cache apps can run on a single upstream database, each of which',\n 'is isolated from the others, with its own permissions, sharding (future feature),',\n 'and change/cvr databases.',\n '',\n 'The metadata of an app is stored in an upstream schema with the same name,',\n 'e.g. \"zero\", and the metadata for each app shard, e.g. client and mutation',\n 'ids, is stored in the \"\\\\{app-id\\\\}_\\\\{#\\\\}\" schema. (Currently there is only a single',\n '\"0\" shard, but this will change with sharding).',\n '',\n 'The CVR and Change data are managed in schemas named \"\\\\{app-id\\\\}_\\\\{shard-num\\\\}/cvr\"',\n 'and \"\\\\{app-id\\\\}_\\\\{shard-num\\\\}/cdc\", respectively, allowing multiple apps and shards',\n 'to share the same database instance (e.g. a Postgres \"cluster\") for CVR and Change management.',\n '',\n 'Due to constraints on replication slot names, an App ID may only consist of',\n 'lower-case letters, numbers, and the underscore character.',\n '',\n 'Note that this option is used by both {bold zero-cache} and {bold zero-deploy-permissions}.',\n ],\n },\n\n publications: {\n type: v.array(v.string()).optional(() => []),\n desc: [\n `Postgres {bold PUBLICATION}s that define the tables and columns to`,\n `replicate. Publication names may not begin with an underscore,`,\n `as zero reserves that prefix for internal use.`,\n ``,\n `If unspecified, zero-cache will create and use an internal publication that`,\n `publishes all tables in the {bold public} schema, i.e.:`,\n ``,\n `CREATE PUBLICATION _\\\\{app-id\\\\}_public_0 FOR TABLES IN SCHEMA public;`,\n ``,\n `Note that changing the set of publications will result in resyncing the replica,`,\n `which may involve downtime (replication lag) while the new replica is initializing.`,\n `To change the set of publications without disrupting an existing app, a new app`,\n `should be created.`,\n ],\n },\n};\n\nexport const shardOptions = {\n id: {\n type: v\n .string()\n .assert(() => {\n throw new Error(\n `ZERO_SHARD_ID is no longer an option. Please use ZERO_APP_ID instead.`,\n // TODO: Link to release / migration notes?\n );\n })\n .optional(),\n hidden: true,\n },\n\n num: {\n type: v.number().default(0),\n desc: [\n `The shard number (from 0 to NUM_SHARDS) of the App. zero will eventually`,\n `support data sharding as a first-class primitive; until then, deploying`,\n `multiple shard-nums creates functionally identical shards. Until sharding is`,\n `actually meaningful, this flag is hidden but available for testing.`,\n ],\n hidden: true,\n },\n};\n\nconst replicaOptions = {\n file: {\n type: v.string().default('zero.db'),\n desc: [\n `File path to the SQLite replica that zero-cache maintains.`,\n `This can be lost, but if it is, zero-cache will have to re-replicate next`,\n `time it starts up.`,\n ],\n },\n\n vacuumIntervalHours: {\n type: v.number().optional(),\n desc: [\n `Performs a VACUUM at server startup if the specified number of hours has elapsed`,\n `since the last VACUUM (or initial-sync). The VACUUM operation is heavyweight`,\n `and requires double the size of the db in disk space. If unspecified, VACUUM`,\n `operations are not performed.`,\n ],\n },\n};\n\nexport type ReplicaOptions = Config<typeof replicaOptions>;\n\nconst perUserMutationLimit = {\n max: {\n type: v.number().optional(),\n desc: [\n `The maximum mutations per user within the specified {bold windowMs}.`,\n `If unset, no rate limiting is enforced.`,\n ],\n },\n windowMs: {\n type: v.number().default(60_000),\n desc: [\n `The sliding window over which the {bold perUserMutationLimitMax} is enforced.`,\n ],\n },\n};\n\nexport type RateLimit = Config<typeof perUserMutationLimit>;\n\nconst authOptions = {\n jwk: {\n type: v.string().optional(),\n desc: [\n `A public key in JWK format used to verify JWTs. Only one of {bold jwk}, {bold jwksUrl} and {bold secret} may be set.`,\n ],\n deprecated: [\n `Use cookie-based authentication or an auth token instead - see https://zero.rocicorp.dev/docs/auth.`,\n ],\n },\n jwksUrl: {\n type: v.string().optional(),\n desc: [\n `A URL that returns a JWK set used to verify JWTs. Only one of {bold jwk}, {bold jwksUrl} and {bold secret} may be set.`,\n ],\n deprecated: [\n `Use cookie-based authentication or an auth token instead - see https://zero.rocicorp.dev/docs/auth.`,\n ],\n },\n secret: {\n type: v.string().optional(),\n desc: [\n `A symmetric key used to verify JWTs. Only one of {bold jwk}, {bold jwksUrl} and {bold secret} may be set.`,\n ],\n deprecated: [\n `Use cookie-based authentication or an auth token instead - see https://zero.rocicorp.dev/docs/auth.`,\n ],\n },\n issuer: {\n type: v.string().optional(),\n desc: [\n `Expected issuer ({bold iss} claim) for JWT validation.`,\n `If set, tokens with a different or missing issuer will be rejected.`,\n ],\n deprecated: [\n `Use cookie-based authentication or an auth token instead - see https://zero.rocicorp.dev/docs/auth.`,\n ],\n },\n audience: {\n type: v.string().optional(),\n desc: [\n `Expected audience ({bold aud} claim) for JWT validation.`,\n `If set, tokens with a different or missing audience will be rejected.`,\n ],\n deprecated: [\n `Use cookie-based authentication or an auth token instead - see https://zero.rocicorp.dev/docs/auth.`,\n ],\n },\n revalidateIntervalSeconds: {\n type: v.number().default(300),\n desc: [\n `The interval in seconds between periodic /query auth revalidation for validated connections.`,\n `If unset, periodic auth revalidation is disabled.`,\n ],\n },\n retransformIntervalSeconds: {\n type: v.number().default(300),\n desc: [\n `The interval in seconds between periodic shared /query retransform work for a client group.`,\n `If unset, periodic shared retransform is disabled.`,\n ],\n },\n};\n\nconst makeDeprecationMessage = (flag: string) =>\n `Use {bold ${flagToEnv(ZERO_ENV_VAR_PREFIX, flag)}} (or {bold --${flag}}) instead.`;\n\nconst makeMutatorQueryOptions = (\n replacement: 'mutate' | 'query' | undefined,\n suffix: string,\n) => ({\n url: {\n type: v.array(v.string()).optional(), // optional until we remove CRUD mutations\n desc: [\n `The URL of the API server to which zero-cache will ${suffix}.`,\n ``,\n `{bold IMPORTANT:} URLs are matched using {bold URLPattern}, a standard Web API.`,\n ``,\n `{bold Pattern Syntax:}`,\n ` URLPattern uses a simple and intuitive syntax similar to Express routes.`,\n ` Wildcards and named parameters make it easy to match multiple URLs.`,\n ``,\n `{bold Basic Examples:}`,\n ` Exact URL match:`,\n ` \"https://api.example.com/mutate\"`,\n ` `,\n ` Any subdomain using wildcard:`,\n ` \"https://*.example.com/mutate\"`,\n ` `,\n ` Multiple subdomain levels:`,\n ` \"https://*.*.example.com/mutate\"`,\n ` `,\n ` Any path under a domain:`,\n ` \"https://api.example.com/*\"`,\n ` `,\n ` Named path parameters:`,\n ` \"https://api.example.com/:version/mutate\"`,\n ` ↳ Matches \"https://api.example.com/v1/mutate\", \"https://api.example.com/v2/mutate\", etc.`,\n ``,\n `{bold Advanced Patterns:}`,\n ` Optional path segments:`,\n ` \"https://api.example.com/:path?\"`,\n ` `,\n ` Regex in segments (for specific patterns):`,\n ` \"https://api.example.com/:version(v\\\\\\\\d+)/mutate\"`,\n ` ↳ Matches only \"v\" followed by digits`,\n ``,\n `{bold Multiple patterns:}`,\n ` [\"https://api1.example.com/mutate\", \"https://api2.example.com/mutate\"]`,\n ``,\n `{bold Note:} Query parameters and URL fragments (#) are automatically ignored during matching.`,\n ``,\n `For full URLPattern syntax, see: https://developer.mozilla.org/en-US/docs/Web/API/URLPattern`,\n ],\n ...(replacement\n ? {deprecated: [makeDeprecationMessage(`${replacement}-url`)]}\n : {}),\n },\n apiKey: {\n type: v.string().optional(),\n desc: [\n `An optional secret used to authorize zero-cache to call the API server handling writes.`,\n ],\n ...(replacement\n ? {deprecated: [makeDeprecationMessage(`${replacement}-api-key`)]}\n : {}),\n },\n forwardCookies: {\n type: v.boolean().default(false),\n desc: [\n `If true, zero-cache will forward cookies from the request.`,\n `This is useful for passing authentication cookies to the API server.`,\n `If false, cookies are not forwarded.`,\n ],\n ...(replacement\n ? {deprecated: [makeDeprecationMessage(`${replacement}-forward-cookies`)]}\n : {}),\n },\n allowedClientHeaders: {\n type: v.array(v.string()).optional(),\n desc: [\n `A list of header names that clients are allowed to set via custom headers.`,\n `If specified, only headers in this list will be forwarded to the ${suffix === 'push mutations' ? 'push' : 'query'} URL.`,\n `Header names are case-insensitive.`,\n `If not specified, no client-provided headers are forwarded.`,\n `Example: ZERO_${replacement ? replacement.toUpperCase() : suffix === 'push mutations' ? 'MUTATE' : 'QUERY'}_ALLOWED_CLIENT_HEADERS=x-request-id,x-correlation-id`,\n ],\n ...(replacement\n ? {\n deprecated: [\n makeDeprecationMessage(`${replacement}-allowed-client-headers`),\n ],\n }\n : {}),\n },\n allowedRequestHeaders: {\n type: v.array(v.string()).optional(),\n desc: [\n `A list of header names to forward from the incoming HTTP request to the ${suffix === 'push mutations' ? 'push' : 'query'} URL.`,\n `Unlike {bold allowed-client-headers} (which forwards headers set by the client), these are taken`,\n `from the HTTP request that established the connection (e.g. headers injected by a proxy or load balancer).`,\n `If a listed header is present on the request, its value is forwarded upstream under the same header name.`,\n `Header names are case-insensitive.`,\n `If not specified, no request headers are forwarded.`,\n `Example: ZERO_${replacement ? replacement.toUpperCase() : suffix === 'push mutations' ? 'MUTATE' : 'QUERY'}_ALLOWED_REQUEST_HEADERS=x-forwarded-for,cf-ray`,\n ],\n ...(replacement\n ? {\n deprecated: [\n makeDeprecationMessage(`${replacement}-allowed-request-headers`),\n ],\n }\n : {}),\n },\n});\n\nconst mutateOptions = makeMutatorQueryOptions(undefined, 'push mutations');\nconst pushOptions = makeMutatorQueryOptions('mutate', 'push mutations');\nconst queryOptions = makeMutatorQueryOptions(undefined, 'send synced queries');\nconst getQueriesOptions = makeMutatorQueryOptions(\n 'query',\n 'send synced queries',\n);\n\nexport type AuthConfig = Config<typeof authOptions>;\n\n/** @deprecated used only by legacy JWT verification helpers */\nexport type LegacyJWTAuthConfig = Pick<\n AuthConfig,\n 'jwk' | 'jwksUrl' | 'secret' | 'issuer' | 'audience'\n>;\n\n// Note: --help will list flags in the order in which they are defined here,\n// so order the fields such that the important (e.g. required) ones are first.\n// (Exported for testing)\nexport const zeroOptions = {\n upstream: {\n db: {\n type: v.string(),\n desc: [\n `The \"upstream\" authoritative postgres database.`,\n `In the future we will support other types of upstream besides PG.`,\n ],\n },\n\n type: {\n type: v.literalUnion('pg', 'custom').default('pg'),\n desc: [\n `The meaning of the {bold upstream-db} depends on the upstream type:`,\n `* {bold pg}: The connection database string, e.g. \"postgres://...\"`,\n `* {bold custom}: The base URI of the change source \"endpoint, e.g.`,\n ` \"https://my-change-source.dev/changes/v0/stream?apiKey=...\"`,\n ],\n hidden: true, // TODO: Unhide when ready to officially support.\n },\n\n maxConns: {\n type: v.number().default(20),\n desc: [\n `The maximum number of connections to open to the upstream database`,\n `for committing mutations. This is divided evenly amongst sync workers.`,\n `In addition to this number, zero-cache uses one connection for the`,\n `replication stream.`,\n ``,\n `Note that this number must allow for at least one connection per`,\n `sync worker, or zero-cache will fail to start. See {bold num-sync-workers}`,\n ],\n },\n\n maxConnsPerWorker: {\n type: v.number().optional(),\n hidden: true, // Passed from main thread to sync workers\n },\n\n pgReplicationSlotFailover: {\n type: v.boolean().optional(),\n desc: [\n `For upstream Postgres versions 17+, creates replication slots with the`,\n `{bold failover} parameter set to {bold true} to enable slot synchronization`,\n `and failover. Note that additional Postgres-level configuration is necessary`,\n `when enabling this option. For details, see:`,\n ``,\n `https://www.postgresql.org/docs/current/logicaldecoding-explanation.html#LOGICALDECODING-REPLICATION-SLOTS-SYNCHRONIZATION`,\n ``,\n `(Note that this option has no effect for Postgres versions before 17.)`,\n ],\n },\n\n pgStreamInboundTimeoutMs: {\n type: v.number().optional(),\n desc: [\n `The time (in milliseconds) without any inbound message from the upstream`,\n `wal sender after which the replication stream is considered unresponsive`,\n `and torn down to force a reconnect.`,\n ``,\n `Defaults to 2x the server's {bold wal_sender_timeout}. That suits idle`,\n `streams, but a busy wal sender can be legitimately silent for longer —`,\n `e.g. while decoding through WAL from unpublished tables, or assembling a`,\n `large transaction that is only sent at commit. If the server's`,\n `{bold wal_sender_timeout} is aggressive (some managed environments`,\n `default it as low as 5 seconds), the resulting teardown aborts and`,\n `replays the in-flight transaction, which can prevent replication from`,\n `ever catching up on a large backlog. Set this option to widen the`,\n `client-side threshold without changing the server setting.`,\n ``,\n `(This option has no effect when {bold wal_sender_timeout} is 0, which`,\n `disables inbound liveness detection entirely.)`,\n ],\n },\n },\n\n /** @deprecated */\n push: pushOptions,\n mutate: mutateOptions,\n /** @deprecated */\n getQueries: getQueriesOptions,\n query: queryOptions,\n\n enableCrudMutations: {\n type: v.boolean().default(true),\n desc: [\n `Enables support for legacy CRUD mutations. When this is {bold false}, no connections`,\n `are made from view-syncers to the upstream db, and push messages with CRUD mutations`,\n `result in an InvalidPush response.`,\n ],\n },\n\n cvr: {\n db: {\n type: v.string().optional(),\n desc: [\n `The Postgres database used to store CVRs. CVRs (client view records) keep track`,\n `of the data synced to clients in order to determine the diff to send on reconnect.`,\n `If unspecified, the {bold upstream-db} will be used.`,\n ],\n },\n\n maxConns: {\n type: v.number().default(30),\n desc: [\n `The maximum number of connections to open to the CVR database.`,\n `This is divided evenly amongst sync workers.`,\n ``,\n `Note that this number must allow for at least one connection per`,\n `sync worker, or zero-cache will fail to start. See {bold num-sync-workers}`,\n ],\n },\n\n maxConnsPerWorker: {\n type: v.number().optional(),\n hidden: true, // Passed from main thread to sync workers\n },\n\n garbageCollectionInactivityThresholdHours: {\n type: v.number().default(48),\n desc: [\n `The duration after which an inactive CVR is eligible for garbage collection.`,\n `Note that garbage collection is an incremental, periodic process which does not`,\n `necessarily purge all eligible CVRs immediately.`,\n ],\n },\n\n garbageCollectionInitialIntervalSeconds: {\n type: v.number().default(60),\n desc: [\n `The initial interval at which to check and garbage collect inactive CVRs.`,\n `This interval is increased exponentially (up to 16 minutes) when there is`,\n `nothing to purge.`,\n ],\n },\n\n garbageCollectionInitialBatchSize: {\n type: v.number().default(25),\n desc: [\n `The initial number of CVRs to purge per garbage collection interval.`,\n `This number is increased linearly if the rate of new CVRs exceeds the rate of`,\n `purged CVRs, in order to reach a steady state.`,\n ``,\n `Setting this to 0 effectively disables CVR garbage collection.`,\n ],\n },\n },\n\n queryHydrationStats: {\n type: v.boolean().optional(),\n desc: [\n `Track and log the number of rows considered by query hydrations which`,\n `take longer than {bold log-slow-hydrate-threshold} milliseconds.`,\n `This is useful for debugging and performance tuning.`,\n ],\n },\n\n sqliteCorruptionChecks: {\n type: v.boolean().default(false),\n desc: [\n `Run SQLite quick_check and integrity_check when corruption is detected.`,\n `These checks scan the replica and can take a long time on large databases.`,\n ],\n hidden: true,\n },\n\n enableQueryPlanner: {\n type: v.boolean().default(true),\n desc: [\n `Enable the query planner for optimizing ZQL queries.`,\n ``,\n `The query planner analyzes and optimizes query execution by determining`,\n `the most efficient join strategies.`,\n ``,\n `You can disable the planner if it is picking bad strategies.`,\n ],\n },\n\n enableQueryCovering: {\n type: v.boolean().default(true),\n desc: [\n `Enable shadow-mode query covering detection during query hydration.`,\n ``,\n `When enabled, view-syncers compare newly hydrated queries against running`,\n `queries with the same root table and log aggregate coverage stats.`,\n ``,\n `You can disable this if covering detection adds too much CPU overhead.`,\n ],\n },\n\n yieldThresholdMs: {\n type: v.number().default(10),\n desc: [\n `The maximum amount of time in milliseconds that a sync worker will`,\n `spend in IVM (processing query hydration and advancement) before yielding`,\n `to the event loop. Lower values increase responsiveness and fairness at`,\n `the cost of reduced throughput.`,\n ],\n },\n\n change: {\n db: {\n type: v.string().optional(),\n desc: [\n `The Postgres database used to store recent replication log entries, in order`,\n `to sync multiple view-syncers without requiring multiple replication slots on`,\n `the upstream database. If unspecified, the {bold upstream-db} will be used.`,\n ],\n },\n\n maxConns: {\n type: v.number().default(5),\n desc: [\n `The maximum number of connections to open to the change database.`,\n `This is used by the {bold change-streamer} for catching up`,\n `{bold zero-cache} replication subscriptions.`,\n ],\n },\n\n statementTimeoutMs: {\n type: v.number().default(20_000),\n desc: [\n `Fail change-log transactions if a statement response from postgres is not received within`,\n `the specified timeout. This differs from a postgres {bold statement_timeout} in that`,\n `it is implemented to handle a pathological case in which Postgres does not return a`,\n `response but otherwise believes the transaction to be idle.`,\n ],\n hidden: true, // make visible if proven to be effective/necessary\n },\n\n logBatchSize: {\n type: v\n .number()\n .assert(\n n => Number.isInteger(n) && n >= 1,\n `change.logBatchSize must be an integer >= 1`,\n )\n .default(2000),\n desc: [\n `The maximum number of change-log rows written per multi-row INSERT to the change`,\n `database. Larger upstream transactions are persisted in batches of this size rather`,\n `than one INSERT per change, which is the dominant cost when replicating large`,\n `transactions (e.g. bulk backfills or migrations). Larger values increase throughput`,\n `at the cost of higher transient memory; the effective batch is internally capped to`,\n `stay within Postgres's bind-parameter limit. Set to {bold 1} to disable batching.`,\n ],\n hidden: true, // make visible if proven to be effective/necessary\n },\n },\n\n replica: replicaOptions,\n\n log: logOptions,\n\n app: appOptions,\n\n shard: shardOptions,\n\n auth: authOptions,\n\n port: {\n type: v.number().default(4848),\n desc: [`The port for sync connections.`],\n },\n\n keepaliveTimeoutMs: {\n type: v.number().optional(),\n desc: [\n `The timeout since the last /keepalive request after which the server will initiate`,\n `a graceful shutdown. This is a workaround for AWS Elastic Container Service, which`,\n `otherwise provides no signal that a target has been deregistered (and should thus begin`,\n `shutdown); the cessation of health checks at /keepalive is instead used as the signal to`,\n `drain. (ECS later sends a SIGTERM before killing the server but only allows a 30-second`,\n `timeout before sending SIGKILL).`,\n ``,\n `Other container runners explicitly send a SIGTERM followed by a configurable drain interval,`,\n `in which case /keepalive logic is not necessary.`,\n ``,\n `When running the server in ECS, this timeout should be set to some multiple of the health`,\n `check interval. If the option is unset, the keepalive timeout is disabled in non-ECS environments,`,\n `and defaults to 20 seconds when run in ECS (determined by the presence of the`,\n `{bold ECS_CONTAINER_METADATA_URI_V4} environment variable as per`,\n `https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-environment-variables.html).`,\n ],\n },\n\n changeStreamer: {\n uri: {\n type: v.string().optional(),\n desc: [\n `When set, connects to the {bold change-streamer} at the given URI.`,\n `In a multi-node setup, this should be specified in {bold view-syncer} options,`,\n `pointing to the {bold replication-manager} URI, which runs a {bold change-streamer}`,\n `on port 4849.`,\n ],\n },\n\n mode: {\n type: v.literalUnion('dedicated', 'discover').default('dedicated'),\n desc: [\n `As an alternative to {bold ZERO_CHANGE_STREAMER_URI}, the {bold ZERO_CHANGE_STREAMER_MODE}`,\n `can be set to \"{bold discover}\" to instruct the {bold view-syncer} to connect to the `,\n `ip address registered by the {bold replication-manager} upon startup.`,\n ``,\n `This may not work in all networking configurations, e.g. certain private `,\n `networking or port forwarding configurations. Using the {bold ZERO_CHANGE_STREAMER_URI}`,\n `with an explicit routable hostname is recommended instead.`,\n ``,\n `Note: This option is ignored if the {bold ZERO_CHANGE_STREAMER_URI} is set.`,\n ],\n },\n\n port: {\n type: v.number().optional(),\n desc: [\n `The port on which the {bold change-streamer} runs. This is an internal`,\n `protocol between the {bold replication-manager} and {bold view-syncers}, which`,\n `runs in the same process tree in local development or a single-node configuration.`,\n ``,\n `If unspecified, defaults to {bold --port} + 1.`,\n ],\n },\n\n /** @deprecated */\n address: {\n type: v.string().optional(),\n deprecated: [\n `Set the {bold ZERO_CHANGE_STREAMER_URI} on view-syncers instead.`,\n ],\n hidden: true,\n },\n\n /** @deprecated */\n protocol: {\n type: v.literalUnion('ws', 'wss').default('ws'),\n deprecated: [\n `Set the {bold ZERO_CHANGE_STREAMER_URI} on view-syncers instead.`,\n ],\n hidden: true,\n },\n\n discoveryInterfacePreferences: {\n type: v.array(v.string()).default([...DEFAULT_PREFERRED_PREFIXES]),\n desc: [\n `The name prefixes to prefer when introspecting the network interfaces to determine`,\n `the externally reachable IP address for change-streamer discovery. This defaults`,\n `to commonly used names for standard ethernet interfaces in order to prevent selecting`,\n `special interfaces such as those for VPNs.`,\n ],\n // More confusing than it's worth to advertise this. The default list should be\n // adjusted to make things work for all environments; it is controlled as a\n // hidden flag as an emergency to unblock people with outlier network configs.\n hidden: true,\n },\n\n startupDelayMs: {\n type: v.number().default(15000),\n desc: [\n `The delay to wait before the change-streamer takes over the replication stream`,\n `(i.e. the handoff during replication-manager updates), to allow loadbalancers to register`,\n `the task as healthy based on healthcheck parameters. Note that if a change stream request`,\n `is received during this interval, the delay will be canceled and the takeover will happen`,\n `immediately, since the incoming request indicates that the task is registered as a target.`,\n ],\n },\n\n sqliteChangeLogMode: {\n type: v.literalUnion('off', 'write', 'compare', 'serve').default('off'),\n desc: [\n `Controls the staged SQLite change-log rollout. Modes are cumulative:`,\n `{bold off}, {bold write}, {bold compare}, and {bold serve}.`,\n ],\n hidden: true,\n },\n\n sqliteChangeLogReadPercent: {\n type: v.number().default(0),\n desc: [\n `The stable percentage of eligible catchup subscriptions served from SQLite.`,\n ],\n hidden: true,\n },\n\n sqliteChangeLogColdReadPercent: {\n type: v.number().default(0),\n desc: [\n `The stable percentage of eligible catchup subscriptions served from a`,\n `SQLite change log that is younger than its retention window, i.e. one`,\n `seeded less than {bold --change-streamer-sqlite-change-log-retention-ms}`,\n `ago. Selection uses the same hash as`,\n `{bold --change-streamer-sqlite-change-log-read-percent}, so a task that`,\n `serves cold is always one that would serve warm.`,\n ],\n hidden: true,\n },\n\n sqliteChangeLogComparePercent: {\n type: v.number().default(1),\n desc: [\n `The stable percentage of committed transactions whose catchup output is`,\n `compared between the Postgres and SQLite change logs in {bold compare} mode.`,\n ],\n hidden: true,\n },\n\n sqliteChangeLogRetentionMs: {\n type: v.number().default(60_000),\n desc: [`The minimum time window retained in the SQLite change log.`],\n hidden: true,\n },\n\n sqliteChangeLogReadBatchRows: {\n type: v.number().default(1000),\n desc: [`The target number of rows in each SQLite catchup read batch.`],\n hidden: true,\n },\n\n sqliteChangeLogPurgeBatchRows: {\n type: v.number().default(1000),\n desc: [`The target number of rows in each SQLite purge batch.`],\n hidden: true,\n },\n\n sqliteChangeLogBarrierTimeoutMs: {\n type: v.number().default(300_000),\n desc: [\n `The maximum wait for the SQLite required-head barrier. This is a`,\n `backstop for a wedged replica on an idle shard, where waiting costs`,\n `nothing and would otherwise go unnoticed. A shard with traffic is`,\n `bounded well before this by the subscriber's backlog reaching its`,\n `high water mark, which is the point at which waiting starts holding`,\n `up replication.`,\n ],\n hidden: true,\n },\n\n backPressureLimitHeapProportion: {\n type: v.number().default(0.04),\n desc: [\n `The percentage of {bold --max-old-space-size} to use as a buffer for absorbing replication`,\n `stream spikes. When the estimated amount of queued data exceeds this threshold, back pressure`,\n `is applied to the replication stream, delaying downstream sync as a result.`,\n ``,\n `The threshold was determined empirically with load testing. Higher thresholds have resulted`,\n `in OOMs. Note also that the byte-counting logic in the queue is strictly an underestimate of`,\n `actual memory usage (but importantly, proportionally correct), so the queue is actually`,\n `using more than what this proportion suggests.`,\n ``,\n `This parameter is exported as an emergency knob to reduce the size of the buffer in the`,\n `event that the server OOMs from back pressure. Resist the urge to {italic increase} this`,\n `proportion, as it is mainly useful for absorbing periodic spikes and does not meaningfully`,\n `affect steady-state replication throughput; the latter is determined by other factors such`,\n `as object serialization and PG throughput`,\n ``,\n `In other words, the back pressure limit does not constrain replication throughput;`,\n `rather, it protects the system when the upstream throughput exceeds the downstream`,\n `throughput.`,\n ],\n },\n\n flowControlConsensusTimeoutProportion: {\n type: v.number().default(2.0),\n desc: [\n `During periodic flow control checks (every 64kb), the amount of time to wait after the majority`,\n `of subscribers have acked, proportional to that interval, after which replication will continue`,\n `even if some subscribers have yet to ack.`,\n ``,\n `This allows a bounded amount of time for backlogged subscribers to catch up on each flush`,\n `without forcing all subscribers to wait for the entire backlog to be processed. It is also`,\n `useful for mitigating the effect of unresponsive subscribers due to severed websocket`,\n `connections or pathological zombie situations (until liveness checks or laggard detection`,\n `disconnects them).`,\n ``,\n `For example, if the majority of subscribers ack a message in 2.5ms, a padding proportion of`,\n `1.0 instructs replication to continue after an additional 2.5ms; for a proportion of 2.0, an`,\n `additional 5.0ms, etc. The default value of 2.0 allows for a subscriber to be 3x slower than the`,\n `majority in the steady state, while similarly bounding the extent to which a temporarily lagging`,\n `subscriber (e.g. due to catchup) slows down the fleet.`,\n ``,\n `Note that subscribers that continually exceed the timeout will eventually be disconnected and`,\n `instructed to shutdown in order to protect the healthy subscribers. It is thus important that`,\n `the proportion account for expected variance in processing speed across subscribers.`,\n ``,\n `Set this to a negative number to disable early flow control releases. (Not recommended, but`,\n `available as an emergency measure.)`,\n ],\n },\n\n flowControlSlowSubscriberGracePeriodSeconds: {\n type: v.number().default(30),\n desc: [\n `The period of time after which a lagging subscriber is disconnected and instructed to`,\n `restart. A subscriber is considered lagging if it {italic continuously} (1) exceeds the`,\n `consensus timeout and (2) fails to exceed the change rate of healthy subscribers. These`,\n `conditions distinguish expected, temporary periods of slowness from pathological`,\n `scenarios, such as zombie tasks, in which the subscriber is unlikely to recover.`,\n ``,\n `In particular, the second condition excludes subscribers that are performing initial`,\n `catchup, as the rate of change for catchup must eventually exceed the rate of upstream`,\n `changes. Note, however, that catchup rate can be legitimately slow during (rare) expensive`,\n `operations such as index creation.`,\n ``,\n `Thus, this grace period caps the amount of time that a lagging subscriber degrades overall`,\n `throughput, but should be long enough to allow for legitimate intervals of slowness.`,\n ``,\n `Note that in the pathological case where a catchup operation exceeds this grace period,`,\n `the system will eventually recover after the next backup/restore cycle, as the expensive`,\n `operation (e.g. index creation) will have been applied to the restored replica and no longer`,\n `executed during catchup.`,\n ``,\n `Set this to 0 or a negative number to disable laggard detection. (Not recommended, but`,\n `available as an emergency measure.)`,\n ],\n },\n },\n\n taskID: {\n type: v.string().optional(),\n desc: [\n `Globally unique identifier for the zero-cache instance.`,\n ``,\n `Setting this to a platform specific task identifier can be useful for debugging.`,\n `If unspecified, zero-cache will attempt to extract the TaskARN if run from within`,\n `an AWS ECS container, and otherwise use a random string.`,\n ],\n },\n\n perUserMutationLimit,\n\n numSyncWorkers: {\n type: v.number().optional(),\n desc: [\n `The number of processes to use for view syncing.`,\n `Leave this unset to use the maximum available parallelism.`,\n `If set to 0, the server runs without sync workers, which is the`,\n `configuration for running the {bold replication-manager}.`,\n ],\n },\n\n autoReset: {\n type: v.boolean().default(true),\n desc: [\n `Automatically wipe and resync the replica when replication is halted.`,\n `This situation can occur for configurations in which the upstream database`,\n `provider prohibits event trigger creation, preventing the zero-cache from`,\n `being able to correctly replicate schema changes. For such configurations,`,\n `an upstream schema change will instead result in halting replication with an`,\n `error indicating that the replica needs to be reset.`,\n ``,\n `When {bold auto-reset} is enabled, zero-cache will respond to such situations`,\n `by shutting down, and when restarted, resetting the replica and all synced `,\n `clients. This is a heavy-weight operation and can result in user-visible`,\n `slowness or downtime if compute resources are scarce.`,\n ],\n },\n\n replicationLag: {\n reportIntervalMs: {\n type: v.number().default(30000),\n desc: [\n `The minimum interval at which replication lag reports are written upstream and`,\n `reported via the {bold zero.replication.total_lag} opentelemetry metric. If`,\n `an expected report is not received before the next interval, Zero retries with`,\n `a new report and increments {bold zero.replication.lag_report_retries}. A`,\n `negative or 0 value disables lag reporting.`,\n ``,\n `This monitoring feature is only support on the postgres upstream type.`,\n ],\n },\n },\n\n adminPassword: {\n type: v.string().optional(),\n desc: [\n `A password used to administer zero-cache server, for example to access the`,\n `/statz endpoint.`,\n '',\n 'A password is optional in development mode but {bold required in production} mode.',\n ],\n },\n\n websocketCompression: {\n type: v.boolean().default(false),\n desc: [\n 'Enable WebSocket per-message deflate compression.',\n '',\n 'Compression can reduce bandwidth usage for sync traffic but',\n 'increases CPU usage on both client and server. Disabled by default.',\n '',\n 'See: https://github.com/websockets/ws#websocket-compression',\n ],\n },\n\n websocketCompressionOptions: {\n type: v.string().optional(),\n desc: [\n 'JSON string containing WebSocket compression options.',\n '',\n 'Only used if websocketCompression is enabled.',\n '',\n 'Example: \\\\{\"zlibDeflateOptions\":\\\\{\"level\":3\\\\},\"threshold\":1024\\\\}',\n '',\n 'See https://github.com/websockets/ws/blob/master/doc/ws.md#new-websocketserveroptions-callback for available options.',\n ],\n },\n\n websocketMaxPayloadBytes: {\n type: v.number().default(10 * 1024 * 1024),\n desc: [\n 'Maximum size of incoming WebSocket messages in bytes.',\n '',\n 'Messages exceeding this limit are rejected before parsing.',\n 'Default: 10MB (10 * 1024 * 1024 = 10485760)',\n ],\n },\n\n litestream: {\n executable: {\n type: v.string().optional(),\n desc: [\n `Path to the {bold litestream} executable. This must be built from the`,\n `{bold rocicorp/litestream} fork. Support for the official binary at v0.5.x`,\n `is planned.`,\n ],\n },\n\n executableV5: {\n type: v.string().optional(),\n desc: [\n `The v0.5.x litestream executable which is used for restoring the backup`,\n `backup when {bold ZERO_LITESTREAM_RESTORE_USING_V5} is specified.`,\n `litestream v0.5.8+ can restore from both v0.3.x and v0.5.x backup formats,`,\n `affording forwards compatibility with a future zero-cache`,\n `version that will use litestream v0.5.x to backup the replica.`,\n ],\n },\n\n restoreUsingV5: {\n type: v.boolean().default(true),\n desc: [\n `Restores the backup using the {bold ZERO_LITESTREAM_EXECUTABLE_V5} if specified.`,\n `This provides a recovery path if rolling back from {bold ZERO_LITESTREAM_BACKUP_USING_V5}`,\n `as v5 restores from both v3 and v5 backups (whichever is more recent).`,\n ],\n },\n\n backupUsingV5: {\n type: v.boolean().default(false),\n desc: [\n `Backs up the replica using Litestream v0.5.x and monitors cleanup`,\n `watermarks by reading the backup through the Litestream SQLite VFS.`,\n `This requires {bold ZERO_LITESTREAM_RESTORE_USING_V5} and`,\n `{bold ZERO_LITESTREAM_VFS_QUERY_EXECUTABLE}`,\n ],\n },\n\n configPath: {\n type: v.string().default('./src/services/litestream/config.yml'),\n desc: [\n `Path to the litestream yaml config file. zero-cache will run this with its`,\n `environment variables, which can be referenced in the file via $\\\\{ENV\\\\}`,\n `substitution, for example:`,\n `* {bold ZERO_REPLICA_FILE} for the db path`,\n `* {bold ZERO_LITESTREAM_BACKUP_LOCATION} for the db replica url`,\n `* {bold ZERO_LITESTREAM_LOG_LEVEL} for the log level`,\n `* {bold ZERO_LOG_FORMAT} for the log type`,\n ],\n },\n\n configPathV5: {\n type: v.string().default('./src/services/litestream/config-v5.yml'),\n desc: [\n `Path to the litestream v5 yaml config file. zero-cache will run this with its`,\n `environment variables, which can be referenced in the file via $\\\\{ENV\\\\}`,\n `substitution, for example:`,\n `* {bold ZERO_REPLICA_FILE} for the db path`,\n `* {bold ZERO_LITESTREAM_BACKUP_LOCATION} for the db replica url`,\n `* {bold ZERO_LITESTREAM_LOG_LEVEL} for the log level`,\n `* {bold ZERO_LOG_FORMAT} for the log type`,\n ],\n },\n\n vfsQueryExecutable: {\n type: v.string().optional(),\n desc: [\n `Path to the rocicorp vfs-query executable that runs the VFS-based`,\n `polling of backup watermark. This is required when backing up with V5.`,\n ],\n },\n\n vfsPollIntervalMs: {\n type: v.number().default(15 * 1000),\n desc: [\n `Interval in milliseconds litestream vfs extension polls the backup store (e.g. s3)`,\n `to determine the most recent backup.`,\n ``,\n `This, in turn, influences how quickly new backups are confirmed, allowing the`,\n `change-streamer to ack the upstream change-source (e.g. replication slot).`,\n ],\n },\n\n vfsPollTimeoutMs: {\n type: v.number().default(10 * 1000),\n desc: [\n `Timeout in milliseconds for requests to the Litestream VFS poller.`,\n ],\n },\n\n vfsLogFile: {\n type: v.string().optional(),\n desc: [\n `Optional file path for logs emitted by the Litestream VFS native`,\n `extension. If unset, the extension writes to stdout.`,\n ],\n },\n\n logLevel: {\n type: v.literalUnion('debug', 'info', 'warn', 'error').default('warn'),\n },\n\n backupURL: {\n type: v.string().optional(),\n desc: [\n `The location of the litestream backup, usually an {bold s3://} URL.`,\n `This is only consulted by the {bold replication-manager}.`,\n `{bold view-syncers} receive this information from the {bold replication-manager}.`,\n ],\n },\n\n endpoint: {\n type: v.string().optional(),\n desc: [\n `The S3-compatible endpoint URL to use for the litestream backup. Only required for non-AWS services.`,\n `The {bold replication-manager} and {bold view-syncers} must have the same endpoint.`,\n ],\n },\n\n region: {\n type: v.string().optional(),\n desc: [\n `The AWS region for the litestream backup bucket. Required for non-standard AWS partitions`,\n `(e.g. GovCloud {bold us-gov-west-1}) where Litestream cannot auto-detect the region.`,\n `The {bold replication-manager} and {bold view-syncers} must have the same region.`,\n ],\n },\n\n port: {\n type: v.number().optional(),\n desc: [\n `Port on which litestream exports metrics, used to determine the replication`,\n `watermark up to which it is safe to purge change log records.`,\n ``,\n `If unspecified, defaults to {bold --port} + 2.`,\n ],\n },\n\n checkpointThresholdMB: {\n type: v.number().default(40),\n desc: [\n `The size of the WAL file at which to perform an SQlite checkpoint to apply`,\n `the writes in the WAL to the main database file. Each checkpoint creates`,\n `a new WAL segment file that will be backed up by litestream (v3). Smaller thresholds`,\n `may improve read performance, at the expense of creating more files to download`,\n `when restoring the replica from the backup.`,\n ``,\n `This setting is only relevant when replicating with litestream v3, and is ignored`,\n `when replicating with litestream v5.`,\n ],\n },\n\n minCheckpointPageCount: {\n type: v.number().optional(),\n desc: [\n `The WAL page count at which SQLite attempts a PASSIVE checkpoint, which`,\n `transfers pages to the main database file without blocking writers.`,\n `Defaults to {bold checkpointThresholdMB * 250} (since SQLite page size is 4KB).`,\n ``,\n `This setting is only relevant when replicating with litestream v3, and is ignored`,\n `when replicating with litestream v5.`,\n ],\n },\n\n maxCheckpointPageCount: {\n type: v.number().optional(),\n desc: [\n `The WAL page count at which SQLite performs a RESTART checkpoint, which`,\n `blocks writers until complete. Defaults to {bold minCheckpointPageCount * 10}.`,\n `Set to {bold 0} to disable RESTART checkpoints entirely.`,\n ``,\n `This setting is only relevant when replicating with litestream v3, and is ignored`,\n `when replicating with litestream v5.`,\n ],\n },\n\n incrementalBackupIntervalMinutes: {\n type: v.number().default(5),\n desc: [\n `The interval between incremental v3 backups of the replica. Shorter intervals`,\n `reduce the amount of change history that needs to be replayed when catching`,\n `up a new view-syncer, at the expense of increasing the number of files needed`,\n `to download for the initial litestream restore.`,\n ``,\n `This option only applies to litestream v3 backups and will be deprecated/removed`,\n `once the zero-cache is transitioned to litestream v5. For configuring v5 backup`,\n `frequency, use {bold ZERO_LITESTREAM_INCREMENTAL_BACKUP_INTERVAL_SECONDS}.`,\n ],\n },\n\n incrementalBackupIntervalSeconds: {\n type: v.number().default(15),\n desc: [\n `The interval between incremental v5 backups of the replica. With litestream v5`,\n `the upstream change source is not ACKed until the corresponding changes have been`,\n `applied to the replica and backed up by litestream. As such, shorter intervals`,\n `incur a higher number of backup storage writes and files managed (e.g. in s3),`,\n `while longer intervals result requiring a larger buffer for changes upstream `,\n `(e.g. per-replication slot wal records). The default value of 15 seconds targets`,\n `an s3 API cost of ~$1/month (not counting storage costs).`,\n ``,\n `This option only applies to litestream v5 backups. For v3 backups, use`,\n `{bold ZERO_LITESTREAM_INCREMENTAL_BACKUP_INTERVAL_MINUTES}.`,\n ],\n },\n\n snapshotBackupIntervalHours: {\n type: v.number().default(4),\n desc: [\n `The interval between snapshot backups of the replica. Snapshot backups`,\n `make a full copy of the database to a new litestream generation. This`,\n `improves restore time at the expense of bandwidth. Applications with a`,\n `large database and low write rate can increase this interval to reduce`,\n `network usage for backups (litestream defaults to 24 hours).`,\n ``,\n `This setting is applied when replicating with either litestream v3 or v5.`,\n `Note, however, that snapshots are generally not needed to improve restore time`,\n `with v5, and so a longer interval (e.g. the litestream default of 24h) is fine.`,\n ],\n },\n\n restoreParallelism: {\n type: v.number().default(48),\n desc: [\n `The number of WAL files to download in parallel when performing the`,\n `initial restore of the replica from the backup.`,\n ],\n },\n\n multipartConcurrency: {\n type: v.number().default(48),\n desc: [\n `The number of parts (of size {bold --litestream-multipart-size} bytes)`,\n `to upload or download in parallel when backing up or restoring the snapshot.`,\n ],\n },\n\n multipartSize: {\n type: v.number().default(16 * 1024 * 1024),\n desc: [\n `The size of each part when uploading or downloading the snapshot with`,\n `{bold --multipart-concurrency}. Note that up to {bold concurrency * size}`,\n `bytes of memory are used when backing up or restoring the snapshot.`,\n ],\n },\n },\n\n storageDBTmpDir: {\n type: v.string().optional(),\n desc: [\n `tmp directory for IVM operator storage. Leave unset to use os.tmpdir()`,\n ],\n },\n\n initialSync: {\n tableCopyWorkers: {\n type: v.number().default(5),\n desc: [\n `The number of parallel workers used to copy tables during initial sync.`,\n `Each worker uses a database connection and will buffer up to (approximately)`,\n `10 MB of table data in memory during initial sync. Increasing the number of`,\n `workers may improve initial sync speed; however, note that local disk throughput`,\n `(i.e. IOPS), upstream CPU, and network bandwidth may also be bottlenecks.`,\n ],\n },\n\n profileCopy: {\n type: v.boolean().optional(),\n hidden: true,\n desc: [\n `Takes a cpu profile during the copy phase initial-sync, storing it as a JSON file`,\n `initial-copy.cpuprofile in the tmp directory.`,\n ],\n },\n\n textCopy: {\n type: v.boolean().default(false),\n desc: [\n `Use text-format COPY instead of binary COPY for initial sync and`,\n `backfill streaming. This is slower but can work around issues with`,\n `binary encoding of certain data types.`,\n ],\n },\n },\n\n shadowSync: {\n enabled: {\n type: v.boolean().default(false),\n desc: [\n `Periodically exercises the initial-sync code path against a sample of`,\n `rows from every published table, writing to a throwaway SQLite database.`,\n `This acts as a canary: if the real initial-sync path ever breaks (schema`,\n `drift, PG version quirks, etc.), the shadow run fails before a customer`,\n `actually needs a full reset.`,\n ],\n },\n\n intervalHours: {\n type: v.number().default(12),\n desc: [\n `The interval between shadow initial-sync runs, in hours. The first`,\n `run fires within [2/3, 1) of this interval after startup, so the`,\n `canary completes at least once per task lifetime (the replication`,\n `manager is restarted every ~24h) while still jittering so a fleet`,\n `restart does not cause all tasks to canary simultaneously.`,\n ],\n },\n\n sampleRate: {\n type: v.number().default(0.1),\n desc: [\n `The BERNOULLI sampling rate for each table (0 < rate <= 1). A value of`,\n `1 disables sampling and copies all rows (still subject to`,\n `{bold max-rows-per-table}).`,\n ],\n },\n\n maxRowsPerTable: {\n type: v.number().default(10000),\n desc: [\n `The hard upper bound on rows copied per table per shadow run. Guards`,\n `against unexpectedly large tables consuming disk / upstream bandwidth.`,\n ],\n },\n },\n\n /** @deprecated */\n targetClientRowCount: {\n type: v.number().default(20_000),\n deprecated: [\n 'This option is no longer used and will be removed in a future version.',\n 'The client-side cache no longer enforces a row limit. Instead, TTL-based expiration',\n 'automatically manages cache size to prevent unbounded growth.',\n ],\n hidden: true,\n },\n\n lazyStartup: {\n type: v.boolean().default(false),\n desc: [\n 'Delay starting the majority of zero-cache until first request.',\n '',\n 'This is mainly intended to avoid connecting to Postgres replication stream',\n 'until the first request is received, which can be useful i.e., for preview instances.',\n '',\n 'Currently only supported in single-node mode.',\n ],\n },\n\n serverVersion: {\n type: v.string().optional(),\n desc: [`The version string outputted to logs when the server starts up.`],\n },\n\n enableTelemetry: {\n type: v.boolean().default(true),\n desc: [\n `Set to false to opt out of telemetry collection.`,\n ``,\n `This helps us improve Zero by collecting anonymous usage data.`,\n `Setting the DO_NOT_TRACK environment variable also disables telemetry.`,\n ],\n },\n\n cloudEvent: {\n sinkEnv: {\n type: v.string().optional(),\n desc: [\n `ENV variable containing a URI to a CloudEvents sink. When set, ZeroEvents`,\n `will be published to the sink as the {bold data} field of CloudEvents.`,\n `The {bold source} field of the CloudEvents will be set to the {bold ZERO_TASK_ID},`,\n `along with any extension attributes specified by the {bold ZERO_CLOUD_EVENT_EXTENSION_OVERRIDES_ENV}.`,\n ``,\n `This configuration is modeled to easily integrate with a knative K_SINK binding,`,\n `(i.e. https://github.com/knative/eventing/blob/main/docs/spec/sources.md#sinkbinding).`,\n `However, any CloudEvents sink can be used.`,\n ],\n },\n\n extensionOverridesEnv: {\n type: v.string().optional(),\n desc: [\n `ENV variable containing a JSON stringified object with an {bold extensions} field`,\n `containing attributes that should be added or overridden on outbound CloudEvents.`,\n ``,\n `This configuration is modeled to easily integrate with a knative K_CE_OVERRIDES binding,`,\n `(i.e. https://github.com/knative/eventing/blob/main/docs/spec/sources.md#sinkbinding).`,\n ],\n },\n },\n};\n\nexport type ZeroConfig = Config<typeof zeroOptions>;\n\nlet loadedConfig: Config<typeof zeroOptions> | undefined;\n\nexport function getZeroConfig(\n opts: Omit<ParseOptions, 'envNamePrefix'> = {},\n): ZeroConfig {\n if (!loadedConfig || singleProcessMode()) {\n loadedConfig = parseOptions(zeroOptions, {\n envNamePrefix: ZERO_ENV_VAR_PREFIX,\n emitDeprecationWarnings: false, // overridden at the top level parse\n ...opts,\n });\n\n if (loadedConfig.queryHydrationStats) {\n runtimeDebugFlags.trackRowCountsVended = true;\n }\n }\n return loadedConfig;\n}\n\n/**\n * Same as {@link getZeroConfig}, with an additional check that the\n * config has already been normalized (i.e. by the top level server/runner).\n */\nexport function getNormalizedZeroConfig(\n opts: Omit<ParseOptions, 'envNamePrefix'> = {},\n): NormalizedZeroConfig {\n const config = getZeroConfig(opts);\n assertNormalized(config);\n return config;\n}\n\n/**\n * Gets the server version from the config if provided. Otherwise it gets it\n * from the Zero package.json.\n */\nexport function getServerVersion(\n config: Pick<ZeroConfig, 'serverVersion'> | undefined,\n): string {\n return config?.serverVersion ?? packageJson.version;\n}\n\nexport function isAdminPasswordValid(\n lc: LogContext,\n config: Pick<NormalizedZeroConfig, 'adminPassword'>,\n password: string | undefined,\n) {\n // If development mode, password is optional\n // We use process.env.NODE_ENV === 'development' as a sign that we're in\n // development mode, rather than a custom env var like ZERO_DEVELOPMENT_MODE,\n // because NODE_ENV is more standard and is already used by many tools.\n // Note that if NODE_ENV is not set, we assume production mode.\n\n if (!password && !config.adminPassword && isDevelopmentMode()) {\n warnOnce(\n lc,\n 'No admin password set; allowing access in development mode only',\n );\n return true;\n }\n\n if (!config.adminPassword) {\n lc.warn?.('No admin password set; denying access');\n return false;\n }\n\n // Use constant-time comparison to prevent timing attacks\n const passwordBuffer = Buffer.from(password ?? '');\n const configBuffer = Buffer.from(config.adminPassword);\n\n // Handle length mismatch in constant time\n if (passwordBuffer.length !== configBuffer.length) {\n // Perform dummy comparison to maintain constant timing\n timingSafeEqual(configBuffer, configBuffer);\n lc.warn?.('Invalid admin password');\n return false;\n }\n\n if (!timingSafeEqual(passwordBuffer, configBuffer)) {\n lc.warn?.('Invalid admin password');\n return false;\n }\n\n lc.debug?.('Admin password accepted');\n return true;\n}\n\nlet hasWarned = false;\n\nfunction warnOnce(lc: LogContext, msg: string) {\n if (!hasWarned) {\n lc.warn?.(msg);\n hasWarned = true;\n }\n}\n\n// For testing purposes - reset the warning state\nexport function resetWarnOnceState() {\n hasWarned = false;\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import type { LogContext } from '@rocicorp/logger';
2
2
  import type postgres from 'postgres';
3
3
  import type { Enum } from '../../../shared/src/enum.ts';
4
+ import { Queue } from '../../../shared/src/queue.ts';
4
5
  import { type PostgresDB, type PostgresTransaction } from '../types/pg.ts';
5
6
  import type * as Mode from './mode-enum.ts';
6
7
  type Mode = Enum<typeof Mode>;
@@ -181,11 +182,13 @@ export declare function sharedSnapshot(): {
181
182
  snapshotID: Promise<string>;
182
183
  };
183
184
  /**
184
- * @returns An `init` Task for importing a snapshot from another transaction.
185
+ * @returns An `init` Task for importing a snapshot from another transaction,
186
+ * as well as an `imported` Queue which resolves or rejects when each
187
+ * worker is initialized.
185
188
  */
186
189
  export declare function importSnapshot(snapshotID: string): {
187
190
  init: Task;
188
- imported: Promise<void>;
191
+ imported: Queue<void>;
189
192
  };
190
193
  /**
191
194
  * A superclass of Errors used for control flow that is needed to handle
@@ -1 +1 @@
1
- {"version":3,"file":"transaction-pool.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/db/transaction-pool.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transaction-pool.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/db/transaction-pool.ts"],"names":[],"mappings":"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"}
@@ -336,14 +336,16 @@ var TransactionPool = class {
336
336
  }
337
337
  };
338
338
  /**
339
- * @returns An `init` Task for importing a snapshot from another transaction.
339
+ * @returns An `init` Task for importing a snapshot from another transaction,
340
+ * as well as an `imported` Queue which resolves or rejects when each
341
+ * worker is initialized.
340
342
  */
341
343
  function importSnapshot(snapshotID) {
342
- const { promise: imported, resolve, reject } = resolver();
344
+ const imported = new Queue();
343
345
  return {
344
346
  init: (tx) => {
345
347
  const stmt = tx.unsafe(`SET TRANSACTION SNAPSHOT '${snapshotID}'`);
346
- stmt.then(() => resolve(), reject);
348
+ stmt.then(() => imported.enqueue(), (err) => imported.enqueueRejection(err));
347
349
  return [stmt];
348
350
  },
349
351
  imported
@@ -1 +1 @@
1
- {"version":3,"file":"transaction-pool.js","names":["#mode","#init","#cleanup","#tasks","#workers","#initialWorkers","#maxWorkers","#responseTimeout","#timeoutTask","#lc","#stmtRunner","#numWorkers","#orphanedQueryCheckInterval","#checkForOrphanedQueries","#db","#addWorker","#start","#stmts","#numWorking","#failure","#done","#process","#pending","#latestDoneIndex","#latestDoneTime","#readRunner"],"sources":["../../../../../zero-cache/src/db/transaction-pool.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {type Resolver, resolver} from '@rocicorp/resolver';\nimport type postgres from 'postgres';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {Enum} from '../../../shared/src/enum.ts';\nimport {Queue} from '../../../shared/src/queue.ts';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {type PostgresDB, type PostgresTransaction} from '../types/pg.ts';\nimport type * as Mode from './mode-enum.ts';\nimport {runTx} from './run-transaction.ts';\n\ntype Mode = Enum<typeof Mode>;\n\ntype MaybePromise<T> = Promise<T> | T;\n\nexport type Statement =\n | postgres.PendingQuery<(postgres.Row & Iterable<postgres.Row>)[]>\n | postgres.PendingQuery<postgres.Row[]>;\n\n/**\n * A {@link Task} is logic run from within a transaction in a {@link TransactionPool}.\n * It returns a list of `Statements` that the transaction executes asynchronously and\n * awaits when it receives the 'done' signal.\n *\n */\nexport type Task = (\n tx: PostgresTransaction,\n lc: LogContext,\n) => MaybePromise<Statement[]>;\n\n/**\n * A {@link ReadTask} is run from within a transaction, but unlike a {@link Task},\n * the results of a ReadTask are opaque to the TransactionPool and returned to the\n * caller of {@link TransactionPool.processReadTask}.\n */\nexport type ReadTask<T> = (\n tx: PostgresTransaction,\n lc: LogContext,\n) => MaybePromise<T>;\n\nexport type Options = {\n mode: Mode;\n\n /**\n * A {@link Task} that is run in each Transaction before it begins\n * processing general tasks. This can be used to to set the transaction\n * mode, export/set snapshots, etc. This will be run even if\n * {@link fail} has been called on the pool.\n */\n init?: Task | undefined;\n\n /**\n * A {@link Task} that is run in each Transaction before it closes.\n * This will be run even if {@link fail} has been called, or if a\n * preceding Task threw an Error.\n */\n cleanup?: Task | undefined;\n\n /**\n * The initial number of transaction workers to process tasks.\n * This is the steady state number of workers that will be kept\n * alive if the TransactionPool is long lived.\n * This must be greater than 0. Defaults to 1.\n */\n initialWorkers?: number;\n\n /**\n * When specified, allows the pool to grow to `maxWorkers`. This\n * must be greater than or equal to `initialWorkers`. On-demand\n * workers will be shut down after an idle timeout of 5 seconds.\n */\n maxWorkers?: number;\n\n /**\n * Aborts the transaction if the response for a statement is not received\n * within the specified timeout. Note that this is different from a Postgres\n * `statement_timeout` or `lock_timeout` in that it is specifically intended\n * to detect situations in which Postgres either never received the\n * statement, or the application never got the response for the executed\n * statement.\n */\n statementResponseTimeout?: number;\n};\n\n/**\n * A TransactionPool is a pool of one or more {@link postgres.TransactionSql}\n * objects that participate in processing a dynamic queue of tasks.\n *\n * This can be used for serializing a set of tasks that arrive asynchronously\n * to a single transaction (for writing) or performing parallel reads across\n * multiple connections at the same snapshot (e.g. read only snapshot transactions).\n */\nexport class TransactionPool {\n #lc: LogContext;\n readonly #mode: Mode;\n readonly #init: TaskRunner | undefined;\n readonly #cleanup: TaskRunner | undefined;\n readonly #tasks = new Queue<TaskRunner | Error | 'done'>();\n readonly #workers: Promise<unknown>[] = [];\n readonly #initialWorkers: number;\n readonly #maxWorkers: number;\n readonly #responseTimeout: number | undefined;\n readonly #timeoutTask: TimeoutTasks;\n #numWorkers: number;\n #numWorking = 0;\n #db: PostgresDB | undefined; // set when running. stored to allow adaptive pool sizing.\n\n #done = false;\n #failure: Error | undefined;\n #orphanedQueryCheckInterval: NodeJS.Timeout;\n\n constructor(\n lc: LogContext,\n opts: Options,\n timeoutTasks = TIMEOUT_TASKS, // Overridden for tests.\n ) {\n const {\n mode,\n init,\n cleanup,\n initialWorkers = 1,\n maxWorkers = initialWorkers,\n statementResponseTimeout,\n } = opts;\n assert(initialWorkers > 0, 'initialWorkers must be positive');\n assert(\n maxWorkers >= initialWorkers,\n 'maxWorkers must be >= initialWorkers',\n );\n\n this.#lc = lc;\n this.#mode = mode;\n this.#init = init ? this.#stmtRunner(init) : undefined;\n this.#cleanup = cleanup ? this.#stmtRunner(cleanup) : undefined;\n this.#initialWorkers = initialWorkers;\n this.#numWorkers = initialWorkers;\n this.#maxWorkers = maxWorkers;\n this.#timeoutTask = timeoutTasks;\n this.#responseTimeout = statementResponseTimeout;\n\n this.#orphanedQueryCheckInterval = setInterval(\n this.#checkForOrphanedQueries,\n Math.min(5_000, statementResponseTimeout ?? 5_000),\n );\n }\n\n /**\n * Starts the pool of workers to process Tasks with transactions opened from the\n * specified {@link db}.\n */\n run(db: PostgresDB): this {\n assert(!this.#db, 'already running');\n this.#db = db;\n for (let i = 0; i < this.#numWorkers; i++) {\n this.#addWorker(db);\n }\n return this;\n }\n\n /**\n * Adds context parameters to internal LogContext. This is useful for context values that\n * are not known when the TransactionPool is constructed (e.g. determined after a database\n * call when the pool is running).\n *\n * Returns an object that can be used to add more parameters.\n */\n addLoggingContext(key: string, value: string) {\n this.#lc = this.#lc.withContext(key, value);\n\n return {\n addLoggingContext: (key: string, value: string) =>\n this.addLoggingContext(key, value),\n };\n }\n\n /**\n * Returns a promise that:\n *\n * * resolves after {@link setDone} has been called (or the the pool as been {@link unref}ed\n * to a 0 ref count), once all added tasks have been processed and all transactions have been\n * committed or closed.\n *\n * * rejects if processing was aborted with {@link fail} or if processing any of\n * the tasks resulted in an error. All uncommitted transactions will have been\n * rolled back.\n *\n * Note that partial failures are possible if processing writes with multiple workers\n * (e.g. `setDone` is called, allowing some workers to commit, after which other\n * workers encounter errors). Using a TransactionPool in this manner does not make\n * sense in terms of transactional semantics, and is thus not recommended.\n *\n * For reads, however, multiple workers is useful for performing parallel reads\n * at the same snapshot. See {@link synchronizedSnapshots} for an example.\n * Resolves or rejects when all workers are done or failed.\n */\n async done() {\n const numWorkers = this.#workers.length;\n await Promise.all(this.#workers);\n\n if (numWorkers < this.#workers.length) {\n // If workers were added after the initial set, they must be awaited to ensure\n // that the results (i.e. rejections) of all workers are accounted for. This only\n // needs to be re-done once, because the fact that the first `await` completed\n // guarantees that the pool is in a terminal state and no new workers can be added.\n await Promise.all(this.#workers);\n }\n this.#lc.debug?.('transaction pool done');\n\n const elapsed = performance.now() - this.#start;\n if (elapsed > 60_000) {\n if (this.#stmts > 0) {\n this.#lc.warn?.(\n `finished long transaction with ${this.#stmts} statements (${elapsed.toFixed(3)} ms)`,\n );\n } else {\n this.#lc.warn?.(\n `finished long read transaction (${elapsed.toFixed(3)} ms)`,\n );\n }\n }\n }\n\n #addWorker(db: PostgresDB) {\n const id = this.#workers.length + 1;\n const lc = this.#lc.withContext('tx', id);\n\n const tt: TimeoutTask =\n this.#workers.length < this.#initialWorkers\n ? this.#timeoutTask.forInitialWorkers\n : this.#timeoutTask.forExtraWorkers;\n const {timeoutMs} = tt;\n const timeoutTask = tt.task === 'done' ? 'done' : this.#stmtRunner(tt.task);\n\n const worker = async (tx: PostgresTransaction) => {\n const start = performance.now();\n try {\n lc.debug?.('started transaction');\n\n let last: Promise<void> = promiseVoid;\n\n const executeTask = async (runner: TaskRunner) => {\n runner !== this.#init && this.#numWorking++;\n const {pending} = await runner.run(tx, lc, () => {\n runner !== this.#init && this.#numWorking--;\n });\n last = pending ?? last;\n };\n\n let task: TaskRunner | Error | 'done' =\n this.#init ?? (await this.#tasks.dequeue(timeoutTask, timeoutMs));\n\n try {\n while (task !== 'done') {\n if (\n task instanceof Error ||\n (task !== this.#init && this.#failure)\n ) {\n throw this.#failure ?? task;\n }\n await executeTask(task);\n\n // await the next task.\n task = await this.#tasks.dequeue(timeoutTask, timeoutMs);\n }\n } finally {\n // Execute the cleanup task even on failure.\n if (this.#cleanup) {\n await executeTask(this.#cleanup);\n }\n }\n\n const elapsed = performance.now() - start;\n lc.debug?.(`closing transaction (${elapsed.toFixed(3)} ms)`);\n // Given the semantics of a Postgres transaction, the last statement\n // will only succeed if all of the preceding statements succeeded.\n return last;\n } catch (e) {\n if (e !== this.#failure) {\n this.fail(e); // A failure in any worker should fail the pool.\n }\n throw e;\n }\n };\n\n const workerTx = runTx(db, worker, {mode: this.#mode})\n .catch(e => {\n if (e instanceof RollbackSignal) {\n // A RollbackSignal is used to gracefully rollback the postgres.js\n // transaction block. It should not be thrown up to the application.\n lc.debug?.('aborted transaction');\n } else {\n throw e;\n }\n })\n .finally(() => this.#numWorkers--);\n\n // Attach a rejection handler immediately to prevent unhandledRejections.\n // The application will handle errors when it awaits processReadTask()\n // or done().\n workerTx.catch(() => {});\n\n this.#workers.push(workerTx);\n\n // After adding the worker, enqueue a terminal signal if we are in either of the\n // terminal states (both of which prevent more tasks from being enqueued), to ensure\n // that the added worker eventually exits.\n if (this.#done) {\n this.#tasks.enqueue('done');\n }\n if (this.#failure) {\n this.#tasks.enqueue(this.#failure);\n }\n }\n\n /**\n * Processes the statements produced by the specified {@link Task},\n * returning a Promise that resolves when the statements are either processed\n * by the database or rejected.\n *\n * Note that statement failures will result in failing the entire\n * TransactionPool (per transaction semantics). However, the returned Promise\n * itself will resolve rather than reject. As such, it is fine to ignore\n * returned Promises in order to pipeline requests to the database. It is\n * recommended to occasionally await them (e.g. after some threshold) in\n * order to avoid memory blowup in the case of database slowness.\n */\n process(task: Task): Promise<void> {\n const r = resolver<void>();\n this.#process(this.#stmtRunner(task, r));\n return r.promise;\n }\n\n readonly #start = performance.now();\n #stmts = 0;\n #latestDoneIndex = 0;\n #latestDoneTime = 0;\n\n readonly #pending = new Map<\n Query,\n {index: number; time: number; abort: (e: ResponseTimeoutError) => void}\n >();\n\n /**\n * Implements the semantics specified in {@link process()}.\n *\n * Specifically:\n * * `freeWorker()` is called as soon as the statements are produced,\n * allowing them to be pipelined to the database.\n * * Statement errors result in failing the transaction pool.\n * * The client-supplied Resolver resolves on success or failure;\n * it is never rejected.\n */\n #stmtRunner(task: Task, r: {resolve: () => void} = resolver()): TaskRunner {\n return {\n run: async (tx, lc, freeWorker) => {\n let stmts: Statement[];\n try {\n stmts = await task(tx, lc);\n } catch (e) {\n r.resolve();\n throw e;\n } finally {\n freeWorker();\n }\n\n if (stmts.length === 0) {\n r.resolve();\n return {pending: null};\n }\n\n // Execute the statements (i.e. send to the db) immediately.\n // The last result is returned for the worker to await before\n // closing the transaction.\n const time = Date.now();\n const last = stmts.reduce((_, stmt) => {\n const query = stmt as unknown as Query;\n const index = ++this.#stmts;\n const {promise: aborted, reject: abort} = resolver();\n aborted.catch(() => {}); // always consider it \"handled\"\n\n this.#pending.set(query, {index, time, abort});\n const responded = stmt\n .execute()\n .then(() => {\n if (index % 1000 === 0) {\n const log = index % 10000 === 0 ? 'info' : 'debug';\n lc[log]?.(\n `executed ${this.#stmts}th statement (${(performance.now() - this.#start).toFixed(3)} ms)`,\n {statement: query.string},\n );\n }\n })\n .catch(e => this.fail(e))\n .finally(() => {\n this.#pending.delete(query);\n if (index > this.#latestDoneIndex) {\n this.#latestDoneIndex = index;\n this.#latestDoneTime = Date.now();\n }\n });\n\n const done = Promise.race([responded, aborted]);\n done.catch(() => {});\n\n return done;\n }, promiseVoid);\n return {pending: last.finally(r.resolve)};\n },\n rejected: r.resolve,\n };\n }\n\n /**\n * Periodically checks the map of `#pending` queries for which Promises\n * have yet to be resolved. Checks for pathological scenarios that have\n * been observed:\n *\n * * A promise does not resolve, even though the transaction is otherwise\n * healthy with keepalives being sent.\n * * A transaction never \"finishes\" at the postgres.js level after the\n * worker exits, implying that postgres.js is awaiting the `last` promise\n * returned. In this case Postgres disconnected the application with an\n * idle-in-transaction timeout, suggesting that all statements did in\n * fact complete.\n */\n readonly #checkForOrphanedQueries = () => {\n if (this.#pending.size === 0) {\n return;\n }\n const now = Date.now();\n for (const [query, {index, time, abort}] of this.#pending.entries()) {\n const statement = query.string;\n const abortWith = (msg: string) => {\n this.#lc.warn?.(msg, {statement});\n const err = new ResponseTimeoutError(msg);\n abort(err);\n this.fail(err);\n this.#pending.delete(query);\n };\n\n if (index < this.#latestDoneIndex) {\n const elapsed = now - this.#latestDoneTime;\n if (this.#responseTimeout && elapsed > this.#responseTimeout) {\n abortWith(\n `statement ${this.#latestDoneIndex} completed ${elapsed} ms ago, but statement ${index} has not.`,\n );\n }\n } else {\n // Nothing out of order, but checking for a response timeout.\n const elapsed = now - time;\n if (this.#responseTimeout && elapsed > this.#responseTimeout) {\n abortWith(`response for statement timed out after ${elapsed} ms`);\n }\n }\n }\n };\n\n /**\n * Processes and returns the result of executing the {@link ReadTask} from\n * within the transaction. An error thrown by the task will result in\n * rejecting the returned Promise, but will not affect the transaction pool\n * itself.\n */\n processReadTask<T>(readTask: ReadTask<T>): Promise<T> {\n const r = resolver<T>();\n this.#process(this.#readRunner(readTask, r));\n return r.promise;\n }\n\n /**\n * Implements the semantics specified in {@link processReadTask()}.\n *\n * Specifically:\n * * `freeWorker()` is called as soon as the result is produced,\n * before resolving the client-supplied Resolver.\n * * Errors result in rejecting the client-supplied Resolver but\n * do not affect transaction pool.\n */\n #readRunner<T>(readTask: ReadTask<T>, r: Resolver<T>): TaskRunner {\n return {\n run: async (tx, lc, freeWorker) => {\n let result: T;\n try {\n result = await readTask(tx, lc);\n freeWorker();\n r.resolve(result);\n } catch (e) {\n freeWorker();\n r.reject(e);\n }\n return {pending: null};\n },\n rejected: r.reject,\n };\n }\n\n #process(runner: TaskRunner): void {\n assert(!this.#done, 'already set done');\n if (this.#failure) {\n runner.rejected(this.#failure);\n return;\n }\n\n this.#tasks.enqueue(runner);\n\n // Check if the pool size can and should be increased.\n if (this.#numWorkers < this.#maxWorkers) {\n const outstanding = this.#tasks.size();\n\n if (outstanding > this.#numWorkers - this.#numWorking) {\n this.#db && this.#addWorker(this.#db);\n this.#numWorkers++;\n this.#lc.debug?.(`Increased pool size to ${this.#numWorkers}`);\n }\n }\n }\n\n /**\n * Ends all workers with a ROLLBACK. Throws if the pool is already done\n * or aborted.\n */\n abort() {\n this.fail(new RollbackSignal());\n }\n\n /**\n * Signals to all workers to end their transaction once all pending tasks have\n * been completed. Throws if the pool is already done or aborted.\n */\n setDone() {\n assert(!this.#done, 'already set done');\n this.#done = true;\n\n for (let i = 0; i < this.#numWorkers; i++) {\n this.#tasks.enqueue('done');\n }\n void Promise.allSettled(this.#workers).then(() =>\n clearInterval(this.#orphanedQueryCheckInterval),\n );\n }\n\n isRunning(): boolean {\n return this.#db !== undefined && !this.#done && this.#failure === undefined;\n }\n\n /**\n * Signals all workers to fail their transactions with the given {@link err}.\n */\n fail(err: unknown) {\n if (!this.#done && !this.#failure) {\n this.#failure = ensureError(err); // Fail fast: this is checked in the worker loop.\n // Logged for informational purposes. It is the responsibility of\n // higher level logic to classify and handle the exception.\n const level =\n this.#failure instanceof ControlFlowError ? 'debug' : 'info';\n this.#lc[level]?.(this.#failure);\n\n for (let i = 0; i < this.#numWorkers; i++) {\n // Enqueue the Error to terminate any workers waiting for tasks.\n this.#tasks.enqueue(this.#failure);\n }\n void Promise.allSettled(this.#workers).then(() =>\n clearInterval(this.#orphanedQueryCheckInterval),\n );\n }\n }\n}\n\ntype SynchronizeSnapshotTasks = {\n /**\n * The `init` Task for the TransactionPool from which the snapshot originates.\n * The pool must have Mode.SERIALIZABLE, and will be set to READ ONLY by the\n * `exportSnapshot` init task. If the TransactionPool has multiple workers, the\n * first worker will export a snapshot that the others set.\n */\n exportSnapshot: Task;\n\n /**\n * The `cleanup` Task for the TransactionPool from which the snapshot\n * originates. This Task will wait for the follower pool to `setSnapshot`\n * to ensure that the snapshot is successfully shared before the originating\n * transaction is closed.\n */\n cleanupExport: Task;\n\n /**\n * The `init` Task for the TransactionPool in which workers will\n * consequently see the same snapshot as that of the first pool. The pool\n * must have Mode.SERIALIZABLE, and will have the ability to perform writes.\n */\n setSnapshot: Task;\n\n /** The ID of the shared snapshot. */\n snapshotID: Promise<string>;\n};\n\n/**\n * Init Tasks for Postgres snapshot synchronization across transactions.\n *\n * https://www.postgresql.org/docs/9.3/functions-admin.html#:~:text=Snapshot%20Synchronization%20Functions,identical%20content%20in%20the%20database.\n */\nexport function synchronizedSnapshots(): SynchronizeSnapshotTasks {\n const {\n promise: snapshotExported,\n resolve: exportSnapshot,\n reject: failExport,\n } = resolver<string>();\n\n const {\n promise: snapshotCaptured,\n resolve: captureSnapshot,\n reject: failCapture,\n } = resolver<unknown>();\n\n // Set by the first worker to run its initTask, who becomes responsible for\n // exporting the snapshot. TODO: Plumb the workerNum and use that instead.\n let firstWorkerRun = false;\n\n // Note: Neither init task should `await`, as processing in each pool can proceed\n // as soon as the statements have been sent to the db. However, the `cleanupExport`\n // task must `await` the result of `setSnapshot` to ensure that exporting transaction\n // does not close before the snapshot has been captured.\n return {\n exportSnapshot: tx => {\n if (!firstWorkerRun) {\n firstWorkerRun = true;\n const stmt =\n tx`SELECT pg_export_snapshot() AS snapshot; SET TRANSACTION READ ONLY;`.simple();\n // Intercept the promise to propagate the information to `snapshotExported`.\n stmt.then(result => exportSnapshot(result[0].snapshot), failExport);\n return [stmt]; // Also return the stmt so that it gets awaited (and errors handled).\n }\n return snapshotExported.then(snapshotID => [\n tx.unsafe(`SET TRANSACTION SNAPSHOT '${snapshotID}'`),\n tx`SET TRANSACTION READ ONLY`.simple(),\n ]);\n },\n\n setSnapshot: tx =>\n snapshotExported.then(snapshotID => {\n const stmt = tx.unsafe(`SET TRANSACTION SNAPSHOT '${snapshotID}'`);\n // Intercept the promise to propagate the information to `cleanupExport`.\n stmt.then(captureSnapshot, failCapture);\n return [stmt];\n }),\n\n cleanupExport: async () => {\n await snapshotCaptured;\n return [];\n },\n\n snapshotID: snapshotExported,\n };\n}\n\n/**\n * Returns `init` and `cleanup` {@link Task}s for a TransactionPool that ensure its workers\n * share a single view of the database. This is used for View Notifier and View Syncer logic\n * that allows multiple entities to perform parallel reads on the same snapshot of the database.\n */\nexport function sharedSnapshot(): {\n init: Task;\n cleanup: Task;\n snapshotID: Promise<string>;\n} {\n const {\n promise: snapshotExported,\n resolve: exportSnapshot,\n reject: failExport,\n } = resolver<string>();\n\n // Set by the first worker to run its initTask, who becomes responsible for\n // exporting the snapshot.\n let firstWorkerRun = false;\n\n // The LogContext of the exporting worker, used to identify its cleanup call.\n // Each worker receives a unique lc instance (via withContext('tx', id)), so\n // reference equality reliably identifies the exporting worker.\n let exporterLc: LogContext | undefined;\n\n // Set when the exporting worker's cleanup runs, signalling that the snapshot\n // is no longer needed and any subsequently spawned workers should skip their\n // initTask.\n let firstWorkerDone = false;\n\n return {\n init: (tx, lc) => {\n if (!firstWorkerRun) {\n firstWorkerRun = true;\n exporterLc = lc; // Remember which worker is the exporter.\n const stmt = tx`SELECT pg_export_snapshot() AS snapshot;`.simple();\n // Intercept the promise to propagate the information to `snapshotExported`.\n stmt.then(result => exportSnapshot(result[0].snapshot), failExport);\n return [stmt]; // Also return the stmt so that it gets awaited (and errors handled).\n }\n if (!firstWorkerDone) {\n return snapshotExported.then(snapshotID => [\n tx.unsafe(`SET TRANSACTION SNAPSHOT '${snapshotID}'`),\n ]);\n }\n lc.debug?.('All work is done. No need to set snapshot');\n return [];\n },\n\n cleanup: (_tx, lc) => {\n // Only the exporting worker's cleanup should disable snapshot-setting.\n // Non-exporter workers may finish early; letting them flip this flag\n // would cause subsequently spawned workers to skip SET TRANSACTION SNAPSHOT\n // and read a newer database view, violating snapshot isolation.\n if (lc === exporterLc) {\n firstWorkerDone = true;\n }\n return [];\n },\n\n snapshotID: snapshotExported,\n };\n}\n\n/**\n * @returns An `init` Task for importing a snapshot from another transaction.\n */\nexport function importSnapshot(snapshotID: string): {\n init: Task;\n imported: Promise<void>;\n} {\n const {promise: imported, resolve, reject} = resolver<void>();\n\n return {\n init: tx => {\n const stmt = tx.unsafe(`SET TRANSACTION SNAPSHOT '${snapshotID}'`);\n stmt.then(() => resolve(), reject);\n return [stmt];\n },\n\n imported,\n };\n}\n\n/**\n * A superclass of Errors used for control flow that is needed to handle\n * another Error but does not constitute an error condition itself (e.g.\n * aborting transactions after a previous one fails). Subclassing this Error\n * will result in lowering the log level from `error` to `debug`.\n */\nexport class ControlFlowError extends Error {\n constructor(cause?: unknown) {\n super();\n this.cause = cause;\n }\n}\n\n/**\n * Internal error used to rollback the worker transaction. This is used\n * instead of executing a `ROLLBACK` statement because the postgres.js\n * library will otherwise try to execute an extraneous `COMMIT`, which\n * results in outputting a \"no transaction in progress\" warning to the\n * database logs.\n *\n * Throwing an exception, on the other hand, executes the postgres.js\n * codepath that calls `ROLLBACK` instead.\n */\nclass RollbackSignal extends ControlFlowError {\n readonly name = 'RollbackSignal';\n readonly message = 'rolling back transaction';\n}\n\nfunction ensureError(err: unknown): Error {\n if (err instanceof Error) {\n return err;\n }\n const error = new Error();\n error.cause = err;\n return error;\n}\n\ninterface TaskRunner {\n /**\n * Manages the running of a Task or ReadTask in two phases:\n *\n * - If the task involves blocking, this is done in the worker. Once the\n * blocking is done, `freeWorker()` is invoked to signal that the worker\n * is available to run another task. Note that this should be invoked\n * *before* resolving the result to the calling thread so that a\n * subsequent task can reuse the same worker.\n *\n * - Task statements are executed on the database asynchronously. The final\n * result of this processing is encapsulated in the returned `pending`\n * Promise. The worker will await the last pending Promise before closing\n * the transaction.\n *\n * @param freeWorker should be called as soon as all blocking operations are\n * completed in order to return the transaction to the pool.\n * @returns A `pending` Promise indicating when the statements have been\n * processed by the database, allowing the transaction to be closed.\n * This should be `null` if there are no transaction-dependent\n * statements to await.\n */\n run(\n tx: PostgresTransaction,\n lc: LogContext,\n freeWorker: () => void,\n ): Promise<{pending: Promise<void> | null}>;\n\n /**\n * Invoked if the TransactionPool is already in a failed state when the task\n * is requested.\n */\n rejected(reason: unknown): void;\n}\n\nconst IDLE_TIMEOUT_MS = 5_000;\n\n// The keepalive interval is settable by ZERO_TRANSACTION_POOL_KEEPALIVE_MS\n// as an emergency measure and is explicitly not made available as a server\n// option. This value is function of how the zero-cache uses transactions, and\n// should never need to be \"tuned\" or adjusted for different environments.\n//\n// Note that it must be shorter than IDLE_IN_TRANSACTION_SESSION_TIMEOUT_MS\n// with sufficient buffering to account for when the process is blocked by\n// synchronous calls (e.g. to the replica).\nconst KEEPALIVE_TIMEOUT_MS = parseInt(\n process.env.ZERO_TRANSACTION_POOL_KEEPALIVE_MS ?? '5000',\n);\n\nconst KEEPALIVE_TASK: Task = (tx, lc) => {\n lc.debug?.(`sending tx keepalive`);\n return [tx`SELECT 1`.simple()];\n};\n\ntype TimeoutTask = {\n timeoutMs: number;\n task: Task | 'done';\n};\n\ntype TimeoutTasks = {\n forInitialWorkers: TimeoutTask;\n forExtraWorkers: TimeoutTask;\n};\n\n// Production timeout tasks. Overridden in tests.\nexport const TIMEOUT_TASKS: TimeoutTasks = {\n forInitialWorkers: {\n timeoutMs: KEEPALIVE_TIMEOUT_MS,\n task: KEEPALIVE_TASK,\n },\n forExtraWorkers: {\n timeoutMs: IDLE_TIMEOUT_MS,\n task: 'done',\n },\n};\n\n// The slice of information from the Query object in Postgres.js that gets logged for debugging.\n// https://github.com/porsager/postgres/blob/f58cd4f3affd3e8ce8f53e42799672d86cd2c70b/src/connection.js#L219\ntype Query = {string: string; parameters: object[]};\n\nexport class ResponseTimeoutError extends Error {\n static readonly name = 'ResponseTimeoutError';\n\n constructor(msg: string) {\n super(msg);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AA4FA,IAAa,kBAAb,MAA6B;CAC3B;CACA;CACA;CACA;CACA,SAAkB,IAAI,MAAmC;CACzD,WAAwC,CAAC;CACzC;CACA;CACA;CACA;CACA;CACA,cAAc;CACd;CAEA,QAAQ;CACR;CACA;CAEA,YACE,IACA,MACA,eAAe,eACf;EACA,MAAM,EACJ,MACA,MACA,SACA,iBAAiB,GACjB,aAAa,gBACb,6BACE;EACJ,OAAO,iBAAiB,GAAG,iCAAiC;EAC5D,OACE,cAAc,gBACd,sCACF;EAEA,KAAKS,MAAM;EACX,KAAKT,QAAQ;EACb,KAAKC,QAAQ,OAAO,KAAKS,YAAY,IAAI,IAAI,KAAA;EAC7C,KAAKR,WAAW,UAAU,KAAKQ,YAAY,OAAO,IAAI,KAAA;EACtD,KAAKL,kBAAkB;EACvB,KAAKM,cAAc;EACnB,KAAKL,cAAc;EACnB,KAAKE,eAAe;EACpB,KAAKD,mBAAmB;EAExB,KAAKK,8BAA8B,YACjC,KAAKC,0BACL,KAAK,IAAI,KAAO,4BAA4B,GAAK,CACnD;CACF;;;;;CAMA,IAAI,IAAsB;EACxB,OAAO,CAAC,KAAKC,KAAK,iBAAiB;EACnC,KAAKA,MAAM;EACX,KAAK,IAAI,IAAI,GAAG,IAAI,KAAKH,aAAa,KACpC,KAAKI,WAAW,EAAE;EAEpB,OAAO;CACT;;;;;;;;CASA,kBAAkB,KAAa,OAAe;EAC5C,KAAKN,MAAM,KAAKA,IAAI,YAAY,KAAK,KAAK;EAE1C,OAAO,EACL,oBAAoB,KAAa,UAC/B,KAAK,kBAAkB,KAAK,KAAK,EACrC;CACF;;;;;;;;;;;;;;;;;;;;;CAsBA,MAAM,OAAO;EACX,MAAM,aAAa,KAAKL,SAAS;EACjC,MAAM,QAAQ,IAAI,KAAKA,QAAQ;EAE/B,IAAI,aAAa,KAAKA,SAAS,QAK7B,MAAM,QAAQ,IAAI,KAAKA,QAAQ;EAEjC,KAAKK,IAAI,QAAQ,uBAAuB;EAExC,MAAM,UAAU,YAAY,IAAI,IAAI,KAAKO;EACzC,IAAI,UAAU,KACZ,IAAI,KAAKC,SAAS,GAChB,KAAKR,IAAI,OACP,kCAAkC,KAAKQ,OAAO,eAAe,QAAQ,QAAQ,CAAC,EAAE,KAClF;OAEA,KAAKR,IAAI,OACP,mCAAmC,QAAQ,QAAQ,CAAC,EAAE,KACxD;CAGN;CAEA,WAAW,IAAgB;EACzB,MAAM,KAAK,KAAKL,SAAS,SAAS;EAClC,MAAM,KAAK,KAAKK,IAAI,YAAY,MAAM,EAAE;EAExC,MAAM,KACJ,KAAKL,SAAS,SAAS,KAAKC,kBACxB,KAAKG,aAAa,oBAClB,KAAKA,aAAa;EACxB,MAAM,EAAC,cAAa;EACpB,MAAM,cAAc,GAAG,SAAS,SAAS,SAAS,KAAKE,YAAY,GAAG,IAAI;EAE1E,MAAM,SAAS,OAAO,OAA4B;GAChD,MAAM,QAAQ,YAAY,IAAI;GAC9B,IAAI;IACF,GAAG,QAAQ,qBAAqB;IAEhC,IAAI,OAAsB;IAE1B,MAAM,cAAc,OAAO,WAAuB;KAChD,WAAW,KAAKT,SAAS,KAAKiB;KAC9B,MAAM,EAAC,YAAW,MAAM,OAAO,IAAI,IAAI,UAAU;MAC/C,WAAW,KAAKjB,SAAS,KAAKiB;KAChC,CAAC;KACD,OAAO,WAAW;IACpB;IAEA,IAAI,OACF,KAAKjB,SAAU,MAAM,KAAKE,OAAO,QAAQ,aAAa,SAAS;IAEjE,IAAI;KACF,OAAO,SAAS,QAAQ;MACtB,IACE,gBAAgB,SACf,SAAS,KAAKF,SAAS,KAAKkB,UAE7B,MAAM,KAAKA,YAAY;MAEzB,MAAM,YAAY,IAAI;MAGtB,OAAO,MAAM,KAAKhB,OAAO,QAAQ,aAAa,SAAS;KACzD;IACF,UAAU;KAER,IAAI,KAAKD,UACP,MAAM,YAAY,KAAKA,QAAQ;IAEnC;IAEA,MAAM,UAAU,YAAY,IAAI,IAAI;IACpC,GAAG,QAAQ,wBAAwB,QAAQ,QAAQ,CAAC,EAAE,KAAK;IAG3D,OAAO;GACT,SAAS,GAAG;IACV,IAAI,MAAM,KAAKiB,UACb,KAAK,KAAK,CAAC;IAEb,MAAM;GACR;EACF;EAEA,MAAM,WAAW,MAAM,IAAI,QAAQ,EAAC,MAAM,KAAKnB,MAAK,CAAC,EAClD,OAAM,MAAK;GACV,IAAI,aAAa,gBAGf,GAAG,QAAQ,qBAAqB;QAEhC,MAAM;EAEV,CAAC,EACA,cAAc,KAAKW,aAAa;EAKnC,SAAS,YAAY,CAAC,CAAC;EAEvB,KAAKP,SAAS,KAAK,QAAQ;EAK3B,IAAI,KAAKgB,OACP,KAAKjB,OAAO,QAAQ,MAAM;EAE5B,IAAI,KAAKgB,UACP,KAAKhB,OAAO,QAAQ,KAAKgB,QAAQ;CAErC;;;;;;;;;;;;;CAcA,QAAQ,MAA2B;EACjC,MAAM,IAAI,SAAe;EACzB,KAAKE,SAAS,KAAKX,YAAY,MAAM,CAAC,CAAC;EACvC,OAAO,EAAE;CACX;CAEA,SAAkB,YAAY,IAAI;CAClC,SAAS;CACT,mBAAmB;CACnB,kBAAkB;CAElB,2BAAoB,IAAI,IAGtB;;;;;;;;;;;CAYF,YAAY,MAAY,IAA2B,SAAS,GAAe;EACzE,OAAO;GACL,KAAK,OAAO,IAAI,IAAI,eAAe;IACjC,IAAI;IACJ,IAAI;KACF,QAAQ,MAAM,KAAK,IAAI,EAAE;IAC3B,SAAS,GAAG;KACV,EAAE,QAAQ;KACV,MAAM;IACR,UAAU;KACR,WAAW;IACb;IAEA,IAAI,MAAM,WAAW,GAAG;KACtB,EAAE,QAAQ;KACV,OAAO,EAAC,SAAS,KAAI;IACvB;IAKA,MAAM,OAAO,KAAK,IAAI;IAiCtB,OAAO,EAAC,SAhCK,MAAM,QAAQ,GAAG,SAAS;KACrC,MAAM,QAAQ;KACd,MAAM,QAAQ,EAAE,KAAKO;KACrB,MAAM,EAAC,SAAS,SAAS,QAAQ,UAAS,SAAS;KACnD,QAAQ,YAAY,CAAC,CAAC;KAEtB,KAAKK,SAAS,IAAI,OAAO;MAAC;MAAO;MAAM;KAAK,CAAC;KAC7C,MAAM,YAAY,KACf,QAAQ,EACR,WAAW;MACV,IAAI,QAAQ,QAAS,GAEnB,GADY,QAAQ,QAAU,IAAI,SAAS,WAEzC,YAAY,KAAKL,OAAO,iBAAiB,YAAY,IAAI,IAAI,KAAKD,QAAQ,QAAQ,CAAC,EAAE,OACrF,EAAC,WAAW,MAAM,OAAM,CAC1B;KAEJ,CAAC,EACA,OAAM,MAAK,KAAK,KAAK,CAAC,CAAC,EACvB,cAAc;MACb,KAAKM,SAAS,OAAO,KAAK;MAC1B,IAAI,QAAQ,KAAKC,kBAAkB;OACjC,KAAKA,mBAAmB;OACxB,KAAKC,kBAAkB,KAAK,IAAI;MAClC;KACF,CAAC;KAEH,MAAM,OAAO,QAAQ,KAAK,CAAC,WAAW,OAAO,CAAC;KAC9C,KAAK,YAAY,CAAC,CAAC;KAEnB,OAAO;IACT,GAAG,WACc,EAAK,QAAQ,EAAE,OAAO,EAAC;GAC1C;GACA,UAAU,EAAE;EACd;CACF;;;;;;;;;;;;;;CAeA,iCAA0C;EACxC,IAAI,KAAKF,SAAS,SAAS,GACzB;EAEF,MAAM,MAAM,KAAK,IAAI;EACrB,KAAK,MAAM,CAAC,OAAO,EAAC,OAAO,MAAM,YAAW,KAAKA,SAAS,QAAQ,GAAG;GACnE,MAAM,YAAY,MAAM;GACxB,MAAM,aAAa,QAAgB;IACjC,KAAKb,IAAI,OAAO,KAAK,EAAC,UAAS,CAAC;IAChC,MAAM,MAAM,IAAI,qBAAqB,GAAG;IACxC,MAAM,GAAG;IACT,KAAK,KAAK,GAAG;IACb,KAAKa,SAAS,OAAO,KAAK;GAC5B;GAEA,IAAI,QAAQ,KAAKC,kBAAkB;IACjC,MAAM,UAAU,MAAM,KAAKC;IAC3B,IAAI,KAAKjB,oBAAoB,UAAU,KAAKA,kBAC1C,UACE,aAAa,KAAKgB,iBAAiB,aAAa,QAAQ,yBAAyB,MAAM,UACzF;GAEJ,OAAO;IAEL,MAAM,UAAU,MAAM;IACtB,IAAI,KAAKhB,oBAAoB,UAAU,KAAKA,kBAC1C,UAAU,0CAA0C,QAAQ,IAAI;GAEpE;EACF;CACF;;;;;;;CAQA,gBAAmB,UAAmC;EACpD,MAAM,IAAI,SAAY;EACtB,KAAKc,SAAS,KAAKI,YAAY,UAAU,CAAC,CAAC;EAC3C,OAAO,EAAE;CACX;;;;;;;;;;CAWA,YAAe,UAAuB,GAA4B;EAChE,OAAO;GACL,KAAK,OAAO,IAAI,IAAI,eAAe;IACjC,IAAI;IACJ,IAAI;KACF,SAAS,MAAM,SAAS,IAAI,EAAE;KAC9B,WAAW;KACX,EAAE,QAAQ,MAAM;IAClB,SAAS,GAAG;KACV,WAAW;KACX,EAAE,OAAO,CAAC;IACZ;IACA,OAAO,EAAC,SAAS,KAAI;GACvB;GACA,UAAU,EAAE;EACd;CACF;CAEA,SAAS,QAA0B;EACjC,OAAO,CAAC,KAAKL,OAAO,kBAAkB;EACtC,IAAI,KAAKD,UAAU;GACjB,OAAO,SAAS,KAAKA,QAAQ;GAC7B;EACF;EAEA,KAAKhB,OAAO,QAAQ,MAAM;EAG1B,IAAI,KAAKQ,cAAc,KAAKL;OACN,KAAKH,OAAO,KAE5B,IAAc,KAAKQ,cAAc,KAAKO,aAAa;IACrD,KAAKJ,OAAO,KAAKC,WAAW,KAAKD,GAAG;IACpC,KAAKH;IACL,KAAKF,IAAI,QAAQ,0BAA0B,KAAKE,aAAa;GAC/D;;CAEJ;;;;;CAMA,QAAQ;EACN,KAAK,KAAK,IAAI,eAAe,CAAC;CAChC;;;;;CAMA,UAAU;EACR,OAAO,CAAC,KAAKS,OAAO,kBAAkB;EACtC,KAAKA,QAAQ;EAEb,KAAK,IAAI,IAAI,GAAG,IAAI,KAAKT,aAAa,KACpC,KAAKR,OAAO,QAAQ,MAAM;EAE5B,QAAa,WAAW,KAAKC,QAAQ,EAAE,WACrC,cAAc,KAAKQ,2BAA2B,CAChD;CACF;CAEA,YAAqB;EACnB,OAAO,KAAKE,QAAQ,KAAA,KAAa,CAAC,KAAKM,SAAS,KAAKD,aAAa,KAAA;CACpE;;;;CAKA,KAAK,KAAc;EACjB,IAAI,CAAC,KAAKC,SAAS,CAAC,KAAKD,UAAU;GACjC,KAAKA,WAAW,YAAY,GAAG;GAG/B,MAAM,QACJ,KAAKA,oBAAoB,mBAAmB,UAAU;GACxD,KAAKV,IAAI,SAAS,KAAKU,QAAQ;GAE/B,KAAK,IAAI,IAAI,GAAG,IAAI,KAAKR,aAAa,KAEpC,KAAKR,OAAO,QAAQ,KAAKgB,QAAQ;GAEnC,QAAa,WAAW,KAAKf,QAAQ,EAAE,WACrC,cAAc,KAAKQ,2BAA2B,CAChD;EACF;CACF;AACF;;;;AA4JA,SAAgB,eAAe,YAG7B;CACA,MAAM,EAAC,SAAS,UAAU,SAAS,WAAU,SAAe;CAE5D,OAAO;EACL,OAAM,OAAM;GACV,MAAM,OAAO,GAAG,OAAO,6BAA6B,WAAW,EAAE;GACjE,KAAK,WAAW,QAAQ,GAAG,MAAM;GACjC,OAAO,CAAC,IAAI;EACd;EAEA;CACF;AACF;;;;;;;AAQA,IAAa,mBAAb,cAAsC,MAAM;CAC1C,YAAY,OAAiB;EAC3B,MAAM;EACN,KAAK,QAAQ;CACf;AACF;;;;;;;;;;;AAYA,IAAM,iBAAN,cAA6B,iBAAiB;CAC5C,OAAgB;CAChB,UAAmB;AACrB;AAEA,SAAS,YAAY,KAAqB;CACxC,IAAI,eAAe,OACjB,OAAO;CAET,MAAM,wBAAQ,IAAI,MAAM;CACxB,MAAM,QAAQ;CACd,OAAO;AACT;AAqCA,IAAM,kBAAkB;AAUxB,IAAM,uBAAuB,SAC3B,QAAQ,IAAI,sCAAsC,MACpD;AAEA,IAAM,kBAAwB,IAAI,OAAO;CACvC,GAAG,QAAQ,sBAAsB;CACjC,OAAO,CAAC,EAAE,WAAW,OAAO,CAAC;AAC/B;AAaA,IAAa,gBAA8B;CACzC,mBAAmB;EACjB,WAAW;EACX,MAAM;CACR;CACA,iBAAiB;EACf,WAAW;EACX,MAAM;CACR;AACF;AAMA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,OAAgB,OAAO;CAEvB,YAAY,KAAa;EACvB,MAAM,GAAG;CACX;AACF"}
1
+ {"version":3,"file":"transaction-pool.js","names":["#mode","#init","#cleanup","#tasks","#workers","#initialWorkers","#maxWorkers","#responseTimeout","#timeoutTask","#lc","#stmtRunner","#numWorkers","#orphanedQueryCheckInterval","#checkForOrphanedQueries","#db","#addWorker","#start","#stmts","#numWorking","#failure","#done","#process","#pending","#latestDoneIndex","#latestDoneTime","#readRunner"],"sources":["../../../../../zero-cache/src/db/transaction-pool.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {type Resolver, resolver} from '@rocicorp/resolver';\nimport type postgres from 'postgres';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {Enum} from '../../../shared/src/enum.ts';\nimport {Queue} from '../../../shared/src/queue.ts';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {type PostgresDB, type PostgresTransaction} from '../types/pg.ts';\nimport type * as Mode from './mode-enum.ts';\nimport {runTx} from './run-transaction.ts';\n\ntype Mode = Enum<typeof Mode>;\n\ntype MaybePromise<T> = Promise<T> | T;\n\nexport type Statement =\n | postgres.PendingQuery<(postgres.Row & Iterable<postgres.Row>)[]>\n | postgres.PendingQuery<postgres.Row[]>;\n\n/**\n * A {@link Task} is logic run from within a transaction in a {@link TransactionPool}.\n * It returns a list of `Statements` that the transaction executes asynchronously and\n * awaits when it receives the 'done' signal.\n *\n */\nexport type Task = (\n tx: PostgresTransaction,\n lc: LogContext,\n) => MaybePromise<Statement[]>;\n\n/**\n * A {@link ReadTask} is run from within a transaction, but unlike a {@link Task},\n * the results of a ReadTask are opaque to the TransactionPool and returned to the\n * caller of {@link TransactionPool.processReadTask}.\n */\nexport type ReadTask<T> = (\n tx: PostgresTransaction,\n lc: LogContext,\n) => MaybePromise<T>;\n\nexport type Options = {\n mode: Mode;\n\n /**\n * A {@link Task} that is run in each Transaction before it begins\n * processing general tasks. This can be used to to set the transaction\n * mode, export/set snapshots, etc. This will be run even if\n * {@link fail} has been called on the pool.\n */\n init?: Task | undefined;\n\n /**\n * A {@link Task} that is run in each Transaction before it closes.\n * This will be run even if {@link fail} has been called, or if a\n * preceding Task threw an Error.\n */\n cleanup?: Task | undefined;\n\n /**\n * The initial number of transaction workers to process tasks.\n * This is the steady state number of workers that will be kept\n * alive if the TransactionPool is long lived.\n * This must be greater than 0. Defaults to 1.\n */\n initialWorkers?: number;\n\n /**\n * When specified, allows the pool to grow to `maxWorkers`. This\n * must be greater than or equal to `initialWorkers`. On-demand\n * workers will be shut down after an idle timeout of 5 seconds.\n */\n maxWorkers?: number;\n\n /**\n * Aborts the transaction if the response for a statement is not received\n * within the specified timeout. Note that this is different from a Postgres\n * `statement_timeout` or `lock_timeout` in that it is specifically intended\n * to detect situations in which Postgres either never received the\n * statement, or the application never got the response for the executed\n * statement.\n */\n statementResponseTimeout?: number;\n};\n\n/**\n * A TransactionPool is a pool of one or more {@link postgres.TransactionSql}\n * objects that participate in processing a dynamic queue of tasks.\n *\n * This can be used for serializing a set of tasks that arrive asynchronously\n * to a single transaction (for writing) or performing parallel reads across\n * multiple connections at the same snapshot (e.g. read only snapshot transactions).\n */\nexport class TransactionPool {\n #lc: LogContext;\n readonly #mode: Mode;\n readonly #init: TaskRunner | undefined;\n readonly #cleanup: TaskRunner | undefined;\n readonly #tasks = new Queue<TaskRunner | Error | 'done'>();\n readonly #workers: Promise<unknown>[] = [];\n readonly #initialWorkers: number;\n readonly #maxWorkers: number;\n readonly #responseTimeout: number | undefined;\n readonly #timeoutTask: TimeoutTasks;\n #numWorkers: number;\n #numWorking = 0;\n #db: PostgresDB | undefined; // set when running. stored to allow adaptive pool sizing.\n\n #done = false;\n #failure: Error | undefined;\n #orphanedQueryCheckInterval: NodeJS.Timeout;\n\n constructor(\n lc: LogContext,\n opts: Options,\n timeoutTasks = TIMEOUT_TASKS, // Overridden for tests.\n ) {\n const {\n mode,\n init,\n cleanup,\n initialWorkers = 1,\n maxWorkers = initialWorkers,\n statementResponseTimeout,\n } = opts;\n assert(initialWorkers > 0, 'initialWorkers must be positive');\n assert(\n maxWorkers >= initialWorkers,\n 'maxWorkers must be >= initialWorkers',\n );\n\n this.#lc = lc;\n this.#mode = mode;\n this.#init = init ? this.#stmtRunner(init) : undefined;\n this.#cleanup = cleanup ? this.#stmtRunner(cleanup) : undefined;\n this.#initialWorkers = initialWorkers;\n this.#numWorkers = initialWorkers;\n this.#maxWorkers = maxWorkers;\n this.#timeoutTask = timeoutTasks;\n this.#responseTimeout = statementResponseTimeout;\n\n this.#orphanedQueryCheckInterval = setInterval(\n this.#checkForOrphanedQueries,\n Math.min(5_000, statementResponseTimeout ?? 5_000),\n );\n }\n\n /**\n * Starts the pool of workers to process Tasks with transactions opened from the\n * specified {@link db}.\n */\n run(db: PostgresDB): this {\n assert(!this.#db, 'already running');\n this.#db = db;\n for (let i = 0; i < this.#numWorkers; i++) {\n this.#addWorker(db);\n }\n return this;\n }\n\n /**\n * Adds context parameters to internal LogContext. This is useful for context values that\n * are not known when the TransactionPool is constructed (e.g. determined after a database\n * call when the pool is running).\n *\n * Returns an object that can be used to add more parameters.\n */\n addLoggingContext(key: string, value: string) {\n this.#lc = this.#lc.withContext(key, value);\n\n return {\n addLoggingContext: (key: string, value: string) =>\n this.addLoggingContext(key, value),\n };\n }\n\n /**\n * Returns a promise that:\n *\n * * resolves after {@link setDone} has been called (or the the pool as been {@link unref}ed\n * to a 0 ref count), once all added tasks have been processed and all transactions have been\n * committed or closed.\n *\n * * rejects if processing was aborted with {@link fail} or if processing any of\n * the tasks resulted in an error. All uncommitted transactions will have been\n * rolled back.\n *\n * Note that partial failures are possible if processing writes with multiple workers\n * (e.g. `setDone` is called, allowing some workers to commit, after which other\n * workers encounter errors). Using a TransactionPool in this manner does not make\n * sense in terms of transactional semantics, and is thus not recommended.\n *\n * For reads, however, multiple workers is useful for performing parallel reads\n * at the same snapshot. See {@link synchronizedSnapshots} for an example.\n * Resolves or rejects when all workers are done or failed.\n */\n async done() {\n const numWorkers = this.#workers.length;\n await Promise.all(this.#workers);\n\n if (numWorkers < this.#workers.length) {\n // If workers were added after the initial set, they must be awaited to ensure\n // that the results (i.e. rejections) of all workers are accounted for. This only\n // needs to be re-done once, because the fact that the first `await` completed\n // guarantees that the pool is in a terminal state and no new workers can be added.\n await Promise.all(this.#workers);\n }\n this.#lc.debug?.('transaction pool done');\n\n const elapsed = performance.now() - this.#start;\n if (elapsed > 60_000) {\n if (this.#stmts > 0) {\n this.#lc.warn?.(\n `finished long transaction with ${this.#stmts} statements (${elapsed.toFixed(3)} ms)`,\n );\n } else {\n this.#lc.warn?.(\n `finished long read transaction (${elapsed.toFixed(3)} ms)`,\n );\n }\n }\n }\n\n #addWorker(db: PostgresDB) {\n const id = this.#workers.length + 1;\n const lc = this.#lc.withContext('tx', id);\n\n const tt: TimeoutTask =\n this.#workers.length < this.#initialWorkers\n ? this.#timeoutTask.forInitialWorkers\n : this.#timeoutTask.forExtraWorkers;\n const {timeoutMs} = tt;\n const timeoutTask = tt.task === 'done' ? 'done' : this.#stmtRunner(tt.task);\n\n const worker = async (tx: PostgresTransaction) => {\n const start = performance.now();\n try {\n lc.debug?.('started transaction');\n\n let last: Promise<void> = promiseVoid;\n\n const executeTask = async (runner: TaskRunner) => {\n runner !== this.#init && this.#numWorking++;\n const {pending} = await runner.run(tx, lc, () => {\n runner !== this.#init && this.#numWorking--;\n });\n last = pending ?? last;\n };\n\n let task: TaskRunner | Error | 'done' =\n this.#init ?? (await this.#tasks.dequeue(timeoutTask, timeoutMs));\n\n try {\n while (task !== 'done') {\n if (\n task instanceof Error ||\n (task !== this.#init && this.#failure)\n ) {\n throw this.#failure ?? task;\n }\n await executeTask(task);\n\n // await the next task.\n task = await this.#tasks.dequeue(timeoutTask, timeoutMs);\n }\n } finally {\n // Execute the cleanup task even on failure.\n if (this.#cleanup) {\n await executeTask(this.#cleanup);\n }\n }\n\n const elapsed = performance.now() - start;\n lc.debug?.(`closing transaction (${elapsed.toFixed(3)} ms)`);\n // Given the semantics of a Postgres transaction, the last statement\n // will only succeed if all of the preceding statements succeeded.\n return last;\n } catch (e) {\n if (e !== this.#failure) {\n this.fail(e); // A failure in any worker should fail the pool.\n }\n throw e;\n }\n };\n\n const workerTx = runTx(db, worker, {mode: this.#mode})\n .catch(e => {\n if (e instanceof RollbackSignal) {\n // A RollbackSignal is used to gracefully rollback the postgres.js\n // transaction block. It should not be thrown up to the application.\n lc.debug?.('aborted transaction');\n } else {\n throw e;\n }\n })\n .finally(() => this.#numWorkers--);\n\n // Attach a rejection handler immediately to prevent unhandledRejections.\n // The application will handle errors when it awaits processReadTask()\n // or done().\n workerTx.catch(() => {});\n\n this.#workers.push(workerTx);\n\n // After adding the worker, enqueue a terminal signal if we are in either of the\n // terminal states (both of which prevent more tasks from being enqueued), to ensure\n // that the added worker eventually exits.\n if (this.#done) {\n this.#tasks.enqueue('done');\n }\n if (this.#failure) {\n this.#tasks.enqueue(this.#failure);\n }\n }\n\n /**\n * Processes the statements produced by the specified {@link Task},\n * returning a Promise that resolves when the statements are either processed\n * by the database or rejected.\n *\n * Note that statement failures will result in failing the entire\n * TransactionPool (per transaction semantics). However, the returned Promise\n * itself will resolve rather than reject. As such, it is fine to ignore\n * returned Promises in order to pipeline requests to the database. It is\n * recommended to occasionally await them (e.g. after some threshold) in\n * order to avoid memory blowup in the case of database slowness.\n */\n process(task: Task): Promise<void> {\n const r = resolver<void>();\n this.#process(this.#stmtRunner(task, r));\n return r.promise;\n }\n\n readonly #start = performance.now();\n #stmts = 0;\n #latestDoneIndex = 0;\n #latestDoneTime = 0;\n\n readonly #pending = new Map<\n Query,\n {index: number; time: number; abort: (e: ResponseTimeoutError) => void}\n >();\n\n /**\n * Implements the semantics specified in {@link process()}.\n *\n * Specifically:\n * * `freeWorker()` is called as soon as the statements are produced,\n * allowing them to be pipelined to the database.\n * * Statement errors result in failing the transaction pool.\n * * The client-supplied Resolver resolves on success or failure;\n * it is never rejected.\n */\n #stmtRunner(task: Task, r: {resolve: () => void} = resolver()): TaskRunner {\n return {\n run: async (tx, lc, freeWorker) => {\n let stmts: Statement[];\n try {\n stmts = await task(tx, lc);\n } catch (e) {\n r.resolve();\n throw e;\n } finally {\n freeWorker();\n }\n\n if (stmts.length === 0) {\n r.resolve();\n return {pending: null};\n }\n\n // Execute the statements (i.e. send to the db) immediately.\n // The last result is returned for the worker to await before\n // closing the transaction.\n const time = Date.now();\n const last = stmts.reduce((_, stmt) => {\n const query = stmt as unknown as Query;\n const index = ++this.#stmts;\n const {promise: aborted, reject: abort} = resolver();\n aborted.catch(() => {}); // always consider it \"handled\"\n\n this.#pending.set(query, {index, time, abort});\n const responded = stmt\n .execute()\n .then(() => {\n if (index % 1000 === 0) {\n const log = index % 10000 === 0 ? 'info' : 'debug';\n lc[log]?.(\n `executed ${this.#stmts}th statement (${(performance.now() - this.#start).toFixed(3)} ms)`,\n {statement: query.string},\n );\n }\n })\n .catch(e => this.fail(e))\n .finally(() => {\n this.#pending.delete(query);\n if (index > this.#latestDoneIndex) {\n this.#latestDoneIndex = index;\n this.#latestDoneTime = Date.now();\n }\n });\n\n const done = Promise.race([responded, aborted]);\n done.catch(() => {});\n\n return done;\n }, promiseVoid);\n return {pending: last.finally(r.resolve)};\n },\n rejected: r.resolve,\n };\n }\n\n /**\n * Periodically checks the map of `#pending` queries for which Promises\n * have yet to be resolved. Checks for pathological scenarios that have\n * been observed:\n *\n * * A promise does not resolve, even though the transaction is otherwise\n * healthy with keepalives being sent.\n * * A transaction never \"finishes\" at the postgres.js level after the\n * worker exits, implying that postgres.js is awaiting the `last` promise\n * returned. In this case Postgres disconnected the application with an\n * idle-in-transaction timeout, suggesting that all statements did in\n * fact complete.\n */\n readonly #checkForOrphanedQueries = () => {\n if (this.#pending.size === 0) {\n return;\n }\n const now = Date.now();\n for (const [query, {index, time, abort}] of this.#pending.entries()) {\n const statement = query.string;\n const abortWith = (msg: string) => {\n this.#lc.warn?.(msg, {statement});\n const err = new ResponseTimeoutError(msg);\n abort(err);\n this.fail(err);\n this.#pending.delete(query);\n };\n\n if (index < this.#latestDoneIndex) {\n const elapsed = now - this.#latestDoneTime;\n if (this.#responseTimeout && elapsed > this.#responseTimeout) {\n abortWith(\n `statement ${this.#latestDoneIndex} completed ${elapsed} ms ago, but statement ${index} has not.`,\n );\n }\n } else {\n // Nothing out of order, but checking for a response timeout.\n const elapsed = now - time;\n if (this.#responseTimeout && elapsed > this.#responseTimeout) {\n abortWith(`response for statement timed out after ${elapsed} ms`);\n }\n }\n }\n };\n\n /**\n * Processes and returns the result of executing the {@link ReadTask} from\n * within the transaction. An error thrown by the task will result in\n * rejecting the returned Promise, but will not affect the transaction pool\n * itself.\n */\n processReadTask<T>(readTask: ReadTask<T>): Promise<T> {\n const r = resolver<T>();\n this.#process(this.#readRunner(readTask, r));\n return r.promise;\n }\n\n /**\n * Implements the semantics specified in {@link processReadTask()}.\n *\n * Specifically:\n * * `freeWorker()` is called as soon as the result is produced,\n * before resolving the client-supplied Resolver.\n * * Errors result in rejecting the client-supplied Resolver but\n * do not affect transaction pool.\n */\n #readRunner<T>(readTask: ReadTask<T>, r: Resolver<T>): TaskRunner {\n return {\n run: async (tx, lc, freeWorker) => {\n let result: T;\n try {\n result = await readTask(tx, lc);\n freeWorker();\n r.resolve(result);\n } catch (e) {\n freeWorker();\n r.reject(e);\n }\n return {pending: null};\n },\n rejected: r.reject,\n };\n }\n\n #process(runner: TaskRunner): void {\n assert(!this.#done, 'already set done');\n if (this.#failure) {\n runner.rejected(this.#failure);\n return;\n }\n\n this.#tasks.enqueue(runner);\n\n // Check if the pool size can and should be increased.\n if (this.#numWorkers < this.#maxWorkers) {\n const outstanding = this.#tasks.size();\n\n if (outstanding > this.#numWorkers - this.#numWorking) {\n this.#db && this.#addWorker(this.#db);\n this.#numWorkers++;\n this.#lc.debug?.(`Increased pool size to ${this.#numWorkers}`);\n }\n }\n }\n\n /**\n * Ends all workers with a ROLLBACK. Throws if the pool is already done\n * or aborted.\n */\n abort() {\n this.fail(new RollbackSignal());\n }\n\n /**\n * Signals to all workers to end their transaction once all pending tasks have\n * been completed. Throws if the pool is already done or aborted.\n */\n setDone() {\n assert(!this.#done, 'already set done');\n this.#done = true;\n\n for (let i = 0; i < this.#numWorkers; i++) {\n this.#tasks.enqueue('done');\n }\n void Promise.allSettled(this.#workers).then(() =>\n clearInterval(this.#orphanedQueryCheckInterval),\n );\n }\n\n isRunning(): boolean {\n return this.#db !== undefined && !this.#done && this.#failure === undefined;\n }\n\n /**\n * Signals all workers to fail their transactions with the given {@link err}.\n */\n fail(err: unknown) {\n if (!this.#done && !this.#failure) {\n this.#failure = ensureError(err); // Fail fast: this is checked in the worker loop.\n // Logged for informational purposes. It is the responsibility of\n // higher level logic to classify and handle the exception.\n const level =\n this.#failure instanceof ControlFlowError ? 'debug' : 'info';\n this.#lc[level]?.(this.#failure);\n\n for (let i = 0; i < this.#numWorkers; i++) {\n // Enqueue the Error to terminate any workers waiting for tasks.\n this.#tasks.enqueue(this.#failure);\n }\n void Promise.allSettled(this.#workers).then(() =>\n clearInterval(this.#orphanedQueryCheckInterval),\n );\n }\n }\n}\n\ntype SynchronizeSnapshotTasks = {\n /**\n * The `init` Task for the TransactionPool from which the snapshot originates.\n * The pool must have Mode.SERIALIZABLE, and will be set to READ ONLY by the\n * `exportSnapshot` init task. If the TransactionPool has multiple workers, the\n * first worker will export a snapshot that the others set.\n */\n exportSnapshot: Task;\n\n /**\n * The `cleanup` Task for the TransactionPool from which the snapshot\n * originates. This Task will wait for the follower pool to `setSnapshot`\n * to ensure that the snapshot is successfully shared before the originating\n * transaction is closed.\n */\n cleanupExport: Task;\n\n /**\n * The `init` Task for the TransactionPool in which workers will\n * consequently see the same snapshot as that of the first pool. The pool\n * must have Mode.SERIALIZABLE, and will have the ability to perform writes.\n */\n setSnapshot: Task;\n\n /** The ID of the shared snapshot. */\n snapshotID: Promise<string>;\n};\n\n/**\n * Init Tasks for Postgres snapshot synchronization across transactions.\n *\n * https://www.postgresql.org/docs/9.3/functions-admin.html#:~:text=Snapshot%20Synchronization%20Functions,identical%20content%20in%20the%20database.\n */\nexport function synchronizedSnapshots(): SynchronizeSnapshotTasks {\n const {\n promise: snapshotExported,\n resolve: exportSnapshot,\n reject: failExport,\n } = resolver<string>();\n\n const {\n promise: snapshotCaptured,\n resolve: captureSnapshot,\n reject: failCapture,\n } = resolver<unknown>();\n\n // Set by the first worker to run its initTask, who becomes responsible for\n // exporting the snapshot. TODO: Plumb the workerNum and use that instead.\n let firstWorkerRun = false;\n\n // Note: Neither init task should `await`, as processing in each pool can proceed\n // as soon as the statements have been sent to the db. However, the `cleanupExport`\n // task must `await` the result of `setSnapshot` to ensure that exporting transaction\n // does not close before the snapshot has been captured.\n return {\n exportSnapshot: tx => {\n if (!firstWorkerRun) {\n firstWorkerRun = true;\n const stmt =\n tx`SELECT pg_export_snapshot() AS snapshot; SET TRANSACTION READ ONLY;`.simple();\n // Intercept the promise to propagate the information to `snapshotExported`.\n stmt.then(result => exportSnapshot(result[0].snapshot), failExport);\n return [stmt]; // Also return the stmt so that it gets awaited (and errors handled).\n }\n return snapshotExported.then(snapshotID => [\n tx.unsafe(`SET TRANSACTION SNAPSHOT '${snapshotID}'`),\n tx`SET TRANSACTION READ ONLY`.simple(),\n ]);\n },\n\n setSnapshot: tx =>\n snapshotExported.then(snapshotID => {\n const stmt = tx.unsafe(`SET TRANSACTION SNAPSHOT '${snapshotID}'`);\n // Intercept the promise to propagate the information to `cleanupExport`.\n stmt.then(captureSnapshot, failCapture);\n return [stmt];\n }),\n\n cleanupExport: async () => {\n await snapshotCaptured;\n return [];\n },\n\n snapshotID: snapshotExported,\n };\n}\n\n/**\n * Returns `init` and `cleanup` {@link Task}s for a TransactionPool that ensure its workers\n * share a single view of the database. This is used for View Notifier and View Syncer logic\n * that allows multiple entities to perform parallel reads on the same snapshot of the database.\n */\nexport function sharedSnapshot(): {\n init: Task;\n cleanup: Task;\n snapshotID: Promise<string>;\n} {\n const {\n promise: snapshotExported,\n resolve: exportSnapshot,\n reject: failExport,\n } = resolver<string>();\n\n // Set by the first worker to run its initTask, who becomes responsible for\n // exporting the snapshot.\n let firstWorkerRun = false;\n\n // The LogContext of the exporting worker, used to identify its cleanup call.\n // Each worker receives a unique lc instance (via withContext('tx', id)), so\n // reference equality reliably identifies the exporting worker.\n let exporterLc: LogContext | undefined;\n\n // Set when the exporting worker's cleanup runs, signalling that the snapshot\n // is no longer needed and any subsequently spawned workers should skip their\n // initTask.\n let firstWorkerDone = false;\n\n return {\n init: (tx, lc) => {\n if (!firstWorkerRun) {\n firstWorkerRun = true;\n exporterLc = lc; // Remember which worker is the exporter.\n const stmt = tx`SELECT pg_export_snapshot() AS snapshot;`.simple();\n // Intercept the promise to propagate the information to `snapshotExported`.\n stmt.then(result => exportSnapshot(result[0].snapshot), failExport);\n return [stmt]; // Also return the stmt so that it gets awaited (and errors handled).\n }\n if (!firstWorkerDone) {\n return snapshotExported.then(snapshotID => [\n tx.unsafe(`SET TRANSACTION SNAPSHOT '${snapshotID}'`),\n ]);\n }\n lc.debug?.('All work is done. No need to set snapshot');\n return [];\n },\n\n cleanup: (_tx, lc) => {\n // Only the exporting worker's cleanup should disable snapshot-setting.\n // Non-exporter workers may finish early; letting them flip this flag\n // would cause subsequently spawned workers to skip SET TRANSACTION SNAPSHOT\n // and read a newer database view, violating snapshot isolation.\n if (lc === exporterLc) {\n firstWorkerDone = true;\n }\n return [];\n },\n\n snapshotID: snapshotExported,\n };\n}\n\n/**\n * @returns An `init` Task for importing a snapshot from another transaction,\n * as well as an `imported` Queue which resolves or rejects when each\n * worker is initialized.\n */\nexport function importSnapshot(snapshotID: string): {\n init: Task;\n imported: Queue<void>;\n} {\n const imported = new Queue<void>();\n\n return {\n init: tx => {\n const stmt = tx.unsafe(`SET TRANSACTION SNAPSHOT '${snapshotID}'`);\n stmt.then(\n () => imported.enqueue(),\n err => imported.enqueueRejection(err),\n );\n return [stmt];\n },\n\n imported,\n };\n}\n\n/**\n * A superclass of Errors used for control flow that is needed to handle\n * another Error but does not constitute an error condition itself (e.g.\n * aborting transactions after a previous one fails). Subclassing this Error\n * will result in lowering the log level from `error` to `debug`.\n */\nexport class ControlFlowError extends Error {\n constructor(cause?: unknown) {\n super();\n this.cause = cause;\n }\n}\n\n/**\n * Internal error used to rollback the worker transaction. This is used\n * instead of executing a `ROLLBACK` statement because the postgres.js\n * library will otherwise try to execute an extraneous `COMMIT`, which\n * results in outputting a \"no transaction in progress\" warning to the\n * database logs.\n *\n * Throwing an exception, on the other hand, executes the postgres.js\n * codepath that calls `ROLLBACK` instead.\n */\nclass RollbackSignal extends ControlFlowError {\n readonly name = 'RollbackSignal';\n readonly message = 'rolling back transaction';\n}\n\nfunction ensureError(err: unknown): Error {\n if (err instanceof Error) {\n return err;\n }\n const error = new Error();\n error.cause = err;\n return error;\n}\n\ninterface TaskRunner {\n /**\n * Manages the running of a Task or ReadTask in two phases:\n *\n * - If the task involves blocking, this is done in the worker. Once the\n * blocking is done, `freeWorker()` is invoked to signal that the worker\n * is available to run another task. Note that this should be invoked\n * *before* resolving the result to the calling thread so that a\n * subsequent task can reuse the same worker.\n *\n * - Task statements are executed on the database asynchronously. The final\n * result of this processing is encapsulated in the returned `pending`\n * Promise. The worker will await the last pending Promise before closing\n * the transaction.\n *\n * @param freeWorker should be called as soon as all blocking operations are\n * completed in order to return the transaction to the pool.\n * @returns A `pending` Promise indicating when the statements have been\n * processed by the database, allowing the transaction to be closed.\n * This should be `null` if there are no transaction-dependent\n * statements to await.\n */\n run(\n tx: PostgresTransaction,\n lc: LogContext,\n freeWorker: () => void,\n ): Promise<{pending: Promise<void> | null}>;\n\n /**\n * Invoked if the TransactionPool is already in a failed state when the task\n * is requested.\n */\n rejected(reason: unknown): void;\n}\n\nconst IDLE_TIMEOUT_MS = 5_000;\n\n// The keepalive interval is settable by ZERO_TRANSACTION_POOL_KEEPALIVE_MS\n// as an emergency measure and is explicitly not made available as a server\n// option. This value is function of how the zero-cache uses transactions, and\n// should never need to be \"tuned\" or adjusted for different environments.\n//\n// Note that it must be shorter than IDLE_IN_TRANSACTION_SESSION_TIMEOUT_MS\n// with sufficient buffering to account for when the process is blocked by\n// synchronous calls (e.g. to the replica).\nconst KEEPALIVE_TIMEOUT_MS = parseInt(\n process.env.ZERO_TRANSACTION_POOL_KEEPALIVE_MS ?? '5000',\n);\n\nconst KEEPALIVE_TASK: Task = (tx, lc) => {\n lc.debug?.(`sending tx keepalive`);\n return [tx`SELECT 1`.simple()];\n};\n\ntype TimeoutTask = {\n timeoutMs: number;\n task: Task | 'done';\n};\n\ntype TimeoutTasks = {\n forInitialWorkers: TimeoutTask;\n forExtraWorkers: TimeoutTask;\n};\n\n// Production timeout tasks. Overridden in tests.\nexport const TIMEOUT_TASKS: TimeoutTasks = {\n forInitialWorkers: {\n timeoutMs: KEEPALIVE_TIMEOUT_MS,\n task: KEEPALIVE_TASK,\n },\n forExtraWorkers: {\n timeoutMs: IDLE_TIMEOUT_MS,\n task: 'done',\n },\n};\n\n// The slice of information from the Query object in Postgres.js that gets logged for debugging.\n// https://github.com/porsager/postgres/blob/f58cd4f3affd3e8ce8f53e42799672d86cd2c70b/src/connection.js#L219\ntype Query = {string: string; parameters: object[]};\n\nexport class ResponseTimeoutError extends Error {\n static readonly name = 'ResponseTimeoutError';\n\n constructor(msg: string) {\n super(msg);\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"change-streamer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"names":[],"mappings":"AAqCA,OAAO,EAIL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAU/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CA6Tf"}
1
+ {"version":3,"file":"change-streamer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"names":[],"mappings":"AAsCA,OAAO,EAIL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAU/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CA4Uf"}