@cosmicdrift/kumiko-types 0.326.1 → 0.328.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/action-icon.d.ts +3 -0
- package/dist/action-icon.js +51 -0
- package/{src/agent-exposure.ts → dist/agent-exposure.d.ts} +8 -15
- package/dist/agent-exposure.js +16 -0
- package/dist/concurrency-mode.d.ts +1 -0
- package/dist/config-scope.d.ts +1 -0
- package/dist/config-scope.js +1 -0
- package/dist/config.d.ts +234 -0
- package/dist/config.js +1 -0
- package/dist/cursor-types.d.ts +16 -0
- package/dist/cursor-types.js +1 -0
- package/dist/db-connection.d.ts +14 -0
- package/dist/db-connection.js +1 -0
- package/dist/define-handler.d.ts +58 -0
- package/dist/define-handler.js +1 -0
- package/dist/derivatives-types.d.ts +53 -0
- package/dist/derivatives-types.js +3 -0
- package/dist/entity-cache.d.ts +16 -0
- package/dist/entity-cache.js +1 -0
- package/dist/entity-handlers.d.ts +54 -0
- package/dist/entity-handlers.js +1 -0
- package/dist/entity-table-meta-types.d.ts +44 -0
- package/dist/entity-table-meta-types.js +6 -0
- package/dist/envelope-cipher-types.d.ts +5 -0
- package/dist/envelope-cipher-types.js +1 -0
- package/dist/event-store-executor-types.d.ts +86 -0
- package/dist/event-store-executor-types.js +1 -0
- package/dist/event-store-types.d.ts +37 -0
- package/dist/event-store-types.js +7 -0
- package/dist/event-type-map.d.ts +8 -0
- package/{src/event-type-map.ts → dist/event-type-map.js} +1 -16
- package/dist/executor-brand.d.ts +8 -0
- package/dist/executor-brand.js +1 -0
- package/dist/extension-options-map.d.ts +9 -0
- package/dist/extension-options-map.js +4 -0
- package/dist/feature.d.ts +518 -0
- package/dist/feature.js +1 -0
- package/dist/field-icon.d.ts +1 -0
- package/dist/field-icon.js +1 -0
- package/dist/fields.d.ts +729 -0
- package/dist/fields.js +50 -0
- package/dist/file-handle-types.d.ts +12 -0
- package/dist/file-handle-types.js +1 -0
- package/dist/file-provider-resolver-types.d.ts +3 -0
- package/dist/file-provider-resolver-types.js +1 -0
- package/dist/file-storage-provider-types.d.ts +17 -0
- package/dist/file-storage-provider-types.js +1 -0
- package/dist/geo-tz.d.ts +17 -0
- package/{src/geo-tz.ts → dist/geo-tz.js} +1 -20
- package/dist/handlers.d.ts +493 -0
- package/dist/handlers.js +49 -0
- package/dist/hook-phase.d.ts +1 -0
- package/dist/hook-phase.js +1 -0
- package/dist/hooks.d.ts +75 -0
- package/dist/hooks.js +1 -0
- package/dist/http-route.d.ts +52 -0
- package/dist/http-route.js +13 -0
- package/dist/identifiers.d.ts +8 -0
- package/{src/identifiers.ts → dist/identifiers.js} +7 -24
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/kms-adapter-types.d.ts +68 -0
- package/dist/kms-adapter-types.js +49 -0
- package/dist/logger.d.ts +7 -0
- package/dist/logger.js +1 -0
- package/dist/multi-stream-apply-context-types.d.ts +18 -0
- package/dist/multi-stream-apply-context-types.js +1 -0
- package/dist/nav-icon.d.ts +3 -0
- package/dist/nav-icon.js +100 -0
- package/dist/nav.d.ts +39 -0
- package/dist/nav.js +1 -0
- package/dist/observability/index.d.ts +3 -0
- package/dist/observability/index.js +2 -0
- package/dist/observability/metric.d.ts +34 -0
- package/dist/observability/metric.js +3 -0
- package/dist/observability/provider.d.ts +24 -0
- package/dist/observability/provider.js +4 -0
- package/dist/observability/span.d.ts +32 -0
- package/dist/observability/span.js +4 -0
- package/dist/ownership.d.ts +33 -0
- package/dist/ownership.js +5 -0
- package/dist/projection.d.ts +38 -0
- package/dist/projection.js +1 -0
- package/dist/rate-limit-types.d.ts +19 -0
- package/dist/rate-limit-types.js +1 -0
- package/dist/relations.d.ts +25 -0
- package/dist/relations.js +1 -0
- package/dist/schema-table-types.d.ts +15 -0
- package/{src/schema-table-types.ts → dist/schema-table-types.js} +0 -20
- package/dist/screen.d.ts +1265 -0
- package/dist/screen.js +3 -0
- package/dist/search-adapter.d.ts +32 -0
- package/dist/search-adapter.js +1 -0
- package/dist/secrets-types.d.ts +52 -0
- package/dist/secrets-types.js +30 -0
- package/dist/snapshot-types.d.ts +13 -0
- package/dist/snapshot-types.js +1 -0
- package/dist/step.d.ts +304 -0
- package/dist/step.js +11 -0
- package/dist/target-ref.d.ts +5 -0
- package/{src/target-ref.ts → dist/target-ref.js} +1 -6
- package/dist/tenancy-brand.d.ts +6 -0
- package/dist/tenancy-brand.js +1 -0
- package/dist/tenant-db-types.d.ts +67 -0
- package/dist/tenant-db-types.js +5 -0
- package/dist/tree-node.d.ts +26 -0
- package/dist/tree-node.js +22 -0
- package/dist/tz-context.d.ts +48 -0
- package/dist/tz-context.js +6 -0
- package/dist/websocket-route.d.ts +46 -0
- package/dist/websocket-route.js +10 -0
- package/dist/where-clause-types.d.ts +20 -0
- package/dist/where-clause-types.js +1 -0
- package/dist/workspace.d.ts +10 -0
- package/dist/workspace.js +1 -0
- package/dist/write-error-info-types.d.ts +12 -0
- package/dist/write-error-info-types.js +1 -0
- package/package.json +362 -119
- package/src/changes.json +12 -0
- package/src/__tests__/handlers.test.ts +0 -40
- package/src/__tests__/plural-forms.test.ts +0 -18
- package/src/action-icon.ts +0 -57
- package/src/concurrency-mode.ts +0 -5
- package/src/config-scope.ts +0 -5
- package/src/config.ts +0 -563
- package/src/cursor-types.ts +0 -18
- package/src/db-connection.ts +0 -25
- package/src/define-handler.ts +0 -138
- package/src/derivatives-types.ts +0 -106
- package/src/entity-cache.ts +0 -35
- package/src/entity-handlers.ts +0 -68
- package/src/entity-table-meta-types.ts +0 -101
- package/src/envelope-cipher-types.ts +0 -6
- package/src/event-store-executor-types.ts +0 -163
- package/src/event-store-types.ts +0 -85
- package/src/executor-brand.ts +0 -19
- package/src/extension-options-map.ts +0 -21
- package/src/feature.ts +0 -1132
- package/src/field-icon.ts +0 -23
- package/src/fields.ts +0 -1114
- package/src/file-handle-types.ts +0 -20
- package/src/file-provider-resolver-types.ts +0 -7
- package/src/file-storage-provider-types.ts +0 -54
- package/src/handlers.ts +0 -1246
- package/src/hook-phase.ts +0 -2
- package/src/hooks.ts +0 -170
- package/src/http-route.ts +0 -78
- package/src/kms-adapter-types.ts +0 -128
- package/src/logger.ts +0 -7
- package/src/multi-stream-apply-context-types.ts +0 -52
- package/src/nav-icon.ts +0 -105
- package/src/nav.ts +0 -139
- package/src/observability/index.ts +0 -28
- package/src/observability/metric.ts +0 -56
- package/src/observability/provider.ts +0 -32
- package/src/observability/span.ts +0 -58
- package/src/ownership.ts +0 -83
- package/src/projection.ts +0 -170
- package/src/rate-limit-types.ts +0 -32
- package/src/relations.ts +0 -52
- package/src/screen.ts +0 -1593
- package/src/search-adapter.ts +0 -45
- package/src/secrets-types.ts +0 -182
- package/src/snapshot-types.ts +0 -36
- package/src/step.ts +0 -390
- package/src/tenancy-brand.ts +0 -9
- package/src/tenant-db-types.ts +0 -116
- package/src/tree-node.ts +0 -134
- package/src/tz-context.ts +0 -63
- package/src/where-clause-types.ts +0 -31
- package/src/workspace.ts +0 -42
- package/src/write-error-info-types.ts +0 -20
- /package/{src/index.ts → dist/concurrency-mode.js} +0 -0
package/dist/fields.d.ts
ADDED
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
import type { VariantSpec } from "./derivatives-types.js";
|
|
2
|
+
import type { OwnershipMap } from "./ownership.js";
|
|
3
|
+
import type { EntityTenancy } from "./tenancy-brand.js";
|
|
4
|
+
export type FieldAccess = {
|
|
5
|
+
readonly read?: OwnershipMap | readonly string[];
|
|
6
|
+
readonly write?: OwnershipMap | readonly string[];
|
|
7
|
+
};
|
|
8
|
+
export type ResolvedPiiFlags = {
|
|
9
|
+
readonly pii?: boolean;
|
|
10
|
+
readonly userOwned?: {
|
|
11
|
+
readonly ownerField: string;
|
|
12
|
+
};
|
|
13
|
+
readonly tenantOwned?: boolean;
|
|
14
|
+
readonly recordOwned?: true;
|
|
15
|
+
readonly anonymize?: () => unknown | Promise<unknown>;
|
|
16
|
+
readonly allowPlaintext?: string;
|
|
17
|
+
/** Equality-Lookups (fetchOne/filter eq) bleiben trotz Verschluesselung
|
|
18
|
+
* moeglich: generierte `<snake>_bidx`-Spalte traegt einen HMAC-Blind-
|
|
19
|
+
* Index, Query-Compiler matchen `(col = $1 OR col_bidx = $2)`. Nur auf
|
|
20
|
+
* text-Feldern MIT Subject-Annotation erlaubt (Boot-Validator).
|
|
21
|
+
* Siehe docs/plans/datenschutz/blind-index.md (kumiko-framework#818). */
|
|
22
|
+
readonly lookupable?: true;
|
|
23
|
+
/** Foreign key into the `user` entity (authorId, assigneeId, ...) with no
|
|
24
|
+
* annotated content of its own. Triggers no encryption — feeds only the
|
|
25
|
+
* GDPR-hook-coverage boot guard, which otherwise can't see subject data
|
|
26
|
+
* that lives purely in an FK column. */
|
|
27
|
+
readonly subjectRef?: true;
|
|
28
|
+
};
|
|
29
|
+
export type PersonalSubject = "self" | {
|
|
30
|
+
readonly of: string;
|
|
31
|
+
} | "tenant";
|
|
32
|
+
export type Findability = "exact" | "fuzzy" | "none" | "secret";
|
|
33
|
+
type NoPersonalAnnotation = {
|
|
34
|
+
readonly personal?: never;
|
|
35
|
+
readonly find?: never;
|
|
36
|
+
readonly reason?: never;
|
|
37
|
+
readonly anonymize?: never;
|
|
38
|
+
};
|
|
39
|
+
export type PersonalAnnotations = {
|
|
40
|
+
readonly personal: PersonalSubject;
|
|
41
|
+
readonly find: Findability;
|
|
42
|
+
readonly anonymize?: () => unknown | Promise<unknown>;
|
|
43
|
+
} | {
|
|
44
|
+
readonly personal: "ref";
|
|
45
|
+
} | {
|
|
46
|
+
readonly personal: false;
|
|
47
|
+
readonly reason: string;
|
|
48
|
+
readonly anonymize?: () => unknown | Promise<unknown>;
|
|
49
|
+
};
|
|
50
|
+
export type LongTextFindability = "none" | "secret";
|
|
51
|
+
export type PersonalAnnotationsLongText = {
|
|
52
|
+
readonly personal: PersonalSubject;
|
|
53
|
+
readonly find: LongTextFindability;
|
|
54
|
+
readonly anonymize?: () => unknown | Promise<unknown>;
|
|
55
|
+
} | {
|
|
56
|
+
readonly personal: "ref";
|
|
57
|
+
} | {
|
|
58
|
+
readonly personal: false;
|
|
59
|
+
readonly reason: string;
|
|
60
|
+
readonly anonymize?: () => unknown | Promise<unknown>;
|
|
61
|
+
};
|
|
62
|
+
export type PersonalAnnotationsNoFind = {
|
|
63
|
+
readonly personal: PersonalSubject;
|
|
64
|
+
readonly anonymize?: () => unknown | Promise<unknown>;
|
|
65
|
+
} | {
|
|
66
|
+
readonly personal: "ref";
|
|
67
|
+
} | {
|
|
68
|
+
readonly personal: false;
|
|
69
|
+
readonly reason: string;
|
|
70
|
+
readonly anonymize?: () => unknown | Promise<unknown>;
|
|
71
|
+
} | NoPersonalAnnotation;
|
|
72
|
+
export type RetentionDef = {
|
|
73
|
+
readonly keepFor: string;
|
|
74
|
+
readonly strategy: "hardDelete" | "softDelete" | "anonymize" | "blockDelete";
|
|
75
|
+
readonly reference?: string;
|
|
76
|
+
};
|
|
77
|
+
export type TextFieldDef = {
|
|
78
|
+
readonly type: "text";
|
|
79
|
+
/** Optional free-text semantics for the AI agent manifest — never rendered;
|
|
80
|
+
* the i18n label is what users see. */
|
|
81
|
+
readonly description?: string;
|
|
82
|
+
readonly maxLength?: number;
|
|
83
|
+
readonly required?: boolean;
|
|
84
|
+
readonly searchable?: boolean;
|
|
85
|
+
readonly sortable?: boolean;
|
|
86
|
+
/** Author erlaubt Screen-Filter auf diesem Feld (Tier 2.7c).
|
|
87
|
+
* Boot-Validator weist Filter mit `filterable !== true` zurück.
|
|
88
|
+
* Default: false — analog zu `sortable`, opt-in. */
|
|
89
|
+
readonly filterable?: boolean;
|
|
90
|
+
readonly encrypted?: boolean;
|
|
91
|
+
readonly sensitive?: boolean;
|
|
92
|
+
/** A later run reads this field's value as an instruction — prompt, rule,
|
|
93
|
+
* template. Every create/update whose payload writes it requires agent.risk
|
|
94
|
+
* "high" on the directly-dispatched entry handler; a revert doesn't undo a
|
|
95
|
+
* run that already read the compromised value. See irreversible-operation-gate. */
|
|
96
|
+
readonly readAsInstruction?: boolean;
|
|
97
|
+
/** "password" is a pure render hint (masked input) — no storage semantics. */
|
|
98
|
+
readonly format?: "email" | "url" | "phone" | "password";
|
|
99
|
+
readonly default?: string;
|
|
100
|
+
readonly access?: FieldAccess;
|
|
101
|
+
/** Mehrzeiliger Text — DefaultInput rendert dann ein <textarea> statt
|
|
102
|
+
* <input type="text">. `true` = Default 4 Zeilen, `{ rows: N }` =
|
|
103
|
+
* explizite Höhe. Search/sort/encrypt verhalten sich unverändert
|
|
104
|
+
* identisch zu single-line — nur die Render-Surface wechselt. */
|
|
105
|
+
readonly multiline?: boolean | {
|
|
106
|
+
readonly rows?: number;
|
|
107
|
+
};
|
|
108
|
+
} & ResolvedPiiFlags;
|
|
109
|
+
/**
|
|
110
|
+
* Long-form text content — source-code, markdown, blog-posts, email-
|
|
111
|
+
* templates, anything that can be megabytes large. Bewusst MINIMALE
|
|
112
|
+
* Surface gegen `text`:
|
|
113
|
+
*
|
|
114
|
+
* - **Kein `sortable`**: ORDER BY auf 100 KB-Strings kostet I/O ohne
|
|
115
|
+
* sinnvolles UX-Outcome (lex-Sortierung von Code ist Nonsense).
|
|
116
|
+
* - **Kein `searchable`**: ILIKE/Substring-Suche auf langen Texten
|
|
117
|
+
* skaliert nicht. Wer wirklich Volltextsuche will, nimmt den
|
|
118
|
+
* SearchAdapter (Meilisearch) — der hat eine eigene Pipeline mit
|
|
119
|
+
* Tokenizer + Index, NICHT diesen field-flag.
|
|
120
|
+
* - **Kein `filterable`**: WHERE auf langen Strings same Story wie
|
|
121
|
+
* sortable.
|
|
122
|
+
* - **Kein `format`**: email/url/phone sind kurz definierte Inputs,
|
|
123
|
+
* longText ist per Definition unstrukturiert.
|
|
124
|
+
*
|
|
125
|
+
* Type-level enforcement statt convention: wer sortable/searchable
|
|
126
|
+
* braucht, nimmt `text` (mit den entsprechenden Skalierungs-Trade-offs).
|
|
127
|
+
* DB-mapping ist identisch zu text (Postgres `text` ist unbounded).
|
|
128
|
+
*/
|
|
129
|
+
export type LongTextFieldDef = {
|
|
130
|
+
readonly type: "longText";
|
|
131
|
+
readonly description?: string;
|
|
132
|
+
/** Optionale soft-Cap. Default unbounded (= Postgres-text-limit, 1 GB).
|
|
133
|
+
* Nützlich für defensive Caps wie 1 MB damit ein verirrter Browser-
|
|
134
|
+
* Paste nicht die DB sprengt. */
|
|
135
|
+
readonly maxLength?: number;
|
|
136
|
+
readonly required?: boolean;
|
|
137
|
+
readonly encrypted?: boolean;
|
|
138
|
+
readonly sensitive?: boolean;
|
|
139
|
+
/** A later run reads this field's value as an instruction — prompt, rule,
|
|
140
|
+
* template. Every create/update whose payload writes it requires agent.risk
|
|
141
|
+
* "high" on the directly-dispatched entry handler; a revert doesn't undo a
|
|
142
|
+
* run that already read the compromised value. See irreversible-operation-gate. */
|
|
143
|
+
readonly readAsInstruction?: boolean;
|
|
144
|
+
readonly default?: string;
|
|
145
|
+
readonly access?: FieldAccess;
|
|
146
|
+
readonly multiline?: boolean | {
|
|
147
|
+
readonly rows?: number;
|
|
148
|
+
};
|
|
149
|
+
} & ResolvedPiiFlags;
|
|
150
|
+
export type BooleanFieldDef = {
|
|
151
|
+
readonly type: "boolean";
|
|
152
|
+
readonly description?: string;
|
|
153
|
+
readonly required?: boolean;
|
|
154
|
+
readonly sortable?: boolean;
|
|
155
|
+
readonly filterable?: boolean;
|
|
156
|
+
readonly sensitive?: boolean;
|
|
157
|
+
readonly default?: boolean;
|
|
158
|
+
readonly access?: FieldAccess;
|
|
159
|
+
};
|
|
160
|
+
export type SelectOptionTone = "ok" | "warn" | "bad" | "neutral";
|
|
161
|
+
export type SelectFieldDef<TOptions extends readonly string[] = readonly string[]> = {
|
|
162
|
+
readonly type: "select";
|
|
163
|
+
readonly description?: string;
|
|
164
|
+
readonly options: TOptions;
|
|
165
|
+
readonly required?: boolean;
|
|
166
|
+
readonly sortable?: boolean;
|
|
167
|
+
readonly filterable?: boolean;
|
|
168
|
+
readonly sensitive?: boolean;
|
|
169
|
+
readonly default?: TOptions[number];
|
|
170
|
+
readonly access?: FieldAccess;
|
|
171
|
+
/** Renderer metadata: `"radio"` always renders the option set as a visible
|
|
172
|
+
* radio group, `"dropdown"` always renders the combobox. Omitted = the
|
|
173
|
+
* renderer's own heuristic picks (radio group for a few short labels,
|
|
174
|
+
* dropdown otherwise). A `"radio"` group with many or long labels wraps —
|
|
175
|
+
* the author owns that trade-off, the renderer does not override it. */
|
|
176
|
+
readonly display?: "radio" | "dropdown";
|
|
177
|
+
/** Status tone per option, shown as a toned badge with dot in lists and
|
|
178
|
+
* detail bands. Options without an entry keep the renderer's own
|
|
179
|
+
* value-based heuristic. */
|
|
180
|
+
readonly optionTones?: {
|
|
181
|
+
readonly [K in TOptions[number]]?: SelectOptionTone;
|
|
182
|
+
};
|
|
183
|
+
} & ResolvedPiiFlags;
|
|
184
|
+
export type MultiSelectFieldDef<TOptions extends readonly string[] = readonly string[]> = {
|
|
185
|
+
readonly type: "multiSelect";
|
|
186
|
+
readonly description?: string;
|
|
187
|
+
readonly options: TOptions;
|
|
188
|
+
readonly required?: boolean;
|
|
189
|
+
readonly filterable?: boolean;
|
|
190
|
+
readonly sensitive?: boolean;
|
|
191
|
+
/** Default-Auswahl. Jeder Eintrag muss in `options` sein (Boot-Validator). */
|
|
192
|
+
readonly default?: readonly TOptions[number][];
|
|
193
|
+
readonly access?: FieldAccess;
|
|
194
|
+
/** Renderer metadata: `"checkboxes"` renders every option as a visible
|
|
195
|
+
* checkbox with a select-all toggle. Default (omitted) stays the combobox
|
|
196
|
+
* dropdown. */
|
|
197
|
+
readonly display?: "checkboxes" | "dropdown";
|
|
198
|
+
/** Renderer metadata: column count for the `"checkboxes"` grid at the widest
|
|
199
|
+
* breakpoint; narrow viewports always collapse to one column. Omitted =
|
|
200
|
+
* the renderer picks a sensible count. Ignored unless
|
|
201
|
+
* `display: "checkboxes"`. */
|
|
202
|
+
readonly columns?: 1 | 2 | 3 | 4;
|
|
203
|
+
/** Renderer metadata: how many grid rows stay visible before the
|
|
204
|
+
* `"checkboxes"` grid becomes scrollable. Omitted = the grid grows with its
|
|
205
|
+
* content and never scrolls. Ignored unless `display: "checkboxes"`. */
|
|
206
|
+
readonly maxRows?: number;
|
|
207
|
+
} & ResolvedPiiFlags;
|
|
208
|
+
/**
|
|
209
|
+
* Storage: `integer` flag decides the Postgres column type, not just Zod
|
|
210
|
+
* validation. `integer: true` → `integer` column (32-bit, ~±2.1 billion),
|
|
211
|
+
* write-boundary enforces `.int()`. Omitted/`false` (the default) →
|
|
212
|
+
* `double precision` column — fractional values are accepted end to end.
|
|
213
|
+
* Need exact decimal storage instead of a binary float (money-adjacent
|
|
214
|
+
* math, no representation error)? Use `createDecimalField` (`numeric`).
|
|
215
|
+
*/
|
|
216
|
+
export type NumberFieldDef = {
|
|
217
|
+
readonly type: "number";
|
|
218
|
+
readonly description?: string;
|
|
219
|
+
readonly required?: boolean;
|
|
220
|
+
readonly sortable?: boolean;
|
|
221
|
+
readonly filterable?: boolean;
|
|
222
|
+
readonly sensitive?: boolean;
|
|
223
|
+
readonly default?: number;
|
|
224
|
+
readonly access?: FieldAccess;
|
|
225
|
+
readonly min?: number;
|
|
226
|
+
/** Upper bound at the write boundary (Zod `.max`). */
|
|
227
|
+
readonly max?: number;
|
|
228
|
+
/** `true` → `integer` column + `.int()` Zod validation. Omitted/`false` →
|
|
229
|
+
* `double precision` column, fractional values allowed. */
|
|
230
|
+
readonly integer?: boolean;
|
|
231
|
+
/** Display-only suffix: a static string, or a sibling field name whose
|
|
232
|
+
* value supplies it. Never converts the stored number. */
|
|
233
|
+
readonly unit?: string | {
|
|
234
|
+
readonly field: string;
|
|
235
|
+
};
|
|
236
|
+
/** Thousands-grouping in the rendered display (Intl `useGrouping`).
|
|
237
|
+
* Default `true`. Set `false` for values that read wrong grouped (e.g. a
|
|
238
|
+
* model year: "2021", not "2.021"). Never affects the stored value. */
|
|
239
|
+
readonly grouping?: boolean;
|
|
240
|
+
} & ResolvedPiiFlags;
|
|
241
|
+
/**
|
|
242
|
+
* 64-bit-Integer-Spalte fuer Audit-Counter, Byte-Sizes, Event-IDs und
|
|
243
|
+
* andere Werte die >2^31 (~2.1 Mrd) wandern koennen. Storage als
|
|
244
|
+
* Postgres `bigint`, JS-Round-trip als `number` (mode:"number" — sicher
|
|
245
|
+
* bis 2^53 ≈ 9 PB, JSON-serialisierbar). Wer >2^53 braucht (rare),
|
|
246
|
+
* nutzt einen `text`-Field mit eigenem Codec.
|
|
247
|
+
*
|
|
248
|
+
* Vorrang vor `NumberFieldDef`-(integer 32-bit-Cap, ~2.1 GB) immer dann
|
|
249
|
+
* wenn der Wert physisch ueber dieses Limit klettern kann: Bytes,
|
|
250
|
+
* Events, Counters in High-Throughput-Apps, Cumulative-Sums. Money
|
|
251
|
+
* hat dafuer den eigenen `MoneyFieldDef` (mit Currency-Spalte).
|
|
252
|
+
*/
|
|
253
|
+
export type BigIntFieldDef = {
|
|
254
|
+
readonly type: "bigInt";
|
|
255
|
+
readonly description?: string;
|
|
256
|
+
readonly required?: boolean;
|
|
257
|
+
readonly sortable?: boolean;
|
|
258
|
+
readonly filterable?: boolean;
|
|
259
|
+
readonly sensitive?: boolean;
|
|
260
|
+
readonly default?: number;
|
|
261
|
+
readonly access?: FieldAccess;
|
|
262
|
+
} & ResolvedPiiFlags;
|
|
263
|
+
/**
|
|
264
|
+
* Exact decimal — Postgres `numeric(precision, scale)`. For values that need
|
|
265
|
+
* fractional precision the integer `number` field (32-bit int) and `money`
|
|
266
|
+
* field (BIGINT minor units + currency) can't hold: interest rates,
|
|
267
|
+
* percentages, ratios, measurements.
|
|
268
|
+
*
|
|
269
|
+
* `precision` = total significant digits, `scale` = digits after the decimal
|
|
270
|
+
* point (both required — no silent default that could truncate). pg returns
|
|
271
|
+
* `numeric` as a string to preserve precision; the read-codec surfaces it as
|
|
272
|
+
* a JS `number` (safe ≤ 2^53, same trade-off as `bigInt` mode:"number" — a
|
|
273
|
+
* value past that boundary loses precision, so keep `precision - scale` ≤ 15).
|
|
274
|
+
*/
|
|
275
|
+
export type DecimalFieldDef = {
|
|
276
|
+
readonly type: "decimal";
|
|
277
|
+
readonly description?: string;
|
|
278
|
+
readonly precision: number;
|
|
279
|
+
readonly scale: number;
|
|
280
|
+
readonly required?: boolean;
|
|
281
|
+
readonly sortable?: boolean;
|
|
282
|
+
readonly filterable?: boolean;
|
|
283
|
+
readonly sensitive?: boolean;
|
|
284
|
+
readonly default?: number;
|
|
285
|
+
readonly access?: FieldAccess;
|
|
286
|
+
} & ResolvedPiiFlags;
|
|
287
|
+
/** Where a money field's currency comes from. `"tenant"` is the
|
|
288
|
+
* tenant-settings bundle's per-tenant currency (`tenant-settings:config:
|
|
289
|
+
* currency`, kumiko-framework#2933); `"literal"` names a fixed ISO code and
|
|
290
|
+
* is checked against the app's `currencies` list at boot
|
|
291
|
+
* (kumiko-framework#2839). Discriminated union so a `{ kind: "field", of }`
|
|
292
|
+
* variant (amount and currency-picker side by side) can still be added
|
|
293
|
+
* without a breaking change, once a multi-currency form actually exists. */
|
|
294
|
+
export type MoneyCurrencySource = {
|
|
295
|
+
readonly kind: "tenant";
|
|
296
|
+
} | {
|
|
297
|
+
readonly kind: "literal";
|
|
298
|
+
readonly code: string;
|
|
299
|
+
};
|
|
300
|
+
/** Qualified config key a `{ kind: "tenant" }` MoneyCurrencySource resolves
|
|
301
|
+
* against — the tenant-settings bundle's per-tenant currency. Lives here
|
|
302
|
+
* (client-safe) rather than framework/engine so renderer code can read it
|
|
303
|
+
* without a runtime-only import (kumiko-framework#2937). */
|
|
304
|
+
export declare const TENANT_CURRENCY_CONFIG_KEY = "tenant-settings:config:currency";
|
|
305
|
+
export type MoneyFieldDef = {
|
|
306
|
+
readonly type: "money";
|
|
307
|
+
readonly description?: string;
|
|
308
|
+
readonly required?: boolean;
|
|
309
|
+
readonly sortable?: boolean;
|
|
310
|
+
readonly filterable?: boolean;
|
|
311
|
+
readonly sensitive?: boolean;
|
|
312
|
+
readonly access?: FieldAccess;
|
|
313
|
+
/** Declares where this field's currency comes from for a currently-empty
|
|
314
|
+
* value in an entityEdit form. Undefined = unchanged existing behaviour
|
|
315
|
+
* (`entity.defaultCurrency ?? "EUR"`); a stored value always keeps its
|
|
316
|
+
* own currency regardless of this declaration. */
|
|
317
|
+
readonly currency?: MoneyCurrencySource;
|
|
318
|
+
};
|
|
319
|
+
export type ReferenceFieldDef = {
|
|
320
|
+
readonly type: "reference";
|
|
321
|
+
readonly description?: string;
|
|
322
|
+
readonly entity: string;
|
|
323
|
+
readonly required?: boolean;
|
|
324
|
+
readonly filterable?: boolean;
|
|
325
|
+
readonly sensitive?: boolean;
|
|
326
|
+
readonly access?: FieldAccess;
|
|
327
|
+
/** Welches Feld der referenced Entity als Display-Label im
|
|
328
|
+
* Select-Dropdown erscheint. Default: "id". Boot-Validator pinst
|
|
329
|
+
* dass das Feld auf der referenced Entity existiert. */
|
|
330
|
+
readonly labelField?: string;
|
|
331
|
+
/** Multi-Reference (Tier 2.7e-Multi): Wert ist ein Array von UUIDs
|
|
332
|
+
* statt single UUID. Storage als jsonb-Array<uuid>. UI rendert
|
|
333
|
+
* Multi-Select-Combobox mit Tag-Anzeige der gewählten Items. */
|
|
334
|
+
readonly multiple?: boolean;
|
|
335
|
+
/** Include a row in this entity's result set on a text-search hit against
|
|
336
|
+
* the referenced entity's `labelField`, instead of only matching the UUID
|
|
337
|
+
* column itself (fw#2660). Requires an explicit `labelField` — the boot
|
|
338
|
+
* validator rejects it otherwise, since the default "id" is a UUID column
|
|
339
|
+
* and ILIKE against it would crash at runtime. */
|
|
340
|
+
readonly searchable?: true;
|
|
341
|
+
/** Order a list by the referenced entity's `labelField` instead of by the raw
|
|
342
|
+
* UUID column (fw#2741). Requires an explicit, non-"id" `labelField` — the
|
|
343
|
+
* boot validator rejects it otherwise, since ordering by a UUID produces a
|
|
344
|
+
* sequence that looks meaningful to the user but is arbitrary. */
|
|
345
|
+
readonly sortable?: true;
|
|
346
|
+
/** Fill the picker from this query handler instead of the referenced
|
|
347
|
+
* entity's own list handler (fw#2780) — for targets whose identity is
|
|
348
|
+
* composed from joined rows, where no single column of the referenced
|
|
349
|
+
* entity is a readable label. Fully-qualified QN
|
|
350
|
+
* ("<feature>:query:<short>"), boot-validated against the registered
|
|
351
|
+
* handlers; result contract `{ rows: { id, label }[] }`, called with
|
|
352
|
+
* `{ limit, search? }` like the default list handler. `id` rather than the
|
|
353
|
+
* `{ value, label }` of `DashboardFilterDefinition.optionsQuery`, because
|
|
354
|
+
* the stored value here IS the referenced row's id.
|
|
355
|
+
*
|
|
356
|
+
* Additive to `labelField`, which keeps serving the paths a query handler
|
|
357
|
+
* cannot back: list cells, `searchable` and `sortable` all resolve to an
|
|
358
|
+
* SQL column on the referenced table.
|
|
359
|
+
*
|
|
360
|
+
* The handler must return the row for an id that is already stored, even
|
|
361
|
+
* when it otherwise curates its result (say, to active records only) —
|
|
362
|
+
* the read-only display resolves a stored value against these rows and
|
|
363
|
+
* falls back to the raw id for one it does not find. */
|
|
364
|
+
readonly optionsQuery?: string;
|
|
365
|
+
} & ResolvedPiiFlags;
|
|
366
|
+
export type DefaultCurrency = "EUR" | "USD" | "GBP" | "CHF" | "JPY" | "SEK" | "NOK" | "DKK" | "PLN" | "CZK" | "CAD" | "AUD" | "NZD" | "CNY" | "INR";
|
|
367
|
+
type EmbeddedSubFieldBase = {
|
|
368
|
+
readonly required?: boolean;
|
|
369
|
+
readonly searchable?: boolean;
|
|
370
|
+
readonly access?: FieldAccess;
|
|
371
|
+
};
|
|
372
|
+
/** `money` is a signed integer amount in minor units (cents); which currency
|
|
373
|
+
* it means is defined by the owning aggregate (a head-level field or tenant
|
|
374
|
+
* config), not per row. Unlike the top-level `money` field (BIGINT + currency
|
|
375
|
+
* column) the value lives in jsonb, so it must stay within ±(2^53 − 1).
|
|
376
|
+
*
|
|
377
|
+
* `decimal` bounds a JSON number to `scale` fractional digits (0–15). `scale`
|
|
378
|
+
* is required — no silent default that could truncate — and there is no
|
|
379
|
+
* `precision`: jsonb has no numeric column behind it whose width `precision`
|
|
380
|
+
* could describe. */
|
|
381
|
+
export type EmbeddedSubFieldDef = (EmbeddedSubFieldBase & {
|
|
382
|
+
readonly type: "text" | "number" | "boolean" | "date" | "money" | "timestamp";
|
|
383
|
+
}) | (EmbeddedSubFieldBase & {
|
|
384
|
+
readonly type: "decimal";
|
|
385
|
+
readonly scale: number;
|
|
386
|
+
}) | (EmbeddedSubFieldBase & {
|
|
387
|
+
readonly type: "select";
|
|
388
|
+
readonly options: readonly string[];
|
|
389
|
+
}) | (EmbeddedSubFieldBase & {
|
|
390
|
+
readonly type: "reference";
|
|
391
|
+
readonly entity: string;
|
|
392
|
+
readonly labelField?: string;
|
|
393
|
+
/** Same contract as `ReferenceFieldDef.optionsQuery` (fw#2780). */
|
|
394
|
+
readonly optionsQuery?: string;
|
|
395
|
+
});
|
|
396
|
+
/** A cell computed from other cells of the same row. `from` names the
|
|
397
|
+
* sub-fields the operation reads (order matters for `subtract`). Renderer
|
|
398
|
+
* metadata — the derived value is rounded server- and client-side to the
|
|
399
|
+
* precision its target sub-field in `schema` declares (money → integer,
|
|
400
|
+
* decimal → `scale`) before it's validated. */
|
|
401
|
+
export type EmbeddedDerivedCellDef = {
|
|
402
|
+
readonly op: "multiply" | "sum" | "subtract";
|
|
403
|
+
readonly from: readonly string[];
|
|
404
|
+
};
|
|
405
|
+
export type EmbeddedFieldDef = {
|
|
406
|
+
readonly type: "embedded";
|
|
407
|
+
readonly description?: string;
|
|
408
|
+
readonly required?: boolean;
|
|
409
|
+
readonly sensitive?: boolean;
|
|
410
|
+
readonly schema: Readonly<Record<string, EmbeddedSubFieldDef>>;
|
|
411
|
+
readonly access?: FieldAccess;
|
|
412
|
+
/** A list of like-shaped objects instead of exactly one: the value is an
|
|
413
|
+
* array and every element is validated against `schema`. Storage stays
|
|
414
|
+
* jsonb, the column default becomes `[]` instead of `{}`. Built via
|
|
415
|
+
* `createEmbeddedListField()`.
|
|
416
|
+
*
|
|
417
|
+
* Only for rows that come into being with their head and stay immutable
|
|
418
|
+
* with it (posting lines, invoice positions). A row with its own lifetime
|
|
419
|
+
* (own from/to dates, own history) belongs in its own entity referencing
|
|
420
|
+
* the head — an embedded array is rewritten whole on every change and has
|
|
421
|
+
* no per-row history. */
|
|
422
|
+
readonly multiple?: boolean;
|
|
423
|
+
/** Minimum row count for an embedded list. Only valid with `multiple: true`. */
|
|
424
|
+
readonly minItems?: number;
|
|
425
|
+
/** Maximum row count for an embedded list. Only valid with `multiple: true`. */
|
|
426
|
+
readonly maxItems?: number;
|
|
427
|
+
/** Renderer metadata: sub-field name → how to compute its cell from other
|
|
428
|
+
* sub-fields of the same row, rounded to that sub-field's declared
|
|
429
|
+
* precision. Only valid with `multiple: true`. */
|
|
430
|
+
readonly derived?: Readonly<Record<string, EmbeddedDerivedCellDef>>;
|
|
431
|
+
/** Renderer metadata: numeric sub-field names to sum in a totals row. Only
|
|
432
|
+
* valid with `multiple: true`. */
|
|
433
|
+
readonly totals?: readonly string[];
|
|
434
|
+
/** Maps an embedded-list subfield name to a sibling top-level money field
|
|
435
|
+
* name — the sum of that subfield across all rows must equal the sibling
|
|
436
|
+
* field's amount. Validated both client- and server-side (schema-builder
|
|
437
|
+
* superRefine). Only meaningful with `multiple: true`. Compares against
|
|
438
|
+
* rounded `derived` cells, i.e. "sum-of-rounded" not "round-of-sum" —
|
|
439
|
+
* see kumiko-framework#1866. */
|
|
440
|
+
readonly totalsMatch?: Readonly<Record<string, string>>;
|
|
441
|
+
} & ResolvedPiiFlags;
|
|
442
|
+
export type JsonbFieldDef = {
|
|
443
|
+
readonly type: "jsonb";
|
|
444
|
+
readonly description?: string;
|
|
445
|
+
readonly sensitive?: boolean;
|
|
446
|
+
/** A later run reads this field's value as an instruction — prompt, rule,
|
|
447
|
+
* template. Every create/update whose payload writes it requires agent.risk
|
|
448
|
+
* "high" on the directly-dispatched entry handler; a revert doesn't undo a
|
|
449
|
+
* run that already read the compromised value. See irreversible-operation-gate. */
|
|
450
|
+
readonly readAsInstruction?: boolean;
|
|
451
|
+
readonly access?: FieldAccess;
|
|
452
|
+
} & ResolvedPiiFlags;
|
|
453
|
+
export type DateFieldDef = {
|
|
454
|
+
readonly type: "date";
|
|
455
|
+
readonly description?: string;
|
|
456
|
+
readonly required?: boolean;
|
|
457
|
+
readonly sortable?: boolean;
|
|
458
|
+
readonly filterable?: boolean;
|
|
459
|
+
readonly sensitive?: boolean;
|
|
460
|
+
readonly access?: FieldAccess;
|
|
461
|
+
/** Erlaubte Datumsgrenzen als ISO `yyyy-mm-dd` (z.B. Geburtsdatum nicht
|
|
462
|
+
* in der Zukunft: `max` = heute). Begrenzt den Picker und wird vom
|
|
463
|
+
* Zod-Schema beim Write durchgesetzt. */
|
|
464
|
+
readonly min?: string;
|
|
465
|
+
readonly max?: string;
|
|
466
|
+
/** Format/Locale-Override für Anzeige und Eingabe-Parsing (z.B.
|
|
467
|
+
* "de-DE"). Default = App-Locale. */
|
|
468
|
+
readonly locale?: string;
|
|
469
|
+
} & ResolvedPiiFlags;
|
|
470
|
+
export type TimestampFieldDef = {
|
|
471
|
+
readonly type: "timestamp";
|
|
472
|
+
readonly description?: string;
|
|
473
|
+
readonly required?: boolean;
|
|
474
|
+
readonly sortable?: boolean;
|
|
475
|
+
readonly filterable?: boolean;
|
|
476
|
+
readonly sensitive?: boolean;
|
|
477
|
+
readonly access?: FieldAccess;
|
|
478
|
+
/**
|
|
479
|
+
* Marker: dieses Timestamp-Feld ist Wall-Clock-Zeit an einem Ort.
|
|
480
|
+
* Wert ist der Name des begleitenden tz-Felds (IANA-Zone).
|
|
481
|
+
*
|
|
482
|
+
* Beispiel: manuelles Pair
|
|
483
|
+
* { pickupAt: { type: "timestamp", locatedBy: "pickupTz" }, pickupTz: { type: "tz" } }
|
|
484
|
+
* — bevorzuge stattdessen `createLocatedTimestampField()`.
|
|
485
|
+
*/
|
|
486
|
+
readonly locatedBy?: string;
|
|
487
|
+
/** Erlaubte Grenzen als ISO-Datetime. Begrenzt den Picker auf
|
|
488
|
+
* Tages-Granularität; die exakte Uhrzeit-Grenze setzt das Zod-Schema
|
|
489
|
+
* beim Write durch. */
|
|
490
|
+
readonly min?: string;
|
|
491
|
+
readonly max?: string;
|
|
492
|
+
/** Format/Locale-Override für Anzeige und Eingabe-Parsing. Default =
|
|
493
|
+
* App-Locale. */
|
|
494
|
+
readonly locale?: string;
|
|
495
|
+
} & ResolvedPiiFlags;
|
|
496
|
+
export type TzFieldDef = {
|
|
497
|
+
readonly type: "tz";
|
|
498
|
+
readonly description?: string;
|
|
499
|
+
readonly required?: boolean;
|
|
500
|
+
readonly sensitive?: boolean;
|
|
501
|
+
readonly access?: FieldAccess;
|
|
502
|
+
} & ResolvedPiiFlags;
|
|
503
|
+
export type LocatedTimestampFieldDef = {
|
|
504
|
+
readonly type: "locatedTimestamp";
|
|
505
|
+
readonly description?: string;
|
|
506
|
+
readonly required?: boolean;
|
|
507
|
+
readonly sortable?: boolean;
|
|
508
|
+
readonly filterable?: boolean;
|
|
509
|
+
readonly sensitive?: boolean;
|
|
510
|
+
readonly access?: FieldAccess;
|
|
511
|
+
/** Erlaubte Grenzen als ISO-Datetime (Wall-Clock). Begrenzt den Picker
|
|
512
|
+
* auf Tages-Granularität; die exakte Uhrzeit-Grenze setzt das Zod-Schema
|
|
513
|
+
* beim Write durch. */
|
|
514
|
+
readonly min?: string;
|
|
515
|
+
readonly max?: string;
|
|
516
|
+
/** Format/Locale-Override für Anzeige und Eingabe-Parsing. Default =
|
|
517
|
+
* App-Locale. */
|
|
518
|
+
readonly locale?: string;
|
|
519
|
+
} & ResolvedPiiFlags;
|
|
520
|
+
export type FileFieldDef = {
|
|
521
|
+
readonly type: "file";
|
|
522
|
+
readonly description?: string;
|
|
523
|
+
readonly required?: boolean;
|
|
524
|
+
readonly maxSize?: string;
|
|
525
|
+
readonly accept?: readonly string[];
|
|
526
|
+
readonly access?: FieldAccess;
|
|
527
|
+
} & ResolvedPiiFlags;
|
|
528
|
+
export type ImageFieldDef = {
|
|
529
|
+
readonly type: "image";
|
|
530
|
+
readonly description?: string;
|
|
531
|
+
readonly required?: boolean;
|
|
532
|
+
readonly maxSize?: string;
|
|
533
|
+
readonly accept?: readonly string[];
|
|
534
|
+
/** Named derived versions of the uploaded image. The name is the only
|
|
535
|
+
* thing a caller may ask for — the spec lives here, never in a request,
|
|
536
|
+
* so nobody can drive arbitrary renders. Boot-validated. */
|
|
537
|
+
readonly variants?: Readonly<Record<string, VariantSpec>>;
|
|
538
|
+
/** Forwarded to the file input's `capture` attribute so a phone opens the
|
|
539
|
+
* camera instead of the file picker. */
|
|
540
|
+
readonly capture?: "environment" | "user";
|
|
541
|
+
readonly access?: FieldAccess;
|
|
542
|
+
} & ResolvedPiiFlags;
|
|
543
|
+
export type FilesFieldDef = {
|
|
544
|
+
readonly type: "files";
|
|
545
|
+
readonly description?: string;
|
|
546
|
+
readonly maxSize?: string;
|
|
547
|
+
readonly accept?: readonly string[];
|
|
548
|
+
readonly maxCount?: number;
|
|
549
|
+
readonly access?: FieldAccess;
|
|
550
|
+
} & ResolvedPiiFlags;
|
|
551
|
+
export type ImagesFieldDef = {
|
|
552
|
+
readonly type: "images";
|
|
553
|
+
readonly description?: string;
|
|
554
|
+
readonly maxSize?: string;
|
|
555
|
+
readonly accept?: readonly string[];
|
|
556
|
+
readonly maxCount?: number;
|
|
557
|
+
readonly variants?: Readonly<Record<string, VariantSpec>>;
|
|
558
|
+
readonly access?: FieldAccess;
|
|
559
|
+
} & ResolvedPiiFlags;
|
|
560
|
+
export type FieldDefinition = TextFieldDef | LongTextFieldDef | BooleanFieldDef | SelectFieldDef | MultiSelectFieldDef | NumberFieldDef | BigIntFieldDef | DecimalFieldDef | MoneyFieldDef | ReferenceFieldDef | EmbeddedFieldDef | JsonbFieldDef | DateFieldDef | TimestampFieldDef | TzFieldDef | LocatedTimestampFieldDef | FileFieldDef | ImageFieldDef | FilesFieldDef | ImagesFieldDef;
|
|
561
|
+
export declare const FIELD_TYPE_NAMES: readonly FieldDefinition["type"][];
|
|
562
|
+
/** FieldDefinition as an entity-less inline form screen (actionForm,
|
|
563
|
+
* secretMint) may declare it: a `money` field there has no entity to borrow
|
|
564
|
+
* `defaultCurrency` from, so it must name its own currency source, or the
|
|
565
|
+
* form seeds a bare `0` that the handler's zod schema then rejects
|
|
566
|
+
* (kumiko-framework#2839). Entity fields keep the optional `currency` —
|
|
567
|
+
* `entity.defaultCurrency` is already boot-enforced for them. */
|
|
568
|
+
export type FormFieldDefinition = Exclude<FieldDefinition, MoneyFieldDef> | (MoneyFieldDef & {
|
|
569
|
+
readonly currency: MoneyCurrencySource;
|
|
570
|
+
});
|
|
571
|
+
export type AnyFileFieldDef = FileFieldDef | ImageFieldDef | FilesFieldDef | ImagesFieldDef;
|
|
572
|
+
export declare const NO_WIDGET_FIELD_TYPES: readonly FieldDefinition["type"][];
|
|
573
|
+
/** Display type a derived value formats as — drives the column's renderer
|
|
574
|
+
* choice in the view-model, parallel to FieldDefinition["type"]. Single-column
|
|
575
|
+
* types only: `money` is excluded because it needs a `<name>Currency`
|
|
576
|
+
* companion column a derived field has no place to put — use `number`/
|
|
577
|
+
* `decimal` plus a `{ format: "currency" }` column renderer instead. */
|
|
578
|
+
export type DerivedValueType = "text" | "number" | "decimal" | "boolean" | "date" | "timestamp";
|
|
579
|
+
/** Clock injected into `derive` — never read `Temporal.Now`/`Date` inside a
|
|
580
|
+
* derive body (no-date-api guard + testability). The list-query handler passes
|
|
581
|
+
* the read-time instant; unit tests pass a fixed one. */
|
|
582
|
+
export type DeriveContext = {
|
|
583
|
+
readonly asOf: Temporal.Instant;
|
|
584
|
+
};
|
|
585
|
+
export type DerivedFieldDef = {
|
|
586
|
+
readonly valueType: DerivedValueType;
|
|
587
|
+
/** Pure function of the stored row + clock. Returns the JSON-safe display
|
|
588
|
+
* value (e.g. integer minor units for a currency column, ISO string for a
|
|
589
|
+
* `date`). */
|
|
590
|
+
readonly derive: (row: Readonly<Record<string, unknown>>, ctx: DeriveContext) => unknown;
|
|
591
|
+
};
|
|
592
|
+
export type DerivedFieldsMap = Readonly<Record<string, DerivedFieldDef>>;
|
|
593
|
+
/** Client-facing projection of DerivedFieldDef — `derive` is server-only and
|
|
594
|
+
* not JSON-safe (would trip the output-walk guard), so the browser schema
|
|
595
|
+
* only ever carries `valueType`. A real `Pick`, not a same-shape cast: TS
|
|
596
|
+
* itself proves `derive` isn't there instead of a `{ valueType } as
|
|
597
|
+
* DerivedFieldDef` cast lying about a field that's actually missing. */
|
|
598
|
+
export type ClientDerivedFieldDef = Pick<DerivedFieldDef, "valueType">;
|
|
599
|
+
export type TransitionMap = Readonly<Record<string, readonly string[]>>;
|
|
600
|
+
/** Composite-Index auf einer Entity. Spalten werden via field-Name
|
|
601
|
+
* referenziert (camelCase). buildEntityTable mapped sie auf snake_case-
|
|
602
|
+
* Spaltennamen und benennt den Index nach Convention:
|
|
603
|
+
*
|
|
604
|
+
* <table>_<col1>_<col2>_idx (non-unique)
|
|
605
|
+
* <table>_<col1>_<col2>_unique (unique)
|
|
606
|
+
*
|
|
607
|
+
* Eine `name`-Override ist erlaubt — Convention-Bruch in Bestandscode
|
|
608
|
+
* vermeidet Migration-Churn beim Refactor.
|
|
609
|
+
*
|
|
610
|
+
* Single-column indices über `tenantId` sind redundant (buildEntityTable
|
|
611
|
+
* legt die immer automatisch an); die Boot-Validation warnt (außer
|
|
612
|
+
* `{ unique: true }` — semantische 1:1-Constraint, kein Performance-Hint). */
|
|
613
|
+
export type EntityIndexDef = {
|
|
614
|
+
readonly columns: readonly [string, ...string[]];
|
|
615
|
+
readonly unique?: boolean;
|
|
616
|
+
readonly name?: string;
|
|
617
|
+
/**
|
|
618
|
+
* Optional SQL-Fragment fuer Partial-Index — `CREATE [UNIQUE] INDEX
|
|
619
|
+
* ... WHERE <condition>`. Postgres-Pattern fuer "Index nur unter
|
|
620
|
+
* bestimmten Bedingungen", typisches Beispiel: ExportJob-Idempotency
|
|
621
|
+
* `UNIQUE(userId) WHERE status IN ('pending', 'running')`.
|
|
622
|
+
*
|
|
623
|
+
* Caller baut das Fragment via drizzle-orm `sql\`...\``-Tagged-
|
|
624
|
+
* Template. table-builder.ts emittiert `.where(def.where)` auf den
|
|
625
|
+
* Drizzle-IndexBuilder — wirkt sowohl fuer unique- als auch fuer
|
|
626
|
+
* non-unique-Indexes (PG erlaubt beides; non-unique partial nutzt
|
|
627
|
+
* man z.B. fuer scharfe BTREE-Indexes nur auf einer Status-Teilmenge
|
|
628
|
+
* statt voller Tabelle).
|
|
629
|
+
*/
|
|
630
|
+
readonly where?: unknown;
|
|
631
|
+
};
|
|
632
|
+
export type FieldsMap = Readonly<Record<string, FieldDefinition>>;
|
|
633
|
+
export type ParentRefDef = {
|
|
634
|
+
/** Field holding the name of the host entity this row hangs off. */
|
|
635
|
+
readonly entityTypeField: string;
|
|
636
|
+
/** Field holding the host row's id. */
|
|
637
|
+
readonly entityIdField: string;
|
|
638
|
+
/** Narrow which registered entities may act as host. Omitted means every
|
|
639
|
+
* registered entity is a candidate, which also widens the read-gate's SQL. */
|
|
640
|
+
readonly allowedTypes?: readonly string[];
|
|
641
|
+
};
|
|
642
|
+
export type EntityDefinition<F extends FieldsMap = FieldsMap, T extends EntityTenancy = EntityTenancy> = {
|
|
643
|
+
readonly table?: string;
|
|
644
|
+
readonly fields: F;
|
|
645
|
+
readonly softDelete?: boolean;
|
|
646
|
+
/** "tenant" (default): auto-filtered by every db accessor. "global": shared across
|
|
647
|
+
* tenants, reachable only via `db.global(table)`. Not named `scope` — config keys use that. */
|
|
648
|
+
readonly tenancy?: T;
|
|
649
|
+
/** This aggregate's event stream lives on SYSTEM_TENANT_ID rather than the
|
|
650
|
+
* creator's tenant. Opt-in per entity (NOT inherited from r.systemScope()):
|
|
651
|
+
* only for genuinely tenant-independent aggregates like `user`. The first
|
|
652
|
+
* event (create) is what's routed; updates resolve the stream tenant upstream. */
|
|
653
|
+
readonly systemStream?: boolean;
|
|
654
|
+
readonly searchWeight?: number;
|
|
655
|
+
readonly defaultCurrency?: string;
|
|
656
|
+
/** Allowed state transitions per field. Boot validates against select options. */
|
|
657
|
+
readonly transitions?: Readonly<Record<string, TransitionMap>>;
|
|
658
|
+
/** Composite-Indices über mehrere Felder. Single-column FK-Indices und
|
|
659
|
+
* der tenant_id-Index werden weiterhin automatisch von buildEntityTable
|
|
660
|
+
* angelegt — diese Liste ist nur für Custom-Indices die der Author
|
|
661
|
+
* explizit deklariert (z.B. `{ unique: true, columns: ["key", "tenantId", "userId"] }`). */
|
|
662
|
+
readonly indexes?: readonly EntityIndexDef[];
|
|
663
|
+
/**
|
|
664
|
+
* PK-Typ der Entity.
|
|
665
|
+
* - `"serial"` (default): bigserial integer — schneller, kompakter, perfekt für klassische CRUD-Entities.
|
|
666
|
+
* - `"uuid"`: uuid mit `gen_random_uuid()` default — verpflichtend für Entities deren `id` als
|
|
667
|
+
* Foreign-Key-Wert in multi-tenant Kontexten reist (z.B. `tenant.id` IS der `tenantId`). Auch für
|
|
668
|
+
* ES-Aggregate (Phase 2+) notwendig, da Events per UUID aggregiert werden.
|
|
669
|
+
*/
|
|
670
|
+
readonly idType?: "serial" | "uuid";
|
|
671
|
+
/**
|
|
672
|
+
* Row-level ownership rules (H.2). read runs as WHERE-predicate on list/
|
|
673
|
+
* detail/queryProjection, scoping which rows the caller sees. write runs
|
|
674
|
+
* pre-save on create/update/delete, scoping which rows the caller may
|
|
675
|
+
* modify (Straddle-safe, multi-role atomic — see engine/ownership.ts).
|
|
676
|
+
*
|
|
677
|
+
* Keys are role names; rules use the `from()` helper or `{ where }`
|
|
678
|
+
* escape hatch. Entity-level ownership is AND-ed with tenant isolation —
|
|
679
|
+
* a user's tenant filter still applies first.
|
|
680
|
+
*/
|
|
681
|
+
readonly access?: {
|
|
682
|
+
readonly read?: OwnershipMap;
|
|
683
|
+
readonly write?: OwnershipMap;
|
|
684
|
+
};
|
|
685
|
+
/**
|
|
686
|
+
* Join-row carrier reference. Rows of this entity hang off a host row named
|
|
687
|
+
* by two of the entity's own fields. Both paths derive one visibility gate
|
|
688
|
+
* from this single declaration: write handlers deny with NotFoundError, and
|
|
689
|
+
* list/detail filter in SQL against the host entity's own read path (tenant
|
|
690
|
+
* scope, soft-delete, `access.read` ownership). A host type that names no
|
|
691
|
+
* registered entity is denied.
|
|
692
|
+
*/
|
|
693
|
+
readonly parentRef?: ParentRefDef;
|
|
694
|
+
/**
|
|
695
|
+
* Default-Retention-Policy fuer diese Entity. Tenant-Admin kann via
|
|
696
|
+
* Compliance-Profile + Tenant-Override (Sprint 2) uebersteuern.
|
|
697
|
+
* Cleanup-Job (Sprint 2) verarbeitet die Strategy:
|
|
698
|
+
*
|
|
699
|
+
* - "hardDelete" → Row physisch weg nach keepFor
|
|
700
|
+
* - "softDelete" → deletedAt = now() (mit core-soft-delete-Feature)
|
|
701
|
+
* - "anonymize" → Felder mit `anonymize`-Funktion ueberschrieben,
|
|
702
|
+
* Row bleibt
|
|
703
|
+
* - "blockDelete" → Cleanup-Job ignoriert; User-Forget loest
|
|
704
|
+
* stattdessen anonymize aus. Buchhaltung, Mandate,
|
|
705
|
+
* Patientenakten.
|
|
706
|
+
*
|
|
707
|
+
* Siehe docs/plans/features/core-data-retention.md.
|
|
708
|
+
*/
|
|
709
|
+
readonly retention?: RetentionDef;
|
|
710
|
+
/**
|
|
711
|
+
* Read-time computed fields, keyed by name. Not stored, not a DB column,
|
|
712
|
+
* not writable — appended to each row by the list/detail query handler and
|
|
713
|
+
* nameable as a column in a declarative `entityList`. See DerivedFieldDef.
|
|
714
|
+
*/
|
|
715
|
+
readonly derivedFields?: DerivedFieldsMap;
|
|
716
|
+
/** Optional free-text semantics for the AI agent manifest — see TextFieldDef.description. */
|
|
717
|
+
readonly description?: string;
|
|
718
|
+
/**
|
|
719
|
+
* Opts this entity's rows into the `tenant-handover` bundled feature
|
|
720
|
+
* (kumiko-framework#3035): a row-bound-grant-legitimated ownership change
|
|
721
|
+
* from an anonymous/public tenant to a freshly signed-up account's tenant.
|
|
722
|
+
* An entity reachable from a transfer's root — the root itself, or any
|
|
723
|
+
* entity whose `parentRef` names the root's type — that is NOT declared
|
|
724
|
+
* `transferable: true` makes the whole transfer fail with a named error
|
|
725
|
+
* instead of silently leaving that entity's rows behind.
|
|
726
|
+
*/
|
|
727
|
+
readonly transferable?: boolean;
|
|
728
|
+
};
|
|
729
|
+
export {};
|