@cosmicdrift/kumiko-framework 0.397.0 → 0.399.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/AGENTS.md +33 -19
- package/agents/api/dev-server-testing.md +23 -0
- package/agents/api/framework-db.md +277 -0
- package/agents/api/framework-engine.md +1804 -0
- package/agents/api/framework-errors.md +119 -0
- package/agents/api/framework-event-store.md +99 -0
- package/agents/api/framework-seeding.md +26 -0
- package/agents/api/framework-ui-types.md +537 -0
- package/agents/api/renderer-web.md +1449 -0
- package/agents/api/testing-helpers.md +189 -0
- package/agents/api/testing-stack.md +144 -0
- package/agents/api/types.md +4962 -0
- package/agents/api.md +20 -0
- package/agents/features-index.md +176 -0
- package/agents/features.md +1 -1
- package/dist/engine/entity-list-runtime-options.d.ts +3 -0
- package/dist/engine/entity-list-runtime-options.js +18 -0
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +1 -0
- package/dist/entrypoint/index.js +1 -0
- package/dist/entrypoint/internals.d.ts +2 -0
- package/dist/files/file-routes.js +18 -6
- package/dist/files/upload-field-resolution.d.ts +2 -0
- package/dist/files/upload-field-resolution.js +25 -0
- package/package.json +5 -5
- package/src/changes.json +18 -0
|
@@ -0,0 +1,1804 @@
|
|
|
1
|
+
<!-- Generated by `bun run gen:agent-docs` from packages/framework/src/engine/index.ts. Do not edit. -->
|
|
2
|
+
|
|
3
|
+
# @cosmicdrift/kumiko-framework/engine
|
|
4
|
+
|
|
5
|
+
Import from `@cosmicdrift/kumiko-framework/engine`. Types used here but not exported by any entry point are in `api/types.md`.
|
|
6
|
+
|
|
7
|
+
## Functions
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
function assertNotificationVariables(registry: Pick<Registry, "getAllNotifications">, notificationType: string, data: Readonly<Record<string, unknown>> | undefined): void;
|
|
11
|
+
function assignableAppRolesFromUsages(usages: { entityName: string; options?: Record<string, unknown> | undefined; }[]): AssignableAppRoles;
|
|
12
|
+
function assignableAppRolesOf(registry: Pick<Registry, "getExtensionUsages">): AssignableAppRoles;
|
|
13
|
+
function attachOutsideTransaction<T extends object, O extends object | undefined>(db: T, outsideTransaction: O): T & { outsideTransaction: O; };
|
|
14
|
+
function bridgeStub(opts?: { user?: SessionUser; log?: HandlerObservability["log"]; dispatch?: Partial<HandlerDispatch>; services?: Partial<ContextServices>; }): Pick<HandlerContext, "dispatch" | "events" | "auth" | "obs" | "hasFeature" | "tz" | "locale" | "user" | "services" | "lifecycle" | "tenants">;
|
|
15
|
+
function buildAppSchema(registry: Registry, options?: BuildAppSchemaOptions): AppSchema;
|
|
16
|
+
function buildConfigFeatureSchema(registry: Registry): ConfigFeatureSchema;
|
|
17
|
+
function buildInsertSchema(entity: EntityDefinition, currencies?: string[], excludedFields?: string[], options?: { allowCallerId?: boolean; }): z.ZodObject<Record<string, z.ZodTypeAny>>;
|
|
18
|
+
function buildManifestFromRegistry(registry: Registry, options: BuildManifestOptions): FeatureManifest;
|
|
19
|
+
function buildPipelineSteps<TPayload, TAwaits extends Record<string, AwaitedEventType> = Record<string, AwaitedEventType>>(pipelineDef: PipelineDef<TPayload, unknown, TAwaits>, event: WriteEvent<TPayload>): StepInstance[];
|
|
20
|
+
function buildSessionRoles(globalRoles: string[], membershipRoles: string[]): string[];
|
|
21
|
+
function buildUpdateSchema(entity: EntityDefinition, currencies?: string[], excludedFields?: string[]): z.ZodObject<Record<string, z.ZodTypeAny>>;
|
|
22
|
+
function canActorAssignRole(actorRoles: string[], role: string, assignableAppRoles?: AssignableAppRoles): boolean;
|
|
23
|
+
function collectCardMetaOverflow(features: FeatureDefinition[]): CardMetaOverflow[];
|
|
24
|
+
function collectWriteHandlerQns(features: FeatureDefinition[]): Set<string>;
|
|
25
|
+
function compareVersions(a: string, b: string): number;
|
|
26
|
+
function computeDefinitionFingerprint<TPayload = unknown, TData = unknown, TAwaits extends Record<string, string> = Record<string, string>>(workflow: Pick<WorkflowDefinition<TPayload, TData, TAwaits>, "name" | "trigger" | "pipelineDef">): string; // Snapshot-at-Start fingerprint.
|
|
27
|
+
function computeEffectiveFeatures(registry: Registry, readToggle: ToggleReader): ReadonlySet<string>;
|
|
28
|
+
function createAnonymousUser(tenantId: TenantId): SessionUser;
|
|
29
|
+
function createBigIntField<R extends true | false = false>(overrides?: Partial<Omit<BigIntFieldDef, "type" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind & { required?: R; }): BigIntFieldDef & { required: R; };
|
|
30
|
+
function createBooleanField<R extends true | false = false>(overrides?: Partial<Omit<BooleanFieldDef, "type" | "required">> & { required?: R; }): BooleanFieldDef & { required: R; default: boolean; };
|
|
31
|
+
function createDateField<R extends true | false = false>(overrides?: Partial<Omit<DateFieldDef, "type" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind & { required?: R; }): DateFieldDef & { required: R; };
|
|
32
|
+
function createDecimalField<R extends true | false = false>(config: { precision: number; scale: number; } & Partial<Omit<DecimalFieldDef, "type" | "precision" | "scale" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind & { required?: R; }): DecimalFieldDef & { required: R; };
|
|
33
|
+
function createDerivedField(spec: DerivedFieldDef): DerivedFieldDef; // Read-time computed field for `EntityDefinition.derivedFields` (NOT `fields`).
|
|
34
|
+
function createEmbeddedField(schema: EmbeddedFieldDef["schema"], overrides?: Partial<Omit<EmbeddedFieldDef, "type" | "schema" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind): EmbeddedFieldDef;
|
|
35
|
+
function createEmbeddedListField(schema: EmbeddedFieldDef["schema"], overrides?: Partial<Omit<EmbeddedFieldDef, "type" | "schema" | "multiple" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind): EmbeddedFieldDef & { multiple: true; };
|
|
36
|
+
function createEntity<const F, const T extends EntityTenancy = "tenant">(def: { table?: string; fields: F; description?: string; softDelete?: boolean; systemStream?: boolean; eventVersion?: number; eventMigrations?: EntityDefinition["eventMigrations"]; searchWeight?: number; defaultCurrency?: string; transitions?: Readonly<Record<string, Readonly<Record<string, string[]>>>>; lockedWhen?: EntityDefinition["lockedWhen"]; indexes?: EntityIndexDef[]; idType?: "serial" | "uuid"; access?: EntityDefinition["access"]; parentRef?: EntityDefinition["parentRef"]; retention?: RetentionDef; derivedFields?: EntityDefinition["derivedFields"]; tenancy?: T; transferable?: boolean; attachedFileAccess?: EntityDefinition["attachedFileAccess"]; }): F extends FieldsMap ? EntityDefinition<F, T> : never;
|
|
37
|
+
function createEntityExecutor<E extends EntityDefinition>(entityName: string, entity: E): { table: EntityTable; executor: EventStoreExecutor<EntityRow<E>>; };
|
|
38
|
+
function createFileField(overrides?: Partial<Omit<FileFieldDef, "type" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind): FileFieldDef;
|
|
39
|
+
function createFilesField(overrides?: Partial<Omit<FilesFieldDef, "type" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind): FilesFieldDef;
|
|
40
|
+
function createImageField(overrides?: Partial<Omit<ImageFieldDef, "type" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind): ImageFieldDef;
|
|
41
|
+
function createImagesField(overrides?: Partial<Omit<ImagesFieldDef, "type" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind): ImagesFieldDef;
|
|
42
|
+
function createJsonbField(overrides?: Partial<Omit<JsonbFieldDef, "type" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind): JsonbFieldDef;
|
|
43
|
+
function createLocatedTimestampField<R extends true | false = false>(overrides?: Partial<Omit<LocatedTimestampFieldDef, "type" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind & { required?: R; }): LocatedTimestampFieldDef & { required: R; }; // Wall-clock time at a place as one atomic field.
|
|
44
|
+
function createLongTextField<R extends true | false = false>(overrides: Partial<Omit<LongTextFieldDef, "type" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotationsLongText & { required?: R; }): EntityLongTextFieldDef & { required: R; };
|
|
45
|
+
function createMoneyField<R extends true | false = false>(overrides?: Partial<Omit<MoneyFieldDef, "type" | "required">> & { required?: R; }): MoneyFieldDef & { required: R; };
|
|
46
|
+
function createMultiSelectField<const TOptions extends string[]>(opts: { options: TOptions; } & Partial<Omit<MultiSelectFieldDef<TOptions>, "type" | "options" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind): MultiSelectFieldDef<TOptions>; // Multi-select field — N values from a fixed option list.
|
|
47
|
+
function createNumberField<R extends true | false = false>(overrides?: Partial<Omit<NumberFieldDef, "type" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind & { required?: R; }): NumberFieldDef & { required: R; }; // Numeric field — `double precision` column by default (fractional values allowed end to end).
|
|
48
|
+
function createRegistry(rawFeatures: FeatureDefinition[]): Registry;
|
|
49
|
+
function createSeed(opts: CreateSeedOptions): ConfigSeedDef;
|
|
50
|
+
function createSelectField<const TOptions extends string[], R extends true | false = false>(opts: { options: TOptions; } & Partial<Omit<SelectFieldDef<NoInfer<TOptions>>, "type" | "options" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind & { required?: R; }): SelectFieldDef<TOptions> & { required: R; };
|
|
51
|
+
function createSystemConfig<T extends ConfigKeyType>(type: T, opts?: ConfigKeyOptions<T>): ConfigKeyDefinition<T>;
|
|
52
|
+
function createSystemSeed(opts: CreateSeedOptions): ConfigSeedDef;
|
|
53
|
+
function createSystemUser(tenantId: TenantId, extraRoles?: string[]): SessionUser;
|
|
54
|
+
function createTenantConfig<T extends ConfigKeyType>(type: T, opts?: ConfigKeyOptions<T>): ConfigKeyDefinition<T>;
|
|
55
|
+
function createTenantSeed(opts: CreateSeedOptions, options?: CreateTenantSeedOptions): ConfigSeedDef;
|
|
56
|
+
function createTextField<R extends true | false = false>(overrides: Partial<Omit<TextFieldDef, "type" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotations & { required?: R; }): EntityTextFieldDef & { required: R; };
|
|
57
|
+
function createTimestampField<R extends true | false = false>(overrides?: Partial<Omit<TimestampFieldDef, "type" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind & { required?: R; }): TimestampFieldDef & { required: R; }; // UTC instant for events occurring at a specific point in time (createdAt, loginAt, actualPickupAt).
|
|
58
|
+
function createTzField<R extends true | false = false>(overrides?: Partial<Omit<TzFieldDef, "type" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind & { required?: R; }): TzFieldDef & { required: R; }; // IANA time-zone name (for example, "Europe/Berlin").
|
|
59
|
+
function createUserConfig<T extends ConfigKeyType>(type: T, opts?: ConfigKeyOptions<T>): ConfigKeyDefinition<T>;
|
|
60
|
+
function declareEscapeHatch(declaration: EscapeHatchDeclaration): void;
|
|
61
|
+
function dedupeFeatures(features: FeatureDefinition[]): FeatureDefinition[];
|
|
62
|
+
function defineApply<TPayload = Record<string, unknown>>(fn: (event: StoredEvent<TPayload>, tx: ExecutorManagedRunner, table: ProjectionTable) => Promise<void>): SingleStreamApplyFn;
|
|
63
|
+
function defineEntityCreateHandler(entityName: string, entity: EntityDefinition, options: EntityWriteHandlerOptions): WriteHandlerDef;
|
|
64
|
+
function defineEntityDeleteHandler(entityName: string, entity: EntityDefinition, options: EntityHandlerOptions): WriteHandlerDef;
|
|
65
|
+
function defineEntityDetailHandler(entityName: string, entity: EntityDefinition, options: EntityQueryHandlerOptions): QueryHandlerDef;
|
|
66
|
+
function defineEntityListHandler(entityName: string, entity: EntityDefinition, options: EntityQueryHandlerOptions): QueryHandlerDef;
|
|
67
|
+
function defineEntityQueryHandler(name: string, entity: EntityDefinition, options: EntityQueryHandlerOptions): QueryHandlerDef;
|
|
68
|
+
function defineEntityRestoreHandler(entityName: string, entity: EntityDefinition, options: EntityHandlerOptions): WriteHandlerDef;
|
|
69
|
+
function defineEntityUpdateHandler(entityName: string, entity: EntityDefinition, options: EntityWriteHandlerOptions): WriteHandlerDef;
|
|
70
|
+
function defineEntityWriteHandler(name: string, entity: EntityDefinition, options: EntityWriteHandlerOptions): WriteHandlerDef;
|
|
71
|
+
function defineFeature<const TName extends string, TExports = undefined>(name: TName, setup: (r: FeatureRegistrar<TName>) => TExports, settings?: DefineFeatureSettings): FeatureDefinition & { exports: TExports; };
|
|
72
|
+
function defineMspApply<TPayload = Record<string, unknown>>(fn: (event: StoredEvent<TPayload>, tx: ExecutorManagedRunner, ctx: MultiStreamApplyContext) => Promise<void>): MultiStreamApplyFn;
|
|
73
|
+
function definePagedQueryHandler<const TName extends string, TSchema extends ZodType, TRow = unknown, TMap extends object = KumikoEventTypeMap>(def: QueryHandlerDefinition<TName, TSchema, CursorResult<TRow>, TMap>, ..._noSecretInResponse: true extends ContainsSecret<CursorResult<TRow>> ? [ secretLeak: "A handler response must not contain a Secret<> — call .reveal() and return the plaintext, or drop the field." ] : [ ]): PagedQueryHandlerDefinition<TName, TSchema, TRow, TMap>;
|
|
74
|
+
function defineProjectionQueryHandler(name: string, projectionQualifiedName: string, options: { access: AccessRule; unsafeAllTenants?: boolean | { reason: string; }; escapeHatch?: EscapeHatchDeclaration; }): QueryHandlerDef;
|
|
75
|
+
function defineQueryHandler<const TName extends string, TSchema extends ZodType, TResult = unknown, TMap extends object = KumikoEventTypeMap>(def: QueryHandlerDefinition<TName, TSchema, TResult, TMap>, ..._noSecretInResponse: true extends ContainsSecret<TResult> ? [ secretLeak: "A handler response must not contain a Secret<> — call .reveal() and return the plaintext, or drop the field." ] : [ ]): QueryHandlerDefinition<TName, TSchema, TResult, TMap>;
|
|
76
|
+
function defineRoles<const T extends string[]>(roles: T): RoleMap<T>;
|
|
77
|
+
function defineStep<TArgs, TResult>(def: StepDef<TArgs, TResult>): StepDef<TArgs, TResult>;
|
|
78
|
+
function defineTransitions<const TMap extends Record<string, string[]>>(map: TMap): TransitionGraph<keyof TMap & string>; // Defines allowed state transitions.
|
|
79
|
+
function defineWorkflow<TPayload = unknown, TData = unknown, const TAwaits extends Record<string, string> = Record<string, string>>(input: WorkflowInput<TPayload, TData, TAwaits>): WorkflowDefinition<TPayload, TData, TAwaits>; // Define a suspendable workflow.
|
|
80
|
+
function defineWriteHandler<const TName extends string, TSchema extends ZodType, TData = unknown, TMap extends object = KumikoEventTypeMap, TPrepared = undefined, TWhenNested extends BeforeTransactionWhenNested | undefined = undefined>(def: WriteHandlerInput<TName, TSchema, TData, TMap, TPrepared, TWhenNested>, ..._noSecretInResponse: true extends ContainsSecret<TData> ? [ secretLeak: "A handler response must not contain a Secret<> — call .reveal() and return the plaintext, or drop the field." ] : [ ]): WriteHandlerDefinition<TName, TSchema, TData, TMap, TPrepared, TWhenNested>;
|
|
81
|
+
function describeWorkflowStepError(error: unknown): string;
|
|
82
|
+
function emitEvent<TPayload>(ctx: EmitCtx, eventDef: EventDef<TPayload>, args: { aggregateId: string; aggregateType: string; payload: TPayload; headers?: AppendEventArgs["headers"]; }): Promise<void>;
|
|
83
|
+
function entityHasAnonymizableSubjectField(fields: EntityDefinition["fields"]): boolean;
|
|
84
|
+
function evaluateEventMatch(match: EventMatch, payload: unknown): boolean;
|
|
85
|
+
function extensionSelectorTargets(registry: Pick<Registry, "getAllExtensionSelectors">): ReadonlyMap<string, string>;
|
|
86
|
+
function extensionUsageEscapeHatchReason(usage: Pick<RegistrarExtensionRegistration, "options">): string | undefined;
|
|
87
|
+
function fieldToZod(field: FieldDefinition, currencies: string[], opts?: { applyDefaults?: boolean; isUpdate?: boolean; }): z.ZodTypeAny;
|
|
88
|
+
function filterEntriesAfter(entries: ChangelogEntry[], version: string): ChangelogEntry[];
|
|
89
|
+
function filterReadFields(entity: EntityDefinition, data: Readonly<Record<string, unknown>>, user: SessionUser): Record<string, unknown>;
|
|
90
|
+
function findForbiddenMembershipRole(roles: string[]): string | undefined;
|
|
91
|
+
function findForbiddenRoleAssignment(actorRoles: string[], assignedRoles: string[], targetCurrentRoles: string[], assignableAppRoles?: AssignableAppRoles): string | undefined;
|
|
92
|
+
function findOverDeepTransferChain(adjacency: TransferAdjacency, onPath: string[]): string[] | undefined;
|
|
93
|
+
function findTierResolverUsage(features: FeatureDefinition[]): RegistrarExtensionRegistration | undefined;
|
|
94
|
+
function from(ref: OwnershipRef, column?: string): FromRule;
|
|
95
|
+
function getStep(kind: StepKind): StepDef | undefined;
|
|
96
|
+
function guardTransition<TStates extends string>(transitions: TransitionGraph<TStates>, from: TStates, to: TStates): void; // Asserts a state transition is allowed.
|
|
97
|
+
function handlerFieldLabelKey(handlerQn: string, fieldName: string): string;
|
|
98
|
+
function handlerFieldReference(schema: z.ZodType, field: string): HandlerFieldReference | undefined;
|
|
99
|
+
function handlerTitleKey(handlerQn: string): string;
|
|
100
|
+
function hasAccess(user: Pick<SessionUser, "roles">, access: AccessRule | undefined): boolean;
|
|
101
|
+
function i18nKey(value: string): string;
|
|
102
|
+
function isAgentVisibleScreen(screen: { agent?: AgentScreenHints; }): boolean; // Screens are the inverse of handlers: the manifest lists every role-visible screen regardless of `description`, so only an explicit opt-out hides one.
|
|
103
|
+
function isAssignableByRole(assignableFrom: AssignableFromRole, actorRole: AssignableFromRole): boolean; // True when `actorRole` ranks at least as high as the role an app role is assignable from.
|
|
104
|
+
function isDeliveryErrorCode(value: unknown): value is DeliveryErrorCode;
|
|
105
|
+
function isEncryptedAtRest(def: Pick<ConfigKeyDefinition, "encrypted" | "backing">): boolean;
|
|
106
|
+
function isFieldsEditSection(section: EditSectionSpec): section is EditFieldsSection;
|
|
107
|
+
function isOpenToAllGranted(rule: AccessRule): boolean;
|
|
108
|
+
function isSystemTenant(tenantId: TenantId | null | undefined): boolean;
|
|
109
|
+
function isTenantDataExtensionHooks(o: unknown): o is TenantDataExtensionHooks;
|
|
110
|
+
function isTenantResourceExtensionHooks(o: unknown): o is TenantResourceExtensionHooks;
|
|
111
|
+
function isTierResolverPlugin(v: unknown): v is TierResolverPlugin; // Narrows a registration's untyped `options` bag to the tier-resolver plugin shape — the only thing every implementation must provide is the `build` factory.
|
|
112
|
+
function isToggleableFeature(feature: FeatureDefinition): feature is FeatureDefinition & { toggleableDefault: boolean; };
|
|
113
|
+
function isUuid(value: unknown): value is string;
|
|
114
|
+
function isValidQn(value: string): boolean;
|
|
115
|
+
function maskWriteOnlyFields<T extends Readonly<Record<string, unknown>>>(entity: EntityDefinition, row: T): T;
|
|
116
|
+
function mayOverrideTenant(user: SessionUser): boolean;
|
|
117
|
+
function mergeAssignedRoles(actorRoles: string[], assignedRoles: string[], targetCurrentRoles: string[], assignableAppRoles: AssignableAppRoles): string[];
|
|
118
|
+
function normalizeAccessEntry(entry: OwnershipMap | string[] | undefined): OwnershipMap | undefined;
|
|
119
|
+
function normalizeEditField(f: EditFieldSpec): Exclude<EditFieldSpec, string>;
|
|
120
|
+
function normalizeListColumn(c: ListColumnSpec): Exclude<ListColumnSpec, string>;
|
|
121
|
+
function pageRows<TRow extends Readonly<Record<TSortField, PagedListRowValue>> & { id: string; }, TSortField extends string>(rows: TRow[], request: PagedListRequest<TSortField>, options: { searchFields: TSortField[]; defaultSort: TSortField; }): CursorResult<TRow>;
|
|
122
|
+
function pagedListSchema<const TSortField extends string>(sortableFields: [ TSortField, ...TSortField[] ]): …;
|
|
123
|
+
function parseChangesetChanges(markdown: string, source: string): PendingChange[]; // Parse structured upgrade metadata embedded in a Changeset body.
|
|
124
|
+
function parseFeatureChangelog(raw: string, featureName: string): FeatureChangelog | null; // Parse a changes.json body.
|
|
125
|
+
function parseQn(value: string): ParsedQn;
|
|
126
|
+
function parseRefTarget(raw: string, currentFeature: string): ParsedRefTarget;
|
|
127
|
+
function parseRefTargetEntityName(raw: string): string;
|
|
128
|
+
function parseTenantId(value: unknown): TenantId | null;
|
|
129
|
+
function projectAppSchemaForRoles(schema: AppSchema, roles: string[]): AppSchema;
|
|
130
|
+
function qn(scope: string, type: QnType, name: string): string;
|
|
131
|
+
function readClaim<T extends ClaimKeyType>(user: SessionUser, handle: ClaimKeyHandle<T>): ClaimKeyJsType<T> | undefined;
|
|
132
|
+
function referenceGroupLabels(ctx: HandlerContext, user: SessionUser, sourceTableName: string, groupByField: string, keys: AggregateKey[], db: TenantDb): Promise<ReadonlyMap<string, string>>;
|
|
133
|
+
function requireTierResolverPlugin(usage: RegistrarExtensionRegistration): TierResolverPlugin;
|
|
134
|
+
function resolveAgentExposure(def: { description?: string; agent?: AgentHandlerHints; }, kind: "query" | "write"): AgentExposure; // Fail-closed: a handler without a `description` stays invisible to the agent unless it opts in explicitly.
|
|
135
|
+
function resolveEntityTransferAdjacency(entities: ReadonlyMap<string, EntityDefinition>, rootEntityType?: string): TransferAdjacency;
|
|
136
|
+
function runPipeline<TPayload, TData, TMap extends object = KumikoEventTypeMap>(pipelineDef: PipelineDef<TPayload, TData>, event: WriteEvent<TPayload>, handlerCtx: HandlerContext<TMap>, workflow?: PipelineCtx["workflow"], resumeFrom?: number): Promise<WriteResult<TData>>;
|
|
137
|
+
function runStepList<TPayload, TMap extends object = KumikoEventTypeMap>(steps: StepInstance[], event: WriteEvent<TPayload>, handlerCtx: HandlerContext<TMap>, stepsAcc: Record<string, unknown>, scopeAcc: Record<string, unknown>, workflow?: PipelineCtx["workflow"], resumeFrom?: number): Promise<StepListOutcome>; // Walk a step-list.
|
|
138
|
+
function sectionFieldSpecs(section: FieldsOrGroupsSection): EditFieldSpec[];
|
|
139
|
+
function selectablePluginIds(registry: Pick<Registry, "getExtensionUsages">, extensionName: string): string[];
|
|
140
|
+
function serializeManifest(manifest: FeatureManifest): string;
|
|
141
|
+
function setFields(table: ProjectionTable, fields: Record<string, unknown> | ((event: Parameters<SingleStreamApplyFn>[0]) => Record<string, unknown>)): SingleStreamApplyFn;
|
|
142
|
+
function stepsPipeline<TPayload = unknown, TData = unknown, TAwaits extends Record<string, AwaitedEventType> = Record<string, AwaitedEventType>>(closure: (ctx: PipelineBuildCtx<TPayload, TAwaits>) => StepInstance[]): PipelineDef<TPayload, TData, TAwaits>;
|
|
143
|
+
function templateVariablesToZod(variables: TemplateVariables): z.ZodObject;
|
|
144
|
+
function tenantChannel(tenantId: TenantId): string;
|
|
145
|
+
function toKebab(input: string): string;
|
|
146
|
+
function typedPayload<TPayload>(event: { type: string; payload: Record<string, unknown>; }, eventDef: EventDef<TPayload>): TPayload;
|
|
147
|
+
function unwrapWorkflowStepError(error: unknown): unknown;
|
|
148
|
+
function validateAppCustomScreenWriteQns(appRoot: string, knownQns: ReadonlySet<string>): void; // Boot-time scan of app `src/**` for `dispatcher.write("…")` string literals.
|
|
149
|
+
function validateBoot(rawFeatures: FeatureDefinition[], options?: ValidateBootOptions): void; // Validates all feature configurations at boot time.
|
|
150
|
+
function validateBundledListsOptions(options: unknown): void;
|
|
151
|
+
function walkStepInstances(steps: StepInstance[], where: string): Generator<StepInstance, void, void>;
|
|
152
|
+
function withFormDerivations<Schema extends z.ZodType>(schema: Schema, screen: FormDerivationScreen): z.ZodPreprocess<Schema, unknown>; // Server counterpart of an actionForm's `derive`: recomputes the derived head fields before the schema validates them, so client-sent totals never count.
|
|
153
|
+
function withResponseData<T>(result: WriteResult<unknown>, data: T): WriteResult<T>; // Override the success-side `data` of a WriteResult while forwarding the failure half untouched.
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Constants
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const ANONYMOUS_ROLE: "anonymous";
|
|
160
|
+
const ANONYMOUS_USER_ID: "anonymous";
|
|
161
|
+
const BUILT_IN_MEMBERSHIP_ROLES: string[]; // Every membership role the engine ranks, excluding the internal `system` actor.
|
|
162
|
+
const CODEMOD_PLACEHOLDER_REASON_MARKER: "state the operator use case here";
|
|
163
|
+
const ConfigScopes: { system: "system"; tenant: "tenant"; user: "user"; };
|
|
164
|
+
const DEFAULT_CURRENCIES: ["EUR", "USD", "GBP", "CHF", "JPY", "SEK", "NOK", "DKK", "PLN", "CZK", "CAD", "AUD", "NZD", "CNY", "INR"];
|
|
165
|
+
const DEFAULT_LOCALES: ["de", "en", "es"];
|
|
166
|
+
const DELIVERY_FAILURE_CODES;
|
|
167
|
+
const DELIVERY_SKIP_REASONS: ["channel_disabled", "preference_disabled", "rate_limited", "no_address", "unsubscribed", "duplicate_idempotency_key"];
|
|
168
|
+
const EXT_ASSIGNABLE_ROLE: "assignableRole"; // `assignableRole` — an app declares a membership role as assignable: `r.useExtension(EXT_ASSIGNABLE_ROLE, "<RoleName>", { assignableFrom?
|
|
169
|
+
const EXT_DERIVATIVE_OVERLAY_RESOLVER: "derivativeOverlayResolver"; // `derivativeOverlayResolver` — per-entityType "resolve this overlay dataToken to its real value" contract for `file-derivatives`' overlay compositing (QR codes on a public variant).
|
|
170
|
+
const EXT_DERIVATIVE_PUBLIC_PREDICATE: "derivativePublicPredicate"; // `derivativePublicPredicate` — per-entityType "is this FileRef's derivative publicly readable?" gate for the anonymous variant route (file-derivatives).
|
|
171
|
+
const EXT_DERIVATIVE_RENDERER: "derivativeRenderer"; // `derivativeRenderer` — File-Derivative-Renderer-Plugin-Selection (file-derivatives).
|
|
172
|
+
const EXT_EXTERNAL_RESOURCE: "externalResource"; // `externalResource` — external-service tenant-destroy hook (webhook subscriptions, Brevo recipient lists, provider customer accounts).
|
|
173
|
+
const EXT_FILE_PROVIDER: "fileProvider"; // `fileProvider` — file-storage provider plugin selection (file-foundation).
|
|
174
|
+
const EXT_INFRA_RESOURCE: "infraResource"; // `infraResource` — Pulumi-managed per-tenant resource tenant-destroy hook (custom domain, cert-manager issuer, dedicated pod/volume).
|
|
175
|
+
const EXT_PRINCIPAL_STATUS: "principalStatus"; // `principalStatus` — "is this principal blocked from signing in" contract, consulted by the active-membership building block; fulfilled by the bundled `user` feature.
|
|
176
|
+
const EXT_SEARCH_ADAPTER: "searchAdapter"; // `searchAdapter` — search-index tenant-destroy hook (Meilisearch index cleanup on tenant-destroy).
|
|
177
|
+
const EXT_STORAGE_PROVIDER: "storageProvider"; // `storageProvider` — file-storage-plugin tenant-destroy hook.
|
|
178
|
+
const EXT_TENANT_DATA: "tenantData"; // `tenantData` — per-entity tenant-destroy hooks (GDPR + end of data processing agreement).
|
|
179
|
+
const EXT_TENANT_LIFECYCLE_STATUS: "tenantLifecycleStatus"; // `tenantLifecycleStatus` — tenant-teardown status contract, fulfilled by the bundled `tenant-lifecycle` feature; absent means no tenant is ever "in teardown".
|
|
180
|
+
const EXT_USER_DATA: "userData"; // `userData` — user-data rights hooks (GDPR Articles 15, 17, and 20).
|
|
181
|
+
const EXT_USER_DATA_ORDER: { REDACT_BEFORE_OWNER: -100; DEFAULT: 0; };
|
|
182
|
+
const FILE_PROVIDER_CONFIG_KEY: "file-foundation:config:provider";
|
|
183
|
+
const FILE_STORAGE_PROVIDER_BOOT_SENTINEL: "configured";
|
|
184
|
+
const FILE_STORAGE_PROVIDER_ENV: "FILE_STORAGE_PROVIDER";
|
|
185
|
+
const HTTP_ERROR_CODE_PATTERN: RegExp;
|
|
186
|
+
const HookPhases: { inTransaction: "inTransaction"; afterCommit: "afterCommit"; };
|
|
187
|
+
const LifecycleHookTypes: { preSave: "preSave"; postSave: "postSave"; preDelete: "preDelete"; postDelete: "postDelete"; postQuery: "postQuery"; };
|
|
188
|
+
const MAX_LIST_LIMIT: 200;
|
|
189
|
+
const MAX_TRANSFER_DEPTH: 5;
|
|
190
|
+
const PII_DIRECT_NAME_HINTS: ReadonlySet<string>;
|
|
191
|
+
const PII_USER_OWNED_NAME_HINTS: ReadonlySet<string>;
|
|
192
|
+
const PII_USER_REFERENCE_NAME_HINTS: ReadonlySet<string>;
|
|
193
|
+
const QnTypes: { write; query; hook; job; notify; event; channel; config; secret; screen; nav; probe };
|
|
194
|
+
const SECURITY_BASELINE_FEATURE_NAMES: ["sessions", "crypto-shredding", "rate-limiting", "audit"];
|
|
195
|
+
const SELECTED_EXTENSIONS_QUERY: "config:query:config-value:selected-extensions";
|
|
196
|
+
const SETTINGS_HUB_FEATURE: "config";
|
|
197
|
+
const STEP_DISPATCHED_TYPE: "kumiko:system:step.dispatched";
|
|
198
|
+
const STEP_DISPATCH_AGGREGATE_TYPE: "step-dispatch";
|
|
199
|
+
const STEP_DISPATCH_FAILED_TYPE: "kumiko:system:step.dispatch-failed";
|
|
200
|
+
const STEP_DISPATCH_REQUESTED_TYPE: "kumiko:system:step.dispatch-requested";
|
|
201
|
+
const SYSTEM_ONLY_JSON_SCHEMA_KEY: "x-kumiko-system-only";
|
|
202
|
+
const SYSTEM_ROLE: "system";
|
|
203
|
+
const SYSTEM_TENANT_ID: TenantId;
|
|
204
|
+
const SYSTEM_USER_ID: "00000000-0000-0000-0000-000000000000";
|
|
205
|
+
const TENANT_CURRENCY_CONFIG_KEY: "tenant-settings:config:currency"; // Qualified config key a `{ kind: "tenant" }` MoneyCurrencySource resolves against — the tenant-settings bundle's per-tenant currency.
|
|
206
|
+
const TENANT_MEMBERSHIPS_QUERY: "tenant:query:memberships";
|
|
207
|
+
const TENANT_TIER_RESOLVER_EXT: "tenantTierResolver"; // Extension name under which a tier-resolver plugin must be registered in the registry.
|
|
208
|
+
const WORKFLOW_AGGREGATE_TYPE: "workflow-run";
|
|
209
|
+
const WORKFLOW_RESUMED_TYPE: "kumiko:system:workflow.step.resumed";
|
|
210
|
+
const WORKFLOW_RETRY_SCHEDULED_TYPE: "kumiko:system:workflow.retry.scheduled";
|
|
211
|
+
const WORKFLOW_RUN_COMPLETED_TYPE: "kumiko:system:workflow.run-completed";
|
|
212
|
+
const WORKFLOW_RUN_FAILED_TYPE: "kumiko:system:workflow.run-failed";
|
|
213
|
+
const WORKFLOW_RUN_STARTED_TYPE: "kumiko:system:workflow.run-started";
|
|
214
|
+
const WORKFLOW_SYSTEM_EVENT_DEFS: ReadonlyMap<string, EventDef>;
|
|
215
|
+
const WORKFLOW_WAITING_FOR_EVENT_TYPE: "kumiko:system:workflow.step.waiting-for-event";
|
|
216
|
+
const WORKFLOW_WAITING_TYPE: "kumiko:system:workflow.step.waiting";
|
|
217
|
+
const access: { all; admin; systemAdmin; system; privileged; authenticated; anonymous; roles; withSystem };
|
|
218
|
+
const entityListSchema;
|
|
219
|
+
const templateVariable: { text; link; richText; money; date; percent; images; list };
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Classes
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
class ProbeFailure extends Error {
|
|
226
|
+
constructor(text: ProbeText);
|
|
227
|
+
}
|
|
228
|
+
class WorkflowStepError extends Error {
|
|
229
|
+
constructor(stepIndex: number, cause: unknown);
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Types
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
type AccessRule = RoleAccessRule | OpenToAllAccessRule;
|
|
237
|
+
type ActionFormScreenDefinition = {
|
|
238
|
+
id: string;
|
|
239
|
+
type: "actionForm";
|
|
240
|
+
fillHeight?: boolean; // Default true: the screen fills the shell content height, its body scrolls inside and the pager / action bar stays pinned.
|
|
241
|
+
nav?: ScreenNavSugar;
|
|
242
|
+
detailFor?: string;
|
|
243
|
+
description?: string;
|
|
244
|
+
agent?: AgentScreenHints;
|
|
245
|
+
dormant?: boolean; // Screen has no nav entry by design (opened via link or navved by the app); exempts it from the nav-area boot check.
|
|
246
|
+
visibleWhen?: ScreenVisibility; // UI-only gate, also on direct URL access; handlers still enforce access.
|
|
247
|
+
fallback?: ScreenFallback; // Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice).
|
|
248
|
+
urlPrefillFields?: string[]; // Derived by buildAppSchema — see EntityEditScreenDefinition.urlPrefillFields.
|
|
249
|
+
writeExcludedFields?: string[]; // Derived by buildAppSchema from the `excludedFields` of the called `handler`; the form shows these fields read-only, not required and never in the payload.
|
|
250
|
+
submitPrefilled?: boolean; // Submit is enabled without edits: values seeded from navigate `params` count as the user's intent.
|
|
251
|
+
handler: string; // Write-handler QN called on submit.
|
|
252
|
+
fields: Readonly<Record<string, FormFieldDefinition>>; // Form shape: field map by name.
|
|
253
|
+
derive?: Readonly<Record<string, FormFieldDerivation>>; // Fields computed from other fields, shown read-only and recomputed on every change.
|
|
254
|
+
layout: EditLayout; // Layout analogous to EntityEditScreen: sections with fields from the fields map above.
|
|
255
|
+
fieldLabels?: Readonly<Record<string, string>>; // Per-field label i18n key override, same type/semantics as `EntityEditScreenDefinition.fieldLabels`.
|
|
256
|
+
submitLabel?: string; // i18n key for the submit button.
|
|
257
|
+
footerActions?: ActionFormFooterAction[]; // Extra buttons in the form footer next to the submit button.
|
|
258
|
+
summary?: {
|
|
259
|
+
title: string;
|
|
260
|
+
subtitle?: string;
|
|
261
|
+
}; // Context box above the fields (i18n keys).
|
|
262
|
+
submitStyle?: "primary" | "danger"; // Visual style of the submit button.
|
|
263
|
+
successMessage?: string; // i18n key of a confirmation shown above the form after a successful submit when the screen stays put (no `redirect`, not drawer-hosted).
|
|
264
|
+
redirect?: string | ActionFormRedirect; // Navigate to this screen ID after a successful submit: either a short ID (e.g. "item-list" — same feature, the nav-router resolves to the full path) or a fully-qualified cross-feature QN (`<feature>:screen:<id>`).
|
|
265
|
+
successLinks?: ActionFormSuccessLink[]; // Links shown in the success box after a successful submit while the screen stays put.
|
|
266
|
+
successFacts?: ActionFormSuccessFact[]; // Key figures from the handler's success payload, shown in the success box while the screen stays put (e.g. charged/skipped/failed of a batch run).
|
|
267
|
+
resetOnSuccess?: boolean; // Reset the form to its initial values after a successful submit (prefill kept, typed values cleared).
|
|
268
|
+
load?: ActionFormLoad; // Load the initial values from a query.
|
|
269
|
+
cancelTarget?: string | false; // Target of the Cancel button.
|
|
270
|
+
listScreenId?: string; // Parent list screen (short id) for breadcrumb + nav highlighting when this screen has no nav entry of its own.
|
|
271
|
+
slots?: ScreenSlots;
|
|
272
|
+
access?: AccessRule;
|
|
273
|
+
};
|
|
274
|
+
type AgentHandlerHints = { // Per-handler hints for the AI-agent manifest.
|
|
275
|
+
expose?: boolean;
|
|
276
|
+
risk?: AgentRisk;
|
|
277
|
+
};
|
|
278
|
+
type AgentRisk = "low" | "mid" | "high";
|
|
279
|
+
type AppContext = SharedContextFields & {
|
|
280
|
+
services?: never;
|
|
281
|
+
db?: DbConnection | TenantDb;
|
|
282
|
+
systemDb?: UncheckedSystemDb;
|
|
283
|
+
registry?: Registry;
|
|
284
|
+
systemUser?: SessionUser;
|
|
285
|
+
log?: Logger;
|
|
286
|
+
triggeredBy?: { id: string; tenantId: TenantId; } | null;
|
|
287
|
+
triggerName?: string; // For job-handler calls triggered by an event (`r.job` with `trigger: { on: ...
|
|
288
|
+
geoTzProvider?: GeoTzProvider; // Optional Geo→zone adapter.
|
|
289
|
+
clock?: Clock; // Overrides the system clock behind ctx.tz in handlers and jobs; tests inject a fixed time.
|
|
290
|
+
_tenantId?: TenantId; // Tenant of the current pipeline call.
|
|
291
|
+
defaultLocale?: string; // Boot-configured default locale (BCP-47).
|
|
292
|
+
};
|
|
293
|
+
type AssignableAppRoles = ReadonlyMap<string, AssignableFromRole>; // App role → lowest built-in role allowed to assign it.
|
|
294
|
+
type AuthClaimsContext = {
|
|
295
|
+
db: TenantDb;
|
|
296
|
+
queryAs: (user: SessionUser, qn: string, payload: unknown) => Promise<unknown>;
|
|
297
|
+
config?: ConfigAccessor;
|
|
298
|
+
};
|
|
299
|
+
type AwaitedEventType = string & {
|
|
300
|
+
__awaitedEvent: unique symbol;
|
|
301
|
+
};
|
|
302
|
+
type BeforeTransactionContext<TMap extends object = KumikoEventTypeMap> = Omit<ReservationContext<TMap>, "db" | "systemDb"> & { // ctx of `beforeTransaction`: external I/O and reads only, so the outside-transaction handles are gone too.
|
|
303
|
+
db: TenantDb & { outsideTransaction: undefined; };
|
|
304
|
+
systemDb?: Omit<UncheckedSystemDb, "outsideTransaction"> & { outsideTransaction: undefined; };
|
|
305
|
+
};
|
|
306
|
+
type BeforeTransactionResult<TPrepared = unknown> = // Outcome of a write handler's pre-transaction phase.
|
|
307
|
+
| {
|
|
308
|
+
isSuccess: true;
|
|
309
|
+
data: TPrepared;
|
|
310
|
+
release?: ReservationRelease;
|
|
311
|
+
}
|
|
312
|
+
| WriteFailure
|
|
313
|
+
;
|
|
314
|
+
type BootOptionName = "mail" | "kmsMode" | "fileStorage" | "billing" | "featureToggles" | "rateLimit"; // runProdApp options a stack can depend on; runProdApp checks the set at boot.
|
|
315
|
+
type BundledListsOptions = { // One `table` block for every entityList, projectionList and relatedList of the bundled features.
|
|
316
|
+
table: ListTableOptions;
|
|
317
|
+
pinFirstColumn?: boolean; // Pins the first column left on every bundled list where no column is pinned.
|
|
318
|
+
};
|
|
319
|
+
type ChangelogEntry = {
|
|
320
|
+
version: string;
|
|
321
|
+
type: ChangelogType;
|
|
322
|
+
title: string;
|
|
323
|
+
detail?: string;
|
|
324
|
+
migration?: string; // Required when type=breaking.
|
|
325
|
+
codemod?: string; // Path (relative to packages/feature-ast/src/, under scripts/codemod/) run by `kumiko upgrade --apply`.
|
|
326
|
+
manualAfterCodemod?: boolean; // The codemod only covers part of the migration: the entry is also recorded as pending manual work after it ran.
|
|
327
|
+
};
|
|
328
|
+
type ChatSendFailureCode = `http_${number}` | Exclude<DeliveryFailureCode, "render_failed" | "send_failed" | "channel_error">;
|
|
329
|
+
type ClaimKeyType = "string" | "number" | "boolean" | "string[]" | "object";
|
|
330
|
+
type ConfigAccessor = {
|
|
331
|
+
(qualifiedKey: string): Promise<string | number | boolean | undefined>;
|
|
332
|
+
<T extends ConfigKeyType>(handle: ConfigKeyHandle<T>): Promise<ConfigValue<T> | undefined>;
|
|
333
|
+
};
|
|
334
|
+
type ConfigAccessorFactory = (deps: { user: { id: string; tenantId: TenantId; }; db: DbConnection | TenantDb; secrets?: ConfigSecretsReader; }) => ConfigAccessor;
|
|
335
|
+
type ConfigCascade = {
|
|
336
|
+
value: string | number | boolean | undefined;
|
|
337
|
+
source: ConfigValueSource;
|
|
338
|
+
levels: ConfigCascadeLevel[];
|
|
339
|
+
};
|
|
340
|
+
type ConfigCascadeLevel = {
|
|
341
|
+
label: string;
|
|
342
|
+
value: string | number | boolean | undefined;
|
|
343
|
+
source: ConfigValueSource;
|
|
344
|
+
isActive: boolean;
|
|
345
|
+
hasValue: boolean;
|
|
346
|
+
};
|
|
347
|
+
type ConfigEditScreenDefinition = {
|
|
348
|
+
id: string;
|
|
349
|
+
type: "configEdit";
|
|
350
|
+
fillHeight?: boolean; // Default true: the form fills the shell content height with a pinned footer.
|
|
351
|
+
nav?: ScreenNavSugar;
|
|
352
|
+
detailFor?: string;
|
|
353
|
+
description?: string;
|
|
354
|
+
agent?: AgentScreenHints;
|
|
355
|
+
dormant?: boolean; // Screen has no nav entry by design (opened via link or navved by the app); exempts it from the nav-area boot check.
|
|
356
|
+
visibleWhen?: ScreenVisibility; // UI-only gate, also on direct URL access; handlers still enforce access.
|
|
357
|
+
fallback?: ScreenFallback; // Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice).
|
|
358
|
+
scope: "tenant" | "system" | "user"; // scope for config:write:set calls.
|
|
359
|
+
configKeys: Readonly<Record<string, string>>; // Map: form field name (short, as referenced in the layout) → fully qualified config key (`<feature>:config:<short>`).
|
|
360
|
+
secretKeys?: Readonly<Record<string, string>>; // Field name → qualified secret key (`<feature>:secret:<short>`), tenant scope only.
|
|
361
|
+
fields: Readonly<Record<string, FieldDefinition>>; // Form shape per field name.
|
|
362
|
+
layout: EditLayout; // Layout: sections with field refs.
|
|
363
|
+
fieldLabels?: Readonly<Record<string, string>>; // Optional per-field label i18n key (field name → key).
|
|
364
|
+
fieldDescriptions?: Readonly<Record<string, string>>; // Per-field help text i18n key (Field-Name → Key), from `mask.description`.
|
|
365
|
+
requiredFields?: string[]; // Field names whose config key declares `required: true` — drives the "Not set.
|
|
366
|
+
submitLabel?: string; // i18n key for the submit button.
|
|
367
|
+
slots?: ScreenSlots;
|
|
368
|
+
access?: AccessRule;
|
|
369
|
+
};
|
|
370
|
+
type ConfigKeyDefinition<T extends ConfigKeyType = ConfigKeyType> = {
|
|
371
|
+
type: T;
|
|
372
|
+
default?: ConfigValue<T>;
|
|
373
|
+
scope: ConfigScope;
|
|
374
|
+
access: ConfigKeyAccess;
|
|
375
|
+
encrypted?: boolean;
|
|
376
|
+
piiEncrypted?: boolean; // User/admin may legitimately see the value — unlike `encrypted` (shared master-key cipher), this is the subject-KMS: the value is encrypted under the DEK of the scope actually written to (tenant-row → tenant subject, user-row → user subject).
|
|
377
|
+
options?: string[];
|
|
378
|
+
optionsQuery?: string; // Only on `type: "select"`: loads the options from this query (QN), contract `{ rows: { value, label, description?, group?
|
|
379
|
+
optionsQueryPayload?: OptionsQueryPayload; // Payload sent with every `optionsQuery` call.
|
|
380
|
+
bounds?: ConfigBounds;
|
|
381
|
+
pattern?: {
|
|
382
|
+
regex: string;
|
|
383
|
+
flags?: string;
|
|
384
|
+
};
|
|
385
|
+
computed?: ConfigComputedFn<T>;
|
|
386
|
+
validate?: ConfigValidateFn<T>;
|
|
387
|
+
allowPerRequest?: boolean;
|
|
388
|
+
required?: boolean;
|
|
389
|
+
env?: string;
|
|
390
|
+
inheritedToTenant?: boolean;
|
|
391
|
+
backing?: ConfigBacking;
|
|
392
|
+
mask?: ConfigMask;
|
|
393
|
+
group?: string;
|
|
394
|
+
};
|
|
395
|
+
type ConfigKeyHandle<T extends ConfigKeyType = ConfigKeyType> = {
|
|
396
|
+
name: string;
|
|
397
|
+
type: T;
|
|
398
|
+
};
|
|
399
|
+
type ConfigKeyType = "text" | "number" | "boolean" | "select";
|
|
400
|
+
type ConfigResolver = {
|
|
401
|
+
get(qualifiedKey: string, keyDef: ConfigKeyDefinition, tenantId: TenantId, userId: string, db: DbConnection | TenantDb, secretsReader?: ConfigSecretsReader): Promise<string | number | boolean | undefined>;
|
|
402
|
+
getWithSource(qualifiedKey: string, keyDef: ConfigKeyDefinition, tenantId: TenantId, userId: string, db: DbConnection | TenantDb, secretsReader?: ConfigSecretsReader): Promise<ConfigValueWithSource>;
|
|
403
|
+
getAll(tenantId: TenantId, userId: string, db: DbConnection | TenantDb): Promise<ReadonlyMap<string, ConfigStoredRow>>;
|
|
404
|
+
getCascade(qualifiedKey: string, keyDef: ConfigKeyDefinition, tenantId: TenantId, userId: string, db: DbConnection | TenantDb, secretsReader?: ConfigSecretsReader): Promise<ConfigCascade>;
|
|
405
|
+
getCascadeBatch(keys: string[], keyDefs: ReadonlyMap<string, ConfigKeyDefinition>, tenantId: TenantId, userId: string, db: DbConnection | TenantDb, secretsReader?: ConfigSecretsReader): Promise<ReadonlyMap<string, ConfigCascade>>;
|
|
406
|
+
};
|
|
407
|
+
type ConfigScope = "system" | "tenant" | "user";
|
|
408
|
+
type ConfigSecretsReader = {
|
|
409
|
+
get(tenantId: TenantId, key: string): Promise<{ reveal: () => string; } | undefined>;
|
|
410
|
+
};
|
|
411
|
+
type ConfigValidateFn<T extends ConfigKeyType = ConfigKeyType> = (value: ConfigValue<T>, ctx: ConfigValidateContext) => Promise<void>;
|
|
412
|
+
type ConfigValue<T extends ConfigKeyType> = T extends "number" ? number : T extends "boolean" ? boolean : T extends "text" | "select" ? string : never;
|
|
413
|
+
type ConfigValueSource = "user-row" | "tenant-row" | "system-row" | "app-override" | "computed" | "default" | "missing";
|
|
414
|
+
type ConfigValueWithSource = {
|
|
415
|
+
value: string | number | boolean | undefined;
|
|
416
|
+
source: ConfigValueSource;
|
|
417
|
+
};
|
|
418
|
+
type ContentCollectionDefinition = {
|
|
419
|
+
id: string;
|
|
420
|
+
kind: string;
|
|
421
|
+
access?: AccessRule;
|
|
422
|
+
ownership?: "tenant" | "user";
|
|
423
|
+
contentFormat?: ContentEditorFormat;
|
|
424
|
+
variableSchema?: Readonly<Record<string, string>>;
|
|
425
|
+
nav: {
|
|
426
|
+
label: string;
|
|
427
|
+
icon?: NavIconKey;
|
|
428
|
+
parent?: string;
|
|
429
|
+
order?: number;
|
|
430
|
+
access?: AccessRule;
|
|
431
|
+
workspaces?: string[];
|
|
432
|
+
createAction?: TreeAction;
|
|
433
|
+
actions?: TreeAction[];
|
|
434
|
+
};
|
|
435
|
+
};
|
|
436
|
+
type DashboardCustomPanel = {
|
|
437
|
+
kind: "custom";
|
|
438
|
+
id: string;
|
|
439
|
+
component: PlatformComponent;
|
|
440
|
+
params?: Readonly<Record<string, unknown>>; // Static author values laid over the screen's filterParams, like `DashboardPanelQueryOptions.params`; the panel wins on a name clash.
|
|
441
|
+
};
|
|
442
|
+
type DashboardI18nText = { // Text a query handler can send untranslated: the renderer translates it.
|
|
443
|
+
i18nKey: string;
|
|
444
|
+
i18nParams?: Readonly<Record<string, DashboardTextParam>>;
|
|
445
|
+
};
|
|
446
|
+
type DeleteContext = {
|
|
447
|
+
kind: "delete";
|
|
448
|
+
id: EntityId;
|
|
449
|
+
data: Readonly<Record<string, unknown>>;
|
|
450
|
+
entityName?: string | undefined;
|
|
451
|
+
event: StoredEvent;
|
|
452
|
+
lockGuardBypassed?: boolean | undefined;
|
|
453
|
+
};
|
|
454
|
+
type DeliveryErrorCode = DeliveryFailureCode | DeliverySkipReason | `http_${number}`;
|
|
455
|
+
type DeliveryFailureCode = (typeof DELIVERY_FAILURE_CODES)[number];
|
|
456
|
+
type DeliverySkipReason = (typeof DELIVERY_SKIP_REASONS)[number];
|
|
457
|
+
type DeriveContext = { // Clock injected into `derive` — never read `Temporal.Now`/`Date` inside a derive body (no-date-api guard + testability).
|
|
458
|
+
asOf: Temporal.Instant;
|
|
459
|
+
};
|
|
460
|
+
type EditFieldSpec =
|
|
461
|
+
| string
|
|
462
|
+
| {
|
|
463
|
+
field: string;
|
|
464
|
+
span?: number;
|
|
465
|
+
visible?: FieldCondition;
|
|
466
|
+
readOnly?: FieldCondition;
|
|
467
|
+
required?: FieldCondition;
|
|
468
|
+
renderer?: FieldRenderer;
|
|
469
|
+
submit?: false; // `false` keeps the field out of the submitted payload — for values that are shown or prefilled for display only (a label, a counter) and that the write handler's schema does not accept.
|
|
470
|
+
icon?: FieldIconKey; // Prefix icon on the input — closed FieldIconKey vocabulary into the FIELD_ICONS registry (renderer/web), analogous to `ScreenNavSugar.icon` / NavIconKey.
|
|
471
|
+
placeholder?: string; // Input placeholder (i18n key, falls back to the literal).
|
|
472
|
+
prefix?: FieldAffix; // Static text shown before the input (i18n key, falls back to the literal), or `{ field }` for the current form value of another field (a hidden field carrying e.g. a base domain).
|
|
473
|
+
suffix?: FieldAffix; // Same as `prefix`, shown after the input.
|
|
474
|
+
fillAction?: FieldFillAction; // Button beside the input that runs a query and writes one scalar of its result into this field.
|
|
475
|
+
refEntity?: string; // Marks this field as a reference lookup instead of a plain value — for projectionDetail fields, which have no `EntityDefinition` to carry a real `reference` field type.
|
|
476
|
+
refLabelField?: string; // Row field on the referenced entity shown as the label (default "id").
|
|
477
|
+
embeddedList?: EmbeddedListEditSpec; // Embedded-list fields only (`createEmbeddedListField`).
|
|
478
|
+
prefill?: EditFieldPrefill; // Fills an empty value when the create form opens; the update form and values from URL params or a handoff are never touched.
|
|
479
|
+
}
|
|
480
|
+
;
|
|
481
|
+
type EditRelatedListSection = {
|
|
482
|
+
id?: string; // Kebab-case tab id, used as the `?tab=` value.
|
|
483
|
+
kind: "relatedList";
|
|
484
|
+
title: string;
|
|
485
|
+
query: string; // Fully qualified query QN.
|
|
486
|
+
parentParam?: string; // Query-payload key the parent record's id is passed under.
|
|
487
|
+
parentFilter?: {
|
|
488
|
+
field: string;
|
|
489
|
+
}; // Server-side WHERE clause pinning this section to the parent record — sent as `payload.filter: { field, op: "eq", value: parentId }`, kept out of the user-facet `filters` array so it can't be cleared by facet interaction.
|
|
490
|
+
columns: ListColumnSpec[];
|
|
491
|
+
entity?: string; // Entity the query's rows belong to: an entity name (same feature) or `feature:entity`.
|
|
492
|
+
description?: string; // i18n key for the one-line hint at the left of the tab toolbar.
|
|
493
|
+
itemNoun?: string; // i18n key holding plural forms for the footer count ("1 Position", "3 Positionen").
|
|
494
|
+
pageSize?: number;
|
|
495
|
+
table?: ListTableOptions; // Opt-in table behaviour: remembered state, reset, column picker, density.
|
|
496
|
+
defaultSort?: ListSortSpec; // Initial sort on mount, applied client-side over the already-loaded rows (this section has no pager; if the fetch is truncated, `nextCursor !== null`, the section shows a hint banner that the sort covers only the loaded rows).
|
|
497
|
+
searchable?: boolean; // Renders a search box whose debounced term rides along as `payload.search` on this section's own query.
|
|
498
|
+
facets?: ListFacetSpec[]; // User-toggleable facet dropdowns — same explicit-label `ListFacetSpec` as `projectionList.facets` (a relatedList has no entity to derive filterable fields from).
|
|
499
|
+
rowClick?: {
|
|
500
|
+
entity: string;
|
|
501
|
+
idColumn?: string;
|
|
502
|
+
}; // Row click opens the target entity's detail screen via ObjectTarget — the `detailFor` lookup owns the entity→screen mapping, so no screenId is named here.
|
|
503
|
+
rowActions?: RowAction[]; // Per-row actions — same type and dispatch semantics as `entityList`/ `projectionList`'s `rowActions` (writeHandler dispatch with a declarative `payload` extractor, or navigate).
|
|
504
|
+
rowActionMode?: RowActionMode; // Layout of the row-action column — see `RowActionMode`.
|
|
505
|
+
bulkActions?: ListBulkAction[]; // Bulk actions over checkbox-selected rows, each pointing at a writeHandler row action.
|
|
506
|
+
rowTone?: RelatedListRowTone; // Tints rows whose field matches — a declarative rule, not a function, so the screen definition stays serializable.
|
|
507
|
+
toolbarActions?: RelatedListToolbarAction[]; // Toolbar actions above the table — same type and dispatch semantics as `entityList`/`projectionList`'s `toolbarActions` ("+ Anlegen" etc.), plus `visible`/`params` evaluated against the parent record (see RelatedListToolbarAction).
|
|
508
|
+
countField?: string; // Record field rendered as a count badge in the tab label when the enclosing `EditLayout.mode` is "tabs" (e.g. an open-items counter).
|
|
509
|
+
actions?: RowAction[]; // Rendered top-right in the section's Card title row — same navigate/drawer/writeHandler dispatch as `ProjectionDetailScreenDefinition.actions`, evaluated against the parent record.
|
|
510
|
+
emptyState?: RelatedListEmptyStateSpec; // Rendered inside the section's Card in place of the table when this section's query returns zero rows.
|
|
511
|
+
};
|
|
512
|
+
type EditSectionSpec = EditFieldsSection | EditExtensionSection | EditRelatedListSection | EditWriteFormSection;
|
|
513
|
+
type EffectiveFeaturesResolver = ((tenantId: TenantId) => ReadonlySet<string>) & { // Resolver callback shape: synchronous (on the dispatcher's hot path), per tenant.
|
|
514
|
+
trialGate?: TrialGate; // Optional live-trial gate, consulted by the dispatcher feature gate ONLY when the synchronous resolver reports a feature as disabled.
|
|
515
|
+
};
|
|
516
|
+
type EntityDefinition<F extends FieldsMap = FieldsMap, T extends EntityTenancy = EntityTenancy> = {
|
|
517
|
+
table?: string;
|
|
518
|
+
fields: F;
|
|
519
|
+
softDelete?: boolean;
|
|
520
|
+
tenancy?: T; // "tenant" (default): auto-filtered by every db accessor.
|
|
521
|
+
systemStream?: boolean; // This aggregate's event stream lives on SYSTEM_TENANT_ID rather than the creator's tenant.
|
|
522
|
+
eventVersion?: number; // Schema version stamped on every lifecycle event (default 1, integer 1..999).
|
|
523
|
+
eventMigrations?: EntityEventMigration[]; // One step per version, fromVersion → fromVersion+1.
|
|
524
|
+
searchWeight?: number;
|
|
525
|
+
defaultCurrency?: string;
|
|
526
|
+
lockedWhen?: FieldCondition; // Rows whose stored state meets this condition are read-only: every executor update and delete on them is rejected with a 409 (`errors.recordLocked`), whatever handler or job calls it.
|
|
527
|
+
transitions?: Readonly<Record<string, TransitionMap>>; // Allowed state transitions per field.
|
|
528
|
+
indexes?: EntityIndexDef[]; // Composite indexes over multiple fields.
|
|
529
|
+
idType?: "serial" | "uuid"; // Entity primary-key type.
|
|
530
|
+
access?: {
|
|
531
|
+
read?: OwnershipMap;
|
|
532
|
+
write?: OwnershipMap;
|
|
533
|
+
}; // Row-level ownership rules (H.2).
|
|
534
|
+
parentRef?: ParentRefDef; // Join-row carrier reference.
|
|
535
|
+
retention?: RetentionDef; // Default retention policy for this entity.
|
|
536
|
+
derivedFields?: DerivedFieldsMap; // Read-time computed fields, keyed by name.
|
|
537
|
+
description?: string; // Optional free-text semantics for the AI agent manifest — see TextFieldDef.description.
|
|
538
|
+
transferable?: boolean; // Opts the rows into `tenant-handover`, a grant-legitimated move from an anonymous tenant to the signed-up account's tenant.
|
|
539
|
+
attachedFileAccess?: "record-only"; // Attached files are readable only through the record's detail query: the files feature's privilegedRoles do not bypass it, the uploader check stays.
|
|
540
|
+
};
|
|
541
|
+
type EntityEditScreenDefinition = {
|
|
542
|
+
id: string;
|
|
543
|
+
type: "entityEdit";
|
|
544
|
+
fillHeight?: boolean; // Default true: the screen fills the shell content height, its body scrolls inside and the pager / action bar stays pinned.
|
|
545
|
+
nav?: ScreenNavSugar;
|
|
546
|
+
detailFor?: string;
|
|
547
|
+
description?: string;
|
|
548
|
+
agent?: AgentScreenHints;
|
|
549
|
+
dormant?: boolean; // Screen has no nav entry by design (opened via link or navved by the app); exempts it from the nav-area boot check.
|
|
550
|
+
visibleWhen?: ScreenVisibility; // UI-only gate, also on direct URL access; handlers still enforce access.
|
|
551
|
+
fallback?: ScreenFallback; // Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice).
|
|
552
|
+
urlPrefillFields?: string[]; // Derived by buildAppSchema from the navigate `params` targeting this screen — the only URL query keys the create form prefills.
|
|
553
|
+
writeExcludedFields?: EntityWriteExcludedFields; // Derived by buildAppSchema from the entity's create/update write handlers (`excludeFields`): create hides these fields, update shows them read-only.
|
|
554
|
+
entity: string;
|
|
555
|
+
recordTitleField?: string; // Entity field whose value names the loaded record in the header breadcrumb (edit mode only).
|
|
556
|
+
titleTemplate?: string; // Screen title (i18n key) with `{field}` placeholders filled from the current form values, so the title follows what the user types (create wizard: "Add {make} {model}").
|
|
557
|
+
readOnly?: FieldCondition; // Shows a saved record read-only while the condition holds for the loaded record (not for unsaved form values): all fields disabled, no save, discard or delete button; record `actions` stay.
|
|
558
|
+
layout: EditLayout;
|
|
559
|
+
submitLabel?: string; // Optional i18n key (or raw string) for the submit button.
|
|
560
|
+
allowCreate?: boolean; // Default true.
|
|
561
|
+
allowDelete?: boolean; // Default true.
|
|
562
|
+
singleton?: boolean; // Default false.
|
|
563
|
+
redirect?: string | ActionFormRedirect; // Navigate to this screen ID after a successful save (create or update).
|
|
564
|
+
listScreenId?: string; // Parent list screen (short id) for breadcrumb + nav highlighting when this screen has no nav entry of its own.
|
|
565
|
+
fieldLabels?: Readonly<Record<string, string>>; // Optional per-field label i18n key (field name → key), overrides the default convention `<feature>:entity:<entity>:field:<name>`.
|
|
566
|
+
actions?: EntityEditAction[]; // Record actions.
|
|
567
|
+
slots?: ScreenSlots;
|
|
568
|
+
access?: AccessRule;
|
|
569
|
+
};
|
|
570
|
+
type EntityId = number | string;
|
|
571
|
+
type EntityListScreenDefinition = {
|
|
572
|
+
id: string;
|
|
573
|
+
type: "entityList";
|
|
574
|
+
fillHeight?: boolean; // Default true: the screen fills the shell content height, its body scrolls inside and the pager / action bar stays pinned.
|
|
575
|
+
nav?: ScreenNavSugar;
|
|
576
|
+
detailFor?: string;
|
|
577
|
+
description?: string;
|
|
578
|
+
agent?: AgentScreenHints;
|
|
579
|
+
dormant?: boolean; // Screen has no nav entry by design (opened via link or navved by the app); exempts it from the nav-area boot check.
|
|
580
|
+
visibleWhen?: ScreenVisibility; // UI-only gate, also on direct URL access; handlers still enforce access.
|
|
581
|
+
fallback?: ScreenFallback; // Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice).
|
|
582
|
+
entity: string;
|
|
583
|
+
createScreen?: string; // Short screen id (same feature) the create button opens instead of the entity's default edit screen.
|
|
584
|
+
createUnavailable?: boolean; // Set by the role projection when `createScreen` is not reachable for the role: the renderer then shows no create button instead of falling back to the entity's edit screen.
|
|
585
|
+
allowCreate?: boolean; // `false` hides this list's "+ New" button (and the empty-state create CTA) while the entity's edit screen keeps its create branch for other entry points, e.g. a prefilled action.
|
|
586
|
+
columns: ListColumnSpec[];
|
|
587
|
+
rowRenderer?: PlatformComponent;
|
|
588
|
+
cardRenderer?: PlatformComponent;
|
|
589
|
+
rowActions?: RowAction[]; // Per-row actions, rendered as an actions column on the right.
|
|
590
|
+
rowActionMode?: RowActionMode; // Layout of the row-action column — see `RowActionMode`.
|
|
591
|
+
bulkActions?: ListBulkAction[]; // Bulk actions over checkbox-selected rows, each pointing at a writeHandler row action.
|
|
592
|
+
toolbarActions?: ToolbarAction[]; // Toolbar actions (list header).
|
|
593
|
+
expandableRow?: EntityListExpandableRow; // Expandable area under each row: a relatedList of the row's record (parentId = the row's `id`).
|
|
594
|
+
filter?: ScreenFilter; // Server-side filter, fixed on the screen — three buckets of the same entity without custom pages (e.g. "Upcoming" / "Active" / "Past" Maintenance).
|
|
595
|
+
pagination?: ListPaginationMode;
|
|
596
|
+
pageSize?: number;
|
|
597
|
+
table?: ListTableOptions; // Opt-in table behaviour: remembered state, reset, column picker, density.
|
|
598
|
+
defaultSort?: ListSortSpec;
|
|
599
|
+
defaultFilters?: Readonly<Record<string, string[] | boolean>>; // Initial facet selection, per filterable field: option values (string facets) or a boolean.
|
|
600
|
+
facets?: Readonly<Record<string, EntityListFacetConfig | false>>; // Per-facet display options for the entity's `filterable` fields, keyed by field name.
|
|
601
|
+
emptyState?: ListEmptyStateSpec; // Replaces the default empty-list title/hint; the create button stays when the list can create.
|
|
602
|
+
createLabel?: string; // i18n key for the primary create button label (default `kumiko.actions.create`).
|
|
603
|
+
searchPlaceholder?: string; // i18n key for the search field placeholder (default `kumiko.list.search-placeholder`).
|
|
604
|
+
searchable?: boolean;
|
|
605
|
+
slots?: ScreenSlots;
|
|
606
|
+
access?: AccessRule;
|
|
607
|
+
};
|
|
608
|
+
type EscapeHatchAuditSink = (event: EscapeHatchUseEvent) => Promise<void>;
|
|
609
|
+
type EscapeHatchDeclaration = {
|
|
610
|
+
reason: string;
|
|
611
|
+
grants?: EscapeHatchGrant[];
|
|
612
|
+
};
|
|
613
|
+
type EscapeHatchKind = "unsafe-raw" | "acknowledge-cross-tenant" | "global-write" | "identity-switch" | "unsafe-all-tenants" | "cross-tenant-read";
|
|
614
|
+
type EscapeHatchUseEvent = {
|
|
615
|
+
handler: string;
|
|
616
|
+
kind: EscapeHatchKind;
|
|
617
|
+
reason: string;
|
|
618
|
+
tenantId: TenantId;
|
|
619
|
+
actor: string;
|
|
620
|
+
caller?: string;
|
|
621
|
+
target?: EscapeHatchTarget;
|
|
622
|
+
};
|
|
623
|
+
type EventDef<TPayload = unknown, TName extends string = string> = {
|
|
624
|
+
name: TName;
|
|
625
|
+
schema: ZodType<TPayload>;
|
|
626
|
+
version: number;
|
|
627
|
+
piiFields: EventPiiStance;
|
|
628
|
+
};
|
|
629
|
+
type ExtensionOptionsArgs<N extends string> = N extends keyof KumikoExtensionOptionsMap ? Record<never, never> extends KumikoExtensionOptionsMap[N] ? [ options?: ExtensionOptionsFor<N> ] : [ options: ExtensionOptionsFor<N> ] : [ options?: Record<string, unknown> ]; // Rest-tuple for useExtension's positional options arg: required for an augmented name with required keys, optional otherwise.
|
|
630
|
+
type ExtensionOptionsFor<N extends string> = N extends keyof KumikoExtensionOptionsMap ? KumikoExtensionOptionsMap[N] & { escapeHatch?: EscapeHatchDeclaration; } : Record<string, unknown>; // Options shape for an extension name: the augmented hook shape plus `escapeHatch`, or an untyped bag if unaugmented.
|
|
631
|
+
type FeatureDefinition = {
|
|
632
|
+
name: string;
|
|
633
|
+
description?: string;
|
|
634
|
+
systemScope: boolean;
|
|
635
|
+
exports?: unknown;
|
|
636
|
+
requires: string[];
|
|
637
|
+
optionalRequires: string[];
|
|
638
|
+
requiredProjections: ReadonlySet<string>;
|
|
639
|
+
requiredSteps: ReadonlySet<string>;
|
|
640
|
+
toggleableDefault?: boolean;
|
|
641
|
+
adoptsBundledLists?: true;
|
|
642
|
+
uiHints?: UiHints;
|
|
643
|
+
entities?: Readonly<Record<string, EntityDefinition>>;
|
|
644
|
+
entityTables?: Readonly<Record<string, unknown>>;
|
|
645
|
+
relations: Readonly<Record<string, EntityRelations>>;
|
|
646
|
+
writeHandlers: Readonly<Record<string, WriteHandlerDef>>;
|
|
647
|
+
queryHandlers: Readonly<Record<string, QueryHandlerDef>>;
|
|
648
|
+
streamHandlers: Readonly<Record<string, StreamHandlerDef>>;
|
|
649
|
+
translations: TranslationKeys;
|
|
650
|
+
hooks?: HookMap;
|
|
651
|
+
entityHooks?: EntityHookMap;
|
|
652
|
+
searchPayloadExtensions?: Readonly<Record<string, OwnedFn<SearchPayloadContributorFn>[]>>;
|
|
653
|
+
configKeys: Readonly<Record<string, ConfigKeyDefinition>>;
|
|
654
|
+
configSeeds: ConfigSeedDef[];
|
|
655
|
+
jobs: Readonly<Record<string, JobDefinition>>;
|
|
656
|
+
registrarExtensions: Readonly<Record<string, RegistrarExtensionDef>>;
|
|
657
|
+
extensionUsages: RegistrarExtensionRegistration[];
|
|
658
|
+
extensionSelectors: ExtensionSelectorDef[];
|
|
659
|
+
exposedApis: ReadonlySet<string>; // Cross-feature API names this feature exposes via `r.exposesApi(name)`.
|
|
660
|
+
usedApis: ReadonlySet<string>; // Cross-feature API names this feature calls.
|
|
661
|
+
bootChecks: BootCheckFn[]; // Boot-time mount-invariant checks declared via `r.bootCheck(fn)`.
|
|
662
|
+
bootRequires?: StackBootRequirement;
|
|
663
|
+
referenceData: ReferenceDataDef[];
|
|
664
|
+
notifications: Readonly<Record<string, NotificationDefinition>>;
|
|
665
|
+
events: Readonly<Record<string, EventDef>>;
|
|
666
|
+
eventMigrations: Readonly<Record<string, EventMigrationDef[]>>;
|
|
667
|
+
configReads: string[];
|
|
668
|
+
handlerEntityMappings: Readonly<Record<string, string>>;
|
|
669
|
+
metrics: Readonly<Record<string, FeatureMetricDef>>;
|
|
670
|
+
secretKeys: Readonly<Record<string, SecretKeyDefinition>>;
|
|
671
|
+
secretNamespaces: Readonly<Record<string, SecretNamespaceDefinition>>;
|
|
672
|
+
probes?: Readonly<Record<string, ProbeDefinition>>;
|
|
673
|
+
projections: Readonly<Record<string, ProjectionDefinition>>;
|
|
674
|
+
entityProjectionExtensions?: Readonly<Record<string, EntityProjectionExtension[]>>;
|
|
675
|
+
multiStreamProjections: Readonly<Record<string, MultiStreamProjectionDefinition>>;
|
|
676
|
+
authClaimsHooks: AuthClaimsFn[];
|
|
677
|
+
claimKeys: Readonly<Record<string, ClaimKeyDefinition>>;
|
|
678
|
+
screens: Readonly<Record<string, ScreenDefinition>>;
|
|
679
|
+
navs: Readonly<Record<string, NavDefinition>>;
|
|
680
|
+
workspaces: Readonly<Record<string, WorkspaceDefinition>>;
|
|
681
|
+
treeActions?: Readonly<Record<string, TreeActionDef>>;
|
|
682
|
+
contentCollections?: Readonly<Record<string, ContentCollectionDefinition>>;
|
|
683
|
+
httpRoutes: Readonly<Record<string, HttpRouteDefinition>>;
|
|
684
|
+
webSocketRoutes: Readonly<Record<string, WebSocketRouteDefinition>>;
|
|
685
|
+
storeTables: Readonly<Record<string, StoreTableEntry>>;
|
|
686
|
+
envSchema?: z.ZodObject<z.ZodRawShape>;
|
|
687
|
+
dedupeOptions?: Readonly<Record<string, unknown>>;
|
|
688
|
+
};
|
|
689
|
+
type FeatureManifest = {
|
|
690
|
+
source: string;
|
|
691
|
+
featureCount: number;
|
|
692
|
+
features: ManifestFeature[];
|
|
693
|
+
systemEvents?: ManifestSystemEvent[];
|
|
694
|
+
tier?: string;
|
|
695
|
+
};
|
|
696
|
+
type FeatureRegistrar<TFeature extends string = string> = { // `TFeature` is the literal feature-name from `defineFeature("foo", ...)` — default-`string` keeps every existing usage zero-config.
|
|
697
|
+
systemScope(): void;
|
|
698
|
+
describe(text: string): void;
|
|
699
|
+
requires: RequiresApi;
|
|
700
|
+
optionalRequires(...featureNames: string[]): void;
|
|
701
|
+
optionalRequires(options: { features: string[]; }): void;
|
|
702
|
+
toggleable(options: { default: boolean; }): void;
|
|
703
|
+
adoptsBundledLists(): void;
|
|
704
|
+
uiHints(hints: UiHints): void;
|
|
705
|
+
entity(definition: EntityObjectDefinition): EntityRef;
|
|
706
|
+
crud(entityName: string, entity: EntityDefinition, options?: RegisterEntityCrudOptions): void;
|
|
707
|
+
writeHandler<TName extends string, TSchema extends ZodType, TPrepared = undefined, TWhenNested extends BeforeTransactionWhenNested | undefined = undefined>(def: WriteHandlerDefinition<TName, TSchema, unknown, KumikoEventTypeMap, TPrepared, TWhenNested>): HandlerRef;
|
|
708
|
+
queryHandler<TName extends string, TSchema extends ZodType>(def: QueryHandlerDefinition<TName, TSchema>): HandlerRef;
|
|
709
|
+
streamHandler<TName extends string, TSchema extends ZodType>(def: StreamHandlerDefinition<TName, TSchema>): HandlerRef;
|
|
710
|
+
streamHandler<TSchema extends ZodType>(name: string, schema: TSchema, handler: StreamHandlerFn<z.infer<TSchema>>, options: { access: AccessRule; rateLimit?: RateLimitDeclaration; escapeHatch?: EscapeHatchDeclaration; }): HandlerRef;
|
|
711
|
+
relation(entity: NameOrRef, relationName: string, definition: RelationDefinition): void;
|
|
712
|
+
relation<TDef extends RelationDefinition>(definition: { entity: NameOrRef; name: string; } & TDef): void;
|
|
713
|
+
hook(definition: HookDefinition): void;
|
|
714
|
+
searchPayloadExtension(entity: NameOrRef, fn: SearchPayloadContributorFn): void;
|
|
715
|
+
config<T extends ConfigKeyType>(keyName: string, def: ConfigKeyDefinition<T>): ConfigKeyHandle<T>;
|
|
716
|
+
config<TKeys extends Readonly<Record<string, ConfigKeyDefinition<ConfigKeyType>>>>(definition: { keys: TKeys; seeds?: Readonly<Record<string, ConfigSeedDef>>; }): { [K in keyof TKeys]: ConfigKeyHandle<TKeys[K]["type"]>; };
|
|
717
|
+
job(name: string, options: Omit<JobDefinition, "name" | "handler">, handler: JobHandlerFn): void;
|
|
718
|
+
job(definition: JobDefinition): void;
|
|
719
|
+
notification(name: string, definition: { trigger?: { on: NameOrRef; }; recipient?: NotificationRecipientFn; data?: NotificationDataFn; templates?: Readonly<Record<string, NotificationTemplateFn>>; variables?: TemplateVariables; record?: NotificationRecordMapping; template?: Readonly<Record<string, NotificationTemplateBody>>; meta?: Readonly<Record<string, NotificationTemplateMeta>>; localizedTemplate?: () => NotificationLocalizedTemplate; renamedFrom?: string[]; systemOnly?: boolean; unsubscribe?: boolean; tenantTransport?: boolean; }): void;
|
|
720
|
+
notification(definition: { name: string; trigger?: { on: NameOrRef; }; recipient?: NotificationRecipientFn; data?: NotificationDataFn; templates?: Readonly<Record<string, NotificationTemplateFn>>; variables?: TemplateVariables; record?: NotificationRecordMapping; template?: Readonly<Record<string, NotificationTemplateBody>>; meta?: Readonly<Record<string, NotificationTemplateMeta>>; localizedTemplate?: () => NotificationLocalizedTemplate; renamedFrom?: string[]; systemOnly?: boolean; unsubscribe?: boolean; tenantTransport?: boolean; }): void;
|
|
721
|
+
translations(def: TranslationsDef): void;
|
|
722
|
+
defineEvent<const TInner extends string, TPayload>(name: TInner, schema: ZodType<TPayload>, options: { version?: number; piiFields: EventPiiStance; migrations?: { fromVersion: number; toVersion: number; transform: EventUpcastFn | DeclarativeEventMigration; }[]; }): EventDef<TPayload, QualifiedEventName<TFeature, TInner>>;
|
|
723
|
+
readsConfig(...qualifiedKeys: string[]): void;
|
|
724
|
+
readsConfig(options: { keys: string[]; }): void;
|
|
725
|
+
referenceData(entity: NameOrRef, data: Record<string, unknown>[], options?: { upsertKey?: string; }): void;
|
|
726
|
+
referenceData(definition: { entity: NameOrRef; data: Record<string, unknown>[]; upsertKey?: string; }): void;
|
|
727
|
+
extendsRegistrar(name: string, def: RegistrarExtensionDef): void;
|
|
728
|
+
useExtension<N extends string>(extensionName: N, entity: NameOrRef, ...options: ExtensionOptionsArgs<N>): void;
|
|
729
|
+
useExtension<N extends string>(definition: { name: N; entity: NameOrRef; } & ExtensionOptionsFor<N>): void;
|
|
730
|
+
extensionSelector(extensionName: string, key: { name: string; } | string, options?: { panels?: ExtensionSelectorPanel[]; }): void; // Declares which config key selects the active provider under an extension point — called by the point-owning foundation (e.g. `r.extensionSelector("mailTransport", configKeys.provider)`).
|
|
731
|
+
exposesApi(apiName: string): void; // Marker declaration: this feature provides a cross-feature API under the given name.
|
|
732
|
+
usesApi(apiName: string): void; // Declares that this feature calls a cross-feature API.
|
|
733
|
+
bootCheck(fn: BootCheckFn): void; // Declares a boot-time mount-invariant for this feature.
|
|
734
|
+
metric(shortName: string, options: MetricOptions): void;
|
|
735
|
+
metric(definition: { name: string; } & MetricOptions): void;
|
|
736
|
+
secret(shortName: string, options: SecretOptions): SecretKeyHandle;
|
|
737
|
+
secret(definition: { name: string; } & SecretOptions): SecretKeyHandle;
|
|
738
|
+
probe(options: ProbeOptions): void;
|
|
739
|
+
secretNamespace(shortName: string, options: SecretNamespaceOptions): SecretNamespaceHandle;
|
|
740
|
+
secretNamespace(definition: { name: string; } & SecretNamespaceOptions): SecretNamespaceHandle;
|
|
741
|
+
projection(definition: ProjectionDefinition): void;
|
|
742
|
+
multiStreamProjection(definition: MultiStreamProjectionDefinition): void;
|
|
743
|
+
extendEntityProjection(entityName: string, extension: EntityProjectionExtension): void;
|
|
744
|
+
authClaims(fn: AuthClaimsFn): void;
|
|
745
|
+
claimKey<T extends ClaimKeyType>(shortName: string, options: { type: T; }): ClaimKeyHandle<T>;
|
|
746
|
+
claimKey<T extends ClaimKeyType>(definition: { name: string; type: T; }): ClaimKeyHandle<T>;
|
|
747
|
+
screen(definition: ScreenDefinition): void;
|
|
748
|
+
nav(definition: NavDefinition): void;
|
|
749
|
+
contentCollection(definition: ContentCollectionDefinition): string;
|
|
750
|
+
workspace(definition: WorkspaceDefinition): void;
|
|
751
|
+
httpRoute(definition: HttpRouteDefinition): void;
|
|
752
|
+
webSocketRoute(definition: WebSocketRouteDefinition): void;
|
|
753
|
+
storeTable(meta: EntityTableMeta, options: StoreTableOptions): void;
|
|
754
|
+
treeActions<const TActions extends Record<string, TreeActionDef>>(actions: TActions): TreeActionsHandle<TFeature, TActions>;
|
|
755
|
+
envSchema(schema: z.ZodObject<z.ZodRawShape>): void;
|
|
756
|
+
};
|
|
757
|
+
type FieldCondition =
|
|
758
|
+
| boolean
|
|
759
|
+
| {
|
|
760
|
+
field: string;
|
|
761
|
+
eq: unknown;
|
|
762
|
+
}
|
|
763
|
+
| {
|
|
764
|
+
field: string;
|
|
765
|
+
ne: unknown;
|
|
766
|
+
}
|
|
767
|
+
| {
|
|
768
|
+
field: string;
|
|
769
|
+
in: unknown[];
|
|
770
|
+
}
|
|
771
|
+
| {
|
|
772
|
+
field: string;
|
|
773
|
+
notIn: unknown[];
|
|
774
|
+
}
|
|
775
|
+
;
|
|
776
|
+
type FieldDefinition = TextFieldDef | LongTextFieldDef | BooleanFieldDef | SelectFieldDef | MultiSelectFieldDef | NumberFieldDef | BigIntFieldDef | DecimalFieldDef | MoneyFieldDef | ReferenceFieldDef | EmbeddedFieldDef | JsonbFieldDef | DateFieldDef | TimestampFieldDef | TzFieldDef | LocatedTimestampFieldDef | FileFieldDef | ImageFieldDef | FilesFieldDef | ImagesFieldDef;
|
|
777
|
+
interface FieldFormatRegistry {
|
|
778
|
+
timestamp: {
|
|
779
|
+
locale?: string;
|
|
780
|
+
dateStyle?: "full" | "long" | "medium" | "short";
|
|
781
|
+
timeStyle?: "full" | "long" | "medium" | "short";
|
|
782
|
+
timeZone?: string; // IANA zone id (e.g. "Europe/Berlin").
|
|
783
|
+
};
|
|
784
|
+
date: {
|
|
785
|
+
locale?: string;
|
|
786
|
+
dateStyle?: "full" | "long" | "medium" | "short";
|
|
787
|
+
};
|
|
788
|
+
boolean: {
|
|
789
|
+
trueLabel?: string;
|
|
790
|
+
falseLabel?: string;
|
|
791
|
+
};
|
|
792
|
+
currency: {
|
|
793
|
+
symbol?: string;
|
|
794
|
+
};
|
|
795
|
+
priority: {
|
|
796
|
+
emptyLabel?: string;
|
|
797
|
+
prefix?: string;
|
|
798
|
+
};
|
|
799
|
+
number: NumberFormatOptions; // `grouping: false` renders without thousands-separators (e.g. a model year: "2021", not "2.021").
|
|
800
|
+
decimal: NumberFormatOptions; // Same options as `number`.
|
|
801
|
+
bigInt: {
|
|
802
|
+
locale?: string;
|
|
803
|
+
grouping?: boolean;
|
|
804
|
+
};
|
|
805
|
+
unit: {
|
|
806
|
+
unit: UnitKey;
|
|
807
|
+
locale?: string;
|
|
808
|
+
unitDisplay?: "long" | "short" | "narrow";
|
|
809
|
+
};
|
|
810
|
+
enumOption: {
|
|
811
|
+
keyPrefix: string;
|
|
812
|
+
tones?: Readonly<Partial<Record<string, SelectOptionTone>>>;
|
|
813
|
+
};
|
|
814
|
+
i18nText: Record<never, never>; // Value is a `DashboardI18nText` (`{ i18nKey, i18nParams }`) a query built server-side, or a plain i18n key, translated in the user's language.
|
|
815
|
+
json: {
|
|
816
|
+
indent?: number;
|
|
817
|
+
}; // JSON-safe structured value rendered as indented text.
|
|
818
|
+
image: {
|
|
819
|
+
variant?: string;
|
|
820
|
+
}; // List cell: FileRef id (or id array) as a thumbnail.
|
|
821
|
+
file: {
|
|
822
|
+
previewOpenByDefault?: boolean;
|
|
823
|
+
}; // FileRef id (or id array) as a download link with the file name.
|
|
824
|
+
}
|
|
825
|
+
type HandlerAuth = {
|
|
826
|
+
resolveClaims: (user: SessionUser) => Promise<Record<string, unknown>>;
|
|
827
|
+
resolveActiveMembership: (userId: string, tenantId: TenantId) => Promise<ActiveMembershipResult>;
|
|
828
|
+
};
|
|
829
|
+
type HandlerContext<TMap extends object = KumikoEventTypeMap> = {
|
|
830
|
+
db: HandlerDb;
|
|
831
|
+
systemDb?: UncheckedSystemDb;
|
|
832
|
+
crossTenantReads?: CrossTenantReads;
|
|
833
|
+
registry: Registry;
|
|
834
|
+
user: SessionUser; // The active SessionUser for the handler call, exposed as a convenience alias for `event.user`.
|
|
835
|
+
rateLimitDigest?: (value: string) => string; // Keyed digest for handler-owned rate-limit keys, so Redis never holds a plaintext or plainly hashed identifier.
|
|
836
|
+
includeDeleted?: boolean;
|
|
837
|
+
dispatch: HandlerDispatch;
|
|
838
|
+
hasFeature: (featureName: string) => Promise<boolean>;
|
|
839
|
+
events: HandlerEvents<TMap>;
|
|
840
|
+
obs: HandlerObservability;
|
|
841
|
+
tz: TzContext;
|
|
842
|
+
locale: string;
|
|
843
|
+
auth: HandlerAuth;
|
|
844
|
+
config?: ConfigAccessor;
|
|
845
|
+
notify?: NotifyFn;
|
|
846
|
+
services: ContextServices;
|
|
847
|
+
lifecycle: HandlerLifecycle;
|
|
848
|
+
tenants: HandlerTenants;
|
|
849
|
+
};
|
|
850
|
+
type HandlerDb = TenantDb & { // `ctx.db` plus `outsideTransaction`: the same tenant scoping, but never bound to the handler's tx, so writes through it survive a rollback.
|
|
851
|
+
outsideTransaction: TenantDb | undefined;
|
|
852
|
+
};
|
|
853
|
+
type HandlerDispatch = { // Cross-feature bridge: `query`/`write` run the target handler AS THE CURRENT USER, sharing the active tx + afterCommit queue.
|
|
854
|
+
query: (qn: string, payload: unknown) => Promise<unknown>;
|
|
855
|
+
queryAs: (user: SessionUser, qn: string, payload: unknown) => Promise<unknown>;
|
|
856
|
+
write: (qn: string, payload: unknown) => Promise<WriteResult>;
|
|
857
|
+
writeAs: (user: SessionUser, qn: string, payload: unknown) => Promise<WriteResult>;
|
|
858
|
+
queryAsMember: MemberReader;
|
|
859
|
+
queryProjection: <T = Record<string, unknown>>(qualifiedName: string, options?: { unsafeAllTenants?: boolean; }) => Promise<T[]>;
|
|
860
|
+
};
|
|
861
|
+
type HandlerEvents<TMap extends object = KumikoEventTypeMap> = { // Event-store surface of a handler.
|
|
862
|
+
append: AppendEventFn<TMap>; // Default append: a typed domain event on one aggregate stream in the current tx.
|
|
863
|
+
unsafeAppend: UnsafeAppendEventFn; // Untyped append (`payload: unknown`) with the same runtime as `append`.
|
|
864
|
+
tryAppend: TryAppendEventFn; // Optimistic append: runs in a savepoint and returns a version conflict as a result instead of throwing, so the handler can continue after losing a race on the same aggregate stream.
|
|
865
|
+
fetchForWriting: (args: FetchForWritingArgs) => Promise<AggregateStreamHandle>;
|
|
866
|
+
load: (aggregateId: string, options?: { asOf?: Temporal.Instant; atVersion?: never; } | { atVersion?: number; asOf?: never; }) => Promise<StoredEvent[]>;
|
|
867
|
+
archive: (aggregateId: string, args: { aggregateType: string; reason?: string; }) => Promise<void>;
|
|
868
|
+
restore: (aggregateId: string) => Promise<void>;
|
|
869
|
+
isArchived: (aggregateId: string) => Promise<boolean>;
|
|
870
|
+
snapshot: (args: { aggregateId: string; aggregateType: string; version: number; state: Record<string, unknown>; snapshotVersion?: number; }) => Promise<void>;
|
|
871
|
+
loadWithSnapshot: <TState extends Record<string, unknown>>(aggregateId: string, reducer: SnapshotReducer<TState>, initial: TState, options?: Omit<LoadAggregateWithSnapshotOptions, "upcastEvent">) => Promise<LoadAggregateWithSnapshotResult<TState>>;
|
|
872
|
+
};
|
|
873
|
+
type HandlerObservability = {
|
|
874
|
+
log: Logger;
|
|
875
|
+
tracer: Tracer;
|
|
876
|
+
metrics: MetricsHandle;
|
|
877
|
+
metricsFor: (featureName: string) => MetricsHandle;
|
|
878
|
+
meter?: Meter;
|
|
879
|
+
};
|
|
880
|
+
type JobContext = {
|
|
881
|
+
db: TenantDb;
|
|
882
|
+
events: JobEvents;
|
|
883
|
+
registry: Registry;
|
|
884
|
+
systemUser: SessionUser;
|
|
885
|
+
obs: JobObservability;
|
|
886
|
+
tz: TzContext;
|
|
887
|
+
triggeredBy: { id: string; tenantId: TenantId; } | null;
|
|
888
|
+
attempt: number;
|
|
889
|
+
finalAttempt: boolean;
|
|
890
|
+
defer: (delayMs: number) => JobDeferral;
|
|
891
|
+
canDefer: boolean;
|
|
892
|
+
systemDb?: UncheckedSystemDb;
|
|
893
|
+
crossTenantReads?: CrossTenantReads;
|
|
894
|
+
dispatch: JobDispatch;
|
|
895
|
+
triggerName?: string;
|
|
896
|
+
triggerEvent?: JobTriggerEvent;
|
|
897
|
+
_tenantId?: TenantId;
|
|
898
|
+
config?: ConfigAccessor;
|
|
899
|
+
notify?: NotifyFn;
|
|
900
|
+
services: ContextServices;
|
|
901
|
+
lifecycle: JobLifecycle;
|
|
902
|
+
};
|
|
903
|
+
type JobDeferral = {
|
|
904
|
+
kind: "job-deferral";
|
|
905
|
+
delayMs: number;
|
|
906
|
+
};
|
|
907
|
+
type JobDefinition = {
|
|
908
|
+
name: string;
|
|
909
|
+
handler: JobHandlerFn;
|
|
910
|
+
trigger: JobTrigger;
|
|
911
|
+
concurrency?: ConcurrencyMode | undefined;
|
|
912
|
+
maxPerTenant?: number | MaxPerTenantOptions | undefined;
|
|
913
|
+
debounceMs?: number | undefined;
|
|
914
|
+
maxDefers?: number | undefined;
|
|
915
|
+
retries?: number | undefined;
|
|
916
|
+
backoff?: JobBackoff | undefined;
|
|
917
|
+
timeout?: number | undefined;
|
|
918
|
+
schema?: ZodType | undefined;
|
|
919
|
+
runOnBoot?: boolean | undefined;
|
|
920
|
+
bootGate?: boolean | undefined;
|
|
921
|
+
perTenant?: boolean | undefined;
|
|
922
|
+
runIn?: JobRunIn | undefined;
|
|
923
|
+
escapeHatch?: EscapeHatchDeclaration | undefined;
|
|
924
|
+
tenantVisibleFailure?: { messageKey: string; subjectFields?: string[] | undefined; } | undefined;
|
|
925
|
+
tenantVisibleRun?: { subjectFields?: string[] | undefined; } | undefined;
|
|
926
|
+
};
|
|
927
|
+
type JobDispatch = Pick<HandlerDispatch, "write" | "writeAs" | "queryAs" | "queryAsMember">;
|
|
928
|
+
type JobEvents = Pick<HandlerEvents, "append">;
|
|
929
|
+
type JobHandlerFn = (payload: Record<string, unknown>, context: JobContext) => Promise<void | JobDeferral>;
|
|
930
|
+
type JobObservability = Pick<HandlerObservability, "log" | "metricsFor" | "meter">;
|
|
931
|
+
type JobTrigger =
|
|
932
|
+
| {
|
|
933
|
+
on: NameOrRef | NameOrRef[];
|
|
934
|
+
where?: Readonly<Record<string, string | number | boolean>>;
|
|
935
|
+
catchUp?: boolean;
|
|
936
|
+
}
|
|
937
|
+
| {
|
|
938
|
+
cron: string;
|
|
939
|
+
tz?: string;
|
|
940
|
+
}
|
|
941
|
+
| {
|
|
942
|
+
manual: true;
|
|
943
|
+
}
|
|
944
|
+
;
|
|
945
|
+
type JsonbFieldDef = ResolvedPiiFlags & {
|
|
946
|
+
type: "jsonb";
|
|
947
|
+
description?: string;
|
|
948
|
+
emptyValue?: JsonbEmptyValue; // `"array"`: the field holds a JSON array, so a row that never wrote it reads `[]` and writes take an array.
|
|
949
|
+
sensitive?: boolean;
|
|
950
|
+
readAsInstruction?: boolean; // A later run reads this field's value as an instruction — prompt, rule, template.
|
|
951
|
+
access?: FieldAccess;
|
|
952
|
+
};
|
|
953
|
+
interface KumikoEventTypeMap {}
|
|
954
|
+
interface KumikoExtensionOptionsMap {}
|
|
955
|
+
type LifecycleHookType = (typeof LifecycleHookTypes)[keyof typeof LifecycleHookTypes];
|
|
956
|
+
type ListColumnSpec =
|
|
957
|
+
| string
|
|
958
|
+
| {
|
|
959
|
+
field: string;
|
|
960
|
+
renderer?: FieldRenderer;
|
|
961
|
+
label?: string; // Optional header, overriding the default `<feature>:entity:<entity>:field:<field>` i18n convention.
|
|
962
|
+
virtual?: boolean; // Marks a column on a query-backed list (projectionList, relatedList, dashboard list, expandableRow) as drawn by a renderer rather than read from a row field.
|
|
963
|
+
refEntity?: string; // Marks this column as a reference lookup instead of a plain value — for projectionList/relatedList columns, which have no `EntityDefinition` to carry a real `reference` field type.
|
|
964
|
+
refLabelField?: string; // Row field on the referenced entity shown as the label (default "id").
|
|
965
|
+
sortable?: boolean; // Marks this column as sortable via a clickable header — for relatedList columns, which have no `EntityDefinition` field to carry `sortable` on.
|
|
966
|
+
valueType?: "number" | "decimal" | "bigInt" | "money"; // Value type of a relatedList column, which has no `EntityDefinition` field to derive it from.
|
|
967
|
+
hideOnNarrow?: boolean; // Leaves the column out of the compact card layout used below the `md` breakpoint (tables keep it).
|
|
968
|
+
hidden?: boolean; // Start state for the column picker: the column begins hidden and the user can show it.
|
|
969
|
+
pinned?: ListColumnPin; // Start pin for the column: it sticks to this edge while the table scrolls horizontally.
|
|
970
|
+
width?: number; // Start width in px.
|
|
971
|
+
minWidth?: number; // Smallest width in px the user can resize to.
|
|
972
|
+
display?: ListColumnDisplay; // Dashboard list panels only.
|
|
973
|
+
badgeToneField?: string; // Row field naming the badge tone; only with `display: "badge"`.
|
|
974
|
+
linkScreenField?: string; // Row field holding a screen QN (`<feature>:screen:<id>`): the cell links to that screen when the row sets it and the user may open it.
|
|
975
|
+
}
|
|
976
|
+
;
|
|
977
|
+
type ListFacetSpec =
|
|
978
|
+
| {
|
|
979
|
+
field: string;
|
|
980
|
+
type: "select";
|
|
981
|
+
label: string;
|
|
982
|
+
options: { value: string; label: string; }[];
|
|
983
|
+
}
|
|
984
|
+
| {
|
|
985
|
+
field: string;
|
|
986
|
+
type: "boolean";
|
|
987
|
+
label: string;
|
|
988
|
+
trueLabel: string;
|
|
989
|
+
falseLabel: string;
|
|
990
|
+
}
|
|
991
|
+
| {
|
|
992
|
+
field: string;
|
|
993
|
+
type: "reference";
|
|
994
|
+
label: string;
|
|
995
|
+
entity: string; // Entity name (same feature) or `feature:entity` (cross-feature), same convention as `ListColumnSpec.refEntity`.
|
|
996
|
+
labelField?: string; // Row field on the referenced entity shown as the option label (default "id").
|
|
997
|
+
}
|
|
998
|
+
| {
|
|
999
|
+
field: string; // Declarative only — the date field the range narrows.
|
|
1000
|
+
type: "dateRange";
|
|
1001
|
+
label: string;
|
|
1002
|
+
params: {
|
|
1003
|
+
from: string;
|
|
1004
|
+
to: string;
|
|
1005
|
+
}; // Query-payload keys the chosen bounds are sent under as ISO instants.
|
|
1006
|
+
}
|
|
1007
|
+
;
|
|
1008
|
+
type ListTableOptions = { // Opt-in table behaviour of a list.
|
|
1009
|
+
rememberState?: boolean; // Keeps search, sort, page size, facets and date range for the browser session and restores them when the screen is opened without list parameters in the URL.
|
|
1010
|
+
resetAll?: boolean; // Replaces the facet reset with one button that resets search, sort, page size, facets and date range, and drops the remembered list parameters (column and density choices stay).
|
|
1011
|
+
columnPicker?: boolean; // Lets the user hide and reorder columns in the view menu.
|
|
1012
|
+
pinning?: boolean; // Lets the user pin columns left or right in the view menu; columns may start pinned via `pinned`.
|
|
1013
|
+
resizing?: boolean; // Switches the table to table-layout fixed; the user drags or uses the arrow keys on the header edge to resize.
|
|
1014
|
+
keyboardNavigation?: boolean; // Rows become one tab stop; ArrowUp/ArrowDown move, Home/End jump, Enter runs the row click.
|
|
1015
|
+
density?: {
|
|
1016
|
+
default?: ListTableDensity; // Row density on first visit.
|
|
1017
|
+
userChoice?: boolean; // Shows the density choice in the view menu and remembers it per session.
|
|
1018
|
+
};
|
|
1019
|
+
export?: ListTableExportOptions; // Shows an export button; the server reads the whole filtered list (not just the visible page).
|
|
1020
|
+
grouping?: ListTableGrouping; // Server-side grouping: group headers with count and the `totals` measures over all matching rows; each open group loads its own rows.
|
|
1021
|
+
totals?: ListTableTotal[]; // Footer row under the rows, computed by the server over all rows matching the current filters and search.
|
|
1022
|
+
};
|
|
1023
|
+
type ManifestConfigKey = {
|
|
1024
|
+
key: string;
|
|
1025
|
+
qualifiedName: string;
|
|
1026
|
+
type: "text" | "number" | "boolean" | "select";
|
|
1027
|
+
scope: string;
|
|
1028
|
+
default: string | number | boolean | null;
|
|
1029
|
+
encrypted: boolean;
|
|
1030
|
+
computed: boolean;
|
|
1031
|
+
validated: boolean;
|
|
1032
|
+
options: string[] | null;
|
|
1033
|
+
bounds: { min?: number; max?: number; } | null;
|
|
1034
|
+
pattern: { regex: string; flags?: string; } | null;
|
|
1035
|
+
writeRoles: string[];
|
|
1036
|
+
readRoles: string[];
|
|
1037
|
+
};
|
|
1038
|
+
type ManifestExtension = {
|
|
1039
|
+
extensionName: string;
|
|
1040
|
+
entityName: string;
|
|
1041
|
+
};
|
|
1042
|
+
type ManifestFeature = {
|
|
1043
|
+
name: string;
|
|
1044
|
+
description: string | null;
|
|
1045
|
+
toggleableDefault: boolean | null;
|
|
1046
|
+
adoptsBundledLists?: true;
|
|
1047
|
+
requires: string[];
|
|
1048
|
+
optionalRequires: string[];
|
|
1049
|
+
configReads: string[];
|
|
1050
|
+
exposesApis: string[];
|
|
1051
|
+
usesApis: string[];
|
|
1052
|
+
extensionsUsed: ManifestExtension[];
|
|
1053
|
+
configKeys: ManifestConfigKey[];
|
|
1054
|
+
secrets: ManifestSecret[];
|
|
1055
|
+
writeHandlers: string[]; // All registered write-handler QNs for this feature (e.g. "user:write:user:create", "auth-email-password:write:login").
|
|
1056
|
+
uiHints?: UiHints; // Picker/scaffolder UI metadata declared via r.uiHints().
|
|
1057
|
+
tier?: string; // Optionaler Herkunfts-Tag (z.B.
|
|
1058
|
+
};
|
|
1059
|
+
type ManifestSecret = {
|
|
1060
|
+
qualifiedName: string;
|
|
1061
|
+
scope: string;
|
|
1062
|
+
label: string | null;
|
|
1063
|
+
hint: string | null;
|
|
1064
|
+
};
|
|
1065
|
+
type ManifestSystemEvent = { // Framework-owned registered event (`kumiko:system:*`), e.g. the workflow run-stream events a projection can apply.
|
|
1066
|
+
name: string;
|
|
1067
|
+
version: number;
|
|
1068
|
+
};
|
|
1069
|
+
type MetricNavigate = {
|
|
1070
|
+
screen?: string;
|
|
1071
|
+
entity?: string;
|
|
1072
|
+
entityId?: string;
|
|
1073
|
+
params?: RowFieldExtractor;
|
|
1074
|
+
tab?: string; // Section id of the tab to activate at the destination.
|
|
1075
|
+
};
|
|
1076
|
+
type MspErrorMode = {
|
|
1077
|
+
continuous?: MspErrorPolicy;
|
|
1078
|
+
rebuild?: MspErrorPolicy;
|
|
1079
|
+
};
|
|
1080
|
+
type MultiStreamApplyFn<TPayload = Record<string, unknown>> = (event: StoredEvent<TPayload>, tx: ExecutorManagedRunner, ctx: MultiStreamApplyContext) => Promise<void>;
|
|
1081
|
+
type MultiStreamProjectionDefinition = {
|
|
1082
|
+
name: string;
|
|
1083
|
+
table?: ProjectionTable;
|
|
1084
|
+
apply: Readonly<Record<string, MultiStreamApplyFn>>;
|
|
1085
|
+
errorMode?: MspErrorMode;
|
|
1086
|
+
runIn?: RunIn;
|
|
1087
|
+
delivery?: "shared" | "per-instance";
|
|
1088
|
+
startFrom?: "beginning" | "now";
|
|
1089
|
+
};
|
|
1090
|
+
type NavDefinition = {
|
|
1091
|
+
id: string;
|
|
1092
|
+
label: string;
|
|
1093
|
+
icon?: NavIconKey;
|
|
1094
|
+
parent?: string;
|
|
1095
|
+
order?: number;
|
|
1096
|
+
screen?: string;
|
|
1097
|
+
target?: TargetRef;
|
|
1098
|
+
actions?: TreeAction[];
|
|
1099
|
+
createAction?: TreeAction;
|
|
1100
|
+
provider?: boolean;
|
|
1101
|
+
access?: AccessRule;
|
|
1102
|
+
workspaces?: string[];
|
|
1103
|
+
};
|
|
1104
|
+
type NavIconKey = (typeof NAV_ICON_KEYS)[number];
|
|
1105
|
+
type NotificationDefinition = {
|
|
1106
|
+
name: string;
|
|
1107
|
+
trigger?: {
|
|
1108
|
+
on: string;
|
|
1109
|
+
};
|
|
1110
|
+
recipient?: NotificationRecipientFn;
|
|
1111
|
+
data?: NotificationDataFn;
|
|
1112
|
+
templates: Readonly<Record<string, NotificationTemplateFn>> | undefined;
|
|
1113
|
+
variables?: TemplateVariables;
|
|
1114
|
+
record?: NotificationRecordMapping;
|
|
1115
|
+
template?: Readonly<Record<string, NotificationTemplateBody>>;
|
|
1116
|
+
meta?: Readonly<Record<string, NotificationTemplateMeta>>;
|
|
1117
|
+
renamedFrom?: string[];
|
|
1118
|
+
localizedTemplate?: () => NotificationLocalizedTemplate;
|
|
1119
|
+
systemOnly?: boolean;
|
|
1120
|
+
unsubscribe?: boolean;
|
|
1121
|
+
tenantTransport?: boolean;
|
|
1122
|
+
};
|
|
1123
|
+
type NotificationLocalizedTemplate = Readonly<Record<string, { body: string; meta: NotificationTemplateMeta; }>>;
|
|
1124
|
+
type NotificationRecordMapping = {
|
|
1125
|
+
entity: string;
|
|
1126
|
+
map: Readonly<Record<string, string>>;
|
|
1127
|
+
};
|
|
1128
|
+
type NotificationTemplateBody = {
|
|
1129
|
+
body: string;
|
|
1130
|
+
};
|
|
1131
|
+
type NotificationTemplateMeta = {
|
|
1132
|
+
subject: string;
|
|
1133
|
+
previewText?: string;
|
|
1134
|
+
layoutSlug?: string;
|
|
1135
|
+
category?: "transactional" | "marketing";
|
|
1136
|
+
sender?: string;
|
|
1137
|
+
senderName?: string;
|
|
1138
|
+
replyTo?: string;
|
|
1139
|
+
};
|
|
1140
|
+
type NotifyDelivery = {
|
|
1141
|
+
channel: string;
|
|
1142
|
+
recipientId: string | null;
|
|
1143
|
+
status: NotifyDeliveryStatus;
|
|
1144
|
+
error: DeliveryErrorCode | null;
|
|
1145
|
+
confirmed?: false;
|
|
1146
|
+
deliveryAttemptId?: string;
|
|
1147
|
+
};
|
|
1148
|
+
type NotifyDeliveryStatus = "queued" | "sent" | "failed" | "skipped";
|
|
1149
|
+
type NotifyDispatched = {
|
|
1150
|
+
kind: "dispatched";
|
|
1151
|
+
deliveries: NotifyDelivery[];
|
|
1152
|
+
};
|
|
1153
|
+
type NotifyFactory = (user: SessionUser, tenantId: TenantId, jobDispatcher?: NotifyJobDispatcher, deliveryDb?: DbConnection, fileProviderResolver?: FileProviderResolver) => NotifyFn;
|
|
1154
|
+
type NotifyFn = {
|
|
1155
|
+
(notificationType: string, options: NotifyOptions & { immediate: true; }): Promise<NotifyDispatched>;
|
|
1156
|
+
(notificationType: string, options: NotifyOptions): Promise<NotifyResult>;
|
|
1157
|
+
};
|
|
1158
|
+
type NotifyJobDispatcher = {
|
|
1159
|
+
dispatch(jobName: string, payload: Record<string, unknown>, meta?: { priority?: number; }): Promise<unknown>;
|
|
1160
|
+
};
|
|
1161
|
+
type NotifyOptions = {
|
|
1162
|
+
to?: string | string[] | { tenant: TenantId; };
|
|
1163
|
+
route?: Readonly<Record<string, string>>;
|
|
1164
|
+
data?: Readonly<Record<string, unknown>>;
|
|
1165
|
+
priority?: NotifyPriority;
|
|
1166
|
+
idempotencyKey?: string;
|
|
1167
|
+
recipientId?: string;
|
|
1168
|
+
immediate?: boolean;
|
|
1169
|
+
locale?: string;
|
|
1170
|
+
};
|
|
1171
|
+
type NotifyPriority = "critical" | "normal" | "low";
|
|
1172
|
+
type NotifyResult = NotifyDispatched | NotifyDeferred;
|
|
1173
|
+
type OwnershipMap = Readonly<Record<string, OwnershipRule>>;
|
|
1174
|
+
type PagedQueryHandlerDefinition<TName extends string = string, TSchema extends ZodType = ZodType, TRow = unknown, TMap extends object = KumikoEventTypeMap> = QueryHandlerDefinition<TName, TSchema, CursorResult<TRow>, TMap> & {
|
|
1175
|
+
[PAGED_QUERY_HANDLER_BRAND]: true;
|
|
1176
|
+
};
|
|
1177
|
+
type PendingChange = {
|
|
1178
|
+
feature: string;
|
|
1179
|
+
type: ChangelogType;
|
|
1180
|
+
title: string;
|
|
1181
|
+
detail?: string;
|
|
1182
|
+
migration?: string;
|
|
1183
|
+
codemod?: string;
|
|
1184
|
+
manualAfterCodemod?: boolean;
|
|
1185
|
+
source: string;
|
|
1186
|
+
};
|
|
1187
|
+
type PipelineCtx<TPayload = unknown, TMap extends object = KumikoEventTypeMap> = HandlerContext<TMap> & { // Pipeline-side context handed to step argument resolvers.
|
|
1188
|
+
event: WriteEvent<TPayload>;
|
|
1189
|
+
steps: Readonly<Record<string, unknown>>;
|
|
1190
|
+
scope: Readonly<Record<string, unknown>>;
|
|
1191
|
+
workflow?: {
|
|
1192
|
+
runId: string;
|
|
1193
|
+
workflowName: string;
|
|
1194
|
+
stepIndex: number; // Current step index in the pipeline — set by the executor before each step.
|
|
1195
|
+
retryAttempt?: number; // Retry attempt counter for the current workflow.retry step.
|
|
1196
|
+
definitionFingerprint?: string; // Snapshot-at-Start fingerprint of the workflow definition that started this run.
|
|
1197
|
+
};
|
|
1198
|
+
};
|
|
1199
|
+
type PostDeleteHookFn = (payload: DeleteContext, context: HandlerContext) => Promise<void>;
|
|
1200
|
+
type PostSaveHookFn = (result: SaveContext, context: HandlerContext) => Promise<void>;
|
|
1201
|
+
type PreDeleteHookFn = (payload: DeleteContext, context: HandlerContext) => Promise<void>;
|
|
1202
|
+
type PreSaveHookFn = (changes: Record<string, unknown>, context: HandlerContext & { previous: Readonly<Record<string, unknown>>; isNew: boolean; }) => Promise<Record<string, unknown>>;
|
|
1203
|
+
type PrincipalProfile = {
|
|
1204
|
+
globalRoles: string[];
|
|
1205
|
+
timezone?: string;
|
|
1206
|
+
locale?: string;
|
|
1207
|
+
};
|
|
1208
|
+
type PrincipalStatus = "active" | "blocked" | "unknown";
|
|
1209
|
+
type PrincipalStatusPlugin = {
|
|
1210
|
+
resolveStatus: (userId: string, deps: { db: DbConnection; }) => Promise<PrincipalStatus>;
|
|
1211
|
+
resolveProfile: (userId: string, deps: { db: DbConnection; }) => Promise<PrincipalProfile | null>;
|
|
1212
|
+
};
|
|
1213
|
+
type ProbeContext = Pick<JobContext, "db" | "registry" | "obs" | "tz" | "services"> & {
|
|
1214
|
+
probeRunId: string;
|
|
1215
|
+
tenantId: TenantId;
|
|
1216
|
+
triggeredByUserId: string;
|
|
1217
|
+
triggeredByEmail: string | undefined;
|
|
1218
|
+
};
|
|
1219
|
+
type ProbeCost = "free" | "paid";
|
|
1220
|
+
type ProbeDefinition = ProbeOptions & {
|
|
1221
|
+
qualifiedName: string;
|
|
1222
|
+
};
|
|
1223
|
+
type ProbeOptions = {
|
|
1224
|
+
name: string;
|
|
1225
|
+
scope: ProbeScope;
|
|
1226
|
+
cost: ProbeCost;
|
|
1227
|
+
area: string; // i18n key, column "Bereich"
|
|
1228
|
+
label: string; // i18n key, column "Check" (used when the probe has no targets)
|
|
1229
|
+
timeoutMs?: number; // Default 10_000
|
|
1230
|
+
fixScreen?: string; // Screen QN linked from a failed result
|
|
1231
|
+
targets?: (ctx: ProbeContext) => Promise<ProbeTarget[]>;
|
|
1232
|
+
run: (ctx: ProbeContext, target: ProbeTarget | undefined) => Promise<ProbeVerdict>; // Failure = throw
|
|
1233
|
+
};
|
|
1234
|
+
type ProbeScope = "tenant" | "system";
|
|
1235
|
+
type ProbeTarget = {
|
|
1236
|
+
id: string;
|
|
1237
|
+
label: ProbeText;
|
|
1238
|
+
ownerUserId?: string;
|
|
1239
|
+
};
|
|
1240
|
+
type ProbeText = {
|
|
1241
|
+
key: string;
|
|
1242
|
+
params?: Readonly<Record<string, string | number>>;
|
|
1243
|
+
};
|
|
1244
|
+
type ProbeVerdict =
|
|
1245
|
+
| {
|
|
1246
|
+
status: "ok";
|
|
1247
|
+
message: ProbeText;
|
|
1248
|
+
}
|
|
1249
|
+
| {
|
|
1250
|
+
status: "skipped";
|
|
1251
|
+
message: ProbeText;
|
|
1252
|
+
}
|
|
1253
|
+
;
|
|
1254
|
+
type ProjectionDefinition = {
|
|
1255
|
+
name: string;
|
|
1256
|
+
source: string | string[];
|
|
1257
|
+
extraSources?: string[];
|
|
1258
|
+
extraSourceRowIds?: Readonly<Record<string, ProjectionRowIdOf>>;
|
|
1259
|
+
table: ProjectionTable;
|
|
1260
|
+
entity?: EntityDefinition;
|
|
1261
|
+
apply: Readonly<Record<string, SingleStreamApplyFn>>;
|
|
1262
|
+
isImplicit?: boolean;
|
|
1263
|
+
};
|
|
1264
|
+
type ProjectionDetailScreenDefinition = {
|
|
1265
|
+
id: string;
|
|
1266
|
+
type: "projectionDetail";
|
|
1267
|
+
fillHeight?: boolean; // Default true: the screen fills the shell content height, its body scrolls inside and the pager / action bar stays pinned.
|
|
1268
|
+
nav?: ScreenNavSugar;
|
|
1269
|
+
detailFor?: string;
|
|
1270
|
+
description?: string;
|
|
1271
|
+
agent?: AgentScreenHints;
|
|
1272
|
+
dormant?: boolean; // Screen has no nav entry by design (opened via link or navved by the app); exempts it from the nav-area boot check.
|
|
1273
|
+
visibleWhen?: ScreenVisibility; // UI-only gate, also on direct URL access; handlers still enforce access.
|
|
1274
|
+
fallback?: ScreenFallback; // Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice).
|
|
1275
|
+
query: string;
|
|
1276
|
+
recordTitleField?: string; // Query output field whose value titles the page header (breadcrumb) when no `header` is set.
|
|
1277
|
+
idParam?: string; // Query-payload key for the row-id.
|
|
1278
|
+
singleton?: boolean; // The server determines the shown row from the caller's session/context instead of a row id in the path — a self-service screen ("my profile", "my data") that has no row to link to (unlike `EntityEditScreenDefinition.
|
|
1279
|
+
live?: boolean; // Refetches the record and its relatedList sections when their entity changes (live SSE), for a screen that shows work a background job is still doing.
|
|
1280
|
+
layout: EditLayout;
|
|
1281
|
+
fieldLabels?: Readonly<Record<string, string>>; // Optional per-field label i18n key (field name → key), analogous to entityEdit.fieldLabels.
|
|
1282
|
+
header?: RecordHeaderSpec; // Record header above the layout — title/subtitle/status name columns in the query row (not literals).
|
|
1283
|
+
metrics?: MetricSpec[]; // Metric band above the layout — column names in the query row, each with its own i18n `label`.
|
|
1284
|
+
metricsStyle?: "band" | "tiles"; // "tiles" draws the metrics as the dashboard KPI tiles (StatCard) instead of the plain band.
|
|
1285
|
+
listScreenId?: string; // Parent list screen (short id) for a "back" navigation.
|
|
1286
|
+
slots?: ScreenSlots;
|
|
1287
|
+
access?: AccessRule;
|
|
1288
|
+
actions?: RowAction[]; // Header action buttons.
|
|
1289
|
+
primaryAction?: string; // Id of the action that stays the visible header button when the actions collapse into a menu (and the one that keeps the phone icon slot when it has an icon and no confirm).
|
|
1290
|
+
inlineActions?: string[]; // Action ids that stay visible buttons next to the primary action when the rest collapses into the menu.
|
|
1291
|
+
valueDisplay?: "form" | "text"; // How head fields render.
|
|
1292
|
+
};
|
|
1293
|
+
type ProjectionListScreenDefinition = {
|
|
1294
|
+
id: string;
|
|
1295
|
+
type: "projectionList";
|
|
1296
|
+
fillHeight?: boolean; // Default true: the screen fills the shell content height, its body scrolls inside and the pager / action bar stays pinned.
|
|
1297
|
+
nav?: ScreenNavSugar;
|
|
1298
|
+
detailFor?: string;
|
|
1299
|
+
description?: string;
|
|
1300
|
+
agent?: AgentScreenHints;
|
|
1301
|
+
dormant?: boolean; // Screen has no nav entry by design (opened via link or navved by the app); exempts it from the nav-area boot check.
|
|
1302
|
+
visibleWhen?: ScreenVisibility; // UI-only gate, also on direct URL access; handlers still enforce access.
|
|
1303
|
+
fallback?: ScreenFallback; // Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice).
|
|
1304
|
+
query: string;
|
|
1305
|
+
columns: ListColumnSpec[];
|
|
1306
|
+
rowRenderer?: PlatformComponent;
|
|
1307
|
+
cardRenderer?: PlatformComponent;
|
|
1308
|
+
rowActions?: RowAction[];
|
|
1309
|
+
rowActionMode?: RowActionMode; // Layout of the row-action column — see `RowActionMode`.
|
|
1310
|
+
bulkActions?: ListBulkAction[]; // Bulk actions over checkbox-selected rows, each pointing at a writeHandler row action.
|
|
1311
|
+
toolbarActions?: ToolbarAction[];
|
|
1312
|
+
pagination?: ListPaginationMode;
|
|
1313
|
+
pageSize?: number;
|
|
1314
|
+
table?: ListTableOptions; // Opt-in table behaviour: remembered state, reset, column picker, density.
|
|
1315
|
+
defaultSort?: ListSortSpec;
|
|
1316
|
+
emptyState?: ListEmptyStateSpec; // Replaces the default empty-list title/hint; the create button stays when the list can create.
|
|
1317
|
+
createLabel?: string; // i18n key for the primary create button label (default `kumiko.actions.create`).
|
|
1318
|
+
searchPlaceholder?: string; // i18n key for the search field placeholder (default `kumiko.list.search-placeholder`).
|
|
1319
|
+
searchable?: boolean;
|
|
1320
|
+
sortable?: boolean; // Derived by buildAppSchema from the query handler's Zod schema (`sort` param present).
|
|
1321
|
+
paginated?: boolean; // Derived by buildAppSchema from the query handler's Zod schema (`cursor` or `offset` param present).
|
|
1322
|
+
filter?: ScreenFilter; // Server-side filter, fixed on the screen — see `ScreenFilter` doc above (entityList).
|
|
1323
|
+
facets?: ListFacetSpec[]; // User-toggleable facet dropdowns — see `ListFacetSpec` doc.
|
|
1324
|
+
slots?: ScreenSlots;
|
|
1325
|
+
access?: AccessRule;
|
|
1326
|
+
};
|
|
1327
|
+
type QueryEvent<TPayload = unknown> = {
|
|
1328
|
+
type: string;
|
|
1329
|
+
payload: TPayload;
|
|
1330
|
+
user: SessionUser;
|
|
1331
|
+
};
|
|
1332
|
+
type QueryHandlerDef = {
|
|
1333
|
+
name: string;
|
|
1334
|
+
schema: ZodType;
|
|
1335
|
+
handler: QueryHandlerFn;
|
|
1336
|
+
[ENTITY_CONVENTION_QUERY_BRAND]?: true; // Set only by defineEntityQueryHandler, which wires the parent-ref read-gate.
|
|
1337
|
+
access: AccessRule;
|
|
1338
|
+
description?: string;
|
|
1339
|
+
agent?: AgentHandlerHints;
|
|
1340
|
+
rateLimit?: RateLimitDeclaration;
|
|
1341
|
+
outputSchema?: ZodType; // Zod schema of the handler's actual return value — the paged envelope `{ rows, nextCursor, total?
|
|
1342
|
+
liveEntities?: string[]; // Entities whose changes this query reflects.
|
|
1343
|
+
escapeHatch?: EscapeHatchDeclaration;
|
|
1344
|
+
};
|
|
1345
|
+
type QueryHandlerDefinition<TName extends string = string, TSchema extends ZodType = ZodType, TResult = unknown, TMap extends object = KumikoEventTypeMap> = {
|
|
1346
|
+
name: TName;
|
|
1347
|
+
schema: TSchema;
|
|
1348
|
+
access: AccessRule;
|
|
1349
|
+
description?: string;
|
|
1350
|
+
agent?: AgentHandlerHints;
|
|
1351
|
+
rateLimit?: RateLimitDeclaration;
|
|
1352
|
+
escapeHatch?: EscapeHatchDeclaration;
|
|
1353
|
+
handler: (query: QueryEvent<z.infer<TSchema>>, context: HandlerContext<TMap>) => Promise<TResult>;
|
|
1354
|
+
outputSchema?: ZodType; // Zod schema of `handler`'s actual return value — see QueryHandlerDef's doc (handlers.ts) for the boot-validator checks this unlocks.
|
|
1355
|
+
liveEntities?: string[]; // Entities whose changes this query reflects.
|
|
1356
|
+
};
|
|
1357
|
+
type RateLimitPer = (typeof RATE_LIMIT_PERS)[number];
|
|
1358
|
+
type ReferenceDataDef = {
|
|
1359
|
+
entityName: string;
|
|
1360
|
+
data: Record<string, unknown>[];
|
|
1361
|
+
upsertKey?: string | undefined;
|
|
1362
|
+
};
|
|
1363
|
+
type Registry = {
|
|
1364
|
+
features: ReadonlyMap<string, FeatureDefinition>;
|
|
1365
|
+
getFeature(name: string): FeatureDefinition | undefined;
|
|
1366
|
+
getEntity(name: string): EntityDefinition | undefined;
|
|
1367
|
+
getReadModelEntity(name: string): EntityDefinition | undefined;
|
|
1368
|
+
getReadModelEntityOwner(name: string): string | undefined;
|
|
1369
|
+
getAllEntities(): ReadonlyMap<string, EntityDefinition>;
|
|
1370
|
+
getWriteHandler(name: string): WriteHandlerDef | undefined;
|
|
1371
|
+
getQueryHandler(name: string): QueryHandlerDef | undefined;
|
|
1372
|
+
getAllQueryHandlers(): ReadonlyMap<string, QueryHandlerDef>;
|
|
1373
|
+
getAllWriteHandlers(): ReadonlyMap<string, WriteHandlerDef>;
|
|
1374
|
+
getStreamHandler(name: string): StreamHandlerDef | undefined;
|
|
1375
|
+
getAllStreamHandlers(): ReadonlyMap<string, StreamHandlerDef>;
|
|
1376
|
+
getSearchableFields(entityName: string): string[];
|
|
1377
|
+
getSortableFields(entityName: string): string[];
|
|
1378
|
+
getRelations(entityName: string): EntityRelations;
|
|
1379
|
+
getSearchableReferences(entityName: string): ReadonlyArray<{ fieldName: string; targetEntityName: string; labelField: string; }>;
|
|
1380
|
+
getSortableReferences(entityName: string): ReadonlyArray<{ fieldName: string; targetEntityName: string; labelField: string; }>;
|
|
1381
|
+
getIncomingRelations(entityName: string): ReadonlyArray<{ sourceEntity: string; relationName: string; relation: RelationDefinition; }>;
|
|
1382
|
+
getPreSaveHooks(name: string, effectiveFeatures?: ReadonlySet<string>): PreSaveHookFn[];
|
|
1383
|
+
getPostSaveHooks(name: string, phase?: HookPhase, effectiveFeatures?: ReadonlySet<string>): PostSaveHookFn[];
|
|
1384
|
+
getPreDeleteHooks(name: string, phase?: HookPhase, effectiveFeatures?: ReadonlySet<string>): PreDeleteHookFn[];
|
|
1385
|
+
getPostDeleteHooks(name: string, phase?: HookPhase, effectiveFeatures?: ReadonlySet<string>): PostDeleteHookFn[];
|
|
1386
|
+
getPostQueryHooks(name: string, effectiveFeatures?: ReadonlySet<string>): PostQueryHookFn[];
|
|
1387
|
+
getEntityPostSaveHooks(entityName: string, phase?: HookPhase, effectiveFeatures?: ReadonlySet<string>): PostSaveHookFn[];
|
|
1388
|
+
getEntityPreDeleteHooks(entityName: string, phase?: HookPhase, effectiveFeatures?: ReadonlySet<string>): PreDeleteHookFn[];
|
|
1389
|
+
getEntityPostDeleteHooks(entityName: string, phase?: HookPhase, effectiveFeatures?: ReadonlySet<string>): PostDeleteHookFn[];
|
|
1390
|
+
getEntityPostQueryHooks(entityName: string, effectiveFeatures?: ReadonlySet<string>): PostQueryHookFn[];
|
|
1391
|
+
getSearchPayloadExtensions(entityName: string, effectiveFeatures?: ReadonlySet<string>): SearchPayloadContributorFn[];
|
|
1392
|
+
getHandlerEntity(qualifiedHandler: string): string | undefined;
|
|
1393
|
+
isHandlerSystemScoped(qualifiedHandler: string): boolean;
|
|
1394
|
+
isJobSystemScoped(qualifiedJobName: string): boolean;
|
|
1395
|
+
getHandlerFeature(qualifiedHandler: string): string | undefined;
|
|
1396
|
+
getJobFeature(qualifiedJobName: string): string | undefined;
|
|
1397
|
+
hasRateLimitedHandler(): boolean;
|
|
1398
|
+
getAllMetrics(): ReadonlyMap<string, FeatureMetricDef & { featureName: string; }>;
|
|
1399
|
+
getAllTranslations(): TranslationKeys;
|
|
1400
|
+
getConfigKey(qualifiedKey: string): ConfigKeyDefinition | undefined;
|
|
1401
|
+
getAllConfigKeys(): ReadonlyMap<string, ConfigKeyDefinition>;
|
|
1402
|
+
getAllConfigSeeds(): ConfigSeedDef[];
|
|
1403
|
+
getAllSecretKeys(): ReadonlyMap<string, SecretKeyDefinition>;
|
|
1404
|
+
getSecretKey(qualifiedName: string): SecretKeyDefinition | undefined;
|
|
1405
|
+
findSecretNamespace(key: string): SecretNamespaceDefinition | undefined;
|
|
1406
|
+
getJob(qualifiedName: string): JobDefinition | undefined;
|
|
1407
|
+
getAllJobs(): ReadonlyMap<string, JobDefinition>;
|
|
1408
|
+
getEvent(qualifiedName: string): EventDef | undefined;
|
|
1409
|
+
getEventUpcasters(): ReadonlyMap<string, { currentVersion: number; chain: ReadonlyMap<number, EventUpcastFn>; }>;
|
|
1410
|
+
getExtension(name: string): RegistrarExtensionDef | undefined;
|
|
1411
|
+
getExtensionUsages(extensionName: string): RegistrarExtensionRegistration[];
|
|
1412
|
+
getAllExtensionSelectors(): ReadonlyMap<string, string>;
|
|
1413
|
+
getExtensionSelectorPanels(extensionName: string): ExtensionSelectorPanel[];
|
|
1414
|
+
getAllNotifications(): ReadonlyMap<string, NotificationDefinition>;
|
|
1415
|
+
getAllReferenceData(): ReferenceDataDef[];
|
|
1416
|
+
getProjectionsForSource(entityName: string): ProjectionDefinition[];
|
|
1417
|
+
getAllProjections(): ReadonlyMap<string, ProjectionDefinition>;
|
|
1418
|
+
getAllStoreTables(): ReadonlyMap<string, StoreTableDef>;
|
|
1419
|
+
getAllMultiStreamProjections(): ReadonlyMap<string, MultiStreamProjectionDefinition>;
|
|
1420
|
+
getMultiStreamProjectionFeature(qualifiedName: string): string | undefined;
|
|
1421
|
+
getAuthClaimsHooks(): AuthClaimsHookDef[];
|
|
1422
|
+
getAllClaimKeys(): ReadonlyMap<string, ClaimKeyDefinition>;
|
|
1423
|
+
getClaimKey(qualifiedName: string): ClaimKeyDefinition | undefined;
|
|
1424
|
+
getAllScreens(): ReadonlyMap<string, ScreenDefinition>;
|
|
1425
|
+
getScreen(qualifiedName: string): ScreenDefinition | undefined;
|
|
1426
|
+
getScreenFeature(qualifiedName: string): string | undefined;
|
|
1427
|
+
getScreensByEntity(entityName: string): ScreenDefinition[];
|
|
1428
|
+
getAllNavs(): ReadonlyMap<string, NavDefinition>;
|
|
1429
|
+
getNav(qualifiedName: string): NavDefinition | undefined;
|
|
1430
|
+
getNavFeature(qualifiedName: string): string | undefined;
|
|
1431
|
+
getNavsByParent(parentQualifiedName: string): NavDefinition[];
|
|
1432
|
+
getTopLevelNavs(): NavDefinition[];
|
|
1433
|
+
getAllWorkspaces(): ReadonlyMap<string, WorkspaceDefinition>;
|
|
1434
|
+
getWorkspace(qualifiedName: string): WorkspaceDefinition | undefined;
|
|
1435
|
+
getWorkspaceFeature(qualifiedName: string): string | undefined;
|
|
1436
|
+
getWorkspaceNavs(workspaceQualifiedName: string): string[];
|
|
1437
|
+
getDefaultWorkspace(): WorkspaceDefinition | undefined;
|
|
1438
|
+
getTreeActions(featureName: string): Readonly<Record<string, TreeActionDef>> | undefined;
|
|
1439
|
+
};
|
|
1440
|
+
type RelationDefinition = BelongsToRelation | HasManyRelation | ManyToManyRelation;
|
|
1441
|
+
type ReservationContext<TMap extends object = KumikoEventTypeMap> = Omit<HandlerContext<TMap>, PhaseWithoutTransaction> & { // ctx of `reserveBeforeTransaction`: nothing bound to the not-yet-open transaction.
|
|
1442
|
+
dispatch: Omit<HandlerDispatch, "write" | "writeAs">;
|
|
1443
|
+
lifecycle: Omit<HandlerLifecycle, "afterCommit" | "afterRollback">;
|
|
1444
|
+
};
|
|
1445
|
+
type ResolvedPiiFlags = {
|
|
1446
|
+
pii?: boolean;
|
|
1447
|
+
userOwned?: {
|
|
1448
|
+
ownerField: string;
|
|
1449
|
+
whenAbsent?: EntityPiiAbsentSubject;
|
|
1450
|
+
};
|
|
1451
|
+
tenantOwned?: boolean;
|
|
1452
|
+
recordOwned?: true;
|
|
1453
|
+
anonymize?: () => unknown | Promise<unknown>;
|
|
1454
|
+
allowPlaintext?: string;
|
|
1455
|
+
lookupable?: true; // Equality lookups (`fetchOne`/`filter eq`) remain possible despite encryption: the generated `<snake>_bidx` column carries an HMAC blind index, and the query compiler matches `(col = $1 OR col_bidx = $2)`.
|
|
1456
|
+
subjectRef?: true; // Foreign key into the `user` entity (authorId, assigneeId, ...) with no annotated content of its own.
|
|
1457
|
+
};
|
|
1458
|
+
type RetentionDef = {
|
|
1459
|
+
keepFor: string;
|
|
1460
|
+
strategy: "hardDelete" | "softDelete" | "anonymize" | "blockDelete";
|
|
1461
|
+
reference?: string;
|
|
1462
|
+
};
|
|
1463
|
+
type RoleAccessPersonalData = "public-intake";
|
|
1464
|
+
type RoleAccessRule = {
|
|
1465
|
+
roles: string[];
|
|
1466
|
+
personalData?: RoleAccessPersonalData;
|
|
1467
|
+
};
|
|
1468
|
+
type RowAction = RowActionWriteHandler | RowActionNavigate | RowActionDrawer | RowActionClientAction;
|
|
1469
|
+
type RowActionWriteHandler = {
|
|
1470
|
+
kind?: "writeHandler"; // Default for RowActions without an explicitly set `kind` — backwards-compatible form.
|
|
1471
|
+
id: string; // Stable id per screen — kebab-case, unique within the action set.
|
|
1472
|
+
label: string; // Display text (i18n key).
|
|
1473
|
+
handler: string; // Qualified name of the server handler, e.g. "publicstatus:write:maintenance:start".
|
|
1474
|
+
payload?: RowFieldExtractor; // Declarative payload per row.
|
|
1475
|
+
confirm?: string; // i18n key for the confirm dialog description.
|
|
1476
|
+
confirmLabel?: string; // i18n key for the confirm button text in the dialog.
|
|
1477
|
+
visible?: FieldCondition; // Conditional visibility per row.
|
|
1478
|
+
style?: "primary" | "secondary" | "danger"; // Visual style.
|
|
1479
|
+
icon?: IconKey; // Overrides the id-based default icon (see ACTION_ICON_BY_ID in kumiko-renderer) — closed IconKey vocabulary into the ICONS registry (renderer/web), analogous to EditFieldSpec.icon.
|
|
1480
|
+
display?: RowActionDisplay; // Inline render form in the row-action column — see `RowActionDisplay`.
|
|
1481
|
+
redirect?: string | ActionFormRedirect; // Where to go after the write succeeded instead of staying on the shown record.
|
|
1482
|
+
};
|
|
1483
|
+
type SaveContext = {
|
|
1484
|
+
kind: "save";
|
|
1485
|
+
id: EntityId;
|
|
1486
|
+
data: Readonly<Record<string, unknown>>;
|
|
1487
|
+
changes: Readonly<Record<string, unknown>>;
|
|
1488
|
+
previous: Readonly<Record<string, unknown>>;
|
|
1489
|
+
isNew: boolean;
|
|
1490
|
+
entityName?: string | undefined;
|
|
1491
|
+
event: StoredEvent;
|
|
1492
|
+
};
|
|
1493
|
+
type ScreenDefinition = EntityListScreenDefinition | ProjectionListScreenDefinition | ProjectionDetailScreenDefinition | DashboardScreenDefinition | EntityEditScreenDefinition | ActionFormScreenDefinition | ConfigEditScreenDefinition | SecretsEditScreenDefinition | SecretMintScreenDefinition | CustomScreenDefinition;
|
|
1494
|
+
type SecretKeyDefinition = {
|
|
1495
|
+
shortName: string;
|
|
1496
|
+
qualifiedName: string;
|
|
1497
|
+
label: {
|
|
1498
|
+
[locale: string]: string;
|
|
1499
|
+
};
|
|
1500
|
+
redact?: (plaintext: string) => string;
|
|
1501
|
+
hint?: {
|
|
1502
|
+
[locale: string]: string;
|
|
1503
|
+
};
|
|
1504
|
+
scope: "tenant";
|
|
1505
|
+
required?: boolean;
|
|
1506
|
+
writeRoles?: string[];
|
|
1507
|
+
valueSchema?: ZodType<string>;
|
|
1508
|
+
};
|
|
1509
|
+
type SecretKeyHandle = {
|
|
1510
|
+
name: string;
|
|
1511
|
+
};
|
|
1512
|
+
type SecretMintScreenDefinition = { // Mint form → one-time reveal → explicit confirm.
|
|
1513
|
+
id: string;
|
|
1514
|
+
type: "secretMint";
|
|
1515
|
+
nav?: ScreenNavSugar;
|
|
1516
|
+
detailFor?: string;
|
|
1517
|
+
description?: string;
|
|
1518
|
+
agent?: AgentScreenHints;
|
|
1519
|
+
dormant?: boolean; // Screen has no nav entry by design (opened via link or navved by the app); exempts it from the nav-area boot check.
|
|
1520
|
+
visibleWhen?: ScreenVisibility; // UI-only gate, also on direct URL access; handlers still enforce access.
|
|
1521
|
+
fallback?: ScreenFallback; // Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice).
|
|
1522
|
+
urlPrefillFields?: string[]; // Derived by buildAppSchema — see EntityEditScreenDefinition.urlPrefillFields.
|
|
1523
|
+
submitPrefilled?: boolean; // Submit is enabled without edits: values seeded from navigate `params` count as the user's intent.
|
|
1524
|
+
handler: string; // Write-handler QN dispatched on submit.
|
|
1525
|
+
fields: Readonly<Record<string, FormFieldDefinition>>;
|
|
1526
|
+
layout: EditLayout;
|
|
1527
|
+
redirect?: string; // Navigate here after the reveal is confirmed.
|
|
1528
|
+
cancelTarget?: string | false;
|
|
1529
|
+
listScreenId?: string;
|
|
1530
|
+
access?: AccessRule;
|
|
1531
|
+
reveal: SecretReveal;
|
|
1532
|
+
submitLabel?: string; // i18n-key for the mint button.
|
|
1533
|
+
confirm?: SecretMintConfirmStep; // Proof-of-receipt step rendered on the reveal card in place of the bare acknowledge button — see `SecretMintConfirmStep` doc.
|
|
1534
|
+
};
|
|
1535
|
+
type SecretNamespaceDefinition = {
|
|
1536
|
+
shortName: string;
|
|
1537
|
+
qualifiedPrefix: string;
|
|
1538
|
+
label: {
|
|
1539
|
+
[locale: string]: string;
|
|
1540
|
+
};
|
|
1541
|
+
hint?: {
|
|
1542
|
+
[locale: string]: string;
|
|
1543
|
+
};
|
|
1544
|
+
scope: "tenant";
|
|
1545
|
+
writeRoles?: string[];
|
|
1546
|
+
nameSchema?: ZodType<string>;
|
|
1547
|
+
valueSchema?: ZodType<string>;
|
|
1548
|
+
};
|
|
1549
|
+
type SecretNamespaceHandle = {
|
|
1550
|
+
prefix: string;
|
|
1551
|
+
keyFor(name: string): string;
|
|
1552
|
+
};
|
|
1553
|
+
type SecretNamespaceOptions = Omit<SecretNamespaceDefinition, "shortName" | "qualifiedPrefix">;
|
|
1554
|
+
type SessionUser = {
|
|
1555
|
+
id: string;
|
|
1556
|
+
tenantId: TenantId;
|
|
1557
|
+
roles: string[];
|
|
1558
|
+
timezone?: string;
|
|
1559
|
+
locale?: string;
|
|
1560
|
+
claims?: Readonly<Record<string, unknown>>;
|
|
1561
|
+
sid?: string;
|
|
1562
|
+
pat?: {
|
|
1563
|
+
tokenId: string;
|
|
1564
|
+
scopes: string[];
|
|
1565
|
+
allowedQns: string[];
|
|
1566
|
+
};
|
|
1567
|
+
origin?: SessionUserOrigin;
|
|
1568
|
+
};
|
|
1569
|
+
type StepInstance = { // An instance of a step in a pipeline — what users build via the step-builder (`r.step.compute(...)`).
|
|
1570
|
+
kind: StepKind;
|
|
1571
|
+
args: unknown;
|
|
1572
|
+
onFailure?: StepFailureStrategy;
|
|
1573
|
+
};
|
|
1574
|
+
type StepResolver<T, TPayload = unknown> = T | ((ctx: PipelineCtx<TPayload>) => T); // A resolver is either a static value or a function that derives the value from the pipeline-context.
|
|
1575
|
+
type StorageProviderDestroyTenantHook = TenantResourceDestroyHook;
|
|
1576
|
+
type StorageProviderHookCtx = TenantResourceHookCtx;
|
|
1577
|
+
type StreamHandlerDef = {
|
|
1578
|
+
name: string;
|
|
1579
|
+
schema: ZodType;
|
|
1580
|
+
handler: StreamHandlerFn;
|
|
1581
|
+
access: AccessRule;
|
|
1582
|
+
rateLimit?: RateLimitDeclaration;
|
|
1583
|
+
escapeHatch?: EscapeHatchDeclaration;
|
|
1584
|
+
};
|
|
1585
|
+
type TargetRef = {
|
|
1586
|
+
featureId: string;
|
|
1587
|
+
action: string;
|
|
1588
|
+
args?: Readonly<Record<string, unknown>>;
|
|
1589
|
+
};
|
|
1590
|
+
type TemplateListVariableDef = TemplateVariableCommon & {
|
|
1591
|
+
type: "list";
|
|
1592
|
+
item: Readonly<Record<string, TemplateScalarVariableDef>>;
|
|
1593
|
+
example?: Readonly<Record<string, unknown>>[];
|
|
1594
|
+
};
|
|
1595
|
+
type TemplateScalarVariableDef = (TemplateVariableCommon & { type: "text"; example?: string; }) | (TemplateVariableCommon & { type: "link"; example?: string; }) | (TemplateVariableCommon & { type: "richText"; example?: string; }) | (TemplateVariableCommon & { type: "money"; example?: number | { amount: number; currency: string; }; }) | (TemplateVariableCommon & { type: "date"; example?: string; }) | (TemplateVariableCommon & { type: "percent"; example?: number; });
|
|
1596
|
+
type TemplateVariableDef = TemplateScalarVariableDef | TemplateListVariableDef | TemplateImagesVariableDef;
|
|
1597
|
+
type TemplateVariables = Readonly<Record<string, TemplateVariableDef>>;
|
|
1598
|
+
type TenantDataDestroyHook = (ctx: TenantDataHookCtx) => Promise<TenantDestroyHookResult | void>;
|
|
1599
|
+
interface TenantDataHookCtx {
|
|
1600
|
+
db: ExecutorManagedTenantDb;
|
|
1601
|
+
registry: Registry;
|
|
1602
|
+
tenantId: TenantId;
|
|
1603
|
+
deadlineAt?: number;
|
|
1604
|
+
fileProviderResolver?: FileProviderResolver;
|
|
1605
|
+
log?: (message: string) => void;
|
|
1606
|
+
}
|
|
1607
|
+
type TenantDestroyHookResult =
|
|
1608
|
+
| {
|
|
1609
|
+
done: true;
|
|
1610
|
+
processed?: number;
|
|
1611
|
+
}
|
|
1612
|
+
| {
|
|
1613
|
+
done: false;
|
|
1614
|
+
processed: number;
|
|
1615
|
+
}
|
|
1616
|
+
;
|
|
1617
|
+
type TenantId = string;
|
|
1618
|
+
type TenantLifecycleStatusPlugin = {
|
|
1619
|
+
resolveStatus: (tenantId: TenantId, deps: { db: DbConnection; }) => Promise<{ status: string; } | null>;
|
|
1620
|
+
};
|
|
1621
|
+
type TenantResourceDestroyHook = (tenantId: TenantId, ctx: TenantResourceHookCtx) => Promise<TenantDestroyHookResult | void>;
|
|
1622
|
+
type TenantResourceExtensionName = typeof EXT_STORAGE_PROVIDER | typeof EXT_SEARCH_ADAPTER | typeof EXT_EXTERNAL_RESOURCE | typeof EXT_INFRA_RESOURCE; // The four `destroyTenant(tenantId, ctx)`-only resource-cleanup extension points.
|
|
1623
|
+
interface TenantResourceHookCtx {
|
|
1624
|
+
tenantId: TenantId;
|
|
1625
|
+
deadlineAt?: number;
|
|
1626
|
+
db: DbRunner;
|
|
1627
|
+
fileProviderResolver?: FileProviderResolver;
|
|
1628
|
+
log?: (message: string) => void;
|
|
1629
|
+
}
|
|
1630
|
+
type TenantUserModel = "single-user" | "multi-user"; // Effective tenant-occupancy model for THIS tenant during a forget run, set by the forget orchestrator.
|
|
1631
|
+
type TenantWriteTarget = {
|
|
1632
|
+
db: TenantDb;
|
|
1633
|
+
streamTenantId: TenantId | undefined;
|
|
1634
|
+
};
|
|
1635
|
+
type TierResolverPlugin = { // Plugin shape for the tier-resolver extension.
|
|
1636
|
+
build: (deps: { db: DbConnection; registry: Registry; cacheSync?: CacheSyncBus; }) => Promise<EffectiveFeaturesResolver>;
|
|
1637
|
+
};
|
|
1638
|
+
type ToggleReader = (featureName: string) => boolean | undefined;
|
|
1639
|
+
type ToolbarAction = ({ kind: "navigate"; id: string; label: string; screen: string; tab?: string; style?: "primary" | "secondary" | "danger"; } | { kind: "writeHandler"; id: string; label: string; handler: string; payload?: Record<string, unknown>; confirm?: string; confirmLabel?: string; style?: "primary" | "secondary" | "danger"; } | { kind: "drawer"; id: string; label: string; screen: string; style?: "primary" | "secondary" | "danger"; title?: string; subtitle?: string; }) & {
|
|
1640
|
+
visibleWhen?: DashboardPanelVisibility; // Shown only while the query's `field` equals `eq` (live, empty payload).
|
|
1641
|
+
};
|
|
1642
|
+
type TransferAdjacency = ReadonlyMap<string, TransferEdge[]>; // Outgoing edges per type: keyed by the type an edge hangs off, so the mover can ask "what hangs off the rows I just moved" for any type it reaches, at any depth, as often as it reaches it.
|
|
1643
|
+
type TransferEdge = {
|
|
1644
|
+
entityName: string;
|
|
1645
|
+
entity: EntityDefinition;
|
|
1646
|
+
parentEntityName: string; // The already-resolved type this edge hangs off — its moved row ids are the parent ids the mover matches this edge's rows against.
|
|
1647
|
+
link: TransferEdgeLink;
|
|
1648
|
+
};
|
|
1649
|
+
type TransferEdgeLink =
|
|
1650
|
+
| {
|
|
1651
|
+
kind: "parentRef";
|
|
1652
|
+
typeField: string;
|
|
1653
|
+
idField: string;
|
|
1654
|
+
}
|
|
1655
|
+
| {
|
|
1656
|
+
kind: "reference";
|
|
1657
|
+
field: string;
|
|
1658
|
+
}
|
|
1659
|
+
;
|
|
1660
|
+
type TransitionGraph<TStates extends string = string> = { // Type-safe transition graph.
|
|
1661
|
+
canTransition: (from: TStates, to: TStates) => boolean;
|
|
1662
|
+
allowedFrom: (from: TStates) => TStates[];
|
|
1663
|
+
assertTransition: (from: TStates, to: TStates) => void;
|
|
1664
|
+
};
|
|
1665
|
+
type TranslationEntry = Readonly<Record<string, TranslationValue>>;
|
|
1666
|
+
type TranslationKeys = Readonly<Record<string, TranslationEntry>>;
|
|
1667
|
+
type TreeAction = TreeActionBase & ({ screen: string; target?: never; } | { target: TargetRef; screen?: never; });
|
|
1668
|
+
type TreeActionDef<TArgs = Record<string, unknown>> = {
|
|
1669
|
+
args?: TArgs;
|
|
1670
|
+
};
|
|
1671
|
+
type TreeChildrenSubscribe = () => Subscribe<TreeNode[]>;
|
|
1672
|
+
type TreeNode = {
|
|
1673
|
+
label: string;
|
|
1674
|
+
icon?: string;
|
|
1675
|
+
state?: TreeNodeState;
|
|
1676
|
+
target?: TargetRef;
|
|
1677
|
+
actions?: TreeAction[];
|
|
1678
|
+
children?: TreeNode[] | TreeChildrenSubscribe;
|
|
1679
|
+
createAction?: TreeAction;
|
|
1680
|
+
};
|
|
1681
|
+
type TrialGate = (tenantId: TenantId, featureName: string) => Promise<boolean>; // Live-Trial-Gate-Shape.
|
|
1682
|
+
type UnitKey = "m2" | "km" | "m" | "kg" | "percent" | "mi";
|
|
1683
|
+
type UserDataDeleteHook = (ctx: UserDataHookCtx, strategy: UserDataDeleteStrategy) => Promise<UserDataDeleteHookResult>;
|
|
1684
|
+
type UserDataDeleteStrategy = "delete" | "anonymize"; // Strategy for the forget path per entity: - "delete": Physically remove the row (profile, personal notes, sessions).
|
|
1685
|
+
type UserDataExportHook = (ctx: UserDataHookCtx) => Promise<UserDataExportSnippet | null>; // Export hook: collects all data belonging to a user for an entity.
|
|
1686
|
+
interface UserDataExportSnippet { // Feature/entity snippet included in the export bundle.
|
|
1687
|
+
entity: string;
|
|
1688
|
+
rows: ReadonlyArray<Record<string, unknown>>;
|
|
1689
|
+
fileRefs?: ReadonlyArray<{ fileRefId: string; storageKey: string; fileName: string; }>; // Optional signed URLs for file references.
|
|
1690
|
+
}
|
|
1691
|
+
interface UserDataHookCtx {
|
|
1692
|
+
db: TenantDb;
|
|
1693
|
+
registry: Registry; // The app registry.
|
|
1694
|
+
tenantId: TenantId;
|
|
1695
|
+
userId: UserId;
|
|
1696
|
+
buildStorageProvider?: (tenantId: TenantId) => Promise<UserDataStorageProvider | undefined>; // Per-tenant storage-provider resolver, injected by the forget orchestrator from the mounted file-foundation — so a hook deletes binaries from the SAME store the upload/export path uses (delete-target == upload-target by construction).
|
|
1697
|
+
tenantModel?: TenantUserModel; // Effective tenant-occupancy model for this tenant — see {@link TenantUserModel}.
|
|
1698
|
+
userEmailBeforeDelete?: string | null; // Original user email captured before the forget transaction anonymizes it.
|
|
1699
|
+
}
|
|
1700
|
+
interface UserDataStorageProvider { // Minimal storage surface a file-aware forget hook needs to erase binaries.
|
|
1701
|
+
delete(storageKey: string): Promise<void>;
|
|
1702
|
+
list(prefix: string): Promise<string[]>;
|
|
1703
|
+
}
|
|
1704
|
+
type ValidateBootOptions = {
|
|
1705
|
+
warnOnUniqueAccessRoles?: boolean; // Warn when an access role is used by exactly one handler/config-key/ field across the whole boot scan — often a typo, but also the normal shape of a legitimate fine-grained role (one role scoped to one admin endpoint on purpose).
|
|
1706
|
+
navAllowlist?: ReadonlySet<string>; // QNs the app's sidebar allowlist admits.
|
|
1707
|
+
navAllowlistExempt?: ReadonlySet<string>; // Nav QNs deliberately left out of the sidebar allowlist because their screen is reachable another way (e.g. as a sub-page of a generated settings hub).
|
|
1708
|
+
env?: Readonly<Record<string, string | undefined>>; // Env the encrypted-field master-key probe reads.
|
|
1709
|
+
entityFieldCipher?: EnvelopeCipher; // The cipher the runner injects after validation (built from the app's `masterKey` or the env keyring).
|
|
1710
|
+
requiredLocales?: string[]; // Locales every translation key must carry across all mounted features (locale packages count).
|
|
1711
|
+
};
|
|
1712
|
+
type ValidationError = {
|
|
1713
|
+
field: string;
|
|
1714
|
+
error: string;
|
|
1715
|
+
};
|
|
1716
|
+
type ValidationHookFn = (data: Readonly<Record<string, unknown>>) => ValidationError[] | null;
|
|
1717
|
+
type WorkflowDefinition<TPayload = unknown, TData = unknown, TAwaits extends Record<string, string> = Record<string, string>> = { // Workflow definition — the result of defineWorkflow().
|
|
1718
|
+
__kind: "workflow";
|
|
1719
|
+
name: string;
|
|
1720
|
+
trigger: WorkflowTrigger;
|
|
1721
|
+
awaits?: TAwaits; // Declared expected events (workflow-resume-loop.md) — the sole route a waitForEvent step can reach an event type.
|
|
1722
|
+
pipelineDef: PipelineDef<TPayload, TData, { [K in keyof TAwaits]: AwaitedEventType; }>; // The pipeline definition containing the step list closure.
|
|
1723
|
+
idempotencyKey?: string | ((event: WriteEvent<TPayload>) => string); // Idempotency key for deduplication — prevents duplicate runs.
|
|
1724
|
+
};
|
|
1725
|
+
type WorkspaceDefinition = {
|
|
1726
|
+
id: string;
|
|
1727
|
+
label: string;
|
|
1728
|
+
icon?: string;
|
|
1729
|
+
order?: number;
|
|
1730
|
+
access?: AccessRule;
|
|
1731
|
+
nav?: string[];
|
|
1732
|
+
default?: boolean;
|
|
1733
|
+
};
|
|
1734
|
+
type WriteEvent<TPayload = unknown> = {
|
|
1735
|
+
type: string;
|
|
1736
|
+
payload: TPayload;
|
|
1737
|
+
user: SessionUser;
|
|
1738
|
+
};
|
|
1739
|
+
type WriteHandlerDef = {
|
|
1740
|
+
name: string;
|
|
1741
|
+
schema: ZodType;
|
|
1742
|
+
handler: WriteHandlerFn;
|
|
1743
|
+
access: AccessRule;
|
|
1744
|
+
description?: string;
|
|
1745
|
+
agent?: AgentHandlerHints;
|
|
1746
|
+
excludedFields?: string[]; // Fields a generic create/update handler never writes (`excludeFields`).
|
|
1747
|
+
unsafeSkipTransitionGuard?: boolean;
|
|
1748
|
+
rateLimit?: RateLimitDeclaration;
|
|
1749
|
+
additionalRateLimits?: PayloadRateLimitOption[]; // Per-payload-field limits, e.g. per recipient address.
|
|
1750
|
+
rateLimitCost?: (payload: unknown) => number; // Tokens each request books on every declared rateLimit bucket, computed from the validated payload (e.g. `(p) => p.files.length`).
|
|
1751
|
+
escapeHatch?: EscapeHatchDeclaration;
|
|
1752
|
+
reserveBeforeTransaction?: PreTransactionReservation; // Reserves before the handler transaction opens.
|
|
1753
|
+
beforeTransaction?: BeforeTransactionPhase; // Runs after all pre-handler gates (and after reserveBeforeTransaction) and before the transaction opens: no transaction and no connection held, for external I/O and reads only.
|
|
1754
|
+
beforeTransactionWhenNested?: BeforeTransactionWhenNested; // `"skip"`: a nested ctx.dispatch.write of this handler runs without the beforeTransaction phase (`event.prepared` is undefined) and without a reservation, so the handler does that work inside its own transaction.
|
|
1755
|
+
transaction?: WriteHandlerTransactionMode; // `"perCommand"`: the handler runs without a transaction of its own.
|
|
1756
|
+
tenantlessAnonymous?: boolean; // Lets an anonymous `POST /api/write` for this handler proceed under SYSTEM_TENANT_ID when the host resolves no tenant.
|
|
1757
|
+
perform?: PipelineDef;
|
|
1758
|
+
};
|
|
1759
|
+
type WriteHandlerDefinition<TName extends string = string, TSchema extends ZodType = ZodType, TData = unknown, TMap extends object = KumikoEventTypeMap, TPrepared = undefined, TWhenNested extends BeforeTransactionWhenNested | undefined = undefined> = {
|
|
1760
|
+
name: TName;
|
|
1761
|
+
schema: TSchema;
|
|
1762
|
+
access: AccessRule;
|
|
1763
|
+
description?: string;
|
|
1764
|
+
agent?: AgentHandlerHints;
|
|
1765
|
+
excludedFields?: string[]; // Set by the entity convention create/update handlers; see WriteHandlerDef.excludedFields.
|
|
1766
|
+
unsafeSkipTransitionGuard?: boolean;
|
|
1767
|
+
rateLimit?: RateLimitDeclaration;
|
|
1768
|
+
additionalRateLimits?: PayloadRateLimitOption[];
|
|
1769
|
+
rateLimitCost?: (payload: z.infer<TSchema>) => number; // Tokens each request books on every declared rateLimit bucket, computed from the validated payload (e.g. `(p) => p.files.length`).
|
|
1770
|
+
escapeHatch?: EscapeHatchDeclaration;
|
|
1771
|
+
reserveBeforeTransaction?: PreTransactionReservation;
|
|
1772
|
+
beforeTransaction?: BeforeTransactionPhase<z.infer<TSchema>, TPrepared>;
|
|
1773
|
+
beforeTransactionWhenNested?: TWhenNested;
|
|
1774
|
+
tenantlessAnonymous?: boolean;
|
|
1775
|
+
transaction?: WriteHandlerTransactionMode;
|
|
1776
|
+
handler: (event: PreparedWriteEvent<z.infer<TSchema>, PreparedOrSkipped<TPrepared, TWhenNested>>, context: HandlerContext<TMap>) => Promise<WriteResult<TData>>;
|
|
1777
|
+
perform?: PipelineDef<z.infer<TSchema>, TData>;
|
|
1778
|
+
};
|
|
1779
|
+
type WriteHandlerFn<TPayload = unknown, TData = unknown> = (event: WriteEvent<TPayload>, context: HandlerContext) => Promise<WriteResult<TData>>;
|
|
1780
|
+
type WriteHandlerInput<TName extends string = string, TSchema extends ZodType = ZodType, TData = unknown, TMap extends object = KumikoEventTypeMap, TPrepared = undefined, TWhenNested extends BeforeTransactionWhenNested | undefined = undefined> = ({ handler: (event: PreparedWriteEvent<z.infer<TSchema>, PreparedOrSkipped<TPrepared, TWhenNested>>, context: HandlerContext<TMap>) => Promise<WriteResult<TData>>; perform?: never; } | { perform: PipelineDef<z.infer<TSchema>, TData>; handler?: never; }) & {
|
|
1781
|
+
name: TName;
|
|
1782
|
+
schema: TSchema;
|
|
1783
|
+
access: AccessRule;
|
|
1784
|
+
description?: string;
|
|
1785
|
+
agent?: AgentHandlerHints;
|
|
1786
|
+
unsafeSkipTransitionGuard?: boolean;
|
|
1787
|
+
rateLimit?: RateLimitDeclaration;
|
|
1788
|
+
additionalRateLimits?: PayloadRateLimitOption[];
|
|
1789
|
+
rateLimitCost?: (payload: z.infer<TSchema>) => number; // Tokens each request books on every declared rateLimit bucket, computed from the validated payload (e.g. `(p) => p.files.length`).
|
|
1790
|
+
escapeHatch?: EscapeHatchDeclaration;
|
|
1791
|
+
reserveBeforeTransaction?: PreTransactionReservation;
|
|
1792
|
+
beforeTransaction?: BeforeTransactionPhase<z.infer<TSchema>, TPrepared>;
|
|
1793
|
+
beforeTransactionWhenNested?: TWhenNested;
|
|
1794
|
+
tenantlessAnonymous?: boolean;
|
|
1795
|
+
transaction?: WriteHandlerTransactionMode;
|
|
1796
|
+
};
|
|
1797
|
+
type WriteResult<TData = unknown> =
|
|
1798
|
+
| {
|
|
1799
|
+
isSuccess: true;
|
|
1800
|
+
data: TData;
|
|
1801
|
+
}
|
|
1802
|
+
| WriteFailure
|
|
1803
|
+
;
|
|
1804
|
+
```
|