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,447 @@
1
+ // A hand-written MIME parser for received mail. Pure: bytes in, a plain object out.
2
+ // It never throws; malformed input degrades to the best reading. Internal to the
3
+ // inbound pipeline (not public API); exported for its tests.
4
+ //
5
+ // The message is held as a latin1 string (one char per byte) while it is split, so
6
+ // offsets are byte offsets and slices convert back losslessly; charsets are decoded
7
+ // only on the final text of a header or body.
8
+
9
+ export interface ParsedAddress {
10
+ readonly address: string
11
+ readonly name: string
12
+ }
13
+
14
+ export interface ParsedAttachment {
15
+ readonly filename: string | null
16
+ readonly mediaType: string
17
+ readonly size: number
18
+ readonly contentId: string | null
19
+ readonly inline: boolean
20
+ readonly content: Buffer
21
+ }
22
+
23
+ export interface ParsedMessage {
24
+ /** Message-ID without angle brackets, or null. */
25
+ readonly messageId: string | null
26
+ /** The first In-Reply-To id without angle brackets, or null. */
27
+ readonly inReplyTo: string | null
28
+ /** In-Reply-To ids (first) then References ids, without angle brackets, duplicates removed. */
29
+ readonly threadIds: readonly string[]
30
+ /** References ids without angle brackets, in order. */
31
+ readonly references: readonly string[]
32
+ /** The author, or null when there is no readable From or the author is ambiguous (see `fromProblem`). */
33
+ readonly from: ParsedAddress | null
34
+ /**
35
+ * Why `from` is null for a message that had a From: several From fields, or several mailboxes in one.
36
+ * Null otherwise. RFC 7489 section 6.6.1: verifiers differ in which one they judge, so none is named.
37
+ */
38
+ readonly fromProblem: 'several-from-fields' | 'several-mailboxes' | null
39
+ readonly to: readonly ParsedAddress[]
40
+ readonly cc: readonly ParsedAddress[]
41
+ readonly replyTo: readonly ParsedAddress[]
42
+ readonly subject: string
43
+ readonly date: Date | null
44
+ readonly text: string | null
45
+ readonly html: string | null
46
+ readonly attachments: readonly ParsedAttachment[]
47
+ }
48
+
49
+ const MAX_DEPTH = 20
50
+
51
+ interface Header { readonly name: string; readonly value: string }
52
+
53
+ interface Entity {
54
+ readonly headers: readonly Header[]
55
+ /** Body, still transfer-encoded, as latin1. */
56
+ readonly body: string
57
+ }
58
+
59
+ function header(entity: Entity, name: string): string | undefined {
60
+ return entity.headers.find(h => h.name === name)?.value
61
+ }
62
+
63
+ function headers(entity: Entity, name: string): string[] {
64
+ return entity.headers.filter(h => h.name === name).map(h => h.value)
65
+ }
66
+
67
+ function splitEntity(raw: string): Entity {
68
+ let at = -1
69
+ let bodyAt = 0
70
+ if (raw.startsWith('\r\n')) { at = 0; bodyAt = 2 } else if (raw.startsWith('\n')) { at = 0; bodyAt = 1 } else {
71
+ const m = /\r?\n\r?\n/.exec(raw)
72
+ if (m !== null) { at = m.index; bodyAt = m.index + m[0].length }
73
+ }
74
+ const head = at < 0 ? raw : raw.slice(0, at)
75
+ const body = at < 0 ? '' : raw.slice(bodyAt)
76
+ const headers: Header[] = []
77
+ let current: { name: string; value: string } | null = null
78
+ for (const line of head.split(/\r?\n/)) {
79
+ if (/^[ \t]/.test(line)) {
80
+ if (current !== null) current.value += ` ${line.trim()}`
81
+ continue
82
+ }
83
+ const colon = line.indexOf(':')
84
+ if (colon <= 0 || /[\s\x00-\x1f]/.test(line.slice(0, colon))) { current = null; continue }
85
+ current = { name: line.slice(0, colon).toLowerCase(), value: line.slice(colon + 1).trim() }
86
+ headers.push(current)
87
+ }
88
+ // A header with 8-bit bytes is UTF-8 when it is valid UTF-8, otherwise latin1 as read.
89
+ return { headers: headers.map(h => ({ name: h.name, value: decodeRawHeader(h.value) })), body }
90
+ }
91
+
92
+ function decodeRawHeader(value: string): string {
93
+ if (!/[^\x00-\x7f]/.test(value)) return value
94
+ try {
95
+ return new TextDecoder('utf-8', { fatal: true }).decode(Buffer.from(value, 'latin1'))
96
+ } catch {
97
+ return value
98
+ }
99
+ }
100
+
101
+ function decodeCharset(bytes: Buffer, charset: string | undefined): string {
102
+ const label = (charset ?? 'utf-8').trim().replace(/^"|"$/g, '').toLowerCase()
103
+ try {
104
+ return new TextDecoder(label === '' ? 'utf-8' : label).decode(bytes)
105
+ } catch {
106
+ return new TextDecoder('utf-8').decode(bytes)
107
+ }
108
+ }
109
+
110
+ // ---- RFC 2047 encoded words ----
111
+
112
+ function decodeQ(text: string): Buffer {
113
+ const bytes: number[] = []
114
+ for (let i = 0; i < text.length; i++) {
115
+ const c = text[i]!
116
+ if (c === '_') bytes.push(0x20)
117
+ else if (c === '=' && /^[0-9a-fA-F]{2}$/.test(text.slice(i + 1, i + 3))) { bytes.push(parseInt(text.slice(i + 1, i + 3), 16)); i += 2 } else bytes.push(c.charCodeAt(0) & 0xff)
118
+ }
119
+ return Buffer.from(bytes)
120
+ }
121
+
122
+ const ENCODED_WORD = /=\?([^?\s]+)\?([bBqQ])\?([^?\s]*)\?=/g
123
+
124
+ /** Decodes RFC 2047 encoded words in `text`. Adjacent words are joined without the whitespace between them, and words in the same charset are decoded together so a multibyte character split across words survives. */
125
+ export function decodeWords(text: string): string {
126
+ if (!text.includes('=?')) return text
127
+ const out: string[] = []
128
+ let run: { charset: string; bytes: Buffer[] } | null = null
129
+ const flush = (): void => {
130
+ if (run !== null) out.push(decodeCharset(Buffer.concat(run.bytes), run.charset))
131
+ run = null
132
+ }
133
+ let last = 0
134
+ for (const m of text.matchAll(ENCODED_WORD)) {
135
+ const between = text.slice(last, m.index)
136
+ if (run !== null && between.trim() === '') {
137
+ // whitespace between adjacent encoded words is dropped
138
+ } else {
139
+ flush()
140
+ out.push(between)
141
+ }
142
+ const charset = m[1]!.split('*')[0]!.toLowerCase()
143
+ const bytes = m[2]!.toLowerCase() === 'b' ? Buffer.from(m[3]!, 'base64') : decodeQ(m[3]!)
144
+ if (run !== null && (run as { charset: string }).charset !== charset) flush()
145
+ if (run === null) run = { charset, bytes: [] }
146
+ ;(run as { bytes: Buffer[] }).bytes.push(bytes)
147
+ last = m.index + m[0].length
148
+ }
149
+ flush()
150
+ out.push(text.slice(last))
151
+ return out.join('')
152
+ }
153
+
154
+ // ---- structured header values ----
155
+
156
+ function unquote(text: string): string {
157
+ const t = text.trim()
158
+ if (t.length >= 2 && t.startsWith('"') && t.endsWith('"')) return t.slice(1, -1).replace(/\\(.)/g, '$1')
159
+ return t
160
+ }
161
+
162
+ interface Structured { readonly value: string; readonly params: ReadonlyMap<string, string> }
163
+
164
+ function splitTop(text: string, separator: string): string[] {
165
+ const parts: string[] = []
166
+ let cur = ''
167
+ let quoted = false
168
+ for (let i = 0; i < text.length; i++) {
169
+ const c = text[i]!
170
+ if (quoted) {
171
+ cur += c
172
+ if (c === '\\' && i + 1 < text.length) cur += text[++i]!
173
+ else if (c === '"') quoted = false
174
+ continue
175
+ }
176
+ if (c === '"') { quoted = true; cur += c; continue }
177
+ if (c === separator) { parts.push(cur); cur = ''; continue }
178
+ cur += c
179
+ }
180
+ parts.push(cur)
181
+ return parts
182
+ }
183
+
184
+ function percentBytes(text: string): Buffer {
185
+ const bytes: number[] = []
186
+ for (let i = 0; i < text.length; i++) {
187
+ if (text[i] === '%' && /^[0-9a-fA-F]{2}$/.test(text.slice(i + 1, i + 3))) { bytes.push(parseInt(text.slice(i + 1, i + 3), 16)); i += 2 } else bytes.push(text.charCodeAt(i) & 0xff)
188
+ }
189
+ return Buffer.from(bytes)
190
+ }
191
+
192
+ /** Parses `value; name=param; name*=utf-8''enc; name*0*=...` and resolves RFC 2231 extended and continued parameters. */
193
+ function parseStructured(text: string | undefined): Structured {
194
+ if (text === undefined) return { value: '', params: new Map() }
195
+ const [first, ...rest] = splitTop(text, ';')
196
+ const plain = new Map<string, string>()
197
+ const groups = new Map<string, { n: number; ext: boolean; value: string }[]>()
198
+ for (const part of rest) {
199
+ const eq = part.indexOf('=')
200
+ if (eq <= 0) continue
201
+ const key = part.slice(0, eq).trim().toLowerCase()
202
+ const value = unquote(part.slice(eq + 1))
203
+ const m = /^([^*]+)(?:\*(\d+))?(\*)?$/.exec(key)
204
+ if (m === null || (m[2] === undefined && m[3] === undefined)) { plain.set(key, value); continue }
205
+ const list = groups.get(m[1]!) ?? []
206
+ list.push({ n: m[2] === undefined ? 0 : Number(m[2]), ext: m[3] !== undefined, value })
207
+ groups.set(m[1]!, list)
208
+ }
209
+ const params = new Map(plain)
210
+ for (const [name, list] of groups) {
211
+ list.sort((a, b) => a.n - b.n)
212
+ let charset: string | undefined
213
+ const chunks: Buffer[] = []
214
+ list.forEach((seg, i) => {
215
+ let v = seg.value
216
+ if (i === 0 && seg.ext) {
217
+ const m = /^([^']*)'[^']*'(.*)$/s.exec(v)
218
+ if (m !== null) { charset = m[1] === '' ? undefined : m[1]; v = m[2]! }
219
+ }
220
+ chunks.push(seg.ext ? percentBytes(v) : Buffer.from(v, 'latin1'))
221
+ })
222
+ params.set(name, decodeCharset(Buffer.concat(chunks), charset))
223
+ }
224
+ return { value: (first ?? '').trim(), params }
225
+ }
226
+
227
+ // ---- addresses ----
228
+
229
+ /** Parses an address list (RFC 5322 mailboxes, groups, quoted names, comments, encoded words). */
230
+ export function parseAddresses(text: string | undefined): ParsedAddress[] {
231
+ if (text === undefined || text.trim() === '') return []
232
+ const mailboxes: string[] = []
233
+ let cur = ''
234
+ let quoted = false
235
+ let angle = false
236
+ let comment = 0
237
+ for (let i = 0; i < text.length; i++) {
238
+ const c = text[i]!
239
+ if (quoted) {
240
+ cur += c
241
+ if (c === '\\' && i + 1 < text.length) cur += text[++i]!
242
+ else if (c === '"') quoted = false
243
+ continue
244
+ }
245
+ if (comment > 0) {
246
+ cur += c
247
+ if (c === '\\' && i + 1 < text.length) cur += text[++i]!
248
+ else if (c === '(') comment++
249
+ else if (c === ')') comment--
250
+ continue
251
+ }
252
+ if (c === '"') { quoted = true; cur += c; continue }
253
+ if (c === '(') { comment = 1; cur += c; continue }
254
+ if (c === '<') angle = true
255
+ else if (c === '>') angle = false
256
+ if (!angle && c === ':') { cur = ''; continue } // group name
257
+ if (!angle && (c === ',' || c === ';')) { mailboxes.push(cur); cur = ''; continue }
258
+ cur += c
259
+ }
260
+ mailboxes.push(cur)
261
+
262
+ const result: ParsedAddress[] = []
263
+ for (const box of mailboxes) {
264
+ const parsed = parseMailbox(box)
265
+ if (parsed !== null) result.push(parsed)
266
+ }
267
+ return result
268
+ }
269
+
270
+ function stripComments(text: string): { text: string; comments: string[] } {
271
+ const comments: string[] = []
272
+ let out = ''
273
+ let depth = 0
274
+ let quoted = false
275
+ let current = ''
276
+ for (let i = 0; i < text.length; i++) {
277
+ const c = text[i]!
278
+ if (quoted) {
279
+ out += c
280
+ if (c === '\\' && i + 1 < text.length) out += text[++i]!
281
+ else if (c === '"') quoted = false
282
+ continue
283
+ }
284
+ if (depth > 0) {
285
+ if (c === '\\' && i + 1 < text.length) { current += text[++i]!; continue }
286
+ if (c === '(') depth++
287
+ else if (c === ')') { depth--; if (depth === 0) { comments.push(current); current = ''; continue } }
288
+ current += c
289
+ continue
290
+ }
291
+ if (c === '"') { quoted = true; out += c; continue }
292
+ if (c === '(') { depth = 1; continue }
293
+ out += c
294
+ }
295
+ return { text: out, comments }
296
+ }
297
+
298
+ function parseMailbox(box: string): ParsedAddress | null {
299
+ const { text, comments } = stripComments(box)
300
+ const t = text.trim()
301
+ if (t === '') return null
302
+ const lt = t.lastIndexOf('<')
303
+ const gt = t.lastIndexOf('>')
304
+ if (lt >= 0 && gt > lt) {
305
+ const address = t.slice(lt + 1, gt).trim().replace(/^.*:/, '')
306
+ const name = decodeWords(unquote(t.slice(0, lt)))
307
+ return address === '' ? null : { address, name }
308
+ }
309
+ const address = unquote(t)
310
+ if (!address.includes('@')) return null
311
+ return { address, name: comments.length > 0 ? decodeWords(comments[0]!.trim()) : '' }
312
+ }
313
+
314
+ function idsOf(text: string | undefined): string[] {
315
+ if (text === undefined) return []
316
+ return [...text.matchAll(/<([^<>\s]+)>/g)].map(m => m[1]!)
317
+ }
318
+
319
+ // ---- bodies ----
320
+
321
+ function decodeTransfer(body: string, encoding: string | undefined): Buffer {
322
+ const e = (encoding ?? '7bit').trim().toLowerCase()
323
+ if (e === 'base64') return Buffer.from(body.replace(/[^A-Za-z0-9+/=_-]/g, ''), 'base64')
324
+ if (e === 'quoted-printable') {
325
+ const joined = body.replace(/[ \t]+(?=\r?\n)/g, '').replace(/=\r?\n/g, '')
326
+ const bytes: number[] = []
327
+ for (let i = 0; i < joined.length; i++) {
328
+ const c = joined[i]!
329
+ if (c === '=' && /^[0-9a-fA-F]{2}$/.test(joined.slice(i + 1, i + 3))) { bytes.push(parseInt(joined.slice(i + 1, i + 3), 16)); i += 2 } else bytes.push(joined.charCodeAt(i) & 0xff)
330
+ }
331
+ return Buffer.from(bytes)
332
+ }
333
+ return Buffer.from(body, 'latin1')
334
+ }
335
+
336
+ function splitMultipart(body: string, boundary: string): string[] {
337
+ const delim = `--${boundary}`
338
+ const parts: string[] = []
339
+ let start = -1
340
+ let pos = 0
341
+ for (;;) {
342
+ const idx = body.indexOf(delim, pos)
343
+ if (idx < 0) break
344
+ pos = idx + 1
345
+ if (idx > 0 && body[idx - 1] !== '\n') continue
346
+ const after = idx + delim.length
347
+ const eol = body.indexOf('\n', after)
348
+ const rest = body.slice(after, eol < 0 ? body.length : eol)
349
+ const closing = rest.startsWith('--')
350
+ if (!closing && !/^[ \t]*\r?$/.test(rest)) continue
351
+ if (start >= 0) {
352
+ let end = idx
353
+ if (body[end - 1] === '\n') end--
354
+ if (body[end - 1] === '\r') end--
355
+ parts.push(body.slice(start, Math.max(start, end)))
356
+ start = -1
357
+ }
358
+ if (closing || eol < 0) return parts
359
+ start = eol + 1
360
+ pos = start
361
+ }
362
+ if (start >= 0) parts.push(body.slice(start)) // no closing boundary
363
+ return parts
364
+ }
365
+
366
+ interface Collected {
367
+ text: string | null
368
+ html: string | null
369
+ attachments: ParsedAttachment[]
370
+ }
371
+
372
+ function walk(entity: Entity, depth: number, out: Collected): void {
373
+ const type = parseStructured(header(entity, 'content-type'))
374
+ const mediaType = (type.value === '' ? 'text/plain' : type.value).toLowerCase()
375
+ const disposition = parseStructured(header(entity, 'content-disposition'))
376
+ const disp = disposition.value.toLowerCase()
377
+ const contentId = idsOf(header(entity, 'content-id'))[0] ?? null
378
+
379
+ if (mediaType.startsWith('multipart/') && depth < MAX_DEPTH) {
380
+ const boundary = type.params.get('boundary')
381
+ if (boundary !== undefined && boundary !== '') {
382
+ for (const part of splitMultipart(entity.body, boundary)) walk(splitEntity(part), depth + 1, out)
383
+ return
384
+ }
385
+ }
386
+
387
+ const rawName = disposition.params.get('filename') ?? type.params.get('name')
388
+ const filename = rawName === undefined ? null : decodeWords(rawName)
389
+ const isText = mediaType === 'text/plain' || mediaType === 'text/html'
390
+ const attached = disp === 'attachment' || filename !== null || !isText || mediaType.startsWith('multipart/')
391
+
392
+ const content = decodeTransfer(entity.body, header(entity, 'content-transfer-encoding'))
393
+ if (!attached) {
394
+ const decoded = decodeCharset(content, type.params.get('charset'))
395
+ if (mediaType === 'text/html') { if (out.html === null) out.html = decoded } else if (out.text === null) out.text = decoded
396
+ return
397
+ }
398
+ out.attachments.push({
399
+ filename,
400
+ mediaType: mediaType.startsWith('multipart/') ? 'application/octet-stream' : mediaType,
401
+ size: content.length,
402
+ contentId,
403
+ inline: disp === 'inline' || (disp === '' && contentId !== null),
404
+ content,
405
+ })
406
+ }
407
+
408
+ const EMPTY: ParsedMessage = {
409
+ messageId: null, inReplyTo: null, threadIds: [], references: [], from: null, fromProblem: null, to: [], cc: [], replyTo: [],
410
+ subject: '', date: null, text: null, html: null, attachments: [],
411
+ }
412
+
413
+ /** Parses a raw RFC 5322 message. Never throws; unreadable input yields as much as could be read. */
414
+ export function parseMime(raw: Buffer): ParsedMessage {
415
+ try {
416
+ const entity = splitEntity(raw.toString('latin1'))
417
+ const out: Collected = { text: null, html: null, attachments: [] }
418
+ walk(entity, 0, out)
419
+ const inReplyToIds = idsOf(header(entity, 'in-reply-to'))
420
+ const references = idsOf(header(entity, 'references'))
421
+ const threadIds = [...new Set([...inReplyToIds, ...references])]
422
+ const date = header(entity, 'date')
423
+ const parsedDate = date === undefined ? null : new Date(date)
424
+ const fromFields = headers(entity, 'from')
425
+ const fromMailboxes = fromFields.length === 1 ? parseAddresses(fromFields[0]) : []
426
+ const fromProblem = fromFields.length > 1 ? 'several-from-fields' as const
427
+ : fromMailboxes.length > 1 ? 'several-mailboxes' as const : null
428
+ return {
429
+ messageId: idsOf(header(entity, 'message-id'))[0] ?? null,
430
+ inReplyTo: inReplyToIds[0] ?? null,
431
+ threadIds,
432
+ references,
433
+ from: fromProblem === null ? fromMailboxes[0] ?? null : null,
434
+ fromProblem,
435
+ to: parseAddresses(header(entity, 'to')),
436
+ cc: parseAddresses(header(entity, 'cc')),
437
+ replyTo: parseAddresses(header(entity, 'reply-to')),
438
+ subject: decodeWords(header(entity, 'subject') ?? ''),
439
+ date: parsedDate === null || Number.isNaN(parsedDate.getTime()) ? null : parsedDate,
440
+ text: out.text,
441
+ html: out.html,
442
+ attachments: out.attachments,
443
+ }
444
+ } catch {
445
+ return EMPTY
446
+ }
447
+ }
@@ -0,0 +1,22 @@
1
+ import { createMail } from './mail.js'
2
+ import { ses, devStandIn } from './providers.js'
3
+ import { snsWebhook } from './sns.js'
4
+ import { createInbound, sesReceipt, inboundFolder } from './inbound.js'
5
+
6
+ /** Mail namespace — email through a job queue, SES v2 or a dev stand-in, bounce and complaint suppression, and receiving. */
7
+ export const mail = {
8
+ /** Creates the mail interface: `send(db, { to, template, data, key })` in the caller's transaction, and the `jobs` that deliver. */
9
+ create: createMail,
10
+ /** Amazon SES v2 provider, SigV4-signed without the AWS SDK. */
11
+ ses,
12
+ /** Development provider that writes each message to a file instead of sending; refused in production. */
13
+ devStandIn,
14
+ /** Inbound handler for SES bounce and complaint events delivered through SNS; suppresses the address. */
15
+ snsWebhook,
16
+ /** Receiving email: `inbound({ source, queue, onMessage, ... })` returns `{ webhook, jobs, scan, sweep, get }`; messages arrive parsed, threaded into conversations, attachments stored as tenant files. */
17
+ inbound: createInbound,
18
+ /** Inbound source: SES receipt rule storing raw messages in S3 and notifying a signed SNS topic. */
19
+ sesReceipt,
20
+ /** Inbound development source: `.eml` files dropped in a folder, received by `scan()`; refused in production. */
21
+ inboundFolder,
22
+ } as const
@@ -0,0 +1,111 @@
1
+ import { mkdir, rename, rm, writeFile } from 'node:fs/promises'
2
+ import { join } from 'node:path'
3
+ import { getBaseLogger } from '../../core/logging/index.js'
4
+ import { signV4, type AwsCredentials } from '../aws/sigv4.js'
5
+ import { awsCredentials } from '../aws/credentials.js'
6
+
7
+ /** One rendered message handed to a provider. */
8
+ export interface OutgoingMessage {
9
+ /** The message row id; unique per message. */
10
+ readonly messageId: string
11
+ readonly from: string
12
+ readonly to: string
13
+ readonly subject: string
14
+ readonly text: string
15
+ readonly html?: string
16
+ readonly configurationSet?: string
17
+ /** Extra headers, e.g. `In-Reply-To` and `References` for a reply. */
18
+ readonly headers?: Readonly<Record<string, string>>
19
+ }
20
+
21
+ /** Sends one rendered message. A throw means it was not accepted and may be retried. */
22
+ export interface MailProvider {
23
+ readonly name: string
24
+ send(message: OutgoingMessage): Promise<{ providerMessageId: string }>
25
+ }
26
+
27
+ export interface SesConfig {
28
+ readonly region: string
29
+ /** Called for every send. Default: `awsCredentials()` (environment, ECS task role, EC2 instance role). */
30
+ readonly credentials?: () => Promise<AwsCredentials>
31
+ readonly configurationSet?: string
32
+ /** Base URL instead of `https://email.<region>.amazonaws.com`; for tests against a local server. */
33
+ readonly endpoint?: string
34
+ }
35
+
36
+ /**
37
+ * Amazon SES v2 provider. Posts to `/v2/email/outbound-emails`, signed with SigV4
38
+ * (no AWS SDK). Credentials default to `awsCredentials()`.
39
+ * A non-2xx answer throws with the status and AWS's message.
40
+ */
41
+ export function ses(config: SesConfig): MailProvider {
42
+ const getCredentials = config.credentials ?? awsCredentials()
43
+ const base = (config.endpoint ?? `https://email.${config.region}.amazonaws.com`).replace(/\/+$/, '')
44
+ return {
45
+ name: 'ses',
46
+ async send(message) {
47
+ const set = message.configurationSet ?? config.configurationSet
48
+ const headerList = Object.entries(message.headers ?? {}).map(([Name, Value]) => ({ Name, Value }))
49
+ const body = JSON.stringify({
50
+ FromEmailAddress: message.from,
51
+ Destination: { ToAddresses: [message.to] },
52
+ Content: {
53
+ Simple: {
54
+ Subject: { Data: message.subject, Charset: 'UTF-8' },
55
+ ...(headerList.length > 0 ? { Headers: headerList } : {}),
56
+ Body: {
57
+ Text: { Data: message.text, Charset: 'UTF-8' },
58
+ ...(message.html !== undefined ? { Html: { Data: message.html, Charset: 'UTF-8' } } : {}),
59
+ },
60
+ },
61
+ },
62
+ ...(set !== undefined ? { ConfigurationSetName: set } : {}),
63
+ })
64
+ const url = `${base}/v2/email/outbound-emails`
65
+ const headers = signV4({
66
+ method: 'POST', url, headers: { 'content-type': 'application/json' }, body,
67
+ region: config.region, service: 'ses', credentials: await getCredentials(),
68
+ })
69
+ const res = await fetch(url, { method: 'POST', headers, body })
70
+ const raw = await res.text()
71
+ let parsed: Record<string, unknown> = {}
72
+ try { parsed = JSON.parse(raw) as Record<string, unknown> } catch { /* not JSON */ }
73
+ if (!res.ok) {
74
+ const detail = String(parsed['message'] ?? parsed['Message'] ?? raw.slice(0, 200))
75
+ throw new Error(`[pipework] SES refused the message: HTTP ${res.status} ${res.headers.get('x-amzn-errortype') ?? ''} ${detail}`.replace(/ +/g, ' '))
76
+ }
77
+ const id = parsed['MessageId']
78
+ if (typeof id !== 'string') throw new Error('[pipework] SES answered without a MessageId.')
79
+ return { providerMessageId: id }
80
+ },
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Development stand-in: writes each rendered message to `<dir>/<messageId>.json`
86
+ * (from, to, subject, text, html, and headers when there are any) and logs one line, sending nothing.
87
+ * The file appears only once it is complete. `mail.create` refuses it when the environment is production.
88
+ */
89
+ export function devStandIn(config: { readonly dir: string }): MailProvider {
90
+ return {
91
+ name: 'dev-stand-in',
92
+ async send(message) {
93
+ await mkdir(config.dir, { recursive: true })
94
+ const file = join(config.dir, `${message.messageId}.json`)
95
+ const { from, to, subject, text, html, headers } = message
96
+ const tmp = join(config.dir, `.${message.messageId}.json.tmp`)
97
+ try {
98
+ await writeFile(tmp, JSON.stringify({
99
+ from, to, subject, text, ...(html !== undefined ? { html } : {}),
100
+ ...(headers !== undefined && Object.keys(headers).length > 0 ? { headers } : {}),
101
+ }, null, 2))
102
+ await rename(tmp, file)
103
+ } catch (err) {
104
+ await rm(tmp, { force: true }).catch(() => {})
105
+ throw err
106
+ }
107
+ getBaseLogger().info({ event: 'email dev stand-in', to, file }, 'email written to file instead of sent')
108
+ return { providerMessageId: `dev-${message.messageId}` }
109
+ },
110
+ }
111
+ }
@@ -0,0 +1,96 @@
1
+ // The tables behind `mail`. Enabled with `email: {}` in the blueprint;
2
+ // `pipework generate` then emits them into the default database's migrations,
3
+ // IF NOT EXISTS. Not tenant-scoped: a bounce notification arrives with no
4
+ // tenant, and a suppressed address is suppressed for every tenant. The same holds
5
+ // for `pipework_email_unsubscribes`: the one-click POST arrives from a mail
6
+ // provider with no tenant, and an opt-out names an address and a list, not a tenant.
7
+
8
+ import { define } from '../../data/domain/index.js'
9
+ import { field } from '../../data/domain/field.js'
10
+ import { schema } from '../../data/schema/namespace.js'
11
+ import { index, unique } from '../../data/db/idx/index.js'
12
+
13
+ export const EMAIL_TABLE_NAMES: ReadonlySet<string> = new Set([
14
+ 'pipework_email_messages', 'pipework_email_suppressions', 'pipework_email_inbound', 'pipework_email_unsubscribes',
15
+ ])
16
+
17
+ export const EMAIL_STATUSES = ['queued', 'sending', 'sent', 'suppressed', 'unsubscribed', 'failed'] as const
18
+ export const INBOUND_STATUSES = ['received', 'processed', 'failed'] as const
19
+ export const INBOUND_SOURCES = ['ses', 'folder'] as const
20
+ export const SUPPRESSION_REASONS = ['bounce', 'complaint'] as const
21
+
22
+ export const emailMessages = define('pipework_email_messages', {
23
+ id: field.uuid().defaultRandom().primaryKey(),
24
+ key: field.text(),
25
+ toAddress: field.text(),
26
+ template: field.text(),
27
+ data: field.jsonb(schema.check.json()),
28
+ status: field.enum(EMAIL_STATUSES).default('queued'),
29
+ providerMessageId: field.text().nullable(),
30
+ error: field.text().nullable(),
31
+ traceId: field.uuid().nullable(),
32
+ createdAt: field.timestamp().default('now'),
33
+ sentAt: field.timestamp().nullable(),
34
+ sendingAt: field.timestamp().nullable(),
35
+ failedAt: field.timestamp().nullable(),
36
+ conversationId: field.uuid().nullable(),
37
+ inReplyToInbound: field.uuid().nullable(),
38
+ list: field.text().nullable(),
39
+ }, {
40
+ indexes: (t) => ({
41
+ byKey: unique('pipework_email_messages_key_key').on(t['key']),
42
+ byProviderId: index('idx_pipework_email_messages_provider_id').on(t['providerMessageId']),
43
+ byConversation: index('idx_pipework_email_messages_conversation').on(t['conversationId']),
44
+ }),
45
+ })
46
+
47
+ export const emailSuppressions = define('pipework_email_suppressions', {
48
+ address: field.text().primaryKey(),
49
+ reason: field.enum(SUPPRESSION_REASONS),
50
+ detail: field.jsonb(schema.check.json()).nullable(),
51
+ createdAt: field.timestamp().default('now'),
52
+ })
53
+
54
+ // One row per (address, list) that opted out of a list's mail. Address lowercased.
55
+ export const emailUnsubscribes = define('pipework_email_unsubscribes', {
56
+ address: field.text(),
57
+ list: field.text(),
58
+ createdAt: field.timestamp().default('now'),
59
+ }, {
60
+ primaryKey: ['address', 'list'],
61
+ })
62
+
63
+ // One row per received message. Bodies are not stored here: the raw message is
64
+ // the copy (in the receipt bucket or the stand-in's `received/` folder) and is
65
+ // deleted after `raw_expires_at`. Not tenant-scoped: the row exists before a
66
+ // tenant is known; `tenant_id` is set once the app's resolver names one.
67
+ export const emailInbound = define('pipework_email_inbound', {
68
+ id: field.uuid().defaultRandom().primaryKey(),
69
+ sourceId: field.text(),
70
+ source: field.enum(INBOUND_SOURCES),
71
+ rawLocation: field.jsonb(schema.check.json()),
72
+ rawExpiresAt: field.timestamp(),
73
+ rawDeletedAt: field.timestamp().nullable(),
74
+ status: field.enum(INBOUND_STATUSES).default('received'),
75
+ error: field.text().nullable(),
76
+ messageIdHeader: field.text().nullable(),
77
+ inReplyTo: field.text().nullable(),
78
+ references: field.jsonb(schema.check.json()).nullable(),
79
+ fromAddress: field.text().nullable(),
80
+ subject: field.text().nullable(),
81
+ conversationId: field.uuid().nullable(),
82
+ tenantId: field.uuid().nullable(),
83
+ verdicts: field.jsonb(schema.check.json()).nullable(),
84
+ recipients: field.jsonb(schema.check.json()).nullable(),
85
+ attachmentFiles: field.jsonb(schema.check.json()).nullable(),
86
+ receivedAt: field.timestamp().default('now'),
87
+ processedAt: field.timestamp().nullable(),
88
+ failedAt: field.timestamp().nullable(),
89
+ traceId: field.uuid().nullable(),
90
+ }, {
91
+ indexes: (t) => ({
92
+ bySource: unique('pipework_email_inbound_source_id_key').on(t['sourceId']),
93
+ byMessageId: index('idx_pipework_email_inbound_message_id').on(t['messageIdHeader']),
94
+ byExpiry: index('idx_pipework_email_inbound_raw_expires').on(t['rawExpiresAt']),
95
+ }),
96
+ })