@cosmicdrift/kumiko-types 0.360.0 → 0.361.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.
@@ -12,6 +12,15 @@ export type PreSaveRunner = (changes: Record<string, unknown>, previous: Readonl
12
12
  export type StreamTenantOption = {
13
13
  readonly streamTenantId?: TenantId | undefined;
14
14
  };
15
+ export declare const WritePurposes: {
16
+ readonly retention: "retention";
17
+ readonly erasure: "erasure";
18
+ };
19
+ export type WritePurpose = (typeof WritePurposes)[keyof typeof WritePurposes];
20
+ export type LockGuardOption = {
21
+ readonly unsafeSkipLockGuard?: boolean;
22
+ readonly writePurpose?: WritePurpose;
23
+ };
15
24
  export type EventStoreExecutor = {
16
25
  create: (payload: Record<string, unknown>, user: SessionUser, db: TenantDb, options?: StreamTenantOption & {
17
26
  preSave?: PreSaveRunner;
@@ -20,7 +29,7 @@ export type EventStoreExecutor = {
20
29
  id: EntityId;
21
30
  version?: number | undefined;
22
31
  changes: Record<string, unknown>;
23
- }, user: SessionUser, db: TenantDb, options?: StreamTenantOption & {
32
+ }, user: SessionUser, db: TenantDb, options?: StreamTenantOption & LockGuardOption & {
24
33
  skipOptimisticLock?: boolean;
25
34
  skipUnchanged?: boolean;
26
35
  includeDeleted?: boolean;
@@ -29,7 +38,7 @@ export type EventStoreExecutor = {
29
38
  }) => Promise<WriteResult<SaveContext>>;
30
39
  delete: (payload: {
31
40
  id: EntityId;
32
- }, user: SessionUser, db: TenantDb, options?: StreamTenantOption) => Promise<WriteResult<DeleteContext>>;
41
+ }, user: SessionUser, db: TenantDb, options?: StreamTenantOption & LockGuardOption) => Promise<WriteResult<DeleteContext>>;
33
42
  forget: (payload: {
34
43
  id: EntityId;
35
44
  }, user: SessionUser, db: TenantDb, options?: StreamTenantOption) => Promise<WriteResult<DeleteContext>>;
@@ -1 +1,3 @@
1
- export {};
1
+ // Why a framework system-user write may reach a locked row. Statutory deletion (retention)
2
+ // and Art. 17 erasure must succeed on locked rows; any other system write stays blocked.
3
+ export const WritePurposes = { retention: "retention", erasure: "erasure" };
package/dist/fields.d.ts CHANGED
@@ -472,6 +472,9 @@ export type EmbeddedSubFieldDef = (EmbeddedSubFieldBase & {
472
472
  }) | (EmbeddedSubFieldBase & {
473
473
  readonly type: "select";
474
474
  readonly options: readonly string[];
475
+ /** Same contract as `SelectFieldDef.display`; in a table cell the
476
+ * default stays a dropdown. */
477
+ readonly display?: "radio" | "dropdown";
475
478
  }) | (EmbeddedSubFieldBase & {
476
479
  readonly type: "reference";
477
480
  readonly entity: string;
@@ -751,6 +754,15 @@ export type EntityDefinition<F extends FieldsMap = FieldsMap, T extends EntityTe
751
754
  readonly eventMigrations?: readonly EntityEventMigration[];
752
755
  readonly searchWeight?: number;
753
756
  readonly defaultCurrency?: string;
757
+ /** Rows whose stored state meets this condition are read-only: every executor
758
+ * update and delete on them is rejected with a 409 (`errors.recordLocked`), whatever
759
+ * handler or job calls it. The write that sets the lock passes, because the check reads the
760
+ * state before the write. Forget is exempt, and so are framework system-user writes that name
761
+ * a `writePurpose` (retention, erasure); a system user without one is blocked like anyone else.
762
+ * A call site that must unlock a row passes `{ unsafeSkipLockGuard: true }` to that one executor call. The entityEdit
763
+ * screen shows such rows in view mode. The condition field must be a plaintext text, select,
764
+ * boolean, number or reference field. */
765
+ readonly lockedWhen?: FieldCondition;
754
766
  /** Allowed state transitions per field. Boot validates against select options. */
755
767
  readonly transitions?: Readonly<Record<string, TransitionMap>>;
756
768
  /** Composite-Indices über mehrere Felder. Single-column FK-Indices und
package/dist/screen.d.ts CHANGED
@@ -55,6 +55,15 @@ export interface FieldFormatRegistry {
55
55
  json: {
56
56
  readonly indent?: number;
57
57
  };
58
+ /** List cell: FileRef id (or id array) as a thumbnail. `variant` names an
59
+ * image variant to request (falls back to the original if it is not served).
60
+ * Outside list cells the raw value is shown. */
61
+ image: {
62
+ readonly variant?: string;
63
+ };
64
+ /** List cell: FileRef id (or id array) as a download link with the file name.
65
+ * Outside list cells the raw value is shown. */
66
+ file: object;
58
67
  }
59
68
  export type FormatSpec = {
60
69
  [K in keyof FieldFormatRegistry]: {
@@ -440,6 +449,8 @@ export type EntityListScreenDefinition = {
440
449
  * through `defaultFilters` and the URL). Fields without an entry keep the
441
450
  * default dropdown. */
442
451
  readonly facets?: Readonly<Record<string, EntityListFacetConfig | false>>;
452
+ /** Replaces the default empty-list title/hint; the create button stays when the list can create. */
453
+ readonly emptyState?: ListEmptyStateSpec;
443
454
  /** i18n key for the primary create button label (default `kumiko.actions.create`). */
444
455
  readonly createLabel?: string;
445
456
  /** i18n key for the search field placeholder (default `kumiko.list.search-placeholder`). */
@@ -519,6 +530,8 @@ export type ProjectionListScreenDefinition = {
519
530
  readonly pagination?: ListPaginationMode;
520
531
  readonly pageSize?: number;
521
532
  readonly defaultSort?: ListSortSpec;
533
+ /** Replaces the default empty-list title/hint; the create button stays when the list can create. */
534
+ readonly emptyState?: ListEmptyStateSpec;
522
535
  /** i18n key for the primary create button label (default `kumiko.actions.create`). */
523
536
  readonly createLabel?: string;
524
537
  /** i18n key for the search field placeholder (default `kumiko.list.search-placeholder`). */
@@ -562,7 +575,10 @@ export type RecordHeaderSpec = {
562
575
  * (`target="_blank"`) instead of plain text. Only valid with the string
563
576
  * form of `subtitle`. */
564
577
  readonly subtitleHref?: string;
565
- /** Badge tone per `status` field value; unlisted values fall back to the value heuristic. */
578
+ /** Record field whose value selects the `statusTones` entry, for a badge text that
579
+ * carries more than the state (e.g. "Terminated on 31.03.2027"). Defaults to `status`. */
580
+ readonly statusToneField?: string;
581
+ /** Badge tone per `statusToneField` value (default: the `status` value); unlisted values fall back to the value heuristic. */
566
582
  readonly statusTones?: {
567
583
  readonly [statusValue: string]: SelectOptionTone;
568
584
  };
@@ -642,6 +658,12 @@ export type ProjectionDetailScreenDefinition = {
642
658
  * `rowClick` has no target here (there is no row to click) and is
643
659
  * rejected by the boot-validator. */
644
660
  readonly actions?: readonly RowAction[];
661
+ /** Id of the action that stays the visible header button when the actions
662
+ * collapse into a menu (and the one that keeps the phone icon slot when it
663
+ * has an icon and no confirm). An `actions` id or `"edit"` (the default edit
664
+ * action); default precedence is `edit`, then `style: "primary"`, then the
665
+ * first action. */
666
+ readonly primaryAction?: string;
645
667
  /** How head fields render. Default "text" — plain text instead of a
646
668
  * disabled Input, since every field on this screen type is forced
647
669
  * readOnly anyway (there is no write path, see the shim doc above).
@@ -934,6 +956,25 @@ export type EditFieldSpec = string | {
934
956
  /** Row field on the referenced entity shown as the label (default
935
957
  * "id"). Only meaningful together with `refEntity`. */
936
958
  readonly refLabelField?: string;
959
+ /** Embedded-list fields only (`createEmbeddedListField`). */
960
+ readonly embeddedList?: EmbeddedListEditSpec;
961
+ /** Fills an empty value when the create form opens; the update form and
962
+ * values from URL params or a handoff are never touched. Unlike a
963
+ * field `default`, it is not applied server-side. */
964
+ readonly prefill?: EditFieldPrefill;
965
+ };
966
+ export type EmbeddedListEditSpec = {
967
+ /** Sub-fields shown as read-only cells; every other cell stays editable. */
968
+ readonly readOnlyCells?: readonly string[];
969
+ /** `true` keeps the rows as they are: no add, remove, duplicate, move or
970
+ * paste-appended rows. Cells stay editable (unless in `readOnlyCells`). */
971
+ readonly fixedRows?: boolean;
972
+ };
973
+ export type EditFieldPrefill = {
974
+ readonly kind: "today";
975
+ } | {
976
+ readonly kind: "currentUser";
977
+ readonly property: "displayName" | "email" | "id";
937
978
  };
938
979
  export type EditSectionSpec = EditFieldsSection | EditExtensionSection | EditRelatedListSection | EditWriteFormSection;
939
980
  export type EditFieldsSection = {
@@ -1037,6 +1078,16 @@ export declare function relatedListGroupHeaderLabel(groupBy: RelatedListGroupBy,
1037
1078
  export type RelatedListRowTone = Exclude<FieldCondition, boolean> & {
1038
1079
  readonly tone: SelectOptionTone;
1039
1080
  };
1081
+ /** Replacement for the default "no rows" box of a list. `title`/`description`
1082
+ * are i18n keys. `action` is a record action evaluated against an empty
1083
+ * record on list screens (the parent record on a relatedList); its icon must
1084
+ * resolve, same as `ProjectionDetailScreenDefinition.actions`. A narrowed list
1085
+ * (search term or changed filters) shows the neutral "no matches" text instead. */
1086
+ export type ListEmptyStateSpec = {
1087
+ readonly title: string;
1088
+ readonly description?: string;
1089
+ readonly action?: RowAction;
1090
+ };
1040
1091
  export type EditRelatedListSection = {
1041
1092
  /** Kebab-case tab id, used as the `?tab=` value. Required by the
1042
1093
  * boot-validator when the enclosing `EditLayout.mode` is "tabs". */
@@ -1137,11 +1188,7 @@ export type EditRelatedListSection = {
1137
1188
  * section's query returns zero rows. Omitted: the DataTable default
1138
1189
  * ("kumiko.list.no-entries") applies. `action`'s icon must resolve, same
1139
1190
  * as `actions` above. */
1140
- readonly emptyState?: {
1141
- readonly title: string;
1142
- readonly description?: string;
1143
- readonly action?: RowAction;
1144
- };
1191
+ readonly emptyState?: ListEmptyStateSpec;
1145
1192
  };
1146
1193
  export type EditWriteFormSection = {
1147
1194
  /** Kebab-case tab id, used as the `?tab=` value. Required by the
@@ -1246,6 +1293,14 @@ export type EntityEditScreenDefinition = {
1246
1293
  * wizard: "Add {make} {model}"). Falls back to the plain title while the
1247
1294
  * placeholders are still empty. */
1248
1295
  readonly titleTemplate?: string;
1296
+ /** Shows a saved record read-only while the condition holds for the loaded
1297
+ * record (not for unsaved form values): all fields disabled, no save, discard
1298
+ * or delete button; record `actions` stay. The title uses
1299
+ * `screen:<id>.view.title`, then `screen:<id>.title`. Never applies to create.
1300
+ * The entity's `lockedWhen` always applies as well; set this only to add a
1301
+ * UI-only condition (either one shows the view).
1302
+ * UI only: the update handler still has to enforce access. */
1303
+ readonly readOnly?: FieldCondition;
1249
1304
  readonly layout: EditLayout;
1250
1305
  /** Optionaler i18n-Key (oder Roh-String) für den Submit-Button. Default
1251
1306
  * `kumiko.actions.save`. Lässt den Auto-Edit-Screen domain-spezifische
@@ -1449,6 +1504,10 @@ export type ActionFormRedirect = {
1449
1504
  * `projectionDetail`); a non-string or missing value navigates without
1450
1505
  * an id, same as a payload without `id` does today. */
1451
1506
  readonly idFrom: string;
1507
+ /** Section id to open on the target `projectionDetail` with `layout.mode:
1508
+ * "tabs"` (the `tab` search param, same as `ToolbarAction` navigate `tab`).
1509
+ * Boot rejects other targets and unknown section ids. */
1510
+ readonly tab?: string;
1452
1511
  };
1453
1512
  /** Author-declared field shown once after a mint-form submit — API token,
1454
1513
  * recovery codes, similar one-time secrets. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-types",
3
- "version": "0.360.0",
3
+ "version": "0.361.0",
4
4
  "description": "Framework-Type-Definitions für Kumiko — FeatureDefinition, BootCheck-Types und die reinen Engine-Types. Erlaubt Downstream-Konsumenten, gegen die Type-Contracts zu bauen, ohne das ganze Framework-Package zu importieren. Enthaelt keine identitaets-sensitiven Runtime-Werte mehr (Error-Klassen leben seit #1629 in kumiko-framework, Brand-Symbole nutzen Symbol.for) und ist deshalb eine plain dependency, keine peerDependency.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
package/src/changes.json CHANGED
@@ -1,4 +1,34 @@
1
1
  [
2
+ {
3
+ "version": "0.361.0",
4
+ "type": "improvement",
5
+ "title": "EditFieldSpec.prefill",
6
+ "detail": "New EditFieldPrefill, today or currentUser with displayName, email or id, on the object form of EditFieldSpec."
7
+ },
8
+ {
9
+ "version": "0.361.0",
10
+ "type": "improvement",
11
+ "title": "entityEdit screens accept a readOnly condition",
12
+ "detail": "EntityEditScreenDefinition.readOnly is a FieldCondition evaluated against the loaded record. UI only, the server is not affected."
13
+ },
14
+ {
15
+ "version": "0.361.0",
16
+ "type": "improvement",
17
+ "title": "EditFieldSpec.embeddedList for read-only cells and fixed rows",
18
+ "detail": "New EmbeddedListEditSpec with readOnlyCells and fixedRows on the object form of EditFieldSpec."
19
+ },
20
+ {
21
+ "version": "0.361.0",
22
+ "type": "improvement",
23
+ "title": "display on embedded select sub-fields",
24
+ "detail": "The select variant of EmbeddedSubFieldDef accepts display radio or dropdown."
25
+ },
26
+ {
27
+ "version": "0.361.0",
28
+ "type": "improvement",
29
+ "title": "EntityDefinition.lockedWhen and the executor options unsafeSkipLockGuard and writePurpose",
30
+ "detail": "lockedWhen is a FieldCondition evaluated against the stored row. update and delete on the EventStoreExecutor take unsafeSkipLockGuard to opt one call out, and writePurpose (WritePurposes.retention or erasure) lets the framework system user through. EntityEditScreenDefinition.readOnly is combined with the entity's lockedWhen (either shows the view)."
31
+ },
2
32
  {
3
33
  "version": "0.357.0",
4
34
  "type": "improvement",