@cosmicdrift/kumiko-types 0.336.1 → 0.338.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 +9 -0
- package/dist/feature.d.ts +25 -0
- package/dist/handlers.d.ts +18 -1
- package/dist/handlers.js +28 -0
- package/dist/screen.d.ts +10 -2
- package/dist/screen.js +8 -0
- package/package.json +1 -1
- package/src/changes.json +14 -0
package/dist/config.d.ts
CHANGED
|
@@ -25,6 +25,14 @@ export type ConfigComputedContext = {
|
|
|
25
25
|
readonly db: DbConnection | TenantDb;
|
|
26
26
|
};
|
|
27
27
|
export type ConfigComputedFn<T extends ConfigKeyType = ConfigKeyType> = (ctx: ConfigComputedContext) => Promise<ConfigValue<T>>;
|
|
28
|
+
export type ConfigValidateContext = {
|
|
29
|
+
readonly key: string;
|
|
30
|
+
readonly scope: ConfigScope;
|
|
31
|
+
readonly tenantId: TenantId;
|
|
32
|
+
readonly userId: string | null;
|
|
33
|
+
readonly db: DbConnection | TenantDb;
|
|
34
|
+
};
|
|
35
|
+
export type ConfigValidateFn<T extends ConfigKeyType = ConfigKeyType> = (value: ConfigValue<T>, ctx: ConfigValidateContext) => Promise<void>;
|
|
28
36
|
export type ConfigBacking = "config" | "secrets";
|
|
29
37
|
export type ConfigSecretsReader = {
|
|
30
38
|
get(tenantId: TenantId, key: string): Promise<{
|
|
@@ -60,6 +68,7 @@ export type ConfigKeyDefinition<T extends ConfigKeyType = ConfigKeyType> = {
|
|
|
60
68
|
readonly flags?: string;
|
|
61
69
|
};
|
|
62
70
|
readonly computed?: ConfigComputedFn<T>;
|
|
71
|
+
readonly validate?: ConfigValidateFn<T>;
|
|
63
72
|
readonly allowPerRequest?: boolean;
|
|
64
73
|
readonly required?: boolean;
|
|
65
74
|
readonly env?: string;
|
package/dist/feature.d.ts
CHANGED
|
@@ -39,8 +39,27 @@ export type SecretKeyDefinition = {
|
|
|
39
39
|
};
|
|
40
40
|
readonly scope: "tenant";
|
|
41
41
|
readonly required?: boolean;
|
|
42
|
+
readonly writeRoles?: readonly string[];
|
|
42
43
|
};
|
|
43
44
|
export type SecretOptions = Omit<SecretKeyDefinition, "shortName" | "qualifiedName">;
|
|
45
|
+
export type SecretNamespaceDefinition = {
|
|
46
|
+
readonly shortName: string;
|
|
47
|
+
readonly qualifiedPrefix: string;
|
|
48
|
+
readonly label: {
|
|
49
|
+
readonly [locale: string]: string;
|
|
50
|
+
};
|
|
51
|
+
readonly hint?: {
|
|
52
|
+
readonly [locale: string]: string;
|
|
53
|
+
};
|
|
54
|
+
readonly scope: "tenant";
|
|
55
|
+
readonly writeRoles?: readonly string[];
|
|
56
|
+
readonly nameSchema?: ZodType<string>;
|
|
57
|
+
};
|
|
58
|
+
export type SecretNamespaceOptions = Omit<SecretNamespaceDefinition, "shortName" | "qualifiedPrefix">;
|
|
59
|
+
export type SecretNamespaceHandle = {
|
|
60
|
+
readonly prefix: string;
|
|
61
|
+
keyFor(name: string): string;
|
|
62
|
+
};
|
|
44
63
|
export type SecretKeyHandle = {
|
|
45
64
|
readonly name: string;
|
|
46
65
|
};
|
|
@@ -150,6 +169,7 @@ export type FeatureDefinition = {
|
|
|
150
169
|
readonly handlerEntityMappings: Readonly<Record<string, string>>;
|
|
151
170
|
readonly metrics: Readonly<Record<string, FeatureMetricDef>>;
|
|
152
171
|
readonly secretKeys: Readonly<Record<string, SecretKeyDefinition>>;
|
|
172
|
+
readonly secretNamespaces: Readonly<Record<string, SecretNamespaceDefinition>>;
|
|
153
173
|
readonly projections: Readonly<Record<string, ProjectionDefinition>>;
|
|
154
174
|
readonly entityProjectionExtensions?: Readonly<Record<string, readonly EntityProjectionExtension[]>>;
|
|
155
175
|
readonly multiStreamProjections: Readonly<Record<string, MultiStreamProjectionDefinition>>;
|
|
@@ -409,6 +429,10 @@ export type FeatureRegistrar<TFeature extends string = string> = {
|
|
|
409
429
|
secret(definition: {
|
|
410
430
|
readonly name: string;
|
|
411
431
|
} & SecretOptions): SecretKeyHandle;
|
|
432
|
+
secretNamespace(shortName: string, options: SecretNamespaceOptions): SecretNamespaceHandle;
|
|
433
|
+
secretNamespace(definition: {
|
|
434
|
+
readonly name: string;
|
|
435
|
+
} & SecretNamespaceOptions): SecretNamespaceHandle;
|
|
412
436
|
projection(definition: ProjectionDefinition): void;
|
|
413
437
|
multiStreamProjection(definition: MultiStreamProjectionDefinition): void;
|
|
414
438
|
extendEntityProjection(entityName: string, extension: EntityProjectionExtension): void;
|
|
@@ -485,6 +509,7 @@ export type Registry = {
|
|
|
485
509
|
getAllConfigSeeds(): readonly ConfigSeedDef[];
|
|
486
510
|
getAllSecretKeys(): ReadonlyMap<string, SecretKeyDefinition>;
|
|
487
511
|
getSecretKey(qualifiedName: string): SecretKeyDefinition | undefined;
|
|
512
|
+
findSecretNamespace(key: string): SecretNamespaceDefinition | undefined;
|
|
488
513
|
getJob(qualifiedName: string): JobDefinition | undefined;
|
|
489
514
|
getAllJobs(): ReadonlyMap<string, JobDefinition>;
|
|
490
515
|
getEvent(qualifiedName: string): EventDef | undefined;
|
package/dist/handlers.d.ts
CHANGED
|
@@ -28,9 +28,18 @@ export type RoleAccessRule = {
|
|
|
28
28
|
readonly personalData?: RoleAccessPersonalData;
|
|
29
29
|
};
|
|
30
30
|
export type AccessRule = RoleAccessRule | OpenToAllAccessRule;
|
|
31
|
+
export declare const ESCAPE_HATCH_GRANTS: readonly ["systemIdentity", "globalWrites", "unsafeRaw"];
|
|
32
|
+
export type EscapeHatchGrant = (typeof ESCAPE_HATCH_GRANTS)[number];
|
|
31
33
|
export type EscapeHatchDeclaration = {
|
|
32
34
|
readonly reason: string;
|
|
33
|
-
|
|
35
|
+
readonly grants?: readonly EscapeHatchGrant[];
|
|
36
|
+
};
|
|
37
|
+
export declare function isEscapeHatchGrant(value: unknown): value is EscapeHatchGrant;
|
|
38
|
+
export declare function escapeHatchAllows(declaration: EscapeHatchDeclaration | undefined, grant: EscapeHatchGrant): boolean;
|
|
39
|
+
export declare function escapeHatchFor(declaration: EscapeHatchDeclaration | undefined, grant: EscapeHatchGrant): EscapeHatchDeclaration | undefined;
|
|
40
|
+
export declare function escapeHatchGrantsProblem(declaration: {
|
|
41
|
+
readonly grants?: unknown;
|
|
42
|
+
}): string | undefined;
|
|
34
43
|
export declare function isOpenToAllGranted(rule: AccessRule): boolean;
|
|
35
44
|
export declare function isUiAccessGranted(access: AccessRule | undefined, userRoles: readonly string[] | undefined): boolean;
|
|
36
45
|
export type SessionUserOrigin = "member-resolution";
|
|
@@ -149,6 +158,14 @@ type SharedContextFields = {
|
|
|
149
158
|
readonly jobRunner?: JobRunnerRef;
|
|
150
159
|
readonly configResolver?: ConfigResolver;
|
|
151
160
|
readonly config?: ConfigAccessor;
|
|
161
|
+
/**
|
|
162
|
+
* Config accessor resolved for ANOTHER tenant, identity = that tenant's system
|
|
163
|
+
* user (the caller's user-scope config never leaks in). Cross-tenant calls
|
|
164
|
+
* throw AccessDeniedError unless the caller is a system identity or has the
|
|
165
|
+
* SystemAdmin role; the caller's own tenant needs no privilege. Present when
|
|
166
|
+
* the config feature wired its accessor factory.
|
|
167
|
+
*/
|
|
168
|
+
readonly configFor?: (tenantId: TenantId) => ConfigAccessor;
|
|
152
169
|
readonly _configAccessorFactory?: ConfigAccessorFactory;
|
|
153
170
|
readonly configEncryption?: import("./envelope-cipher-types.js").EnvelopeCipher;
|
|
154
171
|
readonly rateLimit?: import("./rate-limit-types.js").RateLimitResolver;
|
package/dist/handlers.js
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
export const ESCAPE_HATCH_GRANTS = ["systemIdentity", "globalWrites", "unsafeRaw"];
|
|
2
|
+
export function isEscapeHatchGrant(value) {
|
|
3
|
+
return ESCAPE_HATCH_GRANTS.some((grant) => grant === value);
|
|
4
|
+
}
|
|
5
|
+
export function escapeHatchAllows(declaration, grant) {
|
|
6
|
+
if (declaration === undefined)
|
|
7
|
+
return false;
|
|
8
|
+
return declaration.grants === undefined || declaration.grants.includes(grant);
|
|
9
|
+
}
|
|
10
|
+
// Returns the declaration only when it unlocks `grant`, so call sites keep passing
|
|
11
|
+
// "a declaration or nothing" into grant-gated code.
|
|
12
|
+
export function escapeHatchFor(declaration, grant) {
|
|
13
|
+
return escapeHatchAllows(declaration, grant) ? declaration : undefined;
|
|
14
|
+
}
|
|
15
|
+
// Boot-time check of `grants` (untyped sources can carry anything): must be a non-empty list of known grants.
|
|
16
|
+
export function escapeHatchGrantsProblem(declaration) {
|
|
17
|
+
const { grants } = declaration;
|
|
18
|
+
if (grants === undefined)
|
|
19
|
+
return undefined;
|
|
20
|
+
if (!Array.isArray(grants) || grants.length === 0) {
|
|
21
|
+
return `grants must be a non-empty list of: ${ESCAPE_HATCH_GRANTS.join(", ")}`;
|
|
22
|
+
}
|
|
23
|
+
const unknownGrant = grants.find((grant) => !isEscapeHatchGrant(grant));
|
|
24
|
+
if (unknownGrant !== undefined) {
|
|
25
|
+
return `unknown grant ${JSON.stringify(unknownGrant)} — allowed: ${ESCAPE_HATCH_GRANTS.join(", ")}`;
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
1
29
|
// AccessRule can arrive from untyped sources (pattern-library JSON, Designer)
|
|
2
30
|
// where `openToAll` doesn't actually match the declared union — narrow via
|
|
3
31
|
// `unknown` instead of trusting the static type, deny on anything malformed.
|
package/dist/screen.d.ts
CHANGED
|
@@ -920,13 +920,17 @@ export type RelatedListGroupBy = {
|
|
|
920
920
|
readonly collapsedWhen?: string | number | boolean | null;
|
|
921
921
|
/** i18n key of the group header. Placeholders: `{count}` rows in the group,
|
|
922
922
|
* `{value}` the group's field value, `{lastDate}` the group's latest
|
|
923
|
-
* `dateField` value as a locale date.
|
|
924
|
-
|
|
923
|
+
* `dateField` value as a locale date. A group with neither `label` nor an
|
|
924
|
+
* entry in `labels` renders its rows without a header and never collapses. */
|
|
925
|
+
readonly label?: string;
|
|
925
926
|
/** Header i18n key per group value (as string), overriding `label`. */
|
|
926
927
|
readonly labels?: Readonly<Record<string, string>>;
|
|
927
928
|
/** Row field (ISO date or timestamp) behind `{lastDate}`. */
|
|
928
929
|
readonly dateField?: string;
|
|
929
930
|
};
|
|
931
|
+
/** Group key of a field value; `collapsedWhen` and row values both go through it. */
|
|
932
|
+
export declare function relatedListGroupKey(value: unknown): string;
|
|
933
|
+
export declare function relatedListGroupHeaderLabel(groupBy: RelatedListGroupBy, groupKey: string): string | undefined;
|
|
930
934
|
/** Row tint rule: rows where the `FieldCondition` (`{ field, eq }`, `{ field,
|
|
931
935
|
* in }` …) holds get the tone. `bad` renders red. */
|
|
932
936
|
export type RelatedListRowTone = Exclude<FieldCondition, boolean> & {
|
|
@@ -1482,6 +1486,10 @@ export type SecretsEditScreenDefinition = {
|
|
|
1482
1486
|
readonly fieldHints?: Readonly<Record<string, string>>;
|
|
1483
1487
|
/** field ids whose declaration set `required: true`. */
|
|
1484
1488
|
readonly requiredFields?: readonly string[];
|
|
1489
|
+
/** field id -> write access derived from the key's `writeRoles`. The screen
|
|
1490
|
+
* hides fields the viewer fails; UI only, the secrets write gate stays
|
|
1491
|
+
* authoritative. */
|
|
1492
|
+
readonly fieldAccess?: Readonly<Record<string, AccessRule>>;
|
|
1485
1493
|
readonly sections: readonly SecretsEditSection[];
|
|
1486
1494
|
readonly access?: AccessRule;
|
|
1487
1495
|
};
|
package/dist/screen.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
export function metricField(metric) {
|
|
2
2
|
return typeof metric === "string" ? metric : metric.field;
|
|
3
3
|
}
|
|
4
|
+
/** Group key of a field value; `collapsedWhen` and row values both go through it. */
|
|
5
|
+
export function relatedListGroupKey(value) {
|
|
6
|
+
return value === undefined || value === null ? "" : String(value);
|
|
7
|
+
}
|
|
8
|
+
export function relatedListGroupHeaderLabel(groupBy, groupKey) {
|
|
9
|
+
const labels = groupBy.labels;
|
|
10
|
+
return labels !== undefined && Object.hasOwn(labels, groupKey) ? labels[groupKey] : groupBy.label;
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-types",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.338.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/changes.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.338.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "ctx.configFor on the handler context types",
|
|
6
|
+
"detail": "`configFor?: (tenantId: TenantId) => ConfigAccessor` is part of the shared handler context fields, optional like `config`.",
|
|
7
|
+
"migration": "No code change needed."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"version": "0.337.0",
|
|
11
|
+
"type": "improvement",
|
|
12
|
+
"title": "relatedList groupBy.label is optional; groups without a header show their rows directly",
|
|
13
|
+
"detail": "`RelatedListGroupBy.label` is optional. The header key of a group is `labels[value] ?? label`; a group without one renders its rows without a header row and never collapses. `relatedListGroupKey` and `relatedListGroupHeaderLabel` resolve the group key and its header key; `collapsedWhen: null` now matches rows whose field is empty.",
|
|
14
|
+
"migration": "No code change needed. To hide a header, drop `label` and name only the groups that keep one in `labels`, for example `{ field: \"status\", collapsedWhen: \"done\", labels: { done: \"<key>\" } }`."
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"version": "0.336.0",
|
|
4
18
|
"type": "improvement",
|