@cosmicdrift/kumiko-types 0.319.0 → 0.320.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-types",
3
- "version": "0.319.0",
3
+ "version": "0.320.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>",
@@ -0,0 +1,40 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { isUiAccessGranted } from "../handlers";
3
+
4
+ describe("isUiAccessGranted", () => {
5
+ test("unset access is visible to every signed-in user", () => {
6
+ expect(isUiAccessGranted(undefined, [])).toBe(true);
7
+ expect(isUiAccessGranted(undefined, undefined)).toBe(true);
8
+ });
9
+
10
+ test("openToAll with a non-empty reason grants regardless of roles", () => {
11
+ expect(isUiAccessGranted({ openToAll: { reason: "any signed-in user" } }, [])).toBe(true);
12
+ expect(isUiAccessGranted({ openToAll: { reason: "any signed-in user" } }, undefined)).toBe(
13
+ true,
14
+ );
15
+ });
16
+
17
+ test("openToAll with a blank reason denies — malformed/untyped input narrowed defensively", () => {
18
+ expect(isUiAccessGranted({ openToAll: { reason: " " } }, [])).toBe(false);
19
+ });
20
+
21
+ test("roles rule grants on overlap", () => {
22
+ expect(isUiAccessGranted({ roles: ["Admin", "SystemAdmin"] }, ["User", "Admin"])).toBe(true);
23
+ });
24
+
25
+ test("roles rule denies without overlap", () => {
26
+ expect(isUiAccessGranted({ roles: ["Admin"] }, ["User"])).toBe(false);
27
+ });
28
+
29
+ test("roles rule denies when caller has no roles", () => {
30
+ expect(isUiAccessGranted({ roles: ["Admin"] }, [])).toBe(false);
31
+ });
32
+
33
+ test("roles rule denies when caller roles are undefined", () => {
34
+ expect(isUiAccessGranted({ roles: ["Admin"] }, undefined)).toBe(false);
35
+ });
36
+
37
+ test("empty roles rule denies everyone (explicit default-deny opt-in)", () => {
38
+ expect(isUiAccessGranted({ roles: [] }, ["Admin"])).toBe(false);
39
+ });
40
+ });
package/src/changes.json CHANGED
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.320.0",
4
+ "type": "improvement",
5
+ "title": "ToolbarAction's navigate variant gets `tab`",
6
+ "detail": "The navigate-kind ToolbarAction (entityList, projectionList and\nrelatedList-section toolbars) accepts `tab?: string`, the section id of the\ntab to activate on the target projectionDetail (layout.mode \"tabs\"),\nanalogous to RowActionNavigate.tab and MetricNavigate.tab."
7
+ },
2
8
  {
3
9
  "version": "0.310.0",
4
10
  "type": "improvement",
package/src/config.ts CHANGED
@@ -375,6 +375,10 @@ export type JobDefinition = {
375
375
  readonly name: string;
376
376
  readonly handler: JobHandlerFn;
377
377
  readonly trigger: JobTrigger;
378
+ // "sequential" is mutual exclusion, not a FIFO queue: same-name dispatches
379
+ // never run concurrently, but a loser of the per-name lock is re-enqueued
380
+ // to the back of its queue, so a job dispatched later can still complete
381
+ // before one dispatched earlier (fw#3265). Don't rely on dispatch order.
378
382
  readonly concurrency?: ConcurrencyMode | undefined;
379
383
  readonly maxPerTenant?: number | undefined;
380
384
  readonly debounceMs?: number | undefined;
package/src/handlers.ts CHANGED
@@ -56,6 +56,24 @@ export function isOpenToAllGranted(rule: AccessRule): boolean {
56
56
  return openToAll.reason.trim().length > 0;
57
57
  }
58
58
 
59
+ // UI-visibility predicate — DEFAULT-VISIBLE, the opposite stance from
60
+ // `hasAccess`/AccessRule's own default-deny doc above: an unset `access`
61
+ // means every signed-in user may see the screen/nav/workspace, not nobody.
62
+ // The engine's dispatcher access check stays default-deny; this only
63
+ // decides what a client renders/lists, never what a handler accepts.
64
+ // Same `unknown`-narrowing caution as isOpenToAllGranted — `access` can
65
+ // arrive from untyped sources.
66
+ export function isUiAccessGranted(
67
+ access: AccessRule | undefined,
68
+ userRoles: readonly string[] | undefined,
69
+ ): boolean {
70
+ if (access === undefined) return true;
71
+ if ("openToAll" in access) return isOpenToAllGranted(access);
72
+ if (userRoles === undefined) return false;
73
+ if (!("roles" in access)) return false;
74
+ return access.roles.some((role) => userRoles.includes(role));
75
+ }
76
+
59
77
  // --- Pipeline User ---
60
78
 
61
79
  // Set only on a SessionUser the framework resolved internally for a
package/src/http-route.ts CHANGED
@@ -48,6 +48,12 @@ export type HttpRouteHandlerDeps = {
48
48
  payload: unknown,
49
49
  tenantId: import("./identifiers").TenantId,
50
50
  ) => Promise<unknown>;
51
+ /** Caller IP resolved via the server's configured, trustedProxyHops-aware
52
+ * resolver (same one requestIdMiddleware uses for `/api/*`) — a route
53
+ * handler needing the caller's IP (rate limiting, audit) should read
54
+ * this instead of parsing X-Forwarded-For itself, which trusts
55
+ * whichever hop count the deployment actually has in front of it. */
56
+ readonly clientIp: string;
51
57
  };
52
58
 
53
59
  export type HttpRouteHandler = (
package/src/screen.ts CHANGED
@@ -79,7 +79,7 @@ export interface FieldFormatRegistry {
79
79
  }
80
80
 
81
81
  // Discriminated union derived from the registry — one variant per key.
82
- // JSON-safe: no function members, survives buildAppSchema → window.__KUMIKO_SCHEMA__.
82
+ // JSON-safe: no function members, survives buildAppSchema → GET /api/schema.
83
83
  export type FormatSpec = {
84
84
  [K in keyof FieldFormatRegistry]: { readonly format: K } & FieldFormatRegistry[K];
85
85
  }[keyof FieldFormatRegistry];
@@ -97,7 +97,7 @@ export type FieldRenderer = PlatformComponent | string | FormatSpec;
97
97
  // { field, ne } — true when row[field] !== ne
98
98
  // { field, in } — true when row[field] is one of the given values
99
99
  // { field, notIn } — true when row[field] is none of the given values
100
- // JSON-safe: survives buildAppSchema → window.__KUMIKO_SCHEMA__ stringify.
100
+ // JSON-safe: survives buildAppSchema → GET /api/schema stringify.
101
101
  export type FieldCondition =
102
102
  | boolean
103
103
  | { readonly field: string; readonly eq: unknown }
@@ -196,14 +196,14 @@ export type ScreenFilter = {
196
196
  readonly value: unknown;
197
197
  };
198
198
 
199
- /** Deklarativer Row-Field-Extraktor — JSON-sicher (kein Function-Prop,
200
- * überlebt window.__KUMIKO_SCHEMA__ / buildAppSchema).
199
+ /** Declarative row-field extractor — JSON-safe (no function prop, survives
200
+ * GET /api/schema / buildAppSchema).
201
201
  *
202
- * `pick`: extrahiert Felder 1:1. `{ pick: ["id", "version"] }` → `{ id: row.id, version: row.version }`
203
- * `map`: benennt um. `{ map: { incidentId: "id" } }` → `{ incidentId: row.id }`
202
+ * `pick`: extracts fields 1:1. `{ pick: ["id", "version"] }` → `{ id: row.id, version: row.version }`
203
+ * `map`: renames fields. `{ map: { incidentId: "id" } }` → `{ incidentId: row.id }`
204
204
  *
205
- * Limitation: computed/template-Werte können nicht ausgedrückt werden
206
- * — solche Logik gehört server-side in den Write-Handler. */
205
+ * Limitation: computed/templated values can't be expressed this way —
206
+ * that logic belongs server-side in the write handler. */
207
207
  export type RowFieldExtractor =
208
208
  | { readonly pick: readonly string[] }
209
209
  | { readonly map: Readonly<Record<string, string>> };
@@ -350,6 +350,10 @@ export type ToolbarAction =
350
350
  readonly label: string;
351
351
  /** Screen-id (kurz, unqualified) zu dem navigiert wird. */
352
352
  readonly screen: string;
353
+ /** Section id of the tab to activate on the target projectionDetail
354
+ * (layout.mode "tabs"); merged into the navigate search params next to
355
+ * returnTo. Boot validator checks the id exists on the target. */
356
+ readonly tab?: string;
353
357
  /** Visual style only — unlike the writeHandler variant, "danger" does
354
358
  * not force a confirm dialog here: the target form is the
355
359
  * confirmation. */