@cosmicdrift/kumiko-types 0.363.0 → 0.364.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.
@@ -1,5 +1,8 @@
1
+ export type CacheSyncPublishOptions = {
2
+ readonly remoteOnly?: boolean;
3
+ };
1
4
  export type CacheSyncBus = {
2
- publish(topic: string, message: unknown): void;
5
+ publish(topic: string, message: unknown, options?: CacheSyncPublishOptions): void;
3
6
  subscribe(topic: string, listener: (message: unknown) => void): () => void;
4
7
  onResync(listener: () => void): () => void;
5
8
  };
package/dist/config.d.ts CHANGED
@@ -30,7 +30,7 @@ export type ConfigValidateContext = {
30
30
  readonly scope: ConfigScope;
31
31
  readonly tenantId: TenantId;
32
32
  readonly userId: string | null;
33
- readonly db: DbConnection | TenantDb;
33
+ readonly db: TenantDb;
34
34
  };
35
35
  export type ConfigValidateFn<T extends ConfigKeyType = ConfigKeyType> = (value: ConfigValue<T>, ctx: ConfigValidateContext) => Promise<void>;
36
36
  export type ConfigBacking = "config" | "secrets";
@@ -193,6 +193,9 @@ export type TemplateScalarVariableDef = (TemplateVariableCommon & {
193
193
  }) | (TemplateVariableCommon & {
194
194
  readonly type: "link";
195
195
  readonly example?: string;
196
+ }) | (TemplateVariableCommon & {
197
+ readonly type: "richText";
198
+ readonly example?: string;
196
199
  }) | (TemplateVariableCommon & {
197
200
  readonly type: "money";
198
201
  readonly example?: number | {
@@ -1,9 +1,9 @@
1
1
  import type * as z from "zod";
2
2
  import type { ZodType } from "zod";
3
3
  import type { KumikoEventTypeMap } from "./event-type-map.js";
4
- import type { AccessRule, AgentHandlerHints, EscapeHatchDeclaration, HandlerContext, PayloadRateLimitOption, PreTransactionReservation, QueryEvent, RateLimitDeclaration, WriteEvent, WriteResult } from "./handlers.js";
4
+ import type { AccessRule, AgentHandlerHints, BeforeTransactionPhase, EscapeHatchDeclaration, HandlerContext, PayloadRateLimitOption, PreparedWriteEvent, PreTransactionReservation, QueryEvent, RateLimitDeclaration, WriteHandlerTransactionMode, WriteResult } from "./handlers.js";
5
5
  import type { PipelineDef } from "./step.js";
6
- export type WriteHandlerDefinition<TName extends string = string, TSchema extends ZodType = ZodType, TData = unknown, TMap extends object = KumikoEventTypeMap> = {
6
+ export type WriteHandlerDefinition<TName extends string = string, TSchema extends ZodType = ZodType, TData = unknown, TMap extends object = KumikoEventTypeMap, TPrepared = undefined> = {
7
7
  readonly name: TName;
8
8
  readonly schema: TSchema;
9
9
  readonly access: AccessRule;
@@ -16,11 +16,13 @@ export type WriteHandlerDefinition<TName extends string = string, TSchema extend
16
16
  readonly additionalRateLimits?: readonly PayloadRateLimitOption[];
17
17
  readonly escapeHatch?: EscapeHatchDeclaration;
18
18
  readonly reserveBeforeTransaction?: PreTransactionReservation;
19
+ readonly beforeTransaction?: BeforeTransactionPhase<z.infer<TSchema>, TPrepared>;
19
20
  readonly tenantlessAnonymous?: boolean;
20
- readonly handler: (event: WriteEvent<z.infer<TSchema>>, context: HandlerContext<TMap>) => Promise<WriteResult<TData>>;
21
+ readonly transaction?: WriteHandlerTransactionMode;
22
+ readonly handler: (event: PreparedWriteEvent<z.infer<TSchema>, TPrepared>, context: HandlerContext<TMap>) => Promise<WriteResult<TData>>;
21
23
  readonly perform?: PipelineDef<z.infer<TSchema>, TData>;
22
24
  };
23
- export type WriteHandlerInput<TName extends string = string, TSchema extends ZodType = ZodType, TData = unknown, TMap extends object = KumikoEventTypeMap> = {
25
+ export type WriteHandlerInput<TName extends string = string, TSchema extends ZodType = ZodType, TData = unknown, TMap extends object = KumikoEventTypeMap, TPrepared = undefined> = {
24
26
  readonly name: TName;
25
27
  readonly schema: TSchema;
26
28
  readonly access: AccessRule;
@@ -31,9 +33,11 @@ export type WriteHandlerInput<TName extends string = string, TSchema extends Zod
31
33
  readonly additionalRateLimits?: readonly PayloadRateLimitOption[];
32
34
  readonly escapeHatch?: EscapeHatchDeclaration;
33
35
  readonly reserveBeforeTransaction?: PreTransactionReservation;
36
+ readonly beforeTransaction?: BeforeTransactionPhase<z.infer<TSchema>, TPrepared>;
34
37
  readonly tenantlessAnonymous?: boolean;
38
+ readonly transaction?: WriteHandlerTransactionMode;
35
39
  } & ({
36
- readonly handler: (event: WriteEvent<z.infer<TSchema>>, context: HandlerContext<TMap>) => Promise<WriteResult<TData>>;
40
+ readonly handler: (event: PreparedWriteEvent<z.infer<TSchema>, TPrepared>, context: HandlerContext<TMap>) => Promise<WriteResult<TData>>;
37
41
  readonly perform?: never;
38
42
  } | {
39
43
  readonly perform: PipelineDef<z.infer<TSchema>, TData>;
package/dist/feature.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigSeedDef
4
4
  import type { QueryHandlerDefinition, StreamHandlerDefinition, WriteHandlerDefinition } from "./define-handler.js";
5
5
  import type { RegisterEntityCrudOptions } from "./entity-handlers.js";
6
6
  import type { EntityTableMeta } from "./entity-table-meta-types.js";
7
+ import type { KumikoEventTypeMap } from "./event-type-map.js";
7
8
  import type { ExtensionOptionsArgs, ExtensionOptionsFor } from "./extension-options-map.js";
8
9
  import type { EntityDefinition } from "./fields.js";
9
10
  import type { AccessRule, AgentHandlerHints, AuthClaimsFn, AuthClaimsHookDef, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyType, DeclarativeEventMigration, EntityRef, EscapeHatchDeclaration, EventDef, EventMigrationDef, EventPiiStance, EventUpcastFn, HandlerRef, NameOrRef, PayloadRateLimitOption, QualifiedEventName, QueryHandlerDef, QueryHandlerFn, RateLimitDeclaration, StreamHandlerDef, StreamHandlerFn, WriteHandlerDef, WriteHandlerFn } from "./handlers.js";
@@ -234,7 +235,7 @@ export type FeatureRegistrar<TFeature extends string = string> = {
234
235
  readonly name: string;
235
236
  } & EntityDefinition): EntityRef;
236
237
  crud(entityName: string, entity: EntityDefinition, options?: RegisterEntityCrudOptions): void;
237
- writeHandler<TName extends string, TSchema extends ZodType>(def: WriteHandlerDefinition<TName, TSchema>): HandlerRef;
238
+ writeHandler<TName extends string, TSchema extends ZodType, TPrepared = undefined>(def: WriteHandlerDefinition<TName, TSchema, unknown, KumikoEventTypeMap, TPrepared>): HandlerRef;
238
239
  writeHandler<TSchema extends ZodType>(name: string, schema: TSchema, handler: WriteHandlerFn<z.infer<TSchema>>, options: {
239
240
  access: AccessRule;
240
241
  rateLimit?: RateLimitDeclaration;
package/dist/fields.d.ts CHANGED
@@ -750,7 +750,8 @@ export type EntityDefinition<F extends FieldsMap = FieldsMap, T extends EntityTe
750
750
  readonly systemStream?: boolean;
751
751
  /** Schema version stamped on every lifecycle event (default 1, integer 1..999). Bump it together with a step in `eventMigrations`. */
752
752
  readonly eventVersion?: number;
753
- /** 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. */
753
+ /** 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.
754
+ * `pii` and `encrypted` fields arrive as stored ciphertext bound to their field name (AAD): pass them through under the same key, never rename or derive from them, or the value can no longer be decrypted. */
754
755
  readonly eventMigrations?: readonly EntityEventMigration[];
755
756
  readonly searchWeight?: number;
756
757
  readonly defaultCurrency?: string;
@@ -8,5 +8,8 @@ export type FileHandle = {
8
8
  };
9
9
  export type FileContext = {
10
10
  ref(key: string): FileHandle;
11
+ refFromFileRef(row: {
12
+ readonly storageKey: string;
13
+ }): FileHandle;
11
14
  list(prefix: string): Promise<readonly string[]>;
12
15
  };
@@ -101,6 +101,12 @@ export type WriteEvent<TPayload = unknown> = {
101
101
  readonly payload: TPayload;
102
102
  readonly user: SessionUser;
103
103
  };
104
+ /** What a handler sees when its `beforeTransaction` phase ran: the phase's `data` as `prepared`; absent for handlers without the phase. */
105
+ export type PreparedWriteEvent<TPayload = unknown, TPrepared = unknown> = WriteEvent<TPayload> & ([TPrepared] extends [undefined] ? {
106
+ readonly prepared?: undefined;
107
+ } : {
108
+ readonly prepared: TPrepared;
109
+ });
104
110
  export type QueryEvent<TPayload = unknown> = {
105
111
  readonly type: string;
106
112
  readonly payload: TPayload;
@@ -299,6 +305,11 @@ export type HandlerContext<TMap extends object = KumikoEventTypeMap> = SharedCon
299
305
  } | null;
300
306
  readonly _userId?: string | undefined;
301
307
  readonly _handlerType?: string | undefined;
308
+ /** Keyed digest for handler-owned rate-limit keys, so Redis never holds a
309
+ * plaintext or plainly hashed identifier. Scoped to the running handler:
310
+ * the same input yields a different digest per handler and never matches a
311
+ * dispatcher payload-bucket digest. Absent when the app wires no digest key. */
312
+ readonly rateLimitDigest?: (value: string) => string;
302
313
  readonly includeDeleted?: boolean;
303
314
  readonly runPreSave?: (handlerName: string, changes: Record<string, unknown>, previous: Readonly<Record<string, unknown>>, isNew: boolean) => Promise<Record<string, unknown>>;
304
315
  readonly query: (qn: string, payload: unknown) => Promise<unknown>;
@@ -507,11 +518,13 @@ export type RateLimitOption = {
507
518
  export type RateLimitPayloadBucket = {
508
519
  readonly payloadField: string;
509
520
  };
521
+ export type PayloadBucketNormalization = "email";
510
522
  export type PayloadRateLimitOption = {
511
523
  readonly per: RateLimitPayloadBucket;
512
524
  readonly limit: number;
513
525
  readonly windowSeconds: number;
514
526
  readonly cost?: number;
527
+ readonly normalize?: PayloadBucketNormalization;
515
528
  };
516
529
  export type RateLimitDisabled = {
517
530
  readonly disabled: true;
@@ -552,6 +565,15 @@ export type ReservationHandle = {
552
565
  * The returned release (or handle.release) runs when the transaction does not commit (failure
553
566
  * result, throw, rollback, failed commit). Throwing a KumikoError rejects the write. */
554
567
  export type PreTransactionReservation = (event: WriteEvent, ctx: HandlerContext) => Promise<ReservationRelease | ReservationHandle | undefined>;
568
+ /** Outcome of a write handler's pre-transaction phase. `release` runs when the handler transaction does not commit (failure result, throw, rollback, failed commit with unknown outcome), so it must itself tolerate a commit that actually happened. */
569
+ export type BeforeTransactionResult<TPrepared = unknown> = {
570
+ readonly isSuccess: true;
571
+ readonly data: TPrepared;
572
+ readonly release?: ReservationRelease;
573
+ } | WriteFailure;
574
+ export type BeforeTransactionPhase<TPayload = unknown, TPrepared = unknown> = (event: WriteEvent<TPayload>, ctx: HandlerContext) => Promise<BeforeTransactionResult<TPrepared>>;
575
+ /** `perCommand`: the handler runs without a transaction of its own. */
576
+ export type WriteHandlerTransactionMode = "perCommand";
555
577
  export type WriteHandlerDef = {
556
578
  readonly name: string;
557
579
  readonly schema: ZodType;
@@ -571,6 +593,16 @@ export type WriteHandlerDef = {
571
593
  /** Reserves before the handler transaction opens. Reached through a nested ctx.write the
572
594
  * reservation cannot run, so the dispatcher rejects it unless the top-level batch reserved it. */
573
595
  readonly reserveBeforeTransaction?: PreTransactionReservation;
596
+ /** Runs after all pre-handler gates (and after reserveBeforeTransaction) and before the
597
+ * transaction opens: no transaction and no connection held, for external I/O and reads only.
598
+ * Its `data` reaches the handler as `event.prepared`; a failure result or throw rejects the
599
+ * write before anything is written. A handler with this phase cannot run as a nested ctx.write. */
600
+ readonly beforeTransaction?: BeforeTransactionPhase;
601
+ /** `"perCommand"`: the handler runs without a transaction of its own. Every ctx.write/writeAs it
602
+ * issues runs as its own top-level command with its own transaction and afterCommit hooks;
603
+ * ctx.db writes and event appends throw. It cannot run as a nested write or inside a
604
+ * multi-command batch, and cannot be combined with beforeTransaction/reserveBeforeTransaction. */
605
+ readonly transaction?: WriteHandlerTransactionMode;
574
606
  /** Lets an anonymous `POST /api/write` for this handler proceed under
575
607
  * SYSTEM_TENANT_ID when the host resolves no tenant. Boot requires
576
608
  * `access.roles: ["anonymous"]` and a real `rateLimit`. */
package/dist/hooks.d.ts CHANGED
@@ -23,6 +23,7 @@ export type DeleteContext = {
23
23
  readonly data: Readonly<Record<string, unknown>>;
24
24
  readonly entityName?: string | undefined;
25
25
  readonly event?: StoredEvent | undefined;
26
+ readonly lockGuardBypassed?: boolean | undefined;
26
27
  };
27
28
  export type LifecycleResult = SaveContext | DeleteContext;
28
29
  export type PreSaveHookFn = (changes: Record<string, unknown>, context: AppContext & {
package/dist/screen.d.ts CHANGED
@@ -554,8 +554,10 @@ export type ProjectionListScreenDefinition = {
554
554
  * boot-validator only checks structure (`op:"in"` ⇒ array value). */
555
555
  readonly filter?: ScreenFilter;
556
556
  /** User-toggleable facet dropdowns — see `ListFacetSpec` doc. `field`
557
- * must be a declared column; the bound query handler must accept
558
- * `filters` in its Zod schema — the boot-validator checks both. A
557
+ * must be a declared column, except for `type: "reference"`: it filters
558
+ * by id through the bound query's `filters` and needs a registered
559
+ * `entity` instead. The bound query handler must accept `filters` in its
560
+ * Zod schema — the boot-validator checks both. A
559
561
  * "dateRange" facet instead needs its two `params` keys in that schema;
560
562
  * it sends no `filters` entry. */
561
563
  readonly facets?: readonly ListFacetSpec[];
@@ -1144,7 +1146,9 @@ export type EditRelatedListSection = {
1144
1146
  readonly searchable?: boolean;
1145
1147
  /** User-toggleable facet dropdowns — same explicit-label `ListFacetSpec` as
1146
1148
  * `projectionList.facets` (a relatedList has no entity to derive filterable
1147
- * fields from). `field` must be a declared column and the bound query
1149
+ * fields from). `field` must be a declared column, except for
1150
+ * `type: "reference"`: it filters by id through the bound query's
1151
+ * `filters` and needs a registered `entity` instead. The bound query
1148
1152
  * handler must accept `filters` in its Zod schema — the boot-validator
1149
1153
  * checks both. Selected values ride along as `payload.filters`. */
1150
1154
  readonly facets?: readonly ListFacetSpec[];
@@ -1,4 +1,5 @@
1
1
  /// <reference types="temporal-polyfill/global" preserve="true" />
2
+ import type { DbRunner } from "./db-connection.js";
2
3
  import type { TenantId } from "./identifiers.js";
3
4
  declare const SecretBrand: unique symbol;
4
5
  export type Secret<T = string> = {
@@ -21,6 +22,7 @@ export type SecretKeyRef = string | {
21
22
  readonly name: string;
22
23
  };
23
24
  export interface SecretsContext {
25
+ withRunner(runner: DbRunner): SecretsContext;
24
26
  get(tenantId: TenantId, key: SecretKeyRef, auditCtx?: SecretAuditContext): Promise<Secret<string> | undefined>;
25
27
  has(tenantId: TenantId, key: SecretKeyRef): Promise<boolean>;
26
28
  set(tenantId: TenantId, key: SecretKeyRef, value: string, opts?: {
@@ -5,6 +5,7 @@ export type WriteErrorInfo = {
5
5
  readonly i18nParams?: Readonly<Record<string, unknown>>;
6
6
  readonly message: string;
7
7
  readonly details?: unknown;
8
+ readonly retryAfterSeconds?: number;
8
9
  };
9
10
  export type WriteFailure = {
10
11
  readonly isSuccess: false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-types",
3
- "version": "0.363.0",
3
+ "version": "0.364.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>",