@cosmicdrift/kumiko-types 0.388.0 → 0.390.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/dist/feature.d.ts CHANGED
@@ -1,20 +1,16 @@
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, NotificationLocalizedTemplate, NotificationRecipientFn, NotificationRecordMapping, NotificationTemplateBody, NotificationTemplateFn, NotificationTemplateMeta, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionRegistration, TemplateVariables, TranslationKeys, TranslationsDef } from "./config.ts";
4
- import type { QueryHandlerDefinition, StreamHandlerDefinition, WriteHandlerDefinition } from "./define-handler.ts";
5
- import type { RegisterEntityCrudOptions } from "./entity-handlers.ts";
3
+ import type { ConfigKeyDefinition, ConfigSeedDef, ExtensionSelectorDef, ExtensionSelectorPanel, JobDefinition, NotificationDefinition, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionRegistration, TranslationKeys } from "./config.ts";
6
4
  import type { EntityTableMeta } from "./entity-table-meta-types.ts";
7
- import type { KumikoEventTypeMap } from "./event-type-map.ts";
8
- import type { ExtensionOptionsArgs, ExtensionOptionsFor } from "./extension-options-map.ts";
9
5
  import type { EntityDefinition } from "./fields.ts";
10
- import type { AccessRule, AuthClaimsFn, AuthClaimsHookDef, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyType, DeclarativeEventMigration, EntityRef, EscapeHatchDeclaration, EventDef, EventMigrationDef, EventPiiStance, EventUpcastFn, HandlerRef, NameOrRef, QualifiedEventName, QueryHandlerDef, RateLimitDeclaration, StreamHandlerDef, StreamHandlerFn, WriteHandlerDef } from "./handlers.ts";
11
- import type { EntityHookMap, HookMap, HookPhase, OwnedFn, PostDeleteHookFn, PostQueryHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, SearchPayloadContributorFn, ValidationHookFn } from "./hooks.ts";
6
+ import type { AuthClaimsFn, AuthClaimsHookDef, ClaimKeyDefinition, EventDef, EventMigrationDef, EventUpcastFn, QueryHandlerDef, StreamHandlerDef, WriteHandlerDef } from "./handlers.ts";
7
+ import type { EntityHookMap, HookMap, HookPhase, OwnedFn, PostDeleteHookFn, PostQueryHookFn, PostSaveHookFn, PreDeleteHookFn, PreSaveHookFn, SearchPayloadContributorFn } from "./hooks.ts";
12
8
  import type { HttpRouteDefinition } from "./http-route.ts";
13
9
  import type { ContentCollectionDefinition, NavDefinition } from "./nav.ts";
14
10
  import type { EntityProjectionExtension, MultiStreamProjectionDefinition, ProjectionDefinition } from "./projection.ts";
15
11
  import type { EntityRelations, RelationDefinition } from "./relations.ts";
16
12
  import type { ScreenDefinition } from "./screen.ts";
17
- import type { TreeActionDef, TreeActionsHandle } from "./tree-node.ts";
13
+ import type { TreeActionDef } from "./tree-node.ts";
18
14
  import type { WebSocketRouteDefinition } from "./websocket-route.ts";
19
15
  import type { WorkspaceDefinition } from "./workspace.ts";
20
16
  export type FeatureMetricType = "counter" | "histogram" | "gauge";
@@ -116,7 +112,7 @@ export type BootCheckContext = {
116
112
  };
117
113
  export type BootCheckFn = (ctx: BootCheckContext) => void;
118
114
  /** runProdApp options a stack can depend on; runProdApp checks the set at boot. */
119
- export type BootOptionName = "mail" | "kmsMode" | "fileStorage" | "billing" | "featureToggles" | "demoMode" | "seeds" | "signals" | "rateLimit";
115
+ export type BootOptionName = "mail" | "kmsMode" | "fileStorage" | "billing" | "featureToggles" | "rateLimit";
120
116
  export type StackBootRequirement = {
121
117
  readonly stack: string;
122
118
  readonly options: readonly BootOptionName[];
@@ -133,6 +129,7 @@ export type FeatureDefinition = {
133
129
  readonly requiredProjections: ReadonlySet<string>;
134
130
  readonly requiredSteps: ReadonlySet<string>;
135
131
  readonly toggleableDefault?: boolean;
132
+ readonly adoptsBundledLists?: true;
136
133
  readonly uiHints?: UiHints;
137
134
  readonly entities?: Readonly<Record<string, EntityDefinition>>;
138
135
  readonly entityTables?: Readonly<Record<string, unknown>>;
@@ -198,310 +195,6 @@ export type FeatureDefinition = {
198
195
  readonly envSchema?: z.ZodObject<z.ZodRawShape>;
199
196
  readonly dedupeOptions?: Readonly<Record<string, unknown>>;
200
197
  };
201
- type RefOrRefs = NameOrRef | readonly NameOrRef[];
202
- type HookTarget = RefOrRefs | {
203
- readonly allOf: NameOrRef;
204
- };
205
- export type EntityObjectDefinition = {
206
- readonly name: string;
207
- readonly backingTable?: unknown;
208
- } & EntityDefinition;
209
- type HookEscapeHatch = {
210
- readonly escapeHatch?: EscapeHatchDeclaration;
211
- };
212
- type HookPhaseOption = {
213
- readonly phase?: HookPhase;
214
- };
215
- export type HookDefinition = {
216
- readonly type: "validation";
217
- readonly target: RefOrRefs;
218
- readonly handler: ValidationHookFn;
219
- readonly escapeHatch?: never;
220
- readonly phase?: never;
221
- } | ({
222
- readonly type: "preSave";
223
- readonly target: RefOrRefs;
224
- readonly handler: PreSaveHookFn;
225
- readonly phase?: never;
226
- } & HookEscapeHatch) | ({
227
- readonly type: "postSave";
228
- readonly target: HookTarget;
229
- readonly handler: PostSaveHookFn;
230
- } & HookEscapeHatch & HookPhaseOption) | ({
231
- readonly type: "preDelete";
232
- readonly target: HookTarget;
233
- readonly handler: PreDeleteHookFn;
234
- readonly phase?: never;
235
- } & HookEscapeHatch) | ({
236
- readonly type: "postDelete";
237
- readonly target: HookTarget;
238
- readonly handler: PostDeleteHookFn;
239
- } & HookEscapeHatch & HookPhaseOption) | ({
240
- readonly type: "preQuery";
241
- readonly target: RefOrRefs;
242
- readonly handler: PreQueryHookFn;
243
- readonly phase?: never;
244
- } & HookEscapeHatch) | ({
245
- readonly type: "postQuery";
246
- readonly target: HookTarget;
247
- readonly handler: PostQueryHookFn;
248
- readonly phase?: never;
249
- } & HookEscapeHatch);
250
- /**
251
- * `r.requires({ features })` declares the features this one depends on; the
252
- * `.projection` extension declares read-side projection tables that this
253
- * feature's pipeline steps are allowed to write via
254
- * `r.step.unsafeProjectionUpsert` (hard-required for any unsafeProjection-*
255
- * step usage, see Q10).
256
- *
257
- * @example r.requires({ features: ["auth", "tenant"] });
258
- */
259
- export type RequiresApi = ((options: {
260
- readonly features: readonly string[];
261
- }) => void) & {
262
- readonly projection: (tableName: string) => void;
263
- readonly step: (stepKind: string) => void;
264
- };
265
- /**
266
- * `TFeature` is the literal feature-name from `defineFeature("foo", ...)` —
267
- * default-`string` keeps every existing usage zero-config. Strict-typed
268
- * features (apps that opt into the literal-name flavour) get propagated
269
- * through to `defineEvent` so the returned `EventDef.name` is a literal
270
- * `${CamelToKebab<TFeature>}:event:${CamelToKebab<TInner>}`. That literal
271
- * threads through `ctx.appendEvent({ type: eventDef.name, ... })`,
272
- * keeping strict-mode alive even when handlers route via `eventDef.name`
273
- * instead of hand-typed string literals.
274
- */
275
- export type FeatureRegistrar<TFeature extends string = string> = {
276
- systemScope(): void;
277
- describe(text: string): void;
278
- requires: RequiresApi;
279
- optionalRequires(...featureNames: string[]): void;
280
- optionalRequires(options: {
281
- readonly features: readonly string[];
282
- }): void;
283
- toggleable(options: {
284
- default: boolean;
285
- }): void;
286
- uiHints(hints: UiHints): void;
287
- /**
288
- * @example r.entity({ name: "note", fields: { title: createTextField({ required: true }) } });
289
- */
290
- entity(definition: EntityObjectDefinition): EntityRef;
291
- crud(entityName: string, entity: EntityDefinition, options?: RegisterEntityCrudOptions): void;
292
- /**
293
- * @example r.writeHandler({ name: "note:archive", schema, handler, access: { roles: ["User"] } });
294
- */
295
- writeHandler<TName extends string, TSchema extends ZodType, TPrepared = undefined>(def: WriteHandlerDefinition<TName, TSchema, unknown, KumikoEventTypeMap, TPrepared>): HandlerRef;
296
- /**
297
- * @example r.queryHandler({ name: "note:list", schema, handler, access: { roles: ["User"] } });
298
- */
299
- queryHandler<TName extends string, TSchema extends ZodType>(def: QueryHandlerDefinition<TName, TSchema>): HandlerRef;
300
- streamHandler<TName extends string, TSchema extends ZodType>(def: StreamHandlerDefinition<TName, TSchema>): HandlerRef;
301
- streamHandler<TSchema extends ZodType>(name: string, schema: TSchema, handler: StreamHandlerFn<z.infer<TSchema>>, options: {
302
- access: AccessRule;
303
- rateLimit?: RateLimitDeclaration;
304
- escapeHatch?: EscapeHatchDeclaration;
305
- }): HandlerRef;
306
- relation(entity: NameOrRef, relationName: string, definition: RelationDefinition): void;
307
- relation<TDef extends RelationDefinition>(definition: {
308
- readonly entity: NameOrRef;
309
- readonly name: string;
310
- } & TDef): void;
311
- /**
312
- * @example r.hook({ type: "postSave", target: { allOf: noteRef }, handler: notifyOwner });
313
- */
314
- hook(definition: HookDefinition): void;
315
- searchPayloadExtension(entity: NameOrRef, fn: SearchPayloadContributorFn): void;
316
- config<T extends ConfigKeyType>(keyName: string, def: ConfigKeyDefinition<T>): ConfigKeyHandle<T>;
317
- config<TKeys extends Readonly<Record<string, ConfigKeyDefinition<ConfigKeyType>>>>(definition: {
318
- readonly keys: TKeys;
319
- readonly seeds?: Readonly<Record<string, ConfigSeedDef>>;
320
- }): {
321
- readonly [K in keyof TKeys]: ConfigKeyHandle<TKeys[K]["type"]>;
322
- };
323
- job(name: string, options: Omit<JobDefinition, "name" | "handler">, handler: JobHandlerFn): void;
324
- job(definition: JobDefinition): void;
325
- notification(name: string, definition: {
326
- readonly trigger?: {
327
- readonly on: NameOrRef;
328
- };
329
- readonly recipient?: NotificationRecipientFn;
330
- readonly data?: NotificationDataFn;
331
- readonly templates?: Readonly<Record<string, NotificationTemplateFn>>;
332
- readonly variables?: TemplateVariables;
333
- readonly record?: NotificationRecordMapping;
334
- readonly template?: Readonly<Record<string, NotificationTemplateBody>>;
335
- readonly meta?: Readonly<Record<string, NotificationTemplateMeta>>;
336
- readonly localizedTemplate?: () => NotificationLocalizedTemplate;
337
- readonly renamedFrom?: readonly string[];
338
- readonly systemOnly?: boolean;
339
- readonly unsubscribe?: boolean;
340
- readonly tenantTransport?: boolean;
341
- }): void;
342
- notification(definition: {
343
- readonly name: string;
344
- readonly trigger?: {
345
- readonly on: NameOrRef;
346
- };
347
- readonly recipient?: NotificationRecipientFn;
348
- readonly data?: NotificationDataFn;
349
- readonly templates?: Readonly<Record<string, NotificationTemplateFn>>;
350
- readonly variables?: TemplateVariables;
351
- readonly record?: NotificationRecordMapping;
352
- readonly template?: Readonly<Record<string, NotificationTemplateBody>>;
353
- readonly meta?: Readonly<Record<string, NotificationTemplateMeta>>;
354
- readonly localizedTemplate?: () => NotificationLocalizedTemplate;
355
- readonly renamedFrom?: readonly string[];
356
- readonly systemOnly?: boolean;
357
- readonly unsubscribe?: boolean;
358
- readonly tenantTransport?: boolean;
359
- }): void;
360
- translations(def: TranslationsDef): void;
361
- defineEvent<const TInner extends string, TPayload>(name: TInner, schema: ZodType<TPayload>, options: {
362
- readonly version?: number;
363
- readonly piiFields: EventPiiStance;
364
- readonly migrations?: readonly {
365
- readonly fromVersion: number;
366
- readonly toVersion: number;
367
- readonly transform: EventUpcastFn | DeclarativeEventMigration;
368
- }[];
369
- }): EventDef<TPayload, QualifiedEventName<TFeature, TInner>>;
370
- readsConfig(...qualifiedKeys: string[]): void;
371
- readsConfig(options: {
372
- readonly keys: readonly string[];
373
- }): void;
374
- referenceData(entity: NameOrRef, data: readonly Record<string, unknown>[], options?: {
375
- upsertKey?: string;
376
- }): void;
377
- referenceData(definition: {
378
- readonly entity: NameOrRef;
379
- readonly data: readonly Record<string, unknown>[];
380
- readonly upsertKey?: string;
381
- }): void;
382
- extendsRegistrar(name: string, def: RegistrarExtensionDef): void;
383
- useExtension<N extends string>(extensionName: N, entity: NameOrRef, ...options: ExtensionOptionsArgs<N>): void;
384
- useExtension<N extends string>(definition: {
385
- readonly name: N;
386
- readonly entity: NameOrRef;
387
- } & ExtensionOptionsFor<N>): void;
388
- /**
389
- * Declares which config key selects the active provider under an
390
- * extension point — called by the point-owning foundation (e.g.
391
- * `r.extensionSelector("mailTransport", configKeys.provider)`).
392
- * Readiness gating counts a provider-feature's `required` keys and
393
- * secrets only while that provider is the selected one. Registry-build
394
- * fails on duplicate declarations per extension and on selector keys
395
- * that no mounted feature declares. A masked tenant selector key makes the
396
- * Settings-Hub render one tenant dashboard for the owner: the selector as a
397
- * select of the mounted plugin ids, the selected plugin's config and secrets
398
- * as panels, and plugin features lose their own tenant nav. `config:write:set`
399
- * rejects unknown plugin ids (`""` clears). `options.panels` adds the owner's own
400
- * `custom`/`screen` panels to that dashboard, after the selection and before the
401
- * plugin panels (a short `screen` id means a screen of the declaring feature).
402
- */
403
- extensionSelector(extensionName: string, key: {
404
- readonly name: string;
405
- } | string, options?: {
406
- readonly panels?: readonly ExtensionSelectorPanel[];
407
- }): void;
408
- /**
409
- * Marker declaration: this feature provides a cross-feature API under the
410
- * given name. The implementation is registered separately as a query or
411
- * write handler under the QN pattern; `r.exposesApi` is only a boot-check
412
- * surface.
413
- *
414
- * The boot validator checks that every `r.usesApi(name)` finds a matching
415
- * exposer, that the exposer feature is listed in requires/optionalRequires,
416
- * and that no API name is exposed more than once.
417
- *
418
- * ```ts
419
- * defineFeature("compliance-profiles", (r) => {
420
- * r.exposesApi("compliance.forTenant");
421
- * r.queryHandler({
422
- * name: "compliance:query:for-tenant",
423
- * // ... actual implementation
424
- * });
425
- * });
426
- * ```
427
- */
428
- exposesApi(apiName: string): void;
429
- /**
430
- * Declares that this feature calls a cross-feature API. The boot validator
431
- * checks that another feature provides `r.exposesApi(apiName)` and that
432
- * this feature lists it in `r.requires/optionalRequires`.
433
- *
434
- * ```ts
435
- * defineFeature("user-data-rights", (r) => {
436
- * r.requires({ features: ["compliance-profiles"] });
437
- * r.usesApi("compliance.forTenant");
438
- * });
439
- * ```
440
- */
441
- usesApi(apiName: string): void;
442
- /**
443
- * Declares a boot-time mount-invariant for this feature. `fn` runs once
444
- * at boot with a ctx exposing every mounted feature — throw to fail the
445
- * boot with a clear message. Multiple calls per feature are allowed.
446
- *
447
- * Use for cross-feature invariants that `r.requires` can't express
448
- * because they're conditional (only fail when the feature has a
449
- * specific shape), e.g. "if this feature has PII entities, some
450
- * user-data-hook feature must be mounted" (the prompt-store trap —
451
- * a UserData feature was written but never mounted, and nothing caught
452
- * it at boot):
453
- *
454
- * ```ts
455
- * defineFeature("prompt-store", (r) => {
456
- * const promptFields = { text: createTextField({ personal: "self", find: "none" }) };
457
- * r.entity({ name: "prompt", fields: promptFields });
458
- * r.bootCheck(({ features }) => {
459
- * // Conditional on this feature's own shape (has a pii field) —
460
- * // r.requires({ features: ["user-data-hook"] }) can't express that, it would fail
461
- * // even for a prompt-store variant with no PII fields at all.
462
- * const hasPiiField = Object.values(promptFields).some((f) => f.pii);
463
- * const hasUserDataHook = features.some((f) => f.name === "user-data-hook");
464
- * if (hasPiiField && !hasUserDataHook) {
465
- * throw new Error("prompt-store has PII fields but no user-data-hook feature is mounted");
466
- * }
467
- * });
468
- * });
469
- * ```
470
- */
471
- bootCheck(fn: BootCheckFn): void;
472
- metric(shortName: string, options: MetricOptions): void;
473
- metric(definition: {
474
- readonly name: string;
475
- } & MetricOptions): void;
476
- secret(shortName: string, options: SecretOptions): SecretKeyHandle;
477
- secret(definition: {
478
- readonly name: string;
479
- } & SecretOptions): SecretKeyHandle;
480
- secretNamespace(shortName: string, options: SecretNamespaceOptions): SecretNamespaceHandle;
481
- secretNamespace(definition: {
482
- readonly name: string;
483
- } & SecretNamespaceOptions): SecretNamespaceHandle;
484
- projection(definition: ProjectionDefinition): void;
485
- multiStreamProjection(definition: MultiStreamProjectionDefinition): void;
486
- extendEntityProjection(entityName: string, extension: EntityProjectionExtension): void;
487
- authClaims(fn: AuthClaimsFn): void;
488
- claimKey<T extends ClaimKeyType>(shortName: string, options: {
489
- readonly type: T;
490
- }): ClaimKeyHandle<T>;
491
- claimKey<T extends ClaimKeyType>(definition: {
492
- readonly name: string;
493
- readonly type: T;
494
- }): ClaimKeyHandle<T>;
495
- screen(definition: ScreenDefinition): void;
496
- nav(definition: NavDefinition): void;
497
- contentCollection(definition: ContentCollectionDefinition): string;
498
- workspace(definition: WorkspaceDefinition): void;
499
- httpRoute(definition: HttpRouteDefinition): void;
500
- webSocketRoute(definition: WebSocketRouteDefinition): void;
501
- storeTable(meta: EntityTableMeta, options: StoreTableOptions): void;
502
- treeActions<const TActions extends Record<string, TreeActionDef>>(actions: TActions): TreeActionsHandle<TFeature, TActions>;
503
- envSchema(schema: z.ZodObject<z.ZodRawShape>): void;
504
- };
505
198
  export type Registry = {
506
199
  readonly features: ReadonlyMap<string, FeatureDefinition>;
507
200
  getFeature(name: string): FeatureDefinition | undefined;
@@ -535,7 +228,6 @@ export type Registry = {
535
228
  getPostSaveHooks(name: string, phase?: HookPhase, effectiveFeatures?: ReadonlySet<string>): readonly PostSaveHookFn[];
536
229
  getPreDeleteHooks(name: string, phase?: HookPhase, effectiveFeatures?: ReadonlySet<string>): readonly PreDeleteHookFn[];
537
230
  getPostDeleteHooks(name: string, phase?: HookPhase, effectiveFeatures?: ReadonlySet<string>): readonly PostDeleteHookFn[];
538
- getPreQueryHooks(name: string, effectiveFeatures?: ReadonlySet<string>): readonly PreQueryHookFn[];
539
231
  getPostQueryHooks(name: string, effectiveFeatures?: ReadonlySet<string>): readonly PostQueryHookFn[];
540
232
  getEntityPostSaveHooks(entityName: string, phase?: HookPhase, effectiveFeatures?: ReadonlySet<string>): readonly PostSaveHookFn[];
541
233
  getEntityPreDeleteHooks(entityName: string, phase?: HookPhase, effectiveFeatures?: ReadonlySet<string>): readonly PreDeleteHookFn[];
@@ -595,4 +287,3 @@ export type Registry = {
595
287
  getDefaultWorkspace(): WorkspaceDefinition | undefined;
596
288
  getTreeActions(featureName: string): Readonly<Record<string, TreeActionDef>> | undefined;
597
289
  };
598
- export {};
package/dist/fields.d.ts CHANGED
@@ -103,7 +103,7 @@ export type TextFieldDef = {
103
103
  * (list/detail/write responses) show `true` = set, `null` = empty. Writes:
104
104
  * `""` or omitted = unchanged, `null` = clear, string = new value.
105
105
  * Server-side: executor reads (detail/list in server handlers) and
106
- * postSave/postQuery hooks see the plaintext; ctx.query, dispatcher.query,
106
+ * postSave/postQuery hooks see the plaintext; ctx.dispatch.query, dispatcher.query,
107
107
  * agent tools and `_refs` see true/null; ctx.db sees the ciphertext.
108
108
  * Known limit: masking runs for entity-bound query handlers with array /
109
109
  * {rows} / flat results; an unbound custom query handler that returns
@@ -0,0 +1,109 @@
1
+ /// <reference types="temporal-polyfill/global" preserve="true" />
2
+ import type { KumikoEventTypeMap } from "./event-type-map.ts";
3
+ import type { HandlerLifecycle } from "./handler-context-services.ts";
4
+ import type { ActiveMembershipResult, AggregateStreamHandle, AppendEventFn, FetchForWritingArgs, HandlerContext, MemberReader, SessionUser, TryAppendEventFn, UnsafeAppendEventFn, WriteResult } from "./handlers.ts";
5
+ import type { TenantId } from "./identifiers.ts";
6
+ import type { Logger } from "./logger.ts";
7
+ import type { Meter, MetricsHandle, Tracer } from "./observability/index.ts";
8
+ import type { TenantDb, UncheckedSystemDb } from "./tenant-db-types.ts";
9
+ /**
10
+ * `ctx.db` plus `outsideTransaction`: the same tenant scoping, but never bound to the handler's
11
+ * tx, so writes through it survive a rollback. `undefined` only in the denial phases
12
+ * (member-resolution, before-transaction) and in platform-metering tenant mode; narrow with
13
+ * `if (ctx.db.outsideTransaction)`. For atomic multi-statement writes outside the tx use
14
+ * `runInOwnTransaction(ctx.db.outsideTransaction, fn)`; `fn` must not touch rows the handler's own
15
+ * tx has locked. For r.systemScope() handlers `ctx.db` is a fail-closed guard; use
16
+ * `systemDb.outsideTransaction`.
17
+ */
18
+ export type HandlerDb = TenantDb & {
19
+ readonly outsideTransaction: TenantDb | undefined;
20
+ };
21
+ /** Event-store surface of a handler. TMap propagates the strict event-type-map through `append`. */
22
+ export type HandlerEvents<TMap extends object = KumikoEventTypeMap> = {
23
+ /**
24
+ * Default append: a typed domain event on one aggregate stream in the current tx. The runtime
25
+ * bumps the stream version and fires matching projections in the same transaction. A version
26
+ * conflict throws and poisons the transaction.
27
+ */
28
+ readonly append: AppendEventFn<TMap>;
29
+ /**
30
+ * Untyped append (`payload: unknown`) with the same runtime as `append`. Only for
31
+ * migrations and upcasters, where the event type is not knowable at compile time.
32
+ */
33
+ readonly unsafeAppend: UnsafeAppendEventFn;
34
+ /**
35
+ * Optimistic append: runs in a savepoint and returns a version conflict as a result instead of
36
+ * throwing, so the handler can continue after losing a race on the same aggregate stream.
37
+ */
38
+ readonly tryAppend: TryAppendEventFn;
39
+ readonly fetchForWriting: (args: FetchForWritingArgs) => Promise<AggregateStreamHandle>;
40
+ readonly load: (aggregateId: string, options?: {
41
+ readonly asOf?: Temporal.Instant;
42
+ readonly atVersion?: never;
43
+ } | {
44
+ readonly atVersion?: number;
45
+ readonly asOf?: never;
46
+ }) => Promise<readonly import("./event-store-types.ts").StoredEvent[]>;
47
+ readonly archive: (aggregateId: string, args: {
48
+ readonly aggregateType: string;
49
+ readonly reason?: string;
50
+ }) => Promise<void>;
51
+ readonly restore: (aggregateId: string) => Promise<void>;
52
+ readonly isArchived: (aggregateId: string) => Promise<boolean>;
53
+ readonly snapshot: (args: {
54
+ readonly aggregateId: string;
55
+ readonly aggregateType: string;
56
+ readonly version: number;
57
+ readonly state: Record<string, unknown>;
58
+ readonly snapshotVersion?: number;
59
+ }) => Promise<void>;
60
+ readonly loadWithSnapshot: <TState extends Record<string, unknown>>(aggregateId: string, reducer: import("./snapshot-types.ts").SnapshotReducer<TState>, initial: TState, options?: Omit<import("./snapshot-types.ts").LoadAggregateWithSnapshotOptions, "upcastEvent">) => Promise<import("./snapshot-types.ts").LoadAggregateWithSnapshotResult<TState>>;
61
+ };
62
+ /**
63
+ * Cross-feature bridge: `query`/`write` run the target handler AS THE CURRENT USER, sharing the
64
+ * active tx + afterCommit queue. `queryAs`/`writeAs` switch identity; any target other than the
65
+ * caller itself (or a subset of its roles) needs r.systemScope(), a job, or an escapeHatch.
66
+ */
67
+ export type HandlerDispatch = {
68
+ readonly query: (qn: string, payload: unknown) => Promise<unknown>;
69
+ readonly queryAs: (user: SessionUser, qn: string, payload: unknown) => Promise<unknown>;
70
+ readonly write: (qn: string, payload: unknown) => Promise<WriteResult>;
71
+ readonly writeAs: (user: SessionUser, qn: string, payload: unknown) => Promise<WriteResult>;
72
+ readonly queryAsMember: MemberReader;
73
+ readonly queryProjection: <T = Record<string, unknown>>(qualifiedName: string, options?: {
74
+ readonly unsafeAllTenants?: boolean;
75
+ }) => Promise<readonly T[]>;
76
+ };
77
+ export type HandlerAuth = {
78
+ readonly resolveClaims: (user: SessionUser) => Promise<Record<string, unknown>>;
79
+ readonly resolveActiveMembership: (userId: string, tenantId: TenantId) => Promise<ActiveMembershipResult>;
80
+ };
81
+ export type HandlerObservability = {
82
+ readonly log: Logger;
83
+ readonly tracer: Tracer;
84
+ readonly metrics: MetricsHandle;
85
+ readonly metricsFor: (featureName: string) => MetricsHandle;
86
+ readonly meter?: Meter;
87
+ };
88
+ export type JobEvents = Pick<HandlerEvents, "append">;
89
+ export type JobDispatch = Pick<HandlerDispatch, "write" | "writeAs" | "queryAs" | "queryAsMember">;
90
+ export type JobObservability = Pick<HandlerObservability, "log" | "metricsFor" | "meter">;
91
+ type PhaseWithoutTransaction = "events" | "notify" | "dispatch" | "lifecycle";
92
+ /**
93
+ * ctx of `reserveBeforeTransaction`: nothing bound to the not-yet-open transaction. Reservations
94
+ * write on purpose through `db.outsideTransaction`, so it stays.
95
+ */
96
+ export type ReservationContext<TMap extends object = KumikoEventTypeMap> = Omit<HandlerContext<TMap>, PhaseWithoutTransaction> & {
97
+ readonly dispatch: Omit<HandlerDispatch, "write" | "writeAs">;
98
+ readonly lifecycle: Omit<HandlerLifecycle, "afterCommit" | "afterRollback">;
99
+ };
100
+ /** ctx of `beforeTransaction`: external I/O and reads only, so the outside-transaction handles are gone too. */
101
+ export type BeforeTransactionContext<TMap extends object = KumikoEventTypeMap> = Omit<ReservationContext<TMap>, "db" | "systemDb"> & {
102
+ readonly db: TenantDb & {
103
+ readonly outsideTransaction: undefined;
104
+ };
105
+ readonly systemDb?: Omit<UncheckedSystemDb, "outsideTransaction"> & {
106
+ readonly outsideTransaction: undefined;
107
+ };
108
+ };
109
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,82 @@
1
+ import type { Redis } from "ioredis";
2
+ import type { CacheSyncBus } from "./cache-sync-types.ts";
3
+ import type { ConfigAccessor, ConfigResolver } from "./config.ts";
4
+ import type { DerivativesContext } from "./derivatives-types.ts";
5
+ import type { EntityCache } from "./entity-cache.ts";
6
+ import type { EnvelopeCipher } from "./envelope-cipher-types.ts";
7
+ import type { FileContext } from "./file-handle-types.ts";
8
+ import type { FileProviderResolver } from "./file-provider-resolver-types.ts";
9
+ import type { EscapeHatchAuditSink, JobRunnerRef, NotifyFactory, NotifyFn, SessionUser, TenantWriteTarget } from "./handlers.ts";
10
+ import type { TenantId } from "./identifiers.ts";
11
+ import type { KmsAdapter } from "./kms-adapter-types.ts";
12
+ import type { RateLimitResolver } from "./rate-limit-types.ts";
13
+ import type { SearchAdapter } from "./search-adapter.ts";
14
+ import type { MasterKeyProvider, SecretsContext } from "./secrets-types.ts";
15
+ /**
16
+ * Infrastructure the app wired at boot, as the handler or job sees it. extraContext keys the
17
+ * framework does not know land in `extensions`.
18
+ */
19
+ export type ContextServices = {
20
+ readonly redis?: Redis;
21
+ readonly cacheSync?: CacheSyncBus;
22
+ readonly jobRunner?: JobRunnerRef;
23
+ readonly searchAdapter?: SearchAdapter;
24
+ readonly entityCache?: EntityCache;
25
+ readonly secrets?: SecretsContext;
26
+ readonly kms?: KmsAdapter;
27
+ readonly files?: FileContext;
28
+ readonly derivatives?: DerivativesContext;
29
+ readonly configResolver?: ConfigResolver;
30
+ readonly configEncryption?: EnvelopeCipher;
31
+ readonly rateLimit?: RateLimitResolver;
32
+ readonly effectiveFeatures?: (tenantId: TenantId) => ReadonlySet<string>;
33
+ readonly masterKeyProvider?: MasterKeyProvider;
34
+ readonly _fileProviderResolver?: FileProviderResolver;
35
+ readonly _notifyFactory?: NotifyFactory;
36
+ readonly _notifyInTransactionFactory?: (user: SessionUser, tenantId: TenantId) => NotifyFn;
37
+ readonly _notifyAfterCommitFactory?: (user: SessionUser, tenantId: TenantId) => NotifyFn;
38
+ readonly _escapeHatchAuditSink?: EscapeHatchAuditSink;
39
+ readonly extensions: Readonly<Record<string, unknown>>;
40
+ };
41
+ export type HandlerLifecycle = {
42
+ /**
43
+ * Runs the registered `preSave` hooks (and system preSave hooks) for `handlerName` and returns
44
+ * the hook-transformed `changes`. Entity CRUD handlers forward it to the executor; a custom
45
+ * `r.writeHandler` that bypasses the executor must call it itself. Undefined when no lifecycle
46
+ * pipeline is wired (bare stubs in unit tests).
47
+ */
48
+ readonly runPreSave?: (handlerName: string, changes: Record<string, unknown>, previous: Readonly<Record<string, unknown>>, isNew: boolean) => Promise<Record<string, unknown>>;
49
+ /**
50
+ * Queues a side-effect to run after the outermost write transaction commits (same sink as
51
+ * ctx.dispatch.write). Absent outside a write pipeline; callers fall back to firing immediately.
52
+ */
53
+ readonly afterCommit?: (hook: () => Promise<void>) => void;
54
+ /**
55
+ * Runs when the handler transaction (or enclosing savepoint) does not commit, also after a failed
56
+ * COMMIT of unknown outcome, so write it idempotent and outcome-safe. Present only inside a
57
+ * transaction scope.
58
+ */
59
+ readonly afterRollback?: (hook: () => Promise<void>) => void;
60
+ /**
61
+ * Cancellation. Aborts when the HTTP client disconnects. Undefined for non-HTTP entry points
62
+ * (jobs, MSP-applies). Long-running handlers throw `signal.throwIfAborted()` at chunk boundaries.
63
+ */
64
+ readonly signal?: AbortSignal;
65
+ };
66
+ export type JobLifecycle = Pick<HandlerLifecycle, "signal">;
67
+ export type HandlerTenants = {
68
+ /**
69
+ * Config accessor resolved for ANOTHER tenant, identity = that tenant's system user. Cross-tenant
70
+ * calls throw AccessDeniedError unless the caller is a system identity or has the SystemAdmin
71
+ * role; the caller's own tenant needs no privilege. Present when the config feature wired its
72
+ * accessor factory.
73
+ */
74
+ readonly config?: (tenantId: TenantId) => ConfigAccessor;
75
+ /**
76
+ * Write target for ANOTHER tenant: a tenant-mode db bound to it plus the `streamTenantId` to hand
77
+ * the executor, so the event actor stays the operator. Cross-tenant calls throw AccessDeniedError
78
+ * unless the caller is SystemAdmin; the caller's own tenant yields `streamTenantId: undefined`.
79
+ * Write handlers only; absent for r.systemScope() handlers and without a db.
80
+ */
81
+ readonly writeTarget?: (tenantId: TenantId) => TenantWriteTarget;
82
+ };
@@ -0,0 +1 @@
1
+ export {};