@cosmicdrift/kumiko-types 0.389.0 → 0.390.1

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/config.d.ts CHANGED
@@ -4,7 +4,7 @@ import type { ConfigScope } from "./config-scope.ts";
4
4
  import type { DbConnection } from "./db-connection.ts";
5
5
  import type { EntityFieldDefinition, OptionsQueryPayload } from "./fields.ts";
6
6
  import type { EscapeHatchDeclaration, JobContext, JobDeferral } from "./handlers.ts";
7
- import type { PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn } from "./hooks.ts";
7
+ import type { PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreSaveHookFn } from "./hooks.ts";
8
8
  import type { TenantId } from "./identifiers.ts";
9
9
  import type { NavIconKey } from "./nav-icon.ts";
10
10
  import type { DashboardCustomPanel, DashboardScreenPanel } from "./screen.ts";
@@ -283,7 +283,6 @@ export type RegistrarExtensionHooks = {
283
283
  readonly postSave?: PostSaveHookFn;
284
284
  readonly preDelete?: PreDeleteHookFn;
285
285
  readonly postDelete?: PostDeleteHookFn;
286
- readonly preQuery?: PreQueryHookFn;
287
286
  };
288
287
  export type UiExtensionDef = {
289
288
  readonly editSection?: string;
@@ -100,7 +100,7 @@ export type EventStoreExecutor<Row extends Record<string, unknown> = Record<stri
100
100
  /** Per-call SearchAdapter override. When the
101
101
  * executor didn't get a SearchAdapter via Options at build time
102
102
  * (defaultEntityQueryHandler path), the caller (handler) can pass
103
- * one from ctx.searchAdapter here at runtime.
103
+ * one from ctx.services.searchAdapter here at runtime.
104
104
  * options.searchAdapter (build-time) wins — the runtime override
105
105
  * is the fallback for the default wrapper. */
106
106
  runtimeOptions?: ListRuntimeOptions) => Promise<CursorResult<Row>>;
@@ -1,6 +1,6 @@
1
1
  // Cross-Feature Compile-Time Type Map.
2
2
  //
3
- // Purpose: check ctx.appendEvent / ctx.queryProjection / dispatcher.write against a
3
+ // Purpose: check ctx.events.append / ctx.dispatch.queryProjection / dispatcher.write against a
4
4
  // statically known schema image instead of failing only at boot or runtime
5
5
  // (zod-validate). The Designer/AI layer benefits immediately: autocomplete knows all
6
6
  // event types from all loaded features, and payload-shape mismatches appear in the
@@ -9,7 +9,7 @@ import type { ExtensionOptionsArgs, ExtensionOptionsFor } from "./extension-opti
9
9
  import type { BootCheckFn, MetricOptions, SecretKeyHandle, SecretNamespaceHandle, SecretNamespaceOptions, SecretOptions, StoreTableOptions, UiHints } from "./feature.ts";
10
10
  import type { EntityDefinition } from "./fields.ts";
11
11
  import type { AccessRule, AuthClaimsFn, ClaimKeyHandle, ClaimKeyType, DeclarativeEventMigration, EntityRef, EscapeHatchDeclaration, EventDef, EventPiiStance, EventUpcastFn, HandlerRef, NameOrRef, QualifiedEventName, RateLimitDeclaration, StreamHandlerFn } from "./handlers.ts";
12
- import type { HookPhase, PostDeleteHookFn, PostQueryHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, SearchPayloadContributorFn, ValidationHookFn } from "./hooks.ts";
12
+ import type { HookPhase, PostDeleteHookFn, PostQueryHookFn, PostSaveHookFn, PreDeleteHookFn, PreSaveHookFn, SearchPayloadContributorFn, ValidationHookFn } from "./hooks.ts";
13
13
  import type { HttpRouteDefinition } from "./http-route.ts";
14
14
  import type { ContentCollectionDefinition, NavDefinition } from "./nav.ts";
15
15
  import type { EntityProjectionExtension, MultiStreamProjectionDefinition, ProjectionDefinition } from "./projection.ts";
@@ -57,11 +57,6 @@ export type HookDefinition = {
57
57
  readonly target: HookTarget;
58
58
  readonly handler: PostDeleteHookFn;
59
59
  } & HookEscapeHatch & HookPhaseOption) | ({
60
- readonly type: "preQuery";
61
- readonly target: RefOrRefs;
62
- readonly handler: PreQueryHookFn;
63
- readonly phase?: never;
64
- } & HookEscapeHatch) | ({
65
60
  readonly type: "postQuery";
66
61
  readonly target: HookTarget;
67
62
  readonly handler: PostQueryHookFn;
@@ -88,7 +83,7 @@ export type RequiresApi = ((options: {
88
83
  * features (apps that opt into the literal-name flavour) get propagated
89
84
  * through to `defineEvent` so the returned `EventDef.name` is a literal
90
85
  * `${CamelToKebab<TFeature>}:event:${CamelToKebab<TInner>}`. That literal
91
- * threads through `ctx.appendEvent({ type: eventDef.name, ... })`,
86
+ * threads through `ctx.events.append({ type: eventDef.name, ... })`,
92
87
  * keeping strict-mode alive even when handlers route via `eventDef.name`
93
88
  * instead of hand-typed string literals.
94
89
  */
package/dist/feature.d.ts CHANGED
@@ -4,7 +4,7 @@ import type { ConfigKeyDefinition, ConfigSeedDef, ExtensionSelectorDef, Extensio
4
4
  import type { EntityTableMeta } from "./entity-table-meta-types.ts";
5
5
  import type { EntityDefinition } from "./fields.ts";
6
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, PreQueryHookFn, PreSaveHookFn, SearchPayloadContributorFn } from "./hooks.ts";
7
+ import type { EntityHookMap, HookMap, HookPhase, OwnedFn, PostDeleteHookFn, PostQueryHookFn, PostSaveHookFn, PreDeleteHookFn, PreSaveHookFn, SearchPayloadContributorFn } from "./hooks.ts";
8
8
  import type { HttpRouteDefinition } from "./http-route.ts";
9
9
  import type { ContentCollectionDefinition, NavDefinition } from "./nav.ts";
10
10
  import type { EntityProjectionExtension, MultiStreamProjectionDefinition, ProjectionDefinition } from "./projection.ts";
@@ -228,7 +228,6 @@ export type Registry = {
228
228
  getPostSaveHooks(name: string, phase?: HookPhase, effectiveFeatures?: ReadonlySet<string>): readonly PostSaveHookFn[];
229
229
  getPreDeleteHooks(name: string, phase?: HookPhase, effectiveFeatures?: ReadonlySet<string>): readonly PreDeleteHookFn[];
230
230
  getPostDeleteHooks(name: string, phase?: HookPhase, effectiveFeatures?: ReadonlySet<string>): readonly PostDeleteHookFn[];
231
- getPreQueryHooks(name: string, effectiveFeatures?: ReadonlySet<string>): readonly PreQueryHookFn[];
232
231
  getPostQueryHooks(name: string, effectiveFeatures?: ReadonlySet<string>): readonly PostQueryHookFn[];
233
232
  getEntityPostSaveHooks(entityName: string, phase?: HookPhase, effectiveFeatures?: ReadonlySet<string>): readonly PostSaveHookFn[];
234
233
  getEntityPreDeleteHooks(entityName: string, phase?: HookPhase, effectiveFeatures?: ReadonlySet<string>): readonly PreDeleteHookFn[];
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 {};
@@ -11,11 +11,15 @@ import type { KumikoEventTypeMap } from "./event-type-map.ts";
11
11
  import type { FileContext } from "./file-handle-types.ts";
12
12
  import type { FileProviderResolver } from "./file-provider-resolver-types.ts";
13
13
  import type { GeoTzProvider } from "./geo-tz.ts";
14
+ import type { BeforeTransactionContext, HandlerAuth, HandlerDb, HandlerDispatch, HandlerEvents, HandlerObservability, JobDispatch, JobEvents, JobObservability, ReservationContext } from "./handler-context-facets.ts";
15
+ import type { ContextServices, HandlerLifecycle, HandlerTenants, JobLifecycle } from "./handler-context-services.ts";
14
16
  import type { Logger } from "./logger.ts";
15
- import type { Meter, MetricsHandle, Tracer } from "./observability/index.ts";
17
+ import type { Meter, Tracer } from "./observability/index.ts";
16
18
  import type { SearchAdapter } from "./search-adapter.ts";
17
19
  import type { CrossTenantReads, TenantDb } from "./tenant-db-types.ts";
18
20
  import type { Clock, TzContext } from "./tz-context.ts";
21
+ export type { BeforeTransactionContext, HandlerAuth, HandlerDb, HandlerDispatch, HandlerEvents, HandlerObservability, JobDispatch, JobEvents, JobObservability, ReservationContext, } from "./handler-context-facets.ts";
22
+ export type { ContextServices, HandlerLifecycle, HandlerTenants, JobLifecycle, } from "./handler-context-services.ts";
19
23
  export type OpenToAllPersonalData = "tenant-members";
20
24
  export type OpenToAllDeclaration = {
21
25
  readonly reason: string;
@@ -214,27 +218,12 @@ type SharedContextFields = {
214
218
  readonly jobRunner?: JobRunnerRef;
215
219
  readonly configResolver?: ConfigResolver;
216
220
  readonly config?: ConfigAccessor;
217
- /**
218
- * Config accessor resolved for ANOTHER tenant, identity = that tenant's system
219
- * user (the caller's user-scope config never leaks in). Cross-tenant calls
220
- * throw AccessDeniedError unless the caller is a system identity or has the
221
- * SystemAdmin role; the caller's own tenant needs no privilege. Present when
222
- * the config feature wired its accessor factory.
223
- */
221
+ /** Becomes `ctx.tenants.config`. */
224
222
  readonly configFor?: (tenantId: TenantId) => ConfigAccessor;
225
- /**
226
- * Write target for ANOTHER tenant: a tenant-mode db bound to it plus the
227
- * `streamTenantId` to hand the executor, so the event actor stays the
228
- * operator. Cross-tenant calls throw AccessDeniedError unless the caller is
229
- * SystemAdmin (the tenantIdOverride gate); the caller's own tenant needs no
230
- * privilege and yields `streamTenantId: undefined`. Write handlers only;
231
- * absent for r.systemScope() handlers and without a db.
232
- */
223
+ /** Becomes `ctx.tenants.writeTarget`. */
233
224
  readonly forTenant?: (tenantId: TenantId) => TenantWriteTarget;
234
225
  /** Mints the per-request `ctx.config`; set by the boot (`addConfigAccessorFactory`) from the effective `configResolver`, overriding an app-supplied value. */
235
226
  readonly configAccessorFactory?: ConfigAccessorFactory;
236
- /** @deprecated Use `configAccessorFactory`; the underscore name is removed in 0.390.0. */
237
- readonly _configAccessorFactory?: ConfigAccessorFactory;
238
227
  readonly configEncryption?: import("./envelope-cipher-types.ts").EnvelopeCipher;
239
228
  readonly rateLimit?: import("./rate-limit-types.ts").RateLimitResolver;
240
229
  readonly _rateLimitPayloadDigest?: (value: string) => string;
@@ -255,11 +244,13 @@ type SharedContextFields = {
255
244
  readonly meter?: Meter;
256
245
  readonly signal?: AbortSignal;
257
246
  readonly effectiveFeatures?: (tenantId: TenantId) => ReadonlySet<string>;
247
+ /** Becomes `ctx.lifecycle.afterCommit`. */
258
248
  readonly scheduleAfterCommit?: (hook: () => Promise<void>) => void;
249
+ /** Becomes `ctx.lifecycle.afterRollback`. */
259
250
  readonly scheduleAfterRollback?: (hook: () => Promise<void>) => void;
260
- readonly queryAsMember?: MemberReader;
261
251
  };
262
252
  export type AppContext = SharedContextFields & {
253
+ readonly services?: never;
263
254
  readonly db?: DbConnection | TenantDb;
264
255
  readonly systemDb?: UncheckedSystemDb;
265
256
  readonly registry?: Registry;
@@ -285,7 +276,7 @@ export type AppContext = SharedContextFields & {
285
276
  /** Tenant of the current pipeline call. The dispatcher mirrors
286
277
  * `user.tenantId` here when building HandlerContext so the lifecycle
287
278
  * pipeline and system hooks can access it without the user object.
288
- * `effectiveFeatures(ctx._tenantId)` uses it at hook-filter points. */
279
+ * `effectiveFeatures(ctx.user.tenantId)` uses it at hook-filter points. */
289
280
  readonly _tenantId?: TenantId;
290
281
  /** Boot-configured default locale (BCP-47). Last fallback in ctx.locale's
291
282
  * Request-Locale → Boot-Default chain (dispatch-shared.ts) — used when
@@ -293,18 +284,8 @@ export type AppContext = SharedContextFields & {
293
284
  * Absent falls back to "en". */
294
285
  readonly defaultLocale?: string;
295
286
  };
296
- export type HandlerContext<TMap extends object = KumikoEventTypeMap> = SharedContextFields & {
297
- readonly db: TenantDb;
298
- /**
299
- * Same tenant scoping as `db`, but never bound to the handler's tx: writes through it survive
300
- * a rollback. `undefined` only in the denial phases (member-resolution, before-transaction)
301
- * and in platform-metering tenant mode; narrow with `hasOutsideTransactionDb(ctx)` or take it
302
- * via `requireOutsideTransactionDb(ctx)` (`@cosmicdrift/kumiko-framework/db`). For atomic
303
- * multi-statement writes outside the tx use `runInOwnTransaction(ctx.dbOutsideTransaction, fn)`; `fn`
304
- * must not touch rows the handler's own tx has locked. For
305
- * r.systemScope() handlers it is a fail-closed Proxy; use `systemDb.outsideTransaction`.
306
- */
307
- readonly dbOutsideTransaction: TenantDb | undefined;
287
+ export type HandlerContext<TMap extends object = KumikoEventTypeMap> = {
288
+ readonly db: HandlerDb;
308
289
  readonly systemDb?: UncheckedSystemDb;
309
290
  readonly crossTenantReads?: CrossTenantReads;
310
291
  readonly registry: Registry;
@@ -314,84 +295,35 @@ export type HandlerContext<TMap extends object = KumikoEventTypeMap> = SharedCon
314
295
  * The value is identical to `event.user`; identity switches still use
315
296
  * `queryAs`/`writeAs`. */
316
297
  readonly user: SessionUser;
317
- readonly systemUser?: SessionUser;
318
- readonly log?: Logger;
319
- readonly triggeredBy?: {
320
- readonly id: string;
321
- readonly tenantId: TenantId;
322
- } | null;
323
298
  /** Keyed digest for handler-owned rate-limit keys, so Redis never holds a
324
299
  * plaintext or plainly hashed identifier. Scoped to the running handler:
325
300
  * the same input yields a different digest per handler and never matches a
326
301
  * dispatcher payload-bucket digest. Absent when the app wires no digest key. */
327
302
  readonly rateLimitDigest?: (value: string) => string;
328
303
  readonly includeDeleted?: boolean;
329
- readonly runPreSave?: (handlerName: string, changes: Record<string, unknown>, previous: Readonly<Record<string, unknown>>, isNew: boolean) => Promise<Record<string, unknown>>;
330
- readonly query: (qn: string, payload: unknown) => Promise<unknown>;
331
- readonly queryAs: (user: SessionUser, qn: string, payload: unknown) => Promise<unknown>;
332
- readonly write: (qn: string, payload: unknown) => Promise<WriteResult>;
333
- readonly writeAs: (user: SessionUser, qn: string, payload: unknown) => Promise<WriteResult>;
304
+ readonly dispatch: HandlerDispatch;
334
305
  readonly hasFeature: (featureName: string) => Promise<boolean>;
335
- /**
336
- * Default append: a typed domain event on one aggregate stream in the current tx. The runtime
337
- * bumps the stream version and fires matching projections in the same transaction. A version
338
- * conflict throws and poisons the transaction.
339
- */
340
- readonly appendEvent: AppendEventFn<TMap>;
341
- /**
342
- * Untyped append (`payload: unknown`) with the same runtime as `appendEvent`. Only for
343
- * migrations and upcasters, where the event type is not knowable at compile time.
344
- */
345
- readonly unsafeAppendEvent: UnsafeAppendEventFn;
346
- /**
347
- * Optimistic append: runs in a savepoint and returns a version conflict as a result instead of
348
- * throwing, so the handler can continue after losing a race on the same aggregate stream.
349
- */
350
- readonly tryAppendEvent: TryAppendEventFn;
351
- readonly fetchForWriting: (args: FetchForWritingArgs) => Promise<AggregateStreamHandle>;
352
- readonly loadAggregate: (aggregateId: string, options?: {
353
- readonly asOf?: Temporal.Instant;
354
- readonly atVersion?: never;
355
- } | {
356
- readonly atVersion?: number;
357
- readonly asOf?: never;
358
- }) => Promise<readonly import("./event-store-types.ts").StoredEvent[]>;
359
- readonly archiveStream: (aggregateId: string, args: {
360
- readonly aggregateType: string;
361
- readonly reason?: string;
362
- }) => Promise<void>;
363
- readonly restoreStream: (aggregateId: string) => Promise<void>;
364
- readonly isStreamArchived: (aggregateId: string) => Promise<boolean>;
365
- readonly snapshotAggregate: (args: {
366
- readonly aggregateId: string;
367
- readonly aggregateType: string;
368
- readonly version: number;
369
- readonly state: Record<string, unknown>;
370
- readonly snapshotVersion?: number;
371
- }) => Promise<void>;
372
- readonly loadAggregateWithSnapshot: <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>>;
373
- readonly queryProjection: <T = Record<string, unknown>>(qualifiedName: string, options?: {
374
- readonly unsafeAllTenants?: boolean;
375
- }) => Promise<readonly T[]>;
376
- readonly metrics: MetricsHandle;
377
- readonly metricsFor: (featureName: string) => MetricsHandle;
378
- readonly tracer: Tracer;
306
+ readonly events: HandlerEvents<TMap>;
307
+ readonly obs: HandlerObservability;
379
308
  readonly tz: TzContext;
380
309
  readonly locale: string;
381
- readonly resolveAuthClaims: (user: SessionUser) => Promise<Record<string, unknown>>;
382
- readonly resolveActiveMembership: (userId: string, tenantId: TenantId) => Promise<ActiveMembershipResult>;
383
- readonly queryAsMember: MemberReader;
310
+ readonly auth: HandlerAuth;
311
+ readonly config?: ConfigAccessor;
312
+ readonly notify?: NotifyFn;
313
+ readonly services: ContextServices;
314
+ readonly lifecycle: HandlerLifecycle;
315
+ readonly tenants: HandlerTenants;
384
316
  };
385
317
  export type JobDeferral = {
386
318
  readonly kind: "job-deferral";
387
319
  readonly delayMs: number;
388
320
  };
389
- export type JobContext = SharedContextFields & {
321
+ export type JobContext = {
390
322
  readonly db: TenantDb;
323
+ readonly events: JobEvents;
391
324
  readonly registry: Registry;
392
325
  readonly systemUser: SessionUser;
393
- readonly log: Logger;
394
- readonly metricsFor: (featureName: string) => MetricsHandle;
326
+ readonly obs: JobObservability;
395
327
  readonly tz: TzContext;
396
328
  readonly triggeredBy: {
397
329
  readonly id: string;
@@ -400,15 +332,17 @@ export type JobContext = SharedContextFields & {
400
332
  readonly attempt: number;
401
333
  readonly finalAttempt: boolean;
402
334
  readonly defer: (delayMs: number) => JobDeferral;
335
+ readonly canDefer: boolean;
403
336
  readonly systemDb?: UncheckedSystemDb;
404
337
  readonly crossTenantReads?: CrossTenantReads;
405
- readonly write: (qn: string, payload: unknown) => Promise<WriteResult>;
406
- readonly writeAs: (user: SessionUser, qn: string, payload: unknown) => Promise<WriteResult>;
407
- readonly queryAs: (user: SessionUser, qn: string, payload: unknown) => Promise<unknown>;
408
- readonly queryAsMember: MemberReader;
338
+ readonly dispatch: JobDispatch;
409
339
  readonly triggerName?: string;
410
340
  readonly triggerEvent?: JobTriggerEvent;
411
341
  readonly _tenantId?: TenantId;
342
+ readonly config?: ConfigAccessor;
343
+ readonly notify?: NotifyFn;
344
+ readonly services: ContextServices;
345
+ readonly lifecycle: JobLifecycle;
412
346
  };
413
347
  export type WriteHandlerFn<TPayload = unknown, TData = unknown> = (event: WriteEvent<TPayload>, context: HandlerContext) => Promise<WriteResult<TData>>;
414
348
  export type QueryHandlerFn<TPayload = unknown, TResult = unknown> = (query: QueryEvent<TPayload>, context: HandlerContext) => Promise<TResult>;
@@ -418,7 +352,7 @@ export type StreamHandlerFn<TPayload = unknown, TChunk = unknown> = (query: Quer
418
352
  * → kebab-case. Drives the literal-type of `EventDef.name`, so that
419
353
  * `r.defineEvent("foo", schema)` inside `defineFeature("driverOrders")`
420
354
  * carries `name: "driver-orders:event:foo"` as a literal — strict-mode
421
- * for `ctx.appendEvent({ type: eventDef.name, ... })` lights up.
355
+ * for `ctx.events.append({ type: eventDef.name, ... })` lights up.
422
356
  *
423
357
  * Algorithm mirrors the runtime regex pipeline:
424
358
  * 1. `.` → `-` (dot acts as word-boundary)
@@ -606,14 +540,14 @@ export type ReservationHandle = {
606
540
  * its own short write instead of holding a second connection while the transaction is open.
607
541
  * The returned release (or handle.release) runs when the transaction does not commit (failure
608
542
  * result, throw, rollback, failed commit). Throwing a KumikoError rejects the write. */
609
- export type PreTransactionReservation = (event: WriteEvent, ctx: HandlerContext) => Promise<ReservationRelease | ReservationHandle | undefined>;
543
+ export type PreTransactionReservation = (event: WriteEvent, ctx: ReservationContext) => Promise<ReservationRelease | ReservationHandle | undefined>;
610
544
  /** 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. */
611
545
  export type BeforeTransactionResult<TPrepared = unknown> = {
612
546
  readonly isSuccess: true;
613
547
  readonly data: TPrepared;
614
548
  readonly release?: ReservationRelease;
615
549
  } | WriteFailure;
616
- export type BeforeTransactionPhase<TPayload = unknown, TPrepared = unknown> = (event: WriteEvent<TPayload>, ctx: HandlerContext) => Promise<BeforeTransactionResult<TPrepared>>;
550
+ export type BeforeTransactionPhase<TPayload = unknown, TPrepared = unknown> = (event: WriteEvent<TPayload>, ctx: BeforeTransactionContext) => Promise<BeforeTransactionResult<TPrepared>>;
617
551
  /** `perCommand`: the handler runs without a transaction of its own. */
618
552
  export type WriteHandlerTransactionMode = "perCommand";
619
553
  export type WriteHandlerDef = {
@@ -635,17 +569,17 @@ export type WriteHandlerDef = {
635
569
  * payload (e.g. `(p) => p.files.length`). Replaces the options' `cost`; results below 1 count as 1. */
636
570
  readonly rateLimitCost?: (payload: unknown) => number;
637
571
  readonly escapeHatch?: EscapeHatchDeclaration;
638
- /** Reserves before the handler transaction opens. Reached through a nested ctx.write the
572
+ /** Reserves before the handler transaction opens. Reached through a nested ctx.dispatch.write the
639
573
  * reservation cannot run, so the dispatcher rejects it unless the top-level batch reserved it. */
640
574
  readonly reserveBeforeTransaction?: PreTransactionReservation;
641
575
  /** Runs after all pre-handler gates (and after reserveBeforeTransaction) and before the
642
576
  * transaction opens: no transaction and no connection held, for external I/O and reads only.
643
577
  * Its `data` reaches the handler as `event.prepared`; a failure result or throw rejects the
644
- * write before anything is written. A handler with this phase cannot run as a nested ctx.write.
645
- * `ctx.forTenant` is available but read-only here: its db denies writes for the own and a
578
+ * write before anything is written. A handler with this phase cannot run as a nested ctx.dispatch.write.
579
+ * `ctx.tenants.writeTarget` is available but read-only here: its db denies writes for the own and a
646
580
  * foreign tenant alike (a foreign tenant stays gated by the SystemAdmin check). */
647
581
  readonly beforeTransaction?: BeforeTransactionPhase;
648
- /** `"perCommand"`: the handler runs without a transaction of its own. Every ctx.write/writeAs it
582
+ /** `"perCommand"`: the handler runs without a transaction of its own. Every ctx.dispatch.write/writeAs it
649
583
  * issues runs as its own top-level command with its own transaction and afterCommit hooks;
650
584
  * ctx.db writes and event appends throw. It cannot run as a nested write or inside a
651
585
  * multi-command batch, and cannot be combined with beforeTransaction/reserveBeforeTransaction. */
@@ -692,4 +626,3 @@ export type StreamHandlerDef = {
692
626
  readonly rateLimit?: RateLimitDeclaration;
693
627
  readonly escapeHatch?: EscapeHatchDeclaration;
694
628
  };
695
- export {};
package/dist/hooks.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { StoredEvent } from "./event-store-types.ts";
2
- import type { AppContext } from "./handlers.ts";
2
+ import type { HandlerContext } from "./handlers.ts";
3
3
  import type { HookPhase } from "./hook-phase.ts";
4
4
  import type { EntityId } from "./identifiers.ts";
5
5
  export type ValidationError = {
@@ -26,23 +26,22 @@ export type DeleteContext = {
26
26
  readonly lockGuardBypassed?: boolean | undefined;
27
27
  };
28
28
  export type LifecycleResult = SaveContext | DeleteContext;
29
- export type PreSaveHookFn = (changes: Record<string, unknown>, context: AppContext & {
29
+ export type PreSaveHookFn = (changes: Record<string, unknown>, context: HandlerContext & {
30
30
  readonly previous: Readonly<Record<string, unknown>>;
31
31
  readonly isNew: boolean;
32
32
  }) => Promise<Record<string, unknown>>;
33
- export type PostSaveHookFn = (result: SaveContext, context: AppContext) => Promise<void>;
34
- export type PostSaveBatchHookFn = (results: readonly SaveContext[], context: AppContext) => Promise<void>;
35
- export type PreDeleteHookFn = (payload: DeleteContext, context: AppContext) => Promise<void>;
36
- export type PostDeleteHookFn = (payload: DeleteContext, context: AppContext) => Promise<void>;
37
- export type PostDeleteBatchHookFn = (payloads: readonly DeleteContext[], context: AppContext) => Promise<void>;
38
- export type PreQueryHookFn = (payload: Record<string, unknown>, context: AppContext) => Promise<Record<string, unknown>>;
33
+ export type PostSaveHookFn = (result: SaveContext, context: HandlerContext) => Promise<void>;
34
+ export type PostSaveBatchHookFn = (results: readonly SaveContext[], context: HandlerContext) => Promise<void>;
35
+ export type PreDeleteHookFn = (payload: DeleteContext, context: HandlerContext) => Promise<void>;
36
+ export type PostDeleteHookFn = (payload: DeleteContext, context: HandlerContext) => Promise<void>;
37
+ export type PostDeleteBatchHookFn = (payloads: readonly DeleteContext[], context: HandlerContext) => Promise<void>;
39
38
  export type PostQueryHookFn = (result: {
40
39
  readonly entityName: string | undefined;
41
40
  readonly rows: ReadonlyArray<Record<string, unknown>>;
42
- }, context: AppContext) => Promise<{
41
+ }, context: HandlerContext) => Promise<{
43
42
  rows: ReadonlyArray<Record<string, unknown>>;
44
43
  }>;
45
- export type LifecycleHookFn = PreSaveHookFn | PostSaveHookFn | PreDeleteHookFn | PostDeleteHookFn | PreQueryHookFn | PostQueryHookFn;
44
+ export type LifecycleHookFn = PreSaveHookFn | PostSaveHookFn | PreDeleteHookFn | PostDeleteHookFn | PostQueryHookFn;
46
45
  export type { HookPhase } from "./hook-phase.ts";
47
46
  export type HookOwner = {
48
47
  readonly featureName?: string;
@@ -60,7 +59,6 @@ export type HookMap = {
60
59
  readonly postSave?: Readonly<Record<string, readonly PhasedHook<PostSaveHookFn>[]>>;
61
60
  readonly preDelete?: Readonly<Record<string, readonly PhasedHook<PreDeleteHookFn>[]>>;
62
61
  readonly postDelete?: Readonly<Record<string, readonly PhasedHook<PostDeleteHookFn>[]>>;
63
- readonly preQuery?: Readonly<Record<string, readonly OwnedFn<PreQueryHookFn>[]>>;
64
62
  readonly postQuery?: Readonly<Record<string, readonly OwnedFn<PostQueryHookFn>[]>>;
65
63
  };
66
64
  export type EntityHookMap = {
@@ -1,3 +1,3 @@
1
- // Metric + Meter contract. Feature code interacts through ctx.metrics
1
+ // Metric + Meter contract. Feature code interacts through ctx.obs.metrics
2
2
  // (MetricsHandle); the framework wires Counter/Histogram/Gauge behind that.
3
3
  export {};
package/dist/screen.d.ts CHANGED
@@ -73,7 +73,7 @@ export interface FieldFormatRegistry {
73
73
  /** FileRef id (or id array) as a download link with the file name. On a
74
74
  * read-only detail field it renders a file list with download links and an
75
75
  * inline preview for PDFs; elsewhere the raw value is shown.
76
- * `previewOpenByDefault` opens the first file's PDF preview initially. */
76
+ * `previewOpenByDefault` opens the newest (last) file's PDF preview initially. */
77
77
  file: {
78
78
  readonly previewOpenByDefault?: boolean;
79
79
  };
@@ -6,7 +6,7 @@
6
6
  // but the nominal typing stops it from landing in an HTTP response by
7
7
  // accident — a response-serializer guard + the reveal() cost make the leak
8
8
  // intentional. Framework code that sees `Secret<string>` knows the caller
9
- // has already gone through the audited ctx.secrets.get path.
9
+ // has already gone through the audited ctx.services.secrets.get path.
10
10
  //
11
11
  // Registered symbol, matching KUMIKO_*_SYMBOL in schema-table-types.ts. A
12
12
  // plain Symbol() is per-copy: two resolved copies of this package brand with
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-types",
3
- "version": "0.389.0",
3
+ "version": "0.390.1",
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": "MIT",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -291,13 +291,13 @@
291
291
  "bun-types": "^1.4.4",
292
292
  "hono": "^4.13.1",
293
293
  "temporal-polyfill": "^0.3.2",
294
- "zod": "^4.4.3",
294
+ "zod": "^4.6.7",
295
295
  "ioredis": "^5.10.1"
296
296
  },
297
297
  "peerDependencies": {
298
298
  "hono": "^4.13.1",
299
299
  "temporal-polyfill": "^0.3.2",
300
- "zod": "^4.4.3",
300
+ "zod": "^4.6.7",
301
301
  "ioredis": "^5.10.1",
302
302
  "bun-types": "^1.4.4"
303
303
  },
package/src/changes.json CHANGED
@@ -43,7 +43,7 @@
43
43
  "type": "breaking",
44
44
  "title": "HandlerContext.outsideTransaction removed",
45
45
  "detail": "The deprecated outsideTransaction field no longer exists on HandlerContext. ctx.dbOutsideTransaction together with runInOwnTransaction replaces it.",
46
- "migration": "Replace ctx.outsideTransaction(fn) with runInOwnTransaction(requireOutsideTransactionDb(ctx), fn); both are exported from @cosmicdrift/kumiko-framework/db and fn keeps its (db) => Promise signature."
46
+ "migration": "Replace ctx.outsideTransaction(fn) with runInOwnTransaction(requireOutsideTransactionDb(ctx), fn); both are exported from @cosmicdrift/kumiko-framework/db and fn keeps its (db) => Promise signature. Since #4031 the two helpers are internal and no longer exported from /db; narrow ctx.db.outsideTransaction directly (if (ctx.db.outsideTransaction))."
47
47
  },
48
48
  {
49
49
  "version": "0.377.0",