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,312 @@
1
+ import { sql } from '../../data/db/sql.js'
2
+ import { pipe, pipeRaw, pipeSystem } from '../../data/db/db.js'
3
+ import type { DB } from '../../data/db/index.js'
4
+ import { requireContext } from '../../request/context/store.js'
5
+ import type { Flow } from '../../request/context/types.js'
6
+ import { writeInfraAudit } from '../../data/write/audit/infra-audit.js'
7
+ import { ValidationError, ForbiddenError } from '../../core/errors/index.js'
8
+ import { inTenant as runAsTenant } from '../../auth/tenant/in-tenant.js'
9
+ import { JOIN_REQUEST_STATUSES } from './schema.js'
10
+
11
+ export type JoinRequestStatus = typeof JOIN_REQUEST_STATUSES[number]
12
+
13
+ export interface JoinRequest {
14
+ readonly id: string
15
+ readonly orgId: string
16
+ readonly userId: string
17
+ readonly status: JoinRequestStatus
18
+ readonly reason: string | null
19
+ /** App data given to `request`; may be personal data, so the audit record leaves it out. */
20
+ readonly evidence: unknown
21
+ readonly requestedAt: Date
22
+ readonly expiresAt: Date
23
+ readonly settledAt: Date | null
24
+ /** Who settled it; `null` while open, and for an expiry. */
25
+ readonly settledBy: string | null
26
+ /** The role given on approval. */
27
+ readonly role: string | null
28
+ /** The note given on refusal. */
29
+ readonly note: string | null
30
+ }
31
+
32
+ export interface JoinRequestsConfig {
33
+ /** The app's owner check; roles are the app's. Called inside the org's transaction. */
34
+ isOwner(tx: DB, args: { orgId: string; userId: string }): Promise<boolean>
35
+ /** Adds the member; called inside the approve transaction, so a throw rolls the approval back. */
36
+ addMember(tx: DB, args: { orgId: string; userId: string; role: string | undefined; requestId: string; by: string }): Promise<void>
37
+ /** How long a request stays open. Default 30 days. */
38
+ readonly expiresInMs?: number
39
+ /** Awaited inside the transaction that created the request; a throw rolls it back. */
40
+ onRequested?(tx: DB, request: JoinRequest): void | Promise<void>
41
+ /** Awaited inside the transaction that settled the request (approved, refused, withdrawn, expired); a throw rolls it back. */
42
+ onSettled?(tx: DB, request: JoinRequest): void | Promise<void>
43
+ }
44
+
45
+ export type SettleResult =
46
+ | { readonly ok: true; readonly request: JoinRequest }
47
+ | { readonly ok: false; readonly reason: 'not-found' | 'expired' | 'already-settled' }
48
+
49
+ export interface JoinRequests {
50
+ /**
51
+ * Asks to join `orgId` as `userId`. Runs in the named org (the joiner is not in it). One open request
52
+ * per (org, user): while one is open and unexpired it is returned with `created: false`, with no audit
53
+ * record and no hook. An open one past its expiry is marked expired first. Writes a `JoinRequest:request`
54
+ * audit record when the Flow has a trace with an open step.
55
+ */
56
+ request(orgId: string, userId: string, opts?: { reason?: string; evidence?: unknown }): Promise<{ request: JoinRequest; created: boolean }>
57
+ /** The Flow tenant's owner (`isOwner`) approves: marks it approved and calls `addMember`, in one transaction. A non-owner gets `ForbiddenError`. */
58
+ approve(requestId: string, opts: { role?: string }, by: string): Promise<SettleResult>
59
+ /** The Flow tenant's owner refuses it. A non-owner gets `ForbiddenError`. */
60
+ refuse(requestId: string, opts: { note?: string }, by: string): Promise<SettleResult>
61
+ /** The joiner withdraws their own open request; another user's id is `not-found`. */
62
+ withdraw(requestId: string, userId: string): Promise<SettleResult>
63
+ /** The Flow tenant's requests with `status` (default `open`), newest first. */
64
+ list(opts?: { status?: JoinRequestStatus }): Promise<JoinRequest[]>
65
+ /** One user's requests across orgs, read through the system channel, newest first. */
66
+ mine(userId: string): Promise<JoinRequest[]>
67
+ /** Marks every open request past its expiry as expired; schedule it (a cron job). */
68
+ sweep(): Promise<{ expired: number }>
69
+ }
70
+
71
+ const DEFAULT_EXPIRES_IN_MS = 30 * 24 * 60 * 60 * 1000
72
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
73
+ const MAX_INSERT_ATTEMPTS = 3
74
+
75
+ type Row = Record<string, unknown>
76
+
77
+ function toDate(value: unknown): Date {
78
+ return value instanceof Date ? value : new Date(String(value))
79
+ }
80
+
81
+ function toDateOrNull(value: unknown): Date | null {
82
+ return value === null || value === undefined ? null : toDate(value)
83
+ }
84
+
85
+ function rowToRequest(row: Row): JoinRequest {
86
+ return {
87
+ id: row['id'] as string,
88
+ orgId: row['tenant_id'] as string,
89
+ userId: row['user_id'] as string,
90
+ status: row['status'] as JoinRequestStatus,
91
+ reason: (row['reason'] as string | null) ?? null,
92
+ evidence: row['evidence'] ?? null,
93
+ requestedAt: toDate(row['requested_at']),
94
+ expiresAt: toDate(row['expires_at']),
95
+ settledAt: toDateOrNull(row['settled_at']),
96
+ settledBy: (row['settled_by'] as string | null) ?? null,
97
+ role: (row['role'] as string | null) ?? null,
98
+ note: (row['note'] as string | null) ?? null,
99
+ }
100
+ }
101
+
102
+ function isUniqueViolation(err: unknown): boolean {
103
+ let current: unknown = err
104
+ for (let depth = 0; depth < 4 && typeof current === 'object' && current !== null; depth++) {
105
+ if ((current as { code?: unknown }).code === '23505') return true
106
+ current = (current as { cause?: unknown }).cause
107
+ }
108
+ return false
109
+ }
110
+
111
+ function requireUuid(value: unknown, name: string): string {
112
+ if (typeof value !== 'string' || !UUID.test(value)) {
113
+ throw new ValidationError({ [name]: ['must be a uuid'] }, `joinRequests: ${name} must be a uuid, got ${JSON.stringify(value)}.`)
114
+ }
115
+ return value
116
+ }
117
+
118
+ function requireText(value: unknown, name: string): string {
119
+ if (typeof value !== 'string' || value === '') {
120
+ throw new ValidationError({ [name]: ['must be a non-empty string'] }, `joinRequests: ${name} must be a non-empty string.`)
121
+ }
122
+ return value
123
+ }
124
+
125
+ // The audit record leaves out `evidence`, which may be personal data.
126
+ function auditProjection(request: JoinRequest): Record<string, unknown> {
127
+ const { id, orgId, userId, status, reason, requestedAt, expiresAt, settledAt, settledBy, role, note } = request
128
+ return { id, orgId, userId, status, reason, requestedAt, expiresAt, settledAt, settledBy, role, note }
129
+ }
130
+
131
+ export function createJoinRequests(config: JoinRequestsConfig): JoinRequests {
132
+ if (typeof config.isOwner !== 'function' || typeof config.addMember !== 'function') {
133
+ throw new Error('[pipework] joinRequests.create needs `isOwner` and `addMember`.\n\n Roles are the app\'s: isOwner(tx, { orgId, userId }) says who may settle a request, and\n addMember(tx, { orgId, userId, role, requestId, by }) adds the member inside the approval.\n')
134
+ }
135
+ const expiresInMs = config.expiresInMs ?? DEFAULT_EXPIRES_IN_MS
136
+ if (!Number.isFinite(expiresInMs) || expiresInMs <= 0) {
137
+ throw new Error('[pipework] joinRequests expiresInMs must be a positive number of milliseconds.\n')
138
+ }
139
+
140
+ function requireTenant(): { flow: Flow; tenantId: string } {
141
+ const flow = requireContext()
142
+ if (flow.tenant === null) {
143
+ throw new Error(
144
+ '[pipework] This joinRequests method needs a tenant in the Flow.\n\n' +
145
+ ' approve/refuse/list act for one org. Call them from an authenticated tenant route;\n' +
146
+ ' request/withdraw/mine are the ones for the joiner, who is not in the org.\n',
147
+ )
148
+ }
149
+ return { flow, tenantId: flow.tenant }
150
+ }
151
+
152
+ function inTenant<T>(flow: Flow, tenantId: string, fn: (tx: DB) => Promise<T>): Promise<T> {
153
+ return runAsTenant(flow, tenantId, fn)
154
+ }
155
+
156
+ function audit(flow: Flow, tx: DB, verb: string, request: JoinRequest): Promise<void> {
157
+ return writeInfraAudit(flow, tx, {
158
+ tenantId: request.orgId, entity: 'JoinRequest', verb, entityId: request.id, after: auditProjection(request),
159
+ })
160
+ }
161
+
162
+ async function settled(flow: Flow, tx: DB, verb: string, request: JoinRequest): Promise<void> {
163
+ await audit(flow, tx, verb, request)
164
+ await config.onSettled?.(tx, request)
165
+ }
166
+
167
+ // Says why an open row was not there to settle.
168
+ async function whyNot(tx: DB, requestId: string, orgId: string): Promise<'not-found' | 'expired' | 'already-settled'> {
169
+ const rows = await tx.execute(sql`
170
+ SELECT status, (expires_at <= now()) AS past FROM join_request WHERE id = ${requestId} AND tenant_id = ${orgId}`)
171
+ const row = rows[0]
172
+ if (row === undefined) return 'not-found'
173
+ if (row['status'] === 'expired' || (row['status'] === 'open' && row['past'] === true)) return 'expired'
174
+ return 'already-settled'
175
+ }
176
+
177
+ async function settleAsOwner(kind: 'approve' | 'refuse', requestId: string, by: string, opts: { role?: string; note?: string }): Promise<SettleResult> {
178
+ requireUuid(requestId, 'requestId')
179
+ requireText(by, 'by')
180
+ const { flow, tenantId } = requireTenant()
181
+ return pipe.transaction(async (tx: DB): Promise<SettleResult> => {
182
+ if (!(await config.isOwner(tx, { orgId: tenantId, userId: by }))) {
183
+ throw new ForbiddenError(`Only an owner of the organisation can ${kind} a join request.`, { orgId: tenantId, userId: by })
184
+ }
185
+ const rows = kind === 'approve'
186
+ ? await tx.execute(sql`
187
+ UPDATE join_request SET status = 'approved', settled_at = now(), settled_by = ${by}, role = ${opts.role ?? null}
188
+ WHERE id = ${requestId} AND tenant_id = ${tenantId} AND status = 'open' AND expires_at > now()
189
+ RETURNING *`)
190
+ : await tx.execute(sql`
191
+ UPDATE join_request SET status = 'refused', settled_at = now(), settled_by = ${by}, note = ${opts.note ?? null}
192
+ WHERE id = ${requestId} AND tenant_id = ${tenantId} AND status = 'open' AND expires_at > now()
193
+ RETURNING *`)
194
+ if (rows.length === 0) return { ok: false, reason: await whyNot(tx, requestId, tenantId) }
195
+ const request = rowToRequest(rows[0]!)
196
+ if (kind === 'approve') {
197
+ await config.addMember(tx, { orgId: tenantId, userId: request.userId, role: opts.role, requestId, by })
198
+ }
199
+ await settled(flow, tx, kind, request)
200
+ return { ok: true, request }
201
+ })
202
+ }
203
+
204
+ // Marks the open requests of one org that are past expiry (optionally one user's) as expired.
205
+ async function expireOpen(flow: Flow, tx: DB, orgId: string, userId?: string): Promise<number> {
206
+ const rows = await tx.execute(sql`
207
+ UPDATE join_request SET status = 'expired', settled_at = now()
208
+ WHERE tenant_id = ${orgId} AND status = 'open' AND expires_at <= now()
209
+ ${userId === undefined ? sql`` : sql`AND user_id = ${userId}`}
210
+ RETURNING *`)
211
+ for (const row of rows) await settled(flow, tx, 'expire', rowToRequest(row))
212
+ return rows.length
213
+ }
214
+
215
+ const handle: JoinRequests = {
216
+ async request(orgId, userId, opts) {
217
+ requireUuid(orgId, 'orgId')
218
+ requireText(userId, 'userId')
219
+ const flow = requireContext()
220
+ const evidence = opts?.evidence === undefined || opts.evidence === null ? null : JSON.stringify(opts.evidence)
221
+ return inTenant(flow, orgId, async (tx) => {
222
+ for (let attempt = 1; attempt <= MAX_INSERT_ATTEMPTS; attempt++) {
223
+ const open = await tx.execute(sql`
224
+ SELECT *, (expires_at <= now()) AS past FROM join_request
225
+ WHERE tenant_id = ${orgId} AND user_id = ${userId} AND status = 'open'`)
226
+ if (open.length > 0) {
227
+ if (open[0]!['past'] !== true) return { request: rowToRequest(open[0]!), created: false }
228
+ await expireOpen(flow, tx, orgId, userId)
229
+ }
230
+ try {
231
+ // A savepoint: losing the race to another request aborts only this insert.
232
+ const rows = await tx.transaction((sp: DB) => sp.execute(sql`
233
+ INSERT INTO join_request (tenant_id, user_id, status, reason, evidence, expires_at)
234
+ VALUES (${orgId}, ${userId}, 'open', ${opts?.reason ?? null},
235
+ ${evidence === null ? sql`NULL` : sql`${evidence}::jsonb`},
236
+ now() + ${expiresInMs}::float8 * interval '1 millisecond')
237
+ RETURNING *`))
238
+ const request = rowToRequest(rows[0]!)
239
+ await audit(flow, tx, 'request', request)
240
+ await config.onRequested?.(tx, request)
241
+ return { request, created: true }
242
+ } catch (err) {
243
+ if (!isUniqueViolation(err)) throw err
244
+ }
245
+ }
246
+ throw new Error('[pipework] joinRequests.request could not settle on an open request after repeated conflicts; retry.\n')
247
+ })
248
+ },
249
+
250
+ approve(requestId, opts, by) {
251
+ return settleAsOwner('approve', requestId, by, { ...(opts.role !== undefined ? { role: opts.role } : {}) })
252
+ },
253
+
254
+ refuse(requestId, opts, by) {
255
+ return settleAsOwner('refuse', requestId, by, { ...(opts.note !== undefined ? { note: opts.note } : {}) })
256
+ },
257
+
258
+ async withdraw(requestId, userId) {
259
+ requireUuid(requestId, 'requestId')
260
+ requireText(userId, 'userId')
261
+ const flow = requireContext()
262
+ const found = await pipeSystem().execute(sql`SELECT tenant_id FROM join_request WHERE id = ${requestId}`)
263
+ if (found.length === 0) return { ok: false, reason: 'not-found' }
264
+ const orgId = found[0]!['tenant_id'] as string
265
+ return inTenant(flow, orgId, async (tx): Promise<SettleResult> => {
266
+ const rows = await tx.execute(sql`
267
+ UPDATE join_request SET status = 'withdrawn', settled_at = now(), settled_by = ${userId}
268
+ WHERE id = ${requestId} AND tenant_id = ${orgId} AND user_id = ${userId} AND status = 'open' AND expires_at > now()
269
+ RETURNING *`)
270
+ if (rows.length === 0) {
271
+ const own = await tx.execute(sql`SELECT 1 FROM join_request WHERE id = ${requestId} AND tenant_id = ${orgId} AND user_id = ${userId}`)
272
+ return { ok: false, reason: own.length === 0 ? 'not-found' : await whyNot(tx, requestId, orgId) }
273
+ }
274
+ const request = rowToRequest(rows[0]!)
275
+ await settled(flow, tx, 'withdraw', request)
276
+ return { ok: true, request }
277
+ })
278
+ },
279
+
280
+ async list(opts) {
281
+ const { tenantId } = requireTenant()
282
+ const status = opts?.status ?? 'open'
283
+ if (!(JOIN_REQUEST_STATUSES as readonly string[]).includes(status)) {
284
+ throw new ValidationError({ status: [`must be one of ${JOIN_REQUEST_STATUSES.join(', ')}`] }, 'joinRequests list status is not valid.')
285
+ }
286
+ const rows = await pipeRaw().execute(sql`
287
+ SELECT * FROM join_request WHERE tenant_id = ${tenantId} AND status = ${status}
288
+ ORDER BY requested_at DESC, id DESC`)
289
+ return rows.map(rowToRequest)
290
+ },
291
+
292
+ async mine(userId) {
293
+ requireText(userId, 'userId')
294
+ const rows = await pipeSystem().execute(sql`
295
+ SELECT * FROM join_request WHERE user_id = ${userId} ORDER BY requested_at DESC, id DESC`)
296
+ return rows.map(rowToRequest)
297
+ },
298
+
299
+ async sweep() {
300
+ const flow = requireContext()
301
+ const orgs = await pipeSystem().execute(sql`
302
+ SELECT DISTINCT tenant_id FROM join_request WHERE status = 'open' AND expires_at <= now()`)
303
+ let expired = 0
304
+ for (const org of orgs) {
305
+ const orgId = org['tenant_id'] as string
306
+ expired += await inTenant(flow, orgId, tx => expireOpen(flow, tx, orgId))
307
+ }
308
+ return { expired }
309
+ },
310
+ }
311
+ return handle
312
+ }
@@ -0,0 +1,2 @@
1
+ export { createJoinRequests } from './handle.js'
2
+ export type { JoinRequests, JoinRequestsConfig, JoinRequest, JoinRequestStatus, SettleResult } from './handle.js'
@@ -0,0 +1,7 @@
1
+ import { createJoinRequests } from './handle.js'
2
+
3
+ /** Join requests namespace — a user asks to join an existing org and one of its owners settles it. */
4
+ export const joinRequests = {
5
+ /** Creates a join-request handle: `request`, `approve`, `refuse`, `withdraw`, `list`, `mine` and `sweep`. Needs blueprint `joinRequests: {}`. */
6
+ create: createJoinRequests,
7
+ } as const
@@ -0,0 +1,33 @@
1
+ // The table behind join requests. `join_request` carries a `.tenant()` key (the
2
+ // org the user asks to join), so generated migrations put it under forced
3
+ // row-level security. One open request per (org, user) is enforced by a
4
+ // partial unique index.
5
+
6
+ import { define } from '../../data/domain/index.js'
7
+ import { field } from '../../data/domain/field.js'
8
+ import { schema } from '../../data/schema/namespace.js'
9
+ import { sql } from '../../data/db/sql.js'
10
+ import { index, uniqueIndex } from '../../data/db/idx/index.js'
11
+
12
+ export const JOIN_REQUEST_STATUSES = ['open', 'approved', 'refused', 'withdrawn', 'expired'] as const
13
+
14
+ export const joinRequest = define('join_request', {
15
+ id: field.uuid().defaultRandom().primaryKey(),
16
+ tenantId: field.uuid().tenant(),
17
+ userId: field.text(),
18
+ status: field.enum(JOIN_REQUEST_STATUSES),
19
+ reason: field.text().nullable(),
20
+ evidence: field.jsonb(schema.check.json()).nullable(),
21
+ requestedAt: field.timestamp().default('now'),
22
+ expiresAt: field.timestamp(),
23
+ settledAt: field.timestamp().nullable(),
24
+ settledBy: field.text().nullable(),
25
+ role: field.text().nullable(),
26
+ note: field.text().nullable(),
27
+ }, {
28
+ indexes: (t) => ({
29
+ oneOpen: uniqueIndex('uq_join_request_open').on(t['tenantId'], t['userId']).where(sql.raw(`status = 'open'`)),
30
+ byUser: index('idx_join_request_user').on(t['userId']),
31
+ byStatus: index('idx_join_request_status').on(t['tenantId'], t['status']),
32
+ }),
33
+ })
@@ -0,0 +1,165 @@
1
+ // The delivery core shared by alerts and support events: rows in an outbox table are leased with
2
+ // SKIP LOCKED, sent in order per (group, sink), retried with backoff and given up after maxAttempts.
3
+ // Table and column names are the caller's own constants, never user input.
4
+
5
+ import { sql } from '../../data/db/sql.js'
6
+ import { pipeSystem } from '../../data/db/db.js'
7
+ import { getBaseLogger } from '../../core/logging/index.js'
8
+ import { describeError } from '../../core/logging/error-chain.js'
9
+ import { ValidationError } from '../../core/errors/index.js'
10
+
11
+ const SEND_TIMEOUT_MS = 10_000
12
+ const LEASE_MS = 30_000
13
+ const BATCH = 50
14
+ const MAX_BATCHES = 20
15
+ const MAX_BACKOFF_MS = 3_600_000
16
+
17
+ export interface OutboxTotals {
18
+ delivered: number
19
+ retried: number
20
+ failed: number
21
+ }
22
+
23
+ /** What a sink receives. `kind` is the payload's own kind; `key` the group the row is ordered in (alert key, ticket id). */
24
+ export interface SinkDelivery<P extends { readonly kind: string }> {
25
+ readonly id: string
26
+ readonly kind: P['kind']
27
+ readonly key: string
28
+ readonly sink: string
29
+ readonly payload: P
30
+ /** 1 on the first try. */
31
+ readonly attempt: number
32
+ }
33
+
34
+ /**
35
+ * A place events are sent. `send` throwing is a failed attempt; so is running past 10 s, which is all the
36
+ * outbox gives a send (the row is leased for 30 s). `delivery.id` is the same on every attempt, so a receiver can dedupe on it.
37
+ */
38
+ export interface Sink<P extends { readonly kind: string }> {
39
+ readonly name: string
40
+ send(delivery: SinkDelivery<P>): Promise<void>
41
+ }
42
+
43
+ export interface OutboxSink<D> {
44
+ readonly name: string
45
+ send(delivery: D): Promise<void>
46
+ }
47
+
48
+ export interface OutboxOptions<D> {
49
+ /** The outbox table: `id`, the group column, `kind`, `sink`, `payload`, `attempts`, `next_attempt_at`, `delivered_at`, `failed_at`, `last_error`, `created_at`. */
50
+ readonly table: string
51
+ /** The column holding what a row is ordered within, together with its sink. */
52
+ readonly groupColumn: string
53
+ readonly database: string | undefined
54
+ readonly sinks: ReadonlyMap<string, OutboxSink<D>>
55
+ readonly toDelivery: (row: Record<string, unknown>, attempt: number) => D
56
+ readonly maxAttempts: number
57
+ readonly backoffMs: number
58
+ /** Names the log line written when a delivery is given up. */
59
+ readonly label: string
60
+ readonly onDelivered: (row: Record<string, unknown>, attempt: number) => Promise<void>
61
+ readonly onGivenUp: (row: Record<string, unknown>, attempt: number, error: string) => Promise<void>
62
+ }
63
+
64
+ export interface Outbox {
65
+ /** Delivers what is due now, up to 20 batches; rows that fail wait for the next call. */
66
+ drain(): Promise<OutboxTotals>
67
+ /**
68
+ * Puts given-up rows (`failed_at` set, never delivered) back on the queue: `failed_at` cleared, `attempts` 0, due now.
69
+ * `last_error` stays until the next attempt. Ids are unchanged. Returns each requeued row as it now is, plus
70
+ * `prev_attempts`, the attempts it had when given up.
71
+ */
72
+ requeue(options: { sink?: string; failedSince?: Date }): Promise<Record<string, unknown>[]>
73
+ }
74
+
75
+ function withTimeout(promise: Promise<void>, ms: number): Promise<void> {
76
+ let timer: NodeJS.Timeout | undefined
77
+ const timeout = new Promise<never>((_, reject) => {
78
+ timer = setTimeout(() => reject(new Error(`sink did not answer within ${String(ms)} ms`)), ms)
79
+ })
80
+ return Promise.race([promise, timeout]).finally(() => { clearTimeout(timer) })
81
+ }
82
+
83
+ export function createOutbox<D>(options: OutboxOptions<D>): Outbox {
84
+ const table = sql.raw(`"${options.table}"`)
85
+ const group = sql.raw(`"${options.groupColumn}"`)
86
+ const log = getBaseLogger()
87
+ const db = () => pipeSystem(options.database)
88
+
89
+ // Leases the due rows by pushing next_attempt_at out, so no row lock is held across a send. A row is
90
+ // due only if no older undelivered row of the same group and sink exists: a later event never overtakes an earlier one.
91
+ async function claimDue(dueBefore: string): Promise<Record<string, unknown>[]> {
92
+ return [...await db().execute(sql`
93
+ UPDATE ${table} SET next_attempt_at = clock_timestamp() + ${LEASE_MS} * interval '1 millisecond'
94
+ WHERE id IN (
95
+ SELECT d.id FROM ${table} d
96
+ WHERE d.delivered_at IS NULL AND d.failed_at IS NULL AND d.next_attempt_at <= ${dueBefore}::timestamptz
97
+ AND NOT EXISTS (
98
+ SELECT 1 FROM ${table} o
99
+ WHERE o.${group} = d.${group} AND o.sink = d.sink AND o.delivered_at IS NULL AND o.failed_at IS NULL
100
+ AND (o.created_at, o.id) < (d.created_at, d.id))
101
+ ORDER BY d.next_attempt_at LIMIT ${BATCH} FOR UPDATE OF d SKIP LOCKED)
102
+ RETURNING *`)]
103
+ }
104
+
105
+ async function deliverBatch(batch: Record<string, unknown>[], totals: OutboxTotals): Promise<void> {
106
+ await Promise.all(batch.map(async (row) => {
107
+ const id = row['id'] as string
108
+ const sinkName = row['sink'] as string
109
+ const attempt = Number(row['attempts']) + 1
110
+ const sink = options.sinks.get(sinkName)
111
+ let error: string | null = null
112
+ if (sink === undefined) {
113
+ error = `sink "${sinkName}" is not configured`
114
+ } else {
115
+ try {
116
+ await withTimeout(sink.send(options.toDelivery(row, attempt)), SEND_TIMEOUT_MS)
117
+ } catch (err) {
118
+ error = describeError(err).message
119
+ }
120
+ }
121
+ if (error === null) {
122
+ await db().execute(sql`UPDATE ${table} SET delivered_at = clock_timestamp(), attempts = ${attempt}, last_error = NULL WHERE id = ${id}::uuid`)
123
+ await options.onDelivered(row, attempt)
124
+ totals.delivered++
125
+ } else if (sink === undefined || attempt >= options.maxAttempts) {
126
+ await db().execute(sql`UPDATE ${table} SET failed_at = clock_timestamp(), attempts = ${attempt}, last_error = ${error} WHERE id = ${id}::uuid`)
127
+ log.error({ id, sink: sinkName, kind: row['kind'], attempt, error }, options.label)
128
+ await options.onGivenUp(row, attempt, error)
129
+ totals.failed++
130
+ } else {
131
+ const delay = Math.min(options.backoffMs * 2 ** (attempt - 1), MAX_BACKOFF_MS)
132
+ await db().execute(sql`UPDATE ${table} SET attempts = ${attempt}, last_error = ${error},
133
+ next_attempt_at = clock_timestamp() + ${delay} * interval '1 millisecond' WHERE id = ${id}::uuid`)
134
+ totals.retried++
135
+ }
136
+ }))
137
+ }
138
+
139
+ return {
140
+ async requeue(opts) {
141
+ if (opts.failedSince !== undefined && !(opts.failedSince instanceof Date && !Number.isNaN(opts.failedSince.getTime()))) {
142
+ throw new ValidationError({ failedSince: ['must be a valid Date'] }, 'Outbox failedSince must be a Date.')
143
+ }
144
+ const sinkFilter = opts.sink === undefined ? sql`` : sql`AND sink = ${opts.sink}`
145
+ const sinceFilter = opts.failedSince === undefined ? sql`` : sql`AND failed_at >= ${opts.failedSince.toISOString()}::timestamptz`
146
+ return [...await db().execute(sql`
147
+ UPDATE ${table} t SET failed_at = NULL, attempts = 0, next_attempt_at = clock_timestamp()
148
+ FROM (SELECT id, attempts AS prev_attempts FROM ${table}
149
+ WHERE failed_at IS NOT NULL AND delivered_at IS NULL ${sinkFilter} ${sinceFilter} FOR UPDATE) old
150
+ WHERE t.id = old.id
151
+ RETURNING t.*, old.prev_attempts`)]
152
+ },
153
+ async drain() {
154
+ const totals: OutboxTotals = { delivered: 0, retried: 0, failed: 0 }
155
+ // Rows that fail during this call wait for the next one, however short their backoff.
156
+ const dueBefore = String((await db().execute(sql`SELECT clock_timestamp()::text AS t`))[0]!['t'])
157
+ for (let i = 0; i < MAX_BATCHES; i++) {
158
+ const batch = await claimDue(dueBefore)
159
+ if (batch.length === 0) break
160
+ await deliverBatch(batch, totals)
161
+ }
162
+ return totals
163
+ },
164
+ }
165
+ }
@@ -0,0 +1,32 @@
1
+ import { PipeworkError } from '../../core/errors/base.js'
2
+
3
+ /**
4
+ * Thrown by `quota.call` before sending, when the upstream's recorded budget for `key`
5
+ * has fallen to the reserve kept for other classes. Inside a job it is handed back like
6
+ * `jobs.later` until `resetAt`.
7
+ */
8
+ export class QuotaExhaustedError extends PipeworkError {
9
+ readonly code = 'QUOTA_EXHAUSTED' as const
10
+ readonly httpStatus = 429 as const
11
+ readonly key: string
12
+ readonly class: string
13
+ readonly remaining: number
14
+ readonly reserve: number
15
+ readonly resetAt: Date
16
+ /** Whole seconds from now to `resetAt`; sent as Retry-After and in the 429 body. */
17
+ readonly retryAfterSeconds: number
18
+
19
+ constructor(info: { key: string; class: string; remaining: number; reserve: number; resetAt: Date }) {
20
+ super(
21
+ `[pipework] Quota for "${info.key}" is exhausted for class "${info.class}": ` +
22
+ `${String(info.remaining)} remaining, ${String(info.reserve)} held in reserve; resets at ${info.resetAt.toISOString()}.`,
23
+ )
24
+ this.name = 'QuotaExhaustedError'
25
+ this.key = info.key
26
+ this.class = info.class
27
+ this.remaining = info.remaining
28
+ this.reserve = info.reserve
29
+ this.resetAt = info.resetAt
30
+ this.retryAfterSeconds = Math.max(0, Math.ceil((info.resetAt.getTime() - Date.now()) / 1000))
31
+ }
32
+ }