@cosmicdrift/kumiko-framework 0.386.0 → 0.388.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 (468) hide show
  1. package/AGENTS.md +6 -1
  2. package/LICENSE +21 -57
  3. package/README.md +2 -2
  4. package/agents/examples/anonymous-access.md +1 -1
  5. package/agents/examples/apex-landing.md +204 -0
  6. package/agents/examples/api-worker-split.md +64 -0
  7. package/agents/examples/app-config.md +14 -0
  8. package/agents/examples/auth-mfa.md +115 -0
  9. package/agents/examples/env-composition.md +28 -1
  10. package/agents/examples/legal-pages.md +1 -1
  11. package/agents/examples/prod-boot-options.md +16 -0
  12. package/agents/examples/rate-limiting.md +48 -0
  13. package/agents/examples/record-detail-layout.md +692 -0
  14. package/agents/examples/seed-migration.md +212 -0
  15. package/agents/examples/wizard-form.md +941 -0
  16. package/agents/examples/writeform-section.md +548 -0
  17. package/agents/features.md +19 -2
  18. package/agents/widgets.md +40 -39
  19. package/dist/api/api-constants.js +2 -2
  20. package/dist/api/auth-middleware.js +7 -7
  21. package/dist/api/auth-rate-limiters.js +7 -3
  22. package/dist/api/auth-routes-mfa.js +1 -1
  23. package/dist/api/auth-routes-session.js +1 -1
  24. package/dist/api/auth-routes-shared.js +7 -3
  25. package/dist/api/auth-routes-signup.js +1 -1
  26. package/dist/api/client-error-route.js +8 -2
  27. package/dist/api/client-ip.js +3 -1
  28. package/dist/api/extra-route.js +1 -1
  29. package/dist/api/index.d.ts +3 -1
  30. package/dist/api/index.js +3 -1
  31. package/dist/api/pii-leak-guard.js +4 -2
  32. package/dist/api/readiness.js +5 -5
  33. package/dist/api/redis-sse-broker.js +8 -6
  34. package/dist/api/request-context.d.ts +1 -1
  35. package/dist/api/request-context.js +1 -1
  36. package/dist/api/routes.js +2 -2
  37. package/dist/api/server-boot-checks.d.ts +10 -0
  38. package/dist/api/server-boot-checks.js +157 -0
  39. package/dist/api/server-event-dispatcher.d.ts +26 -0
  40. package/dist/api/server-event-dispatcher.js +208 -0
  41. package/dist/api/server-extra-routes.d.ts +11 -0
  42. package/dist/api/server-extra-routes.js +203 -0
  43. package/dist/api/server-middleware.d.ts +20 -0
  44. package/dist/api/server-middleware.js +211 -0
  45. package/dist/api/server-routes.d.ts +19 -0
  46. package/dist/api/server-routes.js +293 -0
  47. package/dist/api/server-websocket-routes.d.ts +4 -0
  48. package/dist/api/server-websocket-routes.js +108 -0
  49. package/dist/api/server-wiring.d.ts +32 -0
  50. package/dist/api/server-wiring.js +236 -0
  51. package/dist/api/server.d.ts +10 -15
  52. package/dist/api/server.js +48 -1250
  53. package/dist/api/session-cookies.js +4 -4
  54. package/dist/api/sse-broker.js +1 -1
  55. package/dist/api/sse-route.d.ts +13 -13
  56. package/dist/api/sse-route.js +31 -27
  57. package/dist/auth/index.js +4 -4
  58. package/dist/auth/roles.d.ts +4 -4
  59. package/dist/auth/roles.js +24 -24
  60. package/dist/compliance/duration-spec.js +18 -20
  61. package/dist/compliance/index.js +2 -3
  62. package/dist/compliance/override-schema.d.ts +4 -4
  63. package/dist/compliance/override-schema.js +19 -22
  64. package/dist/compliance/profiles.d.ts +26 -29
  65. package/dist/compliance/profiles.js +62 -69
  66. package/dist/compliance/sub-processors.d.ts +28 -30
  67. package/dist/compliance/sub-processors.js +17 -21
  68. package/dist/consumer-cli.js +4 -4
  69. package/dist/crypto/blind-index.js +18 -17
  70. package/dist/crypto/ciphertext-pattern.js +1 -1
  71. package/dist/crypto/event-pii.js +3 -3
  72. package/dist/crypto/kek-source.d.ts +1 -1
  73. package/dist/crypto/kek-source.js +3 -2
  74. package/dist/crypto/kms-adapter.d.ts +1 -1
  75. package/dist/crypto/kms-adapter.js +4 -6
  76. package/dist/crypto/kms-wiring.d.ts +1 -2
  77. package/dist/crypto/kms-wiring.js +2 -2
  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.js +2 -6
  81. package/dist/crypto/pii-field-encryption.d.ts +5 -1
  82. package/dist/crypto/pii-field-encryption.js +7 -5
  83. package/dist/crypto/subject-resolver.d.ts +1 -1
  84. package/dist/crypto/subject-resolver.js +2 -2
  85. package/dist/db/aggregate-sql.d.ts +21 -0
  86. package/dist/db/aggregate-sql.js +155 -0
  87. package/dist/db/apply-entity-event.d.ts +6 -6
  88. package/dist/db/apply-entity-event.js +55 -55
  89. package/dist/db/blind-index-cleanup.js +7 -7
  90. package/dist/db/closed-connection.d.ts +9 -0
  91. package/dist/db/closed-connection.js +95 -0
  92. package/dist/db/collect-table-metas.js +10 -11
  93. package/dist/db/compound-types.d.ts +1 -1
  94. package/dist/db/compound-types.js +12 -12
  95. package/dist/db/cursor.js +3 -4
  96. package/dist/db/dialect.d.ts +2 -2
  97. package/dist/db/dialect.js +3 -3
  98. package/dist/db/distinct-on.d.ts +1 -1
  99. package/dist/db/driver-row-coercion.d.ts +2 -0
  100. package/dist/db/driver-row-coercion.js +186 -0
  101. package/dist/db/driver-value-preparation.d.ts +16 -0
  102. package/dist/db/driver-value-preparation.js +99 -0
  103. package/dist/db/eagerload.d.ts +3 -3
  104. package/dist/db/eagerload.js +21 -12
  105. package/dist/db/entity-field-encryption.d.ts +1 -0
  106. package/dist/db/entity-field-encryption.js +1 -1
  107. package/dist/db/entity-table-meta.d.ts +2 -2
  108. package/dist/db/entity-table-meta.js +105 -112
  109. package/dist/db/event-store-executor-context.d.ts +3 -2
  110. package/dist/db/event-store-executor-context.js +13 -13
  111. package/dist/db/event-store-executor-read-aggregate.d.ts +8 -0
  112. package/dist/db/event-store-executor-read-aggregate.js +112 -0
  113. package/dist/db/event-store-executor-read-filter.d.ts +31 -0
  114. package/dist/db/event-store-executor-read-filter.js +203 -0
  115. package/dist/db/event-store-executor-read-paging.d.ts +14 -0
  116. package/dist/db/event-store-executor-read-paging.js +99 -0
  117. package/dist/db/event-store-executor-read-rows.d.ts +6 -0
  118. package/dist/db/event-store-executor-read-rows.js +32 -0
  119. package/dist/db/event-store-executor-read-search.d.ts +28 -0
  120. package/dist/db/event-store-executor-read-search.js +221 -0
  121. package/dist/db/event-store-executor-read-sort.d.ts +41 -0
  122. package/dist/db/event-store-executor-read-sort.js +130 -0
  123. package/dist/db/event-store-executor-read-where.d.ts +24 -0
  124. package/dist/db/event-store-executor-read-where.js +81 -0
  125. package/dist/db/event-store-executor-read.d.ts +2 -9
  126. package/dist/db/event-store-executor-read.js +50 -704
  127. package/dist/db/event-store-executor-write-steps.d.ts +20 -0
  128. package/dist/db/event-store-executor-write-steps.js +100 -0
  129. package/dist/db/event-store-executor-write.js +401 -510
  130. package/dist/db/index.d.ts +6 -2
  131. package/dist/db/index.js +5 -1
  132. package/dist/db/list-aggregate-labels.d.ts +14 -0
  133. package/dist/db/list-aggregate-labels.js +26 -0
  134. package/dist/db/located-timestamp.d.ts +11 -11
  135. package/dist/db/located-timestamp.js +29 -30
  136. package/dist/db/migrate-generator.d.ts +1 -1
  137. package/dist/db/migrate-generator.js +97 -105
  138. package/dist/db/migrate-runner.js +29 -26
  139. package/dist/db/money.d.ts +6 -6
  140. package/dist/db/money.js +29 -27
  141. package/dist/db/queries/backfill-pii.js +5 -5
  142. package/dist/db/queries/ddl.js +1 -1
  143. package/dist/db/queries/es-ops.d.ts +1 -1
  144. package/dist/db/queries/es-ops.js +1 -1
  145. package/dist/db/queries/event-consumer.js +3 -3
  146. package/dist/db/queries/event-store.js +4 -2
  147. package/dist/db/queries/projection-rebuild.js +3 -3
  148. package/dist/db/queries/raw-sql.d.ts +2 -2
  149. package/dist/db/queries/raw-sql.js +2 -2
  150. package/dist/db/queries/shadow-swap.js +19 -19
  151. package/dist/db/query.d.ts +3 -16
  152. package/dist/db/query.js +8 -761
  153. package/dist/db/rebuild-marker.js +20 -21
  154. package/dist/db/reference-data.js +6 -1
  155. package/dist/db/replay-migration-sql.js +2 -2
  156. package/dist/db/schema-inspection.js +4 -5
  157. package/dist/db/table-builder.d.ts +1 -2
  158. package/dist/db/table-builder.js +137 -190
  159. package/dist/db/table-info.d.ts +10 -0
  160. package/dist/db/table-info.js +45 -0
  161. package/dist/db/temporal-value-guards.d.ts +6 -0
  162. package/dist/db/temporal-value-guards.js +15 -0
  163. package/dist/db/tenant-db-runner.d.ts +1 -0
  164. package/dist/db/tenant-db-runner.js +3 -0
  165. package/dist/db/tenant-db.d.ts +2 -2
  166. package/dist/db/tenant-db.js +48 -11
  167. package/dist/db/where-clause.d.ts +6 -0
  168. package/dist/db/where-clause.js +236 -0
  169. package/dist/derivatives/delete-stored-file.js +6 -2
  170. package/dist/derivatives/derivatives-context.js +1 -1
  171. package/dist/engine/boot-validator/access-roles.js +4 -3
  172. package/dist/engine/boot-validator/api-ext.js +14 -14
  173. package/dist/engine/boot-validator/boot-check.js +1 -1
  174. package/dist/engine/boot-validator/config-deps.js +7 -6
  175. package/dist/engine/boot-validator/detail-screens.js +4 -4
  176. package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
  177. package/dist/engine/boot-validator/entity-handler.js +77 -61
  178. package/dist/engine/boot-validator/form-derivations.js +35 -1
  179. package/dist/engine/boot-validator/gdpr-storage.js +4 -4
  180. package/dist/engine/boot-validator/i18n-keys.js +6 -4
  181. package/dist/engine/boot-validator/index.d.ts +2 -2
  182. package/dist/engine/boot-validator/index.js +22 -20
  183. package/dist/engine/boot-validator/nav.js +5 -5
  184. package/dist/engine/boot-validator/ownership.js +12 -9
  185. package/dist/engine/boot-validator/parent-ref.js +1 -1
  186. package/dist/engine/boot-validator/pii-retention.js +258 -187
  187. package/dist/engine/boot-validator/projection-list-screens.js +7 -7
  188. package/dist/engine/boot-validator/query-refs.js +1 -1
  189. package/dist/engine/boot-validator/screens-actions.d.ts +1 -0
  190. package/dist/engine/boot-validator/screens-actions.js +23 -17
  191. package/dist/engine/boot-validator/screens-dashboard.js +2 -5
  192. package/dist/engine/boot-validator/screens-forms.js +12 -17
  193. package/dist/engine/boot-validator/screens-kind-config-edit.d.ts +3 -0
  194. package/dist/engine/boot-validator/screens-kind-config-edit.js +88 -0
  195. package/dist/engine/boot-validator/screens-kind-entity-edit.d.ts +3 -0
  196. package/dist/engine/boot-validator/screens-kind-entity-edit.js +55 -0
  197. package/dist/engine/boot-validator/screens-kind-entity-list.d.ts +3 -0
  198. package/dist/engine/boot-validator/screens-kind-entity-list.js +228 -0
  199. package/dist/engine/boot-validator/screens-kind-projection-detail.d.ts +3 -0
  200. package/dist/engine/boot-validator/screens-kind-projection-detail.js +235 -0
  201. package/dist/engine/boot-validator/screens-kind-projection-list.d.ts +3 -0
  202. package/dist/engine/boot-validator/screens-kind-projection-list.js +52 -0
  203. package/dist/engine/boot-validator/screens-kind-secret-mint.d.ts +3 -0
  204. package/dist/engine/boot-validator/screens-kind-secret-mint.js +28 -0
  205. package/dist/engine/boot-validator/screens-kind-shared.d.ts +37 -0
  206. package/dist/engine/boot-validator/screens-kind-shared.js +142 -0
  207. package/dist/engine/boot-validator/screens-lists.d.ts +2 -1
  208. package/dist/engine/boot-validator/screens-lists.js +26 -24
  209. package/dist/engine/boot-validator/screens.d.ts +0 -2
  210. package/dist/engine/boot-validator/screens.js +102 -869
  211. package/dist/engine/boot-validator/security-baseline.js +3 -2
  212. package/dist/engine/boot-validator/transfer-graph.js +2 -2
  213. package/dist/engine/boot-validator/workspaces.js +3 -3
  214. package/dist/engine/bridge-stub.js +3 -4
  215. package/dist/engine/build-app-schema.js +130 -170
  216. package/dist/engine/build-config-feature-schema.js +50 -50
  217. package/dist/engine/build-target.js +20 -23
  218. package/dist/engine/codemod/pipeline-codemod.js +20 -13
  219. package/dist/engine/config-helpers.js +4 -4
  220. package/dist/engine/constants.js +2 -2
  221. package/dist/engine/create-app.js +2 -2
  222. package/dist/engine/define-feature.js +21 -13
  223. package/dist/engine/define-handler.js +13 -11
  224. package/dist/engine/define-step.js +1 -1
  225. package/dist/engine/define-workflow.d.ts +5 -5
  226. package/dist/engine/define-workflow.js +6 -6
  227. package/dist/engine/entity-handlers.d.ts +13 -0
  228. package/dist/engine/entity-handlers.js +22 -34
  229. package/dist/engine/entity-list-runtime-options.d.ts +8 -0
  230. package/dist/engine/entity-list-runtime-options.js +18 -0
  231. package/dist/engine/event-def.js +2 -2
  232. package/dist/engine/extension-names.d.ts +16 -17
  233. package/dist/engine/extension-names.js +37 -38
  234. package/dist/engine/extensions/escape-hatch-usage.js +1 -1
  235. package/dist/engine/extensions/tenant-data.js +2 -2
  236. package/dist/engine/extensions/user-data.d.ts +23 -24
  237. package/dist/engine/factories.d.ts +14 -13
  238. package/dist/engine/factories.js +38 -37
  239. package/dist/engine/feature-ast/entity-field-types.js +1 -1
  240. package/dist/engine/feature-ast/extractors/handlers.js +2 -1
  241. package/dist/engine/feature-ast/extractors/hooks.js +1 -1
  242. package/dist/engine/feature-ast/extractors/round2.js +25 -6
  243. package/dist/engine/feature-ast/extractors/shared.d.ts +2 -3
  244. package/dist/engine/feature-ast/extractors/shared.js +2 -3
  245. package/dist/engine/feature-ast/parse.js +13 -13
  246. package/dist/engine/feature-ast/patch.js +4 -182
  247. package/dist/engine/feature-ast/pattern-change-schema.d.ts +3 -0
  248. package/dist/engine/feature-ast/pattern-change-schema.js +2 -1
  249. package/dist/engine/feature-ast/pattern-id-matchers.d.ts +7 -0
  250. package/dist/engine/feature-ast/pattern-id-matchers.js +210 -0
  251. package/dist/engine/feature-ast/patterns.d.ts +11 -2
  252. package/dist/engine/feature-ast/patterns.js +43 -51
  253. package/dist/engine/feature-ast/render.js +50 -89
  254. package/dist/engine/feature-config-events-jobs.js +15 -12
  255. package/dist/engine/feature-entity-handlers.js +12 -5
  256. package/dist/engine/feature-manifest.d.ts +9 -9
  257. package/dist/engine/feature-manifest.js +6 -7
  258. package/dist/engine/feature-ui-extensions.d.ts +3 -3
  259. package/dist/engine/feature-ui-extensions.js +30 -22
  260. package/dist/engine/field-access.js +5 -6
  261. package/dist/engine/form-derivations.js +18 -7
  262. package/dist/engine/handler-field-references.js +1 -1
  263. package/dist/engine/index.d.ts +1 -0
  264. package/dist/engine/index.js +4 -4
  265. package/dist/engine/object-form.d.ts +1 -0
  266. package/dist/engine/object-form.js +14 -4
  267. package/dist/engine/ownership.js +3 -3
  268. package/dist/engine/parse-ref-target.js +5 -7
  269. package/dist/engine/pattern-library/index.js +2 -2
  270. package/dist/engine/pattern-library/library.js +2 -2
  271. package/dist/engine/pattern-library/types.js +3 -3
  272. package/dist/engine/pipeline.js +2 -2
  273. package/dist/engine/project-app-schema-for-roles.d.ts +2 -0
  274. package/dist/engine/project-app-schema-for-roles.js +7 -5
  275. package/dist/engine/projection-helpers.js +9 -9
  276. package/dist/engine/qualified-name.js +2 -2
  277. package/dist/engine/registry-facade.js +1 -1
  278. package/dist/engine/registry-ingest.js +13 -13
  279. package/dist/engine/registry-state.js +17 -19
  280. package/dist/engine/registry-validate.js +12 -12
  281. package/dist/engine/run-in.js +2 -2
  282. package/dist/engine/run-pipeline.js +7 -0
  283. package/dist/engine/schema-builder.js +251 -233
  284. package/dist/engine/screen-filter-ops.js +17 -20
  285. package/dist/engine/screen-helpers.js +4 -3
  286. package/dist/engine/screen-kind.d.ts +7 -0
  287. package/dist/engine/screen-kind.js +1 -0
  288. package/dist/engine/state-machine.d.ts +12 -12
  289. package/dist/engine/state-machine.js +7 -7
  290. package/dist/engine/steps/_resolver-utils.js +4 -3
  291. package/dist/engine/steps/for-each.js +4 -4
  292. package/dist/engine/steps/unsafe-projection-delete.js +1 -1
  293. package/dist/engine/steps/unsafe-projection-upsert.js +4 -4
  294. package/dist/engine/steps/wait-for-event.js +4 -4
  295. package/dist/engine/steps/workflow-system-events.js +3 -3
  296. package/dist/engine/strip-disabled-features-from-app-schema.js +51 -14
  297. package/dist/engine/tier-resolver-extension.d.ts +29 -27
  298. package/dist/engine/tier-resolver-extension.js +26 -26
  299. package/dist/engine/transfer-adjacency.js +2 -2
  300. package/dist/engine/validate-projection-allowlist.js +9 -28
  301. package/dist/engine/walk-step-instances.d.ts +2 -0
  302. package/dist/engine/walk-step-instances.js +38 -0
  303. package/dist/engine/where-rule-lint.js +1 -1
  304. package/dist/engine/write-handler-qn-extract.js +1 -1
  305. package/dist/entrypoint/index.d.ts +4 -1
  306. package/dist/entrypoint/index.js +10 -4
  307. package/dist/env/index.d.ts +7 -6
  308. package/dist/env/index.js +10 -9
  309. package/dist/errors/docs-url.d.ts +1 -1
  310. package/dist/errors/docs-url.js +22 -2
  311. package/dist/errors/transition-details.js +7 -7
  312. package/dist/errors/write-error-info.d.ts +6 -6
  313. package/dist/errors/write-error-info.js +6 -6
  314. package/dist/es-ops/context.d.ts +9 -9
  315. package/dist/es-ops/context.js +29 -29
  316. package/dist/es-ops/index.js +7 -9
  317. package/dist/es-ops/operations-schema.js +4 -10
  318. package/dist/es-ops/runner.d.ts +11 -11
  319. package/dist/es-ops/runner.js +49 -53
  320. package/dist/es-ops/types.d.ts +55 -55
  321. package/dist/es-ops/types.js +11 -11
  322. package/dist/event-store/admin-api.js +7 -8
  323. package/dist/event-store/errors.js +1 -1
  324. package/dist/event-store/event-store.js +12 -6
  325. package/dist/event-store/rebuild-dead-letter.js +1 -1
  326. package/dist/files/bind-pending-file-refs.js +3 -3
  327. package/dist/files/create-file-ref-from-bytes.js +4 -1
  328. package/dist/files/feature.js +9 -10
  329. package/dist/files/file-handle.js +7 -9
  330. package/dist/files/file-ref-entity.js +15 -15
  331. package/dist/files/file-ref-table.js +7 -8
  332. package/dist/files/file-routes.d.ts +7 -0
  333. package/dist/files/file-routes.js +22 -13
  334. package/dist/files/in-memory-provider.js +8 -9
  335. package/dist/files/local-provider.js +18 -25
  336. package/dist/files/provider-resolver.d.ts +7 -7
  337. package/dist/files/resolve-content-type.js +43 -0
  338. package/dist/files/storage-tracking.js +2 -2
  339. package/dist/files/upload-ledger.js +7 -0
  340. package/dist/files/zip-stream.d.ts +12 -19
  341. package/dist/files/zip-stream.js +44 -63
  342. package/dist/i18n/required-surface-keys.d.ts +1 -1
  343. package/dist/i18n/required-surface-keys.js +188 -212
  344. package/dist/internal/stack-support.d.ts +2 -1
  345. package/dist/internal/stack-support.js +1 -1
  346. package/dist/jobs/job-runner-context.d.ts +32 -0
  347. package/dist/jobs/job-runner-context.js +305 -0
  348. package/dist/jobs/job-runner-execution.d.ts +7 -0
  349. package/dist/jobs/job-runner-execution.js +160 -0
  350. package/dist/jobs/job-runner-gates.d.ts +19 -0
  351. package/dist/jobs/job-runner-gates.js +198 -0
  352. package/dist/jobs/job-runner-helpers.d.ts +17 -3
  353. package/dist/jobs/job-runner-helpers.js +69 -4
  354. package/dist/jobs/job-runner-schedulers.d.ts +1 -1
  355. package/dist/jobs/job-runner-schedulers.js +3 -4
  356. package/dist/jobs/job-runner-scope.d.ts +44 -0
  357. package/dist/jobs/job-runner-scope.js +102 -0
  358. package/dist/jobs/job-runner-types.d.ts +12 -0
  359. package/dist/jobs/job-runner.d.ts +0 -1
  360. package/dist/jobs/job-runner.js +57 -669
  361. package/dist/lifecycle/lifecycle.js +3 -5
  362. package/dist/lifecycle/signal-handlers.js +5 -4
  363. package/dist/logging/utils.d.ts +2 -1
  364. package/dist/logging/utils.js +38 -10
  365. package/dist/migrations/index.js +1 -1
  366. package/dist/migrations/kumiko-drift.js +14 -13
  367. package/dist/migrations/pending-rebuilds.d.ts +21 -20
  368. package/dist/migrations/pending-rebuilds.js +27 -27
  369. package/dist/migrations/projection-table-index.d.ts +3 -3
  370. package/dist/migrations/projection-table-index.js +8 -8
  371. package/dist/observability/escape-hatch-report.d.ts +3 -0
  372. package/dist/observability/escape-hatch-report.js +12 -1
  373. package/dist/observability/metric-validator.js +1 -1
  374. package/dist/observability/metrics-handle.js +3 -3
  375. package/dist/pipeline/append-event-core.js +11 -2
  376. package/dist/pipeline/cascade-handler.d.ts +1 -1
  377. package/dist/pipeline/cascade-handler.js +165 -138
  378. package/dist/pipeline/dispatch-batch.js +2 -2
  379. package/dist/pipeline/dispatch-query.js +20 -13
  380. package/dist/pipeline/dispatch-shared.d.ts +14 -3
  381. package/dist/pipeline/dispatch-shared.js +67 -721
  382. package/dist/pipeline/dispatch-stream.js +1 -1
  383. package/dist/pipeline/dispatch-write.js +297 -190
  384. package/dist/pipeline/dispatcher-utils.d.ts +1 -1
  385. package/dist/pipeline/dispatcher-utils.js +1 -1
  386. package/dist/pipeline/event-consumer-state.js +2 -2
  387. package/dist/pipeline/event-dispatcher-admin.js +2 -1
  388. package/dist/pipeline/event-dispatcher-delivery.js +4 -6
  389. package/dist/pipeline/event-dispatcher.js +17 -28
  390. package/dist/pipeline/handler-context-auth.d.ts +9 -0
  391. package/dist/pipeline/handler-context-auth.js +54 -0
  392. package/dist/pipeline/handler-context-config.d.ts +14 -0
  393. package/dist/pipeline/handler-context-config.js +113 -0
  394. package/dist/pipeline/handler-context-db.d.ts +24 -0
  395. package/dist/pipeline/handler-context-db.js +149 -0
  396. package/dist/pipeline/handler-context-dispatch.d.ts +21 -0
  397. package/dist/pipeline/handler-context-dispatch.js +89 -0
  398. package/dist/pipeline/handler-context-events.d.ts +43 -0
  399. package/dist/pipeline/handler-context-events.js +237 -0
  400. package/dist/pipeline/handler-context-notify.d.ts +12 -0
  401. package/dist/pipeline/handler-context-notify.js +55 -0
  402. package/dist/pipeline/handler-context-observability.d.ts +7 -0
  403. package/dist/pipeline/handler-context-observability.js +21 -0
  404. package/dist/pipeline/handler-context-scope.d.ts +48 -0
  405. package/dist/pipeline/handler-context-scope.js +88 -0
  406. package/dist/pipeline/irreversible-operation-gate.js +1 -1
  407. package/dist/pipeline/lifecycle-pipeline.js +9 -9
  408. package/dist/pipeline/member-read-only-transaction.js +1 -3
  409. package/dist/pipeline/msp-rebuild.js +1 -1
  410. package/dist/pipeline/notify-scope.js +12 -1
  411. package/dist/pipeline/pre-handler-gates.js +20 -4
  412. package/dist/pipeline/pre-transaction-reservations.d.ts +1 -0
  413. package/dist/pipeline/pre-transaction-reservations.js +9 -3
  414. package/dist/pipeline/projection-rebuild.js +15 -13
  415. package/dist/pipeline/system-hooks.js +39 -41
  416. package/dist/pipeline/system-identity-switch.js +1 -3
  417. package/dist/pipeline/tenant-timezone-cache.js +1 -1
  418. package/dist/random/generate.d.ts +29 -29
  419. package/dist/random/generate.js +30 -30
  420. package/dist/random/words.js +20 -20
  421. package/dist/rate-limit/middleware.js +9 -5
  422. package/dist/redis/client.js +2 -2
  423. package/dist/redis/pubsub-signal.js +14 -17
  424. package/dist/schema-cli/apply-command.d.ts +2 -0
  425. package/dist/schema-cli/apply-command.js +97 -0
  426. package/dist/schema-cli/baseline-command.d.ts +2 -0
  427. package/dist/schema-cli/baseline-command.js +33 -0
  428. package/dist/schema-cli/generate-command.d.ts +2 -0
  429. package/dist/schema-cli/generate-command.js +90 -0
  430. package/dist/schema-cli/shared.d.ts +39 -0
  431. package/dist/schema-cli/shared.js +95 -0
  432. package/dist/schema-cli/status-command.d.ts +2 -0
  433. package/dist/schema-cli/status-command.js +73 -0
  434. package/dist/schema-cli/validate-boot-input.d.ts +5 -0
  435. package/dist/schema-cli/validate-boot-input.js +130 -0
  436. package/dist/schema-cli/validate-command.d.ts +2 -0
  437. package/dist/schema-cli/validate-command.js +130 -0
  438. package/dist/schema-cli.d.ts +2 -18
  439. package/dist/schema-cli.js +47 -510
  440. package/dist/search/index.js +4 -4
  441. package/dist/search/meilisearch-adapter.js +2 -1
  442. package/dist/search/purge-subject.js +4 -5
  443. package/dist/search/reindex-entity.js +6 -9
  444. package/dist/time/boot-tz-warning.d.ts +2 -2
  445. package/dist/time/boot-tz-warning.js +8 -8
  446. package/dist/time/iana.js +10 -10
  447. package/dist/time/index.js +9 -9
  448. package/dist/time/legacy-date.d.ts +2 -2
  449. package/dist/time/legacy-date.js +6 -8
  450. package/dist/time/polyfill.js +1 -1
  451. package/dist/time/tz-context.d.ts +1 -1
  452. package/dist/time/tz-context.js +1 -1
  453. package/dist/ui-types/app-schema.js +7 -7
  454. package/dist/ui-types/list-row-meta.js +4 -4
  455. package/dist/upgrade-cli.js +159 -130
  456. package/dist/utils/compare.js +1 -1
  457. package/package.json +5 -5
  458. package/src/changes.json +358 -0
  459. package/src/scripts/codemod/crypto-shredding-testing-move.ts +2 -2
  460. package/src/scripts/codemod/feature-layout.ts +10 -2
  461. package/src/scripts/codemod/migrate-cross-tenant.ts +1 -1
  462. package/src/scripts/codemod/migrate-open-to-all.ts +1 -1
  463. package/src/scripts/codemod/object-form.ts +41 -4
  464. package/src/scripts/codemod/package-merge.ts +58 -8
  465. package/src/scripts/codemod/pii-personal-migration.ts +192 -153
  466. package/src/scripts/codemod/testing-injection.ts +122 -42
  467. package/src/scripts/codemod/testing-move.ts +29 -7
  468. package/src/scripts/codemod/unprocessable-error-details-reason.ts +2 -2
@@ -1,20 +1,18 @@
1
- // Rebuild-Marker für den drizzle-freien Migrations-Pfad.
1
+ // Rebuild marker for the drizzle-free migration path.
2
2
  //
3
- // `kumiko schema generate` schreibt neben jedes `NNNN_<name>.sql` einen
4
- // Sibling-Marker `NNNN_<name>.rebuild.json`, der die in dieser Migration
5
- // geänderten/neu angelegten Tabellen listet. `kumiko schema apply` liest den
6
- // Marker für jede frisch applizierte Migration und rebuildet die betroffenen
7
- // Projektionen.
3
+ // `kumiko schema generate` writes a sibling marker
4
+ // `NNNN_<name>.rebuild.json` next to each `NNNN_<name>.sql`, listing the tables
5
+ // changed or created by that migration. `kumiko schema apply` reads the marker
6
+ // for each newly applied migration and rebuilds the affected projections.
8
7
  //
9
- // Bewusst nur **Tabellennamen** (kein Projection-Name): der Generator ist
10
- // registry-frei (kennt die App-Projektionen nicht). Die Auflösung
11
- // Tabelle→Projection passiert app-seitig beim Apply via
12
- // `buildProjectionTableIndex(registry)`. Tabellen ohne zugehörige Projektion
13
- // werden dort einfach übersprungen.
8
+ // Deliberately stores only **table names** (not projection names): the generator
9
+ // is registry-free and does not know the app's projections. The table-to-
10
+ // projection resolution happens in the app during apply via
11
+ // `buildProjectionTableIndex(registry)`. Tables without a corresponding
12
+ // projection are simply skipped there.
14
13
  //
15
- // Marker werden zum generate-Zeitpunkt aus dem strukturierten `SchemaDiff`
16
- // geschrieben — nicht beim Apply aus dem (ggf. hand-editierten) SQL
17
- // re-derived.
14
+ // Markers are written at generate time from the structured `SchemaDiff`, rather
15
+ // than being re-derived during apply from the possibly hand-edited SQL.
18
16
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
19
17
  import { join } from "node:path";
20
18
  import { managedChangeRequiresRecreate } from "./migrate-generator.js";
@@ -29,12 +27,12 @@ function markerPathFor(migrationsDir, migrationId) {
29
27
  // (managedChangeRequiresRecreate: drop/NOT-NULL-w/o-default/unique-index/type-
30
28
  // or nullability-change) — then the table is emptied and must be re-derived from
31
29
  // events. A pure additive nullable column is an in-place `ADD COLUMN` ALTER that
32
- // already brings the table to the target state (same logic #181 applied to
30
+ // already brings the table to the target state (same logic applied to
33
31
  // index-/default-only changes). Rebuilding it anyway is wasted replay AND — the
34
32
  // bug this closes — a co-triggered rebuild can drop the fresh column (rebuild
35
33
  // runs from the rebuilding process's registry meta; on a rolling deploy an older
36
34
  // pod's meta lacks the column → shadow-swap wipes it → phantom migration + boot-
37
- // drift crash; see 0008_add_pending_deletion_request_id / #494 / #835).
35
+ // drift crash).
38
36
  //
39
37
  // If a NEW additive column genuinely needs value-backfill from historical events
40
38
  // (a column DERIVED from existing event fields), opt in explicitly by hand-adding
@@ -60,18 +58,19 @@ export function rebuildTablesFromDiff(diff) {
60
58
  }
61
59
  return [...names].sort();
62
60
  }
63
- // Schreibt `<sqlFilename ohne .sql>.rebuild.json`. Leere Tabellen-Liste →
64
- // kein Marker (z.B. reine Drop-Migration).
61
+ // Writes `<sqlFilename without .sql>.rebuild.json`. An empty table list writes no
62
+ // marker (for example, a drop-only migration).
65
63
  export function writeRebuildMarker(migrationsDir, sqlFilename, tables) {
66
- // skip: leere Tabellen-Liste → kein Marker (z.B. reine Drop-Migration).
64
+ // skip: empty table list means no marker is written (for example, for a
65
+ // migration that only drops tables).
67
66
  if (tables.length === 0)
68
67
  return;
69
68
  const migrationId = sqlFilename.replace(/\.sql$/, "");
70
69
  const marker = { version: MARKER_VERSION, tables };
71
70
  writeFileSync(markerPathFor(migrationsDir, migrationId), `${JSON.stringify(marker, null, 2)}\n`);
72
71
  }
73
- // Liest die Tabellen-Liste für eine applizierte Migration. Kein Marker /
74
- // kaputtes File → leere Liste (Migration ohne Projection-Impact).
72
+ // Reads the table list for an applied migration. A missing or malformed file
73
+ // yields an empty list (the migration has no projection impact).
75
74
  export function readRebuildMarker(migrationsDir, migrationId) {
76
75
  const path = markerPathFor(migrationsDir, migrationId);
77
76
  if (!existsSync(path))
@@ -37,7 +37,12 @@ export async function seedReferenceData(defs, registry, db) {
37
37
  const keyValue = row[upsertKey];
38
38
  if (keyValue === undefined)
39
39
  continue;
40
- const existing = await fetchOne(db, table, { [upsertKey]: keyValue });
40
+ // Soft-deleted rows are invisible to executor.update, so treat them as missing and re-create.
41
+ const existing = await fetchOne(db, table, {
42
+ [upsertKey]: keyValue,
43
+ tenantId: SYSTEM_TENANT_ID,
44
+ ...(entity.softDelete && { isDeleted: false }),
45
+ });
41
46
  if (existing) {
42
47
  const changes = {};
43
48
  for (const [field, value] of Object.entries(row)) {
@@ -70,7 +70,7 @@ function parseColumnNames(body) {
70
70
  // through to the fail-loud check as "unparsed". RENAME TO / RENAME COLUMN
71
71
  // are not listed here: they change identity and are applied explicitly.
72
72
  const SHAPE_NEUTRAL_ALTER_CLAUSE_RE = new RegExp([
73
- `ALTER COLUMN\\s+${IDENT}\\s+TYPE\\b`, // #1085 int/bigint-catchup fixes
73
+ `ALTER COLUMN\\s+${IDENT}\\s+TYPE\\b`, // int/bigint-catchup fixes
74
74
  `ALTER COLUMN\\s+${IDENT}\\s+(SET|DROP)\\s+NOT NULL\\b`,
75
75
  `ALTER COLUMN\\s+${IDENT}\\s+(SET DEFAULT\\b|DROP DEFAULT\\b)`,
76
76
  `(ADD|DROP)\\s+CONSTRAINT\\s+${IDENT}`,
@@ -269,7 +269,7 @@ function applyStatement(schema, statement, context) {
269
269
  // Reuses the real runner's file-discovery + statement-splitting
270
270
  // (loadMigrationsFromDir) instead of a second copy, so any future change to
271
271
  // sub-directory handling, numeric sort order, or the .sql filter can't
272
- // silently drift between the runner and this replay (#1522/9).
272
+ // silently drift between the runner and this replay.
273
273
  export function replayMigrationsDir(migrationsDir) {
274
274
  const schema = new Map();
275
275
  const migrations = loadMigrationsFromDir(migrationsDir, expandDestructiveMarkers);
@@ -26,11 +26,10 @@ function resolveUnsafeClient(db) {
26
26
  // await unsafePushTables(db, { events: eventsTable });
27
27
  export async function tableExists(db, fullyQualifiedName) {
28
28
  const unsafe = resolveUnsafeClient(db);
29
- // quote_ident-Round-trip auf SQL-Seite: ohne Quotes folded postgres
30
- // unquoted identifier case-insensitiv (myWidget → mywidget), während die
31
- // generierte DDL den Namen via quoteIdent("myWidget") → "myWidget" case-
32
- // preserved schreibt. quote_ident sorgt für identische Quotierung beidseits.
33
- // Schema-qualifizierte Namen (`public.events`) werden per Split einzeln quotet.
29
+ // SQL-side quote_ident round-trip: unquoted PostgreSQL identifiers are folded case-insensitively
30
+ // (`myWidget` → `mywidget`), while generated DDL preserves the case via
31
+ // `quoteIdent("myWidget")` → `"myWidget"`. quote_ident keeps both sides quoted identically.
32
+ // Schema-qualified names (`public.events`) are quoted one component at a time after splitting.
34
33
  const dotIdx = fullyQualifiedName.indexOf(".");
35
34
  const [sql, params] = dotIdx >= 0
36
35
  ? [
@@ -196,8 +196,7 @@ export type EntityRow<E extends EntityDefinition = EntityDefinition> = string ex
196
196
  export type { ExecutorManagedRunner, ExecutorManagedWriteCapability, Writable, WritableSchemaTable, WritableTable, } from "@cosmicdrift/kumiko-types/executor-brand";
197
197
  export type { EntityTenancy, TenancyBrand } from "@cosmicdrift/kumiko-types/tenancy-brand";
198
198
  type EntityTableTenancy<E extends EntityDefinition> = [NonNullable<E["tenancy"]>] extends [never] ? "tenant" : [NonNullable<E["tenancy"]>] extends ["global"] ? "global" : "tenant";
199
- export type EntityTable<E extends EntityDefinition = EntityDefinition> = TableColumns<// biome-ignore lint/suspicious/noExplicitAny: drizzle's internal table-config stays generic; we layer typed columns on top via the intersection below.
200
- any> & BaseColumnsType<E> & SoftDeleteColumnsType & ColumnsForEntity<E["fields"]> & TenancyBrand<EntityTableTenancy<E>>;
199
+ export type EntityTable<E extends EntityDefinition = EntityDefinition> = TableColumns & BaseColumnsType<E> & SoftDeleteColumnsType & ColumnsForEntity<E["fields"]> & TenancyBrand<EntityTableTenancy<E>>;
201
200
  export declare function buildBaseColumns(softDelete: boolean, idType?: "serial" | "uuid"): {
202
201
  id: ColumnBuilder<string> | ColumnBuilder<number>;
203
202
  tenantId: ColumnBuilder<string>;
@@ -1,6 +1,5 @@
1
1
  import { KUMIKO_META_SYMBOL } from "@cosmicdrift/kumiko-types/schema-table-types";
2
2
  import { toSnakeCase } from "../utils/case.js";
3
- import { assertUnreachable } from "../utils/index.js";
4
3
  import { markExecutorManagedTable } from "./executor-managed-tables.js";
5
4
  import { columnHandleName } from "./table-introspection.js";
6
5
  export { toSnakeCase } from "../utils/case.js";
@@ -13,173 +12,120 @@ export const SOFT_DELETE_LIVE_ROW_PREDICATE = `"is_deleted" = false`;
13
12
  export function physicalColumnName(table, field) {
14
13
  return columnHandleName(table, field) ?? toSnakeCase(field);
15
14
  }
16
- // Returns column(s) for a field. Most fields return a single entry,
17
- // money returns two (amount + currency).
18
- //
19
- // `required: true` auf einem FieldDefinition mappt **immer** auf .notNull()
20
- // in der DB-Spalte. Die alte Implementation hat das nur für reference-
21
- // fields gemacht — text/select/number/etc. waren stillschweigend nullable
22
- // in der DB, auch wenn der API-Validator required erzwungen hat. Folge:
23
- // hand-written PgTable-Definitionen mussten daneben mit .notNull() pflegen,
24
- // was zu Doppel-Definitionen + Schema-Drift führte. Jetzt ist r.entity
25
- // die einzige Wahrheit.
26
- function fieldToColumns(name, field, entity) {
27
- const snakeName = toSnakeCase(name);
28
- switch (field.type) {
29
- case "text":
30
- case "longText": {
31
- // Beide mappen auf PG `text` (unbounded). Unterschied lebt nur
32
- // im Type-Layer: longText hat kein sortable/searchable/filterable
33
- // (Sprint 5b vorab). Reihenfolge default() VOR notNull(): drizzle's
34
- // column-builder chained beides; ohne default() hat die generierte
35
- // SQL keinen DEFAULT-clause (bricht ALTER TABLE ADD COLUMN auf
36
- // existing rows). longText hat heute kein default-feld im type,
37
- // aber der check `field.default !== undefined` ist defensive.
38
- const base = text(snakeName);
39
- const withDefault = "default" in field && field.default !== undefined ? base.default(field.default) : base;
40
- const mainCol = { [name]: field.required ? withDefault.notNull() : withDefault };
41
- // lookupable → nullable HMAC-Blind-Index-Spalte (lock-step mit
42
- // fieldToColumnMeta in entity-table-meta.ts).
43
- if (field.type === "text" && field.lookupable === true) {
44
- return { ...mainCol, [`${name}Bidx`]: text(`${snakeName}_bidx`) };
45
- }
46
- return mainCol;
47
- }
48
- case "boolean":
49
- return {
50
- [name]: field.default !== undefined
51
- ? boolean(snakeName).default(field.default).notNull()
52
- : field.required
53
- ? boolean(snakeName).notNull()
54
- : boolean(snakeName),
55
- };
56
- case "select": {
57
- // default() durchreichen — entity-table-meta rendert deklarierte
58
- // defaults, dieser Builder MUSS lock-step bleiben (sonst trägt das
59
- // Meta am buildEntityTable-Objekt eine andere Spalte als die
60
- // Migration, #255-Follow-up-Befund).
61
- const base = text(snakeName);
62
- const col = field.default !== undefined ? base.default(field.default) : base;
63
- return { [name]: field.required ? col.notNull() : col };
64
- }
65
- case "multiSelect":
66
- // jsonb-Array<string> mit Default `[]` und immer NOT NULL.
67
- //
68
- // Der `required`-Flag auf MultiSelectFieldDef wird hier bewusst
69
- // ignoriert: Mit Default `[]` ist das Feld strukturell never-null
70
- // (Insert ohne Wert → leeres Array, nicht NULL). Read-Side-Code
71
- // braucht keinen null-check, das ist API-Garantie. Wer "wirklich
72
- // null" will (= "Feld noch nie gesetzt") nutzt einen separaten
73
- // Status-Field oder ein optional-typed reference statt eines
74
- // multi-select.
75
- return { [name]: jsonb(snakeName).default([]).notNull() };
76
- case "number": {
77
- const base = field.integer === true ? integer(snakeName) : doublePrecision(snakeName);
78
- const col = field.default !== undefined ? base.default(field.default) : base;
79
- return { [name]: field.required ? col.notNull() : col };
80
- }
81
- case "decimal": {
82
- const base = decimalColumn(snakeName, field.precision, field.scale);
83
- const col = field.default !== undefined ? base.default(field.default) : base;
84
- return { [name]: field.required ? col.notNull() : col };
85
- }
86
- case "bigInt": {
87
- // 64-bit-Integer fuer Audit-Counter, Byte-Sizes, Cumulative-Sums.
88
- // mode:"number" liefert JS-`number` (sicher bis 2^53 ≈ 9 PB) statt
89
- // JS-`bigint` — JSON-serialisierbar, Frontend-tauglich. Wer >2^53
90
- // braucht (Astronomie-Astronomie), nutzt einen Text-Field mit
91
- // eigenem Codec.
92
- const base = bigint(snakeName, { mode: "number" });
93
- const col = field.default !== undefined ? base.default(field.default) : base;
94
- return { [name]: field.required ? col.notNull() : col };
95
- }
96
- case "reference":
97
- // Tier 2.7e-3: FK-Style UUID-Spalte. Multi-Mode (Tier 2.7e-Multi)
98
- // speichert UUIDs als jsonb-Array<string>. Single-Mode bleibt
99
- // klassische UUID-Spalte (NOT NULL nur bei required).
100
- if (field.multiple === true) {
101
- return { [name]: jsonb(snakeName).default([]).notNull() };
102
- }
103
- return {
104
- [name]: field.required ? uuid(snakeName).notNull() : uuid(snakeName),
105
- };
106
- case "money":
107
- // BIGINT storing the integer minor unit (cents for EUR, yen for JPY —
108
- // the currency column tells you which). INTEGER would cap at ~21 M EUR
109
- // which is too tight for B2B invoices, property values or balance
110
- // aggregates. BIGINT handles up to ~90 trillion EUR safely in JS.
111
- // Currency hat immer einen Default, ist also strukturell .notNull().
112
- return {
113
- [name]: field.required ? moneyAmount(snakeName).notNull() : moneyAmount(snakeName),
114
- [`${name}Currency`]: text(`${snakeName}_currency`)
115
- .default(entity.defaultCurrency ?? "EUR")
116
- .notNull(),
117
- };
118
- case "embedded":
119
- // jsonb mit default `{}` und immer NOT NULL — analog zu multiSelect.
120
- // `required` wird bewusst ignoriert weil der Default das Feld
121
- // strukturell never-null macht. Wer optional-embedded möchte (=
122
- // "Feld komplett weglassen können") modelliert das über ein
123
- // wrapper-feld mit boolean-flag oder discriminierte-union.
124
- // multiple → the value is a list of rows, so the empty default is `[]`.
125
- return field.multiple === true
126
- ? { [name]: jsonb(snakeName).default([]).notNull() }
127
- : { [name]: jsonb(snakeName).default({}).notNull() };
128
- case "jsonb":
129
- // Free-form jsonb — keys nicht schema-validated. Default `{}`, NOT NULL
130
- // (analog zu embedded). Use-case: custom-fields-Bundle's host-entity-
131
- // `customFields`-Spalte (tenant-definierte dynamische keys).
132
- return { [name]: jsonb(snakeName).default({}).notNull() };
133
- case "date": {
134
- // Real PG `date` — no time-of-day, no timezone. Temporal.PlainDate
135
- // end-to-end (see plainDate() in dialect.ts). Was aliased on instant()
136
- // = TIMESTAMPTZ until kumiko-framework#1924 (TZ-dependent round-trip).
137
- const col = plainDate(snakeName);
138
- return { [name]: field.required ? col.notNull() : col };
139
- }
140
- case "timestamp": {
141
- // UTC-Instant — gespeichert als TIMESTAMPTZ in PG, gelesen/geschrieben
142
- // als Temporal.Instant via instant() customType (siehe dialect.ts).
143
- // Sprint F: Single-Mode-Welt — Caller-Code kennt nur Temporal.Instant,
144
- // nie JS-Date. Auch Vergleiche (lte/gt/orderBy) akzeptieren Instants
145
- // direkt, kein .toString()-Cast nötig.
146
- const col = instant(snakeName);
147
- return { [name]: field.required ? col.notNull() : col };
148
- }
149
- case "tz": {
150
- // IANA-Zonenname als TEXT — Validierung über Zod-Schema (kommt im
151
- // Validator-Schritt). Snake-Convention: `pickup_tz`.
152
- const col = text(snakeName);
153
- return { [name]: field.required ? col.notNull() : col };
154
- }
155
- case "locatedTimestamp": {
156
- // ZWEI Spalten als atomares Pair: <name>_utc TIMESTAMPTZ + <name>_tz TEXT.
157
- // _utc ist instant() (Temporal.Instant), _tz ist text (IANA-Name).
158
- // Auto-Convert (at+tz → utc beim Insert; utc+tz → at beim Read) wird
159
- // im Executor verdrahtet (Phase C). required propagiert auf beide.
160
- const utc = instant(`${snakeName}_utc`);
161
- const tz = text(`${snakeName}_tz`);
162
- return {
163
- [`${name}Utc`]: field.required ? utc.notNull() : utc,
164
- [`${name}Tz`]: field.required ? tz.notNull() : tz,
165
- };
166
- }
167
- case "file":
168
- case "image": {
169
- // Single file: stores fileRefId as UUID — must match fileRefsTable.id
170
- // (uuid column). Anything narrower (integer, text length-limited) would
171
- // silently truncate or type-coerce at INSERT time and the FK reference
172
- // would be unusable.
173
- const col = uuid(snakeName);
174
- return { [name]: field.required ? col.notNull() : col };
175
- }
176
- case "files":
177
- case "images":
178
- // Ordered fileRef UUID array, same shape as multiSelect: never null.
179
- return { [name]: jsonb(snakeName).default([]).notNull() };
180
- default:
181
- assertUnreachable(field, "field type");
15
+ // `required: true` maps to NOT NULL for every field type, so r.entity stays the
16
+ // single source of truth and hand-written tables never add .notNull() on top.
17
+ function notNullWhenRequired(column, required) {
18
+ return required ? column.notNull() : column;
19
+ }
20
+ // Without a DEFAULT clause ALTER TABLE ADD COLUMN breaks on existing rows, and
21
+ // entity-table-meta renders declared defaults too, so both builders stay in lockstep.
22
+ function withDeclaredDefault(column, value) {
23
+ return value !== undefined ? column.default(value) : column;
24
+ }
25
+ // longText differs from text only in the type layer (no sortable/searchable/filterable).
26
+ function textColumns(name, snakeName, field) {
27
+ const declaredDefault = "default" in field ? field.default : undefined;
28
+ const mainCol = {
29
+ [name]: notNullWhenRequired(withDeclaredDefault(text(snakeName), declaredDefault), field.required),
30
+ };
31
+ // Nullable HMAC blind-index column, in lockstep with fieldToColumnMeta in entity-table-meta.ts.
32
+ if (field.type === "text" && field.lookupable === true) {
33
+ return { ...mainCol, [`${name}Bidx`]: text(`${snakeName}_bidx`) };
182
34
  }
35
+ return mainCol;
36
+ }
37
+ function booleanColumns(name, snakeName, field) {
38
+ if (field.default !== undefined) {
39
+ return { [name]: boolean(snakeName).default(field.default).notNull() };
40
+ }
41
+ return { [name]: notNullWhenRequired(boolean(snakeName), field.required) };
42
+ }
43
+ function numberColumns(name, snakeName, field) {
44
+ const base = field.integer === true ? integer(snakeName) : doublePrecision(snakeName);
45
+ return { [name]: notNullWhenRequired(withDeclaredDefault(base, field.default), field.required) };
46
+ }
47
+ function referenceColumns(name, snakeName, field) {
48
+ if (field.multiple === true)
49
+ return { [name]: emptyJsonbArray(snakeName) };
50
+ return { [name]: notNullWhenRequired(uuid(snakeName), field.required) };
51
+ }
52
+ // BIGINT minor units: INTEGER would cap at about 21 M EUR. The currency column
53
+ // always has a default, so it is structurally NOT NULL.
54
+ function moneyColumns(name, snakeName, field, entity) {
55
+ return {
56
+ [name]: notNullWhenRequired(moneyAmount(snakeName), field.required),
57
+ [`${name}Currency`]: text(`${snakeName}_currency`)
58
+ .default(entity.defaultCurrency ?? "EUR")
59
+ .notNull(),
60
+ };
61
+ }
62
+ // <name>_utc + <name>_tz form an atomic pair the executor converts on write and
63
+ // read, so `required` propagates to both columns.
64
+ function locatedTimestampColumns(name, snakeName, field) {
65
+ return {
66
+ [`${name}Utc`]: notNullWhenRequired(instant(`${snakeName}_utc`), field.required),
67
+ [`${name}Tz`]: notNullWhenRequired(text(`${snakeName}_tz`), field.required),
68
+ };
69
+ }
70
+ // The `[]` default makes the column structurally never null, so `required` is
71
+ // intentionally ignored and read-side code needs no null check.
72
+ function emptyJsonbArray(snakeName) {
73
+ return jsonb(snakeName).default([]).notNull();
74
+ }
75
+ // Same reasoning as emptyJsonbArray; optional embedded data needs a wrapper field.
76
+ function emptyJsonbObject(snakeName) {
77
+ return jsonb(snakeName).default({}).notNull();
78
+ }
79
+ function singleColumnNotNullWhenRequired(column) {
80
+ return (name, snakeName, field) => ({
81
+ [name]: notNullWhenRequired(column(snakeName), field.required),
82
+ });
83
+ }
84
+ // Each entry has a type-level twin in ColumnsForField and a meta-level twin in
85
+ // fieldToColumnMeta (entity-table-meta.ts).
86
+ const COLUMN_EMITTERS = {
87
+ text: textColumns,
88
+ longText: textColumns,
89
+ boolean: booleanColumns,
90
+ select: (name, snakeName, field) => ({
91
+ [name]: notNullWhenRequired(withDeclaredDefault(text(snakeName), field.default), field.required),
92
+ }),
93
+ multiSelect: (name, snakeName) => ({ [name]: emptyJsonbArray(snakeName) }),
94
+ number: numberColumns,
95
+ decimal: (name, snakeName, field) => ({
96
+ [name]: notNullWhenRequired(withDeclaredDefault(decimalColumn(snakeName, field.precision, field.scale), field.default), field.required),
97
+ }),
98
+ // mode "number" keeps the value JSON-serializable (safe up to 2^53).
99
+ bigInt: (name, snakeName, field) => ({
100
+ [name]: notNullWhenRequired(withDeclaredDefault(bigint(snakeName, { mode: "number" }), field.default), field.required),
101
+ }),
102
+ reference: referenceColumns,
103
+ money: moneyColumns,
104
+ embedded: (name, snakeName, field) => ({
105
+ [name]: field.multiple === true ? emptyJsonbArray(snakeName) : emptyJsonbObject(snakeName),
106
+ }),
107
+ jsonb: (name, snakeName) => ({ [name]: emptyJsonbObject(snakeName) }),
108
+ date: singleColumnNotNullWhenRequired(plainDate),
109
+ timestamp: singleColumnNotNullWhenRequired(instant),
110
+ tz: singleColumnNotNullWhenRequired(text),
111
+ locatedTimestamp: locatedTimestampColumns,
112
+ // Must match fileRefsTable.id (uuid); anything narrower breaks the reference.
113
+ file: singleColumnNotNullWhenRequired(uuid),
114
+ image: singleColumnNotNullWhenRequired(uuid),
115
+ files: (name, snakeName) => ({ [name]: emptyJsonbArray(snakeName) }),
116
+ images: (name, snakeName) => ({ [name]: emptyJsonbArray(snakeName) }),
117
+ };
118
+ function emitFieldColumns(type, name, field, entity) {
119
+ // A type from untyped input must not reach an Object.prototype member.
120
+ if (!Object.hasOwn(COLUMN_EMITTERS, type)) {
121
+ throw new Error(`[Kumiko] unhandled field type: ${String(field)}`);
122
+ }
123
+ const emit = COLUMN_EMITTERS[type];
124
+ return emit(name, toSnakeCase(name), field, entity);
125
+ }
126
+ // Most fields return one column; money and locatedTimestamp return two.
127
+ function fieldToColumns(name, field, entity) {
128
+ return emitFieldColumns(field.type, name, field, entity);
183
129
  }
184
130
  // Accepts both camelCase (`tenantMembership`) and kebab-case (`tenant-membership`)
185
131
  // entity / field names. Implementation lives in utils/case — re-exported here
@@ -188,12 +134,12 @@ function fieldToColumns(name, field, entity) {
188
134
  * Derives a table name from an entity name:
189
135
  * 1. camelCase → snake_case (e.g. "memberTask" → "member_task")
190
136
  * 2. Simple English pluralization (category→categories, status→statuses, task→tasks)
191
- * 3. `read_` prefix — markiert die Tabelle als Event-Sourced-Read-Model,
192
- * damit im DB-Tool sofort erkennbar ist dass App-Code nicht direkt
193
- * reinschreibt. Event-Store (kumiko_events) + Framework-State
194
- * (kumiko_*) haben ihren eigenen Prefix, normale App-Side-Tables
195
- * (ohne ES-Anbindung) haben keinen — die drei Kategorien sind damit
196
- * im Tabellenbrowser unterscheidbar.
137
+ * 3. `read_` prefix — marks the table as an event-sourced read model so
138
+ * it is immediately distinguishable in database tools and application
139
+ * code is not expected to write to it directly. The event store
140
+ * (`kumiko_events`) and framework state (`kumiko_*`) use their own
141
+ * prefixes; ordinary app-side tables without event-sourcing have none.
142
+ * The three categories are therefore distinguishable in the table browser.
197
143
  */
198
144
  const ES_PLURAL_SUFFIXES = ["s", "sh", "ch", "x"];
199
145
  export const READ_MODEL_PREFIX = "read_";
@@ -219,12 +165,13 @@ export function buildBaseColumns(softDelete, idType = "uuid") {
219
165
  id: idColumn,
220
166
  tenantId: uuid("tenant_id").notNull(),
221
167
  version: integer("version").default(1).notNull(),
222
- // Sprint F: Temporal.Instant durchgängig (siehe instant() in dialect.ts).
223
- // Vorher mode default "date" → Inkonsistenz mit user-defined timestamp
224
- // Felder (mode "string"). Jetzt: ein Mode für alle Timestamps.
168
+ // Temporal.Instant is used throughout (see instant() in dialect.ts).
169
+ // Previously the default mode was "date", which was inconsistent with
170
+ // user-defined timestamp fields (mode "string"). A single mode is now
171
+ // used for all timestamps.
225
172
  // customType doesn't expose Drizzle's `defaultNow()` shortcut — use raw
226
- // SQL so PG sets the value on insert and we don't need to pass an
227
- // Instant from JS for every row create.
173
+ // SQL so PG sets the value on insert and callers need not pass an
174
+ // Instant for every row creation.
228
175
  insertedAt: instant("inserted_at").default(sql `now()`).notNull(),
229
176
  modifiedAt: instant("modified_at"),
230
177
  // User-IDs are stringified UUIDs post-ES migration. Text (not uuid) so the
@@ -295,9 +242,9 @@ export function buildEntityTable(entityName, entity, options) {
295
242
  }
296
243
  // Default table name derived from entityName (e.g. "memberTask" → "read_member_tasks")
297
244
  const baseTableName = entity.table ?? toTableName(entityName);
298
- // featureName-prefix wird zwischen read_ und den base-Namen geschoben,
299
- // damit alle read-models einheitlich mit `read_` starten — egal ob
300
- // featureName gesetzt ist oder nicht. Beispiel:
245
+ // The featureName prefix is inserted between read_ and the base name so
246
+ // all read models consistently start with `read_`, whether or not
247
+ // featureName is set. Examples:
301
248
  // featureName="shop", base="read_orders" → "read_shop_orders"
302
249
  // featureName=undef, base="read_orders" → "read_orders"
303
250
  // featureName="shop", base="orders" (no read_) → "shop_orders"
@@ -349,7 +296,7 @@ export function buildEntityTable(entityName, entity, options) {
349
296
  indexes[`${tableName}_${toSnakeCase(fieldName)}_idx`] = index(`${tableName}_${toSnakeCase(fieldName)}_idx`).on(column);
350
297
  }
351
298
  }
352
- // lookupable-Felder: Index auf der bidx-Spalte (lock-step mit
299
+ // lookupable fields: index the bidx column (kept in lockstep with
353
300
  // deriveEntityTableMeta).
354
301
  const bidxFieldByField = new Map();
355
302
  for (const [name, field] of Object.entries(entity.fields)) {
@@ -362,10 +309,10 @@ export function buildEntityTable(entityName, entity, options) {
362
309
  indexes[idxName] = index(idxName).on(column);
363
310
  }
364
311
  }
365
- // entity.indexes = composite/unique-Indices die der Author explizit
366
- // deklariert hat. Spalten werden via field-name (camelCase) angesprochen,
367
- // der Index-Name folgt der Convention <table>_<col1>_<col2>_<unique|idx>
368
- // — Override via index.name möglich.
312
+ // entity.indexes contains composite/unique indexes explicitly declared by
313
+ // the author. Columns are addressed by field name (camelCase), and the
314
+ // index name follows <table>_<col1>_<col2>_<unique|idx>; index.name can
315
+ // override the convention.
369
316
  for (const def of entity.indexes ?? []) {
370
317
  const cols = def.columns
371
318
  .map((fieldName) => tHandle[fieldName])
@@ -388,12 +335,12 @@ export function buildEntityTable(entityName, entity, options) {
388
335
  }
389
336
  else if (def.unique === true && entity.softDelete === true) {
390
337
  // Soft-deleted rows keep their value, so a full unique index would
391
- // permanently block reuse; kept in lock-step with entity-table-meta.ts (framework#2593).
338
+ // permanently block reuse; kept in lock-step with entity-table-meta.ts.
392
339
  chain = chain.where(sql.raw(SOFT_DELETE_LIVE_ROW_PREDICATE));
393
340
  }
394
341
  indexes[indexName] = chain;
395
- // Partielles bidx-Pendant für unique-Indices über lookupable-Spalten
396
- // (lock-step mit deriveEntityTableMeta).
342
+ // Partial bidx counterpart for unique indexes over lookupable fields
343
+ // (kept in lockstep with deriveEntityTableMeta).
397
344
  if (def.unique === true && def.where === undefined) {
398
345
  const bidxFieldNames = def.columns.map((c) => bidxFieldByField.get(c) ?? c);
399
346
  if (bidxFieldNames.some((c, i) => c !== def.columns[i])) {
@@ -406,7 +353,7 @@ export function buildEntityTable(entityName, entity, options) {
406
353
  .map((c) => `"${toSnakeCase(c)}" IS NOT NULL`);
407
354
  // Soft-deleted rows keep their bidx hash — without this, a
408
355
  // restored row can't reuse a value a soft-deleted sibling still
409
- // holds (framework#2464).
356
+ // holds.
410
357
  const whereText = (entity.softDelete === true
411
358
  ? [...notNullParts, SOFT_DELETE_LIVE_ROW_PREDICATE]
412
359
  : notNullParts).join(" AND ");
@@ -0,0 +1,10 @@
1
+ import type { TableLike } from "./query.ts";
2
+ export type TableInfo = {
3
+ readonly name: string;
4
+ readonly columnOf: (field: string) => string;
5
+ readonly pgTypeOf: (column: string) => string | undefined;
6
+ readonly bigintJsModeOf: (column: string) => "number" | "bigint" | undefined;
7
+ readonly fieldOf: (column: string) => string;
8
+ readonly hasColumn: (fieldOrColumn: string) => boolean;
9
+ };
10
+ export declare function extractTableInfo(table: TableLike): TableInfo;
@@ -0,0 +1,45 @@
1
+ import { camelCase as envCamelCase } from "../env/index.js";
2
+ import { toSnakeCase } from "./table-builder.js";
3
+ import { asEntityTableMeta } from "./table-introspection.js";
4
+ // Idempotent snake_case → camelCase. `env.camelCase` always lowercases first
5
+ // (designed for SHOUT_CASE input) — for already-camelCase keys (mock rows
6
+ // in tests, projection-aliased columns) it would silently produce "tenantid"
7
+ // instead of leaving "tenantId" alone. Guard with an underscore check so
8
+ // the conversion only fires when the key is actually snake-shaped.
9
+ function snakeToCamel(key) {
10
+ if (!key.includes("_"))
11
+ return key;
12
+ return envCamelCase(key);
13
+ }
14
+ export function extractTableInfo(table) {
15
+ const meta = asEntityTableMeta(table);
16
+ if (!meta) {
17
+ throw new Error("extractTableInfo: table is not a kumiko EntityTableMeta — " +
18
+ "build it via buildEntityTable / deriveEntityTableMeta / table().");
19
+ }
20
+ const colByField = new Map();
21
+ const fieldByCol = new Map();
22
+ const typeByCol = new Map();
23
+ const bigintModeByCol = new Map();
24
+ for (const c of meta.columns) {
25
+ typeByCol.set(c.name, c.pgType);
26
+ if (c.bigintJsMode !== undefined)
27
+ bigintModeByCol.set(c.name, c.bigintJsMode);
28
+ // EntityTableMeta column names are snake_case. Map snake → snake AND
29
+ // derive a camelCase JS field-name so result rows can be renamed back
30
+ // to the API shape (`aggregate_id` → `aggregateId`).
31
+ colByField.set(c.name, c.name);
32
+ const camel = snakeToCamel(c.name);
33
+ if (camel !== c.name)
34
+ colByField.set(camel, c.name);
35
+ fieldByCol.set(c.name, camel === c.name ? c.name : camel);
36
+ }
37
+ return {
38
+ name: meta.tableName,
39
+ columnOf: (field) => colByField.get(field) ?? toSnakeCase(field),
40
+ pgTypeOf: (col) => typeByCol.get(col),
41
+ bigintJsModeOf: (col) => bigintModeByCol.get(col),
42
+ fieldOf: (col) => fieldByCol.get(col) ?? snakeToCamel(col),
43
+ hasColumn: (fieldOrColumn) => colByField.has(fieldOrColumn) || fieldByCol.has(fieldOrColumn),
44
+ };
45
+ }
@@ -0,0 +1,6 @@
1
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
2
+ export declare function isTemporalInstant(v: unknown): v is Temporal.Instant;
3
+ export declare function isTemporalPlainDate(v: unknown): v is Temporal.PlainDate;
4
+ export declare function isTemporalPlainDateTimeLike(v: unknown): v is {
5
+ toPlainDate(): Temporal.PlainDate;
6
+ };
@@ -0,0 +1,15 @@
1
+ import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
2
+ export function isTemporalInstant(v) {
3
+ return propertyOf(v, Symbol.toStringTag) === "Temporal.Instant";
4
+ }
5
+ export function isTemporalPlainDate(v) {
6
+ return propertyOf(v, Symbol.toStringTag) === "Temporal.PlainDate";
7
+ }
8
+ export function isTemporalPlainDateTimeLike(v) {
9
+ const tag = propertyOf(v, Symbol.toStringTag);
10
+ return ((tag === "Temporal.PlainDateTime" || tag === "Temporal.ZonedDateTime") &&
11
+ typeof v === "object" &&
12
+ v !== null &&
13
+ "toPlainDate" in v &&
14
+ typeof v.toPlainDate === "function");
15
+ }
@@ -6,5 +6,6 @@ export declare function isDbRunner(value: unknown): value is DbRunner;
6
6
  export declare function bindTenantDbRunner(tenantDb: TenantDb, runner: DbRunner): void;
7
7
  export declare function tenantDbRunner(tenantDb: TenantDb): DbRunner;
8
8
  export declare function denyTenantDbWrites(tenantDb: TenantDb, reason: string): void;
9
+ export declare function tenantDbWritesDeniedReason(tenantDb: TenantDb): string | undefined;
9
10
  export declare function tenantDbWritesDeniedError(operation: string, reason: string): InternalError;
10
11
  export declare function tenantDbWriteRunner(tenantDb: TenantDb, operation: string): DbRunner;