@cosmicdrift/kumiko-types 0.386.0 → 0.388.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/dist/fields.d.ts CHANGED
@@ -17,11 +17,10 @@ export type ResolvedPiiFlags = {
17
17
  readonly recordOwned?: true;
18
18
  readonly anonymize?: () => unknown | Promise<unknown>;
19
19
  readonly allowPlaintext?: string;
20
- /** Equality-Lookups (fetchOne/filter eq) bleiben trotz Verschluesselung
21
- * moeglich: generierte `<snake>_bidx`-Spalte traegt einen HMAC-Blind-
22
- * Index, Query-Compiler matchen `(col = $1 OR col_bidx = $2)`. Nur auf
23
- * text-Feldern MIT Subject-Annotation erlaubt (Boot-Validator).
24
- * Siehe docs/plans/datenschutz/blind-index.md (kumiko-framework#818). */
20
+ /** Equality lookups (`fetchOne`/`filter eq`) remain possible despite encryption:
21
+ * the generated `<snake>_bidx` column carries an HMAC blind index, and the
22
+ * query compiler matches `(col = $1 OR col_bidx = $2)`. Allowed only on text
23
+ * fields with subject annotations, as enforced by the boot validator. */
25
24
  readonly lookupable?: true;
26
25
  /** Foreign key into the `user` entity (authorId, assigneeId, ...) with no
27
26
  * annotated content of its own. Triggers no encryption — feeds only the
@@ -94,9 +93,9 @@ export type TextFieldDef = {
94
93
  readonly required?: boolean;
95
94
  readonly searchable?: boolean;
96
95
  readonly sortable?: boolean;
97
- /** Author erlaubt Screen-Filter auf diesem Feld (Tier 2.7c).
98
- * Boot-Validator weist Filter mit `filterable !== true` zurück.
99
- * Default: false — analog zu `sortable`, opt-in. */
96
+ /** Allows screen filters on this field.
97
+ * The boot validator rejects filters with `filterable !== true`.
98
+ * Default: false, analogous to `sortable`, and therefore opt-in. */
100
99
  readonly filterable?: boolean;
101
100
  readonly encrypted?: boolean;
102
101
  readonly sensitive?: boolean;
@@ -125,40 +124,38 @@ export type TextFieldDef = {
125
124
  readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
126
125
  readonly default?: string;
127
126
  readonly access?: FieldAccess;
128
- /** Mehrzeiliger Text — DefaultInput rendert dann ein <textarea> statt
129
- * <input type="text">. `true` = Default 4 Zeilen, `{ rows: N }` =
130
- * explizite Höhe. Search/sort/encrypt verhalten sich unverändert
131
- * identisch zu single-line — nur die Render-Surface wechselt. */
127
+ /** Multiline text causes DefaultInput to render a <textarea> instead of
128
+ * <input type="text">. `true` uses four rows by default; `{ rows: N }`
129
+ * sets an explicit height. Search, sort, and encryption remain unchanged;
130
+ * only the rendering surface differs. */
132
131
  readonly multiline?: boolean | {
133
132
  readonly rows?: number;
134
133
  };
135
134
  } & ResolvedPiiFlags;
136
135
  /**
137
- * Long-form text content — source-code, markdown, blog-posts, email-
138
- * templates, anything that can be megabytes large. Bewusst MINIMALE
139
- * Surface gegen `text`:
136
+ * Long-form text content — source code, markdown, blog posts, email
137
+ * templates, and anything that can be megabytes large. Its surface is
138
+ * intentionally minimal compared with `text`:
140
139
  *
141
- * - **Kein `sortable`**: ORDER BY auf 100 KB-Strings kostet I/O ohne
142
- * sinnvolles UX-Outcome (lex-Sortierung von Code ist Nonsense).
143
- * - **Kein `searchable`**: ILIKE/Substring-Suche auf langen Texten
144
- * skaliert nicht. Wer wirklich Volltextsuche will, nimmt den
145
- * SearchAdapter (Meilisearch) — der hat eine eigene Pipeline mit
146
- * Tokenizer + Index, NICHT diesen field-flag.
147
- * - **Kein `filterable`**: WHERE auf langen Strings same Story wie
148
- * sortable.
149
- * - **Kein `format`**: email/url/phone sind kurz definierte Inputs,
150
- * longText ist per Definition unstrukturiert.
140
+ * - **No `sortable`**: ordering 100 KB strings costs I/O without useful UX;
141
+ * lexicographic ordering of code is not meaningful.
142
+ * - **No `searchable`**: ILIKE/substring searches do not scale on long text.
143
+ * Full-text search should use the SearchAdapter (Meilisearch), which has
144
+ * its own tokenizer and index rather than this field flag.
145
+ * - **No `filterable`**: filtering long strings has the same issue as sorting.
146
+ * - **No `format`**: email/url/phone are short, structured inputs, whereas
147
+ * longText is unstructured by definition.
151
148
  *
152
- * Type-level enforcement statt convention: wer sortable/searchable
153
- * braucht, nimmt `text` (mit den entsprechenden Skalierungs-Trade-offs).
154
- * DB-mapping ist identisch zu text (Postgres `text` ist unbounded).
149
+ * Type-level enforcement means callers who need sorting or searching must use
150
+ * `text` and accept its scaling trade-offs. Database mapping is identical to
151
+ * `text` (Postgres `text` is unbounded).
155
152
  */
156
153
  export type LongTextFieldDef = {
157
154
  readonly type: "longText";
158
155
  readonly description?: string;
159
- /** Optionale soft-Cap. Default unbounded (= Postgres-text-limit, 1 GB).
160
- * Nützlich für defensive Caps wie 1 MB damit ein verirrter Browser-
161
- * Paste nicht die DB sprengt. */
156
+ /** Optional soft cap. Default is unbounded (= Postgres text limit, 1 GB).
157
+ * Useful for defensive limits such as 1 MB so an accidental browser paste
158
+ * does not overwhelm the database. */
162
159
  readonly maxLength?: number;
163
160
  readonly required?: boolean;
164
161
  readonly encrypted?: boolean;
@@ -265,7 +262,7 @@ export type MultiSelectFieldDef<TOptions extends readonly string[] = readonly st
265
262
  readonly required?: boolean;
266
263
  readonly filterable?: boolean;
267
264
  readonly sensitive?: boolean;
268
- /** Default-Auswahl. Jeder Eintrag muss in `options` sein (Boot-Validator). */
265
+ /** Default selection. Every entry must occur in `options` (boot validator). */
269
266
  readonly default?: readonly TOptions[number][];
270
267
  readonly access?: FieldAccess;
271
268
  /** Renderer metadata: `"checkboxes"` renders every option as a visible
@@ -321,16 +318,15 @@ export type NumberFieldDef = {
321
318
  readonly grouping?: boolean;
322
319
  } & ResolvedPiiFlags;
323
320
  /**
324
- * 64-bit-Integer-Spalte fuer Audit-Counter, Byte-Sizes, Event-IDs und
325
- * andere Werte die >2^31 (~2.1 Mrd) wandern koennen. Storage als
326
- * Postgres `bigint`, JS-Round-trip als `number` (mode:"number" — sicher
327
- * bis 2^53 ≈ 9 PB, JSON-serialisierbar). Wer >2^53 braucht (rare),
328
- * nutzt einen `text`-Field mit eigenem Codec.
321
+ * 64-bit integer field for audit counters, byte sizes, event IDs, and other
322
+ * values that can exceed 2^31 (~2.1 billion). Stored as Postgres `bigint`,
323
+ * with a JavaScript `number` round trip (`mode:"number"`, safe through 2^53
324
+ * ≈ 9 PB and JSON-serializable). Values above 2^53 should use a `text` field
325
+ * with a custom codec.
329
326
  *
330
- * Vorrang vor `NumberFieldDef`-(integer 32-bit-Cap, ~2.1 GB) immer dann
331
- * wenn der Wert physisch ueber dieses Limit klettern kann: Bytes,
332
- * Events, Counters in High-Throughput-Apps, Cumulative-Sums. Money
333
- * hat dafuer den eigenen `MoneyFieldDef` (mit Currency-Spalte).
327
+ * Prefer this over `NumberFieldDef`'s 32-bit integer cap whenever a value can
328
+ * physically exceed that limit: bytes, events, high-throughput counters, and
329
+ * cumulative sums. Money has its own `MoneyFieldDef` with a currency column.
334
330
  */
335
331
  export type BigIntFieldDef = {
336
332
  readonly type: "bigInt";
@@ -368,9 +364,9 @@ export type DecimalFieldDef = {
368
364
  } & ResolvedPiiFlags;
369
365
  /** Where a money field's currency comes from. `"tenant"` is the
370
366
  * tenant-settings bundle's per-tenant currency (`tenant-settings:config:
371
- * currency`, kumiko-framework#2933); `"literal"` names a fixed ISO code and
372
- * is checked against the app's `currencies` list at boot
373
- * (kumiko-framework#2839). Discriminated union so a `{ kind: "field", of }`
367
+ * currency`); `"literal"` names a fixed ISO code and
368
+ * is checked against the app's `currencies` list at boot.
369
+ * Discriminated union so a `{ kind: "field", of }`
374
370
  * variant (amount and currency-picker side by side) can still be added
375
371
  * without a breaking change, once a multi-currency form actually exists. */
376
372
  export type MoneyCurrencySource = {
@@ -382,7 +378,7 @@ export type MoneyCurrencySource = {
382
378
  /** Qualified config key a `{ kind: "tenant" }` MoneyCurrencySource resolves
383
379
  * against — the tenant-settings bundle's per-tenant currency. Lives here
384
380
  * (client-safe) rather than framework/engine so renderer code can read it
385
- * without a runtime-only import (kumiko-framework#2937). */
381
+ * without a runtime-only import. */
386
382
  export declare const TENANT_CURRENCY_CONFIG_KEY = "tenant-settings:config:currency";
387
383
  export type MoneyFieldDef = {
388
384
  readonly type: "money";
@@ -406,27 +402,27 @@ export type ReferenceFieldDef = {
406
402
  readonly filterable?: boolean;
407
403
  readonly sensitive?: boolean;
408
404
  readonly access?: FieldAccess;
409
- /** Welches Feld der referenced Entity als Display-Label im
410
- * Select-Dropdown erscheint. Default: "id". Boot-Validator pinst
411
- * dass das Feld auf der referenced Entity existiert. */
405
+ /** Field of the referenced entity displayed as the Select dropdown label.
406
+ * Default: "id". The boot validator ensures that the field exists on the
407
+ * referenced entity. */
412
408
  readonly labelField?: string;
413
- /** Multi-Reference (Tier 2.7e-Multi): Wert ist ein Array von UUIDs
414
- * statt single UUID. Storage als jsonb-Array<uuid>. UI rendert
415
- * Multi-Select-Combobox mit Tag-Anzeige der gewählten Items. */
409
+ /** Multi-reference: the value is an array of UUIDs rather than a single UUID.
410
+ * Stored as a jsonb array of UUIDs; the UI renders a multi-select combobox
411
+ * with tags for the selected items. */
416
412
  readonly multiple?: boolean;
417
413
  /** Include a row in this entity's result set on a text-search hit against
418
414
  * the referenced entity's `labelField`, instead of only matching the UUID
419
- * column itself (fw#2660). Requires an explicit `labelField` — the boot
415
+ * column itself. Requires an explicit `labelField` — the boot
420
416
  * validator rejects it otherwise, since the default "id" is a UUID column
421
417
  * and ILIKE against it would crash at runtime. */
422
418
  readonly searchable?: true;
423
419
  /** Order a list by the referenced entity's `labelField` instead of by the raw
424
- * UUID column (fw#2741). Requires an explicit, non-"id" `labelField` — the
420
+ * UUID column. Requires an explicit, non-"id" `labelField` — the
425
421
  * boot validator rejects it otherwise, since ordering by a UUID produces a
426
422
  * sequence that looks meaningful to the user but is arbitrary. */
427
423
  readonly sortable?: true;
428
424
  /** Fill the picker from this query handler instead of the referenced
429
- * entity's own list handler (fw#2780) — for targets whose identity is
425
+ * entity's own list handler — for targets whose identity is
430
426
  * composed from joined rows, where no single column of the referenced
431
427
  * entity is a readable label. Fully-qualified QN
432
428
  * ("<feature>:query:<short>"), boot-validated against the registered
@@ -481,7 +477,7 @@ export type EmbeddedSubFieldDef = (EmbeddedSubFieldBase & {
481
477
  readonly type: "reference";
482
478
  readonly entity: string;
483
479
  readonly labelField?: string;
484
- /** Same contract as `ReferenceFieldDef.optionsQuery` (fw#2780). */
480
+ /** Same contract as `ReferenceFieldDef.optionsQuery`. */
485
481
  readonly optionsQuery?: string;
486
482
  });
487
483
  /** A cell computed from other cells of the same row. `from` names the
@@ -535,7 +531,7 @@ export type EmbeddedFieldDef = {
535
531
  * field's amount. Validated both client- and server-side (schema-builder
536
532
  * superRefine). Only meaningful with `multiple: true`. Compares against
537
533
  * rounded `derived` cells, i.e. "sum-of-rounded" not "round-of-sum" —
538
- * see kumiko-framework#1866. */
534
+ * see the embedded-list totals-matching behavior. */
539
535
  readonly totalsMatch?: Readonly<Record<string, string>>;
540
536
  } & ResolvedPiiFlags;
541
537
  export type JsonbFieldDef = {
@@ -557,13 +553,13 @@ export type DateFieldDef = {
557
553
  readonly filterable?: boolean;
558
554
  readonly sensitive?: boolean;
559
555
  readonly access?: FieldAccess;
560
- /** Erlaubte Datumsgrenzen als ISO `yyyy-mm-dd` (z.B. Geburtsdatum nicht
561
- * in der Zukunft: `max` = heute). Begrenzt den Picker und wird vom
562
- * Zod-Schema beim Write durchgesetzt. */
556
+ /** Permitted date bounds as ISO `yyyy-mm-dd` (for example, a birth date
557
+ * cannot be in the future when `max` is today). The bounds constrain the
558
+ * picker and are enforced by the Zod schema on write. */
563
559
  readonly min?: string;
564
560
  readonly max?: string;
565
- /** Format/Locale-Override für Anzeige und Eingabe-Parsing (z.B.
566
- * "de-DE"). Default = App-Locale. */
561
+ /** Format/locale override for display and input parsing (for example,
562
+ * "de-DE"). Default: app locale. */
567
563
  readonly locale?: string;
568
564
  } & ResolvedPiiFlags;
569
565
  export type TimestampFieldDef = {
@@ -575,21 +571,20 @@ export type TimestampFieldDef = {
575
571
  readonly sensitive?: boolean;
576
572
  readonly access?: FieldAccess;
577
573
  /**
578
- * Marker: dieses Timestamp-Feld ist Wall-Clock-Zeit an einem Ort.
579
- * Wert ist der Name des begleitenden tz-Felds (IANA-Zone).
574
+ * Marker: this timestamp field represents wall-clock time at a location.
575
+ * The value is the name of the accompanying tz field (IANA zone).
580
576
  *
581
- * Beispiel: manuelles Pair
577
+ * Example: manual pair
582
578
  * { pickupAt: { type: "timestamp", locatedBy: "pickupTz" }, pickupTz: { type: "tz" } }
583
- * — bevorzuge stattdessen `createLocatedTimestampField()`.
579
+ * — prefer `createLocatedTimestampField()` instead.
584
580
  */
585
581
  readonly locatedBy?: string;
586
- /** Erlaubte Grenzen als ISO-Datetime. Begrenzt den Picker auf
587
- * Tages-Granularität; die exakte Uhrzeit-Grenze setzt das Zod-Schema
588
- * beim Write durch. */
582
+ /** Permitted bounds as ISO datetime values. The picker is limited to day
583
+ * granularity; the Zod schema enforces the exact time bound on write. */
589
584
  readonly min?: string;
590
585
  readonly max?: string;
591
- /** Format/Locale-Override für Anzeige und Eingabe-Parsing. Default =
592
- * App-Locale. */
586
+ /** Format/locale override for display and input parsing. Default: app
587
+ * locale. */
593
588
  readonly locale?: string;
594
589
  } & ResolvedPiiFlags;
595
590
  export type TzFieldDef = {
@@ -607,13 +602,13 @@ export type LocatedTimestampFieldDef = {
607
602
  readonly filterable?: boolean;
608
603
  readonly sensitive?: boolean;
609
604
  readonly access?: FieldAccess;
610
- /** Erlaubte Grenzen als ISO-Datetime (Wall-Clock). Begrenzt den Picker
611
- * auf Tages-Granularität; die exakte Uhrzeit-Grenze setzt das Zod-Schema
612
- * beim Write durch. */
605
+ /** Permitted bounds as ISO datetime values (wall-clock time). The picker is
606
+ * limited to day granularity; the Zod schema enforces the exact time bound
607
+ * on write. */
613
608
  readonly min?: string;
614
609
  readonly max?: string;
615
- /** Format/Locale-Override für Anzeige und Eingabe-Parsing. Default =
616
- * App-Locale. */
610
+ /** Format/locale override for display and input parsing. Default: app
611
+ * locale. */
617
612
  readonly locale?: string;
618
613
  } & ResolvedPiiFlags;
619
614
  export type FileFieldDef = {
@@ -673,7 +668,7 @@ export declare const FIELD_TYPE_NAMES: readonly FieldDefinition["type"][];
673
668
  * secretMint) may declare it: a `money` field there has no entity to borrow
674
669
  * `defaultCurrency` from, so it must name its own currency source, or the
675
670
  * form seeds a bare `0` that the handler's zod schema then rejects
676
- * (kumiko-framework#2839). Entity fields keep the optional `currency` —
671
+ * . Entity fields keep the optional `currency` —
677
672
  * `entity.defaultCurrency` is already boot-enforced for them. */
678
673
  export type FormFieldDefinition = Exclude<FieldDefinition, MoneyFieldDef> | (MoneyFieldDef & {
679
674
  readonly currency: MoneyCurrencySource;
@@ -707,35 +702,35 @@ export type DerivedFieldsMap = Readonly<Record<string, DerivedFieldDef>>;
707
702
  * DerivedFieldDef` cast lying about a field that's actually missing. */
708
703
  export type ClientDerivedFieldDef = Pick<DerivedFieldDef, "valueType">;
709
704
  export type TransitionMap = Readonly<Record<string, readonly string[]>>;
710
- /** Composite-Index auf einer Entity. Spalten werden via field-Name
711
- * referenziert (camelCase). buildEntityTable mapped sie auf snake_case-
712
- * Spaltennamen und benennt den Index nach Convention:
705
+ /** Composite index on an entity. Columns are referenced by field name
706
+ * (camelCase). `buildEntityTable` maps them to snake_case column names and
707
+ * names the index by convention:
713
708
  *
714
709
  * <table>_<col1>_<col2>_idx (non-unique)
715
710
  * <table>_<col1>_<col2>_unique (unique)
716
711
  *
717
- * Eine `name`-Override ist erlaubt — Convention-Bruch in Bestandscode
718
- * vermeidet Migration-Churn beim Refactor.
712
+ * A `name` override is allowed to avoid migration churn when refactoring
713
+ * existing code.
719
714
  *
720
- * Single-column indices über `tenantId` sind redundant (buildEntityTable
721
- * legt die immer automatisch an); die Boot-Validation warnt (außer
722
- * `{ unique: true }` — semantische 1:1-Constraint, kein Performance-Hint). */
715
+ * Single-column indexes over `tenantId` are redundant because
716
+ * `buildEntityTable` creates them automatically; boot validation warns about
717
+ * them unless `{ unique: true }`, which expresses a semantic 1:1 constraint
718
+ * rather than a performance hint. */
723
719
  export type EntityIndexDef = {
724
720
  readonly columns: readonly [string, ...string[]];
725
721
  readonly unique?: boolean;
726
722
  readonly name?: string;
727
723
  /**
728
- * Optional SQL-Fragment fuer Partial-Index — `CREATE [UNIQUE] INDEX
729
- * ... WHERE <condition>`. Postgres-Pattern fuer "Index nur unter
730
- * bestimmten Bedingungen", typisches Beispiel: ExportJob-Idempotency
731
- * `UNIQUE(userId) WHERE status IN ('pending', 'running')`.
724
+ * Optional SQL fragment for a partial index — `CREATE [UNIQUE] INDEX
725
+ * ... WHERE <condition>`. This is PostgreSQL's pattern for indexing only
726
+ * rows that meet specified conditions; a typical example is export-job
727
+ * idempotency: `UNIQUE(userId) WHERE status IN ('pending', 'running')`.
732
728
  *
733
- * Caller baut das Fragment via drizzle-orm `sql\`...\``-Tagged-
734
- * Template. table-builder.ts emittiert `.where(def.where)` auf den
735
- * Drizzle-IndexBuilder — wirkt sowohl fuer unique- als auch fuer
736
- * non-unique-Indexes (PG erlaubt beides; non-unique partial nutzt
737
- * man z.B. fuer scharfe BTREE-Indexes nur auf einer Status-Teilmenge
738
- * statt voller Tabelle).
729
+ * Callers build the fragment with the drizzle-orm `sql\`...\`` tagged
730
+ * template. table-builder.ts emits `.where(def.where)` on the Drizzle
731
+ * IndexBuilder. It applies to both unique and non-unique indexes; a
732
+ * non-unique partial index can target a status subset instead of the full
733
+ * table.
739
734
  */
740
735
  readonly where?: unknown;
741
736
  };
@@ -800,17 +795,20 @@ export type EntityDefinition<F extends FieldsMap = FieldsMap, T extends EntityTe
800
795
  readonly lockedWhen?: FieldCondition;
801
796
  /** Allowed state transitions per field. Boot validates against select options. */
802
797
  readonly transitions?: Readonly<Record<string, TransitionMap>>;
803
- /** Composite-Indices über mehrere Felder. Single-column FK-Indices und
804
- * der tenant_id-Index werden weiterhin automatisch von buildEntityTable
805
- * angelegt — diese Liste ist nur für Custom-Indices die der Author
806
- * explizit deklariert (z.B. `{ unique: true, columns: ["key", "tenantId", "userId"] }`). */
798
+ /** Composite indexes over multiple fields. Single-column foreign-key indexes
799
+ * and the tenant_id index are still created automatically by
800
+ * `buildEntityTable`; this list is only for custom indexes explicitly
801
+ * declared by the author (for example,
802
+ * `{ unique: true, columns: ["key", "tenantId", "userId"] }`). */
807
803
  readonly indexes?: readonly EntityIndexDef[];
808
804
  /**
809
- * PK-Typ der Entity.
810
- * - `"serial"` (default): bigserial integer — schneller, kompakter, perfekt für klassische CRUD-Entities.
811
- * - `"uuid"`: uuid mit `gen_random_uuid()` default — verpflichtend für Entities deren `id` als
812
- * Foreign-Key-Wert in multi-tenant Kontexten reist (z.B. `tenant.id` IS der `tenantId`). Auch für
813
- * ES-Aggregate (Phase 2+) notwendig, da Events per UUID aggregiert werden.
805
+ * Entity primary-key type.
806
+ * - `"serial"` (default): bigserial integer — faster and more compact, suitable
807
+ * for classic CRUD entities.
808
+ * - `"uuid"`: UUID with a `gen_random_uuid()` default — required for entities
809
+ * whose `id` travels as a foreign-key value in multi-tenant contexts (for
810
+ * example, `tenant.id` is the `tenantId`). Also required for event-sourced
811
+ * aggregates because events are grouped by UUID.
814
812
  */
815
813
  readonly idType?: "serial" | "uuid";
816
814
  /**
@@ -837,19 +835,19 @@ export type EntityDefinition<F extends FieldsMap = FieldsMap, T extends EntityTe
837
835
  */
838
836
  readonly parentRef?: ParentRefDef;
839
837
  /**
840
- * Default-Retention-Policy fuer diese Entity. Tenant-Admin kann via
841
- * Compliance-Profile + Tenant-Override (Sprint 2) uebersteuern.
842
- * Cleanup-Job (Sprint 2) verarbeitet die Strategy:
838
+ * Default retention policy for this entity. Tenant administrators can
839
+ * override it through compliance profiles and tenant overrides.
840
+ * The cleanup job applies these strategies:
843
841
  *
844
- * - "hardDelete" → Row physisch weg nach keepFor
845
- * - "softDelete" → deletedAt = now() (mit core-soft-delete-Feature)
846
- * - "anonymize" → Felder mit `anonymize`-Funktion ueberschrieben,
847
- * Row bleibt
848
- * - "blockDelete" → Cleanup-Job ignoriert; User-Forget loest
849
- * stattdessen anonymize aus. Buchhaltung, Mandate,
850
- * Patientenakten.
842
+ * - "hardDelete" → physically remove the row after `keepFor`
843
+ * - "softDelete" → set `deletedAt = now()` (with the core-soft-delete feature)
844
+ * - "anonymize" → overwrite fields with an `anonymize` function while the
845
+ * row remains
846
+ * - "blockDelete" → the cleanup job skips the row; user forget instead
847
+ * triggers anonymization. Accounting records, mandates,
848
+ * and patient records.
851
849
  *
852
- * Siehe docs/plans/features/core-data-retention.md.
850
+ * See docs/plans/features/core-data-retention.md.
853
851
  */
854
852
  readonly retention?: RetentionDef;
855
853
  /**
@@ -862,7 +860,7 @@ export type EntityDefinition<F extends FieldsMap = FieldsMap, T extends EntityTe
862
860
  readonly description?: string;
863
861
  /**
864
862
  * Opts this entity's rows into the `tenant-handover` bundled feature
865
- * (kumiko-framework#3035): a row-bound-grant-legitimated ownership change
863
+ *: a row-bound-grant-legitimated ownership change
866
864
  * from an anonymous/public tenant to a freshly signed-up account's tenant.
867
865
  * An entity reachable from a transfer's root — the root itself, or any
868
866
  * entity whose `parentRef` names the root's type — that is NOT declared
package/dist/fields.js CHANGED
@@ -12,7 +12,7 @@ export function optionsQueryFieldRefs(payload) {
12
12
  /** Qualified config key a `{ kind: "tenant" }` MoneyCurrencySource resolves
13
13
  * against — the tenant-settings bundle's per-tenant currency. Lives here
14
14
  * (client-safe) rather than framework/engine so renderer code can read it
15
- * without a runtime-only import (kumiko-framework#2937). */
15
+ * without a runtime-only import. */
16
16
  export const TENANT_CURRENCY_CONFIG_KEY = "tenant-settings:config:currency";
17
17
  // Exhaustive catalogue of `FieldDefinition["type"]` values, derived from a
18
18
  // `Record` so an added/removed union member fails to compile here instead of
@@ -41,13 +41,16 @@ const FIELD_TYPE_NAME_FLAGS = {
41
41
  files: true,
42
42
  images: true,
43
43
  };
44
- export const FIELD_TYPE_NAMES = Object.keys(FIELD_TYPE_NAME_FLAGS);
44
+ function isFieldTypeName(name) {
45
+ return Object.hasOwn(FIELD_TYPE_NAME_FLAGS, name);
46
+ }
47
+ export const FIELD_TYPE_NAMES = Object.keys(FIELD_TYPE_NAME_FLAGS).filter(isFieldTypeName);
45
48
  // Field types with no bound, editable widget on the auto-wired entityEdit
46
49
  // path (render-field.tsx renders a read-only banner instead) — shared by
47
- // the boot validator (packages/framework/src/engine/boot-validator/screens.ts)
50
+ // the boot validator (packages/framework/src/engine/boot-validator/screens-forms.ts)
48
51
  // and the client-side required-field check (packages/renderer/src/app/form-schema.ts).
49
52
  // Embedded LIST fields (`multiple: true`) are the one exception — they get
50
- // their own EmbeddedListField grid widget (#1838) — callers must check that
53
+ // their own EmbeddedListField grid widget — callers must check that
51
54
  // separately, this array alone doesn't distinguish plain embedded from embedded lists.
52
55
  // A plain array, not a Set: kumiko-types stays free of module-scope runtime
53
56
  // identity (guard-types-class-free.test.ts) since a consumer may resolve two
@@ -268,23 +268,23 @@ export type AppContext = SharedContextFields & {
268
268
  readonly id: string;
269
269
  readonly tenantId: TenantId;
270
270
  } | null;
271
- /** Bei Job-Handler-Aufrufen die aus einem Event-Trigger heraus laufen
272
- * (r.job mit `trigger: { on: ... }`): der Name des Handlers der das
273
- * Event ausgelöst hat. Bei Multi-Trigger-Jobs (`on: [...]`) ist das
274
- * die einzige Möglichkeit für den Handler zu wissen WELCHER Trigger
275
- * gefeuert hat. Cron- und manual-Jobs lassen das Feld undefined. */
271
+ /** For job-handler calls triggered by an event
272
+ * (`r.job` with `trigger: { on: ... }`): the name of the handler that
273
+ * emitted the event. For multi-trigger jobs (`on: [...]`), this is the
274
+ * only way for the handler to know which trigger fired. Cron and manual
275
+ * jobs leave the field undefined. */
276
276
  readonly triggerName?: string;
277
- /** Optionaler Geo→Zone-Adapter. Wenn gesetzt (via buildServer-context oder
278
- * runProdApp/runDevApp extraContext), reicht der Dispatcher ihn an
279
- * ctx.tz.fromCoordinates / fromAddress weiter. Ohne Provider werfen diese. */
277
+ /** Optional Geo→zone adapter. When set (via buildServer context or
278
+ * `runProdApp`/`runDevApp` `extraContext`), the dispatcher passes it to
279
+ * `ctx.tz.fromCoordinates` / `fromAddress`. These methods throw without
280
+ * a provider. */
280
281
  readonly geoTzProvider?: GeoTzProvider;
281
282
  /** Overrides the system clock behind ctx.tz in handlers and jobs; tests inject a fixed time. */
282
283
  readonly clock?: Clock;
283
- /** Tenant des aktuellen Pipeline-Calls. Wird vom Dispatcher beim Bauen
284
- * des HandlerContext aus `user.tenantId` gespiegelt, damit lifecycle-
285
- * pipeline + system-hooks den Wert ohne Zugriff auf user-Object haben.
286
- * Sprint-8a Tier-Composition: `effectiveFeatures(ctx._tenantId)`-call
287
- * in den hook-filter-Stellen. */
284
+ /** Tenant of the current pipeline call. The dispatcher mirrors
285
+ * `user.tenantId` here when building HandlerContext so the lifecycle
286
+ * pipeline and system hooks can access it without the user object.
287
+ * `effectiveFeatures(ctx._tenantId)` uses it at hook-filter points. */
288
288
  readonly _tenantId?: TenantId;
289
289
  /** Boot-configured default locale (BCP-47). Last fallback in ctx.locale's
290
290
  * Request-Locale → Boot-Default chain (dispatch-shared.ts) — used when
@@ -307,13 +307,11 @@ export type HandlerContext<TMap extends object = KumikoEventTypeMap> = SharedCon
307
307
  readonly systemDb?: UncheckedSystemDb;
308
308
  readonly crossTenantReads?: CrossTenantReads;
309
309
  readonly registry: Registry;
310
- /** Aktiver SessionUser des Handler-Aufrufs — Convenience-Alias zu
311
- * `event.user`. Existiert weil Handler intuitiv `ctx.user.tenantId`
312
- * schreiben (Context = "kennt seinen User") und der Pfad sonst nur
313
- * über `event.user` läuft, was typo-anfällig ist und stillschweigend
314
- * zu `internal_error` führt wenn der falsche Pfad gewählt wird.
315
- * Identisch zum event.user-Wert; Identity-Switches nutzen
316
- * weiterhin queryAs/writeAs. */
310
+ /** The active SessionUser for the handler call, exposed as a convenience
311
+ * alias for `event.user`. Handlers naturally write `ctx.user.tenantId`;
312
+ * without the alias that path does not exist and silently ends in `internal_error`.
313
+ * The value is identical to `event.user`; identity switches still use
314
+ * `queryAs`/`writeAs`. */
317
315
  readonly user: SessionUser;
318
316
  readonly systemUser?: SessionUser;
319
317
  readonly log?: Logger;
@@ -634,6 +632,9 @@ export type WriteHandlerDef = {
634
632
  /** Per-payload-field limits, e.g. per recipient address. Complements
635
633
  * `rateLimit`; anonymous handlers still need an ip-keyed `rateLimit`. */
636
634
  readonly additionalRateLimits?: readonly PayloadRateLimitOption[];
635
+ /** Tokens each request books on every declared rateLimit bucket, computed from the validated
636
+ * payload (e.g. `(p) => p.files.length`). Replaces the options' `cost`; results below 1 count as 1. */
637
+ readonly rateLimitCost?: (payload: unknown) => number;
637
638
  readonly escapeHatch?: EscapeHatchDeclaration;
638
639
  /** Reserves before the handler transaction opens. Reached through a nested ctx.write the
639
640
  * reservation cannot run, so the dispatcher rejects it unless the top-level batch reserved it. */
@@ -676,7 +677,7 @@ export type QueryHandlerDef = {
676
677
  * the boot-validator skips any column-reference check that would need
677
678
  * it (screens.ts projectionList/relatedList `columns`, projectionDetail
678
679
  * `header`/`metrics`, dashboard `valueField`/`subField`/etc.) rather
679
- * than requiring it retroactively. See fw#2493. */
680
+ * than requiring it retroactively. */
680
681
  readonly outputSchema?: ZodType;
681
682
  /** Entities whose changes this query reflects. Anonymous callers with
682
683
  * access to this query receive /api/sse change signals (no field values)
@@ -1,19 +1,19 @@
1
1
  import type { Context } from "hono";
2
2
  import type { RateLimitOption } from "./handlers.ts";
3
- /** Subset von HTTP-Methoden den wir aktiv unterstützen. Hono spricht
4
- * alle, aber das hier sind die einzigen die ein Feature-Author
5
- * realistisch deklariert. */
3
+ /** Subset of HTTP methods actively supported. Hono supports all methods,
4
+ * but these are the only ones a feature author is expected to declare. */
6
5
  export type HttpRouteMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
7
- /** Dependencies die der Handler vom Framework bekommt. App-Author kann
8
- * die App selbst aufrufen (`deps.app.fetch(...)` für intern-call) oder
9
- * direkt per dispatcher Daten ziehen. Db/Redis sind die rohen Connections
10
- * — wer Tenant-Scope braucht muss durch dispatcher.query gehen.
6
+ /** Dependencies provided to the handler by the framework. The app can be
7
+ * called directly (`deps.app.fetch(...)` for internal calls), or data can
8
+ * be fetched through the dispatcher. Db/Redis are raw connections — tenant
9
+ * scoping requires going through dispatcher.query.
11
10
  *
12
- * Hono-typing: `Context<any, any>` weil das Hono-Type-Param-Setup nur
13
- * intern relevant ist. Concrete Hono-app wird im Boot-Path zugewiesen. */
11
+ * Hono typing: `Context<any, any>` is used because Hono's type-parameter
12
+ * setup is only relevant internally. The concrete Hono app is assigned
13
+ * during boot. */
14
14
  export type HttpRouteHandlerDeps = {
15
- /** Die Hono-app — Handler kann via app.fetch(...) interne Routes
16
- * ansprechen (z.B. /api/query mit der vollen Auth-/Anonymous-Chain). */
15
+ /** The Hono app — the handler can use app.fetch(...) to call internal routes
16
+ * (e.g. /api/query with the full auth/anonymous chain). */
17
17
  readonly app: import("hono").Hono<any, any>;
18
18
  /** Run a query handler in-process, forcing a SPECIFIC tenant — WITHOUT
19
19
  * going through the public /api/query HTTP layer (no header parsing, no
@@ -45,9 +45,9 @@ export type HttpRouteRateLimit = Omit<RateLimitOption, "per"> & {
45
45
  readonly per: "ip" | "ip+handler";
46
46
  };
47
47
  export type HttpRouteDefinition = {
48
- /** HTTP-Methode — bei Hono-Mount via app.{get,post,...}(path). */
48
+ /** HTTP method — mounted in Hono via app.{get,post,...}(path). */
49
49
  readonly method: HttpRouteMethod;
50
- /** URL-Pfad (Hono-Pattern, z.B. "/feed.xml" oder "/og/:tenantId.png"). */
50
+ /** URL path (Hono pattern, e.g. "/feed.xml" or "/og/:tenantId.png"). */
51
51
  readonly path: string;
52
52
  /** true = public, no session required. false = mounted behind the
53
53
  * session auth chain (global IP rate limit when configured, PII
@@ -56,8 +56,8 @@ export type HttpRouteDefinition = {
56
56
  * `/api/*` chain only. A request without a session gets 401. The
57
57
  * handler reads the caller via getUser(c). */
58
58
  readonly anonymous: boolean;
59
- /** Hono-Handler. Bekommt Hono-Context + Framework-Deps; returnt
60
- * Response (sync oder async). */
59
+ /** Hono handler. Receives the Hono context and framework dependencies;
60
+ * returns a Response synchronously or asynchronously. */
61
61
  readonly handler: HttpRouteHandler;
62
62
  /** Enforced before the route's guards through the server's rate-limit
63
63
  * resolver (fail-open like the global L1 limiter: a limiter outage
@@ -4,7 +4,7 @@
4
4
  // feature author controls the wire format themselves.
5
5
  //
6
6
  // Pattern symmetric to r.queryHandler / r.writeHandler: definition as
7
- // part of the feature (not the app bootstrapping). Phase-3 multi-tenant
7
+ // part of the feature (not the app bootstrapping). Multi-tenant support
8
8
  // becomes trivial because tenant context is picked up via host resolution.
9
9
  //
10
10
  // Escape-hatch stays: runProdApp.extraRoutes (declarative list with
@@ -0,0 +1,38 @@
1
+ import * as z from "zod";
2
+ import type { AggregateKey } from "./aggregate-types.ts";
3
+ export declare const MAX_LIST_AGGREGATE_MEASURES = 20;
4
+ export declare const LIST_AGGREGATE_MEASURE_KINDS: readonly ["sum", "avg", "count", "min", "max"];
5
+ export type ListAggregateMeasureKind = (typeof LIST_AGGREGATE_MEASURE_KINDS)[number];
6
+ export declare const listAggregatePayloadSchema: z.ZodObject<{
7
+ groupBy: z.ZodOptional<z.ZodString>;
8
+ measures: z.ZodDefault<z.ZodArray<z.ZodObject<{
9
+ field: z.ZodString;
10
+ kind: z.ZodEnum<{
11
+ avg: "avg";
12
+ count: "count";
13
+ max: "max";
14
+ min: "min";
15
+ sum: "sum";
16
+ }>;
17
+ }, z.core.$strip>>>;
18
+ }, z.core.$strip>;
19
+ export type ListAggregatePayload = {
20
+ readonly groupBy?: string | undefined;
21
+ readonly measures: readonly {
22
+ readonly field: string;
23
+ readonly kind: ListAggregateMeasureKind;
24
+ }[];
25
+ };
26
+ export type ListAggregateValue = number | string | null;
27
+ export type ListAggregateGroup = {
28
+ readonly key: AggregateKey;
29
+ readonly label?: string | null;
30
+ readonly count: number;
31
+ readonly measures: readonly ListAggregateValue[];
32
+ };
33
+ export type ListAggregateResult = {
34
+ readonly groups: readonly ListAggregateGroup[];
35
+ readonly truncated?: true;
36
+ readonly searchTruncated?: true;
37
+ };
38
+ export declare function isListAggregateResult(value: unknown): value is ListAggregateResult;