@cosmicdrift/kumiko-framework 0.330.2 → 0.332.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/db/blind-index-cleanup.d.ts +1 -0
- package/dist/db/blind-index-cleanup.js +12 -0
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/queries/event-consumer.d.ts +2 -1
- package/dist/db/queries/event-consumer.js +12 -2
- package/dist/engine/boot-validator/action-wiring.js +3 -0
- package/dist/engine/boot-validator/config-deps.d.ts +1 -0
- package/dist/engine/boot-validator/config-deps.js +24 -0
- package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
- package/dist/engine/boot-validator/entity-handler.js +20 -0
- package/dist/engine/boot-validator/index.js +6 -2
- package/dist/engine/boot-validator/projection-list-screens.js +11 -6
- package/dist/engine/boot-validator/query-output-columns.js +23 -2
- package/dist/engine/boot-validator/query-refs.d.ts +2 -1
- package/dist/engine/boot-validator/query-refs.js +24 -1
- package/dist/engine/boot-validator/screens.d.ts +3 -1
- package/dist/engine/boot-validator/screens.js +180 -78
- package/dist/engine/boot-validator/selector-owner-panels.d.ts +2 -0
- package/dist/engine/boot-validator/selector-owner-panels.js +25 -0
- package/dist/engine/boot-validator/url-prefill-fields.js +18 -1
- package/dist/engine/build-app-schema.js +3 -0
- package/dist/engine/build-config-feature-schema.js +40 -6
- package/dist/engine/changeset-changes.js +6 -0
- package/dist/engine/config-helpers.d.ts +2 -0
- package/dist/engine/config-helpers.js +9 -0
- package/dist/engine/entity-handlers.js +1 -1
- package/dist/engine/feature-ui-extensions.d.ts +4 -2
- package/dist/engine/feature-ui-extensions.js +40 -3
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/project-app-schema-for-roles.js +30 -2
- package/dist/engine/registry-facade.js +3 -0
- package/dist/engine/registry-ingest.js +2 -0
- package/dist/engine/registry-state.d.ts +2 -1
- package/dist/engine/registry-state.js +1 -0
- package/dist/engine/schema-builder.d.ts +3 -1
- package/dist/engine/schema-builder.js +5 -6
- package/dist/engine/types/index.d.ts +2 -2
- package/dist/errors/reasons.js +7 -6
- package/dist/i18n/required-surface-keys.js +27 -10
- package/dist/pipeline/dispatch-write.js +26 -2
- package/dist/pipeline/event-consumer-state.js +6 -0
- package/dist/pipeline/event-dispatcher-admin.js +36 -16
- package/dist/pipeline/event-dispatcher-delivery.d.ts +2 -0
- package/dist/pipeline/event-dispatcher-delivery.js +22 -2
- package/dist/pipeline/event-dispatcher.js +1 -0
- package/dist/ui-types/index.d.ts +1 -1
- package/package.json +4 -4
- package/src/changes.json +54 -0
package/dist/engine/index.d.ts
CHANGED
|
@@ -71,7 +71,7 @@ export { describeWorkflowStepError } from "./steps/describe-workflow-step-error.
|
|
|
71
71
|
export { ANONYMOUS_ROLE, ANONYMOUS_USER_ID, createAnonymousUser, createSystemUser, SYSTEM_ROLE, SYSTEM_USER_ID, } from "./system-user.js";
|
|
72
72
|
export { type EffectiveFeaturesResolver, findTierResolverUsage, isTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, type TierResolverPlugin, type TrialGate, } from "./tier-resolver-extension.js";
|
|
73
73
|
export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.js";
|
|
74
|
-
export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, CamelToKebab, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardFeedPanel, DashboardFilterDefinition, DashboardListPanel, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DateFieldDef, DecimalFieldDef, DeleteContext, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EntityDefinition, EntityEditScreenDefinition, EntityId, EntityListScreenDefinition, 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, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationRecipientFn, NotificationTemplateFn, NotifyFactory, NotifyFn, NotifyOptions, NotifyPriority, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RecordHeaderSpec, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TenantId, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.js";
|
|
74
|
+
export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, CamelToKebab, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardFeedPanel, DashboardFilterDefinition, DashboardListPanel, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DateFieldDef, DecimalFieldDef, DeleteContext, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EntityDefinition, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, 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, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationRecipientFn, NotificationTemplateFn, NotifyFactory, NotifyFn, NotifyOptions, NotifyPriority, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TenantId, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.js";
|
|
75
75
|
export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, HookPhases, isAgentVisibleScreen, isOpenToAllGranted, resolveAgentExposure, } from "./types/index.js";
|
|
76
76
|
export type { AwaitedEventType, PipelineBuildCtx, PipelineCtx, PipelineDef, StepBuilder, StepDef, StepFailureStrategy, StepInstance, StepKind, StepNamespace, StepResolver, } from "./types/step.js";
|
|
77
77
|
export { runValidation } from "./validation.js";
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
// - WorkspaceSchema.definition.nav — the raw explicit nav-QN list,
|
|
56
56
|
// verbatim in JSON, distinct from the computed/filtered navMembers
|
|
57
57
|
// - entityList.createScreen (same-feature target, dropped when not kept)
|
|
58
|
+
// - entityList.expandableRow (a relatedList: same projection as a relatedList section)
|
|
58
59
|
// - listScreenId (projectionDetail/entityEdit/actionForm/secretMint/custom —
|
|
59
60
|
// the breadcrumb "back to list" target). It's serialized, so a visible
|
|
60
61
|
// screen would otherwise disclose a denied screen's short id. Resolved
|
|
@@ -400,6 +401,22 @@ function projectMetrics(metrics, indices, keptScreenQns) {
|
|
|
400
401
|
});
|
|
401
402
|
return changed ? next : metrics;
|
|
402
403
|
}
|
|
404
|
+
function projectHeader(header, indices, keptScreenQns) {
|
|
405
|
+
const subtitle = header?.subtitle;
|
|
406
|
+
if (header === undefined || typeof subtitle === "string" || subtitle === undefined) {
|
|
407
|
+
return header;
|
|
408
|
+
}
|
|
409
|
+
let changed = false;
|
|
410
|
+
const nextParts = subtitle.map((part) => {
|
|
411
|
+
if (typeof part === "string" || part.navigate === undefined)
|
|
412
|
+
return part;
|
|
413
|
+
if (keepMetricNavigate(part.navigate, indices, keptScreenQns))
|
|
414
|
+
return part;
|
|
415
|
+
changed = true;
|
|
416
|
+
return part.field;
|
|
417
|
+
});
|
|
418
|
+
return changed ? { ...header, subtitle: nextParts } : header;
|
|
419
|
+
}
|
|
403
420
|
function projectDashboardPanels(panels, feature, keptScreenQns) {
|
|
404
421
|
const kept = panels.filter((p) => p.kind !== "screen" || isScreenTargetKept(feature.featureName, p.screen, keptScreenQns));
|
|
405
422
|
return kept.length === panels.length ? panels : kept;
|
|
@@ -433,21 +450,31 @@ function projectListScreen(original, feature, indices, keptScreenQns) {
|
|
|
433
450
|
const { rowActions, toolbarActions, ...rest } = screen;
|
|
434
451
|
const nextRowActions = projectRowActionArray(rowActions, feature, indices, keptScreenQns);
|
|
435
452
|
const nextToolbarActions = projectToolbarActionArray(toolbarActions, feature, indices, keptScreenQns);
|
|
436
|
-
|
|
453
|
+
const expandableRow = screen.type === "entityList" ? screen.expandableRow : undefined;
|
|
454
|
+
const nextExpandableRow = expandableRow === undefined
|
|
455
|
+
? undefined
|
|
456
|
+
: projectRelatedListSection(expandableRow, feature, indices, keptScreenQns);
|
|
457
|
+
if (nextRowActions === rowActions &&
|
|
458
|
+
nextToolbarActions === toolbarActions &&
|
|
459
|
+
nextExpandableRow === expandableRow) {
|
|
437
460
|
return screen;
|
|
461
|
+
}
|
|
438
462
|
return {
|
|
439
463
|
...rest,
|
|
440
464
|
...(nextRowActions !== undefined && { rowActions: nextRowActions }),
|
|
441
465
|
...(nextToolbarActions !== undefined && { toolbarActions: nextToolbarActions }),
|
|
466
|
+
...(nextExpandableRow !== undefined && { expandableRow: nextExpandableRow }),
|
|
442
467
|
};
|
|
443
468
|
}
|
|
444
469
|
function projectProjectionDetailScreen(screen, feature, indices, keptScreenQns) {
|
|
445
|
-
const { metrics, actions, layout, listScreenId, ...rest } = screen;
|
|
470
|
+
const { metrics, header, actions, layout, listScreenId, ...rest } = screen;
|
|
446
471
|
const nextMetrics = projectMetrics(metrics, indices, keptScreenQns);
|
|
472
|
+
const nextHeader = projectHeader(header, indices, keptScreenQns);
|
|
447
473
|
const nextActions = projectRowActionArray(actions, feature, indices, keptScreenQns);
|
|
448
474
|
const nextLayout = projectEditLayout(layout, feature, indices, keptScreenQns);
|
|
449
475
|
const nextListScreenId = projectListScreenId(listScreenId, indices, keptScreenQns);
|
|
450
476
|
const unchanged = nextMetrics === metrics &&
|
|
477
|
+
nextHeader === header &&
|
|
451
478
|
nextActions === actions &&
|
|
452
479
|
nextLayout === layout &&
|
|
453
480
|
nextListScreenId === listScreenId;
|
|
@@ -456,6 +483,7 @@ function projectProjectionDetailScreen(screen, feature, indices, keptScreenQns)
|
|
|
456
483
|
return {
|
|
457
484
|
...rest,
|
|
458
485
|
layout: nextLayout,
|
|
486
|
+
...(nextHeader !== undefined && { header: nextHeader }),
|
|
459
487
|
...(nextMetrics !== undefined && { metrics: nextMetrics }),
|
|
460
488
|
...(nextActions !== undefined && { actions: nextActions }),
|
|
461
489
|
...(nextListScreenId !== undefined && { listScreenId: nextListScreenId }),
|
|
@@ -149,6 +149,9 @@ export function buildRegistryFacade(state) {
|
|
|
149
149
|
getAllExtensionSelectors() {
|
|
150
150
|
return state.extensionSelectorMap;
|
|
151
151
|
},
|
|
152
|
+
getExtensionSelectorPanels(extensionName) {
|
|
153
|
+
return state.extensionSelectorPanelsMap.get(extensionName) ?? [];
|
|
154
|
+
},
|
|
152
155
|
getAllNotifications() {
|
|
153
156
|
return state.notificationMap;
|
|
154
157
|
},
|
|
@@ -189,6 +189,8 @@ export function populateExtensionsAndSeeds(state, feature) {
|
|
|
189
189
|
`(feature "${feature.name}") — one owning feature declares the selector.`);
|
|
190
190
|
}
|
|
191
191
|
state.extensionSelectorMap.set(sel.extensionName, sel.qualifiedKey);
|
|
192
|
+
if (sel.panels !== undefined)
|
|
193
|
+
state.extensionSelectorPanelsMap.set(sel.extensionName, sel.panels);
|
|
192
194
|
}
|
|
193
195
|
state.allReferenceData.push(...(feature.referenceData ?? []));
|
|
194
196
|
state.allConfigSeeds.push(...(feature.configSeeds ?? []));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type QnType, qualifyEntityName } from "./qualified-name.js";
|
|
2
|
-
import type { AuthClaimsHookDef, ClaimKeyDefinition, ConfigKeyDefinition, ConfigSeedDef, EntityDefinition, EntityProjectionExtension, EventDef, EventUpcastFn, FeatureDefinition, FeatureMetricDef, HookPhase, JobDefinition, MultiStreamProjectionDefinition, NavDefinition, NotificationDefinition, OwnedFn, PhasedHook, PostDeleteHookFn, PostQueryHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, QueryHandlerDef, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionRegistration, RelationDefinition, ScreenDefinition, SearchPayloadContributorFn, SecretKeyDefinition, StoreTableDef, StreamHandlerDef, TranslationValue, TreeActionDef, WorkspaceDefinition, WriteHandlerDef } from "./types/index.js";
|
|
2
|
+
import type { AuthClaimsHookDef, ClaimKeyDefinition, ConfigKeyDefinition, ConfigSeedDef, EntityDefinition, EntityProjectionExtension, EventDef, EventUpcastFn, ExtensionSelectorPanel, FeatureDefinition, FeatureMetricDef, HookPhase, JobDefinition, MultiStreamProjectionDefinition, NavDefinition, NotificationDefinition, OwnedFn, PhasedHook, PostDeleteHookFn, PostQueryHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, QueryHandlerDef, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionRegistration, RelationDefinition, ScreenDefinition, SearchPayloadContributorFn, SecretKeyDefinition, StoreTableDef, StreamHandlerDef, TranslationValue, TreeActionDef, WorkspaceDefinition, WriteHandlerDef } from "./types/index.js";
|
|
3
3
|
export type IncomingRelation = {
|
|
4
4
|
sourceEntity: string;
|
|
5
5
|
relationName: string;
|
|
@@ -46,6 +46,7 @@ export type RegistryState = {
|
|
|
46
46
|
extensionMap: Map<string, RegistrarExtensionDef>;
|
|
47
47
|
extensionUsages: RegistrarExtensionRegistration[];
|
|
48
48
|
extensionSelectorMap: Map<string, string>;
|
|
49
|
+
extensionSelectorPanelsMap: Map<string, readonly ExtensionSelectorPanel[]>;
|
|
49
50
|
allReferenceData: ReferenceDataDef[];
|
|
50
51
|
allConfigSeeds: ConfigSeedDef[];
|
|
51
52
|
mergedTranslations: Record<string, Record<string, TranslationValue>>;
|
|
@@ -4,5 +4,7 @@ export declare function isRepresentableAtScale(n: number, scale: number): boolea
|
|
|
4
4
|
export declare function fieldToZod(field: FieldDefinition, currencies: readonly string[], opts?: {
|
|
5
5
|
readonly applyDefaults?: boolean;
|
|
6
6
|
}): z.ZodTypeAny;
|
|
7
|
-
export declare function buildInsertSchema(entity: EntityDefinition, currencies?: readonly string[], excludedFields?: readonly string[]
|
|
7
|
+
export declare function buildInsertSchema(entity: EntityDefinition, currencies?: readonly string[], excludedFields?: readonly string[], options?: {
|
|
8
|
+
readonly allowCallerId?: boolean;
|
|
9
|
+
}): z.ZodObject<Record<string, z.ZodTypeAny>>;
|
|
8
10
|
export declare function buildUpdateSchema(entity: EntityDefinition, currencies?: readonly string[], excludedFields?: readonly string[]): z.ZodObject<Record<string, z.ZodTypeAny>>;
|
|
@@ -366,7 +366,7 @@ function applyTotalsMatchRefinements(entity, schema) {
|
|
|
366
366
|
function notWritableField() {
|
|
367
367
|
return z.never().optional();
|
|
368
368
|
}
|
|
369
|
-
export function buildInsertSchema(entity, currencies = [...DEFAULT_CURRENCIES], excludedFields = []) {
|
|
369
|
+
export function buildInsertSchema(entity, currencies = [...DEFAULT_CURRENCIES], excludedFields = [], options = {}) {
|
|
370
370
|
const shape = {};
|
|
371
371
|
for (const [name, field] of Object.entries(entity.fields)) {
|
|
372
372
|
if (excludedFields.includes(name)) {
|
|
@@ -378,11 +378,10 @@ export function buildInsertSchema(entity, currencies = [...DEFAULT_CURRENCIES],
|
|
|
378
378
|
const isRequired = "required" in field && field.required === true;
|
|
379
379
|
shape[name] = isRequired || hasDefault ? zodField : zodField.optional();
|
|
380
380
|
}
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
shape["id"] = z.uuid().optional();
|
|
381
|
+
// Opt-in only for the generic create verb, which honors the id for system identities;
|
|
382
|
+
// other schema consumers must keep stripping an unknown `id`.
|
|
383
|
+
if (options.allowCallerId)
|
|
384
|
+
shape["id"] = z.uuid().optional();
|
|
386
385
|
return applyTotalsMatchRefinements(entity, z.object(shape));
|
|
387
386
|
}
|
|
388
387
|
export function buildUpdateSchema(entity, currencies = [...DEFAULT_CURRENCIES], excludedFields = []) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { isAgentVisibleScreen, resolveAgentExposure, } from "@cosmicdrift/kumiko-types/agent-exposure";
|
|
2
|
-
export type { ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigBounds, ConfigCascade, ConfigCascadeLevel, ConfigComputedContext, ConfigComputedFn, ConfigDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValue, ConfigValueSource, ConfigValueWithSource, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, ExtensionSelectorDef, JobBackoff, JobBackoffStrategy, JobDefinition, JobHandlerFn, JobRunIn, JobTrigger, NotificationDataFn, NotificationDefinition, NotificationRecipientFn, NotificationTemplateFn, PluralForms, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionHooks, RegistrarExtensionRegistration, RunIn, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, UiExtensionDef, } from "@cosmicdrift/kumiko-types/config";
|
|
2
|
+
export type { ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigBounds, ConfigCascade, ConfigCascadeLevel, ConfigComputedContext, ConfigComputedFn, ConfigDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValue, ConfigValueSource, ConfigValueWithSource, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, ExtensionSelectorDef, ExtensionSelectorPanel, JobBackoff, JobBackoffStrategy, JobDefinition, JobHandlerFn, JobRunIn, JobTrigger, NotificationDataFn, NotificationDefinition, NotificationRecipientFn, NotificationTemplateFn, PluralForms, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionHooks, RegistrarExtensionRegistration, RunIn, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, UiExtensionDef, } from "@cosmicdrift/kumiko-types/config";
|
|
3
3
|
export type { QueryHandlerDefinition, StreamHandlerDefinition, WriteHandlerDefinition, WriteHandlerInput, } from "@cosmicdrift/kumiko-types/define-handler";
|
|
4
4
|
export type { EntityCrudRegistrar, EntityCrudVerb, EntityHandlerOptions, EntityQueryHandlerOptions, EntityWriteHandlerOptions, RegisterEntityCrudOptions, } from "@cosmicdrift/kumiko-types/entity-handlers";
|
|
5
5
|
export type { KumikoEntityTypeMap, KumikoEventTypeMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, } from "@cosmicdrift/kumiko-types/event-type-map";
|
|
@@ -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, 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, DashboardChartPanel, DashboardCustomPanel, DashboardFeedPanel, DashboardFilterDefinition, DashboardListPanel, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditScreenDefinition, EntityListScreenDefinition, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormWidth, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, 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, DashboardChartPanel, DashboardCustomPanel, DashboardFeedPanel, DashboardFilterDefinition, DashboardListPanel, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormWidth, ListColumnSpec, 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 } 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";
|
package/dist/errors/reasons.js
CHANGED
|
@@ -66,16 +66,17 @@ export const FrameworkReasons = {
|
|
|
66
66
|
// sister catalog to FrameworkReasons instead of extending it — see the
|
|
67
67
|
// file-header note above for why these carry the `agent.` prefix.
|
|
68
68
|
export const AgentReasons = {
|
|
69
|
-
//
|
|
70
|
-
// catalog
|
|
69
|
+
// Error turn in the agent loop when the model requests a tool outside the
|
|
70
|
+
// run's catalog; UnprocessableError in rule validation (handler missing from
|
|
71
|
+
// the caller's role-filtered manifest, or `ai-agent-edit` not mounted).
|
|
71
72
|
toolNotAllowed: "agent.tool_not_allowed",
|
|
72
|
-
//
|
|
73
|
-
//
|
|
73
|
+
// Not thrown: the run ends with a `clarify` turn (`payload.reason`) once
|
|
74
|
+
// MAX_AGENT_ROUNDS is reached.
|
|
74
75
|
iterationLimit: "agent.iteration_limit",
|
|
75
76
|
// AccessDeniedError: the tool exists, but the caller's roles don't cover
|
|
76
77
|
// the handler behind it.
|
|
77
78
|
permissionDenied: "agent.permission_denied",
|
|
78
|
-
//
|
|
79
|
-
//
|
|
79
|
+
// UnprocessableError: a permission rule with `always` was requested for an
|
|
80
|
+
// `agent: { risk: "high" }` handler; `details.handlerQn` names it.
|
|
80
81
|
highRiskNoAlways: "agent.high_risk_no_always",
|
|
81
82
|
};
|
|
@@ -107,6 +107,30 @@ function pushToolbarActionKeys(out, action) {
|
|
|
107
107
|
// toolbar button AND the Drawer title, see ToolbarDrawerHost) is
|
|
108
108
|
// already pushed above.
|
|
109
109
|
}
|
|
110
|
+
function pushRelatedListSectionKeys(out, section) {
|
|
111
|
+
pushKey(out, section.title);
|
|
112
|
+
pushKey(out, section.description);
|
|
113
|
+
pushKey(out, section.itemNoun);
|
|
114
|
+
for (const col of section.columns) {
|
|
115
|
+
const normalized = normalizeListColumn(col);
|
|
116
|
+
if (normalized.label !== undefined)
|
|
117
|
+
pushKey(out, normalized.label);
|
|
118
|
+
}
|
|
119
|
+
for (const action of section.rowActions ?? [])
|
|
120
|
+
pushRowActionKeys(out, action);
|
|
121
|
+
}
|
|
122
|
+
function pushExpandableRowKeys(out, section) {
|
|
123
|
+
pushRelatedListSectionKeys(out, section);
|
|
124
|
+
for (const action of section.actions ?? [])
|
|
125
|
+
pushRowActionKeys(out, action);
|
|
126
|
+
for (const action of section.toolbarActions ?? [])
|
|
127
|
+
pushToolbarActionKeys(out, action);
|
|
128
|
+
pushKey(out, section.emptyState?.title);
|
|
129
|
+
pushKey(out, section.emptyState?.description);
|
|
130
|
+
if (section.emptyState?.action !== undefined) {
|
|
131
|
+
pushRowActionKeys(out, section.emptyState.action);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
110
134
|
export function requiredKeysFromScreen(featureName, screen, options = {}) {
|
|
111
135
|
const { treatDotFormAsKey = false } = options;
|
|
112
136
|
const out = new Set();
|
|
@@ -129,6 +153,8 @@ export function requiredKeysFromScreen(featureName, screen, options = {}) {
|
|
|
129
153
|
pushRowActionKeys(out, action);
|
|
130
154
|
for (const action of list.toolbarActions ?? [])
|
|
131
155
|
pushToolbarActionKeys(out, action);
|
|
156
|
+
if (list.expandableRow !== undefined)
|
|
157
|
+
pushExpandableRowKeys(out, list.expandableRow);
|
|
132
158
|
break;
|
|
133
159
|
}
|
|
134
160
|
case "projectionList": {
|
|
@@ -301,16 +327,7 @@ export function requiredKeysFromScreen(featureName, screen, options = {}) {
|
|
|
301
327
|
continue;
|
|
302
328
|
}
|
|
303
329
|
if (section.kind === "relatedList") {
|
|
304
|
-
|
|
305
|
-
pushKey(out, section.description);
|
|
306
|
-
pushKey(out, section.itemNoun);
|
|
307
|
-
for (const col of section.columns) {
|
|
308
|
-
const normalized = normalizeListColumn(col);
|
|
309
|
-
if (normalized.label !== undefined)
|
|
310
|
-
pushKey(out, normalized.label);
|
|
311
|
-
}
|
|
312
|
-
for (const action of section.rowActions ?? [])
|
|
313
|
-
pushRowActionKeys(out, action);
|
|
330
|
+
pushRelatedListSectionKeys(out, section);
|
|
314
331
|
continue;
|
|
315
332
|
}
|
|
316
333
|
if (isWriteFormEditSection(section)) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { selectRowForUpdateById } from "../db/queries/entity-read.js";
|
|
2
2
|
import { asEntityTableMeta, selectMany } from "../db/query.js";
|
|
3
3
|
import { buildEntityTable, toSnakeCase } from "../db/table-builder.js";
|
|
4
|
+
import { createTenantDb, hasTenantColumn } from "../db/tenant-db.js";
|
|
4
5
|
import { tenantDbRunner } from "../db/tenant-db-runner.js";
|
|
5
6
|
import { hasAccess } from "../engine/access.js";
|
|
6
7
|
import { ConfigScopes } from "../engine/constants.js";
|
|
@@ -126,6 +127,24 @@ function isForeignTenantParentRow(parentRow, user) {
|
|
|
126
127
|
const rowTenantId = parentRow["tenantId"];
|
|
127
128
|
return typeof rowTenantId === "string" && rowTenantId !== user.tenantId;
|
|
128
129
|
}
|
|
130
|
+
// A custom create handler may return a row without a string tenantId; the row's
|
|
131
|
+
// tenant is then unknown from the payload, so re-read it through the caller's tenant
|
|
132
|
+
// filter. Fail-closed: anything that cannot be verified counts as not visible.
|
|
133
|
+
// Tenant reads also return SYSTEM reference rows, so the row's own tenantId must match
|
|
134
|
+
// (same rule as the string path) unless the entity has no tenant column.
|
|
135
|
+
async function isParentRowHiddenFromCaller(ctx, parentEntityName, parentId, user, tx) {
|
|
136
|
+
if (parentEntityName === undefined)
|
|
137
|
+
return true;
|
|
138
|
+
const table = getTable(ctx, parentEntityName);
|
|
139
|
+
const source = resolveDbSource(ctx, tx);
|
|
140
|
+
if (!table || !source)
|
|
141
|
+
return true;
|
|
142
|
+
const callerDb = createTenantDb(source, user.tenantId, "tenant");
|
|
143
|
+
const visibleRow = await callerDb.fetchOne(table, { id: parentId });
|
|
144
|
+
if (visibleRow === undefined)
|
|
145
|
+
return true;
|
|
146
|
+
return hasTenantColumn(table) && visibleRow["tenantId"] !== user.tenantId;
|
|
147
|
+
}
|
|
129
148
|
// Nested-write orchestration (v1: depth=1, create-only, hasMany-only).
|
|
130
149
|
//
|
|
131
150
|
// When a parent `:create` handler's payload carries values under keys
|
|
@@ -199,13 +218,18 @@ export async function executeNestedWrite(ctx, type, payload, user, origin, tx, a
|
|
|
199
218
|
}));
|
|
200
219
|
}
|
|
201
220
|
// A custom create handler may return an existing foreign row; verify it before attaching children.
|
|
202
|
-
|
|
221
|
+
const parentEntityName = registry.getHandlerEntity(type);
|
|
222
|
+
const isCallerScoped = !registry.isHandlerSystemScoped(type);
|
|
223
|
+
const isParentForeign = isCallerScoped &&
|
|
224
|
+
(typeof parentRow["tenantId"] === "string"
|
|
225
|
+
? isForeignTenantParentRow(parentRow, user)
|
|
226
|
+
: await isParentRowHiddenFromCaller(ctx, parentEntityName, parentId, user, tx));
|
|
227
|
+
if (isParentForeign) {
|
|
203
228
|
return writeFailure(new AccessDeniedError({
|
|
204
229
|
message: `nested-write: parent row belongs to another tenant — refusing to attach children to "${type}"`,
|
|
205
230
|
details: { reason: FrameworkReasons.fieldAccessDenied, handler: type },
|
|
206
231
|
}));
|
|
207
232
|
}
|
|
208
|
-
const parentEntityName = registry.getHandlerEntity(type);
|
|
209
233
|
if (parentEntityName) {
|
|
210
234
|
const parentEntity = registry.getEntity(parentEntityName);
|
|
211
235
|
if (parentEntity) {
|
|
@@ -29,6 +29,10 @@ export const eventConsumerStateTable = pgTable("kumiko_event_consumers", {
|
|
|
29
29
|
// skipPoisonEvent() — an operator vouching the consumer is healthy.
|
|
30
30
|
rearmCount: integer("rearm_count").notNull().default(0),
|
|
31
31
|
pendingGaps: jsonb("pending_gaps").$type().default([]).notNull(),
|
|
32
|
+
// Id of the event whose handler failed last (halt-on-poison). skipPoisonEvent
|
|
33
|
+
// skips exactly this id; guessing the smallest visible id would drop a healthy
|
|
34
|
+
// writer that committed late into a pending gap. Null once a delivery succeeds.
|
|
35
|
+
lastFailedEventId: bigint("last_failed_event_id", { mode: "bigint" }),
|
|
32
36
|
lastError: text("last_error"),
|
|
33
37
|
updatedAt: instant("updated_at", { precision: 3 }).notNull().default(sql `now()`),
|
|
34
38
|
}, (t) => ({
|
|
@@ -78,6 +82,8 @@ export async function createEventConsumerStateTable(db) {
|
|
|
78
82
|
/* ifNotExists */ true);
|
|
79
83
|
await alterTableAddColumn(db, "kumiko_event_consumers", "pending_gaps", "jsonb", " DEFAULT '[]'::jsonb", " NOT NULL",
|
|
80
84
|
/* ifNotExists */ true);
|
|
85
|
+
await alterTableAddColumn(db, "kumiko_event_consumers", "last_failed_event_id", "bigint", "", "",
|
|
86
|
+
/* ifNotExists */ true);
|
|
81
87
|
// Runs on every boot, including mid-rolling-deploy: a new pod can delete
|
|
82
88
|
// an old pod's still-in-use per-instance row out from under it. That old
|
|
83
89
|
// pod's next acquireConsumer then finds no row, delivers no more SSE/
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { advanceConsumerPastEventReturning, removePendingGapReturning, updateConsumerStatusReturning, } from "../db/queries/event-consumer.js";
|
|
1
|
+
import { advanceConsumerPastEventReturning, removePendingGapReturning, selectSnapshotXmax, updateConsumerStatusReturning, } from "../db/queries/event-consumer.js";
|
|
2
2
|
import { selectNextEventIdAfter, selectSmallestVisibleIdInRanges, } from "../db/queries/event-store.js";
|
|
3
3
|
import { coerceRow, extractTableInfo, selectMany } from "../db/query.js";
|
|
4
4
|
import { getEventsHighWaterMark } from "../event-store/index.js";
|
|
5
|
-
import { eventConsumerStateTable, SHARED_INSTANCE_SENTINEL } from "./event-consumer-state.js";
|
|
5
|
+
import { eventConsumerStateTable, SHARED_INSTANCE_SENTINEL, } from "./event-consumer-state.js";
|
|
6
6
|
import { rangeContainsId, splitRangeExcludingIds, toIdRanges } from "./pending-gap-ranges.js";
|
|
7
7
|
// --- Ops recovery surface ---
|
|
8
8
|
//
|
|
@@ -76,29 +76,38 @@ export async function enableConsumer(db, name, instanceId = SHARED_INSTANCE_SENT
|
|
|
76
76
|
}
|
|
77
77
|
return applyConsumerStatusTransition(db, name, instanceId, "idle");
|
|
78
78
|
}
|
|
79
|
-
// skipPoisonEvent
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
// TX so concurrent dispatcher passes can't
|
|
85
|
-
// idempotent no-op.
|
|
79
|
+
// skipPoisonEvent skips the event whose handler failed last
|
|
80
|
+
// (last_failed_event_id, set by the delivery's halt-on-poison branch): inside
|
|
81
|
+
// pending_gaps it is split out of its range, otherwise the cursor moves onto
|
|
82
|
+
// it. Without a recorded id (consumer parked before that column existed) it
|
|
83
|
+
// falls back to a guess: the smallest visible pending-gap id, else the next
|
|
84
|
+
// event after the cursor. Single TX so concurrent dispatcher passes can't
|
|
85
|
+
// double-advance. Nothing to skip → idempotent no-op.
|
|
86
86
|
export async function skipPoisonEvent(db, name, instanceId = SHARED_INSTANCE_SENTINEL) {
|
|
87
87
|
const before = await requireConsumerRow(db, name, instanceId);
|
|
88
88
|
return db.begin(async (tx) => {
|
|
89
89
|
const pendingGaps = before.pendingGaps;
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
const failedEventId = before.lastFailedEventId;
|
|
91
|
+
const failedIsPending = failedEventId !== null && pendingGaps.some((gap) => rangeContainsId(gap, failedEventId));
|
|
92
|
+
const failedIsAboveCursor = failedEventId !== null && failedEventId > before.lastProcessedEventId;
|
|
93
|
+
const pendingPoisonId = failedIsPending
|
|
94
|
+
? failedEventId
|
|
95
|
+
: failedIsAboveCursor
|
|
96
|
+
? null
|
|
97
|
+
: await selectSmallestVisibleIdInRanges(tx, toIdRanges(pendingGaps));
|
|
98
|
+
if (pendingPoisonId !== null) {
|
|
99
|
+
const newPendingGaps = pendingGaps.flatMap((gap) => rangeContainsId(gap, pendingPoisonId)
|
|
100
|
+
? splitRangeExcludingIds(gap, [pendingPoisonId])
|
|
94
101
|
: [gap]);
|
|
95
102
|
const raw = await removePendingGapReturning(tx, name, instanceId, newPendingGaps);
|
|
96
103
|
const updated = raw && coerceRow(raw, extractTableInfo(eventConsumerStateTable));
|
|
97
104
|
if (!updated)
|
|
98
105
|
throw new Error(`Consumer "${name}" (instance_id="${instanceId}") vanished mid-skip — retry.`);
|
|
99
|
-
return { ...normalizeConsumerState(updated), skippedEventId:
|
|
106
|
+
return { ...normalizeConsumerState(updated), skippedEventId: pendingPoisonId };
|
|
100
107
|
}
|
|
101
|
-
const poisonId =
|
|
108
|
+
const poisonId = failedIsAboveCursor
|
|
109
|
+
? failedEventId
|
|
110
|
+
: await selectNextEventIdAfter(tx, before.lastProcessedEventId);
|
|
102
111
|
if (poisonId === null) {
|
|
103
112
|
const [unchanged] = await selectMany(tx, eventConsumerStateTable, {
|
|
104
113
|
name,
|
|
@@ -108,7 +117,18 @@ export async function skipPoisonEvent(db, name, instanceId = SHARED_INSTANCE_SEN
|
|
|
108
117
|
throw new Error(`Consumer "${name}" (instance_id="${instanceId}") vanished — retry.`);
|
|
109
118
|
return { ...normalizeConsumerState(unchanged), skippedEventId: null };
|
|
110
119
|
}
|
|
111
|
-
|
|
120
|
+
// Delivery stops at the poison event, so ids between the cursor and it were
|
|
121
|
+
// invisible during that pass (in-flight commits). Jumping the cursor over them
|
|
122
|
+
// would lose them; keep them as a pending gap like the dispatcher's own gaps.
|
|
123
|
+
const skippedWindowStart = before.lastProcessedEventId + 1n;
|
|
124
|
+
const gapToAdd = poisonId > skippedWindowStart
|
|
125
|
+
? {
|
|
126
|
+
from: skippedWindowStart.toString(),
|
|
127
|
+
to: (poisonId - 1n).toString(),
|
|
128
|
+
xmax: await selectSnapshotXmax(tx),
|
|
129
|
+
}
|
|
130
|
+
: null;
|
|
131
|
+
const raw = await advanceConsumerPastEventReturning(tx, name, instanceId, poisonId, gapToAdd);
|
|
112
132
|
const updated = raw && coerceRow(raw, extractTableInfo(eventConsumerStateTable));
|
|
113
133
|
if (!updated)
|
|
114
134
|
throw new Error(`Consumer "${name}" (instance_id="${instanceId}") vanished mid-skip — retry.`);
|
|
@@ -13,6 +13,7 @@ export type ConsumerStateRowShape = {
|
|
|
13
13
|
readonly attempts: number;
|
|
14
14
|
readonly rearmCount: number;
|
|
15
15
|
readonly pendingGaps: readonly PendingGapEntry[];
|
|
16
|
+
readonly lastFailedEventId: bigint | null;
|
|
16
17
|
readonly lastError: string | null;
|
|
17
18
|
readonly updatedAt: Temporal.Instant;
|
|
18
19
|
};
|
|
@@ -58,6 +59,7 @@ export type DeliveryOutcome = {
|
|
|
58
59
|
readonly deadLettered: boolean;
|
|
59
60
|
readonly processed: number;
|
|
60
61
|
readonly failed: number;
|
|
62
|
+
readonly failedEventId: bigint | null;
|
|
61
63
|
readonly resolvedPendingIds: readonly bigint[];
|
|
62
64
|
};
|
|
63
65
|
export declare function deliverEvents(consumer: EventConsumer, events: ReadonlyArray<StoredEventRow>, context: AppContext, maxAttempts: number, state: ConsumerStateRow): Promise<DeliveryOutcome>;
|
|
@@ -171,6 +171,7 @@ export async function deliverEvents(consumer, events, context, maxAttempts, stat
|
|
|
171
171
|
const effectiveMaxAttempts = consumer.errorPolicy?.maxAttempts ?? maxAttempts;
|
|
172
172
|
let processed = 0;
|
|
173
173
|
let failed = 0;
|
|
174
|
+
let failedEventId = null;
|
|
174
175
|
const resolvedPendingIds = [];
|
|
175
176
|
// A pending row sits below startCursor: it resolves its gap but never moves
|
|
176
177
|
// the cursor backward. ORDER BY id walks all pending rows first.
|
|
@@ -188,7 +189,16 @@ export async function deliverEvents(consumer, events, context, maxAttempts, stat
|
|
|
188
189
|
for (const row of events)
|
|
189
190
|
resolve(row.id);
|
|
190
191
|
processed = events.length;
|
|
191
|
-
return {
|
|
192
|
+
return {
|
|
193
|
+
cursor,
|
|
194
|
+
attempts,
|
|
195
|
+
lastError,
|
|
196
|
+
deadLettered,
|
|
197
|
+
processed,
|
|
198
|
+
failed,
|
|
199
|
+
failedEventId,
|
|
200
|
+
resolvedPendingIds,
|
|
201
|
+
};
|
|
192
202
|
}
|
|
193
203
|
// fall through to the per-event loop below; the batch failure itself
|
|
194
204
|
// does NOT bump attempts — only a per-event failure does.
|
|
@@ -209,12 +219,22 @@ export async function deliverEvents(consumer, events, context, maxAttempts, stat
|
|
|
209
219
|
}
|
|
210
220
|
attempts += 1;
|
|
211
221
|
lastError = errMessage;
|
|
222
|
+
failedEventId = row.id;
|
|
212
223
|
if (attempts >= effectiveMaxAttempts)
|
|
213
224
|
deadLettered = true;
|
|
214
225
|
break;
|
|
215
226
|
}
|
|
216
227
|
}
|
|
217
|
-
return {
|
|
228
|
+
return {
|
|
229
|
+
cursor,
|
|
230
|
+
attempts,
|
|
231
|
+
lastError,
|
|
232
|
+
deadLettered,
|
|
233
|
+
processed,
|
|
234
|
+
failed,
|
|
235
|
+
failedEventId,
|
|
236
|
+
resolvedPendingIds,
|
|
237
|
+
};
|
|
218
238
|
}
|
|
219
239
|
// Shared requestContext scope for one apply — event-derived fields
|
|
220
240
|
// (correlationId, causationId, writeOrigin) come from `causationSource`;
|
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.js";
|
|
10
10
|
export { isOpenToAllGranted, isUiAccessGranted } from "../engine/types/handlers.js";
|
|
11
11
|
export type { IconKey, NavDefinition, NavIconKey } from "../engine/types/nav.js";
|
|
12
|
-
export type { ActionFormRedirect, ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartKind, DashboardChartPanel, DashboardChartTone, DashboardCustomPanel, DashboardFeedPanel, DashboardFilterDefinition, DashboardListPanel, DashboardPanelDefinition, DashboardPanelEmptyState, DashboardPanelQueryOptions, DashboardPanelSpan, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScopeDefinition, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardTimeRangeDefinition, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditScreenDefinition, EntityListScreenDefinition, FieldCondition, FieldIconKey, FieldRenderer, FormWidth, ListBadgeTone, ListColumnDisplay, ListColumnSpec, ListFacetSpec, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RelatedListToolbarAction, RowAction, RowActionDrawer, RowActionNavigate, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, } from "../engine/types/screen.js";
|
|
12
|
+
export type { ActionFormRedirect, ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartKind, DashboardChartPanel, DashboardChartTone, DashboardCustomPanel, DashboardFeedPanel, DashboardFilterDefinition, DashboardListPanel, DashboardPanelDefinition, DashboardPanelEmptyState, DashboardPanelQueryOptions, DashboardPanelSpan, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScopeDefinition, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardTimeRangeDefinition, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, FieldCondition, FieldIconKey, FieldRenderer, FormWidth, ListBadgeTone, ListColumnDisplay, ListColumnSpec, ListFacetSpec, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSubtitlePart, RelatedListToolbarAction, RowAction, RowActionDrawer, RowActionNavigate, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, } from "../engine/types/screen.js";
|
|
13
13
|
export { metricField } from "../engine/types/screen.js";
|
|
14
14
|
export type { TargetRef } from "../engine/types/target-ref.js";
|
|
15
15
|
export type { TreeAction, TreeNode, TreeNodeState } from "../engine/types/tree-node.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.332.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>",
|
|
@@ -206,8 +206,8 @@
|
|
|
206
206
|
"./package.json": "./package.json"
|
|
207
207
|
},
|
|
208
208
|
"dependencies": {
|
|
209
|
-
"@cosmicdrift/kumiko-http": "0.
|
|
210
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
209
|
+
"@cosmicdrift/kumiko-http": "0.332.0",
|
|
210
|
+
"@cosmicdrift/kumiko-types": "0.332.0",
|
|
211
211
|
"bullmq": "^5.76.7",
|
|
212
212
|
"bun-types": "^1.3.13",
|
|
213
213
|
"hono": "^4.13.1",
|
|
@@ -223,7 +223,7 @@
|
|
|
223
223
|
"zod": "^4.4.3"
|
|
224
224
|
},
|
|
225
225
|
"devDependencies": {
|
|
226
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.
|
|
226
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.332.0",
|
|
227
227
|
"bun-types": "^1.3.13",
|
|
228
228
|
"pino-pretty": "^13.1.3"
|
|
229
229
|
},
|