@cosmicdrift/kumiko-types 0.270.0 → 0.274.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/package.json +1 -1
- package/src/define-handler.ts +5 -5
- package/src/entity-handlers.ts +18 -15
- package/src/event-store-executor-types.ts +2 -2
- package/src/feature.ts +4 -4
- package/src/handlers.ts +42 -13
- package/src/screen.ts +102 -8
- package/src/step.ts +3 -1
- package/src/tenant-db-types.ts +0 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-types",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.274.0",
|
|
4
4
|
"description": "Framework-Type-Definitions für Kumiko — FeatureDefinition, BootCheck-Types und die reinen Engine-Types. Erlaubt Downstream-Konsumenten, gegen die Type-Contracts zu bauen, ohne das ganze Framework-Package zu importieren. Enthaelt keine identitaets-sensitiven Runtime-Werte mehr (Error-Klassen leben seit #1629 in kumiko-framework, Brand-Symbole nutzen Symbol.for) und ist deshalb eine plain dependency, keine peerDependency.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
package/src/define-handler.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
EscapeHatchDeclaration,
|
|
7
7
|
HandlerContext,
|
|
8
8
|
QueryEvent,
|
|
9
|
-
|
|
9
|
+
RateLimitDeclaration,
|
|
10
10
|
WriteEvent,
|
|
11
11
|
WriteResult,
|
|
12
12
|
} from "./handlers";
|
|
@@ -39,7 +39,7 @@ export type WriteHandlerDefinition<
|
|
|
39
39
|
readonly description?: string;
|
|
40
40
|
readonly agent?: AgentHandlerHints;
|
|
41
41
|
readonly unsafeSkipTransitionGuard?: boolean;
|
|
42
|
-
readonly rateLimit?:
|
|
42
|
+
readonly rateLimit?: RateLimitDeclaration;
|
|
43
43
|
readonly escapeHatch?: EscapeHatchDeclaration;
|
|
44
44
|
readonly handler: (
|
|
45
45
|
event: WriteEvent<z.infer<TSchema>>,
|
|
@@ -67,7 +67,7 @@ export type WriteHandlerInput<
|
|
|
67
67
|
readonly description?: string;
|
|
68
68
|
readonly agent?: AgentHandlerHints;
|
|
69
69
|
readonly unsafeSkipTransitionGuard?: boolean;
|
|
70
|
-
readonly rateLimit?:
|
|
70
|
+
readonly rateLimit?: RateLimitDeclaration;
|
|
71
71
|
readonly escapeHatch?: EscapeHatchDeclaration;
|
|
72
72
|
} & (
|
|
73
73
|
| {
|
|
@@ -96,7 +96,7 @@ export type QueryHandlerDefinition<
|
|
|
96
96
|
readonly access: AccessRule;
|
|
97
97
|
readonly description?: string;
|
|
98
98
|
readonly agent?: AgentHandlerHints;
|
|
99
|
-
readonly rateLimit?:
|
|
99
|
+
readonly rateLimit?: RateLimitDeclaration;
|
|
100
100
|
readonly escapeHatch?: EscapeHatchDeclaration;
|
|
101
101
|
readonly handler: (
|
|
102
102
|
query: QueryEvent<z.infer<TSchema>>,
|
|
@@ -125,7 +125,7 @@ export type StreamHandlerDefinition<
|
|
|
125
125
|
readonly name: TName;
|
|
126
126
|
readonly schema: TSchema;
|
|
127
127
|
readonly access: AccessRule;
|
|
128
|
-
readonly rateLimit?:
|
|
128
|
+
readonly rateLimit?: RateLimitDeclaration;
|
|
129
129
|
// Stream handlers can't reach db.global() (that gate is write-only), but
|
|
130
130
|
// they can still switch identity to SYSTEM via ctx.queryAs — this opts
|
|
131
131
|
// in, same contract as WriteHandlerDefinition.escapeHatch.
|
package/src/entity-handlers.ts
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
import type { EntityDefinition } from "./fields";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
AccessRule,
|
|
4
|
+
AgentHandlerHints,
|
|
5
|
+
EscapeHatchDeclaration,
|
|
6
|
+
QueryHandlerDef,
|
|
7
|
+
WriteHandlerDef,
|
|
8
|
+
} from "./handlers";
|
|
3
9
|
|
|
4
10
|
export type EntityHandlerOptions = {
|
|
5
11
|
readonly access: AccessRule;
|
|
6
12
|
readonly description?: string;
|
|
7
13
|
readonly agent?: AgentHandlerHints;
|
|
8
|
-
/**
|
|
9
|
-
* SystemAdmin-only operator handler over an
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*
|
|
19
|
-
* entity write-ownership rules that compare against the acting user's
|
|
20
|
-
* tenant, so `access` is the only remaining gate — grant it to operator
|
|
21
|
-
* roles only. */
|
|
14
|
+
/** Lifts row filtering for this ONE handler across every tenant instead of
|
|
15
|
+
* the caller's own — for a SystemAdmin-only operator handler over an
|
|
16
|
+
* otherwise tenant-scoped entity. Every use reports an
|
|
17
|
+
* `acknowledge-cross-tenant` escape-hatch audit event. On write handlers
|
|
18
|
+
* update/delete/restore address the target row's own tenant stream.
|
|
19
|
+
* `access` stays the only caller gate. Unlike `escapeHatch` on a
|
|
20
|
+
* hand-written handler this does NOT grant `ctx.db.unsafeRaw`,
|
|
21
|
+
* `db.global()` writes or identity switches. */
|
|
22
|
+
readonly escapeHatch?: EscapeHatchDeclaration;
|
|
23
|
+
/** @deprecated Use `escapeHatch: { reason }` — removed in a future release;
|
|
24
|
+
* run `scripts/codemod/migrate-cross-tenant.ts`. */
|
|
22
25
|
readonly crossTenant?: boolean;
|
|
23
26
|
};
|
|
24
27
|
|
|
@@ -74,7 +74,7 @@ export type EventStoreExecutor = {
|
|
|
74
74
|
filter?:
|
|
75
75
|
| {
|
|
76
76
|
readonly field: string;
|
|
77
|
-
readonly op: "eq" | "ne" | "lt" | "gt" | "in";
|
|
77
|
+
readonly op: "eq" | "ne" | "lt" | "gt" | "lte" | "gte" | "in";
|
|
78
78
|
readonly value: unknown;
|
|
79
79
|
}
|
|
80
80
|
| undefined;
|
|
@@ -83,7 +83,7 @@ export type EventStoreExecutor = {
|
|
|
83
83
|
filters?:
|
|
84
84
|
| ReadonlyArray<{
|
|
85
85
|
readonly field: string;
|
|
86
|
-
readonly op: "eq" | "ne" | "lt" | "gt" | "in";
|
|
86
|
+
readonly op: "eq" | "ne" | "lt" | "gt" | "lte" | "gte" | "in";
|
|
87
87
|
readonly value: unknown;
|
|
88
88
|
}>
|
|
89
89
|
| undefined;
|
package/src/feature.ts
CHANGED
|
@@ -45,7 +45,7 @@ import type {
|
|
|
45
45
|
QualifiedEventName,
|
|
46
46
|
QueryHandlerDef,
|
|
47
47
|
QueryHandlerFn,
|
|
48
|
-
|
|
48
|
+
RateLimitDeclaration,
|
|
49
49
|
StreamHandlerDef,
|
|
50
50
|
StreamHandlerFn,
|
|
51
51
|
WriteHandlerDef,
|
|
@@ -457,7 +457,7 @@ export type FeatureRegistrar<TFeature extends string = string> = {
|
|
|
457
457
|
handler: WriteHandlerFn<z.infer<TSchema>>,
|
|
458
458
|
options: {
|
|
459
459
|
access: AccessRule;
|
|
460
|
-
rateLimit?:
|
|
460
|
+
rateLimit?: RateLimitDeclaration;
|
|
461
461
|
description?: string;
|
|
462
462
|
agent?: AgentHandlerHints;
|
|
463
463
|
escapeHatch?: EscapeHatchDeclaration;
|
|
@@ -473,7 +473,7 @@ export type FeatureRegistrar<TFeature extends string = string> = {
|
|
|
473
473
|
handler: QueryHandlerFn<z.infer<TSchema>>,
|
|
474
474
|
options: {
|
|
475
475
|
access: AccessRule;
|
|
476
|
-
rateLimit?:
|
|
476
|
+
rateLimit?: RateLimitDeclaration;
|
|
477
477
|
outputSchema?: ZodType;
|
|
478
478
|
description?: string;
|
|
479
479
|
agent?: AgentHandlerHints;
|
|
@@ -490,7 +490,7 @@ export type FeatureRegistrar<TFeature extends string = string> = {
|
|
|
490
490
|
handler: StreamHandlerFn<z.infer<TSchema>>,
|
|
491
491
|
options: {
|
|
492
492
|
access: AccessRule;
|
|
493
|
-
rateLimit?:
|
|
493
|
+
rateLimit?: RateLimitDeclaration;
|
|
494
494
|
escapeHatch?: EscapeHatchDeclaration;
|
|
495
495
|
},
|
|
496
496
|
): HandlerRef;
|
package/src/handlers.ts
CHANGED
|
@@ -287,6 +287,29 @@ export type NotifyFn = (notificationType: string, options: NotifyOptions) => Pro
|
|
|
287
287
|
// Concrete implementation in bundled-features/delivery (cross-package boundary)
|
|
288
288
|
export type NotifyFactory = (user: SessionUser, tenantId: TenantId) => NotifyFn;
|
|
289
289
|
|
|
290
|
+
export type EscapeHatchKind =
|
|
291
|
+
| "unsafe-raw"
|
|
292
|
+
| "acknowledge-cross-tenant"
|
|
293
|
+
| "global-write"
|
|
294
|
+
| "identity-switch"
|
|
295
|
+
| "unsafe-all-tenants";
|
|
296
|
+
export type EscapeHatchTarget = { readonly id: string; readonly tenantId: TenantId };
|
|
297
|
+
export type EscapeHatchUseEvent = {
|
|
298
|
+
readonly handler: string;
|
|
299
|
+
readonly kind: EscapeHatchKind;
|
|
300
|
+
readonly reason: string;
|
|
301
|
+
readonly tenantId: TenantId;
|
|
302
|
+
readonly actor: string;
|
|
303
|
+
readonly target?: EscapeHatchTarget;
|
|
304
|
+
};
|
|
305
|
+
export type EscapeHatchAuditSink = (event: EscapeHatchUseEvent) => Promise<void>;
|
|
306
|
+
// Bound to handler/tenant/actor of one invocation; called once per actual escape-hatch use.
|
|
307
|
+
export type EscapeHatchReporter = (
|
|
308
|
+
kind: EscapeHatchKind,
|
|
309
|
+
reason: string,
|
|
310
|
+
target?: EscapeHatchTarget,
|
|
311
|
+
) => void;
|
|
312
|
+
|
|
290
313
|
// Shared optional fields across all execution contexts
|
|
291
314
|
type SharedContextFields = {
|
|
292
315
|
readonly redis?: Redis;
|
|
@@ -323,6 +346,8 @@ type SharedContextFields = {
|
|
|
323
346
|
readonly entityCache?: EntityCache;
|
|
324
347
|
readonly notify?: NotifyFn;
|
|
325
348
|
readonly _notifyFactory?: NotifyFactory;
|
|
349
|
+
// Wired at boot when the `audit` feature is mounted; absent → structured warn log.
|
|
350
|
+
readonly _escapeHatchAuditSink?: EscapeHatchAuditSink;
|
|
326
351
|
// Tenant-scoped secrets accessor. Present when the app wired a
|
|
327
352
|
// MasterKeyProvider at boot. Feature code reads ctx.secrets.get(...)
|
|
328
353
|
// to pull a plaintext secret; Secret<string> carries the brand that
|
|
@@ -595,8 +620,9 @@ export type HandlerContext<TMap extends object = KumikoEventTypeMap> = SharedCon
|
|
|
595
620
|
// name without the feature having to import the drizzle-table directly.
|
|
596
621
|
//
|
|
597
622
|
// Auto-applies tenant_id filter when the projection table has a tenant_id
|
|
598
|
-
// column
|
|
599
|
-
//
|
|
623
|
+
// column. { unsafeAllTenants: true } opts out but requires a grant —
|
|
624
|
+
// r.systemScope() or a declared escapeHatch on the handler (or hook).
|
|
625
|
+
// Unknown projection name throws.
|
|
600
626
|
readonly queryProjection: <T = Record<string, unknown>>(
|
|
601
627
|
qualifiedName: string,
|
|
602
628
|
options?: { readonly unsafeAllTenants?: boolean },
|
|
@@ -690,14 +716,10 @@ export type JobContext = SharedContextFields & {
|
|
|
690
716
|
readonly systemUser: SessionUser;
|
|
691
717
|
readonly log: Logger;
|
|
692
718
|
readonly triggeredBy: { readonly id: string; readonly tenantId: TenantId } | null;
|
|
693
|
-
// Only present for jobs whose owning feature declares r.systemScope()
|
|
694
|
-
//
|
|
695
|
-
//
|
|
696
|
-
//
|
|
697
|
-
// like reindexEntity() reaches through `.raw` on that TenantDb. `.raw`
|
|
698
|
-
// bypasses tenant filtering entirely, so it's only safe to hand to a
|
|
699
|
-
// helper that filters by tenantId itself (as reindexEntity does) — never
|
|
700
|
-
// pass it to code that trusts the connection to already be scoped.
|
|
719
|
+
// Only present for jobs whose owning feature declares r.systemScope().
|
|
720
|
+
// assertTenantMatch()/acknowledgeCrossTenant() return a TenantDb; a raw,
|
|
721
|
+
// tenant-unfiltered DbRunner instead comes from ctx.systemDb.unsafeRaw(reason)
|
|
722
|
+
// — only safe for a helper that filters by tenantId itself (e.g. reindexEntity).
|
|
701
723
|
readonly systemDb?: UncheckedSystemDb;
|
|
702
724
|
readonly write: (qn: string, payload: unknown) => Promise<WriteResult>;
|
|
703
725
|
readonly writeAs: (user: SessionUser, qn: string, payload: unknown) => Promise<WriteResult>;
|
|
@@ -1053,6 +1075,13 @@ export type RateLimitOption = {
|
|
|
1053
1075
|
readonly cost?: number;
|
|
1054
1076
|
};
|
|
1055
1077
|
|
|
1078
|
+
export type RateLimitDisabled = { readonly disabled: true; readonly reason: string };
|
|
1079
|
+
export type RateLimitDeclaration = RateLimitOption | RateLimitDisabled;
|
|
1080
|
+
|
|
1081
|
+
export function isRateLimitDisabled(v: RateLimitDeclaration | undefined): v is RateLimitDisabled {
|
|
1082
|
+
return v !== undefined && "disabled" in v && v.disabled === true;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1056
1085
|
export type AgentRisk = "low" | "mid" | "high";
|
|
1057
1086
|
|
|
1058
1087
|
/** Per-handler hints for the AI-agent manifest. `expose` overrides the
|
|
@@ -1075,7 +1104,7 @@ export type WriteHandlerDef = {
|
|
|
1075
1104
|
readonly description?: string;
|
|
1076
1105
|
readonly agent?: AgentHandlerHints;
|
|
1077
1106
|
readonly unsafeSkipTransitionGuard?: boolean;
|
|
1078
|
-
readonly rateLimit?:
|
|
1107
|
+
readonly rateLimit?: RateLimitDeclaration;
|
|
1079
1108
|
readonly escapeHatch?: EscapeHatchDeclaration;
|
|
1080
1109
|
// Set when the author wrote a `perform: stepsPipeline(...)` block. Boot-
|
|
1081
1110
|
// validators (projection-allowlist) and Designer/AI tooling read this
|
|
@@ -1104,7 +1133,7 @@ export type QueryHandlerDef = {
|
|
|
1104
1133
|
readonly access: AccessRule;
|
|
1105
1134
|
readonly description?: string;
|
|
1106
1135
|
readonly agent?: AgentHandlerHints;
|
|
1107
|
-
readonly rateLimit?:
|
|
1136
|
+
readonly rateLimit?: RateLimitDeclaration;
|
|
1108
1137
|
/** Zod schema of the handler's actual return value — the paged envelope
|
|
1109
1138
|
* `{ rows, nextCursor, total? }` for a `definePagedQueryHandler`, or the
|
|
1110
1139
|
* flat record (optionally `.nullable()`) for a plain query handler.
|
|
@@ -1125,7 +1154,7 @@ export type StreamHandlerDef = {
|
|
|
1125
1154
|
readonly schema: ZodType;
|
|
1126
1155
|
readonly handler: StreamHandlerFn;
|
|
1127
1156
|
readonly access: AccessRule;
|
|
1128
|
-
readonly rateLimit?:
|
|
1157
|
+
readonly rateLimit?: RateLimitDeclaration;
|
|
1129
1158
|
// Stream handlers can't reach db.global() (that gate is write-only), but
|
|
1130
1159
|
// they can still switch identity to SYSTEM via ctx.queryAs — this opts
|
|
1131
1160
|
// in, same contract as WriteHandlerDef.escapeHatch.
|
package/src/screen.ts
CHANGED
|
@@ -171,21 +171,23 @@ export type ListSortSpec = {
|
|
|
171
171
|
// unterscheidet sie.
|
|
172
172
|
//
|
|
173
173
|
// Operatoren (Drizzle-konsistent):
|
|
174
|
-
// eq/ne
|
|
175
|
-
// lt/gt
|
|
176
|
-
//
|
|
174
|
+
// eq/ne → field = value | field != value
|
|
175
|
+
// lt/gt → field < value | field > value (numerisch / temporal)
|
|
176
|
+
// lte/gte → field <= value | field >= value (numerisch / temporal)
|
|
177
|
+
// in → field IN (...values), value muss readonly array sein
|
|
177
178
|
//
|
|
178
179
|
// Field muss in der Entity existieren UND `filterable: true` haben
|
|
179
|
-
// (Boot-Validator pinned beides). `lt`/`gt` nur auf
|
|
180
|
-
// Field-Types (number/money/date/timestamp/
|
|
181
|
-
// text/boolean/select/multiSelect lehnt der
|
|
180
|
+
// (Boot-Validator pinned beides). `lt`/`gt`/`lte`/`gte` nur auf
|
|
181
|
+
// vergleichbaren Field-Types (number/money/date/timestamp/
|
|
182
|
+
// locatedTimestamp); auf text/boolean/select/multiSelect lehnt der
|
|
183
|
+
// Validator das ab.
|
|
182
184
|
//
|
|
183
185
|
// Security-Modell: Filter ist UX-Bucketing, KEINE Access-Boundary. Der
|
|
184
186
|
// Server appliziert den filter aus dem Payload — der Client kann ihn
|
|
185
187
|
// weglassen oder durch einen anderen ersetzen. Boundary bleiben
|
|
186
188
|
// access-rule + Tenant-Scope; Felder mit Sicherheits-Bias (encrypted,
|
|
187
189
|
// restricted) müssen dort geschützt werden, nicht über den Screen-Filter.
|
|
188
|
-
export type ScreenFilterOp = "eq" | "ne" | "lt" | "gt" | "in";
|
|
190
|
+
export type ScreenFilterOp = "eq" | "ne" | "lt" | "gt" | "lte" | "gte" | "in";
|
|
189
191
|
export type ScreenFilter = {
|
|
190
192
|
readonly field: string;
|
|
191
193
|
readonly op: ScreenFilterOp;
|
|
@@ -377,6 +379,32 @@ export type ToolbarAction =
|
|
|
377
379
|
readonly style?: "primary" | "secondary" | "danger";
|
|
378
380
|
};
|
|
379
381
|
|
|
382
|
+
// relatedList-only extension of ToolbarAction:
|
|
383
|
+
// a relatedList section's toolbar renders inside a projectionDetail, which
|
|
384
|
+
// has a record to evaluate against — a plain entityList/projectionList
|
|
385
|
+
// toolbar does not, so `visible`/`params` live here instead of on the base
|
|
386
|
+
// union. `visible` uses the same FieldCondition as header actions/
|
|
387
|
+
// RowAction; `params` reuses RowActionNavigate's RowFieldExtractor, applied
|
|
388
|
+
// to the enclosing record instead of a clicked row. Every field is optional,
|
|
389
|
+
// so a plain ToolbarAction is already a valid RelatedListToolbarAction —
|
|
390
|
+
// callers pass either type without a cast.
|
|
391
|
+
export type RelatedListToolbarAction =
|
|
392
|
+
| (Extract<ToolbarAction, { readonly kind: "navigate" }> & {
|
|
393
|
+
/** Conditional visibility, evaluated against the relatedList's parent
|
|
394
|
+
* record (the "Akte"). */
|
|
395
|
+
readonly visible?: FieldCondition;
|
|
396
|
+
/** Declarative URL search params extracted from the parent record,
|
|
397
|
+
* prefilling the target screen. Replaces the implicit
|
|
398
|
+
* `{ [parentParam]: parentId }` default when set. */
|
|
399
|
+
readonly params?: RowFieldExtractor;
|
|
400
|
+
})
|
|
401
|
+
| (Extract<ToolbarAction, { readonly kind: "writeHandler" }> & {
|
|
402
|
+
readonly visible?: FieldCondition;
|
|
403
|
+
})
|
|
404
|
+
| (Extract<ToolbarAction, { readonly kind: "drawer" }> & {
|
|
405
|
+
readonly visible?: FieldCondition;
|
|
406
|
+
});
|
|
407
|
+
|
|
380
408
|
export type EntityListScreenDefinition = {
|
|
381
409
|
readonly id: string;
|
|
382
410
|
readonly type: "entityList";
|
|
@@ -384,6 +412,9 @@ export type EntityListScreenDefinition = {
|
|
|
384
412
|
readonly detailFor?: string;
|
|
385
413
|
readonly description?: string;
|
|
386
414
|
readonly agent?: AgentHandlerHints;
|
|
415
|
+
/** Screen has no nav entry by design (opened via link or navved by the
|
|
416
|
+
* app); exempts it from the nav-area boot check. */
|
|
417
|
+
readonly dormant?: boolean;
|
|
387
418
|
readonly entity: string;
|
|
388
419
|
readonly columns: readonly ListColumnSpec[];
|
|
389
420
|
// Row renderer (Desktop) — when omitted, renderer draws the default table
|
|
@@ -455,6 +486,17 @@ export type ListFacetSpec =
|
|
|
455
486
|
readonly label: string;
|
|
456
487
|
readonly trueLabel: string;
|
|
457
488
|
readonly falseLabel: string;
|
|
489
|
+
}
|
|
490
|
+
| {
|
|
491
|
+
readonly field: string;
|
|
492
|
+
readonly type: "reference";
|
|
493
|
+
readonly label: string;
|
|
494
|
+
/** Entity name (same feature) or `feature:entity` (cross-feature), same
|
|
495
|
+
* convention as `ListColumnSpec.refEntity`. Options load at render time. */
|
|
496
|
+
readonly entity: string;
|
|
497
|
+
/** Row field on the referenced entity shown as the option label
|
|
498
|
+
* (default "id"). */
|
|
499
|
+
readonly labelField?: string;
|
|
458
500
|
};
|
|
459
501
|
|
|
460
502
|
export type ProjectionListScreenDefinition = {
|
|
@@ -464,6 +506,9 @@ export type ProjectionListScreenDefinition = {
|
|
|
464
506
|
readonly detailFor?: string;
|
|
465
507
|
readonly description?: string;
|
|
466
508
|
readonly agent?: AgentHandlerHints;
|
|
509
|
+
/** Screen has no nav entry by design (opened via link or navved by the
|
|
510
|
+
* app); exempts it from the nav-area boot check. */
|
|
511
|
+
readonly dormant?: boolean;
|
|
467
512
|
readonly query: string;
|
|
468
513
|
readonly columns: readonly ListColumnSpec[];
|
|
469
514
|
readonly rowRenderer?: PlatformComponent;
|
|
@@ -558,6 +603,9 @@ export type ProjectionDetailScreenDefinition = {
|
|
|
558
603
|
readonly detailFor?: string;
|
|
559
604
|
readonly description?: string;
|
|
560
605
|
readonly agent?: AgentHandlerHints;
|
|
606
|
+
/** Screen has no nav entry by design (opened via link or navved by the
|
|
607
|
+
* app); exempts it from the nav-area boot check. */
|
|
608
|
+
readonly dormant?: boolean;
|
|
561
609
|
readonly query: string;
|
|
562
610
|
/** Query-payload key for the row-id. Default "id". */
|
|
563
611
|
readonly idParam?: string;
|
|
@@ -763,6 +811,9 @@ export type DashboardScreenDefinition = {
|
|
|
763
811
|
readonly detailFor?: string;
|
|
764
812
|
readonly description?: string;
|
|
765
813
|
readonly agent?: AgentHandlerHints;
|
|
814
|
+
/** Screen has no nav entry by design (opened via link or navved by the
|
|
815
|
+
* app); exempts it from the nav-area boot check. */
|
|
816
|
+
readonly dormant?: boolean;
|
|
766
817
|
readonly panels: readonly DashboardPanelDefinition[];
|
|
767
818
|
readonly filter?: DashboardFilterDefinition;
|
|
768
819
|
readonly slots?: ScreenSlots;
|
|
@@ -835,7 +886,17 @@ export type EditFieldsSection = {
|
|
|
835
886
|
* (subtitle-only section). */
|
|
836
887
|
readonly description?: string;
|
|
837
888
|
readonly columns?: number;
|
|
889
|
+
/** Mutually exclusive with `groups` — pass `[]` when using `groups`; the
|
|
890
|
+
* boot-validator rejects both non-empty or both empty. */
|
|
838
891
|
readonly fields: readonly EditFieldSpec[];
|
|
892
|
+
/** Splits the section into multiple titled cards instead of one flat grid.
|
|
893
|
+
* Mutually exclusive with `fields`; fields named here still need to exist. */
|
|
894
|
+
readonly groups?: readonly {
|
|
895
|
+
readonly title: string;
|
|
896
|
+
readonly fields: readonly EditFieldSpec[];
|
|
897
|
+
/** Default 2. */
|
|
898
|
+
readonly columns?: number;
|
|
899
|
+
}[];
|
|
839
900
|
/** Rendered left of the section title, `text-muted-foreground` — closed
|
|
840
901
|
* IconKey vocabulary into the ICONS registry (renderer-web), analogous
|
|
841
902
|
* to EditFieldSpec.icon. No title → no icon, and no heuristic derives
|
|
@@ -889,6 +950,15 @@ export type EditRelatedListSection = {
|
|
|
889
950
|
readonly query: string;
|
|
890
951
|
/** Query-payload key the parent record's id is passed under. Default "id". */
|
|
891
952
|
readonly parentParam?: string;
|
|
953
|
+
/** Server-side WHERE clause pinning this section to the parent record —
|
|
954
|
+
* sent as `payload.filter: { field, op: "eq", value: parentId }`, kept
|
|
955
|
+
* out of the user-facet `filters` array so it can't be cleared by facet
|
|
956
|
+
* interaction. Lets a tab reuse the generic `<entity>:list` query instead
|
|
957
|
+
* of a bespoke child-rows handler. Mutually exclusive with `parentParam`
|
|
958
|
+
* (the boot-validator rejects both). `field` must be a real field on the
|
|
959
|
+
* entity behind `query`, and that query's Zod schema must accept
|
|
960
|
+
* `filter` (same requirement `filter`/`facets` already have). */
|
|
961
|
+
readonly parentFilter?: { readonly field: string };
|
|
892
962
|
readonly columns: readonly ListColumnSpec[];
|
|
893
963
|
readonly pageSize?: number;
|
|
894
964
|
/** Initial sort on mount, applied client-side over the already-loaded rows
|
|
@@ -921,6 +991,11 @@ export type EditRelatedListSection = {
|
|
|
921
991
|
* writeHandler action re-runs this section's own query, same as a
|
|
922
992
|
* projectionList row action re-running its list query. */
|
|
923
993
|
readonly rowActions?: readonly RowAction[];
|
|
994
|
+
/** Toolbar actions above the table — same type and dispatch semantics as
|
|
995
|
+
* `entityList`/`projectionList`'s `toolbarActions` ("+ Anlegen" etc.),
|
|
996
|
+
* plus `visible`/`params` evaluated against the parent record (see
|
|
997
|
+
* RelatedListToolbarAction). */
|
|
998
|
+
readonly toolbarActions?: readonly RelatedListToolbarAction[];
|
|
924
999
|
/** Record field rendered as a count badge in the tab label when the
|
|
925
1000
|
* enclosing `EditLayout.mode` is "tabs" (e.g. an open-items counter).
|
|
926
1001
|
* Ignored outside tabs mode or when the field's value is not a finite
|
|
@@ -995,6 +1070,9 @@ export type EntityEditScreenDefinition = {
|
|
|
995
1070
|
readonly detailFor?: string;
|
|
996
1071
|
readonly description?: string;
|
|
997
1072
|
readonly agent?: AgentHandlerHints;
|
|
1073
|
+
/** Screen has no nav entry by design (opened via link or navved by the
|
|
1074
|
+
* app); exempts it from the nav-area boot check. */
|
|
1075
|
+
readonly dormant?: boolean;
|
|
998
1076
|
/** Derived by buildAppSchema from the navigate `params` targeting this
|
|
999
1077
|
* screen — the only URL query keys the create form prefills. An authored
|
|
1000
1078
|
* value is overwritten. */
|
|
@@ -1092,6 +1170,9 @@ export type ActionFormScreenDefinition = {
|
|
|
1092
1170
|
readonly detailFor?: string;
|
|
1093
1171
|
readonly description?: string;
|
|
1094
1172
|
readonly agent?: AgentHandlerHints;
|
|
1173
|
+
/** Screen has no nav entry by design (opened via link or navved by the
|
|
1174
|
+
* app); exempts it from the nav-area boot check. */
|
|
1175
|
+
readonly dormant?: boolean;
|
|
1095
1176
|
/** Derived by buildAppSchema — see EntityEditScreenDefinition.urlPrefillFields. */
|
|
1096
1177
|
readonly urlPrefillFields?: readonly string[];
|
|
1097
1178
|
/** Write-Handler-QN der bei Submit gerufen wird. Form-Object landet
|
|
@@ -1104,6 +1185,9 @@ export type ActionFormScreenDefinition = {
|
|
|
1104
1185
|
/** Layout analog zu EntityEditScreen: sections mit fields aus dem
|
|
1105
1186
|
* fields-Map oben. */
|
|
1106
1187
|
readonly layout: EditLayout;
|
|
1188
|
+
/** Per-field label i18n key override, same type/semantics as
|
|
1189
|
+
* `EntityEditScreenDefinition.fieldLabels`. Falls back to the convention when absent. */
|
|
1190
|
+
readonly fieldLabels?: Readonly<Record<string, string>>;
|
|
1107
1191
|
/** i18n-key für den Submit-Button. Default: i18n-Default des
|
|
1108
1192
|
* Renderers (typischerweise "actions.submit"). */
|
|
1109
1193
|
readonly submitLabel?: string;
|
|
@@ -1223,6 +1307,9 @@ export type SecretMintScreenDefinition = {
|
|
|
1223
1307
|
readonly detailFor?: string;
|
|
1224
1308
|
readonly description?: string;
|
|
1225
1309
|
readonly agent?: AgentHandlerHints;
|
|
1310
|
+
/** Screen has no nav entry by design (opened via link or navved by the
|
|
1311
|
+
* app); exempts it from the nav-area boot check. */
|
|
1312
|
+
readonly dormant?: boolean;
|
|
1226
1313
|
/** Derived by buildAppSchema — see EntityEditScreenDefinition.urlPrefillFields. */
|
|
1227
1314
|
readonly urlPrefillFields?: readonly string[];
|
|
1228
1315
|
/** Write-handler QN dispatched on submit. */
|
|
@@ -1276,7 +1363,8 @@ export type CustomScreenDefinition = {
|
|
|
1276
1363
|
* positive (kumiko-framework#2034). Only set this on screens the
|
|
1277
1364
|
* feature itself never navs — a screen the feature DOES nav still needs
|
|
1278
1365
|
* its client plugin mounted by every consumer, and should keep
|
|
1279
|
-
* triggering the diagnostic if it's missing.
|
|
1366
|
+
* triggering the diagnostic if it's missing. Also gates the
|
|
1367
|
+
* boot-validator's nav-area check, same as `dormant` on other screens. */
|
|
1280
1368
|
readonly dormant?: boolean;
|
|
1281
1369
|
};
|
|
1282
1370
|
|
|
@@ -1320,6 +1408,9 @@ export type ConfigEditScreenDefinition = {
|
|
|
1320
1408
|
readonly detailFor?: string;
|
|
1321
1409
|
readonly description?: string;
|
|
1322
1410
|
readonly agent?: AgentHandlerHints;
|
|
1411
|
+
/** Screen has no nav entry by design (opened via link or navved by the
|
|
1412
|
+
* app); exempts it from the nav-area boot check. */
|
|
1413
|
+
readonly dormant?: boolean;
|
|
1323
1414
|
/** scope für config:write:set Calls. Muss zur Scope-Deklaration der
|
|
1324
1415
|
* in `configKeys` referenzierten Keys passen — Boot-Validator
|
|
1325
1416
|
* prüft das gegen die Registry. */
|
|
@@ -1364,6 +1455,9 @@ export type SecretsEditScreenDefinition = {
|
|
|
1364
1455
|
readonly detailFor?: string;
|
|
1365
1456
|
readonly description?: string;
|
|
1366
1457
|
readonly agent?: AgentHandlerHints;
|
|
1458
|
+
/** Screen has no nav entry by design (opened via link or navved by the
|
|
1459
|
+
* app); exempts it from the nav-area boot check. */
|
|
1460
|
+
readonly dormant?: boolean;
|
|
1367
1461
|
/** field id -> qualified secret name (`<feature>:secret:<kebab>`). */
|
|
1368
1462
|
readonly secretKeys: Readonly<Record<string, string>>;
|
|
1369
1463
|
/** field id -> i18n key for the label. */
|
package/src/step.ts
CHANGED
|
@@ -262,13 +262,14 @@ export type StepNamespace = {
|
|
|
262
262
|
readonly where: StepResolver<WhereObject>;
|
|
263
263
|
}) => StepInstance;
|
|
264
264
|
// Read sub-namespace — thin wrapper on selectMany/fetchOne (bun-db).
|
|
265
|
-
//
|
|
265
|
+
// Tenant-filtered like ctx.db; unsafeAllTenants needs escapeHatch on the handler (or systemScope).
|
|
266
266
|
readonly read: {
|
|
267
267
|
readonly findOne: (
|
|
268
268
|
name: string,
|
|
269
269
|
opts: {
|
|
270
270
|
readonly table: unknown;
|
|
271
271
|
readonly where: StepResolver<WhereObject | undefined>;
|
|
272
|
+
readonly unsafeAllTenants?: { readonly reason: string };
|
|
272
273
|
},
|
|
273
274
|
) => StepInstance;
|
|
274
275
|
readonly findMany: (
|
|
@@ -277,6 +278,7 @@ export type StepNamespace = {
|
|
|
277
278
|
readonly table: unknown;
|
|
278
279
|
readonly where?: StepResolver<WhereObject | undefined>;
|
|
279
280
|
readonly limit?: number;
|
|
281
|
+
readonly unsafeAllTenants?: { readonly reason: string };
|
|
280
282
|
},
|
|
281
283
|
) => StepInstance;
|
|
282
284
|
};
|
package/src/tenant-db-types.ts
CHANGED
|
@@ -53,15 +53,6 @@ export type TenantDbMode = "tenant" | "system";
|
|
|
53
53
|
export type TenantDb = {
|
|
54
54
|
readonly tenantId: TenantId;
|
|
55
55
|
readonly mode: TenantDbMode;
|
|
56
|
-
/**
|
|
57
|
-
* Underlying DbRunner. Framework-internal use (event-store, migrations) —
|
|
58
|
-
* bypasses tenant-filter. Feature code uses the typed helpers above so the
|
|
59
|
-
* automatic scoping stays intact.
|
|
60
|
-
* @deprecated Use `ctx.db.unsafeRaw(reason)` / `db.global(table)` (method-
|
|
61
|
-
* form) instead — both make the cross-tenant intent an explicit, named
|
|
62
|
-
* declaration instead of a silent unfiltered escape hatch. Removal fw#2860.
|
|
63
|
-
*/
|
|
64
|
-
readonly raw: DbRunner;
|
|
65
56
|
/**
|
|
66
57
|
* Unfiltered DbRunner escape hatch for handlers/hooks that declare `escapeHatch: { reason }`.
|
|
67
58
|
* Throws `AccessDeniedError` when ungranted, or `Error` when `reason` is empty.
|