@cosmicdrift/kumiko-types 0.409.0 → 0.411.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.
@@ -0,0 +1,4 @@
1
+ import type { AccessRule } from "./handlers.ts";
2
+ export declare function isOpenToAllGranted(rule: AccessRule): boolean;
3
+ export declare function isUiAccessGranted(access: AccessRule | undefined, userRoles: readonly string[] | undefined): boolean;
4
+ export declare function isAnySignedInGranted(rule: AccessRule): boolean;
@@ -0,0 +1,42 @@
1
+ // AccessRule can arrive from untyped sources (pattern-library JSON, Designer)
2
+ // where `openToAll` doesn't actually match the declared union — narrow via
3
+ // `unknown` instead of trusting the static type, deny on anything malformed.
4
+ export function isOpenToAllGranted(rule) {
5
+ if (!("openToAll" in rule))
6
+ return false;
7
+ const openToAll = rule.openToAll;
8
+ if (typeof openToAll !== "object" || openToAll === null)
9
+ return false;
10
+ if (!("reason" in openToAll) || typeof openToAll.reason !== "string")
11
+ return false;
12
+ return openToAll.reason.trim().length > 0;
13
+ }
14
+ const ANONYMOUS_ACCESS_ROLE = "anonymous";
15
+ // UI-visibility predicate — DEFAULT-VISIBLE, the opposite stance from
16
+ // `hasAccess` and the default-deny doc on AccessRule (handlers.ts): an unset `access`
17
+ // means every signed-in user may see the screen/nav/workspace, not nobody.
18
+ // The engine's dispatcher access check stays default-deny; this only
19
+ // decides what a client renders/lists, never what a handler accepts.
20
+ // Same `unknown`-narrowing caution as isOpenToAllGranted — `access` can
21
+ // arrive from untyped sources.
22
+ export function isUiAccessGranted(access, userRoles) {
23
+ if (access === undefined)
24
+ return true;
25
+ if ("openToAll" in access)
26
+ return isOpenToAllGranted(access);
27
+ if (userRoles === undefined)
28
+ return false;
29
+ if (!("roles" in access))
30
+ return false;
31
+ if (isAnySignedInGranted(access) && !userRoles.includes(ANONYMOUS_ACCESS_ROLE))
32
+ return true;
33
+ return access.roles.some((role) => userRoles.includes(role));
34
+ }
35
+ // Same `unknown`-narrowing caution as isOpenToAllGranted. Only granted next to the
36
+ // anonymous role, so a rule can never become "every signed-in user" through this flag.
37
+ export function isAnySignedInGranted(rule) {
38
+ if (!("roles" in rule) || !("anySignedIn" in rule))
39
+ return false;
40
+ const anySignedIn = rule.anySignedIn;
41
+ return (anySignedIn === true && Array.isArray(rule.roles) && rule.roles.includes(ANONYMOUS_ACCESS_ROLE));
42
+ }
@@ -12,6 +12,14 @@ import type { KmsAdapter } from "./kms-adapter-types.ts";
12
12
  import type { RateLimitResolver } from "./rate-limit-types.ts";
13
13
  import type { SearchAdapter } from "./search-adapter.ts";
14
14
  import type { MasterKeyProvider, SecretsContext } from "./secrets-types.ts";
15
+ /** Addressed live signal: the SSE frame reaches only the listed users of the caller's own tenant. */
16
+ export type UserLiveSignals = {
17
+ push(userIds: readonly string[], signal: {
18
+ readonly type: string;
19
+ readonly entity?: string;
20
+ readonly id?: string;
21
+ }): Promise<void>;
22
+ };
15
23
  /**
16
24
  * Infrastructure the app wired at boot, as the handler or job sees it. extraContext keys the
17
25
  * framework does not know land in `extensions`.
@@ -19,6 +27,7 @@ import type { MasterKeyProvider, SecretsContext } from "./secrets-types.ts";
19
27
  export type ContextServices = {
20
28
  readonly redis?: Redis;
21
29
  readonly cacheSync?: CacheSyncBus;
30
+ readonly userLiveSignals?: UserLiveSignals;
22
31
  readonly jobRunner?: JobRunnerRef;
23
32
  readonly searchAdapter?: SearchAdapter;
24
33
  readonly entityCache?: EntityCache;
@@ -12,14 +12,14 @@ import type { FileContext } from "./file-handle-types.ts";
12
12
  import type { FileProviderResolver } from "./file-provider-resolver-types.ts";
13
13
  import type { GeoTzProvider } from "./geo-tz.ts";
14
14
  import type { BeforeTransactionContext, HandlerAuth, HandlerDb, HandlerDispatch, HandlerEvents, HandlerObservability, JobDispatch, JobEvents, JobObservability, ReservationContext } from "./handler-context-facets.ts";
15
- import type { ContextServices, HandlerLifecycle, HandlerTenants, JobLifecycle } from "./handler-context-services.ts";
15
+ import type { ContextServices, HandlerLifecycle, HandlerTenants, JobLifecycle, UserLiveSignals } from "./handler-context-services.ts";
16
16
  import type { Logger } from "./logger.ts";
17
17
  import type { Meter, Tracer } from "./observability/index.ts";
18
18
  import type { SearchAdapter } from "./search-adapter.ts";
19
19
  import type { CrossTenantReads, TenantDb } from "./tenant-db-types.ts";
20
20
  import type { Clock, TzContext } from "./tz-context.ts";
21
21
  export type { BeforeTransactionContext, HandlerAuth, HandlerDb, HandlerDispatch, HandlerEvents, HandlerObservability, JobDispatch, JobEvents, JobObservability, ReservationContext, } from "./handler-context-facets.ts";
22
- export type { ContextServices, HandlerLifecycle, HandlerTenants, JobLifecycle, } from "./handler-context-services.ts";
22
+ export type { ContextServices, HandlerLifecycle, HandlerTenants, JobLifecycle, UserLiveSignals, } from "./handler-context-services.ts";
23
23
  import type { AgentHandlerHints } from "./agent-hints.ts";
24
24
  export type OpenToAllPersonalData = "tenant-members";
25
25
  export type OpenToAllDeclaration = {
@@ -33,6 +33,7 @@ export type RoleAccessPersonalData = "public-intake";
33
33
  export type RoleAccessRule = {
34
34
  readonly roles: readonly string[];
35
35
  readonly personalData?: RoleAccessPersonalData;
36
+ readonly anySignedIn?: true;
36
37
  };
37
38
  export type AccessRule = RoleAccessRule | OpenToAllAccessRule;
38
39
  export declare const ESCAPE_HATCH_GRANTS: readonly ["systemIdentity", "globalWrites", "unsafeRaw", "crossTenantReads"];
@@ -48,8 +49,7 @@ export declare function crossTenantReadsGrantFor(declaration: EscapeHatchDeclara
48
49
  export declare function escapeHatchGrantsProblem(declaration: {
49
50
  readonly grants?: unknown;
50
51
  }): string | undefined;
51
- export declare function isOpenToAllGranted(rule: AccessRule): boolean;
52
- export declare function isUiAccessGranted(access: AccessRule | undefined, userRoles: readonly string[] | undefined): boolean;
52
+ export { isAnySignedInGranted, isOpenToAllGranted, isUiAccessGranted, } from "./access-predicates.ts";
53
53
  export type SessionUserOrigin = "member-resolution";
54
54
  export type SessionUser = {
55
55
  readonly id: string;
@@ -222,6 +222,7 @@ export type TenantWriteTarget = {
222
222
  type SharedContextFields = {
223
223
  readonly redis?: Redis;
224
224
  readonly cacheSync?: CacheSyncBus;
225
+ readonly userLiveSignals?: UserLiveSignals;
225
226
  readonly jobRunner?: JobRunnerRef;
226
227
  readonly configResolver?: ConfigResolver;
227
228
  readonly config?: ConfigAccessor;
package/dist/handlers.js CHANGED
@@ -39,37 +39,7 @@ export function escapeHatchGrantsProblem(declaration) {
39
39
  }
40
40
  return undefined;
41
41
  }
42
- // AccessRule can arrive from untyped sources (pattern-library JSON, Designer)
43
- // where `openToAll` doesn't actually match the declared union — narrow via
44
- // `unknown` instead of trusting the static type, deny on anything malformed.
45
- export function isOpenToAllGranted(rule) {
46
- if (!("openToAll" in rule))
47
- return false;
48
- const openToAll = rule.openToAll;
49
- if (typeof openToAll !== "object" || openToAll === null)
50
- return false;
51
- if (!("reason" in openToAll) || typeof openToAll.reason !== "string")
52
- return false;
53
- return openToAll.reason.trim().length > 0;
54
- }
55
- // UI-visibility predicate — DEFAULT-VISIBLE, the opposite stance from
56
- // `hasAccess`/AccessRule's own default-deny doc above: an unset `access`
57
- // means every signed-in user may see the screen/nav/workspace, not nobody.
58
- // The engine's dispatcher access check stays default-deny; this only
59
- // decides what a client renders/lists, never what a handler accepts.
60
- // Same `unknown`-narrowing caution as isOpenToAllGranted — `access` can
61
- // arrive from untyped sources.
62
- export function isUiAccessGranted(access, userRoles) {
63
- if (access === undefined)
64
- return true;
65
- if ("openToAll" in access)
66
- return isOpenToAllGranted(access);
67
- if (userRoles === undefined)
68
- return false;
69
- if (!("roles" in access))
70
- return false;
71
- return access.roles.some((role) => userRoles.includes(role));
72
- }
42
+ export { isAnySignedInGranted, isOpenToAllGranted, isUiAccessGranted, } from "./access-predicates.js";
73
43
  // Closed vocabulary for delivery failures and skips. Only these values (plus
74
44
  // `http_<status>`) are ever stored or returned as a delivery error, never a raw
75
45
  // error message: provider errors can carry URLs, tokens and addresses.
@@ -0,0 +1,42 @@
1
+ export declare const PRESENCE_FEATURE: "presence";
2
+ export declare const PRESENCE_WRITES: {
3
+ readonly heartbeat: "presence:write:heartbeat";
4
+ readonly leave: "presence:write:leave";
5
+ };
6
+ export declare const PRESENCE_QUERIES: {
7
+ readonly list: "presence:query:list";
8
+ };
9
+ export declare const PRESENCE_CHANGED_EVENT: "presence:event:changed";
10
+ export declare const PRESENCE_HEARTBEAT_INTERVAL_MS = 15000;
11
+ export declare const PRESENCE_LIST_REFETCH_INTERVAL_MS = 25000;
12
+ export declare const PRESENCE_FOCUS_MAX_LENGTH = 128;
13
+ export declare const PRESENCE_LIST_MAX_IDS = 100;
14
+ export type PresenceActivity = "viewing" | "editing";
15
+ export type PresenceEntry = {
16
+ readonly userId: string;
17
+ readonly displayName: string;
18
+ readonly activity: PresenceActivity;
19
+ readonly focus?: string;
20
+ /** ISO timestamp from which the user has been in this activity and focus. */
21
+ readonly since: string;
22
+ };
23
+ export type PresenceHeartbeatPayload = {
24
+ readonly entity: string;
25
+ readonly id: string;
26
+ readonly activity: PresenceActivity;
27
+ readonly focus?: string;
28
+ };
29
+ export type PresenceLeavePayload = {
30
+ readonly entity: string;
31
+ readonly id: string;
32
+ };
33
+ export type PresenceListPayload = {
34
+ readonly entity: string;
35
+ readonly ids: readonly string[];
36
+ };
37
+ export type PresenceHeartbeatResult = {
38
+ readonly others: readonly PresenceEntry[];
39
+ };
40
+ export type PresenceListResult = {
41
+ readonly byId: Readonly<Record<string, readonly PresenceEntry[]>>;
42
+ };
@@ -0,0 +1,15 @@
1
+ // Contract between the presence feature (server) and the presence hooks and widgets in the
2
+ // renderer. The renderer must not import bundled-features, so both sides share this file.
3
+ export const PRESENCE_FEATURE = "presence";
4
+ export const PRESENCE_WRITES = {
5
+ heartbeat: "presence:write:heartbeat",
6
+ leave: "presence:write:leave",
7
+ };
8
+ export const PRESENCE_QUERIES = {
9
+ list: "presence:query:list",
10
+ };
11
+ export const PRESENCE_CHANGED_EVENT = "presence:event:changed";
12
+ export const PRESENCE_HEARTBEAT_INTERVAL_MS = 15_000;
13
+ export const PRESENCE_LIST_REFETCH_INTERVAL_MS = 25_000;
14
+ export const PRESENCE_FOCUS_MAX_LENGTH = 128;
15
+ export const PRESENCE_LIST_MAX_IDS = 100;
@@ -81,10 +81,12 @@ export type ActionFormScreenDefinition = {
81
81
  * Order in the array = order in the footer, before the submit button. */
82
82
  readonly footerActions?: readonly ActionFormFooterAction[];
83
83
  /** Context box above the fields (i18n keys). `{name}` placeholders resolve from the
84
- * drawer prefill (`params` of the opening row action), formatted for display. */
84
+ * drawer prefill (`params` of the opening row action), formatted for display, and from
85
+ * the fixed `params` (e.g. the app name a feature option sets), which win on a name clash. */
85
86
  readonly summary?: {
86
87
  readonly title: string;
87
88
  readonly subtitle?: string;
89
+ readonly params?: Readonly<Record<string, string>>;
88
90
  };
89
91
  /** Visual style of the submit button. "danger" renders it red — for a form
90
92
  * whose handler is destructive (terminate, revoke, delete), so the warning
@@ -127,6 +127,10 @@ type EditFieldsSectionBase = {
127
127
  * and in the "up next" box. Wizard layouts only; unlike `description` it
128
128
  * is not shown inside the step. */
129
129
  readonly subtitle?: string;
130
+ /** Wizard layouts only: text under this step's title in the "up next" box while the step before it is active; falls back to `subtitle`. */
131
+ readonly upNextHint?: string;
132
+ /** Wizard layouts only: field of this section whose formatted value shows under the step label in the step rail once the step is done. Needs a field of this section. */
133
+ readonly summaryField?: string;
130
134
  /** Rendered top-right in the section's Card title row — same
131
135
  * navigate/drawer/writeHandler dispatch as `ProjectionDetailScreenDefinition.actions`,
132
136
  * evaluated against the same record. Every action must resolve an icon
@@ -154,6 +158,14 @@ export type EditExtensionSection = {
154
158
  readonly id?: string;
155
159
  readonly kind: "extension";
156
160
  readonly title: string;
161
+ /** Wizard layouts only: intro text above the step, same slot as `EditFieldsSection.description`. */
162
+ readonly description?: string;
163
+ /** Wizard layouts only: short line under this step's title in the step rail and the "up next" box. */
164
+ readonly subtitle?: string;
165
+ /** Wizard layouts only: text under this step's title in the "up next" box while the step before it is active; falls back to `subtitle`. */
166
+ readonly upNextHint?: string;
167
+ /** Wizard layouts only: field whose formatted value shows under the step label once the step is done. On an `actionForm` it names a field of the screen's `fields`; elsewhere a field of a fields section of the layout. */
168
+ readonly summaryField?: string;
157
169
  readonly component: PlatformComponent;
158
170
  /** When true, the extension registers via `useExtensionFormSubmit` and the
159
171
  * surrounding form's Save button should activate for its dirty state.
@@ -374,16 +386,24 @@ export type EditLayout = {
374
386
  * list of independent preferences rather than one record form. Default
375
387
  * "form" keeps the grid of cells. */
376
388
  readonly variant?: "form" | "settings-list";
389
+ /** Hides the asterisk after required field labels. Default false (shown). */
390
+ readonly hideRequiredMarker?: boolean;
377
391
  /** Persists in-progress wizard state as a resumable draft instead of
378
392
  * discarding it on navigation away. */
379
393
  readonly draft?: boolean;
380
- /** Wizard layouts only. `upNext: true` adds an "up next" box to the step
381
- * rail with the following step's title and `subtitle`; the last step
382
- * shows none. */
394
+ /** Wizard layouts only. `aside.upNext: true` adds an "up next" box to the step
395
+ * rail with the following step's title and its `upNextHint` (else `subtitle`) only.
396
+ * The last step shows the box only with `finishDescription` (i18n key): the
397
+ * finish action as title, that text under it. */
383
398
  readonly wizard?: {
384
399
  readonly aside?: {
385
400
  readonly upNext: true;
401
+ readonly finishDescription?: string;
386
402
  };
403
+ /** Also offers "Save and close" while creating a record (default: update mode only). */
404
+ readonly saveAndCloseOnCreate?: boolean;
405
+ /** Disables the finish button (and blocks the submit) while the condition holds on the form values. */
406
+ readonly submitDisabledWhen?: FieldCondition;
387
407
  };
388
408
  };
389
409
  export type EntityWriteExcludedFields = {
@@ -146,6 +146,8 @@ export type RowActionWriteHandler = {
146
146
  readonly confirmLabel?: string;
147
147
  /** Conditional visibility per row. */
148
148
  readonly visible?: FieldCondition;
149
+ /** projectionDetail header action only: shown while one of these tab (section) ids is active. Needs `layout.mode: "tabs"`; default every tab. */
150
+ readonly showOnTabs?: readonly string[];
149
151
  /** Visual style. "danger" renders red + forces a confirm dialog (even
150
152
  * without an explicit `confirm` key). */
151
153
  readonly style?: "primary" | "secondary" | "danger";
@@ -185,6 +187,8 @@ export type RowActionNavigateBase = {
185
187
  readonly tab?: string;
186
188
  /** Conditional visibility per row. */
187
189
  readonly visible?: FieldCondition;
190
+ /** projectionDetail header action only: shown while one of these tab (section) ids is active. Needs `layout.mode: "tabs"`; default every tab. */
191
+ readonly showOnTabs?: readonly string[];
188
192
  /** Visual style only — unlike the writeHandler variants, "danger" does not
189
193
  * force a confirm dialog here: the target form is the confirmation. */
190
194
  readonly style?: "primary" | "secondary" | "danger";
@@ -228,6 +232,8 @@ export type RowActionDrawer = {
228
232
  readonly params?: RowFieldExtractor;
229
233
  /** Per-row conditional visibility. */
230
234
  readonly visible?: FieldCondition;
235
+ /** projectionDetail header action only: shown while one of these tab (section) ids is active. Needs `layout.mode: "tabs"`; default every tab. */
236
+ readonly showOnTabs?: readonly string[];
231
237
  /** Visual style only — unlike the writeHandler variants, "danger" does not
232
238
  * force a confirm dialog here: the target form is the confirmation. */
233
239
  readonly style?: "primary" | "secondary" | "danger";
@@ -266,6 +272,8 @@ export type RowActionClientAction = {
266
272
  readonly confirmLabel?: string;
267
273
  /** Conditional visibility per row. */
268
274
  readonly visible?: FieldCondition;
275
+ /** projectionDetail header action only: shown while one of these tab (section) ids is active. Needs `layout.mode: "tabs"`; default every tab. */
276
+ readonly showOnTabs?: readonly string[];
269
277
  /** Visual style. "danger" forces a confirm dialog, like a writeHandler. */
270
278
  readonly style?: "primary" | "secondary" | "danger";
271
279
  /** Overrides the id-based default icon, see `RowActionWriteHandler.icon`. */
@@ -3,7 +3,7 @@ export declare const LIST_TABLE_DENSITIES: readonly ["comfortable", "compact"];
3
3
  export type ListTableDensity = (typeof LIST_TABLE_DENSITIES)[number];
4
4
  export declare const LIST_COLUMN_PINS: readonly ["left", "right"];
5
5
  export type ListColumnPin = (typeof LIST_COLUMN_PINS)[number];
6
- export declare const LIST_TABLE_OPTION_KEYS: readonly ["rememberState", "resetAll", "columnPicker", "density", "pinning", "resizing", "keyboardNavigation", "export", "grouping", "totals"];
6
+ export declare const LIST_TABLE_OPTION_KEYS: readonly ["rememberState", "resetAll", "columnPicker", "density", "pinning", "resizing", "keyboardNavigation", "export", "grouping", "totals", "compactHeader"];
7
7
  export declare const LIST_TABLE_GROUPING_KEYS: readonly ["field", "label", "labels", "collapsedWhen", "userChoice", "fields"];
8
8
  export declare const LIST_TABLE_TOTAL_KEYS: readonly ["field", "kind"];
9
9
  export declare const LIST_TABLE_EXPORT_KEYS: readonly ["xlsx", "maxRows"];
@@ -25,6 +25,8 @@ export type ListTableOptions = {
25
25
  readonly resizing?: boolean;
26
26
  /** Rows become one tab stop; ArrowUp/ArrowDown move, Home/End jump, Enter runs the row click. Space stays reserved for selection. */
27
27
  readonly keyboardNavigation?: boolean;
28
+ /** Renders the header row smaller (12px) and semibold. */
29
+ readonly compactHeader?: boolean;
28
30
  readonly density?: {
29
31
  /** Row density on first visit. Default "comfortable". */
30
32
  readonly default?: ListTableDensity;
@@ -11,6 +11,7 @@ export const LIST_TABLE_OPTION_KEYS = [
11
11
  "export",
12
12
  "grouping",
13
13
  "totals",
14
+ "compactHeader",
14
15
  ];
15
16
  export const LIST_TABLE_GROUPING_KEYS = [
16
17
  "field",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-types",
3
- "version": "0.409.0",
3
+ "version": "0.411.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>",
@@ -46,6 +46,10 @@
46
46
  "types": "./dist/in-app-notification-types.d.ts",
47
47
  "default": "./dist/in-app-notification-types.js"
48
48
  },
49
+ "./presence-types": {
50
+ "types": "./dist/presence-types.d.ts",
51
+ "default": "./dist/presence-types.js"
52
+ },
49
53
  "./target-ref": {
50
54
  "types": "./dist/target-ref.d.ts",
51
55
  "default": "./dist/target-ref.js"
@@ -349,6 +353,10 @@
349
353
  "types": "./dist/in-app-notification-types.d.ts",
350
354
  "default": "./dist/in-app-notification-types.js"
351
355
  },
356
+ "./presence-types": {
357
+ "types": "./dist/presence-types.d.ts",
358
+ "default": "./dist/presence-types.js"
359
+ },
352
360
  "./target-ref": {
353
361
  "types": "./dist/target-ref.d.ts",
354
362
  "default": "./dist/target-ref.js"