@cosmicdrift/kumiko-framework 0.354.0 → 0.355.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/entity-field-encryption.d.ts +10 -1
- package/dist/db/entity-field-encryption.js +31 -2
- package/dist/db/event-store-executor-context.d.ts +8 -2
- package/dist/db/event-store-executor-context.js +41 -9
- package/dist/db/event-store-executor-read.js +22 -11
- package/dist/db/event-store-executor-write.js +95 -56
- package/dist/db/queries/event-store.d.ts +1 -0
- package/dist/db/queries/event-store.js +11 -0
- package/dist/engine/build-app-schema.js +32 -5
- package/dist/engine/cross-tenant.d.ts +2 -1
- package/dist/engine/cross-tenant.js +18 -14
- package/dist/engine/entity-handlers.js +17 -16
- package/dist/engine/factories.d.ts +2 -0
- package/dist/engine/feature-ast/extractors/handlers.js +1 -0
- package/dist/engine/feature-entity-handlers.js +1 -0
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/index.js +1 -1
- package/dist/engine/registry-validate.d.ts +7 -1
- package/dist/engine/registry-validate.js +82 -2
- package/dist/engine/registry.js +2 -1
- package/dist/engine/schema-builder.js +35 -2
- package/dist/engine/types/index.d.ts +1 -1
- package/dist/errors/reasons.d.ts +1 -0
- package/dist/errors/reasons.js +2 -0
- package/dist/event-store/event-store.d.ts +1 -0
- package/dist/event-store/event-store.js +4 -1
- package/dist/event-store/index.d.ts +1 -1
- package/dist/event-store/index.js +1 -1
- package/dist/event-store/snapshot.d.ts +1 -0
- package/dist/event-store/snapshot.js +12 -3
- package/dist/jobs/job-runner.js +32 -17
- package/dist/observability/escape-hatch-report.d.ts +5 -2
- package/dist/observability/escape-hatch-report.js +52 -22
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +1 -1
- package/dist/pipeline/dispatch-query.js +4 -0
- package/dist/pipeline/dispatch-shared.js +33 -6
- package/dist/pipeline/dispatch-stream.js +4 -0
- package/dist/pipeline/dispatcher-utils.d.ts +4 -0
- package/dist/pipeline/dispatcher-utils.js +10 -0
- package/dist/pipeline/dispatcher.js +7 -7
- package/dist/pipeline/index.d.ts +1 -1
- package/dist/pipeline/index.js +1 -1
- package/dist/pipeline/pre-handler-gates.js +4 -0
- package/dist/ui-types/index.d.ts +1 -1
- package/package.json +4 -4
- package/src/changes.json +41 -0
|
@@ -9,10 +9,11 @@ import { AccessDeniedError, FeatureDisabledError, InternalError, memberResolutio
|
|
|
9
9
|
import { archiveStream as archiveStreamHelper, isStreamArchived, restoreStream as restoreStreamHelper, } from "../event-store/archive.js";
|
|
10
10
|
import { IdempotentAppendConflictError as EventStoreIdempotentAppendConflictError, VersionConflictError as EventStoreVersionConflictError, } from "../event-store/errors.js";
|
|
11
11
|
import { getStreamVersion, loadAggregate, loadAggregateAsOf, } from "../event-store/event-store.js";
|
|
12
|
-
import { loadAggregateWithSnapshot, saveSnapshot, } from "../event-store/snapshot.js";
|
|
12
|
+
import { effectiveSnapshotGeneration, loadAggregateWithSnapshot, saveSnapshot, } from "../event-store/snapshot.js";
|
|
13
13
|
import { upcastStoredEvent, upcastStoredEvents } from "../event-store/upcaster.js";
|
|
14
14
|
import { createFileContext } from "../files/file-handle.js";
|
|
15
15
|
import { DEFAULT_LOCALE, isValidLocaleTag } from "../i18n/request-locale.js";
|
|
16
|
+
import { createFallbackLogger } from "../logging/utils.js";
|
|
16
17
|
import { createEscapeHatchReporter, createMetricsHandle, createNoopMetricsHandle, createSafeMetricsHandle, emitDispatcherError, emitDispatcherHandler, getFallbackTracer, observabilityContext, } from "../observability/index.js";
|
|
17
18
|
import { buildBucketKey, buildPayloadBucketKey, normalizePayloadBucketValue, } from "../rate-limit/index.js";
|
|
18
19
|
import { createTzContext, isValidIanaTimeZone } from "../time/index.js";
|
|
@@ -23,7 +24,7 @@ import { resolveAuthClaims as runAuthClaimsResolver } from "./auth-claims-resolv
|
|
|
23
24
|
import { runBatch, unwrapSingle } from "./dispatch-batch.js";
|
|
24
25
|
import { executeQuery } from "./dispatch-query.js";
|
|
25
26
|
import { executeWrite } from "./dispatch-write.js";
|
|
26
|
-
import { dispatcherSpanAttributes, isFailedWriteResult, } from "./dispatcher-utils.js";
|
|
27
|
+
import { dispatcherSpanAttributes, isFailedWriteResult, NestedWriteRollback, } from "./dispatcher-utils.js";
|
|
27
28
|
import { createMemberReaderFn } from "./member-reader.js";
|
|
28
29
|
import { createGatedIdentitySwitch, createGatedMemberReader, createGatedProjectionReader, isSystemIdentity, systemIdentitySwitchDenied, } from "./system-identity-switch.js";
|
|
29
30
|
import { buildPersonalDataGate, rootWriteOrigin } from "./write-origin.js";
|
|
@@ -220,7 +221,7 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
|
|
|
220
221
|
tenantId: user.tenantId,
|
|
221
222
|
actor: user.id,
|
|
222
223
|
sink: context._escapeHatchAuditSink,
|
|
223
|
-
log: context.log,
|
|
224
|
+
log: context.log ?? createFallbackLogger("dispatcher"),
|
|
224
225
|
window: ctx.escapeHatchReportWindow,
|
|
225
226
|
});
|
|
226
227
|
const identitySwitchGrantReason = identitySwitchEscapeHatch?.reason ??
|
|
@@ -339,13 +340,38 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
|
|
|
339
340
|
// Cross-feature bridge: ctx.query/write share the current tx + afterCommitHooks sink.
|
|
340
341
|
// queryAs/writeAs to anyone but the caller itself needs r.systemScope() or { escapeHatch } (system-identity-switch.ts).
|
|
341
342
|
const bridgeSink = afterCommitHooks ?? [];
|
|
343
|
+
// A failed inner write has already appended its event and projections when a later
|
|
344
|
+
// step (e.g. an inTransaction hook) fails, and the outer handler may swallow the
|
|
345
|
+
// failure result. The savepoint makes the failure undo exactly the inner write; the
|
|
346
|
+
// inner afterCommit hooks only reach the outer sink if it succeeded.
|
|
347
|
+
const executeNestedWriteInSavepoint = async (asUser, targetType, payload) => {
|
|
348
|
+
if (!tx)
|
|
349
|
+
return executeWrite(ctx, targetType, payload, asUser, origin, tx, bridgeSink);
|
|
350
|
+
const innerAfterCommitHooks = [];
|
|
351
|
+
try {
|
|
352
|
+
const result = await runInSavepoint(tx, async (savepointTx) => {
|
|
353
|
+
const innerResult = await executeWrite(ctx, targetType, payload, asUser, origin, savepointTx, // @cast-boundary driver savepoint handle is a transaction handle
|
|
354
|
+
innerAfterCommitHooks);
|
|
355
|
+
if (isFailedWriteResult(innerResult))
|
|
356
|
+
throw new NestedWriteRollback(innerResult);
|
|
357
|
+
return innerResult;
|
|
358
|
+
});
|
|
359
|
+
bridgeSink.push(...innerAfterCommitHooks);
|
|
360
|
+
return result;
|
|
361
|
+
}
|
|
362
|
+
catch (e) {
|
|
363
|
+
if (e instanceof NestedWriteRollback)
|
|
364
|
+
return e.failure;
|
|
365
|
+
throw e;
|
|
366
|
+
}
|
|
367
|
+
};
|
|
342
368
|
// Post-commit hooks have no transaction left, so a bare executeWrite would
|
|
343
369
|
// auto-commit event append, projections and inTransaction hooks separately
|
|
344
370
|
// and its afterCommit hooks would land in an already-flushed sink. A
|
|
345
371
|
// batch-of-one gives the write its own transaction and its own flush.
|
|
346
372
|
const writeAsUser = async (asUser, targetType, payload) => isPostCommit
|
|
347
373
|
? unwrapSingle(await runBatch(ctx, [{ type: targetType, payload }], asUser, undefined, origin))
|
|
348
|
-
:
|
|
374
|
+
: executeNestedWriteInSavepoint(asUser, targetType, payload);
|
|
349
375
|
const scheduleAfterCommit = (hook) => {
|
|
350
376
|
bridgeSink.push(hook);
|
|
351
377
|
};
|
|
@@ -560,7 +586,7 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
|
|
|
560
586
|
aggregateType: snapshotArgs.aggregateType,
|
|
561
587
|
version: snapshotArgs.version,
|
|
562
588
|
state: snapshotArgs.state,
|
|
563
|
-
snapshotVersion: snapshotArgs.snapshotVersion,
|
|
589
|
+
snapshotVersion: effectiveSnapshotGeneration(snapshotArgs.snapshotVersion ?? 1, registry.getEntity(snapshotArgs.aggregateType)?.eventVersion),
|
|
564
590
|
});
|
|
565
591
|
},
|
|
566
592
|
loadAggregateWithSnapshot: async (aggregateId, reducer, initial, loadOptions) => {
|
|
@@ -579,6 +605,7 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
|
|
|
579
605
|
const upcastCtx = { db: dbSource, tenantId: user.tenantId };
|
|
580
606
|
return loadAggregateWithSnapshot(dbSource, aggregateId, user.tenantId, reducer, initial, {
|
|
581
607
|
...loadOptions,
|
|
608
|
+
entityEventVersionOf: (aggregateType) => registry.getEntity(aggregateType)?.eventVersion,
|
|
582
609
|
upcastEvent: (event) => upcastStoredEvent(event, upcasters, upcastCtx), // @wrapper-known semantic-alias
|
|
583
610
|
});
|
|
584
611
|
},
|
|
@@ -1057,7 +1084,7 @@ function buildAuthClaimsContext(ctx, user) {
|
|
|
1057
1084
|
tenantId: user.tenantId,
|
|
1058
1085
|
actor: user.id,
|
|
1059
1086
|
sink: context._escapeHatchAuditSink,
|
|
1060
|
-
log: context.log,
|
|
1087
|
+
log: context.log ?? createFallbackLogger("dispatcher"),
|
|
1061
1088
|
window: ctx.escapeHatchReportWindow,
|
|
1062
1089
|
}),
|
|
1063
1090
|
projectionRegistry: ctx.registry,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { accessInvalidationCredentialFor } from "../api/sse-broker.js";
|
|
2
2
|
import { scheduleTokenExpiry } from "../api/token-expiry-timer.js";
|
|
3
3
|
import { hasAccess } from "../engine/access.js";
|
|
4
|
+
import { tenantOverrideDenied } from "../engine/cross-tenant.js";
|
|
4
5
|
import { AccessDeniedError, memberResolutionReadOnlyDenied, NotFoundError, validationErrorFromZod, } from "../errors/index.js";
|
|
5
6
|
import { assertNoSecretLeak } from "../secrets/index.js";
|
|
6
7
|
import { buildHandlerContext, enforceRateLimit, ensureFeatureEnabled, isMemberResolutionPrincipal, runStreamInstrumented, } from "./dispatch-shared.js";
|
|
@@ -33,6 +34,9 @@ async function* executeStreamInner(ctx, type, payload, user, origin, options) {
|
|
|
33
34
|
if (!parsed.success) {
|
|
34
35
|
throw validationErrorFromZod(parsed.error);
|
|
35
36
|
}
|
|
37
|
+
const overrideDenied = tenantOverrideDenied(user, parsed.data);
|
|
38
|
+
if (overrideDenied)
|
|
39
|
+
throw overrideDenied;
|
|
36
40
|
// Idle (heartbeat-only) streams must also cut on access revoke or token expiry — race each
|
|
37
41
|
// pull against an ended Deferred instead of a post-chunk boolean.
|
|
38
42
|
let resolveEnded;
|
|
@@ -31,6 +31,10 @@ export declare class BatchRollback extends Error {
|
|
|
31
31
|
readonly failureError: WriteErrorInfo;
|
|
32
32
|
constructor(failedIndex: number, failureError: WriteErrorInfo);
|
|
33
33
|
}
|
|
34
|
+
export declare class NestedWriteRollback extends Error {
|
|
35
|
+
readonly failure: WriteResult;
|
|
36
|
+
constructor(failure: WriteResult);
|
|
37
|
+
}
|
|
34
38
|
export type HandlerType = string | HandlerRef;
|
|
35
39
|
export declare function resolveType(type: HandlerType): string;
|
|
36
40
|
export declare function wrapToKumiko(e: unknown): KumikoError;
|
|
@@ -127,6 +127,16 @@ export class BatchRollback extends Error {
|
|
|
127
127
|
this.name = "BatchRollback";
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
|
+
// Thrown inside a nested write's savepoint so the driver rolls the savepoint back;
|
|
131
|
+
// the caller unwraps it into the original failure result.
|
|
132
|
+
export class NestedWriteRollback extends Error {
|
|
133
|
+
failure;
|
|
134
|
+
constructor(failure) {
|
|
135
|
+
super("nested write rollback");
|
|
136
|
+
this.failure = failure;
|
|
137
|
+
this.name = "NestedWriteRollback";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
130
140
|
export function resolveType(type) {
|
|
131
141
|
return typeof type === "string" ? type : type.name;
|
|
132
142
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { runWithWriteOrigin } from "../api/request-context.js";
|
|
2
2
|
import { TENANT_MEMBERSHIPS_QUERY } from "../engine/extension-names.js";
|
|
3
3
|
import { InternalError, reraiseAsKumikoError } from "../errors/index.js";
|
|
4
|
-
import { createEscapeHatchReportWindow, getFallbackMeter, getFallbackTracer, registerStandardMetrics, } from "../observability/index.js";
|
|
4
|
+
import { createEscapeHatchReportWindow, getFallbackMeter, getFallbackTracer, registerStandardMetrics, withEscapeHatchAuditScope, } from "../observability/index.js";
|
|
5
5
|
import { CACHE_SYNC_TOPICS, isTenantConfigSyncMessage } from "../redis/cache-sync-topics.js";
|
|
6
6
|
import { INTERACTIVE_SIGN_IN_POLICY, resolveActiveMembershipFn } from "./active-membership.js";
|
|
7
7
|
import { runBatch, unwrapSingle } from "./dispatch-batch.js";
|
|
@@ -102,14 +102,14 @@ export function createDispatcher(registry, context, options = {}) {
|
|
|
102
102
|
async write(typeOrRef, payload, user, requestId) {
|
|
103
103
|
const type = resolveType(typeOrRef);
|
|
104
104
|
// Idempotency handled inside runBatch (caches BatchResult under requestId).
|
|
105
|
-
const batchResult = await runBatch(ctx, [{ type, payload }], user, requestId);
|
|
105
|
+
const batchResult = await withEscapeHatchAuditScope(() => runBatch(ctx, [{ type, payload }], user, requestId));
|
|
106
106
|
return unwrapSingle(batchResult);
|
|
107
107
|
},
|
|
108
|
-
batch: (commands, user, requestId) => runBatch(ctx, commands, user, requestId),
|
|
108
|
+
batch: (commands, user, requestId) => withEscapeHatchAuditScope(() => runBatch(ctx, commands, user, requestId)),
|
|
109
109
|
query: (typeOrRef, payload, user) => {
|
|
110
110
|
const type = resolveType(typeOrRef);
|
|
111
111
|
const origin = rootWriteOrigin(registry, type, user);
|
|
112
|
-
return runWithWriteOrigin(origin, () => executeQuery(ctx, type, payload, user, origin));
|
|
112
|
+
return withEscapeHatchAuditScope(() => runWithWriteOrigin(origin, () => executeQuery(ctx, type, payload, user, origin)));
|
|
113
113
|
},
|
|
114
114
|
stream: (typeOrRef, payload, user, options) => {
|
|
115
115
|
const type = resolveType(typeOrRef);
|
|
@@ -117,7 +117,7 @@ export function createDispatcher(registry, context, options = {}) {
|
|
|
117
117
|
},
|
|
118
118
|
async command(typeOrRef, payload, user) {
|
|
119
119
|
const type = resolveType(typeOrRef);
|
|
120
|
-
const batchResult = await runBatch(ctx, [{ type, payload }], user);
|
|
120
|
+
const batchResult = await withEscapeHatchAuditScope(() => runBatch(ctx, [{ type, payload }], user));
|
|
121
121
|
const result = unwrapSingle(batchResult);
|
|
122
122
|
if (!result.isSuccess) {
|
|
123
123
|
throw reraiseAsKumikoError(result.error);
|
|
@@ -129,12 +129,12 @@ export function createDispatcher(registry, context, options = {}) {
|
|
|
129
129
|
};
|
|
130
130
|
dispatcherInternals.set(dispatcher, {
|
|
131
131
|
writeWithOrigin: async (type, payload, user, inheritedOrigin) => {
|
|
132
|
-
const batchResult = await runBatch(ctx, [{ type, payload }], user, undefined, inheritedOrigin);
|
|
132
|
+
const batchResult = await withEscapeHatchAuditScope(() => runBatch(ctx, [{ type, payload }], user, undefined, inheritedOrigin));
|
|
133
133
|
return unwrapSingle(batchResult);
|
|
134
134
|
},
|
|
135
135
|
queryWithOrigin: (type, payload, user, inheritedOrigin) => {
|
|
136
136
|
const origin = effectiveWriteOrigin(rootWriteOrigin(registry, type, user), inheritedOrigin);
|
|
137
|
-
return runWithWriteOrigin(origin, () => executeQuery(ctx, type, payload, user, origin));
|
|
137
|
+
return withEscapeHatchAuditScope(() => runWithWriteOrigin(origin, () => executeQuery(ctx, type, payload, user, origin)));
|
|
138
138
|
},
|
|
139
139
|
});
|
|
140
140
|
return dispatcher;
|
package/dist/pipeline/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { EscapeHatchReportWindow } from "../observability/escape-hatch-report.js";
|
|
2
|
-
export { createEscapeHatchReporter, createEscapeHatchReportWindow, ESCAPE_HATCH_USED_SIGNAL, fallbackEscapeHatchReporter, reportEscapeHatchUse, UNATTRIBUTED_ACTOR, } from "../observability/escape-hatch-report.js";
|
|
2
|
+
export { createEscapeHatchReporter, createEscapeHatchReportWindow, ESCAPE_HATCH_USED_SIGNAL, fallbackEscapeHatchReporter, flushEscapeHatchAudits, reportEscapeHatchUse, UNATTRIBUTED_ACTOR, withEscapeHatchAuditScope, } from "../observability/escape-hatch-report.js";
|
|
3
3
|
export type { AppendDomainEventCoreDeps } from "./append-event-core.js";
|
|
4
4
|
export { appendDomainEventCore } from "./append-event-core.js";
|
|
5
5
|
export type { ResolveAuthClaimsArgs } from "./auth-claims-resolver.js";
|
package/dist/pipeline/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createEscapeHatchReporter, createEscapeHatchReportWindow, ESCAPE_HATCH_USED_SIGNAL, fallbackEscapeHatchReporter, reportEscapeHatchUse, UNATTRIBUTED_ACTOR, } from "../observability/escape-hatch-report.js";
|
|
1
|
+
export { createEscapeHatchReporter, createEscapeHatchReportWindow, ESCAPE_HATCH_USED_SIGNAL, fallbackEscapeHatchReporter, flushEscapeHatchAudits, reportEscapeHatchUse, UNATTRIBUTED_ACTOR, withEscapeHatchAuditScope, } from "../observability/escape-hatch-report.js";
|
|
2
2
|
export { appendDomainEventCore } from "./append-event-core.js";
|
|
3
3
|
export { resolveAuthClaims } from "./auth-claims-resolver.js";
|
|
4
4
|
export { createCascadeDeleteHook } from "./cascade-handler.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hasAccess } from "../engine/access.js";
|
|
2
|
+
import { tenantOverrideDenied } from "../engine/cross-tenant.js";
|
|
2
3
|
import { checkWriteFieldRoles } from "../engine/field-access.js";
|
|
3
4
|
import { runValidation } from "../engine/validation.js";
|
|
4
5
|
import { AccessDeniedError, FrameworkReasons, isKumikoError, ValidationError, validationErrorFromZod, writeFailure, } from "../errors/index.js";
|
|
@@ -81,6 +82,9 @@ async function rejectBeforeParse(ctx, handler, type, user, mode) {
|
|
|
81
82
|
return undefined;
|
|
82
83
|
}
|
|
83
84
|
async function rejectAfterParse(ctx, handler, type, user, payload, mode) {
|
|
85
|
+
const overrideDenied = tenantOverrideDenied(user, payload);
|
|
86
|
+
if (overrideDenied)
|
|
87
|
+
return writeFailure(overrideDenied);
|
|
84
88
|
if (mode === "charge") {
|
|
85
89
|
const rateLimited = await rejectPayloadRateLimited(ctx, handler, type, user, payload);
|
|
86
90
|
if (rateLimited)
|
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 { 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, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditScreenDefinition, EntityListExpandableRow, EntityListFacetConfig, EntityListFacetExtraOption, EntityListScreenDefinition, FieldCondition, FieldIconKey, FieldRenderer, FormWidth, ListBadgeTone, ListColumnDisplay, ListColumnSpec, 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.js";
|
|
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, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditScreenDefinition, EntityListExpandableRow, EntityListFacetConfig, EntityListFacetExtraOption, EntityListScreenDefinition, EntityWriteExcludedFields, FieldCondition, FieldIconKey, FieldRenderer, FormWidth, ListBadgeTone, ListColumnDisplay, ListColumnSpec, 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.js";
|
|
13
13
|
export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } 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.355.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>",
|
|
@@ -210,8 +210,8 @@
|
|
|
210
210
|
"./package.json": "./package.json"
|
|
211
211
|
},
|
|
212
212
|
"dependencies": {
|
|
213
|
-
"@cosmicdrift/kumiko-http": "0.
|
|
214
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
213
|
+
"@cosmicdrift/kumiko-http": "0.355.0",
|
|
214
|
+
"@cosmicdrift/kumiko-types": "0.355.0",
|
|
215
215
|
"bullmq": "^5.76.7",
|
|
216
216
|
"bun-types": "^1.3.13",
|
|
217
217
|
"hono": "^4.13.1",
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
"zod": "^4.4.3"
|
|
228
228
|
},
|
|
229
229
|
"devDependencies": {
|
|
230
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.
|
|
230
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.355.0",
|
|
231
231
|
"bun-types": "^1.3.13",
|
|
232
232
|
"pino-pretty": "^13.1.3"
|
|
233
233
|
},
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,45 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.355.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Entity events are versionable: eventVersion + eventMigrations on r.entity",
|
|
6
|
+
"detail": "Entities can declare eventVersion and row-shaped eventMigrations; lifecycle events are stamped with the version and upcast on rebuild and aggregate load. A version bump invalidates entity snapshots."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.355.0",
|
|
10
|
+
"type": "improvement",
|
|
11
|
+
"title": "excludeFields per verb reaches the renderer and the agent manifest",
|
|
12
|
+
"detail": "WriteHandlerDef carries excludedFields; entityEdit screens get writeExcludedFields per verb. The edit form shows them read-only and keeps them out of the payload, create hides them, and the agent manifest lists them per handler."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.355.0",
|
|
16
|
+
"type": "breaking",
|
|
17
|
+
"title": "Identity and tenant switches are explicitly gated; escape-hatch audit is durable",
|
|
18
|
+
"migration": "Job ctx.writeAs now needs r.systemScope() on the feature or escapeHatch: { reason } on the job; use ctx.write when the job acts as its own system user. A payload tenantIdOverride is refused by the dispatcher for non-SystemAdmin callers (handlers no longer call crossTenantOverrideDenied, which is removed; use mayOverrideTenant for other cross-tenant fields). Entity convention handlers with a cross-tenant escapeHatch now keep the operator as actor in the row stream (executor option streamTenantId, system-mode db only). createEscapeHatchReporter requires log; pass the runtime logger and share one report window per runtime. EscapeHatchUseEvent and the audit sink payload gain an optional caller. Call flushEscapeHatchAudits() before closing the database on shutdown."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"version": "0.355.0",
|
|
22
|
+
"type": "fix",
|
|
23
|
+
"title": "A failed nested ctx.write is rolled back to a savepoint instead of leaking its event and projection into the outer commit",
|
|
24
|
+
"detail": "A nested `ctx.write` / `ctx.writeAs` (and workflow `call-feature`) now runs in a savepoint of the outer transaction: when the inner write fails after its event and projection were written, for example because an in-transaction hook throws, the inner change is rolled back even if the outer handler swallows the failure result and commits, and a SQL error in the inner write no longer poisons the outer transaction. The inner write's afterCommit hooks only run when it succeeded."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"version": "0.355.0",
|
|
28
|
+
"type": "fix",
|
|
29
|
+
"title": "Unknown record ids show a localized empty state; an undecryptable encrypted field reads null instead of failing the whole list",
|
|
30
|
+
"detail": "Edit and detail screens show a localized empty state with a way back to the list for an unknown or deleted record id instead of a hard-coded English banner, and an entity list or detail no longer fails with a 500 when one row's encrypted field cannot be decrypted: that field reads null, the rest of the row and list are served, and the failure is logged at error level with entity, row id and field"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"version": "0.354.1",
|
|
34
|
+
"type": "fix",
|
|
35
|
+
"title": "Executor list returns the stream version, so updates built on list rows no longer fail with version_conflict after ctx.appendEvent"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"version": "0.354.1",
|
|
39
|
+
"type": "fix",
|
|
40
|
+
"title": "Update schema accepts null on optional fields, so a preSave hook or client can clear a stored optional value",
|
|
41
|
+
"detail": "Update schema accepts null on optional fields, so a preSave hook or client can clear a stored optional value without presave_hook_invalid_output or a validation error"
|
|
42
|
+
},
|
|
2
43
|
{
|
|
3
44
|
"version": "0.354.0",
|
|
4
45
|
"type": "fix",
|