@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/config.d.ts +2 -2
- package/dist/db-connection.d.ts +4 -4
- package/dist/event-store-executor-types.d.ts +1 -1
- package/dist/event-type-map.js +1 -1
- package/dist/feature-registrar.d.ts +321 -0
- package/dist/feature-registrar.js +1 -0
- package/dist/feature.d.ts +6 -315
- package/dist/fields.d.ts +1 -1
- package/dist/handler-context-facets.d.ts +109 -0
- package/dist/handler-context-facets.js +1 -0
- package/dist/handler-context-services.d.ts +82 -0
- package/dist/handler-context-services.js +1 -0
- package/dist/handlers.d.ts +39 -107
- package/dist/hooks.d.ts +9 -11
- package/dist/list-aggregate-types.d.ts +6 -1
- package/dist/list-aggregate-types.js +3 -0
- package/dist/observability/metric.js +1 -1
- package/dist/screen-entity-edit.d.ts +8 -20
- package/dist/screen-entity-edit.js +6 -4
- package/dist/screen-entity-list.d.ts +21 -8
- package/dist/screen-expandable-row.d.ts +16 -0
- package/dist/screen-expandable-row.js +1 -0
- package/dist/screen-list-bulk.d.ts +12 -0
- package/dist/screen-list-bulk.js +3 -0
- package/dist/screen-list-table.d.ts +70 -0
- package/dist/screen-list-table.js +28 -0
- package/dist/screen-projection.d.ts +6 -0
- package/dist/screen.d.ts +12 -4
- package/dist/screen.js +2 -0
- package/dist/secrets-types.js +1 -1
- package/dist/tenant-db-types.d.ts +5 -0
- package/package.json +12 -4
- package/src/changes.json +14 -1
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,
|
|
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 {
|
|
11
|
-
import type { EntityHookMap, HookMap, HookPhase, OwnedFn, PostDeleteHookFn, PostQueryHookFn, PostSaveHookFn, PreDeleteHookFn,
|
|
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
|
|
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" | "
|
|
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 {};
|