@cosmicdrift/kumiko-types 0.388.0 → 0.389.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +1 -0
- package/dist/db-connection.d.ts +4 -4
- package/dist/feature-registrar.d.ts +326 -0
- package/dist/feature-registrar.js +1 -0
- package/dist/feature.d.ts +6 -314
- package/dist/handlers.d.ts +5 -6
- package/dist/list-aggregate-types.d.ts +6 -1
- package/dist/list-aggregate-types.js +3 -0
- package/dist/screen-entity-edit.d.ts +8 -20
- package/dist/screen-entity-edit.js +6 -4
- package/dist/screen-entity-list.d.ts +21 -8
- package/dist/screen-expandable-row.d.ts +16 -0
- package/dist/screen-expandable-row.js +1 -0
- package/dist/screen-list-bulk.d.ts +12 -0
- package/dist/screen-list-bulk.js +3 -0
- package/dist/screen-list-table.d.ts +70 -0
- package/dist/screen-list-table.js +28 -0
- package/dist/screen-projection.d.ts +6 -0
- package/dist/screen.d.ts +12 -4
- package/dist/screen.js +2 -0
- package/dist/tenant-db-types.d.ts +5 -0
- package/package.json +12 -4
- package/src/changes.json +13 -0
package/dist/config.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ export type ConfigValidateContext = {
|
|
|
31
31
|
readonly tenantId: TenantId;
|
|
32
32
|
readonly userId: string | null;
|
|
33
33
|
readonly db: TenantDb;
|
|
34
|
+
readonly readCascade: (qualifiedKey: string) => Promise<ConfigCascade>;
|
|
34
35
|
};
|
|
35
36
|
export type ConfigValidateFn<T extends ConfigKeyType = ConfigKeyType> = (value: ConfigValue<T>, ctx: ConfigValidateContext) => Promise<void>;
|
|
36
37
|
export type ConfigBacking = "config" | "secrets";
|
package/dist/db-connection.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type { SQL, TransactionSQL } from "bun";
|
|
2
2
|
export type RawDbClient = {
|
|
3
3
|
unsafe: (sql: string, params?: readonly unknown[]) => PromiseLike<readonly unknown[]>;
|
|
4
4
|
begin: <T>(fn: (tx: DbTx) => Promise<T>) => Promise<T>;
|
|
@@ -6,11 +6,11 @@ export type RawDbClient = {
|
|
|
6
6
|
timeout?: number;
|
|
7
7
|
}) => Promise<void>;
|
|
8
8
|
};
|
|
9
|
-
export type DbConnection =
|
|
10
|
-
export type DbTx =
|
|
9
|
+
export type DbConnection = SQL | RawDbClient;
|
|
10
|
+
export type DbTx = TransactionSQL | RawDbClient;
|
|
11
11
|
export type DbRunner = DbConnection | DbTx;
|
|
12
12
|
export type DbRow = Record<string, unknown>;
|
|
13
|
-
export type PgClient =
|
|
13
|
+
export type PgClient = SQL;
|
|
14
14
|
export type PgListenSubscription = {
|
|
15
15
|
unlisten(): Promise<void>;
|
|
16
16
|
};
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import type * as z from "zod";
|
|
2
|
+
import type { ZodType } from "zod";
|
|
3
|
+
import type { ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigSeedDef, ExtensionSelectorPanel, JobDefinition, JobHandlerFn, NotificationDataFn, NotificationLocalizedTemplate, NotificationRecipientFn, NotificationRecordMapping, NotificationTemplateBody, NotificationTemplateFn, NotificationTemplateMeta, RegistrarExtensionDef, TemplateVariables, TranslationsDef } from "./config.ts";
|
|
4
|
+
import type { QueryHandlerDefinition, StreamHandlerDefinition, WriteHandlerDefinition } from "./define-handler.ts";
|
|
5
|
+
import type { RegisterEntityCrudOptions } from "./entity-handlers.ts";
|
|
6
|
+
import type { EntityTableMeta } from "./entity-table-meta-types.ts";
|
|
7
|
+
import type { KumikoEventTypeMap } from "./event-type-map.ts";
|
|
8
|
+
import type { ExtensionOptionsArgs, ExtensionOptionsFor } from "./extension-options-map.ts";
|
|
9
|
+
import type { BootCheckFn, MetricOptions, SecretKeyHandle, SecretNamespaceHandle, SecretNamespaceOptions, SecretOptions, StoreTableOptions, UiHints } from "./feature.ts";
|
|
10
|
+
import type { EntityDefinition } from "./fields.ts";
|
|
11
|
+
import type { AccessRule, AuthClaimsFn, ClaimKeyHandle, ClaimKeyType, DeclarativeEventMigration, EntityRef, EscapeHatchDeclaration, EventDef, EventPiiStance, EventUpcastFn, HandlerRef, NameOrRef, QualifiedEventName, RateLimitDeclaration, StreamHandlerFn } from "./handlers.ts";
|
|
12
|
+
import type { HookPhase, PostDeleteHookFn, PostQueryHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, SearchPayloadContributorFn, ValidationHookFn } from "./hooks.ts";
|
|
13
|
+
import type { HttpRouteDefinition } from "./http-route.ts";
|
|
14
|
+
import type { ContentCollectionDefinition, NavDefinition } from "./nav.ts";
|
|
15
|
+
import type { EntityProjectionExtension, MultiStreamProjectionDefinition, ProjectionDefinition } from "./projection.ts";
|
|
16
|
+
import type { RelationDefinition } from "./relations.ts";
|
|
17
|
+
import type { ScreenDefinition } from "./screen.ts";
|
|
18
|
+
import type { TreeActionDef, TreeActionsHandle } from "./tree-node.ts";
|
|
19
|
+
import type { WebSocketRouteDefinition } from "./websocket-route.ts";
|
|
20
|
+
import type { WorkspaceDefinition } from "./workspace.ts";
|
|
21
|
+
type RefOrRefs = NameOrRef | readonly NameOrRef[];
|
|
22
|
+
type HookTarget = RefOrRefs | {
|
|
23
|
+
readonly allOf: NameOrRef;
|
|
24
|
+
};
|
|
25
|
+
export type EntityObjectDefinition = {
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly backingTable?: unknown;
|
|
28
|
+
} & EntityDefinition;
|
|
29
|
+
type HookEscapeHatch = {
|
|
30
|
+
readonly escapeHatch?: EscapeHatchDeclaration;
|
|
31
|
+
};
|
|
32
|
+
type HookPhaseOption = {
|
|
33
|
+
readonly phase?: HookPhase;
|
|
34
|
+
};
|
|
35
|
+
export type HookDefinition = {
|
|
36
|
+
readonly type: "validation";
|
|
37
|
+
readonly target: RefOrRefs;
|
|
38
|
+
readonly handler: ValidationHookFn;
|
|
39
|
+
readonly escapeHatch?: never;
|
|
40
|
+
readonly phase?: never;
|
|
41
|
+
} | ({
|
|
42
|
+
readonly type: "preSave";
|
|
43
|
+
readonly target: RefOrRefs;
|
|
44
|
+
readonly handler: PreSaveHookFn;
|
|
45
|
+
readonly phase?: never;
|
|
46
|
+
} & HookEscapeHatch) | ({
|
|
47
|
+
readonly type: "postSave";
|
|
48
|
+
readonly target: HookTarget;
|
|
49
|
+
readonly handler: PostSaveHookFn;
|
|
50
|
+
} & HookEscapeHatch & HookPhaseOption) | ({
|
|
51
|
+
readonly type: "preDelete";
|
|
52
|
+
readonly target: HookTarget;
|
|
53
|
+
readonly handler: PreDeleteHookFn;
|
|
54
|
+
readonly phase?: never;
|
|
55
|
+
} & HookEscapeHatch) | ({
|
|
56
|
+
readonly type: "postDelete";
|
|
57
|
+
readonly target: HookTarget;
|
|
58
|
+
readonly handler: PostDeleteHookFn;
|
|
59
|
+
} & HookEscapeHatch & HookPhaseOption) | ({
|
|
60
|
+
readonly type: "preQuery";
|
|
61
|
+
readonly target: RefOrRefs;
|
|
62
|
+
readonly handler: PreQueryHookFn;
|
|
63
|
+
readonly phase?: never;
|
|
64
|
+
} & HookEscapeHatch) | ({
|
|
65
|
+
readonly type: "postQuery";
|
|
66
|
+
readonly target: HookTarget;
|
|
67
|
+
readonly handler: PostQueryHookFn;
|
|
68
|
+
readonly phase?: never;
|
|
69
|
+
} & HookEscapeHatch);
|
|
70
|
+
/**
|
|
71
|
+
* `r.requires({ features })` declares the features this one depends on; the
|
|
72
|
+
* `.projection` extension declares read-side projection tables that this
|
|
73
|
+
* feature's pipeline steps are allowed to write via
|
|
74
|
+
* `r.step.unsafeProjectionUpsert` (hard-required for any unsafeProjection-*
|
|
75
|
+
* step usage, see Q10).
|
|
76
|
+
*
|
|
77
|
+
* @example r.requires({ features: ["auth", "tenant"] });
|
|
78
|
+
*/
|
|
79
|
+
export type RequiresApi = ((options: {
|
|
80
|
+
readonly features: readonly string[];
|
|
81
|
+
}) => void) & {
|
|
82
|
+
readonly projection: (tableName: string) => void;
|
|
83
|
+
readonly step: (stepKind: string) => void;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* `TFeature` is the literal feature-name from `defineFeature("foo", ...)` —
|
|
87
|
+
* default-`string` keeps every existing usage zero-config. Strict-typed
|
|
88
|
+
* features (apps that opt into the literal-name flavour) get propagated
|
|
89
|
+
* through to `defineEvent` so the returned `EventDef.name` is a literal
|
|
90
|
+
* `${CamelToKebab<TFeature>}:event:${CamelToKebab<TInner>}`. That literal
|
|
91
|
+
* threads through `ctx.appendEvent({ type: eventDef.name, ... })`,
|
|
92
|
+
* keeping strict-mode alive even when handlers route via `eventDef.name`
|
|
93
|
+
* instead of hand-typed string literals.
|
|
94
|
+
*/
|
|
95
|
+
export type FeatureRegistrar<TFeature extends string = string> = {
|
|
96
|
+
systemScope(): void;
|
|
97
|
+
describe(text: string): void;
|
|
98
|
+
requires: RequiresApi;
|
|
99
|
+
optionalRequires(...featureNames: string[]): void;
|
|
100
|
+
optionalRequires(options: {
|
|
101
|
+
readonly features: readonly string[];
|
|
102
|
+
}): void;
|
|
103
|
+
toggleable(options: {
|
|
104
|
+
default: boolean;
|
|
105
|
+
}): void;
|
|
106
|
+
adoptsBundledLists(): void;
|
|
107
|
+
uiHints(hints: UiHints): void;
|
|
108
|
+
/**
|
|
109
|
+
* @example r.entity({ name: "note", fields: { title: createTextField({ required: true }) } });
|
|
110
|
+
*/
|
|
111
|
+
entity(definition: EntityObjectDefinition): EntityRef;
|
|
112
|
+
crud(entityName: string, entity: EntityDefinition, options?: RegisterEntityCrudOptions): void;
|
|
113
|
+
/**
|
|
114
|
+
* @example r.writeHandler({ name: "note:archive", schema, handler, access: { roles: ["User"] } });
|
|
115
|
+
*/
|
|
116
|
+
writeHandler<TName extends string, TSchema extends ZodType, TPrepared = undefined>(def: WriteHandlerDefinition<TName, TSchema, unknown, KumikoEventTypeMap, TPrepared>): HandlerRef;
|
|
117
|
+
/**
|
|
118
|
+
* @example r.queryHandler({ name: "note:list", schema, handler, access: { roles: ["User"] } });
|
|
119
|
+
*/
|
|
120
|
+
queryHandler<TName extends string, TSchema extends ZodType>(def: QueryHandlerDefinition<TName, TSchema>): HandlerRef;
|
|
121
|
+
streamHandler<TName extends string, TSchema extends ZodType>(def: StreamHandlerDefinition<TName, TSchema>): HandlerRef;
|
|
122
|
+
streamHandler<TSchema extends ZodType>(name: string, schema: TSchema, handler: StreamHandlerFn<z.infer<TSchema>>, options: {
|
|
123
|
+
access: AccessRule;
|
|
124
|
+
rateLimit?: RateLimitDeclaration;
|
|
125
|
+
escapeHatch?: EscapeHatchDeclaration;
|
|
126
|
+
}): HandlerRef;
|
|
127
|
+
relation(entity: NameOrRef, relationName: string, definition: RelationDefinition): void;
|
|
128
|
+
relation<TDef extends RelationDefinition>(definition: {
|
|
129
|
+
readonly entity: NameOrRef;
|
|
130
|
+
readonly name: string;
|
|
131
|
+
} & TDef): void;
|
|
132
|
+
/**
|
|
133
|
+
* @example r.hook({ type: "postSave", target: { allOf: noteRef }, handler: notifyOwner });
|
|
134
|
+
*/
|
|
135
|
+
hook(definition: HookDefinition): void;
|
|
136
|
+
searchPayloadExtension(entity: NameOrRef, fn: SearchPayloadContributorFn): void;
|
|
137
|
+
config<T extends ConfigKeyType>(keyName: string, def: ConfigKeyDefinition<T>): ConfigKeyHandle<T>;
|
|
138
|
+
config<TKeys extends Readonly<Record<string, ConfigKeyDefinition<ConfigKeyType>>>>(definition: {
|
|
139
|
+
readonly keys: TKeys;
|
|
140
|
+
readonly seeds?: Readonly<Record<string, ConfigSeedDef>>;
|
|
141
|
+
}): {
|
|
142
|
+
readonly [K in keyof TKeys]: ConfigKeyHandle<TKeys[K]["type"]>;
|
|
143
|
+
};
|
|
144
|
+
job(name: string, options: Omit<JobDefinition, "name" | "handler">, handler: JobHandlerFn): void;
|
|
145
|
+
job(definition: JobDefinition): void;
|
|
146
|
+
notification(name: string, definition: {
|
|
147
|
+
readonly trigger?: {
|
|
148
|
+
readonly on: NameOrRef;
|
|
149
|
+
};
|
|
150
|
+
readonly recipient?: NotificationRecipientFn;
|
|
151
|
+
readonly data?: NotificationDataFn;
|
|
152
|
+
readonly templates?: Readonly<Record<string, NotificationTemplateFn>>;
|
|
153
|
+
readonly variables?: TemplateVariables;
|
|
154
|
+
readonly record?: NotificationRecordMapping;
|
|
155
|
+
readonly template?: Readonly<Record<string, NotificationTemplateBody>>;
|
|
156
|
+
readonly meta?: Readonly<Record<string, NotificationTemplateMeta>>;
|
|
157
|
+
readonly localizedTemplate?: () => NotificationLocalizedTemplate;
|
|
158
|
+
readonly renamedFrom?: readonly string[];
|
|
159
|
+
readonly systemOnly?: boolean;
|
|
160
|
+
readonly unsubscribe?: boolean;
|
|
161
|
+
readonly tenantTransport?: boolean;
|
|
162
|
+
}): void;
|
|
163
|
+
notification(definition: {
|
|
164
|
+
readonly name: string;
|
|
165
|
+
readonly trigger?: {
|
|
166
|
+
readonly on: NameOrRef;
|
|
167
|
+
};
|
|
168
|
+
readonly recipient?: NotificationRecipientFn;
|
|
169
|
+
readonly data?: NotificationDataFn;
|
|
170
|
+
readonly templates?: Readonly<Record<string, NotificationTemplateFn>>;
|
|
171
|
+
readonly variables?: TemplateVariables;
|
|
172
|
+
readonly record?: NotificationRecordMapping;
|
|
173
|
+
readonly template?: Readonly<Record<string, NotificationTemplateBody>>;
|
|
174
|
+
readonly meta?: Readonly<Record<string, NotificationTemplateMeta>>;
|
|
175
|
+
readonly localizedTemplate?: () => NotificationLocalizedTemplate;
|
|
176
|
+
readonly renamedFrom?: readonly string[];
|
|
177
|
+
readonly systemOnly?: boolean;
|
|
178
|
+
readonly unsubscribe?: boolean;
|
|
179
|
+
readonly tenantTransport?: boolean;
|
|
180
|
+
}): void;
|
|
181
|
+
translations(def: TranslationsDef): void;
|
|
182
|
+
defineEvent<const TInner extends string, TPayload>(name: TInner, schema: ZodType<TPayload>, options: {
|
|
183
|
+
readonly version?: number;
|
|
184
|
+
readonly piiFields: EventPiiStance;
|
|
185
|
+
readonly migrations?: readonly {
|
|
186
|
+
readonly fromVersion: number;
|
|
187
|
+
readonly toVersion: number;
|
|
188
|
+
readonly transform: EventUpcastFn | DeclarativeEventMigration;
|
|
189
|
+
}[];
|
|
190
|
+
}): EventDef<TPayload, QualifiedEventName<TFeature, TInner>>;
|
|
191
|
+
readsConfig(...qualifiedKeys: string[]): void;
|
|
192
|
+
readsConfig(options: {
|
|
193
|
+
readonly keys: readonly string[];
|
|
194
|
+
}): void;
|
|
195
|
+
referenceData(entity: NameOrRef, data: readonly Record<string, unknown>[], options?: {
|
|
196
|
+
upsertKey?: string;
|
|
197
|
+
}): void;
|
|
198
|
+
referenceData(definition: {
|
|
199
|
+
readonly entity: NameOrRef;
|
|
200
|
+
readonly data: readonly Record<string, unknown>[];
|
|
201
|
+
readonly upsertKey?: string;
|
|
202
|
+
}): void;
|
|
203
|
+
extendsRegistrar(name: string, def: RegistrarExtensionDef): void;
|
|
204
|
+
useExtension<N extends string>(extensionName: N, entity: NameOrRef, ...options: ExtensionOptionsArgs<N>): void;
|
|
205
|
+
useExtension<N extends string>(definition: {
|
|
206
|
+
readonly name: N;
|
|
207
|
+
readonly entity: NameOrRef;
|
|
208
|
+
} & ExtensionOptionsFor<N>): void;
|
|
209
|
+
/**
|
|
210
|
+
* Declares which config key selects the active provider under an
|
|
211
|
+
* extension point — called by the point-owning foundation (e.g.
|
|
212
|
+
* `r.extensionSelector("mailTransport", configKeys.provider)`).
|
|
213
|
+
* Readiness gating counts a provider-feature's `required` keys and
|
|
214
|
+
* secrets only while that provider is the selected one. Registry-build
|
|
215
|
+
* fails on duplicate declarations per extension and on selector keys
|
|
216
|
+
* that no mounted feature declares. A masked tenant selector key makes the
|
|
217
|
+
* Settings-Hub render one tenant dashboard for the owner: the selector as a
|
|
218
|
+
* select of the mounted plugin ids, the selected plugin's config and secrets
|
|
219
|
+
* as panels, and plugin features lose their own tenant nav. `config:write:set`
|
|
220
|
+
* rejects unknown plugin ids (`""` clears). `options.panels` adds the owner's own
|
|
221
|
+
* `custom`/`screen` panels to that dashboard, after the selection and before the
|
|
222
|
+
* plugin panels (a short `screen` id means a screen of the declaring feature).
|
|
223
|
+
*/
|
|
224
|
+
extensionSelector(extensionName: string, key: {
|
|
225
|
+
readonly name: string;
|
|
226
|
+
} | string, options?: {
|
|
227
|
+
readonly panels?: readonly ExtensionSelectorPanel[];
|
|
228
|
+
}): void;
|
|
229
|
+
/**
|
|
230
|
+
* Marker declaration: this feature provides a cross-feature API under the
|
|
231
|
+
* given name. The implementation is registered separately as a query or
|
|
232
|
+
* write handler under the QN pattern; `r.exposesApi` is only a boot-check
|
|
233
|
+
* surface.
|
|
234
|
+
*
|
|
235
|
+
* The boot validator checks that every `r.usesApi(name)` finds a matching
|
|
236
|
+
* exposer, that the exposer feature is listed in requires/optionalRequires,
|
|
237
|
+
* and that no API name is exposed more than once.
|
|
238
|
+
*
|
|
239
|
+
* ```ts
|
|
240
|
+
* defineFeature("compliance-profiles", (r) => {
|
|
241
|
+
* r.exposesApi("compliance.forTenant");
|
|
242
|
+
* r.queryHandler({
|
|
243
|
+
* name: "compliance:query:for-tenant",
|
|
244
|
+
* // ... actual implementation
|
|
245
|
+
* });
|
|
246
|
+
* });
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
exposesApi(apiName: string): void;
|
|
250
|
+
/**
|
|
251
|
+
* Declares that this feature calls a cross-feature API. The boot validator
|
|
252
|
+
* checks that another feature provides `r.exposesApi(apiName)` and that
|
|
253
|
+
* this feature lists it in `r.requires/optionalRequires`.
|
|
254
|
+
*
|
|
255
|
+
* ```ts
|
|
256
|
+
* defineFeature("user-data-rights", (r) => {
|
|
257
|
+
* r.requires({ features: ["compliance-profiles"] });
|
|
258
|
+
* r.usesApi("compliance.forTenant");
|
|
259
|
+
* });
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
usesApi(apiName: string): void;
|
|
263
|
+
/**
|
|
264
|
+
* Declares a boot-time mount-invariant for this feature. `fn` runs once
|
|
265
|
+
* at boot with a ctx exposing every mounted feature — throw to fail the
|
|
266
|
+
* boot with a clear message. Multiple calls per feature are allowed.
|
|
267
|
+
*
|
|
268
|
+
* Use for cross-feature invariants that `r.requires` can't express
|
|
269
|
+
* because they're conditional (only fail when the feature has a
|
|
270
|
+
* specific shape), e.g. "if this feature has PII entities, some
|
|
271
|
+
* user-data-hook feature must be mounted" (the prompt-store trap —
|
|
272
|
+
* a UserData feature was written but never mounted, and nothing caught
|
|
273
|
+
* it at boot):
|
|
274
|
+
*
|
|
275
|
+
* ```ts
|
|
276
|
+
* defineFeature("prompt-store", (r) => {
|
|
277
|
+
* const promptFields = { text: createTextField({ personal: "self", find: "none" }) };
|
|
278
|
+
* r.entity({ name: "prompt", fields: promptFields });
|
|
279
|
+
* r.bootCheck(({ features }) => {
|
|
280
|
+
* // Conditional on this feature's own shape (has a pii field) —
|
|
281
|
+
* // r.requires({ features: ["user-data-hook"] }) can't express that, it would fail
|
|
282
|
+
* // even for a prompt-store variant with no PII fields at all.
|
|
283
|
+
* const hasPiiField = Object.values(promptFields).some((f) => f.pii);
|
|
284
|
+
* const hasUserDataHook = features.some((f) => f.name === "user-data-hook");
|
|
285
|
+
* if (hasPiiField && !hasUserDataHook) {
|
|
286
|
+
* throw new Error("prompt-store has PII fields but no user-data-hook feature is mounted");
|
|
287
|
+
* }
|
|
288
|
+
* });
|
|
289
|
+
* });
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
bootCheck(fn: BootCheckFn): void;
|
|
293
|
+
metric(shortName: string, options: MetricOptions): void;
|
|
294
|
+
metric(definition: {
|
|
295
|
+
readonly name: string;
|
|
296
|
+
} & MetricOptions): void;
|
|
297
|
+
secret(shortName: string, options: SecretOptions): SecretKeyHandle;
|
|
298
|
+
secret(definition: {
|
|
299
|
+
readonly name: string;
|
|
300
|
+
} & SecretOptions): SecretKeyHandle;
|
|
301
|
+
secretNamespace(shortName: string, options: SecretNamespaceOptions): SecretNamespaceHandle;
|
|
302
|
+
secretNamespace(definition: {
|
|
303
|
+
readonly name: string;
|
|
304
|
+
} & SecretNamespaceOptions): SecretNamespaceHandle;
|
|
305
|
+
projection(definition: ProjectionDefinition): void;
|
|
306
|
+
multiStreamProjection(definition: MultiStreamProjectionDefinition): void;
|
|
307
|
+
extendEntityProjection(entityName: string, extension: EntityProjectionExtension): void;
|
|
308
|
+
authClaims(fn: AuthClaimsFn): void;
|
|
309
|
+
claimKey<T extends ClaimKeyType>(shortName: string, options: {
|
|
310
|
+
readonly type: T;
|
|
311
|
+
}): ClaimKeyHandle<T>;
|
|
312
|
+
claimKey<T extends ClaimKeyType>(definition: {
|
|
313
|
+
readonly name: string;
|
|
314
|
+
readonly type: T;
|
|
315
|
+
}): ClaimKeyHandle<T>;
|
|
316
|
+
screen(definition: ScreenDefinition): void;
|
|
317
|
+
nav(definition: NavDefinition): void;
|
|
318
|
+
contentCollection(definition: ContentCollectionDefinition): string;
|
|
319
|
+
workspace(definition: WorkspaceDefinition): void;
|
|
320
|
+
httpRoute(definition: HttpRouteDefinition): void;
|
|
321
|
+
webSocketRoute(definition: WebSocketRouteDefinition): void;
|
|
322
|
+
storeTable(meta: EntityTableMeta, options: StoreTableOptions): void;
|
|
323
|
+
treeActions<const TActions extends Record<string, TreeActionDef>>(actions: TActions): TreeActionsHandle<TFeature, TActions>;
|
|
324
|
+
envSchema(schema: z.ZodObject<z.ZodRawShape>): void;
|
|
325
|
+
};
|
|
326
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/feature.d.ts
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import type * as z from "zod";
|
|
2
2
|
import type { ZodType } from "zod";
|
|
3
|
-
import type { ConfigKeyDefinition,
|
|
4
|
-
import type { QueryHandlerDefinition, StreamHandlerDefinition, WriteHandlerDefinition } from "./define-handler.ts";
|
|
5
|
-
import type { RegisterEntityCrudOptions } from "./entity-handlers.ts";
|
|
3
|
+
import type { ConfigKeyDefinition, ConfigSeedDef, ExtensionSelectorDef, ExtensionSelectorPanel, JobDefinition, NotificationDefinition, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionRegistration, TranslationKeys } from "./config.ts";
|
|
6
4
|
import type { EntityTableMeta } from "./entity-table-meta-types.ts";
|
|
7
|
-
import type { KumikoEventTypeMap } from "./event-type-map.ts";
|
|
8
|
-
import type { ExtensionOptionsArgs, ExtensionOptionsFor } from "./extension-options-map.ts";
|
|
9
5
|
import type { EntityDefinition } from "./fields.ts";
|
|
10
|
-
import type {
|
|
11
|
-
import type { EntityHookMap, HookMap, HookPhase, OwnedFn, PostDeleteHookFn, PostQueryHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, SearchPayloadContributorFn
|
|
6
|
+
import type { AuthClaimsFn, AuthClaimsHookDef, ClaimKeyDefinition, EventDef, EventMigrationDef, EventUpcastFn, QueryHandlerDef, StreamHandlerDef, WriteHandlerDef } from "./handlers.ts";
|
|
7
|
+
import type { EntityHookMap, HookMap, HookPhase, OwnedFn, PostDeleteHookFn, PostQueryHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, SearchPayloadContributorFn } from "./hooks.ts";
|
|
12
8
|
import type { HttpRouteDefinition } from "./http-route.ts";
|
|
13
9
|
import type { ContentCollectionDefinition, NavDefinition } from "./nav.ts";
|
|
14
10
|
import type { EntityProjectionExtension, MultiStreamProjectionDefinition, ProjectionDefinition } from "./projection.ts";
|
|
15
11
|
import type { EntityRelations, RelationDefinition } from "./relations.ts";
|
|
16
12
|
import type { ScreenDefinition } from "./screen.ts";
|
|
17
|
-
import type { TreeActionDef
|
|
13
|
+
import type { TreeActionDef } from "./tree-node.ts";
|
|
18
14
|
import type { WebSocketRouteDefinition } from "./websocket-route.ts";
|
|
19
15
|
import type { WorkspaceDefinition } from "./workspace.ts";
|
|
20
16
|
export type FeatureMetricType = "counter" | "histogram" | "gauge";
|
|
@@ -116,7 +112,7 @@ export type BootCheckContext = {
|
|
|
116
112
|
};
|
|
117
113
|
export type BootCheckFn = (ctx: BootCheckContext) => void;
|
|
118
114
|
/** runProdApp options a stack can depend on; runProdApp checks the set at boot. */
|
|
119
|
-
export type BootOptionName = "mail" | "kmsMode" | "fileStorage" | "billing" | "featureToggles" | "
|
|
115
|
+
export type BootOptionName = "mail" | "kmsMode" | "fileStorage" | "billing" | "featureToggles" | "rateLimit";
|
|
120
116
|
export type StackBootRequirement = {
|
|
121
117
|
readonly stack: string;
|
|
122
118
|
readonly options: readonly BootOptionName[];
|
|
@@ -133,6 +129,7 @@ export type FeatureDefinition = {
|
|
|
133
129
|
readonly requiredProjections: ReadonlySet<string>;
|
|
134
130
|
readonly requiredSteps: ReadonlySet<string>;
|
|
135
131
|
readonly toggleableDefault?: boolean;
|
|
132
|
+
readonly adoptsBundledLists?: true;
|
|
136
133
|
readonly uiHints?: UiHints;
|
|
137
134
|
readonly entities?: Readonly<Record<string, EntityDefinition>>;
|
|
138
135
|
readonly entityTables?: Readonly<Record<string, unknown>>;
|
|
@@ -198,310 +195,6 @@ export type FeatureDefinition = {
|
|
|
198
195
|
readonly envSchema?: z.ZodObject<z.ZodRawShape>;
|
|
199
196
|
readonly dedupeOptions?: Readonly<Record<string, unknown>>;
|
|
200
197
|
};
|
|
201
|
-
type RefOrRefs = NameOrRef | readonly NameOrRef[];
|
|
202
|
-
type HookTarget = RefOrRefs | {
|
|
203
|
-
readonly allOf: NameOrRef;
|
|
204
|
-
};
|
|
205
|
-
export type EntityObjectDefinition = {
|
|
206
|
-
readonly name: string;
|
|
207
|
-
readonly backingTable?: unknown;
|
|
208
|
-
} & EntityDefinition;
|
|
209
|
-
type HookEscapeHatch = {
|
|
210
|
-
readonly escapeHatch?: EscapeHatchDeclaration;
|
|
211
|
-
};
|
|
212
|
-
type HookPhaseOption = {
|
|
213
|
-
readonly phase?: HookPhase;
|
|
214
|
-
};
|
|
215
|
-
export type HookDefinition = {
|
|
216
|
-
readonly type: "validation";
|
|
217
|
-
readonly target: RefOrRefs;
|
|
218
|
-
readonly handler: ValidationHookFn;
|
|
219
|
-
readonly escapeHatch?: never;
|
|
220
|
-
readonly phase?: never;
|
|
221
|
-
} | ({
|
|
222
|
-
readonly type: "preSave";
|
|
223
|
-
readonly target: RefOrRefs;
|
|
224
|
-
readonly handler: PreSaveHookFn;
|
|
225
|
-
readonly phase?: never;
|
|
226
|
-
} & HookEscapeHatch) | ({
|
|
227
|
-
readonly type: "postSave";
|
|
228
|
-
readonly target: HookTarget;
|
|
229
|
-
readonly handler: PostSaveHookFn;
|
|
230
|
-
} & HookEscapeHatch & HookPhaseOption) | ({
|
|
231
|
-
readonly type: "preDelete";
|
|
232
|
-
readonly target: HookTarget;
|
|
233
|
-
readonly handler: PreDeleteHookFn;
|
|
234
|
-
readonly phase?: never;
|
|
235
|
-
} & HookEscapeHatch) | ({
|
|
236
|
-
readonly type: "postDelete";
|
|
237
|
-
readonly target: HookTarget;
|
|
238
|
-
readonly handler: PostDeleteHookFn;
|
|
239
|
-
} & HookEscapeHatch & HookPhaseOption) | ({
|
|
240
|
-
readonly type: "preQuery";
|
|
241
|
-
readonly target: RefOrRefs;
|
|
242
|
-
readonly handler: PreQueryHookFn;
|
|
243
|
-
readonly phase?: never;
|
|
244
|
-
} & HookEscapeHatch) | ({
|
|
245
|
-
readonly type: "postQuery";
|
|
246
|
-
readonly target: HookTarget;
|
|
247
|
-
readonly handler: PostQueryHookFn;
|
|
248
|
-
readonly phase?: never;
|
|
249
|
-
} & HookEscapeHatch);
|
|
250
|
-
/**
|
|
251
|
-
* `r.requires({ features })` declares the features this one depends on; the
|
|
252
|
-
* `.projection` extension declares read-side projection tables that this
|
|
253
|
-
* feature's pipeline steps are allowed to write via
|
|
254
|
-
* `r.step.unsafeProjectionUpsert` (hard-required for any unsafeProjection-*
|
|
255
|
-
* step usage, see Q10).
|
|
256
|
-
*
|
|
257
|
-
* @example r.requires({ features: ["auth", "tenant"] });
|
|
258
|
-
*/
|
|
259
|
-
export type RequiresApi = ((options: {
|
|
260
|
-
readonly features: readonly string[];
|
|
261
|
-
}) => void) & {
|
|
262
|
-
readonly projection: (tableName: string) => void;
|
|
263
|
-
readonly step: (stepKind: string) => void;
|
|
264
|
-
};
|
|
265
|
-
/**
|
|
266
|
-
* `TFeature` is the literal feature-name from `defineFeature("foo", ...)` —
|
|
267
|
-
* default-`string` keeps every existing usage zero-config. Strict-typed
|
|
268
|
-
* features (apps that opt into the literal-name flavour) get propagated
|
|
269
|
-
* through to `defineEvent` so the returned `EventDef.name` is a literal
|
|
270
|
-
* `${CamelToKebab<TFeature>}:event:${CamelToKebab<TInner>}`. That literal
|
|
271
|
-
* threads through `ctx.appendEvent({ type: eventDef.name, ... })`,
|
|
272
|
-
* keeping strict-mode alive even when handlers route via `eventDef.name`
|
|
273
|
-
* instead of hand-typed string literals.
|
|
274
|
-
*/
|
|
275
|
-
export type FeatureRegistrar<TFeature extends string = string> = {
|
|
276
|
-
systemScope(): void;
|
|
277
|
-
describe(text: string): void;
|
|
278
|
-
requires: RequiresApi;
|
|
279
|
-
optionalRequires(...featureNames: string[]): void;
|
|
280
|
-
optionalRequires(options: {
|
|
281
|
-
readonly features: readonly string[];
|
|
282
|
-
}): void;
|
|
283
|
-
toggleable(options: {
|
|
284
|
-
default: boolean;
|
|
285
|
-
}): void;
|
|
286
|
-
uiHints(hints: UiHints): void;
|
|
287
|
-
/**
|
|
288
|
-
* @example r.entity({ name: "note", fields: { title: createTextField({ required: true }) } });
|
|
289
|
-
*/
|
|
290
|
-
entity(definition: EntityObjectDefinition): EntityRef;
|
|
291
|
-
crud(entityName: string, entity: EntityDefinition, options?: RegisterEntityCrudOptions): void;
|
|
292
|
-
/**
|
|
293
|
-
* @example r.writeHandler({ name: "note:archive", schema, handler, access: { roles: ["User"] } });
|
|
294
|
-
*/
|
|
295
|
-
writeHandler<TName extends string, TSchema extends ZodType, TPrepared = undefined>(def: WriteHandlerDefinition<TName, TSchema, unknown, KumikoEventTypeMap, TPrepared>): HandlerRef;
|
|
296
|
-
/**
|
|
297
|
-
* @example r.queryHandler({ name: "note:list", schema, handler, access: { roles: ["User"] } });
|
|
298
|
-
*/
|
|
299
|
-
queryHandler<TName extends string, TSchema extends ZodType>(def: QueryHandlerDefinition<TName, TSchema>): HandlerRef;
|
|
300
|
-
streamHandler<TName extends string, TSchema extends ZodType>(def: StreamHandlerDefinition<TName, TSchema>): HandlerRef;
|
|
301
|
-
streamHandler<TSchema extends ZodType>(name: string, schema: TSchema, handler: StreamHandlerFn<z.infer<TSchema>>, options: {
|
|
302
|
-
access: AccessRule;
|
|
303
|
-
rateLimit?: RateLimitDeclaration;
|
|
304
|
-
escapeHatch?: EscapeHatchDeclaration;
|
|
305
|
-
}): HandlerRef;
|
|
306
|
-
relation(entity: NameOrRef, relationName: string, definition: RelationDefinition): void;
|
|
307
|
-
relation<TDef extends RelationDefinition>(definition: {
|
|
308
|
-
readonly entity: NameOrRef;
|
|
309
|
-
readonly name: string;
|
|
310
|
-
} & TDef): void;
|
|
311
|
-
/**
|
|
312
|
-
* @example r.hook({ type: "postSave", target: { allOf: noteRef }, handler: notifyOwner });
|
|
313
|
-
*/
|
|
314
|
-
hook(definition: HookDefinition): void;
|
|
315
|
-
searchPayloadExtension(entity: NameOrRef, fn: SearchPayloadContributorFn): void;
|
|
316
|
-
config<T extends ConfigKeyType>(keyName: string, def: ConfigKeyDefinition<T>): ConfigKeyHandle<T>;
|
|
317
|
-
config<TKeys extends Readonly<Record<string, ConfigKeyDefinition<ConfigKeyType>>>>(definition: {
|
|
318
|
-
readonly keys: TKeys;
|
|
319
|
-
readonly seeds?: Readonly<Record<string, ConfigSeedDef>>;
|
|
320
|
-
}): {
|
|
321
|
-
readonly [K in keyof TKeys]: ConfigKeyHandle<TKeys[K]["type"]>;
|
|
322
|
-
};
|
|
323
|
-
job(name: string, options: Omit<JobDefinition, "name" | "handler">, handler: JobHandlerFn): void;
|
|
324
|
-
job(definition: JobDefinition): void;
|
|
325
|
-
notification(name: string, definition: {
|
|
326
|
-
readonly trigger?: {
|
|
327
|
-
readonly on: NameOrRef;
|
|
328
|
-
};
|
|
329
|
-
readonly recipient?: NotificationRecipientFn;
|
|
330
|
-
readonly data?: NotificationDataFn;
|
|
331
|
-
readonly templates?: Readonly<Record<string, NotificationTemplateFn>>;
|
|
332
|
-
readonly variables?: TemplateVariables;
|
|
333
|
-
readonly record?: NotificationRecordMapping;
|
|
334
|
-
readonly template?: Readonly<Record<string, NotificationTemplateBody>>;
|
|
335
|
-
readonly meta?: Readonly<Record<string, NotificationTemplateMeta>>;
|
|
336
|
-
readonly localizedTemplate?: () => NotificationLocalizedTemplate;
|
|
337
|
-
readonly renamedFrom?: readonly string[];
|
|
338
|
-
readonly systemOnly?: boolean;
|
|
339
|
-
readonly unsubscribe?: boolean;
|
|
340
|
-
readonly tenantTransport?: boolean;
|
|
341
|
-
}): void;
|
|
342
|
-
notification(definition: {
|
|
343
|
-
readonly name: string;
|
|
344
|
-
readonly trigger?: {
|
|
345
|
-
readonly on: NameOrRef;
|
|
346
|
-
};
|
|
347
|
-
readonly recipient?: NotificationRecipientFn;
|
|
348
|
-
readonly data?: NotificationDataFn;
|
|
349
|
-
readonly templates?: Readonly<Record<string, NotificationTemplateFn>>;
|
|
350
|
-
readonly variables?: TemplateVariables;
|
|
351
|
-
readonly record?: NotificationRecordMapping;
|
|
352
|
-
readonly template?: Readonly<Record<string, NotificationTemplateBody>>;
|
|
353
|
-
readonly meta?: Readonly<Record<string, NotificationTemplateMeta>>;
|
|
354
|
-
readonly localizedTemplate?: () => NotificationLocalizedTemplate;
|
|
355
|
-
readonly renamedFrom?: readonly string[];
|
|
356
|
-
readonly systemOnly?: boolean;
|
|
357
|
-
readonly unsubscribe?: boolean;
|
|
358
|
-
readonly tenantTransport?: boolean;
|
|
359
|
-
}): void;
|
|
360
|
-
translations(def: TranslationsDef): void;
|
|
361
|
-
defineEvent<const TInner extends string, TPayload>(name: TInner, schema: ZodType<TPayload>, options: {
|
|
362
|
-
readonly version?: number;
|
|
363
|
-
readonly piiFields: EventPiiStance;
|
|
364
|
-
readonly migrations?: readonly {
|
|
365
|
-
readonly fromVersion: number;
|
|
366
|
-
readonly toVersion: number;
|
|
367
|
-
readonly transform: EventUpcastFn | DeclarativeEventMigration;
|
|
368
|
-
}[];
|
|
369
|
-
}): EventDef<TPayload, QualifiedEventName<TFeature, TInner>>;
|
|
370
|
-
readsConfig(...qualifiedKeys: string[]): void;
|
|
371
|
-
readsConfig(options: {
|
|
372
|
-
readonly keys: readonly string[];
|
|
373
|
-
}): void;
|
|
374
|
-
referenceData(entity: NameOrRef, data: readonly Record<string, unknown>[], options?: {
|
|
375
|
-
upsertKey?: string;
|
|
376
|
-
}): void;
|
|
377
|
-
referenceData(definition: {
|
|
378
|
-
readonly entity: NameOrRef;
|
|
379
|
-
readonly data: readonly Record<string, unknown>[];
|
|
380
|
-
readonly upsertKey?: string;
|
|
381
|
-
}): void;
|
|
382
|
-
extendsRegistrar(name: string, def: RegistrarExtensionDef): void;
|
|
383
|
-
useExtension<N extends string>(extensionName: N, entity: NameOrRef, ...options: ExtensionOptionsArgs<N>): void;
|
|
384
|
-
useExtension<N extends string>(definition: {
|
|
385
|
-
readonly name: N;
|
|
386
|
-
readonly entity: NameOrRef;
|
|
387
|
-
} & ExtensionOptionsFor<N>): void;
|
|
388
|
-
/**
|
|
389
|
-
* Declares which config key selects the active provider under an
|
|
390
|
-
* extension point — called by the point-owning foundation (e.g.
|
|
391
|
-
* `r.extensionSelector("mailTransport", configKeys.provider)`).
|
|
392
|
-
* Readiness gating counts a provider-feature's `required` keys and
|
|
393
|
-
* secrets only while that provider is the selected one. Registry-build
|
|
394
|
-
* fails on duplicate declarations per extension and on selector keys
|
|
395
|
-
* that no mounted feature declares. A masked tenant selector key makes the
|
|
396
|
-
* Settings-Hub render one tenant dashboard for the owner: the selector as a
|
|
397
|
-
* select of the mounted plugin ids, the selected plugin's config and secrets
|
|
398
|
-
* as panels, and plugin features lose their own tenant nav. `config:write:set`
|
|
399
|
-
* rejects unknown plugin ids (`""` clears). `options.panels` adds the owner's own
|
|
400
|
-
* `custom`/`screen` panels to that dashboard, after the selection and before the
|
|
401
|
-
* plugin panels (a short `screen` id means a screen of the declaring feature).
|
|
402
|
-
*/
|
|
403
|
-
extensionSelector(extensionName: string, key: {
|
|
404
|
-
readonly name: string;
|
|
405
|
-
} | string, options?: {
|
|
406
|
-
readonly panels?: readonly ExtensionSelectorPanel[];
|
|
407
|
-
}): void;
|
|
408
|
-
/**
|
|
409
|
-
* Marker declaration: this feature provides a cross-feature API under the
|
|
410
|
-
* given name. The implementation is registered separately as a query or
|
|
411
|
-
* write handler under the QN pattern; `r.exposesApi` is only a boot-check
|
|
412
|
-
* surface.
|
|
413
|
-
*
|
|
414
|
-
* The boot validator checks that every `r.usesApi(name)` finds a matching
|
|
415
|
-
* exposer, that the exposer feature is listed in requires/optionalRequires,
|
|
416
|
-
* and that no API name is exposed more than once.
|
|
417
|
-
*
|
|
418
|
-
* ```ts
|
|
419
|
-
* defineFeature("compliance-profiles", (r) => {
|
|
420
|
-
* r.exposesApi("compliance.forTenant");
|
|
421
|
-
* r.queryHandler({
|
|
422
|
-
* name: "compliance:query:for-tenant",
|
|
423
|
-
* // ... actual implementation
|
|
424
|
-
* });
|
|
425
|
-
* });
|
|
426
|
-
* ```
|
|
427
|
-
*/
|
|
428
|
-
exposesApi(apiName: string): void;
|
|
429
|
-
/**
|
|
430
|
-
* Declares that this feature calls a cross-feature API. The boot validator
|
|
431
|
-
* checks that another feature provides `r.exposesApi(apiName)` and that
|
|
432
|
-
* this feature lists it in `r.requires/optionalRequires`.
|
|
433
|
-
*
|
|
434
|
-
* ```ts
|
|
435
|
-
* defineFeature("user-data-rights", (r) => {
|
|
436
|
-
* r.requires({ features: ["compliance-profiles"] });
|
|
437
|
-
* r.usesApi("compliance.forTenant");
|
|
438
|
-
* });
|
|
439
|
-
* ```
|
|
440
|
-
*/
|
|
441
|
-
usesApi(apiName: string): void;
|
|
442
|
-
/**
|
|
443
|
-
* Declares a boot-time mount-invariant for this feature. `fn` runs once
|
|
444
|
-
* at boot with a ctx exposing every mounted feature — throw to fail the
|
|
445
|
-
* boot with a clear message. Multiple calls per feature are allowed.
|
|
446
|
-
*
|
|
447
|
-
* Use for cross-feature invariants that `r.requires` can't express
|
|
448
|
-
* because they're conditional (only fail when the feature has a
|
|
449
|
-
* specific shape), e.g. "if this feature has PII entities, some
|
|
450
|
-
* user-data-hook feature must be mounted" (the prompt-store trap —
|
|
451
|
-
* a UserData feature was written but never mounted, and nothing caught
|
|
452
|
-
* it at boot):
|
|
453
|
-
*
|
|
454
|
-
* ```ts
|
|
455
|
-
* defineFeature("prompt-store", (r) => {
|
|
456
|
-
* const promptFields = { text: createTextField({ personal: "self", find: "none" }) };
|
|
457
|
-
* r.entity({ name: "prompt", fields: promptFields });
|
|
458
|
-
* r.bootCheck(({ features }) => {
|
|
459
|
-
* // Conditional on this feature's own shape (has a pii field) —
|
|
460
|
-
* // r.requires({ features: ["user-data-hook"] }) can't express that, it would fail
|
|
461
|
-
* // even for a prompt-store variant with no PII fields at all.
|
|
462
|
-
* const hasPiiField = Object.values(promptFields).some((f) => f.pii);
|
|
463
|
-
* const hasUserDataHook = features.some((f) => f.name === "user-data-hook");
|
|
464
|
-
* if (hasPiiField && !hasUserDataHook) {
|
|
465
|
-
* throw new Error("prompt-store has PII fields but no user-data-hook feature is mounted");
|
|
466
|
-
* }
|
|
467
|
-
* });
|
|
468
|
-
* });
|
|
469
|
-
* ```
|
|
470
|
-
*/
|
|
471
|
-
bootCheck(fn: BootCheckFn): void;
|
|
472
|
-
metric(shortName: string, options: MetricOptions): void;
|
|
473
|
-
metric(definition: {
|
|
474
|
-
readonly name: string;
|
|
475
|
-
} & MetricOptions): void;
|
|
476
|
-
secret(shortName: string, options: SecretOptions): SecretKeyHandle;
|
|
477
|
-
secret(definition: {
|
|
478
|
-
readonly name: string;
|
|
479
|
-
} & SecretOptions): SecretKeyHandle;
|
|
480
|
-
secretNamespace(shortName: string, options: SecretNamespaceOptions): SecretNamespaceHandle;
|
|
481
|
-
secretNamespace(definition: {
|
|
482
|
-
readonly name: string;
|
|
483
|
-
} & SecretNamespaceOptions): SecretNamespaceHandle;
|
|
484
|
-
projection(definition: ProjectionDefinition): void;
|
|
485
|
-
multiStreamProjection(definition: MultiStreamProjectionDefinition): void;
|
|
486
|
-
extendEntityProjection(entityName: string, extension: EntityProjectionExtension): void;
|
|
487
|
-
authClaims(fn: AuthClaimsFn): void;
|
|
488
|
-
claimKey<T extends ClaimKeyType>(shortName: string, options: {
|
|
489
|
-
readonly type: T;
|
|
490
|
-
}): ClaimKeyHandle<T>;
|
|
491
|
-
claimKey<T extends ClaimKeyType>(definition: {
|
|
492
|
-
readonly name: string;
|
|
493
|
-
readonly type: T;
|
|
494
|
-
}): ClaimKeyHandle<T>;
|
|
495
|
-
screen(definition: ScreenDefinition): void;
|
|
496
|
-
nav(definition: NavDefinition): void;
|
|
497
|
-
contentCollection(definition: ContentCollectionDefinition): string;
|
|
498
|
-
workspace(definition: WorkspaceDefinition): void;
|
|
499
|
-
httpRoute(definition: HttpRouteDefinition): void;
|
|
500
|
-
webSocketRoute(definition: WebSocketRouteDefinition): void;
|
|
501
|
-
storeTable(meta: EntityTableMeta, options: StoreTableOptions): void;
|
|
502
|
-
treeActions<const TActions extends Record<string, TreeActionDef>>(actions: TActions): TreeActionsHandle<TFeature, TActions>;
|
|
503
|
-
envSchema(schema: z.ZodObject<z.ZodRawShape>): void;
|
|
504
|
-
};
|
|
505
198
|
export type Registry = {
|
|
506
199
|
readonly features: ReadonlyMap<string, FeatureDefinition>;
|
|
507
200
|
getFeature(name: string): FeatureDefinition | undefined;
|
|
@@ -595,4 +288,3 @@ export type Registry = {
|
|
|
595
288
|
getDefaultWorkspace(): WorkspaceDefinition | undefined;
|
|
596
289
|
getTreeActions(featureName: string): Readonly<Record<string, TreeActionDef>> | undefined;
|
|
597
290
|
};
|
|
598
|
-
export {};
|
package/dist/handlers.d.ts
CHANGED
|
@@ -256,6 +256,7 @@ type SharedContextFields = {
|
|
|
256
256
|
readonly signal?: AbortSignal;
|
|
257
257
|
readonly effectiveFeatures?: (tenantId: TenantId) => ReadonlySet<string>;
|
|
258
258
|
readonly scheduleAfterCommit?: (hook: () => Promise<void>) => void;
|
|
259
|
+
readonly scheduleAfterRollback?: (hook: () => Promise<void>) => void;
|
|
259
260
|
readonly queryAsMember?: MemberReader;
|
|
260
261
|
};
|
|
261
262
|
export type AppContext = SharedContextFields & {
|
|
@@ -350,6 +351,10 @@ export type HandlerContext<TMap extends object = KumikoEventTypeMap> = SharedCon
|
|
|
350
351
|
readonly fetchForWriting: (args: FetchForWritingArgs) => Promise<AggregateStreamHandle>;
|
|
351
352
|
readonly loadAggregate: (aggregateId: string, options?: {
|
|
352
353
|
readonly asOf?: Temporal.Instant;
|
|
354
|
+
readonly atVersion?: never;
|
|
355
|
+
} | {
|
|
356
|
+
readonly atVersion?: number;
|
|
357
|
+
readonly asOf?: never;
|
|
353
358
|
}) => Promise<readonly import("./event-store-types.ts").StoredEvent[]>;
|
|
354
359
|
readonly archiveStream: (aggregateId: string, args: {
|
|
355
360
|
readonly aggregateType: string;
|
|
@@ -405,12 +410,6 @@ export type JobContext = SharedContextFields & {
|
|
|
405
410
|
readonly triggerEvent?: JobTriggerEvent;
|
|
406
411
|
readonly _tenantId?: TenantId;
|
|
407
412
|
};
|
|
408
|
-
/**
|
|
409
|
-
* What a provider plugin (`build`, `capabilitiesFor`, ...) may read. Both write/query handlers
|
|
410
|
-
* and jobs can produce one (`providerPluginContextFromHandler`, `providerPluginContextFromJob`),
|
|
411
|
-
* so a plugin never depends on a request-bound field.
|
|
412
|
-
*/
|
|
413
|
-
export type ProviderPluginContext = Pick<HandlerContext, "db" | "dbOutsideTransaction" | "systemDb" | "registry" | "user" | "log" | "triggeredBy" | "config" | "secrets">;
|
|
414
413
|
export type WriteHandlerFn<TPayload = unknown, TData = unknown> = (event: WriteEvent<TPayload>, context: HandlerContext) => Promise<WriteResult<TData>>;
|
|
415
414
|
export type QueryHandlerFn<TPayload = unknown, TResult = unknown> = (query: QueryEvent<TPayload>, context: HandlerContext) => Promise<TResult>;
|
|
416
415
|
export type StreamHandlerFn<TPayload = unknown, TChunk = unknown> = (query: QueryEvent<TPayload>, context: HandlerContext) => AsyncGenerator<TChunk>;
|
|
@@ -23,7 +23,12 @@ export type ListAggregatePayload = {
|
|
|
23
23
|
readonly kind: ListAggregateMeasureKind;
|
|
24
24
|
}[];
|
|
25
25
|
};
|
|
26
|
-
export type
|
|
26
|
+
export type ListAggregateCurrencyAmount = {
|
|
27
|
+
readonly currency: string;
|
|
28
|
+
readonly value: number;
|
|
29
|
+
};
|
|
30
|
+
export type ListAggregateValue = number | string | null | readonly ListAggregateCurrencyAmount[];
|
|
31
|
+
export declare function isListAggregateCurrencyAmounts(value: ListAggregateValue | undefined): value is readonly ListAggregateCurrencyAmount[];
|
|
27
32
|
export type ListAggregateGroup = {
|
|
28
33
|
readonly key: AggregateKey;
|
|
29
34
|
readonly label?: string | null;
|
|
@@ -9,6 +9,9 @@ export const listAggregatePayloadSchema = z.object({
|
|
|
9
9
|
.max(MAX_LIST_AGGREGATE_MEASURES)
|
|
10
10
|
.default([]),
|
|
11
11
|
});
|
|
12
|
+
export function isListAggregateCurrencyAmounts(value) {
|
|
13
|
+
return Array.isArray(value);
|
|
14
|
+
}
|
|
12
15
|
const LIST_AGGREGATE_RESULT_KEYS = ["groups", "truncated", "searchTruncated"];
|
|
13
16
|
// Closed key set: a row that merely has a `groups` field (jsonb column) must not
|
|
14
17
|
// be mistaken for an aggregate and skip the row-level read filter.
|
|
@@ -6,6 +6,8 @@ import type { FieldRenderer, PlatformComponent, ScreenNavSugar, ScreenSlots } fr
|
|
|
6
6
|
import type { ActionFormRedirect } from "./screen-action-form.ts";
|
|
7
7
|
import type { DashboardPanelVisibility } from "./screen-dashboard.ts";
|
|
8
8
|
import type { ListColumnSpec, ListSortSpec, RelatedListToolbarAction, RowAction, RowActionMode, RowFieldExtractor } from "./screen-entity-list.ts";
|
|
9
|
+
import type { ListBulkAction } from "./screen-list-bulk.ts";
|
|
10
|
+
import type { ListTableGrouping, ListTableOptions } from "./screen-list-table.ts";
|
|
9
11
|
import type { ListFacetSpec } from "./screen-projection.ts";
|
|
10
12
|
export type EditFieldSpec = string | {
|
|
11
13
|
readonly field: string;
|
|
@@ -168,25 +170,9 @@ export type EditExtensionSection = {
|
|
|
168
170
|
* (declared or id-derived); the boot-validator rejects one that doesn't. */
|
|
169
171
|
readonly actions?: readonly RowAction[];
|
|
170
172
|
};
|
|
171
|
-
/**
|
|
172
|
-
* a collapsible header. */
|
|
173
|
-
export type RelatedListGroupBy = {
|
|
174
|
-
readonly field: string;
|
|
175
|
-
/** Groups whose field value equals this start collapsed; the others start open. */
|
|
176
|
-
readonly collapsedWhen?: string | number | boolean | null;
|
|
177
|
-
/** i18n key of the group header. Placeholders: `{count}` rows in the group,
|
|
178
|
-
* `{value}` the group's field value, `{lastDate}` the group's latest
|
|
179
|
-
* `dateField` value as a locale date. A group with neither `label` nor an
|
|
180
|
-
* entry in `labels` renders its rows without a header and never collapses. */
|
|
181
|
-
readonly label?: string;
|
|
182
|
-
/** Header i18n key per group value (as string), overriding `label`. */
|
|
183
|
-
readonly labels?: Readonly<Record<string, string>>;
|
|
184
|
-
/** Row field (ISO date or timestamp) behind `{lastDate}`. */
|
|
185
|
-
readonly dateField?: string;
|
|
186
|
-
};
|
|
187
|
-
/** Group key of a field value; `collapsedWhen` and row values both go through it. */
|
|
173
|
+
/** Group key of a field value; `collapsedWhen` and server group keys both go through it. */
|
|
188
174
|
export declare function relatedListGroupKey(value: unknown): string;
|
|
189
|
-
export declare function relatedListGroupHeaderLabel(
|
|
175
|
+
export declare function relatedListGroupHeaderLabel(grouping: Pick<ListTableGrouping, "label" | "labels">, groupKey: string): string | undefined;
|
|
190
176
|
/** Row tint rule: rows where the `FieldCondition` (`{ field, eq }`, `{ field,
|
|
191
177
|
* in }` …) holds get the tone. `bad` renders red. */
|
|
192
178
|
export type RelatedListRowTone = Exclude<FieldCondition, boolean> & {
|
|
@@ -240,6 +226,8 @@ export type EditRelatedListSection = {
|
|
|
240
226
|
* "3 Positionen"). Omitted: generic "entries" wording. */
|
|
241
227
|
readonly itemNoun?: string;
|
|
242
228
|
readonly pageSize?: number;
|
|
229
|
+
/** Opt-in table behaviour: remembered state, reset, column picker, density. */
|
|
230
|
+
readonly table?: ListTableOptions;
|
|
243
231
|
/** Initial sort on mount, applied client-side over the already-loaded rows
|
|
244
232
|
* (this section has no pager; if the fetch is truncated, `nextCursor !== null`,
|
|
245
233
|
* the section shows a hint banner that the sort covers only the loaded rows).
|
|
@@ -278,8 +266,8 @@ export type EditRelatedListSection = {
|
|
|
278
266
|
readonly rowActions?: readonly RowAction[];
|
|
279
267
|
/** Layout of the row-action column — see `RowActionMode`. */
|
|
280
268
|
readonly rowActionMode?: RowActionMode;
|
|
281
|
-
/**
|
|
282
|
-
readonly
|
|
269
|
+
/** Bulk actions over checkbox-selected rows, each pointing at a writeHandler row action. Adds a checkbox column and a selection bar; without it the list has no checkboxes. */
|
|
270
|
+
readonly bulkActions?: readonly ListBulkAction[];
|
|
283
271
|
/** Tints rows whose field matches — a declarative rule, not a function, so
|
|
284
272
|
* the screen definition stays serializable. */
|
|
285
273
|
readonly rowTone?: RelatedListRowTone;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
/** Group key of a field value; `collapsedWhen` and
|
|
1
|
+
/** Group key of a field value; `collapsedWhen` and server group keys both go through it. */
|
|
2
2
|
export function relatedListGroupKey(value) {
|
|
3
3
|
return value === undefined || value === null ? "" : String(value);
|
|
4
4
|
}
|
|
5
|
-
export function relatedListGroupHeaderLabel(
|
|
6
|
-
const labels =
|
|
7
|
-
return labels !== undefined && Object.hasOwn(labels, groupKey)
|
|
5
|
+
export function relatedListGroupHeaderLabel(grouping, groupKey) {
|
|
6
|
+
const labels = grouping.labels;
|
|
7
|
+
return labels !== undefined && Object.hasOwn(labels, groupKey)
|
|
8
|
+
? labels[groupKey]
|
|
9
|
+
: grouping.label;
|
|
8
10
|
}
|
|
@@ -4,7 +4,10 @@ import type { IconKey } from "./nav-icon.ts";
|
|
|
4
4
|
import type { FieldRenderer, PlatformComponent, ScreenNavSugar, ScreenSlots } from "./screen.ts";
|
|
5
5
|
import type { ActionFormRedirect } from "./screen-action-form.ts";
|
|
6
6
|
import type { DashboardPanelVisibility } from "./screen-dashboard.ts";
|
|
7
|
-
import type {
|
|
7
|
+
import type { ListEmptyStateSpec } from "./screen-entity-edit.ts";
|
|
8
|
+
import type { EntityListExpandableRow } from "./screen-expandable-row.ts";
|
|
9
|
+
import type { ListBulkAction } from "./screen-list-bulk.ts";
|
|
10
|
+
import type { ListColumnPin, ListTableOptions } from "./screen-list-table.ts";
|
|
8
11
|
export type ListColumnSpec = string | {
|
|
9
12
|
readonly field: string;
|
|
10
13
|
readonly renderer?: FieldRenderer;
|
|
@@ -49,6 +52,16 @@ export type ListColumnSpec = string | {
|
|
|
49
52
|
* `md` breakpoint (tables keep it). For columns that only make sense
|
|
50
53
|
* next to the others, like a sort key. */
|
|
51
54
|
readonly hideOnNarrow?: boolean;
|
|
55
|
+
/** Start state for the column picker: the column begins hidden and the
|
|
56
|
+
* user can show it. Only valid together with `table.columnPicker`. */
|
|
57
|
+
readonly hidden?: boolean;
|
|
58
|
+
/** Start pin for the column: it sticks to this edge while the table scrolls
|
|
59
|
+
* horizontally. Only valid together with `table.pinning`. */
|
|
60
|
+
readonly pinned?: ListColumnPin;
|
|
61
|
+
/** Start width in px. Only valid together with `table.resizing`. */
|
|
62
|
+
readonly width?: number;
|
|
63
|
+
/** Smallest width in px the user can resize to. Only valid together with `table.resizing`. */
|
|
64
|
+
readonly minWidth?: number;
|
|
52
65
|
/** Dashboard list panels only. "bar": field holds a 0..1 fraction, drawn
|
|
53
66
|
* as bar + percent. "badge": field holds a label, drawn as a pill whose
|
|
54
67
|
* tone comes from `badgeToneField`. "datetime": field holds an ISO string or
|
|
@@ -321,11 +334,6 @@ export type RelatedListToolbarAction = (Extract<ToolbarAction, {
|
|
|
321
334
|
}> & {
|
|
322
335
|
readonly visible?: FieldCondition;
|
|
323
336
|
});
|
|
324
|
-
/** Expandable area of an `entityList` row: a relatedList of the row's record.
|
|
325
|
-
* `id` (tab id) and `countField` (tab badge) have no meaning outside a tabs
|
|
326
|
-
* layout and are therefore omitted. `kind` stays the discriminator so further
|
|
327
|
-
* expansion kinds can be added without a breaking change. */
|
|
328
|
-
export type EntityListExpandableRow = Omit<EditRelatedListSection, "id" | "countField">;
|
|
329
337
|
/** An extra choice on a facet next to the field's own options: a named
|
|
330
338
|
* bundle of values ("Running" = several statuses) or "no filter" (`values:
|
|
331
339
|
* []`, e.g. "All"). Chips only. */
|
|
@@ -340,8 +348,9 @@ export type EntityListFacetExtraOption = {
|
|
|
340
348
|
};
|
|
341
349
|
export type EntityListFacetConfig = {
|
|
342
350
|
/** `"select"` (default): dropdown with checkboxes. `"chips"`: one toggle
|
|
343
|
-
* chip per option, single choice.
|
|
344
|
-
|
|
351
|
+
* chip per option, single choice. `"range"`: "from" and "to" inputs on a
|
|
352
|
+
* number, decimal, money or bigInt field, sent as `gte`/`lte`. */
|
|
353
|
+
readonly display?: "select" | "chips" | "range";
|
|
345
354
|
/** Chips show how many rows each option matches (other facets and the
|
|
346
355
|
* search apply). Counts come from the list query itself, one `limit: 1`
|
|
347
356
|
* count request per chip. */
|
|
@@ -387,6 +396,8 @@ export type EntityListScreenDefinition = {
|
|
|
387
396
|
readonly rowActions?: readonly RowAction[];
|
|
388
397
|
/** Layout of the row-action column — see `RowActionMode`. */
|
|
389
398
|
readonly rowActionMode?: RowActionMode;
|
|
399
|
+
/** Bulk actions over checkbox-selected rows, each pointing at a writeHandler row action. Adds a checkbox column and a selection bar; without it the list has no checkboxes. */
|
|
400
|
+
readonly bulkActions?: readonly ListBulkAction[];
|
|
390
401
|
/** Toolbar actions (list header). "Open Incident", "Schedule Maintenance"
|
|
391
402
|
* etc. — next to "+ New" if present. Array order = UI order,
|
|
392
403
|
* primary-style links. */
|
|
@@ -403,6 +414,8 @@ export type EntityListScreenDefinition = {
|
|
|
403
414
|
readonly filter?: ScreenFilter;
|
|
404
415
|
readonly pagination?: ListPaginationMode;
|
|
405
416
|
readonly pageSize?: number;
|
|
417
|
+
/** Opt-in table behaviour: remembered state, reset, column picker, density. */
|
|
418
|
+
readonly table?: ListTableOptions;
|
|
406
419
|
readonly defaultSort?: ListSortSpec;
|
|
407
420
|
/** Initial facet selection, per filterable field: option values (string
|
|
408
421
|
* facets) or a boolean. Applies only while the URL carries no value for
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { EditRelatedListSection } from "./screen-entity-edit.ts";
|
|
2
|
+
import type { ListTableGrouping } from "./screen-list-table.ts";
|
|
3
|
+
/** Server-side grouping of an expandable row's sub-list: a fixed `field`, no "Group by" choice. */
|
|
4
|
+
export type ExpandableRowGrouping = Omit<ListTableGrouping, "field" | "userChoice" | "fields"> & {
|
|
5
|
+
readonly field: string;
|
|
6
|
+
};
|
|
7
|
+
/** Expandable area of an `entityList` row: a relatedList of the row's record.
|
|
8
|
+
* `id` (tab id) and `countField` (tab badge) have no meaning outside a tabs
|
|
9
|
+
* layout and are therefore omitted. `kind` stays the discriminator so further
|
|
10
|
+
* expansion kinds can be added without a breaking change. `table` takes only
|
|
11
|
+
* `grouping`; the other table options belong on the entityList itself. */
|
|
12
|
+
export type EntityListExpandableRow = Omit<EditRelatedListSection, "id" | "countField" | "table" | "bulkActions"> & {
|
|
13
|
+
readonly table?: {
|
|
14
|
+
readonly grouping?: ExpandableRowGrouping;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const LIST_BULK_ACTION_KEYS: readonly ["id", "rowAction", "confirm"];
|
|
2
|
+
/** Most rows one selection can hold; the loaded page caps it further. */
|
|
3
|
+
export declare const MAX_BULK_SELECTION = 100;
|
|
4
|
+
/** An action that runs a writeHandler row action once per checkbox-selected row. One `/api/write` per row, so access, validation and audit stay per row. */
|
|
5
|
+
export type ListBulkAction = {
|
|
6
|
+
/** Stable id, kebab-case, unique among the screen's bulk actions. */
|
|
7
|
+
readonly id: string;
|
|
8
|
+
/** Id of a writeHandler entry in the same screen's `rowActions`; it supplies handler, payload, label, style and `visible`. Rows the action's `visible` hides are skipped. */
|
|
9
|
+
readonly rowAction: string;
|
|
10
|
+
/** i18n key of the one confirm dialog for the whole selection; gets `{count}` and `{action}`. Without it a `confirm` or `style: "danger"` on the row action still asks once with the default text. */
|
|
11
|
+
readonly confirm?: string;
|
|
12
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ListAggregateMeasureKind } from "./list-aggregate-types.ts";
|
|
2
|
+
export declare const LIST_TABLE_DENSITIES: readonly ["comfortable", "compact"];
|
|
3
|
+
export type ListTableDensity = (typeof LIST_TABLE_DENSITIES)[number];
|
|
4
|
+
export declare const LIST_COLUMN_PINS: readonly ["left", "right"];
|
|
5
|
+
export type ListColumnPin = (typeof LIST_COLUMN_PINS)[number];
|
|
6
|
+
export declare const LIST_TABLE_OPTION_KEYS: readonly ["rememberState", "resetAll", "columnPicker", "density", "pinning", "resizing", "keyboardNavigation", "export", "grouping", "totals"];
|
|
7
|
+
export declare const LIST_TABLE_GROUPING_KEYS: readonly ["field", "label", "labels", "collapsedWhen", "userChoice", "fields"];
|
|
8
|
+
export declare const LIST_TABLE_TOTAL_KEYS: readonly ["field", "kind"];
|
|
9
|
+
export declare const LIST_TABLE_EXPORT_KEYS: readonly ["xlsx", "maxRows"];
|
|
10
|
+
export declare const DEFAULT_EXPORT_MAX_ROWS = 10000;
|
|
11
|
+
/** Hard cap: the server builds the whole workbook in memory. */
|
|
12
|
+
export declare const MAX_EXPORT_MAX_ROWS = 100000;
|
|
13
|
+
export declare const LIST_TABLE_DENSITY_KEYS: readonly ["default", "userChoice"];
|
|
14
|
+
/** Opt-in table behaviour of a list. Without the block a list renders and behaves exactly as before. */
|
|
15
|
+
export type ListTableOptions = {
|
|
16
|
+
/** 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. A link with parameters always wins. */
|
|
17
|
+
readonly rememberState?: boolean;
|
|
18
|
+
/** 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). */
|
|
19
|
+
readonly resetAll?: boolean;
|
|
20
|
+
/** Lets the user hide and reorder columns in the view menu. Columns marked `hidden` start hidden. */
|
|
21
|
+
readonly columnPicker?: boolean;
|
|
22
|
+
/** Lets the user pin columns left or right in the view menu; columns may start pinned via `pinned`. Pinned columns stay sticky while scrolling horizontally. No effect in the card layout below `md`. */
|
|
23
|
+
readonly pinning?: boolean;
|
|
24
|
+
/** Switches the table to table-layout fixed; the user drags or uses the arrow keys on the header edge to resize. `width` and `minWidth` on a column are start values; widths are remembered per session. No effect in the card layout. */
|
|
25
|
+
readonly resizing?: boolean;
|
|
26
|
+
/** Rows become one tab stop; ArrowUp/ArrowDown move, Home/End jump, Enter runs the row click. Space stays reserved for selection. */
|
|
27
|
+
readonly keyboardNavigation?: boolean;
|
|
28
|
+
readonly density?: {
|
|
29
|
+
/** Row density on first visit. Default "comfortable". */
|
|
30
|
+
readonly default?: ListTableDensity;
|
|
31
|
+
/** Shows the density choice in the view menu and remembers it per session. */
|
|
32
|
+
readonly userChoice?: boolean;
|
|
33
|
+
};
|
|
34
|
+
/** Shows an export button; the server reads the whole filtered list (not just the visible page). */
|
|
35
|
+
readonly export?: ListTableExportOptions;
|
|
36
|
+
/** Server-side grouping: group headers with count and the `totals` measures over all matching rows; each open group loads its own rows. */
|
|
37
|
+
readonly grouping?: ListTableGrouping;
|
|
38
|
+
/** Footer row under the rows, computed by the server over all rows matching the current filters and search. In grouped mode the same measures show in each group header. */
|
|
39
|
+
readonly totals?: readonly ListTableTotal[];
|
|
40
|
+
};
|
|
41
|
+
export type ListTableGrouping = {
|
|
42
|
+
/** Start grouping. Without it the list starts ungrouped and the user picks from `fields`. */
|
|
43
|
+
readonly field?: string;
|
|
44
|
+
/** Groups whose key equals this start collapsed and load no rows until opened. */
|
|
45
|
+
readonly collapsedWhen?: string | number | boolean | null;
|
|
46
|
+
/** i18n key of the group header. Placeholders: `{count}` rows in the group, `{value}` the group's value. Default: the value itself. */
|
|
47
|
+
readonly label?: string;
|
|
48
|
+
/** Header i18n key per group key (as string), overriding `label`. */
|
|
49
|
+
readonly labels?: Readonly<Record<string, string>>;
|
|
50
|
+
/** Shows "Group by" in the view menu (needs `fields` or `field`) and remembers the choice per session. */
|
|
51
|
+
readonly userChoice?: boolean;
|
|
52
|
+
/** Fields offered under "Group by"; `field` is always offered. */
|
|
53
|
+
readonly fields?: readonly string[];
|
|
54
|
+
};
|
|
55
|
+
export type ListTableTotal = {
|
|
56
|
+
readonly field: string;
|
|
57
|
+
/** sum and avg on number, decimal, money and bigInt; min and max also on date and timestamp. Money is shown per currency, never added across currencies. */
|
|
58
|
+
readonly kind: ListAggregateMeasureKind;
|
|
59
|
+
};
|
|
60
|
+
export type ListTableExportOptions = {
|
|
61
|
+
readonly xlsx: true;
|
|
62
|
+
/** Export is refused above this many rows. Default 10000, hard cap 100000. Encrypted, sensitive and virtual columns are never written. */
|
|
63
|
+
readonly maxRows?: number;
|
|
64
|
+
};
|
|
65
|
+
/** One `table` block for every entityList, projectionList and relatedList of the bundled features. A screen's own `table` keys win per key. `grouping` and `totals` are not accepted here. */
|
|
66
|
+
export type BundledListsOptions = {
|
|
67
|
+
readonly table: ListTableOptions;
|
|
68
|
+
/** Pins the first column left on every bundled list where no column is pinned. Needs `table.pinning: true`. */
|
|
69
|
+
readonly pinFirstColumn?: boolean;
|
|
70
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const LIST_TABLE_DENSITIES = ["comfortable", "compact"];
|
|
2
|
+
export const LIST_COLUMN_PINS = ["left", "right"];
|
|
3
|
+
export const LIST_TABLE_OPTION_KEYS = [
|
|
4
|
+
"rememberState",
|
|
5
|
+
"resetAll",
|
|
6
|
+
"columnPicker",
|
|
7
|
+
"density",
|
|
8
|
+
"pinning",
|
|
9
|
+
"resizing",
|
|
10
|
+
"keyboardNavigation",
|
|
11
|
+
"export",
|
|
12
|
+
"grouping",
|
|
13
|
+
"totals",
|
|
14
|
+
];
|
|
15
|
+
export const LIST_TABLE_GROUPING_KEYS = [
|
|
16
|
+
"field",
|
|
17
|
+
"label",
|
|
18
|
+
"labels",
|
|
19
|
+
"collapsedWhen",
|
|
20
|
+
"userChoice",
|
|
21
|
+
"fields",
|
|
22
|
+
];
|
|
23
|
+
export const LIST_TABLE_TOTAL_KEYS = ["field", "kind"];
|
|
24
|
+
export const LIST_TABLE_EXPORT_KEYS = ["xlsx", "maxRows"];
|
|
25
|
+
export const DEFAULT_EXPORT_MAX_ROWS = 10000;
|
|
26
|
+
/** Hard cap: the server builds the whole workbook in memory. */
|
|
27
|
+
export const MAX_EXPORT_MAX_ROWS = 100000;
|
|
28
|
+
export const LIST_TABLE_DENSITY_KEYS = ["default", "userChoice"];
|
|
@@ -4,6 +4,8 @@ import type { PlatformComponent, ScreenNavSugar, ScreenSlots } from "./screen.ts
|
|
|
4
4
|
import type { DashboardPanelVisibility } from "./screen-dashboard.ts";
|
|
5
5
|
import type { EditLayout, ListEmptyStateSpec } from "./screen-entity-edit.ts";
|
|
6
6
|
import type { ListColumnSpec, ListPaginationMode, ListSortSpec, RowAction, RowActionMode, RowFieldExtractor, ScreenFilter, ToolbarAction } from "./screen-entity-list.ts";
|
|
7
|
+
import type { ListBulkAction } from "./screen-list-bulk.ts";
|
|
8
|
+
import type { ListTableOptions } from "./screen-list-table.ts";
|
|
7
9
|
export type ListFacetSpec = {
|
|
8
10
|
readonly field: string;
|
|
9
11
|
readonly type: "select";
|
|
@@ -71,9 +73,13 @@ export type ProjectionListScreenDefinition = {
|
|
|
71
73
|
readonly rowActions?: readonly RowAction[];
|
|
72
74
|
/** Layout of the row-action column — see `RowActionMode`. */
|
|
73
75
|
readonly rowActionMode?: RowActionMode;
|
|
76
|
+
/** Bulk actions over checkbox-selected rows, each pointing at a writeHandler row action. Adds a checkbox column and a selection bar; without it the list has no checkboxes. */
|
|
77
|
+
readonly bulkActions?: readonly ListBulkAction[];
|
|
74
78
|
readonly toolbarActions?: readonly ToolbarAction[];
|
|
75
79
|
readonly pagination?: ListPaginationMode;
|
|
76
80
|
readonly pageSize?: number;
|
|
81
|
+
/** Opt-in table behaviour: remembered state, reset, column picker, density. */
|
|
82
|
+
readonly table?: ListTableOptions;
|
|
77
83
|
readonly defaultSort?: ListSortSpec;
|
|
78
84
|
/** Replaces the default empty-list title/hint; the create button stays when the list can create. */
|
|
79
85
|
readonly emptyState?: ListEmptyStateSpec;
|
package/dist/screen.d.ts
CHANGED
|
@@ -72,8 +72,11 @@ export interface FieldFormatRegistry {
|
|
|
72
72
|
};
|
|
73
73
|
/** FileRef id (or id array) as a download link with the file name. On a
|
|
74
74
|
* read-only detail field it renders a file list with download links and an
|
|
75
|
-
* inline preview for PDFs; elsewhere the raw value is shown.
|
|
76
|
-
|
|
75
|
+
* inline preview for PDFs; elsewhere the raw value is shown.
|
|
76
|
+
* `previewOpenByDefault` opens the first file's PDF preview initially. */
|
|
77
|
+
file: {
|
|
78
|
+
readonly previewOpenByDefault?: boolean;
|
|
79
|
+
};
|
|
77
80
|
}
|
|
78
81
|
export type FormatSpec = {
|
|
79
82
|
[K in keyof FieldFormatRegistry]: {
|
|
@@ -111,7 +114,12 @@ export type { ActionFormFooterAction, ActionFormLoad, ActionFormRedirect, Action
|
|
|
111
114
|
export type { ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, SecretsEditScreenDefinition, SecretsEditSection, } from "./screen-custom-config.ts";
|
|
112
115
|
export type { DashboardChartKind, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardChartTone, DashboardCurrencyFormat, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelEmptyState, DashboardPanelGate, DashboardPanelQueryOptions, DashboardPanelSpan, DashboardPanelVisibility, DashboardProgressListPanel, DashboardProgressTone, DashboardRecordBinding, DashboardRefresh, DashboardRelativeTimeFormat, DashboardRelativeTimeParam, DashboardScopeDefinition, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardStatTone, DashboardText, DashboardTextBlock, DashboardTextPanel, DashboardTextParam, DashboardTimeRangeDefinition, DashboardValueFormat, } from "./screen-dashboard.ts";
|
|
113
116
|
export { DASHBOARD_MIN_REFRESH_SECONDS, DASHBOARD_STAT_TONES } from "./screen-dashboard.ts";
|
|
114
|
-
export type { EditExtensionSection, EditFieldGroup, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EmbeddedListEditSpec, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityWriteExcludedFields, FieldAffix, FieldFillAction, FormWidth, ListEmptyStateSpec,
|
|
117
|
+
export type { EditExtensionSection, EditFieldGroup, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EmbeddedListEditSpec, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityWriteExcludedFields, FieldAffix, FieldFillAction, FormWidth, ListEmptyStateSpec, RelatedListRowTone, } from "./screen-entity-edit.ts";
|
|
115
118
|
export { relatedListGroupHeaderLabel, relatedListGroupKey } from "./screen-entity-edit.ts";
|
|
116
|
-
export type {
|
|
119
|
+
export type { EntityListFacetConfig, EntityListFacetExtraOption, EntityListScreenDefinition, ListBadgeTone, ListColumnDisplay, ListColumnSpec, ListPaginationMode, ListSortDir, ListSortSpec, RelatedListToolbarAction, RowAction, RowActionClientAction, RowActionDisplay, RowActionDrawer, RowActionMode, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenFilter, ScreenFilterOp, ToolbarAction, } from "./screen-entity-list.ts";
|
|
120
|
+
export type { EntityListExpandableRow, ExpandableRowGrouping } from "./screen-expandable-row.ts";
|
|
121
|
+
export type { ListBulkAction } from "./screen-list-bulk.ts";
|
|
122
|
+
export { LIST_BULK_ACTION_KEYS, MAX_BULK_SELECTION } from "./screen-list-bulk.ts";
|
|
123
|
+
export type { BundledListsOptions, ListColumnPin, ListTableDensity, ListTableExportOptions, ListTableGrouping, ListTableOptions, ListTableTotal, } from "./screen-list-table.ts";
|
|
124
|
+
export { DEFAULT_EXPORT_MAX_ROWS, LIST_COLUMN_PINS, LIST_TABLE_DENSITIES, LIST_TABLE_DENSITY_KEYS, LIST_TABLE_EXPORT_KEYS, LIST_TABLE_GROUPING_KEYS, LIST_TABLE_OPTION_KEYS, LIST_TABLE_TOTAL_KEYS, MAX_EXPORT_MAX_ROWS, } from "./screen-list-table.ts";
|
|
117
125
|
export type { ListFacetSpec, MetricNavigate, MetricSpec, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, } from "./screen-projection.ts";
|
package/dist/screen.js
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { DASHBOARD_MIN_REFRESH_SECONDS, DASHBOARD_STAT_TONES } from "./screen-dashboard.js";
|
|
2
2
|
export { relatedListGroupHeaderLabel, relatedListGroupKey } from "./screen-entity-edit.js";
|
|
3
|
+
export { LIST_BULK_ACTION_KEYS, MAX_BULK_SELECTION } from "./screen-list-bulk.js";
|
|
4
|
+
export { DEFAULT_EXPORT_MAX_ROWS, LIST_COLUMN_PINS, LIST_TABLE_DENSITIES, LIST_TABLE_DENSITY_KEYS, LIST_TABLE_EXPORT_KEYS, LIST_TABLE_GROUPING_KEYS, LIST_TABLE_OPTION_KEYS, LIST_TABLE_TOTAL_KEYS, MAX_EXPORT_MAX_ROWS, } from "./screen-list-table.js";
|
|
@@ -58,6 +58,11 @@ type TenantDbNonWrites = {
|
|
|
58
58
|
/** Postgres `now()`; inside a transaction this is the transaction start. */
|
|
59
59
|
databaseNow(): Promise<Date>;
|
|
60
60
|
aggregate(table: SchemaTable | EntityTableMeta, spec: AggregateSpec, where?: WhereObject): Promise<readonly AggregateRow[]>;
|
|
61
|
+
upsert<T = Record<string, unknown>>(table: WritableTable, values: Record<string, unknown>, options: TenantDbUpsertOptions): Promise<T | undefined>;
|
|
62
|
+
};
|
|
63
|
+
export type TenantDbUpsertOptions = {
|
|
64
|
+
readonly conflictTarget: readonly string[];
|
|
65
|
+
readonly update?: Record<string, unknown>;
|
|
61
66
|
};
|
|
62
67
|
export type TenantDb = TenantDbNonWrites & TenantDbWrites<WritableTable>;
|
|
63
68
|
export type ExecutorManagedTenantDb = TenantDbNonWrites & TenantDbWrites<EntityTableMeta> & ExecutorManagedWriteCapability;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-types",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.389.0",
|
|
4
4
|
"description": "Framework-Type-Definitions für Kumiko — FeatureDefinition, BootCheck-Types und die reinen Engine-Types. Erlaubt Downstream-Konsumenten, gegen die Type-Contracts zu bauen, ohne das ganze Framework-Package zu importieren. Enthaelt keine identitaets-sensitiven Runtime-Werte mehr (Error-Klassen leben seit #1629 in kumiko-framework, Brand-Symbole nutzen Symbol.for) und ist deshalb eine plain dependency, keine peerDependency.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -178,6 +178,10 @@
|
|
|
178
178
|
"types": "./dist/feature.d.ts",
|
|
179
179
|
"default": "./dist/feature.js"
|
|
180
180
|
},
|
|
181
|
+
"./feature-registrar": {
|
|
182
|
+
"types": "./dist/feature-registrar.d.ts",
|
|
183
|
+
"default": "./dist/feature-registrar.js"
|
|
184
|
+
},
|
|
181
185
|
"./fields": {
|
|
182
186
|
"types": "./dist/fields.d.ts",
|
|
183
187
|
"default": "./dist/fields.js"
|
|
@@ -284,18 +288,18 @@
|
|
|
284
288
|
}
|
|
285
289
|
},
|
|
286
290
|
"devDependencies": {
|
|
291
|
+
"bun-types": "^1.4.4",
|
|
287
292
|
"hono": "^4.13.1",
|
|
288
|
-
"postgres": "npm:@bender0oo0/postgres@3.4.9-kumiko.2",
|
|
289
293
|
"temporal-polyfill": "^0.3.2",
|
|
290
294
|
"zod": "^4.4.3",
|
|
291
295
|
"ioredis": "^5.10.1"
|
|
292
296
|
},
|
|
293
297
|
"peerDependencies": {
|
|
294
298
|
"hono": "^4.13.1",
|
|
295
|
-
"postgres": "^3.4.9-0",
|
|
296
299
|
"temporal-polyfill": "^0.3.2",
|
|
297
300
|
"zod": "^4.4.3",
|
|
298
|
-
"ioredis": "^5.10.1"
|
|
301
|
+
"ioredis": "^5.10.1",
|
|
302
|
+
"bun-types": "^1.4.4"
|
|
299
303
|
},
|
|
300
304
|
"publishConfig": {
|
|
301
305
|
"registry": "https://registry.npmjs.org",
|
|
@@ -461,6 +465,10 @@
|
|
|
461
465
|
"types": "./dist/feature.d.ts",
|
|
462
466
|
"default": "./dist/feature.js"
|
|
463
467
|
},
|
|
468
|
+
"./feature-registrar": {
|
|
469
|
+
"types": "./dist/feature-registrar.d.ts",
|
|
470
|
+
"default": "./dist/feature-registrar.js"
|
|
471
|
+
},
|
|
464
472
|
"./fields": {
|
|
465
473
|
"types": "./dist/fields.d.ts",
|
|
466
474
|
"default": "./dist/fields.js"
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.389.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "DbConnection, DbTx and PgClient are Bun.SQL types and postgres is no longer a peer dependency",
|
|
6
|
+
"detail": "DbConnection is SQL | RawDbClient, DbTx is TransactionSQL | RawDbClient and PgClient is SQL from the bun module, so the package type-checks against bun-types instead of postgres. The postgres peer dependency is removed.",
|
|
7
|
+
"migration": "Remove postgres from your dependencies unless your own code imports it. Annotations that named postgres.Sql or postgres.TransactionSql for framework handles become SQL and TransactionSQL from \"bun\", or the DbConnection and DbTx aliases. Manual migration, there is no codemod."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"version": "0.389.0",
|
|
11
|
+
"type": "improvement",
|
|
12
|
+
"title": "Config validators can read a key's cascade",
|
|
13
|
+
"detail": "ConfigValidateContext gains readCascade(qualifiedKey), which returns the full cascade of any registered key from the writing tenant and user. Unknown keys throw."
|
|
14
|
+
},
|
|
2
15
|
{
|
|
3
16
|
"version": "0.386.0",
|
|
4
17
|
"type": "improvement",
|