@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.
- package/AGENTS.md +338 -0
- package/agents/examples/access-control.md +355 -0
- package/agents/examples/anonymous-access-multitenant.md +446 -0
- package/agents/examples/anonymous-access.md +288 -0
- package/agents/examples/apex-landing.md +528 -0
- package/agents/examples/apex-surface-auth.md +355 -0
- package/agents/examples/api-worker-split.md +434 -0
- package/agents/examples/auth-claims.md +351 -0
- package/agents/examples/auth-foundation-providers.md +155 -0
- package/agents/examples/auth-mfa.md +319 -0
- package/agents/examples/basic-entity.md +420 -0
- package/agents/examples/boot-check.md +122 -0
- package/agents/examples/chat-channels.md +254 -0
- package/agents/examples/compliance-profiles-demo.md +307 -0
- package/agents/examples/cross-feature-events.md +317 -0
- package/agents/examples/crypto-shredding-hr.md +314 -0
- package/agents/examples/currencies-global.md +609 -0
- package/agents/examples/currencies-per-tenant.md +453 -0
- package/agents/examples/custom-fields-basic.md +315 -0
- package/agents/examples/custom-handlers.md +444 -0
- package/agents/examples/dashboard-stats.md +229 -0
- package/agents/examples/delivery-notifications.md +432 -0
- package/agents/examples/designer-demo.md +86 -0
- package/agents/examples/e2e-generator.md +211 -0
- package/agents/examples/embedded-entity-form.md +598 -0
- package/agents/examples/embedded.md +649 -0
- package/agents/examples/encrypted-tenant-config.md +414 -0
- package/agents/examples/error-contract.md +535 -0
- package/agents/examples/event-sourcing.md +1287 -0
- package/agents/examples/feature-toggles.md +274 -0
- package/agents/examples/field-access.md +271 -0
- package/agents/examples/files-post-processing.md +433 -0
- package/agents/examples/i18n.md +166 -0
- package/agents/examples/idempotency.md +179 -0
- package/agents/examples/inbound-mail-triage.md +315 -0
- package/agents/examples/lane-routing.md +246 -0
- package/agents/examples/ledger-banking.md +215 -0
- package/agents/examples/ledger-invoicing.md +236 -0
- package/agents/examples/legal-pages.md +393 -0
- package/agents/examples/lifecycle-hooks.md +368 -0
- package/agents/examples/managed-config.md +428 -0
- package/agents/examples/managed-pages.md +353 -0
- package/agents/examples/notes-history-basic.md +346 -0
- package/agents/examples/ownership.md +404 -0
- package/agents/examples/pdf-audit-export.md +306 -0
- package/agents/examples/pipeline-basics.md +792 -0
- package/agents/examples/public-share-token.md +351 -0
- package/agents/examples/rate-limiting.md +211 -0
- package/agents/examples/realtime-sse.md +187 -0
- package/agents/examples/record-detail-layout.md +297 -0
- package/agents/examples/reference-data.md +151 -0
- package/agents/examples/relations.md +280 -0
- package/agents/examples/screens-nav.md +353 -0
- package/agents/examples/search.md +233 -0
- package/agents/examples/secrets-demo.md +417 -0
- package/agents/examples/security-baseline.md +187 -0
- package/agents/examples/seed-migration.md +99 -0
- package/agents/examples/session-revocation.md +250 -0
- package/agents/examples/state-machine.md +920 -0
- package/agents/examples/tags-basic.md +401 -0
- package/agents/examples/tenant-isolation.md +173 -0
- package/agents/examples/tenant-settings.md +275 -0
- package/agents/examples/tier-admin.md +347 -0
- package/agents/examples/timezones.md +366 -0
- package/agents/examples/user-data-rights.md +418 -0
- package/agents/examples/user-profile.md +226 -0
- package/agents/examples/webhook-step.md +597 -0
- package/agents/examples/wizard-form.md +379 -0
- package/agents/examples/workflow-engine.md +1731 -0
- package/agents/examples/writeform-section.md +300 -0
- package/agents/examples.md +75 -0
- package/agents/features.md +768 -0
- package/agents/widgets.md +181 -0
- package/dist/api/auth-middleware.d.ts +2 -0
- package/dist/api/auth-middleware.js +7 -0
- package/dist/api/auth-routes.d.ts +0 -1
- package/dist/api/auth-routes.js +49 -46
- package/dist/api/extra-route.d.ts +3 -0
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.js +1 -1
- package/dist/api/routes.js +46 -17
- package/dist/api/server.d.ts +4 -3
- package/dist/api/server.js +39 -10
- package/dist/api/session-cookies.d.ts +1 -0
- package/dist/api/session-cookies.js +20 -5
- package/dist/api/sse-route.js +16 -1
- package/dist/bun-db/connection.js +3 -16
- package/dist/bun-db/index.d.ts +1 -1
- package/dist/bun-db/index.js +1 -1
- package/dist/bun-db/query.d.ts +15 -3
- package/dist/bun-db/query.js +76 -24
- package/dist/crypto/owner-transition-rekey.d.ts +1 -0
- package/dist/crypto/owner-transition-rekey.js +7 -3
- package/dist/crypto/pii-field-encryption.d.ts +1 -0
- package/dist/crypto/pii-field-encryption.js +9 -2
- package/dist/crypto/subject-resolver.d.ts +1 -0
- package/dist/crypto/subject-resolver.js +7 -0
- package/dist/db/api.d.ts +4 -3
- package/dist/db/apply-entity-event.js +26 -2
- package/dist/db/blind-index-cleanup.js +2 -1
- package/dist/db/bun-provider.js +9 -18
- package/dist/db/collect-table-metas.js +11 -7
- package/dist/db/dialect.d.ts +1 -1
- package/dist/db/dialect.js +2 -0
- package/dist/db/eagerload.js +4 -3
- package/dist/db/event-store-executor-context.d.ts +6 -6
- package/dist/db/event-store-executor-context.js +25 -22
- package/dist/db/event-store-executor-read.js +31 -16
- package/dist/db/event-store-executor-write.d.ts +1 -1
- package/dist/db/event-store-executor-write.js +84 -63
- package/dist/db/index.d.ts +2 -1
- package/dist/db/index.js +2 -1
- package/dist/db/lock-guard.d.ts +4 -0
- package/dist/db/lock-guard.js +14 -0
- package/dist/db/migrate-generator.js +1 -1
- package/dist/db/migrate-runner.d.ts +1 -0
- package/dist/db/migrate-runner.js +64 -38
- package/dist/db/queries/backfill-pii.js +63 -25
- package/dist/db/queries/event-consumer.d.ts +6 -6
- package/dist/db/queries/event-consumer.js +2 -1
- package/dist/db/queries/event-store-transfer.js +15 -4
- package/dist/db/queries/event-store.d.ts +1 -1
- package/dist/db/queries/event-store.js +11 -10
- package/dist/db/queries/pii-backfill-state.js +3 -2
- package/dist/db/queries/postgres-array-literal.d.ts +2 -0
- package/dist/db/queries/postgres-array-literal.js +6 -0
- package/dist/db/queries/projection-rebuild.d.ts +1 -1
- package/dist/db/queries/projection-rebuild.js +3 -2
- package/dist/db/queries/shadow-swap.d.ts +1 -1
- package/dist/db/queries/shadow-swap.js +12 -8
- package/dist/db/query.d.ts +1 -1
- package/dist/db/query.js +1 -1
- package/dist/db/retired-framework-tables.js +22 -4
- package/dist/db/table-builder.js +6 -2
- package/dist/db/tenant-db-runner.d.ts +4 -0
- package/dist/db/tenant-db-runner.js +17 -0
- package/dist/db/tenant-db.d.ts +4 -0
- package/dist/db/tenant-db.js +50 -5
- package/dist/derivatives/delete-stored-file.d.ts +4 -0
- package/dist/derivatives/delete-stored-file.js +9 -0
- package/dist/derivatives/derivatives-context.js +1 -1
- package/dist/derivatives/index.d.ts +1 -1
- package/dist/derivatives/index.js +1 -1
- package/dist/engine/boot-validator/access-declarations.js +2 -2
- package/dist/engine/boot-validator/api-ext.js +3 -3
- package/dist/engine/boot-validator/boot-check.js +1 -1
- package/dist/engine/boot-validator/config-deps.js +13 -13
- package/dist/engine/boot-validator/detail-screens.js +1 -1
- package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
- package/dist/engine/boot-validator/entity-handler.js +87 -35
- package/dist/engine/boot-validator/entity-list-screens.js +2 -2
- package/dist/engine/boot-validator/handler-field-references.js +19 -9
- package/dist/engine/boot-validator/i18n-keys.js +2 -2
- package/dist/engine/boot-validator/nav.js +1 -1
- package/dist/engine/boot-validator/pii-retention.js +8 -8
- package/dist/engine/boot-validator/projection-list-screens.js +5 -5
- package/dist/engine/boot-validator/query-output-columns.js +7 -7
- package/dist/engine/boot-validator/query-refs.js +14 -2
- package/dist/engine/boot-validator/screens.d.ts +2 -1
- package/dist/engine/boot-validator/screens.js +153 -95
- package/dist/engine/boot-validator/url-prefill-fields.js +54 -35
- package/dist/engine/dedupe-features.js +31 -4
- package/dist/engine/define-handler.d.ts +2 -2
- package/dist/engine/define-handler.js +8 -1
- package/dist/engine/entity-handlers.js +14 -7
- package/dist/engine/feature-ast/extractors/handlers.js +2 -0
- package/dist/engine/feature-ast/pattern-change-schema.d.ts +2 -0
- package/dist/engine/feature-ast/pattern-change-schema.js +1 -0
- package/dist/engine/feature-ast/render.js +10 -2
- package/dist/engine/feature-entity-handlers.d.ts +2 -2
- package/dist/engine/feature-entity-handlers.js +12 -3
- package/dist/engine/handler-field-references.d.ts +21 -2
- package/dist/engine/handler-field-references.js +55 -17
- package/dist/engine/index.d.ts +3 -3
- package/dist/engine/index.js +1 -1
- package/dist/engine/schema-builder.js +2 -1
- package/dist/engine/screen-helpers.d.ts +1 -0
- package/dist/engine/screen-helpers.js +2 -2
- package/dist/engine/soft-delete-cleanup.js +10 -3
- package/dist/engine/steps/_read-source.js +5 -2
- package/dist/engine/steps/retry.js +2 -1
- package/dist/engine/steps/unsafe-projection-delete.js +2 -2
- package/dist/engine/steps/unsafe-projection-upsert.js +2 -2
- package/dist/engine/template-variables.d.ts +3 -0
- package/dist/engine/template-variables.js +3 -0
- package/dist/engine/tier-resolver-extension.d.ts +1 -0
- package/dist/engine/tier-resolver-extension.js +6 -0
- package/dist/engine/types/index.d.ts +1 -1
- package/dist/entrypoint/index.d.ts +2 -2
- package/dist/env/_zod-introspect.d.ts +1 -0
- package/dist/errors/classes.js +1 -0
- package/dist/errors/docs-url.js +6 -6
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/errors/kumiko-error.d.ts +2 -0
- package/dist/errors/kumiko-error.js +2 -0
- package/dist/errors/subject-erased.d.ts +1 -0
- package/dist/errors/subject-erased.js +6 -0
- package/dist/errors/write-error-info.js +2 -0
- package/dist/event-store/admin-api.js +3 -2
- package/dist/event-store/errors.d.ts +9 -0
- package/dist/event-store/errors.js +20 -0
- package/dist/event-store/event-store.d.ts +6 -0
- package/dist/event-store/event-store.js +28 -6
- package/dist/event-store/index.d.ts +1 -1
- package/dist/event-store/index.js +1 -1
- package/dist/event-store/tenant-db-append.js +2 -2
- package/dist/files/assert-file-refs-belong-to-tenant.d.ts +4 -5
- package/dist/files/assert-file-refs-belong-to-tenant.js +7 -15
- package/dist/files/bind-pending-file-refs.d.ts +2 -0
- package/dist/files/bind-pending-file-refs.js +3 -3
- package/dist/files/bind-referenced-files.write.d.ts +1 -1
- package/dist/files/bind-referenced-files.write.js +22 -13
- package/dist/files/create-file-ref-from-bytes.d.ts +6 -3
- package/dist/files/create-file-ref-from-bytes.js +67 -4
- package/dist/files/create-file-ref.d.ts +24 -1
- package/dist/files/create-file-ref.js +67 -25
- package/dist/files/dispose-removed-file-refs.js +21 -6
- package/dist/files/feature.d.ts +1 -0
- package/dist/files/feature.js +2 -1
- package/dist/files/file-handle.d.ts +1 -1
- package/dist/files/file-handle.js +12 -0
- package/dist/files/file-routes.js +10 -6
- package/dist/files/index.d.ts +3 -3
- package/dist/files/index.js +2 -2
- package/dist/files/load-save-file-refs.d.ts +10 -0
- package/dist/files/load-save-file-refs.js +47 -0
- package/dist/files/purge-expired-file-ref-bytes.d.ts +4 -1
- package/dist/files/purge-expired-file-ref-bytes.js +46 -21
- package/dist/files/resolve-content-type.js +40 -4
- package/dist/files/storage-tracking.js +5 -4
- package/dist/files/types.js +5 -8
- package/dist/files/zip-entry-names.d.ts +1 -0
- package/dist/files/zip-entry-names.js +46 -0
- package/dist/jobs/index.d.ts +1 -1
- package/dist/jobs/job-runner.d.ts +5 -1
- package/dist/jobs/job-runner.js +124 -37
- package/dist/observability/escape-hatch-report.d.ts +2 -1
- package/dist/observability/escape-hatch-report.js +48 -7
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +1 -1
- package/dist/observability/metrics-handle.d.ts +1 -0
- package/dist/observability/metrics-handle.js +7 -0
- package/dist/observability/standard-metrics.js +4 -4
- package/dist/pipeline/cascade-handler.js +5 -2
- package/dist/pipeline/dispatch-batch.js +79 -31
- package/dist/pipeline/dispatch-query.d.ts +2 -1
- package/dist/pipeline/dispatch-query.js +6 -6
- package/dist/pipeline/dispatch-shared.d.ts +7 -2
- package/dist/pipeline/dispatch-shared.js +136 -48
- package/dist/pipeline/dispatch-stream.js +10 -4
- package/dist/pipeline/dispatch-write.js +34 -22
- package/dist/pipeline/dispatcher.js +3 -1
- package/dist/pipeline/event-dispatcher.d.ts +2 -2
- package/dist/pipeline/event-dispatcher.js +11 -9
- package/dist/pipeline/event-retention.d.ts +1 -0
- package/dist/pipeline/event-retention.js +7 -1
- package/dist/pipeline/index.d.ts +1 -0
- package/dist/pipeline/index.js +1 -0
- package/dist/pipeline/notify-scope.js +3 -1
- package/dist/pipeline/pre-transaction-reservations.d.ts +3 -1
- package/dist/pipeline/pre-transaction-reservations.js +29 -9
- package/dist/rate-limit/bucket.d.ts +3 -3
- package/dist/rate-limit/bucket.js +14 -4
- package/dist/redis/cache-sync-bus.js +9 -3
- package/dist/schema-cli.js +12 -1
- package/dist/search/meilisearch-adapter.js +36 -9
- package/dist/stack/db.d.ts +2 -2
- package/dist/stack/test-stack.js +2 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +1 -0
- package/dist/testing/multipart-helper.d.ts +6 -0
- package/dist/testing/multipart-helper.js +1 -1
- package/dist/testing/zip-fixture.d.ts +1 -0
- package/dist/testing/zip-fixture.js +33 -0
- package/dist/ui-types/card-column-roles.js +4 -2
- package/dist/ui-types/index.d.ts +1 -1
- package/dist/ui-types/index.js +1 -1
- package/dist/upgrade-cli.js +46 -13
- package/package.json +7 -5
- 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 |
|