@cosmicdrift/kumiko-framework 0.363.0 → 0.365.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 (281) hide show
  1. package/AGENTS.md +338 -0
  2. package/agents/examples/access-control.md +355 -0
  3. package/agents/examples/anonymous-access-multitenant.md +446 -0
  4. package/agents/examples/anonymous-access.md +288 -0
  5. package/agents/examples/apex-landing.md +528 -0
  6. package/agents/examples/apex-surface-auth.md +355 -0
  7. package/agents/examples/api-worker-split.md +434 -0
  8. package/agents/examples/auth-claims.md +351 -0
  9. package/agents/examples/auth-foundation-providers.md +155 -0
  10. package/agents/examples/auth-mfa.md +319 -0
  11. package/agents/examples/basic-entity.md +420 -0
  12. package/agents/examples/boot-check.md +122 -0
  13. package/agents/examples/chat-channels.md +254 -0
  14. package/agents/examples/compliance-profiles-demo.md +307 -0
  15. package/agents/examples/cross-feature-events.md +317 -0
  16. package/agents/examples/crypto-shredding-hr.md +314 -0
  17. package/agents/examples/currencies-global.md +609 -0
  18. package/agents/examples/currencies-per-tenant.md +453 -0
  19. package/agents/examples/custom-fields-basic.md +315 -0
  20. package/agents/examples/custom-handlers.md +444 -0
  21. package/agents/examples/dashboard-stats.md +229 -0
  22. package/agents/examples/delivery-notifications.md +432 -0
  23. package/agents/examples/designer-demo.md +86 -0
  24. package/agents/examples/e2e-generator.md +211 -0
  25. package/agents/examples/embedded-entity-form.md +598 -0
  26. package/agents/examples/embedded.md +649 -0
  27. package/agents/examples/encrypted-tenant-config.md +414 -0
  28. package/agents/examples/error-contract.md +535 -0
  29. package/agents/examples/event-sourcing.md +1287 -0
  30. package/agents/examples/feature-toggles.md +274 -0
  31. package/agents/examples/field-access.md +271 -0
  32. package/agents/examples/files-post-processing.md +433 -0
  33. package/agents/examples/i18n.md +166 -0
  34. package/agents/examples/idempotency.md +179 -0
  35. package/agents/examples/inbound-mail-triage.md +315 -0
  36. package/agents/examples/lane-routing.md +246 -0
  37. package/agents/examples/ledger-banking.md +215 -0
  38. package/agents/examples/ledger-invoicing.md +236 -0
  39. package/agents/examples/legal-pages.md +393 -0
  40. package/agents/examples/lifecycle-hooks.md +368 -0
  41. package/agents/examples/managed-config.md +428 -0
  42. package/agents/examples/managed-pages.md +353 -0
  43. package/agents/examples/notes-history-basic.md +346 -0
  44. package/agents/examples/ownership.md +404 -0
  45. package/agents/examples/pdf-audit-export.md +306 -0
  46. package/agents/examples/pipeline-basics.md +792 -0
  47. package/agents/examples/public-share-token.md +351 -0
  48. package/agents/examples/rate-limiting.md +211 -0
  49. package/agents/examples/realtime-sse.md +187 -0
  50. package/agents/examples/record-detail-layout.md +297 -0
  51. package/agents/examples/reference-data.md +151 -0
  52. package/agents/examples/relations.md +280 -0
  53. package/agents/examples/screens-nav.md +353 -0
  54. package/agents/examples/search.md +233 -0
  55. package/agents/examples/secrets-demo.md +417 -0
  56. package/agents/examples/security-baseline.md +187 -0
  57. package/agents/examples/seed-migration.md +99 -0
  58. package/agents/examples/session-revocation.md +250 -0
  59. package/agents/examples/state-machine.md +920 -0
  60. package/agents/examples/tags-basic.md +401 -0
  61. package/agents/examples/tenant-isolation.md +173 -0
  62. package/agents/examples/tenant-settings.md +275 -0
  63. package/agents/examples/tier-admin.md +347 -0
  64. package/agents/examples/timezones.md +366 -0
  65. package/agents/examples/user-data-rights.md +418 -0
  66. package/agents/examples/user-profile.md +226 -0
  67. package/agents/examples/webhook-step.md +597 -0
  68. package/agents/examples/wizard-form.md +379 -0
  69. package/agents/examples/workflow-engine.md +1731 -0
  70. package/agents/examples/writeform-section.md +300 -0
  71. package/agents/examples.md +75 -0
  72. package/agents/features.md +768 -0
  73. package/agents/widgets.md +181 -0
  74. package/dist/api/auth-middleware.d.ts +2 -0
  75. package/dist/api/auth-middleware.js +7 -0
  76. package/dist/api/auth-routes.d.ts +0 -1
  77. package/dist/api/auth-routes.js +49 -46
  78. package/dist/api/extra-route.d.ts +3 -0
  79. package/dist/api/index.d.ts +2 -2
  80. package/dist/api/index.js +1 -1
  81. package/dist/api/routes.js +46 -17
  82. package/dist/api/server.d.ts +4 -3
  83. package/dist/api/server.js +39 -10
  84. package/dist/api/session-cookies.d.ts +1 -0
  85. package/dist/api/session-cookies.js +20 -5
  86. package/dist/api/sse-route.js +16 -1
  87. package/dist/bun-db/connection.js +3 -16
  88. package/dist/bun-db/index.d.ts +1 -1
  89. package/dist/bun-db/index.js +1 -1
  90. package/dist/bun-db/query.d.ts +15 -3
  91. package/dist/bun-db/query.js +76 -24
  92. package/dist/crypto/owner-transition-rekey.d.ts +1 -0
  93. package/dist/crypto/owner-transition-rekey.js +7 -3
  94. package/dist/crypto/pii-field-encryption.d.ts +1 -0
  95. package/dist/crypto/pii-field-encryption.js +9 -2
  96. package/dist/crypto/subject-resolver.d.ts +1 -0
  97. package/dist/crypto/subject-resolver.js +7 -0
  98. package/dist/db/api.d.ts +4 -3
  99. package/dist/db/apply-entity-event.js +26 -2
  100. package/dist/db/blind-index-cleanup.js +2 -1
  101. package/dist/db/bun-provider.js +9 -18
  102. package/dist/db/collect-table-metas.js +11 -7
  103. package/dist/db/dialect.d.ts +1 -1
  104. package/dist/db/dialect.js +2 -0
  105. package/dist/db/eagerload.js +4 -3
  106. package/dist/db/event-store-executor-context.d.ts +6 -6
  107. package/dist/db/event-store-executor-context.js +25 -22
  108. package/dist/db/event-store-executor-read.js +31 -16
  109. package/dist/db/event-store-executor-write.d.ts +1 -1
  110. package/dist/db/event-store-executor-write.js +84 -63
  111. package/dist/db/index.d.ts +2 -1
  112. package/dist/db/index.js +2 -1
  113. package/dist/db/lock-guard.d.ts +4 -0
  114. package/dist/db/lock-guard.js +14 -0
  115. package/dist/db/migrate-generator.js +1 -1
  116. package/dist/db/migrate-runner.d.ts +1 -0
  117. package/dist/db/migrate-runner.js +64 -38
  118. package/dist/db/queries/backfill-pii.js +63 -25
  119. package/dist/db/queries/event-consumer.d.ts +6 -6
  120. package/dist/db/queries/event-consumer.js +2 -1
  121. package/dist/db/queries/event-store-transfer.js +15 -4
  122. package/dist/db/queries/event-store.d.ts +1 -1
  123. package/dist/db/queries/event-store.js +11 -10
  124. package/dist/db/queries/pii-backfill-state.js +3 -2
  125. package/dist/db/queries/postgres-array-literal.d.ts +2 -0
  126. package/dist/db/queries/postgres-array-literal.js +6 -0
  127. package/dist/db/queries/projection-rebuild.d.ts +1 -1
  128. package/dist/db/queries/projection-rebuild.js +3 -2
  129. package/dist/db/queries/shadow-swap.d.ts +1 -1
  130. package/dist/db/queries/shadow-swap.js +12 -8
  131. package/dist/db/query.d.ts +1 -1
  132. package/dist/db/query.js +1 -1
  133. package/dist/db/retired-framework-tables.js +22 -4
  134. package/dist/db/table-builder.js +6 -2
  135. package/dist/db/tenant-db-runner.d.ts +4 -0
  136. package/dist/db/tenant-db-runner.js +17 -0
  137. package/dist/db/tenant-db.d.ts +4 -0
  138. package/dist/db/tenant-db.js +50 -5
  139. package/dist/derivatives/delete-stored-file.d.ts +4 -0
  140. package/dist/derivatives/delete-stored-file.js +9 -0
  141. package/dist/derivatives/derivatives-context.js +1 -1
  142. package/dist/derivatives/index.d.ts +1 -1
  143. package/dist/derivatives/index.js +1 -1
  144. package/dist/engine/boot-validator/access-declarations.js +2 -2
  145. package/dist/engine/boot-validator/api-ext.js +3 -3
  146. package/dist/engine/boot-validator/boot-check.js +1 -1
  147. package/dist/engine/boot-validator/config-deps.js +13 -13
  148. package/dist/engine/boot-validator/detail-screens.js +1 -1
  149. package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
  150. package/dist/engine/boot-validator/entity-handler.js +87 -35
  151. package/dist/engine/boot-validator/entity-list-screens.js +2 -2
  152. package/dist/engine/boot-validator/handler-field-references.js +19 -9
  153. package/dist/engine/boot-validator/i18n-keys.js +2 -2
  154. package/dist/engine/boot-validator/nav.js +1 -1
  155. package/dist/engine/boot-validator/pii-retention.js +8 -8
  156. package/dist/engine/boot-validator/projection-list-screens.js +5 -5
  157. package/dist/engine/boot-validator/query-output-columns.js +7 -7
  158. package/dist/engine/boot-validator/query-refs.js +14 -2
  159. package/dist/engine/boot-validator/screens.d.ts +2 -1
  160. package/dist/engine/boot-validator/screens.js +153 -95
  161. package/dist/engine/boot-validator/url-prefill-fields.js +54 -35
  162. package/dist/engine/dedupe-features.js +31 -4
  163. package/dist/engine/define-handler.d.ts +2 -2
  164. package/dist/engine/define-handler.js +8 -1
  165. package/dist/engine/entity-handlers.js +14 -7
  166. package/dist/engine/feature-ast/extractors/handlers.js +2 -0
  167. package/dist/engine/feature-ast/pattern-change-schema.d.ts +2 -0
  168. package/dist/engine/feature-ast/pattern-change-schema.js +1 -0
  169. package/dist/engine/feature-ast/render.js +10 -2
  170. package/dist/engine/feature-entity-handlers.d.ts +2 -2
  171. package/dist/engine/feature-entity-handlers.js +12 -3
  172. package/dist/engine/handler-field-references.d.ts +21 -2
  173. package/dist/engine/handler-field-references.js +55 -17
  174. package/dist/engine/index.d.ts +3 -3
  175. package/dist/engine/index.js +1 -1
  176. package/dist/engine/schema-builder.js +2 -1
  177. package/dist/engine/screen-helpers.d.ts +1 -0
  178. package/dist/engine/screen-helpers.js +2 -2
  179. package/dist/engine/soft-delete-cleanup.js +10 -3
  180. package/dist/engine/steps/_read-source.js +5 -2
  181. package/dist/engine/steps/retry.js +2 -1
  182. package/dist/engine/steps/unsafe-projection-delete.js +2 -2
  183. package/dist/engine/steps/unsafe-projection-upsert.js +2 -2
  184. package/dist/engine/template-variables.d.ts +3 -0
  185. package/dist/engine/template-variables.js +3 -0
  186. package/dist/engine/tier-resolver-extension.d.ts +1 -0
  187. package/dist/engine/tier-resolver-extension.js +6 -0
  188. package/dist/engine/types/index.d.ts +1 -1
  189. package/dist/entrypoint/index.d.ts +2 -2
  190. package/dist/env/_zod-introspect.d.ts +1 -0
  191. package/dist/errors/classes.js +1 -0
  192. package/dist/errors/docs-url.js +6 -6
  193. package/dist/errors/index.d.ts +1 -1
  194. package/dist/errors/index.js +1 -1
  195. package/dist/errors/kumiko-error.d.ts +2 -0
  196. package/dist/errors/kumiko-error.js +2 -0
  197. package/dist/errors/subject-erased.d.ts +1 -0
  198. package/dist/errors/subject-erased.js +6 -0
  199. package/dist/errors/write-error-info.js +2 -0
  200. package/dist/event-store/admin-api.js +3 -2
  201. package/dist/event-store/errors.d.ts +9 -0
  202. package/dist/event-store/errors.js +20 -0
  203. package/dist/event-store/event-store.d.ts +6 -0
  204. package/dist/event-store/event-store.js +28 -6
  205. package/dist/event-store/index.d.ts +1 -1
  206. package/dist/event-store/index.js +1 -1
  207. package/dist/event-store/tenant-db-append.js +2 -2
  208. package/dist/files/assert-file-refs-belong-to-tenant.d.ts +4 -5
  209. package/dist/files/assert-file-refs-belong-to-tenant.js +7 -15
  210. package/dist/files/bind-pending-file-refs.d.ts +2 -0
  211. package/dist/files/bind-pending-file-refs.js +3 -3
  212. package/dist/files/bind-referenced-files.write.d.ts +1 -1
  213. package/dist/files/bind-referenced-files.write.js +22 -13
  214. package/dist/files/create-file-ref-from-bytes.d.ts +6 -3
  215. package/dist/files/create-file-ref-from-bytes.js +67 -4
  216. package/dist/files/create-file-ref.d.ts +24 -1
  217. package/dist/files/create-file-ref.js +67 -25
  218. package/dist/files/dispose-removed-file-refs.js +21 -6
  219. package/dist/files/feature.d.ts +1 -0
  220. package/dist/files/feature.js +2 -1
  221. package/dist/files/file-handle.d.ts +1 -1
  222. package/dist/files/file-handle.js +12 -0
  223. package/dist/files/file-routes.js +10 -6
  224. package/dist/files/index.d.ts +3 -3
  225. package/dist/files/index.js +2 -2
  226. package/dist/files/load-save-file-refs.d.ts +10 -0
  227. package/dist/files/load-save-file-refs.js +47 -0
  228. package/dist/files/purge-expired-file-ref-bytes.d.ts +4 -1
  229. package/dist/files/purge-expired-file-ref-bytes.js +46 -21
  230. package/dist/files/resolve-content-type.js +40 -4
  231. package/dist/files/storage-tracking.js +5 -4
  232. package/dist/files/types.js +5 -8
  233. package/dist/files/zip-entry-names.d.ts +1 -0
  234. package/dist/files/zip-entry-names.js +46 -0
  235. package/dist/jobs/index.d.ts +1 -1
  236. package/dist/jobs/job-runner.d.ts +5 -1
  237. package/dist/jobs/job-runner.js +124 -37
  238. package/dist/observability/escape-hatch-report.d.ts +2 -1
  239. package/dist/observability/escape-hatch-report.js +48 -7
  240. package/dist/observability/index.d.ts +1 -1
  241. package/dist/observability/index.js +1 -1
  242. package/dist/observability/metrics-handle.d.ts +1 -0
  243. package/dist/observability/metrics-handle.js +7 -0
  244. package/dist/observability/standard-metrics.js +4 -4
  245. package/dist/pipeline/cascade-handler.js +5 -2
  246. package/dist/pipeline/dispatch-batch.js +79 -31
  247. package/dist/pipeline/dispatch-query.d.ts +2 -1
  248. package/dist/pipeline/dispatch-query.js +6 -6
  249. package/dist/pipeline/dispatch-shared.d.ts +7 -2
  250. package/dist/pipeline/dispatch-shared.js +136 -48
  251. package/dist/pipeline/dispatch-stream.js +10 -4
  252. package/dist/pipeline/dispatch-write.js +34 -22
  253. package/dist/pipeline/dispatcher.js +3 -1
  254. package/dist/pipeline/event-dispatcher.d.ts +2 -2
  255. package/dist/pipeline/event-dispatcher.js +11 -9
  256. package/dist/pipeline/event-retention.d.ts +1 -0
  257. package/dist/pipeline/event-retention.js +7 -1
  258. package/dist/pipeline/index.d.ts +1 -0
  259. package/dist/pipeline/index.js +1 -0
  260. package/dist/pipeline/notify-scope.js +3 -1
  261. package/dist/pipeline/pre-transaction-reservations.d.ts +3 -1
  262. package/dist/pipeline/pre-transaction-reservations.js +29 -9
  263. package/dist/rate-limit/bucket.d.ts +3 -3
  264. package/dist/rate-limit/bucket.js +14 -4
  265. package/dist/redis/cache-sync-bus.js +9 -3
  266. package/dist/schema-cli.js +12 -1
  267. package/dist/search/meilisearch-adapter.js +36 -9
  268. package/dist/stack/db.d.ts +2 -2
  269. package/dist/stack/test-stack.js +2 -1
  270. package/dist/testing/index.d.ts +1 -0
  271. package/dist/testing/index.js +1 -0
  272. package/dist/testing/multipart-helper.d.ts +6 -0
  273. package/dist/testing/multipart-helper.js +1 -1
  274. package/dist/testing/zip-fixture.d.ts +1 -0
  275. package/dist/testing/zip-fixture.js +33 -0
  276. package/dist/ui-types/card-column-roles.js +4 -2
  277. package/dist/ui-types/index.d.ts +1 -1
  278. package/dist/ui-types/index.js +1 -1
  279. package/dist/upgrade-cli.js +46 -13
  280. package/package.json +7 -5
  281. package/src/changes.json +512 -8
package/AGENTS.md ADDED
@@ -0,0 +1,338 @@
1
+ # Kumiko for coding agents
2
+
3
+ This file ships inside `@cosmicdrift/kumiko-framework`. It always matches the
4
+ installed version. Read it before you add or change a feature in a Kumiko app.
5
+
6
+ Next to it, under `agents/`, are generated files. They are rebuilt from the
7
+ framework source on every release, so they never describe an API that does
8
+ not exist in your version:
9
+
10
+ | File | What it holds |
11
+ | --- | --- |
12
+ | `agents/features.md` | Every bundled feature: name, purpose, config keys, dependencies |
13
+ | `agents/widgets.md` | Every value export of `@cosmicdrift/kumiko-renderer-web` with its short description |
14
+ | `agents/examples.md` | Index of the worked examples, one per concept |
15
+ | `agents/examples/<name>.md` | One tested sample app: README plus every source and test file |
16
+
17
+ The paths are relative to this file. From an app root they live under
18
+ `node_modules/@cosmicdrift/kumiko-framework/`. Type signatures are in
19
+ `dist/*.d.ts` of each package; this file does not repeat them.
20
+
21
+ Order of work for a new feature:
22
+
23
+ 1. Search `agents/features.md` for a bundled feature that already does it.
24
+ 2. Find the closest example in `agents/examples.md` and read it fully.
25
+ 3. Build the feature with the declarative building blocks below.
26
+ 4. Write an integration test against the real stack.
27
+ 5. Run boot validation and the tests. Boot errors name the fix; apply it.
28
+
29
+ ## Mental model
30
+
31
+ A Kumiko app is a list of features. Everything a feature does is registered
32
+ on the `r` registrar inside `defineFeature(name, (r) => { ... })`. There is no
33
+ router, controller or ORM to wire by hand. The framework reads the
34
+ registrations, validates them at boot, and builds HTTP endpoints, database
35
+ tables, events, screens and navigation from them.
36
+
37
+ **Feature.** A named unit (`defineFeature("tasks", ...)`). It owns entities,
38
+ handlers, screens, navs, translations, config and events. Features talk to
39
+ each other through events, declared APIs and extensions, never through
40
+ imports of each other's internals. An app mounts features in
41
+ `src/run-config.ts` (`APP_FEATURES`).
42
+
43
+ **Entity.** A typed record built with `createEntity({ fields })` and field
44
+ factories such as `createTextField`, `createNumberField`,
45
+ `createBooleanField`. An entity is event sourced: every write appends an
46
+ event, and the table you query is a projection of those events. You never
47
+ write the table yourself.
48
+
49
+ **Handler.** The only way in. A write handler changes state, a query handler
50
+ reads it. Both have a qualified name (QN), a Zod payload schema and an access
51
+ rule. `r.crud(name, entity, options)` registers the entity plus create,
52
+ update, delete, restore, list and detail handlers in one call. Its options
53
+ need an access rule for both sides, `write: { access }` and
54
+ `read: { access }`; `verbAccess: { delete: { roles: ["Admin"] } }`
55
+ overrides a single verb. Without them boot stops. Turn single
56
+ verbs off with `verbs: { delete: false }` and register them yourself when
57
+ they need different access or logic. Custom logic goes into
58
+ `r.writeHandler(qn, schema, fn, { access })` and
59
+ `r.queryHandler(qn, schema, fn, { access })`; inside, use the entity
60
+ executor from `createEntityExecutor(name, entity)`.
61
+
62
+ **Screen.** A declarative page: `r.screen({ id, type, ... })`. Types include
63
+ `entityList`, `entityEdit`, `projectionList`, `projectionDetail`,
64
+ `actionForm`, `dashboard` and `custom`. The renderer builds the UI from the
65
+ declaration. Columns, sections, row actions, tabs, wizard steps and
66
+ conditional fields are data, not JSX. A `dashboard` lists `panels`; a
67
+ `stat` panel names a query handler and the `valueField` to show from its
68
+ result, and a `stat-group` panel holds several stats. The query returns one
69
+ flat object with those fields.
70
+
71
+ **Nav.** A sidebar entry: `r.nav({ id, label, screen, parent, order })`.
72
+ Parents may belong to another feature; refer to them by QN.
73
+
74
+ **Events.** Declare a domain event with `r.defineEvent(name, zodSchema)`.
75
+ Emit it inside a write handler with `emitEvent(ctx, def, { ... })` or
76
+ `ctx.appendEvent(...)`; it commits in the same transaction as the write.
77
+ React to it with `r.projection(...)` (state on the same stream) or
78
+ `r.multiStreamProjection({ name, apply })` (async consumer across streams,
79
+ at least once, ordered).
80
+
81
+ **Tenant scoping.** Every request runs for one tenant. `ctx.db`, executors
82
+ and queries are already filtered to that tenant. You do not pass a
83
+ `tenantId` around and you do not add tenant filters yourself. A feature
84
+ that holds global data calls `r.systemScope()`.
85
+
86
+ **Access.** Default deny. Every handler states who may call it:
87
+ `{ access: { roles: ["Admin", "User"] } }`, or
88
+ `{ access: { openToAll: { reason: "<why every signed-in user may>" } } }`.
89
+ Screens and nav entries can narrow visibility further with their own
90
+ `access`. Anonymous endpoints use the `anonymous` role. Field level and row
91
+ level rules exist as well (see the table below).
92
+ An `openToAll` write handler whose payload contains personal fields must
93
+ say that every tenant member may write them:
94
+ `{ openToAll: { reason: "...", personalData: "tenant-members" } }`. The
95
+ alternatives are roles, or binding each row to its caller. Boot names all
96
+ three. On query handlers `personalData` is only valid as
97
+ `access: { roles: ["anonymous"], personalData: "public-intake" }`.
98
+
99
+ **Personal data.** Every text field says whether it holds personal data;
100
+ the field types reject a text field without that stance.
101
+ `personal: false, reason: "is_business_data"` (a snake_case reason) for
102
+ business data. For personal data name the subject, `personal: "self"`,
103
+ `personal: { of: "<ownerField>" }` or `personal: "tenant"`, plus
104
+ `find: "exact" | "fuzzy" | "none" | "secret"`. A plain foreign key to a user
105
+ is `personal: "ref"`. `personal: "self"` is for the row that is the user.
106
+ When the record itself is the person, such as a contact or a customer that
107
+ is not a user, use `personal: { of: "id" }`; the entity then needs a uuid
108
+ `id`. The framework encrypts personal fields per subject and
109
+ can erase them on request.
110
+
111
+ **Client.** The web side of a feature is a `ClientFeatureDefinition` in the
112
+ feature's `web/index.ts` (name, translations, custom screen components). It
113
+ is mounted in `src/client.tsx`. Declarative screens need no client code.
114
+
115
+ ## Qualified names
116
+
117
+ Names are built from the feature name, a kind and an id. Use the QN wherever
118
+ another feature or a test refers to something.
119
+
120
+ | Thing | Pattern | Example from the scaffolded demo feature |
121
+ | --- | --- | --- |
122
+ | Write handler | `<feature>:write:<entity>:<verb>` | `tasks:write:task:create` |
123
+ | Query handler | `<feature>:query:<entity>:<verb>` | `tasks:query:task:list` |
124
+ | Screen | `<feature>:screen:<id>` | `tasks:screen:task-list` |
125
+ | Nav entry | `<feature>:nav:<id>` | `tasks:nav:tasks` |
126
+ | Translation key | `<feature>:<path>` | `tasks:nav.tasks` |
127
+ | Entity field label | `<feature>:entity:<entity>:field:<field>` | `tasks:entity:task:field:title` |
128
+
129
+ Inside one feature, a screen id without prefix (`screen: "task-edit"`) refers
130
+ to a screen of the same feature.
131
+
132
+ ## Where things live in an app
133
+
134
+ The scaffold (`bun create kumiko-app <name>`) produces this layout. Keep it.
135
+
136
+ | Path | Content |
137
+ | --- | --- |
138
+ | `src/run-config.ts` | `APP_FEATURES`, the mounted feature list |
139
+ | `src/features/<name>/feature.ts` | `defineFeature(...)` with entities, handlers, screens, navs |
140
+ | `src/features/<name>/index.ts` | Public exports of the feature |
141
+ | `src/features/<name>/web/index.ts` | Client feature definition |
142
+ | `src/client.tsx` | Web entry, mounts the client features |
143
+ | `src/__tests__/*.test.ts` | Unit tests, no services |
144
+ | `src/__tests__/*.integration.test.ts` | Integration tests against Postgres and Redis |
145
+ | `e2e/*.spec.ts` | Playwright tests against a booted app |
146
+ | `kumiko/schema.ts`, `kumiko/migrations/` | Database schema and generated migrations |
147
+
148
+ The scaffolded `src/features/tasks/feature.ts` and
149
+ `src/__tests__/tasks.integration.test.ts` are the smallest complete pair of
150
+ feature and test. Start from them.
151
+
152
+ ## Commands in an app
153
+
154
+ | Goal | Command |
155
+ | --- | --- |
156
+ | Run the app with demo data | `bun dev` |
157
+ | Boot validation without serving | `bun run boot` |
158
+ | Types | `bun run typecheck` |
159
+ | Lint | `bun run lint` |
160
+ | Unit tests | `bun run test` |
161
+ | Integration tests (needs `docker compose up -d`) | `bun run test:integration` |
162
+ | Browser tests | `bun run e2e` |
163
+ | New migration after an entity change or a newly mounted bundled feature | `bun run schema:generate <name>` |
164
+ | Check migrations against the features, no database needed | `bun kumiko-schema validate` |
165
+ | Apply migrations | `bun run schema:apply` |
166
+
167
+ `<name>` is required and becomes part of the file name
168
+ (`schema:generate add-invoice`). Mounting a bundled feature in
169
+ `APP_FEATURES` adds its tables, so it needs a migration like your own
170
+ entities do.
171
+
172
+ ## Rules: never this, do that
173
+
174
+ These rules are enforced by boot validation, guards or review. Breaking them
175
+ produces code that compiles and then fails in production.
176
+
177
+ ### Data and events
178
+
179
+ - Never `INSERT`, `UPDATE` or `DELETE` on an entity table or the events
180
+ table, and never call a raw database client for it. Write through a
181
+ handler: `r.crud`, the `defineEntity*Handler` helpers, the executor from
182
+ `createEntityExecutor`, or `emitEvent` / `ctx.appendEvent`.
183
+ - Never store a value that can be computed. Derive it in a query or a
184
+ projection.
185
+ - Never hand-write SQL for reads that an executor `list` or `detail` covers.
186
+ Use `r.queryHandler` with the executor, or a projection with
187
+ `ctx.queryProjection`.
188
+ - Never filter or count the rows of one `list` page in code. `list` returns
189
+ one page (default 50, `limit` at most 200). Pass
190
+ `filter: { field, op, value }` (`eq`, `ne`, `lt`, `gt`, `lte`, `gte`, `in`)
191
+ so the database filters, and `totalCount: true` to get `total` over all
192
+ matching rows; `limit: 1, totalCount: true` is a count query.
193
+ - Never change an event schema in place once it has been emitted. Version it
194
+ (see `agents/examples/event-sourcing.md`).
195
+ - Never read or write another tenant's data. `ctx.db` is tenant scoped on
196
+ purpose. Cross-tenant work belongs in system features with explicit
197
+ system access.
198
+
199
+ ### Handlers and access
200
+
201
+ - Never register a handler without `access`. Use roles, or `openToAll` with
202
+ a concrete `reason`.
203
+ - Never throw plain `Error` for an expected domain failure. Return a typed
204
+ failure from `@cosmicdrift/kumiko-framework/errors` (for example
205
+ `failNotFound(entity, id)`); it reaches the client as a structured error
206
+ with a reason and, where documented, a `docsUrl`.
207
+ - Never parse request input by hand. The Zod schema of the handler is the
208
+ boundary; inside the handler the payload is typed.
209
+ - Read-modify-write updates pass the `version` they read, so concurrent
210
+ writes fail with `version_conflict` instead of overwriting each other.
211
+
212
+ ### Types
213
+
214
+ - Never use `any`. Validate external data once at the boundary (Zod), then
215
+ work with the inferred type.
216
+ - Never use `as` to force a type except at a real system boundary. Narrow
217
+ unions with a discriminant and `unknown` with a type guard.
218
+ - Never write a magic string where a union type or a QN exists.
219
+
220
+ ### UI
221
+
222
+ - Never write your own JSX for a list, form, detail page or dashboard.
223
+ Declare a screen. Use `type: "custom"` only when no screen type fits.
224
+ - Inside a custom screen, never build your own card, table, badge, button or
225
+ chart. Use the widgets from `@cosmicdrift/kumiko-renderer-web`
226
+ (`agents/widgets.md`) and the primitives from `usePrimitives()`.
227
+ - Never fetch data with `useEffect` plus `fetch`. Use `useQuery`
228
+ (`live: true` for server-sent updates) and `useMutation`.
229
+ - Never use inline styles, hex colors or Tailwind palette classes. Use the
230
+ theme tokens (`bg-primary`, `text-status-ok`, ...).
231
+ - Never put user-facing text in code. Use translation keys and `t("...")`;
232
+ register the texts with `r.translations` and the client translations.
233
+
234
+ ### Tests
235
+
236
+ - Integration tests run against the real stack over real HTTP. In an app:
237
+ `setupAppTestStack([feature])` plus `seedTenant(stack)` from
238
+ `@cosmicdrift/kumiko-testing`, then `tenant.api.writeOk` and
239
+ `tenant.api.queryOk`. Never assemble a dispatcher yourself
240
+ (`createTestDispatcher` or similar); it skips the production wiring.
241
+ - Every test seeds its own tenant. No shared tenant, no global fixtures.
242
+ - Assert behavior through handlers and their results, not internal tables.
243
+ - A red or flaky test gets diagnosed. Never fix it with a longer timeout, a
244
+ retry, `serial` or fewer workers.
245
+ - Tests that call real external providers end in `.real.test.ts` and run
246
+ only via `bun run test:real`, never in CI.
247
+
248
+ ### Boot errors
249
+
250
+ - Boot validation stops the app when a declaration is inconsistent: an
251
+ unknown field in a column, a nav pointing at a missing screen, an
252
+ encrypted field marked searchable. The message names the
253
+ feature, the place and the fix. Apply the fix the message names instead
254
+ of working around the check.
255
+
256
+ ## I need X, use Y
257
+
258
+ Before building anything here, search `agents/features.md`: auth, tenants,
259
+ mail, files, jobs, billing, audit, notifications and GDPR flows already exist
260
+ as bundled features.
261
+
262
+ | I need | Use | Read |
263
+ | --- | --- | --- |
264
+ | A CRUD entity with list and edit | `createEntity` + `r.crud` + `entityList` / `entityEdit` screens | `agents/examples/basic-entity.md` |
265
+ | Business logic beyond CRUD | `r.writeHandler` / `r.queryHandler` with `createEntityExecutor` | `agents/examples/custom-handlers.md` |
266
+ | Filtered lists and counts | Executor `list` with `filter` and `totalCount` | `agents/examples/custom-handlers.md` |
267
+ | Key figures on a dashboard | `dashboard` screen with `stat` / `stat-group` panels and a query handler | `agents/examples/dashboard-stats.md` |
268
+ | Relations between entities | `r.relation(entity, name, { type, target, foreignKey, onDelete })` | `agents/examples/relations.md` |
269
+ | Structured sub-objects in a field | Embedded fields | `agents/examples/embedded.md` |
270
+ | List, edit, custom screens and navs | `r.screen`, `r.nav` | `agents/examples/screens-nav.md` |
271
+ | A record page with header, metrics, tabs | `projectionDetail` with `header`, `metrics`, `layout.mode: "tabs"` | `agents/examples/record-detail-layout.md` |
272
+ | A multi-step form | `layout.mode: "wizard"` | `agents/examples/wizard-form.md` |
273
+ | A form inside a drawer | `RenderEdit` in a `Drawer` | `agents/examples/embedded-entity-form.md` |
274
+ | Role-based access | `access: { roles }`, `openToAll` | `agents/examples/access-control.md` |
275
+ | Field-level access | Field access rules | `agents/examples/field-access.md` |
276
+ | Row ownership | Ownership rules | `agents/examples/ownership.md` |
277
+ | Tenant separation | Built in; see what it guarantees | `agents/examples/tenant-isolation.md` |
278
+ | Public or anonymous endpoints | `anonymous` role | `agents/examples/anonymous-access.md` |
279
+ | Allowed state changes | Entity transitions | `agents/examples/state-machine.md` |
280
+ | React to a write in another feature | `r.defineEvent` + `emitEvent` + `r.multiStreamProjection` | `agents/examples/cross-feature-events.md` |
281
+ | Projections, event versions, history | `r.projection`, `ctx.loadAggregate`, `ctx.queryProjection` | `agents/examples/event-sourcing.md` |
282
+ | Before or after save logic | Lifecycle hooks | `agents/examples/lifecycle-hooks.md` |
283
+ | Typed errors with reasons | `@cosmicdrift/kumiko-framework/errors` | `agents/examples/error-contract.md` |
284
+ | Translations | `r.translations` + client translations | `agents/examples/i18n.md` |
285
+ | Config per tenant or system | Managed config | `agents/examples/managed-config.md` |
286
+ | Tenant defaults (currency, locale) | Tenant settings | `agents/examples/tenant-settings.md` |
287
+ | Secrets such as API keys | Secrets with envelope encryption | `agents/examples/secrets-demo.md` |
288
+ | Personal data and erasure | `personal` / `find` annotations, crypto shredding | `agents/examples/crypto-shredding-hr.md` |
289
+ | GDPR export and forget for my data | `user-data-rights` extension | `agents/examples/user-data-rights.md` |
290
+ | Full-text search | Search feature | `agents/examples/search.md` |
291
+ | Live updates in the browser | Server-sent events, `useQuery({ live: true })` | `agents/examples/realtime-sse.md` |
292
+ | Feature toggles | Toggleable features | `agents/examples/feature-toggles.md` |
293
+ | Rate limits | Rate limiting | `agents/examples/rate-limiting.md` |
294
+ | Idempotent writes | Request id dedupe | `agents/examples/idempotency.md` |
295
+ | File variants and images | File fields, derivatives | `agents/examples/files-post-processing.md` |
296
+ | Money fields | Currency fields | `agents/examples/currencies-per-tenant.md` |
297
+ | Dates, times, time zones | Time-aware field types, `ctx.tz` | `agents/examples/timezones.md` |
298
+ | Tags or notes on any entity | Tags, notes history | `agents/examples/tags-basic.md`, `agents/examples/notes-history-basic.md` |
299
+ | Tenant-defined extra fields | Custom fields | `agents/examples/custom-fields-basic.md` |
300
+ | Lookup tables and seeds | Upsert seeds | `agents/examples/reference-data.md` |
301
+ | Multi-step background flows | Workflow engine | `agents/examples/workflow-engine.md` |
302
+ | A mount invariant for my feature | `r.bootCheck` | `agents/examples/boot-check.md` |
303
+ | A widget for a custom screen | Search by concern | `agents/widgets.md` |
304
+ | A bundled feature and its config | Search by name or purpose | `agents/features.md` |
305
+
306
+ ## Reading an example
307
+
308
+ Each `agents/examples/<name>.md` starts with the sample's README, then every
309
+ source file with its path. `src/feature.ts` holds the feature;
310
+ `src/__tests__/*.integration.test.ts` shows how it is tested. Samples inside
311
+ the framework repo set up their stack with `setupTestStack` from
312
+ `@cosmicdrift/kumiko-framework/stack` and create tables in the test. In an
313
+ app, `setupAppTestStack` and `seedTenant` do that for you; keep the
314
+ assertions, take the setup from your scaffolded integration test. Table
315
+ setup in sample tests (`unsafeCreateEntityTable`, `createEventsTable`) and
316
+ any raw `DELETE FROM` cleanup are framework internals. Never copy them;
317
+ isolate tests with one tenant each.
318
+
319
+ Some samples import from `@app/define`. That alias points at the generated
320
+ `.kumiko/define.ts`, which re-exports the engine with event types narrowed to
321
+ the app's own `r.defineEvent` declarations. `bun dev` and `bun run build`
322
+ regenerate it; never edit it. Without it, import the same names from
323
+ `@cosmicdrift/kumiko-framework/engine`.
324
+
325
+ ## Package entry points
326
+
327
+ Import only from these subpaths of `@cosmicdrift/kumiko-framework`. Deep
328
+ imports into `src/` or `dist/` break on the next release.
329
+
330
+ | Subpath | Use it for |
331
+ | --- | --- |
332
+ | `/engine` | `defineFeature`, `createEntity`, field factories, handler helpers, executors, events |
333
+ | `/errors` | Typed failures and error helpers |
334
+ | `/stack` | Test stack, `TestUsers`, `createTestUser` |
335
+ | `/testing` | Assertion helpers such as `expectErrorIncludes` |
336
+ | `/ui-types` | Screen definition types (`EntityListScreenDefinition`, ...) |
337
+ | `/db` | `buildEntityTable` and database helpers for tests and migrations |
338
+ | `/event-store` | Event store helpers |