@cosmicdrift/kumiko-types 0.289.0 → 0.291.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/changes.json +6 -0
- package/src/config.ts +16 -0
- package/src/event-store-types.ts +13 -0
- package/src/feature.ts +6 -0
- package/src/fields.ts +41 -6
- package/src/handlers.ts +24 -7
- package/src/screen.ts +8 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-types",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.291.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,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.290.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "EventMetadata: feature + handler + UNATTRIBUTED_ORIGIN",
|
|
6
|
+
"detail": "EventMetadata traegt zwei neue optionale Felder (feature, handler) und exportiert die Sentinel-Konstante UNATTRIBUTED_ORIGIN fuer Appends ausserhalb eines attribuierten Ausfuehrungsscopes. Additiv, bestehende Zeilen bleiben ohne Backfill lesbar."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.289.0",
|
|
4
10
|
"type": "improvement",
|
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/event-store-types.ts
CHANGED
|
@@ -37,8 +37,21 @@ export type EventMetadata = {
|
|
|
37
37
|
// app reads them when filtering/auditing. Keep values JSON-primitive
|
|
38
38
|
// (string|number|boolean) so JSON serialization stays bulletproof.
|
|
39
39
|
readonly headers?: Readonly<Record<string, string | number | boolean>>;
|
|
40
|
+
// Feature that owned the execution scope which wrote this event, and the
|
|
41
|
+
// qualified name of the handler/consumer/job inside it. Derived in
|
|
42
|
+
// event-store.append() from the ambient request scope, never passed in by
|
|
43
|
+
// the writer — attribution handed over can lie, attribution derived cannot.
|
|
44
|
+
// Optional so rows written before #3043 stay readable without a backfill;
|
|
45
|
+
// every new row carries both, falling back to UNATTRIBUTED_ORIGIN.
|
|
46
|
+
readonly feature?: string;
|
|
47
|
+
readonly handler?: string;
|
|
40
48
|
};
|
|
41
49
|
|
|
50
|
+
// Stamped when an append runs outside any attributed scope (seed scripts,
|
|
51
|
+
// tests, direct event-store use). A distinct value, not an omission —
|
|
52
|
+
// omitted would be indistinguishable from a pre-#3043 row.
|
|
53
|
+
export const UNATTRIBUTED_ORIGIN = "unknown";
|
|
54
|
+
|
|
42
55
|
// Generic over the payload shape. Default = Record<string, unknown> keeps
|
|
43
56
|
// all existing consumers backwards-compatible; annotate `StoredEvent<MyEventPayload>`
|
|
44
57
|
// where a typed payload read is needed.
|
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/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
|
|
|
@@ -1180,8 +1180,11 @@ export type ActionFormScreenDefinition = {
|
|
|
1180
1180
|
readonly handler: string;
|
|
1181
1181
|
/** Form-Shape: Field-Map pro Name. Nutzt dieselben FieldDefinitions
|
|
1182
1182
|
* wie Entity-Felder. Mindestens ein Feld erforderlich (Boot-
|
|
1183
|
-
* Validator).
|
|
1184
|
-
|
|
1183
|
+
* Validator).
|
|
1184
|
+
*
|
|
1185
|
+
* A `money` field here must name its own currency source: this screen
|
|
1186
|
+
* has no entity whose `defaultCurrency` it could inherit (fw#2839). */
|
|
1187
|
+
readonly fields: Readonly<Record<string, FormFieldDefinition>>;
|
|
1185
1188
|
/** Layout analog zu EntityEditScreen: sections mit fields aus dem
|
|
1186
1189
|
* fields-Map oben. */
|
|
1187
1190
|
readonly layout: EditLayout;
|
|
@@ -1272,7 +1275,7 @@ export type SecretRevealField = {
|
|
|
1272
1275
|
export type SecretMintConfirmStep = {
|
|
1273
1276
|
/** Write-handler QN dispatched when the confirm form is submitted. */
|
|
1274
1277
|
readonly handler: string;
|
|
1275
|
-
readonly fields: Readonly<Record<string,
|
|
1278
|
+
readonly fields: Readonly<Record<string, FormFieldDefinition>>;
|
|
1276
1279
|
readonly layout: EditLayout;
|
|
1277
1280
|
/** Mint success-payload fields merged into the confirm payload (e.g. a
|
|
1278
1281
|
* short-lived setup token). Held in component state only — never rendered,
|
|
@@ -1314,7 +1317,7 @@ export type SecretMintScreenDefinition = {
|
|
|
1314
1317
|
readonly urlPrefillFields?: readonly string[];
|
|
1315
1318
|
/** Write-handler QN dispatched on submit. */
|
|
1316
1319
|
readonly handler: string;
|
|
1317
|
-
readonly fields: Readonly<Record<string,
|
|
1320
|
+
readonly fields: Readonly<Record<string, FormFieldDefinition>>;
|
|
1318
1321
|
readonly layout: EditLayout;
|
|
1319
1322
|
/** Navigate here after the reveal is confirmed. Short id (same-feature) or
|
|
1320
1323
|
* a fully-qualified cross-feature screen QN — no result-id navigation
|