@cosmicdrift/kumiko-framework 0.365.0 → 0.366.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/agents/examples/access-control.md +32 -30
- package/agents/examples/anonymous-access-multitenant.md +14 -16
- package/agents/examples/anonymous-access.md +36 -43
- package/agents/examples/auth-foundation-providers.md +1 -1
- package/agents/examples/basic-entity.md +1 -1
- package/agents/examples/boot-check.md +3 -3
- package/agents/examples/chat-channels.md +1 -1
- package/agents/examples/cross-feature-events.md +7 -7
- package/agents/examples/currencies-global.md +3 -3
- package/agents/examples/currencies-per-tenant.md +2 -2
- package/agents/examples/custom-fields-basic.md +4 -3
- package/agents/examples/custom-handlers.md +40 -44
- package/agents/examples/dashboard-stats.md +6 -6
- package/agents/examples/delivery-notifications.md +9 -9
- package/agents/examples/designer-demo.md +2 -1
- package/agents/examples/embedded-entity-form.md +13 -13
- package/agents/examples/embedded.md +1 -1
- package/agents/examples/encrypted-tenant-config.md +1 -1
- package/agents/examples/error-contract.md +25 -25
- package/agents/examples/event-sourcing.md +96 -76
- package/agents/examples/feature-toggles.md +29 -21
- package/agents/examples/files-post-processing.md +22 -26
- package/agents/examples/idempotency.md +17 -19
- package/agents/examples/inbound-mail-triage.md +1 -1
- package/agents/examples/lane-routing.md +10 -12
- package/agents/examples/legal-pages.md +1 -1
- package/agents/examples/lifecycle-hooks.md +59 -39
- package/agents/examples/managed-config.md +9 -9
- package/agents/examples/managed-pages.md +1 -1
- package/agents/examples/notes-history-basic.md +3 -3
- package/agents/examples/pdf-audit-export.md +7 -7
- package/agents/examples/pipeline-basics.md +8 -8
- package/agents/examples/public-share-token.md +1 -1
- package/agents/examples/rate-limiting.md +7 -9
- package/agents/examples/record-detail-layout.md +26 -20
- package/agents/examples/reference-data.md +1 -1
- package/agents/examples/relations.md +2 -2
- package/agents/examples/screens-nav.md +2 -2
- package/agents/examples/search.md +17 -19
- package/agents/examples/security-baseline.md +1 -1
- package/agents/examples/state-machine.md +1 -1
- package/agents/examples/tags-basic.md +3 -3
- package/agents/examples/tenant-settings.md +2 -2
- package/agents/examples/timezones.md +12 -12
- package/agents/examples/user-data-rights.md +3 -3
- package/agents/examples/user-profile.md +1 -1
- package/agents/examples/webhook-step.md +12 -2
- package/agents/examples/writeform-section.md +21 -15
- package/agents/features.md +1 -1
- package/agents/widgets.md +5 -0
- package/dist/api/server.js +20 -4
- package/dist/bun-db/query.js +16 -1
- package/dist/crypto/in-memory-kms-adapter.js +4 -7
- package/dist/crypto/pg-kms-adapter.js +10 -6
- package/dist/db/entity-table-meta.js +4 -4
- package/dist/db/event-store-executor-write.js +16 -12
- package/dist/engine/boot-validator/api-ext.js +2 -2
- package/dist/engine/boot-validator/config-deps.js +1 -1
- package/dist/engine/boot-validator/handler-field-references.js +3 -10
- package/dist/engine/boot-validator/screens.js +1 -1
- package/dist/engine/build-app-schema.js +3 -6
- package/dist/engine/define-feature.js +5 -5
- package/dist/engine/entity-handlers.d.ts +2 -2
- package/dist/engine/entity-handlers.js +5 -5
- package/dist/engine/factories.d.ts +11 -11
- package/dist/engine/factories.js +11 -11
- package/dist/engine/feature-ast/extractors/round2.js +16 -2
- package/dist/engine/feature-ast/render.js +4 -7
- package/dist/engine/feature-config-events-jobs.d.ts +2 -0
- package/dist/engine/feature-config-events-jobs.js +3 -0
- package/dist/engine/feature-entity-handlers.d.ts +4 -23
- package/dist/engine/feature-entity-handlers.js +77 -100
- package/dist/engine/feature-ui-extensions.d.ts +9 -14
- package/dist/engine/feature-ui-extensions.js +22 -14
- package/dist/engine/handler-field-references.d.ts +25 -0
- package/dist/engine/handler-field-references.js +80 -6
- package/dist/engine/index.d.ts +3 -3
- package/dist/engine/index.js +2 -2
- package/dist/engine/object-form.d.ts +2 -0
- package/dist/engine/object-form.js +13 -10
- package/dist/engine/registry-state.js +2 -2
- package/dist/engine/registry-validate.d.ts +2 -0
- package/dist/engine/registry-validate.js +42 -2
- package/dist/engine/registry.js +3 -1
- package/dist/engine/strip-disabled-features-from-app-schema.d.ts +6 -0
- package/dist/engine/strip-disabled-features-from-app-schema.js +89 -0
- package/dist/engine/types/index.d.ts +2 -2
- package/dist/files/feature.js +1 -1
- package/dist/jobs/job-runner.d.ts +1 -0
- package/dist/jobs/job-runner.js +83 -17
- package/dist/jobs/max-per-tenant.d.ts +8 -0
- package/dist/jobs/max-per-tenant.js +14 -0
- package/dist/pipeline/dispatch-query.js +1 -1
- package/dist/pipeline/dispatch-shared.d.ts +2 -1
- package/dist/pipeline/dispatch-shared.js +3 -6
- package/dist/pipeline/index.d.ts +2 -0
- package/dist/pipeline/index.js +1 -0
- package/dist/pipeline/multi-stream-apply-context.d.ts +1 -0
- package/dist/pipeline/multi-stream-apply-context.js +1 -0
- package/dist/pipeline/tenant-timezone-config-key.d.ts +1 -0
- package/dist/pipeline/tenant-timezone-config-key.js +6 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +1 -0
- package/dist/testing/manual-clock.d.ts +14 -0
- package/dist/testing/manual-clock.js +12 -0
- package/dist/time/index.d.ts +1 -1
- package/dist/time/tz-context.d.ts +1 -1
- package/dist/time/tz-context.js +9 -4
- package/dist/upgrade-cli.js +13 -2
- package/package.json +4 -4
- package/src/changes.json +58 -0
- package/src/scripts/codemod/README.md +23 -0
- package/src/scripts/codemod/object-form.ts +709 -0
|
@@ -4,6 +4,7 @@ import { WEBSOCKET_MAX_CONNECTIONS_PER_USER_LIMIT, WEBSOCKET_MAX_PAYLOAD_BYTES,
|
|
|
4
4
|
import { bindHookEscapeHatchGrant } from "../pipeline/system-identity-switch.js";
|
|
5
5
|
import { LifecycleHookTypes } from "./constants.js";
|
|
6
6
|
import { resolveName } from "./handler-helpers.js";
|
|
7
|
+
import { isObjectArgument, throwPositionalFormRemoved } from "./object-form.js";
|
|
7
8
|
import { isKebabSegment, isValidQn, qualifyEntityName, toKebab } from "./qualified-name.js";
|
|
8
9
|
import { HookPhases } from "./types/index.js";
|
|
9
10
|
// Builds hooks/extensions/projections/screens/nav/workspace/tables/tree-actions
|
|
@@ -43,8 +44,8 @@ function isEntityWideHookType(type) {
|
|
|
43
44
|
type === LifecycleHookTypes.postDelete ||
|
|
44
45
|
type === LifecycleHookTypes.postQuery);
|
|
45
46
|
}
|
|
46
|
-
// r.hook(type, { allOf: entity },
|
|
47
|
-
// entity"
|
|
47
|
+
// r.hook({ type, target: { allOf: entity }, handler }) — "all write/query handlers of
|
|
48
|
+
// this entity". Hook-fn casts
|
|
48
49
|
// below: @cast-boundary engine-bridge — typed Dev-API (LifecycleHookFn) →
|
|
49
50
|
// erased Map<entityName, fn>.
|
|
50
51
|
function registerEntityWideHook(state, featureName, type, entityName, fn, options) {
|
|
@@ -107,21 +108,28 @@ export function buildUiExtensionsMethods(state, name) {
|
|
|
107
108
|
state.navs[navDefinition.id] = Object.fromEntries(Object.entries(navDefinition).filter(([, value]) => value !== undefined));
|
|
108
109
|
}
|
|
109
110
|
return {
|
|
110
|
-
hook(
|
|
111
|
+
hook(definition) {
|
|
112
|
+
if (!isObjectArgument(definition))
|
|
113
|
+
throwPositionalFormRemoved(name, "hook");
|
|
114
|
+
const { type, target, handler: fn } = definition;
|
|
115
|
+
const options = {
|
|
116
|
+
...(definition.phase !== undefined && { phase: definition.phase }),
|
|
117
|
+
...(definition.escapeHatch !== undefined && { escapeHatch: definition.escapeHatch }),
|
|
118
|
+
};
|
|
111
119
|
if (type === "validation") {
|
|
112
120
|
if (options?.escapeHatch !== undefined) {
|
|
113
|
-
throw new Error(`[Feature ${name}] r.hook("validation",
|
|
121
|
+
throw new Error(`[Feature ${name}] r.hook({ type: "validation", … }) does not accept escapeHatch — ` +
|
|
114
122
|
"validation hooks receive no context to switch identity with.");
|
|
115
123
|
}
|
|
116
124
|
}
|
|
117
125
|
else if (options?.escapeHatch !== undefined &&
|
|
118
126
|
options.escapeHatch.reason.trim().length === 0) {
|
|
119
|
-
throw new Error(`[Feature ${name}] r.hook("${type}",
|
|
127
|
+
throw new Error(`[Feature ${name}] r.hook({ type: "${type}", … }) declares escapeHatch: { reason: "" } — ` +
|
|
120
128
|
"the reason must be a non-empty string explaining why this hook switches identity to SYSTEM.");
|
|
121
129
|
}
|
|
122
130
|
const hookGrantsProblem = options?.escapeHatch && escapeHatchGrantsProblem(options.escapeHatch);
|
|
123
131
|
if (hookGrantsProblem) {
|
|
124
|
-
throw new Error(`[Feature ${name}] r.hook("${type}",
|
|
132
|
+
throw new Error(`[Feature ${name}] r.hook({ type: "${type}", … }) declares an invalid escapeHatch — ${hookGrantsProblem}`);
|
|
125
133
|
}
|
|
126
134
|
// Wrapped once for both branches below; validation hooks stay unwrapped (no context).
|
|
127
135
|
const hookLabel = `${type} hook of feature "${name}"`;
|
|
@@ -135,7 +143,7 @@ export function buildUiExtensionsMethods(state, name) {
|
|
|
135
143
|
!Array.isArray(target) &&
|
|
136
144
|
"allOf" in target) {
|
|
137
145
|
if (!isEntityWideHookType(type)) {
|
|
138
|
-
throw new Error(`[Feature ${name}] r.hook("${type}", { allOf },
|
|
146
|
+
throw new Error(`[Feature ${name}] r.hook({ type: "${type}", target: { allOf }, … }) only supports ` +
|
|
139
147
|
`postSave/preDelete/postDelete/postQuery, not "${type}".`);
|
|
140
148
|
}
|
|
141
149
|
registerEntityWideHook(state, name, type, resolveName(target.allOf), wrapped, // @cast-boundary engine-bridge
|
|
@@ -241,20 +249,20 @@ export function buildUiExtensionsMethods(state, name) {
|
|
|
241
249
|
});
|
|
242
250
|
},
|
|
243
251
|
/**
|
|
244
|
-
* Marker
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
252
|
+
* Marker declaration: this feature provides a cross-feature API under
|
|
253
|
+
* the given name. The implementation is registered separately as a query
|
|
254
|
+
* or write handler under the QN pattern; r.exposesApi only feeds the
|
|
255
|
+
* boot check.
|
|
248
256
|
*
|
|
249
|
-
*
|
|
257
|
+
* Example:
|
|
250
258
|
* defineFeature("compliance-profiles", (r) => {
|
|
251
259
|
* r.exposesApi("compliance.forTenant");
|
|
252
260
|
* r.queryHandler({ name: "compliance:query:for-tenant", ... });
|
|
253
261
|
* });
|
|
254
262
|
* defineFeature("user-data-rights", (r) => {
|
|
255
|
-
* r.requires("compliance-profiles");
|
|
263
|
+
* r.requires({ features: ["compliance-profiles"] });
|
|
256
264
|
* r.usesApi("compliance.forTenant");
|
|
257
|
-
* //
|
|
265
|
+
* // in a handler: ctx.callQuery("compliance:query:for-tenant", ...)
|
|
258
266
|
* });
|
|
259
267
|
*/
|
|
260
268
|
exposesApi(apiName) {
|
|
@@ -18,5 +18,30 @@ export type HandlerFieldReferenceReading = {
|
|
|
18
18
|
readonly kind: "ok";
|
|
19
19
|
readonly reference: HandlerFieldReference;
|
|
20
20
|
};
|
|
21
|
+
export type HandlerFieldPathSegment = {
|
|
22
|
+
readonly kind: "field";
|
|
23
|
+
readonly name: string;
|
|
24
|
+
} | {
|
|
25
|
+
readonly kind: "element";
|
|
26
|
+
} | {
|
|
27
|
+
readonly kind: "recordKey";
|
|
28
|
+
} | {
|
|
29
|
+
readonly kind: "recordValue";
|
|
30
|
+
};
|
|
31
|
+
export type HandlerFieldReferenceAtPath = {
|
|
32
|
+
readonly path: string;
|
|
33
|
+
readonly segments: readonly HandlerFieldPathSegment[];
|
|
34
|
+
readonly reading: Exclude<HandlerFieldReferenceReading, {
|
|
35
|
+
kind: "none";
|
|
36
|
+
}>;
|
|
37
|
+
};
|
|
21
38
|
export declare function readHandlerFieldReference(schema: z.ZodType, field: string): HandlerFieldReferenceReading;
|
|
39
|
+
/** All reference metas of a handler input schema, including fields nested in arrays,
|
|
40
|
+
* objects and records. `z.lazy()` is not followed. */
|
|
41
|
+
export declare function readHandlerFieldReferences(schema: z.ZodType): readonly HandlerFieldReferenceAtPath[];
|
|
42
|
+
export declare function handlerFieldReferences(schema: z.ZodType): readonly {
|
|
43
|
+
readonly path: string;
|
|
44
|
+
readonly segments: readonly HandlerFieldPathSegment[];
|
|
45
|
+
readonly reference: HandlerFieldReference;
|
|
46
|
+
}[];
|
|
22
47
|
export declare function handlerFieldReference(schema: z.ZodType, field: string): HandlerFieldReference | undefined;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
|
-
import { zodDef, zodMeta, zodShapeField } from "../env/_zod-introspect.js";
|
|
2
|
+
import { zodDef, zodMeta, zodShape, zodShapeField } from "../env/_zod-introspect.js";
|
|
3
3
|
export const HANDLER_FIELD_REFERENCES_META_KEY = "references";
|
|
4
4
|
const MAX_WRAPPER_DEPTH = 8;
|
|
5
|
+
const MAX_NESTING_DEPTH = 8;
|
|
5
6
|
const referenceMetaSchema = z.union([
|
|
6
7
|
z.string().min(1),
|
|
7
8
|
z.object({ entity: z.string().min(1), by: z.string().min(1).optional() }),
|
|
@@ -42,11 +43,7 @@ function isArrayField(field) {
|
|
|
42
43
|
}
|
|
43
44
|
return false;
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
-
const objectSchema = unwrapObjectSchema(schema);
|
|
47
|
-
const fieldSchema = objectSchema && zodShapeField(objectSchema, field);
|
|
48
|
-
if (fieldSchema === undefined)
|
|
49
|
-
return { kind: "none" };
|
|
46
|
+
function readFieldReference(fieldSchema) {
|
|
50
47
|
const raw = findRawReferenceMeta(fieldSchema);
|
|
51
48
|
if (raw === undefined)
|
|
52
49
|
return { kind: "none" };
|
|
@@ -63,6 +60,83 @@ export function readHandlerFieldReference(schema, field) {
|
|
|
63
60
|
},
|
|
64
61
|
};
|
|
65
62
|
}
|
|
63
|
+
export function readHandlerFieldReference(schema, field) {
|
|
64
|
+
const objectSchema = unwrapObjectSchema(schema);
|
|
65
|
+
const fieldSchema = objectSchema && zodShapeField(objectSchema, field);
|
|
66
|
+
if (fieldSchema === undefined)
|
|
67
|
+
return { kind: "none" };
|
|
68
|
+
return readFieldReference(fieldSchema);
|
|
69
|
+
}
|
|
70
|
+
function pathOf(segments) {
|
|
71
|
+
let path = "";
|
|
72
|
+
for (const segment of segments) {
|
|
73
|
+
if (segment.kind === "field")
|
|
74
|
+
path += path === "" ? segment.name : `.${segment.name}`;
|
|
75
|
+
else if (segment.kind === "element")
|
|
76
|
+
path += "[]";
|
|
77
|
+
else if (segment.kind === "recordKey")
|
|
78
|
+
path += "{key}";
|
|
79
|
+
else
|
|
80
|
+
path += "{}";
|
|
81
|
+
}
|
|
82
|
+
return path;
|
|
83
|
+
}
|
|
84
|
+
function collectAt(schema, segments, out) {
|
|
85
|
+
const reading = readFieldReference(schema);
|
|
86
|
+
if (reading.kind !== "none")
|
|
87
|
+
out.push({ path: pathOf(segments), segments, reading });
|
|
88
|
+
}
|
|
89
|
+
function walkNested(schema, segments, depth, out) {
|
|
90
|
+
// skip: schemas nested deeper than MAX_NESTING_DEPTH are not inspected
|
|
91
|
+
if (depth > MAX_NESTING_DEPTH)
|
|
92
|
+
return;
|
|
93
|
+
let current = schema;
|
|
94
|
+
let currentSegments = segments;
|
|
95
|
+
for (let layer = 0; layer < MAX_WRAPPER_DEPTH && current !== undefined; layer++) {
|
|
96
|
+
if (current instanceof z.ZodObject) {
|
|
97
|
+
walkObject(current, currentSegments, depth, out);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
if (current instanceof z.ZodRecord) {
|
|
101
|
+
const { keyType, valueType } = current;
|
|
102
|
+
if (keyType instanceof z.ZodType) {
|
|
103
|
+
collectAt(keyType, [...currentSegments, { kind: "recordKey" }], out);
|
|
104
|
+
}
|
|
105
|
+
if (valueType instanceof z.ZodType) {
|
|
106
|
+
const valueSegments = [...currentSegments, { kind: "recordValue" }];
|
|
107
|
+
collectAt(valueType, valueSegments, out);
|
|
108
|
+
walkNested(valueType, valueSegments, depth + 1, out);
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
if (current instanceof z.ZodArray) {
|
|
113
|
+
currentSegments = [...currentSegments, { kind: "element" }];
|
|
114
|
+
current = zodDef(current)?.element;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
current = zodDef(current)?.innerType;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function walkObject(objectSchema, segments, depth, out) {
|
|
121
|
+
for (const [name, fieldSchema] of Object.entries(zodShape(objectSchema))) {
|
|
122
|
+
const fieldSegments = [...segments, { kind: "field", name }];
|
|
123
|
+
collectAt(fieldSchema, fieldSegments, out);
|
|
124
|
+
walkNested(fieldSchema, fieldSegments, depth + 1, out);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** All reference metas of a handler input schema, including fields nested in arrays,
|
|
128
|
+
* objects and records. `z.lazy()` is not followed. */
|
|
129
|
+
export function readHandlerFieldReferences(schema) {
|
|
130
|
+
const objectSchema = unwrapObjectSchema(schema);
|
|
131
|
+
if (objectSchema === undefined)
|
|
132
|
+
return [];
|
|
133
|
+
const out = [];
|
|
134
|
+
walkObject(objectSchema, [], 0, out);
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
export function handlerFieldReferences(schema) {
|
|
138
|
+
return readHandlerFieldReferences(schema).flatMap(({ path, segments, reading }) => reading.kind === "ok" ? [{ path, segments, reference: reading.reference }] : []);
|
|
139
|
+
}
|
|
66
140
|
export function handlerFieldReference(schema, field) {
|
|
67
141
|
const reading = readHandlerFieldReference(schema, field);
|
|
68
142
|
return reading.kind === "ok" ? reading.reference : undefined;
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export type { WorkflowDefinition, WorkflowInput, WorkflowTrigger } from "./defin
|
|
|
25
25
|
export { computeDefinitionFingerprint, defineWorkflow } from "./define-workflow.js";
|
|
26
26
|
export type { ToggleReader } from "./effective-features.js";
|
|
27
27
|
export { computeEffectiveFeatures, isToggleableFeature } from "./effective-features.js";
|
|
28
|
-
export { createEntityExecutor, defineEntityCreateHandler, defineEntityDeleteHandler, defineEntityDetailHandler, defineEntityListHandler, defineEntityQueryHandler, defineEntityRestoreHandler, defineEntityUpdateHandler, defineEntityWriteHandler, defineProjectionQueryHandler, type EntityCrudRegistrar, entityListSchema, MAX_LIST_LIMIT, type RegisterEntityCrudOptions,
|
|
28
|
+
export { createEntityExecutor, defineEntityCreateHandler, defineEntityDeleteHandler, defineEntityDetailHandler, defineEntityListHandler, defineEntityQueryHandler, defineEntityRestoreHandler, defineEntityUpdateHandler, defineEntityWriteHandler, defineProjectionQueryHandler, type EntityCrudRegistrar, entityListSchema, MAX_LIST_LIMIT, type RegisterEntityCrudOptions, } from "./entity-handlers.js";
|
|
29
29
|
export { declareEscapeHatch } from "./escape-hatch-declaration.js";
|
|
30
30
|
export type { EmitCtx } from "./event-helpers.js";
|
|
31
31
|
export { emitEvent, typedPayload } from "./event-helpers.js";
|
|
@@ -43,7 +43,7 @@ export { addPattern, applyChanges, createFeaturePatcher, getPatternSchema, group
|
|
|
43
43
|
export { type ChangelogEntry, type ChangelogType, compareVersions, type FeatureChangelog, filterEntriesAfter, parseFeatureChangelog, sortEntries, validateChangelog, } from "./feature-changelog.js";
|
|
44
44
|
export { type BuildManifestOptions, buildManifestFromRegistry, type FeatureManifest, type ManifestConfigKey, type ManifestExtension, type ManifestFeature, type ManifestSecret, type ManifestSystemEvent, serializeManifest, } from "./feature-manifest.js";
|
|
45
45
|
export { checkWriteFieldOwnership, checkWriteFieldRoles, filterReadFields, maskWriteOnlyFields, } from "./field-access.js";
|
|
46
|
-
export { HANDLER_FIELD_REFERENCES_META_KEY, type HandlerFieldReference, type HandlerFieldReferenceMeta, handlerFieldReference, } from "./handler-field-references.js";
|
|
46
|
+
export { HANDLER_FIELD_REFERENCES_META_KEY, type HandlerFieldPathSegment, type HandlerFieldReference, type HandlerFieldReferenceAtPath, type HandlerFieldReferenceMeta, handlerFieldReference, handlerFieldReferences, readHandlerFieldReferences, } from "./handler-field-references.js";
|
|
47
47
|
export { resolveName, withResponseData } from "./handler-helpers.js";
|
|
48
48
|
export { i18nKey } from "./i18n-key.js";
|
|
49
49
|
export { buildSessionRoles, findForbiddenMembershipRole, isForbiddenMembershipRole, stripForbiddenMembershipRoles, } from "./membership-roles.js";
|
|
@@ -81,7 +81,7 @@ export { assertNotificationVariables, templateVariable, templateVariablesToZod,
|
|
|
81
81
|
export { type EffectiveFeaturesResolver, findTierResolverUsage, isTierResolverPlugin, requireTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, type TierResolverPlugin, type TrialGate, } from "./tier-resolver-extension.js";
|
|
82
82
|
export { findOverDeepTransferChain, resolveTransferAdjacency as resolveEntityTransferAdjacency, type TransferAdjacency, type TransferEdge, type TransferEdgeLink, } from "./transfer-adjacency.js";
|
|
83
83
|
export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.js";
|
|
84
|
-
export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BeforeTransactionPhase, BeforeTransactionResult, 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, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EmbeddedListEditSpec, 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, NotificationLocalizedTemplate, NotificationRecipientFn, NotificationRecordMapping, NotificationTemplateBody, NotificationTemplateFn, NotificationTemplateMeta, NotifyDeferred, NotifyDelivery, NotifyDeliveryStatus, NotifyDispatched, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreparedWriteEvent, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitPer, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyDefinition, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TemplateListVariableDef, TemplateScalarVariableDef, TemplateVariableDef, TemplateVariables, TenantId, TenantWriteTarget, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.js";
|
|
84
|
+
export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BeforeTransactionPhase, BeforeTransactionResult, 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, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EmbeddedListEditSpec, EntityDefinition, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, EntityObjectDefinition, EntityRef, EntityRelations, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, ExtensionOptionsArgs, ExtensionOptionsFor, FeatureDefinition, FeatureRegistrar, FieldAccess, FieldCondition, FieldDefinition, FieldFormatRegistry, FieldIconKey, FieldRenderer, FileFieldDef, FilesFieldDef, Findability, FormatSpec, FormWidth, HandlerContext, HasManyRelation, HookDefinition, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MaxPerTenantOptions, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationLocalizedTemplate, NotificationRecipientFn, NotificationRecordMapping, NotificationTemplateBody, NotificationTemplateFn, NotificationTemplateMeta, NotifyDeferred, NotifyDelivery, NotifyDeliveryStatus, NotifyDispatched, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreparedWriteEvent, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitPer, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyDefinition, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TemplateListVariableDef, TemplateScalarVariableDef, TemplateVariableDef, TemplateVariables, TenantId, TenantWriteTarget, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.js";
|
|
85
85
|
export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, HookPhases, HTTP_ERROR_CODE_PATTERN, isAgentVisibleScreen, isDeliveryErrorCode, isOpenToAllGranted, resolveAgentExposure, } from "./types/index.js";
|
|
86
86
|
export type { AwaitedEventType, PipelineBuildCtx, PipelineCtx, PipelineDef, StepBuilder, StepDef, StepFailureStrategy, StepInstance, StepKind, StepNamespace, StepResolver, } from "./types/step.js";
|
|
87
87
|
export { runValidation } from "./validation.js";
|
package/dist/engine/index.js
CHANGED
|
@@ -25,7 +25,7 @@ export { createEntityExecutor, defineEntityCreateHandler, defineEntityDeleteHand
|
|
|
25
25
|
// Apps nehmen die verb-spezifischen Wrapper oben. Existierende
|
|
26
26
|
// Caller (Integration-Tests, alte bundled-features) bleiben so
|
|
27
27
|
// unverändert lauffähig.
|
|
28
|
-
defineEntityQueryHandler, defineEntityRestoreHandler, defineEntityUpdateHandler, defineEntityWriteHandler, defineProjectionQueryHandler, entityListSchema, MAX_LIST_LIMIT,
|
|
28
|
+
defineEntityQueryHandler, defineEntityRestoreHandler, defineEntityUpdateHandler, defineEntityWriteHandler, defineProjectionQueryHandler, entityListSchema, MAX_LIST_LIMIT, } from "./entity-handlers.js";
|
|
29
29
|
export { declareEscapeHatch } from "./escape-hatch-declaration.js";
|
|
30
30
|
export { emitEvent, typedPayload } from "./event-helpers.js";
|
|
31
31
|
export { EXT_ASSIGNABLE_ROLE, EXT_DERIVATIVE_OVERLAY_RESOLVER, EXT_DERIVATIVE_PUBLIC_PREDICATE, EXT_DERIVATIVE_RENDERER, EXT_EXTERNAL_RESOURCE, EXT_FILE_PROVIDER, EXT_INFRA_RESOURCE, EXT_PRINCIPAL_STATUS, EXT_SEARCH_ADAPTER, EXT_STORAGE_PROVIDER, EXT_TENANT_DATA, EXT_TENANT_LIFECYCLE_STATUS, EXT_USER_DATA, EXT_USER_DATA_ORDER, FILE_PROVIDER_CONFIG_KEY, FILE_STORAGE_PROVIDER_BOOT_SENTINEL, FILE_STORAGE_PROVIDER_ENV, TENANT_MEMBERSHIPS_QUERY, } from "./extension-names.js";
|
|
@@ -38,7 +38,7 @@ export { addPattern, applyChanges, createFeaturePatcher, getPatternSchema, group
|
|
|
38
38
|
export { compareVersions, filterEntriesAfter, parseFeatureChangelog, sortEntries, validateChangelog, } from "./feature-changelog.js";
|
|
39
39
|
export { buildManifestFromRegistry, serializeManifest, } from "./feature-manifest.js";
|
|
40
40
|
export { checkWriteFieldOwnership, checkWriteFieldRoles, filterReadFields, maskWriteOnlyFields, } from "./field-access.js";
|
|
41
|
-
export { HANDLER_FIELD_REFERENCES_META_KEY, handlerFieldReference, } from "./handler-field-references.js";
|
|
41
|
+
export { HANDLER_FIELD_REFERENCES_META_KEY, handlerFieldReference, handlerFieldReferences, readHandlerFieldReferences, } from "./handler-field-references.js";
|
|
42
42
|
export { resolveName, withResponseData } from "./handler-helpers.js";
|
|
43
43
|
export { i18nKey } from "./i18n-key.js";
|
|
44
44
|
// findForbiddenMembershipRole/isForbiddenMembershipRole/
|
|
@@ -2,3 +2,5 @@ export declare function splitNamedDefinition<T extends {
|
|
|
2
2
|
readonly name: string;
|
|
3
3
|
}>(definition: T): [string, Omit<T, "name">];
|
|
4
4
|
export declare function unwrapArrayForm<K extends string>(args: readonly [Record<K, readonly string[]>] | readonly string[], key: K): readonly string[];
|
|
5
|
+
export declare function isObjectArgument(value: unknown): boolean;
|
|
6
|
+
export declare function throwPositionalFormRemoved(featureName: string, method: string): never;
|
|
@@ -1,17 +1,12 @@
|
|
|
1
|
-
// Shared
|
|
2
|
-
// (`r.entity({ name: "item", ... })`)
|
|
3
|
-
//
|
|
4
|
-
// renderer emits for Designer/AI-generated code — a single object argument
|
|
5
|
-
// with named fields is easier to generate correctly than positional args
|
|
6
|
-
// whose count/order vary per method.
|
|
1
|
+
// Shared helpers for registrar methods that take a single object argument
|
|
2
|
+
// (`r.entity({ name: "item", ... })`). The positional overloads are gone; the
|
|
3
|
+
// runtime check below turns a leftover positional call into a pointer at the codemod.
|
|
7
4
|
export function splitNamedDefinition(definition) {
|
|
8
5
|
const { name, ...rest } = definition;
|
|
9
6
|
return [name, rest];
|
|
10
7
|
}
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// same string array under `key` (the feature-ast renderer's canonical
|
|
14
|
-
// shape for Designer/AI-generated code).
|
|
8
|
+
// `optionalRequires` and `readConfig` still accept variadic strings next to a
|
|
9
|
+
// single object wrapping the same string array under `key`.
|
|
15
10
|
export function unwrapArrayForm(args, key) {
|
|
16
11
|
const [first] = args;
|
|
17
12
|
if (typeof first === "object" && first !== null && key in first) {
|
|
@@ -19,3 +14,11 @@ export function unwrapArrayForm(args, key) {
|
|
|
19
14
|
}
|
|
20
15
|
return args;
|
|
21
16
|
}
|
|
17
|
+
export function isObjectArgument(value) {
|
|
18
|
+
return typeof value === "object" && value !== null;
|
|
19
|
+
}
|
|
20
|
+
// Removed in the first release after 2026-10-29; until then a positional call fails with
|
|
21
|
+
// a pointer at the codemod instead of an obscure "undefined is not a function".
|
|
22
|
+
export function throwPositionalFormRemoved(featureName, method) {
|
|
23
|
+
throw new Error(`[Feature ${featureName}] positional r.${method}(…) removed in 0.366, run \`bun kumiko-upgrade --apply\``);
|
|
24
|
+
}
|
|
@@ -12,7 +12,7 @@ const IMPLICIT_PROJECTION_SUFFIX = "-entity";
|
|
|
12
12
|
// durch implicit-projection-equivalence.integration.ts).
|
|
13
13
|
export function buildImplicitProjection(featureName, entityName, entity, qualify, backingTable, extensions = []) {
|
|
14
14
|
const name = qualify(featureName, "projection", `${entityName}${IMPLICIT_PROJECTION_SUFFIX}`);
|
|
15
|
-
// Backing table (r.entity(name, def,
|
|
15
|
+
// Backing table (r.entity({ name, ...def, backingTable })) is the one physical table
|
|
16
16
|
// object shared by executor-writes, rebuild-replay, test-push and
|
|
17
17
|
// collectTableMetas — restoring the #255 invariant (test-push == generate).
|
|
18
18
|
// Validated as a superset of the field-derived columns so a field/table
|
|
@@ -107,7 +107,7 @@ function collectExtensionSources(projectionName, entityName, extensions) {
|
|
|
107
107
|
function resolveBackingTable(entityName, entity, backingTable) {
|
|
108
108
|
const tableMeta = asEntityTableMeta(backingTable);
|
|
109
109
|
if (!tableMeta) {
|
|
110
|
-
throw new Error(`r.entity("${entityName}", …,
|
|
110
|
+
throw new Error(`r.entity({ name: "${entityName}", …, backingTable }): the backing table carries no ` +
|
|
111
111
|
"EntityTableMeta — build it via table() / buildEntityTable.");
|
|
112
112
|
}
|
|
113
113
|
assertBackingTableSuperset(entityName, deriveEntityTableMeta(entityName, entity), tableMeta);
|
|
@@ -19,6 +19,7 @@ export declare function buildEventUpcasterChains(state: RegistryState, features:
|
|
|
19
19
|
export declare function validateProjectionApplyKeys(state: RegistryState): void;
|
|
20
20
|
export declare function validateRequiredFeatures(state: RegistryState, features: readonly FeatureDefinition[]): void;
|
|
21
21
|
export declare function validateNotificationRecordMappings(state: RegistryState): void;
|
|
22
|
+
export declare function validateNotificationRenamedFrom(state: RegistryState): void;
|
|
22
23
|
export declare function resolveNotificationTriggersAndRegisterHooks(state: RegistryState): void;
|
|
23
24
|
export declare function validateLifecycleHookTargets(state: RegistryState): void;
|
|
24
25
|
export declare function validateEntityHookTargets(state: RegistryState, features: readonly FeatureDefinition[]): void;
|
|
@@ -26,6 +27,7 @@ export declare function resolveJobTriggers(state: RegistryState): void;
|
|
|
26
27
|
export declare function validateLiveEntities(state: RegistryState): void;
|
|
27
28
|
export declare function validateBootGates(state: RegistryState): void;
|
|
28
29
|
export declare function validateJobBackoff(state: RegistryState): void;
|
|
30
|
+
export declare function validateMaxPerTenant(state: RegistryState): void;
|
|
29
31
|
export declare function validateExtensionUsageTargets(state: RegistryState): void;
|
|
30
32
|
export declare function computeHasRateLimitedHandler(state: RegistryState): void;
|
|
31
33
|
export declare function publishEventPiiCatalog(state: RegistryState): void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { configureEventPiiCatalog } from "../crypto/event-pii.js";
|
|
2
2
|
import { hasSearchablePlaintext, isSensitiveLabelField } from "../db/entity-field-encryption.js";
|
|
3
3
|
import { entityEventName } from "../db/event-store-executor-context.js";
|
|
4
|
+
import { resolveMaxPerTenant } from "../jobs/max-per-tenant.js";
|
|
4
5
|
import { bindHookEscapeHatchGrant } from "../pipeline/system-identity-switch.js";
|
|
5
6
|
import { resolveName } from "./handler-helpers.js";
|
|
6
7
|
import { parseRefTargetEntityName } from "./parse-ref-target.js";
|
|
@@ -461,7 +462,7 @@ export function validateProjectionApplyKeys(state) {
|
|
|
461
462
|
const validEventTypes = new Set();
|
|
462
463
|
// Two source-modes are legal:
|
|
463
464
|
//
|
|
464
|
-
// (a) Registered entity (r.entity(src, ...)) — the "normal" case:
|
|
465
|
+
// (a) Registered entity (r.entity({ name: src, ... })) — the "normal" case:
|
|
465
466
|
// auto-lifecycle events `<src>.created/.updated/.deleted/.restored`
|
|
466
467
|
// fire when the event-store-executor writes, and any domain-event
|
|
467
468
|
// (r.defineEvent) appended onto an aggregate of that type is
|
|
@@ -496,7 +497,7 @@ export function validateProjectionApplyKeys(state) {
|
|
|
496
497
|
throw new Error(`Projection "${projName}" declares source(s) [${unregistered.join(", ")}] that are not registered entities, ` +
|
|
497
498
|
`and has no domain-event apply-keys. This is either a typo or a missing r.defineEvent registration. ` +
|
|
498
499
|
`Events-only projections need at least one apply-key from r.defineEvent; ` +
|
|
499
|
-
`CRUD-style projections need r.entity("${unregistered[0]}", ...).`);
|
|
500
|
+
`CRUD-style projections need r.entity({ name: "${unregistered[0]}", ... }).`);
|
|
500
501
|
}
|
|
501
502
|
}
|
|
502
503
|
for (const applyKey of Object.keys(projDef.apply)) {
|
|
@@ -546,6 +547,25 @@ export function validateNotificationRecordMappings(state) {
|
|
|
546
547
|
}
|
|
547
548
|
}
|
|
548
549
|
}
|
|
550
|
+
export function validateNotificationRenamedFrom(state) {
|
|
551
|
+
const ownerByAlias = new Map();
|
|
552
|
+
for (const [qualifiedName, notifDef] of state.notificationMap) {
|
|
553
|
+
for (const alias of notifDef.renamedFrom ?? []) {
|
|
554
|
+
const where = `Notification "${qualifiedName}" renamedFrom "${alias}"`;
|
|
555
|
+
if (alias === "*") {
|
|
556
|
+
throw new Error(`${where}: "*" is the preference wildcard, not a notification name`);
|
|
557
|
+
}
|
|
558
|
+
if (state.notificationMap.has(alias)) {
|
|
559
|
+
throw new Error(`${where} is a current notification name; remove it from renamedFrom or rename the notification`);
|
|
560
|
+
}
|
|
561
|
+
const otherOwner = ownerByAlias.get(alias);
|
|
562
|
+
if (otherOwner !== undefined && otherOwner !== qualifiedName) {
|
|
563
|
+
throw new Error(`${where} is also declared by notification "${otherOwner}"; an alias can have only one owner, remove it from one of them`);
|
|
564
|
+
}
|
|
565
|
+
ownerByAlias.set(alias, qualifiedName);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
549
569
|
export function resolveNotificationTriggersAndRegisterHooks(state) {
|
|
550
570
|
// Resolve notification triggers and register postSave hooks
|
|
551
571
|
// Done after all features are registered so cross-feature triggers work
|
|
@@ -744,6 +764,26 @@ export function validateJobBackoff(state) {
|
|
|
744
764
|
}
|
|
745
765
|
}
|
|
746
766
|
}
|
|
767
|
+
export function validateMaxPerTenant(state) {
|
|
768
|
+
for (const [jobName, jobDef] of state.jobMap) {
|
|
769
|
+
const resolved = resolveMaxPerTenant(jobDef);
|
|
770
|
+
if (!resolved)
|
|
771
|
+
continue;
|
|
772
|
+
if (!Number.isInteger(resolved.limit) || resolved.limit <= 0) {
|
|
773
|
+
throw new Error(`Job "${jobName}" maxPerTenant limit must be a positive integer (got ${resolved.limit})`);
|
|
774
|
+
}
|
|
775
|
+
if (typeof jobDef.maxPerTenant !== "object")
|
|
776
|
+
continue;
|
|
777
|
+
const { deferMs } = jobDef.maxPerTenant;
|
|
778
|
+
if (deferMs !== undefined && (!Number.isInteger(deferMs) || deferMs <= 0)) {
|
|
779
|
+
throw new Error(`Job "${jobName}" maxPerTenant.deferMs must be a positive integer (got ${deferMs})`);
|
|
780
|
+
}
|
|
781
|
+
// The inline boot-gate path has no BullMQ token to move the job to delayed with.
|
|
782
|
+
if (resolved.onLimit === "defer" && jobDef.bootGate) {
|
|
783
|
+
throw new Error(`Job "${jobName}" cannot combine bootGate with maxPerTenant onLimit "defer"`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
747
787
|
export function validateExtensionUsageTargets(state) {
|
|
748
788
|
// Validate: extension usages must reference existing extensions
|
|
749
789
|
for (const usage of state.extensionUsages) {
|
package/dist/engine/registry.js
CHANGED
|
@@ -3,7 +3,7 @@ import { dedupeFeatures } from "./dedupe-features.js";
|
|
|
3
3
|
import { buildRegistryFacade } from "./registry-facade.js";
|
|
4
4
|
import { populateClaimsAndAuth, populateConfigKeys, populateEvents, populateExtensionsAndSeeds, populateFeatureCore, populateHandlers, populateHooks, populateJobsAndNotifications, populateMetricsAndSecrets, populateProjectionsAndTables, populateScreensNavWorkspaces, populateSystemEvents, populateTranslations, } from "./registry-ingest.js";
|
|
5
5
|
import { createInitialState } from "./registry-state.js";
|
|
6
|
-
import { applyExtensionUsages, autoWireSoftDeleteJobs, buildEntityEventUpcasterChains, buildEventUpcasterChains, buildImplicitProjections, buildIncomingRelations, buildSearchableSortableCaches, computeHasRateLimitedHandler, finalizeWorkspaceNavMembership, populateHandlerEntityMappings, publishEventPiiCatalog, resolveJobTriggers, resolveNotificationTriggersAndRegisterHooks, validateBootGates, validateEntityHookTargets, validateEventMigrationVersions, validateExtensionSelectors, validateExtensionUsageTargets, validateFieldAccessHandlersAreEntityMapped, validateJobBackoff, validateLifecycleHookTargets, validateLiveEntities, validateNotificationRecordMappings, validateProjectionApplyKeys, validateRelationTargetsExist, validateRequiredFeatures, } from "./registry-validate.js";
|
|
6
|
+
import { applyExtensionUsages, autoWireSoftDeleteJobs, buildEntityEventUpcasterChains, buildEventUpcasterChains, buildImplicitProjections, buildIncomingRelations, buildSearchableSortableCaches, computeHasRateLimitedHandler, finalizeWorkspaceNavMembership, populateHandlerEntityMappings, publishEventPiiCatalog, resolveJobTriggers, resolveNotificationTriggersAndRegisterHooks, validateBootGates, validateEntityHookTargets, validateEventMigrationVersions, validateExtensionSelectors, validateExtensionUsageTargets, validateFieldAccessHandlersAreEntityMapped, validateJobBackoff, validateLifecycleHookTargets, validateLiveEntities, validateMaxPerTenant, validateNotificationRecordMappings, validateNotificationRenamedFrom, validateProjectionApplyKeys, validateRelationTargetsExist, validateRequiredFeatures, } from "./registry-validate.js";
|
|
7
7
|
// This is where the magic happens. By "magic" I mean: precomputed maps.
|
|
8
8
|
// I build everything once at boot (hooks, relations, searchable fields, ...)
|
|
9
9
|
// so nothing has to iterate over objects at runtime. O(1) instead of O(n*m).
|
|
@@ -42,12 +42,14 @@ export function createRegistry(rawFeatures) {
|
|
|
42
42
|
validateRequiredFeatures(state, features);
|
|
43
43
|
resolveNotificationTriggersAndRegisterHooks(state);
|
|
44
44
|
validateNotificationRecordMappings(state);
|
|
45
|
+
validateNotificationRenamedFrom(state);
|
|
45
46
|
validateLifecycleHookTargets(state);
|
|
46
47
|
validateEntityHookTargets(state, features);
|
|
47
48
|
resolveJobTriggers(state);
|
|
48
49
|
validateBootGates(state);
|
|
49
50
|
validateLiveEntities(state);
|
|
50
51
|
validateJobBackoff(state);
|
|
52
|
+
validateMaxPerTenant(state);
|
|
51
53
|
validateExtensionUsageTargets(state);
|
|
52
54
|
computeHasRateLimitedHandler(state);
|
|
53
55
|
publishEventPiiCatalog(state);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AppSchema } from "../ui-types/app-schema.js";
|
|
2
|
+
import type { EffectiveFeaturesResolver } from "./tier-resolver-extension.js";
|
|
3
|
+
import type { Registry } from "./types/feature.js";
|
|
4
|
+
import type { TenantId } from "./types/identifiers.js";
|
|
5
|
+
export declare function resolveDisabledFeatureNames(registry: Registry, effectiveFeatures: EffectiveFeaturesResolver, tenantId: TenantId): Promise<readonly string[]>;
|
|
6
|
+
export declare function stripDisabledFeaturesFromAppSchema(schema: AppSchema, registry: Registry, disabledFeatureNames: readonly string[]): AppSchema;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Tenant-level counterpart to projectAppSchemaForRoles: removes the UI surface
|
|
2
|
+
// (screens, navs, content collections, workspaces) of features the tenant has
|
|
3
|
+
// switched off via r.toggleable(). Entities and translations stay — enabled
|
|
4
|
+
// features may still reference them. Run BEFORE the role projection: it keeps
|
|
5
|
+
// only targets that exist in the schema, so once the disabled screens/navs are
|
|
6
|
+
// gone it also drops every action, redirect, nav and workspace member that
|
|
7
|
+
// pointed at them. The one reference it does not resolve is a nav `parent`
|
|
8
|
+
// pointing at a missing nav; that is pruned here.
|
|
9
|
+
import { isToggleableFeature } from "./effective-features.js";
|
|
10
|
+
import { isValidQn, qualifyEntityName } from "./qualified-name.js";
|
|
11
|
+
// Sorted, so the result doubles as a stable cache key. The trial gate is
|
|
12
|
+
// consulted like the dispatcher's feature gate does, but only for toggleable
|
|
13
|
+
// features: a trial can only unlock tier (toggleable) features, so a
|
|
14
|
+
// cascade-disabled always-on feature needs no gate call.
|
|
15
|
+
export async function resolveDisabledFeatureNames(registry, effectiveFeatures, tenantId) {
|
|
16
|
+
const enabled = effectiveFeatures(tenantId);
|
|
17
|
+
const disabled = [];
|
|
18
|
+
for (const [featureName, feature] of registry.features) {
|
|
19
|
+
if (enabled.has(featureName))
|
|
20
|
+
continue;
|
|
21
|
+
if (isToggleableFeature(feature) &&
|
|
22
|
+
(await effectiveFeatures.trialGate?.(tenantId, featureName))) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
disabled.push(featureName);
|
|
26
|
+
}
|
|
27
|
+
return disabled.sort();
|
|
28
|
+
}
|
|
29
|
+
function navParentQn(featureName, parent) {
|
|
30
|
+
return isValidQn(parent) ? parent : qualifyEntityName(featureName, "nav", parent);
|
|
31
|
+
}
|
|
32
|
+
// Navs of enabled features whose parent chain ends in a removed nav.
|
|
33
|
+
function collectOrphanedNavQns(features, removedNavQns) {
|
|
34
|
+
const orphaned = new Set();
|
|
35
|
+
let changed = true;
|
|
36
|
+
while (changed) {
|
|
37
|
+
changed = false;
|
|
38
|
+
for (const feature of features) {
|
|
39
|
+
for (const nav of feature.navs ?? []) {
|
|
40
|
+
const qn = qualifyEntityName(feature.featureName, "nav", nav.id);
|
|
41
|
+
if (removedNavQns.has(qn) || nav.parent === undefined)
|
|
42
|
+
continue;
|
|
43
|
+
if (removedNavQns.has(navParentQn(feature.featureName, nav.parent))) {
|
|
44
|
+
removedNavQns.add(qn);
|
|
45
|
+
orphaned.add(qn);
|
|
46
|
+
changed = true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return orphaned;
|
|
52
|
+
}
|
|
53
|
+
export function stripDisabledFeaturesFromAppSchema(schema, registry, disabledFeatureNames) {
|
|
54
|
+
if (disabledFeatureNames.length === 0)
|
|
55
|
+
return schema;
|
|
56
|
+
const disabled = new Set(disabledFeatureNames);
|
|
57
|
+
const removedNavQns = new Set();
|
|
58
|
+
for (const feature of schema.features) {
|
|
59
|
+
if (!disabled.has(feature.featureName))
|
|
60
|
+
continue;
|
|
61
|
+
for (const nav of feature.navs ?? []) {
|
|
62
|
+
removedNavQns.add(qualifyEntityName(feature.featureName, "nav", nav.id));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const orphanedNavQns = collectOrphanedNavQns(schema.features, removedNavQns);
|
|
66
|
+
const features = schema.features.map((feature) => {
|
|
67
|
+
if (disabled.has(feature.featureName)) {
|
|
68
|
+
const { navs: _navs, contentCollections: _collections, workspaces: _ws, ...rest } = feature;
|
|
69
|
+
return { ...rest, screens: [] };
|
|
70
|
+
}
|
|
71
|
+
if (orphanedNavQns.size === 0 || feature.navs === undefined)
|
|
72
|
+
return feature;
|
|
73
|
+
const navs = feature.navs.filter((nav) => !orphanedNavQns.has(qualifyEntityName(feature.featureName, "nav", nav.id)));
|
|
74
|
+
if (navs.length === feature.navs.length)
|
|
75
|
+
return feature;
|
|
76
|
+
const { navs: _navs, ...rest } = feature;
|
|
77
|
+
return navs.length > 0 ? { ...rest, navs } : rest;
|
|
78
|
+
});
|
|
79
|
+
// Workspace definitions carry the short id the author declared; the owning
|
|
80
|
+
// feature is only known to the registry.
|
|
81
|
+
const disabledWorkspaceIds = new Set(disabledFeatureNames.flatMap((featureName) => Object.keys(registry.features.get(featureName)?.workspaces ?? {})));
|
|
82
|
+
const workspaces = schema.workspaces?.filter((ws) => !disabledWorkspaceIds.has(ws.definition.id));
|
|
83
|
+
const { workspaces: _original, ...restSchema } = schema;
|
|
84
|
+
return {
|
|
85
|
+
...restSchema,
|
|
86
|
+
features,
|
|
87
|
+
...(workspaces !== undefined && workspaces.length > 0 && { workspaces }),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export { isAgentVisibleScreen, resolveAgentExposure, } from "@cosmicdrift/kumiko-types/agent-exposure";
|
|
2
|
-
export type { ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigBounds, ConfigCascade, ConfigCascadeLevel, ConfigComputedContext, ConfigComputedFn, ConfigDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValidateContext, ConfigValidateFn, ConfigValue, ConfigValueSource, ConfigValueWithSource, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, ExtensionSelectorDef, ExtensionSelectorPanel, JobBackoff, JobBackoffStrategy, JobDefinition, JobHandlerFn, JobRunIn, JobTrigger, NotificationDataFn, NotificationDefinition, NotificationLocalizedTemplate, NotificationRecipientFn, NotificationRecordMapping, NotificationTemplateBody, NotificationTemplateFn, NotificationTemplateMeta, PluralForms, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionHooks, RegistrarExtensionRegistration, RunIn, TemplateListVariableDef, TemplateScalarVariableDef, TemplateVariableDef, TemplateVariables, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, UiExtensionDef, } from "@cosmicdrift/kumiko-types/config";
|
|
2
|
+
export type { ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigBounds, ConfigCascade, ConfigCascadeLevel, ConfigComputedContext, ConfigComputedFn, ConfigDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValidateContext, ConfigValidateFn, ConfigValue, ConfigValueSource, ConfigValueWithSource, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, ExtensionSelectorDef, ExtensionSelectorPanel, JobBackoff, JobBackoffStrategy, JobDefinition, JobHandlerFn, JobRunIn, JobTrigger, MaxPerTenantOptions, NotificationDataFn, NotificationDefinition, NotificationLocalizedTemplate, NotificationRecipientFn, NotificationRecordMapping, NotificationTemplateBody, NotificationTemplateFn, NotificationTemplateMeta, PluralForms, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionHooks, RegistrarExtensionRegistration, RunIn, TemplateListVariableDef, TemplateScalarVariableDef, TemplateVariableDef, TemplateVariables, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, UiExtensionDef, } from "@cosmicdrift/kumiko-types/config";
|
|
3
3
|
export type { QueryHandlerDefinition, StreamHandlerDefinition, WriteHandlerDefinition, WriteHandlerInput, } from "@cosmicdrift/kumiko-types/define-handler";
|
|
4
4
|
export type { EntityCrudRegistrar, EntityCrudVerb, EntityHandlerOptions, EntityQueryHandlerOptions, EntityWriteHandlerOptions, RegisterEntityCrudOptions, } from "@cosmicdrift/kumiko-types/entity-handlers";
|
|
5
5
|
export type { KumikoEntityTypeMap, KumikoEventTypeMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, } from "@cosmicdrift/kumiko-types/event-type-map";
|
|
6
6
|
export type { ExtensionOptionsArgs, ExtensionOptionsFor, KumikoExtensionOptionsMap, } from "@cosmicdrift/kumiko-types/extension-options-map";
|
|
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";
|
|
7
|
+
export type { BootCheckContext, BootCheckFn, EntityObjectDefinition, FeatureDefinition, FeatureMetricDef, FeatureMetricType, FeatureRegistrar, HookDefinition, 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
10
|
export type { AccessRule, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AggregateStreamHandle, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BeforeTransactionPhase, BeforeTransactionResult, 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, NotifyDeferred, NotifyDelivery, NotifyDeliveryStatus, NotifyDispatched, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, PayloadBucketNormalization, PayloadRateLimitOption, PreparedWriteEvent, 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";
|
package/dist/files/feature.js
CHANGED
|
@@ -22,7 +22,7 @@ export function createFilesFeature(options = {}) {
|
|
|
22
22
|
category: "storage",
|
|
23
23
|
recommended: false,
|
|
24
24
|
});
|
|
25
|
-
r.entity(FILE_REF_ENTITY_NAME, fileRefEntity);
|
|
25
|
+
r.entity({ name: FILE_REF_ENTITY_NAME, ...fileRefEntity });
|
|
26
26
|
r.writeHandler(bindReferencedFilesWrite);
|
|
27
27
|
return { routeOptions: options };
|
|
28
28
|
});
|