@cosmicdrift/kumiko-framework 0.389.0 → 0.390.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 (616) hide show
  1. package/AGENTS.md +9 -4
  2. package/README.md +1 -1
  3. package/agents/examples/apex-surface-auth.md +2 -3
  4. package/agents/examples/api-worker-split.md +9 -2
  5. package/agents/examples/auth-claims.md +1 -1
  6. package/agents/examples/cross-feature-events.md +12 -12
  7. package/agents/examples/crypto-shredding-hr.md +2 -2
  8. package/agents/examples/encrypted-tenant-config.md +1 -1
  9. package/agents/examples/error-contract.md +12 -7
  10. package/agents/examples/event-sourcing.md +29 -29
  11. package/agents/examples/files-post-processing.md +10 -8
  12. package/agents/examples/inbound-mail-triage.md +1 -0
  13. package/agents/examples/lane-routing.md +2 -1
  14. package/agents/examples/legal-pages.md +2 -2
  15. package/agents/examples/lifecycle-hooks.md +3 -3
  16. package/agents/examples/managed-config.md +4 -4
  17. package/agents/examples/managed-pages.md +1 -1
  18. package/agents/examples/relations.md +19 -4
  19. package/agents/examples/search.md +4 -4
  20. package/agents/examples/secrets-demo.md +9 -6
  21. package/agents/examples/state-machine.md +2 -2
  22. package/agents/examples/tenant-settings.md +1 -1
  23. package/agents/examples/user-data-rights.md +3 -6
  24. package/agents/examples/workflow-engine.md +53 -47
  25. package/agents/examples.md +4 -4
  26. package/agents/features.md +1 -1
  27. package/dist/api/auth-middleware.js +1 -1
  28. package/dist/api/auth-routes-invite.js +2 -1
  29. package/dist/api/entity-read-access.d.ts +3 -1
  30. package/dist/api/export/export-columns.d.ts +3 -1
  31. package/dist/api/export/export-route.js +3 -2
  32. package/dist/api/export/export-screen.d.ts +2 -1
  33. package/dist/api/extra-route.d.ts +3 -1
  34. package/dist/api/index.d.ts +23 -27
  35. package/dist/api/index.js +13 -15
  36. package/dist/api/jwt.js +1 -1
  37. package/dist/api/observability-middleware.d.ts +1 -1
  38. package/dist/api/observability-middleware.js +2 -1
  39. package/dist/api/request-context-internals.d.ts +31 -0
  40. package/dist/api/request-context-internals.js +11 -0
  41. package/dist/api/request-context-scopes.d.ts +14 -0
  42. package/dist/api/request-context-scopes.js +69 -0
  43. package/dist/api/request-context.d.ts +1 -40
  44. package/dist/api/request-context.js +22 -51
  45. package/dist/api/request-id-middleware.d.ts +1 -1
  46. package/dist/api/route-registrars.d.ts +2 -1
  47. package/dist/api/route-registrars.js +1 -1
  48. package/dist/api/routes.js +7 -3
  49. package/dist/api/server-boot-checks.d.ts +1 -1
  50. package/dist/api/server-boot-checks.js +2 -2
  51. package/dist/api/server-event-dispatcher.d.ts +6 -1
  52. package/dist/api/server-event-dispatcher.js +3 -4
  53. package/dist/api/server-extra-routes.d.ts +1 -1
  54. package/dist/api/server-extra-routes.js +2 -1
  55. package/dist/api/server-middleware.js +3 -1
  56. package/dist/api/server-routes.d.ts +2 -1
  57. package/dist/api/server-wiring.js +6 -4
  58. package/dist/api/server.d.ts +2 -1
  59. package/dist/api/sse-route.d.ts +2 -1
  60. package/dist/api/tenant-scoped-secrets.d.ts +2 -0
  61. package/dist/api/tenant-scoped-secrets.js +13 -0
  62. package/dist/api/websocket-route.d.ts +1 -1
  63. package/dist/auth/index.d.ts +0 -1
  64. package/dist/auth/roles.d.ts +0 -5
  65. package/dist/compliance/duration-spec.d.ts +0 -1
  66. package/dist/compliance/duration-spec.js +0 -5
  67. package/dist/compliance/index.d.ts +4 -4
  68. package/dist/compliance/index.js +2 -2
  69. package/dist/compliance/profiles.d.ts +0 -15
  70. package/dist/compliance/profiles.js +0 -23
  71. package/dist/config/index.d.ts +10 -10
  72. package/dist/config/index.js +13 -1
  73. package/dist/config/internals.d.ts +9 -0
  74. package/dist/config/internals.js +1 -0
  75. package/dist/consumer-cli-internals.d.ts +4 -0
  76. package/dist/consumer-cli-internals.js +1 -0
  77. package/dist/consumer-cli.d.ts +1 -4
  78. package/dist/consumer-cli.js +5 -1
  79. package/dist/crypto/event-pii.js +1 -1
  80. package/dist/crypto/index.d.ts +8 -9
  81. package/dist/crypto/index.js +7 -8
  82. package/dist/crypto/pg-kms-adapter.js +6 -1
  83. package/dist/db/apply-entity-event.d.ts +1 -1
  84. package/dist/db/apply-entity-event.js +15 -47
  85. package/dist/db/blind-index-cleanup.js +91 -77
  86. package/dist/db/collect-table-metas.js +1 -1
  87. package/dist/db/compound-types.d.ts +1 -1
  88. package/dist/db/compound-types.js +0 -7
  89. package/dist/db/config-seed.d.ts +2 -1
  90. package/dist/db/config-seed.js +6 -1
  91. package/dist/db/connection.d.ts +3 -1
  92. package/dist/db/connection.js +15 -5
  93. package/dist/db/cross-tenant-reads.d.ts +1 -1
  94. package/dist/db/cross-tenant-reads.js +1 -1
  95. package/dist/db/dialect.d.ts +0 -5
  96. package/dist/db/dialect.js +0 -3
  97. package/dist/db/eagerload.d.ts +2 -1
  98. package/dist/db/entity-field-encryption.d.ts +1 -1
  99. package/dist/db/entity-table-meta.d.ts +1 -1
  100. package/dist/db/event-store-executor-context.d.ts +3 -1
  101. package/dist/db/event-store-executor-context.js +4 -3
  102. package/dist/db/event-store-executor-read-filter.d.ts +1 -1
  103. package/dist/db/event-store-executor-read-rows.d.ts +2 -1
  104. package/dist/db/event-store-executor-read-search.d.ts +2 -1
  105. package/dist/db/event-store-executor-read-sort.d.ts +2 -1
  106. package/dist/db/event-store-executor-read.js +2 -2
  107. package/dist/db/event-store-executor-write-atomic.js +8 -7
  108. package/dist/db/event-store-executor-write-steps.d.ts +3 -2
  109. package/dist/db/event-store-executor-write.js +7 -4
  110. package/dist/db/event-store-executor.d.ts +1 -1
  111. package/dist/db/executor-managed-tables.d.ts +7 -1
  112. package/dist/db/executor-managed-tables.js +11 -2
  113. package/dist/db/index.d.ts +29 -28
  114. package/dist/db/index.js +22 -20
  115. package/dist/db/known-table-names.d.ts +6 -0
  116. package/dist/db/known-table-names.js +21 -0
  117. package/dist/db/list-aggregate-labels.d.ts +1 -1
  118. package/dist/db/located-timestamp.d.ts +1 -1
  119. package/dist/db/located-timestamp.js +0 -5
  120. package/dist/db/migrate-generator.d.ts +17 -1
  121. package/dist/db/migrate-generator.js +50 -20
  122. package/dist/db/migrate-primary-key.d.ts +12 -0
  123. package/dist/db/migrate-primary-key.js +45 -0
  124. package/dist/db/migrate-rls-diff.d.ts +20 -0
  125. package/dist/db/migrate-rls-diff.js +61 -0
  126. package/dist/db/migrate-runner.js +4 -0
  127. package/dist/db/money.d.ts +1 -1
  128. package/dist/db/money.js +1 -1
  129. package/dist/db/parent-ref-clause.d.ts +2 -1
  130. package/dist/db/pg-error.d.ts +2 -0
  131. package/dist/db/pg-error.js +11 -0
  132. package/dist/db/queries/ddl.d.ts +3 -0
  133. package/dist/db/queries/ddl.js +34 -1
  134. package/dist/db/queries/event-store.js +4 -2
  135. package/dist/db/queries/event-tenants.d.ts +2 -0
  136. package/dist/db/queries/event-tenants.js +17 -0
  137. package/dist/db/queries/rebuild-rls-guard.d.ts +2 -0
  138. package/dist/db/queries/rebuild-rls-guard.js +29 -0
  139. package/dist/db/queries/rls-boot-catalog.d.ts +16 -0
  140. package/dist/db/queries/rls-boot-catalog.js +39 -0
  141. package/dist/db/queries/rls-catalog.d.ts +6 -0
  142. package/dist/db/queries/rls-catalog.js +82 -0
  143. package/dist/db/queries/rls-scope.d.ts +3 -0
  144. package/dist/db/queries/rls-scope.js +17 -0
  145. package/dist/db/queries/rls-shape.d.ts +10 -0
  146. package/dist/db/queries/rls-shape.js +42 -0
  147. package/dist/db/queries/session-advisory-lock.d.ts +2 -0
  148. package/dist/db/queries/session-advisory-lock.js +23 -0
  149. package/dist/db/queries/shadow-swap.d.ts +1 -2
  150. package/dist/db/queries/shadow-swap.js +9 -25
  151. package/dist/db/queries/test-stack.d.ts +4 -1
  152. package/dist/db/queries/test-stack.js +83 -2
  153. package/dist/db/query.js +11 -7
  154. package/dist/db/reference-data.d.ts +1 -1
  155. package/dist/db/reference-data.js +6 -1
  156. package/dist/db/render-ddl.d.ts +6 -2
  157. package/dist/db/render-ddl.js +7 -7
  158. package/dist/db/render-rls-ddl.d.ts +35 -0
  159. package/dist/db/render-rls-ddl.js +127 -0
  160. package/dist/db/replay-migration-sql.d.ts +3 -1
  161. package/dist/db/replay-migration-sql.js +22 -5
  162. package/dist/db/replay-rls.d.ts +25 -0
  163. package/dist/db/replay-rls.js +156 -0
  164. package/dist/db/row-level-security-boot-check.d.ts +5 -0
  165. package/dist/db/row-level-security-boot-check.js +85 -0
  166. package/dist/db/row-level-security-connection.d.ts +3 -0
  167. package/dist/db/row-level-security-connection.js +213 -0
  168. package/dist/db/row-level-security-scope.d.ts +33 -0
  169. package/dist/db/row-level-security-scope.js +127 -0
  170. package/dist/db/scope-tenant-db-to-tenant.d.ts +3 -0
  171. package/dist/db/scope-tenant-db-to-tenant.js +14 -0
  172. package/dist/db/sql-identifier.d.ts +1 -0
  173. package/dist/db/sql-identifier.js +3 -0
  174. package/dist/db/table-builder.d.ts +2 -1
  175. package/dist/db/table-builder.js +1 -1
  176. package/dist/db/table-info.js +1 -1
  177. package/dist/db/table-push.d.ts +11 -2
  178. package/dist/db/table-push.js +15 -2
  179. package/dist/db/tenant-db-runner.d.ts +3 -1
  180. package/dist/db/tenant-db-runner.js +4 -3
  181. package/dist/db/tenant-db-scoped-rebind.d.ts +10 -0
  182. package/dist/db/tenant-db-scoped-rebind.js +33 -0
  183. package/dist/db/tenant-db-statement-runner.d.ts +13 -0
  184. package/dist/db/tenant-db-statement-runner.js +48 -0
  185. package/dist/db/tenant-db.d.ts +17 -12
  186. package/dist/db/tenant-db.js +49 -120
  187. package/dist/db/tenant-id-filter.d.ts +3 -0
  188. package/dist/db/tenant-id-filter.js +30 -0
  189. package/dist/db/transaction-serialization.d.ts +6 -0
  190. package/dist/db/transaction-serialization.js +60 -0
  191. package/dist/derivatives/delete-stored-file.js +1 -1
  192. package/dist/derivatives/index.d.ts +2 -5
  193. package/dist/derivatives/index.js +2 -3
  194. package/dist/engine/access.d.ts +2 -1
  195. package/dist/engine/access.js +1 -1
  196. package/dist/engine/boot-validator/entity-handler.d.ts +2 -1
  197. package/dist/engine/boot-validator/entity-handler.js +1 -1
  198. package/dist/engine/boot-validator/entity-list-aggregate-options.d.ts +1 -1
  199. package/dist/engine/boot-validator/list-table-aggregate-options.d.ts +1 -1
  200. package/dist/engine/boot-validator/list-table-aggregate-query.d.ts +2 -1
  201. package/dist/engine/boot-validator/nav.d.ts +3 -1
  202. package/dist/engine/boot-validator/nav.js +1 -1
  203. package/dist/engine/boot-validator/ownership.d.ts +2 -1
  204. package/dist/engine/boot-validator/parent-ref.d.ts +2 -1
  205. package/dist/engine/boot-validator/projection-list-screens.d.ts +2 -1
  206. package/dist/engine/boot-validator/query-refs.d.ts +2 -1
  207. package/dist/engine/boot-validator/screens-forms.d.ts +2 -1
  208. package/dist/engine/boot-validator/screens-kind-shared.d.ts +3 -1
  209. package/dist/engine/boot-validator/screens.d.ts +2 -1
  210. package/dist/engine/boot-validator/workspaces.d.ts +3 -1
  211. package/dist/engine/bridge-stub.d.ts +5 -2
  212. package/dist/engine/bridge-stub.js +39 -24
  213. package/dist/engine/config-helpers.d.ts +2 -2
  214. package/dist/engine/config-helpers.js +0 -9
  215. package/dist/engine/constants.d.ts +0 -10
  216. package/dist/engine/constants.js +0 -8
  217. package/dist/engine/create-app.js +1 -1
  218. package/dist/engine/define-feature.d.ts +2 -1
  219. package/dist/engine/define-feature.js +1 -2
  220. package/dist/engine/define-handler.d.ts +1 -1
  221. package/dist/engine/define-step.d.ts +0 -1
  222. package/dist/engine/define-step.js +0 -3
  223. package/dist/engine/embedded-derived.d.ts +1 -1
  224. package/dist/engine/entity-handlers.d.ts +2 -1
  225. package/dist/engine/entity-handlers.js +7 -7
  226. package/dist/engine/entity-list-runtime-options.js +3 -1
  227. package/dist/engine/entity-list-searchability.d.ts +2 -1
  228. package/dist/engine/event-def.d.ts +2 -1
  229. package/dist/engine/event-helpers.d.ts +3 -1
  230. package/dist/engine/event-helpers.js +3 -3
  231. package/dist/engine/extension-names.d.ts +2 -8
  232. package/dist/engine/extension-names.js +2 -2
  233. package/dist/engine/extension-selector-plugins.d.ts +1 -1
  234. package/dist/engine/extensions/storage-provider.d.ts +1 -2
  235. package/dist/engine/extensions/user-data.d.ts +0 -8
  236. package/dist/engine/factories.d.ts +2 -1
  237. package/dist/engine/feature-builder-state.d.ts +8 -1
  238. package/dist/engine/feature-changelog.d.ts +1 -1
  239. package/dist/engine/feature-config-events-jobs.d.ts +4 -1
  240. package/dist/engine/feature-entity-handlers.d.ts +5 -1
  241. package/dist/engine/feature-manifest.js +1 -1
  242. package/dist/engine/feature-ui-extensions.d.ts +7 -1
  243. package/dist/engine/feature-ui-extensions.js +6 -4
  244. package/dist/engine/field-access.d.ts +2 -1
  245. package/dist/engine/handler-field-references.d.ts +0 -4
  246. package/dist/engine/index.d.ts +57 -54
  247. package/dist/engine/index.js +22 -27
  248. package/dist/engine/instruction-fields.d.ts +1 -1
  249. package/dist/engine/personal-data-fields.d.ts +1 -1
  250. package/dist/engine/qualified-name.d.ts +0 -1
  251. package/dist/engine/read-claim.d.ts +2 -1
  252. package/dist/engine/registry-facade.js +0 -3
  253. package/dist/engine/registry-ingest.d.ts +2 -1
  254. package/dist/engine/registry-ingest.js +1 -2
  255. package/dist/engine/registry-state.d.ts +12 -2
  256. package/dist/engine/registry-state.js +2 -3
  257. package/dist/engine/registry-validate.d.ts +3 -1
  258. package/dist/engine/registry-validate.js +7 -7
  259. package/dist/engine/run-in.d.ts +1 -1
  260. package/dist/engine/run-in.js +0 -1
  261. package/dist/engine/schema-builder.d.ts +1 -1
  262. package/dist/engine/schema-builder.js +1 -1
  263. package/dist/engine/screen-filter-ops.d.ts +2 -1
  264. package/dist/engine/screen-filter-ops.js +0 -5
  265. package/dist/engine/soft-delete-cleanup.d.ts +1 -1
  266. package/dist/engine/soft-delete-cleanup.js +4 -4
  267. package/dist/engine/state-machine.js +2 -1
  268. package/dist/engine/steps/_read-source.js +1 -1
  269. package/dist/engine/steps/aggregate-append-event.js +2 -2
  270. package/dist/engine/steps/call-feature.js +2 -2
  271. package/dist/engine/steps/mail-send.js +1 -1
  272. package/dist/engine/steps/retry.js +1 -1
  273. package/dist/engine/steps/wait-for-event.js +1 -1
  274. package/dist/engine/steps/wait.js +1 -1
  275. package/dist/engine/steps/webhook-send.js +1 -1
  276. package/dist/engine/template-variables.d.ts +2 -1
  277. package/dist/engine/transfer-adjacency.d.ts +1 -1
  278. package/dist/engine/types/index.d.ts +8 -31
  279. package/dist/engine/types/index.js +2 -10
  280. package/dist/engine/validation.d.ts +2 -1
  281. package/dist/entrypoint/index.d.ts +7 -35
  282. package/dist/entrypoint/index.js +7 -1
  283. package/dist/entrypoint/internals.d.ts +40 -0
  284. package/dist/entrypoint/internals.js +1 -0
  285. package/dist/entrypoint/row-level-security-mode-check.d.ts +3 -0
  286. package/dist/entrypoint/row-level-security-mode-check.js +19 -0
  287. package/dist/env/dry-run-internals.d.ts +10 -0
  288. package/dist/env/dry-run-internals.js +1 -0
  289. package/dist/env/dry-run.d.ts +1 -10
  290. package/dist/env/dry-run.js +2 -1
  291. package/dist/env/index.d.ts +1 -85
  292. package/dist/env/index.js +6 -50
  293. package/dist/env/internals.d.ts +87 -0
  294. package/dist/env/internals.js +53 -0
  295. package/dist/errors/classes.js +2 -2
  296. package/dist/errors/index.d.ts +5 -14
  297. package/dist/errors/index.js +4 -9
  298. package/dist/errors/member-resolution.js +1 -1
  299. package/dist/errors/reasons.d.ts +0 -1
  300. package/dist/errors/reasons.js +7 -7
  301. package/dist/errors/serialize.d.ts +0 -18
  302. package/dist/errors/serialize.js +0 -27
  303. package/dist/es-ops/index.d.ts +4 -4
  304. package/dist/es-ops/index.js +2 -2
  305. package/dist/es-ops/operations-schema.d.ts +1 -1
  306. package/dist/es-ops/types.d.ts +4 -5
  307. package/dist/event-store/admin-api-internals.d.ts +14 -0
  308. package/dist/event-store/admin-api-internals.js +1 -0
  309. package/dist/event-store/admin-api.d.ts +1 -14
  310. package/dist/event-store/admin-api.js +11 -6
  311. package/dist/event-store/archive.js +4 -2
  312. package/dist/event-store/errors.js +2 -2
  313. package/dist/event-store/event-store.js +41 -34
  314. package/dist/event-store/events-schema.js +4 -1
  315. package/dist/event-store/index.d.ts +11 -13
  316. package/dist/event-store/index.js +7 -9
  317. package/dist/event-store/rebuild-dead-letter.js +4 -2
  318. package/dist/event-store/snapshot.js +4 -3
  319. package/dist/event-store/stream-scope.d.ts +2 -0
  320. package/dist/event-store/stream-scope.js +6 -0
  321. package/dist/event-store/upcaster-dead-letter.js +4 -2
  322. package/dist/event-store/upcaster.d.ts +2 -1
  323. package/dist/files/action-form-upload.d.ts +2 -1
  324. package/dist/files/action-form-upload.js +1 -1
  325. package/dist/files/assert-file-refs-belong-to-tenant.d.ts +2 -1
  326. package/dist/files/assert-file-refs-belong-to-tenant.js +1 -1
  327. package/dist/files/bind-pending-file-refs.d.ts +2 -1
  328. package/dist/files/bind-pending-file-refs.js +2 -1
  329. package/dist/files/bind-referenced-files.write.js +1 -1
  330. package/dist/files/create-file-ref-from-bytes.d.ts +6 -3
  331. package/dist/files/create-file-ref-from-bytes.js +3 -3
  332. package/dist/files/create-file-ref.d.ts +3 -2
  333. package/dist/files/create-file-ref.js +2 -2
  334. package/dist/files/dispose-removed-file-refs.d.ts +2 -1
  335. package/dist/files/dispose-removed-file-refs.js +1 -1
  336. package/dist/files/feature.js +2 -2
  337. package/dist/files/file-handle.js +1 -1
  338. package/dist/files/file-routes.d.ts +1 -1
  339. package/dist/files/file-routes.js +12 -2
  340. package/dist/files/index.d.ts +11 -13
  341. package/dist/files/index.js +5 -7
  342. package/dist/files/load-save-file-refs.d.ts +2 -1
  343. package/dist/files/load-save-file-refs.js +1 -1
  344. package/dist/files/provider-resolver.d.ts +5 -4
  345. package/dist/files/provider-resolver.js +4 -4
  346. package/dist/files/storage-tracking.js +4 -2
  347. package/dist/files/sweep-orphaned-uploads.d.ts +1 -1
  348. package/dist/files/sweep-orphaned-uploads.js +1 -1
  349. package/dist/i18n/index.d.ts +2 -8
  350. package/dist/i18n/index.js +1 -1
  351. package/dist/i18n/internals.d.ts +7 -0
  352. package/dist/i18n/internals.js +1 -0
  353. package/dist/i18n/required-surface-keys.d.ts +4 -1
  354. package/dist/internal/row-level-security.d.ts +3 -0
  355. package/dist/internal/row-level-security.js +3 -0
  356. package/dist/internal/stack-support.d.ts +1 -1
  357. package/dist/internal/stack-support.js +1 -1
  358. package/dist/internal/testing.d.ts +1 -0
  359. package/dist/internal/testing.js +9 -5
  360. package/dist/jobs/index.d.ts +1 -1
  361. package/dist/jobs/job-append-event.d.ts +12 -0
  362. package/dist/jobs/job-append-event.js +30 -0
  363. package/dist/jobs/job-runner-context.d.ts +2 -2
  364. package/dist/jobs/job-runner-context.js +46 -16
  365. package/dist/jobs/job-runner-execution.d.ts +1 -1
  366. package/dist/jobs/job-runner-execution.js +6 -4
  367. package/dist/jobs/job-runner-gates.d.ts +1 -1
  368. package/dist/jobs/job-runner-gates.js +0 -1
  369. package/dist/jobs/job-runner-helpers.d.ts +11 -4
  370. package/dist/jobs/job-runner-helpers.js +10 -1
  371. package/dist/jobs/job-runner-scope.d.ts +5 -2
  372. package/dist/jobs/job-runner-types.d.ts +2 -1
  373. package/dist/jobs/job-runner.js +19 -6
  374. package/dist/jobs/max-per-tenant.d.ts +1 -1
  375. package/dist/logging/index.d.ts +1 -2
  376. package/dist/logging/index.js +1 -1
  377. package/dist/logging/pino-logger.js +1 -1
  378. package/dist/logging/utils.d.ts +1 -0
  379. package/dist/logging/utils.js +6 -0
  380. package/dist/migrations/index.d.ts +3 -3
  381. package/dist/migrations/index.js +3 -3
  382. package/dist/migrations/pending-rebuilds.d.ts +1 -1
  383. package/dist/migrations/pending-rebuilds.js +1 -1
  384. package/dist/migrations/pii-event-backfill.js +4 -1
  385. package/dist/observability/escape-hatch-report.d.ts +2 -1
  386. package/dist/observability/index.d.ts +7 -15
  387. package/dist/observability/index.js +4 -12
  388. package/dist/observability/metric-validator.js +1 -1
  389. package/dist/observability/metrics-handle.d.ts +0 -1
  390. package/dist/observability/metrics-handle.js +2 -15
  391. package/dist/observability/recording-tracer.d.ts +1 -2
  392. package/dist/observability/recording-tracer.js +0 -6
  393. package/dist/observability/sensitive-filter.d.ts +0 -2
  394. package/dist/observability/sensitive-filter.js +0 -29
  395. package/dist/pipeline/active-membership.d.ts +2 -1
  396. package/dist/pipeline/active-membership.js +6 -2
  397. package/dist/pipeline/append-event-core.d.ts +2 -1
  398. package/dist/pipeline/append-event-core.js +2 -2
  399. package/dist/pipeline/attach-outside-transaction.d.ts +3 -0
  400. package/dist/pipeline/attach-outside-transaction.js +13 -0
  401. package/dist/pipeline/auth-claims-resolver.d.ts +2 -1
  402. package/dist/pipeline/cascade-handler.d.ts +2 -1
  403. package/dist/pipeline/cascade-handler.js +3 -2
  404. package/dist/pipeline/config-accessor-factory.d.ts +1 -1
  405. package/dist/pipeline/config-accessor-factory.js +1 -1
  406. package/dist/pipeline/context-services.d.ts +56 -0
  407. package/dist/pipeline/context-services.js +73 -0
  408. package/dist/pipeline/dispatch-batch.d.ts +2 -1
  409. package/dist/pipeline/dispatch-batch.js +31 -18
  410. package/dist/pipeline/dispatch-query.js +5 -4
  411. package/dist/pipeline/dispatch-scope.d.ts +6 -0
  412. package/dist/pipeline/dispatch-scope.js +27 -0
  413. package/dist/pipeline/dispatch-shared.d.ts +13 -9
  414. package/dist/pipeline/dispatch-shared.js +75 -198
  415. package/dist/pipeline/dispatch-stream.js +8 -4
  416. package/dist/pipeline/dispatch-write.d.ts +2 -1
  417. package/dist/pipeline/dispatch-write.js +10 -8
  418. package/dist/pipeline/dispatcher-utils.d.ts +3 -1
  419. package/dist/pipeline/dispatcher-utils.js +8 -2
  420. package/dist/pipeline/dispatcher.d.ts +3 -2
  421. package/dist/pipeline/dispatcher.js +4 -3
  422. package/dist/pipeline/event-consumer-state.d.ts +0 -1
  423. package/dist/pipeline/event-dispatcher-delivery.d.ts +1 -1
  424. package/dist/pipeline/event-dispatcher-delivery.js +4 -2
  425. package/dist/pipeline/event-dispatcher.d.ts +2 -2
  426. package/dist/pipeline/event-dispatcher.js +7 -3
  427. package/dist/pipeline/event-retention.js +59 -56
  428. package/dist/pipeline/handler-access-error.js +2 -1
  429. package/dist/pipeline/handler-context-auth.d.ts +5 -6
  430. package/dist/pipeline/handler-context-auth.js +7 -4
  431. package/dist/pipeline/handler-context-config.d.ts +2 -5
  432. package/dist/pipeline/handler-context-config.js +2 -2
  433. package/dist/pipeline/handler-context-db.d.ts +32 -7
  434. package/dist/pipeline/handler-context-db.js +26 -17
  435. package/dist/pipeline/handler-context-dispatch.d.ts +13 -7
  436. package/dist/pipeline/handler-context-dispatch.js +9 -8
  437. package/dist/pipeline/handler-context-events.d.ts +22 -36
  438. package/dist/pipeline/handler-context-events.js +36 -31
  439. package/dist/pipeline/handler-context-member-resolution.d.ts +2 -0
  440. package/dist/pipeline/handler-context-member-resolution.js +110 -0
  441. package/dist/pipeline/handler-context-notify.d.ts +3 -2
  442. package/dist/pipeline/handler-context-notify.js +1 -1
  443. package/dist/pipeline/handler-context-observability.d.ts +2 -4
  444. package/dist/pipeline/handler-context-observability.js +7 -5
  445. package/dist/pipeline/handler-context-phase-denials.d.ts +11 -0
  446. package/dist/pipeline/handler-context-phase-denials.js +98 -0
  447. package/dist/pipeline/handler-context-scope.d.ts +7 -10
  448. package/dist/pipeline/handler-context-scope.js +18 -20
  449. package/dist/pipeline/index.d.ts +9 -21
  450. package/dist/pipeline/index.js +5 -7
  451. package/dist/pipeline/irreversible-operation-gate.d.ts +1 -1
  452. package/dist/pipeline/irreversible-operation-gate.js +2 -2
  453. package/dist/pipeline/lifecycle-pipeline.d.ts +8 -7
  454. package/dist/pipeline/lifecycle-pipeline.js +9 -15
  455. package/dist/pipeline/member-read-only-transaction.js +11 -3
  456. package/dist/pipeline/member-reader.d.ts +1 -1
  457. package/dist/pipeline/member-reader.js +2 -2
  458. package/dist/pipeline/msp-rebuild.js +5 -3
  459. package/dist/pipeline/multi-stream-apply-context.js +3 -2
  460. package/dist/pipeline/notify-scope.d.ts +2 -1
  461. package/dist/pipeline/pre-handler-gates.d.ts +2 -1
  462. package/dist/pipeline/pre-transaction-reservations.d.ts +3 -3
  463. package/dist/pipeline/pre-transaction-reservations.js +9 -7
  464. package/dist/pipeline/projection-rebuild.js +12 -5
  465. package/dist/pipeline/projection-state.d.ts +0 -6
  466. package/dist/pipeline/projection-state.js +0 -5
  467. package/dist/pipeline/projections-runner.d.ts +2 -1
  468. package/dist/pipeline/projections-runner.js +10 -6
  469. package/dist/pipeline/system-hooks.d.ts +3 -1
  470. package/dist/pipeline/system-hooks.js +6 -4
  471. package/dist/pipeline/system-identity-switch.d.ts +4 -2
  472. package/dist/pipeline/system-identity-switch.js +51 -36
  473. package/dist/pipeline/typed-append-event.d.ts +2 -0
  474. package/dist/pipeline/typed-append-event.js +4 -0
  475. package/dist/pipeline/write-origin.js +2 -2
  476. package/dist/pipeline/write-result-masking.d.ts +1 -1
  477. package/dist/random/index.d.ts +1 -2
  478. package/dist/random/index.js +1 -2
  479. package/dist/rate-limit/bucket.d.ts +2 -1
  480. package/dist/rate-limit/middleware.js +2 -1
  481. package/dist/redis/index.d.ts +3 -4
  482. package/dist/redis/index.js +2 -2
  483. package/dist/schema-cli/apply-command.js +5 -2
  484. package/dist/schema-cli/baseline-command.js +7 -2
  485. package/dist/schema-cli/generate-command.js +7 -9
  486. package/dist/schema-cli/shared.d.ts +4 -0
  487. package/dist/schema-cli/shared.js +21 -2
  488. package/dist/schema-cli/status-command.js +8 -2
  489. package/dist/schema-cli/validate-command.js +52 -4
  490. package/dist/schema-cli.d.ts +0 -1
  491. package/dist/search/configure-tenant-index.d.ts +1 -3
  492. package/dist/search/configure-tenant-index.js +0 -4
  493. package/dist/search/index.d.ts +1 -4
  494. package/dist/search/index.js +0 -2
  495. package/dist/search/meilisearch-adapter-internals.d.ts +8 -0
  496. package/dist/search/meilisearch-adapter-internals.js +9 -0
  497. package/dist/search/meilisearch-adapter.d.ts +1 -8
  498. package/dist/search/meilisearch-adapter.js +6 -7
  499. package/dist/search/meilisearch-peer.d.ts +2 -0
  500. package/dist/search/meilisearch-peer.js +5 -0
  501. package/dist/search/reindex-entity.js +93 -87
  502. package/dist/secrets/index.d.ts +3 -4
  503. package/dist/secrets/index.js +2 -3
  504. package/dist/seeding/index.d.ts +2 -2
  505. package/dist/seeding/index.js +1 -2
  506. package/dist/time/index.d.ts +2 -2
  507. package/dist/time/index.js +1 -1
  508. package/dist/time/tz-context.d.ts +0 -5
  509. package/dist/time/tz-context.js +0 -9
  510. package/dist/ui-types/index.d.ts +8 -11
  511. package/dist/ui-types/index.js +3 -3
  512. package/dist/upgrade-changelog.d.ts +3 -2
  513. package/dist/upgrade-changelog.js +12 -8
  514. package/dist/upgrade-cli-internals.d.ts +8 -0
  515. package/dist/upgrade-cli-internals.js +63 -0
  516. package/dist/upgrade-cli.d.ts +1 -9
  517. package/dist/upgrade-cli.js +10 -64
  518. package/dist/utils/import-optional-peer.d.ts +6 -0
  519. package/dist/utils/import-optional-peer.js +19 -0
  520. package/dist/utils/index.d.ts +1 -4
  521. package/dist/utils/index.js +1 -4
  522. package/package.json +22 -18
  523. package/src/changes.json +206 -0
  524. package/dist/crypto/request-kms-cache.d.ts +0 -8
  525. package/dist/crypto/request-kms-cache.js +0 -21
  526. package/dist/engine/codemod/index.d.ts +0 -2
  527. package/dist/engine/codemod/index.js +0 -1
  528. package/dist/engine/codemod/pipeline-codemod.d.ts +0 -50
  529. package/dist/engine/codemod/pipeline-codemod.js +0 -536
  530. package/dist/engine/feature-ast/entity-field-types.d.ts +0 -13
  531. package/dist/engine/feature-ast/entity-field-types.js +0 -35
  532. package/dist/engine/feature-ast/extractors/ai-steps.d.ts +0 -6
  533. package/dist/engine/feature-ast/extractors/ai-steps.js +0 -254
  534. package/dist/engine/feature-ast/extractors/events.d.ts +0 -5
  535. package/dist/engine/feature-ast/extractors/events.js +0 -275
  536. package/dist/engine/feature-ast/extractors/handlers.d.ts +0 -24
  537. package/dist/engine/feature-ast/extractors/handlers.js +0 -358
  538. package/dist/engine/feature-ast/extractors/hooks.d.ts +0 -24
  539. package/dist/engine/feature-ast/extractors/hooks.js +0 -248
  540. package/dist/engine/feature-ast/extractors/index.d.ts +0 -14
  541. package/dist/engine/feature-ast/extractors/index.js +0 -13
  542. package/dist/engine/feature-ast/extractors/jobs-routes.d.ts +0 -7
  543. package/dist/engine/feature-ast/extractors/jobs-routes.js +0 -145
  544. package/dist/engine/feature-ast/extractors/projections-screens.d.ts +0 -10
  545. package/dist/engine/feature-ast/extractors/projections-screens.js +0 -195
  546. package/dist/engine/feature-ast/extractors/resolve-import.d.ts +0 -21
  547. package/dist/engine/feature-ast/extractors/resolve-import.js +0 -180
  548. package/dist/engine/feature-ast/extractors/round1.d.ts +0 -11
  549. package/dist/engine/feature-ast/extractors/round1.js +0 -87
  550. package/dist/engine/feature-ast/extractors/round2.d.ts +0 -7
  551. package/dist/engine/feature-ast/extractors/round2.js +0 -191
  552. package/dist/engine/feature-ast/extractors/round3.d.ts +0 -23
  553. package/dist/engine/feature-ast/extractors/round3.js +0 -292
  554. package/dist/engine/feature-ast/extractors/round5.d.ts +0 -9
  555. package/dist/engine/feature-ast/extractors/round5.js +0 -68
  556. package/dist/engine/feature-ast/extractors/round6.d.ts +0 -4
  557. package/dist/engine/feature-ast/extractors/round6.js +0 -17
  558. package/dist/engine/feature-ast/extractors/shared.d.ts +0 -94
  559. package/dist/engine/feature-ast/extractors/shared.js +0 -343
  560. package/dist/engine/feature-ast/index.d.ts +0 -16
  561. package/dist/engine/feature-ast/index.js +0 -10
  562. package/dist/engine/feature-ast/parse.d.ts +0 -31
  563. package/dist/engine/feature-ast/parse.js +0 -485
  564. package/dist/engine/feature-ast/patch.d.ts +0 -212
  565. package/dist/engine/feature-ast/patch.js +0 -611
  566. package/dist/engine/feature-ast/patcher.d.ts +0 -217
  567. package/dist/engine/feature-ast/patcher.js +0 -281
  568. package/dist/engine/feature-ast/pattern-change-schema.d.ts +0 -1238
  569. package/dist/engine/feature-ast/pattern-change-schema.js +0 -1072
  570. package/dist/engine/feature-ast/pattern-id-matchers.d.ts +0 -7
  571. package/dist/engine/feature-ast/pattern-id-matchers.js +0 -211
  572. package/dist/engine/feature-ast/pattern-id-schema.d.ts +0 -251
  573. package/dist/engine/feature-ast/pattern-id-schema.js +0 -166
  574. package/dist/engine/feature-ast/patterns.d.ts +0 -322
  575. package/dist/engine/feature-ast/patterns.js +0 -107
  576. package/dist/engine/feature-ast/render.d.ts +0 -47
  577. package/dist/engine/feature-ast/render.js +0 -666
  578. package/dist/engine/feature-ast/source-location.d.ts +0 -17
  579. package/dist/engine/feature-ast/source-location.js +0 -27
  580. package/dist/engine/pattern-library/index.d.ts +0 -2
  581. package/dist/engine/pattern-library/index.js +0 -3
  582. package/dist/engine/pattern-library/library.d.ts +0 -14
  583. package/dist/engine/pattern-library/library.js +0 -94
  584. package/dist/engine/pattern-library/mixed-schemas.d.ts +0 -16
  585. package/dist/engine/pattern-library/mixed-schemas.js +0 -559
  586. package/dist/engine/pattern-library/opaque-schemas.d.ts +0 -7
  587. package/dist/engine/pattern-library/opaque-schemas.js +0 -115
  588. package/dist/engine/pattern-library/shared-fields.d.ts +0 -122
  589. package/dist/engine/pattern-library/shared-fields.js +0 -88
  590. package/dist/engine/pattern-library/static-schemas.d.ts +0 -21
  591. package/dist/engine/pattern-library/static-schemas.js +0 -487
  592. package/dist/engine/pattern-library/types.d.ts +0 -158
  593. package/dist/engine/pattern-library/types.js +0 -36
  594. package/dist/engine/resolve-config-or-param.d.ts +0 -18
  595. package/dist/engine/resolve-config-or-param.js +0 -76
  596. package/src/scripts/codemod/README.md +0 -187
  597. package/src/scripts/codemod/codemod-support.ts +0 -334
  598. package/src/scripts/codemod/collect-source-files.ts +0 -48
  599. package/src/scripts/codemod/crypto-shredding-testing-move.ts +0 -119
  600. package/src/scripts/codemod/db-factory.ts +0 -810
  601. package/src/scripts/codemod/feature-factory.ts +0 -429
  602. package/src/scripts/codemod/feature-layout.ts +0 -424
  603. package/src/scripts/codemod/import-extensions.ts +0 -118
  604. package/src/scripts/codemod/migrate-cross-tenant.ts +0 -328
  605. package/src/scripts/codemod/migrate-db-raw.ts +0 -401
  606. package/src/scripts/codemod/migrate-open-to-all.ts +0 -235
  607. package/src/scripts/codemod/object-form.ts +0 -746
  608. package/src/scripts/codemod/package-merge.ts +0 -299
  609. package/src/scripts/codemod/pii-personal-migration.ts +0 -816
  610. package/src/scripts/codemod/remove-engine-payload-casts.ts +0 -318
  611. package/src/scripts/codemod/s3-imports.ts +0 -236
  612. package/src/scripts/codemod/tenant-db-options.ts +0 -454
  613. package/src/scripts/codemod/testing-injection.ts +0 -215
  614. package/src/scripts/codemod/testing-move-targets.ts +0 -161
  615. package/src/scripts/codemod/testing-move.ts +0 -542
  616. package/src/scripts/codemod/unprocessable-error-details-reason.ts +0 -411
@@ -1,4 +1,6 @@
1
+ import { extractPgError } from "../pg-error.js";
1
2
  import { asRawClient } from "../query.js";
3
+ import { PROJECTION_REBUILD_SCHEMA } from "./shadow-swap.js";
2
4
  import { quoteTableIdent } from "./table-ops.js";
3
5
  // Re-exported for back-compat — the generic DDL helpers moved to ./ddl so
4
6
  // the prod-boot path (db/table-push.ts, pipeline/event-consumer-state.ts)
@@ -12,9 +14,88 @@ export async function databaseExists(db, dbName) {
12
14
  const rows = await asRawClient(db).unsafe(`SELECT EXISTS (SELECT 1 FROM pg_database WHERE datname = $1) AS exists`, [dbName]);
13
15
  return rows[0]?.exists === true;
14
16
  }
15
- export async function createDatabase(db, dbName, templateDbName) {
17
+ export async function createDatabase(db, dbName, templateDbName, owner) {
16
18
  const template = templateDbName === undefined ? "" : ` TEMPLATE ${quoteTableIdent(templateDbName)}`;
17
- await asRawClient(db).unsafe(`CREATE DATABASE ${quoteTableIdent(dbName)}${template}`);
19
+ const ownerClause = owner === undefined ? "" : ` OWNER ${quoteTableIdent(owner)}`;
20
+ await asRawClient(db).unsafe(`CREATE DATABASE ${quoteTableIdent(dbName)}${template}${ownerClause}`);
21
+ }
22
+ const DUPLICATE_OBJECT = "42710";
23
+ const UNIQUE_VIOLATION = "23505";
24
+ const INTERNAL_ERROR = "XX000";
25
+ const MAX_CAUSE_DEPTH = 8;
26
+ // XX000 is Postgres' generic internal error; only the concurrent catalog update counts as a lost race.
27
+ // The driver error carrying the message may sit below a wrapper, as for the SQLSTATE.
28
+ function messageInCauseChain(error, needle) {
29
+ let current = error;
30
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH && current instanceof Error; depth++) {
31
+ if (current.message.includes(needle))
32
+ return true;
33
+ current = current.cause;
34
+ }
35
+ return false;
36
+ }
37
+ function isLostRoleRace(error) {
38
+ const pgError = extractPgError(error);
39
+ if (pgError?.code === DUPLICATE_OBJECT || pgError?.code === UNIQUE_VIOLATION)
40
+ return true;
41
+ return (pgError?.code === INTERNAL_ERROR && messageInCauseChain(error, "tuple concurrently updated"));
42
+ }
43
+ // Parallel workers race on CREATE ROLE and ALTER ROLE; losing either race means the role has the
44
+ // attributes and password this call wanted, which is the goal.
45
+ export async function createApplicationRole(admin, role, password) {
46
+ const raw = asRawClient(admin);
47
+ const existing = await raw.unsafe(`SELECT 1 FROM pg_roles WHERE rolname = $1`, [role]);
48
+ const attributes = `LOGIN NOSUPERUSER NOBYPASSRLS PASSWORD ${quoteLiteral(password)}`;
49
+ // An earlier run may have created the role with another password, so an existing role is realigned.
50
+ const statement = existing.length > 0
51
+ ? `ALTER ROLE ${quoteTableIdent(role)} ${attributes}`
52
+ : `CREATE ROLE ${quoteTableIdent(role)} ${attributes}`;
53
+ try {
54
+ await raw.unsafe(statement);
55
+ }
56
+ catch (error) {
57
+ if (!isLostRoleRace(error))
58
+ throw error;
59
+ }
60
+ }
61
+ function quoteLiteral(value) {
62
+ return `'${value.replace(/'/g, "''")}'`;
63
+ }
64
+ export async function transferDatabaseOwnership(admin, dbName, owner) {
65
+ await asRawClient(admin).unsafe(`ALTER DATABASE ${quoteTableIdent(dbName)} OWNER TO ${quoteTableIdent(owner)}`);
66
+ }
67
+ const OWNED_SCHEMA_PUBLIC = "public";
68
+ const OWNED_RELATION_KINDS = {
69
+ r: "TABLE",
70
+ p: "TABLE",
71
+ S: "SEQUENCE",
72
+ v: "VIEW",
73
+ m: "MATERIALIZED VIEW",
74
+ };
75
+ // Run on a connection to the database itself. REASSIGN OWNED fails for a bootstrap superuser, so owners move per relation.
76
+ // The rebuild schema is included: a persistent database that already rebuilt as superuser would otherwise refuse the
77
+ // application role's next rebuild for lack of CREATE on it.
78
+ export async function transferPublicSchemaOwnership(db, owner) {
79
+ const raw = asRawClient(db);
80
+ const ownedSchemas = [OWNED_SCHEMA_PUBLIC, PROJECTION_REBUILD_SCHEMA];
81
+ const rebuildSchema = await raw.unsafe(`SELECT 1 FROM pg_namespace WHERE nspname = $1`, [
82
+ PROJECTION_REBUILD_SCHEMA,
83
+ ]);
84
+ if (rebuildSchema.length > 0) {
85
+ await raw.unsafe(`ALTER SCHEMA ${quoteTableIdent(PROJECTION_REBUILD_SCHEMA)} OWNER TO ${quoteTableIdent(owner)}`);
86
+ }
87
+ const relations = await raw.unsafe(`SELECT n.nspname, c.relname, c.relkind::text AS relkind
88
+ FROM pg_class c
89
+ JOIN pg_namespace n ON n.oid = c.relnamespace
90
+ JOIN pg_roles r ON r.oid = c.relowner
91
+ WHERE n.nspname = ANY($2::text[]) AND c.relkind IN ('r', 'p', 'S', 'v', 'm') AND r.rolname <> $1
92
+ AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = c.oid AND d.deptype IN ('a', 'i'))`, [owner, ownedSchemas]);
93
+ for (const { nspname, relname, relkind } of relations) {
94
+ const kind = OWNED_RELATION_KINDS[relkind];
95
+ if (kind === undefined)
96
+ continue;
97
+ await raw.unsafe(`ALTER ${kind} ${quoteTableIdent(nspname)}.${quoteTableIdent(relname)} OWNER TO ${quoteTableIdent(owner)}`);
98
+ }
18
99
  }
19
100
  export async function renameDatabase(db, fromName, toName) {
20
101
  await asRawClient(db).unsafe(`ALTER DATABASE ${quoteTableIdent(fromName)} RENAME TO ${quoteTableIdent(toName)}`);
package/dist/db/query.js CHANGED
@@ -24,6 +24,7 @@ import { executorManagedWriteDenied } from "./executor-managed-tables.js";
24
24
  import { extractPgError } from "./pg-error.js";
25
25
  import { extractTableInfo } from "./table-info.js";
26
26
  import { asEntityTableMeta, requireEntityTableMeta } from "./table-introspection.js";
27
+ import { openSerializedSavepoint } from "./transaction-serialization.js";
27
28
  import { buildWhereClause } from "./where-clause.js";
28
29
  // table() (dialect) stores the canonical, shadow-proof EntityTableMeta under
29
30
  // `Symbol.for("kumiko:schema:Meta")`. The column handles on the table object are
@@ -33,7 +34,10 @@ import { buildWhereClause } from "./where-clause.js";
33
34
  export { asEntityTableMeta, requireEntityTableMeta };
34
35
  export function hasBegin(client) {
35
36
  // Tx/savepoint handles can keep a begin() that throws; only savepoint() tells them apart from a pool.
36
- return typeof client.begin === "function" && typeof client.savepoint !== "function";
37
+ return typeof client.begin === "function" && !hasSavepoint(client);
38
+ }
39
+ function hasSavepoint(client) {
40
+ return typeof client.savepoint === "function";
37
41
  }
38
42
  function isRawClient(value) {
39
43
  return typeof propertyOf(value, "unsafe") === "function";
@@ -71,10 +75,10 @@ function savepointRunnerOf(sp) {
71
75
  }
72
76
  export async function runInSavepoint(tx, fn) {
73
77
  const raw = asRawClient(tx);
74
- if (typeof raw.savepoint !== "function") {
78
+ if (!hasSavepoint(raw)) {
75
79
  throw new Error("runInSavepoint: transaction handle has no savepoint() — pass the driver tx (Bun.SQL TransactionSQL).");
76
80
  }
77
- return raw.savepoint((sp) => fn(savepointRunnerOf(sp)));
81
+ return openSerializedSavepoint(raw, (sp) => fn(savepointRunnerOf(sp)));
78
82
  }
79
83
  // Same error-confinement as runInSavepoint, but for call sites that don't
80
84
  // know whether `db` is a bare pool connection or an active transaction
@@ -99,12 +103,12 @@ function isSavepointOnDeadTransactionCode(code) {
99
103
  }
100
104
  export async function runInSavepointIfSupported(db, fn) {
101
105
  const raw = asRawClient(db);
102
- if (typeof raw.savepoint !== "function")
106
+ if (!hasSavepoint(raw))
103
107
  return fn(savepointRunnerOf(db));
104
108
  // An error from inside fn must never be remapped, even if it carries a closed-connection code.
105
109
  let fnStarted = false;
106
110
  try {
107
- return await raw.savepoint((sp) => {
111
+ return await openSerializedSavepoint(raw, (sp) => {
108
112
  fnStarted = true;
109
113
  return fn(savepointRunnerOf(sp));
110
114
  });
@@ -117,11 +121,11 @@ export async function runInSavepointIfSupported(db, fn) {
117
121
  ? "runInSavepointIfSupported: the transaction handle's connection is gone — the transaction " +
118
122
  "already committed (almost always database work in an afterCommit hook) or the connection dropped. " +
119
123
  "afterCommit is the default phase of r.hook/postSave/postDelete. Use HookPhases.inTransaction " +
120
- "for writes that must roll back with the trigger, or ctx.dbOutsideTransaction for work that must survive a rollback."
124
+ "for writes that must roll back with the trigger, or ctx.db.outsideTransaction for work that must survive a rollback."
121
125
  : "runInSavepointIfSupported: savepoint on a transaction that has already committed — " +
122
126
  "this is almost always database work in an afterCommit hook. afterCommit is the default " +
123
127
  "phase of r.hook/postSave/postDelete. Use HookPhases.inTransaction for writes that must " +
124
- "roll back with the trigger, or ctx.dbOutsideTransaction for work that must survive a rollback.",
128
+ "roll back with the trigger, or ctx.db.outsideTransaction for work that must survive a rollback.",
125
129
  cause: e instanceof Error ? e : undefined,
126
130
  });
127
131
  }
@@ -1,5 +1,5 @@
1
+ import type { ReferenceDataDef } from "@cosmicdrift/kumiko-types/config";
1
2
  import type { Registry } from "../engine/index.ts";
2
- import type { ReferenceDataDef } from "../engine/types/index.ts";
3
3
  import type { DbConnection } from "./connection.ts";
4
4
  /**
5
5
  * Seed reference data at boot time.
@@ -19,7 +19,12 @@ export async function seedReferenceData(defs, registry, db) {
19
19
  let inserted = 0;
20
20
  let updated = 0;
21
21
  const by = createSystemUser(SYSTEM_TENANT_ID);
22
- const tdb = createTenantDb({ db, tenantId: SYSTEM_TENANT_ID, mode: "system" });
22
+ const tdb = createTenantDb({
23
+ db,
24
+ tenantId: SYSTEM_TENANT_ID,
25
+ mode: "system",
26
+ reason: "reference data lives in the system tenant",
27
+ });
23
28
  for (const def of defs) {
24
29
  const entity = registry.getEntity(def.entityName);
25
30
  if (!entity)
@@ -1,4 +1,8 @@
1
1
  import type { EntityTableMeta, IndexMeta } from "./entity-table-meta.ts";
2
+ import { type RowLevelSecurityMode } from "./render-rls-ddl.ts";
2
3
  export declare function renderIndex(tableName: string, idx: IndexMeta): string;
3
- export declare function renderTableDdl(meta: EntityTableMeta): readonly string[];
4
- export declare function renderTablesDdl(metas: readonly EntityTableMeta[]): readonly string[];
4
+ export type RenderDdlOptions = {
5
+ readonly rowLevelSecurity?: RowLevelSecurityMode;
6
+ };
7
+ export declare function renderTableDdl(meta: EntityTableMeta, options?: RenderDdlOptions): readonly string[];
8
+ export declare function renderTablesDdl(metas: readonly EntityTableMeta[], options?: RenderDdlOptions): readonly string[];
@@ -7,9 +7,8 @@
7
7
  // NO-MAGIC-ON-DATA: this renderer never runs at app runtime, only as a build
8
8
  // step. The runner reads the checked-in SQL, not renderer output.
9
9
  import { pgTypeToSqlType } from "./dialect.js";
10
- function quoteIdent(name) {
11
- return `"${name.replace(/"/g, '""')}"`;
12
- }
10
+ import { renderTableRowLevelSecurityDdl } from "./render-rls-ddl.js";
11
+ import { quoteIdent } from "./sql-identifier.js";
13
12
  function renderColumn(col) {
14
13
  const parts = [quoteIdent(col.name), pgTypeToSqlType(col.pgType)];
15
14
  if (col.identity)
@@ -36,7 +35,7 @@ export function renderIndex(tableName, idx) {
36
35
  const where = idx.whereSql !== undefined ? ` WHERE ${idx.whereSql}` : "";
37
36
  return `CREATE ${kind} IF NOT EXISTS ${quoteIdent(idx.name)} ON ${quoteIdent(tableName)} (${colList})${where};`;
38
37
  }
39
- export function renderTableDdl(meta) {
38
+ export function renderTableDdl(meta, options = {}) {
40
39
  const colLines = meta.columns.map(renderColumn);
41
40
  const lines = [...colLines];
42
41
  if (meta.compositePrimaryKey !== undefined) {
@@ -45,11 +44,12 @@ export function renderTableDdl(meta) {
45
44
  }
46
45
  const create = `CREATE TABLE IF NOT EXISTS ${quoteIdent(meta.tableName)} (\n ${lines.join(",\n ")}\n);`;
47
46
  const indexes = meta.indexes.map((idx) => renderIndex(meta.tableName, idx));
48
- return [create, ...indexes];
47
+ const policies = options.rowLevelSecurity === "on" ? renderTableRowLevelSecurityDdl(meta) : [];
48
+ return [create, ...indexes, ...policies];
49
49
  }
50
- export function renderTablesDdl(metas) {
50
+ export function renderTablesDdl(metas, options = {}) {
51
51
  const stmts = [];
52
52
  for (const m of metas)
53
- stmts.push(...renderTableDdl(m));
53
+ stmts.push(...renderTableDdl(m, options));
54
54
  return stmts;
55
55
  }
@@ -0,0 +1,35 @@
1
+ import type { EntityTableMeta } from "./entity-table-meta.ts";
2
+ export declare const ROW_LEVEL_SECURITY_MODES: readonly ["off", "on"];
3
+ export type RowLevelSecurityMode = (typeof ROW_LEVEL_SECURITY_MODES)[number];
4
+ export declare const DEFAULT_ROW_LEVEL_SECURITY_MODE: RowLevelSecurityMode;
5
+ export declare const TENANT_ISOLATION_POLICY = "kumiko_tenant_isolation";
6
+ export declare const SYSTEM_TENANT_READ_POLICY = "kumiko_system_tenant_read";
7
+ export declare const TENANT_ID_SETTING = "kumiko.tenant_id";
8
+ export declare const SCOPE_SETTING = "kumiko.scope";
9
+ export declare const SYSTEM_SCOPE = "system";
10
+ export declare const POLICY_COMMANDS: readonly ["ALL", "SELECT", "INSERT", "UPDATE", "DELETE"];
11
+ export declare const POLICY_KINDS: readonly ["PERMISSIVE", "RESTRICTIVE"];
12
+ export type RowLevelSecurityPolicy = {
13
+ readonly name: string;
14
+ readonly command: (typeof POLICY_COMMANDS)[number];
15
+ readonly kind: (typeof POLICY_KINDS)[number];
16
+ readonly rolesSql: string;
17
+ readonly using?: string;
18
+ readonly withCheck?: string;
19
+ };
20
+ export type TableRowLevelSecurity = {
21
+ readonly policies: readonly RowLevelSecurityPolicy[];
22
+ };
23
+ export type RowLevelSecurityFlags = {
24
+ readonly enabled: boolean;
25
+ readonly forced: boolean;
26
+ };
27
+ export declare function tenantRowLevelSecurityFor(meta: EntityTableMeta): TableRowLevelSecurity | undefined;
28
+ export declare function rowLevelSecurityByTable(metas: readonly EntityTableMeta[]): Record<string, TableRowLevelSecurity>;
29
+ export declare function renderCreatePolicy(tableSql: string, policy: RowLevelSecurityPolicy): string;
30
+ export declare function renderRowLevelSecurityFlags(tableSql: string, flags: RowLevelSecurityFlags): string[];
31
+ export declare function renderEnableRowLevelSecurity(tableSql: string, next: TableRowLevelSecurity, previous?: TableRowLevelSecurity): string[];
32
+ export declare function renderDisableRowLevelSecurity(tableSql: string, previous: TableRowLevelSecurity): string[];
33
+ export declare function renderTableRowLevelSecurityDdl(meta: EntityTableMeta): readonly string[];
34
+ export declare function isSameTableRowLevelSecurity(a: TableRowLevelSecurity, b: TableRowLevelSecurity): boolean;
35
+ export declare function isTableRowLevelSecurityRecord(value: unknown): value is Record<string, TableRowLevelSecurity>;
@@ -0,0 +1,127 @@
1
+ // Pure renderer for Postgres row level security: EntityTableMeta → policy DDL.
2
+ // Every table with a `tenant_id` column gets two policies; tables without one are untouched.
3
+ import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-types/identifiers";
4
+ import { isOneOf, isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
5
+ import { pgTypeToSqlType } from "./dialect.js";
6
+ import { quoteIdent } from "./sql-identifier.js";
7
+ export const ROW_LEVEL_SECURITY_MODES = ["off", "on"];
8
+ export const DEFAULT_ROW_LEVEL_SECURITY_MODE = "on";
9
+ export const TENANT_ISOLATION_POLICY = "kumiko_tenant_isolation";
10
+ export const SYSTEM_TENANT_READ_POLICY = "kumiko_system_tenant_read";
11
+ export const TENANT_ID_SETTING = "kumiko.tenant_id";
12
+ export const SCOPE_SETTING = "kumiko.scope";
13
+ export const SYSTEM_SCOPE = "system";
14
+ const TENANT_COLUMN = "tenant_id";
15
+ export const POLICY_COMMANDS = ["ALL", "SELECT", "INSERT", "UPDATE", "DELETE"];
16
+ export const POLICY_KINDS = ["PERMISSIVE", "RESTRICTIVE"];
17
+ export function tenantRowLevelSecurityFor(meta) {
18
+ const tenantColumn = meta.columns.find((col) => col.name === TENANT_COLUMN);
19
+ if (tenantColumn === undefined)
20
+ return undefined;
21
+ const sqlType = pgTypeToSqlType(tenantColumn.pgType);
22
+ const column = quoteIdent(TENANT_COLUMN);
23
+ // NULLIF: after a transaction-local set_config, current_setting returns '' instead of NULL.
24
+ const sessionTenant = `NULLIF(current_setting('${TENANT_ID_SETTING}', true), '')`;
25
+ const isolation = `${column} = ${sessionTenant}::${sqlType} OR current_setting('${SCOPE_SETTING}', true) = '${SYSTEM_SCOPE}'`;
26
+ return {
27
+ policies: [
28
+ {
29
+ name: TENANT_ISOLATION_POLICY,
30
+ command: "ALL",
31
+ kind: "PERMISSIVE",
32
+ rolesSql: "PUBLIC",
33
+ using: isolation,
34
+ withCheck: isolation,
35
+ },
36
+ {
37
+ name: SYSTEM_TENANT_READ_POLICY,
38
+ command: "SELECT",
39
+ kind: "PERMISSIVE",
40
+ rolesSql: "PUBLIC",
41
+ using: `${column} = '${SYSTEM_TENANT_ID}'::${sqlType} AND ${sessionTenant} IS NOT NULL`,
42
+ },
43
+ ],
44
+ };
45
+ }
46
+ export function rowLevelSecurityByTable(metas) {
47
+ const byTable = {};
48
+ for (const meta of metas) {
49
+ const spec = tenantRowLevelSecurityFor(meta);
50
+ if (spec !== undefined)
51
+ byTable[meta.tableName] = spec;
52
+ }
53
+ return byTable;
54
+ }
55
+ export function renderCreatePolicy(tableSql, policy) {
56
+ const using = policy.using !== undefined ? ` USING (${policy.using})` : "";
57
+ const withCheck = policy.withCheck !== undefined ? ` WITH CHECK (${policy.withCheck})` : "";
58
+ return `CREATE POLICY ${quoteIdent(policy.name)} ON ${tableSql} AS ${policy.kind} FOR ${policy.command} TO ${policy.rolesSql}${using}${withCheck};`;
59
+ }
60
+ function renderDropPolicy(tableSql, policyName) {
61
+ return `DROP POLICY IF EXISTS ${quoteIdent(policyName)} ON ${tableSql};`;
62
+ }
63
+ export function renderRowLevelSecurityFlags(tableSql, flags) {
64
+ return [
65
+ `ALTER TABLE ${tableSql} ${flags.enabled ? "ENABLE" : "DISABLE"} ROW LEVEL SECURITY;`,
66
+ `ALTER TABLE ${tableSql} ${flags.forced ? "FORCE" : "NO FORCE"} ROW LEVEL SECURITY;`,
67
+ ];
68
+ }
69
+ export function renderEnableRowLevelSecurity(tableSql, next, previous) {
70
+ const nextNames = new Set(next.policies.map((policy) => policy.name));
71
+ const vanished = (previous?.policies ?? []).filter((policy) => !nextNames.has(policy.name));
72
+ return [
73
+ ...vanished.map((policy) => renderDropPolicy(tableSql, policy.name)),
74
+ ...next.policies.flatMap((policy) => [
75
+ renderDropPolicy(tableSql, policy.name),
76
+ renderCreatePolicy(tableSql, policy),
77
+ ]),
78
+ ...renderRowLevelSecurityFlags(tableSql, { enabled: true, forced: true }),
79
+ ];
80
+ }
81
+ export function renderDisableRowLevelSecurity(tableSql, previous) {
82
+ return [
83
+ ...previous.policies.map((policy) => renderDropPolicy(tableSql, policy.name)),
84
+ ...renderRowLevelSecurityFlags(tableSql, { enabled: false, forced: false }).reverse(),
85
+ ];
86
+ }
87
+ export function renderTableRowLevelSecurityDdl(meta) {
88
+ const spec = tenantRowLevelSecurityFor(meta);
89
+ if (spec === undefined)
90
+ return [];
91
+ return renderEnableRowLevelSecurity(quoteIdent(meta.tableName), spec);
92
+ }
93
+ function isSamePolicy(a, b) {
94
+ return (a.name === b.name &&
95
+ a.command === b.command &&
96
+ a.kind === b.kind &&
97
+ a.rolesSql === b.rolesSql &&
98
+ a.using === b.using &&
99
+ a.withCheck === b.withCheck);
100
+ }
101
+ export function isSameTableRowLevelSecurity(a, b) {
102
+ return (a.policies.length === b.policies.length &&
103
+ a.policies.every((policy, index) => {
104
+ const other = b.policies[index];
105
+ return other !== undefined && isSamePolicy(policy, other);
106
+ }));
107
+ }
108
+ function isOptionalString(value) {
109
+ return value === undefined || typeof value === "string";
110
+ }
111
+ function isRowLevelSecurityPolicy(value) {
112
+ return (isPlainObject(value) &&
113
+ typeof value["name"] === "string" &&
114
+ typeof value["rolesSql"] === "string" &&
115
+ isOneOf(POLICY_COMMANDS, value["command"]) &&
116
+ isOneOf(POLICY_KINDS, value["kind"]) &&
117
+ isOptionalString(value["using"]) &&
118
+ isOptionalString(value["withCheck"]));
119
+ }
120
+ function isTableRowLevelSecurity(value) {
121
+ return (isPlainObject(value) &&
122
+ Array.isArray(value["policies"]) &&
123
+ value["policies"].every(isRowLevelSecurityPolicy));
124
+ }
125
+ export function isTableRowLevelSecurityRecord(value) {
126
+ return isPlainObject(value) && Object.values(value).every(isTableRowLevelSecurity);
127
+ }
@@ -1,13 +1,15 @@
1
1
  import type { Snapshot } from "./migrate-generator.ts";
2
+ import { type ReplayedRowLevelSecurity } from "./replay-rls.ts";
2
3
  export type ReplayedTable = {
3
4
  readonly columns: ReadonlySet<string>;
4
5
  readonly columnsUnknown?: boolean;
6
+ readonly rowLevelSecurity?: ReplayedRowLevelSecurity;
5
7
  };
6
8
  export type ReplayedSchema = ReadonlyMap<string, ReplayedTable>;
7
9
  export declare function replayMigrationsDir(migrationsDir: string): ReplayedSchema;
8
10
  export type ReplayMismatch = {
9
11
  readonly tableName: string;
10
- readonly kind: "missing-table" | "unexpected-table" | "column-drift";
12
+ readonly kind: "missing-table" | "unexpected-table" | "column-drift" | "row-level-security-drift";
11
13
  readonly detail: string;
12
14
  };
13
15
  export declare function diffReplayAgainstSnapshot(replayed: ReplayedSchema, snapshot: Snapshot): readonly ReplayMismatch[];
@@ -9,6 +9,7 @@
9
9
  // either. Reuses `loadMigrationsFromDir`'s statement-splitting so the replay
10
10
  // sees exactly what the real runner would execute.
11
11
  import { loadMigrationsFromDir } from "./migrate-runner.js";
12
+ import { applyRowLevelSecurityStatement, IDENT, replayedOnlyRowLevelSecurityDrift, rowLevelSecurityDrift, TABLE_REF, } from "./replay-rls.js";
12
13
  // Migration files comment out destructive ops (DROP TABLE/COLUMN) as
13
14
  // `-- DESTRUCTIVE: <stmt>; -- uncomment + ensure backup` so the real
14
15
  // migrate-runner never executes them unattended. For replay purposes the
@@ -48,10 +49,6 @@ function splitTopLevel(body) {
48
49
  parts.push(current);
49
50
  return parts;
50
51
  }
51
- // Identifiers in hand-written migrations aren't always quoted (the generator
52
- // always quotes, but the header explicitly invites hand-editing) — optional
53
- // quotes so `CREATE TABLE foo (...)` parses the same as `CREATE TABLE "foo" (...)`.
54
- const IDENT = `"?([^"\\s(;,.]+)"?`;
55
52
  function parseColumnNames(body) {
56
53
  const columns = new Set();
57
54
  for (const part of splitTopLevel(body)) {
@@ -197,7 +194,7 @@ function applyStatement(schema, statement, context) {
197
194
  // instead of matching only the first clause, in statement order so an
198
195
  // add-then-drop of the same column (unusual, but not impossible) resolves
199
196
  // correctly.
200
- const alterTable = statement.match(new RegExp(`^ALTER TABLE\\s+${IDENT}\\s+([\\s\\S]*?);?\\s*$`, "i"));
197
+ const alterTable = statement.match(new RegExp(`^ALTER TABLE\\s+(?:IF EXISTS\\s+)?(?:ONLY\\s+)?${TABLE_REF}\\s+([\\s\\S]*?);?\\s*$`, "i"));
201
198
  const alterTableName = alterTable?.[1];
202
199
  const alterBody = alterTable?.[2];
203
200
  if (alterTableName !== undefined && alterBody !== undefined) {
@@ -276,10 +273,29 @@ export function replayMigrationsDir(migrationsDir) {
276
273
  for (const migration of migrations) {
277
274
  for (const statement of migration.statements) {
278
275
  applyStatement(schema, statement, { file: migration.id });
276
+ applyRowLevelSecurityStatement(schema, statement, { file: migration.id });
279
277
  }
280
278
  }
281
279
  return schema;
282
280
  }
281
+ function rowLevelSecurityMismatches(replayed, snapshot) {
282
+ const mismatches = [];
283
+ const specs = snapshot.rowLevelSecurity ?? {};
284
+ for (const { tableName } of snapshot.tables) {
285
+ const table = replayed.get(tableName);
286
+ // skip: a missing table is already reported as missing-table
287
+ if (!table)
288
+ continue;
289
+ const spec = Object.hasOwn(specs, tableName) ? specs[tableName] : undefined;
290
+ const detail = spec === undefined
291
+ ? replayedOnlyRowLevelSecurityDrift(table.rowLevelSecurity)
292
+ : rowLevelSecurityDrift(table.rowLevelSecurity, tableName, spec);
293
+ if (detail !== undefined) {
294
+ mismatches.push({ tableName, kind: "row-level-security-drift", detail });
295
+ }
296
+ }
297
+ return mismatches;
298
+ }
283
299
  // Compares what the migration files actually produce (`replayed`) against
284
300
  // what `.snapshot.json` claims (`snapshot`) — the check that would have
285
301
  // caught kumiko-studio's 0016 misgeneration (snapshot correct, SQL wrong).
@@ -314,6 +330,7 @@ export function diffReplayAgainstSnapshot(replayed, snapshot) {
314
330
  });
315
331
  }
316
332
  }
333
+ mismatches.push(...rowLevelSecurityMismatches(replayed, snapshot));
317
334
  for (const tableName of replayed.keys()) {
318
335
  if (!snapshotTableNames.has(tableName)) {
319
336
  mismatches.push({
@@ -0,0 +1,25 @@
1
+ import { type TableRowLevelSecurity } from "./render-rls-ddl.ts";
2
+ export type ReplayedPolicies = ReadonlyMap<string, string | undefined>;
3
+ export type ReplayedRowLevelSecurity = {
4
+ readonly enabled: boolean;
5
+ readonly forced: boolean;
6
+ readonly policies: ReplayedPolicies;
7
+ };
8
+ export type MutableReplayedRowLevelSecurity = {
9
+ enabled: boolean;
10
+ forced: boolean;
11
+ policies: Map<string, string | undefined>;
12
+ };
13
+ type TableWithRowLevelSecurity = {
14
+ rowLevelSecurity?: MutableReplayedRowLevelSecurity;
15
+ };
16
+ export declare const IDENT = "\"?([^\"\\s(;,.]+)\"?";
17
+ export declare const TABLE_REF = "(?:\"?public\"?\\.)?\"?([^\"\\s(;,.]+)\"?";
18
+ type StatementContext = {
19
+ readonly file: string;
20
+ };
21
+ type ReplayTables = ReadonlyMap<string, TableWithRowLevelSecurity>;
22
+ export declare function applyRowLevelSecurityStatement(tables: ReplayTables, statement: string, context: StatementContext): void;
23
+ export declare function rowLevelSecurityDrift(replayed: ReplayedRowLevelSecurity | undefined, tableName: string, expected: TableRowLevelSecurity): string | undefined;
24
+ export declare function replayedOnlyRowLevelSecurityDrift(replayed: ReplayedRowLevelSecurity | undefined): string | undefined;
25
+ export {};
@@ -0,0 +1,156 @@
1
+ // Row level security part of the migration replay: tracks ENABLE/FORCE flags and
2
+ // policy definitions per table so they can be compared against the snapshot.
3
+ import { renderCreatePolicy } from "./render-rls-ddl.js";
4
+ import { quoteIdent } from "./sql-identifier.js";
5
+ // Identifiers in hand-written migrations aren't always quoted (the generator
6
+ // always quotes, but the header explicitly invites hand-editing) — optional
7
+ // quotes so `CREATE TABLE foo (...)` parses the same as `CREATE TABLE "foo" (...)`.
8
+ export const IDENT = `"?([^"\\s(;,.]+)"?`;
9
+ export const TABLE_REF = `(?:"?public"?\\.)?${IDENT}`;
10
+ const ALTER_TABLE_RE = new RegExp(`^ALTER\\s+TABLE\\s+(?:IF\\s+EXISTS\\s+)?(?:ONLY\\s+)?${TABLE_REF}\\s+([\\s\\S]*?);?\\s*$`, "i");
11
+ const FLAG_CLAUSE_RE = /\b(ENABLE|DISABLE|NO\s+FORCE|FORCE)\s+ROW\s+LEVEL\s+SECURITY\b/gi;
12
+ const CREATE_POLICY_RE = new RegExp(`^CREATE\\s+POLICY\\s+${IDENT}\\s+ON\\s+${TABLE_REF}(?=[\\s;]|$)([\\s\\S]*)$`, "i");
13
+ const DROP_POLICY_RE = new RegExp(`^DROP\\s+POLICY\\s+(IF\\s+EXISTS\\s+)?${IDENT}\\s+ON\\s+${TABLE_REF}(?=[\\s;]|$)`, "i");
14
+ const ALTER_POLICY_RE = new RegExp(`^ALTER\\s+POLICY\\s+${IDENT}\\s+ON\\s+${TABLE_REF}(?=[\\s;]|$)([\\s\\S]*)$`, "i");
15
+ const RENAME_POLICY_RE = new RegExp(`^\\s*RENAME\\s+TO\\s+${IDENT}\\s*;?\\s*$`, "i");
16
+ const POLICY_OR_RLS_RE = /\bPOLICY\b|ROW\s+LEVEL\s+SECURITY/i;
17
+ const ANY_POLICY_STATEMENT_RE = /^(CREATE|ALTER|DROP)\s+POLICY\b/i;
18
+ function stateOf(table) {
19
+ table.rowLevelSecurity ??= {
20
+ enabled: false,
21
+ forced: false,
22
+ policies: new Map(),
23
+ };
24
+ return table.rowLevelSecurity;
25
+ }
26
+ function applyFlagClauses(table, alterBody) {
27
+ for (const [, clause] of alterBody.matchAll(FLAG_CLAUSE_RE)) {
28
+ const state = stateOf(table);
29
+ switch (clause?.toUpperCase().replace(/\s+/g, " ")) {
30
+ case "ENABLE":
31
+ state.enabled = true;
32
+ break;
33
+ case "DISABLE":
34
+ state.enabled = false;
35
+ break;
36
+ case "FORCE":
37
+ state.forced = true;
38
+ break;
39
+ case "NO FORCE":
40
+ state.forced = false;
41
+ break;
42
+ }
43
+ }
44
+ }
45
+ function normalizePolicyDefinition(text) {
46
+ return text.replace(/\s+/g, " ").replace(/;\s*$/, "").trim();
47
+ }
48
+ function applyAlterPolicy(table, policyName, alterBody) {
49
+ const { policies } = stateOf(table);
50
+ const newName = alterBody.match(RENAME_POLICY_RE)?.[1];
51
+ if (newName === undefined) {
52
+ if (policies.has(policyName))
53
+ policies.set(policyName, undefined);
54
+ // skip: body is not a rename, the altered-flag above is all that is tracked
55
+ return;
56
+ }
57
+ // skip: policy not tracked, nothing to rename
58
+ if (!policies.has(policyName))
59
+ return;
60
+ policies.set(newName, policies.get(policyName));
61
+ policies.delete(policyName);
62
+ }
63
+ function failUnparsedPolicy(statement, file) {
64
+ const prefix = statement.slice(0, 200).replace(/\s+/g, " ").trim();
65
+ throw new Error(`replayMigrationsDir: unparsed policy statement in ${file} — expected CREATE POLICY <name> ON <table>, ` +
66
+ `DROP POLICY [IF EXISTS] <name> ON <table> or ALTER POLICY <name> ON <table>. Statement: ${prefix}${statement.length > 200 ? "…" : ""}`);
67
+ }
68
+ function tryApplyAlterTable(tables, statement) {
69
+ const alter = statement.match(ALTER_TABLE_RE);
70
+ if (alter?.[1] === undefined || alter[2] === undefined)
71
+ return false;
72
+ const table = tables.get(alter[1]);
73
+ if (table)
74
+ applyFlagClauses(table, alter[2]);
75
+ return true;
76
+ }
77
+ function tryApplyCreatePolicy(tables, statement) {
78
+ const created = statement.match(CREATE_POLICY_RE);
79
+ if (created?.[1] === undefined || created[2] === undefined)
80
+ return false;
81
+ const table = tables.get(created[2]);
82
+ if (table) {
83
+ stateOf(table).policies.set(created[1], normalizePolicyDefinition(created[3] ?? ""));
84
+ }
85
+ return true;
86
+ }
87
+ function tryApplyDropPolicy(tables, statement, context) {
88
+ const dropped = statement.match(DROP_POLICY_RE);
89
+ if (dropped?.[2] === undefined || dropped[3] === undefined)
90
+ return false;
91
+ const table = tables.get(dropped[3]);
92
+ if (!table)
93
+ return true;
94
+ const removed = stateOf(table).policies.delete(dropped[2]);
95
+ if (!removed && dropped[1] === undefined) {
96
+ throw new Error(`replayMigrationsDir: ${context.file} drops policy "${dropped[2]}" on "${dropped[3]}" which no earlier migration created`);
97
+ }
98
+ return true;
99
+ }
100
+ function tryApplyAlterPolicy(tables, statement) {
101
+ const altered = statement.match(ALTER_POLICY_RE);
102
+ if (altered?.[1] === undefined || altered[2] === undefined || altered[3] === undefined) {
103
+ return false;
104
+ }
105
+ const table = tables.get(altered[2]);
106
+ if (table)
107
+ applyAlterPolicy(table, altered[1], altered[3]);
108
+ return true;
109
+ }
110
+ export function applyRowLevelSecurityStatement(tables, statement, context) {
111
+ if (/^DO\b/i.test(statement) && POLICY_OR_RLS_RE.test(statement)) {
112
+ throw new Error(`replayMigrationsDir: ${context.file} changes policies or row level security inside a DO block, which the replay cannot evaluate — write the statements at top level`);
113
+ }
114
+ const handled = tryApplyAlterTable(tables, statement) ||
115
+ tryApplyCreatePolicy(tables, statement) ||
116
+ tryApplyDropPolicy(tables, statement, context) ||
117
+ tryApplyAlterPolicy(tables, statement);
118
+ if (!handled && ANY_POLICY_STATEMENT_RE.test(statement)) {
119
+ failUnparsedPolicy(statement, context.file);
120
+ }
121
+ }
122
+ function expectedPolicyDefinition(tableName, policy) {
123
+ const rendered = renderCreatePolicy(quoteIdent(tableName), policy);
124
+ const definition = rendered.match(CREATE_POLICY_RE)?.[3];
125
+ if (definition === undefined) {
126
+ throw new Error(`rendered policy "${policy.name}" is not a CREATE POLICY statement`);
127
+ }
128
+ return normalizePolicyDefinition(definition);
129
+ }
130
+ export function rowLevelSecurityDrift(replayed, tableName, expected) {
131
+ const parts = [];
132
+ if (replayed?.enabled !== true)
133
+ parts.push("ROW LEVEL SECURITY is not enabled");
134
+ if (replayed?.forced !== true)
135
+ parts.push("ROW LEVEL SECURITY is not forced");
136
+ const actual = replayed?.policies ?? new Map();
137
+ const expectedNames = new Set(expected.policies.map((policy) => policy.name));
138
+ for (const policy of expected.policies) {
139
+ if (!actual.has(policy.name)) {
140
+ parts.push(`missing policy: ${policy.name}`);
141
+ }
142
+ else if (actual.get(policy.name) !== expectedPolicyDefinition(tableName, policy)) {
143
+ parts.push(`policy ${policy.name} deviates from the generated definition (hand-edited or altered)`);
144
+ }
145
+ }
146
+ const unexpected = [...actual.keys()].filter((name) => !expectedNames.has(name));
147
+ if (unexpected.length > 0)
148
+ parts.push(`unexpected policies: ${unexpected.join(", ")}`);
149
+ return parts.length > 0 ? parts.join("; ") : undefined;
150
+ }
151
+ export function replayedOnlyRowLevelSecurityDrift(replayed) {
152
+ const hasRowLevelSecurity = replayed !== undefined && (replayed.enabled || replayed.forced || replayed.policies.size > 0);
153
+ return hasRowLevelSecurity
154
+ ? "migrations enable row level security or create policies but .snapshot.json has no row level security spec for this table"
155
+ : undefined;
156
+ }