@cosmicdrift/kumiko-framework 0.339.0 → 0.341.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/api/index.d.ts +2 -1
- package/dist/api/index.js +1 -1
- package/dist/api/route-registrars.d.ts +4 -2
- package/dist/api/route-registrars.js +9 -1
- package/dist/api/server.d.ts +2 -1
- package/dist/api/server.js +43 -5
- package/dist/bun-db/index.d.ts +2 -2
- package/dist/bun-db/index.js +1 -1
- package/dist/bun-db/query.d.ts +25 -1
- package/dist/bun-db/query.js +136 -15
- package/dist/db/queries/shadow-swap.d.ts +2 -1
- package/dist/db/queries/shadow-swap.js +70 -7
- package/dist/db/query-api.d.ts +1 -1
- package/dist/db/query.d.ts +1 -1
- package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
- package/dist/engine/boot-validator/entity-handler.js +48 -0
- package/dist/engine/define-handler.js +1 -0
- package/dist/engine/feature-ast/extractors/handlers.d.ts +2 -1
- package/dist/engine/feature-ast/extractors/handlers.js +11 -1
- package/dist/engine/feature-ast/extractors/hooks.d.ts +2 -1
- package/dist/engine/feature-ast/extractors/hooks.js +14 -0
- package/dist/engine/feature-ast/extractors/index.d.ts +1 -1
- package/dist/engine/feature-ast/extractors/index.js +1 -1
- package/dist/engine/feature-ast/patch.js +1 -0
- package/dist/engine/feature-ast/pattern-change-schema.d.ts +20 -0
- package/dist/engine/feature-ast/pattern-change-schema.js +10 -0
- package/dist/engine/feature-ast/patterns.d.ts +2 -1
- package/dist/engine/feature-ast/render.js +3 -0
- package/dist/engine/feature-entity-handlers.d.ts +2 -1
- package/dist/engine/feature-entity-handlers.js +4 -0
- package/dist/engine/feature-ui-extensions.js +9 -0
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/membership-roles.js +5 -1
- package/dist/engine/pattern-library/mixed-schemas.js +5 -0
- package/dist/engine/registry-state.js +38 -5
- package/dist/engine/registry-validate.js +2 -0
- package/dist/engine/steps/unsafe-projection-upsert.js +1 -1
- package/dist/engine/tier-resolver-extension.d.ts +2 -0
- package/dist/engine/types/index.d.ts +2 -2
- package/dist/entrypoint/index.js +25 -6
- package/dist/errors/classes.d.ts +5 -0
- package/dist/errors/classes.js +13 -0
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/files/file-routes.js +7 -2
- package/dist/files/index.d.ts +1 -0
- package/dist/files/index.js +1 -0
- package/dist/files/provider-resolver.d.ts +2 -0
- package/dist/files/provider-resolver.js +16 -1
- package/dist/files/resolve-content-type.d.ts +11 -0
- package/dist/files/resolve-content-type.js +71 -0
- package/dist/files/types.d.ts +1 -1
- package/dist/files/types.js +5 -1
- package/dist/jobs/job-runner.js +1 -1
- package/dist/pipeline/dispatch-shared.d.ts +4 -1
- package/dist/pipeline/dispatch-shared.js +70 -9
- package/dist/pipeline/dispatch-write.js +35 -10
- package/dist/pipeline/dispatcher.d.ts +2 -0
- package/dist/pipeline/dispatcher.js +17 -1
- package/dist/pipeline/projection-rebuild.js +1 -1
- package/dist/rate-limit/bucket.d.ts +4 -0
- package/dist/rate-limit/bucket.js +25 -0
- package/dist/rate-limit/index.d.ts +2 -2
- package/dist/rate-limit/index.js +2 -2
- package/dist/rate-limit/middleware.d.ts +9 -0
- package/dist/rate-limit/middleware.js +33 -2
- package/dist/rate-limit/resolver.js +2 -1
- package/dist/redis/cache-sync-bus.d.ts +24 -0
- package/dist/redis/cache-sync-bus.js +152 -0
- package/dist/redis/cache-sync-topics.d.ts +17 -0
- package/dist/redis/cache-sync-topics.js +22 -0
- package/dist/redis/index.d.ts +4 -0
- package/dist/redis/index.js +2 -0
- package/dist/redis/pubsub-signal.d.ts +1 -0
- package/dist/redis/pubsub-signal.js +28 -0
- package/dist/stack/redis.d.ts +6 -1
- package/dist/stack/redis.js +7 -2
- package/dist/stack/test-stack.d.ts +15 -0
- package/dist/stack/test-stack.js +21 -1
- package/package.json +4 -4
- package/src/changes.json +54 -0
package/dist/db/query-api.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { SelectOptions, WhereObject, WhereOperator, WhereValue, } from "../db/query.js";
|
|
1
|
+
export type { JsonTextMatch, SelectOptions, WhereObject, WhereOperator, WhereValue, } from "../db/query.js";
|
|
2
2
|
export { aggregateWhere, asRawClient, countWhere, deleteMany, fetchOne, insertMany, insertOne, runInSavepoint, selectMany, transaction, updateMany, } from "../db/query.js";
|
package/dist/db/query.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { type AnyDb, aggregateWhere, asEntityTableMeta, asRawClient, coerceRow, countWhere, type DeleteManyBatchedOptions, type DeleteManyBatchedResult, deleteMany, deleteManyBatched, extractTableInfo, fetchOne, type IncrementCounterOptions, incrementCounter, insertMany, insertOne, type OrderByClause, runInNewTransaction, runInSavepoint, runInSavepointIfSupported, type SelectOptions, selectMany, type TableInfo, transaction, type UpsertOnConflictOptions, unsafeReadRetrying, updateMany, upsertByPk, upsertOnConflict, type WhereObject, type WhereOperator, type WhereValue, } from "../bun-db/query.js";
|
|
1
|
+
export { type AnyDb, aggregateWhere, asEntityTableMeta, asRawClient, coerceRow, countWhere, type DeleteManyBatchedOptions, type DeleteManyBatchedResult, deleteMany, deleteManyBatched, extractTableInfo, fetchOne, type IncrementCounterOptions, incrementCounter, insertMany, insertOne, type JsonTextMatch, type OrderByClause, runInNewTransaction, runInSavepoint, runInSavepointIfSupported, type SelectOptions, selectMany, type TableInfo, transaction, type UpsertOnConflictOptions, unsafeReadRetrying, updateMany, upsertByPk, upsertOnConflict, type WhereObject, type WhereOperator, type WhereValue, } from "../bun-db/query.js";
|
|
@@ -6,6 +6,7 @@ export declare const PII_USER_REFERENCE_NAME_HINTS: ReadonlySet<string>;
|
|
|
6
6
|
/** Extensions with preSave must target an entity that has mapped write handlers. */
|
|
7
7
|
export declare function validateExtensionPreSaveWiring(features: readonly FeatureDefinition[]): void;
|
|
8
8
|
export declare function validateHandlerAccess(feature: FeatureDefinition): void;
|
|
9
|
+
export declare function validateAdditionalRateLimits(featureName: string, handlerName: string, handler: FeatureDefinition["writeHandlers"][string]): void;
|
|
9
10
|
export declare function validateTenantlessAnonymous(featureName: string, handlerName: string, handler: FeatureDefinition["writeHandlers"][string]): void;
|
|
10
11
|
export declare function validateRateLimitDisabledReason(featureName: string, kind: "write" | "query" | "stream", handlerName: string, rateLimit: FeatureDefinition["writeHandlers"][string]["rateLimit"]): void;
|
|
11
12
|
export declare function validateNoAllRoleInHandlerAccess(featureName: string, kind: "write" | "query" | "stream", handlerName: string, access: NonNullable<FeatureDefinition["writeHandlers"][string]["access"]>): void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isRateLimitDisabled } from "@cosmicdrift/kumiko-types/handlers";
|
|
2
|
+
import { ZodObject } from "zod";
|
|
2
3
|
import { VARIANT_NAME_PATTERN } from "../../derivatives/variant-key.js";
|
|
3
4
|
import { access as accessPresets } from "../config-helpers.js";
|
|
4
5
|
import { parseRefTarget } from "../parse-ref-target.js";
|
|
@@ -136,6 +137,53 @@ export function validateHandlerAccess(feature) {
|
|
|
136
137
|
}
|
|
137
138
|
for (const [name, handler] of Object.entries(feature.writeHandlers)) {
|
|
138
139
|
validateTenantlessAnonymous(feature.name, name, handler);
|
|
140
|
+
validateAdditionalRateLimits(feature.name, name, handler);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const IP_BUCKETED_RATE_LIMIT_PER = new Set(["ip", "ip+handler"]);
|
|
144
|
+
// Payload buckets only complement the per-IP bucket: an anonymous caller
|
|
145
|
+
// who rotates IPs is what they exist for, and without the IP bucket a single
|
|
146
|
+
// IP could still burn through unlimited distinct addresses.
|
|
147
|
+
export function validateAdditionalRateLimits(featureName, handlerName, handler) {
|
|
148
|
+
const { additionalRateLimits } = handler;
|
|
149
|
+
// skip: handler declares no payload buckets
|
|
150
|
+
if (additionalRateLimits === undefined)
|
|
151
|
+
return;
|
|
152
|
+
const where = `write handler "${featureName}:write:${handlerName}" declares additionalRateLimits`;
|
|
153
|
+
if (additionalRateLimits.length === 0) {
|
|
154
|
+
throw new Error(`${where} as an empty array — declare at least one bucket or drop the option.`);
|
|
155
|
+
}
|
|
156
|
+
validateAnonymousHasIpBucket(where, handler);
|
|
157
|
+
const shape = handler.schema instanceof ZodObject ? handler.schema.shape : undefined;
|
|
158
|
+
for (const entry of additionalRateLimits) {
|
|
159
|
+
validatePayloadBucket(where, shape, entry);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function validateAnonymousHasIpBucket(where, handler) {
|
|
163
|
+
const { access, rateLimit } = handler;
|
|
164
|
+
const allowsAnonymous = "roles" in access && access.roles.includes("anonymous");
|
|
165
|
+
// skip: only anonymous callers need an ip bucket
|
|
166
|
+
if (!allowsAnonymous)
|
|
167
|
+
return;
|
|
168
|
+
const hasIpBucket = rateLimit !== undefined &&
|
|
169
|
+
!isRateLimitDisabled(rateLimit) &&
|
|
170
|
+
IP_BUCKETED_RATE_LIMIT_PER.has(rateLimit.per);
|
|
171
|
+
if (!hasIpBucket) {
|
|
172
|
+
throw new Error(`${where} and allows anonymous callers, but has no ip-keyed rateLimit ` +
|
|
173
|
+
`(per: "ip" or "ip+handler", not disabled). Payload buckets only complement the IP bucket.`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function validatePayloadBucket(where, shape, entry) {
|
|
177
|
+
const field = entry.per.payloadField;
|
|
178
|
+
if (shape === undefined || !Object.hasOwn(shape, field)) {
|
|
179
|
+
throw new Error(`${where} with payloadField "${field}", which is not a field of the handler's schema ` +
|
|
180
|
+
`(the schema must be a Zod object containing it).`);
|
|
181
|
+
}
|
|
182
|
+
if (!Number.isInteger(entry.limit) || entry.limit <= 0) {
|
|
183
|
+
throw new Error(`${where} for "${field}" with limit ${entry.limit} — must be a positive integer.`);
|
|
184
|
+
}
|
|
185
|
+
if (!Number.isInteger(entry.windowSeconds) || entry.windowSeconds <= 0) {
|
|
186
|
+
throw new Error(`${where} for "${field}" with windowSeconds ${entry.windowSeconds} — must be a positive integer.`);
|
|
139
187
|
}
|
|
140
188
|
}
|
|
141
189
|
export function validateTenantlessAnonymous(featureName, handlerName, handler) {
|
|
@@ -50,6 +50,7 @@ export function defineWriteHandler(def,
|
|
|
50
50
|
unsafeSkipTransitionGuard: def.unsafeSkipTransitionGuard,
|
|
51
51
|
}),
|
|
52
52
|
...(def.rateLimit && { rateLimit: def.rateLimit }),
|
|
53
|
+
...(def.additionalRateLimits && { additionalRateLimits: def.additionalRateLimits }),
|
|
53
54
|
...(def.escapeHatch && { escapeHatch: def.escapeHatch }),
|
|
54
55
|
...(def.tenantlessAnonymous && { tenantlessAnonymous: true }),
|
|
55
56
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CallExpression, SourceFile } from "ts-morph";
|
|
2
|
-
import type { AccessRule, AgentHandlerHints, AgentRisk, EscapeHatchDeclaration, RateLimitDeclaration } from "../../types/handlers.js";
|
|
2
|
+
import type { AccessRule, AgentHandlerHints, AgentRisk, EscapeHatchDeclaration, PayloadRateLimitOption, RateLimitDeclaration } from "../../types/handlers.js";
|
|
3
3
|
import type { QueryHandlerPattern, StreamHandlerPattern, WriteHandlerPattern } from "../patterns.js";
|
|
4
4
|
import type { SourceLocation } from "../source-location.js";
|
|
5
5
|
import { type ExtractOutput, type RawRefSentinel } from "./shared.js";
|
|
@@ -12,6 +12,7 @@ export type ParsedHandlerCall = {
|
|
|
12
12
|
readonly description?: string;
|
|
13
13
|
readonly agent?: AgentHandlerHints | RawRefSentinel;
|
|
14
14
|
readonly rateLimit?: RateLimitDeclaration | RawRefSentinel;
|
|
15
|
+
readonly additionalRateLimits?: readonly PayloadRateLimitOption[] | RawRefSentinel;
|
|
15
16
|
readonly unsafeSkipTransitionGuard?: boolean;
|
|
16
17
|
readonly escapeHatch?: EscapeHatchDeclaration | RawRefSentinel;
|
|
17
18
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ENTITY_CONVENTION_QUERY_BRAND } from "@cosmicdrift/kumiko-types/handlers";
|
|
2
2
|
import { SyntaxKind } from "ts-morph";
|
|
3
3
|
import { sourceLocationFromNode } from "../source-location.js";
|
|
4
|
-
import { readHeaderValueOrRaw, readOptionalAccessRule, readOptionalEscapeHatch, readOptionalRateLimit, } from "./hooks.js";
|
|
4
|
+
import { readHeaderValueOrRaw, readOptionalAccessRule, readOptionalAdditionalRateLimits, readOptionalEscapeHatch, readOptionalRateLimit, } from "./hooks.js";
|
|
5
5
|
import { fail, findFunctionLiteral, isPlainObject, isRawRefSentinel, ok, readBooleanProperty, readDataLiteralNode, readNameLiteral, readObjectPropertyInitializer, readPropertyKey, } from "./shared.js";
|
|
6
6
|
export const AGENT_RISK_VALUES = ["low", "mid", "high"];
|
|
7
7
|
function isAgentRisk(value) {
|
|
@@ -31,6 +31,7 @@ const WRITE_HANDLER_KEY_KINDS = {
|
|
|
31
31
|
agent: "modeled",
|
|
32
32
|
unsafeSkipTransitionGuard: "modeled",
|
|
33
33
|
rateLimit: "modeled",
|
|
34
|
+
additionalRateLimits: "modeled",
|
|
34
35
|
escapeHatch: "modeled",
|
|
35
36
|
tenantlessAnonymous: "opaque",
|
|
36
37
|
perform: "opaque",
|
|
@@ -176,6 +177,9 @@ function readHandlerHeaderFields(obj, methodName, sourceFile) {
|
|
|
176
177
|
const rateLimitResult = readHeaderField(readObjectPropertyInitializer(obj, "rateLimit"), readOptionalRateLimit, methodName, sourceFile, "rateLimit must be { per, limit, windowSeconds } or { disabled: true, reason }, or a reference to one");
|
|
177
178
|
if (rateLimitResult.kind === "error")
|
|
178
179
|
return rateLimitResult;
|
|
180
|
+
const additionalRateLimitsResult = readHeaderField(readObjectPropertyInitializer(obj, "additionalRateLimits"), readOptionalAdditionalRateLimits, methodName, sourceFile, "additionalRateLimits must be an array of { per: { payloadField }, limit, windowSeconds }, or a reference to one");
|
|
181
|
+
if (additionalRateLimitsResult.kind === "error")
|
|
182
|
+
return additionalRateLimitsResult;
|
|
179
183
|
const escapeHatchResult = readHeaderField(readObjectPropertyInitializer(obj, "escapeHatch"), readOptionalEscapeHatch, methodName, sourceFile, "escapeHatch must be { reason: string }, or a reference to one");
|
|
180
184
|
if (escapeHatchResult.kind === "error")
|
|
181
185
|
return escapeHatchResult;
|
|
@@ -185,6 +189,9 @@ function readHandlerHeaderFields(obj, methodName, sourceFile) {
|
|
|
185
189
|
return ok({
|
|
186
190
|
...(accessResult.pattern !== undefined && { access: accessResult.pattern }),
|
|
187
191
|
...(rateLimitResult.pattern !== undefined && { rateLimit: rateLimitResult.pattern }),
|
|
192
|
+
...(additionalRateLimitsResult.pattern !== undefined && {
|
|
193
|
+
additionalRateLimits: additionalRateLimitsResult.pattern,
|
|
194
|
+
}),
|
|
188
195
|
...(escapeHatchResult.pattern !== undefined && { escapeHatch: escapeHatchResult.pattern }),
|
|
189
196
|
...(agentResult.pattern !== undefined && { agent: agentResult.pattern }),
|
|
190
197
|
});
|
|
@@ -307,6 +314,9 @@ export function extractWriteHandler(call, sourceFile) {
|
|
|
307
314
|
...(parsed.pattern.description !== undefined && { description: parsed.pattern.description }),
|
|
308
315
|
...(parsed.pattern.agent !== undefined && { agent: parsed.pattern.agent }),
|
|
309
316
|
...(parsed.pattern.rateLimit !== undefined && { rateLimit: parsed.pattern.rateLimit }),
|
|
317
|
+
...(parsed.pattern.additionalRateLimits !== undefined && {
|
|
318
|
+
additionalRateLimits: parsed.pattern.additionalRateLimits,
|
|
319
|
+
}),
|
|
310
320
|
...(parsed.pattern.unsafeSkipTransitionGuard === true && { unsafeSkipTransitionGuard: true }),
|
|
311
321
|
...(parsed.pattern.escapeHatch !== undefined && { escapeHatch: parsed.pattern.escapeHatch }),
|
|
312
322
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CallExpression, Node, SourceFile } from "ts-morph";
|
|
2
2
|
import type { LifecycleHookType } from "../../constants.js";
|
|
3
|
-
import type { AccessRule, EscapeHatchDeclaration, RateLimitDeclaration } from "../../types/handlers.js";
|
|
3
|
+
import type { AccessRule, EscapeHatchDeclaration, PayloadRateLimitOption, RateLimitDeclaration } from "../../types/handlers.js";
|
|
4
4
|
import type { HookPhase } from "../../types/hooks.js";
|
|
5
5
|
import type { AuthClaimsPattern, HookPattern } from "../patterns.js";
|
|
6
6
|
import { type ExtractOutput, type RawRefSentinel } from "./shared.js";
|
|
@@ -18,6 +18,7 @@ export declare function isHookType(value: string): value is LifecycleHookType |
|
|
|
18
18
|
export declare function readOptionalPhase(node: Node | undefined): HookPhase | undefined;
|
|
19
19
|
export declare function readOptionalAccessRule(value: unknown): AccessRule | undefined;
|
|
20
20
|
export declare function readOptionalRateLimit(value: unknown): RateLimitDeclaration | undefined;
|
|
21
|
+
export declare function readOptionalAdditionalRateLimits(value: unknown): readonly PayloadRateLimitOption[] | undefined;
|
|
21
22
|
export declare function readOptionalEscapeHatch(value: unknown): EscapeHatchDeclaration | undefined;
|
|
22
23
|
export declare function extractHook(call: CallExpression, sourceFile: SourceFile): ExtractOutput<HookPattern>;
|
|
23
24
|
export declare function extractAuthClaims(call: CallExpression, sourceFile: SourceFile): ExtractOutput<AuthClaimsPattern>;
|
|
@@ -71,6 +71,20 @@ export function readOptionalRateLimit(value) {
|
|
|
71
71
|
return undefined;
|
|
72
72
|
return value;
|
|
73
73
|
}
|
|
74
|
+
function isPayloadRateLimitOption(value) {
|
|
75
|
+
if (!isPlainObject(value))
|
|
76
|
+
return false;
|
|
77
|
+
const per = value["per"];
|
|
78
|
+
return (isPlainObject(per) &&
|
|
79
|
+
typeof per["payloadField"] === "string" &&
|
|
80
|
+
typeof value["limit"] === "number" &&
|
|
81
|
+
typeof value["windowSeconds"] === "number");
|
|
82
|
+
}
|
|
83
|
+
export function readOptionalAdditionalRateLimits(value) {
|
|
84
|
+
if (!Array.isArray(value) || !value.every(isPayloadRateLimitOption))
|
|
85
|
+
return undefined;
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
74
88
|
export function readOptionalEscapeHatch(value) {
|
|
75
89
|
if (!isPlainObject(value))
|
|
76
90
|
return undefined;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { extractAiClassify, extractAiExtract, extractAiGenerate, } from "./ai-steps.js";
|
|
2
2
|
export { extractDefineEvent, extractNotification, } from "./events.js";
|
|
3
3
|
export { extractQueryHandler, extractStreamHandler, extractWriteHandler, type ParsedHandlerCall, parseHandlerCall, } from "./handlers.js";
|
|
4
|
-
export { extractAuthClaims, extractHook, isHookType, readOptionalAccessRule, readOptionalPhase, readOptionalRateLimit, } from "./hooks.js";
|
|
4
|
+
export { extractAuthClaims, extractHook, isHookType, readOptionalAccessRule, readOptionalAdditionalRateLimits, readOptionalPhase, readOptionalRateLimit, } from "./hooks.js";
|
|
5
5
|
export { extractHttpRoute, extractJob, isHttpRouteMethod, } from "./jobs-routes.js";
|
|
6
6
|
export { collectScreenOpaqueProps, extractMultiStreamProjection, extractProjection, extractScreen, readApplyBodies, readScreenStatic, } from "./projections-screens.js";
|
|
7
7
|
export { findImportBindingForLocalName, resolveModuleFile, } from "./resolve-import.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { extractAiClassify, extractAiExtract, extractAiGenerate, } from "./ai-steps.js";
|
|
2
2
|
export { extractDefineEvent, extractNotification, } from "./events.js";
|
|
3
3
|
export { extractQueryHandler, extractStreamHandler, extractWriteHandler, parseHandlerCall, } from "./handlers.js";
|
|
4
|
-
export { extractAuthClaims, extractHook, isHookType, readOptionalAccessRule, readOptionalPhase, readOptionalRateLimit, } from "./hooks.js";
|
|
4
|
+
export { extractAuthClaims, extractHook, isHookType, readOptionalAccessRule, readOptionalAdditionalRateLimits, readOptionalPhase, readOptionalRateLimit, } from "./hooks.js";
|
|
5
5
|
export { extractHttpRoute, extractJob, isHttpRouteMethod, } from "./jobs-routes.js";
|
|
6
6
|
export { collectScreenOpaqueProps, extractMultiStreamProjection, extractProjection, extractScreen, readApplyBodies, readScreenStatic, } from "./projections-screens.js";
|
|
7
7
|
export { findImportBindingForLocalName, resolveModuleFile, } from "./resolve-import.js";
|
|
@@ -195,6 +195,16 @@ export declare const PATTERN_SCHEMAS_BY_KIND: {
|
|
|
195
195
|
}, z.core.$strict>]>, z.ZodObject<{
|
|
196
196
|
__raw: z.ZodString;
|
|
197
197
|
}, z.core.$strict>]>>;
|
|
198
|
+
additionalRateLimits: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodObject<{
|
|
199
|
+
per: z.ZodObject<{
|
|
200
|
+
payloadField: z.ZodString;
|
|
201
|
+
}, z.core.$strict>;
|
|
202
|
+
limit: z.ZodNumber;
|
|
203
|
+
windowSeconds: z.ZodNumber;
|
|
204
|
+
cost: z.ZodOptional<z.ZodNumber>;
|
|
205
|
+
}, z.core.$strict>>, z.ZodObject<{
|
|
206
|
+
__raw: z.ZodString;
|
|
207
|
+
}, z.core.$strict>]>>;
|
|
198
208
|
escapeHatch: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
199
209
|
reason: z.ZodString;
|
|
200
210
|
grants: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
@@ -707,6 +717,16 @@ export declare const patternSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
707
717
|
}, z.core.$strict>]>, z.ZodObject<{
|
|
708
718
|
__raw: z.ZodString;
|
|
709
719
|
}, z.core.$strict>]>>;
|
|
720
|
+
additionalRateLimits: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodObject<{
|
|
721
|
+
per: z.ZodObject<{
|
|
722
|
+
payloadField: z.ZodString;
|
|
723
|
+
}, z.core.$strict>;
|
|
724
|
+
limit: z.ZodNumber;
|
|
725
|
+
windowSeconds: z.ZodNumber;
|
|
726
|
+
cost: z.ZodOptional<z.ZodNumber>;
|
|
727
|
+
}, z.core.$strict>>, z.ZodObject<{
|
|
728
|
+
__raw: z.ZodString;
|
|
729
|
+
}, z.core.$strict>]>>;
|
|
710
730
|
escapeHatch: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
711
731
|
reason: z.ZodString;
|
|
712
732
|
grants: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
@@ -157,6 +157,14 @@ const rateLimitOptionSchema = z
|
|
|
157
157
|
cost: z.number().optional(),
|
|
158
158
|
})
|
|
159
159
|
.strict();
|
|
160
|
+
const additionalRateLimitsSchema = z.array(z
|
|
161
|
+
.object({
|
|
162
|
+
per: z.object({ payloadField: z.string() }).strict(),
|
|
163
|
+
limit: z.number(),
|
|
164
|
+
windowSeconds: z.number(),
|
|
165
|
+
cost: z.number().optional(),
|
|
166
|
+
})
|
|
167
|
+
.strict());
|
|
160
168
|
const agentHandlerHintsSchema = z
|
|
161
169
|
.object({
|
|
162
170
|
expose: z.boolean().optional(),
|
|
@@ -192,6 +200,7 @@ const WRITE_HANDLER_HEADER_SHAPE = {
|
|
|
192
200
|
description: z.string(),
|
|
193
201
|
agent: agentHandlerHintsSchema,
|
|
194
202
|
rateLimit: rateLimitOptionSchema,
|
|
203
|
+
additionalRateLimits: additionalRateLimitsSchema,
|
|
195
204
|
unsafeSkipTransitionGuard: z.boolean(),
|
|
196
205
|
escapeHatch: escapeHatchSchema,
|
|
197
206
|
};
|
|
@@ -238,6 +247,7 @@ const WRITE_HANDLER_PATTERN_HEADER_SHAPE = {
|
|
|
238
247
|
access: orRawRef(accessRuleSchema),
|
|
239
248
|
agent: orRawRef(agentHandlerHintsSchema),
|
|
240
249
|
rateLimit: orRawRef(z.union([rateLimitOptionSchema, rateLimitDisabledSchema])),
|
|
250
|
+
additionalRateLimits: orRawRef(additionalRateLimitsSchema),
|
|
241
251
|
escapeHatch: orRawRef(escapeHatchSchema),
|
|
242
252
|
};
|
|
243
253
|
const QUERY_HANDLER_PATTERN_HEADER_SHAPE = {
|
|
@@ -2,7 +2,7 @@ import type { LifecycleHookType } from "../constants.js";
|
|
|
2
2
|
import type { ConfigKeyDefinition, ConfigKeyType, JobDefinition, ReferenceDataDef, RunIn, TranslationKeys } from "../types/config.js";
|
|
3
3
|
import type { MetricOptions, SecretNamespaceOptions, SecretOptions } from "../types/feature.js";
|
|
4
4
|
import type { EntityDefinition } from "../types/fields.js";
|
|
5
|
-
import type { AccessRule, AgentHandlerHints, ClaimKeyType, EscapeHatchDeclaration, RateLimitDeclaration } from "../types/handlers.js";
|
|
5
|
+
import type { AccessRule, AgentHandlerHints, ClaimKeyType, EscapeHatchDeclaration, PayloadRateLimitOption, RateLimitDeclaration } from "../types/handlers.js";
|
|
6
6
|
import type { HookPhase } from "../types/hooks.js";
|
|
7
7
|
import type { HttpRouteMethod } from "../types/http-route.js";
|
|
8
8
|
import type { NavDefinition } from "../types/nav.js";
|
|
@@ -142,6 +142,7 @@ export type WriteHandlerPattern = {
|
|
|
142
142
|
readonly description?: string;
|
|
143
143
|
readonly agent?: AgentHandlerHints | RawRefSentinel;
|
|
144
144
|
readonly rateLimit?: RateLimitDeclaration | RawRefSentinel;
|
|
145
|
+
readonly additionalRateLimits?: readonly PayloadRateLimitOption[] | RawRefSentinel;
|
|
145
146
|
readonly unsafeSkipTransitionGuard?: boolean;
|
|
146
147
|
readonly escapeHatch?: EscapeHatchDeclaration | RawRefSentinel;
|
|
147
148
|
};
|
|
@@ -358,6 +358,9 @@ function renderWriteHandler(p) {
|
|
|
358
358
|
lines.push(` agent: ${renderValue(p.agent)},`);
|
|
359
359
|
if (p.rateLimit !== undefined)
|
|
360
360
|
lines.push(` rateLimit: ${renderValue(p.rateLimit)},`);
|
|
361
|
+
if (p.additionalRateLimits !== undefined) {
|
|
362
|
+
lines.push(` additionalRateLimits: ${renderValue(p.additionalRateLimits)},`);
|
|
363
|
+
}
|
|
361
364
|
if (p.unsafeSkipTransitionGuard === true)
|
|
362
365
|
lines.push(" unsafeSkipTransitionGuard: true,");
|
|
363
366
|
if (p.escapeHatch !== undefined)
|
|
@@ -3,7 +3,7 @@ import type { ZodType } from "zod";
|
|
|
3
3
|
import { type QueryHandlerDefinition, type StreamHandlerDefinition, type WriteHandlerDefinition } from "./define-handler.js";
|
|
4
4
|
import type { RegisterEntityCrudOptions } from "./entity-handlers.js";
|
|
5
5
|
import type { FeatureBuilderState } from "./feature-builder-state.js";
|
|
6
|
-
import type { AccessRule, AgentHandlerHints, EntityDefinition, EntityRef, EscapeHatchDeclaration, HandlerRef, NameOrRef, QueryHandlerFn, RateLimitDeclaration, RelationDefinition, StreamHandlerFn, WriteHandlerFn } from "./types/index.js";
|
|
6
|
+
import type { AccessRule, AgentHandlerHints, EntityDefinition, EntityRef, EscapeHatchDeclaration, HandlerRef, NameOrRef, PayloadRateLimitOption, QueryHandlerFn, RateLimitDeclaration, RelationDefinition, StreamHandlerFn, WriteHandlerFn } from "./types/index.js";
|
|
7
7
|
export declare function buildEntityHandlerMethods<TName extends string>(state: FeatureBuilderState, name: TName): {
|
|
8
8
|
entity(nameOrDefinition: string | ({
|
|
9
9
|
readonly name: string;
|
|
@@ -17,6 +17,7 @@ export declare function buildEntityHandlerMethods<TName extends string>(state: F
|
|
|
17
17
|
writeHandler<TName_1 extends string, TSchema extends ZodType>(nameOrDef: string | WriteHandlerDefinition<TName_1, TSchema>, schema?: TSchema, handler?: WriteHandlerFn<z.infer<TSchema>>, options?: {
|
|
18
18
|
access: AccessRule;
|
|
19
19
|
rateLimit?: RateLimitDeclaration;
|
|
20
|
+
additionalRateLimits?: readonly PayloadRateLimitOption[];
|
|
20
21
|
description?: string;
|
|
21
22
|
agent?: AgentHandlerHints;
|
|
22
23
|
escapeHatch?: EscapeHatchDeclaration;
|
|
@@ -105,6 +105,7 @@ export function buildEntityHandlerMethods(state, name) {
|
|
|
105
105
|
...agentSlots(def),
|
|
106
106
|
...(def.unsafeSkipTransitionGuard && { unsafeSkipTransitionGuard: true }),
|
|
107
107
|
...(def.rateLimit && { rateLimit: def.rateLimit }),
|
|
108
|
+
...(def.additionalRateLimits && { additionalRateLimits: def.additionalRateLimits }),
|
|
108
109
|
...(def.escapeHatch && { escapeHatch: def.escapeHatch }),
|
|
109
110
|
...(def.tenantlessAnonymous && { tenantlessAnonymous: true }),
|
|
110
111
|
// Forward the pipeline-build closure so boot-validators and
|
|
@@ -129,6 +130,9 @@ export function buildEntityHandlerMethods(state, name) {
|
|
|
129
130
|
access: inline.options.access,
|
|
130
131
|
...agentSlots(inline.options),
|
|
131
132
|
...(inline.options.rateLimit && { rateLimit: inline.options.rateLimit }),
|
|
133
|
+
...(inline.options.additionalRateLimits && {
|
|
134
|
+
additionalRateLimits: inline.options.additionalRateLimits,
|
|
135
|
+
}),
|
|
132
136
|
...(inline.options.escapeHatch && { escapeHatch: inline.options.escapeHatch }),
|
|
133
137
|
};
|
|
134
138
|
tryMapEntity(state, name, nameOrDef);
|
|
@@ -421,6 +421,15 @@ export function buildUiExtensionsMethods(state, name) {
|
|
|
421
421
|
throw new Error(`[Feature ${name}] httpRoute "${definition.method} ${definition.path}" must declare ` +
|
|
422
422
|
`anonymous: true | false — true mounts it public, false behind the session auth chain.`);
|
|
423
423
|
}
|
|
424
|
+
const routeLimit = definition.rateLimit;
|
|
425
|
+
if (routeLimit !== undefined &&
|
|
426
|
+
(!Number.isInteger(routeLimit.limit) ||
|
|
427
|
+
routeLimit.limit < 1 ||
|
|
428
|
+
!Number.isInteger(routeLimit.windowSeconds) ||
|
|
429
|
+
routeLimit.windowSeconds < 1)) {
|
|
430
|
+
throw new Error(`[Feature ${name}] httpRoute "${definition.method} ${definition.path}" rateLimit needs ` +
|
|
431
|
+
`positive integer limit and windowSeconds.`);
|
|
432
|
+
}
|
|
424
433
|
const key = `${definition.method} ${definition.path}`;
|
|
425
434
|
if (state.httpRoutes[key]) {
|
|
426
435
|
throw new Error(`[Feature ${name}] HTTP-Route "${key}" already registered. ` +
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -74,7 +74,7 @@ export { WORKFLOW_SYSTEM_EVENT_DEFS } from "./steps/workflow-system-events.js";
|
|
|
74
74
|
export { ANONYMOUS_ROLE, ANONYMOUS_USER_ID, createAnonymousUser, createSystemUser, SYSTEM_ROLE, SYSTEM_USER_ID, } from "./system-user.js";
|
|
75
75
|
export { type EffectiveFeaturesResolver, findTierResolverUsage, isTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, type TierResolverPlugin, type TrialGate, } from "./tier-resolver-extension.js";
|
|
76
76
|
export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.js";
|
|
77
|
-
export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, CamelToKebab, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValidateContext, ConfigValidateFn, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardFeedPanel, DashboardFilterDefinition, DashboardListPanel, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DateFieldDef, DecimalFieldDef, DeleteContext, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EntityDefinition, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, EntityRef, EntityRelations, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, ExtensionOptionsArgs, ExtensionOptionsFor, FeatureDefinition, FeatureRegistrar, FieldAccess, FieldCondition, FieldDefinition, FieldFormatRegistry, FieldIconKey, FieldRenderer, FileFieldDef, FilesFieldDef, Findability, FormatSpec, FormWidth, HandlerContext, HasManyRelation, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationRecipientFn, NotificationTemplateFn, NotifyFactory, NotifyFn, NotifyOptions, NotifyPriority, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, 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";
|
|
77
|
+
export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, CamelToKebab, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValidateContext, ConfigValidateFn, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardFeedPanel, DashboardFilterDefinition, DashboardListPanel, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DateFieldDef, DecimalFieldDef, DeleteContext, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EntityDefinition, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, EntityRef, EntityRelations, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, ExtensionOptionsArgs, ExtensionOptionsFor, FeatureDefinition, FeatureRegistrar, FieldAccess, FieldCondition, FieldDefinition, FieldFormatRegistry, FieldIconKey, FieldRenderer, FileFieldDef, FilesFieldDef, Findability, FormatSpec, FormWidth, HandlerContext, HasManyRelation, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationRecipientFn, NotificationTemplateFn, 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";
|
|
78
78
|
export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, HookPhases, isAgentVisibleScreen, isOpenToAllGranted, resolveAgentExposure, } from "./types/index.js";
|
|
79
79
|
export type { AwaitedEventType, PipelineBuildCtx, PipelineCtx, PipelineDef, StepBuilder, StepDef, StepFailureStrategy, StepInstance, StepKind, StepNamespace, StepResolver, } from "./types/step.js";
|
|
80
80
|
export { runValidation } from "./validation.js";
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// matching strip (anonymous/all only) to globalRoles, where SystemAdmin
|
|
11
11
|
// legitimately lives.
|
|
12
12
|
import { access } from "./config-helpers.js";
|
|
13
|
+
import { SYSTEM_ROLE } from "./system-user.js";
|
|
13
14
|
export const FORBIDDEN_MEMBERSHIP_ROLES = new Set([
|
|
14
15
|
...access.privileged, // system, SystemAdmin
|
|
15
16
|
...access.all, // all
|
|
@@ -26,10 +27,13 @@ export function findForbiddenMembershipRole(roles) {
|
|
|
26
27
|
export function stripForbiddenMembershipRoles(roles) {
|
|
27
28
|
return roles.filter((role) => !isForbiddenMembershipRole(role));
|
|
28
29
|
}
|
|
29
|
-
// "anonymous"/"all" are never legitimate global roles;
|
|
30
|
+
// "anonymous"/"all" are never legitimate global roles; SystemAdmin stays.
|
|
31
|
+
// "system" is the in-process identity (system-identity-switch treats any user
|
|
32
|
+
// carrying it as System) and must never end up in a session.
|
|
30
33
|
const NON_MINTABLE_GLOBAL_ROLES = new Set([
|
|
31
34
|
...access.all,
|
|
32
35
|
...access.anonymous,
|
|
36
|
+
SYSTEM_ROLE,
|
|
33
37
|
]);
|
|
34
38
|
function stripNonMintableGlobalRoles(roles) {
|
|
35
39
|
return roles.filter((role) => !NON_MINTABLE_GLOBAL_ROLES.has(role));
|
|
@@ -78,6 +78,11 @@ export const writeHandlerSchema = {
|
|
|
78
78
|
label: { en: "Rate limit", de: "Rate-Limit" },
|
|
79
79
|
input: "json-readonly",
|
|
80
80
|
},
|
|
81
|
+
{
|
|
82
|
+
path: "additionalRateLimits",
|
|
83
|
+
label: { en: "Per-payload rate limits", de: "Payload-Rate-Limits" },
|
|
84
|
+
input: "json-readonly",
|
|
85
|
+
},
|
|
81
86
|
{
|
|
82
87
|
path: "unsafeSkipTransitionGuard",
|
|
83
88
|
label: { en: "Skip transition guard", de: "Übergangs-Guard überspringen" },
|
|
@@ -43,7 +43,6 @@ export function buildImplicitProjection(featureName, entityName, entity, qualify
|
|
|
43
43
|
// r.extendEntityProjection: merge extension applies into the rebuild
|
|
44
44
|
// replay. Collisions with lifecycle applies (or another extension) are
|
|
45
45
|
// authoring bugs — fail at boot, not by silently overwriting a handler.
|
|
46
|
-
const extraSources = [];
|
|
47
46
|
for (const ext of extensions) {
|
|
48
47
|
for (const [eventType, fn] of Object.entries(ext.apply)) {
|
|
49
48
|
if (apply[eventType]) {
|
|
@@ -52,20 +51,54 @@ export function buildImplicitProjection(featureName, entityName, entity, qualify
|
|
|
52
51
|
}
|
|
53
52
|
apply[eventType] = fn;
|
|
54
53
|
}
|
|
55
|
-
for (const s of ext.sources ?? []) {
|
|
56
|
-
if (s !== entityName && !extraSources.includes(s))
|
|
57
|
-
extraSources.push(s);
|
|
58
|
-
}
|
|
59
54
|
}
|
|
55
|
+
const { extraSources, extraSourceRowIds } = collectExtensionSources(name, entityName, extensions);
|
|
60
56
|
return {
|
|
61
57
|
name,
|
|
62
58
|
source: entityName,
|
|
63
59
|
...(extraSources.length > 0 && { extraSources }),
|
|
60
|
+
...(Object.keys(extraSourceRowIds).length > 0 && { extraSourceRowIds }),
|
|
64
61
|
table: drizzleTable,
|
|
65
62
|
apply,
|
|
66
63
|
isImplicit: true,
|
|
67
64
|
};
|
|
68
65
|
}
|
|
66
|
+
function assertRowIdOfSources(projectionName, entityName, ext) {
|
|
67
|
+
// skip: extension maps no rowIdOf, nothing to validate
|
|
68
|
+
if (ext.rowIdOf === undefined)
|
|
69
|
+
return;
|
|
70
|
+
const sources = ext.sources ?? [];
|
|
71
|
+
if (sources.length === 0) {
|
|
72
|
+
throw new Error(`Implicit projection "${projectionName}": extendEntityProjection declares rowIdOf without ` +
|
|
73
|
+
`\`sources\` — rowIdOf maps events of the declared sources to row ids.`);
|
|
74
|
+
}
|
|
75
|
+
if (sources.includes(entityName)) {
|
|
76
|
+
throw new Error(`Implicit projection "${projectionName}": extendEntityProjection rowIdOf cannot apply to the ` +
|
|
77
|
+
`entity's own stream "${entityName}" — host-stream rows are keyed by the aggregate id.`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function collectExtensionSources(projectionName, entityName, extensions) {
|
|
81
|
+
const extraSources = [];
|
|
82
|
+
const extraSourceRowIds = {};
|
|
83
|
+
const rowIdDeclaredBy = new Map();
|
|
84
|
+
for (const ext of extensions) {
|
|
85
|
+
for (const s of ext.sources ?? []) {
|
|
86
|
+
if (s !== entityName && !extraSources.includes(s))
|
|
87
|
+
extraSources.push(s);
|
|
88
|
+
}
|
|
89
|
+
assertRowIdOfSources(projectionName, entityName, ext);
|
|
90
|
+
for (const s of ext.sources ?? []) {
|
|
91
|
+
if (rowIdDeclaredBy.has(s) && rowIdDeclaredBy.get(s) !== ext.rowIdOf) {
|
|
92
|
+
throw new Error(`Implicit projection "${projectionName}": source "${s}" is declared by several extensions with ` +
|
|
93
|
+
`different or missing rowIdOf — the row id for its events is ambiguous.`);
|
|
94
|
+
}
|
|
95
|
+
rowIdDeclaredBy.set(s, ext.rowIdOf);
|
|
96
|
+
if (ext.rowIdOf !== undefined)
|
|
97
|
+
extraSourceRowIds[s] = ext.rowIdOf;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { extraSources, extraSourceRowIds };
|
|
101
|
+
}
|
|
69
102
|
// Validates a r.entity backing table is a superset of the entity's field-
|
|
70
103
|
// derived columns, then hands it back as the projection table. The cast is a
|
|
71
104
|
// system-boundary reconstitution: the table is stored as `unknown` on
|
|
@@ -655,6 +655,8 @@ export function computeHasRateLimitedHandler(state) {
|
|
|
655
655
|
for (const h of state.writeHandlerMap.values()) {
|
|
656
656
|
if (h.rateLimit !== undefined && !isRateLimitDisabled(h.rateLimit))
|
|
657
657
|
return true;
|
|
658
|
+
if (h.additionalRateLimits !== undefined)
|
|
659
|
+
return true;
|
|
658
660
|
}
|
|
659
661
|
for (const h of state.queryHandlerMap.values()) {
|
|
660
662
|
if (h.rateLimit !== undefined && !isRateLimitDisabled(h.rateLimit))
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// natural key + tenantId). Skips lifecycle hooks, field-access,
|
|
5
5
|
// crypto-shredding, schema-versioning, audit-trail, read-access-log.
|
|
6
6
|
// See "Was unsafeProjection.* überspringt" in
|
|
7
|
-
// docs/plans/architecture/intern/step-vocabulary.md.
|
|
7
|
+
// kumiko-platform/docs/archive/plans/architecture/intern/step-vocabulary.md.
|
|
8
8
|
//
|
|
9
9
|
// Use only on tables explicitly declared via r.requires.projection in
|
|
10
10
|
// the owning feature. Aggregate-tables (registered via r.entity) are
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CacheSyncBus } from "@cosmicdrift/kumiko-types/cache-sync-types";
|
|
1
2
|
import type { DbConnection } from "../db/connection.js";
|
|
2
3
|
import type { RegistrarExtensionRegistration } from "./types/config.js";
|
|
3
4
|
import type { FeatureDefinition, Registry } from "./types/feature.js";
|
|
@@ -44,6 +45,7 @@ export type TierResolverPlugin = {
|
|
|
44
45
|
readonly build: (deps: {
|
|
45
46
|
readonly db: DbConnection;
|
|
46
47
|
readonly registry: Registry;
|
|
48
|
+
readonly cacheSync?: CacheSyncBus;
|
|
47
49
|
}) => Promise<EffectiveFeaturesResolver>;
|
|
48
50
|
};
|
|
49
51
|
/**
|
|
@@ -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, 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, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, DeclarativeEventMigration, DispatchWriteRef, EntityRef, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, EventMigrationDef, EventPiiFields, EventPiiStance, EventUpcastCtx, EventUpcastFn, FetchForWritingArgs, HandlerContext, HandlerRef, JobContext, JobRunnerRef, JobTriggerEvent, MemberReader, NameOrRef, NotifyFactory, NotifyFn, NotifyOptions, NotifyPriority, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, 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, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, DeclarativeEventMigration, 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";
|
|
11
11
|
export { 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";
|
|
@@ -15,7 +15,7 @@ export type { EntityId, TenantId } from "@cosmicdrift/kumiko-types/identifiers";
|
|
|
15
15
|
export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-types/identifiers";
|
|
16
16
|
export type { ContentCollectionDefinition, NavDefinition, NavIconKey, } from "@cosmicdrift/kumiko-types/nav";
|
|
17
17
|
export type { FromRule, FromRuleKind, OwnershipClause, OwnershipMap, OwnershipRef, OwnershipRule, SqlFragment, WhereRule, WhereRuleContext, } from "@cosmicdrift/kumiko-types/ownership";
|
|
18
|
-
export type { EntityProjectionExtension, MspErrorMode, MspErrorPolicy, MultiStreamApplyFn, MultiStreamProjectionDefinition, ProjectionDefinition, ProjectionTable, SingleStreamApplyFn, } from "@cosmicdrift/kumiko-types/projection";
|
|
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
20
|
export type { ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardFeedPanel, DashboardFilterDefinition, DashboardListPanel, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormWidth, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, UnitKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
21
21
|
export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
|