@cosmicdrift/kumiko-types 0.354.1 → 0.356.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.
@@ -9,6 +9,8 @@ export type WriteHandlerDefinition<TName extends string = string, TSchema extend
9
9
  readonly access: AccessRule;
10
10
  readonly description?: string;
11
11
  readonly agent?: AgentHandlerHints;
12
+ /** Set by the entity convention create/update handlers; see WriteHandlerDef.excludedFields. */
13
+ readonly excludedFields?: readonly string[];
12
14
  readonly unsafeSkipTransitionGuard?: boolean;
13
15
  readonly rateLimit?: RateLimitDeclaration;
14
16
  readonly additionalRateLimits?: readonly PayloadRateLimitOption[];
@@ -2,22 +2,25 @@ import type { CursorResult } from "./cursor-types.js";
2
2
  import type { EntityDefinition } from "./fields.js";
3
3
  import type { SessionUser, WriteResult } from "./handlers.js";
4
4
  import type { DeleteContext, SaveContext } from "./hooks.js";
5
- import type { EntityId } from "./identifiers.js";
5
+ import type { EntityId, TenantId } from "./identifiers.js";
6
6
  import type { SearchAdapter } from "./search-adapter.js";
7
7
  import type { TenantDb } from "./tenant-db-types.js";
8
8
  export type ParentVisibilityOption = {
9
9
  readonly entities: ReadonlyMap<string, EntityDefinition>;
10
10
  };
11
11
  export type PreSaveRunner = (changes: Record<string, unknown>, previous: Readonly<Record<string, unknown>>, isNew: boolean) => Promise<Record<string, unknown>>;
12
+ export type StreamTenantOption = {
13
+ readonly streamTenantId?: TenantId | undefined;
14
+ };
12
15
  export type EventStoreExecutor = {
13
- create: (payload: Record<string, unknown>, user: SessionUser, db: TenantDb, options?: {
16
+ create: (payload: Record<string, unknown>, user: SessionUser, db: TenantDb, options?: StreamTenantOption & {
14
17
  preSave?: PreSaveRunner;
15
18
  }) => Promise<WriteResult<SaveContext>>;
16
19
  update: (payload: {
17
20
  id: EntityId;
18
21
  version?: number | undefined;
19
22
  changes: Record<string, unknown>;
20
- }, user: SessionUser, db: TenantDb, options?: {
23
+ }, user: SessionUser, db: TenantDb, options?: StreamTenantOption & {
21
24
  skipOptimisticLock?: boolean;
22
25
  skipUnchanged?: boolean;
23
26
  preSave?: PreSaveRunner;
@@ -25,13 +28,13 @@ export type EventStoreExecutor = {
25
28
  }) => Promise<WriteResult<SaveContext>>;
26
29
  delete: (payload: {
27
30
  id: EntityId;
28
- }, user: SessionUser, db: TenantDb) => Promise<WriteResult<DeleteContext>>;
31
+ }, user: SessionUser, db: TenantDb, options?: StreamTenantOption) => Promise<WriteResult<DeleteContext>>;
29
32
  forget: (payload: {
30
33
  id: EntityId;
31
- }, user: SessionUser, db: TenantDb) => Promise<WriteResult<DeleteContext>>;
34
+ }, user: SessionUser, db: TenantDb, options?: StreamTenantOption) => Promise<WriteResult<DeleteContext>>;
32
35
  restore: (payload: {
33
36
  id: EntityId;
34
- }, user: SessionUser, db: TenantDb) => Promise<WriteResult<SaveContext>>;
37
+ }, user: SessionUser, db: TenantDb, options?: StreamTenantOption) => Promise<WriteResult<SaveContext>>;
35
38
  list: (payload: {
36
39
  cursor?: string | undefined;
37
40
  limit?: number | undefined;
package/dist/fields.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  /// <reference types="temporal-polyfill/global" preserve="true" />
2
2
  import type { VariantSpec } from "./derivatives-types.js";
3
+ import type { EventUpcastCtx } from "./handlers.js";
3
4
  import type { OwnershipMap } from "./ownership.js";
4
5
  import type { EntityTenancy } from "./tenancy-brand.js";
5
6
  export type FieldAccess = {
@@ -713,6 +714,11 @@ export type ParentRefDef = {
713
714
  * registered entity is a candidate, which also widens the read-gate's SQL. */
714
715
  readonly allowedTypes?: readonly string[];
715
716
  };
717
+ export type EntityEventMigration = {
718
+ readonly fromVersion: number;
719
+ readonly toVersion: number;
720
+ readonly transform: (fields: Readonly<Record<string, unknown>>, ctx: EventUpcastCtx) => Record<string, unknown> | Promise<Record<string, unknown>>;
721
+ };
716
722
  export type EntityDefinition<F extends FieldsMap = FieldsMap, T extends EntityTenancy = EntityTenancy> = {
717
723
  readonly table?: string;
718
724
  readonly fields: F;
@@ -725,6 +731,10 @@ export type EntityDefinition<F extends FieldsMap = FieldsMap, T extends EntityTe
725
731
  * only for genuinely tenant-independent aggregates like `user`. The first
726
732
  * event (create) is what's routed; updates resolve the stream tenant upstream. */
727
733
  readonly systemStream?: boolean;
734
+ /** Schema version stamped on every lifecycle event (default 1, integer 1..999). Bump it together with a step in `eventMigrations`. */
735
+ readonly eventVersion?: number;
736
+ /** One step per version, fromVersion → fromVersion+1. Transforms run on row-shaped field records; the record may be partial, touch only keys that are present. */
737
+ readonly eventMigrations?: readonly EntityEventMigration[];
728
738
  readonly searchWeight?: number;
729
739
  readonly defaultCurrency?: string;
730
740
  /** Allowed state transitions per field. Boot validates against select options. */
@@ -178,10 +178,15 @@ export type EscapeHatchUseEvent = {
178
178
  readonly reason: string;
179
179
  readonly tenantId: TenantId;
180
180
  readonly actor: string;
181
+ readonly caller?: string;
181
182
  readonly target?: EscapeHatchTarget;
182
183
  };
183
184
  export type EscapeHatchAuditSink = (event: EscapeHatchUseEvent) => Promise<void>;
184
185
  export type EscapeHatchReporter = (kind: EscapeHatchKind, reason: string, target?: EscapeHatchTarget) => void;
186
+ export type TenantWriteTarget = {
187
+ readonly db: TenantDb;
188
+ readonly streamTenantId: TenantId | undefined;
189
+ };
185
190
  type SharedContextFields = {
186
191
  readonly redis?: Redis;
187
192
  readonly cacheSync?: CacheSyncBus;
@@ -196,6 +201,15 @@ type SharedContextFields = {
196
201
  * the config feature wired its accessor factory.
197
202
  */
198
203
  readonly configFor?: (tenantId: TenantId) => ConfigAccessor;
204
+ /**
205
+ * Write target for ANOTHER tenant: a tenant-mode db bound to it plus the
206
+ * `streamTenantId` to hand the executor, so the event actor stays the
207
+ * operator. Cross-tenant calls throw AccessDeniedError unless the caller is
208
+ * SystemAdmin (the tenantIdOverride gate); the caller's own tenant needs no
209
+ * privilege and yields `streamTenantId: undefined`. Write handlers only;
210
+ * absent for r.systemScope() handlers and without a db.
211
+ */
212
+ readonly forTenant?: (tenantId: TenantId) => TenantWriteTarget;
199
213
  readonly _configAccessorFactory?: ConfigAccessorFactory;
200
214
  readonly configEncryption?: import("./envelope-cipher-types.js").EnvelopeCipher;
201
215
  readonly rateLimit?: import("./rate-limit-types.js").RateLimitResolver;
@@ -534,6 +548,9 @@ export type WriteHandlerDef = {
534
548
  readonly access: AccessRule;
535
549
  readonly description?: string;
536
550
  readonly agent?: AgentHandlerHints;
551
+ /** Fields a generic create/update handler never writes (`excludeFields`).
552
+ * Set only when non-empty; the renderer and the agent manifest read it from here. */
553
+ readonly excludedFields?: readonly string[];
537
554
  readonly unsafeSkipTransitionGuard?: boolean;
538
555
  readonly rateLimit?: RateLimitDeclaration;
539
556
  /** Per-payload-field limits, e.g. per recipient address. Complements
package/dist/screen.d.ts CHANGED
@@ -1205,6 +1205,10 @@ export type EditLayout = {
1205
1205
  };
1206
1206
  };
1207
1207
  };
1208
+ export type EntityWriteExcludedFields = {
1209
+ readonly create?: readonly string[];
1210
+ readonly update?: readonly string[];
1211
+ };
1208
1212
  export type EntityEditScreenDefinition = {
1209
1213
  readonly id: string;
1210
1214
  readonly type: "entityEdit";
@@ -1228,6 +1232,10 @@ export type EntityEditScreenDefinition = {
1228
1232
  * screen — the only URL query keys the create form prefills. An authored
1229
1233
  * value is overwritten. */
1230
1234
  readonly urlPrefillFields?: readonly string[];
1235
+ /** Derived by buildAppSchema from the entity's create/update write handlers
1236
+ * (`excludeFields`): create hides these fields, update shows them read-only.
1237
+ * An authored value is overwritten. */
1238
+ readonly writeExcludedFields?: EntityWriteExcludedFields;
1231
1239
  readonly entity: string;
1232
1240
  /** Entity field whose value names the loaded record in the header breadcrumb (edit mode only). */
1233
1241
  readonly recordTitleField?: string;
@@ -1291,16 +1299,24 @@ export type EntityEditScreenDefinition = {
1291
1299
  * das `mask.title`-Label des Config-Keys. Fehlt ein Eintrag, gilt die
1292
1300
  * Konvention. */
1293
1301
  readonly fieldLabels?: Readonly<Record<string, string>>;
1294
- /** Header action buttons. Reuses `RowAction` — analogous to
1302
+ /** Record actions. Reuses `RowAction` — analogous to
1295
1303
  * `ProjectionDetailScreenDefinition.actions` (the loaded record stands
1296
1304
  * in for the "row"). `rowClick` has no target here and is rejected by
1297
1305
  * the boot-validator, same as on projectionDetail. Not rendered in
1298
1306
  * create mode — every action targets an existing record, which the
1299
- * create branch doesn't have yet. */
1300
- readonly actions?: readonly RowAction[];
1307
+ * create branch doesn't have yet. By default an action renders with the
1308
+ * secondary form actions (footer, next to copy-link/delete/cancel);
1309
+ * `placement: "menu"` moves it into the header "…" menu, ahead of
1310
+ * copy-link/delete. Without a header menu (drawer, card form, shell
1311
+ * without a header slot) a "menu" action falls back to inline. */
1312
+ readonly actions?: readonly EntityEditAction[];
1301
1313
  readonly slots?: ScreenSlots;
1302
1314
  readonly access?: AccessRule;
1303
1315
  };
1316
+ export type EntityEditActionPlacement = "inline" | "menu";
1317
+ export type EntityEditAction = RowAction & {
1318
+ readonly placement?: EntityEditActionPlacement;
1319
+ };
1304
1320
  export type ActionFormScreenDefinition = {
1305
1321
  readonly id: string;
1306
1322
  readonly type: "actionForm";
@@ -10,4 +10,5 @@ export type LoadAggregateWithSnapshotOptions = {
10
10
  readonly upcastEvent?: (event: StoredEvent) => Promise<StoredEvent>;
11
11
  readonly snapshotEvery?: number;
12
12
  readonly snapshotVersion?: number;
13
+ readonly entityEventVersionOf?: (aggregateType: string) => number | undefined;
13
14
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-types",
3
- "version": "0.354.1",
3
+ "version": "0.356.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>",