@cosmicdrift/kumiko-framework 1.0.0 → 2.0.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 (424) hide show
  1. package/README.md +9 -38
  2. package/package.json +21 -2
  3. package/src/__tests__/anonymous-access.integration.test.ts +185 -0
  4. package/src/__tests__/consumer-cli.integration.test.ts +172 -0
  5. package/src/__tests__/entity-permalink-open.integration.test.ts +94 -0
  6. package/src/__tests__/full-stack.integration.test.ts +1 -1
  7. package/src/__tests__/schema-cli-temporal-polyfill.test.ts +48 -0
  8. package/src/__tests__/schema-cli.integration.test.ts +50 -0
  9. package/src/__tests__/store-table.integration.test.ts +93 -0
  10. package/src/api/__tests__/api.test.ts +373 -0
  11. package/src/api/__tests__/auth-middleware-anonymous-access-boot.test.ts +40 -0
  12. package/src/api/__tests__/auth-routes-cookie.test.ts +17 -1
  13. package/src/api/__tests__/auth-routes-invalid-body-invite.test.ts +253 -0
  14. package/src/api/__tests__/auth-routes-mfa-preauth-confirm.test.ts +222 -0
  15. package/src/api/__tests__/auth-routes-mfa-preauth-enable-start.test.ts +249 -0
  16. package/src/api/__tests__/auth-routes-mfa-verify.test.ts +202 -0
  17. package/src/api/__tests__/auth-routes-trusted-proxy.test.ts +135 -0
  18. package/src/api/__tests__/batch.integration.test.ts +101 -11
  19. package/src/api/__tests__/csrf-constants-sync.test.ts +20 -0
  20. package/src/api/__tests__/dispatcher-live.integration.test.ts +74 -0
  21. package/src/api/__tests__/jwt.test.ts +200 -1
  22. package/src/api/__tests__/login-rate-limiter-sweep.test.ts +50 -0
  23. package/src/api/__tests__/pat-scope.test.ts +36 -0
  24. package/src/api/__tests__/pii-leak-guard.integration.test.ts +103 -0
  25. package/src/api/__tests__/redis-login-rate-limiter.integration.test.ts +138 -0
  26. package/src/api/__tests__/request-id-middleware.test.ts +51 -0
  27. package/src/api/__tests__/server-boot-guards.test.ts +71 -0
  28. package/src/api/__tests__/server-jwt-ttl.test.ts +58 -0
  29. package/src/api/__tests__/sse-broker.test.ts +57 -0
  30. package/src/api/__tests__/sse-route.test.ts +4 -0
  31. package/src/api/api-constants.ts +11 -0
  32. package/src/api/auth-middleware.ts +248 -40
  33. package/src/api/auth-routes.ts +576 -95
  34. package/src/api/index.ts +13 -4
  35. package/src/api/jwt.ts +170 -11
  36. package/src/api/pat-scope.ts +14 -0
  37. package/src/api/pii-leak-guard.ts +47 -0
  38. package/src/api/request-context.ts +3 -0
  39. package/src/api/request-id-middleware.ts +2 -0
  40. package/src/api/routes.ts +169 -2
  41. package/src/api/server.ts +112 -16
  42. package/src/api/sse-broker.ts +39 -0
  43. package/src/bun-db/__tests__/PATTERN.md +0 -1
  44. package/src/bun-db/__tests__/coerce-row-temporal.test.ts +41 -0
  45. package/src/bun-db/__tests__/select-many-retry.test.ts +79 -0
  46. package/src/bun-db/__tests__/write-brand.test.ts +21 -0
  47. package/src/bun-db/connection.ts +3 -3
  48. package/src/bun-db/index.ts +2 -0
  49. package/src/bun-db/query.ts +105 -32
  50. package/src/consumer-cli.ts +134 -0
  51. package/src/crypto/__tests__/blind-index.test.ts +130 -0
  52. package/src/crypto/__tests__/event-pii.test.ts +161 -0
  53. package/src/crypto/__tests__/kek-rotation.integration.test.ts +180 -0
  54. package/src/crypto/__tests__/kms-adapter-contract.ts +134 -0
  55. package/src/crypto/__tests__/kms-adapter.contract.test.ts +4 -0
  56. package/src/crypto/__tests__/pg-kms-adapter.integration.test.ts +117 -0
  57. package/src/crypto/__tests__/pii-field-encryption.test.ts +376 -0
  58. package/src/crypto/__tests__/request-kms-cache.test.ts +74 -0
  59. package/src/crypto/__tests__/subject-resolver.test.ts +108 -0
  60. package/src/crypto/blind-index.ts +118 -0
  61. package/src/crypto/event-pii.ts +70 -0
  62. package/src/crypto/in-memory-kms-adapter.ts +50 -0
  63. package/src/crypto/index.ts +67 -0
  64. package/src/crypto/kms-adapter.ts +2 -0
  65. package/src/crypto/pg-kms-adapter.ts +295 -0
  66. package/src/crypto/pii-field-encryption.ts +248 -0
  67. package/src/crypto/request-kms-cache.ts +38 -0
  68. package/src/crypto/subject-resolver.ts +91 -0
  69. package/src/db/__tests__/assert-no-unreachable-live-rows.integration.test.ts +191 -0
  70. package/src/db/__tests__/blind-index.integration.test.ts +248 -0
  71. package/src/db/__tests__/build-filter-where.test.ts +34 -0
  72. package/src/db/__tests__/collect-table-metas.test.ts +10 -10
  73. package/src/db/__tests__/config-seed.integration.test.ts +13 -5
  74. package/src/db/__tests__/dialect-instant.test.ts +1 -4
  75. package/src/db/__tests__/entity-field-encryption.test.ts +7 -7
  76. package/src/db/__tests__/entity-table-meta-source.test.ts +50 -0
  77. package/src/db/__tests__/event-store-executor-context.pii-roundtrip.test.ts +67 -0
  78. package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +402 -0
  79. package/src/db/__tests__/event-store-executor.integration.test.ts +299 -29
  80. package/src/db/__tests__/feature-table-sources.test.ts +34 -0
  81. package/src/db/__tests__/implicit-projection-equivalence.integration.test.ts +118 -39
  82. package/src/db/__tests__/instant-to-driver-temporal.test.ts +21 -0
  83. package/src/db/__tests__/located-timestamp.test.ts +19 -0
  84. package/src/db/__tests__/migrate-generator.test.ts +21 -0
  85. package/src/db/__tests__/migrate-runner.test.ts +61 -0
  86. package/src/db/__tests__/number-field-fractional.integration.test.ts +58 -0
  87. package/src/db/__tests__/rebuild-marker.test.ts +13 -3
  88. package/src/db/__tests__/replay-migration-sql.test.ts +384 -0
  89. package/src/db/__tests__/schema-inspection.test.ts +7 -0
  90. package/src/db/__tests__/schema-migration.integration.test.ts +1 -1
  91. package/src/db/__tests__/table-builder-meta-lockstep.test.ts +39 -0
  92. package/src/db/__tests__/tenant-db-where-merge.test.ts +49 -3
  93. package/src/db/__tests__/tenant-db.integration.test.ts +6 -2
  94. package/src/db/api.ts +2 -2
  95. package/src/db/apply-entity-event.ts +18 -14
  96. package/src/db/blind-index-cleanup.ts +55 -0
  97. package/src/db/bun-provider.ts +2 -2
  98. package/src/db/collect-table-metas.ts +6 -7
  99. package/src/db/config-seed.ts +9 -9
  100. package/src/db/connection.ts +7 -12
  101. package/src/db/cursor.ts +1 -18
  102. package/src/db/dialect.ts +20 -26
  103. package/src/db/entity-field-encryption.ts +81 -24
  104. package/src/db/entity-table-meta-types.ts +2 -0
  105. package/src/db/entity-table-meta.ts +63 -90
  106. package/src/db/event-store-executor-context.ts +404 -0
  107. package/src/db/event-store-executor-read.ts +275 -0
  108. package/src/db/event-store-executor-write.ts +644 -0
  109. package/src/db/event-store-executor.ts +28 -1155
  110. package/src/db/feature-table-sources.ts +6 -5
  111. package/src/db/index.ts +20 -3
  112. package/src/db/located-timestamp.ts +4 -0
  113. package/src/db/migrate-generator.ts +39 -6
  114. package/src/db/migrate-runner.ts +107 -11
  115. package/src/db/pg-error.ts +9 -1
  116. package/src/db/postgres-provider.ts +2 -2
  117. package/src/db/queries/__tests__/event-store-idempotency-index.integration.test.ts +80 -0
  118. package/src/db/queries/backfill-pii.ts +277 -0
  119. package/src/db/queries/ddl.ts +45 -0
  120. package/src/db/queries/event-consumer.ts +35 -2
  121. package/src/db/queries/event-store.ts +126 -19
  122. package/src/db/queries/projection-rebuild.ts +22 -8
  123. package/src/db/queries/shadow-swap.ts +183 -0
  124. package/src/db/queries/test-stack.ts +4 -30
  125. package/src/db/query-api.ts +1 -0
  126. package/src/db/query.ts +1 -0
  127. package/src/db/rebuild-marker.ts +18 -2
  128. package/src/db/reference-data.ts +2 -3
  129. package/src/db/replay-migration-sql.ts +257 -0
  130. package/src/db/schema-inspection.ts +1 -1
  131. package/src/db/table-builder.ts +102 -71
  132. package/src/db/tenant-db.ts +15 -53
  133. package/src/engine/__tests__/boot-validator-action-wiring.test.ts +242 -0
  134. package/src/engine/__tests__/boot-validator-boot-check.test.ts +99 -0
  135. package/src/engine/__tests__/boot-validator-dashboard.test.ts +237 -0
  136. package/src/engine/__tests__/boot-validator-entity-list.test.ts +104 -0
  137. package/src/engine/__tests__/boot-validator-gdpr-storage.test.ts +7 -69
  138. package/src/engine/__tests__/boot-validator-i18n-keys.test.ts +26 -5
  139. package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +3 -19
  140. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +246 -4
  141. package/src/engine/__tests__/boot-validator.test.ts +219 -15
  142. package/src/engine/__tests__/build-app-schema.test.ts +82 -0
  143. package/src/engine/__tests__/build-config-feature-schema.test.ts +125 -0
  144. package/src/engine/__tests__/build-target.test.ts +3 -11
  145. package/src/engine/__tests__/codemod-pipeline.test.ts +152 -21
  146. package/src/engine/__tests__/config-helpers.test.ts +16 -0
  147. package/src/engine/__tests__/define-feature-entity-mapping.test.ts +6 -0
  148. package/src/engine/__tests__/define-roles.test.ts +21 -0
  149. package/src/engine/__tests__/engine.test.ts +163 -1
  150. package/src/engine/__tests__/entity-handlers.test.ts +80 -0
  151. package/src/engine/__tests__/event-migration-declarative.test.ts +65 -0
  152. package/src/engine/__tests__/event-type-map-augmentation.test.ts +24 -0
  153. package/src/engine/__tests__/extend-entity-projection.test.ts +123 -0
  154. package/src/engine/__tests__/factories-time.test.ts +2 -66
  155. package/src/engine/__tests__/feature-crud-shorthand.test.ts +28 -0
  156. package/src/engine/__tests__/feature-manifest.test.ts +31 -1
  157. package/src/engine/__tests__/field-access.test.ts +23 -1
  158. package/src/engine/__tests__/hook-phases.test.ts +5 -5
  159. package/src/engine/__tests__/membership-roles.test.ts +4 -10
  160. package/src/engine/__tests__/nav.test.ts +86 -1
  161. package/src/engine/__tests__/pipeline-engine.test.ts +9 -9
  162. package/src/engine/__tests__/pipeline-handler.integration.test.ts +18 -18
  163. package/src/engine/__tests__/pipeline-observability.integration.test.ts +2 -2
  164. package/src/engine/__tests__/pipeline-performance.integration.test.ts +3 -3
  165. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +9 -9
  166. package/src/engine/__tests__/post-query-hook.test.ts +7 -7
  167. package/src/engine/__tests__/registrar-object-form.test.ts +141 -0
  168. package/src/engine/__tests__/registry.test.ts +144 -0
  169. package/src/engine/__tests__/schema-builder.test.ts +18 -0
  170. package/src/engine/__tests__/screen.test.ts +147 -1
  171. package/src/engine/__tests__/soft-delete-cleanup.test.ts +3 -0
  172. package/src/engine/__tests__/store-table.test.ts +229 -0
  173. package/src/engine/__tests__/tier-resolver-extension.test.ts +19 -1
  174. package/src/engine/__tests__/{visual-tree-patterns.test.ts → tree-actions-patterns.test.ts} +7 -95
  175. package/src/engine/__tests__/validate-projection-allowlist.test.ts +15 -15
  176. package/src/engine/boot-validator/__tests__/config-deps.test.ts +92 -0
  177. package/src/engine/boot-validator/action-wiring.ts +149 -0
  178. package/src/engine/boot-validator/boot-check.ts +21 -0
  179. package/src/engine/boot-validator/config-deps.ts +40 -4
  180. package/src/engine/boot-validator/entity-handler.ts +20 -21
  181. package/src/engine/boot-validator/entity-list-screens.ts +88 -0
  182. package/src/engine/boot-validator/gdpr-storage.ts +0 -20
  183. package/src/engine/boot-validator/i18n-keys.ts +58 -4
  184. package/src/engine/boot-validator/index.ts +33 -12
  185. package/src/engine/boot-validator/nav.ts +125 -0
  186. package/src/engine/boot-validator/pii-retention.ts +95 -10
  187. package/src/engine/boot-validator/{screens-nav.ts → screens.ts} +214 -191
  188. package/src/engine/boot-validator/workspaces.ts +68 -0
  189. package/src/engine/build-app-schema.ts +16 -0
  190. package/src/engine/build-config-feature-schema.ts +44 -7
  191. package/src/engine/codemod/pipeline-codemod.ts +5 -5
  192. package/src/engine/config-helpers.ts +15 -0
  193. package/src/engine/constants.ts +32 -6
  194. package/src/engine/create-app.ts +11 -0
  195. package/src/engine/define-feature.ts +95 -947
  196. package/src/engine/define-handler.ts +28 -94
  197. package/src/engine/define-workflow.ts +1 -1
  198. package/src/engine/effective-features.ts +12 -2
  199. package/src/engine/entity-handlers.ts +76 -11
  200. package/src/engine/extensions/tenant-data.ts +19 -0
  201. package/src/engine/extensions/user-data.ts +29 -2
  202. package/src/engine/factories.ts +8 -49
  203. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +26 -29
  204. package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/feature.ts +9 -0
  205. package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/screens.ts +4 -0
  206. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -2
  207. package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +18 -8
  208. package/src/engine/feature-ast/__tests__/parse.test.ts +1640 -160
  209. package/src/engine/feature-ast/__tests__/patch.test.ts +206 -12
  210. package/src/engine/feature-ast/__tests__/patcher.test.ts +20 -23
  211. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +380 -5
  212. package/src/engine/feature-ast/extractors/events.ts +322 -0
  213. package/src/engine/feature-ast/extractors/handlers.ts +234 -0
  214. package/src/engine/feature-ast/extractors/hooks.ts +243 -0
  215. package/src/engine/feature-ast/extractors/index.ts +34 -31
  216. package/src/engine/feature-ast/extractors/jobs-routes.ts +221 -0
  217. package/src/engine/feature-ast/extractors/projections-screens.ts +269 -0
  218. package/src/engine/feature-ast/extractors/round1.ts +3 -3
  219. package/src/engine/feature-ast/extractors/round5.ts +3 -3
  220. package/src/engine/feature-ast/extractors/round6.ts +2 -36
  221. package/src/engine/feature-ast/extractors/shared.ts +64 -6
  222. package/src/engine/feature-ast/index.ts +2 -4
  223. package/src/engine/feature-ast/parse.ts +130 -23
  224. package/src/engine/feature-ast/patch.ts +39 -50
  225. package/src/engine/feature-ast/patcher.ts +37 -38
  226. package/src/engine/feature-ast/patterns.ts +50 -62
  227. package/src/engine/feature-ast/render.ts +67 -44
  228. package/src/engine/feature-builder-state.ts +168 -0
  229. package/src/engine/feature-config-events-jobs.ts +403 -0
  230. package/src/engine/feature-entity-handlers.ts +208 -0
  231. package/src/engine/feature-manifest.ts +2 -1
  232. package/src/engine/feature-ui-extensions.ts +500 -0
  233. package/src/engine/field-access.ts +13 -2
  234. package/src/engine/field-helpers.ts +31 -0
  235. package/src/engine/handler-helpers.ts +26 -0
  236. package/src/engine/hook-helpers.ts +16 -0
  237. package/src/engine/index.ts +23 -7
  238. package/src/engine/membership-roles.ts +13 -0
  239. package/src/engine/object-form.ts +27 -0
  240. package/src/engine/ownership.ts +26 -79
  241. package/src/engine/pattern-library/__tests__/library.test.ts +7 -20
  242. package/src/engine/pattern-library/library.ts +44 -1152
  243. package/src/engine/pattern-library/mixed-schemas.ts +450 -0
  244. package/src/engine/pattern-library/opaque-schemas.ts +124 -0
  245. package/src/engine/pattern-library/shared-fields.ts +75 -0
  246. package/src/engine/pattern-library/static-schemas.ts +456 -0
  247. package/src/engine/pipeline.ts +6 -11
  248. package/src/engine/registry-facade.ts +362 -0
  249. package/src/engine/registry-ingest.ts +478 -0
  250. package/src/engine/registry-state.ts +388 -0
  251. package/src/engine/registry-validate.ts +642 -0
  252. package/src/engine/registry.ts +78 -1658
  253. package/src/engine/run-pipeline.ts +1 -1
  254. package/src/engine/schema-builder.ts +1 -0
  255. package/src/engine/screen-helpers.ts +54 -0
  256. package/src/engine/soft-delete-cleanup.ts +6 -2
  257. package/src/engine/steps/__tests__/duration-utils.test.ts +20 -0
  258. package/src/engine/steps/_duration-utils.ts +2 -0
  259. package/src/engine/steps/unsafe-projection-upsert.ts +1 -4
  260. package/src/engine/tier-resolver-extension.ts +3 -2
  261. package/src/engine/types/config.ts +2 -482
  262. package/src/engine/types/define-handler.ts +2 -0
  263. package/src/engine/types/entity-handlers.ts +2 -0
  264. package/src/engine/types/event-type-map.ts +1 -37
  265. package/src/engine/types/feature.ts +2 -972
  266. package/src/engine/types/fields.ts +2 -675
  267. package/src/engine/types/handlers.ts +2 -774
  268. package/src/engine/types/hooks.ts +2 -184
  269. package/src/engine/types/http-route.ts +1 -54
  270. package/src/engine/types/identifiers.ts +1 -47
  271. package/src/engine/types/index.ts +86 -40
  272. package/src/engine/types/nav.ts +2 -63
  273. package/src/engine/types/ownership.ts +2 -0
  274. package/src/engine/types/projection.ts +2 -138
  275. package/src/engine/types/relations.ts +1 -51
  276. package/src/engine/types/screen.ts +2 -574
  277. package/src/engine/types/step.ts +2 -334
  278. package/src/engine/types/target-ref.ts +1 -21
  279. package/src/engine/types/tree-node.ts +1 -132
  280. package/src/engine/types/workspace.ts +2 -49
  281. package/src/engine/validate-projection-allowlist.ts +6 -6
  282. package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.test.ts +120 -1
  283. package/src/entrypoint/index.ts +49 -6
  284. package/src/errors/classes.ts +22 -1
  285. package/src/errors/field-issue.ts +0 -3
  286. package/src/errors/index.ts +1 -1
  287. package/src/errors/write-error-info.ts +10 -22
  288. package/src/es-ops/README.md +1 -1
  289. package/src/es-ops/__tests__/runner.integration.test.ts +74 -0
  290. package/src/es-ops/context.ts +4 -2
  291. package/src/es-ops/types.ts +8 -1
  292. package/src/event-store/__tests__/admin-api.integration.test.ts +27 -1
  293. package/src/event-store/__tests__/backfill-pii.integration.test.ts +279 -0
  294. package/src/event-store/__tests__/event-store.integration.test.ts +201 -0
  295. package/src/event-store/__tests__/row-to-stored-event.test.ts +2 -2
  296. package/src/event-store/__tests__/snapshot.integration.test.ts +86 -0
  297. package/src/event-store/__tests__/unscoped-stream-primitives.guard.test.ts +58 -0
  298. package/src/event-store/__tests__/upcaster.integration.test.ts +77 -45
  299. package/src/event-store/admin-api.ts +11 -4
  300. package/src/event-store/errors.ts +2 -35
  301. package/src/event-store/event-store.ts +64 -78
  302. package/src/event-store/events-schema.ts +11 -13
  303. package/src/event-store/index.ts +18 -3
  304. package/src/event-store/rebuild-dead-letter.ts +111 -0
  305. package/src/event-store/snapshot.ts +63 -40
  306. package/src/event-store/types.ts +2 -0
  307. package/src/files/__tests__/build-storage-key.test.ts +28 -0
  308. package/src/files/__tests__/file-ref-entity.test.ts +8 -0
  309. package/src/files/__tests__/files.integration.test.ts +24 -0
  310. package/src/files/__tests__/in-memory-provider.contract.test.ts +4 -0
  311. package/src/files/__tests__/local-provider.test.ts +31 -0
  312. package/src/files/__tests__/provider-resolver.test.ts +70 -0
  313. package/src/files/__tests__/write-stream.test.ts +13 -0
  314. package/src/files/file-handle.ts +2 -19
  315. package/src/files/file-ref-entity.ts +2 -2
  316. package/src/files/file-routes.ts +13 -0
  317. package/src/files/index.ts +1 -1
  318. package/src/files/local-provider.ts +27 -8
  319. package/src/files/provider-resolver.ts +31 -20
  320. package/src/files/types.ts +13 -55
  321. package/src/i18n/__tests__/required-surface-keys.test.ts +17 -0
  322. package/src/i18n/required-surface-keys.ts +120 -1
  323. package/src/jobs/__tests__/job-queue-depth.integration.test.ts +82 -0
  324. package/src/jobs/__tests__/jobs.integration.test.ts +329 -3
  325. package/src/jobs/job-runner.ts +82 -12
  326. package/src/logging/types.ts +1 -7
  327. package/src/migrations/pending-rebuilds.ts +1 -1
  328. package/src/observability/__tests__/observability.integration.test.ts +4 -1
  329. package/src/observability/__tests__/recording-tracer.test.ts +6 -4
  330. package/src/observability/index.ts +2 -0
  331. package/src/observability/noop-provider.ts +0 -9
  332. package/src/observability/recording-tracer.ts +0 -12
  333. package/src/observability/standard-metrics.ts +64 -2
  334. package/src/observability/types/index.ts +1 -29
  335. package/src/observability/types/metric.ts +1 -56
  336. package/src/observability/types/provider.ts +1 -32
  337. package/src/observability/types/span.ts +1 -64
  338. package/src/pipeline/__tests__/ctx-bridge.integration.test.ts +2 -2
  339. package/src/pipeline/__tests__/dispatcher.test.ts +366 -1
  340. package/src/pipeline/__tests__/event-consumer-state.integration.test.ts +31 -0
  341. package/src/pipeline/__tests__/event-dispatcher-delivery-max-attempts.test.ts +126 -0
  342. package/src/pipeline/__tests__/event-dispatcher-rearm.integration.test.ts +263 -0
  343. package/src/pipeline/__tests__/event-dispatcher.integration.test.ts +10 -0
  344. package/src/pipeline/__tests__/job-trigger-consumer.integration.test.ts +106 -0
  345. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +308 -77
  346. package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +16 -8
  347. package/src/pipeline/__tests__/post-query-hook.integration.test.ts +3 -3
  348. package/src/pipeline/__tests__/projection-rebuild.integration.test.ts +80 -2
  349. package/src/pipeline/__tests__/rebuild-poison-quarantine.integration.test.ts +274 -0
  350. package/src/pipeline/__tests__/try-append-event.integration.test.ts +166 -0
  351. package/src/pipeline/dispatch-batch.ts +187 -0
  352. package/src/pipeline/dispatch-query.ts +165 -0
  353. package/src/pipeline/dispatch-shared.ts +826 -0
  354. package/src/pipeline/dispatch-stream.ts +90 -0
  355. package/src/pipeline/dispatch-write.ts +451 -0
  356. package/src/pipeline/dispatcher-utils.ts +1 -1
  357. package/src/pipeline/dispatcher.ts +44 -1372
  358. package/src/pipeline/entity-cache.ts +2 -33
  359. package/src/pipeline/event-consumer-state.ts +30 -2
  360. package/src/pipeline/event-dispatcher-admin.ts +293 -0
  361. package/src/pipeline/event-dispatcher-delivery.ts +305 -0
  362. package/src/pipeline/event-dispatcher.ts +85 -542
  363. package/src/pipeline/index.ts +2 -0
  364. package/src/pipeline/msp-rebuild.ts +42 -3
  365. package/src/pipeline/multi-stream-apply-context.ts +4 -42
  366. package/src/pipeline/projection-rebuild.ts +105 -3
  367. package/src/pipeline/system-hooks.ts +144 -1
  368. package/src/rate-limit/__tests__/resolver.integration.test.ts +18 -0
  369. package/src/rate-limit/resolver.ts +16 -32
  370. package/src/schema-cli.ts +76 -16
  371. package/src/search/__tests__/meilisearch-adapter.integration.test.ts +207 -185
  372. package/src/search/__tests__/meilisearch-ids.test.ts +30 -0
  373. package/src/search/__tests__/reindex-entity.integration.test.ts +144 -0
  374. package/src/search/index.ts +6 -0
  375. package/src/search/meilisearch-adapter.ts +13 -12
  376. package/src/search/reindex-entity.ts +177 -0
  377. package/src/search/types.ts +1 -39
  378. package/src/secrets/__tests__/contains-secret.test.ts +34 -0
  379. package/src/secrets/__tests__/envelope-cipher.test.ts +59 -0
  380. package/src/secrets/__tests__/envelope.test.ts +1 -1
  381. package/src/secrets/dek-cache.ts +26 -5
  382. package/src/secrets/envelope-cipher.ts +53 -0
  383. package/src/secrets/envelope.ts +5 -3
  384. package/src/secrets/index.ts +13 -1
  385. package/src/secrets/stored-envelope.ts +46 -0
  386. package/src/secrets/types.ts +2 -162
  387. package/src/stack/__tests__/event-collector.test.ts +42 -0
  388. package/src/stack/__tests__/setup-test-stack-jobs.integration.test.ts +125 -0
  389. package/src/stack/db.ts +2 -1
  390. package/src/stack/push-entity-projection-tables.ts +4 -3
  391. package/src/stack/redis.ts +8 -0
  392. package/src/stack/request-helper.ts +38 -2
  393. package/src/stack/table-helpers.ts +6 -4
  394. package/src/stack/test-stack.ts +249 -131
  395. package/src/testing/__tests__/late-bound.test.ts +32 -0
  396. package/src/testing/__tests__/wait-for.test.ts +59 -0
  397. package/src/testing/boot-validator-fixture.ts +121 -0
  398. package/src/testing/e2e-generator.ts +8 -0
  399. package/src/testing/file-provider-contract.ts +104 -0
  400. package/src/testing/handler-context.ts +3 -1
  401. package/src/testing/index.ts +5 -0
  402. package/src/testing/late-bound.ts +5 -3
  403. package/src/testing/mutable-master-key-provider.ts +29 -3
  404. package/src/testing/wait-for.ts +3 -0
  405. package/src/testing/without-ambient-temporal.ts +14 -0
  406. package/src/time/__tests__/tz-dateline.test.ts +8 -10
  407. package/src/time/geo-tz.ts +1 -32
  408. package/src/time/index.ts +1 -0
  409. package/src/time/legacy-date.ts +18 -0
  410. package/src/time/polyfill.ts +21 -38
  411. package/src/time/tz-context.ts +44 -85
  412. package/src/ui-types/app-schema.ts +12 -0
  413. package/src/ui-types/index.ts +20 -7
  414. package/src/utils/__tests__/safe-json-temporal.test.ts +18 -0
  415. package/src/utils/safe-json.ts +13 -1
  416. package/src/__tests__/raw-table.integration.test.ts +0 -116
  417. package/src/bun-db/__tests__/bun-test-stack.ts +0 -6
  418. package/src/db/__tests__/encryption.test.ts +0 -39
  419. package/src/db/encryption.ts +0 -39
  420. package/src/db/row-helpers.ts +0 -4
  421. package/src/engine/__tests__/raw-table.test.ts +0 -150
  422. package/src/engine/__tests__/unmanaged-table.test.ts +0 -127
  423. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +0 -184
  424. package/src/engine/feature-ast/extractors/round4.ts +0 -1366
@@ -1,675 +1,2 @@
1
- // --- Field Types ---
2
-
3
- // OwnershipMap is declared in engine/ownership.ts — field-access maps to
4
- // per-role ownership rules. A legacy `readonly string[]` form is still
5
- // accepted at the type layer during migration: features that pass an
6
- // array are auto-normalized to { [role]: "all" } at registry build.
7
- // Long-term: string[] disappears.
8
- import type { OwnershipMap } from "../ownership";
9
-
10
- export type FieldAccess = {
11
- readonly read?: OwnershipMap | readonly string[];
12
- readonly write?: OwnershipMap | readonly string[];
13
- };
14
-
15
- // `sensitive: true` — the field's value is excluded from event payloads
16
- // (create data, update changes/previous, delete/restore previous). The entity
17
- // row still stores it; only the immutable event-log won't. Use for data that
18
- // must never land in permanent history: password hashes, API tokens,
19
- // unhashed PII, bank details, tax IDs. The trade-off: event-replay and
20
- // custom projections cannot read sensitive field values. See
21
- // docs/plans/architecture/projections.md.
22
-
23
- // --- PII / Subject-Key Annotations (DSGVO Art. 17 — Crypto-Shredding) ---
24
- //
25
- // Felder die PII enthalten werden in Sprint 3 (crypto-shredding) mit einem
26
- // Subject-Schluessel encrypted gespeichert. Subject = die natuerliche Person
27
- // oder der Tenant der die Daten "besitzt". Loeschung erfolgt durch Vernichten
28
- // des Subject-Keys ("Crypto-Shredding") — der Datensatz bleibt physisch
29
- // (Audit-Trail bewahrt), ist aber nicht mehr entschluesselbar. Sprint 0
30
- // fuegt nur die Schema-Marker + Boot-Validation ein; Encrypt/Decrypt-Mechanik
31
- // kommt in Sprint 3.
32
- //
33
- // Drei orthogonale Markierungen:
34
- // - `pii: true` — Subject = die Entity selbst.
35
- // Beispiel: user.email gehoert User Marc.
36
- // - `userOwned: { ownerField }` — Subject = der User der im genannten
37
- // Field referenziert ist.
38
- // Beispiel: comment.body gehoert
39
- // comment.authorId.
40
- // - `tenantOwned: true` — Subject = der aktuelle Tenant
41
- // (ctx.tenantId zur Schreibzeit).
42
- // Beispiel: tenantBranding.brandColor.
43
- //
44
- // `anonymize` ist die Pro-Feld-Funktion die der retention-Cleanup-Job
45
- // (Sprint 2) aufruft wenn die Entity-Strategy "anonymize" lautet oder die
46
- // `blockDelete`-Frist abgelaufen ist. Beispiel: `() => "[ANONYMIZED]"` oder
47
- // `() => null`.
48
- //
49
- // `allowPlaintext` unterdrueckt PII-Heuristik-Boot-Warnings fuer Felder die
50
- // zwar PII-Naming haben (email, name, body) aber bewusst Klartext bleiben
51
- // sollen — z.B. ticket.title als Geschaeftsdaten. Wert ist eine Begruendung
52
- // wie "is-business-data".
53
- //
54
- // `anonymize` darf sync oder async sein — der Cleanup-Job (Sprint 2)
55
- // awaited den Return. Async-Funktionen sind sinnvoll wenn die Anonymisierung
56
- // einen Lookup braucht (z.B. konsistente Pseudonyme aus separater Tabelle).
57
- //
58
- // Siehe docs/plans/datenschutz/crypto-shredding.md und docs/plans/datenschutz/roadmap.md.
59
- export type PiiAnnotations = {
60
- readonly pii?: boolean;
61
- readonly userOwned?: { readonly ownerField: string };
62
- readonly tenantOwned?: boolean;
63
- readonly anonymize?: () => unknown | Promise<unknown>;
64
- readonly allowPlaintext?: string;
65
- };
66
-
67
- // --- Retention (DSGVO Art. 5(1)(e) + HGB/AO Aufbewahrungspflichten) ---
68
- //
69
- // Pro Entity definiert der Author eine Default-Retention-Policy. Tenant-
70
- // Admin uebersteuert sie via Compliance-Profile + Tenant-Override (Sprint 2).
71
- // Vier Strategien:
72
- //
73
- // - "hardDelete" — Row physisch weg nach `keepFor`. Logs, Sessions.
74
- // - "softDelete" — `deletedAt = now()`. Erlaubt spaetere Restore.
75
- // - "anonymize" — Felder mit `anonymize`-Funktion ueberschrieben,
76
- // Row bleibt. Order/Invoice mit gemischter PII +
77
- // Geschaeftsdaten.
78
- // - "blockDelete" — Cleanup-Job ignoriert; User-Forget loest stattdessen
79
- // `anonymize` aus. Buchhaltung, Mandate, Patientenakten.
80
- //
81
- // `keepFor` ist eine Duration-String wie "30d", "10y", "6m". Parser
82
- // kommt im Cleanup-Job (Sprint 2). `reference` ist das Field das den
83
- // Lebenszeit-Anker liefert (Default: `createdAt`). Sessions z.B. nutzen
84
- // `lastSeenAt` damit aktive Sessions nicht weggemueht werden.
85
- //
86
- // Siehe docs/plans/features/core-data-retention.md und Sprint 2 in roadmap.md.
87
- export type RetentionDef = {
88
- readonly keepFor: string;
89
- readonly strategy: "hardDelete" | "softDelete" | "anonymize" | "blockDelete";
90
- readonly reference?: string;
91
- };
92
-
93
- export type TextFieldDef = {
94
- readonly type: "text";
95
- readonly maxLength?: number;
96
- readonly required?: boolean;
97
- readonly searchable?: boolean;
98
- readonly sortable?: boolean;
99
- /** Author erlaubt Screen-Filter auf diesem Feld (Tier 2.7c).
100
- * Boot-Validator weist Filter mit `filterable !== true` zurück.
101
- * Default: false — analog zu `sortable`, opt-in. */
102
- readonly filterable?: boolean;
103
- readonly encrypted?: boolean;
104
- readonly sensitive?: boolean;
105
- readonly format?: "email" | "url" | "phone";
106
- readonly default?: string;
107
- readonly access?: FieldAccess;
108
- /** Mehrzeiliger Text — DefaultInput rendert dann ein <textarea> statt
109
- * <input type="text">. `true` = Default 4 Zeilen, `{ rows: N }` =
110
- * explizite Höhe. Search/sort/encrypt verhalten sich unverändert
111
- * identisch zu single-line — nur die Render-Surface wechselt. */
112
- readonly multiline?: boolean | { readonly rows?: number };
113
- } & PiiAnnotations;
114
-
115
- /**
116
- * Long-form text content — source-code, markdown, blog-posts, email-
117
- * templates, anything that can be megabytes large. Bewusst MINIMALE
118
- * Surface gegen `text`:
119
- *
120
- * - **Kein `sortable`**: ORDER BY auf 100 KB-Strings kostet I/O ohne
121
- * sinnvolles UX-Outcome (lex-Sortierung von Code ist Nonsense).
122
- * - **Kein `searchable`**: ILIKE/Substring-Suche auf langen Texten
123
- * skaliert nicht. Wer wirklich Volltextsuche will, nimmt den
124
- * SearchAdapter (Meilisearch) — der hat eine eigene Pipeline mit
125
- * Tokenizer + Index, NICHT diesen field-flag.
126
- * - **Kein `filterable`**: WHERE auf langen Strings same Story wie
127
- * sortable.
128
- * - **Kein `format`**: email/url/phone sind kurz definierte Inputs,
129
- * longText ist per Definition unstrukturiert.
130
- *
131
- * Type-level enforcement statt convention: wer sortable/searchable
132
- * braucht, nimmt `text` (mit den entsprechenden Skalierungs-Trade-offs).
133
- * DB-mapping ist identisch zu text (Postgres `text` ist unbounded).
134
- */
135
- export type LongTextFieldDef = {
136
- readonly type: "longText";
137
- /** Optionale soft-Cap. Default unbounded (= Postgres-text-limit, 1 GB).
138
- * Nützlich für defensive Caps wie 1 MB damit ein verirrter Browser-
139
- * Paste nicht die DB sprengt. */
140
- readonly maxLength?: number;
141
- readonly required?: boolean;
142
- readonly encrypted?: boolean;
143
- readonly sensitive?: boolean;
144
- readonly default?: string;
145
- readonly access?: FieldAccess;
146
- readonly multiline?: boolean | { readonly rows?: number };
147
- } & PiiAnnotations;
148
-
149
- export type BooleanFieldDef = {
150
- readonly type: "boolean";
151
- readonly required?: boolean;
152
- readonly sortable?: boolean;
153
- readonly filterable?: boolean;
154
- readonly sensitive?: boolean;
155
- readonly default?: boolean;
156
- readonly access?: FieldAccess;
157
- };
158
-
159
- export type SelectFieldDef<TOptions extends readonly string[] = readonly string[]> = {
160
- readonly type: "select";
161
- readonly options: TOptions;
162
- readonly required?: boolean;
163
- readonly sortable?: boolean;
164
- readonly filterable?: boolean;
165
- readonly sensitive?: boolean;
166
- readonly default?: TOptions[number];
167
- readonly access?: FieldAccess;
168
- } & PiiAnnotations;
169
-
170
- // Mehrere Werte aus einer festen Options-Liste — UI rendert als
171
- // Checkbox-/Multi-Select-Kontrolle. Storage: jsonb-Array<string>;
172
- // jeder Eintrag muss in `options` enthalten sein.
173
- //
174
- // Wann statt `select`: wenn der User mehr als einen Wert gleichzeitig
175
- // auswählen darf (Führerscheinklassen, Tags, Sprachen, Skills).
176
- // Wann statt `embedded` mit Booleans: wenn die Option-Liste nicht
177
- // hardcoded sein soll oder bei mehr als ~5 Optionen — sonst explodiert
178
- // das embedded-Schema.
179
- //
180
- // Ordering: das Array bewahrt die Caller-Reihenfolge (jsonb-array, nicht
181
- // set). Das Framework dedupliziert beim Schreiben nicht — Validator
182
- // rejected Duplikate erst wenn Bedarf da ist.
183
- export type MultiSelectFieldDef<TOptions extends readonly string[] = readonly string[]> = {
184
- readonly type: "multiSelect";
185
- readonly options: TOptions;
186
- readonly required?: boolean;
187
- readonly filterable?: boolean;
188
- readonly sensitive?: boolean;
189
- /** Default-Auswahl. Jeder Eintrag muss in `options` sein (Boot-Validator). */
190
- readonly default?: readonly TOptions[number][];
191
- readonly access?: FieldAccess;
192
- } & PiiAnnotations;
193
-
194
- export type NumberFieldDef = {
195
- readonly type: "number";
196
- readonly required?: boolean;
197
- readonly sortable?: boolean;
198
- readonly filterable?: boolean;
199
- readonly sensitive?: boolean;
200
- readonly default?: number;
201
- readonly access?: FieldAccess;
202
- // Write-boundary constraints (Zod-level, no migration/storage impact — the
203
- // Postgres column stays a plain numeric). Opt-in, so existing entities are
204
- // unaffected.
205
- readonly min?: number;
206
- readonly integer?: boolean;
207
- } & PiiAnnotations;
208
-
209
- /**
210
- * 64-bit-Integer-Spalte fuer Audit-Counter, Byte-Sizes, Event-IDs und
211
- * andere Werte die >2^31 (~2.1 Mrd) wandern koennen. Storage als
212
- * Postgres `bigint`, JS-Round-trip als `number` (mode:"number" — sicher
213
- * bis 2^53 ≈ 9 PB, JSON-serialisierbar). Wer >2^53 braucht (rare),
214
- * nutzt einen `text`-Field mit eigenem Codec.
215
- *
216
- * Vorrang vor `NumberFieldDef`-(integer 32-bit-Cap, ~2.1 GB) immer dann
217
- * wenn der Wert physisch ueber dieses Limit klettern kann: Bytes,
218
- * Events, Counters in High-Throughput-Apps, Cumulative-Sums. Money
219
- * hat dafuer den eigenen `MoneyFieldDef` (mit Currency-Spalte).
220
- */
221
- export type BigIntFieldDef = {
222
- readonly type: "bigInt";
223
- readonly required?: boolean;
224
- readonly sortable?: boolean;
225
- readonly filterable?: boolean;
226
- readonly sensitive?: boolean;
227
- readonly default?: number;
228
- readonly access?: FieldAccess;
229
- } & PiiAnnotations;
230
-
231
- /**
232
- * Exact decimal — Postgres `numeric(precision, scale)`. For values that need
233
- * fractional precision the integer `number` field (32-bit int) and `money`
234
- * field (BIGINT minor units + currency) can't hold: interest rates,
235
- * percentages, ratios, measurements.
236
- *
237
- * `precision` = total significant digits, `scale` = digits after the decimal
238
- * point (both required — no silent default that could truncate). pg returns
239
- * `numeric` as a string to preserve precision; the read-codec surfaces it as
240
- * a JS `number` (safe ≤ 2^53, same trade-off as `bigInt` mode:"number" — a
241
- * value past that boundary loses precision, so keep `precision - scale` ≤ 15).
242
- */
243
- export type DecimalFieldDef = {
244
- readonly type: "decimal";
245
- readonly precision: number;
246
- readonly scale: number;
247
- readonly required?: boolean;
248
- readonly sortable?: boolean;
249
- readonly filterable?: boolean;
250
- readonly sensitive?: boolean;
251
- readonly default?: number;
252
- readonly access?: FieldAccess;
253
- } & PiiAnnotations;
254
-
255
- export type MoneyFieldDef = {
256
- readonly type: "money";
257
- readonly required?: boolean;
258
- readonly sortable?: boolean;
259
- readonly filterable?: boolean;
260
- readonly sensitive?: boolean;
261
- readonly access?: FieldAccess;
262
- };
263
-
264
- // Reference-Field (Tier 2.7e-3) — FK-Style Verweis auf eine andere
265
- // Entity. Gespeichert als UUID-Spalte (uuid type), Read-Side liefert
266
- // optional die referenced Row mit (Tier 2.7e-4 eagerload).
267
- //
268
- // `entity` akzeptiert zwei Formen:
269
- // - kurz ("customer") — same-feature reference, Default-Pfad.
270
- // - qualifiziert ("users:user") — cross-feature, Format
271
- // "<featureName>:<entityName>". Renderer baut die Lookup-Query-QN
272
- // gegen das angegebene Feature (`users:query:user:list`).
273
- //
274
- // `labelField` (optional) — welches Feld der referenced Entity wird
275
- // im Select-Dropdown als Label gezeigt. Default: "id". Best practice
276
- // ist ein menschlich-lesbares Feld wie "name", "title", "email".
277
- export type ReferenceFieldDef = {
278
- readonly type: "reference";
279
- readonly entity: string;
280
- readonly required?: boolean;
281
- readonly filterable?: boolean;
282
- readonly sensitive?: boolean;
283
- readonly access?: FieldAccess;
284
- /** Welches Feld der referenced Entity als Display-Label im
285
- * Select-Dropdown erscheint. Default: "id". Boot-Validator pinst
286
- * dass das Feld auf der referenced Entity existiert. */
287
- readonly labelField?: string;
288
- /** Multi-Reference (Tier 2.7e-Multi): Wert ist ein Array von UUIDs
289
- * statt single UUID. Storage als jsonb-Array<uuid>. UI rendert
290
- * Multi-Select-Combobox mit Tag-Anzeige der gewählten Items. */
291
- readonly multiple?: boolean;
292
- };
293
-
294
- // --- Currency ---
295
-
296
- export const DEFAULT_CURRENCIES = [
297
- "EUR",
298
- "USD",
299
- "GBP",
300
- "CHF",
301
- "JPY",
302
- "SEK",
303
- "NOK",
304
- "DKK",
305
- "PLN",
306
- "CZK",
307
- "CAD",
308
- "AUD",
309
- "NZD",
310
- "CNY",
311
- "INR",
312
- ] as const;
313
-
314
- export type DefaultCurrency = (typeof DEFAULT_CURRENCIES)[number];
315
-
316
- // --- Embedded Object ---
317
-
318
- export type EmbeddedSubFieldDef = {
319
- readonly type: "text" | "number" | "boolean" | "date";
320
- readonly required?: boolean;
321
- readonly searchable?: boolean;
322
- readonly access?: FieldAccess;
323
- };
324
-
325
- export type EmbeddedFieldDef = {
326
- readonly type: "embedded";
327
- readonly required?: boolean;
328
- readonly sensitive?: boolean;
329
- readonly schema: Readonly<Record<string, EmbeddedSubFieldDef>>;
330
- readonly access?: FieldAccess;
331
- } & PiiAnnotations;
332
-
333
- // Free-form jsonb — keys/shape NOT validated at write-time. Use for:
334
- // - Tenant-defined extension data (custom-fields-bundle uses this for
335
- // `customFields` on host-entities — keys are dynamic per fieldDefinition)
336
- // - Configuration-blobs with shape that evolves outside Stammfeld-schema
337
- // - AI-inferred metadata where shape is provider-dependent
338
- //
339
- // Vs. embedded: embedded enforces a typed sub-schema; jsonb accepts any
340
- // JSON-shaped object. Read-side both map to Postgres `jsonb`. Default `{}`
341
- // + NOT NULL, identisch zu embedded.
342
- export type JsonbFieldDef = {
343
- readonly type: "jsonb";
344
- readonly sensitive?: boolean;
345
- readonly access?: FieldAccess;
346
- } & PiiAnnotations;
347
-
348
- // Legacy "date" — JS-Date-Object, semantisch unklar (Wall-Clock vs Instant).
349
- // Für neue Felder bevorzuge:
350
- // - `timestamp` für UTC-Instant ("wann ist das passiert")
351
- // - `locatedTimestamp(name)` Helper für Termine die an einem Ort
352
- // stattfinden ("Pickup um 10:00 in Lissabon")
353
- // - (kommt) `plainDate` für Kalender-Daten ohne Uhrzeit (z.B. Geburtstag)
354
- // Siehe docs/plans/architecture/timezones.md
355
- export type DateFieldDef = {
356
- readonly type: "date";
357
- readonly required?: boolean;
358
- readonly sortable?: boolean;
359
- readonly filterable?: boolean;
360
- readonly sensitive?: boolean;
361
- readonly access?: FieldAccess;
362
- /** Erlaubte Datumsgrenzen als ISO `yyyy-mm-dd` (z.B. Geburtsdatum nicht
363
- * in der Zukunft: `max` = heute). Begrenzt den Picker und wird vom
364
- * Zod-Schema beim Write durchgesetzt. */
365
- readonly min?: string;
366
- readonly max?: string;
367
- /** Format/Locale-Override für Anzeige und Eingabe-Parsing (z.B.
368
- * "de-DE"). Default = App-Locale. */
369
- readonly locale?: string;
370
- } & PiiAnnotations;
371
-
372
- // UTC-Instant (Temporal.Instant). Für Ereignisse die zu einem bestimmten
373
- // Augenblick passieren, ohne Location-Bezug: createdAt, loginAt, actualPickupAt.
374
- // JSON-Form: ISO-UTC-String "2026-04-18T10:00:00Z" via .toJSON().
375
- //
376
- // Mit `locatedBy: "<name>Tz"` markiert: bildet ein Wall-Clock+TZ-Pair mit dem
377
- // referenzierten tz-Feld. JSON-Form wird dann zwei Felder ({ at, tz }), DB
378
- // speichert Wall-Clock+tz und konvertiert transparent (siehe DB-Wrapper,
379
- // kommt in einer späteren Iteration).
380
- //
381
- // Verwendung über den `locatedTimestamp(name)` Helper, der das Pair atomar
382
- // erzeugt und die Marker korrekt verdrahtet.
383
- export type TimestampFieldDef = {
384
- readonly type: "timestamp";
385
- readonly required?: boolean;
386
- readonly sortable?: boolean;
387
- readonly filterable?: boolean;
388
- readonly sensitive?: boolean;
389
- readonly access?: FieldAccess;
390
- /**
391
- * Marker: dieses Timestamp-Feld ist Wall-Clock-Zeit an einem Ort.
392
- * Wert ist der Name des begleitenden tz-Felds (IANA-Zone).
393
- *
394
- * Beispiel: `locatedTimestamp("pickup")` erzeugt
395
- * { pickupAt: { type: "timestamp", locatedBy: "pickupTz" }, pickupTz: { type: "tz" } }
396
- */
397
- readonly locatedBy?: string;
398
- /** Erlaubte Grenzen als ISO-Datetime. Begrenzt den Picker auf
399
- * Tages-Granularität; die exakte Uhrzeit-Grenze setzt das Zod-Schema
400
- * beim Write durch. */
401
- readonly min?: string;
402
- readonly max?: string;
403
- /** Format/Locale-Override für Anzeige und Eingabe-Parsing. Default =
404
- * App-Locale. */
405
- readonly locale?: string;
406
- } & PiiAnnotations;
407
-
408
- // IANA-Zonenname (z.B. "Europe/Berlin", "America/Los_Angeles").
409
- // Wird via `Intl.supportedValuesOf("timeZone")` validiert (kommt im
410
- // Zod-Validator-Schritt). Eigener Field-Typ damit Type-Safety + Storage
411
- // (TEXT-Spalte) korrekt sind und der `locatedBy`-Marker eindeutig auflöst.
412
- export type TzFieldDef = {
413
- readonly type: "tz";
414
- readonly required?: boolean;
415
- readonly sensitive?: boolean;
416
- readonly access?: FieldAccess;
417
- } & PiiAnnotations;
418
-
419
- // Wall-Clock-Termin an einem Ort als ATOMARES Konzept.
420
- // EIN Feld in der Schema-Definition, ZWEI Spalten in der DB
421
- // (`<name>_utc TIMESTAMPTZ` + `<name>_tz TEXT`), DREI Felder im API-Object
422
- // ({ at, tz, utc }). Drizzle-Wrapper macht die Konvertierung transparent —
423
- // Feature-Code sieht das 3-Felder-Object beim Read und schreibt
424
- // { at, tz } beim Insert (utc wird berechnet).
425
- //
426
- // API-Form:
427
- // Write: { at: "2026-04-15T10:00:00", tz: "Europe/Lisbon" }
428
- // Read: { at: "2026-04-15T10:00:00", tz: "Europe/Lisbon", utc: "2026-04-15T09:00:00Z" }
429
- //
430
- // Default-Sicht für `at`: Wall-Clock am Ort (`tz`). Wer User-lokale Sicht
431
- // will, projeziert `utc` separat per ctx.tz.fromInstantInZone(utc, userTz).
432
- //
433
- // Ersetzt das alte `locatedTimestamp(name)` Helper-Pattern (zwei separate
434
- // Pair-Felder). Sauberer Single-Field-Typ + Auto-Convert-Logik.
435
- //
436
- // Siehe docs/plans/architecture/timezones.md.
437
- export type LocatedTimestampFieldDef = {
438
- readonly type: "locatedTimestamp";
439
- readonly required?: boolean;
440
- readonly sortable?: boolean;
441
- readonly filterable?: boolean;
442
- readonly sensitive?: boolean;
443
- readonly access?: FieldAccess;
444
- /** Erlaubte Grenzen als ISO-Datetime (Wall-Clock). Begrenzt den Picker
445
- * auf Tages-Granularität; die exakte Uhrzeit-Grenze setzt das Zod-Schema
446
- * beim Write durch. */
447
- readonly min?: string;
448
- readonly max?: string;
449
- /** Format/Locale-Override für Anzeige und Eingabe-Parsing. Default =
450
- * App-Locale. */
451
- readonly locale?: string;
452
- } & PiiAnnotations;
453
-
454
- export type FileFieldDef = {
455
- readonly type: "file";
456
- readonly required?: boolean;
457
- readonly maxSize?: string;
458
- readonly accept?: readonly string[];
459
- readonly access?: FieldAccess;
460
- };
461
-
462
- export type ImageFieldDef = {
463
- readonly type: "image";
464
- readonly required?: boolean;
465
- readonly maxSize?: string;
466
- readonly accept?: readonly string[];
467
- readonly thumbnails?: boolean;
468
- readonly access?: FieldAccess;
469
- };
470
-
471
- export type FilesFieldDef = {
472
- readonly type: "files";
473
- readonly maxSize?: string;
474
- readonly accept?: readonly string[];
475
- readonly maxCount?: number;
476
- readonly access?: FieldAccess;
477
- };
478
-
479
- export type ImagesFieldDef = {
480
- readonly type: "images";
481
- readonly maxSize?: string;
482
- readonly accept?: readonly string[];
483
- readonly maxCount?: number;
484
- readonly thumbnails?: boolean;
485
- readonly access?: FieldAccess;
486
- };
487
-
488
- export type FieldDefinition =
489
- | TextFieldDef
490
- | LongTextFieldDef
491
- | BooleanFieldDef
492
- | SelectFieldDef
493
- | MultiSelectFieldDef
494
- | NumberFieldDef
495
- | BigIntFieldDef
496
- | DecimalFieldDef
497
- | MoneyFieldDef
498
- | ReferenceFieldDef
499
- | EmbeddedFieldDef
500
- | JsonbFieldDef
501
- | DateFieldDef
502
- | TimestampFieldDef
503
- | TzFieldDef
504
- | LocatedTimestampFieldDef
505
- | FileFieldDef
506
- | ImageFieldDef
507
- | FilesFieldDef
508
- | ImagesFieldDef;
509
-
510
- // Union of all field variants that represent uploaded files. They share
511
- // `maxSize` and `accept`, which is what upload validation cares about.
512
- export type AnyFileFieldDef = FileFieldDef | ImageFieldDef | FilesFieldDef | ImagesFieldDef;
513
-
514
- export function isFileField(field: FieldDefinition | undefined): field is AnyFileFieldDef {
515
- if (!field) return false;
516
- return (
517
- field.type === "file" ||
518
- field.type === "image" ||
519
- field.type === "files" ||
520
- field.type === "images"
521
- );
522
- }
523
-
524
- // --- Derived (computed) fields ---
525
- //
526
- // A derived field is read-time only: its value is computed from the stored row
527
- // (and the clock) when a list/detail query runs, never persisted. It lives in
528
- // `EntityDefinition.derivedFields` — deliberately NOT in `fields`, so it
529
- // produces no DB column, never enters a write schema, and can't be the target
530
- // of an entityEdit. A declarative `entityList` can name it like any column and
531
- // the view-model renders the appended value.
532
- //
533
- // LIMIT: derived columns are DISPLAY ONLY. A declarative `entityList` loads its
534
- // rows server-side and a column-header sort round-trips to the server, where
535
- // `executor.list` sorts/filters/searches over real SQL columns — so a derived
536
- // field (no column) silently no-ops. There is no client-side sort path. Need a
537
- // derived value sortable/searchable? Materialize it as a stored field; it then
538
- // rides the existing `searchable`/`sortable` machinery. Time-dependent values
539
- // (as-of-today) can't be materialized without a daily re-index anyway.
540
-
541
- /** Display type a derived value formats as — drives the column's renderer
542
- * choice in the view-model, parallel to FieldDefinition["type"]. Single-column
543
- * types only: `money` is excluded because it needs a `<name>Currency`
544
- * companion column a derived field has no place to put — use `number`/
545
- * `decimal` plus a `{ format: "currency" }` column renderer instead. */
546
- export type DerivedValueType = "text" | "number" | "decimal" | "boolean" | "date" | "timestamp";
547
-
548
- /** Clock injected into `derive` — never read `Temporal.Now`/`Date` inside a
549
- * derive body (no-date-api guard + testability). The list-query handler passes
550
- * the read-time instant; unit tests pass a fixed one. */
551
- export type DeriveContext = {
552
- readonly asOf: Temporal.Instant;
553
- };
554
-
555
- export type DerivedFieldDef = {
556
- readonly valueType: DerivedValueType;
557
- /** Pure function of the stored row + clock. Returns the JSON-safe display
558
- * value (e.g. integer minor units for a currency column, ISO string for a
559
- * `date`). */
560
- readonly derive: (row: Readonly<Record<string, unknown>>, ctx: DeriveContext) => unknown;
561
- };
562
-
563
- export type DerivedFieldsMap = Readonly<Record<string, DerivedFieldDef>>;
564
-
565
- /** Client-facing projection of DerivedFieldDef — `derive` is server-only and
566
- * not JSON-safe (would trip the output-walk guard), so the browser schema
567
- * only ever carries `valueType`. A real `Pick`, not a same-shape cast: TS
568
- * itself proves `derive` isn't there instead of a `{ valueType } as
569
- * DerivedFieldDef` cast lying about a field that's actually missing. */
570
- export type ClientDerivedFieldDef = Pick<DerivedFieldDef, "valueType">;
571
-
572
- // --- Entity ---
573
-
574
- // --- State Transitions ---
575
-
576
- export type TransitionMap = Readonly<Record<string, readonly string[]>>;
577
-
578
- /** Composite-Index auf einer Entity. Spalten werden via field-Name
579
- * referenziert (camelCase). buildEntityTable mapped sie auf snake_case-
580
- * Spaltennamen und benennt den Index nach Convention:
581
- *
582
- * <table>_<col1>_<col2>_idx (non-unique)
583
- * <table>_<col1>_<col2>_unique (unique)
584
- *
585
- * Eine `name`-Override ist erlaubt — Convention-Bruch in Bestandscode
586
- * vermeidet Migration-Churn beim Refactor.
587
- *
588
- * Single-column indices über `tenantId` sind redundant (buildEntityTable
589
- * legt die immer automatisch an); die Boot-Validation warnt (außer
590
- * `{ unique: true }` — semantische 1:1-Constraint, kein Performance-Hint). */
591
- export type EntityIndexDef = {
592
- readonly columns: readonly [string, ...string[]];
593
- readonly unique?: boolean;
594
- readonly name?: string;
595
- /**
596
- * Optional SQL-Fragment fuer Partial-Index — `CREATE [UNIQUE] INDEX
597
- * ... WHERE <condition>`. Postgres-Pattern fuer "Index nur unter
598
- * bestimmten Bedingungen", typisches Beispiel: ExportJob-Idempotency
599
- * `UNIQUE(userId) WHERE status IN ('pending', 'running')`.
600
- *
601
- * Caller baut das Fragment via drizzle-orm `sql\`...\``-Tagged-
602
- * Template. table-builder.ts emittiert `.where(def.where)` auf den
603
- * Drizzle-IndexBuilder — wirkt sowohl fuer unique- als auch fuer
604
- * non-unique-Indexes (PG erlaubt beides; non-unique partial nutzt
605
- * man z.B. fuer scharfe BTREE-Indexes nur auf einer Status-Teilmenge
606
- * statt voller Tabelle).
607
- */
608
- readonly where?: unknown;
609
- };
610
-
611
- export type FieldsMap = Readonly<Record<string, FieldDefinition>>;
612
-
613
- export type EntityDefinition<F extends FieldsMap = FieldsMap> = {
614
- readonly table?: string;
615
- readonly fields: F;
616
- readonly softDelete?: boolean;
617
- /** This aggregate's event stream lives on SYSTEM_TENANT_ID rather than the
618
- * creator's tenant. Opt-in per entity (NOT inherited from r.systemScope()):
619
- * only for genuinely tenant-independent aggregates like `user`. The first
620
- * event (create) is what's routed; updates resolve the stream tenant upstream. */
621
- readonly systemStream?: boolean;
622
- readonly searchWeight?: number;
623
- readonly defaultCurrency?: string;
624
- /** Allowed state transitions per field. Boot validates against select options. */
625
- readonly transitions?: Readonly<Record<string, TransitionMap>>;
626
- /** Composite-Indices über mehrere Felder. Single-column FK-Indices und
627
- * der tenant_id-Index werden weiterhin automatisch von buildEntityTable
628
- * angelegt — diese Liste ist nur für Custom-Indices die der Author
629
- * explizit deklariert (z.B. `{ unique: true, columns: ["key", "tenantId", "userId"] }`). */
630
- readonly indexes?: readonly EntityIndexDef[];
631
- /**
632
- * PK-Typ der Entity.
633
- * - `"serial"` (default): bigserial integer — schneller, kompakter, perfekt für klassische CRUD-Entities.
634
- * - `"uuid"`: uuid mit `gen_random_uuid()` default — verpflichtend für Entities deren `id` als
635
- * Foreign-Key-Wert in multi-tenant Kontexten reist (z.B. `tenant.id` IS der `tenantId`). Auch für
636
- * ES-Aggregate (Phase 2+) notwendig, da Events per UUID aggregiert werden.
637
- */
638
- readonly idType?: "serial" | "uuid";
639
- /**
640
- * Row-level ownership rules (H.2). read runs as WHERE-predicate on list/
641
- * detail/queryProjection, scoping which rows the caller sees. write runs
642
- * pre-save on create/update/delete, scoping which rows the caller may
643
- * modify (Straddle-safe, multi-role atomic — see engine/ownership.ts).
644
- *
645
- * Keys are role names; rules use the `from()` helper or `{ where }`
646
- * escape hatch. Entity-level ownership is AND-ed with tenant isolation —
647
- * a user's tenant filter still applies first.
648
- */
649
- readonly access?: {
650
- readonly read?: OwnershipMap;
651
- readonly write?: OwnershipMap;
652
- };
653
- /**
654
- * Default-Retention-Policy fuer diese Entity. Tenant-Admin kann via
655
- * Compliance-Profile + Tenant-Override (Sprint 2) uebersteuern.
656
- * Cleanup-Job (Sprint 2) verarbeitet die Strategy:
657
- *
658
- * - "hardDelete" → Row physisch weg nach keepFor
659
- * - "softDelete" → deletedAt = now() (mit core-soft-delete-Feature)
660
- * - "anonymize" → Felder mit `anonymize`-Funktion ueberschrieben,
661
- * Row bleibt
662
- * - "blockDelete" → Cleanup-Job ignoriert; User-Forget loest
663
- * stattdessen anonymize aus. Buchhaltung, Mandate,
664
- * Patientenakten.
665
- *
666
- * Siehe docs/plans/features/core-data-retention.md.
667
- */
668
- readonly retention?: RetentionDef;
669
- /**
670
- * Read-time computed fields, keyed by name. Not stored, not a DB column,
671
- * not writable — appended to each row by the list/detail query handler and
672
- * nameable as a column in a declarative `entityList`. See DerivedFieldDef.
673
- */
674
- readonly derivedFields?: DerivedFieldsMap;
675
- };
1
+ // Legacy path re-exported for callers still importing this module directly.
2
+ export type * from "@cosmicdrift/kumiko-types/fields";