@cosmicdrift/kumiko-types 0.393.0 → 0.395.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/config.d.ts CHANGED
@@ -144,6 +144,7 @@ export type JobHandlerFn = (payload: Record<string, unknown>, context: JobContex
144
144
  export type JobTrigger = {
145
145
  readonly on: import("./handlers.ts").NameOrRef | readonly import("./handlers.ts").NameOrRef[];
146
146
  readonly where?: Readonly<Record<string, string | number | boolean>>;
147
+ readonly catchUp?: boolean;
147
148
  } | {
148
149
  readonly cron: string;
149
150
  readonly tz?: string;
@@ -1,9 +1,9 @@
1
1
  import type * as z from "zod";
2
2
  import type { ZodType } from "zod";
3
3
  import type { KumikoEventTypeMap } from "./event-type-map.ts";
4
- import type { AccessRule, AgentHandlerHints, BeforeTransactionPhase, EscapeHatchDeclaration, HandlerContext, PayloadRateLimitOption, PreparedWriteEvent, PreTransactionReservation, QueryEvent, RateLimitDeclaration, WriteHandlerTransactionMode, WriteResult } from "./handlers.ts";
4
+ import type { AccessRule, AgentHandlerHints, BeforeTransactionPhase, BeforeTransactionWhenNested, EscapeHatchDeclaration, HandlerContext, PayloadRateLimitOption, PreparedOrSkipped, PreparedWriteEvent, PreTransactionReservation, QueryEvent, RateLimitDeclaration, WriteHandlerTransactionMode, WriteResult } from "./handlers.ts";
5
5
  import type { PipelineDef } from "./step.ts";
6
- export type WriteHandlerDefinition<TName extends string = string, TSchema extends ZodType = ZodType, TData = unknown, TMap extends object = KumikoEventTypeMap, TPrepared = undefined> = {
6
+ export type WriteHandlerDefinition<TName extends string = string, TSchema extends ZodType = ZodType, TData = unknown, TMap extends object = KumikoEventTypeMap, TPrepared = undefined, TWhenNested extends BeforeTransactionWhenNested | undefined = undefined> = {
7
7
  readonly name: TName;
8
8
  readonly schema: TSchema;
9
9
  readonly access: AccessRule;
@@ -20,12 +20,13 @@ export type WriteHandlerDefinition<TName extends string = string, TSchema extend
20
20
  readonly escapeHatch?: EscapeHatchDeclaration;
21
21
  readonly reserveBeforeTransaction?: PreTransactionReservation;
22
22
  readonly beforeTransaction?: BeforeTransactionPhase<z.infer<TSchema>, TPrepared>;
23
+ readonly beforeTransactionWhenNested?: TWhenNested;
23
24
  readonly tenantlessAnonymous?: boolean;
24
25
  readonly transaction?: WriteHandlerTransactionMode;
25
- readonly handler: (event: PreparedWriteEvent<z.infer<TSchema>, TPrepared>, context: HandlerContext<TMap>) => Promise<WriteResult<TData>>;
26
+ readonly handler: (event: PreparedWriteEvent<z.infer<TSchema>, PreparedOrSkipped<TPrepared, TWhenNested>>, context: HandlerContext<TMap>) => Promise<WriteResult<TData>>;
26
27
  readonly perform?: PipelineDef<z.infer<TSchema>, TData>;
27
28
  };
28
- export type WriteHandlerInput<TName extends string = string, TSchema extends ZodType = ZodType, TData = unknown, TMap extends object = KumikoEventTypeMap, TPrepared = undefined> = {
29
+ export type WriteHandlerInput<TName extends string = string, TSchema extends ZodType = ZodType, TData = unknown, TMap extends object = KumikoEventTypeMap, TPrepared = undefined, TWhenNested extends BeforeTransactionWhenNested | undefined = undefined> = {
29
30
  readonly name: TName;
30
31
  readonly schema: TSchema;
31
32
  readonly access: AccessRule;
@@ -40,10 +41,11 @@ export type WriteHandlerInput<TName extends string = string, TSchema extends Zod
40
41
  readonly escapeHatch?: EscapeHatchDeclaration;
41
42
  readonly reserveBeforeTransaction?: PreTransactionReservation;
42
43
  readonly beforeTransaction?: BeforeTransactionPhase<z.infer<TSchema>, TPrepared>;
44
+ readonly beforeTransactionWhenNested?: TWhenNested;
43
45
  readonly tenantlessAnonymous?: boolean;
44
46
  readonly transaction?: WriteHandlerTransactionMode;
45
47
  } & ({
46
- readonly handler: (event: PreparedWriteEvent<z.infer<TSchema>, TPrepared>, context: HandlerContext<TMap>) => Promise<WriteResult<TData>>;
48
+ readonly handler: (event: PreparedWriteEvent<z.infer<TSchema>, PreparedOrSkipped<TPrepared, TWhenNested>>, context: HandlerContext<TMap>) => Promise<WriteResult<TData>>;
47
49
  readonly perform?: never;
48
50
  } | {
49
51
  readonly perform: PipelineDef<z.infer<TSchema>, TData>;
@@ -8,7 +8,7 @@ import type { KumikoEventTypeMap } from "./event-type-map.ts";
8
8
  import type { ExtensionOptionsArgs, ExtensionOptionsFor } from "./extension-options-map.ts";
9
9
  import type { BootCheckFn, MetricOptions, SecretKeyHandle, SecretNamespaceHandle, SecretNamespaceOptions, SecretOptions, StoreTableOptions, UiHints } from "./feature.ts";
10
10
  import type { EntityDefinition } from "./fields.ts";
11
- import type { AccessRule, AuthClaimsFn, ClaimKeyHandle, ClaimKeyType, DeclarativeEventMigration, EntityRef, EscapeHatchDeclaration, EventDef, EventPiiStance, EventUpcastFn, HandlerRef, NameOrRef, QualifiedEventName, RateLimitDeclaration, StreamHandlerFn } from "./handlers.ts";
11
+ import type { AccessRule, AuthClaimsFn, BeforeTransactionWhenNested, ClaimKeyHandle, ClaimKeyType, DeclarativeEventMigration, EntityRef, EscapeHatchDeclaration, EventDef, EventPiiStance, EventUpcastFn, HandlerRef, NameOrRef, QualifiedEventName, RateLimitDeclaration, StreamHandlerFn } from "./handlers.ts";
12
12
  import type { HookPhase, PostDeleteHookFn, PostQueryHookFn, PostSaveHookFn, PreDeleteHookFn, PreSaveHookFn, SearchPayloadContributorFn, ValidationHookFn } from "./hooks.ts";
13
13
  import type { HttpRouteDefinition } from "./http-route.ts";
14
14
  import type { ContentCollectionDefinition, NavDefinition } from "./nav.ts";
@@ -109,7 +109,7 @@ export type FeatureRegistrar<TFeature extends string = string> = {
109
109
  /**
110
110
  * @example r.writeHandler({ name: "note:archive", schema, handler, access: { roles: ["User"] } });
111
111
  */
112
- writeHandler<TName extends string, TSchema extends ZodType, TPrepared = undefined>(def: WriteHandlerDefinition<TName, TSchema, unknown, KumikoEventTypeMap, TPrepared>): HandlerRef;
112
+ writeHandler<TName extends string, TSchema extends ZodType, TPrepared = undefined, TWhenNested extends BeforeTransactionWhenNested | undefined = undefined>(def: WriteHandlerDefinition<TName, TSchema, unknown, KumikoEventTypeMap, TPrepared, TWhenNested>): HandlerRef;
113
113
  /**
114
114
  * @example r.queryHandler({ name: "note:list", schema, handler, access: { roles: ["User"] } });
115
115
  */
package/dist/feature.d.ts CHANGED
@@ -201,6 +201,8 @@ export type Registry = {
201
201
  readonly features: ReadonlyMap<string, FeatureDefinition>;
202
202
  getFeature(name: string): FeatureDefinition | undefined;
203
203
  getEntity(name: string): EntityDefinition | undefined;
204
+ getReadModelEntity(name: string): EntityDefinition | undefined;
205
+ getReadModelEntityOwner(name: string): string | undefined;
204
206
  getAllEntities(): ReadonlyMap<string, EntityDefinition>;
205
207
  getWriteHandler(name: string): WriteHandlerDef | undefined;
206
208
  getQueryHandler(name: string): QueryHandlerDef | undefined;
package/dist/fields.d.ts CHANGED
@@ -858,15 +858,13 @@ export type EntityDefinition<F extends FieldsMap = FieldsMap, T extends EntityTe
858
858
  readonly derivedFields?: DerivedFieldsMap;
859
859
  /** Optional free-text semantics for the AI agent manifest — see TextFieldDef.description. */
860
860
  readonly description?: string;
861
- /**
862
- * Opts this entity's rows into the `tenant-handover` bundled feature
863
- *: a row-bound-grant-legitimated ownership change
864
- * from an anonymous/public tenant to a freshly signed-up account's tenant.
865
- * An entity reachable from a transfer's root — the root itself, or any
866
- * entity whose `parentRef` names the root's type — that is NOT declared
867
- * `transferable: true` makes the whole transfer fail with a named error
868
- * instead of silently leaving that entity's rows behind.
869
- */
861
+ /** Opts the rows into `tenant-handover`, a grant-legitimated move from an anonymous tenant to the
862
+ * signed-up account's tenant. An entity reachable from a transfer's root (the root, or one whose
863
+ * `parentRef` names the root's type) without `transferable: true` fails the whole transfer with
864
+ * a named error instead of silently leaving its rows behind. */
870
865
  readonly transferable?: boolean;
866
+ /** Attached files are readable only through the record's detail query: the files feature's
867
+ * privilegedRoles do not bypass it, the uploader check stays. */
868
+ readonly attachedFileAccess?: "record-only";
871
869
  };
872
870
  export {};
@@ -132,6 +132,7 @@ export type JobTriggerEvent = {
132
132
  };
133
133
  export type JobRunnerRef = {
134
134
  handleEvent(eventName: string, payload: Record<string, unknown>, user?: SessionUser, triggerEvent?: JobTriggerEvent): Promise<void>;
135
+ handleCatchUpEvent?(jobName: string, eventName: string, payload: Record<string, unknown>, user: SessionUser, triggerEvent: JobTriggerEvent): Promise<void>;
135
136
  dispatch?(jobName: string, payload: Record<string, unknown>, meta?: {
136
137
  readonly priority?: number;
137
138
  readonly jobId?: string;
@@ -261,10 +262,11 @@ export type AppContext = SharedContextFields & {
261
262
  readonly tenantId: TenantId;
262
263
  } | null;
263
264
  /** For job-handler calls triggered by an event
264
- * (`r.job` with `trigger: { on: ... }`): the name of the handler that
265
- * emitted the event. For multi-trigger jobs (`on: [...]`), this is the
266
- * only way for the handler to know which trigger fired. Cron and manual
267
- * jobs leave the field undefined. */
265
+ * (`r.job` with `trigger: { on: ... }`): the qualified name of the event
266
+ * that fired the trigger, e.g. `inbound-mail-foundation:event:inbound-message-received`.
267
+ * For multi-trigger jobs (`on: [...]`), this is the only way for the
268
+ * handler to know which trigger fired. Cron and manual jobs leave the
269
+ * field undefined. */
268
270
  readonly triggerName?: string;
269
271
  /** Optional Geo→zone adapter. When set (via buildServer context or
270
272
  * `runProdApp`/`runDevApp` `extraContext`), the dispatcher passes it to
@@ -550,6 +552,12 @@ export type BeforeTransactionResult<TPrepared = unknown> = {
550
552
  export type BeforeTransactionPhase<TPayload = unknown, TPrepared = unknown> = (event: WriteEvent<TPayload>, ctx: BeforeTransactionContext) => Promise<BeforeTransactionResult<TPrepared>>;
551
553
  /** `perCommand`: the handler runs without a transaction of its own. */
552
554
  export type WriteHandlerTransactionMode = "perCommand";
555
+ /** `skip`: run as a nested write without the `beforeTransaction` phase. */
556
+ export type BeforeTransactionWhenNested = "skip";
557
+ /** The `prepared` type a handler sees: with `beforeTransactionWhenNested: "skip"` a nested run leaves it undefined. */
558
+ export type PreparedOrSkipped<TPrepared, TWhenNested> = [TWhenNested] extends [
559
+ BeforeTransactionWhenNested
560
+ ] ? TPrepared | undefined : TPrepared;
553
561
  export type WriteHandlerDef = {
554
562
  readonly name: string;
555
563
  readonly schema: ZodType;
@@ -579,6 +587,11 @@ export type WriteHandlerDef = {
579
587
  * `ctx.tenants.writeTarget` is available but read-only here: its db denies writes for the own and a
580
588
  * foreign tenant alike (a foreign tenant stays gated by the SystemAdmin check). */
581
589
  readonly beforeTransaction?: BeforeTransactionPhase;
590
+ /** `"skip"`: a nested ctx.dispatch.write of this handler runs without the beforeTransaction phase
591
+ * (`event.prepared` is undefined) and without a reservation, so the handler does that work inside
592
+ * its own transaction. Top-level commands still run the phase. Requires beforeTransaction; not
593
+ * combinable with reserveBeforeTransaction or transaction: "perCommand". */
594
+ readonly beforeTransactionWhenNested?: BeforeTransactionWhenNested;
582
595
  /** `"perCommand"`: the handler runs without a transaction of its own. Every ctx.dispatch.write/writeAs it
583
596
  * issues runs as its own top-level command with its own transaction and afterCommit hooks;
584
597
  * ctx.db writes and event appends throw. It cannot run as a nested write or inside a
@@ -0,0 +1 @@
1
+ export declare function toKebab(input: string): string;
@@ -0,0 +1,14 @@
1
+ // Convert camelCase or dot.separated strings to kebab-case.
2
+ // "task.create" → "task-create"
3
+ // "ticketAssigned" → "ticket-assigned"
4
+ // "billing-period.create" → "billing-period-create"
5
+ // "monthlyReport" → "monthly-report"
6
+ // Already kebab-case → unchanged
7
+ // Colons are preserved: "task:create" → "task:create"
8
+ export function toKebab(input) {
9
+ return input
10
+ .replace(/\./g, "-") // dots → dashes
11
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2") // consecutive uppercase: SSEBroadcast → SSE-Broadcast
12
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2") // camelCase boundaries: ticketAssigned → ticket-Assigned
13
+ .toLowerCase();
14
+ }
package/dist/nav.d.ts CHANGED
@@ -18,7 +18,7 @@ export type NavDefinition = {
18
18
  readonly workspaces?: readonly string[];
19
19
  };
20
20
  /** Client editor format for r.contentCollection() — not template body storage formats. */
21
- export type ContentEditorFormat = "plain" | "rich" | "markdown" | "document";
21
+ export type ContentEditorFormat = "plain" | "document";
22
22
  export type ContentCollectionDefinition = {
23
23
  readonly id: string;
24
24
  readonly kind: string;
@@ -4,6 +4,7 @@ import type { IconKey } from "./nav-icon.ts";
4
4
  import type { ScreenNavSugar, ScreenSlots } from "./screen.ts";
5
5
  import type { ScreenVisibility } from "./screen-dashboard.ts";
6
6
  import type { EditLayout } from "./screen-entity-edit.ts";
7
+ import type { ScreenFallback } from "./screen-fallback.ts";
7
8
  /** A form field computed from other fields of the same form. `listSum` adds one
8
9
  * numeric cell over all rows of an embedded list field; its `whenEmpty`
9
10
  * fallback applies while the list has no rows (it must not be a `listSum`). */
@@ -37,7 +38,7 @@ export type ActionFormScreenDefinition = {
37
38
  /** UI-only gate, also on direct URL access; handlers still enforce access. Unmet or failed renders `fallback`. */
38
39
  readonly visibleWhen?: ScreenVisibility;
39
40
  /** Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice). */
40
- readonly fallback?: string;
41
+ readonly fallback?: ScreenFallback;
41
42
  /** Derived by buildAppSchema — see EntityEditScreenDefinition.urlPrefillFields. */
42
43
  readonly urlPrefillFields?: readonly string[];
43
44
  /** Derived by buildAppSchema from the `excludedFields` of the called `handler`; the form
@@ -274,7 +275,7 @@ export type SecretMintScreenDefinition = {
274
275
  /** UI-only gate, also on direct URL access; handlers still enforce access. Unmet or failed renders `fallback`. */
275
276
  readonly visibleWhen?: ScreenVisibility;
276
277
  /** Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice). */
277
- readonly fallback?: string;
278
+ readonly fallback?: ScreenFallback;
278
279
  /** Derived by buildAppSchema — see EntityEditScreenDefinition.urlPrefillFields. */
279
280
  readonly urlPrefillFields?: readonly string[];
280
281
  /** Submit is enabled without edits: values seeded from navigate `params` count as the
@@ -3,6 +3,7 @@ import type { AccessRule, AgentScreenHints } from "./handlers.ts";
3
3
  import type { PlatformComponent, ScreenNavSugar, ScreenSlots } from "./screen.ts";
4
4
  import type { ScreenVisibility } from "./screen-dashboard.ts";
5
5
  import type { EditLayout } from "./screen-entity-edit.ts";
6
+ import type { ScreenFallback } from "./screen-fallback.ts";
6
7
  export type CustomScreenRoute = {
7
8
  readonly path: string;
8
9
  readonly component: PlatformComponent;
@@ -32,7 +33,7 @@ export type CustomScreenDefinition = {
32
33
  /** UI-only gate, also on direct URL access; handlers still enforce access. Unmet or failed renders `fallback`. */
33
34
  readonly visibleWhen?: ScreenVisibility;
34
35
  /** Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice). */
35
- readonly fallback?: string;
36
+ readonly fallback?: ScreenFallback;
36
37
  };
37
38
  export type ConfigEditScreenDefinition = {
38
39
  readonly id: string;
@@ -50,7 +51,7 @@ export type ConfigEditScreenDefinition = {
50
51
  /** UI-only gate, also on direct URL access; handlers still enforce access. Unmet or failed renders `fallback`. */
51
52
  readonly visibleWhen?: ScreenVisibility;
52
53
  /** Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice). */
53
- readonly fallback?: string;
54
+ readonly fallback?: ScreenFallback;
54
55
  /** scope for config:write:set calls. Must match the scope declaration of
55
56
  * the keys referenced in `configKeys` — the boot validator checks that
56
57
  * against the registry. */
@@ -103,7 +104,7 @@ export type SecretsEditScreenDefinition = {
103
104
  /** UI-only gate, also on direct URL access; handlers still enforce access. Unmet or failed renders `fallback`. */
104
105
  readonly visibleWhen?: ScreenVisibility;
105
106
  /** Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice). */
106
- readonly fallback?: string;
107
+ readonly fallback?: ScreenFallback;
107
108
  /** field id -> qualified secret name (`<feature>:secret:<kebab>`). */
108
109
  readonly secretKeys: Readonly<Record<string, string>>;
109
110
  /** field id -> i18n key for the label. */
@@ -1,6 +1,7 @@
1
1
  import type { AccessRule, AgentScreenHints } from "./handlers.ts";
2
2
  import type { PlatformComponent, ScreenNavSugar, ScreenSlots } from "./screen.ts";
3
3
  import type { ListColumnSpec, RowAction } from "./screen-entity-list.ts";
4
+ import type { ScreenFallback } from "./screen-fallback.ts";
4
5
  /** Text a query handler can send untranslated: the renderer translates it. */
5
6
  export type DashboardI18nText = {
6
7
  readonly i18nKey: string;
@@ -331,7 +332,7 @@ export type DashboardScreenDefinition = {
331
332
  /** UI-only gate, also on direct URL access; handlers still enforce access. Unmet or failed renders `fallback`. */
332
333
  readonly visibleWhen?: ScreenVisibility;
333
334
  /** Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice). */
334
- readonly fallback?: string;
335
+ readonly fallback?: ScreenFallback;
335
336
  readonly panels: readonly DashboardPanelDefinition[];
336
337
  /** Binds the dashboard to one record via a URL search param, see DashboardRecordBinding. */
337
338
  readonly record?: DashboardRecordBinding;
@@ -6,6 +6,7 @@ import type { FieldRenderer, PlatformComponent, ScreenNavSugar, ScreenSlots } fr
6
6
  import type { ActionFormRedirect } from "./screen-action-form.ts";
7
7
  import type { ScreenVisibility } from "./screen-dashboard.ts";
8
8
  import type { ListColumnSpec, ListSortSpec, RelatedListToolbarAction, RowAction, RowActionMode, RowFieldExtractor } from "./screen-entity-list.ts";
9
+ import type { ScreenFallback } from "./screen-fallback.ts";
9
10
  import type { ListBulkAction } from "./screen-list-bulk.ts";
10
11
  import type { ListTableGrouping, ListTableOptions } from "./screen-list-table.ts";
11
12
  import type { ListFacetSpec } from "./screen-projection.ts";
@@ -391,7 +392,7 @@ export type EntityEditScreenDefinition = {
391
392
  /** UI-only gate, also on direct URL access; handlers still enforce access. Unmet or failed renders `fallback`. */
392
393
  readonly visibleWhen?: ScreenVisibility;
393
394
  /** Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice). */
394
- readonly fallback?: string;
395
+ readonly fallback?: ScreenFallback;
395
396
  /** Derived by buildAppSchema from the navigate `params` targeting this
396
397
  * screen — the only URL query keys the create form prefills. An authored
397
398
  * value is overwritten. */
@@ -3,9 +3,10 @@ import type { AccessRule, AgentScreenHints } from "./handlers.ts";
3
3
  import type { IconKey } from "./nav-icon.ts";
4
4
  import type { FieldRenderer, PlatformComponent, ScreenNavSugar, ScreenSlots } from "./screen.ts";
5
5
  import type { ActionFormRedirect } from "./screen-action-form.ts";
6
- import type { ScreenVisibility } from "./screen-dashboard.ts";
6
+ import type { DashboardPanelVisibility, ScreenVisibility } from "./screen-dashboard.ts";
7
7
  import type { ListEmptyStateSpec } from "./screen-entity-edit.ts";
8
8
  import type { EntityListExpandableRow } from "./screen-expandable-row.ts";
9
+ import type { ScreenFallback } from "./screen-fallback.ts";
9
10
  import type { ListBulkAction } from "./screen-list-bulk.ts";
10
11
  import type { ListColumnPin, ListTableOptions } from "./screen-list-table.ts";
11
12
  export type ListColumnSpec = string | {
@@ -274,7 +275,7 @@ export type RowActionClientAction = {
274
275
  * (default `kumiko.clientAction.success`). */
275
276
  readonly successMessage?: string;
276
277
  };
277
- export type ToolbarAction = {
278
+ export type ToolbarAction = ({
278
279
  readonly kind: "navigate";
279
280
  readonly id: string;
280
281
  readonly label: string;
@@ -319,6 +320,11 @@ export type ToolbarAction = {
319
320
  readonly title?: string;
320
321
  /** Line under the drawer heading (i18n key). */
321
322
  readonly subtitle?: string;
323
+ }) & {
324
+ /** Shown only while the query's `field` equals `eq` (live, empty payload). Hidden while the
325
+ * query loads and when it fails. Named like the screen and panel gates because it is
326
+ * query-based; a list toolbar has no record to evaluate a row condition against. */
327
+ readonly visibleWhen?: DashboardPanelVisibility;
322
328
  };
323
329
  export type RelatedListToolbarAction = (Extract<ToolbarAction, {
324
330
  readonly kind: "navigate";
@@ -390,7 +396,7 @@ export type EntityListScreenDefinition = {
390
396
  /** UI-only gate, also on direct URL access; handlers still enforce access. Unmet or failed renders `fallback`. */
391
397
  readonly visibleWhen?: ScreenVisibility;
392
398
  /** Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice). */
393
- readonly fallback?: string;
399
+ readonly fallback?: ScreenFallback;
394
400
  readonly entity: string;
395
401
  /** Short screen id (same feature) the create button opens instead of the entity's default edit screen. */
396
402
  readonly createScreen?: string;
@@ -0,0 +1,16 @@
1
+ /** Why a screen gate is not open. `hidden`: the gate query answered but the condition is unmet
2
+ * (also a missing route id with `idParam`); `forbidden`/`notFound`: the gate query failed with
3
+ * HTTP 403/404; `error`: any other failure. */
4
+ export type ScreenFallbackReason = "hidden" | "forbidden" | "notFound" | "error";
5
+ /** Per-reason fallback screens (same-feature short id or cross-feature QN). A reason without an
6
+ * entry gets the default notice. At least one key is required. */
7
+ export type ScreenFallbackByReason = {
8
+ readonly [Reason in ScreenFallbackReason]?: string;
9
+ };
10
+ /** Screen-level `fallback`: one screen for every reason, or one per reason. */
11
+ export type ScreenFallback = string | ScreenFallbackByReason;
12
+ export declare const SCREEN_FALLBACK_REASONS: readonly ScreenFallbackReason[];
13
+ /** The fallback screen for one reason, or undefined when the map has no entry for it. */
14
+ export declare function fallbackTargetFor(fallback: ScreenFallback, reason: ScreenFallbackReason): string | undefined;
15
+ /** Every screen a fallback can render, in reason order. */
16
+ export declare function fallbackTargets(fallback: ScreenFallback): readonly string[];
@@ -0,0 +1,16 @@
1
+ export const SCREEN_FALLBACK_REASONS = [
2
+ "hidden",
3
+ "forbidden",
4
+ "notFound",
5
+ "error",
6
+ ];
7
+ /** The fallback screen for one reason, or undefined when the map has no entry for it. */
8
+ export function fallbackTargetFor(fallback, reason) {
9
+ return typeof fallback === "string" ? fallback : fallback[reason];
10
+ }
11
+ /** Every screen a fallback can render, in reason order. */
12
+ export function fallbackTargets(fallback) {
13
+ if (typeof fallback === "string")
14
+ return [fallback];
15
+ return SCREEN_FALLBACK_REASONS.flatMap((reason) => fallback[reason] ?? []);
16
+ }
@@ -5,6 +5,7 @@ import type { FormatSpec, PlatformComponent, ScreenNavSugar, ScreenSlots } from
5
5
  import type { DashboardStatTone, ScreenVisibility } from "./screen-dashboard.ts";
6
6
  import type { EditLayout, ListEmptyStateSpec } from "./screen-entity-edit.ts";
7
7
  import type { ListColumnSpec, ListPaginationMode, ListSortSpec, RowAction, RowActionMode, RowFieldExtractor, ScreenFilter, ToolbarAction } from "./screen-entity-list.ts";
8
+ import type { ScreenFallback } from "./screen-fallback.ts";
8
9
  import type { ListBulkAction } from "./screen-list-bulk.ts";
9
10
  import type { ListTableOptions } from "./screen-list-table.ts";
10
11
  export type ListFacetSpec = {
@@ -66,7 +67,7 @@ export type ProjectionListScreenDefinition = {
66
67
  /** UI-only gate, also on direct URL access; handlers still enforce access. Unmet or failed renders `fallback`. */
67
68
  readonly visibleWhen?: ScreenVisibility;
68
69
  /** Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice). */
69
- readonly fallback?: string;
70
+ readonly fallback?: ScreenFallback;
70
71
  readonly query: string;
71
72
  readonly columns: readonly ListColumnSpec[];
72
73
  readonly rowRenderer?: PlatformComponent;
@@ -176,7 +177,7 @@ export type ProjectionDetailScreenDefinition = {
176
177
  /** UI-only gate, also on direct URL access; handlers still enforce access. Unmet or failed renders `fallback`. */
177
178
  readonly visibleWhen?: ScreenVisibility;
178
179
  /** Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice). */
179
- readonly fallback?: string;
180
+ readonly fallback?: ScreenFallback;
180
181
  readonly query: string;
181
182
  /** Query output field whose value titles the page header (breadcrumb) when no `header` is set. */
182
183
  readonly recordTitleField?: string;
package/dist/screen.d.ts CHANGED
@@ -123,6 +123,8 @@ export type { EditExtensionSection, EditFieldGroup, EditFieldPrefill, EditFieldS
123
123
  export { relatedListGroupHeaderLabel, relatedListGroupKey } from "./screen-entity-edit.ts";
124
124
  export type { EntityListFacetConfig, EntityListFacetExtraOption, EntityListScreenDefinition, ListBadgeTone, ListColumnDisplay, ListColumnSpec, ListPaginationMode, ListSortDir, ListSortSpec, RelatedListToolbarAction, RowAction, RowActionClientAction, RowActionDisplay, RowActionDrawer, RowActionMode, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenFilter, ScreenFilterOp, ToolbarAction, } from "./screen-entity-list.ts";
125
125
  export type { EntityListExpandableRow, ExpandableRowGrouping } from "./screen-expandable-row.ts";
126
+ export type { ScreenFallback, ScreenFallbackByReason, ScreenFallbackReason, } from "./screen-fallback.ts";
127
+ export { fallbackTargetFor, fallbackTargets, SCREEN_FALLBACK_REASONS } from "./screen-fallback.ts";
126
128
  export type { ListBulkAction } from "./screen-list-bulk.ts";
127
129
  export { LIST_BULK_ACTION_KEYS, MAX_BULK_SELECTION } from "./screen-list-bulk.ts";
128
130
  export type { BundledListsOptions, ListColumnPin, ListTableDensity, ListTableExportOptions, ListTableGrouping, ListTableOptions, ListTableTotal, } from "./screen-list-table.ts";
package/dist/screen.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { DASHBOARD_MIN_REFRESH_SECONDS, DASHBOARD_STAT_TONES } from "./screen-dashboard.js";
2
2
  export { relatedListGroupHeaderLabel, relatedListGroupKey } from "./screen-entity-edit.js";
3
+ export { fallbackTargetFor, fallbackTargets, SCREEN_FALLBACK_REASONS } from "./screen-fallback.js";
3
4
  export { LIST_BULK_ACTION_KEYS, MAX_BULK_SELECTION } from "./screen-list-bulk.js";
4
5
  export { DEFAULT_EXPORT_MAX_ROWS, LIST_COLUMN_PINS, LIST_TABLE_DENSITIES, LIST_TABLE_DENSITY_KEYS, LIST_TABLE_EXPORT_KEYS, LIST_TABLE_GROUPING_KEYS, LIST_TABLE_OPTION_KEYS, LIST_TABLE_TOTAL_KEYS, MAX_EXPORT_MAX_ROWS, } from "./screen-list-table.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-types",
3
- "version": "0.393.0",
3
+ "version": "0.395.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": "MIT",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -22,6 +22,10 @@
22
22
  "types": "./dist/index.d.ts",
23
23
  "default": "./dist/index.js"
24
24
  },
25
+ "./kebab-case": {
26
+ "types": "./dist/kebab-case.d.ts",
27
+ "default": "./dist/kebab-case.js"
28
+ },
25
29
  "./identifiers": {
26
30
  "types": "./dist/identifiers.d.ts",
27
31
  "default": "./dist/identifiers.js"
@@ -313,6 +317,10 @@
313
317
  "types": "./dist/index.d.ts",
314
318
  "default": "./dist/index.js"
315
319
  },
320
+ "./kebab-case": {
321
+ "types": "./dist/kebab-case.d.ts",
322
+ "default": "./dist/kebab-case.js"
323
+ },
316
324
  "./identifiers": {
317
325
  "types": "./dist/identifiers.d.ts",
318
326
  "default": "./dist/identifiers.js"