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,200 @@
1
+ import { createPublicKey, verify as cryptoVerify } from 'node:crypto'
2
+ import { sql } from '../../data/db/sql.js'
3
+ import { pipeSystem } from '../../data/db/db.js'
4
+ import { Builder } from '../../request/di/builder.js'
5
+ import { UnauthorizedError, ValidationError } from '../../core/errors/index.js'
6
+ import { getBaseLogger } from '../../core/logging/index.js'
7
+ import type { Handler } from '../../request/di/types.js'
8
+ import { __flowStep } from '../../trace/runtime/flow-step.js'
9
+ import { auditEmail } from './audit.js'
10
+ import { normalizeAddress } from './mail.js'
11
+
12
+ export interface SnsWebhookConfig {
13
+ readonly path: string
14
+ /** The SNS topics whose messages are accepted. SNS signs every topic, so without this anyone could subscribe the endpoint to their own topic. */
15
+ readonly topicArns: readonly string[]
16
+ /** The SNS region; certificate and SubscribeURL hosts must be `sns.<region>.amazonaws.com`. Default: any AWS region. */
17
+ readonly region?: string
18
+ /** Fetches the signing certificate PEM. Default: `fetch`. Tests serve their own. */
19
+ readonly fetchCert?: (url: string) => Promise<string>
20
+ /** Confirms a subscription by GET of its SubscribeURL. Default: `fetch`. */
21
+ readonly confirm?: (url: string) => Promise<void>
22
+ }
23
+
24
+ type SnsMessage = Record<string, string | undefined>
25
+
26
+ const NOTIFICATION_FIELDS = ['Message', 'MessageId', 'Subject', 'Timestamp', 'TopicArn', 'Type']
27
+ const CONFIRMATION_FIELDS = ['Message', 'MessageId', 'SubscribeURL', 'Timestamp', 'Token', 'TopicArn', 'Type']
28
+
29
+ export function snsStringToSign(msg: SnsMessage): string {
30
+ const names = msg['Type'] === 'Notification' ? NOTIFICATION_FIELDS : CONFIRMATION_FIELDS
31
+ return names.filter(n => msg[n] !== undefined).map(n => `${n}\n${msg[n]!}\n`).join('')
32
+ }
33
+
34
+ function isSnsHost(url: string, region: string | undefined): boolean {
35
+ let u: URL
36
+ try { u = new URL(url) } catch { return false }
37
+ if (u.protocol !== 'https:' || u.username !== '' || u.password !== '' || u.port !== '') return false
38
+ const host = region !== undefined ? `sns.${region}.amazonaws.com` : null
39
+ return host !== null ? u.hostname === host : /^sns\.[a-z0-9-]+\.amazonaws\.com$/.test(u.hostname)
40
+ }
41
+
42
+ async function defaultFetchCert(url: string): Promise<string> {
43
+ const res = await fetch(url, { redirect: 'error' })
44
+ if (!res.ok) throw new Error(`certificate fetch answered HTTP ${res.status}`)
45
+ return res.text()
46
+ }
47
+
48
+ async function defaultConfirm(url: string): Promise<void> {
49
+ const res = await fetch(url, { redirect: 'error' })
50
+ if (!res.ok) throw new Error(`subscription confirmation answered HTTP ${res.status}`)
51
+ }
52
+
53
+ function refuse(reason: string): never {
54
+ getBaseLogger().warn({ event: 'sns message refused', reason }, 'SNS message refused')
55
+ throw new UnauthorizedError('SNS message verification failed.\n')
56
+ }
57
+
58
+ interface Recipient { address: string; bounceType: string | null; kind: 'bounce' | 'complaint' }
59
+
60
+ function recipientsOf(event: Record<string, unknown>): { recipients: Recipient[]; sesMessageId: string | null } {
61
+ const type = String(event['eventType'] ?? event['notificationType'] ?? '').toLowerCase()
62
+ const mailPart = event['mail'] as { messageId?: unknown } | undefined
63
+ const sesMessageId = typeof mailPart?.messageId === 'string' ? mailPart.messageId : null
64
+ const list = (v: unknown): string[] =>
65
+ Array.isArray(v) ? (v as ({ emailAddress?: unknown } | null)[]).flatMap(r => (typeof r?.emailAddress === 'string' ? [r.emailAddress] : [])) : []
66
+ if (type === 'bounce') {
67
+ const bounce = event['bounce'] as { bounceType?: unknown; bouncedRecipients?: unknown } | undefined
68
+ const bounceType = typeof bounce?.bounceType === 'string' ? bounce.bounceType : null
69
+ return { recipients: list(bounce?.bouncedRecipients).map(address => ({ address, bounceType, kind: 'bounce' as const })), sesMessageId }
70
+ }
71
+ if (type === 'complaint') {
72
+ const complaint = event['complaint'] as { complainedRecipients?: unknown } | undefined
73
+ return { recipients: list(complaint?.complainedRecipients).map(address => ({ address, bounceType: null, kind: 'complaint' as const })), sesMessageId }
74
+ }
75
+ return { recipients: [], sesMessageId }
76
+ }
77
+
78
+ const processEvent = __flowStep({ ref: 'pipework/email:notification' }, async (event: Record<string, unknown>): Promise<void> => {
79
+ const { recipients, sesMessageId } = recipientsOf(event)
80
+ const db = pipeSystem()
81
+ for (const r of recipients) {
82
+ const address = normalizeAddress(r.address)
83
+ const found = sesMessageId === null ? [] : await db.execute(sql`
84
+ SELECT id, trace_id FROM pipework_email_messages WHERE provider_message_id = ${sesMessageId}`)
85
+ const messageId = (found[0]?.['id'] as string | undefined) ?? null
86
+ const messageTraceId = (found[0]?.['trace_id'] as string | undefined) ?? null
87
+ const permanent = r.kind === 'complaint' || r.bounceType === 'Permanent'
88
+ if (permanent) {
89
+ await db.execute(sql`
90
+ INSERT INTO pipework_email_suppressions (address, reason, detail)
91
+ VALUES (${address}, ${r.kind}, ${JSON.stringify({ bounceType: r.bounceType, sesMessageId })}::jsonb)
92
+ ON CONFLICT (address) DO UPDATE SET reason = EXCLUDED.reason, detail = EXCLUDED.detail`)
93
+ getBaseLogger().warn({ event: 'email suppressed', address, reason: r.kind }, 'email suppressed')
94
+ } else {
95
+ getBaseLogger().info({ event: 'email bounce not suppressed', address, bounceType: r.bounceType }, 'transient bounce: address kept')
96
+ }
97
+ await auditEmail(r.kind, messageId, { address, bounceType: r.bounceType, suppressed: permanent, messageTraceId })
98
+ }
99
+ })
100
+
101
+ export interface SnsHandlerConfig {
102
+ readonly path: string
103
+ readonly topicArns: readonly string[]
104
+ readonly region?: string
105
+ readonly fetchCert?: (url: string) => Promise<string>
106
+ readonly confirm?: (url: string) => Promise<void>
107
+ }
108
+
109
+ /**
110
+ * The shared SNS endpoint: parses the body, verifies the signature (SignatureVersion 1 or 2) against the
111
+ * certificate at `SigningCertURL`, checks `TopicArn` against `topicArns`, confirms subscriptions, and
112
+ * passes each verified `Notification`'s `Message` text to `onNotification`. Anything unverified is a 401
113
+ * before `onNotification` runs. Used by `snsWebhook` and the inbound mail webhook.
114
+ */
115
+ export function snsHandler(
116
+ config: SnsHandlerConfig,
117
+ onNotification: (message: string) => Promise<Record<string, unknown> | void>,
118
+ ): Handler<Record<string, unknown>, unknown> {
119
+ if (config.topicArns.length === 0) throw new Error('[pipework] An SNS webhook needs at least one topic ARN in topicArns.')
120
+ const fetchCert = config.fetchCert ?? defaultFetchCert
121
+ const confirm = config.confirm ?? defaultConfirm
122
+ const certs = new Map<string, string>()
123
+
124
+ async function verifySignature(msg: SnsMessage): Promise<void> {
125
+ const url = msg['SigningCertURL']
126
+ const signature = msg['Signature']
127
+ const version = msg['SignatureVersion']
128
+ if (url === undefined || signature === undefined) refuse('missing signature fields')
129
+ if (version !== '1' && version !== '2') refuse('unknown SignatureVersion')
130
+ if (!isSnsHost(url, config.region) || !new URL(url).pathname.endsWith('.pem')) refuse('SigningCertURL is not an SNS certificate URL')
131
+ let pem = certs.get(url)
132
+ if (pem === undefined) {
133
+ try { pem = await fetchCert(url) } catch { refuse('certificate could not be fetched') }
134
+ certs.set(url, pem)
135
+ }
136
+ let ok = false
137
+ try {
138
+ ok = cryptoVerify(version === '1' ? 'RSA-SHA1' : 'RSA-SHA256', Buffer.from(snsStringToSign(msg)), createPublicKey(pem), Buffer.from(signature, 'base64'))
139
+ } catch { ok = false }
140
+ if (!ok) refuse('signature does not match')
141
+ if (msg['TopicArn'] === undefined || !config.topicArns.includes(msg['TopicArn'])) refuse('TopicArn is not a configured topic')
142
+ }
143
+
144
+ return new Builder()
145
+ .public()
146
+ .rawBody()
147
+ .request()
148
+ .route('POST', config.path)
149
+ .fit(async ({ rawBody }) => {
150
+ let parsed: unknown
151
+ try {
152
+ parsed = JSON.parse(rawBody.toString('utf-8'))
153
+ } catch {
154
+ throw new ValidationError({ body: ['is not JSON'] }, 'SNS message is not JSON.')
155
+ }
156
+ if (typeof parsed !== 'object' || parsed === null) throw new ValidationError({ body: ['is not an object'] }, 'SNS message is not an object.')
157
+ const msg = parsed as SnsMessage
158
+ await verifySignature(msg)
159
+
160
+ if (msg['Type'] === 'SubscriptionConfirmation') {
161
+ const subscribeUrl = msg['SubscribeURL']
162
+ if (subscribeUrl === undefined || !isSnsHost(subscribeUrl, config.region)) refuse('SubscribeURL is not an SNS URL')
163
+ await confirm(subscribeUrl)
164
+ return { status: 200, body: { ok: true, confirmed: true } }
165
+ }
166
+ if (msg['Type'] === 'Notification') {
167
+ const extra = await onNotification(msg['Message'] ?? '')
168
+ return { status: 200, body: { ok: true, ...(extra ?? {}) } }
169
+ }
170
+ return { status: 200, body: { ok: true } }
171
+ })
172
+ }
173
+
174
+ /**
175
+ * Receives SES bounce and complaint events delivered through SNS (HTTP/S subscription).
176
+ * A permanent bounce or any complaint puts the address (lowercased) on
177
+ * `pipework_email_suppressions`, which the send job checks; a transient or undetermined
178
+ * bounce is only logged. Each one is a trace step with an `Email:bounce` / `Email:complaint`
179
+ * audit record whose `after` carries `messageTraceId`, the trace of the original send.
180
+ * A suppression is logged at warn as `email suppressed`; an app raises an alert for it with `alerts.create(...).raise`.
181
+ *
182
+ * Only messages from the configured `topicArns` are accepted (401 otherwise, before any subscription is
183
+ * confirmed or event processed): SNS signs every topic, including an attacker's own.
184
+ * Every message's signature (SignatureVersion 1 or 2) is checked against the certificate
185
+ * at `SigningCertURL`, which must be `https://sns.<region>.amazonaws.com/...`; a failure is a 401.
186
+ * A SubscriptionConfirmation is confirmed by fetching its `SubscribeURL`.
187
+ * SES events delivered through EventBridge are not supported.
188
+ */
189
+ export function snsWebhook(config: SnsWebhookConfig): Handler<Record<string, unknown>, unknown> {
190
+ if (config.topicArns.length === 0) throw new Error('[pipework] mail.snsWebhook needs at least one topic ARN in topicArns.')
191
+ return snsHandler(config, async (message) => {
192
+ let event: Record<string, unknown>
193
+ try {
194
+ event = JSON.parse(message) as Record<string, unknown>
195
+ } catch {
196
+ return { ignored: true }
197
+ }
198
+ await processEvent(event)
199
+ })
200
+ }
@@ -0,0 +1,44 @@
1
+ import { createHmac, timingSafeEqual } from 'node:crypto'
2
+
3
+ /** A list name: what `SendInput.list` accepts. */
4
+ export const LIST_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/i
5
+ export const MIN_UNSUBSCRIBE_SECRET = 32
6
+
7
+ const DOMAIN = 'pipework mail unsubscribe\0'
8
+
9
+ const b64 = (data: Buffer | string): string => Buffer.from(data).toString('base64url')
10
+
11
+ function signature(secret: string, payload: string): Buffer {
12
+ return createHmac('sha256', secret).update(DOMAIN + payload).digest()
13
+ }
14
+
15
+ /** The signed token naming a normalized address and a list; it never expires. */
16
+ export function signUnsubscribeToken(secret: string, address: string, list: string): string {
17
+ const payload = b64(JSON.stringify({ a: address, l: list }))
18
+ return `${payload}.${b64(signature(secret, payload))}`
19
+ }
20
+
21
+ /** The address and list a token names, or null for anything malformed, tampered or signed with another secret. Never throws. */
22
+ export function verifyUnsubscribeToken(secret: string, token: unknown): { readonly address: string; readonly list: string } | null {
23
+ if (typeof token !== 'string') return null
24
+ const parts = token.split('.')
25
+ if (parts.length !== 2) return null
26
+ const [payload, given] = parts as [string, string]
27
+ const expected = signature(secret, payload)
28
+ const got = Buffer.from(given, 'base64url')
29
+ if (got.length !== expected.length || !timingSafeEqual(got, expected)) return null
30
+ try {
31
+ const parsed = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')) as { a?: unknown; l?: unknown }
32
+ if (typeof parsed.a !== 'string' || typeof parsed.l !== 'string' || parsed.a === '' || !LIST_RE.test(parsed.l)) return null
33
+ return { address: parsed.a, list: parsed.l }
34
+ } catch {
35
+ return null
36
+ }
37
+ }
38
+
39
+ /** The unsubscribe link for a token: `url` with `t` set. */
40
+ export function unsubscribeLink(url: string, token: string): string {
41
+ const u = new URL(url)
42
+ u.searchParams.set('t', token)
43
+ return u.toString()
44
+ }
@@ -0,0 +1,88 @@
1
+ import { isIP } from 'node:net'
2
+
3
+ type Cidr4 = readonly [number, number]
4
+
5
+ /** IPv4 ranges a stranger's URL may not reach: [first address as uint32, prefix length]. */
6
+ const REFUSED_V4: readonly Cidr4[] = [
7
+ [0x00000000, 8], [0x0a000000, 8], [0x64400000, 10], [0x7f000000, 8], [0xa9fe0000, 16],
8
+ [0xac100000, 12], [0xc0000000, 24], [0xc0a80000, 16], [0xc6120000, 15], [0xe0000000, 4], [0xf0000000, 4],
9
+ ]
10
+
11
+ function parseV4(text: string): number[] | null {
12
+ const parts = text.split('.')
13
+ if (parts.length !== 4) return null
14
+ const out: number[] = []
15
+ for (const p of parts) {
16
+ if (!/^\d{1,3}$/.test(p)) return null
17
+ const n = Number(p)
18
+ if (n > 255) return null
19
+ out.push(n)
20
+ }
21
+ return out
22
+ }
23
+
24
+ /** The 16 bytes of an IPv6 address (dotted tail and zone id allowed), or null when it is not one. */
25
+ function parseV6(input: string): number[] | null {
26
+ let text = input
27
+ const zone = text.indexOf('%')
28
+ if (zone !== -1) text = text.slice(0, zone)
29
+ let tail: number[] = []
30
+ const lastColon = text.lastIndexOf(':')
31
+ if (text.includes('.')) {
32
+ const v4 = parseV4(text.slice(lastColon + 1))
33
+ if (v4 === null) return null
34
+ tail = v4
35
+ text = text.slice(0, lastColon + 1) + '0:0'
36
+ }
37
+ const halves = text.split('::')
38
+ if (halves.length > 2) return null
39
+ const groups = (s: string): string[] => (s === '' ? [] : s.split(':'))
40
+ const head = groups(halves[0]!)
41
+ const rest = halves.length === 2 ? groups(halves[1]!) : []
42
+ const missing = 8 - head.length - rest.length
43
+ if (halves.length === 2 ? missing < 1 : missing !== 0) return null
44
+ const all = [...head, ...(halves.length === 2 ? new Array<string>(missing).fill('0') : []), ...rest]
45
+ const bytes: number[] = []
46
+ for (const g of all) {
47
+ if (!/^[0-9a-f]{1,4}$/i.test(g)) return null
48
+ const n = parseInt(g, 16)
49
+ bytes.push(n >> 8, n & 0xff)
50
+ }
51
+ if (tail.length === 4) bytes.splice(12, 4, ...tail)
52
+ return bytes
53
+ }
54
+
55
+ function v4Refused(b: readonly number[]): boolean {
56
+ const n = ((b[0]! << 24) | (b[1]! << 16) | (b[2]! << 8) | b[3]!) >>> 0
57
+ return REFUSED_V4.some(([base, len]) => {
58
+ const mask = len === 0 ? 0 : (0xffffffff << (32 - len)) >>> 0
59
+ return ((n & mask) >>> 0) === base
60
+ })
61
+ }
62
+
63
+ /**
64
+ * True when `address` (IPv4 or IPv6 text, no brackets) is a public address the fetch may connect to.
65
+ * Anything that is not an IP address, and every private, loopback, link-local, CGNAT, unique-local, site-local,
66
+ * multicast, reserved, unspecified, documentation or IPv4-embedded form of those, is not public.
67
+ */
68
+ export function isPublicAddress(address: string): boolean {
69
+ const kind = isIP(address)
70
+ if (kind === 4) {
71
+ const b = parseV4(address)
72
+ return b !== null && !v4Refused(b)
73
+ }
74
+ if (kind !== 6) return false
75
+ const b = parseV6(address)
76
+ if (b === null) return false
77
+ const first96Zero = b.slice(0, 12).every(x => x === 0)
78
+ if (first96Zero) return false // ::, ::1 and the deprecated IPv4-compatible block
79
+ const mapped = b.slice(0, 10).every(x => x === 0) && b[10] === 0xff && b[11] === 0xff
80
+ const nat64 = b[0] === 0 && b[1] === 0x64 && b[2] === 0xff && b[3] === 0x9b && b.slice(4, 12).every(x => x === 0)
81
+ if (mapped || nat64) return !v4Refused(b.slice(12, 16))
82
+ const hi = b[0]!
83
+ if (hi === 0xff) return false // ff00::/8
84
+ if ((hi & 0xfe) === 0xfc) return false // fc00::/7
85
+ if (hi === 0xfe && (b[1]! & 0xc0) >= 0x80) return false // fe80::/10 and fec0::/10
86
+ if (hi === 0x20 && b[1] === 0x01 && b[2] === 0x0d && b[3] === 0xb8) return false // 2001:db8::/32
87
+ return true
88
+ }
@@ -0,0 +1,87 @@
1
+ import { getBaseLogger } from '../../core/logging/index.js'
2
+
3
+ export interface ConnectRetryOptions {
4
+ /** Waits between tries; the number of tries is delaysMs.length + 1. Default [5_000, 15_000, 40_000] (4 tries over ~60 s). */
5
+ delaysMs?: readonly number[]
6
+ /** Stops waiting and throws the last fault when aborted (checked before each try and during each wait). */
7
+ signal?: AbortSignal
8
+ /** Called before each wait with the fault, the 1-based try that failed, and the wait. */
9
+ onRetry?: (info: { error: unknown; attempt: number; delayMs: number }) => void
10
+ }
11
+
12
+ const DEFAULT_DELAYS: readonly number[] = [5_000, 15_000, 40_000]
13
+ const MAX_DEPTH = 8
14
+
15
+ const UNSENT_CODES = new Set(['UND_ERR_CONNECT_TIMEOUT', 'EAI_AGAIN', 'ENOTFOUND', 'ECONNREFUSED', 'ENETUNREACH', 'EHOSTUNREACH'])
16
+ const SENT_CODES = new Set(['ECONNRESET', 'UND_ERR_SOCKET', 'EPIPE', 'UND_ERR_HEADERS_TIMEOUT', 'UND_ERR_BODY_TIMEOUT'])
17
+
18
+ /** The first unsent-phase code found in the error's chain, or null when it is not an unsent fault. */
19
+ function unsentCode(err: unknown): string | null {
20
+ const state: { found: string | null; sent: boolean } = { found: null, sent: false }
21
+ const seen = new Set<unknown>()
22
+ const walk = (node: unknown, depth: number): void => {
23
+ if (typeof node !== 'object' || node === null || depth > MAX_DEPTH || seen.has(node)) return
24
+ seen.add(node)
25
+ const { code, name, cause, errors } = node as { code?: unknown; name?: unknown; cause?: unknown; errors?: unknown }
26
+ if (name === 'AbortError' || name === 'TimeoutError') state.sent = true
27
+ if (typeof code === 'string') {
28
+ if (SENT_CODES.has(code)) state.sent = true
29
+ else if (UNSENT_CODES.has(code) && state.found === null) state.found = code
30
+ }
31
+ walk(cause, depth + 1)
32
+ if (Array.isArray(errors)) for (const e of errors) walk(e, depth + 1)
33
+ }
34
+ walk(err, 0)
35
+ return state.sent ? null : state.found
36
+ }
37
+
38
+ /** True when the fault happened while connecting (nothing was sent), so the request can be sent again safely. */
39
+ export function isUnsentFault(err: unknown): boolean {
40
+ return unsentCode(err) !== null
41
+ }
42
+
43
+ const aborted = (signal: AbortSignal | undefined): boolean => signal?.aborted === true
44
+
45
+ function wait(ms: number, signal: AbortSignal | undefined): Promise<void> {
46
+ return new Promise<void>((resolve) => {
47
+ if (signal?.aborted === true) { resolve(); return }
48
+ const done = (): void => { clearTimeout(timer); signal?.removeEventListener('abort', done); resolve() }
49
+ const timer = setTimeout(done, ms)
50
+ signal?.addEventListener('abort', done, { once: true })
51
+ })
52
+ }
53
+
54
+ /**
55
+ * Runs `send`, and sends again after a wait when it failed in the connect phase. Any other fault is thrown at once.
56
+ * When the tries run out, or `signal` aborts, the last fault is thrown unchanged.
57
+ */
58
+ export async function retryUnsent<R>(send: () => Promise<R>, opts: ConnectRetryOptions = {}): Promise<R> {
59
+ const delays = opts.delaysMs ?? DEFAULT_DELAYS
60
+ for (let attempt = 1; ; attempt++) {
61
+ try {
62
+ return await send()
63
+ } catch (error) {
64
+ const code = unsentCode(error)
65
+ const delayMs = delays[attempt - 1]
66
+ if (code === null || delayMs === undefined || aborted(opts.signal)) throw error
67
+ getBaseLogger().warn({ code, attempt, delayMs }, 'connect failed before anything was sent; trying again')
68
+ opts.onRetry?.({ error, attempt, delayMs })
69
+ await wait(delayMs, opts.signal)
70
+ if (aborted(opts.signal)) throw error
71
+ }
72
+ }
73
+ }
74
+
75
+ /** `fetch` that is tried again only when the connection could not be made. A response of any status is returned. */
76
+ export function fetchRetryingUnsent(
77
+ input: string | URL | Request,
78
+ init?: RequestInit,
79
+ opts: ConnectRetryOptions & { fetch?: typeof fetch } = {},
80
+ ): Promise<Response> {
81
+ const doFetch = opts.fetch ?? fetch
82
+ const signal = opts.signal ?? init?.signal ?? undefined
83
+ return retryUnsent(
84
+ () => doFetch(input instanceof Request ? input.clone() : input, init),
85
+ { ...opts, ...(signal !== undefined ? { signal } : {}) },
86
+ )
87
+ }