@cosmicdrift/kumiko-framework 0.387.0 → 0.389.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 (375) hide show
  1. package/AGENTS.md +9 -1
  2. package/agents/examples/anonymous-access.md +1 -1
  3. package/agents/examples/apex-landing.md +204 -0
  4. package/agents/examples/api-worker-split.md +64 -0
  5. package/agents/examples/app-config.md +14 -0
  6. package/agents/examples/auth-mfa.md +115 -0
  7. package/agents/examples/env-composition.md +28 -1
  8. package/agents/examples/golden-path.md +13 -1
  9. package/agents/examples/legal-pages.md +1 -1
  10. package/agents/examples/lifecycle-hooks.md +55 -2
  11. package/agents/examples/list-table-options.md +466 -0
  12. package/agents/examples/pdf-audit-export.md +4 -2
  13. package/agents/examples/prod-boot-options.md +16 -0
  14. package/agents/examples/rate-limiting.md +48 -0
  15. package/agents/examples/record-detail-layout.md +692 -0
  16. package/agents/examples/seed-migration.md +212 -0
  17. package/agents/examples/state-machine.md +3 -2
  18. package/agents/examples/tenant-isolation.md +73 -1
  19. package/agents/examples/wizard-form.md +941 -0
  20. package/agents/examples/writeform-section.md +548 -0
  21. package/agents/examples.md +1 -0
  22. package/agents/features.md +19 -2
  23. package/agents/widgets.md +2 -0
  24. package/dist/api/api-constants.d.ts +1 -0
  25. package/dist/api/api-constants.js +2 -0
  26. package/dist/api/auth-routes-shared.d.ts +2 -0
  27. package/dist/api/auth-routes-shared.js +5 -0
  28. package/dist/api/client-error-route.js +10 -5
  29. package/dist/api/entity-read-access.d.ts +2 -1
  30. package/dist/api/entity-read-access.js +14 -0
  31. package/dist/api/export/export-cells-reference.d.ts +9 -0
  32. package/dist/api/export/export-cells-reference.js +21 -0
  33. package/dist/api/export/export-cells.d.ts +11 -0
  34. package/dist/api/export/export-cells.js +136 -0
  35. package/dist/api/export/export-ciphertext-guard.d.ts +2 -0
  36. package/dist/api/export/export-ciphertext-guard.js +27 -0
  37. package/dist/api/export/export-columns.d.ts +29 -0
  38. package/dist/api/export/export-columns.js +85 -0
  39. package/dist/api/export/export-errors.d.ts +4 -0
  40. package/dist/api/export/export-errors.js +14 -0
  41. package/dist/api/export/export-route.d.ts +4 -0
  42. package/dist/api/export/export-route.js +74 -0
  43. package/dist/api/export/export-rows.d.ts +13 -0
  44. package/dist/api/export/export-rows.js +62 -0
  45. package/dist/api/export/export-screen.d.ts +12 -0
  46. package/dist/api/export/export-screen.js +47 -0
  47. package/dist/api/export/export-xlsx-writer.d.ts +4 -0
  48. package/dist/api/export/export-xlsx-writer.js +21 -0
  49. package/dist/api/extra-route.d.ts +4 -0
  50. package/dist/api/index.d.ts +3 -1
  51. package/dist/api/index.js +3 -1
  52. package/dist/api/pii-leak-guard.d.ts +3 -0
  53. package/dist/api/pii-leak-guard.js +3 -2
  54. package/dist/api/request-context.d.ts +1 -1
  55. package/dist/api/request-context.js +1 -1
  56. package/dist/api/routes.d.ts +16 -0
  57. package/dist/api/routes.js +2 -2
  58. package/dist/api/server-boot-checks.d.ts +10 -0
  59. package/dist/api/server-boot-checks.js +157 -0
  60. package/dist/api/server-event-dispatcher.d.ts +26 -0
  61. package/dist/api/server-event-dispatcher.js +208 -0
  62. package/dist/api/server-extra-routes.d.ts +11 -0
  63. package/dist/api/server-extra-routes.js +206 -0
  64. package/dist/api/server-middleware.d.ts +20 -0
  65. package/dist/api/server-middleware.js +211 -0
  66. package/dist/api/server-routes.d.ts +19 -0
  67. package/dist/api/server-routes.js +299 -0
  68. package/dist/api/server-websocket-routes.d.ts +4 -0
  69. package/dist/api/server-websocket-routes.js +108 -0
  70. package/dist/api/server-wiring.d.ts +32 -0
  71. package/dist/api/server-wiring.js +238 -0
  72. package/dist/api/server.d.ts +10 -15
  73. package/dist/api/server.js +48 -1251
  74. package/dist/api/session-cookies.js +4 -4
  75. package/dist/api/sse-route.d.ts +2 -1
  76. package/dist/api/sse-route.js +27 -13
  77. package/dist/config/index.d.ts +3 -1
  78. package/dist/crypto/owner-transition-rekey.d.ts +5 -1
  79. package/dist/crypto/owner-transition-rekey.js +1 -1
  80. package/dist/crypto/pg-kms-adapter.d.ts +1 -0
  81. package/dist/crypto/pg-kms-adapter.js +12 -18
  82. package/dist/crypto/pii-field-encryption.d.ts +5 -1
  83. package/dist/crypto/pii-field-encryption.js +3 -1
  84. package/dist/db/aggregate-sql.d.ts +23 -0
  85. package/dist/db/aggregate-sql.js +165 -0
  86. package/dist/db/closed-connection.d.ts +9 -0
  87. package/dist/db/closed-connection.js +88 -0
  88. package/dist/db/connection.d.ts +4 -5
  89. package/dist/db/connection.js +29 -20
  90. package/dist/db/dialect.js +1 -1
  91. package/dist/db/distinct-on.d.ts +1 -1
  92. package/dist/db/driver-row-coercion.d.ts +2 -0
  93. package/dist/db/driver-row-coercion.js +186 -0
  94. package/dist/db/driver-value-preparation.d.ts +16 -0
  95. package/dist/db/driver-value-preparation.js +101 -0
  96. package/dist/db/entity-field-encryption.d.ts +1 -0
  97. package/dist/db/entity-field-encryption.js +1 -1
  98. package/dist/db/entity-table-meta.js +103 -110
  99. package/dist/db/event-store-executor-context.d.ts +2 -1
  100. package/dist/db/event-store-executor-read-aggregate-currencies.d.ts +17 -0
  101. package/dist/db/event-store-executor-read-aggregate-currencies.js +40 -0
  102. package/dist/db/event-store-executor-read-aggregate.d.ts +8 -0
  103. package/dist/db/event-store-executor-read-aggregate.js +143 -0
  104. package/dist/db/event-store-executor-read-filter.d.ts +31 -0
  105. package/dist/db/event-store-executor-read-filter.js +206 -0
  106. package/dist/db/event-store-executor-read-paging.d.ts +14 -0
  107. package/dist/db/event-store-executor-read-paging.js +99 -0
  108. package/dist/db/event-store-executor-read-rows.d.ts +6 -0
  109. package/dist/db/event-store-executor-read-rows.js +32 -0
  110. package/dist/db/event-store-executor-read-search.d.ts +28 -0
  111. package/dist/db/event-store-executor-read-search.js +221 -0
  112. package/dist/db/event-store-executor-read-sort.d.ts +41 -0
  113. package/dist/db/event-store-executor-read-sort.js +130 -0
  114. package/dist/db/event-store-executor-read-where.d.ts +24 -0
  115. package/dist/db/event-store-executor-read-where.js +81 -0
  116. package/dist/db/event-store-executor-read.d.ts +2 -9
  117. package/dist/db/event-store-executor-read.js +48 -701
  118. package/dist/db/event-store-executor-write-atomic.d.ts +4 -0
  119. package/dist/db/event-store-executor-write-atomic.js +41 -0
  120. package/dist/db/event-store-executor-write-steps.d.ts +17 -0
  121. package/dist/db/event-store-executor-write-steps.js +95 -0
  122. package/dist/db/event-store-executor-write.js +405 -509
  123. package/dist/db/event-store-executor.js +2 -1
  124. package/dist/db/executor-managed-tables.js +1 -1
  125. package/dist/db/index.d.ts +7 -3
  126. package/dist/db/index.js +6 -2
  127. package/dist/db/list-aggregate-labels.d.ts +14 -0
  128. package/dist/db/list-aggregate-labels.js +26 -0
  129. package/dist/db/migrate-generator.js +71 -86
  130. package/dist/db/migrate-runner.js +7 -4
  131. package/dist/db/pg-error.js +26 -21
  132. package/dist/db/queries/backfill-pii.js +3 -6
  133. package/dist/db/queries/database-now.js +2 -1
  134. package/dist/db/queries/shadow-swap.js +2 -3
  135. package/dist/db/query.d.ts +16 -15
  136. package/dist/db/query.js +76 -804
  137. package/dist/db/reference-data.js +19 -2
  138. package/dist/db/statement-timeout.js +2 -1
  139. package/dist/db/table-builder.js +113 -166
  140. package/dist/db/table-info.d.ts +10 -0
  141. package/dist/db/table-info.js +45 -0
  142. package/dist/db/temporal-value-guards.d.ts +6 -0
  143. package/dist/db/temporal-value-guards.js +15 -0
  144. package/dist/db/tenant-db-runner.d.ts +1 -0
  145. package/dist/db/tenant-db-runner.js +3 -0
  146. package/dist/db/tenant-db-upsert.d.ts +5 -0
  147. package/dist/db/tenant-db-upsert.js +91 -0
  148. package/dist/db/tenant-db.js +50 -10
  149. package/dist/db/where-clause.d.ts +6 -0
  150. package/dist/db/where-clause.js +236 -0
  151. package/dist/derivatives/delete-stored-file.js +2 -1
  152. package/dist/engine/boot-validator/boot-check.js +1 -1
  153. package/dist/engine/boot-validator/bundled-lists-table.d.ts +2 -0
  154. package/dist/engine/boot-validator/bundled-lists-table.js +36 -0
  155. package/dist/engine/boot-validator/detail-screens.js +2 -2
  156. package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
  157. package/dist/engine/boot-validator/entity-handler.js +17 -0
  158. package/dist/engine/boot-validator/entity-list-aggregate-options.d.ts +3 -0
  159. package/dist/engine/boot-validator/entity-list-aggregate-options.js +23 -0
  160. package/dist/engine/boot-validator/form-derivations.js +35 -1
  161. package/dist/engine/boot-validator/i18n-keys.js +5 -3
  162. package/dist/engine/boot-validator/index.js +2 -0
  163. package/dist/engine/boot-validator/list-bulk-actions.d.ts +3 -0
  164. package/dist/engine/boot-validator/list-bulk-actions.js +49 -0
  165. package/dist/engine/boot-validator/list-column-layout.d.ts +8 -0
  166. package/dist/engine/boot-validator/list-column-layout.js +41 -0
  167. package/dist/engine/boot-validator/list-table-aggregate-options.d.ts +24 -0
  168. package/dist/engine/boot-validator/list-table-aggregate-options.js +145 -0
  169. package/dist/engine/boot-validator/list-table-aggregate-query.d.ts +10 -0
  170. package/dist/engine/boot-validator/list-table-aggregate-query.js +31 -0
  171. package/dist/engine/boot-validator/list-table-export-options.d.ts +1 -0
  172. package/dist/engine/boot-validator/list-table-export-options.js +27 -0
  173. package/dist/engine/boot-validator/list-table-options.d.ts +3 -0
  174. package/dist/engine/boot-validator/list-table-options.js +77 -0
  175. package/dist/engine/boot-validator/list-table-reject-unknown-keys.d.ts +1 -0
  176. package/dist/engine/boot-validator/list-table-reject-unknown-keys.js +7 -0
  177. package/dist/engine/boot-validator/pii-retention.js +233 -155
  178. package/dist/engine/boot-validator/projection-list-screens.d.ts +1 -1
  179. package/dist/engine/boot-validator/projection-list-screens.js +3 -13
  180. package/dist/engine/boot-validator/related-list-table-rules.d.ts +4 -0
  181. package/dist/engine/boot-validator/related-list-table-rules.js +42 -0
  182. package/dist/engine/boot-validator/screens-actions.d.ts +1 -0
  183. package/dist/engine/boot-validator/screens-actions.js +8 -1
  184. package/dist/engine/boot-validator/screens-dashboard.js +2 -5
  185. package/dist/engine/boot-validator/screens-forms.js +2 -6
  186. package/dist/engine/boot-validator/screens-kind-config-edit.d.ts +3 -0
  187. package/dist/engine/boot-validator/screens-kind-config-edit.js +88 -0
  188. package/dist/engine/boot-validator/screens-kind-entity-edit.d.ts +3 -0
  189. package/dist/engine/boot-validator/screens-kind-entity-edit.js +55 -0
  190. package/dist/engine/boot-validator/screens-kind-entity-list.d.ts +3 -0
  191. package/dist/engine/boot-validator/screens-kind-entity-list.js +235 -0
  192. package/dist/engine/boot-validator/screens-kind-projection-detail.d.ts +3 -0
  193. package/dist/engine/boot-validator/screens-kind-projection-detail.js +235 -0
  194. package/dist/engine/boot-validator/screens-kind-projection-list.d.ts +3 -0
  195. package/dist/engine/boot-validator/screens-kind-projection-list.js +56 -0
  196. package/dist/engine/boot-validator/screens-kind-secret-mint.d.ts +3 -0
  197. package/dist/engine/boot-validator/screens-kind-secret-mint.js +28 -0
  198. package/dist/engine/boot-validator/screens-kind-shared.d.ts +37 -0
  199. package/dist/engine/boot-validator/screens-kind-shared.js +142 -0
  200. package/dist/engine/boot-validator/screens-lists.d.ts +2 -1
  201. package/dist/engine/boot-validator/screens-lists.js +24 -11
  202. package/dist/engine/boot-validator/screens.d.ts +0 -2
  203. package/dist/engine/boot-validator/screens.js +99 -860
  204. package/dist/engine/build-app-schema.d.ts +2 -1
  205. package/dist/engine/build-app-schema.js +65 -97
  206. package/dist/engine/build-config-feature-schema.js +6 -1
  207. package/dist/engine/define-feature.js +13 -2
  208. package/dist/engine/define-handler.js +1 -0
  209. package/dist/engine/entity-handlers.d.ts +13 -0
  210. package/dist/engine/entity-handlers.js +10 -21
  211. package/dist/engine/entity-list-runtime-options.d.ts +8 -0
  212. package/dist/engine/entity-list-runtime-options.js +18 -0
  213. package/dist/engine/feature-ast/extractors/handlers.js +1 -0
  214. package/dist/engine/feature-ast/extractors/index.d.ts +1 -1
  215. package/dist/engine/feature-ast/extractors/index.js +1 -1
  216. package/dist/engine/feature-ast/extractors/round1.d.ts +2 -1
  217. package/dist/engine/feature-ast/extractors/round1.js +6 -0
  218. package/dist/engine/feature-ast/extractors/round2.js +25 -6
  219. package/dist/engine/feature-ast/index.d.ts +1 -1
  220. package/dist/engine/feature-ast/parse.js +3 -1
  221. package/dist/engine/feature-ast/patch.d.ts +2 -0
  222. package/dist/engine/feature-ast/patch.js +5 -182
  223. package/dist/engine/feature-ast/pattern-change-schema.d.ts +10 -245
  224. package/dist/engine/feature-ast/pattern-change-schema.js +10 -163
  225. package/dist/engine/feature-ast/pattern-id-matchers.d.ts +7 -0
  226. package/dist/engine/feature-ast/pattern-id-matchers.js +211 -0
  227. package/dist/engine/feature-ast/pattern-id-schema.d.ts +251 -0
  228. package/dist/engine/feature-ast/pattern-id-schema.js +166 -0
  229. package/dist/engine/feature-ast/patterns.d.ts +14 -1
  230. package/dist/engine/feature-ast/patterns.js +44 -51
  231. package/dist/engine/feature-ast/render.js +52 -87
  232. package/dist/engine/feature-builder-state.d.ts +1 -0
  233. package/dist/engine/feature-builder-state.js +1 -0
  234. package/dist/engine/feature-entity-handlers.js +3 -0
  235. package/dist/engine/feature-manifest.d.ts +1 -0
  236. package/dist/engine/feature-manifest.js +1 -0
  237. package/dist/engine/field-access.d.ts +1 -0
  238. package/dist/engine/field-access.js +12 -0
  239. package/dist/engine/form-derivations.js +18 -7
  240. package/dist/engine/handler-field-references.js +1 -1
  241. package/dist/engine/index.d.ts +2 -2
  242. package/dist/engine/index.js +1 -1
  243. package/dist/engine/pattern-library/library.js +2 -1
  244. package/dist/engine/pattern-library/static-schemas.d.ts +1 -0
  245. package/dist/engine/pattern-library/static-schemas.js +9 -0
  246. package/dist/engine/project-app-schema-for-roles.d.ts +2 -0
  247. package/dist/engine/project-app-schema-for-roles.js +6 -4
  248. package/dist/engine/registry-ingest.js +1 -1
  249. package/dist/engine/schema-builder.js +228 -212
  250. package/dist/engine/screen-filter-ops.js +2 -0
  251. package/dist/engine/screen-helpers.js +1 -1
  252. package/dist/engine/screen-kind.d.ts +7 -0
  253. package/dist/engine/screen-kind.js +1 -0
  254. package/dist/engine/strip-disabled-features-from-app-schema.js +58 -17
  255. package/dist/engine/types/feature.d.ts +1 -0
  256. package/dist/engine/types/index.d.ts +4 -3
  257. package/dist/engine/types/screen.d.ts +1 -1
  258. package/dist/engine/types/screen.js +1 -1
  259. package/dist/engine/validate-projection-allowlist.js +2 -1
  260. package/dist/entrypoint/index.d.ts +6 -1
  261. package/dist/entrypoint/index.js +10 -1
  262. package/dist/errors/docs-url.d.ts +1 -1
  263. package/dist/errors/docs-url.js +22 -2
  264. package/dist/es-ops/context.js +1 -1
  265. package/dist/event-store/admin-api.js +1 -1
  266. package/dist/event-store/event-store.d.ts +1 -0
  267. package/dist/event-store/event-store.js +17 -8
  268. package/dist/event-store/provenance-append.js +1 -1
  269. package/dist/files/bind-pending-file-refs.js +3 -3
  270. package/dist/files/create-file-ref-from-bytes.js +7 -1
  271. package/dist/files/create-file-ref.d.ts +3 -0
  272. package/dist/files/create-file-ref.js +11 -2
  273. package/dist/files/file-handle.js +2 -3
  274. package/dist/files/file-routes.d.ts +2 -0
  275. package/dist/files/file-routes.js +40 -10
  276. package/dist/files/provider-resolver.d.ts +1 -0
  277. package/dist/files/provider-resolver.js +1 -1
  278. package/dist/files/record-write-check.d.ts +6 -0
  279. package/dist/files/record-write-check.js +13 -0
  280. package/dist/files/resolve-content-type.js +43 -0
  281. package/dist/files/upload-ledger.d.ts +2 -2
  282. package/dist/files/upload-ledger.js +11 -4
  283. package/dist/i18n/required-surface-keys.js +189 -208
  284. package/dist/internal/stack-support.d.ts +2 -1
  285. package/dist/internal/stack-support.js +1 -1
  286. package/dist/internal/testing.js +1 -1
  287. package/dist/jobs/job-runner-context.d.ts +32 -0
  288. package/dist/jobs/job-runner-context.js +305 -0
  289. package/dist/jobs/job-runner-execution.d.ts +7 -0
  290. package/dist/jobs/job-runner-execution.js +160 -0
  291. package/dist/jobs/job-runner-gates.d.ts +19 -0
  292. package/dist/jobs/job-runner-gates.js +198 -0
  293. package/dist/jobs/job-runner-helpers.d.ts +17 -3
  294. package/dist/jobs/job-runner-helpers.js +61 -2
  295. package/dist/jobs/job-runner-scope.d.ts +44 -0
  296. package/dist/jobs/job-runner-scope.js +102 -0
  297. package/dist/jobs/job-runner-types.d.ts +12 -0
  298. package/dist/jobs/job-runner.d.ts +0 -1
  299. package/dist/jobs/job-runner.js +50 -657
  300. package/dist/observability/escape-hatch-report.d.ts +3 -0
  301. package/dist/observability/escape-hatch-report.js +21 -3
  302. package/dist/pipeline/after-rollback-sink.d.ts +6 -0
  303. package/dist/pipeline/after-rollback-sink.js +32 -0
  304. package/dist/pipeline/cascade-handler.js +165 -138
  305. package/dist/pipeline/dispatch-batch.js +8 -1
  306. package/dist/pipeline/dispatch-query.js +10 -3
  307. package/dist/pipeline/dispatch-shared.d.ts +13 -3
  308. package/dist/pipeline/dispatch-shared.js +62 -710
  309. package/dist/pipeline/dispatch-write.js +296 -189
  310. package/dist/pipeline/event-dispatcher-admin.js +2 -1
  311. package/dist/pipeline/event-dispatcher-delivery.js +6 -6
  312. package/dist/pipeline/event-dispatcher.d.ts +1 -0
  313. package/dist/pipeline/event-dispatcher.js +6 -9
  314. package/dist/pipeline/handler-context-auth.d.ts +9 -0
  315. package/dist/pipeline/handler-context-auth.js +54 -0
  316. package/dist/pipeline/handler-context-config.d.ts +14 -0
  317. package/dist/pipeline/handler-context-config.js +113 -0
  318. package/dist/pipeline/handler-context-db.d.ts +24 -0
  319. package/dist/pipeline/handler-context-db.js +149 -0
  320. package/dist/pipeline/handler-context-dispatch.d.ts +23 -0
  321. package/dist/pipeline/handler-context-dispatch.js +101 -0
  322. package/dist/pipeline/handler-context-events.d.ts +41 -0
  323. package/dist/pipeline/handler-context-events.js +239 -0
  324. package/dist/pipeline/handler-context-notify.d.ts +12 -0
  325. package/dist/pipeline/handler-context-notify.js +55 -0
  326. package/dist/pipeline/handler-context-observability.d.ts +7 -0
  327. package/dist/pipeline/handler-context-observability.js +21 -0
  328. package/dist/pipeline/handler-context-scope.d.ts +48 -0
  329. package/dist/pipeline/handler-context-scope.js +88 -0
  330. package/dist/pipeline/notify-scope.js +12 -1
  331. package/dist/pipeline/pre-handler-gates.js +20 -4
  332. package/dist/pipeline/pre-transaction-reservations.d.ts +1 -0
  333. package/dist/pipeline/pre-transaction-reservations.js +9 -3
  334. package/dist/pipeline/projection-rebuild.js +2 -1
  335. package/dist/pipeline/system-hooks.d.ts +3 -1
  336. package/dist/pipeline/system-hooks.js +55 -6
  337. package/dist/pipeline/write-result-masking.js +13 -10
  338. package/dist/schema-cli/apply-command.d.ts +2 -0
  339. package/dist/schema-cli/apply-command.js +97 -0
  340. package/dist/schema-cli/baseline-command.d.ts +2 -0
  341. package/dist/schema-cli/baseline-command.js +33 -0
  342. package/dist/schema-cli/generate-command.d.ts +2 -0
  343. package/dist/schema-cli/generate-command.js +90 -0
  344. package/dist/schema-cli/shared.d.ts +39 -0
  345. package/dist/schema-cli/shared.js +95 -0
  346. package/dist/schema-cli/status-command.d.ts +2 -0
  347. package/dist/schema-cli/status-command.js +73 -0
  348. package/dist/schema-cli/validate-boot-input.d.ts +5 -0
  349. package/dist/schema-cli/validate-boot-input.js +130 -0
  350. package/dist/schema-cli/validate-command.d.ts +2 -0
  351. package/dist/schema-cli/validate-command.js +130 -0
  352. package/dist/schema-cli.d.ts +2 -18
  353. package/dist/schema-cli.js +47 -604
  354. package/dist/search/meilisearch-adapter.js +2 -1
  355. package/dist/search/reindex-entity.js +15 -5
  356. package/dist/ui-types/index.d.ts +2 -2
  357. package/dist/ui-types/index.js +1 -1
  358. package/dist/upgrade-changelog.d.ts +2 -0
  359. package/dist/upgrade-changelog.js +14 -0
  360. package/dist/upgrade-cli.d.ts +3 -12
  361. package/dist/upgrade-cli.js +171 -197
  362. package/dist/upgrade-marker-parse.d.ts +32 -0
  363. package/dist/upgrade-marker-parse.js +73 -0
  364. package/package.json +9 -6
  365. package/src/changes.json +555 -0
  366. package/src/scripts/codemod/codemod-support.ts +1 -1
  367. package/src/scripts/codemod/db-factory.ts +1 -1
  368. package/src/scripts/codemod/feature-layout.ts +10 -2
  369. package/src/scripts/codemod/object-form.ts +41 -4
  370. package/src/scripts/codemod/package-merge.ts +58 -8
  371. package/src/scripts/codemod/pii-personal-migration.ts +175 -145
  372. package/src/scripts/codemod/testing-injection.ts +122 -42
  373. package/src/scripts/codemod/testing-move.ts +29 -7
  374. package/dist/engine/provider-plugin-context.d.ts +0 -3
  375. package/dist/engine/provider-plugin-context.js +0 -28
@@ -552,9 +552,58 @@ function readPatRevokedScope(payload) {
552
552
  const tokenIds = readNonEmptyStringArray(payload["tokenIds"]);
553
553
  return tokenIds ? { kind: "pat-tokens", tokenIds } : { kind: "user" };
554
554
  }
555
- export function createAccessInvalidationEventConsumer(sseBroker) {
555
+ const ACCESS_INVALIDATION_BACKOFF_INITIAL_MS = 250;
556
+ const ACCESS_INVALIDATION_BACKOFF_MAX_MS = 10_000;
557
+ // Only the publish can fail because Redis is unreachable. The consumer must keep retrying those
558
+ // until Redis is back (dead-lettering would silently stop every later revocation reaching open
559
+ // streams), while any other failure keeps the dispatcher's attempt limit so one poison event
560
+ // cannot hold back every later revocation for good.
561
+ class AccessInvalidationTransportError extends Error {
562
+ constructor(cause) {
563
+ super(cause instanceof Error ? cause.message : String(cause), { cause });
564
+ this.name = "AccessInvalidationTransportError";
565
+ }
566
+ }
567
+ function isAccessInvalidationTransportError(error) {
568
+ return error instanceof AccessInvalidationTransportError;
569
+ }
570
+ // The gate rethrows without touching Redis until the backoff elapsed.
571
+ function withTransportBackoff(handle, now) {
572
+ let delayMs = ACCESS_INVALIDATION_BACKOFF_INITIAL_MS;
573
+ let retryNotBefore = 0;
574
+ let transportError = null;
575
+ return async (event) => {
576
+ if (transportError !== null && now() < retryNotBefore)
577
+ throw transportError;
578
+ try {
579
+ await handle(event);
580
+ delayMs = ACCESS_INVALIDATION_BACKOFF_INITIAL_MS;
581
+ transportError = null;
582
+ }
583
+ catch (err) {
584
+ if (isAccessInvalidationTransportError(err)) {
585
+ transportError = err;
586
+ retryNotBefore = now() + delayMs;
587
+ delayMs = Math.min(delayMs * 2, ACCESS_INVALIDATION_BACKOFF_MAX_MS);
588
+ }
589
+ throw err;
590
+ }
591
+ };
592
+ }
593
+ export function createAccessInvalidationEventConsumer(sseBroker, opts = {}) {
594
+ const now = opts.now ?? Date.now;
595
+ const publish = async (userId, scope) => {
596
+ try {
597
+ await sseBroker.publishAccessInvalidation(userId, scope);
598
+ }
599
+ catch (cause) {
600
+ throw new AccessInvalidationTransportError(cause);
601
+ }
602
+ };
556
603
  return {
557
604
  name: ACCESS_INVALIDATION_CONSUMER_NAME,
605
+ // Count-based dead-lettering would end after a few seconds of Redis downtime.
606
+ errorPolicy: { isTransientError: isAccessInvalidationTransportError },
558
607
  // Shared delivery, same reasoning as SSE broadcast: one
559
608
  // cursor reads every event once and calls sseBroker.publishAccessInvalidation
560
609
  // below, which a Redis-backed broker republishes to every replica's
@@ -565,7 +614,7 @@ export function createAccessInvalidationEventConsumer(sseBroker) {
565
614
  // existed has nothing to invalidate — replaying it would be a no-op at
566
615
  // best and a spurious stream-close at worst.
567
616
  startFrom: "now",
568
- handler: async (event) => {
617
+ handler: withTransportBackoff(async (event) => {
569
618
  if (event.type === SESSION_REVOKED_EVENT_TYPE) {
570
619
  const userId = event.payload["userId"];
571
620
  // skip: malformed session-revoked payload — fail open on this one
@@ -574,7 +623,7 @@ export function createAccessInvalidationEventConsumer(sseBroker) {
574
623
  // every user behind one bad row).
575
624
  if (typeof userId !== "string" || userId.length === 0)
576
625
  return;
577
- await sseBroker.publishAccessInvalidation(userId, readSessionRevokedScope(event.payload));
626
+ await publish(userId, readSessionRevokedScope(event.payload));
578
627
  }
579
628
  if (event.type === PAT_REVOKED_EVENT_TYPE) {
580
629
  const userId = event.payload["userId"];
@@ -582,7 +631,7 @@ export function createAccessInvalidationEventConsumer(sseBroker) {
582
631
  // session-revoked above.
583
632
  if (typeof userId !== "string" || userId.length === 0)
584
633
  return;
585
- await sseBroker.publishAccessInvalidation(userId, readPatRevokedScope(event.payload));
634
+ await publish(userId, readPatRevokedScope(event.payload));
586
635
  }
587
636
  if (event.type === TENANT_MEMBERSHIP_UPDATED_EVENT_TYPE ||
588
637
  event.type === TENANT_MEMBERSHIP_DELETED_EVENT_TYPE) {
@@ -591,8 +640,8 @@ export function createAccessInvalidationEventConsumer(sseBroker) {
591
640
  // reasoning as above.
592
641
  if (userId === undefined)
593
642
  return;
594
- await sseBroker.publishAccessInvalidation(userId);
643
+ await publish(userId);
595
644
  }
596
- },
645
+ }, now),
597
646
  };
598
647
  }
@@ -1,19 +1,24 @@
1
1
  import { maskWriteOnlyFields } from "../engine/field-access.js";
2
2
  import { isLifecycleResult } from "./dispatcher-utils.js";
3
+ // The echoed event carries plaintext entity data; only hooks and projections may read it.
3
4
  function maskLifecycleOrRow(entity, data) {
4
5
  if (typeof data !== "object" || data === null || Array.isArray(data))
5
6
  return data;
6
- if (!isLifecycleResult(data))
7
- return maskWriteOnlyFields(entity, data); // @cast-boundary engine-payload
8
- if (data.kind === "save") {
7
+ if (!isLifecycleResult(data)) {
8
+ return entity ? maskWriteOnlyFields(entity, data) : data; // @cast-boundary engine-payload
9
+ }
10
+ const { event: _event, ...lifecycle } = data;
11
+ if (!entity)
12
+ return lifecycle;
13
+ if (lifecycle.kind === "save") {
9
14
  return {
10
- ...data,
11
- data: maskWriteOnlyFields(entity, data.data),
12
- changes: maskWriteOnlyFields(entity, data.changes),
13
- previous: maskWriteOnlyFields(entity, data.previous),
15
+ ...lifecycle,
16
+ data: maskWriteOnlyFields(entity, lifecycle.data),
17
+ changes: maskWriteOnlyFields(entity, lifecycle.changes),
18
+ previous: maskWriteOnlyFields(entity, lifecycle.previous),
14
19
  };
15
20
  }
16
- return { ...data, data: maskWriteOnlyFields(entity, data.data) };
21
+ return { ...lifecycle, data: maskWriteOnlyFields(entity, lifecycle.data) };
17
22
  }
18
23
  // Copy-on-mask: the original results still feed postSaveBatch hooks, which
19
24
  // need the plaintext. Only what leaves the dispatcher is masked.
@@ -22,8 +27,6 @@ export function maskWriteResultForClient(registry, handlerType, result) {
22
27
  return result;
23
28
  const entityName = registry.getHandlerEntity(handlerType);
24
29
  const entity = entityName ? registry.getEntity(entityName) : undefined;
25
- if (!entity)
26
- return result;
27
30
  const data = maskLifecycleOrRow(entity, result.data);
28
31
  return data === result.data ? result : { ...result, data };
29
32
  }
@@ -0,0 +1,2 @@
1
+ import { type SchemaCliContext } from "./shared.ts";
2
+ export declare function runApplyCommand(ctx: SchemaCliContext): Promise<number>;
@@ -0,0 +1,97 @@
1
+ import { configureBlindIndexKey, configurePiiSubjectKms, resolveKmsWiringAsync, } from "../crypto/index.js";
2
+ import { createDbConnection, runMigrationsFromDir } from "../db/index.js";
3
+ import { createRegistry } from "../engine/registry.js";
4
+ import { createEventsTable } from "../event-store/index.js";
5
+ import { queueRebuildsFromMarkers, reportPendingRebuildRun, runPendingRebuilds, } from "../migrations/index.js";
6
+ import { createEventConsumerStateTable, createProjectionStateTable } from "../pipeline/index.js";
7
+ import { migrationsDirExistsOrReport, readDatabaseUrlOrReport, } from "./shared.js";
8
+ async function ensureFrameworkInfrastructureTables(db) {
9
+ // Framework infrastructure tables (event store + pipeline state) are not captured by
10
+ // `generate`, which handles only entity read tables. Existing databases have them
11
+ // from the legacy-drizzle foundation; without them, a greenfield database (the
12
+ // first app without a cutover) would lack kumiko_events and runProdApp would fail
13
+ // during boot. All three are idempotent (tableExists-gated), so they are no-ops for
14
+ // existing databases.
15
+ await createEventsTable(db);
16
+ await createEventConsumerStateTable(db);
17
+ await createProjectionStateTable(db);
18
+ }
19
+ function reportAppliedMigrations(out, result) {
20
+ out.log("");
21
+ if (result.applied.length === 0) {
22
+ out.log(` ✓ All ${result.skipped.length} migrations already applied.`);
23
+ }
24
+ else {
25
+ out.log(` ✓ Applied ${result.applied.length}:`);
26
+ for (const id of result.applied)
27
+ out.log(` + ${id}`);
28
+ if (result.skipped.length > 0)
29
+ out.log(` (${result.skipped.length} already applied)`);
30
+ }
31
+ out.log("");
32
+ }
33
+ function configureRebuildKms(wiring) {
34
+ if ("kms" in wiring) {
35
+ configurePiiSubjectKms(wiring.kms);
36
+ configureBlindIndexKey(wiring.blindIndexKey);
37
+ }
38
+ }
39
+ async function rebuildQueuedProjections(db, ctx, features, appliedIds) {
40
+ const thisRunTables = await queueRebuildsFromMarkers(db, {
41
+ migrationsDir: ctx.migrationsDir,
42
+ appliedIds,
43
+ });
44
+ const registry = createRegistry(features);
45
+ const rebuildRun = await runPendingRebuilds(db, registry, { thisRunTables });
46
+ reportPendingRebuildRun(rebuildRun, ctx.out.log);
47
+ }
48
+ export async function runApplyCommand(ctx) {
49
+ const { out, options, migrationsDir } = ctx;
50
+ const dbUrl = readDatabaseUrlOrReport(out);
51
+ if (dbUrl === undefined)
52
+ return 1;
53
+ if (!migrationsDirExistsOrReport(ctx))
54
+ return 1;
55
+ const { db, close } = createDbConnection({ url: dbUrl });
56
+ let wiring;
57
+ try {
58
+ const result = await runMigrationsFromDir(db, migrationsDir);
59
+ await ensureFrameworkInfrastructureTables(db);
60
+ reportAppliedMigrations(out, result);
61
+ // Projection-rebuild for tables a freshly applied migration changed
62
+ // (marker NNNN_<name>.rebuild.json from `generate`). Needs the composed
63
+ // feature set → only when the caller passed `features` (the app bin);
64
+ // the dev CLI omits it and applies migrations only. Runs unconditionally
65
+ // (not gated on result.applied.length > 0): a persistent queue
66
+ // (kumiko_pending_rebuilds) survives a failed/crashed rebuild from an
67
+ // earlier apply, so a later apply with zero new migrations still must
68
+ // retry it — otherwise a failed rebuild is silently never retried,
69
+ // since the migration itself is already tracked applied.
70
+ if (options.features) {
71
+ // A rebuild replays applyEntityEvent, which computes bidx columns from
72
+ // configureBlindIndexKey/configurePiiSubjectKms — both boot-injected by
73
+ // run{Prod,Dev}App but never by this standalone CLI (the migrate-db
74
+ // initContainer). Without wiring them here, a rebuild would silently
75
+ // null out bidx columns and, for ciphertext, throw only once it hits
76
+ // the row.
77
+ wiring = await resolveKmsWiringAsync(process.env, {
78
+ logPrefix: "[kumiko schema apply]",
79
+ log: out.log,
80
+ slots: options.kmsSlots,
81
+ });
82
+ configureRebuildKms(wiring);
83
+ await rebuildQueuedProjections(db, ctx, options.features, result.applied);
84
+ }
85
+ return 0;
86
+ }
87
+ catch (e) {
88
+ out.err("");
89
+ out.err(` ✗ ${e instanceof Error ? (e.stack ?? e.message) : String(e)}`);
90
+ out.err("");
91
+ return 1;
92
+ }
93
+ finally {
94
+ await wiring?.close();
95
+ await close();
96
+ }
97
+ }
@@ -0,0 +1,2 @@
1
+ import { type SchemaCliContext } from "./shared.ts";
2
+ export declare function runBaselineCommand(ctx: SchemaCliContext): Promise<number>;
@@ -0,0 +1,33 @@
1
+ import { baselineMigrations, createDbConnection, loadMigrationsFromDir } from "../db/index.js";
2
+ import { describeThrown, migrationsDirExistsOrReport, readDatabaseUrlOrReport, } from "./shared.js";
3
+ export async function runBaselineCommand(ctx) {
4
+ const { out, migrationsDir } = ctx;
5
+ // Adopt an existing DB: mark all checked-in migrations as applied WITHOUT
6
+ // running their SQL (prod tables already exist — cutover from the legacy
7
+ // drizzle system). Afterwards the boot-gate is drift-free.
8
+ const dbUrl = readDatabaseUrlOrReport(out);
9
+ if (dbUrl === undefined)
10
+ return 1;
11
+ if (!migrationsDirExistsOrReport(ctx))
12
+ return 1;
13
+ const { db, close } = createDbConnection({ url: dbUrl, maxConnections: 1 });
14
+ try {
15
+ const result = await baselineMigrations(db, loadMigrationsFromDir(migrationsDir));
16
+ out.log("");
17
+ out.log(` ✓ Marked ${result.marked.length} migration(s) as applied (no SQL run):`);
18
+ for (const id of result.marked)
19
+ out.log(` + ${id}`);
20
+ if (result.alreadyTracked.length > 0) {
21
+ out.log(` (${result.alreadyTracked.length} already tracked)`);
22
+ }
23
+ out.log("");
24
+ return 0;
25
+ }
26
+ catch (e) {
27
+ out.err(` ✗ ${describeThrown(e)}`);
28
+ return 1;
29
+ }
30
+ finally {
31
+ await close();
32
+ }
33
+ }
@@ -0,0 +1,2 @@
1
+ import { type SchemaCliContext } from "./shared.ts";
2
+ export declare function runGenerateCommand(ctx: SchemaCliContext): Promise<number>;
@@ -0,0 +1,90 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { assertValidMigrationName, generateMigration, readMigrationSqlTexts, rebuildTablesFromDiff, writeRebuildMarker, writeSnapshotJson, } from "../db/index.js";
4
+ import { describeThrown, loadPreviousSnapshot, loadSchemaModOrReport, nextSequenceNumber, SNAPSHOT_FILENAME, } from "./shared.js";
5
+ const GENERATE_USAGE = " Usage: schema generate <name>";
6
+ function parseGenerateName(ctx) {
7
+ const { argv, out } = ctx;
8
+ const name = argv[1];
9
+ if (name === "--help" || name === "-h") {
10
+ out.log(GENERATE_USAGE);
11
+ return { kind: "exit", code: 0 };
12
+ }
13
+ if (!name) {
14
+ out.err(GENERATE_USAGE);
15
+ return { kind: "exit", code: 1 };
16
+ }
17
+ // name lands unescaped in `${seq}_${name}.sql` (generateMigration) —
18
+ // shared allowlist blocks flag-like names, path traversal, and
19
+ // ENAMETOOLONG-length names.
20
+ try {
21
+ assertValidMigrationName(name);
22
+ }
23
+ catch (e) {
24
+ out.err(` ${describeThrown(e)}`);
25
+ out.err(GENERATE_USAGE);
26
+ return { kind: "exit", code: 1 };
27
+ }
28
+ // Trailing args (e.g. a typo'd `--dry-run` — there is no such flag) were
29
+ // silently ignored and the migration got written anyway. Reject them
30
+ // before any IO instead of pretending they did something.
31
+ if (argv.length > 2) {
32
+ out.err(` Unrecognized argument(s): ${argv.slice(2).join(" ")}`);
33
+ out.err(GENERATE_USAGE);
34
+ return { kind: "exit", code: 1 };
35
+ }
36
+ return { kind: "name", name };
37
+ }
38
+ function isEmptyMigration(result) {
39
+ return (result.diff.newTables.length === 0 &&
40
+ result.diff.changedTables.length === 0 &&
41
+ result.diff.droppedTables.length === 0 &&
42
+ result.retiredDrops.length === 0);
43
+ }
44
+ function writeMigrationFiles(migrationsDir, snapshotPath, result) {
45
+ if (!existsSync(migrationsDir))
46
+ mkdirSync(migrationsDir, { recursive: true });
47
+ writeFileSync(join(migrationsDir, result.filename), result.sqlContent);
48
+ writeSnapshotJson(snapshotPath, result.snapshot);
49
+ }
50
+ function reportGeneratedMigration(out, result, rebuildTables) {
51
+ out.log("");
52
+ out.log(` ✓ ${result.filename}`);
53
+ out.log(` new tables: ${result.diff.newTables.length}, changed: ${result.diff.changedTables.length}, dropped: ${result.diff.droppedTables.length}, retired framework tables dropped: ${result.retiredDrops.length}`);
54
+ if (rebuildTables.length > 0) {
55
+ out.log(` rebuild-marker: ${result.filename.replace(/\.sql$/, ".rebuild.json")} (${rebuildTables.length} table(s))`);
56
+ }
57
+ out.log("");
58
+ out.log(" Review, hand-edit if needed, then git add + commit. Apply via: schema apply");
59
+ out.log("");
60
+ }
61
+ export async function runGenerateCommand(ctx) {
62
+ const { out, migrationsDir } = ctx;
63
+ const parsed = parseGenerateName(ctx);
64
+ if (parsed.kind === "exit")
65
+ return parsed.code;
66
+ const schemaMod = await loadSchemaModOrReport(ctx);
67
+ if (schemaMod === undefined)
68
+ return 1;
69
+ const snapshotPath = join(migrationsDir, SNAPSHOT_FILENAME);
70
+ const prevSnapshot = loadPreviousSnapshot(snapshotPath);
71
+ const result = generateMigration({
72
+ metas: schemaMod.entityMetas,
73
+ prevSnapshot,
74
+ name: parsed.name,
75
+ sequenceNumber: nextSequenceNumber(migrationsDir),
76
+ priorMigrationsSql: readMigrationSqlTexts(migrationsDir),
77
+ });
78
+ if (isEmptyMigration(result)) {
79
+ out.log(" No schema changes detected — no new migration file written.");
80
+ return 0;
81
+ }
82
+ writeMigrationFiles(migrationsDir, snapshotPath, result);
83
+ // Rebuild markers are written only for incremental migrations — the initial
84
+ // migration (prevSnapshot===null) only creates tables, so there are no historical
85
+ // events to replay.
86
+ const rebuildTables = prevSnapshot === null ? [] : rebuildTablesFromDiff(result.diff);
87
+ writeRebuildMarker(migrationsDir, result.filename, rebuildTables);
88
+ reportGeneratedMigration(out, result, rebuildTables);
89
+ return 0;
90
+ }
@@ -0,0 +1,39 @@
1
+ import { type renderTablesDdl } from "../db/index.ts";
2
+ import type { FeatureDefinition } from "../engine/types/feature.ts";
3
+ export type SchemaCliOut = {
4
+ readonly log: (line: string) => void;
5
+ readonly err: (line: string) => void;
6
+ };
7
+ export type RunSchemaCliOptions = {
8
+ readonly features?: undefined;
9
+ readonly kmsSlots?: undefined;
10
+ } | {
11
+ /** Composed app features. When given, `apply` rebuilds the projections whose
12
+ * tables a freshly applied migration changed (via its `.rebuild.json`
13
+ * marker). Omitted (dev `kumiko schema`) → no rebuild, migrations only. */
14
+ readonly features: readonly FeatureDefinition[];
15
+ /** Schema-declared Key Manager slots, `kmsSlotsOf(composedEnv.schema)`, or
16
+ * `[]` when the app wires none. This CLI parses no env schema, so the
17
+ * rebuild's KMS wiring cannot derive them itself. */
18
+ readonly kmsSlots: readonly string[];
19
+ };
20
+ export type SchemaCliContext = {
21
+ readonly argv: readonly string[];
22
+ readonly out: SchemaCliOut;
23
+ readonly options: RunSchemaCliOptions;
24
+ readonly schemaFile: string;
25
+ readonly migrationsDir: string;
26
+ };
27
+ export type SchemaSubcommandHandler = (ctx: SchemaCliContext) => Promise<number>;
28
+ export declare const SNAPSHOT_FILENAME = ".snapshot.json";
29
+ export type SchemaMod = {
30
+ readonly entityMetas: Parameters<typeof renderTablesDdl>[0];
31
+ readonly features?: unknown;
32
+ readonly validateBootOptions?: unknown;
33
+ };
34
+ export declare function describeThrown(e: unknown): string;
35
+ export declare function nextSequenceNumber(migrationsDir: string): number;
36
+ export declare function loadPreviousSnapshot(snapshotPath: string): import("../db/migrate-generator.ts").Snapshot | null;
37
+ export declare function loadSchemaModOrReport(ctx: SchemaCliContext): Promise<SchemaMod | undefined>;
38
+ export declare function readDatabaseUrlOrReport(out: SchemaCliOut): string | undefined;
39
+ export declare function migrationsDirExistsOrReport(ctx: SchemaCliContext): boolean;
@@ -0,0 +1,95 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { existsSync, readdirSync } from "node:fs";
10
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
11
+ import { assertUniqueTableNames, loadSnapshotJson } from "../db/index.js";
12
+ import { isEntityTableMeta } from "../db/table-introspection.js";
13
+ export const SNAPSHOT_FILENAME = ".snapshot.json";
14
+ export function describeThrown(e) {
15
+ return e instanceof Error ? e.message : String(e);
16
+ }
17
+ function tableNameHint(entry) {
18
+ if (isPlainObject(entry) && typeof entry["tableName"] === "string")
19
+ return entry["tableName"];
20
+ return "unknown";
21
+ }
22
+ // Single load-path for kumiko/schema.ts (512/3) — `generate` and `validate`
23
+ // both need ENTITY_METAS (and validate additionally wants FEATURES); this is
24
+ // the one place that enforces "ENTITY_METAS must be an array" so the two
25
+ // commands can't drift on the check or its wording. Throws — callers that
26
+ // want a graceful exit code (validate) catch it themselves.
27
+ async function loadSchemaModFromApp(schemaFile) {
28
+ // bun imports TS directly — no spawn needed.
29
+ const mod = await import(__rewriteRelativeImportExtension(schemaFile));
30
+ const entityMetas = mod["ENTITY_METAS"];
31
+ if (!Array.isArray(entityMetas)) {
32
+ throw new Error(`Schema file ${schemaFile} must declare \`export const ENTITY_METAS: EntityTableMeta[]\`.`);
33
+ }
34
+ const validMetas = [];
35
+ for (const [index, entry] of entityMetas.entries()) {
36
+ if (!isEntityTableMeta(entry)) {
37
+ throw new Error(`Schema file ${schemaFile}: ENTITY_METAS[${index}] is not an EntityTableMeta (table: ${tableNameHint(entry)}).`);
38
+ }
39
+ validMetas.push(entry);
40
+ }
41
+ assertUniqueTableNames(validMetas);
42
+ return {
43
+ entityMetas: validMetas,
44
+ features: mod["FEATURES"],
45
+ validateBootOptions: mod["VALIDATE_BOOT_OPTIONS"],
46
+ };
47
+ }
48
+ export function nextSequenceNumber(migrationsDir) {
49
+ if (!existsSync(migrationsDir))
50
+ return 1;
51
+ const files = readdirSync(migrationsDir).filter((f) => f.endsWith(".sql"));
52
+ let max = 0;
53
+ for (const f of files) {
54
+ const m = f.match(/^(\d+)_/);
55
+ if (m) {
56
+ const n = Number(m[1]);
57
+ if (n > max)
58
+ max = n;
59
+ }
60
+ }
61
+ return max + 1;
62
+ }
63
+ export function loadPreviousSnapshot(snapshotPath) {
64
+ return existsSync(snapshotPath) ? loadSnapshotJson(snapshotPath) : null;
65
+ }
66
+ export async function loadSchemaModOrReport(ctx) {
67
+ const { out, schemaFile } = ctx;
68
+ if (!existsSync(schemaFile)) {
69
+ out.err(` ${schemaFile} is missing.`);
70
+ out.err(" App convention: kumiko/schema.ts with");
71
+ out.err(" export const ENTITY_METAS: EntityTableMeta[] = [...]");
72
+ return undefined;
73
+ }
74
+ try {
75
+ return await loadSchemaModFromApp(schemaFile);
76
+ }
77
+ catch (e) {
78
+ out.err(` ${describeThrown(e)}`);
79
+ return undefined;
80
+ }
81
+ }
82
+ export function readDatabaseUrlOrReport(out) {
83
+ const dbUrl = process.env["DATABASE_URL"];
84
+ if (!dbUrl) {
85
+ out.err(" DATABASE_URL not set.");
86
+ return undefined;
87
+ }
88
+ return dbUrl;
89
+ }
90
+ export function migrationsDirExistsOrReport(ctx) {
91
+ if (existsSync(ctx.migrationsDir))
92
+ return true;
93
+ ctx.out.err(` ${ctx.migrationsDir} is missing — run schema generate <name> first.`);
94
+ return false;
95
+ }
@@ -0,0 +1,2 @@
1
+ import { type SchemaCliContext } from "./shared.ts";
2
+ export declare function runStatusCommand(ctx: SchemaCliContext): Promise<number>;
@@ -0,0 +1,73 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { createDbConnection, fetchAppliedMigrations, findCommentedDropTables, isRetiredFrameworkTable, loadMigrationsFromDir, readMigrationSqlTexts, tableExists, } from "../db/index.js";
4
+ import { describeThrown, loadPreviousSnapshot, readDatabaseUrlOrReport, SNAPSHOT_FILENAME, } from "./shared.js";
5
+ // A table that a migration only drops as a commented DESTRUCTIVE marker and
6
+ // that the snapshot no longer has is gone on paper but still in the live DB.
7
+ async function reportLeftoverDroppedTables(db, migrationsDir, out) {
8
+ const snapshot = loadPreviousSnapshot(join(migrationsDir, SNAPSHOT_FILENAME));
9
+ const leftovers = [];
10
+ for (const tableName of findCommentedDropTables(readMigrationSqlTexts(migrationsDir), snapshot)) {
11
+ if (await tableExists(db, tableName))
12
+ leftovers.push(tableName);
13
+ }
14
+ // skip: nothing left over, nothing to report
15
+ if (leftovers.length === 0)
16
+ return;
17
+ out.log(" Tables dropped only as a commented DESTRUCTIVE marker but still in the database:");
18
+ for (const tableName of leftovers) {
19
+ out.log(isRetiredFrameworkTable(tableName)
20
+ ? ` ! ${tableName} — retired framework table: the next \`schema generate <name>\` creates the drop`
21
+ : ` ! ${tableName} — app table: after a backup, write a new migration with the drop`);
22
+ }
23
+ out.log("");
24
+ }
25
+ async function fetchAppliedMigrationIds(db) {
26
+ // On a fresh database where `kumiko schema apply` has never run, the tracking table is
27
+ // absent, which means "nothing applied". Connection and permission errors must not
28
+ // be swallowed (false pending results would hide the problem) — tableExists checks
29
+ // only for existence, while all other errors propagate.
30
+ const trackingExists = await tableExists(db, "_kumiko_migrations");
31
+ return trackingExists
32
+ ? new Set((await fetchAppliedMigrations(db)).map((a) => a.id))
33
+ : new Set();
34
+ }
35
+ export async function runStatusCommand(ctx) {
36
+ const { out, migrationsDir } = ctx;
37
+ const dbUrl = readDatabaseUrlOrReport(out);
38
+ if (dbUrl === undefined)
39
+ return 1;
40
+ if (!existsSync(migrationsDir)) {
41
+ out.log(" No kumiko/migrations/ — the app is still on the legacy drizzle path.");
42
+ return 0;
43
+ }
44
+ const local = loadMigrationsFromDir(migrationsDir);
45
+ const { db, close } = createDbConnection({ url: dbUrl, maxConnections: 1 });
46
+ try {
47
+ const applied = await fetchAppliedMigrationIds(db);
48
+ out.log("");
49
+ out.log(` ${local.length} migrations in ${migrationsDir}:`);
50
+ for (const m of local)
51
+ out.log(` ${applied.has(m.id) ? "✓" : " "} ${m.id}`);
52
+ const pending = local.filter((m) => !applied.has(m.id)).length;
53
+ out.log("");
54
+ out.log(` ${applied.size} applied, ${pending} pending.`);
55
+ out.log("");
56
+ await reportLeftoverDroppedTables(db, migrationsDir, out);
57
+ return pending === 0 ? 0 : 1;
58
+ }
59
+ catch (e) {
60
+ // Message only, never dbUrl: it can carry the password.
61
+ out.err(` ✗ schema status failed: ${describeThrown(e)}`);
62
+ out.err(' Check that DATABASE_URL is reachable and the database exists (the scaffold\'s docker-compose creates "app").');
63
+ return 1;
64
+ }
65
+ finally {
66
+ try {
67
+ await close();
68
+ }
69
+ catch {
70
+ // The connection already failed and was reported above; a failing close must not throw again.
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,5 @@
1
+ import type { ValidateBootOptions } from "../engine/boot-validator.ts";
2
+ import type { FeatureDefinition } from "../engine/types/feature.ts";
3
+ export declare function withFileStoragePresence(env: Readonly<Record<string, string | undefined>>): Record<string, string | undefined>;
4
+ export declare function parseValidateBootOptions(raw: unknown): ValidateBootOptions;
5
+ export declare function featureDefinitionsOf(value: unknown): readonly FeatureDefinition[] | undefined;