@cosmicdrift/kumiko-types 0.256.0 → 0.258.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.256.0",
3
+ "version": "0.258.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>",
@@ -6,6 +6,15 @@ import type { EntityId } from "./identifiers";
6
6
  import type { SearchAdapter } from "./search-adapter";
7
7
  import type { TenantDb } from "./tenant-db-types";
8
8
 
9
+ // Read-time gate for join-row entities (EntityDefinition.parentRef): the
10
+ // registered-entity map the SQL clause checks host candidates against.
11
+ // Lives here (not in db/parent-ref-clause.ts) because the Registry only
12
+ // exists at request time — the same extension-point shape as
13
+ // referenceSearch/referenceSort below (fw#2660/fw#2741).
14
+ export type ParentVisibilityOption = {
15
+ readonly entities: ReadonlyMap<string, EntityDefinition>;
16
+ };
17
+
9
18
  // Runs the caller's preSave hooks against changes not yet persisted, for
10
19
  // the executor's create()/update() only — restore/delete/forget have no
11
20
  // `changes` to shape. See HandlerContext.runPreSave for how this gets bound
@@ -120,6 +129,9 @@ export type EventStoreExecutor = {
120
129
  }>;
121
130
  readonly resolveEntity: (entityName: string) => EntityDefinition | undefined;
122
131
  };
132
+ // Join-row read-gate (EntityDefinition.parentRef) — same request-time
133
+ // registry dependency as referenceSearch/referenceSort above.
134
+ readonly parentVisibility?: ParentVisibilityOption;
123
135
  },
124
136
  ) => Promise<CursorResult<Record<string, unknown>>>;
125
137
 
@@ -127,5 +139,6 @@ export type EventStoreExecutor = {
127
139
  payload: { id: EntityId },
128
140
  user: SessionUser,
129
141
  db: TenantDb,
142
+ options?: { readonly parentVisibility?: ParentVisibilityOption },
130
143
  ) => Promise<Record<string, unknown> | null>;
131
144
  };
package/src/fields.ts CHANGED
@@ -255,7 +255,8 @@ export type TextFieldDef = {
255
255
  readonly filterable?: boolean;
256
256
  readonly encrypted?: boolean;
257
257
  readonly sensitive?: boolean;
258
- readonly format?: "email" | "url" | "phone";
258
+ /** "password" is a pure render hint (masked input) — no storage semantics. */
259
+ readonly format?: "email" | "url" | "phone" | "password";
259
260
  readonly default?: string;
260
261
  readonly access?: FieldAccess;
261
262
  /** Mehrzeiliger Text — DefaultInput rendert dann ein <textarea> statt
@@ -903,6 +904,16 @@ export type EntityIndexDef = {
903
904
 
904
905
  export type FieldsMap = Readonly<Record<string, FieldDefinition>>;
905
906
 
907
+ export type ParentRefDef = {
908
+ /** Field holding the name of the host entity this row hangs off. */
909
+ readonly entityTypeField: string;
910
+ /** Field holding the host row's id. */
911
+ readonly entityIdField: string;
912
+ /** Narrow which registered entities may act as host. Omitted means every
913
+ * registered entity is a candidate, which also widens the read-gate's SQL. */
914
+ readonly allowedTypes?: readonly string[];
915
+ };
916
+
906
917
  export type EntityDefinition<F extends FieldsMap = FieldsMap> = {
907
918
  readonly table?: string;
908
919
  readonly fields: F;
@@ -943,6 +954,15 @@ export type EntityDefinition<F extends FieldsMap = FieldsMap> = {
943
954
  readonly read?: OwnershipMap;
944
955
  readonly write?: OwnershipMap;
945
956
  };
957
+ /**
958
+ * Join-row carrier reference. Rows of this entity hang off a host row named
959
+ * by two of the entity's own fields. Both paths derive one visibility gate
960
+ * from this single declaration: write handlers deny with NotFoundError, and
961
+ * list/detail filter in SQL against the host entity's own read path (tenant
962
+ * scope, soft-delete, `access.read` ownership). A host type that names no
963
+ * registered entity is denied.
964
+ */
965
+ readonly parentRef?: ParentRefDef;
946
966
  /**
947
967
  * Default-Retention-Policy fuer diese Entity. Tenant-Admin kann via
948
968
  * Compliance-Profile + Tenant-Override (Sprint 2) uebersteuern.
package/src/handlers.ts CHANGED
@@ -1011,10 +1011,20 @@ export type WriteHandlerDef = {
1011
1011
  readonly perform?: import("./step").PipelineDef;
1012
1012
  };
1013
1013
 
1014
+ // Marker set by defineEntityQueryHandler on both of its verbs. A string, not
1015
+ // a Symbol(), for the same reason as PAGED_QUERY_HANDLER_BRAND: a symlinked
1016
+ // workspace can evaluate a module twice, and two Symbol() calls would not
1017
+ // compare equal across those instances.
1018
+ export const ENTITY_CONVENTION_QUERY_BRAND = "__kumikoEntityConventionQuery";
1019
+
1014
1020
  export type QueryHandlerDef = {
1015
1021
  readonly name: string;
1016
1022
  readonly schema: ZodType;
1017
1023
  readonly handler: QueryHandlerFn;
1024
+ /** Set only by defineEntityQueryHandler, which wires the parent-ref
1025
+ * read-gate. The boot-validator requires it on a `parentRef` entity's
1026
+ * list/detail handler — see boot-validator/parent-ref.ts. */
1027
+ readonly [ENTITY_CONVENTION_QUERY_BRAND]?: true;
1018
1028
  readonly access?: AccessRule;
1019
1029
  readonly description?: string;
1020
1030
  readonly agent?: AgentHandlerHints;
@@ -13,7 +13,11 @@ export type TenantId = string;
13
13
  // already holds a TenantId from a trusted source (JWT payload, server
14
14
  // config) skips this — the helper is for **untrusted input** crossing
15
15
  // the system boundary.
16
- const TENANT_ID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
16
+ //
17
+ // Exported so SQL-side checks (a Postgres `~` regex match) can share the
18
+ // exact same shape instead of growing a second copy that drifts.
19
+ export const UUID_SHAPE_PATTERN = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$";
20
+ const TENANT_ID_REGEX = new RegExp(UUID_SHAPE_PATTERN);
17
21
 
18
22
  // Shared shape check for any UUID-formatted identifier crossing an
19
23
  // untrusted boundary (fileRefId, entity ids, ...) — same loose,
package/src/nav-icon.ts CHANGED
@@ -86,7 +86,9 @@ export type NavIconKey =
86
86
  | "info"
87
87
  | "check-circle"
88
88
  | "x-circle"
89
- | "loader";
89
+ | "loader"
90
+ | "mic"
91
+ | "circle-stop";
90
92
 
91
93
  // Alias kept because the union stopped being nav-only — actions and fields
92
94
  // (added on this branch) also key their icons against it.
package/src/screen.ts CHANGED
@@ -1129,6 +1129,85 @@ export type ActionFormRedirect = {
1129
1129
  readonly idFrom: string;
1130
1130
  };
1131
1131
 
1132
+ /** Author-declared field shown once after a mint-form submit — API token,
1133
+ * recovery codes, similar one-time secrets. */
1134
+ export type SecretRevealField = {
1135
+ /** Flat field name in the handler's success payload. */
1136
+ readonly field: string;
1137
+ /** i18n key for this value's label. */
1138
+ readonly label: string;
1139
+ /** "code" (default) renders one monospaced value; "list" renders a
1140
+ * string-array payload one value per line (recovery codes); "qr" renders
1141
+ * the value as a scannable QR code (otpauth://-style enrollment URI) —
1142
+ * platforms without a QR-capable SecretReveal primitive fall back to the
1143
+ * monospaced text display. */
1144
+ readonly display?: "code" | "list" | "qr";
1145
+ /** Default true — offer a copy-to-clipboard control next to the value. */
1146
+ readonly copyable?: boolean;
1147
+ };
1148
+
1149
+ /** Post-reveal proof-of-receipt step. A mint whose effect is only armed once
1150
+ * the user proves they received the secret (TOTP enrollment: scan the code,
1151
+ * then enter one) declares it here — the reveal card renders this form in
1152
+ * place of the bare acknowledge button. */
1153
+ export type SecretMintConfirmStep = {
1154
+ /** Write-handler QN dispatched when the confirm form is submitted. */
1155
+ readonly handler: string;
1156
+ readonly fields: Readonly<Record<string, FieldDefinition>>;
1157
+ readonly layout: EditLayout;
1158
+ /** Mint success-payload fields merged into the confirm payload (e.g. a
1159
+ * short-lived setup token). Held in component state only — never rendered,
1160
+ * never in the URL, a query cache, nav or a persisted draft. */
1161
+ readonly carry?: readonly string[];
1162
+ readonly submitLabel?: string;
1163
+ /** i18n key for the banner shown after a successful confirm when the screen
1164
+ * declares no `redirect`. Default "kumiko.secretMint.done". */
1165
+ readonly doneMessage?: string;
1166
+ };
1167
+
1168
+ export type SecretReveal = {
1169
+ readonly fields: readonly SecretRevealField[];
1170
+ /** i18n key. Default "kumiko.secretMint.title". */
1171
+ readonly title?: string;
1172
+ /** i18n key for the "this is the only time you see it" warning.
1173
+ * Default "kumiko.secretMint.warning". */
1174
+ readonly warning?: string;
1175
+ /** i18n key for the acknowledge button. Default "kumiko.secretMint.confirm". */
1176
+ readonly confirmLabel?: string;
1177
+ };
1178
+
1179
+ /** Mint form → one-time reveal → explicit confirm. The secret (API token,
1180
+ * recovery codes) lives only in the write-handler's own success payload —
1181
+ * no stored value backs it, so no query can redisplay it later. That gap
1182
+ * is why this needs its own screen type instead of a flag on `actionForm`,
1183
+ * which discards the success payload after extracting the navigation id. */
1184
+ export type SecretMintScreenDefinition = {
1185
+ readonly id: string;
1186
+ readonly type: "secretMint";
1187
+ readonly nav?: ScreenNavSugar;
1188
+ readonly detailFor?: string;
1189
+ readonly description?: string;
1190
+ readonly agent?: AgentHandlerHints;
1191
+ /** Write-handler QN dispatched on submit. */
1192
+ readonly handler: string;
1193
+ readonly fields: Readonly<Record<string, FieldDefinition>>;
1194
+ readonly layout: EditLayout;
1195
+ /** Navigate here after the reveal is confirmed. Short id (same-feature) or
1196
+ * a fully-qualified cross-feature screen QN — no result-id navigation
1197
+ * form exists here (unlike `ActionFormScreenDefinition.redirect`), since
1198
+ * the confirmed screen carries no entity id to thread through. */
1199
+ readonly redirect?: string;
1200
+ readonly cancelTarget?: string | false;
1201
+ readonly listScreenId?: string;
1202
+ readonly access?: AccessRule;
1203
+ readonly reveal: SecretReveal;
1204
+ /** i18n-key for the mint button. Default: renderer's own default. */
1205
+ readonly submitLabel?: string;
1206
+ /** Proof-of-receipt step rendered on the reveal card in place of the bare
1207
+ * acknowledge button — see `SecretMintConfirmStep` doc. */
1208
+ readonly confirm?: SecretMintConfirmStep;
1209
+ };
1210
+
1132
1211
  // --- custom ---
1133
1212
 
1134
1213
  // Sub-route declared by a custom screen (Expo Router / URL-routing use).
@@ -1307,4 +1386,5 @@ export type ScreenDefinition =
1307
1386
  | ActionFormScreenDefinition
1308
1387
  | ConfigEditScreenDefinition
1309
1388
  | SecretsEditScreenDefinition
1389
+ | SecretMintScreenDefinition
1310
1390
  | CustomScreenDefinition;