@cosmicdrift/kumiko-framework 0.377.0 → 0.379.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 (118) hide show
  1. package/AGENTS.md +4 -1
  2. package/agents/examples/compose-stacks.md +1 -2
  3. package/agents/examples/currencies-global.md +2 -3
  4. package/agents/examples/delivery-notifications.md +25 -2
  5. package/agents/examples/embedded.md +177 -0
  6. package/agents/examples/event-sourcing.md +6 -5
  7. package/agents/examples/legal-pages.md +5 -5
  8. package/agents/examples/reference-data.md +26 -21
  9. package/agents/examples/relations.md +2 -1
  10. package/agents/features.md +5 -5
  11. package/agents/widgets.md +1 -0
  12. package/dist/api/entity-read-access.d.ts +2 -0
  13. package/dist/api/entity-read-access.js +27 -0
  14. package/dist/api/index.d.ts +1 -0
  15. package/dist/api/index.js +1 -0
  16. package/dist/api/server.js +10 -3
  17. package/dist/api/sse-route.d.ts +5 -0
  18. package/dist/api/sse-route.js +59 -9
  19. package/dist/db/entity-table-from-registry.d.ts +2 -1
  20. package/dist/db/entity-table-meta.d.ts +0 -2
  21. package/dist/db/entity-table-meta.js +1 -3
  22. package/dist/db/event-store-executor-write.js +21 -16
  23. package/dist/db/executor-managed-tables.d.ts +6 -0
  24. package/dist/db/executor-managed-tables.js +40 -0
  25. package/dist/db/index.d.ts +4 -2
  26. package/dist/db/index.js +4 -2
  27. package/dist/db/money.d.ts +3 -2
  28. package/dist/db/money.js +2 -7
  29. package/dist/db/queries/backfill-pii.js +22 -12
  30. package/dist/db/queries/database-now.d.ts +5 -0
  31. package/dist/db/queries/database-now.js +12 -0
  32. package/dist/db/queries/event-store-orphan-rows.d.ts +8 -0
  33. package/dist/db/queries/event-store-orphan-rows.js +32 -0
  34. package/dist/db/query.js +28 -0
  35. package/dist/db/reference-data.d.ts +5 -4
  36. package/dist/db/reference-data.js +23 -21
  37. package/dist/db/table-builder.js +2 -0
  38. package/dist/db/tenant-db-runner.d.ts +2 -0
  39. package/dist/db/tenant-db-runner.js +7 -0
  40. package/dist/db/tenant-db.d.ts +3 -0
  41. package/dist/db/tenant-db.js +54 -18
  42. package/dist/engine/boot-validator/entity-handler.js +12 -4
  43. package/dist/engine/boot-validator/form-derivations.d.ts +4 -0
  44. package/dist/engine/boot-validator/form-derivations.js +82 -0
  45. package/dist/engine/boot-validator/query-refs.js +8 -0
  46. package/dist/engine/boot-validator/screens.js +50 -12
  47. package/dist/engine/build-config-feature-schema.js +33 -10
  48. package/dist/engine/embedded-derived.d.ts +1 -1
  49. package/dist/engine/embedded-derived.js +7 -1
  50. package/dist/engine/feature-ui-extensions.js +4 -0
  51. package/dist/engine/form-derivations.d.ts +21 -0
  52. package/dist/engine/form-derivations.js +175 -0
  53. package/dist/engine/index.d.ts +4 -1
  54. package/dist/engine/index.js +2 -0
  55. package/dist/engine/minor-units.d.ts +2 -0
  56. package/dist/engine/minor-units.js +8 -0
  57. package/dist/engine/registry-state.js +2 -0
  58. package/dist/engine/registry-validate.js +4 -1
  59. package/dist/engine/schema-builder.js +18 -4
  60. package/dist/engine/screen-helpers.d.ts +1 -0
  61. package/dist/engine/screen-helpers.js +3 -0
  62. package/dist/engine/soft-delete-cleanup.js +53 -24
  63. package/dist/engine/steps/unsafe-projection-delete.js +2 -1
  64. package/dist/engine/template-variables.d.ts +2 -1
  65. package/dist/engine/template-variables.js +4 -0
  66. package/dist/engine/types/index.d.ts +2 -2
  67. package/dist/engine/with-form-derivations.d.ts +9 -0
  68. package/dist/engine/with-form-derivations.js +16 -0
  69. package/dist/entrypoint/index.d.ts +1 -3
  70. package/dist/errors/reasons.d.ts +1 -0
  71. package/dist/errors/reasons.js +4 -1
  72. package/dist/errors/serialize.d.ts +1 -0
  73. package/dist/errors/serialize.js +9 -5
  74. package/dist/errors/write-error-info.js +3 -2
  75. package/dist/errors/zod-bridge.js +7 -0
  76. package/dist/event-store/index.d.ts +1 -0
  77. package/dist/event-store/index.js +1 -0
  78. package/dist/files/action-form-upload.d.ts +16 -0
  79. package/dist/files/action-form-upload.js +45 -0
  80. package/dist/files/bind-pending-file-refs.d.ts +14 -0
  81. package/dist/files/bind-pending-file-refs.js +44 -4
  82. package/dist/files/create-file-ref.d.ts +3 -0
  83. package/dist/files/create-file-ref.js +41 -13
  84. package/dist/files/file-routes.js +29 -2
  85. package/dist/files/index.d.ts +2 -1
  86. package/dist/files/index.js +1 -0
  87. package/dist/files/purge-expired-file-ref-bytes.d.ts +3 -2
  88. package/dist/files/purge-expired-file-ref-bytes.js +11 -5
  89. package/dist/i18n/settings-hub-keys.js +1 -0
  90. package/dist/internal/testing.d.ts +2 -0
  91. package/dist/internal/testing.js +15 -1
  92. package/dist/migrations/pii-event-backfill.js +14 -7
  93. package/dist/observability/escape-hatch-report.d.ts +1 -1
  94. package/dist/observability/escape-hatch-report.js +14 -3
  95. package/dist/pipeline/cascade-handler.js +79 -10
  96. package/dist/pipeline/dispatch-shared.js +1 -9
  97. package/dist/pipeline/msp-rebuild.js +5 -3
  98. package/dist/pipeline/projection-rebuild.js +4 -2
  99. package/dist/pipeline/projections-runner.js +2 -1
  100. package/dist/rate-limit/index.d.ts +1 -1
  101. package/dist/rate-limit/index.js +1 -1
  102. package/dist/rate-limit/middleware.d.ts +4 -0
  103. package/dist/rate-limit/middleware.js +5 -2
  104. package/dist/search/configure-tenant-index.d.ts +4 -0
  105. package/dist/search/configure-tenant-index.js +8 -0
  106. package/dist/search/index.d.ts +1 -0
  107. package/dist/search/index.js +1 -0
  108. package/dist/ui-types/index.d.ts +4 -2
  109. package/dist/ui-types/index.js +3 -2
  110. package/dist/ui-types/list-row-meta.d.ts +3 -0
  111. package/dist/ui-types/list-row-meta.js +12 -1
  112. package/kumiko-es-tables.json +6 -0
  113. package/package.json +4 -3
  114. package/src/changes.json +142 -0
  115. package/src/scripts/codemod/README.md +20 -0
  116. package/src/scripts/codemod/codemod-support.ts +1 -1
  117. package/src/scripts/codemod/feature-layout.ts +416 -0
  118. package/src/scripts/codemod/package-merge.ts +0 -11
@@ -1,4 +1,4 @@
1
1
  export { buildBucketKey, buildPayloadBucketKey, createPayloadDigest, normalizePayloadBucketValue, } from "./bucket.js";
2
- export { authEndpointRateLimit, globalIpRateLimit, httpRouteRateLimit, } from "./middleware.js";
2
+ export { authEndpointRateLimit, DEFAULT_GLOBAL_RATE_LIMIT, globalIpRateLimit, httpRouteRateLimit, } from "./middleware.js";
3
3
  export { createRateLimitResolver, } from "./resolver.js";
4
4
  export { createUnlimitedRateLimitResolver } from "./unlimited-resolver.js";
@@ -11,6 +11,10 @@ export type GlobalIpRateLimitOptions = {
11
11
  readonly extractIp?: (c: Context) => string | undefined;
12
12
  readonly onFailClosed?: (err: unknown) => void;
13
13
  };
14
+ export declare const DEFAULT_GLOBAL_RATE_LIMIT: {
15
+ readonly limit: 600;
16
+ readonly windowSeconds: 60;
17
+ };
14
18
  export declare function globalIpRateLimit(opts: GlobalIpRateLimitOptions): MiddlewareHandler;
15
19
  export type HttpRouteRateLimitOptions = {
16
20
  readonly resolver: RateLimitResolver;
@@ -3,9 +3,12 @@ import { clientIpSourceFromHonoContext, createClientIpResolver, } from "../api/c
3
3
  import { requestContext } from "../api/request-context.js";
4
4
  import { RateLimitError, serializeError } from "../errors/index.js";
5
5
  import { toPublicBucketName } from "./bucket.js";
6
+ // Shared with runProdApp (server-runtime re-exports it). The auth middleware default stays
7
+ // stricter than runProdApp's auth limit (30): a bare authEndpointRateLimit guards brute force.
8
+ export const DEFAULT_GLOBAL_RATE_LIMIT = { limit: 600, windowSeconds: 60 };
6
9
  export function globalIpRateLimit(opts) {
7
- const limit = opts.limit ?? 1000;
8
- const windowSeconds = opts.windowSeconds ?? 60;
10
+ const limit = opts.limit ?? DEFAULT_GLOBAL_RATE_LIMIT.limit;
11
+ const windowSeconds = opts.windowSeconds ?? DEFAULT_GLOBAL_RATE_LIMIT.windowSeconds;
9
12
  const clientIpResolver = opts.clientIpResolver ??
10
13
  createClientIpResolver(opts.trustedProxyHops ?? 0, "globalIpRateLimit");
11
14
  const extractIp = opts.extractIp ?? ((c) => clientIpResolver.resolve(clientIpSourceFromHonoContext(c)));
@@ -0,0 +1,4 @@
1
+ import type { Registry, TenantId } from "../engine/types/index.ts";
2
+ import type { SearchAdapter } from "./types.ts";
3
+ export declare function collectSearchableFieldNames(registry: Registry): readonly string[];
4
+ export declare function configureTenantSearchIndex(searchAdapter: SearchAdapter, registry: Registry, tenantId: TenantId): Promise<void>;
@@ -0,0 +1,8 @@
1
+ import { deriveSearchAdapterConfig } from "./derive-search-adapter-config.js";
2
+ export function collectSearchableFieldNames(registry) {
3
+ return deriveSearchAdapterConfig(registry)?.searchableFields ?? [];
4
+ }
5
+ export async function configureTenantSearchIndex(searchAdapter, registry, tenantId) {
6
+ const fields = collectSearchableFieldNames(registry);
7
+ await searchAdapter.configure(tenantId, { searchableFields: fields, rankingFields: fields });
8
+ }
@@ -1,3 +1,4 @@
1
+ export { collectSearchableFieldNames, configureTenantSearchIndex, } from "./configure-tenant-index.ts";
1
2
  export { deriveSearchAdapterConfig } from "./derive-search-adapter-config.ts";
2
3
  export { createInMemorySearchAdapter } from "./in-memory-adapter.ts";
3
4
  export { purgeSearchDocumentsForSubject } from "./purge-subject.ts";
@@ -2,6 +2,7 @@
2
2
  // Damit lädt der Main-Barrel keinen Meilisearch-Client beim bloßen Anfassen
3
3
  // von SearchAdapter-Types. Apps die Meilisearch nicht nutzen, ziehen den
4
4
  // Client-Code nicht mit rein.
5
+ export { collectSearchableFieldNames, configureTenantSearchIndex, } from "./configure-tenant-index.js";
5
6
  export { deriveSearchAdapterConfig } from "./derive-search-adapter-config.js";
6
7
  export { createInMemorySearchAdapter } from "./in-memory-adapter.js";
7
8
  export { purgeSearchDocumentsForSubject } from "./purge-subject.js";
@@ -1,10 +1,12 @@
1
1
  export { NO_WIDGET_FIELD_TYPES } from "@cosmicdrift/kumiko-types/fields";
2
2
  export type { DerivedCellRoundingTarget } from "../engine/embedded-derived.ts";
3
3
  export { computeDerivedCellValue, roundDerivedCellValue } from "../engine/embedded-derived.ts";
4
+ export type { FormDerivationScreen } from "../engine/form-derivations.ts";
5
+ export { applyFormDerivations, isFormDerivationInputMode, isUnusedFallbackOperand, } from "../engine/form-derivations.ts";
4
6
  export type { ParsedRefTarget } from "../engine/parse-ref-target.ts";
5
7
  export { parseRefTarget } from "../engine/parse-ref-target.ts";
6
8
  export type { FieldsOrGroupsSection, UnavailableSelectOption } from "../engine/screen-helpers.ts";
7
- export { availableSelectOptions, evalFieldCondition, explicitListScreenId, findUnavailableSelectOptions, isExtensionEditSection, isFieldsEditSection, isFormatSpec, isRecordLocked, isUnchangedValue, isWriteFormEditSection, normalizeEditField, normalizeListColumn, resolveNavParentScreen, sectionFieldSpecs, } from "../engine/screen-helpers.ts";
9
+ export { ACTION_FORM_PSEUDO_ENTITY, availableSelectOptions, evalFieldCondition, explicitListScreenId, findUnavailableSelectOptions, isExtensionEditSection, isFieldsEditSection, isFormatSpec, isRecordLocked, isUnchangedValue, isWriteFormEditSection, normalizeEditField, normalizeListColumn, resolveNavParentScreen, sectionFieldSpecs, } from "../engine/screen-helpers.ts";
8
10
  export type { BooleanFieldDef, DateFieldDef, EmbeddedDerivedCellDef, EntityDefinition, FieldDefinition, FileFieldDef, FilesFieldDef, ImageFieldDef, ImagesFieldDef, NumberFieldDef, OptionsQueryPayload, OptionsQueryPayloadValue, SelectFieldDef, SelectOptionTone, TextFieldDef, } from "../engine/types/fields.ts";
9
11
  export type { AccessRule, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, RoleAccessPersonalData, RoleAccessRule, } from "../engine/types/handlers.ts";
10
12
  export { isOpenToAllGranted, isUiAccessGranted } from "../engine/types/handlers.ts";
@@ -20,5 +22,5 @@ export type { CardColumnRoles, CardRoleColumn } from "./card-column-roles.ts";
20
22
  export { CARD_META_MAX, cardColumnRoles, cardFileDisplayKind } from "./card-column-roles.ts";
21
23
  export { type Formality, formalLocaleTag } from "./formality.ts";
22
24
  export type { ListRowMetaColumnType, ListRowMetaReference, ReferenceLookupSource, SystemReferenceLabel, } from "./list-row-meta.ts";
23
- export { LIST_ROW_META_COLUMNS, LIST_ROW_META_REFERENCES, REFERENCE_LOOKUP_SOURCES, SYSTEM_REFERENCE_LABELS, } from "./list-row-meta.ts";
25
+ export { LIST_ROW_META_COLUMNS, LIST_ROW_META_REFERENCES, REFERENCE_LOOKUP_SOURCES, SYSTEM_REFERENCE_LABELS, UNRESOLVED_REFERENCE_LABEL_KEYS, } from "./list-row-meta.ts";
24
26
  export { type PluralForms, resolveTranslationValue, type TranslationValue, translationValueOtherText, } from "./plural.ts";
@@ -21,12 +21,13 @@
21
21
  // helper with no cross-module side-effects.
22
22
  export { NO_WIDGET_FIELD_TYPES } from "@cosmicdrift/kumiko-types/fields";
23
23
  export { computeDerivedCellValue, roundDerivedCellValue } from "../engine/embedded-derived.js";
24
+ export { applyFormDerivations, isFormDerivationInputMode, isUnusedFallbackOperand, } from "../engine/form-derivations.js";
24
25
  export { parseRefTarget } from "../engine/parse-ref-target.js";
25
- export { availableSelectOptions, evalFieldCondition, explicitListScreenId, findUnavailableSelectOptions, isExtensionEditSection, isFieldsEditSection, isFormatSpec, isRecordLocked, isUnchangedValue, isWriteFormEditSection, normalizeEditField, normalizeListColumn, resolveNavParentScreen, sectionFieldSpecs, } from "../engine/screen-helpers.js";
26
+ export { ACTION_FORM_PSEUDO_ENTITY, availableSelectOptions, evalFieldCondition, explicitListScreenId, findUnavailableSelectOptions, isExtensionEditSection, isFieldsEditSection, isFormatSpec, isRecordLocked, isUnchangedValue, isWriteFormEditSection, normalizeEditField, normalizeListColumn, resolveNavParentScreen, sectionFieldSpecs, } from "../engine/screen-helpers.js";
26
27
  export { isOpenToAllGranted, isUiAccessGranted } from "../engine/types/handlers.js";
27
28
  export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "../engine/types/screen.js";
28
29
  export { ACTION_FORM_ENTITY, handlerFieldLabelKey, handlerTitleKey, PROJECTION_DETAIL_ENTITY, WRITE_FORM_SECTION_ENTITY, } from "../i18n/required-surface-keys.js";
29
30
  export { CARD_META_MAX, cardColumnRoles, cardFileDisplayKind } from "./card-column-roles.js";
30
31
  export { formalLocaleTag } from "./formality.js";
31
- export { LIST_ROW_META_COLUMNS, LIST_ROW_META_REFERENCES, REFERENCE_LOOKUP_SOURCES, SYSTEM_REFERENCE_LABELS, } from "./list-row-meta.js";
32
+ export { LIST_ROW_META_COLUMNS, LIST_ROW_META_REFERENCES, REFERENCE_LOOKUP_SOURCES, SYSTEM_REFERENCE_LABELS, UNRESOLVED_REFERENCE_LABEL_KEYS, } from "./list-row-meta.js";
32
33
  export { resolveTranslationValue, translationValueOtherText, } from "./plural.js";
@@ -13,6 +13,9 @@ export type ReferenceLookupSource = {
13
13
  export declare const REFERENCE_LOOKUP_SOURCES: Readonly<Record<string, ReferenceLookupSource>>;
14
14
  export type SystemReferenceLabel = {
15
15
  readonly id: string;
16
+ /** Further literals that mean the same system identity (e.g. the "system" createdBy literal). */
17
+ readonly aliases?: readonly string[];
16
18
  readonly labelKey: string;
17
19
  };
18
20
  export declare const SYSTEM_REFERENCE_LABELS: Readonly<Record<string, SystemReferenceLabel>>;
21
+ export declare const UNRESOLVED_REFERENCE_LABEL_KEYS: Readonly<Record<string, string>>;
@@ -61,5 +61,16 @@ export const SYSTEM_REFERENCE_LABELS = {
61
61
  "tenant:tenant": { id: SYSTEM_TENANT_ID, labelKey: "kumiko.reference.system-tenant" },
62
62
  // createdBy on a system write (fw#3103) — SYSTEM_USER_ID is an alias for
63
63
  // "no human caller", so read_users never holds a matching row.
64
- "user:user": { id: SYSTEM_USER_ID, labelKey: "kumiko.reference.system-user" },
64
+ "user:user": {
65
+ id: SYSTEM_USER_ID,
66
+ aliases: ["system"],
67
+ labelKey: "kumiko.reference.system-user",
68
+ },
69
+ };
70
+ // Label for a reference id that is neither a system identity nor has a
71
+ // visible row (deleted user, seed actor, row outside the caller's scope).
72
+ // Without it a reference cell falls back to the raw UUID. Opt-in per entity:
73
+ // for entities whose id is meaningful to the reader the raw id stays.
74
+ export const UNRESOLVED_REFERENCE_LABEL_KEYS = {
75
+ "user:user": "kumiko.reference.unknown-user",
65
76
  };
@@ -0,0 +1,6 @@
1
+ [
2
+ {
3
+ "module": "files/file-ref-table",
4
+ "export": "fileRefsTable"
5
+ }
6
+ ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-framework",
3
- "version": "0.377.0",
3
+ "version": "0.379.0",
4
4
  "description": "Framework core — engine, pipeline, API, DB, and every other bit that makes Kumiko go.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -199,7 +199,7 @@
199
199
  "./package.json": "./package.json"
200
200
  },
201
201
  "dependencies": {
202
- "@cosmicdrift/kumiko-types": "0.377.0",
202
+ "@cosmicdrift/kumiko-types": "0.379.0",
203
203
  "bullmq": "^5.76.7",
204
204
  "bun-types": "^1.3.13",
205
205
  "hono": "^4.13.1",
@@ -215,7 +215,7 @@
215
215
  "zod": "^4.4.3"
216
216
  },
217
217
  "devDependencies": {
218
- "@cosmicdrift/kumiko-testing": "0.377.0",
218
+ "@cosmicdrift/kumiko-testing": "0.379.0",
219
219
  "bun-types": "^1.3.13",
220
220
  "pino-pretty": "^13.1.3"
221
221
  },
@@ -394,6 +394,7 @@
394
394
  },
395
395
  "files": [
396
396
  "dist",
397
+ "kumiko-es-tables.json",
397
398
  "presets",
398
399
  "src/changes.json",
399
400
  "src/scripts/codemod",
package/src/changes.json CHANGED
@@ -1,4 +1,146 @@
1
1
  [
2
+ {
3
+ "version": "0.379.0",
4
+ "type": "improvement",
5
+ "title": "PII backfill encrypts jsonb object and array fields",
6
+ "detail": "runPiiEventBackfill and backfillEventPiiEncryption now retro-encrypt plaintext jsonb objects and arrays of personal fields and personal jsonb scalars, and createPiiBackfillStateTable bootstraps under an advisory lock so concurrent first boots no longer skip the ALTER. Completed backfill states rerun once because the transform version changed."
7
+ },
8
+ {
9
+ "version": "0.379.0",
10
+ "type": "improvement",
11
+ "title": "applyEntityEvent is exported from the db barrel, extension sources accept auto-verbs",
12
+ "detail": "applyEntityEvent can be used by extendEntityProjection apply handlers to project stored entity events of a foreign aggregate type. validateProjectionApplyKeys accepts the entity auto-verbs for an extension source that is not a registered entity."
13
+ },
14
+ {
15
+ "version": "0.379.0",
16
+ "type": "breaking",
17
+ "title": "SSE entity signals follow the read access of the connection",
18
+ "detail": "A signed-in connection receives an entity signal only when a query reading that entity lets one of its roles in (SseRouteOptions.entityReadAccess, built by collectEntityReadAccess). Entities without a reading query no longer go on the channel. When the reading access depends on a row or owner filter, the signal goes out without id and version, so a client refetches its query instead of learning which row changed.",
19
+ "migration": "Apps that rely on a signal for an entity no query exposes to the user must add a query for that entity with a matching access rule. Custom buildServer callers that build SseRouteOptions themselves must pass entityReadAccess."
20
+ },
21
+ {
22
+ "version": "0.379.0",
23
+ "type": "fix",
24
+ "title": "Cause details only with NODE_ENV development or test",
25
+ "detail": "internal_error responses and write error infos carry the cause snapshot (name, message, stack) only when NODE_ENV is explicitly development or test. A missing or misspelled NODE_ENV no longer exposes stack traces, paths or upstream URLs. kumiko-dev starts the server with NODE_ENV=development when none is set, so local dev keeps the details; a dev script that runs the server directly (bun --watch bin/server.ts) must set NODE_ENV=development itself to see them."
26
+ },
27
+ {
28
+ "version": "0.379.0",
29
+ "type": "breaking",
30
+ "title": "Global IP rate-limit middleware default matches runProdApp",
31
+ "detail": "globalIpRateLimit defaults to 600 requests per 60 seconds (was 1000). DEFAULT_GLOBAL_RATE_LIMIT is exported from the api barrel and shared with server-runtime. authEndpointRateLimit keeps its default of 5 per 60 seconds.",
32
+ "migration": "Apps that use globalIpRateLimit directly and relied on 1000 pass limit explicitly. runProdApp users see no change."
33
+ },
34
+ {
35
+ "version": "0.379.0",
36
+ "type": "improvement",
37
+ "title": "Boot warning when /metrics is off in production",
38
+ "detail": "buildServer logs a warning in production when no metrics option is set, which happens when PROMETHEUS_METRICS_TOKEN is unset."
39
+ },
40
+ {
41
+ "version": "0.379.0",
42
+ "type": "improvement",
43
+ "title": "Package ships its event-store table catalog",
44
+ "detail": "kumiko-es-tables.json is included in the published files; no runtime change."
45
+ },
46
+ {
47
+ "version": "0.379.0",
48
+ "type": "breaking",
49
+ "title": "Direct writes on event-fed tables throw at runtime",
50
+ "detail": "buildEntityTable and r.projection mark their tables. insertOne, updateMany, deleteMany, the upsert and counter helpers, ctx.db write methods and db.global() writes throw AccessDeniedError on a marked table unless the runner carries the framework write capability. The executor, projection apply, rebuild, unsafeProjectionDelete, tenant-destroy hooks and deleteRowsWithoutReplayableStream hold it; ctx.db.unsafeRaw() never does. Retention deletes only rows whose stream is gone or archived. The cascade delete hook, the soft-delete purge jobs and seedReferenceData now write through the executor, so their effect survives a rebuild; cascade children are written as the system user, and softDelete children are soft-deleted instead of hard-deleted. The direct-entity-writes guard has no exceptions left.",
51
+ "migration": "Write event-fed rows through the executor (r.crud, ctx.write, createEventStoreExecutor). Projection apply functions must use the db they receive. Test seeds and teardown use seedRow, seedRows, updateRows, deleteRows and clearTables from kumiko-testing instead of hand-written wipe helpers. Do not pass ctx.db.unsafeRaw() to code that deletes event-fed rows; pass ctx.db inside a tenant-destroy hook. Retention code calls deleteRowsWithoutReplayableStream after pruning or archiving the events. seedReferenceData takes the registry instead of a table map. Affects kumiko-enterprise, kumiko-studio and solon."
52
+ },
53
+ {
54
+ "version": "0.379.0",
55
+ "type": "fix",
56
+ "title": "Tests pin the money units of form derivations",
57
+ "detail": "form-derivations tests now cover realistic money values (1234,56 EUR plus 234,57 EUR, 0,10 plus 0,20) and minor-unit list cells; no behaviour change.",
58
+ "migration": "None."
59
+ },
60
+ {
61
+ "version": "0.379.0",
62
+ "type": "fix",
63
+ "title": "A slow SSE client no longer buffers without bound",
64
+ "detail": "The SSE route tracks the writes a client has not consumed. A client whose queued frames make no progress for SSE_STALLED_CLIENT_TIMEOUT_MS (one heartbeat interval), or that queues SSE_MAX_PENDING_WRITES_PER_CLIENT frames, is aborted and unregistered; it reconnects and resyncs. A large broadcast batch does not drop a client that keeps reading, and one failing client no longer stops the broadcast to the others. SseRouteOptions.now makes the clock injectable."
65
+ },
66
+ {
67
+ "version": "0.379.0",
68
+ "type": "improvement",
69
+ "title": "Deprecations name their removal release",
70
+ "detail": "The three @deprecated APIs without a target release (the minor-units money accessor and the landingPath props of the invite and signup screens) now say they are removed in 0.390.0."
71
+ },
72
+ {
73
+ "version": "0.379.0",
74
+ "type": "breaking",
75
+ "title": "buildEntityTableMeta and observabilityOptions removed",
76
+ "detail": "buildEntityTableMeta was a deprecated alias of deriveEntityTableMeta. The deprecated observabilityOptions option of the entrypoint and the prod and worker runners is gone as well, it had no effect since the HTTP route label no longer carries a query string.",
77
+ "migration": "Rename buildEntityTableMeta to deriveEntityTableMeta (same signature). Delete observabilityOptions from the entrypoint and runner options, nothing replaces it."
78
+ },
79
+ {
80
+ "version": "0.379.0",
81
+ "type": "improvement",
82
+ "title": "readDatabaseNow(db) in framework/db",
83
+ "detail": "Typed read of the Postgres clock for a TenantDb without unsafeRaw. Inside a transaction now() is the transaction start, so repeated reads agree."
84
+ },
85
+ {
86
+ "version": "0.379.0",
87
+ "type": "fix",
88
+ "title": "SaveContext.event no longer reaches JSON responses",
89
+ "detail": "The event echoed on the SaveContext of create, update and restore is non-enumerable, so a handler returning the save result no longer leaks event data (PII) in its response; hooks and projections still read it by property."
90
+ },
91
+ {
92
+ "version": "0.379.0",
93
+ "type": "fix",
94
+ "title": "Dashboard navigate params may bind a record",
95
+ "detail": "The boot validator accepts navigate params on a dashboard row action that has a record binding (record.param) but no filter; it still rejects params when the dashboard reads no filter, time range or record binding."
96
+ },
97
+ {
98
+ "version": "0.379.0",
99
+ "type": "breaking",
100
+ "title": "Re-export shim packages removed",
101
+ "detail": "The shims headless, dispatcher-live, renderer-web, http and repo-manifest are gone. Their code lives in kumiko-renderer (/headless, /live, /web), kumiko-framework (/http) and kumiko-guards (/repo-manifest).",
102
+ "migration": "Run bun kumiko-upgrade --apply, or bun kumiko codemod package-merge . --write. It rewrites the imports and package.json dependencies of all five shims to their target packages.",
103
+ "codemod": "scripts/codemod/package-merge.ts"
104
+ },
105
+ {
106
+ "version": "0.379.0",
107
+ "type": "breaking",
108
+ "title": "TenantDb.databaseNow",
109
+ "detail": "TenantDb has a databaseNow() method returning Postgres now(); custom TenantDb implementations must add it.",
110
+ "migration": "Add databaseNow(): Promise<Date> to hand-built TenantDb objects and mocks, returning the Postgres now()."
111
+ },
112
+ {
113
+ "version": "0.378.0",
114
+ "type": "improvement",
115
+ "title": "actionForm can derive head totals from an embedded list",
116
+ "detail": "An actionForm screen declares `derive` for head fields: sum, subtract, multiply, percent over other fields, or listSum over one cell of an embedded list with an optional whenEmpty fallback. The renderer recomputes them live and shows them read-only; a listSum without whenEmpty becomes an input while the list is empty. withFormDerivations(schema, screen) recomputes them on the server before validation, so client totals are never trusted; boot fails when the handler schema is not wrapped with the screen's own derive. A field that only feeds a `whenEmpty` fallback is read-only while the lists have rows. New embedded derived op `percent` (from[0] * from[1] / 100).",
117
+ "migration": "Add `derive` to the actionForm screen and wrap the handler schema with `withFormDerivations(schema, screen)`; new embedded derived op `percent`."
118
+ },
119
+ {
120
+ "version": "0.378.0",
121
+ "type": "improvement",
122
+ "title": "Extension-selector settings form is one standard configEdit screen",
123
+ "detail": "Without owner panels the provider form is the `<owner>-tenant` screen itself (no dashboard, no `<owner>-tenant-selection`). The provider select offers an empty \"platform default\" option next to the plugin ids."
124
+ },
125
+ {
126
+ "version": "0.378.0",
127
+ "type": "improvement",
128
+ "title": "Search index setup helpers",
129
+ "detail": "collectSearchableFieldNames(registry) returns the deduplicated searchable fields of all entities and configureTenantSearchIndex(adapter, registry, tenantId) configures a tenant index with them, both exported from the search entry."
130
+ },
131
+ {
132
+ "version": "0.378.0",
133
+ "type": "improvement",
134
+ "title": "TenantDb.advisoryXactLock",
135
+ "detail": "ctx.db.advisoryXactLock(namespace, key) takes a transaction-scoped pg_advisory_xact_lock keyed on the handle's own tenantId plus key, so tenants never contend and the caller cannot pick another tenant. It needs no unsafeRaw grant."
136
+ },
137
+ {
138
+ "version": "0.378.0",
139
+ "type": "breaking",
140
+ "title": "Boot check for projectionList writeHandler actions",
141
+ "detail": "Boot now rejects projectionList rowActions and toolbarActions of kind writeHandler whose handler is not a registered write-handler QN. Such actions only failed on click before.",
142
+ "migration": "Fix the handler QN to \"<feature>:write:<short>\" of a handler declared via r.writeHandler(...), or remove the action. Apps that fail to boot name the screen and action id in the error."
143
+ },
2
144
  {
3
145
  "version": "0.377.0",
4
146
  "type": "improvement",
@@ -165,3 +165,23 @@ bun node_modules/@cosmicdrift/kumiko-framework/src/scripts/codemod/package-merge
165
165
 
166
166
  Runs via `bun kumiko-upgrade --apply`. Without a mode flag it writes. `--check` exits 1 while any
167
167
  site is left. Run your package manager's install afterwards to refresh the lockfile.
168
+
169
+ ## feature-layout.ts
170
+
171
+ Applies the feature layout rule "the runtime of a file follows from its path": everything
172
+ under `src/features/<feature>/web/` is client code, the rest of the feature is server or
173
+ isomorphic. Every file that carries a runtime-client marker outside `web/` moves to
174
+ `<feature>/web/` (sub-structure kept), the relative imports, re-exports, `import()` calls and
175
+ `mock.module` strings that point at it or out of it are rewritten, `package.json` export
176
+ targets follow, and the marker is dropped. A marker the path already implies (client in
177
+ `web/`, test in `__tests__/`) is only removed. Idempotent.
178
+
179
+ ```bash
180
+ bun node_modules/@cosmicdrift/kumiko-framework/src/scripts/codemod/feature-layout.ts <dir> [--check | --dry-run | --write] [--no-format]
181
+ ```
182
+
183
+ Features are found as `src/features/<feature>/` and `bundled-features/src/<feature>/` below
184
+ `<dir>`; `--feature-dir <dir>` names a directory that is itself one feature and
185
+ `--features-root <dir>` one whose children are features. A target that already exists in
186
+ `web/` and a marker no path rule covers (for example `dev` outside `dev/`) are reported as
187
+ `manual` and left alone. `--check` exits 1 while anything is left.
@@ -180,7 +180,7 @@ function findBiomeBinary(startDir: string): string | undefined {
180
180
 
181
181
  const FORMAT_BATCH_SIZE = 50;
182
182
 
183
- async function formatWithBiome(
183
+ export async function formatWithBiome(
184
184
  codemodName: string,
185
185
  files: readonly string[],
186
186
  cwd: string,