@cosmicdrift/kumiko-types 0.362.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";
@@ -183,14 +183,67 @@ export type NotificationRecipientFn = (result: import("./hooks.js").SaveContext)
183
183
  } | null;
184
184
  export type NotificationDataFn = (result: import("./hooks.js").SaveContext) => Record<string, unknown>;
185
185
  export type NotificationTemplateFn = (data: Record<string, unknown>) => Record<string, unknown>;
186
+ type TemplateVariableCommon = {
187
+ readonly label?: string;
188
+ readonly required?: boolean;
189
+ };
190
+ export type TemplateScalarVariableDef = (TemplateVariableCommon & {
191
+ readonly type: "text";
192
+ readonly example?: string;
193
+ }) | (TemplateVariableCommon & {
194
+ readonly type: "link";
195
+ readonly example?: string;
196
+ }) | (TemplateVariableCommon & {
197
+ readonly type: "richText";
198
+ readonly example?: string;
199
+ }) | (TemplateVariableCommon & {
200
+ readonly type: "money";
201
+ readonly example?: number | {
202
+ readonly amount: number;
203
+ readonly currency: string;
204
+ };
205
+ }) | (TemplateVariableCommon & {
206
+ readonly type: "date";
207
+ readonly example?: string;
208
+ });
209
+ export type TemplateListVariableDef = TemplateVariableCommon & {
210
+ readonly type: "list";
211
+ readonly item: Readonly<Record<string, TemplateScalarVariableDef>>;
212
+ readonly example?: readonly Readonly<Record<string, unknown>>[];
213
+ };
214
+ export type TemplateVariableDef = TemplateScalarVariableDef | TemplateListVariableDef;
215
+ export type TemplateVariables = Readonly<Record<string, TemplateVariableDef>>;
216
+ export type NotificationTemplateMeta = {
217
+ readonly subject: string;
218
+ readonly previewText?: string;
219
+ readonly layoutSlug?: string;
220
+ readonly category?: "transactional" | "marketing";
221
+ readonly sender?: string;
222
+ };
223
+ export type NotificationTemplateBody = {
224
+ readonly body: string;
225
+ };
226
+ export type NotificationLocalizedTemplate = Readonly<Record<string, {
227
+ readonly body: string;
228
+ readonly meta: NotificationTemplateMeta;
229
+ }>>;
230
+ export type NotificationRecordMapping = {
231
+ readonly entity: string;
232
+ readonly map: Readonly<Record<string, string>>;
233
+ };
186
234
  export type NotificationDefinition = {
187
235
  readonly name: string;
188
- readonly trigger: {
236
+ readonly trigger?: {
189
237
  readonly on: string;
190
238
  };
191
- readonly recipient: NotificationRecipientFn;
192
- readonly data: NotificationDataFn;
239
+ readonly recipient?: NotificationRecipientFn;
240
+ readonly data?: NotificationDataFn;
193
241
  readonly templates: Readonly<Record<string, NotificationTemplateFn>> | undefined;
242
+ readonly variables?: TemplateVariables;
243
+ readonly record?: NotificationRecordMapping;
244
+ readonly template?: Readonly<Record<string, NotificationTemplateBody>>;
245
+ readonly meta?: Readonly<Record<string, NotificationTemplateMeta>>;
246
+ readonly localizedTemplate?: () => NotificationLocalizedTemplate;
194
247
  };
195
248
  export type PluralForms = {
196
249
  readonly zero?: string;
@@ -259,3 +312,4 @@ export type CreateUserSeedOptions = {
259
312
  readonly tenantId: string;
260
313
  readonly userId: string;
261
314
  };
315
+ export {};
@@ -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
@@ -1,9 +1,10 @@
1
1
  import type * as z from "zod";
2
2
  import type { ZodType } from "zod";
3
- import type { ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigSeedDef, ExtensionSelectorDef, ExtensionSelectorPanel, JobDefinition, JobHandlerFn, NotificationDataFn, NotificationDefinition, NotificationRecipientFn, NotificationTemplateFn, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionRegistration, TranslationKeys, TranslationsDef } from "./config.js";
3
+ import type { ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigSeedDef, ExtensionSelectorDef, ExtensionSelectorPanel, JobDefinition, JobHandlerFn, NotificationDataFn, NotificationDefinition, NotificationLocalizedTemplate, NotificationRecipientFn, NotificationRecordMapping, NotificationTemplateBody, NotificationTemplateFn, NotificationTemplateMeta, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionRegistration, TemplateVariables, TranslationKeys, TranslationsDef } from "./config.js";
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;
@@ -296,21 +297,31 @@ export type FeatureRegistrar<TFeature extends string = string> = {
296
297
  job(name: string, options: Omit<JobDefinition, "name" | "handler">, handler: JobHandlerFn): void;
297
298
  job(definition: JobDefinition): void;
298
299
  notification(name: string, definition: {
299
- readonly trigger: {
300
+ readonly trigger?: {
300
301
  readonly on: NameOrRef;
301
302
  };
302
- readonly recipient: NotificationRecipientFn;
303
- readonly data: NotificationDataFn;
303
+ readonly recipient?: NotificationRecipientFn;
304
+ readonly data?: NotificationDataFn;
304
305
  readonly templates?: Readonly<Record<string, NotificationTemplateFn>>;
306
+ readonly variables?: TemplateVariables;
307
+ readonly record?: NotificationRecordMapping;
308
+ readonly template?: Readonly<Record<string, NotificationTemplateBody>>;
309
+ readonly meta?: Readonly<Record<string, NotificationTemplateMeta>>;
310
+ readonly localizedTemplate?: () => NotificationLocalizedTemplate;
305
311
  }): void;
306
312
  notification(definition: {
307
313
  readonly name: string;
308
- readonly trigger: {
314
+ readonly trigger?: {
309
315
  readonly on: NameOrRef;
310
316
  };
311
- readonly recipient: NotificationRecipientFn;
312
- readonly data: NotificationDataFn;
317
+ readonly recipient?: NotificationRecipientFn;
318
+ readonly data?: NotificationDataFn;
313
319
  readonly templates?: Readonly<Record<string, NotificationTemplateFn>>;
320
+ readonly variables?: TemplateVariables;
321
+ readonly record?: NotificationRecordMapping;
322
+ readonly template?: Readonly<Record<string, NotificationTemplateBody>>;
323
+ readonly meta?: Readonly<Record<string, NotificationTemplateMeta>>;
324
+ readonly localizedTemplate?: () => NotificationLocalizedTemplate;
314
325
  }): void;
315
326
  translations(def: TranslationsDef): void;
316
327
  defineEvent<const TInner extends string, TPayload>(name: TInner, schema: ZodType<TPayload>, options: {
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;
@@ -157,16 +163,26 @@ export type NotifyDelivery = {
157
163
  readonly confirmed?: false;
158
164
  readonly deliveryAttemptId?: string;
159
165
  };
160
- export type NotifyResult = {
166
+ export type NotifyDispatched = {
167
+ readonly kind: "dispatched";
161
168
  readonly deliveries: readonly NotifyDelivery[];
162
169
  };
163
- export type NotifyFn = (notificationType: string, options: NotifyOptions) => Promise<NotifyResult>;
170
+ export type NotifyDeferred = {
171
+ readonly kind: "deferred";
172
+ };
173
+ export type NotifyResult = NotifyDispatched | NotifyDeferred;
174
+ export type NotifyFn = {
175
+ (notificationType: string, options: NotifyOptions & {
176
+ readonly immediate: true;
177
+ }): Promise<NotifyDispatched>;
178
+ (notificationType: string, options: NotifyOptions): Promise<NotifyResult>;
179
+ };
164
180
  export type NotifyJobDispatcher = {
165
181
  dispatch(jobName: string, payload: Record<string, unknown>, meta?: {
166
182
  readonly priority?: number;
167
183
  }): Promise<unknown>;
168
184
  };
169
- export type NotifyFactory = (user: SessionUser, tenantId: TenantId, jobDispatcher?: NotifyJobDispatcher) => NotifyFn;
185
+ export type NotifyFactory = (user: SessionUser, tenantId: TenantId, jobDispatcher?: NotifyJobDispatcher, deliveryDb?: DbConnection, fileProviderResolver?: FileProviderResolver) => NotifyFn;
170
186
  export type EscapeHatchKind = "unsafe-raw" | "acknowledge-cross-tenant" | "global-write" | "identity-switch" | "unsafe-all-tenants" | "cross-tenant-read";
171
187
  export type EscapeHatchTarget = {
172
188
  readonly id: string;
@@ -221,6 +237,7 @@ type SharedContextFields = {
221
237
  readonly entityCache?: EntityCache;
222
238
  readonly notify?: NotifyFn;
223
239
  readonly _notifyFactory?: NotifyFactory;
240
+ readonly _notifyInTransactionFactory?: (user: SessionUser, tenantId: TenantId) => NotifyFn;
224
241
  readonly _escapeHatchAuditSink?: EscapeHatchAuditSink;
225
242
  readonly secrets?: import("./secrets-types.js").SecretsContext;
226
243
  readonly masterKeyProvider?: import("./secrets-types.js").MasterKeyProvider;
@@ -288,6 +305,11 @@ export type HandlerContext<TMap extends object = KumikoEventTypeMap> = SharedCon
288
305
  } | null;
289
306
  readonly _userId?: string | undefined;
290
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;
291
313
  readonly includeDeleted?: boolean;
292
314
  readonly runPreSave?: (handlerName: string, changes: Record<string, unknown>, previous: Readonly<Record<string, unknown>>, isNew: boolean) => Promise<Record<string, unknown>>;
293
315
  readonly query: (qn: string, payload: unknown) => Promise<unknown>;
@@ -496,11 +518,13 @@ export type RateLimitOption = {
496
518
  export type RateLimitPayloadBucket = {
497
519
  readonly payloadField: string;
498
520
  };
521
+ export type PayloadBucketNormalization = "email";
499
522
  export type PayloadRateLimitOption = {
500
523
  readonly per: RateLimitPayloadBucket;
501
524
  readonly limit: number;
502
525
  readonly windowSeconds: number;
503
526
  readonly cost?: number;
527
+ readonly normalize?: PayloadBucketNormalization;
504
528
  };
505
529
  export type RateLimitDisabled = {
506
530
  readonly disabled: true;
@@ -541,6 +565,15 @@ export type ReservationHandle = {
541
565
  * The returned release (or handle.release) runs when the transaction does not commit (failure
542
566
  * result, throw, rollback, failed commit). Throwing a KumikoError rejects the write. */
543
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";
544
577
  export type WriteHandlerDef = {
545
578
  readonly name: string;
546
579
  readonly schema: ZodType;
@@ -560,6 +593,16 @@ export type WriteHandlerDef = {
560
593
  /** Reserves before the handler transaction opens. Reached through a nested ctx.write the
561
594
  * reservation cannot run, so the dispatcher rejects it unless the top-level batch reserved it. */
562
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;
563
606
  /** Lets an anonymous `POST /api/write` for this handler proceed under
564
607
  * SYSTEM_TENANT_ID when the host resolves no tenant. Boot requires
565
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 & {
@@ -34,7 +34,7 @@ export function subjectIdFromKey(key) {
34
34
  if (key.startsWith("user:"))
35
35
  return { kind: "user", userId: key.slice("user:".length) };
36
36
  if (key.startsWith("tenant:")) {
37
- return { kind: "tenant", tenantId: key.slice("tenant:".length) }; // @cast-boundary parse of a key this module minted
37
+ return { kind: "tenant", tenantId: key.slice("tenant:".length) };
38
38
  }
39
39
  if (key.startsWith("record:")) {
40
40
  const rest = key.slice("record:".length);
package/dist/nav.d.ts CHANGED
@@ -18,7 +18,7 @@ export type NavDefinition = {
18
18
  readonly workspaces?: readonly string[];
19
19
  };
20
20
  /** Client editor format for r.contentCollection() — not template body storage formats. */
21
- export type ContentEditorFormat = "plain" | "rich" | "markdown";
21
+ export type ContentEditorFormat = "plain" | "rich" | "markdown" | "document";
22
22
  export type ContentCollectionDefinition = {
23
23
  readonly id: string;
24
24
  readonly kind: string;
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?: {
@@ -0,0 +1,8 @@
1
+ /** Non-null object that is not an array — shared guard for deep-merge and AST extractors. */
2
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
3
+ /** Membership test that narrows `candidate` to the element union of a const tuple. */
4
+ export declare function isOneOf<const T extends readonly unknown[]>(values: T, candidate: unknown): candidate is T[number];
5
+ /** Reads a string `code` (e.g. Node `ErrnoException`, Postgres SQLSTATE) off an unknown thrown value. */
6
+ export declare function errorCodeOf(error: unknown): string | undefined;
7
+ /** Reads one property off an unknown value (object or function); undefined for primitives, null and absent keys. */
8
+ export declare function propertyOf(value: unknown, key: PropertyKey): unknown;
@@ -0,0 +1,21 @@
1
+ /** Non-null object that is not an array — shared guard for deep-merge and AST extractors. */
2
+ export function isPlainObject(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ /** Membership test that narrows `candidate` to the element union of a const tuple. */
6
+ export function isOneOf(values, candidate) {
7
+ return values.some((value) => value === candidate);
8
+ }
9
+ /** Reads a string `code` (e.g. Node `ErrnoException`, Postgres SQLSTATE) off an unknown thrown value. */
10
+ export function errorCodeOf(error) {
11
+ if (typeof error !== "object" || error === null || !("code" in error))
12
+ return undefined;
13
+ return typeof error.code === "string" ? error.code : undefined;
14
+ }
15
+ /** Reads one property off an unknown value (object or function); undefined for primitives, null and absent keys. */
16
+ export function propertyOf(value, key) {
17
+ if ((typeof value !== "object" && typeof value !== "function") || value === null) {
18
+ return undefined;
19
+ }
20
+ return Reflect.get(value, key);
21
+ }
@@ -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.362.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>",
@@ -26,6 +26,10 @@
26
26
  "types": "./dist/identifiers.d.ts",
27
27
  "default": "./dist/identifiers.js"
28
28
  },
29
+ "./type-guards": {
30
+ "types": "./dist/type-guards.d.ts",
31
+ "default": "./dist/type-guards.js"
32
+ },
29
33
  "./session-cookies": {
30
34
  "types": "./dist/session-cookies.d.ts",
31
35
  "default": "./dist/session-cookies.js"
@@ -297,6 +301,10 @@
297
301
  "types": "./dist/identifiers.d.ts",
298
302
  "default": "./dist/identifiers.js"
299
303
  },
304
+ "./type-guards": {
305
+ "types": "./dist/type-guards.d.ts",
306
+ "default": "./dist/type-guards.js"
307
+ },
300
308
  "./session-cookies": {
301
309
  "types": "./dist/session-cookies.d.ts",
302
310
  "default": "./dist/session-cookies.js"