@cosmicdrift/kumiko-framework 0.373.0 → 0.374.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/widgets.md +1 -0
- package/dist/engine/boot-validator/projection-list-screens.js +28 -37
- package/dist/engine/boot-validator/query-refs.js +26 -8
- package/dist/engine/boot-validator/screens.d.ts +1 -1
- package/dist/engine/boot-validator/screens.js +327 -41
- package/dist/engine/boot-validator/url-prefill-fields.js +3 -0
- package/dist/engine/build-app-schema.js +7 -3
- package/dist/engine/entity-list-searchability.d.ts +5 -0
- package/dist/engine/entity-list-searchability.js +35 -0
- package/dist/engine/feature-ui-extensions.js +5 -2
- package/dist/engine/index.d.ts +2 -1
- package/dist/engine/index.js +1 -0
- package/dist/engine/paged-list.d.ts +29 -0
- package/dist/engine/paged-list.js +56 -0
- package/dist/engine/project-app-schema-for-roles.js +37 -6
- package/dist/engine/types/index.d.ts +1 -1
- package/dist/i18n/required-surface-keys.js +50 -2
- package/dist/ui-types/index.d.ts +1 -1
- package/package.json +5 -5
- package/src/changes.json +61 -0
|
@@ -27,6 +27,7 @@ import { ZodObject } from "zod";
|
|
|
27
27
|
import { collectUrlPrefillFieldsByScreenQn } from "./boot-validator/url-prefill-fields.js";
|
|
28
28
|
import { buildConfigFeatureSchema, SETTINGS_HUB_FEATURE, } from "./build-config-feature-schema.js";
|
|
29
29
|
import { isEncryptedAtRest } from "./config-helpers.js";
|
|
30
|
+
import { isListQueryWithoutSearchableFields } from "./entity-list-searchability.js";
|
|
30
31
|
import { QnTypes, qualifyEntityName } from "./qualified-name.js";
|
|
31
32
|
export function buildAppSchema(registry, options = {}) {
|
|
32
33
|
const features = [];
|
|
@@ -341,11 +342,14 @@ function projectConfigEditScreen(screen, encryptedConfigKeyQns) {
|
|
|
341
342
|
return fields === undefined ? screen : { ...screen, fields };
|
|
342
343
|
}
|
|
343
344
|
function projectProjectionListScreen(screen, registry) {
|
|
344
|
-
const
|
|
345
|
-
const capabilities = deriveProjectionListCapabilities(schema);
|
|
345
|
+
const handler = registry.getQueryHandler(screen.query);
|
|
346
|
+
const capabilities = deriveProjectionListCapabilities(handler?.schema);
|
|
347
|
+
// A search box over an entity without searchable fields would never match.
|
|
348
|
+
const searchable = capabilities.searchable &&
|
|
349
|
+
!isListQueryWithoutSearchableFields(screen.query, handler, (name) => registry.getEntity(name));
|
|
346
350
|
return {
|
|
347
351
|
...screen,
|
|
348
|
-
searchable: screen.searchable ??
|
|
352
|
+
searchable: screen.searchable ?? searchable,
|
|
349
353
|
sortable: capabilities.sortable,
|
|
350
354
|
paginated: capabilities.paginated,
|
|
351
355
|
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { EntityDefinition, QueryHandlerDef } from "./types/index.ts";
|
|
2
|
+
export declare function resolveEntityBehindListQuery(queryQn: string, handler: QueryHandlerDef | undefined, lookupEntity: (entityName: string) => EntityDefinition | undefined): EntityDefinition | undefined;
|
|
3
|
+
/** True when the list query is an entity-convention one over an entity with no
|
|
4
|
+
* searchable field — a search box would never match anything. */
|
|
5
|
+
export declare function isListQueryWithoutSearchableFields(queryQn: string, handler: QueryHandlerDef | undefined, lookupEntity: (entityName: string) => EntityDefinition | undefined): boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ENTITY_CONVENTION_QUERY_BRAND } from "@cosmicdrift/kumiko-types/handlers";
|
|
2
|
+
import { parseQn } from "./qualified-name.js";
|
|
3
|
+
// Mirrors buildSearchableSortableCaches (registry-validate.ts): a `search`
|
|
4
|
+
// payload only matches text fields, embedded sub-fields and references that
|
|
5
|
+
// opted in with `searchable: true`.
|
|
6
|
+
function entityHasSearchableField(entity) {
|
|
7
|
+
return Object.values(entity.fields).some((field) => {
|
|
8
|
+
if (field.type === "embedded") {
|
|
9
|
+
return Object.values(field.schema).some((subField) => subField.searchable === true);
|
|
10
|
+
}
|
|
11
|
+
return (field.type === "text" || field.type === "reference") && field.searchable === true;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
// Entity-convention list queries (ENTITY_CONVENTION_QUERY_BRAND) carry no
|
|
15
|
+
// entity on the handler; the entity name is recovered from the query QN's
|
|
16
|
+
// `<entityName>:list` naming convention. Hand-written handlers resolve to
|
|
17
|
+
// undefined — "capability unknown, no throw".
|
|
18
|
+
export function resolveEntityBehindListQuery(queryQn, handler, lookupEntity) {
|
|
19
|
+
if (handler === undefined || handler[ENTITY_CONVENTION_QUERY_BRAND] !== true)
|
|
20
|
+
return undefined;
|
|
21
|
+
let entityName;
|
|
22
|
+
try {
|
|
23
|
+
entityName = parseQn(queryQn).name.split(":")[0] ?? "";
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return entityName === "" ? undefined : lookupEntity(entityName);
|
|
29
|
+
}
|
|
30
|
+
/** True when the list query is an entity-convention one over an entity with no
|
|
31
|
+
* searchable field — a search box would never match anything. */
|
|
32
|
+
export function isListQueryWithoutSearchableFields(queryQn, handler, lookupEntity) {
|
|
33
|
+
const entity = resolveEntityBehindListQuery(queryQn, handler, lookupEntity);
|
|
34
|
+
return entity !== undefined && !entityHasSearchableField(entity);
|
|
35
|
+
}
|
|
@@ -31,9 +31,12 @@ function qualifySelectorPanel(featureName, panel) {
|
|
|
31
31
|
throw new Error(`[Feature ${featureName}] extensionSelector screen panel "${panel.id}" must not set a label — ` +
|
|
32
32
|
`the settings dashboard has no i18n namespace for it; the embedded screen carries its own title.`);
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
const qualify = (ref) => ref === "" || isValidQn(ref) ? ref : qualifyEntityName(featureName, "screen", ref);
|
|
35
|
+
const screen = qualify(panel.screen);
|
|
36
|
+
const fallback = panel.fallback === undefined ? undefined : qualify(panel.fallback);
|
|
37
|
+
if (screen === panel.screen && fallback === panel.fallback)
|
|
35
38
|
return panel;
|
|
36
|
-
return { ...panel, screen
|
|
39
|
+
return { ...panel, screen, ...(fallback !== undefined && { fallback }) };
|
|
37
40
|
}
|
|
38
41
|
throw new Error(`[Feature ${featureName}] extensionSelector panel ${JSON.stringify(panel)} has an unsupported kind — ` +
|
|
39
42
|
`only "custom" and "screen" panels are allowed.`);
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ export { i18nKey } from "./i18n-key.ts";
|
|
|
51
51
|
export { buildSessionRoles, findForbiddenMembershipRole, isForbiddenMembershipRole, stripForbiddenMembershipRoles, } from "./membership-roles.ts";
|
|
52
52
|
export type { OwnershipClause, OwnershipMap, OwnershipRef, OwnershipRule } from "./ownership.ts";
|
|
53
53
|
export { buildOwnershipClause, combineClauses, from, normalizeAccessEntry, userCanCreateFieldRow, userCanReadFieldRow, userCanWriteFieldRow, } from "./ownership.ts";
|
|
54
|
+
export { type PagedListRequest, type PagedListRowValue, pagedListSchema, pageRows, } from "./paged-list.ts";
|
|
54
55
|
export type { ParsedRefTarget } from "./parse-ref-target.ts";
|
|
55
56
|
export { parseRefTarget, parseRefTargetEntityName } from "./parse-ref-target.ts";
|
|
56
57
|
export { buildPipelineSteps, stepsPipeline } from "./pipeline.ts";
|
|
@@ -83,7 +84,7 @@ export { assertNotificationVariables, templateVariable, templateVariablesToZod,
|
|
|
83
84
|
export { type EffectiveFeaturesResolver, findTierResolverUsage, isTierResolverPlugin, requireTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, type TierResolverPlugin, type TrialGate, } from "./tier-resolver-extension.ts";
|
|
84
85
|
export { findOverDeepTransferChain, resolveTransferAdjacency as resolveEntityTransferAdjacency, type TransferAdjacency, type TransferEdge, type TransferEdgeLink, } from "./transfer-adjacency.ts";
|
|
85
86
|
export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.ts";
|
|
86
|
-
export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BeforeTransactionPhase, BeforeTransactionResult, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, BootOptionName, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValidateContext, ConfigValidateFn, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardValueFormat, DateFieldDef, DecimalFieldDef, DeleteContext, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EmbeddedListEditSpec, EntityDefinition, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, EntityObjectDefinition, EntityRef, EntityRelations, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, ExtensionOptionsArgs, ExtensionOptionsFor, FeatureDefinition, FeatureRegistrar, FieldAccess, FieldCondition, FieldDefinition, FieldFormatRegistry, FieldIconKey, FieldRenderer, FileFieldDef, FilesFieldDef, Findability, FormatSpec, FormWidth, HandlerContext, HasManyRelation, HookDefinition, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MaxPerTenantOptions, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationLocalizedTemplate, NotificationRecipientFn, NotificationRecordMapping, NotificationTemplateBody, NotificationTemplateFn, NotificationTemplateMeta, NotifyDeferred, NotifyDelivery, NotifyDeliveryStatus, NotifyDispatched, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreparedWriteEvent, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitPer, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyDefinition, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StackBootRequirement, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TemplateListVariableDef, TemplateScalarVariableDef, TemplateVariableDef, TemplateVariables, TenantId, TenantWriteTarget, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.ts";
|
|
87
|
+
export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BeforeTransactionPhase, BeforeTransactionResult, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, BootOptionName, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValidateContext, ConfigValidateFn, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCurrencyFormat, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardProgressTone, DashboardRecordBinding, DashboardRefresh, DashboardRelativeTimeFormat, DashboardRelativeTimeParam, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextBlock, DashboardTextPanel, DashboardTextParam, DashboardValueFormat, DateFieldDef, DecimalFieldDef, DeleteContext, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EmbeddedListEditSpec, EntityDefinition, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, EntityObjectDefinition, EntityRef, EntityRelations, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, ExtensionOptionsArgs, ExtensionOptionsFor, FeatureDefinition, FeatureRegistrar, FieldAccess, FieldCondition, FieldDefinition, FieldFormatRegistry, FieldIconKey, FieldRenderer, FileFieldDef, FilesFieldDef, Findability, FormatSpec, FormWidth, HandlerContext, HasManyRelation, HookDefinition, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MaxPerTenantOptions, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationLocalizedTemplate, NotificationRecipientFn, NotificationRecordMapping, NotificationTemplateBody, NotificationTemplateFn, NotificationTemplateMeta, NotifyDeferred, NotifyDelivery, NotifyDeliveryStatus, NotifyDispatched, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreparedWriteEvent, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitPer, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyDefinition, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StackBootRequirement, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TemplateListVariableDef, TemplateScalarVariableDef, TemplateVariableDef, TemplateVariables, TenantId, TenantWriteTarget, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.ts";
|
|
87
88
|
export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, HookPhases, HTTP_ERROR_CODE_PATTERN, isAgentVisibleScreen, isDeliveryErrorCode, isOpenToAllGranted, resolveAgentExposure, } from "./types/index.ts";
|
|
88
89
|
export type { AwaitedEventType, PipelineBuildCtx, PipelineCtx, PipelineDef, StepBuilder, StepDef, StepFailureStrategy, StepInstance, StepKind, StepNamespace, StepResolver, } from "./types/step.ts";
|
|
89
90
|
export { runValidation } from "./validation.ts";
|
package/dist/engine/index.js
CHANGED
|
@@ -51,6 +51,7 @@ export { i18nKey } from "./i18n-key.js";
|
|
|
51
51
|
// surface.
|
|
52
52
|
export { buildSessionRoles, findForbiddenMembershipRole, isForbiddenMembershipRole, stripForbiddenMembershipRoles, } from "./membership-roles.js";
|
|
53
53
|
export { buildOwnershipClause, combineClauses, from, normalizeAccessEntry, userCanCreateFieldRow, userCanReadFieldRow, userCanWriteFieldRow, } from "./ownership.js";
|
|
54
|
+
export { pagedListSchema, pageRows, } from "./paged-list.js";
|
|
54
55
|
export { parseRefTarget, parseRefTargetEntityName } from "./parse-ref-target.js";
|
|
55
56
|
export { buildPipelineSteps, stepsPipeline } from "./pipeline.js";
|
|
56
57
|
export { projectAppSchemaForRoles } from "./project-app-schema-for-roles.js";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CursorResult } from "@cosmicdrift/kumiko-types/cursor-types";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
export type PagedListRowValue = string | number | boolean | null;
|
|
4
|
+
export declare function pagedListSchema<const TSortField extends string>(sortableFields: readonly [TSortField, ...TSortField[]]): z.ZodObject<{
|
|
5
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
6
|
+
offset: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
search: z.ZodOptional<z.ZodString>;
|
|
8
|
+
sort: z.ZodOptional<z.ZodEnum<{ [k in TSortField]: k; } extends infer T ? { [k in keyof T]: T[k]; } : never>>;
|
|
9
|
+
sortDirection: z.ZodOptional<z.ZodEnum<{
|
|
10
|
+
asc: "asc";
|
|
11
|
+
desc: "desc";
|
|
12
|
+
}>>;
|
|
13
|
+
totalCount: z.ZodOptional<z.ZodBoolean>;
|
|
14
|
+
cursor: z.ZodOptional<z.ZodNever>;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
export type PagedListRequest<TSortField extends string> = {
|
|
17
|
+
readonly limit: number;
|
|
18
|
+
readonly offset?: number | undefined;
|
|
19
|
+
readonly search?: string | undefined;
|
|
20
|
+
readonly sort?: TSortField | undefined;
|
|
21
|
+
readonly sortDirection?: "asc" | "desc" | undefined;
|
|
22
|
+
readonly totalCount?: boolean | undefined;
|
|
23
|
+
};
|
|
24
|
+
export declare function pageRows<TRow extends Readonly<Record<TSortField, PagedListRowValue>> & {
|
|
25
|
+
readonly id: string;
|
|
26
|
+
}, TSortField extends string>(rows: readonly TRow[], request: PagedListRequest<TSortField>, options: {
|
|
27
|
+
readonly searchFields: readonly TSortField[];
|
|
28
|
+
readonly defaultSort: TSortField;
|
|
29
|
+
}): CursorResult<TRow>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Search, sort and offset paging for projectionList queries whose rows are
|
|
2
|
+
// assembled in memory (merged from several tables, no single SQL query to
|
|
3
|
+
// page). The projectionList pager sends `offset` + `totalCount`, never a
|
|
4
|
+
// cursor, so `nextCursor` is always null here.
|
|
5
|
+
import * as z from "zod";
|
|
6
|
+
import { MAX_LIST_LIMIT } from "./entity-handlers.js";
|
|
7
|
+
export function pagedListSchema(sortableFields) {
|
|
8
|
+
return z.object({
|
|
9
|
+
limit: z.number().int().min(1).max(MAX_LIST_LIMIT).default(50),
|
|
10
|
+
offset: z.number().int().nonnegative().optional(),
|
|
11
|
+
search: z.string().optional(),
|
|
12
|
+
sort: z.enum(sortableFields).optional(),
|
|
13
|
+
sortDirection: z.enum(["asc", "desc"]).optional(),
|
|
14
|
+
totalCount: z.boolean().optional(),
|
|
15
|
+
// Declared so a caller still paging by cursor gets a 400 instead of page one.
|
|
16
|
+
cursor: z.never({ error: "cursor paging is not supported here; page with offset" }).optional(),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function isMissing(value) {
|
|
20
|
+
return value === null || value === undefined;
|
|
21
|
+
}
|
|
22
|
+
// Missing values sort last in both directions; numbers and booleans compare
|
|
23
|
+
// by value, everything else as a locale-aware string.
|
|
24
|
+
function compareRowValues(a, b, directionMultiplier) {
|
|
25
|
+
if (isMissing(a) && isMissing(b))
|
|
26
|
+
return 0;
|
|
27
|
+
if (isMissing(a))
|
|
28
|
+
return 1;
|
|
29
|
+
if (isMissing(b))
|
|
30
|
+
return -1;
|
|
31
|
+
if (typeof a === "number" && typeof b === "number")
|
|
32
|
+
return (a - b) * directionMultiplier;
|
|
33
|
+
if (typeof a === "boolean" && typeof b === "boolean") {
|
|
34
|
+
return (Number(a) - Number(b)) * directionMultiplier;
|
|
35
|
+
}
|
|
36
|
+
return String(a).localeCompare(String(b)) * directionMultiplier;
|
|
37
|
+
}
|
|
38
|
+
export function pageRows(rows, request, options) {
|
|
39
|
+
const needle = request.search?.trim().toLowerCase() ?? "";
|
|
40
|
+
const matching = needle === ""
|
|
41
|
+
? rows
|
|
42
|
+
: rows.filter((row) => options.searchFields.some((field) => String(row[field] ?? "")
|
|
43
|
+
.toLowerCase()
|
|
44
|
+
.includes(needle)));
|
|
45
|
+
const sortField = request.sort ?? options.defaultSort;
|
|
46
|
+
const directionMultiplier = request.sortDirection === "desc" ? -1 : 1;
|
|
47
|
+
// The source rows come unordered, so ties fall back to the id; otherwise a
|
|
48
|
+
// tied row could show up on two pages or on none.
|
|
49
|
+
const sorted = [...matching].sort((a, b) => compareRowValues(a[sortField], b[sortField], directionMultiplier) || a.id.localeCompare(b.id));
|
|
50
|
+
const offset = request.offset ?? 0;
|
|
51
|
+
return {
|
|
52
|
+
rows: sorted.slice(offset, offset + request.limit),
|
|
53
|
+
nextCursor: null,
|
|
54
|
+
...(request.totalCount === true && { total: matching.length }),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
// resolves app-wide via a global short-id map, mirroring
|
|
40
40
|
// resolveRowActionNavigateTarget; kind:"drawer" resolves same-feature
|
|
41
41
|
// only, mirroring validateDrawerTargetAction)
|
|
42
|
-
// - projectionDetail header actions + metrics[].navigate
|
|
43
|
-
// - entityEdit/actionForm/secretMint redirect + actionForm cancelTarget
|
|
42
|
+
// - projectionDetail and dashboard header actions + projectionDetail metrics[].navigate
|
|
43
|
+
// - entityEdit/actionForm/secretMint redirect + actionForm successLinks and cancelTarget
|
|
44
44
|
// (resolveScreenTargetQn: same-feature short id or cross-feature QN)
|
|
45
45
|
// - dashboard DashboardScreenPanel.screen (resolveScreenTargetQn)
|
|
46
46
|
// - every EditSectionSpec kind's own `actions` (fields/extension/writeForm/
|
|
@@ -418,8 +418,27 @@ function projectHeader(header, indices, keptScreenQns) {
|
|
|
418
418
|
return changed ? { ...header, subtitle: nextParts } : header;
|
|
419
419
|
}
|
|
420
420
|
function projectDashboardPanels(panels, feature, keptScreenQns) {
|
|
421
|
-
|
|
422
|
-
|
|
421
|
+
let changed = false;
|
|
422
|
+
const kept = [];
|
|
423
|
+
for (const panel of panels) {
|
|
424
|
+
if (panel.kind !== "screen") {
|
|
425
|
+
kept.push(panel);
|
|
426
|
+
}
|
|
427
|
+
else if (!isScreenTargetKept(feature.featureName, panel.screen, keptScreenQns)) {
|
|
428
|
+
changed = true;
|
|
429
|
+
}
|
|
430
|
+
else if (panel.fallback !== undefined &&
|
|
431
|
+
!isScreenTargetKept(feature.featureName, panel.fallback, keptScreenQns)) {
|
|
432
|
+
// The role cannot reach the stand-in, so the tile just hides.
|
|
433
|
+
const { fallback: _unreachable, ...withoutFallback } = panel;
|
|
434
|
+
kept.push(withoutFallback);
|
|
435
|
+
changed = true;
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
kept.push(panel);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return changed ? kept : panels;
|
|
423
442
|
}
|
|
424
443
|
// --- per-screen-type projection ---
|
|
425
444
|
function projectListScreenId(listScreenId, indices, keptScreenQns) {
|
|
@@ -512,12 +531,15 @@ function projectEntityEditScreen(screen, feature, indices, keptScreenQns) {
|
|
|
512
531
|
};
|
|
513
532
|
}
|
|
514
533
|
function projectActionFormScreen(screen, feature, indices, keptScreenQns) {
|
|
515
|
-
const { redirect, cancelTarget, layout, listScreenId, ...rest } = screen;
|
|
534
|
+
const { redirect, successLinks, cancelTarget, layout, listScreenId, ...rest } = screen;
|
|
516
535
|
const nextRedirect = projectRedirect(feature.featureName, redirect, keptScreenQns);
|
|
536
|
+
const keptSuccessLinks = successLinks?.filter((link) => isRedirectTargetKept(feature.featureName, link, keptScreenQns));
|
|
537
|
+
const nextSuccessLinks = keptSuccessLinks?.length === 0 ? undefined : keptSuccessLinks;
|
|
517
538
|
const nextCancelTarget = projectCancelTarget(feature.featureName, cancelTarget, keptScreenQns);
|
|
518
539
|
const nextLayout = projectEditLayout(layout, feature, indices, keptScreenQns);
|
|
519
540
|
const nextListScreenId = projectListScreenId(listScreenId, indices, keptScreenQns);
|
|
520
541
|
const unchanged = nextRedirect === redirect &&
|
|
542
|
+
nextSuccessLinks?.length === successLinks?.length &&
|
|
521
543
|
nextCancelTarget === cancelTarget &&
|
|
522
544
|
nextLayout === layout &&
|
|
523
545
|
nextListScreenId === listScreenId;
|
|
@@ -527,6 +549,7 @@ function projectActionFormScreen(screen, feature, indices, keptScreenQns) {
|
|
|
527
549
|
...rest,
|
|
528
550
|
layout: nextLayout,
|
|
529
551
|
...(nextRedirect !== undefined && { redirect: nextRedirect }),
|
|
552
|
+
...(nextSuccessLinks !== undefined && { successLinks: nextSuccessLinks }),
|
|
530
553
|
...(nextCancelTarget !== undefined && { cancelTarget: nextCancelTarget }),
|
|
531
554
|
...(nextListScreenId !== undefined && { listScreenId: nextListScreenId }),
|
|
532
555
|
};
|
|
@@ -565,8 +588,16 @@ function projectScreen(screen, feature, indices, keptScreenQns) {
|
|
|
565
588
|
case "projectionDetail":
|
|
566
589
|
return projectProjectionDetailScreen(screen, feature, indices, keptScreenQns);
|
|
567
590
|
case "dashboard": {
|
|
591
|
+
const { actions, ...rest } = screen;
|
|
568
592
|
const nextPanels = projectDashboardPanels(screen.panels, feature, keptScreenQns);
|
|
569
|
-
|
|
593
|
+
const nextActions = projectRowActionArray(actions, feature, indices, keptScreenQns);
|
|
594
|
+
if (nextPanels === screen.panels && nextActions === actions)
|
|
595
|
+
return screen;
|
|
596
|
+
return {
|
|
597
|
+
...rest,
|
|
598
|
+
panels: nextPanels,
|
|
599
|
+
...(nextActions !== undefined && { actions: nextActions }),
|
|
600
|
+
};
|
|
570
601
|
}
|
|
571
602
|
case "entityEdit":
|
|
572
603
|
return projectEntityEditScreen(screen, feature, indices, keptScreenQns);
|
|
@@ -17,7 +17,7 @@ export type { ContentCollectionDefinition, NavDefinition, NavIconKey, } from "@c
|
|
|
17
17
|
export type { FromRule, FromRuleKind, OwnershipClause, OwnershipMap, OwnershipRef, OwnershipRule, SqlFragment, WhereRule, WhereRuleContext, } from "@cosmicdrift/kumiko-types/ownership";
|
|
18
18
|
export type { EntityProjectionExtension, MspErrorMode, MspErrorPolicy, MultiStreamApplyFn, MultiStreamProjectionDefinition, ProjectionDefinition, ProjectionRowIdOf, ProjectionTable, SingleStreamApplyFn, } from "@cosmicdrift/kumiko-types/projection";
|
|
19
19
|
export type { BelongsToRelation, EntityRelations, HasManyRelation, ManyToManyRelation, RelationDefinition, } from "@cosmicdrift/kumiko-types/relations";
|
|
20
|
-
export type { ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelGate, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardValueFormat, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EmbeddedListEditSpec, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormWidth, ListColumnSpec, ListEmptyStateSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, UnitKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
20
|
+
export type { ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardCurrencyFormat, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelGate, DashboardPanelVisibility, DashboardProgressListPanel, DashboardProgressTone, DashboardRecordBinding, DashboardRefresh, DashboardRelativeTimeFormat, DashboardRelativeTimeParam, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextBlock, DashboardTextPanel, DashboardTextParam, DashboardValueFormat, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EmbeddedListEditSpec, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormWidth, ListColumnSpec, ListEmptyStateSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, UnitKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
21
21
|
export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
22
22
|
export type { TargetRef } from "@cosmicdrift/kumiko-types/target-ref";
|
|
23
23
|
export type { Subscribe, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, } from "@cosmicdrift/kumiko-types/tree-node";
|
|
@@ -45,6 +45,19 @@ function pushKey(out, value, treatAsKey = false) {
|
|
|
45
45
|
function editFieldName(f) {
|
|
46
46
|
return typeof f === "string" ? f : f.field;
|
|
47
47
|
}
|
|
48
|
+
// Placeholder, affix texts and the fill button label of a field spec; a `{ field }`
|
|
49
|
+
// affix reads another form field and is no key.
|
|
50
|
+
function pushEditFieldTextKeys(out, spec, treatAsKey = false) {
|
|
51
|
+
// skip: shorthand field name carries no texts
|
|
52
|
+
if (typeof spec === "string")
|
|
53
|
+
return;
|
|
54
|
+
pushKey(out, spec.placeholder, treatAsKey);
|
|
55
|
+
for (const affix of [spec.prefix, spec.suffix]) {
|
|
56
|
+
if (typeof affix === "string")
|
|
57
|
+
pushKey(out, affix, treatAsKey);
|
|
58
|
+
}
|
|
59
|
+
pushKey(out, spec.fillAction?.label, treatAsKey);
|
|
60
|
+
}
|
|
48
61
|
// Group titles are translated exactly like the section title (computeEditViewModel).
|
|
49
62
|
function pushSectionTitles(out, section, treatAsKey = false) {
|
|
50
63
|
pushKey(out, section.title, treatAsKey);
|
|
@@ -67,15 +80,20 @@ function pushActionFormShapedKeys(out, featureName, step) {
|
|
|
67
80
|
pushSectionTitles(out, section);
|
|
68
81
|
for (const f of sectionFieldSpecs(section)) {
|
|
69
82
|
out.add(fieldLabelKey(featureName, ACTION_FORM_ENTITY, editFieldName(f)));
|
|
83
|
+
pushEditFieldTextKeys(out, f);
|
|
70
84
|
}
|
|
71
85
|
}
|
|
72
86
|
}
|
|
73
87
|
function pushRowActionKeys(out, action) {
|
|
74
88
|
pushKey(out, action.label);
|
|
75
|
-
if (action.kind === "writeHandler" ||
|
|
89
|
+
if (action.kind === "writeHandler" ||
|
|
90
|
+
action.kind === "clientAction" ||
|
|
91
|
+
action.kind === undefined) {
|
|
76
92
|
pushKey(out, action.confirm);
|
|
77
93
|
pushKey(out, action.confirmLabel);
|
|
78
94
|
}
|
|
95
|
+
if (action.kind === "clientAction")
|
|
96
|
+
pushKey(out, action.successMessage);
|
|
79
97
|
if (action.kind === "drawer") {
|
|
80
98
|
pushKey(out, action.title);
|
|
81
99
|
pushKey(out, action.subtitle);
|
|
@@ -90,6 +108,8 @@ function pushDashboardScreenKeys(out, dashboard) {
|
|
|
90
108
|
pushKey(out, option.label);
|
|
91
109
|
pushKey(out, dashboard.scope?.badge);
|
|
92
110
|
pushKey(out, dashboard.scope?.notice);
|
|
111
|
+
for (const action of dashboard.actions ?? [])
|
|
112
|
+
pushRowActionKeys(out, action);
|
|
93
113
|
}
|
|
94
114
|
function pushDashboardPanelKeys(out, panel) {
|
|
95
115
|
// skip: custom-Panel übersetzt sich selbst, kein Key hier
|
|
@@ -98,8 +118,12 @@ function pushDashboardPanelKeys(out, panel) {
|
|
|
98
118
|
pushKey(out, panel.label);
|
|
99
119
|
if (panel.kind === "chart" || panel.kind === "stat-group")
|
|
100
120
|
pushKey(out, panel.subtitle);
|
|
101
|
-
if (panel.kind === "
|
|
121
|
+
if (panel.kind === "text")
|
|
122
|
+
pushDashboardTextBlockKeys(out, panel);
|
|
123
|
+
if (panel.kind === "chart") {
|
|
124
|
+
pushKey(out, panel.unit);
|
|
102
125
|
pushDashboardChartRangeKeys(out, panel);
|
|
126
|
+
}
|
|
103
127
|
if (panel.kind === "chart" ||
|
|
104
128
|
panel.kind === "list" ||
|
|
105
129
|
panel.kind === "feed" ||
|
|
@@ -107,6 +131,9 @@ function pushDashboardPanelKeys(out, panel) {
|
|
|
107
131
|
pushKey(out, panel.emptyLabel);
|
|
108
132
|
pushKey(out, panel.emptyHint);
|
|
109
133
|
}
|
|
134
|
+
pushDashboardChildLabelKeys(out, panel);
|
|
135
|
+
}
|
|
136
|
+
function pushDashboardChildLabelKeys(out, panel) {
|
|
110
137
|
if (panel.kind === "stat-group") {
|
|
111
138
|
for (const stat of panel.stats)
|
|
112
139
|
pushKey(out, stat.label);
|
|
@@ -119,6 +146,20 @@ function pushDashboardPanelKeys(out, panel) {
|
|
|
119
146
|
}
|
|
120
147
|
}
|
|
121
148
|
}
|
|
149
|
+
function pushDashboardTextBlockKeys(out, panel) {
|
|
150
|
+
for (const block of panel.blocks) {
|
|
151
|
+
// skip: code is shown verbatim, not translated
|
|
152
|
+
if (block.kind === "code")
|
|
153
|
+
continue;
|
|
154
|
+
if (block.kind === "list") {
|
|
155
|
+
for (const item of block.items)
|
|
156
|
+
pushKey(out, item);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
pushKey(out, block.text);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
122
163
|
function pushDashboardChartRangeKeys(out, panel) {
|
|
123
164
|
for (const option of panel.ranges?.options ?? [])
|
|
124
165
|
pushKey(out, option.label);
|
|
@@ -243,6 +284,7 @@ export function requiredKeysFromScreen(featureName, screen, options = {}) {
|
|
|
243
284
|
continue; // rejected at boot, unreachable here
|
|
244
285
|
pushSectionTitles(out, section);
|
|
245
286
|
for (const f of sectionFieldSpecs(section)) {
|
|
287
|
+
pushEditFieldTextKeys(out, f);
|
|
246
288
|
const fieldName = editFieldName(f);
|
|
247
289
|
const override = edit.fieldLabels?.[fieldName];
|
|
248
290
|
if (override !== undefined)
|
|
@@ -260,6 +302,8 @@ export function requiredKeysFromScreen(featureName, screen, options = {}) {
|
|
|
260
302
|
pushKey(out, form.summary?.subtitle);
|
|
261
303
|
for (const action of form.footerActions ?? [])
|
|
262
304
|
pushKey(out, action.label);
|
|
305
|
+
for (const link of form.successLinks ?? [])
|
|
306
|
+
pushKey(out, link.label);
|
|
263
307
|
for (const fieldName of Object.keys(form.fields)) {
|
|
264
308
|
const override = form.fieldLabels?.[fieldName];
|
|
265
309
|
if (override !== undefined)
|
|
@@ -276,6 +320,7 @@ export function requiredKeysFromScreen(featureName, screen, options = {}) {
|
|
|
276
320
|
continue; // rejected at boot, unreachable here
|
|
277
321
|
pushSectionTitles(out, section);
|
|
278
322
|
for (const f of sectionFieldSpecs(section)) {
|
|
323
|
+
pushEditFieldTextKeys(out, f);
|
|
279
324
|
const fieldName = editFieldName(f);
|
|
280
325
|
const override = form.fieldLabels?.[fieldName];
|
|
281
326
|
if (override !== undefined)
|
|
@@ -328,6 +373,7 @@ export function requiredKeysFromScreen(featureName, screen, options = {}) {
|
|
|
328
373
|
pushSectionTitles(out, section, treatDotFormAsKey);
|
|
329
374
|
pushKey(out, section.description, treatDotFormAsKey);
|
|
330
375
|
for (const f of sectionFieldSpecs(section)) {
|
|
376
|
+
pushEditFieldTextKeys(out, f, treatDotFormAsKey);
|
|
331
377
|
const fieldName = editFieldName(f);
|
|
332
378
|
const override = config.fieldLabels?.[fieldName];
|
|
333
379
|
if (override !== undefined)
|
|
@@ -366,6 +412,7 @@ export function requiredKeysFromScreen(featureName, screen, options = {}) {
|
|
|
366
412
|
pushKey(out, section.submitLabel);
|
|
367
413
|
// kumiko-lint-ignore section-fields-raw writeForm sections carry no groups (EditWriteFormSection)
|
|
368
414
|
for (const f of section.fields) {
|
|
415
|
+
pushEditFieldTextKeys(out, f);
|
|
369
416
|
const fieldName = editFieldName(f);
|
|
370
417
|
out.add(fieldLabelKey(featureName, WRITE_FORM_SECTION_ENTITY, fieldName));
|
|
371
418
|
}
|
|
@@ -373,6 +420,7 @@ export function requiredKeysFromScreen(featureName, screen, options = {}) {
|
|
|
373
420
|
}
|
|
374
421
|
pushSectionTitles(out, section);
|
|
375
422
|
for (const f of sectionFieldSpecs(section)) {
|
|
423
|
+
pushEditFieldTextKeys(out, f);
|
|
376
424
|
const fieldName = editFieldName(f);
|
|
377
425
|
const override = detail.fieldLabels?.[fieldName];
|
|
378
426
|
if (override !== undefined)
|
package/dist/ui-types/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type { BooleanFieldDef, DateFieldDef, EmbeddedDerivedCellDef, EntityDefin
|
|
|
9
9
|
export type { AccessRule, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, RoleAccessPersonalData, RoleAccessRule, } from "../engine/types/handlers.ts";
|
|
10
10
|
export { isOpenToAllGranted, isUiAccessGranted } from "../engine/types/handlers.ts";
|
|
11
11
|
export type { IconKey, NavDefinition, NavIconKey } from "../engine/types/nav.ts";
|
|
12
|
-
export type { ActionFormFooterAction, ActionFormRedirect, ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartKind, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardChartTone, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelEmptyState, DashboardPanelGate, DashboardPanelQueryOptions, DashboardPanelSpan, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScopeDefinition, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardTimeRangeDefinition, DashboardValueFormat, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EmbeddedListEditSpec, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityListExpandableRow, EntityListFacetConfig, EntityListFacetExtraOption, EntityListScreenDefinition, EntityWriteExcludedFields, FieldCondition, FieldIconKey, FieldRenderer, FormWidth, ListBadgeTone, ListColumnDisplay, ListColumnSpec, ListEmptyStateSpec, ListFacetSpec, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSubtitlePart, RelatedListGroupBy, RelatedListRowTone, RelatedListToolbarAction, RowAction, RowActionDisplay, RowActionDrawer, RowActionMode, RowActionNavigate, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, } from "../engine/types/screen.ts";
|
|
12
|
+
export type { ActionFormFooterAction, ActionFormLoad, ActionFormRedirect, ActionFormScreenDefinition, ActionFormSuccessLink, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, 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, DashboardText, DashboardTextBlock, DashboardTextPanel, DashboardTextParam, DashboardTimeRangeDefinition, DashboardValueFormat, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EmbeddedListEditSpec, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityListExpandableRow, EntityListFacetConfig, EntityListFacetExtraOption, EntityListScreenDefinition, EntityWriteExcludedFields, FieldAffix, FieldCondition, FieldFillAction, FieldIconKey, FieldRenderer, FormWidth, ListBadgeTone, ListColumnDisplay, ListColumnSpec, ListEmptyStateSpec, ListFacetSpec, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSubtitlePart, RelatedListGroupBy, RelatedListRowTone, RelatedListToolbarAction, RowAction, RowActionClientAction, RowActionDisplay, RowActionDrawer, RowActionMode, RowActionNavigate, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, } from "../engine/types/screen.ts";
|
|
13
13
|
export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "../engine/types/screen.ts";
|
|
14
14
|
export type { TargetRef } from "../engine/types/target-ref.ts";
|
|
15
15
|
export type { TreeAction, TreeNode, TreeNodeState } from "../engine/types/tree-node.ts";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.374.0",
|
|
4
4
|
"description": "Framework core — engine, pipeline, API, DB, and every other bit that makes Kumiko go.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -199,8 +199,8 @@
|
|
|
199
199
|
"./package.json": "./package.json"
|
|
200
200
|
},
|
|
201
201
|
"dependencies": {
|
|
202
|
-
"@cosmicdrift/kumiko-http": "0.
|
|
203
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
202
|
+
"@cosmicdrift/kumiko-http": "0.374.0",
|
|
203
|
+
"@cosmicdrift/kumiko-types": "0.374.0",
|
|
204
204
|
"bullmq": "^5.76.7",
|
|
205
205
|
"bun-types": "^1.3.13",
|
|
206
206
|
"hono": "^4.13.1",
|
|
@@ -216,8 +216,8 @@
|
|
|
216
216
|
"zod": "^4.4.3"
|
|
217
217
|
},
|
|
218
218
|
"devDependencies": {
|
|
219
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.
|
|
220
|
-
"@cosmicdrift/kumiko-testing": "0.
|
|
219
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.374.0",
|
|
220
|
+
"@cosmicdrift/kumiko-testing": "0.374.0",
|
|
221
221
|
"bun-types": "^1.3.13",
|
|
222
222
|
"pino-pretty": "^13.1.3"
|
|
223
223
|
},
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,65 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.374.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "actionForm successLinks and resetOnSuccess",
|
|
6
|
+
"detail": "successLinks keeps the actionForm open after a successful submit and lists links resolved like redirect; boot rejects it together with redirect, with an unknown target or with a record target without idFrom. resetOnSuccess clears typed values after success. Role projection drops links to screens the role cannot reach."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.374.0",
|
|
10
|
+
"type": "improvement",
|
|
11
|
+
"title": "clientAction row actions",
|
|
12
|
+
"detail": "RowAction gains kind \"clientAction\" with a client-registered handler name; boot rejects a name that is not <feature>:<kebab>, and the surface keys include its confirm, confirmLabel and successMessage."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.374.0",
|
|
16
|
+
"type": "improvement",
|
|
17
|
+
"title": "Boot validation for screen-panel fallback and text panels",
|
|
18
|
+
"detail": "DashboardScreenPanel.fallback needs visibleWhen and a registered, embeddable target without its own visibleWhen. DashboardTextPanel (kind \"text\", exported with DashboardTextBlock) rejects empty blocks and empty lists; its text keys are required translation keys. Extension-selector panels qualify a short fallback ref, and role projection drops a fallback the role cannot reach."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"version": "0.374.0",
|
|
22
|
+
"type": "improvement",
|
|
23
|
+
"title": "Dashboard record binding, header actions, refresh and relative time",
|
|
24
|
+
"detail": "Dashboards take record, actions and refresh; stat panels and text params gain a relativeTime format; the surface keys include kumiko.dashboard.record.missing."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"version": "0.374.0",
|
|
28
|
+
"type": "breaking",
|
|
29
|
+
"title": "DashboardChartPanel.valueFormat only takes the currency format",
|
|
30
|
+
"detail": "DashboardValueFormat is now a union of DashboardCurrencyFormat and DashboardRelativeTimeFormat. Stat panels take both, chart panels only the currency one, and boot rejects relativeTime on a chart.",
|
|
31
|
+
"migration": "A chart panel that declares valueFormat keeps working with { kind: \"currency\", currency }. Code that types a chart's valueFormat as DashboardValueFormat should use DashboardCurrencyFormat instead; relativeTime on a chart was never meaningful and now fails boot."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"version": "0.374.0",
|
|
35
|
+
"type": "improvement",
|
|
36
|
+
"title": "Derived list searchability, actionForm load and drawer rowClick",
|
|
37
|
+
"detail": "projectionList searchable is derived false for entity-convention lists without searchable fields (searchable true is rejected at boot), actionForm.load registers a query checked at boot, and a drawer row action may set rowClick, counted with navigate rowClick and rejected on projectionDetail header actions."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"version": "0.374.0",
|
|
41
|
+
"type": "improvement",
|
|
42
|
+
"title": "Boot checks for field affixes and fillAction",
|
|
43
|
+
"detail": "Boot fails when a fillAction query is not a registered query handler, or when a { field } affix or a fillAction params source is not a field of the same form."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"version": "0.374.0",
|
|
47
|
+
"type": "improvement",
|
|
48
|
+
"title": "pagedListSchema and pageRows for in-memory list queries",
|
|
49
|
+
"detail": "pagedListSchema(sortableFields) and pageRows(rows, request, { searchFields, defaultSort }) cover search, sort (missing values last, ties by id), offset paging and an optional total for projectionList queries over merged rows. PagedListRequest and PagedListRowValue are exported; nextCursor is always null."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"version": "0.374.0",
|
|
53
|
+
"type": "improvement",
|
|
54
|
+
"title": "DashboardProgressTone type and unlimited label key",
|
|
55
|
+
"detail": "New exported type DashboardProgressTone for progress-list row tones. The default translations gain kumiko.dashboard.progress.unlimited."
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"version": "0.374.0",
|
|
59
|
+
"type": "improvement",
|
|
60
|
+
"title": "Dashboard chart panels take a y-axis unit",
|
|
61
|
+
"detail": "DashboardChartPanel.unit (i18n key) is shown once on the y axis of a timeseries chart, with or without valueFormat. The boot validator rejects unit on other chart kinds; an untranslated unit key fails the required-surface key check."
|
|
62
|
+
},
|
|
2
63
|
{
|
|
3
64
|
"version": "0.373.0",
|
|
4
65
|
"type": "improvement",
|