@cosmicdrift/kumiko-types 0.290.0 → 0.292.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 +1 -1
- package/src/config.ts +16 -0
- package/src/feature.ts +6 -0
- package/src/fields.ts +41 -6
- package/src/handlers.ts +24 -7
- package/src/identifiers.ts +6 -0
- package/src/screen.ts +27 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-types",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.292.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/config.ts
CHANGED
|
@@ -394,6 +394,22 @@ export type JobDefinition = {
|
|
|
394
394
|
readonly runIn?: JobRunIn | undefined;
|
|
395
395
|
// Grants `ctx.db.unsafeRaw(reason)` for this job, audited as `unsafe-raw`.
|
|
396
396
|
readonly escapeHatch?: EscapeHatchDeclaration | undefined;
|
|
397
|
+
// Opt in to a tenant-visible failure record (`jobs:query:failures`). Only a
|
|
398
|
+
// translation key ever reaches the tenant: a thrown KumikoError's `i18nKey`
|
|
399
|
+
// when it carries one, otherwise `messageKey`. The provider's own message
|
|
400
|
+
// stays in the run log. Only the last attempt records, and a later
|
|
401
|
+
// successful run of the same job and subject clears the record.
|
|
402
|
+
// `subjectFields` names payload fields that scope the record (e.g.
|
|
403
|
+
// `["campaignId", "language"]`); each must hold a primitive or the run
|
|
404
|
+
// throws. Omit it for one record per job and tenant. Their values are
|
|
405
|
+
// stored in clear, unlike the encrypted run payload — never name a PII
|
|
406
|
+
// field here.
|
|
407
|
+
readonly tenantVisibleFailure?:
|
|
408
|
+
| {
|
|
409
|
+
readonly messageKey: string;
|
|
410
|
+
readonly subjectFields?: readonly string[] | undefined;
|
|
411
|
+
}
|
|
412
|
+
| undefined;
|
|
397
413
|
};
|
|
398
414
|
|
|
399
415
|
// --- Notifications ---
|
package/src/feature.ts
CHANGED
|
@@ -617,6 +617,12 @@ export type FeatureRegistrar<TFeature extends string = string> = {
|
|
|
617
617
|
// personal data (encrypted under that user's DEK, crypto-shredding #799),
|
|
618
618
|
// or `"none"` to declare the payload holds no personal data. append()
|
|
619
619
|
// enforces the resulting catalog on every write path.
|
|
620
|
+
//
|
|
621
|
+
// When the schema lets the owner field be null/undefined, the stance must
|
|
622
|
+
// also say what happens on those writes — `whenAbsent: "tenant"` encrypts
|
|
623
|
+
// under the envelope tenant key, `whenAbsent: "plaintext"` acknowledges an
|
|
624
|
+
// unshreddable value. Registration fails without one, and an owner that is
|
|
625
|
+
// empty at append time with no declared fallback aborts the write (fw#2776).
|
|
620
626
|
defineEvent<const TInner extends string, TPayload>(
|
|
621
627
|
name: TInner,
|
|
622
628
|
schema: ZodType<TPayload>,
|
package/src/fields.ts
CHANGED
|
@@ -445,12 +445,16 @@ export type DecimalFieldDef = {
|
|
|
445
445
|
readonly access?: FieldAccess;
|
|
446
446
|
} & ResolvedPiiFlags;
|
|
447
447
|
|
|
448
|
-
/** Where a money field's currency comes from
|
|
449
|
-
*
|
|
450
|
-
*
|
|
451
|
-
* the
|
|
452
|
-
*
|
|
453
|
-
|
|
448
|
+
/** Where a money field's currency comes from. `"tenant"` is the
|
|
449
|
+
* tenant-settings bundle's per-tenant currency (`tenant-settings:config:
|
|
450
|
+
* currency`, kumiko-framework#2933); `"literal"` names a fixed ISO code and
|
|
451
|
+
* is checked against the app's `currencies` list at boot
|
|
452
|
+
* (kumiko-framework#2839). Discriminated union so a `{ kind: "field", of }`
|
|
453
|
+
* variant (amount and currency-picker side by side) can still be added
|
|
454
|
+
* without a breaking change, once a multi-currency form actually exists. */
|
|
455
|
+
export type MoneyCurrencySource =
|
|
456
|
+
| { readonly kind: "tenant" }
|
|
457
|
+
| { readonly kind: "literal"; readonly code: string };
|
|
454
458
|
|
|
455
459
|
/** Qualified config key a `{ kind: "tenant" }` MoneyCurrencySource resolves
|
|
456
460
|
* against — the tenant-settings bundle's per-tenant currency. Lives here
|
|
@@ -513,6 +517,25 @@ export type ReferenceFieldDef = {
|
|
|
513
517
|
* boot validator rejects it otherwise, since ordering by a UUID produces a
|
|
514
518
|
* sequence that looks meaningful to the user but is arbitrary. */
|
|
515
519
|
readonly sortable?: true;
|
|
520
|
+
/** Fill the picker from this query handler instead of the referenced
|
|
521
|
+
* entity's own list handler (fw#2780) — for targets whose identity is
|
|
522
|
+
* composed from joined rows, where no single column of the referenced
|
|
523
|
+
* entity is a readable label. Fully-qualified QN
|
|
524
|
+
* ("<feature>:query:<short>"), boot-validated against the registered
|
|
525
|
+
* handlers; result contract `{ rows: { id, label }[] }`, called with
|
|
526
|
+
* `{ limit, search? }` like the default list handler. `id` rather than the
|
|
527
|
+
* `{ value, label }` of `DashboardFilterDefinition.optionsQuery`, because
|
|
528
|
+
* the stored value here IS the referenced row's id.
|
|
529
|
+
*
|
|
530
|
+
* Additive to `labelField`, which keeps serving the paths a query handler
|
|
531
|
+
* cannot back: list cells, `searchable` and `sortable` all resolve to an
|
|
532
|
+
* SQL column on the referenced table.
|
|
533
|
+
*
|
|
534
|
+
* The handler must return the row for an id that is already stored, even
|
|
535
|
+
* when it otherwise curates its result (say, to active records only) —
|
|
536
|
+
* the read-only display resolves a stored value against these rows and
|
|
537
|
+
* falls back to the raw id for one it does not find. */
|
|
538
|
+
readonly optionsQuery?: string;
|
|
516
539
|
} & ResolvedPiiFlags;
|
|
517
540
|
|
|
518
541
|
// --- Currency ---
|
|
@@ -568,6 +591,8 @@ export type EmbeddedSubFieldDef =
|
|
|
568
591
|
readonly type: "reference";
|
|
569
592
|
readonly entity: string;
|
|
570
593
|
readonly labelField?: string;
|
|
594
|
+
/** Same contract as `ReferenceFieldDef.optionsQuery` (fw#2780). */
|
|
595
|
+
readonly optionsQuery?: string;
|
|
571
596
|
});
|
|
572
597
|
|
|
573
598
|
/** A cell computed from other cells of the same row. `from` names the
|
|
@@ -811,6 +836,16 @@ export type FieldDefinition =
|
|
|
811
836
|
| FilesFieldDef
|
|
812
837
|
| ImagesFieldDef;
|
|
813
838
|
|
|
839
|
+
/** FieldDefinition as an entity-less inline form screen (actionForm,
|
|
840
|
+
* secretMint) may declare it: a `money` field there has no entity to borrow
|
|
841
|
+
* `defaultCurrency` from, so it must name its own currency source, or the
|
|
842
|
+
* form seeds a bare `0` that the handler's zod schema then rejects
|
|
843
|
+
* (kumiko-framework#2839). Entity fields keep the optional `currency` —
|
|
844
|
+
* `entity.defaultCurrency` is already boot-enforced for them. */
|
|
845
|
+
export type FormFieldDefinition =
|
|
846
|
+
| Exclude<FieldDefinition, MoneyFieldDef>
|
|
847
|
+
| (MoneyFieldDef & { readonly currency: MoneyCurrencySource });
|
|
848
|
+
|
|
814
849
|
// Union of all field variants that represent uploaded files. They share
|
|
815
850
|
// `maxSize` and `accept`, which is what upload validation cares about.
|
|
816
851
|
export type AnyFileFieldDef = FileFieldDef | ImageFieldDef | FilesFieldDef | ImagesFieldDef;
|
package/src/handlers.ts
CHANGED
|
@@ -832,9 +832,7 @@ export type QualifiedEventName<
|
|
|
832
832
|
> = `${CamelToKebab<TFeature>}:event:${CamelToKebab<TInner>}`;
|
|
833
833
|
|
|
834
834
|
// PII payload fields on a custom event (#799): `field` is encrypted under
|
|
835
|
-
// the DEK the subject spec names (crypto-shredding).
|
|
836
|
-
// value leaves a `personal: { of }` field plaintext — there is no user key
|
|
837
|
-
// to shred for system-triggered events.
|
|
835
|
+
// the DEK the subject spec names (crypto-shredding).
|
|
838
836
|
//
|
|
839
837
|
// `{ personal: { of: "<ownerField>" } }` is the canonical user-subject form,
|
|
840
838
|
// matching the entity-field `personal` vocabulary (see fields.ts).
|
|
@@ -842,8 +840,21 @@ export type QualifiedEventName<
|
|
|
842
840
|
// (envelope.tenantId) or its own aggregate stream (envelope.aggregateType +
|
|
843
841
|
// aggregateId) as the subject instead — resolved via `resolveEventSubject`
|
|
844
842
|
// in `packages/framework/src/crypto/subject-resolver.ts` (fw#2801).
|
|
843
|
+
//
|
|
844
|
+
// `whenAbsent` decides what happens when the named owner field carries no
|
|
845
|
+
// id (system- or cron-triggered writes): "tenant" re-targets the envelope
|
|
846
|
+
// tenant key, "plaintext" is an explicit acknowledgement that the value
|
|
847
|
+
// ships unencrypted and cannot be crypto-shredded. Undeclared is not a
|
|
848
|
+
// third option — the append fails closed instead of leaking (fw#2776).
|
|
849
|
+
export type EventPiiAbsentSubject = "plaintext" | "tenant";
|
|
850
|
+
|
|
845
851
|
export type EventPiiSubject =
|
|
846
|
-
| {
|
|
852
|
+
| {
|
|
853
|
+
readonly personal: {
|
|
854
|
+
readonly of: string;
|
|
855
|
+
readonly whenAbsent?: EventPiiAbsentSubject;
|
|
856
|
+
};
|
|
857
|
+
}
|
|
847
858
|
| { readonly personal: "tenant" }
|
|
848
859
|
| { readonly personal: "self" }
|
|
849
860
|
| {
|
|
@@ -854,15 +865,21 @@ export type EventPiiSubject =
|
|
|
854
865
|
export type EventPiiFields = Readonly<Record<string, EventPiiSubject>>;
|
|
855
866
|
|
|
856
867
|
export type NormalizedEventPiiSubject =
|
|
857
|
-
| {
|
|
868
|
+
| {
|
|
869
|
+
readonly kind: "user";
|
|
870
|
+
readonly ownerField: string;
|
|
871
|
+
readonly whenAbsent: EventPiiAbsentSubject | undefined;
|
|
872
|
+
}
|
|
858
873
|
| { readonly kind: "tenant" }
|
|
859
874
|
| { readonly kind: "self" };
|
|
860
875
|
|
|
861
876
|
export function normalizeEventPiiSubject(spec: EventPiiSubject): NormalizedEventPiiSubject {
|
|
862
|
-
if ("subjectField" in spec)
|
|
877
|
+
if ("subjectField" in spec) {
|
|
878
|
+
return { kind: "user", ownerField: spec.subjectField, whenAbsent: undefined };
|
|
879
|
+
}
|
|
863
880
|
if (spec.personal === "tenant") return { kind: "tenant" };
|
|
864
881
|
if (spec.personal === "self") return { kind: "self" };
|
|
865
|
-
return { kind: "user", ownerField: spec.personal.of };
|
|
882
|
+
return { kind: "user", ownerField: spec.personal.of, whenAbsent: spec.personal.whenAbsent };
|
|
866
883
|
}
|
|
867
884
|
|
|
868
885
|
// The full set of PII stances a defineEvent() call may declare. "none" is a
|
package/src/identifiers.ts
CHANGED
|
@@ -49,6 +49,12 @@ export function isSystemTenant(tenantId: TenantId | null | undefined): boolean {
|
|
|
49
49
|
return !tenantId || tenantId === SYSTEM_TENANT_ID;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// "No human caller" marker on event-store `createdBy`. Stringified so it
|
|
53
|
+
// round-trips through SessionUser.id; not a real UUID, and createdBy is text.
|
|
54
|
+
// Lives here rather than next to createSystemUser() so client-side code (the
|
|
55
|
+
// reference-label map) can read it without importing a runtime module.
|
|
56
|
+
export const SYSTEM_USER_ID = "00000000-0000-0000-0000-000000000000";
|
|
57
|
+
|
|
52
58
|
// Primary-key identifier for any entity row. Two shapes coexist because of
|
|
53
59
|
// the entity-def `idType` switch: classic CRUD entities keep `serial` (number),
|
|
54
60
|
// while tenant + ES aggregates run on `uuid` (string). Call sites that pass
|
package/src/screen.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FieldIconKey } from "./field-icon";
|
|
2
|
-
import type { FieldDefinition } from "./fields";
|
|
2
|
+
import type { FieldDefinition, FormFieldDefinition } from "./fields";
|
|
3
3
|
import type { AccessRule, AgentHandlerHints } from "./handlers";
|
|
4
4
|
import type { IconKey, NavIconKey } from "./nav-icon";
|
|
5
5
|
|
|
@@ -473,6 +473,8 @@ export type EntityListScreenDefinition = {
|
|
|
473
473
|
// `falseLabel`) are i18n keys resolved via `translate()` with passthrough
|
|
474
474
|
// for unknown keys (fw#2373) — not raw display strings. Sent to the server
|
|
475
475
|
// as `{field, op:"in", value}` in `payload.filters`, ANDed with `filter`.
|
|
476
|
+
// Exception: "dateRange" sends its bounds as the two top-level payload keys
|
|
477
|
+
// named in `params`, not as a `filters` entry.
|
|
476
478
|
export type ListFacetSpec =
|
|
477
479
|
| {
|
|
478
480
|
readonly field: string;
|
|
@@ -497,6 +499,20 @@ export type ListFacetSpec =
|
|
|
497
499
|
/** Row field on the referenced entity shown as the option label
|
|
498
500
|
* (default "id"). */
|
|
499
501
|
readonly labelField?: string;
|
|
502
|
+
}
|
|
503
|
+
| {
|
|
504
|
+
/** Declarative only — the date field the range narrows. It is NOT sent
|
|
505
|
+
* to the server (the bounds go under `params`); it namespaces the
|
|
506
|
+
* facet's URL keys and tells a reading agent which column this list
|
|
507
|
+
* is time-rangeable on. */
|
|
508
|
+
readonly field: string;
|
|
509
|
+
readonly type: "dateRange";
|
|
510
|
+
readonly label: string;
|
|
511
|
+
/** Query-payload keys the chosen bounds are sent under as ISO instants.
|
|
512
|
+
* Explicit rather than a `from`/`to` convention — a query is free to
|
|
513
|
+
* name its time bounds anything, and the boot-validator checks these
|
|
514
|
+
* keys against the handler's Zod schema. */
|
|
515
|
+
readonly params: { readonly from: string; readonly to: string };
|
|
500
516
|
};
|
|
501
517
|
|
|
502
518
|
export type ProjectionListScreenDefinition = {
|
|
@@ -535,7 +551,9 @@ export type ProjectionListScreenDefinition = {
|
|
|
535
551
|
readonly filter?: ScreenFilter;
|
|
536
552
|
/** User-toggleable facet dropdowns — see `ListFacetSpec` doc. `field`
|
|
537
553
|
* must be a declared column; the bound query handler must accept
|
|
538
|
-
* `filters` in its Zod schema — the boot-validator checks both.
|
|
554
|
+
* `filters` in its Zod schema — the boot-validator checks both. A
|
|
555
|
+
* "dateRange" facet instead needs its two `params` keys in that schema;
|
|
556
|
+
* it sends no `filters` entry. */
|
|
539
557
|
readonly facets?: readonly ListFacetSpec[];
|
|
540
558
|
readonly slots?: ScreenSlots;
|
|
541
559
|
readonly access?: AccessRule;
|
|
@@ -1180,8 +1198,11 @@ export type ActionFormScreenDefinition = {
|
|
|
1180
1198
|
readonly handler: string;
|
|
1181
1199
|
/** Form-Shape: Field-Map pro Name. Nutzt dieselben FieldDefinitions
|
|
1182
1200
|
* wie Entity-Felder. Mindestens ein Feld erforderlich (Boot-
|
|
1183
|
-
* Validator).
|
|
1184
|
-
|
|
1201
|
+
* Validator).
|
|
1202
|
+
*
|
|
1203
|
+
* A `money` field here must name its own currency source: this screen
|
|
1204
|
+
* has no entity whose `defaultCurrency` it could inherit (fw#2839). */
|
|
1205
|
+
readonly fields: Readonly<Record<string, FormFieldDefinition>>;
|
|
1185
1206
|
/** Layout analog zu EntityEditScreen: sections mit fields aus dem
|
|
1186
1207
|
* fields-Map oben. */
|
|
1187
1208
|
readonly layout: EditLayout;
|
|
@@ -1272,7 +1293,7 @@ export type SecretRevealField = {
|
|
|
1272
1293
|
export type SecretMintConfirmStep = {
|
|
1273
1294
|
/** Write-handler QN dispatched when the confirm form is submitted. */
|
|
1274
1295
|
readonly handler: string;
|
|
1275
|
-
readonly fields: Readonly<Record<string,
|
|
1296
|
+
readonly fields: Readonly<Record<string, FormFieldDefinition>>;
|
|
1276
1297
|
readonly layout: EditLayout;
|
|
1277
1298
|
/** Mint success-payload fields merged into the confirm payload (e.g. a
|
|
1278
1299
|
* short-lived setup token). Held in component state only — never rendered,
|
|
@@ -1314,7 +1335,7 @@ export type SecretMintScreenDefinition = {
|
|
|
1314
1335
|
readonly urlPrefillFields?: readonly string[];
|
|
1315
1336
|
/** Write-handler QN dispatched on submit. */
|
|
1316
1337
|
readonly handler: string;
|
|
1317
|
-
readonly fields: Readonly<Record<string,
|
|
1338
|
+
readonly fields: Readonly<Record<string, FormFieldDefinition>>;
|
|
1318
1339
|
readonly layout: EditLayout;
|
|
1319
1340
|
/** Navigate here after the reveal is confirmed. Short id (same-feature) or
|
|
1320
1341
|
* a fully-qualified cross-feature screen QN — no result-id navigation
|