@cosmicdrift/kumiko-framework 0.355.0 → 0.357.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/event-store-executor-context.d.ts +4 -1
- package/dist/db/event-store-executor-context.js +11 -6
- package/dist/db/event-store-executor-write.js +14 -7
- package/dist/engine/boot-validator/card-meta-overflow.d.ts +10 -0
- package/dist/engine/boot-validator/card-meta-overflow.js +95 -0
- package/dist/engine/boot-validator/entity-handler.js +3 -2
- package/dist/engine/boot-validator/index.d.ts +1 -0
- package/dist/engine/boot-validator/index.js +4 -1
- package/dist/engine/boot-validator/query-refs.js +3 -0
- package/dist/engine/boot-validator/screens.js +5 -0
- package/dist/engine/boot-validator.d.ts +1 -1
- package/dist/engine/boot-validator.js +1 -1
- package/dist/engine/index.d.ts +2 -2
- package/dist/engine/index.js +1 -1
- package/dist/engine/types/index.d.ts +2 -2
- package/dist/files/file-ref-entity.d.ts +6 -0
- package/dist/files/file-ref-table.d.ts +6 -0
- package/dist/i18n/required-surface-keys.js +1 -0
- package/dist/jobs/job-runner.js +1 -2
- package/dist/pipeline/dispatch-shared.js +29 -4
- package/dist/pipeline/system-hooks.js +7 -3
- package/dist/testing/shared-entities.d.ts +10 -0
- package/dist/ui-types/card-column-roles.d.ts +14 -0
- package/dist/ui-types/card-column-roles.js +9 -0
- package/dist/ui-types/index.d.ts +3 -1
- package/dist/ui-types/index.js +1 -0
- package/package.json +4 -4
- package/src/changes.json +23 -0
|
@@ -25,10 +25,13 @@ export type ExecutorContext = {
|
|
|
25
25
|
readonly entityCache?: EntityCache;
|
|
26
26
|
readonly searchAdapter?: SearchAdapter;
|
|
27
27
|
readonly softDelete: boolean;
|
|
28
|
+
readonly streamTenantCreateFailure: (db: TenantDb, payload: Record<string, unknown>, requestedStreamTenantId: TenantId | undefined) => WriteFailure | undefined;
|
|
28
29
|
readonly streamTenantFor: (user: SessionUser, requestedStreamTenantId?: TenantId) => TenantId;
|
|
29
30
|
readonly streamTenantOverrideFailure: (db: TenantDb, row: Record<string, unknown>, requestedStreamTenantId: TenantId | undefined) => WriteFailure | undefined;
|
|
30
31
|
readonly idFilter: (id: EntityId) => WhereObject;
|
|
31
|
-
readonly loadById: (id: EntityId, db: TenantDb
|
|
32
|
+
readonly loadById: (id: EntityId, db: TenantDb, options?: {
|
|
33
|
+
readonly includeDeleted?: boolean;
|
|
34
|
+
}) => Promise<Record<string, unknown> | null>;
|
|
32
35
|
readonly assertStreamWritable: (db: TenantDb, id: EntityId, tenantId: TenantId) => Promise<void>;
|
|
33
36
|
readonly loadWithOwnership: (db: TenantDb, idWhere: WhereObject, ownership: {
|
|
34
37
|
kind: "pass";
|
|
@@ -118,14 +118,15 @@ export function buildExecutorContext(table, entity, options) {
|
|
|
118
118
|
// there. Everything else stays on the caller's tenant (byte-identical to the
|
|
119
119
|
// old hardcoded user.tenantId). Single source of truth for the stream key.
|
|
120
120
|
const streamTenantFor = (user, requestedStreamTenantId) => entity.systemStream ? SYSTEM_TENANT_ID : (requestedStreamTenantId ?? user.tenantId);
|
|
121
|
-
// Only a system-mode db (r.systemScope() / acknowledged cross-tenant)
|
|
122
|
-
//
|
|
121
|
+
// Only a system-mode db (r.systemScope() / acknowledged cross-tenant) or a tenant-mode db bound
|
|
122
|
+
// to exactly that stream tenant (ctx.forTenant) may address another tenant's stream, and only
|
|
123
|
+
// the tenant of the row it just loaded.
|
|
123
124
|
const streamTenantOverrideFailure = (db, row, requestedStreamTenantId) => {
|
|
124
125
|
if (requestedStreamTenantId === undefined)
|
|
125
126
|
return undefined;
|
|
126
|
-
if (db.mode !== "system") {
|
|
127
|
+
if (db.mode !== "system" && db.tenantId !== requestedStreamTenantId) {
|
|
127
128
|
return writeFailure(new AccessDeniedError({
|
|
128
|
-
message: `${entityName}: streamTenantId requires a system-mode db`,
|
|
129
|
+
message: `${entityName}: streamTenantId requires a system-mode db or a db bound to that tenant`,
|
|
129
130
|
}));
|
|
130
131
|
}
|
|
131
132
|
if (row["tenantId"] !== requestedStreamTenantId) {
|
|
@@ -135,6 +136,8 @@ export function buildExecutorContext(table, entity, options) {
|
|
|
135
136
|
}
|
|
136
137
|
return undefined;
|
|
137
138
|
};
|
|
139
|
+
// Create has no loaded row: the payload's own tenantId (if any) must agree with the target stream.
|
|
140
|
+
const streamTenantCreateFailure = (db, payload, requestedStreamTenantId) => streamTenantOverrideFailure(db, { tenantId: payload["tenantId"] ?? requestedStreamTenantId }, requestedStreamTenantId);
|
|
138
141
|
// idType default (undefined) is now "uuid" — the ES-pivot made UUID the
|
|
139
142
|
// only valid aggregate-id type. Explicit `idType: "serial"` is the only
|
|
140
143
|
// shape that's incompatible with the event-store and still rejected.
|
|
@@ -269,8 +272,9 @@ export function buildExecutorContext(table, entity, options) {
|
|
|
269
272
|
filter["isDeleted"] = false;
|
|
270
273
|
return filter;
|
|
271
274
|
}
|
|
272
|
-
async function loadById(id, db) {
|
|
273
|
-
|
|
275
|
+
async function loadById(id, db, options) {
|
|
276
|
+
// Only the soft-delete predicate is relaxed; the tenant filter of db.fetchOne stays.
|
|
277
|
+
const row = await db.fetchOne(table, options?.includeDeleted ? { id } : idFilter(id));
|
|
274
278
|
if (!row)
|
|
275
279
|
return null;
|
|
276
280
|
return await decryptForRead(rehydrateCompoundTypes(row, entity));
|
|
@@ -416,6 +420,7 @@ export function buildExecutorContext(table, entity, options) {
|
|
|
416
420
|
softDelete,
|
|
417
421
|
streamTenantFor,
|
|
418
422
|
streamTenantOverrideFailure,
|
|
423
|
+
streamTenantCreateFailure,
|
|
419
424
|
idFilter,
|
|
420
425
|
loadById,
|
|
421
426
|
assertStreamWritable,
|
|
@@ -118,7 +118,7 @@ async function runPreSave(preSave, changes, previous, isNew, entityName, action,
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
export function createWriteVerbs(ctx) {
|
|
121
|
-
const { table, entity, entityName, entityCache, softDelete, streamTenantFor, streamTenantOverrideFailure, encryptForStorage, decryptForRead, applyDefaults, stripSensitive, loadById, assertStreamWritable, loadExpectSnapshot, } = ctx;
|
|
121
|
+
const { table, entity, entityName, entityCache, softDelete, streamTenantFor, streamTenantOverrideFailure, streamTenantCreateFailure, encryptForStorage, decryptForRead, applyDefaults, stripSensitive, loadById, assertStreamWritable, loadExpectSnapshot, } = ctx;
|
|
122
122
|
const entityInstructionFieldNames = instructionFieldNames(entity);
|
|
123
123
|
const eventVersion = entity.eventVersion ?? 1;
|
|
124
124
|
let insertSchema;
|
|
@@ -163,6 +163,10 @@ export function createWriteVerbs(ctx) {
|
|
|
163
163
|
"SYSTEM_TENANT_ID or omitted.",
|
|
164
164
|
});
|
|
165
165
|
}
|
|
166
|
+
const streamTenantId = options?.streamTenantId;
|
|
167
|
+
const streamTenantMismatch = streamTenantCreateFailure(db, payload, streamTenantId);
|
|
168
|
+
if (streamTenantMismatch)
|
|
169
|
+
return streamTenantMismatch;
|
|
166
170
|
const runner = tenantDbRunner(db);
|
|
167
171
|
// Respect an explicit id in the payload (seed pattern, SCIM import). Without
|
|
168
172
|
// one the framework mints a fresh UUIDv7 via generateId. Strip it out of the
|
|
@@ -179,7 +183,7 @@ export function createWriteVerbs(ctx) {
|
|
|
179
183
|
assertInstructionFieldWriteAllowed(entityName, "create", Object.keys(data), entityInstructionFieldNames);
|
|
180
184
|
// H.2 — entity-level write-ownership on create. No oldRow exists, so
|
|
181
185
|
// only the new row is checked. No Straddle concern for creates.
|
|
182
|
-
if (!userCanCreateFieldRow(user, entity.access?.write, data)) {
|
|
186
|
+
if (!userCanCreateFieldRow(ownershipSubject(user, streamTenantId), entity.access?.write, data)) {
|
|
183
187
|
return writeFailure(new UnprocessableError("ownership_denied", {
|
|
184
188
|
i18nKey: "errors.ownershipDenied",
|
|
185
189
|
details: { scope: "entity", entityName, action: "create", userId: user.id },
|
|
@@ -195,7 +199,7 @@ export function createWriteVerbs(ctx) {
|
|
|
195
199
|
// field is still evaluated against the full post-hook row (`data`) —
|
|
196
200
|
// an ownership rule can reference a column only a hook populates
|
|
197
201
|
// (kumiko-framework#1672).
|
|
198
|
-
const fieldDeniedCreate = checkWriteFieldOwnership(entity, applyDefaults(payloadWithoutId), user, undefined, data);
|
|
202
|
+
const fieldDeniedCreate = checkWriteFieldOwnership(entity, applyDefaults(payloadWithoutId), ownershipSubject(user, streamTenantId), undefined, data);
|
|
199
203
|
if (fieldDeniedCreate) {
|
|
200
204
|
return writeFailure(new UnprocessableError("ownership_denied", {
|
|
201
205
|
i18nKey: "errors.ownershipDenied",
|
|
@@ -219,6 +223,7 @@ export function createWriteVerbs(ctx) {
|
|
|
219
223
|
const flatCreateData = flattenCompoundTypes(data, entity);
|
|
220
224
|
const flatData = await encryptForStorage(flatCreateData, user, {
|
|
221
225
|
subjectSource: { ...flatCreateData, id: aggregateId },
|
|
226
|
+
keyTenantId: streamTenantId,
|
|
222
227
|
});
|
|
223
228
|
// 1. Append event (same TX as the projection write — both must succeed
|
|
224
229
|
// or both roll back; the dispatcher wraps both in one transaction).
|
|
@@ -246,7 +251,7 @@ export function createWriteVerbs(ctx) {
|
|
|
246
251
|
event = await runInSavepointIfSupported(runner, async (sp) => append(sp, {
|
|
247
252
|
aggregateId,
|
|
248
253
|
aggregateType: entityName,
|
|
249
|
-
tenantId: streamTenantFor(user),
|
|
254
|
+
tenantId: streamTenantFor(user, streamTenantId),
|
|
250
255
|
expectedVersion: 0,
|
|
251
256
|
type: entityEventName(entityName, "created"),
|
|
252
257
|
eventVersion,
|
|
@@ -258,7 +263,7 @@ export function createWriteVerbs(ctx) {
|
|
|
258
263
|
if (e instanceof EventStoreVersionConflict) {
|
|
259
264
|
let currentVersion = -1;
|
|
260
265
|
try {
|
|
261
|
-
currentVersion = await getStreamVersion(runner, aggregateId, streamTenantFor(user));
|
|
266
|
+
currentVersion = await getStreamVersion(runner, aggregateId, streamTenantFor(user, streamTenantId));
|
|
262
267
|
}
|
|
263
268
|
catch {
|
|
264
269
|
// Lookup failure — keep the sentinel.
|
|
@@ -308,7 +313,7 @@ export function createWriteVerbs(ctx) {
|
|
|
308
313
|
// Compound-Types in einem Pass.
|
|
309
314
|
const projection = await decryptForRead(rehydrateCompoundTypes(row, entity));
|
|
310
315
|
if (entityCache && entityName) {
|
|
311
|
-
await entityCache.del(user.tenantId, entityName, aggregateId);
|
|
316
|
+
await entityCache.del(streamTenantId ?? user.tenantId, entityName, aggregateId);
|
|
312
317
|
}
|
|
313
318
|
// The echoed event is the one projections see (and the dispatcher later
|
|
314
319
|
// skips), so build it before projecting.
|
|
@@ -338,7 +343,9 @@ export function createWriteVerbs(ctx) {
|
|
|
338
343
|
"be SYSTEM_TENANT_ID or omitted.",
|
|
339
344
|
});
|
|
340
345
|
}
|
|
341
|
-
const previous = await loadById(payload.id, db
|
|
346
|
+
const previous = await loadById(payload.id, db, {
|
|
347
|
+
includeDeleted: updateOptions?.includeDeleted === true,
|
|
348
|
+
});
|
|
342
349
|
if (!previous)
|
|
343
350
|
return writeFailure(new NotFoundError(entityName, payload.id));
|
|
344
351
|
const streamTenantId = updateOptions?.streamTenantId;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FeatureDefinition } from "../types/index.js";
|
|
2
|
+
export type CardMetaOverflow = {
|
|
3
|
+
readonly feature: string;
|
|
4
|
+
readonly screen: string;
|
|
5
|
+
readonly location?: string;
|
|
6
|
+
readonly count: number;
|
|
7
|
+
readonly fields: readonly string[];
|
|
8
|
+
};
|
|
9
|
+
export declare function collectCardMetaOverflow(features: readonly FeatureDefinition[]): readonly CardMetaOverflow[];
|
|
10
|
+
export declare function validateCardMetaOverflow(features: readonly FeatureDefinition[]): void;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { CARD_META_MAX, cardColumnRoles } from "../../ui-types/card-column-roles.js";
|
|
2
|
+
import { LIST_ROW_META_COLUMNS } from "../../ui-types/list-row-meta.js";
|
|
3
|
+
import { parseRefTarget } from "../parse-ref-target.js";
|
|
4
|
+
import { normalizeListColumn } from "../screen-helpers.js";
|
|
5
|
+
// Mirrors the type resolution of headless computeListViewModel; only the
|
|
6
|
+
// "select" outcome matters for the card status role.
|
|
7
|
+
function resolveColumnType(spec, storedType, entity) {
|
|
8
|
+
const col = normalizeListColumn(spec);
|
|
9
|
+
if (col.refEntity !== undefined && (storedType === undefined || storedType === "text")) {
|
|
10
|
+
return "reference";
|
|
11
|
+
}
|
|
12
|
+
if (storedType !== undefined)
|
|
13
|
+
return storedType;
|
|
14
|
+
const derived = entity?.derivedFields?.[col.field];
|
|
15
|
+
if (derived !== undefined)
|
|
16
|
+
return derived.valueType;
|
|
17
|
+
return LIST_ROW_META_COLUMNS[col.field] ?? "text";
|
|
18
|
+
}
|
|
19
|
+
function countOverflow(feature, screen, location, columns, storedTypeOf, entity) {
|
|
20
|
+
const cardColumns = columns.map((spec) => {
|
|
21
|
+
const col = normalizeListColumn(spec);
|
|
22
|
+
return {
|
|
23
|
+
field: col.field,
|
|
24
|
+
type: resolveColumnType(spec, storedTypeOf(col.field), entity),
|
|
25
|
+
renderer: col.renderer,
|
|
26
|
+
hideOnNarrow: col.hideOnNarrow,
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
const { meta } = cardColumnRoles(cardColumns);
|
|
30
|
+
if (meta.length <= CARD_META_MAX)
|
|
31
|
+
return undefined;
|
|
32
|
+
return {
|
|
33
|
+
feature: feature.name,
|
|
34
|
+
screen,
|
|
35
|
+
...(location !== undefined && { location }),
|
|
36
|
+
count: meta.length,
|
|
37
|
+
fields: meta.map((col) => col.field),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function relatedListOverflow(features, feature, screen, location, section) {
|
|
41
|
+
let source;
|
|
42
|
+
if (section.entity !== undefined) {
|
|
43
|
+
const target = parseRefTarget(section.entity, feature.name);
|
|
44
|
+
source = features.find((f) => f.name === target.featureName)?.entities?.[target.entityName];
|
|
45
|
+
}
|
|
46
|
+
const synthesizedTypes = new Map(section.columns.map((spec) => {
|
|
47
|
+
const col = normalizeListColumn(spec);
|
|
48
|
+
return [col.field, col.valueType ?? "text"];
|
|
49
|
+
}));
|
|
50
|
+
return countOverflow(feature, screen, location, section.columns, (field) => source?.fields[field]?.type ?? synthesizedTypes.get(field), source);
|
|
51
|
+
}
|
|
52
|
+
// Every declaration whose columns the narrow layout draws as a DataTable card.
|
|
53
|
+
// Dashboard list panels have their own table and are not covered.
|
|
54
|
+
export function collectCardMetaOverflow(features) {
|
|
55
|
+
const found = [];
|
|
56
|
+
const add = (overflow) => {
|
|
57
|
+
if (overflow !== undefined)
|
|
58
|
+
found.push(overflow);
|
|
59
|
+
};
|
|
60
|
+
for (const feature of features) {
|
|
61
|
+
for (const screen of Object.values(feature.screens)) {
|
|
62
|
+
if (screen.type === "entityList") {
|
|
63
|
+
const entity = feature.entities?.[screen.entity];
|
|
64
|
+
add(countOverflow(feature, screen.id, undefined, screen.columns, (field) => entity?.fields[field]?.type, entity));
|
|
65
|
+
if (screen.expandableRow !== undefined) {
|
|
66
|
+
add(relatedListOverflow(features, feature, screen.id, `expandableRow "${screen.expandableRow.title}"`, screen.expandableRow));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else if (screen.type === "projectionList") {
|
|
70
|
+
// The projection shim types every column "text"; only refEntity changes it.
|
|
71
|
+
add(countOverflow(feature, screen.id, undefined, screen.columns, () => "text", undefined));
|
|
72
|
+
}
|
|
73
|
+
else if (screen.type === "projectionDetail") {
|
|
74
|
+
const isTabs = screen.layout.mode === "tabs";
|
|
75
|
+
for (const section of screen.layout.sections) {
|
|
76
|
+
if (!("kind" in section) || section.kind !== "relatedList")
|
|
77
|
+
continue;
|
|
78
|
+
const location = isTabs
|
|
79
|
+
? `tab "${section.id ?? section.title}" section "${section.title}"`
|
|
80
|
+
: `section "${section.title}"`;
|
|
81
|
+
add(relatedListOverflow(features, feature, screen.id, location, section));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return found;
|
|
87
|
+
}
|
|
88
|
+
export function validateCardMetaOverflow(features) {
|
|
89
|
+
const overflows = collectCardMetaOverflow(features);
|
|
90
|
+
// skip: nothing overflows, so there is nothing to report
|
|
91
|
+
if (overflows.length === 0)
|
|
92
|
+
return;
|
|
93
|
+
const lines = overflows.map((o) => ` - Feature "${o.feature}" screen "${o.screen}"${o.location !== undefined ? ` ${o.location}` : ""}: ${o.count} meta columns (${o.fields.join(", ")})`);
|
|
94
|
+
throw new Error(`List cards show at most ${CARD_META_MAX} meta columns (all columns except title, status select and hideOnNarrow ones); more would be cut off on narrow screens:\n${lines.join("\n")}\nReduce each to ${CARD_META_MAX} columns or mark the others hideOnNarrow: true.`);
|
|
95
|
+
}
|
|
@@ -710,11 +710,12 @@ function assertNoSelectOptionsQuery(field, path, featureName) {
|
|
|
710
710
|
export function validateEntitySelectAvailabilityQuery(feature, queryHandlers) {
|
|
711
711
|
for (const [entityName, entity] of Object.entries(feature.entities ?? {})) {
|
|
712
712
|
for (const [fieldName, field] of Object.entries(entity.fields)) {
|
|
713
|
-
if (field.type !== "select"
|
|
713
|
+
if ((field.type !== "select" && field.type !== "multiSelect") ||
|
|
714
|
+
field.optionsAvailabilityQuery === undefined)
|
|
714
715
|
continue;
|
|
715
716
|
const query = field.optionsAvailabilityQuery;
|
|
716
717
|
if (query.length === 0 || !queryHandlers.has(query)) {
|
|
717
|
-
throw new Error(`[Feature ${feature.name}] Select field "${fieldName}" on entity "${entityName}" ` +
|
|
718
|
+
throw new Error(`[Feature ${feature.name}] ${field.type === "select" ? "Select" : "MultiSelect"} field "${fieldName}" on entity "${entityName}" ` +
|
|
718
719
|
`declares optionsAvailabilityQuery "${query}" which is not a registered query-handler. ` +
|
|
719
720
|
`Check the QN spelling (expected "<feature>:query:<short>") and that the handler ` +
|
|
720
721
|
`is declared via r.queryHandler(...).`);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { EnvelopeCipher } from "../../secrets/envelope-cipher.js";
|
|
2
2
|
import type { FeatureDefinition } from "../types/index.js";
|
|
3
|
+
export { type CardMetaOverflow, collectCardMetaOverflow } from "./card-meta-overflow.js";
|
|
3
4
|
export { validateAppCustomScreenWriteQns } from "./custom-screen-write-qns.js";
|
|
4
5
|
export { collectWriteHandlerQns } from "./nav.js";
|
|
5
6
|
export { SECURITY_BASELINE_FEATURE_NAMES } from "./security-baseline.js";
|
|
@@ -7,6 +7,7 @@ import { warnOnUniqueAccessRoles } from "./access-roles.js";
|
|
|
7
7
|
import { validateActionWiring, validateFieldWiring } from "./action-wiring.js";
|
|
8
8
|
import { validateApiExposureMatching, validateExtensionUsages } from "./api-ext.js";
|
|
9
9
|
import { validateFeatureBootChecks } from "./boot-check.js";
|
|
10
|
+
import { validateCardMetaOverflow } from "./card-meta-overflow.js";
|
|
10
11
|
import { validateCircularDeps, validateConfigKeyAllowPerRequest, validateConfigKeyBacking, validateConfigKeyBounds, validateConfigKeyComputed, validateConfigKeyOptionsQuery, validateConfigKeyPiiEncrypted, validateConfigKeyRequired, validateConfigReads, warnOnToggleableDependencies, } from "./config-deps.js";
|
|
11
12
|
import { collectDetailForScreens } from "./detail-screens.js";
|
|
12
13
|
import { validateDerivedFieldCollisions, validateEmbeddedFields, validateEncryptedFields, validateEntityIndexes, validateEntitySelectAvailabilityQuery, validateEntitySelectConditionalOptions, validateEntitySelectOptionsQuery, validateExtendSchemaCollisions, validateExtensionPreSaveWiring, validateFileFields, validateHandlerAccess, validateImageVariants, validateLocatedTimestamps, validateMultiSelectFields, validateMultiStreamProjections, validateReferenceFields, validateSearchableFieldReadAccess, validateTransitions, } from "./entity-handler.js";
|
|
@@ -29,10 +30,11 @@ import { validateExtensionSelectorPanels } from "./selector-owner-panels.js";
|
|
|
29
30
|
import { validateTransferGraph } from "./transfer-graph.js";
|
|
30
31
|
import { validateSubmitPrefilledHiddenFields } from "./url-prefill-fields.js";
|
|
31
32
|
import { collectWorkspaceQns, resolveNavAllowlist, validateDefaultWorkspaceUniqueness, validateWorkspaces, } from "./workspaces.js";
|
|
32
|
-
export { validateAppCustomScreenWriteQns } from "./custom-screen-write-qns.js";
|
|
33
33
|
// Re-export: wird von run-dev-app.ts benötigt um Write-Handler-QNs
|
|
34
34
|
// an den Codegen zu übergeben. Nicht Teil von validateBoot, aber
|
|
35
35
|
// dieselbe Extraktionslogik.
|
|
36
|
+
export { collectCardMetaOverflow } from "./card-meta-overflow.js";
|
|
37
|
+
export { validateAppCustomScreenWriteQns } from "./custom-screen-write-qns.js";
|
|
36
38
|
export { collectWriteHandlerQns } from "./nav.js";
|
|
37
39
|
export { SECURITY_BASELINE_FEATURE_NAMES } from "./security-baseline.js";
|
|
38
40
|
export { MAX_TRANSFER_DEPTH } from "./transfer-graph.js";
|
|
@@ -217,6 +219,7 @@ export function validateBoot(rawFeatures, options) {
|
|
|
217
219
|
validateI18nSurfaceKeys(features);
|
|
218
220
|
validateEntityListScreens(features);
|
|
219
221
|
validateSubmitPrefilledHiddenFields(features);
|
|
222
|
+
validateCardMetaOverflow(features);
|
|
220
223
|
// Must run before validateProjectionListScreens: an unresolvable query
|
|
221
224
|
// there is silently treated as "capability absent" and surfaces as a
|
|
222
225
|
// misleading "no search parameter in its Zod schema" error instead of
|
|
@@ -45,6 +45,9 @@ function checkDashboardQueryRefs(queryHandlers, featureName, screenId, screen) {
|
|
|
45
45
|
}
|
|
46
46
|
function checkFormFieldQueryRefs(queryHandlers, featureName, screenId, screenType, fields) {
|
|
47
47
|
for (const [fieldName, field] of Object.entries(fields)) {
|
|
48
|
+
if (field.type === "multiSelect" && field.optionsAvailabilityQuery !== undefined) {
|
|
49
|
+
checkQueryRef(queryHandlers, field.optionsAvailabilityQuery, () => `[Feature ${featureName}] Screen "${screenId}" (${screenType}) multiSelect field "${fieldName}" optionsAvailabilityQuery`);
|
|
50
|
+
}
|
|
48
51
|
if (field.type !== "select")
|
|
49
52
|
continue;
|
|
50
53
|
if (field.optionsQuery !== undefined) {
|
|
@@ -550,6 +550,11 @@ function validateFormSelectOptions(featureName, screenId, context, fieldName, fd
|
|
|
550
550
|
throw new Error(`${where} has optionsQueryPayload without optionsQuery`);
|
|
551
551
|
}
|
|
552
552
|
}
|
|
553
|
+
else if (fdef.type === "multiSelect" && fdef.optionsAvailabilityQuery !== undefined) {
|
|
554
|
+
if (fdef.optionsAvailabilityQuery.length === 0) {
|
|
555
|
+
throw new Error(`[Feature ${featureName}] Screen "${screenId}" (${context}) multiSelect field "${fieldName}" has an empty optionsAvailabilityQuery`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
553
558
|
}
|
|
554
559
|
// Fail-closed currency-source gate (fw#2839), the runtime half of the
|
|
555
560
|
// narrowed `FormFieldDefinition` — an untyped JS consumer has no compiler to
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, type ValidateBootOptions, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator/index.js";
|
|
1
|
+
export { type CardMetaOverflow, collectCardMetaOverflow, collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, type ValidateBootOptions, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator/index.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator/index.js";
|
|
1
|
+
export { collectCardMetaOverflow, collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator/index.js";
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { hasAccess } from "./access.js";
|
|
|
3
3
|
export { isPrincipalStatusPlugin, isTenantLifecycleStatusPlugin, type PrincipalProfile, type PrincipalStatus, type PrincipalStatusPlugin, TENANT_TEARDOWN_STATUSES, type TenantLifecycleStatusPlugin, } from "./active-membership.js";
|
|
4
4
|
export { PII_DIRECT_NAME_HINTS, PII_USER_OWNED_NAME_HINTS, PII_USER_REFERENCE_NAME_HINTS, } from "./boot-validator/entity-handler.js";
|
|
5
5
|
export { entityHasAnonymizableSubjectField } from "./boot-validator/pii-retention.js";
|
|
6
|
-
export { collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, type ValidateBootOptions, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator.js";
|
|
6
|
+
export { type CardMetaOverflow, collectCardMetaOverflow, collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, type ValidateBootOptions, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator.js";
|
|
7
7
|
export { type BuildAppSchemaOptions, buildAppSchema } from "./build-app-schema.js";
|
|
8
8
|
export type { ConfigFeatureSchema } from "./build-config-feature-schema.js";
|
|
9
9
|
export { buildConfigFeatureSchema, SETTINGS_HUB_FEATURE, SETTINGS_HUB_WORKSPACE, } from "./build-config-feature-schema.js";
|
|
@@ -80,7 +80,7 @@ export { ANONYMOUS_ROLE, ANONYMOUS_USER_ID, createAnonymousUser, createSystemUse
|
|
|
80
80
|
export { type EffectiveFeaturesResolver, findTierResolverUsage, isTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, type TierResolverPlugin, type TrialGate, } from "./tier-resolver-extension.js";
|
|
81
81
|
export { findOverDeepTransferChain, resolveTransferAdjacency as resolveEntityTransferAdjacency, type TransferAdjacency, type TransferEdge, type TransferEdgeLink, } from "./transfer-adjacency.js";
|
|
82
82
|
export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.js";
|
|
83
|
-
export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, 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, 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, NotifyDelivery, NotifyDeliveryStatus, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, 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, 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, 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";
|
|
83
|
+
export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, 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, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EntityDefinition, EntityEditAction, EntityEditActionPlacement, 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, NotifyDelivery, NotifyDeliveryStatus, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, 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, 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, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, 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.js";
|
|
84
84
|
export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, HookPhases, HTTP_ERROR_CODE_PATTERN, isAgentVisibleScreen, isDeliveryErrorCode, isOpenToAllGranted, resolveAgentExposure, } from "./types/index.js";
|
|
85
85
|
export type { AwaitedEventType, PipelineBuildCtx, PipelineCtx, PipelineDef, StepBuilder, StepDef, StepFailureStrategy, StepInstance, StepKind, StepNamespace, StepResolver, } from "./types/step.js";
|
|
86
86
|
export { runValidation } from "./validation.js";
|
package/dist/engine/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { hasAccess } from "./access.js";
|
|
|
4
4
|
export { isPrincipalStatusPlugin, isTenantLifecycleStatusPlugin, TENANT_TEARDOWN_STATUSES, } from "./active-membership.js";
|
|
5
5
|
export { PII_DIRECT_NAME_HINTS, PII_USER_OWNED_NAME_HINTS, PII_USER_REFERENCE_NAME_HINTS, } from "./boot-validator/entity-handler.js";
|
|
6
6
|
export { entityHasAnonymizableSubjectField } from "./boot-validator/pii-retention.js";
|
|
7
|
-
export { collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator.js";
|
|
7
|
+
export { collectCardMetaOverflow, collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator.js";
|
|
8
8
|
export { buildAppSchema } from "./build-app-schema.js";
|
|
9
9
|
export { buildConfigFeatureSchema, SETTINGS_HUB_FEATURE, SETTINGS_HUB_WORKSPACE, } from "./build-config-feature-schema.js";
|
|
10
10
|
export { buildTarget } from "./build-target.js";
|
|
@@ -7,7 +7,7 @@ export type { ExtensionOptionsArgs, ExtensionOptionsFor, KumikoExtensionOptionsM
|
|
|
7
7
|
export type { BootCheckContext, BootCheckFn, FeatureDefinition, FeatureMetricDef, FeatureMetricType, FeatureRegistrar, MetricOptions, Registry, SecretKeyDefinition, SecretKeyHandle, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretOptions, StoreTableDef, StoreTableEntry, StoreTableOptions, UiHintOption, UiHints, } from "@cosmicdrift/kumiko-types/feature";
|
|
8
8
|
export type { FieldIconKey } from "@cosmicdrift/kumiko-types/field-icon";
|
|
9
9
|
export type { AnyFileFieldDef, BigIntFieldDef, BooleanFieldDef, ClientDerivedFieldDef, DateFieldDef, DecimalFieldDef, DefaultCurrency, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EmbeddedDerivedCellDef, EmbeddedFieldDef, EmbeddedSubFieldDef, EntityDefinition, EntityEventMigration, EntityIndexDef, FieldAccess, FieldDefinition, FieldsMap, FileFieldDef, FilesFieldDef, Findability, ImageFieldDef, ImagesFieldDef, JsonbFieldDef, LocatedTimestampFieldDef, LongTextFieldDef, LongTextFindability, MoneyCurrencySource, MoneyFieldDef, MultiSelectFieldDef, NumberFieldDef, OptionsQueryPayload, OptionsQueryPayloadValue, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, ReferenceFieldDef, ResolvedPiiFlags, RetentionDef, SelectFieldDef, SelectOptionTone, TextFieldDef, TimestampFieldDef, TransitionMap, TzFieldDef, } from "@cosmicdrift/kumiko-types/fields";
|
|
10
|
-
export type { AccessRule, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AggregateStreamHandle, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, DeclarativeEventMigration, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DispatchWriteRef, EntityRef, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, EventMigrationDef, EventPiiFields, EventPiiStance, EventUpcastCtx, EventUpcastFn, FetchForWritingArgs, HandlerContext, HandlerRef, JobContext, JobRunnerRef, JobTriggerEvent, MemberReader, NameOrRef, NotifyDelivery, NotifyDeliveryStatus, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, PayloadRateLimitOption, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitDeclaration, RateLimitDisabled, RateLimitOption, RateLimitPer, RoleAccessPersonalData, RoleAccessRule, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, TryAppendEventFn, TryAppendEventResult, UnsafeAppendEventFn, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "@cosmicdrift/kumiko-types/handlers";
|
|
10
|
+
export type { AccessRule, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AggregateStreamHandle, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, DeclarativeEventMigration, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DispatchWriteRef, EntityRef, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, EventMigrationDef, EventPiiFields, EventPiiStance, EventUpcastCtx, EventUpcastFn, FetchForWritingArgs, HandlerContext, HandlerRef, JobContext, JobRunnerRef, JobTriggerEvent, MemberReader, NameOrRef, NotifyDelivery, NotifyDeliveryStatus, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, PayloadRateLimitOption, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitDeclaration, RateLimitDisabled, RateLimitOption, RateLimitPer, RoleAccessPersonalData, RoleAccessRule, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, TenantWriteTarget, TryAppendEventFn, TryAppendEventResult, UnsafeAppendEventFn, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "@cosmicdrift/kumiko-types/handlers";
|
|
11
11
|
export { DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, HTTP_ERROR_CODE_PATTERN, isDeliveryErrorCode, isOpenToAllGranted, isRateLimitDisabled, isUiAccessGranted, } from "@cosmicdrift/kumiko-types/handlers";
|
|
12
12
|
export type { DeleteContext, EntityHookMap, HookMap, HookOwner, HookPhase, LifecycleHookFn, LifecycleResult, OwnedFn, PhasedHook, PostDeleteBatchHookFn, PostDeleteHookFn, PostQueryHookFn, PostSaveBatchHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, SaveContext, SearchPayloadContributorFn, ValidationError, ValidationHookFn, } from "@cosmicdrift/kumiko-types/hooks";
|
|
13
13
|
export type { HttpRouteDefinition, HttpRouteHandler, HttpRouteHandlerDeps, HttpRouteMethod, } from "@cosmicdrift/kumiko-types/http-route";
|
|
@@ -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, 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";
|
|
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, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditAction, EntityEditActionPlacement, 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, 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";
|
|
@@ -4,6 +4,7 @@ export declare const fileRefEntity: import("@cosmicdrift/kumiko-types/fields").E
|
|
|
4
4
|
readonly description?: string;
|
|
5
5
|
readonly maxLength?: number;
|
|
6
6
|
readonly minLength?: number;
|
|
7
|
+
readonly autoComplete?: string;
|
|
7
8
|
readonly required?: boolean;
|
|
8
9
|
readonly searchable?: boolean;
|
|
9
10
|
readonly sortable?: boolean;
|
|
@@ -26,6 +27,7 @@ export declare const fileRefEntity: import("@cosmicdrift/kumiko-types/fields").E
|
|
|
26
27
|
readonly description?: string;
|
|
27
28
|
readonly maxLength?: number;
|
|
28
29
|
readonly minLength?: number;
|
|
30
|
+
readonly autoComplete?: string;
|
|
29
31
|
readonly required?: boolean;
|
|
30
32
|
readonly searchable?: boolean;
|
|
31
33
|
readonly sortable?: boolean;
|
|
@@ -48,6 +50,7 @@ export declare const fileRefEntity: import("@cosmicdrift/kumiko-types/fields").E
|
|
|
48
50
|
readonly description?: string;
|
|
49
51
|
readonly maxLength?: number;
|
|
50
52
|
readonly minLength?: number;
|
|
53
|
+
readonly autoComplete?: string;
|
|
51
54
|
readonly required?: boolean;
|
|
52
55
|
readonly searchable?: boolean;
|
|
53
56
|
readonly sortable?: boolean;
|
|
@@ -82,6 +85,7 @@ export declare const fileRefEntity: import("@cosmicdrift/kumiko-types/fields").E
|
|
|
82
85
|
readonly description?: string;
|
|
83
86
|
readonly maxLength?: number;
|
|
84
87
|
readonly minLength?: number;
|
|
88
|
+
readonly autoComplete?: string;
|
|
85
89
|
readonly required?: boolean;
|
|
86
90
|
readonly searchable?: boolean;
|
|
87
91
|
readonly sortable?: boolean;
|
|
@@ -104,6 +108,7 @@ export declare const fileRefEntity: import("@cosmicdrift/kumiko-types/fields").E
|
|
|
104
108
|
readonly description?: string;
|
|
105
109
|
readonly maxLength?: number;
|
|
106
110
|
readonly minLength?: number;
|
|
111
|
+
readonly autoComplete?: string;
|
|
107
112
|
readonly required?: boolean;
|
|
108
113
|
readonly searchable?: boolean;
|
|
109
114
|
readonly sortable?: boolean;
|
|
@@ -126,6 +131,7 @@ export declare const fileRefEntity: import("@cosmicdrift/kumiko-types/fields").E
|
|
|
126
131
|
readonly description?: string;
|
|
127
132
|
readonly maxLength?: number;
|
|
128
133
|
readonly minLength?: number;
|
|
134
|
+
readonly autoComplete?: string;
|
|
129
135
|
readonly required?: boolean;
|
|
130
136
|
readonly searchable?: boolean;
|
|
131
137
|
readonly sortable?: boolean;
|
|
@@ -4,6 +4,7 @@ export declare const fileRefsTable: import("../db/table-builder.js").EntityTable
|
|
|
4
4
|
readonly description?: string;
|
|
5
5
|
readonly maxLength?: number;
|
|
6
6
|
readonly minLength?: number;
|
|
7
|
+
readonly autoComplete?: string;
|
|
7
8
|
readonly required?: boolean;
|
|
8
9
|
readonly searchable?: boolean;
|
|
9
10
|
readonly sortable?: boolean;
|
|
@@ -26,6 +27,7 @@ export declare const fileRefsTable: import("../db/table-builder.js").EntityTable
|
|
|
26
27
|
readonly description?: string;
|
|
27
28
|
readonly maxLength?: number;
|
|
28
29
|
readonly minLength?: number;
|
|
30
|
+
readonly autoComplete?: string;
|
|
29
31
|
readonly required?: boolean;
|
|
30
32
|
readonly searchable?: boolean;
|
|
31
33
|
readonly sortable?: boolean;
|
|
@@ -48,6 +50,7 @@ export declare const fileRefsTable: import("../db/table-builder.js").EntityTable
|
|
|
48
50
|
readonly description?: string;
|
|
49
51
|
readonly maxLength?: number;
|
|
50
52
|
readonly minLength?: number;
|
|
53
|
+
readonly autoComplete?: string;
|
|
51
54
|
readonly required?: boolean;
|
|
52
55
|
readonly searchable?: boolean;
|
|
53
56
|
readonly sortable?: boolean;
|
|
@@ -82,6 +85,7 @@ export declare const fileRefsTable: import("../db/table-builder.js").EntityTable
|
|
|
82
85
|
readonly description?: string;
|
|
83
86
|
readonly maxLength?: number;
|
|
84
87
|
readonly minLength?: number;
|
|
88
|
+
readonly autoComplete?: string;
|
|
85
89
|
readonly required?: boolean;
|
|
86
90
|
readonly searchable?: boolean;
|
|
87
91
|
readonly sortable?: boolean;
|
|
@@ -104,6 +108,7 @@ export declare const fileRefsTable: import("../db/table-builder.js").EntityTable
|
|
|
104
108
|
readonly description?: string;
|
|
105
109
|
readonly maxLength?: number;
|
|
106
110
|
readonly minLength?: number;
|
|
111
|
+
readonly autoComplete?: string;
|
|
107
112
|
readonly required?: boolean;
|
|
108
113
|
readonly searchable?: boolean;
|
|
109
114
|
readonly sortable?: boolean;
|
|
@@ -126,6 +131,7 @@ export declare const fileRefsTable: import("../db/table-builder.js").EntityTable
|
|
|
126
131
|
readonly description?: string;
|
|
127
132
|
readonly maxLength?: number;
|
|
128
133
|
readonly minLength?: number;
|
|
134
|
+
readonly autoComplete?: string;
|
|
129
135
|
readonly required?: boolean;
|
|
130
136
|
readonly searchable?: boolean;
|
|
131
137
|
readonly sortable?: boolean;
|
|
@@ -283,6 +283,7 @@ export function requiredKeysFromScreen(featureName, screen, options = {}) {
|
|
|
283
283
|
pushKey(out, mint.reveal.title);
|
|
284
284
|
pushKey(out, mint.reveal.warning);
|
|
285
285
|
pushKey(out, mint.reveal.confirmLabel);
|
|
286
|
+
pushKey(out, mint.reveal.acknowledge);
|
|
286
287
|
for (const revealField of mint.reveal.fields) {
|
|
287
288
|
pushKey(out, revealField.label);
|
|
288
289
|
}
|
package/dist/jobs/job-runner.js
CHANGED
|
@@ -869,7 +869,6 @@ export function createJobRunner(options) {
|
|
|
869
869
|
}
|
|
870
870
|
return dispatchWriteRef.queryAs(user, qn, payload, jobOrigin);
|
|
871
871
|
};
|
|
872
|
-
// Only writeAs is gated for jobs; queryAs stays ungated (fw#2859).
|
|
873
872
|
const gatedIdentitySwitch = createGatedIdentitySwitch(`job "${jobName}"`, jobSystemUser, isSystemJob || identitySwitchHatch !== undefined, {
|
|
874
873
|
queryAs: ungatedQueryAs,
|
|
875
874
|
writeAs: async (user, qn, payload) => {
|
|
@@ -909,7 +908,7 @@ export function createJobRunner(options) {
|
|
|
909
908
|
return dispatchWriteRef.write(jobSystemUser, qn, payload, jobOrigin);
|
|
910
909
|
},
|
|
911
910
|
writeAs: gatedIdentitySwitch.writeAs,
|
|
912
|
-
queryAs:
|
|
911
|
+
queryAs: gatedIdentitySwitch.queryAs,
|
|
913
912
|
queryAsMember: (userId, qn, payload) => {
|
|
914
913
|
if (!dispatchWriteRef) {
|
|
915
914
|
throw new Error("JobContext.queryAsMember called before dispatcher attached — call attachDispatcher() first");
|
|
@@ -3,9 +3,10 @@ import { requestContext, runWithOrigin } from "../api/request-context.js";
|
|
|
3
3
|
import { runInSavepoint, selectMany } from "../db/query.js";
|
|
4
4
|
import { createTenantDb, createUncheckedSystemDb, hasTenantColumn, runInOwnTransaction, } from "../db/tenant-db.js";
|
|
5
5
|
import { createDerivativesContext } from "../derivatives/derivatives-context.js";
|
|
6
|
+
import { mayOverrideTenant } from "../engine/cross-tenant.js";
|
|
6
7
|
import { createSystemUser } from "../engine/system-user.js";
|
|
7
8
|
import { isRateLimitDisabled, resolveAgentExposure } from "../engine/types/index.js";
|
|
8
|
-
import { AccessDeniedError, FeatureDisabledError, InternalError, memberResolutionReadOnlyDenied, RateLimitError, RateLimitUnavailableError, VersionConflictError, } from "../errors/index.js";
|
|
9
|
+
import { AccessDeniedError, FeatureDisabledError, FrameworkReasons, InternalError, memberResolutionReadOnlyDenied, RateLimitError, RateLimitUnavailableError, VersionConflictError, } from "../errors/index.js";
|
|
9
10
|
import { archiveStream as archiveStreamHelper, isStreamArchived, restoreStream as restoreStreamHelper, } from "../event-store/archive.js";
|
|
10
11
|
import { IdempotentAppendConflictError as EventStoreIdempotentAppendConflictError, VersionConflictError as EventStoreVersionConflictError, } from "../event-store/errors.js";
|
|
11
12
|
import { getStreamVersion, loadAggregate, loadAggregateAsOf, } from "../event-store/event-store.js";
|
|
@@ -178,6 +179,7 @@ export function applyMemberResolutionReadOnly(handlerContext) {
|
|
|
178
179
|
restoreStream: denyMemberResolutionWrite,
|
|
179
180
|
snapshotAggregate: denyMemberResolutionWrite,
|
|
180
181
|
queryAsMember: denyMemberResolutionWrite,
|
|
182
|
+
forTenant: undefined,
|
|
181
183
|
resolveActiveMembership: denyMemberResolutionWrite,
|
|
182
184
|
scheduleAfterCommit: () => {
|
|
183
185
|
throw memberResolutionReadOnlyDenied();
|
|
@@ -226,9 +228,11 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
|
|
|
226
228
|
});
|
|
227
229
|
const identitySwitchGrantReason = identitySwitchEscapeHatch?.reason ??
|
|
228
230
|
(isSystem ? `r.systemScope() feature "${featureName ?? "unknown"}"` : undefined);
|
|
229
|
-
const buildTenantScopedDb = (source, signal) => createTenantDb(source, user.tenantId, isSystem ? "system" : "tenant", context.tracer, context.meter, signal, {
|
|
230
|
-
|
|
231
|
-
|
|
231
|
+
const buildTenantScopedDb = (source, signal, foreignTenantId) => createTenantDb(source, foreignTenantId ?? user.tenantId, isSystem && foreignTenantId === undefined ? "system" : "tenant", context.tracer, context.meter, signal, {
|
|
232
|
+
...(foreignTenantId === undefined && {
|
|
233
|
+
globalWrites: escapeHatchFor(writeEscapeHatch, "globalWrites"),
|
|
234
|
+
unsafeRaw: escapeHatchFor(handlerEscapeHatch, "unsafeRaw"),
|
|
235
|
+
}),
|
|
232
236
|
report: reportEscapeHatch,
|
|
233
237
|
memberReadOnly: isMemberResolutionPrincipal(user),
|
|
234
238
|
personalDataGate: buildPersonalDataGate(registry, origin),
|
|
@@ -309,6 +313,26 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
|
|
|
309
313
|
});
|
|
310
314
|
}
|
|
311
315
|
: undefined;
|
|
316
|
+
// Write handlers only, and not r.systemScope() ones (they have ctx.systemDb). The gate is the
|
|
317
|
+
// dispatcher's tenantIdOverride predicate, so no caller reaches more than it could via
|
|
318
|
+
// tenantIdOverride. The foreign db carries no unsafeRaw/globalWrites grant and is tenant mode
|
|
319
|
+
// bound to the target; the executor accepts streamTenantId only on a db bound to that tenant.
|
|
320
|
+
const forTenant = !isSystem && dbSource && registry.getWriteHandler(type) !== undefined
|
|
321
|
+
? (targetTenantId) => {
|
|
322
|
+
if (targetTenantId === user.tenantId && exposedDb) {
|
|
323
|
+
return { db: exposedDb, streamTenantId: undefined };
|
|
324
|
+
}
|
|
325
|
+
if (!mayOverrideTenant(user)) {
|
|
326
|
+
throw new AccessDeniedError({
|
|
327
|
+
details: { reason: FrameworkReasons.tenantOverrideRequiresSystemAdmin },
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
db: buildTenantScopedDb(dbSource, reqCtx?.signal, targetTenantId),
|
|
332
|
+
streamTenantId: targetTenantId,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
: undefined;
|
|
312
336
|
// ctx.files resolved per-tenant through file-foundation (lazy — the
|
|
313
337
|
// provider is only resolved when a handle actually does I/O). Boot wires
|
|
314
338
|
// _fileProviderResolver when a file-provider plugin is mounted; falls back
|
|
@@ -721,6 +745,7 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
|
|
|
721
745
|
notify,
|
|
722
746
|
...(config && { config }),
|
|
723
747
|
...(configFor && { configFor }),
|
|
748
|
+
...(forTenant && { forTenant }),
|
|
724
749
|
...(files && { files }),
|
|
725
750
|
...(derivatives && { derivatives }),
|
|
726
751
|
// preSave hooks need `changes`/`previous`/`isNew`, which only exist once
|
|
@@ -37,15 +37,19 @@ import { InternalError } from "../errors/index.js";
|
|
|
37
37
|
// call per tenant instead of one round-trip per event; handler stays the
|
|
38
38
|
// per-event fallback the dispatcher falls back to when a batch throws.
|
|
39
39
|
export const SEARCH_CONSUMER_NAME = "system:consumer:search";
|
|
40
|
+
// An executor update with includeDeleted leaves the row deleted; indexing it would make it findable again.
|
|
41
|
+
function isUpdateOfSoftDeletedRow(event, verb) {
|
|
42
|
+
return (verb === "updated" && reconstructStateForSearch(event.payload, "updated")["isDeleted"] === true);
|
|
43
|
+
}
|
|
40
44
|
// Resolves what one event implies for the search index, without touching
|
|
41
45
|
// the adapter — same decision tree the old per-event handler ran inline.
|
|
42
46
|
async function resolveSearchOperation(event, ctx, registry) {
|
|
43
47
|
const entityName = event.aggregateType;
|
|
44
48
|
const verb = event.type.split(".").pop();
|
|
45
49
|
const tenantId = event.tenantId;
|
|
46
|
-
// skip: delete/forgotten
|
|
47
|
-
// makes sense for created/updated/restored
|
|
48
|
-
if (verb === "deleted" || verb === "forgotten") {
|
|
50
|
+
// skip: delete/forgotten (and an update on a still soft-deleted row) remove the
|
|
51
|
+
// index entry — reconstruct only makes sense for live created/updated/restored.
|
|
52
|
+
if (verb === "deleted" || verb === "forgotten" || isUpdateOfSoftDeletedRow(event, verb)) {
|
|
49
53
|
return { kind: "remove", tenantId, entityType: entityName, entityId: event.aggregateId };
|
|
50
54
|
}
|
|
51
55
|
let state;
|
|
@@ -4,6 +4,7 @@ export declare const sharedWidgetEntity: import("@cosmicdrift/kumiko-types/field
|
|
|
4
4
|
readonly description?: string;
|
|
5
5
|
readonly maxLength?: number;
|
|
6
6
|
readonly minLength?: number;
|
|
7
|
+
readonly autoComplete?: string;
|
|
7
8
|
readonly required?: boolean;
|
|
8
9
|
readonly searchable?: boolean;
|
|
9
10
|
readonly sortable?: boolean;
|
|
@@ -28,6 +29,7 @@ export declare const sharedWidgetTable: import("../db/table-builder.js").EntityT
|
|
|
28
29
|
readonly description?: string;
|
|
29
30
|
readonly maxLength?: number;
|
|
30
31
|
readonly minLength?: number;
|
|
32
|
+
readonly autoComplete?: string;
|
|
31
33
|
readonly required?: boolean;
|
|
32
34
|
readonly searchable?: boolean;
|
|
33
35
|
readonly sortable?: boolean;
|
|
@@ -52,6 +54,7 @@ export declare const sharedUserEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
52
54
|
readonly description?: string;
|
|
53
55
|
readonly maxLength?: number;
|
|
54
56
|
readonly minLength?: number;
|
|
57
|
+
readonly autoComplete?: string;
|
|
55
58
|
readonly required?: boolean;
|
|
56
59
|
readonly searchable?: boolean;
|
|
57
60
|
readonly sortable?: boolean;
|
|
@@ -74,6 +77,7 @@ export declare const sharedUserEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
74
77
|
readonly description?: string;
|
|
75
78
|
readonly maxLength?: number;
|
|
76
79
|
readonly minLength?: number;
|
|
80
|
+
readonly autoComplete?: string;
|
|
77
81
|
readonly required?: boolean;
|
|
78
82
|
readonly searchable?: boolean;
|
|
79
83
|
readonly sortable?: boolean;
|
|
@@ -96,6 +100,7 @@ export declare const sharedUserEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
96
100
|
readonly description?: string;
|
|
97
101
|
readonly maxLength?: number;
|
|
98
102
|
readonly minLength?: number;
|
|
103
|
+
readonly autoComplete?: string;
|
|
99
104
|
readonly required?: boolean;
|
|
100
105
|
readonly searchable?: boolean;
|
|
101
106
|
readonly sortable?: boolean;
|
|
@@ -123,6 +128,7 @@ export declare const sharedUserTable: import("../db/table-builder.js").EntityTab
|
|
|
123
128
|
readonly description?: string;
|
|
124
129
|
readonly maxLength?: number;
|
|
125
130
|
readonly minLength?: number;
|
|
131
|
+
readonly autoComplete?: string;
|
|
126
132
|
readonly required?: boolean;
|
|
127
133
|
readonly searchable?: boolean;
|
|
128
134
|
readonly sortable?: boolean;
|
|
@@ -145,6 +151,7 @@ export declare const sharedUserTable: import("../db/table-builder.js").EntityTab
|
|
|
145
151
|
readonly description?: string;
|
|
146
152
|
readonly maxLength?: number;
|
|
147
153
|
readonly minLength?: number;
|
|
154
|
+
readonly autoComplete?: string;
|
|
148
155
|
readonly required?: boolean;
|
|
149
156
|
readonly searchable?: boolean;
|
|
150
157
|
readonly sortable?: boolean;
|
|
@@ -167,6 +174,7 @@ export declare const sharedUserTable: import("../db/table-builder.js").EntityTab
|
|
|
167
174
|
readonly description?: string;
|
|
168
175
|
readonly maxLength?: number;
|
|
169
176
|
readonly minLength?: number;
|
|
177
|
+
readonly autoComplete?: string;
|
|
170
178
|
readonly required?: boolean;
|
|
171
179
|
readonly searchable?: boolean;
|
|
172
180
|
readonly sortable?: boolean;
|
|
@@ -194,6 +202,7 @@ export declare const sharedItemEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
194
202
|
readonly description?: string;
|
|
195
203
|
readonly maxLength?: number;
|
|
196
204
|
readonly minLength?: number;
|
|
205
|
+
readonly autoComplete?: string;
|
|
197
206
|
readonly required?: boolean;
|
|
198
207
|
readonly searchable?: boolean;
|
|
199
208
|
readonly sortable?: boolean;
|
|
@@ -237,6 +246,7 @@ export declare const sharedItemTable: import("../db/table-builder.js").EntityTab
|
|
|
237
246
|
readonly description?: string;
|
|
238
247
|
readonly maxLength?: number;
|
|
239
248
|
readonly minLength?: number;
|
|
249
|
+
readonly autoComplete?: string;
|
|
240
250
|
readonly required?: boolean;
|
|
241
251
|
readonly searchable?: boolean;
|
|
242
252
|
readonly sortable?: boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const CARD_META_MAX = 3;
|
|
2
|
+
export type CardRoleColumn = {
|
|
3
|
+
readonly field: string;
|
|
4
|
+
readonly highlighted?: boolean;
|
|
5
|
+
readonly type?: string;
|
|
6
|
+
readonly renderer?: unknown;
|
|
7
|
+
readonly hideOnNarrow?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type CardColumnRoles<C extends CardRoleColumn> = {
|
|
10
|
+
readonly title: C | undefined;
|
|
11
|
+
readonly status: C | undefined;
|
|
12
|
+
readonly meta: readonly C[];
|
|
13
|
+
};
|
|
14
|
+
export declare function cardColumnRoles<C extends CardRoleColumn>(columns: readonly C[]): CardColumnRoles<C>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const CARD_META_MAX = 3;
|
|
2
|
+
// One rule for the narrow-layout list card, shared by the renderer and the
|
|
3
|
+
// boot validator so the validator counts exactly what the card would draw.
|
|
4
|
+
export function cardColumnRoles(columns) {
|
|
5
|
+
const title = columns.find((col) => col.highlighted === true) ?? columns[0];
|
|
6
|
+
const status = columns.find((col) => col !== title && col.type === "select" && col.renderer === undefined);
|
|
7
|
+
const meta = columns.filter((col) => col !== title && col !== status && col.hideOnNarrow !== true);
|
|
8
|
+
return { title, status, meta };
|
|
9
|
+
}
|
package/dist/ui-types/index.d.ts
CHANGED
|
@@ -9,13 +9,15 @@ 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, 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";
|
|
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, EntityEditAction, EntityEditActionPlacement, 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";
|
|
16
16
|
export type { WorkspaceDefinition } from "../engine/types/workspace.js";
|
|
17
17
|
export { ACTION_FORM_ENTITY, handlerFieldLabelKey, handlerTitleKey, PROJECTION_DETAIL_ENTITY, WRITE_FORM_SECTION_ENTITY, } from "../i18n/required-surface-keys.js";
|
|
18
18
|
export type { AppSchema, FeatureSchema, QualifiedContentCollection, WorkspaceSchema, } from "./app-schema.js";
|
|
19
|
+
export type { CardColumnRoles, CardRoleColumn } from "./card-column-roles.js";
|
|
20
|
+
export { CARD_META_MAX, cardColumnRoles } from "./card-column-roles.js";
|
|
19
21
|
export { type Formality, formalLocaleTag } from "./formality.js";
|
|
20
22
|
export type { ListRowMetaColumnType, ListRowMetaReference, ReferenceLookupSource, SystemReferenceLabel, } from "./list-row-meta.js";
|
|
21
23
|
export { LIST_ROW_META_COLUMNS, LIST_ROW_META_REFERENCES, REFERENCE_LOOKUP_SOURCES, SYSTEM_REFERENCE_LABELS, } from "./list-row-meta.js";
|
package/dist/ui-types/index.js
CHANGED
|
@@ -26,6 +26,7 @@ export { availableSelectOptions, evalFieldCondition, explicitListScreenId, findU
|
|
|
26
26
|
export { isOpenToAllGranted, isUiAccessGranted } from "../engine/types/handlers.js";
|
|
27
27
|
export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "../engine/types/screen.js";
|
|
28
28
|
export { ACTION_FORM_ENTITY, handlerFieldLabelKey, handlerTitleKey, PROJECTION_DETAIL_ENTITY, WRITE_FORM_SECTION_ENTITY, } from "../i18n/required-surface-keys.js";
|
|
29
|
+
export { CARD_META_MAX, cardColumnRoles } from "./card-column-roles.js";
|
|
29
30
|
export { formalLocaleTag } from "./formality.js";
|
|
30
31
|
export { LIST_ROW_META_COLUMNS, LIST_ROW_META_REFERENCES, REFERENCE_LOOKUP_SOURCES, SYSTEM_REFERENCE_LABELS, } from "./list-row-meta.js";
|
|
31
32
|
export { resolveTranslationValue, translationValueOtherText, } from "./plural.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.357.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.357.0",
|
|
214
|
+
"@cosmicdrift/kumiko-types": "0.357.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.357.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,27 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.357.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Executor update accepts includeDeleted for soft-deleted rows",
|
|
6
|
+
"detail": "`executor.update(payload, user, db, { includeDeleted: true })` loads the row without the soft-delete filter (the tenant filter stays), so server code can move a trashed row together with its parent (for example a team change) and a later restore lands consistently. The row stays deleted. The default is unchanged (NotFound) and the generic HTTP update handler never sets the option. The projection apply was already unfiltered by `isDeleted`, so live and rebuild agree. The search consumer treats an update on a soft-deleted row as a removal, so the moved row does not reappear in search."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.357.0",
|
|
10
|
+
"type": "improvement",
|
|
11
|
+
"title": "Boot validation covers multiSelect optionsAvailabilityQuery on entity and screen fields"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"version": "0.356.0",
|
|
15
|
+
"type": "improvement",
|
|
16
|
+
"title": "Executor create accepts streamTenantId; ctx.forTenant(tenantId) for gated cross-tenant writes",
|
|
17
|
+
"detail": "createEventStoreExecutor().create takes { streamTenantId } (stream, event tenant, row tenant, PII key and cache invalidation follow it, the acting user stays the operator). The override is accepted on a system-mode db and also on a tenant-mode db bound to exactly that stream tenant. New handler-context method ctx.forTenant(tenantId) returns { db, streamTenantId }: a tenant-mode db bound to the target tenant plus the stream override to pass to the executor, so a handler needs no unsafeRaw escape hatch for cross-tenant writes. A foreign tenant is gated to SystemAdmin and system identities (AccessDeniedError with reason tenant_override_requires_system_admin otherwise); the caller's own tenant returns streamTenantId undefined. It is not offered to r.systemScope() handlers. The bundled cross-tenant handlers (tenant removeMember/updateMemberRoles, tier setTenantTier, custom-fields system fields, template-resolver upserts/set, managed-pages set, compliance-profiles setProfile) no longer rewrite event.user.tenantId; the event actor is the operator. tenant cancelPendingInvitation takes an optional streamTenantId."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"version": "0.356.0",
|
|
21
|
+
"type": "breaking",
|
|
22
|
+
"title": "Job ctx.queryAs is gated like ctx.writeAs",
|
|
23
|
+
"migration": "Jobs calling ctx.queryAs with createSystemUser(...) or any identity other than the job's own caller now fail with AccessDeniedError (system_identity_switch_denied / identity_switch_denied). Declare r.systemScope() on the job's feature or escapeHatch: { reason } on the job, the same grant ctx.writeAs already needs. The escape-hatch guard exempts jobs by name, so this surfaces only at runtime; grep your jobs for ctx.queryAs. Known consumer sites: offlot-app channel-text generate-texts.job, campaign-calendar sync, campaign-channel-foundation seed-starter-channels."
|
|
24
|
+
},
|
|
2
25
|
{
|
|
3
26
|
"version": "0.355.0",
|
|
4
27
|
"type": "improvement",
|