@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/LICENSE +21 -57
- package/README.md +1 -1
- package/dist/config.d.ts +3 -1
- package/dist/define-handler.d.ts +6 -0
- package/dist/entity-table-meta-types.js +1 -1
- package/dist/event-store-executor-types.d.ts +38 -22
- package/dist/event-store-types.js +1 -1
- package/dist/event-type-map.js +12 -12
- package/dist/feature.d.ts +23 -23
- package/dist/fields.d.ts +113 -115
- package/dist/fields.js +7 -4
- package/dist/handlers.d.ts +22 -21
- package/dist/http-route.d.ts +15 -15
- package/dist/http-route.js +1 -1
- package/dist/list-aggregate-types.d.ts +38 -0
- package/dist/list-aggregate-types.js +19 -0
- package/dist/screen-action-form.d.ts +3 -3
- package/dist/screen-custom-config.d.ts +7 -8
- package/dist/screen-entity-edit.d.ts +1 -1
- package/dist/screen-entity-list.d.ts +2 -2
- package/dist/screen-projection.d.ts +2 -2
- package/dist/screen.d.ts +1 -1
- package/dist/step.d.ts +1 -1
- package/dist/target-ref.js +10 -13
- package/dist/tree-node.js +17 -17
- package/package.json +11 -3
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
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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
|
-
/**
|
|
98
|
-
*
|
|
99
|
-
* Default: false
|
|
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
|
-
/**
|
|
129
|
-
* <input type="text">. `true`
|
|
130
|
-
*
|
|
131
|
-
*
|
|
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
|
|
138
|
-
* templates, anything that can be megabytes large.
|
|
139
|
-
*
|
|
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
|
-
* - **
|
|
142
|
-
*
|
|
143
|
-
* - **
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* - **
|
|
148
|
-
*
|
|
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
|
|
153
|
-
*
|
|
154
|
-
*
|
|
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
|
-
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
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
|
|
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
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
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
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
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
|
|
372
|
-
* is checked against the app's `currencies` list at boot
|
|
373
|
-
*
|
|
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
|
|
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
|
-
/**
|
|
410
|
-
*
|
|
411
|
-
*
|
|
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-
|
|
414
|
-
*
|
|
415
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
561
|
-
* in
|
|
562
|
-
* Zod
|
|
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/
|
|
566
|
-
* "de-DE"). Default
|
|
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:
|
|
579
|
-
*
|
|
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
|
-
*
|
|
577
|
+
* Example: manual pair
|
|
582
578
|
* { pickupAt: { type: "timestamp", locatedBy: "pickupTz" }, pickupTz: { type: "tz" } }
|
|
583
|
-
* —
|
|
579
|
+
* — prefer `createLocatedTimestampField()` instead.
|
|
584
580
|
*/
|
|
585
581
|
readonly locatedBy?: string;
|
|
586
|
-
/**
|
|
587
|
-
*
|
|
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/
|
|
592
|
-
*
|
|
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
|
-
/**
|
|
611
|
-
*
|
|
612
|
-
*
|
|
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/
|
|
616
|
-
*
|
|
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
|
-
*
|
|
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
|
|
711
|
-
*
|
|
712
|
-
*
|
|
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
|
-
*
|
|
718
|
-
*
|
|
712
|
+
* A `name` override is allowed to avoid migration churn when refactoring
|
|
713
|
+
* existing code.
|
|
719
714
|
*
|
|
720
|
-
* Single-column
|
|
721
|
-
*
|
|
722
|
-
* `{ unique: true }
|
|
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
|
|
729
|
-
* ... WHERE <condition>`.
|
|
730
|
-
*
|
|
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
|
-
*
|
|
734
|
-
*
|
|
735
|
-
*
|
|
736
|
-
* non-unique
|
|
737
|
-
*
|
|
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
|
|
804
|
-
*
|
|
805
|
-
*
|
|
806
|
-
*
|
|
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
|
-
*
|
|
810
|
-
* - `"serial"` (default): bigserial integer —
|
|
811
|
-
*
|
|
812
|
-
*
|
|
813
|
-
*
|
|
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
|
|
841
|
-
*
|
|
842
|
-
*
|
|
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" →
|
|
845
|
-
* - "softDelete" → deletedAt = now() (
|
|
846
|
-
* - "anonymize" →
|
|
847
|
-
*
|
|
848
|
-
* - "blockDelete" →
|
|
849
|
-
*
|
|
850
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
package/dist/handlers.d.ts
CHANGED
|
@@ -268,23 +268,23 @@ export type AppContext = SharedContextFields & {
|
|
|
268
268
|
readonly id: string;
|
|
269
269
|
readonly tenantId: TenantId;
|
|
270
270
|
} | null;
|
|
271
|
-
/**
|
|
272
|
-
* (r.job
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
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
|
-
/**
|
|
278
|
-
* runProdApp
|
|
279
|
-
* ctx.tz.fromCoordinates / fromAddress
|
|
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
|
|
284
|
-
*
|
|
285
|
-
* pipeline
|
|
286
|
-
*
|
|
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
|
-
/**
|
|
311
|
-
* `event.user`.
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
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.
|
|
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)
|
package/dist/http-route.d.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import type { Context } from "hono";
|
|
2
2
|
import type { RateLimitOption } from "./handlers.ts";
|
|
3
|
-
/** Subset
|
|
4
|
-
*
|
|
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
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
|
13
|
-
*
|
|
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
|
-
/**
|
|
16
|
-
*
|
|
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
|
|
48
|
+
/** HTTP method — mounted in Hono via app.{get,post,...}(path). */
|
|
49
49
|
readonly method: HttpRouteMethod;
|
|
50
|
-
/** URL
|
|
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
|
|
60
|
-
* Response
|
|
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
|
package/dist/http-route.js
CHANGED
|
@@ -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).
|
|
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;
|