@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 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";
@@ -1,4 +1,4 @@
1
- import type postgres from "postgres";
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 = ReturnType<typeof postgres> | RawDbClient;
10
- export type DbTx = postgres.TransactionSql<Record<string, unknown>> | RawDbClient;
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 = ReturnType<typeof postgres>;
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, ConfigKeyHandle, ConfigKeyType, ConfigSeedDef, ExtensionSelectorDef, ExtensionSelectorPanel, JobDefinition, JobHandlerFn, NotificationDataFn, NotificationDefinition, NotificationLocalizedTemplate, NotificationRecipientFn, NotificationRecordMapping, NotificationTemplateBody, NotificationTemplateFn, NotificationTemplateMeta, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionRegistration, TemplateVariables, TranslationKeys, TranslationsDef } from "./config.ts";
4
- import type { QueryHandlerDefinition, StreamHandlerDefinition, WriteHandlerDefinition } from "./define-handler.ts";
5
- import type { RegisterEntityCrudOptions } from "./entity-handlers.ts";
3
+ import type { ConfigKeyDefinition, ConfigSeedDef, ExtensionSelectorDef, ExtensionSelectorPanel, JobDefinition, NotificationDefinition, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionRegistration, TranslationKeys } from "./config.ts";
6
4
  import type { EntityTableMeta } from "./entity-table-meta-types.ts";
7
- import type { KumikoEventTypeMap } from "./event-type-map.ts";
8
- import type { ExtensionOptionsArgs, ExtensionOptionsFor } from "./extension-options-map.ts";
9
5
  import type { EntityDefinition } from "./fields.ts";
10
- import type { AccessRule, AuthClaimsFn, AuthClaimsHookDef, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyType, DeclarativeEventMigration, EntityRef, EscapeHatchDeclaration, EventDef, EventMigrationDef, EventPiiStance, EventUpcastFn, HandlerRef, NameOrRef, QualifiedEventName, QueryHandlerDef, RateLimitDeclaration, StreamHandlerDef, StreamHandlerFn, WriteHandlerDef } from "./handlers.ts";
11
- import type { EntityHookMap, HookMap, HookPhase, OwnedFn, PostDeleteHookFn, PostQueryHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, SearchPayloadContributorFn, ValidationHookFn } from "./hooks.ts";
6
+ import type { AuthClaimsFn, AuthClaimsHookDef, ClaimKeyDefinition, EventDef, EventMigrationDef, EventUpcastFn, QueryHandlerDef, StreamHandlerDef, WriteHandlerDef } from "./handlers.ts";
7
+ import type { EntityHookMap, HookMap, HookPhase, OwnedFn, PostDeleteHookFn, PostQueryHookFn, PostSaveHookFn, PreDeleteHookFn, 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, TreeActionsHandle } from "./tree-node.ts";
13
+ import type { TreeActionDef } from "./tree-node.ts";
18
14
  import type { WebSocketRouteDefinition } from "./websocket-route.ts";
19
15
  import type { WorkspaceDefinition } from "./workspace.ts";
20
16
  export type FeatureMetricType = "counter" | "histogram" | "gauge";
@@ -116,7 +112,7 @@ export type BootCheckContext = {
116
112
  };
117
113
  export type BootCheckFn = (ctx: BootCheckContext) => void;
118
114
  /** runProdApp options a stack can depend on; runProdApp checks the set at boot. */
119
- export type BootOptionName = "mail" | "kmsMode" | "fileStorage" | "billing" | "featureToggles" | "demoMode" | "seeds" | "signals" | "rateLimit";
115
+ export type BootOptionName = "mail" | "kmsMode" | "fileStorage" | "billing" | "featureToggles" | "rateLimit";
120
116
  export type StackBootRequirement = {
121
117
  readonly stack: string;
122
118
  readonly options: readonly BootOptionName[];
@@ -133,6 +129,7 @@ export type FeatureDefinition = {
133
129
  readonly requiredProjections: ReadonlySet<string>;
134
130
  readonly requiredSteps: ReadonlySet<string>;
135
131
  readonly toggleableDefault?: boolean;
132
+ readonly adoptsBundledLists?: true;
136
133
  readonly uiHints?: UiHints;
137
134
  readonly entities?: Readonly<Record<string, EntityDefinition>>;
138
135
  readonly entityTables?: Readonly<Record<string, unknown>>;
@@ -198,310 +195,6 @@ export type FeatureDefinition = {
198
195
  readonly envSchema?: z.ZodObject<z.ZodRawShape>;
199
196
  readonly dedupeOptions?: Readonly<Record<string, unknown>>;
200
197
  };
201
- type RefOrRefs = NameOrRef | readonly NameOrRef[];
202
- type HookTarget = RefOrRefs | {
203
- readonly allOf: NameOrRef;
204
- };
205
- export type EntityObjectDefinition = {
206
- readonly name: string;
207
- readonly backingTable?: unknown;
208
- } & EntityDefinition;
209
- type HookEscapeHatch = {
210
- readonly escapeHatch?: EscapeHatchDeclaration;
211
- };
212
- type HookPhaseOption = {
213
- readonly phase?: HookPhase;
214
- };
215
- export type HookDefinition = {
216
- readonly type: "validation";
217
- readonly target: RefOrRefs;
218
- readonly handler: ValidationHookFn;
219
- readonly escapeHatch?: never;
220
- readonly phase?: never;
221
- } | ({
222
- readonly type: "preSave";
223
- readonly target: RefOrRefs;
224
- readonly handler: PreSaveHookFn;
225
- readonly phase?: never;
226
- } & HookEscapeHatch) | ({
227
- readonly type: "postSave";
228
- readonly target: HookTarget;
229
- readonly handler: PostSaveHookFn;
230
- } & HookEscapeHatch & HookPhaseOption) | ({
231
- readonly type: "preDelete";
232
- readonly target: HookTarget;
233
- readonly handler: PreDeleteHookFn;
234
- readonly phase?: never;
235
- } & HookEscapeHatch) | ({
236
- readonly type: "postDelete";
237
- readonly target: HookTarget;
238
- readonly handler: PostDeleteHookFn;
239
- } & HookEscapeHatch & HookPhaseOption) | ({
240
- readonly type: "preQuery";
241
- readonly target: RefOrRefs;
242
- readonly handler: PreQueryHookFn;
243
- readonly phase?: never;
244
- } & HookEscapeHatch) | ({
245
- readonly type: "postQuery";
246
- readonly target: HookTarget;
247
- readonly handler: PostQueryHookFn;
248
- readonly phase?: never;
249
- } & HookEscapeHatch);
250
- /**
251
- * `r.requires({ features })` declares the features this one depends on; the
252
- * `.projection` extension declares read-side projection tables that this
253
- * feature's pipeline steps are allowed to write via
254
- * `r.step.unsafeProjectionUpsert` (hard-required for any unsafeProjection-*
255
- * step usage, see Q10).
256
- *
257
- * @example r.requires({ features: ["auth", "tenant"] });
258
- */
259
- export type RequiresApi = ((options: {
260
- readonly features: readonly string[];
261
- }) => void) & {
262
- readonly projection: (tableName: string) => void;
263
- readonly step: (stepKind: string) => void;
264
- };
265
- /**
266
- * `TFeature` is the literal feature-name from `defineFeature("foo", ...)` —
267
- * default-`string` keeps every existing usage zero-config. Strict-typed
268
- * features (apps that opt into the literal-name flavour) get propagated
269
- * through to `defineEvent` so the returned `EventDef.name` is a literal
270
- * `${CamelToKebab<TFeature>}:event:${CamelToKebab<TInner>}`. That literal
271
- * threads through `ctx.appendEvent({ type: eventDef.name, ... })`,
272
- * keeping strict-mode alive even when handlers route via `eventDef.name`
273
- * instead of hand-typed string literals.
274
- */
275
- export type FeatureRegistrar<TFeature extends string = string> = {
276
- systemScope(): void;
277
- describe(text: string): void;
278
- requires: RequiresApi;
279
- optionalRequires(...featureNames: string[]): void;
280
- optionalRequires(options: {
281
- readonly features: readonly string[];
282
- }): void;
283
- toggleable(options: {
284
- default: boolean;
285
- }): void;
286
- uiHints(hints: UiHints): void;
287
- /**
288
- * @example r.entity({ name: "note", fields: { title: createTextField({ required: true }) } });
289
- */
290
- entity(definition: EntityObjectDefinition): EntityRef;
291
- crud(entityName: string, entity: EntityDefinition, options?: RegisterEntityCrudOptions): void;
292
- /**
293
- * @example r.writeHandler({ name: "note:archive", schema, handler, access: { roles: ["User"] } });
294
- */
295
- writeHandler<TName extends string, TSchema extends ZodType, TPrepared = undefined>(def: WriteHandlerDefinition<TName, TSchema, unknown, KumikoEventTypeMap, TPrepared>): HandlerRef;
296
- /**
297
- * @example r.queryHandler({ name: "note:list", schema, handler, access: { roles: ["User"] } });
298
- */
299
- queryHandler<TName extends string, TSchema extends ZodType>(def: QueryHandlerDefinition<TName, TSchema>): HandlerRef;
300
- streamHandler<TName extends string, TSchema extends ZodType>(def: StreamHandlerDefinition<TName, TSchema>): HandlerRef;
301
- streamHandler<TSchema extends ZodType>(name: string, schema: TSchema, handler: StreamHandlerFn<z.infer<TSchema>>, options: {
302
- access: AccessRule;
303
- rateLimit?: RateLimitDeclaration;
304
- escapeHatch?: EscapeHatchDeclaration;
305
- }): HandlerRef;
306
- relation(entity: NameOrRef, relationName: string, definition: RelationDefinition): void;
307
- relation<TDef extends RelationDefinition>(definition: {
308
- readonly entity: NameOrRef;
309
- readonly name: string;
310
- } & TDef): void;
311
- /**
312
- * @example r.hook({ type: "postSave", target: { allOf: noteRef }, handler: notifyOwner });
313
- */
314
- hook(definition: HookDefinition): void;
315
- searchPayloadExtension(entity: NameOrRef, fn: SearchPayloadContributorFn): void;
316
- config<T extends ConfigKeyType>(keyName: string, def: ConfigKeyDefinition<T>): ConfigKeyHandle<T>;
317
- config<TKeys extends Readonly<Record<string, ConfigKeyDefinition<ConfigKeyType>>>>(definition: {
318
- readonly keys: TKeys;
319
- readonly seeds?: Readonly<Record<string, ConfigSeedDef>>;
320
- }): {
321
- readonly [K in keyof TKeys]: ConfigKeyHandle<TKeys[K]["type"]>;
322
- };
323
- job(name: string, options: Omit<JobDefinition, "name" | "handler">, handler: JobHandlerFn): void;
324
- job(definition: JobDefinition): void;
325
- notification(name: string, definition: {
326
- readonly trigger?: {
327
- readonly on: NameOrRef;
328
- };
329
- readonly recipient?: NotificationRecipientFn;
330
- readonly data?: NotificationDataFn;
331
- readonly templates?: Readonly<Record<string, NotificationTemplateFn>>;
332
- readonly variables?: TemplateVariables;
333
- readonly record?: NotificationRecordMapping;
334
- readonly template?: Readonly<Record<string, NotificationTemplateBody>>;
335
- readonly meta?: Readonly<Record<string, NotificationTemplateMeta>>;
336
- readonly localizedTemplate?: () => NotificationLocalizedTemplate;
337
- readonly renamedFrom?: readonly string[];
338
- readonly systemOnly?: boolean;
339
- readonly unsubscribe?: boolean;
340
- readonly tenantTransport?: boolean;
341
- }): void;
342
- notification(definition: {
343
- readonly name: string;
344
- readonly trigger?: {
345
- readonly on: NameOrRef;
346
- };
347
- readonly recipient?: NotificationRecipientFn;
348
- readonly data?: NotificationDataFn;
349
- readonly templates?: Readonly<Record<string, NotificationTemplateFn>>;
350
- readonly variables?: TemplateVariables;
351
- readonly record?: NotificationRecordMapping;
352
- readonly template?: Readonly<Record<string, NotificationTemplateBody>>;
353
- readonly meta?: Readonly<Record<string, NotificationTemplateMeta>>;
354
- readonly localizedTemplate?: () => NotificationLocalizedTemplate;
355
- readonly renamedFrom?: readonly string[];
356
- readonly systemOnly?: boolean;
357
- readonly unsubscribe?: boolean;
358
- readonly tenantTransport?: boolean;
359
- }): void;
360
- translations(def: TranslationsDef): void;
361
- defineEvent<const TInner extends string, TPayload>(name: TInner, schema: ZodType<TPayload>, options: {
362
- readonly version?: number;
363
- readonly piiFields: EventPiiStance;
364
- readonly migrations?: readonly {
365
- readonly fromVersion: number;
366
- readonly toVersion: number;
367
- readonly transform: EventUpcastFn | DeclarativeEventMigration;
368
- }[];
369
- }): EventDef<TPayload, QualifiedEventName<TFeature, TInner>>;
370
- readsConfig(...qualifiedKeys: string[]): void;
371
- readsConfig(options: {
372
- readonly keys: readonly string[];
373
- }): void;
374
- referenceData(entity: NameOrRef, data: readonly Record<string, unknown>[], options?: {
375
- upsertKey?: string;
376
- }): void;
377
- referenceData(definition: {
378
- readonly entity: NameOrRef;
379
- readonly data: readonly Record<string, unknown>[];
380
- readonly upsertKey?: string;
381
- }): void;
382
- extendsRegistrar(name: string, def: RegistrarExtensionDef): void;
383
- useExtension<N extends string>(extensionName: N, entity: NameOrRef, ...options: ExtensionOptionsArgs<N>): void;
384
- useExtension<N extends string>(definition: {
385
- readonly name: N;
386
- readonly entity: NameOrRef;
387
- } & ExtensionOptionsFor<N>): void;
388
- /**
389
- * Declares which config key selects the active provider under an
390
- * extension point — called by the point-owning foundation (e.g.
391
- * `r.extensionSelector("mailTransport", configKeys.provider)`).
392
- * Readiness gating counts a provider-feature's `required` keys and
393
- * secrets only while that provider is the selected one. Registry-build
394
- * fails on duplicate declarations per extension and on selector keys
395
- * that no mounted feature declares. A masked tenant selector key makes the
396
- * Settings-Hub render one tenant dashboard for the owner: the selector as a
397
- * select of the mounted plugin ids, the selected plugin's config and secrets
398
- * as panels, and plugin features lose their own tenant nav. `config:write:set`
399
- * rejects unknown plugin ids (`""` clears). `options.panels` adds the owner's own
400
- * `custom`/`screen` panels to that dashboard, after the selection and before the
401
- * plugin panels (a short `screen` id means a screen of the declaring feature).
402
- */
403
- extensionSelector(extensionName: string, key: {
404
- readonly name: string;
405
- } | string, options?: {
406
- readonly panels?: readonly ExtensionSelectorPanel[];
407
- }): void;
408
- /**
409
- * Marker declaration: this feature provides a cross-feature API under the
410
- * given name. The implementation is registered separately as a query or
411
- * write handler under the QN pattern; `r.exposesApi` is only a boot-check
412
- * surface.
413
- *
414
- * The boot validator checks that every `r.usesApi(name)` finds a matching
415
- * exposer, that the exposer feature is listed in requires/optionalRequires,
416
- * and that no API name is exposed more than once.
417
- *
418
- * ```ts
419
- * defineFeature("compliance-profiles", (r) => {
420
- * r.exposesApi("compliance.forTenant");
421
- * r.queryHandler({
422
- * name: "compliance:query:for-tenant",
423
- * // ... actual implementation
424
- * });
425
- * });
426
- * ```
427
- */
428
- exposesApi(apiName: string): void;
429
- /**
430
- * Declares that this feature calls a cross-feature API. The boot validator
431
- * checks that another feature provides `r.exposesApi(apiName)` and that
432
- * this feature lists it in `r.requires/optionalRequires`.
433
- *
434
- * ```ts
435
- * defineFeature("user-data-rights", (r) => {
436
- * r.requires({ features: ["compliance-profiles"] });
437
- * r.usesApi("compliance.forTenant");
438
- * });
439
- * ```
440
- */
441
- usesApi(apiName: string): void;
442
- /**
443
- * Declares a boot-time mount-invariant for this feature. `fn` runs once
444
- * at boot with a ctx exposing every mounted feature — throw to fail the
445
- * boot with a clear message. Multiple calls per feature are allowed.
446
- *
447
- * Use for cross-feature invariants that `r.requires` can't express
448
- * because they're conditional (only fail when the feature has a
449
- * specific shape), e.g. "if this feature has PII entities, some
450
- * user-data-hook feature must be mounted" (the prompt-store trap —
451
- * a UserData feature was written but never mounted, and nothing caught
452
- * it at boot):
453
- *
454
- * ```ts
455
- * defineFeature("prompt-store", (r) => {
456
- * const promptFields = { text: createTextField({ personal: "self", find: "none" }) };
457
- * r.entity({ name: "prompt", fields: promptFields });
458
- * r.bootCheck(({ features }) => {
459
- * // Conditional on this feature's own shape (has a pii field) —
460
- * // r.requires({ features: ["user-data-hook"] }) can't express that, it would fail
461
- * // even for a prompt-store variant with no PII fields at all.
462
- * const hasPiiField = Object.values(promptFields).some((f) => f.pii);
463
- * const hasUserDataHook = features.some((f) => f.name === "user-data-hook");
464
- * if (hasPiiField && !hasUserDataHook) {
465
- * throw new Error("prompt-store has PII fields but no user-data-hook feature is mounted");
466
- * }
467
- * });
468
- * });
469
- * ```
470
- */
471
- bootCheck(fn: BootCheckFn): void;
472
- metric(shortName: string, options: MetricOptions): void;
473
- metric(definition: {
474
- readonly name: string;
475
- } & MetricOptions): void;
476
- secret(shortName: string, options: SecretOptions): SecretKeyHandle;
477
- secret(definition: {
478
- readonly name: string;
479
- } & SecretOptions): SecretKeyHandle;
480
- secretNamespace(shortName: string, options: SecretNamespaceOptions): SecretNamespaceHandle;
481
- secretNamespace(definition: {
482
- readonly name: string;
483
- } & SecretNamespaceOptions): SecretNamespaceHandle;
484
- projection(definition: ProjectionDefinition): void;
485
- multiStreamProjection(definition: MultiStreamProjectionDefinition): void;
486
- extendEntityProjection(entityName: string, extension: EntityProjectionExtension): void;
487
- authClaims(fn: AuthClaimsFn): void;
488
- claimKey<T extends ClaimKeyType>(shortName: string, options: {
489
- readonly type: T;
490
- }): ClaimKeyHandle<T>;
491
- claimKey<T extends ClaimKeyType>(definition: {
492
- readonly name: string;
493
- readonly type: T;
494
- }): ClaimKeyHandle<T>;
495
- screen(definition: ScreenDefinition): void;
496
- nav(definition: NavDefinition): void;
497
- contentCollection(definition: ContentCollectionDefinition): string;
498
- workspace(definition: WorkspaceDefinition): void;
499
- httpRoute(definition: HttpRouteDefinition): void;
500
- webSocketRoute(definition: WebSocketRouteDefinition): void;
501
- storeTable(meta: EntityTableMeta, options: StoreTableOptions): void;
502
- treeActions<const TActions extends Record<string, TreeActionDef>>(actions: TActions): TreeActionsHandle<TFeature, TActions>;
503
- envSchema(schema: z.ZodObject<z.ZodRawShape>): void;
504
- };
505
198
  export type Registry = {
506
199
  readonly features: ReadonlyMap<string, FeatureDefinition>;
507
200
  getFeature(name: string): FeatureDefinition | undefined;
@@ -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 {};
@@ -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 ListAggregateValue = number | string | null;
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
- /** Rows are grouped by `field` in order of first appearance. Every group gets
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(groupBy: RelatedListGroupBy, groupKey: string): string | undefined;
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
- /** Groups rows by the value of a field under collapsible group headers. */
282
- readonly groupBy?: RelatedListGroupBy;
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 row values both go through it. */
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(groupBy, groupKey) {
6
- const labels = groupBy.labels;
7
- return labels !== undefined && Object.hasOwn(labels, groupKey) ? labels[groupKey] : groupBy.label;
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 { EditRelatedListSection, ListEmptyStateSpec } from "./screen-entity-edit.ts";
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
- readonly display?: "select" | "chips";
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,3 @@
1
+ export const LIST_BULK_ACTION_KEYS = ["id", "rowAction", "confirm"];
2
+ /** Most rows one selection can hold; the loaded page caps it further. */
3
+ export const MAX_BULK_SELECTION = 100;
@@ -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
- file: object;
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, RelatedListGroupBy, RelatedListRowTone, } from "./screen-entity-edit.ts";
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 { EntityListExpandableRow, 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";
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.388.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",