pipework 0.252.0 → 0.252.1

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 (690) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/package.json +4 -3
  3. package/src/async/jobs/abortable-delay.ts +18 -0
  4. package/src/async/jobs/claims.ts +52 -0
  5. package/src/async/jobs/cron.ts +179 -0
  6. package/src/async/jobs/errors.ts +56 -0
  7. package/src/async/jobs/exclusion.ts +117 -0
  8. package/src/async/jobs/execute.ts +219 -0
  9. package/src/async/jobs/index.ts +10 -0
  10. package/src/async/jobs/listener.ts +135 -0
  11. package/src/async/jobs/namespace.ts +20 -0
  12. package/src/async/jobs/queue-door.ts +44 -0
  13. package/src/async/jobs/queue.ts +1012 -0
  14. package/src/async/jobs/run-script.ts +67 -0
  15. package/src/async/jobs/scheduler.ts +218 -0
  16. package/src/async/jobs/schedules-table.ts +37 -0
  17. package/src/async/jobs/serialize-error.ts +48 -0
  18. package/src/async/jobs/table.ts +191 -0
  19. package/src/async/pipeline/define.ts +53 -0
  20. package/src/async/pipeline/errors.ts +35 -0
  21. package/src/async/pipeline/execute.ts +53 -0
  22. package/src/async/pipeline/index.ts +13 -0
  23. package/src/async/pipeline/namespace.ts +12 -0
  24. package/src/async/pipeline/resume.ts +67 -0
  25. package/src/async/pipeline/run-steps.ts +98 -0
  26. package/src/async/pipeline/types.ts +57 -0
  27. package/src/async/state-machine/define.ts +64 -0
  28. package/src/async/state-machine/errors.ts +29 -0
  29. package/src/async/state-machine/index.ts +3 -0
  30. package/src/async/state-machine/namespace.ts +12 -0
  31. package/src/async/state-machine/transition.ts +90 -0
  32. package/src/async/state-machine/types.ts +37 -0
  33. package/src/async/steps/errors.ts +31 -0
  34. package/src/async/steps/graph.ts +1214 -0
  35. package/src/async/steps/hash.ts +40 -0
  36. package/src/async/steps/index.ts +7 -0
  37. package/src/async/steps/namespace.ts +7 -0
  38. package/src/async/steps/table.ts +97 -0
  39. package/src/async/steps/types.ts +318 -0
  40. package/src/audit/chain/index.ts +9 -0
  41. package/src/audit/chain/install.ts +113 -0
  42. package/src/audit/chain/retire.ts +52 -0
  43. package/src/audit/chain/verify.ts +257 -0
  44. package/src/audit/query/cursor.ts +37 -0
  45. package/src/audit/query/index.ts +9 -0
  46. package/src/audit/query/query.ts +223 -0
  47. package/src/audit/schema/audit-record-fields.ts +68 -0
  48. package/src/audit/schema/audit-record.ts +23 -0
  49. package/src/auth/auth/audit.ts +31 -0
  50. package/src/auth/auth/chain.ts +84 -0
  51. package/src/auth/auth/cookie.ts +136 -0
  52. package/src/auth/auth/email-links.ts +311 -0
  53. package/src/auth/auth/guard.ts +128 -0
  54. package/src/auth/auth/index.ts +18 -0
  55. package/src/auth/auth/multi-org.ts +337 -0
  56. package/src/auth/auth/namespace.ts +34 -0
  57. package/src/auth/auth/parse-expiry.ts +13 -0
  58. package/src/auth/auth/passwords.ts +110 -0
  59. package/src/auth/auth/schema.ts +110 -0
  60. package/src/auth/auth/sessions.ts +565 -0
  61. package/src/auth/auth/sign-in.ts +156 -0
  62. package/src/auth/auth/signing-keys.ts +69 -0
  63. package/src/auth/auth/types.ts +26 -0
  64. package/src/auth/auth/uuid.ts +10 -0
  65. package/src/auth/rbac/cache.ts +55 -0
  66. package/src/auth/rbac/check.ts +30 -0
  67. package/src/auth/rbac/create.ts +35 -0
  68. package/src/auth/rbac/index.ts +5 -0
  69. package/src/auth/rbac/middleware.ts +20 -0
  70. package/src/auth/rbac/namespace.ts +15 -0
  71. package/src/auth/rbac/resolve.ts +31 -0
  72. package/src/auth/rbac/schema.sql +31 -0
  73. package/src/auth/rbac/seed.ts +67 -0
  74. package/src/auth/rbac/tables.ts +32 -0
  75. package/src/auth/rbac/types.ts +65 -0
  76. package/src/auth/tenant/extract.ts +33 -0
  77. package/src/auth/tenant/in-tenant.ts +49 -0
  78. package/src/auth/tenant/index.ts +7 -0
  79. package/src/auth/tenant/namespace.ts +23 -0
  80. package/src/auth/tenant/propagate.ts +20 -0
  81. package/src/auth/tenant/rls.ts +120 -0
  82. package/src/auth/tenant/scope.ts +33 -0
  83. package/src/auth/tenant/scoped-db.ts +581 -0
  84. package/src/auth/tenant/types.ts +14 -0
  85. package/src/auth/tenant/validate.ts +27 -0
  86. package/src/cli/commands/add.ts +158 -0
  87. package/src/cli/commands/alerts.ts +98 -0
  88. package/src/cli/commands/check.ts +1418 -0
  89. package/src/cli/commands/codemod.ts +78 -0
  90. package/src/cli/commands/db.ts +356 -0
  91. package/src/cli/commands/dev.ts +101 -0
  92. package/src/cli/commands/generate.ts +75 -0
  93. package/src/cli/commands/init.ts +36 -0
  94. package/src/cli/commands/install.ts +28 -0
  95. package/src/cli/commands/lint.ts +62 -0
  96. package/src/cli/commands/migrate.ts +132 -0
  97. package/src/cli/commands/openapi.ts +63 -0
  98. package/src/cli/commands/remove.ts +72 -0
  99. package/src/cli/commands/run.ts +50 -0
  100. package/src/cli/commands/scope.ts +54 -0
  101. package/src/cli/commands/serve.ts +68 -0
  102. package/src/cli/commands/shard.ts +31 -0
  103. package/src/cli/commands/shared/config-pipework-version.ts +28 -0
  104. package/src/cli/commands/shared/find-config.ts +22 -0
  105. package/src/cli/commands/shared/load-config.ts +35 -0
  106. package/src/cli/commands/shared/pnpm-install.ts +95 -0
  107. package/src/cli/commands/shared/report-removed.ts +8 -0
  108. package/src/cli/commands/sync.ts +32 -0
  109. package/src/cli/commands/test.ts +307 -0
  110. package/src/cli/commands/upgrade.ts +48 -0
  111. package/src/cli/dev-bootstrap.ts +24 -0
  112. package/src/cli/errors.ts +10 -0
  113. package/src/cli/index.ts +303 -0
  114. package/src/cli/output.ts +17 -0
  115. package/src/cli/port.ts +15 -0
  116. package/src/cli/surface-kinds.ts +20 -0
  117. package/src/client/index.ts +4 -0
  118. package/src/client/poll.ts +130 -0
  119. package/src/client/report-fault.ts +119 -0
  120. package/src/core/config/app-pool.ts +19 -0
  121. package/src/core/config/aws-env.ts +29 -0
  122. package/src/core/config/base-dir.ts +24 -0
  123. package/src/core/config/discover.ts +64 -0
  124. package/src/core/config/env-resolve.ts +87 -0
  125. package/src/core/config/env-types.ts +116 -0
  126. package/src/core/config/env.ts +55 -0
  127. package/src/core/config/errors.ts +9 -0
  128. package/src/core/config/import-config.ts +40 -0
  129. package/src/core/config/index.ts +8 -0
  130. package/src/core/config/load.ts +417 -0
  131. package/src/core/config/namespace.ts +14 -0
  132. package/src/core/config/resolve.ts +195 -0
  133. package/src/core/config/schema.ts +488 -0
  134. package/src/core/config/statement-observer.ts +48 -0
  135. package/src/core/config/ts-inline-hooks.ts +75 -0
  136. package/src/core/config/ts-register.ts +4 -0
  137. package/src/core/config/ts-resolve-hooks.ts +101 -0
  138. package/src/core/errors/base.ts +49 -0
  139. package/src/core/errors/http.ts +146 -0
  140. package/src/core/errors/index.ts +2 -0
  141. package/src/core/errors/namespace.ts +31 -0
  142. package/src/core/lifecycle/drain.ts +33 -0
  143. package/src/core/lifecycle/health.ts +72 -0
  144. package/src/core/lifecycle/index.ts +4 -0
  145. package/src/core/lifecycle/namespace.ts +7 -0
  146. package/src/core/lifecycle/startup.ts +102 -0
  147. package/src/core/lifecycle/types.ts +36 -0
  148. package/src/core/logging/context.ts +60 -0
  149. package/src/core/logging/error-chain.ts +68 -0
  150. package/src/core/logging/index.ts +5 -0
  151. package/src/core/logging/logger.ts +65 -0
  152. package/src/core/logging/namespace.ts +16 -0
  153. package/src/core/logging/proxy.ts +22 -0
  154. package/src/core/logging/redact-match.ts +24 -0
  155. package/src/core/logging/types.ts +47 -0
  156. package/src/core/pipework.ts +566 -0
  157. package/src/core/runtime/broker.ts +69 -0
  158. package/src/core/surface/http.ts +133 -0
  159. package/src/core/surface/index.ts +11 -0
  160. package/src/core/surface/job-timeout.ts +40 -0
  161. package/src/core/surface/namespace.ts +13 -0
  162. package/src/core/surface/script.ts +33 -0
  163. package/src/core/surface/start.ts +142 -0
  164. package/src/core/surface/types.ts +225 -0
  165. package/src/core/surface/validate.ts +88 -0
  166. package/src/core/surface/worker.ts +528 -0
  167. package/src/core/validation/connectivity.ts +49 -0
  168. package/src/core/validation/domain.ts +99 -0
  169. package/src/core/validation/index.ts +5 -0
  170. package/src/core/validation/queue-tables.ts +312 -0
  171. package/src/core/validation/rls-role.ts +138 -0
  172. package/src/core/validation/startup.ts +51 -0
  173. package/src/core/validation/surfaces.ts +65 -0
  174. package/src/core/validation/types.ts +25 -0
  175. package/src/data/db/advisory-lock.ts +55 -0
  176. package/src/data/db/aggregates.ts +172 -0
  177. package/src/data/db/array-elements.ts +111 -0
  178. package/src/data/db/bulk-set.ts +53 -0
  179. package/src/data/db/col/index.ts +14 -0
  180. package/src/data/db/composed.ts +53 -0
  181. package/src/data/db/db.ts +276 -0
  182. package/src/data/db/excluded.ts +16 -0
  183. package/src/data/db/expressions.ts +473 -0
  184. package/src/data/db/filter.ts +90 -0
  185. package/src/data/db/function-source.ts +114 -0
  186. package/src/data/db/identifiers.ts +16 -0
  187. package/src/data/db/idx/index.ts +9 -0
  188. package/src/data/db/in-list.ts +49 -0
  189. package/src/data/db/index.ts +9 -0
  190. package/src/data/db/insert-split.ts +137 -0
  191. package/src/data/db/instrumentation.ts +82 -0
  192. package/src/data/db/maintenance.ts +28 -0
  193. package/src/data/db/namespace.ts +107 -0
  194. package/src/data/db/owner-labels.ts +9 -0
  195. package/src/data/db/pool.ts +187 -0
  196. package/src/data/db/query-label.ts +13 -0
  197. package/src/data/db/query-watch.ts +116 -0
  198. package/src/data/db/raw-client-warning.ts +37 -0
  199. package/src/data/db/readonly.ts +23 -0
  200. package/src/data/db/reservation.ts +151 -0
  201. package/src/data/db/schema-features.ts +14 -0
  202. package/src/data/db/serializable.ts +83 -0
  203. package/src/data/db/sql.ts +1 -0
  204. package/src/data/db/timeouts.ts +76 -0
  205. package/src/data/db/types.ts +18 -0
  206. package/src/data/db/unbound.ts +33 -0
  207. package/src/data/db/values.ts +29 -0
  208. package/src/data/domain/brand.ts +15 -0
  209. package/src/data/domain/build-factory.ts +193 -0
  210. package/src/data/domain/build-schema.ts +161 -0
  211. package/src/data/domain/build-table.ts +355 -0
  212. package/src/data/domain/define.ts +239 -0
  213. package/src/data/domain/external-registry.ts +34 -0
  214. package/src/data/domain/external.ts +77 -0
  215. package/src/data/domain/field.ts +279 -0
  216. package/src/data/domain/index.ts +13 -0
  217. package/src/data/domain/naming.ts +6 -0
  218. package/src/data/domain/projections.ts +57 -0
  219. package/src/data/domain/trace-safe-projection.ts +31 -0
  220. package/src/data/domain/traits.ts +108 -0
  221. package/src/data/domain/types.ts +289 -0
  222. package/src/data/jsonb/composed.ts +18 -0
  223. package/src/data/jsonb/index.ts +1 -0
  224. package/src/data/jsonb/namespace.ts +30 -0
  225. package/src/data/jsonb/query.ts +86 -0
  226. package/src/data/migrate/append-only-generate.ts +47 -0
  227. package/src/data/migrate/applied.ts +47 -0
  228. package/src/data/migrate/apply.ts +309 -0
  229. package/src/data/migrate/check.ts +119 -0
  230. package/src/data/migrate/diff.ts +357 -0
  231. package/src/data/migrate/door-generate.ts +130 -0
  232. package/src/data/migrate/framework-rls.ts +58 -0
  233. package/src/data/migrate/generate.ts +201 -0
  234. package/src/data/migrate/history.ts +215 -0
  235. package/src/data/migrate/index.ts +9 -0
  236. package/src/data/migrate/internal-definitions.ts +175 -0
  237. package/src/data/migrate/post-process.ts +73 -0
  238. package/src/data/migrate/pragma.ts +73 -0
  239. package/src/data/migrate/provision-framework.ts +147 -0
  240. package/src/data/migrate/rls-generate.ts +61 -0
  241. package/src/data/migrate/run.ts +124 -0
  242. package/src/data/migrate/safety.ts +167 -0
  243. package/src/data/migrate/snapshot.ts +368 -0
  244. package/src/data/migrate/sql-emit.ts +392 -0
  245. package/src/data/migrate/statements.ts +312 -0
  246. package/src/data/query/alias.ts +126 -0
  247. package/src/data/query/batch.ts +8 -0
  248. package/src/data/query/cache/core/cache.ts +78 -0
  249. package/src/data/query/cache/core/index.ts +1 -0
  250. package/src/data/query/cache/core/types.ts +29 -0
  251. package/src/data/query/cache/readme.md +230 -0
  252. package/src/data/query/casing.ts +76 -0
  253. package/src/data/query/collation.ts +11 -0
  254. package/src/data/query/column-builder.ts +370 -0
  255. package/src/data/query/column.ts +150 -0
  256. package/src/data/query/entity.ts +42 -0
  257. package/src/data/query/errors.ts +33 -0
  258. package/src/data/query/index.ts +14 -0
  259. package/src/data/query/logger.ts +47 -0
  260. package/src/data/query/operations.ts +45 -0
  261. package/src/data/query/pg-core/alias.ts +12 -0
  262. package/src/data/query/pg-core/checks.ts +32 -0
  263. package/src/data/query/pg-core/columns/all.ts +71 -0
  264. package/src/data/query/pg-core/columns/bigint.ts +110 -0
  265. package/src/data/query/pg-core/columns/bigserial.ts +128 -0
  266. package/src/data/query/pg-core/columns/boolean.ts +43 -0
  267. package/src/data/query/pg-core/columns/char.ts +85 -0
  268. package/src/data/query/pg-core/columns/cidr.ts +43 -0
  269. package/src/data/query/pg-core/columns/common.ts +366 -0
  270. package/src/data/query/pg-core/columns/custom.ts +232 -0
  271. package/src/data/query/pg-core/columns/date.common.ts +15 -0
  272. package/src/data/query/pg-core/columns/date.ts +112 -0
  273. package/src/data/query/pg-core/columns/double-precision.ts +55 -0
  274. package/src/data/query/pg-core/columns/enum.ts +201 -0
  275. package/src/data/query/pg-core/columns/index.ts +35 -0
  276. package/src/data/query/pg-core/columns/inet.ts +43 -0
  277. package/src/data/query/pg-core/columns/int.common.ts +57 -0
  278. package/src/data/query/pg-core/columns/integer.ts +53 -0
  279. package/src/data/query/pg-core/columns/interval.ts +83 -0
  280. package/src/data/query/pg-core/columns/json.ts +58 -0
  281. package/src/data/query/pg-core/columns/jsonb.ts +56 -0
  282. package/src/data/query/pg-core/columns/line.ts +117 -0
  283. package/src/data/query/pg-core/columns/macaddr.ts +43 -0
  284. package/src/data/query/pg-core/columns/macaddr8.ts +43 -0
  285. package/src/data/query/pg-core/columns/numeric.ts +227 -0
  286. package/src/data/query/pg-core/columns/point.ts +127 -0
  287. package/src/data/query/pg-core/columns/postgis_extension/geometry.ts +119 -0
  288. package/src/data/query/pg-core/columns/postgis_extension/utils.ts +47 -0
  289. package/src/data/query/pg-core/columns/real.ts +58 -0
  290. package/src/data/query/pg-core/columns/serial.ts +55 -0
  291. package/src/data/query/pg-core/columns/smallint.ts +53 -0
  292. package/src/data/query/pg-core/columns/smallserial.ts +60 -0
  293. package/src/data/query/pg-core/columns/text.ts +67 -0
  294. package/src/data/query/pg-core/columns/time.ts +72 -0
  295. package/src/data/query/pg-core/columns/timestamp.ts +163 -0
  296. package/src/data/query/pg-core/columns/uuid.ts +51 -0
  297. package/src/data/query/pg-core/columns/varchar.ts +89 -0
  298. package/src/data/query/pg-core/columns/vector_extension/bit.ts +68 -0
  299. package/src/data/query/pg-core/columns/vector_extension/halfvec.ts +80 -0
  300. package/src/data/query/pg-core/columns/vector_extension/sparsevec.ts +67 -0
  301. package/src/data/query/pg-core/columns/vector_extension/vector.ts +80 -0
  302. package/src/data/query/pg-core/db.ts +674 -0
  303. package/src/data/query/pg-core/dialect.ts +1404 -0
  304. package/src/data/query/pg-core/expressions.ts +25 -0
  305. package/src/data/query/pg-core/foreign-keys.ts +130 -0
  306. package/src/data/query/pg-core/index.ts +21 -0
  307. package/src/data/query/pg-core/indexes.ts +276 -0
  308. package/src/data/query/pg-core/policies.ts +56 -0
  309. package/src/data/query/pg-core/primary-keys.ts +62 -0
  310. package/src/data/query/pg-core/query-builders/count.ts +86 -0
  311. package/src/data/query/pg-core/query-builders/delete.ts +293 -0
  312. package/src/data/query/pg-core/query-builders/index.ts +7 -0
  313. package/src/data/query/pg-core/query-builders/insert.ts +452 -0
  314. package/src/data/query/pg-core/query-builders/query-builder.ts +123 -0
  315. package/src/data/query/pg-core/query-builders/query.ts +157 -0
  316. package/src/data/query/pg-core/query-builders/raw.ts +49 -0
  317. package/src/data/query/pg-core/query-builders/refresh-materialized-view.ts +108 -0
  318. package/src/data/query/pg-core/query-builders/select.ts +1346 -0
  319. package/src/data/query/pg-core/query-builders/select.types.ts +437 -0
  320. package/src/data/query/pg-core/query-builders/update.ts +626 -0
  321. package/src/data/query/pg-core/roles.ts +41 -0
  322. package/src/data/query/pg-core/schema.ts +72 -0
  323. package/src/data/query/pg-core/sequence.ts +41 -0
  324. package/src/data/query/pg-core/session.ts +292 -0
  325. package/src/data/query/pg-core/subquery.ts +46 -0
  326. package/src/data/query/pg-core/table.ts +252 -0
  327. package/src/data/query/pg-core/unique-constraint.ts +73 -0
  328. package/src/data/query/pg-core/utils/array.ts +95 -0
  329. package/src/data/query/pg-core/utils/index.ts +1 -0
  330. package/src/data/query/pg-core/utils.ts +104 -0
  331. package/src/data/query/pg-core/view-base.ts +14 -0
  332. package/src/data/query/pg-core/view-common.ts +1 -0
  333. package/src/data/query/pg-core/view.ts +435 -0
  334. package/src/data/query/pg-core/with-builder.ts +41 -0
  335. package/src/data/query/postgres-js/README.md +46 -0
  336. package/src/data/query/postgres-js/array-parser.ts +45 -0
  337. package/src/data/query/postgres-js/driver-error-boundary.ts +149 -0
  338. package/src/data/query/postgres-js/driver.ts +163 -0
  339. package/src/data/query/postgres-js/index.ts +2 -0
  340. package/src/data/query/postgres-js/session.ts +234 -0
  341. package/src/data/query/primary-key.ts +11 -0
  342. package/src/data/query/query-builders/query-builder.ts +19 -0
  343. package/src/data/query/query-builders/select.types.ts +175 -0
  344. package/src/data/query/query-promise.ts +35 -0
  345. package/src/data/query/relations.ts +725 -0
  346. package/src/data/query/runnable-query.ts +12 -0
  347. package/src/data/query/selection-proxy.ts +121 -0
  348. package/src/data/query/session.ts +8 -0
  349. package/src/data/query/sql/expressions/conditions.ts +741 -0
  350. package/src/data/query/sql/expressions/index.ts +2 -0
  351. package/src/data/query/sql/expressions/select.ts +45 -0
  352. package/src/data/query/sql/functions/aggregate.ts +129 -0
  353. package/src/data/query/sql/functions/index.ts +2 -0
  354. package/src/data/query/sql/functions/vector.ts +178 -0
  355. package/src/data/query/sql/index.ts +3 -0
  356. package/src/data/query/sql/sql.ts +794 -0
  357. package/src/data/query/subquery.ts +53 -0
  358. package/src/data/query/table.ts +208 -0
  359. package/src/data/query/table.utils.ts +2 -0
  360. package/src/data/query/tracing-utils.ts +3 -0
  361. package/src/data/query/tracing.ts +11 -0
  362. package/src/data/query/utils.ts +357 -0
  363. package/src/data/query/version.ts +4 -0
  364. package/src/data/query/view-common.ts +1 -0
  365. package/src/data/schema/branded.ts +13 -0
  366. package/src/data/schema/generated/coerce.ts +5 -0
  367. package/src/data/schema/generated/combinator.ts +6 -0
  368. package/src/data/schema/generated/encoding.ts +5 -0
  369. package/src/data/schema/generated/error.ts +6 -0
  370. package/src/data/schema/generated/format.ts +5 -0
  371. package/src/data/schema/generated/type-constructor.ts +10 -0
  372. package/src/data/schema/generated/type.ts +6 -0
  373. package/src/data/schema/index.ts +6 -0
  374. package/src/data/schema/json-schema.ts +33 -0
  375. package/src/data/schema/namespace.ts +55 -0
  376. package/src/data/schema/parse.ts +39 -0
  377. package/src/data/schema/types.ts +39 -0
  378. package/src/data/temporal/append-only.ts +134 -0
  379. package/src/data/temporal/close.ts +27 -0
  380. package/src/data/temporal/columns.ts +10 -0
  381. package/src/data/temporal/definition-queries.ts +70 -0
  382. package/src/data/temporal/errors.ts +10 -0
  383. package/src/data/temporal/get-current.ts +22 -0
  384. package/src/data/temporal/index.ts +9 -0
  385. package/src/data/temporal/namespace.ts +36 -0
  386. package/src/data/temporal/query.ts +25 -0
  387. package/src/data/temporal/resolve.ts +17 -0
  388. package/src/data/temporal/revise.ts +67 -0
  389. package/src/data/temporal/types.ts +22 -0
  390. package/src/data/valve/artifact.ts +33 -0
  391. package/src/data/valve/contract-diff.ts +21 -0
  392. package/src/data/valve/cross.ts +57 -0
  393. package/src/data/valve/crossing-ledger.ts +50 -0
  394. package/src/data/valve/decision.ts +44 -0
  395. package/src/data/valve/durable-crossing.ts +52 -0
  396. package/src/data/valve/execute-crossing.ts +77 -0
  397. package/src/data/valve/faucet.ts +56 -0
  398. package/src/data/valve/gate.ts +114 -0
  399. package/src/data/valve/graph.ts +77 -0
  400. package/src/data/valve/index.ts +53 -0
  401. package/src/data/valve/namespace.ts +57 -0
  402. package/src/data/valve/registry.ts +50 -0
  403. package/src/data/valve/schema/valve-crossing-fields.ts +23 -0
  404. package/src/data/valve/schema/valve-crossing.ts +9 -0
  405. package/src/data/valve/supply.ts +154 -0
  406. package/src/data/valve/types.ts +143 -0
  407. package/src/data/vector/columns.ts +27 -0
  408. package/src/data/vector/index.ts +4 -0
  409. package/src/data/vector/namespace.ts +33 -0
  410. package/src/data/vector/query.ts +85 -0
  411. package/src/data/vector/validate.ts +16 -0
  412. package/src/data/write/audit/actor.ts +15 -0
  413. package/src/data/write/audit/audit-columns.ts +18 -0
  414. package/src/data/write/audit/audit-select.ts +111 -0
  415. package/src/data/write/audit/bound-transaction.ts +14 -0
  416. package/src/data/write/audit/cte-compile.ts +32 -0
  417. package/src/data/write/audit/decode-returning.ts +47 -0
  418. package/src/data/write/audit/final-select.ts +43 -0
  419. package/src/data/write/audit/infra-audit.ts +36 -0
  420. package/src/data/write/audit/primary-key.ts +65 -0
  421. package/src/data/write/audit/projection.ts +41 -0
  422. package/src/data/write/audit/read-touches.ts +86 -0
  423. package/src/data/write/audit/touch-match.ts +78 -0
  424. package/src/data/write/audit/verb-delete.ts +62 -0
  425. package/src/data/write/audit/verb-insert.ts +36 -0
  426. package/src/data/write/audit/verb-select.ts +47 -0
  427. package/src/data/write/audit/verb-update.ts +60 -0
  428. package/src/data/write/audit/verb-upsert.ts +104 -0
  429. package/src/data/write/conflict.ts +43 -0
  430. package/src/data/write/delete.ts +38 -0
  431. package/src/data/write/execute.ts +243 -0
  432. package/src/data/write/index.ts +11 -0
  433. package/src/data/write/insert.ts +63 -0
  434. package/src/data/write/state.ts +43 -0
  435. package/src/data/write/update.ts +54 -0
  436. package/src/data/write/upsert.ts +58 -0
  437. package/src/data/write/values.ts +19 -0
  438. package/src/index.ts +230 -0
  439. package/src/infra/access-links/code.ts +70 -0
  440. package/src/infra/access-links/handle.ts +892 -0
  441. package/src/infra/access-links/index.ts +9 -0
  442. package/src/infra/access-links/namespace.ts +30 -0
  443. package/src/infra/access-links/person-check.ts +177 -0
  444. package/src/infra/access-links/schema.ts +71 -0
  445. package/src/infra/alerts/async-sources.ts +231 -0
  446. package/src/infra/alerts/audit.ts +36 -0
  447. package/src/infra/alerts/beat.ts +123 -0
  448. package/src/infra/alerts/client-faults.ts +111 -0
  449. package/src/infra/alerts/faults.ts +156 -0
  450. package/src/infra/alerts/handle.ts +778 -0
  451. package/src/infra/alerts/http.ts +192 -0
  452. package/src/infra/alerts/index.ts +12 -0
  453. package/src/infra/alerts/infra-sources.ts +180 -0
  454. package/src/infra/alerts/namespace.ts +81 -0
  455. package/src/infra/alerts/redact.ts +21 -0
  456. package/src/infra/alerts/request-faults.ts +41 -0
  457. package/src/infra/alerts/schema.ts +124 -0
  458. package/src/infra/alerts/sinks.ts +125 -0
  459. package/src/infra/alerts/source-options.ts +26 -0
  460. package/src/infra/alerts/sources.ts +503 -0
  461. package/src/infra/alerts/subsystem-sources.ts +137 -0
  462. package/src/infra/audit/emit.ts +87 -0
  463. package/src/infra/audit/index.ts +2 -0
  464. package/src/infra/audit/namespace.ts +22 -0
  465. package/src/infra/audit/schema.sql +35 -0
  466. package/src/infra/audit/types.ts +18 -0
  467. package/src/infra/aws/credentials.ts +147 -0
  468. package/src/infra/aws/sigv4.ts +140 -0
  469. package/src/infra/cache/create.ts +58 -0
  470. package/src/infra/cache/entry.ts +81 -0
  471. package/src/infra/cache/index.ts +4 -0
  472. package/src/infra/cache/namespace.ts +10 -0
  473. package/src/infra/cache/tenant.ts +93 -0
  474. package/src/infra/cache/types.ts +24 -0
  475. package/src/infra/crawl/audit.ts +35 -0
  476. package/src/infra/crawl/errors.ts +25 -0
  477. package/src/infra/crawl/handle.ts +693 -0
  478. package/src/infra/crawl/index.ts +9 -0
  479. package/src/infra/crawl/links.ts +160 -0
  480. package/src/infra/crawl/namespace.ts +36 -0
  481. package/src/infra/crawl/robots.ts +100 -0
  482. package/src/infra/crawl/schema.ts +89 -0
  483. package/src/infra/credit-line/handle.ts +883 -0
  484. package/src/infra/credit-line/index.ts +6 -0
  485. package/src/infra/credit-line/namespace.ts +7 -0
  486. package/src/infra/credit-line/schema.ts +96 -0
  487. package/src/infra/credits/handle.ts +706 -0
  488. package/src/infra/credits/index.ts +5 -0
  489. package/src/infra/credits/namespace.ts +7 -0
  490. package/src/infra/credits/schema.ts +83 -0
  491. package/src/infra/email/audit.ts +36 -0
  492. package/src/infra/email/inbound.ts +622 -0
  493. package/src/infra/email/index.ts +11 -0
  494. package/src/infra/email/mail.ts +517 -0
  495. package/src/infra/email/mime.ts +447 -0
  496. package/src/infra/email/namespace.ts +22 -0
  497. package/src/infra/email/providers.ts +111 -0
  498. package/src/infra/email/schema.ts +96 -0
  499. package/src/infra/email/sns.ts +200 -0
  500. package/src/infra/email/unsubscribe.ts +44 -0
  501. package/src/infra/fetch/address.ts +88 -0
  502. package/src/infra/fetch/connect-retry.ts +87 -0
  503. package/src/infra/fetch/handle.ts +329 -0
  504. package/src/infra/fetch/index.ts +7 -0
  505. package/src/infra/fetch/namespace.ts +70 -0
  506. package/src/infra/idempotency/index.ts +3 -0
  507. package/src/infra/idempotency/middleware.ts +87 -0
  508. package/src/infra/idempotency/store.ts +92 -0
  509. package/src/infra/idempotency/types.ts +30 -0
  510. package/src/infra/join-requests/handle.ts +312 -0
  511. package/src/infra/join-requests/index.ts +2 -0
  512. package/src/infra/join-requests/namespace.ts +7 -0
  513. package/src/infra/join-requests/schema.ts +33 -0
  514. package/src/infra/outbox/core.ts +165 -0
  515. package/src/infra/quota/errors.ts +32 -0
  516. package/src/infra/quota/handle.ts +215 -0
  517. package/src/infra/quota/index.ts +5 -0
  518. package/src/infra/quota/namespace.ts +7 -0
  519. package/src/infra/quota/schema.ts +15 -0
  520. package/src/infra/storage/backend.ts +34 -0
  521. package/src/infra/storage/handle.ts +513 -0
  522. package/src/infra/storage/index.ts +9 -0
  523. package/src/infra/storage/local.ts +104 -0
  524. package/src/infra/storage/namespace.ts +13 -0
  525. package/src/infra/storage/s3.ts +257 -0
  526. package/src/infra/storage/schema.ts +24 -0
  527. package/src/infra/support/handle.ts +854 -0
  528. package/src/infra/support/http.ts +229 -0
  529. package/src/infra/support/index.ts +7 -0
  530. package/src/infra/support/namespace.ts +12 -0
  531. package/src/infra/support/schema.ts +96 -0
  532. package/src/infra/webhook/inbound.ts +45 -0
  533. package/src/infra/webhook/index.ts +5 -0
  534. package/src/infra/webhook/namespace.ts +24 -0
  535. package/src/infra/webhook/outbound.ts +315 -0
  536. package/src/infra/webhook/sign.ts +32 -0
  537. package/src/infra/webhook/verify.ts +82 -0
  538. package/src/invariants/assert.ts +26 -0
  539. package/src/invariants/index.ts +1 -0
  540. package/src/lint/config.ts +56 -0
  541. package/src/lint/doctrine.ts +163 -0
  542. package/src/lint/include.ts +97 -0
  543. package/src/lint/index.ts +3 -0
  544. package/src/lint/preset.ts +10 -0
  545. package/src/lint/resolver.ts +218 -0
  546. package/src/lint/rules/no-reexports.ts +46 -0
  547. package/src/request/behavior/audited.ts +85 -0
  548. package/src/request/behavior/cached.ts +62 -0
  549. package/src/request/behavior/compose.ts +28 -0
  550. package/src/request/behavior/index.ts +5 -0
  551. package/src/request/behavior/namespace.ts +16 -0
  552. package/src/request/behavior/types.ts +26 -0
  553. package/src/request/behavior/versioned.ts +54 -0
  554. package/src/request/context/create.ts +103 -0
  555. package/src/request/context/guard.ts +49 -0
  556. package/src/request/context/index.ts +4 -0
  557. package/src/request/context/run-in-transaction.ts +200 -0
  558. package/src/request/context/store.ts +28 -0
  559. package/src/request/context/types.ts +50 -0
  560. package/src/request/di/builder.ts +535 -0
  561. package/src/request/di/index.ts +8 -0
  562. package/src/request/di/resolve.ts +271 -0
  563. package/src/request/di/types.ts +262 -0
  564. package/src/request/fitting/namespace.ts +7 -0
  565. package/src/request/fixture/batch.ts +71 -0
  566. package/src/request/fixture/builder.ts +26 -0
  567. package/src/request/fixture/errors.ts +10 -0
  568. package/src/request/fixture/index.ts +8 -0
  569. package/src/request/fixture/namespace.ts +23 -0
  570. package/src/request/fixture/paginate.ts +44 -0
  571. package/src/request/fixture/to-handlers.ts +260 -0
  572. package/src/request/fixture/types.ts +79 -0
  573. package/src/request/flow/act-as.ts +78 -0
  574. package/src/request/flow/namespace.ts +17 -0
  575. package/src/request/http/error-step.ts +42 -0
  576. package/src/request/http/file.ts +141 -0
  577. package/src/request/http/index.ts +4 -0
  578. package/src/request/http/middleware.ts +141 -0
  579. package/src/request/http/namespace.ts +5 -0
  580. package/src/request/http/rate-limit-classes.ts +170 -0
  581. package/src/request/http/response.ts +82 -0
  582. package/src/request/http/server.ts +916 -0
  583. package/src/request/http/sniff.ts +261 -0
  584. package/src/request/http/types.ts +250 -0
  585. package/src/request/http/upload.ts +284 -0
  586. package/src/request/internal/text.ts +6 -0
  587. package/src/request/openapi/generate.ts +205 -0
  588. package/src/request/openapi/index.ts +2 -0
  589. package/src/request/openapi/namespace.ts +7 -0
  590. package/src/test/auth.ts +13 -0
  591. package/src/test/auto-setup.ts +45 -0
  592. package/src/test/context.ts +91 -0
  593. package/src/test/crawl-fixture.ts +157 -0
  594. package/src/test/discover-fork.ts +17 -0
  595. package/src/test/fingerprint.ts +135 -0
  596. package/src/test/globals.d.ts +7 -0
  597. package/src/test/index.ts +19 -0
  598. package/src/test/namespace.ts +87 -0
  599. package/src/test/plugin.ts +189 -0
  600. package/src/test/progress.ts +185 -0
  601. package/src/test/reap-after-run.ts +28 -0
  602. package/src/test/reap.ts +286 -0
  603. package/src/test/reset.ts +253 -0
  604. package/src/test/savepoints.ts +172 -0
  605. package/src/test/setup.ts +1422 -0
  606. package/src/test/system-channel.ts +50 -0
  607. package/src/test/vitest-plugin.ts +142 -0
  608. package/src/test/vitest-workspace.ts +507 -0
  609. package/src/test/vitest.ts +333 -0
  610. package/src/test/watch.ts +105 -0
  611. package/src/trace/capture/bounded-serializer.ts +146 -0
  612. package/src/trace/cardinality/analyze.ts +173 -0
  613. package/src/trace/cardinality/branch-context.ts +214 -0
  614. package/src/trace/cardinality/check.ts +33 -0
  615. package/src/trace/cardinality/classify-edge.ts +64 -0
  616. package/src/trace/cardinality/structural-max.ts +107 -0
  617. package/src/trace/check/coverage-check.ts +188 -0
  618. package/src/trace/check/flow-check.ts +103 -0
  619. package/src/trace/check/flow-step-site.ts +234 -0
  620. package/src/trace/check/io-pattern.ts +116 -0
  621. package/src/trace/codemod/add-cardinality.ts +152 -0
  622. package/src/trace/codemod/run.ts +80 -0
  623. package/src/trace/entry/child-trace.ts +21 -0
  624. package/src/trace/entry/close-trace.ts +87 -0
  625. package/src/trace/entry/finalize-trace.ts +28 -0
  626. package/src/trace/entry/open-trace.ts +138 -0
  627. package/src/trace/entry/parent-trace.ts +20 -0
  628. package/src/trace/entry/parse-traceparent.ts +39 -0
  629. package/src/trace/entry/process-start.ts +38 -0
  630. package/src/trace/entry/trace-context.ts +26 -0
  631. package/src/trace/entry/tracing-enabled.ts +10 -0
  632. package/src/trace/external/record.ts +195 -0
  633. package/src/trace/external/report.ts +66 -0
  634. package/src/trace/namespace.ts +19 -0
  635. package/src/trace/partition/day.ts +47 -0
  636. package/src/trace/partition/index.ts +14 -0
  637. package/src/trace/partition/partition-name.ts +23 -0
  638. package/src/trace/partition/plan.ts +82 -0
  639. package/src/trace/partition/schedule.ts +26 -0
  640. package/src/trace/partition/startup-hook.ts +39 -0
  641. package/src/trace/partition/sync.ts +154 -0
  642. package/src/trace/partition/tables.ts +53 -0
  643. package/src/trace/read/tree.ts +83 -0
  644. package/src/trace/retain/copy-on-error.ts +76 -0
  645. package/src/trace/runtime/expire-stale-traces.ts +54 -0
  646. package/src/trace/runtime/flow-step.ts +147 -0
  647. package/src/trace/runtime/flush-steps.ts +31 -0
  648. package/src/trace/runtime/pending-writes.ts +54 -0
  649. package/src/trace/runtime/step-buffer.ts +298 -0
  650. package/src/trace/runtime/step-error.ts +21 -0
  651. package/src/trace/runtime/step-meta.ts +55 -0
  652. package/src/trace/runtime/thenable.ts +12 -0
  653. package/src/trace/runtime/truncate-trace.ts +62 -0
  654. package/src/trace/schema/trace-fields.ts +52 -0
  655. package/src/trace/schema/trace-retained.ts +22 -0
  656. package/src/trace/schema/trace-step-fields.ts +44 -0
  657. package/src/trace/schema/trace-step-retained.ts +25 -0
  658. package/src/trace/schema/trace-step.ts +18 -0
  659. package/src/trace/schema/trace.ts +12 -0
  660. package/src/trace/transformer/apply.ts +79 -0
  661. package/src/trace/transformer/build-meta-literal.ts +30 -0
  662. package/src/trace/transformer/find-flow-step-sites.ts +85 -0
  663. package/src/trace/transformer/inject-flow-step-import.ts +39 -0
  664. package/src/trace/transformer/transform-error.ts +14 -0
  665. package/src/trace/transformer/transform-flow-steps.ts +123 -0
  666. package/src/trace/transformer/wrap-function-declaration.ts +62 -0
  667. package/src/trace/transformer/wrap-variable-declaration.ts +67 -0
  668. package/src/workspace/affected.ts +110 -0
  669. package/src/workspace/changed-files.ts +163 -0
  670. package/src/workspace/check-deps.ts +213 -0
  671. package/src/workspace/checksum.ts +20 -0
  672. package/src/workspace/dependency-graph.ts +67 -0
  673. package/src/workspace/detect-pipework-copies.ts +93 -0
  674. package/src/workspace/discover.ts +206 -0
  675. package/src/workspace/generate.ts +401 -0
  676. package/src/workspace/index.ts +7 -0
  677. package/src/workspace/module-imports-pipework.ts +39 -0
  678. package/src/workspace/module-package-name.ts +12 -0
  679. package/src/workspace/scrub-git-env.ts +23 -0
  680. package/src/workspace/source-paths.ts +36 -0
  681. package/src/workspace/sync.ts +182 -0
  682. package/dist/REFERENCE.md +0 -1477
  683. package/dist/infra/access-links/rate-limit.d.ts +0 -10
  684. package/dist/infra/access-links/rate-limit.d.ts.map +0 -1
  685. package/dist/infra/access-links/rate-limit.js +0 -28
  686. package/dist/infra/access-links/rate-limit.js.map +0 -1
  687. package/dist/infra/email/sigv4.d.ts +0 -24
  688. package/dist/infra/email/sigv4.d.ts.map +0 -1
  689. package/dist/infra/email/sigv4.js +0 -55
  690. package/dist/infra/email/sigv4.js.map +0 -1
@@ -0,0 +1,88 @@
1
+ import type { ResolvedConfig } from '../config/load.js'
2
+ import type { Surface, SurfaceMap } from './types.js'
3
+ import { DEFAULT_DB } from '../../request/di/types.js'
4
+
5
+ export function validateSurfaces(config: ResolvedConfig, surfaces: SurfaceMap): void {
6
+ const problems: string[] = []
7
+ const configuredDbs = new Set(config.databaseNames())
8
+
9
+ for (const [name, surface] of Object.entries(surfaces)) {
10
+ for (const db of surface.manifest.databases) {
11
+ if (db === DEFAULT_DB) {
12
+ if (configuredDbs.size > 1) {
13
+ try {
14
+ config.defaultDatabaseName()
15
+ } catch {
16
+ problems.push(
17
+ `Surface "${name}" uses the default database, but multiple databases are configured.\n` +
18
+ ` Configured: ${[...configuredDbs].join(', ')}\n` +
19
+ ` Fix: Pass an explicit database name — e.g., surface.worker({ database: "${[...configuredDbs][0]}" })`,
20
+ )
21
+ }
22
+ }
23
+ } else if (!configuredDbs.has(db)) {
24
+ const available = [...configuredDbs].join(', ')
25
+ problems.push(
26
+ `Surface "${name}" requires database "${db}" which is not configured.\n` +
27
+ ` Configured databases: ${available || '(none)'}\n` +
28
+ ` Fix: Add "${db}" to your databases config, or correct the name in the surface definition.`,
29
+ )
30
+ }
31
+ }
32
+
33
+ validateSurfaceKind(name, surface, problems)
34
+ }
35
+
36
+ if (problems.length > 0) {
37
+ throw new Error(
38
+ `[pipework] Surface validation failed:\n\n${problems.map((p, i) => ` ${i + 1}. ${p}`).join('\n\n')}\n`,
39
+ )
40
+ }
41
+ }
42
+
43
+ function validateSurfaceKind(name: string, surface: Surface, problems: string[]): void {
44
+ if (surface.kind === 'worker') {
45
+ if (surface.manifest.jobTypes.length === 0) {
46
+ problems.push(
47
+ `Surface "${name}" is a worker with no job handlers.\n` +
48
+ ' Fix: Pass at least one job handler — surface.worker({ jobs: [myJob] })',
49
+ )
50
+ }
51
+
52
+ const seen = new Set<string>()
53
+ for (const jobType of surface.manifest.jobTypes) {
54
+ if (seen.has(jobType)) {
55
+ problems.push(
56
+ `Surface "${name}" has duplicate job type "${jobType}".\n` +
57
+ ' Each job type must have exactly one handler.',
58
+ )
59
+ }
60
+ seen.add(jobType)
61
+ }
62
+ }
63
+ }
64
+
65
+ export function validateNoJobTypeDuplicates(surfaces: SurfaceMap): void {
66
+ const owners = new Map<string, string>()
67
+ const problems: string[] = []
68
+
69
+ for (const [name, surface] of Object.entries(surfaces)) {
70
+ for (const jobType of surface.manifest.jobTypes) {
71
+ const existing = owners.get(jobType)
72
+ if (existing !== undefined) {
73
+ problems.push(
74
+ `Job type "${jobType}" is handled by both "${existing}" and "${name}".\n` +
75
+ ' Each job type must be handled by exactly one worker surface.',
76
+ )
77
+ } else {
78
+ owners.set(jobType, name)
79
+ }
80
+ }
81
+ }
82
+
83
+ if (problems.length > 0) {
84
+ throw new Error(
85
+ `[pipework] Job type conflict:\n\n${problems.map((p, i) => ` ${i + 1}. ${p}`).join('\n\n')}\n`,
86
+ )
87
+ }
88
+ }
@@ -0,0 +1,528 @@
1
+ import { randomUUID } from 'node:crypto'
2
+ import type { SurfaceBuilder, SurfaceHandle, WorkerSurfaceConfig, SurfaceContext, JobEvent } from './types.js'
3
+ import type { Handler } from '../../request/di/types.js'
4
+ import { DEFAULT_DB } from '../../request/di/types.js'
5
+ import { createQueue } from '../../async/jobs/queue.js'
6
+ import type { ClaimedFlow, ClaimedJob } from '../../async/jobs/queue.js'
7
+ import { abortableDelay } from '../../async/jobs/abortable-delay.js'
8
+ import type { ListenClient, ListenHandle } from '../../async/jobs/listener.js'
9
+ import { executeJob } from '../../async/jobs/execute.js'
10
+ import { JobLaterError, JobPermanentError } from '../../async/jobs/errors.js'
11
+ import { QuotaExhaustedError } from '../../infra/quota/errors.js'
12
+ import { executeWithTimeout } from './job-timeout.js'
13
+ import { createJobContext } from '../../request/context/create.js'
14
+ import { runInContext } from '../../request/context/store.js'
15
+ import { executeInTransaction } from '../../request/context/run-in-transaction.js'
16
+ import { isTracingEnabled } from '../../trace/entry/tracing-enabled.js'
17
+ import { openTrace } from '../../trace/entry/open-trace.js'
18
+ import { finalizeTrace } from '../../trace/entry/finalize-trace.js'
19
+ import type { Manifold } from '../pipework.js'
20
+ import { getBaseLogger } from '../logging/index.js'
21
+ import { pgErrorCode } from '../../data/db/serializable.js'
22
+ import { sql } from '../../data/db/sql.js'
23
+ import { frameworkDoorDb } from '../../data/db/db.js'
24
+ import { recordBeat, beatsEnabled } from '../../infra/alerts/beat.js'
25
+ import { recordFault, faultMessage, useFaultTarget } from '../../infra/alerts/faults.js'
26
+ import { parseCron } from '../../async/jobs/cron.js'
27
+ import { registerSchedulesDefinition } from '../../async/jobs/schedules-table.js'
28
+ import { describeError } from '../logging/error-chain.js'
29
+
30
+ export function createWorkerSurface(config: WorkerSurfaceConfig): SurfaceBuilder {
31
+ const selections = (config.graphs ?? []).map(g => 'graph' in g ? g : { graph: g, classes: undefined })
32
+ const graphs = [...new Set(selections.map(g => g.graph))]
33
+ const graphHandlers = selections.flatMap(g => g.graph.worker.handlers(config.database, g.classes))
34
+ const allJobs = [...config.jobs, ...graphHandlers]
35
+ const databases = collectWorkerDatabases({ ...config, jobs: allJobs })
36
+ const jobTypes = collectJobTypes(allJobs)
37
+ validateJobHandlers(allJobs)
38
+ const deps: Record<string, string> = {}
39
+ // Created at surface construction, not start(): createQueue() registers the
40
+ // queue table definition, and `pipework generate` discovers surfaces without
41
+ // starting them — construction time is what makes the DDL reach migrations.
42
+ const queue = createQueue(config.queue)
43
+ for (const g of graphs) {
44
+ const graphTable = (g.queue.definition as unknown as { $name: string }).$name
45
+ const workerTable = (queue.definition as unknown as { $name: string }).$name
46
+ if (graphTable !== workerTable) {
47
+ throw new Error(
48
+ `[pipework] Step graph "${g.name}" enqueues on queue table "${graphTable}", but this worker surface reads "${workerTable}".\n\n` +
49
+ ` Set the worker's queue to the graph's: createWorkerSurface({ graphs, queue: { table: "${graphTable}" } }).\n`,
50
+ )
51
+ }
52
+ }
53
+ const schedules = config.schedules
54
+ if (schedules !== undefined) {
55
+ validateSchedules(schedules, jobTypes)
56
+ registerSchedulesDefinition()
57
+ }
58
+ const scheduledJobs = new Set((schedules ?? []).map(s => s.job))
59
+
60
+ const surface: SurfaceBuilder = {
61
+ kind: 'worker',
62
+ manifest: { databases, jobTypes, dependencies: deps },
63
+
64
+ dependencies(d: Record<string, string>) {
65
+ Object.assign(deps, d)
66
+ return surface
67
+ },
68
+
69
+ async start(context: SurfaceContext): Promise<SurfaceHandle> {
70
+ const logger = getBaseLogger()
71
+ const dbName = config.database ?? context.config.defaultDatabaseName()
72
+ const dbConfig = context.config.database(dbName)
73
+ const conn = context.pool.getOrCreate(dbConfig)
74
+ const db = conn.drizzle
75
+
76
+ await db.execute(sql`SELECT 1`)
77
+
78
+ // A tenant-column queue is row-secured: on a platform-mode database its claim, reap, heartbeat and
79
+ // completion run through the queue's framework door (generated by `pipework generate`), which opens
80
+ // that one table. Everything else the surface does (beats, faults, schedules) needs no door.
81
+ const queueDb = (dbConfig as { readonly rls?: { readonly platformRole?: string } }).rls?.platformRole !== undefined && queue.door !== null && (context.pool as { isOverridden?(name: string): boolean }).isOverridden?.(dbName) !== true
82
+ ? frameworkDoorDb(db, queue.door)
83
+ : db
84
+
85
+ // Loop beats go on the loop's own connection, outside any job transaction, and only where the blueprint has `alerts`.
86
+ const beatsOn = beatsEnabled(context.config)
87
+ const beat = (name: string, outcome: { ok: boolean; error?: string }, everyMs: number): void => {
88
+ if (beatsOn) void recordBeat(db, name, { ...outcome, everyMs })
89
+ }
90
+ // Failures this surface only logs are recorded for `alerts.source.faults`, again only where the blueprint has `alerts`.
91
+ const releaseFaults = beatsOn ? useFaultTarget(db) : (): void => undefined
92
+ const CLAIM_BEAT_EVERY_MS = 60_000
93
+ let lastClaimBeatAt = 0
94
+ const claimBeat = (outcome: { ok: boolean; error?: string }): void => {
95
+ const now = Date.now()
96
+ if (!beatsOn || now - lastClaimBeatAt < CLAIM_BEAT_EVERY_MS) return
97
+ lastClaimBeatAt = now
98
+ beat('jobs.claim', outcome, CLAIM_BEAT_EVERY_MS)
99
+ }
100
+
101
+ if (config.listen === true) {
102
+ const rawConn = conn as unknown as { readonly client: { listen(channel: string, cb: (payload: string) => void): Promise<{ unlisten(): Promise<void> }> } }
103
+ const listenClient: ListenClient = {
104
+ async listen(channel: string, cb: (payload: string) => void): Promise<ListenHandle> {
105
+ const sub = await rawConn.client.listen(channel, cb)
106
+ return { unlisten: () => sub.unlisten() }
107
+ },
108
+ }
109
+ await queue.startListener(listenClient)
110
+ logger.info('LISTEN/NOTIFY active for job completion')
111
+ }
112
+
113
+ // Schedules are in place before any claim loop runs, so a job the scheduler
114
+ // enqueues is never claimed ahead of its registration.
115
+ if (schedules !== undefined) {
116
+ const removed = await queue.syncSchedules(
117
+ db,
118
+ schedules.map(s => ({
119
+ name: s.job,
120
+ cron: s.cron,
121
+ ...(s.timezone !== undefined ? { timezone: s.timezone } : {}),
122
+ ...(s.payload !== undefined ? { payload: s.payload } : {}),
123
+ })),
124
+ jobTypes,
125
+ )
126
+ if (removed.length > 0) logger.info({ removed }, 'Removed schedules no longer declared')
127
+ queue.startScheduler(db, {
128
+ checkIntervalMs: config.schedulerIntervalMs ?? 15_000,
129
+ names: [...scheduledJobs],
130
+ onError: (err) => { logger.error({ err }, 'Scheduler tick failed'); void recordFault('scheduler', faultMessage(err)) },
131
+ onCheck: (outcome) => { beat('jobs.scheduler', outcome, config.schedulerIntervalMs ?? 15_000) },
132
+ })
133
+ }
134
+
135
+ const dispatch = buildDispatchTable(allJobs)
136
+ const handledTypes = [...dispatch.keys()]
137
+ const instance = context as unknown as Manifold
138
+ const releaseGraphs = graphs.map(g => queue.onDeadLetter(
139
+ (tx, job) => g.worker.onDeadLetter(tx, job, { instance, database: config.database }),
140
+ ))
141
+ const concurrency = config.concurrency ?? 1
142
+ const pollIntervalMs = config.pollIntervalMs ?? 1000
143
+ const shutdownDeadlineMs = config.shutdownDeadlineMs ?? 30_000
144
+ const heartbeatIntervalMs = config.heartbeatIntervalMs ?? 30_000
145
+ const priorityTimeouts = config.timeouts ?? {}
146
+
147
+ const maxConsecutiveClaimFailures = config.maxConsecutiveClaimFailures ?? 10
148
+
149
+ const shouldRun = { value: true }
150
+ // claim() retries transient conflicts until clean (unbounded), so
151
+ // shouldRun alone cannot end a loop stuck under contention — the abort
152
+ // signal is what makes shutdown observable inside claim and the sleeps.
153
+ const shutdown = new AbortController()
154
+ // Jobs this worker is running: the claim each holds and the controller that ends its handler.
155
+ // `endedBy` is set before the abort, so the loop knows the job was taken from it and must not fail it.
156
+ interface Running { readonly job: ClaimedJob; readonly controller: AbortController; endedBy: 'handed-back' | 'claim-lost' | null }
157
+ const running = new Map<string, Running>()
158
+ const eventJob = (job: ClaimedJob): JobEvent['job'] => ({
159
+ id: job.id, type: job.type, attempt: job.attempt, maxAttempts: job.maxAttempts, tenant: job.flow.tenant,
160
+ })
161
+
162
+ // Runs the app's callback as the job's tenant and actor, in its own transaction, as a child of the job's
163
+ // trace. Nothing it does reaches the caller: a throw is logged.
164
+ const emit = async (event: JobEvent, flow: ClaimedFlow): Promise<void> => {
165
+ const callback = config.onJobEvent
166
+ if (callback === undefined) return
167
+ try {
168
+ const auth = flow.actor !== null ? { userId: flow.actor } : null
169
+ const trace = isTracingEnabled(instance)
170
+ ? openTrace({
171
+ instance,
172
+ entryKind: 'job',
173
+ parent: flow.traceId !== null && flow.rootId !== null
174
+ ? { id: flow.traceId, rootId: flow.rootId, stepId: flow.stepId }
175
+ : null,
176
+ tenant: flow.tenant,
177
+ auth,
178
+ requestId: event.job.id,
179
+ sessionId: null,
180
+ })
181
+ : null
182
+ const ctx = createJobContext({
183
+ instance, jobId: event.job.id, jobType: event.job.type, attempt: event.job.attempt, maxAttempts: event.job.maxAttempts, tenant: flow.tenant, auth, trace,
184
+ })
185
+ const run = async (): Promise<void> => { await callback(event) }
186
+ const work = ctx.tenant === null
187
+ ? runInContext(ctx, run)
188
+ : executeInTransaction(ctx, [dbName], null, 5000, run)
189
+ await finalizeTrace(instance, ctx.trace, work)
190
+ } catch (err) {
191
+ logger.error({ err, kind: event.kind, jobId: event.job.id }, 'onJobEvent callback failed')
192
+ void recordFault('job-event-callback', faultMessage(err), { kind: event.kind, jobId: event.job.id })
193
+ }
194
+ }
195
+ // A claim that throws is never a quiet queue: it is logged with its
196
+ // Postgres code and counted. A successful claim clears the count; enough
197
+ // consecutive failures end the surface rather than let it poll forever.
198
+ let consecutiveClaimFailures = 0
199
+
200
+ const claimLoop = async () => {
201
+ const workerId = randomUUID()
202
+ while (shouldRun.value) {
203
+ try {
204
+ const claimed = await queue.claim(queueDb, workerId, shutdown.signal, handledTypes, instance.release)
205
+ consecutiveClaimFailures = 0
206
+ claimBeat({ ok: true })
207
+ if (claimed === null) {
208
+ await abortableDelay(pollIntervalMs, shutdown.signal)
209
+ continue
210
+ }
211
+
212
+ const handler = dispatch.get(claimed.type)
213
+
214
+ if (handler === undefined) {
215
+ const failedNoHandler = await queue.failAttempt(queueDb, claimed.id, new Error(
216
+ `[pipework] No handler registered for job type "${claimed.type}".\n\n` +
217
+ ` Registered types: ${[...dispatch.keys()].join(', ')}\n` +
218
+ ' This job was enqueued with a type that no handler recognizes.\n'
219
+ ), claimed.claimToken)
220
+ if (failedNoHandler?.outcome === 'dead_letter') {
221
+ await emit({ kind: 'dead-lettered', cause: 'no-handler', error: failedNoHandler.error, job: eventJob(claimed) }, claimed.flow)
222
+ }
223
+ continue
224
+ }
225
+
226
+ const entry: Running = { job: claimed, controller: new AbortController(), endedBy: null }
227
+ running.set(claimed.id, entry)
228
+ const heartbeatInterval = setInterval(() => {
229
+ queue.heartbeat(queueDb, claimed.id, claimed.claimToken).then((held) => {
230
+ // A loop busy with a long job is alive.
231
+ claimBeat({ ok: true })
232
+ if (held || entry.endedBy !== null) return
233
+ entry.endedBy = 'claim-lost'
234
+ entry.controller.abort(new Error(
235
+ `[pipework] Job "${claimed.type}" (${claimed.id}) lost its claim: the row was reaped or taken by another worker.`,
236
+ ))
237
+ return emit({ kind: 'claim-lost', job: eventJob(claimed) }, claimed.flow)
238
+ }).catch((err: unknown) => {
239
+ logger.error({ err, jobId: claimed.id }, 'Job heartbeat failed')
240
+ void recordFault('queue-heartbeat', faultMessage(err), { jobId: claimed.id })
241
+ })
242
+ }, heartbeatIntervalMs)
243
+
244
+ const timedOut = { value: false }
245
+ try {
246
+ const timeoutMs = resolvePriorityTimeout(claimed, handler, priorityTimeouts)
247
+ const result = await executeWithTimeout(
248
+ (timeoutSignal) => {
249
+ if (timeoutSignal !== undefined) {
250
+ timeoutSignal.addEventListener('abort', () => {
251
+ timedOut.value = true
252
+ entry.controller.abort(timeoutSignal.reason)
253
+ }, { once: true })
254
+ }
255
+ return executeJob(handler, {
256
+ instance,
257
+ jobId: claimed.id,
258
+ jobType: claimed.type,
259
+ attempt: claimed.attempt,
260
+ maxAttempts: claimed.maxAttempts,
261
+ // A scheduled type enqueued with no flow came from the scheduler: its own root trace.
262
+ entryKind: scheduledJobs.has(claimed.type) && claimed.flow.traceId === null && claimed.flow.tenant === null
263
+ ? 'cron'
264
+ : 'job',
265
+ input: claimed.payload,
266
+ tenant: claimed.flow.tenant,
267
+ parent: claimed.flow.traceId !== null && claimed.flow.rootId !== null
268
+ ? { id: claimed.flow.traceId, rootId: claimed.flow.rootId, stepId: claimed.flow.stepId }
269
+ : null,
270
+ ...(claimed.flow.actor !== null ? { auth: { userId: claimed.flow.actor } } : {}),
271
+ ...(claimed.previous !== null
272
+ ? { restart: { reason: claimed.previous.reason, previousWorkerId: claimed.previous.workerId } }
273
+ : {}),
274
+ signal: entry.controller.signal,
275
+ })
276
+ },
277
+ timeoutMs,
278
+ claimed.type,
279
+ claimed.id,
280
+ )
281
+ await queue.complete(queueDb, claimed.id, result, claimed.claimToken)
282
+ } catch (err) {
283
+ // A job taken from this worker (handed back, claim lost) is not its failure to record.
284
+ if (entry.endedBy === null) {
285
+ if (err instanceof JobLaterError) await queue.later(queueDb, claimed.id, err, claimed.claimToken)
286
+ else if (err instanceof QuotaExhaustedError) {
287
+ const delayMs = Math.max(0, err.resetAt.getTime() - Date.now())
288
+ await queue.later(queueDb, claimed.id, new JobLaterError(err.message, { delayMs, cause: err }), claimed.claimToken)
289
+ }
290
+ else {
291
+ const permanent = err instanceof JobPermanentError
292
+ const failed = await queue.failAttempt(queueDb, claimed.id, err, claimed.claimToken, permanent ? { permanent: true } : undefined)
293
+ if (failed?.outcome === 'dead_letter') {
294
+ const cause = permanent ? 'permanent' : timedOut.value ? 'timeout' : 'failed'
295
+ await emit({ kind: 'dead-lettered', cause, error: failed.error, job: eventJob(claimed) }, claimed.flow)
296
+ }
297
+ }
298
+ }
299
+ } finally {
300
+ clearInterval(heartbeatInterval)
301
+ running.delete(claimed.id)
302
+ }
303
+ } catch (err) {
304
+ if (shutdown.signal.aborted) return
305
+
306
+ consecutiveClaimFailures++
307
+ claimBeat({ ok: false, error: describeError(err).message })
308
+ logger.error(
309
+ { err, pgCode: pgErrorCode(err), consecutiveFailures: consecutiveClaimFailures, maxConsecutiveClaimFailures },
310
+ 'Job claim failed',
311
+ )
312
+
313
+ if (consecutiveClaimFailures >= maxConsecutiveClaimFailures) {
314
+ logger.fatal(
315
+ { err, pgCode: pgErrorCode(err), consecutiveFailures: consecutiveClaimFailures },
316
+ `Worker surface stopping: ${String(consecutiveClaimFailures)} consecutive claim failures`,
317
+ )
318
+ shouldRun.value = false
319
+ shutdown.abort()
320
+ process.exitCode = 1
321
+ return
322
+ }
323
+
324
+ await abortableDelay(pollIntervalMs, shutdown.signal)
325
+ }
326
+ }
327
+ }
328
+
329
+ const workers = Array.from({ length: concurrency }, () => claimLoop())
330
+
331
+ let reaperTimer: ReturnType<typeof setInterval> | null = null
332
+ const reaperByDefault = config.reaper === undefined && scheduledJobs.size > 0
333
+ if (reaperByDefault) logger.info('Reaper on: this worker declares schedules (set reaper: false to turn it off)')
334
+ if (reaperByDefault || (config.reaper !== undefined && config.reaper !== false)) {
335
+ const reaperIntervalMs = typeof config.reaper === 'object'
336
+ ? (config.reaper.intervalMs ?? 60_000)
337
+ : 60_000
338
+ reaperTimer = setInterval(() => {
339
+ void queue.reap(queueDb).then(async (reaped) => {
340
+ beat('jobs.reaper', { ok: true }, reaperIntervalMs)
341
+ if (reaped.length === 0) return
342
+ logger.info({ reaped: reaped.length }, `Reaped ${String(reaped.length)} stale job(s)`)
343
+ for (const r of reaped) {
344
+ await emit({
345
+ kind: 'reaped',
346
+ outcome: r.outcome,
347
+ job: { id: r.jobId, type: r.type, attempt: r.attempt, maxAttempts: r.maxAttempts, tenant: r.flow.tenant },
348
+ }, r.flow)
349
+ }
350
+ }).catch((err) => {
351
+ beat('jobs.reaper', { ok: false, error: describeError(err).message }, reaperIntervalMs)
352
+ logger.error({ err }, 'Reaper failed')
353
+ void recordFault('queue-reaper', faultMessage(err))
354
+ })
355
+ }, reaperIntervalMs)
356
+ reaperTimer.unref()
357
+ }
358
+
359
+ return {
360
+ kind: 'worker',
361
+
362
+ fireSchedule(job: string): Promise<string> {
363
+ if (!scheduledJobs.has(job)) {
364
+ return Promise.reject(new Error(
365
+ `[pipework] No schedule declared for job "${job}" on this worker surface.\n\n` +
366
+ ` Declared: ${[...scheduledJobs].join(', ') || '(none)'}\n`,
367
+ ))
368
+ }
369
+ return queue.fireSchedule(db, job)
370
+ },
371
+
372
+ async stop(force?: AbortSignal) {
373
+ logger.info({ inFlight: running.size, deadlineMs: shutdownDeadlineMs }, 'Worker surface draining')
374
+ queue.stopScheduler()
375
+ shouldRun.value = false
376
+ shutdown.abort()
377
+
378
+ if (reaperTimer !== null) {
379
+ clearInterval(reaperTimer)
380
+ reaperTimer = null
381
+ }
382
+
383
+ if (config.listen === true) {
384
+ await queue.stopListener()
385
+ }
386
+
387
+ const deadline = Date.now() + shutdownDeadlineMs
388
+
389
+ // Defense in depth behind the abort signal: a claim loop that
390
+ // somehow ignores cancellation must not wedge stop(). The deadline
391
+ // leg is aborted when the loops win so its timer cannot hold the
392
+ // event loop open after a clean shutdown.
393
+ const deadlineAc = new AbortController()
394
+ const waitEnd = AbortSignal.any(force === undefined ? [deadlineAc.signal] : [deadlineAc.signal, force])
395
+ const loopsExited = await Promise.race([
396
+ Promise.allSettled(workers).then(() => { deadlineAc.abort(); return true }),
397
+ abortableDelay(shutdownDeadlineMs, waitEnd).then(() => false),
398
+ ])
399
+ if (!loopsExited) {
400
+ logger.warn('Claim loops did not exit within the shutdown deadline')
401
+ }
402
+
403
+ while (running.size > 0 && Date.now() < deadline && force?.aborted !== true) {
404
+ logger.info({ inFlight: running.size, remainingMs: deadline - Date.now() }, 'Waiting for in-flight jobs')
405
+ await abortableDelay(Math.min(500, deadline - Date.now()), force)
406
+ }
407
+
408
+ if (running.size > 0) {
409
+ const stranded = [...running.values()]
410
+ logger.warn({ inFlight: stranded.length }, 'Shutdown deadline reached with in-flight jobs; handing them back')
411
+ for (const entry of stranded) {
412
+ entry.endedBy = 'handed-back'
413
+ entry.controller.abort(new Error(
414
+ `[pipework] Job "${entry.job.type}" (${entry.job.id}) aborted: the worker is stopping and handed it back to the queue.`,
415
+ ))
416
+ }
417
+ for (const { job } of stranded) {
418
+ try {
419
+ if (await queue.handBack(queueDb, job.id, job.claimToken)) {
420
+ await emit({ kind: 'handed-back', job: eventJob(job) }, job.flow)
421
+ }
422
+ } catch (err) {
423
+ logger.error({ err, jobId: job.id }, 'Handing back a job failed')
424
+ void recordFault('queue-handback', faultMessage(err), { jobId: job.id })
425
+ }
426
+ }
427
+ } else {
428
+ logger.info('Worker surface drained cleanly')
429
+ }
430
+ for (const release of releaseGraphs) release()
431
+ releaseFaults()
432
+ },
433
+ }
434
+ },
435
+ }
436
+
437
+ return surface
438
+ }
439
+
440
+ function buildDispatchTable(
441
+ jobs: readonly Handler<Record<string, unknown>, unknown>[],
442
+ ): Map<string, Handler<Record<string, unknown>, unknown>> {
443
+ const table = new Map<string, Handler<Record<string, unknown>, unknown>>()
444
+ for (const job of jobs) {
445
+ const jobType = job.meta.jobType
446
+ if (typeof jobType === 'string') {
447
+ table.set(jobType, job)
448
+ }
449
+ }
450
+ return table
451
+ }
452
+
453
+ function validateJobHandlers(jobs: readonly Handler<Record<string, unknown>, unknown>[]): void {
454
+ const seen = new Set<string>()
455
+ for (const job of jobs) {
456
+ if (!job.meta.isJob || job.meta.jobType === null) {
457
+ throw new Error(
458
+ '[pipework] All handlers passed to surface.worker({ jobs }) must be created with .job("type-name").\n\n' +
459
+ ' Example:\n' +
460
+ ' fitting().use().job("send-email").input(schema).fit(handler)\n'
461
+ )
462
+ }
463
+ if (seen.has(job.meta.jobType)) {
464
+ throw new Error(
465
+ `[pipework] Duplicate job type "${job.meta.jobType}" in worker surface.\n\n` +
466
+ ' Each job type must have exactly one handler. If you need multiple handlers,\n' +
467
+ ' create separate worker surfaces.\n'
468
+ )
469
+ }
470
+ seen.add(job.meta.jobType)
471
+ }
472
+ }
473
+
474
+ function validateSchedules(
475
+ schedules: readonly { readonly job: string; readonly cron: string }[],
476
+ jobTypes: readonly string[],
477
+ ): void {
478
+ const seen = new Set<string>()
479
+ for (const s of schedules) {
480
+ if (!jobTypes.includes(s.job)) {
481
+ throw new Error(
482
+ `[pipework] Schedule for job "${s.job}", but this worker surface handles no such job type.\n\n` +
483
+ ` Handled types: ${jobTypes.join(', ') || '(none)'}\n` +
484
+ ' A schedule fires a job this surface runs; put the schedule on the surface that handles it.\n',
485
+ )
486
+ }
487
+ if (seen.has(s.job)) {
488
+ throw new Error(`[pipework] Job "${s.job}" is scheduled twice on this worker surface; a job type has one schedule.\n`)
489
+ }
490
+ seen.add(s.job)
491
+ parseCron(s.cron)
492
+ }
493
+ }
494
+
495
+ function collectWorkerDatabases(config: WorkerSurfaceConfig): string[] {
496
+ const dbs = new Set<string>()
497
+ if (config.database !== undefined) {
498
+ dbs.add(config.database)
499
+ } else {
500
+ dbs.add(DEFAULT_DB)
501
+ }
502
+ for (const job of config.jobs) {
503
+ for (const db of job.meta.databases) {
504
+ dbs.add(db)
505
+ }
506
+ }
507
+ return [...dbs]
508
+ }
509
+
510
+ function collectJobTypes(jobs: readonly Handler<Record<string, unknown>, unknown>[]): string[] {
511
+ const types: string[] = []
512
+ for (const j of jobs) {
513
+ if (typeof j.meta.jobType === 'string') {
514
+ types.push(j.meta.jobType)
515
+ }
516
+ }
517
+ return types
518
+ }
519
+
520
+ function resolvePriorityTimeout(
521
+ claimed: { readonly priority: number },
522
+ handler: Handler<Record<string, unknown>, unknown>,
523
+ priorityTimeouts: Readonly<Record<number, number>>,
524
+ ): number | undefined {
525
+ const fromPriority = priorityTimeouts[claimed.priority]
526
+ if (fromPriority !== undefined) return fromPriority
527
+ return handler.meta.jobTimeout ?? undefined
528
+ }
@@ -0,0 +1,49 @@
1
+ import type { ValidationIssue } from './types.js'
2
+ import type { ResolvedDatabase } from '../config/load.js'
3
+
4
+ export async function validateConnectivity(
5
+ databases: ReadonlyMap<string, ResolvedDatabase>,
6
+ timeoutMs = 5000,
7
+ ): Promise<ValidationIssue[]> {
8
+ const issues: ValidationIssue[] = []
9
+
10
+ const checks = [...databases.entries()].map(async ([name, db]) => {
11
+ try {
12
+ // A platform-mode database is reached at runtime as the platform role; the owner is not dialled.
13
+ await testConnection(db.rls?.platformRole !== undefined && db.systemUrl !== undefined ? db.systemUrl : db.url, timeoutMs)
14
+ } catch (err) {
15
+ issues.push({
16
+ phase: 'database',
17
+ message: `cannot connect to database "${name}": ${err instanceof Error ? err.message : String(err)}`,
18
+ })
19
+ }
20
+ })
21
+
22
+ await Promise.all(checks)
23
+ return issues
24
+ }
25
+
26
+ async function testConnection(url: string, timeoutMs: number): Promise<void> {
27
+ const { default: postgres } = await import('postgres')
28
+
29
+ const sql = postgres(url, {
30
+ max: 1,
31
+ idle_timeout: 1,
32
+ connect_timeout: Math.ceil(timeoutMs / 1000),
33
+ onnotice: () => {},
34
+ })
35
+
36
+ try {
37
+ await sql`SELECT 1`
38
+ } finally {
39
+ await sql.end({ timeout: 1 })
40
+ }
41
+ }
42
+
43
+ /**
44
+ * The probe's `connect_timeout` option. A number pins it; `null` omits it so
45
+ * postgres.js reads PGCONNECT_TIMEOUT (seconds), else its 30 s default.
46
+ */
47
+ export function probeConnectTimeout(timeoutMs: number | null): { connect_timeout?: number } {
48
+ return timeoutMs === null ? {} : { connect_timeout: Math.ceil(timeoutMs / 1000) }
49
+ }