@starci/hfs 4.2.17 → 4.4.0

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 (564) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +7 -0
  3. package/db/migrate.mjs +361 -0
  4. package/db/post-cutover.mjs +136 -0
  5. package/emit/db-types.mjs +10 -1
  6. package/lint/run.mjs +1 -1
  7. package/package.json +4 -1
  8. package/release/gate.mjs +298 -0
  9. package/release/index.mjs +103 -0
  10. package/release/push.mjs +110 -0
  11. package/runtime/knowledge/hfs/canon-pins.yaml +11 -11
  12. package/runtime/knowledge/hfs/fe-stack.yaml +22 -1
  13. package/runtime/knowledge/hfs/rules.yaml +3437 -160
  14. package/runtime/knowledge/hfs/slots.yaml +430 -92
  15. package/runtime/knowledge/patterns/be/api.yaml +92 -2
  16. package/runtime/knowledge/patterns/be/cli.yaml +37 -9
  17. package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
  18. package/runtime/knowledge/patterns/be/jobs.yaml +20 -2
  19. package/runtime/knowledge/patterns/be/projections.yaml +4 -2
  20. package/runtime/knowledge/patterns/be/queues.yaml +11 -3
  21. package/runtime/knowledge/patterns/be/reactors.yaml +20 -1
  22. package/runtime/knowledge/patterns/be/realtime.yaml +25 -3
  23. package/runtime/knowledge/patterns/be/saga.yaml +20 -3
  24. package/runtime/knowledge/patterns/be/webhooks.yaml +210 -36
  25. package/runtime/knowledge/patterns/fe/folder.yaml +4 -0
  26. package/runtime/knowledge/sonar-gate.yaml +1 -1
  27. package/runtime/modules/kernel/failure-codes.yaml +3128 -47
  28. package/runtime/modules/models/runtimes.yaml +1 -1
  29. package/runtime/modules/schemas/application-stacks.schema.yaml +663 -0
  30. package/runtime/packages/cli/src/catalog.generated.mjs +14104 -0
  31. package/runtime/packages/cli/src/validate-args.mjs +213 -0
  32. package/runtime/scripts/api/git/add.mjs +5 -0
  33. package/runtime/scripts/api/git/cat-file.mjs +5 -0
  34. package/runtime/scripts/api/git/init.mjs +7 -0
  35. package/runtime/scripts/api/git/lib.mjs +3 -0
  36. package/runtime/scripts/api/git/tag.mjs +5 -0
  37. package/runtime/scripts/api/node/lib.mjs +20 -0
  38. package/runtime/scripts/api/node/run-node.mjs +6 -0
  39. package/runtime/scripts/api/sops/lib.mjs +41 -0
  40. package/runtime/scripts/api/sops/resolve-sops.mjs +7 -0
  41. package/runtime/scripts/api/sops/seal.mjs +1 -1
  42. package/runtime/scripts/api/sops/selected-age-recipient.mjs +7 -0
  43. package/runtime/scripts/api/supabase/database-dump.mjs +5 -0
  44. package/runtime/scripts/api/supabase/database-query.mjs +5 -0
  45. package/runtime/scripts/api/supabase/lib.mjs +44 -0
  46. package/runtime/scripts/api/supabase/migration-repair.mjs +5 -0
  47. package/runtime/scripts/hfs/app-wiring.mjs +201 -0
  48. package/runtime/scripts/hfs/architecture/backend-app-files.mjs +103 -0
  49. package/runtime/scripts/hfs/architecture/backend.mjs +22 -10
  50. package/runtime/scripts/hfs/architecture/background-unowned.mjs +157 -41
  51. package/runtime/scripts/hfs/architecture/canon-clones.mjs +240 -0
  52. package/runtime/scripts/hfs/architecture/canon-jsx-clones.mjs +143 -0
  53. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +6 -3
  54. package/runtime/scripts/hfs/architecture/clones.mjs +17 -8
  55. package/runtime/scripts/hfs/architecture/connection-map.mjs +14 -12
  56. package/runtime/scripts/hfs/architecture/context-map.mjs +25 -14
  57. package/runtime/scripts/hfs/architecture/context-owner.mjs +1 -2
  58. package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +149 -0
  59. package/runtime/scripts/hfs/architecture/dead-exports.mjs +23 -50
  60. package/runtime/scripts/hfs/architecture/default-deny.mjs +8 -6
  61. package/runtime/scripts/hfs/architecture/frontend-block-render.mjs +104 -0
  62. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +30 -11
  63. package/runtime/scripts/hfs/architecture/frontend-world.mjs +1 -1
  64. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +153 -0
  65. package/runtime/scripts/hfs/architecture/hfs.mjs +3 -3
  66. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +16 -3
  67. package/runtime/scripts/hfs/architecture/machine-ast.mjs +17 -0
  68. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +49 -25
  69. package/runtime/scripts/hfs/architecture/next-data-inference.mjs +176 -0
  70. package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +26 -0
  71. package/runtime/scripts/hfs/architecture/next-data.mjs +31 -23
  72. package/runtime/scripts/hfs/architecture/reachability.mjs +14 -0
  73. package/runtime/scripts/hfs/architecture/register-once.mjs +15 -7
  74. package/runtime/scripts/hfs/architecture/required-files.mjs +2 -1
  75. package/runtime/scripts/hfs/architecture/source-names.mjs +48 -3
  76. package/runtime/scripts/hfs/architecture/staged-consumers.mjs +37 -0
  77. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +214 -17
  78. package/runtime/scripts/hfs/architecture/supabase-be.mjs +1 -6
  79. package/runtime/scripts/hfs/architecture/supabase.mjs +59 -21
  80. package/runtime/scripts/hfs/architecture/surface.mjs +2 -1
  81. package/runtime/scripts/hfs/architecture/tiers.mjs +11 -4
  82. package/runtime/scripts/hfs/architecture/typescript.mjs +1 -8
  83. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +9 -13
  84. package/runtime/scripts/hfs/check-results.mjs +28 -0
  85. package/runtime/scripts/hfs/check.mjs +63 -72
  86. package/runtime/scripts/hfs/coverage-from.mjs +70 -0
  87. package/runtime/scripts/hfs/db-baseline-verb.mjs +276 -0
  88. package/runtime/scripts/hfs/db-plan-verb.mjs +254 -0
  89. package/runtime/scripts/hfs/db-restamp-verb.mjs +60 -0
  90. package/runtime/scripts/hfs/declaration-shape.mjs +39 -3
  91. package/runtime/scripts/hfs/doc-commands.mjs +367 -0
  92. package/runtime/scripts/hfs/edition-slots.mjs +2 -2
  93. package/runtime/scripts/hfs/example-secrets.mjs +158 -0
  94. package/runtime/scripts/hfs/manifest-shape.mjs +91 -6
  95. package/runtime/scripts/hfs/path-findings.mjs +32 -2
  96. package/runtime/scripts/hfs/refusal-codes.mjs +6 -0
  97. package/runtime/scripts/hfs/rule-catalog.mjs +156 -11
  98. package/runtime/scripts/hfs/rule-params-shape.mjs +22 -10
  99. package/runtime/scripts/hfs/rules/a15-findings.mjs +88 -0
  100. package/runtime/scripts/hfs/rules/app-env-required.mjs +20 -0
  101. package/runtime/scripts/hfs/rules/app-root.mjs +2 -2
  102. package/runtime/scripts/hfs/rules/assets.mjs +79 -0
  103. package/runtime/scripts/hfs/rules/boot-proof.mjs +16 -0
  104. package/runtime/scripts/hfs/rules/canon-copy-drift.mjs +59 -0
  105. package/runtime/scripts/hfs/rules/contract.mjs +66 -5
  106. package/runtime/scripts/hfs/rules/coverage-below.mjs +230 -0
  107. package/runtime/scripts/hfs/rules/database-access.mjs +343 -0
  108. package/runtime/scripts/hfs/rules/database-baseline.mjs +142 -0
  109. package/runtime/scripts/hfs/rules/database-config.mjs +250 -2
  110. package/runtime/scripts/hfs/rules/database-constants.mjs +9 -0
  111. package/runtime/scripts/hfs/rules/database-migrations.mjs +317 -37
  112. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +119 -9
  113. package/runtime/scripts/hfs/rules/database-queue-sources.mjs +176 -0
  114. package/runtime/scripts/hfs/rules/database-queues.mjs +361 -0
  115. package/runtime/scripts/hfs/rules/database-sql.mjs +91 -9
  116. package/runtime/scripts/hfs/rules/database.mjs +11 -2
  117. package/runtime/scripts/hfs/rules/deploy-scan.mjs +192 -0
  118. package/runtime/scripts/hfs/rules/deploy-workflow.mjs +219 -0
  119. package/runtime/scripts/hfs/rules/doc-fresh.mjs +286 -0
  120. package/runtime/scripts/hfs/rules/docker-health.mjs +249 -0
  121. package/runtime/scripts/hfs/rules/docker.mjs +24 -7
  122. package/runtime/scripts/hfs/rules/edition.mjs +11 -10
  123. package/runtime/scripts/hfs/rules/env-parity.mjs +373 -0
  124. package/runtime/scripts/hfs/rules/env-value-shape.mjs +113 -0
  125. package/runtime/scripts/hfs/rules/event-bus.mjs +3 -2
  126. package/runtime/scripts/hfs/rules/event-tables.mjs +100 -0
  127. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +115 -15
  128. package/runtime/scripts/hfs/rules/fe-stack.mjs +57 -0
  129. package/runtime/scripts/hfs/rules/feature-files.mjs +28 -0
  130. package/runtime/scripts/hfs/rules/frontend-tree.mjs +54 -35
  131. package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -2
  132. package/runtime/scripts/hfs/rules/lexicon.mjs +62 -0
  133. package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +89 -0
  134. package/runtime/scripts/hfs/rules/monorepo.mjs +14 -1
  135. package/runtime/scripts/hfs/rules/pipeline.mjs +8 -1
  136. package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -0
  137. package/runtime/scripts/hfs/rules/release-provenance.mjs +211 -0
  138. package/runtime/scripts/hfs/rules/saga-delivery.mjs +66 -0
  139. package/runtime/scripts/hfs/rules/saga.mjs +8 -16
  140. package/runtime/scripts/hfs/rules/script-hygiene.mjs +151 -0
  141. package/runtime/scripts/hfs/rules/secret-custody-only.mjs +65 -0
  142. package/runtime/scripts/hfs/rules/secrets.mjs +26 -0
  143. package/runtime/scripts/hfs/rules/services.mjs +4 -4
  144. package/runtime/scripts/hfs/rules/stack-tooling.mjs +93 -0
  145. package/runtime/scripts/hfs/rules/stacks-deploy.mjs +76 -0
  146. package/runtime/scripts/hfs/rules/stacks-ingress.mjs +87 -0
  147. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +111 -0
  148. package/runtime/scripts/hfs/rules/stacks-release.mjs +366 -0
  149. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +168 -0
  150. package/runtime/scripts/hfs/rules/stacks-swarm.mjs +417 -0
  151. package/runtime/scripts/hfs/rules/stacks.mjs +13 -8
  152. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +1 -1
  153. package/runtime/scripts/hfs/rules/supply-chain.mjs +167 -0
  154. package/runtime/scripts/hfs/rules/unused-symbol-flow.mjs +101 -0
  155. package/runtime/scripts/hfs/rules/unused-symbol-ledger.mjs +77 -0
  156. package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +165 -0
  157. package/runtime/scripts/hfs/rules/unused-symbol-values.mjs +103 -0
  158. package/runtime/scripts/hfs/rules/unused-symbol.mjs +320 -0
  159. package/runtime/scripts/hfs/runtime-rules/source-ast.mjs +185 -0
  160. package/runtime/scripts/hfs/runtime-rules/workflow-source.mjs +19 -0
  161. package/runtime/scripts/hfs/sealed-members.mjs +101 -0
  162. package/runtime/scripts/hfs/secret-bootstrap-crypto.mjs +200 -0
  163. package/runtime/scripts/hfs/secret-document.mjs +97 -0
  164. package/runtime/scripts/hfs/secret-edit.mjs +170 -0
  165. package/runtime/scripts/hfs/secret-errors.mjs +20 -0
  166. package/runtime/scripts/hfs/secret-files.mjs +124 -0
  167. package/runtime/scripts/hfs/secret-init.mjs +300 -0
  168. package/runtime/scripts/hfs/secret-output.mjs +100 -0
  169. package/runtime/scripts/hfs/secret-sops.mjs +130 -0
  170. package/runtime/scripts/hfs/secret-store.mjs +163 -0
  171. package/runtime/scripts/hfs/secret.mjs +162 -193
  172. package/runtime/scripts/hfs/slot-imports.mjs +18 -3
  173. package/runtime/scripts/hfs/slot-required.mjs +13 -0
  174. package/runtime/scripts/hfs/slot-semantic-problems.mjs +2 -1
  175. package/runtime/scripts/hfs/slots.mjs +1 -0
  176. package/runtime/scripts/hfs/stack-config-kit.mjs +34 -0
  177. package/runtime/scripts/hfs/stack-deploy-config.mjs +313 -0
  178. package/runtime/scripts/hfs/stack-ingress-config.mjs +237 -0
  179. package/runtime/scripts/hfs/stacks-gate.mjs +414 -0
  180. package/runtime/scripts/hfs/starcistacks-runtime.mjs +61 -0
  181. package/runtime/scripts/hfs/tree-findings.mjs +22 -0
  182. package/runtime/scripts/lib/command-tokens.mjs +126 -0
  183. package/runtime/scripts/lib/compose-values.mjs +11 -0
  184. package/runtime/scripts/lib/deployment-findings.mjs +7 -0
  185. package/runtime/scripts/lib/json-clone.mjs +61 -0
  186. package/runtime/scripts/lib/list.mjs +4 -0
  187. package/runtime/scripts/lib/roots.mjs +59 -0
  188. package/runtime/scripts/lib/runtime-example.mjs +13 -0
  189. package/runtime/scripts/lib/secret-patterns.mjs +60 -5
  190. package/runtime/scripts/lib/sql-ast.mjs +32 -0
  191. package/runtime/scripts/lib/stack-services.mjs +1 -1
  192. package/runtime/scripts/lib/swarm-stack.mjs +126 -0
  193. package/runtime/scripts/lib/trim-end.mjs +8 -0
  194. package/runtime/scripts/lib/ts-analysis.mjs +23 -0
  195. package/runtime/scripts/lib/ts-ast.mjs +27 -2
  196. package/runtime/scripts/lib/workflow-events.mjs +8 -0
  197. package/scaffold/add-app.mjs +77 -59
  198. package/scaffold/add-cli-lite.mjs +24 -17
  199. package/scaffold/add-compose.mjs +79 -0
  200. package/scaffold/add-database-lite.mjs +14 -0
  201. package/scaffold/add-fe-overlay.mjs +122 -0
  202. package/scaffold/add-fe-page.mjs +269 -0
  203. package/scaffold/add-inbox-lite.mjs +87 -124
  204. package/scaffold/add-plan.mjs +183 -0
  205. package/scaffold/add-table.mjs +132 -70
  206. package/scaffold/add.mjs +186 -204
  207. package/scaffold/app.mjs +128 -43
  208. package/scaffold/edition-gate.mjs +3 -3
  209. package/scaffold/image.mjs +2 -2
  210. package/scaffold/lite-exports.mjs +2 -1
  211. package/scaffold/service.mjs +2 -2
  212. package/src/main.mjs +238 -119
  213. package/stack/age-install.mjs +37 -0
  214. package/stack/backup.mjs +100 -0
  215. package/stack/build-args.mjs +42 -0
  216. package/stack/build.mjs +173 -0
  217. package/stack/cli.mjs +130 -0
  218. package/stack/config.mjs +57 -0
  219. package/stack/context.mjs +57 -0
  220. package/stack/converge.mjs +107 -0
  221. package/stack/deploy.mjs +301 -0
  222. package/stack/digest.mjs +16 -0
  223. package/stack/docker-transport.mjs +53 -0
  224. package/stack/edge-alias.mjs +118 -0
  225. package/stack/errors.mjs +21 -0
  226. package/stack/image-scan.mjs +152 -0
  227. package/stack/ingress-create.mjs +114 -0
  228. package/stack/ingress-publish.mjs +273 -0
  229. package/stack/ingress.mjs +706 -0
  230. package/stack/jobs.mjs +51 -0
  231. package/stack/live-restore.mjs +177 -0
  232. package/stack/prepare.mjs +266 -0
  233. package/stack/private-file.mjs +22 -0
  234. package/stack/provision.mjs +90 -0
  235. package/stack/prune.mjs +46 -0
  236. package/stack/release-store.mjs +87 -0
  237. package/stack/remote.mjs +205 -0
  238. package/stack/render.mjs +149 -0
  239. package/stack/retire.mjs +47 -0
  240. package/stack/sealed.mjs +149 -0
  241. package/stack/task.mjs +42 -0
  242. package/stack/verify.mjs +99 -0
  243. package/sync/ci-commands.mjs +13 -0
  244. package/sync/index.mjs +127 -64
  245. package/sync/stack-images.mjs +127 -0
  246. package/templates/app/ci-workflows/github/workflows/ci.yml +5 -1
  247. package/templates/app/ci-workflows/github/workflows/images.yml +15 -3
  248. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +148 -0
  249. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +7 -3
  250. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +38 -6
  251. package/templates/app/ci-workflows-lite/github/workflows/images.yml +14 -2
  252. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +148 -0
  253. package/templates/app/codegen-graphql.mjs.partial +13 -2
  254. package/templates/app/codegen-lite/scripts/codegen.mjs +6 -1
  255. package/templates/app/codegen-prelude.mjs.partial +8 -0
  256. package/templates/app/gitignore +4 -0
  257. package/templates/app/gitignore-lite +4 -0
  258. package/templates/app/hooks/husky/pre-commit +7 -12
  259. package/templates/app/package-scripts/package.json +1 -0
  260. package/templates/app/package-scripts/supabase.json +7 -0
  261. package/templates/app/package-scripts-lite/package.json +1 -7
  262. package/templates/app/skeleton/.editorconfig +1 -0
  263. package/templates/app/skeleton/.gitattributes +7 -1
  264. package/templates/app/skeleton/.github/dependabot.yml +17 -0
  265. package/templates/app/skeleton/.nvmrc +1 -1
  266. package/templates/app/skeleton/.sops.yaml +1 -1
  267. package/templates/app/skeleton/.starcistacks/application-stacks.yaml +134 -1
  268. package/templates/app/skeleton/.starcistacks/dev/README.md +16 -0
  269. package/templates/app/skeleton/.starcistacks/dev/environment.json +5 -0
  270. package/templates/app/skeleton/.starcistacks/dev/infra/compose/stack.yaml +71 -0
  271. package/templates/app/skeleton/.starcistacks/dev/runtime/env/KEYS.md +14 -0
  272. package/templates/app/skeleton/.starcistacks/production/README.md +37 -0
  273. package/templates/app/skeleton/.starcistacks/production/environment.json +5 -0
  274. package/templates/app/skeleton/.starcistacks/production/infra/swarm/Caddyfile +15 -0
  275. package/templates/app/skeleton/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  276. package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  277. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  278. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +9 -0
  279. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  280. package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +142 -0
  281. package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +14 -0
  282. package/templates/app/skeleton/README.md +6 -6
  283. package/templates/app/skeleton/scripts/codegen.mjs +1 -2
  284. package/templates/app/skeleton-lite/.editorconfig +16 -0
  285. package/templates/app/skeleton-lite/.gitattributes +8 -0
  286. package/templates/app/skeleton-lite/.github/dependabot.yml +17 -0
  287. package/templates/app/skeleton-lite/.nvmrc +1 -0
  288. package/templates/app/skeleton-lite/.sops.yaml +3 -2
  289. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +193 -0
  290. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +20 -0
  291. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +2 -1
  292. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +16 -0
  293. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +11 -0
  294. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +7 -0
  295. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +6 -0
  296. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +8 -0
  297. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +6 -0
  298. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +120 -0
  299. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -0
  300. package/templates/app/skeleton-lite/.starcistacks/production/README.md +38 -0
  301. package/templates/app/skeleton-lite/.starcistacks/production/environment.json +6 -0
  302. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/Caddyfile +11 -0
  303. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  304. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  305. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  306. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/images.lock.yaml +8 -0
  307. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  308. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +121 -0
  309. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +21 -0
  310. package/templates/app/skeleton-lite/.starciwork/brand/index.yaml +12 -0
  311. package/templates/app/skeleton-lite/.starciwork/workspace.yaml +9 -0
  312. package/templates/app/skeleton-lite/README.md +8 -4
  313. package/templates/app/skeleton-lite/supabase/config.toml +8 -1
  314. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +15 -6
  315. package/templates/app/starciwork.gitignore-lite +6 -0
  316. package/templates/app/upgrade-full/.starcistacks/dev/README.md +13 -7
  317. package/templates/be/image/worker/Dockerfile +1 -1
  318. package/templates/be/inbox/database-client.spec.ts.tpl +22 -28
  319. package/templates/be/inbox/database-client.ts.tpl +14 -28
  320. package/templates/be/inbox/delivery-recorded.event.ts.tpl +36 -0
  321. package/templates/be/inbox/event-index.ts.tpl +1 -0
  322. package/templates/be/inbox/events.json.tpl +10 -0
  323. package/templates/be/inbox/migration.sql.tpl +1 -0
  324. package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +42 -0
  325. package/templates/be/inbox/platform/event-bus/event-bus.decorators.ts.tpl +9 -0
  326. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +6 -0
  327. package/templates/be/inbox/platform/event-bus/event-bus.module.ts.tpl +19 -0
  328. package/templates/be/inbox/platform/event-bus/event-bus.options.ts.tpl +2 -0
  329. package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +16 -0
  330. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +45 -0
  331. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +15 -0
  332. package/templates/be/inbox/platform/event-bus/index.ts.tpl +5 -0
  333. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +5 -0
  334. package/templates/be/inbox/platform/inbox/inbox.decorators.ts.tpl +9 -0
  335. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +6 -0
  336. package/templates/be/inbox/platform/inbox/inbox.module.ts.tpl +19 -0
  337. package/templates/be/inbox/platform/inbox/inbox.options.ts.tpl +2 -0
  338. package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +10 -0
  339. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +41 -0
  340. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +19 -0
  341. package/templates/be/inbox/platform/inbox/index.ts.tpl +3 -0
  342. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +7 -0
  343. package/templates/be/inbox/platform/migration.sql.tpl +48 -0
  344. package/templates/be/inbox/service.spec.ts.tpl +49 -20
  345. package/templates/be/inbox/service.ts.tpl +27 -16
  346. package/templates/be/inbox/sql.ts.tpl +2 -2
  347. package/templates/be/inbox/webhook-door.spec.ts.tpl +3 -3
  348. package/templates/be/inbox/webhook-door.ts.tpl +4 -3
  349. package/templates/be/patterns/api/feature.module-definition.ts.tpl +6 -0
  350. package/templates/be/patterns/api/feature.module.ts.tpl +2 -1
  351. package/templates/be/patterns/api/feature.options.ts.tpl +2 -0
  352. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +6 -0
  353. package/templates/be/patterns/api/graphql.module.ts.tpl +3 -2
  354. package/templates/be/patterns/api/graphql.options.ts.tpl +2 -0
  355. package/templates/be/patterns/cli/group.module-definition.ts.tpl +6 -0
  356. package/templates/be/patterns/cli/group.module.ts.tpl +2 -1
  357. package/templates/be/patterns/cli/group.options.ts.tpl +2 -0
  358. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +0 -3
  359. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +0 -2
  360. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +6 -0
  361. package/templates/be/patterns/jobs/job.module.ts.tpl +2 -1
  362. package/templates/be/patterns/jobs/job.options.ts.tpl +2 -0
  363. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +6 -0
  364. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +6 -3
  365. package/templates/be/patterns/jobs/job.queue.options.ts.tpl +2 -0
  366. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +3 -1
  367. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +25 -9
  368. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +1 -1
  369. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +1 -1
  370. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +2 -2
  371. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +11 -0
  372. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +10 -0
  373. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +6 -0
  374. package/templates/be/patterns/queues/queue.module.ts.tpl +2 -1
  375. package/templates/be/patterns/queues/queue.options.ts.tpl +2 -0
  376. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +6 -0
  377. package/templates/be/patterns/reactors/message.module.ts.tpl +6 -3
  378. package/templates/be/patterns/reactors/message.options.ts.tpl +2 -0
  379. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +6 -0
  380. package/templates/be/patterns/reactors/reactor.module.ts.tpl +2 -1
  381. package/templates/be/patterns/reactors/reactor.options.ts.tpl +2 -0
  382. package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +188 -0
  383. package/templates/be/patterns/realtime/channel.gateway.ts.tpl +22 -0
  384. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +6 -0
  385. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +2 -1
  386. package/templates/be/patterns/realtime/channel.graphql.options.ts.tpl +2 -0
  387. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +6 -0
  388. package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +110 -0
  389. package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +7 -0
  390. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +6 -0
  391. package/templates/be/patterns/saga/saga.message.module.ts.tpl +6 -3
  392. package/templates/be/patterns/saga/saga.message.options.ts.tpl +2 -0
  393. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +6 -0
  394. package/templates/be/patterns/saga/saga.module.ts.tpl +2 -1
  395. package/templates/be/patterns/saga/saga.options.ts.tpl +2 -0
  396. package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +3 -3
  397. package/templates/be/patterns/webhooks/webhook.door.ts.tpl +3 -3
  398. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +6 -0
  399. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +2 -1
  400. package/templates/be/patterns/webhooks/webhook.http.options.ts.tpl +2 -0
  401. package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +80 -26
  402. package/templates/be/platform/graphql/localize-errors.mapper.ts +2 -5
  403. package/templates/be/skeleton/apps/__app__/src/__app__.env.ts +21 -0
  404. package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +1 -1
  405. package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -45
  406. package/templates/be/skeleton/apps/__app__/src/compose/api.ts +4 -0
  407. package/templates/be/skeleton/apps/__app__/src/compose/domain.ts +11 -0
  408. package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +44 -0
  409. package/templates/be/skeleton/apps/__app__/src/compose/system.ts +5 -0
  410. package/templates/be/skeleton/apps/__app__/src/compose/webhooks.ts +4 -0
  411. package/templates/be/skeleton/apps/__app__/src/main.ts +18 -10
  412. package/templates/be/skeleton/apps/cli/src/app.module.ts +3 -13
  413. package/templates/be/skeleton/apps/cli/src/cli.env.ts +13 -0
  414. package/templates/be/skeleton/apps/cli/src/cli.options.ts +7 -17
  415. package/templates/be/skeleton/apps/cli/src/compose/cli.ts +5 -0
  416. package/templates/be/skeleton/apps/cli/src/compose/platform.ts +24 -0
  417. package/templates/be/skeleton/apps/cli/src/main.ts +3 -2
  418. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.contracts.ts +2 -0
  419. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.handler.ts +22 -0
  420. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.query.ts +11 -0
  421. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +6 -0
  422. package/templates/be/skeleton/src/features/api/system-health/system-health.module.ts +5 -3
  423. package/templates/be/skeleton/src/features/api/system-health/system-health.options.ts +2 -0
  424. package/templates/be/skeleton/src/features/api/system-health/transport/http/ready.controller.ts +19 -0
  425. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +6 -0
  426. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module.ts +8 -3
  427. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.options.ts +2 -0
  428. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +6 -0
  429. package/templates/be/skeleton/src/features/cli/cli.module.ts +3 -2
  430. package/templates/be/skeleton/src/features/cli/cli.options.ts +2 -0
  431. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +6 -0
  432. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +2 -1
  433. package/templates/be/skeleton/src/features/cli/migrate/migrate.options.ts +2 -0
  434. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +6 -0
  435. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +2 -1
  436. package/templates/be/skeleton/src/features/cli/seed/seed.options.ts +2 -0
  437. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +5 -6
  438. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +56 -4
  439. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +36 -5
  440. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +39 -8
  441. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +14 -8
  442. package/templates/be/skeleton/src/modules/domain/identity/credential.contracts.ts +26 -0
  443. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.spec.ts +119 -0
  444. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.ts +43 -0
  445. package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +16 -0
  446. package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -1
  447. package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +22 -4
  448. package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +8 -2
  449. package/templates/be/skeleton/src/modules/domain/identity/index.ts +1 -0
  450. package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +6 -6
  451. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +11 -4
  452. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.spec.ts +32 -0
  453. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.ts +14 -0
  454. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +7 -1
  455. package/templates/be/skeleton/src/modules/platform/database/index.ts +2 -0
  456. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +8 -0
  457. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +25 -6
  458. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +16 -6
  459. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.spec.ts +130 -0
  460. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.ts +58 -0
  461. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +3 -0
  462. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -4
  463. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -10
  464. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +3 -1
  465. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +2 -0
  466. package/templates/be/skeleton/src/modules/platform/http-security/index.ts +5 -0
  467. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +12 -5
  468. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +13 -8
  469. package/templates/be/skeleton/src/modules/platform/http-security/readiness.contracts.ts +5 -0
  470. package/templates/be/skeleton/src/modules/platform/http-security/readiness.decorators.ts +7 -0
  471. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.spec.ts +86 -0
  472. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.ts +39 -0
  473. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +139 -131
  474. package/templates/be/skeleton/src/modules/platform/i18n/index.ts +1 -1
  475. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +91 -67
  476. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +5 -4
  477. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +0 -2
  478. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +20 -6
  479. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +16 -4
  480. package/templates/be/skeleton/src/modules/platform/retry/index.ts +3 -0
  481. package/templates/be/skeleton/src/modules/platform/retry/retry.decorators.ts +9 -0
  482. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +7 -0
  483. package/templates/be/skeleton/src/modules/platform/retry/retry.module.ts +19 -0
  484. package/templates/be/skeleton/src/modules/platform/retry/retry.options.ts +2 -0
  485. package/templates/be/skeleton/src/modules/platform/retry/retry.port.ts +11 -0
  486. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.spec.ts +36 -0
  487. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.ts +10 -0
  488. package/templates/be/skeleton/src/tests/contract/http-security/http-security.contract-spec.ts +54 -0
  489. package/templates/be/skeleton-lite/apps/api/src/api.env.ts +25 -0
  490. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +1 -1
  491. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +18 -58
  492. package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +4 -0
  493. package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +10 -0
  494. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +16 -0
  495. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +47 -0
  496. package/templates/be/skeleton-lite/apps/api/src/compose/system.ts +5 -0
  497. package/templates/be/skeleton-lite/apps/api/src/compose/webhooks.ts +4 -0
  498. package/templates/be/skeleton-lite/apps/api/src/main.ts +5 -13
  499. package/templates/be/skeleton-lite/apps/cli/src/cli.env.ts +17 -0
  500. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +12 -0
  501. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.spec.ts +71 -5
  502. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +29 -2
  503. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +68 -15
  504. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +14 -9
  505. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +9 -2
  506. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +16 -1
  507. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +19 -4
  508. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +5 -1
  509. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -1
  510. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -3
  511. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +17 -5
  512. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +24 -13
  513. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +5 -0
  514. package/templates/be/skeleton-lite/src/modules/platform/database/errors/database.error.ts +16 -0
  515. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +3 -0
  516. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +5 -2
  517. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +4 -1
  518. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +1 -1
  519. package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +7 -0
  520. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +29 -0
  521. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +16 -0
  522. package/templates/be/table/api/feature.module-definition.ts.tpl +6 -0
  523. package/templates/be/table/api/feature.module.ts.tpl +2 -1
  524. package/templates/be/table/api/feature.options.ts.tpl +2 -0
  525. package/templates/be/table/api/graphql.module-definition.ts.tpl +6 -0
  526. package/templates/be/table/api/graphql.module.ts.tpl +3 -2
  527. package/templates/be/table/api/graphql.options.ts.tpl +2 -0
  528. package/templates/be/table/migration.sql.tpl +1 -0
  529. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +4 -6
  530. package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +7 -0
  531. package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +5 -0
  532. package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +9 -0
  533. package/templates/be/upgrade-full/apps/cli/src/main.ts +3 -0
  534. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +3 -2
  535. package/templates/fe/auth/supabase-client.ts.tpl +34 -0
  536. package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +11 -0
  537. package/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/index.tsx +6 -1
  538. package/templates/fe/skeleton/apps/__app__/src/components/leaves/Icon/index.tsx +4 -0
  539. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/component.tsx +31 -0
  540. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/index.tsx +24 -0
  541. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +6 -1
  542. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +3 -4
  543. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/index.ts +1 -0
  544. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +15 -0
  545. package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +18 -0
  546. package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +19 -0
  547. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +7 -1
  548. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/en.json +57 -0
  549. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +6 -0
  550. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/en.json +31 -0
  551. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +1 -1
  552. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +1 -0
  553. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +1 -34
  554. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +7 -1
  555. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +57 -0
  556. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +30 -24
  557. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +1 -1
  558. package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
  559. package/templates/fe/transport/graphql/clients/links/locale.ts +2 -2
  560. package/upgrade/check.mjs +3 -1
  561. package/upgrade/file-state.mjs +68 -0
  562. package/upgrade/index.mjs +58 -94
  563. package/runtime/scripts/api/git/log.mjs +0 -5
  564. package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +0 -55
@@ -1,47 +1,41 @@
1
- import { fakeTransaction, mockEntityManager } from "@starci/jest-preset"
1
+ import { mockEntityManager } from "@starci/jest-preset"
2
2
  import type { EntityManager } from "typeorm"
3
3
  import { sql } from "./database.sql"
4
4
  import { DatabaseErrorCode } from "./errors/database.error"
5
- import { acceptWebhookDelivery } from "./webhook-inbox.client"
5
+ import { recordWebhookDelivery } from "./webhook-inbox.client"
6
6
 
7
- // The client runs whatever claim and completion statements its caller owns; opaque function calls stand in for them here.
8
- const INSERT = sql`SELECT id FROM claim_spec_inbox($1, $2, $3::jsonb)`
9
- const COMPLETE = sql`SELECT id FROM complete_spec_inbox($1)`
7
+ // The caller owns the conflict-safe pending-row insert; an opaque function call stands in for it here.
8
+ const INSERT = sql`SELECT id FROM record_spec_inbox($1, $2, $3::jsonb)`
10
9
  const PAYLOAD = { id: "delivery-1" }
11
10
 
12
- const accept = (entityManager: EntityManager, deliveryId: string | undefined, process: (manager: EntityManager) => Promise<void>) =>
13
- acceptWebhookDelivery({ entityManager, insert: INSERT, complete: COMPLETE, provider: "spec", deliveryId, payload: PAYLOAD, process })
11
+ const record = (manager: EntityManager, deliveryId: string | undefined) =>
12
+ recordWebhookDelivery({ manager, insert: INSERT, provider: "spec", deliveryId, payload: PAYLOAD })
14
13
 
15
- describe("acceptWebhookDelivery", () => {
16
- it("claims a new delivery, processes it and marks it complete in one committed transaction", async () => {
17
- const transaction = fakeTransaction(mockEntityManager({ query: [[INSERT, [{ id: "inbox-1" }]], [COMPLETE, [{ id: "inbox-1" }]]] }))
18
- const process = jest.fn<Promise<void>, [EntityManager]>().mockResolvedValue(undefined)
14
+ describe("recordWebhookDelivery", () => {
15
+ it("records a new delivery through the caller's transaction manager", async () => {
16
+ const manager = mockEntityManager({ query: [INSERT, [{ id: "inbox-1" }]] })
19
17
 
20
- await accept(transaction.em, " delivery-1 ", process)
18
+ const receiptId = await record(manager, " delivery-1 ")
21
19
 
22
- expect(process).toHaveBeenCalledTimes(1)
23
- expect(transaction.em.query).toHaveBeenCalledWith(INSERT, ["spec", "delivery-1", JSON.stringify(PAYLOAD)])
24
- expect(transaction.em.query).toHaveBeenCalledWith(COMPLETE, ["inbox-1"])
25
- expect(transaction.commits).toBe(1)
20
+ expect(manager.query).toHaveBeenCalledTimes(1)
21
+ expect(manager.query).toHaveBeenCalledWith(INSERT, ["spec", "delivery-1", JSON.stringify(PAYLOAD)])
22
+ expect(receiptId).toBe("inbox-1")
26
23
  })
27
24
 
28
- it("acknowledges a duplicate without processing it again", async () => {
29
- const transaction = fakeTransaction(mockEntityManager({ query: [INSERT, []] }))
30
- const process = jest.fn<Promise<void>, [EntityManager]>()
25
+ it("acknowledges a duplicate without any completion or business write", async () => {
26
+ const manager = mockEntityManager({ query: [INSERT, []] })
31
27
 
32
- await accept(transaction.em, "delivery-1", process)
28
+ const receiptId = await record(manager, "delivery-1")
33
29
 
34
- expect(process).not.toHaveBeenCalled()
35
- expect(transaction.em.query).toHaveBeenCalledTimes(1)
30
+ expect(manager.query).toHaveBeenCalledTimes(1)
31
+ expect(receiptId).toBeUndefined()
36
32
  })
37
33
 
38
- it.each([["no", undefined], ["a blank", " "]])("refuses %s delivery id before opening a transaction", async (_label, deliveryId) => {
39
- const transaction = fakeTransaction(mockEntityManager())
40
- const process = jest.fn<Promise<void>, [EntityManager]>()
34
+ it.each([["no", undefined], ["a blank", " "]])("refuses %s delivery id before writing a receipt", async (_label, deliveryId) => {
35
+ const manager = mockEntityManager()
41
36
 
42
- await expect(accept(transaction.em, deliveryId, process)).rejects.toMatchObject({ code: DatabaseErrorCode.WebhookDeliveryIdRequired })
37
+ await expect(record(manager, deliveryId)).rejects.toMatchObject({ code: DatabaseErrorCode.WebhookDeliveryIdRequired })
43
38
 
44
- expect(transaction.outcomes).toEqual([])
45
- expect(process).not.toHaveBeenCalled()
39
+ expect(manager.query).not.toHaveBeenCalled()
46
40
  })
47
41
  })
@@ -2,49 +2,35 @@ import type { EntityManager } from "typeorm"
2
2
  import { DatabaseError, DatabaseErrorCode } from "./errors/database.error"
3
3
  import type { SqlText } from "./database.sql"
4
4
 
5
- interface WebhookInboxStatements {
5
+ interface RecordWebhookDelivery {
6
+ readonly manager: EntityManager
6
7
  readonly insert: SqlText
7
- readonly complete: SqlText
8
- }
9
-
10
- interface AcceptWebhookDelivery extends WebhookInboxStatements {
11
- readonly entityManager: EntityManager
12
8
  readonly provider: string
13
9
  readonly deliveryId: string | undefined
14
10
  readonly payload: object
15
- readonly process: (manager: EntityManager) => Promise<void>
16
11
  }
17
12
 
18
- interface InboxIdentity {
19
- readonly id: string
20
- }
13
+ type WebhookReceiptId = string | undefined
21
14
 
22
15
  /**
23
- * Claims and processes one delivery in one EntityManager transaction. A conflict returns without calling `process`, so
24
- * a redelivery receives the original empty acknowledgement and cannot repeat the domain mutation.
16
+ * Records one pending delivery using the intake's transaction manager and returns its durable receipt identity.
17
+ * Business processing and the completion marker belong to the reactor or job that consumes the committed row.
25
18
  */
26
- export const acceptWebhookDelivery = async ({
27
- entityManager,
19
+ export const recordWebhookDelivery = async ({
20
+ manager,
28
21
  insert,
29
- complete,
30
22
  provider,
31
23
  deliveryId,
32
24
  payload,
33
- process,
34
- }: AcceptWebhookDelivery): Promise<void> => {
25
+ }: RecordWebhookDelivery): Promise<WebhookReceiptId> => {
35
26
  const normalizedDeliveryId = deliveryId?.trim()
36
27
  if (!normalizedDeliveryId) {
37
28
  throw new DatabaseError({ code: DatabaseErrorCode.WebhookDeliveryIdRequired })
38
29
  }
39
- await entityManager.transaction(async (manager) => {
40
- const claimed = await manager.query<Array<InboxIdentity>>(insert, [
41
- provider,
42
- normalizedDeliveryId,
43
- JSON.stringify(payload),
44
- ])
45
- const identity = claimed[0]
46
- if (identity === undefined) return
47
- await process(manager)
48
- await manager.query(complete, [identity.id])
49
- })
30
+ const rows: Array<{ readonly id: string }> = await manager.query(insert, [
31
+ provider,
32
+ normalizedDeliveryId,
33
+ JSON.stringify(payload),
34
+ ])
35
+ return rows[0]?.id
50
36
  }
@@ -0,0 +1,36 @@
1
+ import { BaseEvent } from "@modules/platform/event-bus"
2
+ import type { ParsedEvent } from "@modules/platform/event-bus"
3
+
4
+ /** A durable receipt reference; processing reads the provider payload after the intake commits. */
5
+ export interface @@Provider@@DeliveryRecordedPayload {
6
+ readonly receiptId: string
7
+ }
8
+
9
+ /** Version 1 of `@@provider@@.delivery-recorded`, published once by the provider inbox intake. */
10
+ export class @@Provider@@DeliveryRecordedEvent extends BaseEvent {
11
+ static readonly eventName = "@@provider@@.delivery-recorded"
12
+ static readonly version = 1
13
+
14
+ readonly eventName = @@Provider@@DeliveryRecordedEvent.eventName
15
+
16
+ private constructor(
17
+ readonly eventId: string,
18
+ readonly payload: @@Provider@@DeliveryRecordedPayload,
19
+ ) {
20
+ super()
21
+ }
22
+
23
+ /** The receipt identity is stable across relay retries and distinct from a consumer's inbox source. */
24
+ static create(payload: @@Provider@@DeliveryRecordedPayload): @@Provider@@DeliveryRecordedEvent {
25
+ return new @@Provider@@DeliveryRecordedEvent(payload.receiptId, payload)
26
+ }
27
+
28
+ /** Reads an outbox envelope; malformed deliveries never reach a processing consumer. */
29
+ static parse(envelope: unknown): ParsedEvent<@@Provider@@DeliveryRecordedEvent> {
30
+ if (typeof envelope !== "object" || envelope === null || !("eventId" in envelope) || !("payload" in envelope)) return null
31
+ const { eventId, payload } = envelope
32
+ if (typeof eventId !== "string" || !eventId.trim() || typeof payload !== "object" || payload === null || !("receiptId" in payload)) return null
33
+ if (typeof payload.receiptId !== "string" || !payload.receiptId.trim()) return null
34
+ return new @@Provider@@DeliveryRecordedEvent(eventId, { receiptId: payload.receiptId })
35
+ }
36
+ }
@@ -0,0 +1 @@
1
+ export { @@Provider@@DeliveryRecordedEvent } from "./@@provider@@-delivery-recorded.event"
@@ -0,0 +1,10 @@
1
+ {
2
+ "events": {
3
+ "@@provider@@.delivery-recorded": {
4
+ "payload": { "receiptId": "string" },
5
+ "version": 1
6
+ }
7
+ },
8
+ "schema": "starci/event-contract@1",
9
+ "service": "@@provider@@"
10
+ }
@@ -9,6 +9,7 @@ create table public.@@table@@ (
9
9
  );
10
10
 
11
11
  alter table public.@@table@@ enable row level security;
12
+ alter table public.@@table@@ force row level security;
12
13
 
13
14
  -- The dedicated app_be PostgreSQL role is the only application path. With no anon or authenticated policy,
14
15
  -- RLS denies those PostgREST roles by default; the service-role JWT is deliberately not granted here.
@@ -0,0 +1,42 @@
1
+ /** The base of each typed event in modules/events: its stable identity, name and payload. */
2
+ export abstract class BaseEvent {
3
+ /** A receiver uses this stable identity to deduplicate redelivery. */
4
+ abstract readonly eventId: string
5
+ /** The event name listed by the service's vendored event contract. */
6
+ abstract readonly eventName: string
7
+ /** The fact that a reactor or job processes after commit. */
8
+ abstract readonly payload: object
9
+ }
10
+
11
+ /** Parsing either returns the event or refuses a mismatched envelope. */
12
+ export type ParsedEvent<Event extends BaseEvent> = Event | null
13
+
14
+ /** The class side of a typed event, shared by publishers and later consumers. */
15
+ export interface EventClass<Event extends BaseEvent> {
16
+ /** The contract's event name. */
17
+ readonly eventName: string
18
+ /** The contract's payload version. */
19
+ readonly version: number
20
+ /** Reads the event from its durable envelope. */
21
+ parse(envelope: unknown): ParsedEvent<Event>
22
+ }
23
+
24
+ /** One committed delivery received by a reactor. */
25
+ export interface EventDelivery<Event extends BaseEvent> {
26
+ /** The consumer's idempotency key. */
27
+ readonly eventId: string
28
+ /** The typed event. */
29
+ readonly event: Event
30
+ /** The number of attempts, including this one. */
31
+ readonly attempt: number
32
+ }
33
+
34
+ /** The typed event's durable envelope, read only after the intake transaction commits. */
35
+ export interface EventEnvelope {
36
+ /** The stable event id. */
37
+ readonly eventId: string
38
+ /** The contract's event name. */
39
+ readonly eventName: string
40
+ /** The data of the fact. */
41
+ readonly payload: object
42
+ }
@@ -0,0 +1,9 @@
1
+ import { injector } from "@modules/platform/composition"
2
+ import type { TypedParameterDecorator } from "@modules/platform/composition"
3
+ import type { EventBus } from "./event-bus.port"
4
+
5
+ /** Token of the transactional EventBus publisher. */
6
+ export const EVENT_BUS: unique symbol = Symbol("platform.event-bus")
7
+
8
+ /** Injects the EventBus port. Parameter type: EventBus. */
9
+ export const InjectEventBus = (): TypedParameterDecorator<EventBus> => injector<EventBus>(EVENT_BUS)
@@ -0,0 +1,6 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { EventBusOptions } from "./event-bus.options"
3
+
4
+ export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<EventBusOptions>()
5
+ .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
6
+ .build()
@@ -0,0 +1,19 @@
1
+ import { Module } from "@nestjs/common"
2
+ import type { DynamicModule } from "@nestjs/common"
3
+ import { EVENT_BUS } from "./event-bus.decorators"
4
+ import { ConfigurableModuleClass, OPTIONS_TYPE } from "./event-bus.module-definition"
5
+ import { EventBusService } from "./event-bus.service"
6
+
7
+ @Module({})
8
+ /** Provides the transactional lite EventBus publisher, shared by domain intake services. */
9
+ export class EventBusModule extends ConfigurableModuleClass {
10
+ /** Registers the publishing capability once per app. */
11
+ static register(options: typeof OPTIONS_TYPE): DynamicModule {
12
+ const base = super.register(options)
13
+ return {
14
+ ...base,
15
+ providers: [...(base.providers ?? []), { provide: EVENT_BUS, useClass: EventBusService }],
16
+ exports: [EVENT_BUS],
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,2 @@
1
+ /** The lite publisher uses the caller's transaction and has no runtime options. */
2
+ export interface EventBusOptions {}
@@ -0,0 +1,16 @@
1
+ import type { EntityManager } from "typeorm"
2
+ import type { BaseEvent, EventClass, EventDelivery } from "./event-bus.contracts"
3
+
4
+ /** The publishing side of the lite event bus; the reactor or job owns delivery of committed outbox rows. */
5
+ export interface EventBus {
6
+ /** Writes the typed event in the caller's transaction; a rollback writes no durable event. */
7
+ publish(event: BaseEvent, tx: EntityManager): Promise<void>
8
+ }
9
+
10
+ /** A later message transport receives one typed event and forwards its delivery id. */
11
+ export interface EventConsumer<Event extends BaseEvent> {
12
+ /** The event class the consumer reads. */
13
+ readonly event: EventClass<Event>
14
+ /** Applies one delivery after commit; a throw leaves the delivery available for retry. */
15
+ handle(delivery: EventDelivery<Event>): Promise<void>
16
+ }
@@ -0,0 +1,45 @@
1
+ import { Test } from "@nestjs/testing"
2
+ import { fakeTransaction, mockEntityManager } from "@starci/jest-preset"
3
+ import { BaseEvent } from "./event-bus.contracts"
4
+ import { EventBusService } from "./event-bus.service"
5
+ import { INSERT_OUTBOX_EVENT } from "./persistence/event-bus.sql"
6
+
7
+ class ReceiptRecordedEvent extends BaseEvent {
8
+ readonly eventId = "receipt-1"
9
+ readonly eventName = "spec.delivery-recorded"
10
+ readonly payload = { receiptId: "receipt-1" }
11
+ }
12
+
13
+ describe("EventBusService", () => {
14
+ const build = async () => {
15
+ const module = await Test.createTestingModule({ providers: [EventBusService] }).compile()
16
+ return module.get(EventBusService)
17
+ }
18
+
19
+ it("writes the typed envelope through the receipt transaction", async () => {
20
+ const subject = await build()
21
+ const transaction = fakeTransaction(mockEntityManager({ query: [INSERT_OUTBOX_EVENT, []] }))
22
+ const event = new ReceiptRecordedEvent()
23
+
24
+ await transaction.em.transaction((manager) => subject.publish(event, manager))
25
+
26
+ expect(transaction.em.query).toHaveBeenCalledWith(INSERT_OUTBOX_EVENT, [
27
+ "receipt-1",
28
+ "spec.delivery-recorded",
29
+ JSON.stringify({ eventId: event.eventId, eventName: event.eventName, payload: event.payload }),
30
+ ])
31
+ expect(transaction.commits).toBe(1)
32
+ })
33
+
34
+ it("propagates an outbox failure so the receipt transaction rolls back", async () => {
35
+ const subject = await build()
36
+ const manager = mockEntityManager({ query: [INSERT_OUTBOX_EVENT, []] })
37
+ jest.spyOn(manager, "query").mockRejectedValue(new Error("outbox unavailable"))
38
+ const transaction = fakeTransaction(manager)
39
+
40
+ await expect(transaction.em.transaction((tx) => subject.publish(new ReceiptRecordedEvent(), tx))).rejects.toThrow("outbox unavailable")
41
+
42
+ expect(transaction.commits).toBe(0)
43
+ expect(transaction.rollbacks).toBe(1)
44
+ })
45
+ })
@@ -0,0 +1,15 @@
1
+ import { Injectable } from "@nestjs/common"
2
+ import type { EntityManager } from "typeorm"
3
+ import type { BaseEvent, EventEnvelope } from "./event-bus.contracts"
4
+ import type { EventBus } from "./event-bus.port"
5
+ import { INSERT_OUTBOX_EVENT } from "./persistence/event-bus.sql"
6
+
7
+ @Injectable()
8
+ /** Writes a receipt event into the same transaction as its inbox receipt, without running any consumer. */
9
+ export class EventBusService implements EventBus {
10
+ /** The outbox commits or rolls back with the receipt; only a later reactor or job processes it. */
11
+ async publish(event: BaseEvent, tx: EntityManager): Promise<void> {
12
+ const envelope: EventEnvelope = { eventId: event.eventId, eventName: event.eventName, payload: event.payload }
13
+ await tx.query(INSERT_OUTBOX_EVENT, [event.eventId, event.eventName, JSON.stringify(envelope)])
14
+ }
15
+ }
@@ -0,0 +1,5 @@
1
+ export { BaseEvent } from "./event-bus.contracts"
2
+ export type { EventClass, EventDelivery, EventEnvelope, ParsedEvent } from "./event-bus.contracts"
3
+ export { EVENT_BUS, InjectEventBus } from "./event-bus.decorators"
4
+ export { EventBusModule } from "./event-bus.module"
5
+ export type { EventBus, EventConsumer } from "./event-bus.port"
@@ -0,0 +1,5 @@
1
+ import { sql } from "@modules/platform/database"
2
+
3
+ /** Writes only the typed envelope; a reactor or job reads the committed row afterward. */
4
+ export const INSERT_OUTBOX_EVENT = sql`INSERT INTO public.event_bus (event_id, event_name, payload)
5
+ VALUES ($1, $2, $3::jsonb)`
@@ -0,0 +1,9 @@
1
+ import { injector } from "@modules/platform/composition"
2
+ import type { TypedParameterDecorator } from "@modules/platform/composition"
3
+ import type { Inbox } from "./inbox.port"
4
+
5
+ /** Token of the shared, transaction-owned inbox claims. */
6
+ export const INBOX: unique symbol = Symbol("platform.inbox")
7
+
8
+ /** Injects the Inbox port. Parameter type: Inbox. */
9
+ export const InjectInbox = (): TypedParameterDecorator<Inbox> => injector<Inbox>(INBOX)
@@ -0,0 +1,6 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { InboxOptions } from "./inbox.options"
3
+
4
+ export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<InboxOptions>()
5
+ .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
6
+ .build()
@@ -0,0 +1,19 @@
1
+ import { Module } from "@nestjs/common"
2
+ import type { DynamicModule } from "@nestjs/common"
3
+ import { INBOX } from "./inbox.decorators"
4
+ import { ConfigurableModuleClass, OPTIONS_TYPE } from "./inbox.module-definition"
5
+ import { PostgresInbox } from "./inbox.service"
6
+
7
+ @Module({})
8
+ /** Provides the lite Inbox port; all claims use the domain service's own transaction. */
9
+ export class InboxModule extends ConfigurableModuleClass {
10
+ /** Registers the shared inbox once per app. */
11
+ static register(options: typeof OPTIONS_TYPE): DynamicModule {
12
+ const base = super.register(options)
13
+ return {
14
+ ...base,
15
+ providers: [...(base.providers ?? []), { provide: INBOX, useClass: PostgresInbox }],
16
+ exports: [INBOX],
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,2 @@
1
+ /** The lite inbox uses the caller's transaction and has no runtime options. */
2
+ export interface InboxOptions {}
@@ -0,0 +1,10 @@
1
+ import type { EntityManager } from "typeorm"
2
+
3
+ /** The signed provider header may omit its delivery identity; the adapter refuses that delivery. */
4
+ export type InboxEventId = string | undefined
5
+
6
+ /** Deduplicates a delivery inside the transaction that records its receipt or applies its effect. */
7
+ export interface Inbox {
8
+ /** Claims once per source and event id; a rolled-back transaction gives the claim back automatically. */
9
+ claim(source: string, eventId: InboxEventId, tx: EntityManager): Promise<boolean>
10
+ }
@@ -0,0 +1,41 @@
1
+ import { Test } from "@nestjs/testing"
2
+ import { fakeTransaction, mockEntityManager } from "@starci/jest-preset"
3
+ import { DatabaseErrorCode } from "@modules/platform/database"
4
+ import { PostgresInbox } from "./inbox.service"
5
+ import { CLAIM_EVENT } from "./persistence/inbox.sql"
6
+
7
+ describe("PostgresInbox", () => {
8
+ const build = async () => {
9
+ const module = await Test.createTestingModule({ providers: [PostgresInbox] }).compile()
10
+ return module.get(PostgresInbox)
11
+ }
12
+
13
+ it("claims a normalized delivery id through the caller's transaction", async () => {
14
+ const subject = await build()
15
+ const transaction = fakeTransaction(mockEntityManager({ query: [CLAIM_EVENT, [{ event_id: "delivery-1" }]] }))
16
+
17
+ const fresh = await transaction.em.transaction((manager) => subject.claim("webhook:spec", " delivery-1 ", manager))
18
+
19
+ expect(fresh).toBe(true)
20
+ expect(transaction.em.query).toHaveBeenCalledWith(CLAIM_EVENT, ["webhook:spec", "delivery-1"])
21
+ expect(transaction.commits).toBe(1)
22
+ })
23
+
24
+ it("returns false when the source and event id have already been claimed", async () => {
25
+ const subject = await build()
26
+ const manager = mockEntityManager({ query: [CLAIM_EVENT, []] })
27
+
28
+ await expect(subject.claim("webhook:spec", "delivery-1", manager)).resolves.toBe(false)
29
+
30
+ expect(manager.query).toHaveBeenCalledTimes(1)
31
+ })
32
+
33
+ it.each([["no", undefined], ["a blank", " "]])("refuses %s delivery id before writing a claim", async (_label, eventId) => {
34
+ const subject = await build()
35
+ const manager = mockEntityManager()
36
+
37
+ await expect(subject.claim("webhook:spec", eventId, manager)).rejects.toMatchObject({ code: DatabaseErrorCode.WebhookDeliveryIdRequired })
38
+
39
+ expect(manager.query).not.toHaveBeenCalled()
40
+ })
41
+ })
@@ -0,0 +1,19 @@
1
+ import { Injectable } from "@nestjs/common"
2
+ import type { EntityManager } from "typeorm"
3
+ import { DatabaseError, DatabaseErrorCode } from "@modules/platform/database"
4
+ import type { Inbox, InboxEventId } from "./inbox.port"
5
+ import { CLAIM_EVENT } from "./persistence/inbox.sql"
6
+
7
+ @Injectable()
8
+ /** Claims a delivery with the receipt transaction, so a failure can be retried without losing the delivery. */
9
+ export class PostgresInbox implements Inbox {
10
+ /** The first claim wins; the same source and delivery id becomes a no-op on redelivery. */
11
+ async claim(source: string, eventId: InboxEventId, tx: EntityManager): Promise<boolean> {
12
+ const normalizedEventId = eventId?.trim()
13
+ if (!normalizedEventId) {
14
+ throw new DatabaseError({ code: DatabaseErrorCode.WebhookDeliveryIdRequired })
15
+ }
16
+ const rows: Array<object> = await tx.query(CLAIM_EVENT, [source, normalizedEventId])
17
+ return rows.length > 0
18
+ }
19
+ }
@@ -0,0 +1,3 @@
1
+ export { INBOX, InjectInbox } from "./inbox.decorators"
2
+ export { InboxModule } from "./inbox.module"
3
+ export type { Inbox, InboxEventId } from "./inbox.port"
@@ -0,0 +1,7 @@
1
+ import { sql } from "@modules/platform/database"
2
+
3
+ /** The unique source and delivery id let only one transaction record or process each delivery. */
4
+ export const CLAIM_EVENT = sql`INSERT INTO public.inbox (source, event_id)
5
+ VALUES ($1, $2)
6
+ ON CONFLICT (source, event_id) DO NOTHING
7
+ RETURNING event_id`
@@ -0,0 +1,48 @@
1
+ -- Both the claim and receipt event use the intake transaction; after-commit processing reads the outbox separately.
2
+ create table public.inbox (
3
+ source text not null check (source <> ''),
4
+ event_id text not null check (event_id <> ''),
5
+ claimed_at timestamptz not null default now(),
6
+ primary key (source, event_id)
7
+ );
8
+
9
+ alter table public.inbox enable row level security;
10
+ alter table public.inbox force row level security;
11
+
12
+ create policy "inbox_app_be_select"
13
+ on public.inbox for select to app_be
14
+ using (source <> '');
15
+
16
+ create policy "inbox_app_be_insert"
17
+ on public.inbox for insert to app_be
18
+ with check (source <> '');
19
+
20
+ revoke all on table public.inbox from public, anon, authenticated;
21
+ grant select, insert on table public.inbox to app_be;
22
+
23
+ create table public.event_bus (
24
+ event_id text primary key check (event_id <> ''),
25
+ event_name text not null check (event_name <> ''),
26
+ payload jsonb not null,
27
+ recorded_at timestamptz not null default now(),
28
+ delivered_at timestamptz
29
+ );
30
+
31
+ alter table public.event_bus enable row level security;
32
+ alter table public.event_bus force row level security;
33
+
34
+ create policy "event_bus_app_be_select"
35
+ on public.event_bus for select to app_be
36
+ using (event_name <> '');
37
+
38
+ create policy "event_bus_app_be_insert"
39
+ on public.event_bus for insert to app_be
40
+ with check (event_name <> '');
41
+
42
+ create policy "event_bus_app_be_update"
43
+ on public.event_bus for update to app_be
44
+ using (event_name <> '')
45
+ with check (event_name <> '');
46
+
47
+ revoke all on table public.event_bus from public, anon, authenticated;
48
+ grant select, insert, update on table public.event_bus to app_be;