@cosmicdrift/kumiko-framework 0.372.0 → 0.374.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/examples/rate-limiting.md +65 -1
- package/agents/widgets.md +1 -0
- package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
- package/dist/engine/boot-validator/entity-handler.js +47 -16
- package/dist/engine/boot-validator/projection-list-screens.js +28 -37
- package/dist/engine/boot-validator/query-refs.js +26 -8
- package/dist/engine/boot-validator/screens.d.ts +1 -1
- package/dist/engine/boot-validator/screens.js +327 -41
- package/dist/engine/boot-validator/url-prefill-fields.js +3 -0
- package/dist/engine/build-app-schema.js +7 -3
- package/dist/engine/entity-list-searchability.d.ts +5 -0
- package/dist/engine/entity-list-searchability.js +35 -0
- package/dist/engine/feature-ast/extractors/handlers.js +1 -1
- package/dist/engine/feature-ast/extractors/hooks.js +8 -8
- package/dist/engine/feature-ast/pattern-change-schema.d.ts +150 -6
- package/dist/engine/feature-ast/pattern-change-schema.js +4 -3
- package/dist/engine/feature-ui-extensions.js +5 -2
- package/dist/engine/index.d.ts +2 -1
- package/dist/engine/index.js +1 -0
- package/dist/engine/paged-list.d.ts +29 -0
- package/dist/engine/paged-list.js +56 -0
- package/dist/engine/project-app-schema-for-roles.js +37 -6
- package/dist/engine/types/index.d.ts +3 -3
- package/dist/engine/types/index.js +1 -1
- package/dist/files/bind-pending-file-refs.d.ts +31 -1
- package/dist/files/bind-pending-file-refs.js +80 -6
- package/dist/files/index.d.ts +2 -0
- package/dist/files/index.js +1 -0
- package/dist/files/load-save-file-refs.js +3 -1
- package/dist/i18n/required-surface-keys.js +50 -2
- package/dist/pipeline/dispatch-shared.js +55 -35
- package/dist/rate-limit/index.d.ts +1 -1
- package/dist/rate-limit/resolver.d.ts +1 -1
- package/dist/rate-limit/resolver.js +104 -2
- package/dist/rate-limit/unlimited-resolver.js +1 -0
- package/dist/ui-types/index.d.ts +1 -1
- package/package.json +5 -5
- package/src/changes.json +74 -0
|
@@ -27,6 +27,7 @@ import { ZodObject } from "zod";
|
|
|
27
27
|
import { collectUrlPrefillFieldsByScreenQn } from "./boot-validator/url-prefill-fields.js";
|
|
28
28
|
import { buildConfigFeatureSchema, SETTINGS_HUB_FEATURE, } from "./build-config-feature-schema.js";
|
|
29
29
|
import { isEncryptedAtRest } from "./config-helpers.js";
|
|
30
|
+
import { isListQueryWithoutSearchableFields } from "./entity-list-searchability.js";
|
|
30
31
|
import { QnTypes, qualifyEntityName } from "./qualified-name.js";
|
|
31
32
|
export function buildAppSchema(registry, options = {}) {
|
|
32
33
|
const features = [];
|
|
@@ -341,11 +342,14 @@ function projectConfigEditScreen(screen, encryptedConfigKeyQns) {
|
|
|
341
342
|
return fields === undefined ? screen : { ...screen, fields };
|
|
342
343
|
}
|
|
343
344
|
function projectProjectionListScreen(screen, registry) {
|
|
344
|
-
const
|
|
345
|
-
const capabilities = deriveProjectionListCapabilities(schema);
|
|
345
|
+
const handler = registry.getQueryHandler(screen.query);
|
|
346
|
+
const capabilities = deriveProjectionListCapabilities(handler?.schema);
|
|
347
|
+
// A search box over an entity without searchable fields would never match.
|
|
348
|
+
const searchable = capabilities.searchable &&
|
|
349
|
+
!isListQueryWithoutSearchableFields(screen.query, handler, (name) => registry.getEntity(name));
|
|
346
350
|
return {
|
|
347
351
|
...screen,
|
|
348
|
-
searchable: screen.searchable ??
|
|
352
|
+
searchable: screen.searchable ?? searchable,
|
|
349
353
|
sortable: capabilities.sortable,
|
|
350
354
|
paginated: capabilities.paginated,
|
|
351
355
|
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { EntityDefinition, QueryHandlerDef } from "./types/index.ts";
|
|
2
|
+
export declare function resolveEntityBehindListQuery(queryQn: string, handler: QueryHandlerDef | undefined, lookupEntity: (entityName: string) => EntityDefinition | undefined): EntityDefinition | undefined;
|
|
3
|
+
/** True when the list query is an entity-convention one over an entity with no
|
|
4
|
+
* searchable field — a search box would never match anything. */
|
|
5
|
+
export declare function isListQueryWithoutSearchableFields(queryQn: string, handler: QueryHandlerDef | undefined, lookupEntity: (entityName: string) => EntityDefinition | undefined): boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ENTITY_CONVENTION_QUERY_BRAND } from "@cosmicdrift/kumiko-types/handlers";
|
|
2
|
+
import { parseQn } from "./qualified-name.js";
|
|
3
|
+
// Mirrors buildSearchableSortableCaches (registry-validate.ts): a `search`
|
|
4
|
+
// payload only matches text fields, embedded sub-fields and references that
|
|
5
|
+
// opted in with `searchable: true`.
|
|
6
|
+
function entityHasSearchableField(entity) {
|
|
7
|
+
return Object.values(entity.fields).some((field) => {
|
|
8
|
+
if (field.type === "embedded") {
|
|
9
|
+
return Object.values(field.schema).some((subField) => subField.searchable === true);
|
|
10
|
+
}
|
|
11
|
+
return (field.type === "text" || field.type === "reference") && field.searchable === true;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
// Entity-convention list queries (ENTITY_CONVENTION_QUERY_BRAND) carry no
|
|
15
|
+
// entity on the handler; the entity name is recovered from the query QN's
|
|
16
|
+
// `<entityName>:list` naming convention. Hand-written handlers resolve to
|
|
17
|
+
// undefined — "capability unknown, no throw".
|
|
18
|
+
export function resolveEntityBehindListQuery(queryQn, handler, lookupEntity) {
|
|
19
|
+
if (handler === undefined || handler[ENTITY_CONVENTION_QUERY_BRAND] !== true)
|
|
20
|
+
return undefined;
|
|
21
|
+
let entityName;
|
|
22
|
+
try {
|
|
23
|
+
entityName = parseQn(queryQn).name.split(":")[0] ?? "";
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return entityName === "" ? undefined : lookupEntity(entityName);
|
|
29
|
+
}
|
|
30
|
+
/** True when the list query is an entity-convention one over an entity with no
|
|
31
|
+
* searchable field — a search box would never match anything. */
|
|
32
|
+
export function isListQueryWithoutSearchableFields(queryQn, handler, lookupEntity) {
|
|
33
|
+
const entity = resolveEntityBehindListQuery(queryQn, handler, lookupEntity);
|
|
34
|
+
return entity !== undefined && !entityHasSearchableField(entity);
|
|
35
|
+
}
|
|
@@ -178,7 +178,7 @@ function readHandlerHeaderFields(obj, methodName, sourceFile) {
|
|
|
178
178
|
const accessResult = readHeaderField(readObjectPropertyInitializer(obj, "access"), readOptionalAccessRule, methodName, sourceFile, "access must be a recognized AccessRule ({ roles: [...] } or { openToAll: { reason } }), or a reference to one");
|
|
179
179
|
if (accessResult.kind === "error")
|
|
180
180
|
return accessResult;
|
|
181
|
-
const rateLimitResult = readHeaderField(readObjectPropertyInitializer(obj, "rateLimit"), readOptionalRateLimit, methodName, sourceFile, "rateLimit must be { per, limit, windowSeconds } or { disabled: true, reason }, or a reference to one");
|
|
181
|
+
const rateLimitResult = readHeaderField(readObjectPropertyInitializer(obj, "rateLimit"), readOptionalRateLimit, methodName, sourceFile, "rateLimit must be { per, limit, windowSeconds }, a non-empty list of those, or { disabled: true, reason }, or a reference to one");
|
|
182
182
|
if (rateLimitResult.kind === "error")
|
|
183
183
|
return rateLimitResult;
|
|
184
184
|
const additionalRateLimitsResult = readHeaderField(readObjectPropertyInitializer(obj, "additionalRateLimits"), readOptionalAdditionalRateLimits, methodName, sourceFile, "additionalRateLimits must be an array of { per: { payloadField }, limit, windowSeconds }, or a reference to one");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isEscapeHatchGrant } from "@cosmicdrift/kumiko-types/handlers";
|
|
1
|
+
import { isEscapeHatchGrant, isRateLimitOption } from "@cosmicdrift/kumiko-types/handlers";
|
|
2
2
|
import { SyntaxKind } from "ts-morph";
|
|
3
3
|
import { sourceLocationFromNode } from "../source-location.js";
|
|
4
4
|
import { containsRawRefSentinel, fail, findFunctionLiteral, isPlainObject, ok, readDataLiteralNode, readNameLiteral, readNameOrRef, readNameOrRefOrList, readObjectPropertyInitializer, } from "./shared.js";
|
|
@@ -52,6 +52,12 @@ export function readOptionalAccessRule(value) {
|
|
|
52
52
|
return undefined;
|
|
53
53
|
}
|
|
54
54
|
export function readOptionalRateLimit(value) {
|
|
55
|
+
if (Array.isArray(value)) {
|
|
56
|
+
if (!value.every(isRateLimitOption))
|
|
57
|
+
return undefined;
|
|
58
|
+
const [first, ...rest] = value;
|
|
59
|
+
return first === undefined ? undefined : [first, ...rest];
|
|
60
|
+
}
|
|
55
61
|
if (!isPlainObject(value))
|
|
56
62
|
return undefined;
|
|
57
63
|
if (value["disabled"] === true) {
|
|
@@ -63,13 +69,7 @@ export function readOptionalRateLimit(value) {
|
|
|
63
69
|
return undefined;
|
|
64
70
|
return { disabled: true, reason: value["reason"] };
|
|
65
71
|
}
|
|
66
|
-
|
|
67
|
-
return undefined;
|
|
68
|
-
if (typeof value["limit"] !== "number")
|
|
69
|
-
return undefined;
|
|
70
|
-
if (typeof value["windowSeconds"] !== "number")
|
|
71
|
-
return undefined;
|
|
72
|
-
return value;
|
|
72
|
+
return isRateLimitOption(value) ? value : undefined;
|
|
73
73
|
}
|
|
74
74
|
function isPayloadRateLimitOption(value) {
|
|
75
75
|
if (!isPlainObject(value))
|
|
@@ -189,7 +189,31 @@ export declare const PATTERN_SCHEMAS_BY_KIND: {
|
|
|
189
189
|
limit: z.ZodNumber;
|
|
190
190
|
windowSeconds: z.ZodNumber;
|
|
191
191
|
cost: z.ZodOptional<z.ZodNumber>;
|
|
192
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
192
|
+
}, z.core.$strict>, z.ZodTuple<[z.ZodObject<{
|
|
193
|
+
per: z.ZodEnum<{
|
|
194
|
+
ip: "ip";
|
|
195
|
+
"ip+handler": "ip+handler";
|
|
196
|
+
tenant: "tenant";
|
|
197
|
+
"tenant+handler": "tenant+handler";
|
|
198
|
+
user: "user";
|
|
199
|
+
"user+handler": "user+handler";
|
|
200
|
+
}>;
|
|
201
|
+
limit: z.ZodNumber;
|
|
202
|
+
windowSeconds: z.ZodNumber;
|
|
203
|
+
cost: z.ZodOptional<z.ZodNumber>;
|
|
204
|
+
}, z.core.$strict>], z.ZodObject<{
|
|
205
|
+
per: z.ZodEnum<{
|
|
206
|
+
ip: "ip";
|
|
207
|
+
"ip+handler": "ip+handler";
|
|
208
|
+
tenant: "tenant";
|
|
209
|
+
"tenant+handler": "tenant+handler";
|
|
210
|
+
user: "user";
|
|
211
|
+
"user+handler": "user+handler";
|
|
212
|
+
}>;
|
|
213
|
+
limit: z.ZodNumber;
|
|
214
|
+
windowSeconds: z.ZodNumber;
|
|
215
|
+
cost: z.ZodOptional<z.ZodNumber>;
|
|
216
|
+
}, z.core.$strict>>, z.ZodObject<{
|
|
193
217
|
disabled: z.ZodLiteral<true>;
|
|
194
218
|
reason: z.ZodString;
|
|
195
219
|
}, z.core.$strict>]>, z.ZodObject<{
|
|
@@ -257,7 +281,31 @@ export declare const PATTERN_SCHEMAS_BY_KIND: {
|
|
|
257
281
|
limit: z.ZodNumber;
|
|
258
282
|
windowSeconds: z.ZodNumber;
|
|
259
283
|
cost: z.ZodOptional<z.ZodNumber>;
|
|
260
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
284
|
+
}, z.core.$strict>, z.ZodTuple<[z.ZodObject<{
|
|
285
|
+
per: z.ZodEnum<{
|
|
286
|
+
ip: "ip";
|
|
287
|
+
"ip+handler": "ip+handler";
|
|
288
|
+
tenant: "tenant";
|
|
289
|
+
"tenant+handler": "tenant+handler";
|
|
290
|
+
user: "user";
|
|
291
|
+
"user+handler": "user+handler";
|
|
292
|
+
}>;
|
|
293
|
+
limit: z.ZodNumber;
|
|
294
|
+
windowSeconds: z.ZodNumber;
|
|
295
|
+
cost: z.ZodOptional<z.ZodNumber>;
|
|
296
|
+
}, z.core.$strict>], z.ZodObject<{
|
|
297
|
+
per: z.ZodEnum<{
|
|
298
|
+
ip: "ip";
|
|
299
|
+
"ip+handler": "ip+handler";
|
|
300
|
+
tenant: "tenant";
|
|
301
|
+
"tenant+handler": "tenant+handler";
|
|
302
|
+
user: "user";
|
|
303
|
+
"user+handler": "user+handler";
|
|
304
|
+
}>;
|
|
305
|
+
limit: z.ZodNumber;
|
|
306
|
+
windowSeconds: z.ZodNumber;
|
|
307
|
+
cost: z.ZodOptional<z.ZodNumber>;
|
|
308
|
+
}, z.core.$strict>>, z.ZodObject<{
|
|
261
309
|
disabled: z.ZodLiteral<true>;
|
|
262
310
|
reason: z.ZodString;
|
|
263
311
|
}, z.core.$strict>]>, z.ZodObject<{
|
|
@@ -303,7 +351,31 @@ export declare const PATTERN_SCHEMAS_BY_KIND: {
|
|
|
303
351
|
limit: z.ZodNumber;
|
|
304
352
|
windowSeconds: z.ZodNumber;
|
|
305
353
|
cost: z.ZodOptional<z.ZodNumber>;
|
|
306
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
354
|
+
}, z.core.$strict>, z.ZodTuple<[z.ZodObject<{
|
|
355
|
+
per: z.ZodEnum<{
|
|
356
|
+
ip: "ip";
|
|
357
|
+
"ip+handler": "ip+handler";
|
|
358
|
+
tenant: "tenant";
|
|
359
|
+
"tenant+handler": "tenant+handler";
|
|
360
|
+
user: "user";
|
|
361
|
+
"user+handler": "user+handler";
|
|
362
|
+
}>;
|
|
363
|
+
limit: z.ZodNumber;
|
|
364
|
+
windowSeconds: z.ZodNumber;
|
|
365
|
+
cost: z.ZodOptional<z.ZodNumber>;
|
|
366
|
+
}, z.core.$strict>], z.ZodObject<{
|
|
367
|
+
per: z.ZodEnum<{
|
|
368
|
+
ip: "ip";
|
|
369
|
+
"ip+handler": "ip+handler";
|
|
370
|
+
tenant: "tenant";
|
|
371
|
+
"tenant+handler": "tenant+handler";
|
|
372
|
+
user: "user";
|
|
373
|
+
"user+handler": "user+handler";
|
|
374
|
+
}>;
|
|
375
|
+
limit: z.ZodNumber;
|
|
376
|
+
windowSeconds: z.ZodNumber;
|
|
377
|
+
cost: z.ZodOptional<z.ZodNumber>;
|
|
378
|
+
}, z.core.$strict>>, z.ZodObject<{
|
|
307
379
|
disabled: z.ZodLiteral<true>;
|
|
308
380
|
reason: z.ZodString;
|
|
309
381
|
}, z.core.$strict>]>, z.ZodObject<{
|
|
@@ -712,7 +784,31 @@ export declare const patternSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
712
784
|
limit: z.ZodNumber;
|
|
713
785
|
windowSeconds: z.ZodNumber;
|
|
714
786
|
cost: z.ZodOptional<z.ZodNumber>;
|
|
715
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
787
|
+
}, z.core.$strict>, z.ZodTuple<[z.ZodObject<{
|
|
788
|
+
per: z.ZodEnum<{
|
|
789
|
+
ip: "ip";
|
|
790
|
+
"ip+handler": "ip+handler";
|
|
791
|
+
tenant: "tenant";
|
|
792
|
+
"tenant+handler": "tenant+handler";
|
|
793
|
+
user: "user";
|
|
794
|
+
"user+handler": "user+handler";
|
|
795
|
+
}>;
|
|
796
|
+
limit: z.ZodNumber;
|
|
797
|
+
windowSeconds: z.ZodNumber;
|
|
798
|
+
cost: z.ZodOptional<z.ZodNumber>;
|
|
799
|
+
}, z.core.$strict>], z.ZodObject<{
|
|
800
|
+
per: z.ZodEnum<{
|
|
801
|
+
ip: "ip";
|
|
802
|
+
"ip+handler": "ip+handler";
|
|
803
|
+
tenant: "tenant";
|
|
804
|
+
"tenant+handler": "tenant+handler";
|
|
805
|
+
user: "user";
|
|
806
|
+
"user+handler": "user+handler";
|
|
807
|
+
}>;
|
|
808
|
+
limit: z.ZodNumber;
|
|
809
|
+
windowSeconds: z.ZodNumber;
|
|
810
|
+
cost: z.ZodOptional<z.ZodNumber>;
|
|
811
|
+
}, z.core.$strict>>, z.ZodObject<{
|
|
716
812
|
disabled: z.ZodLiteral<true>;
|
|
717
813
|
reason: z.ZodString;
|
|
718
814
|
}, z.core.$strict>]>, z.ZodObject<{
|
|
@@ -779,7 +875,31 @@ export declare const patternSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
779
875
|
limit: z.ZodNumber;
|
|
780
876
|
windowSeconds: z.ZodNumber;
|
|
781
877
|
cost: z.ZodOptional<z.ZodNumber>;
|
|
782
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
878
|
+
}, z.core.$strict>, z.ZodTuple<[z.ZodObject<{
|
|
879
|
+
per: z.ZodEnum<{
|
|
880
|
+
ip: "ip";
|
|
881
|
+
"ip+handler": "ip+handler";
|
|
882
|
+
tenant: "tenant";
|
|
883
|
+
"tenant+handler": "tenant+handler";
|
|
884
|
+
user: "user";
|
|
885
|
+
"user+handler": "user+handler";
|
|
886
|
+
}>;
|
|
887
|
+
limit: z.ZodNumber;
|
|
888
|
+
windowSeconds: z.ZodNumber;
|
|
889
|
+
cost: z.ZodOptional<z.ZodNumber>;
|
|
890
|
+
}, z.core.$strict>], z.ZodObject<{
|
|
891
|
+
per: z.ZodEnum<{
|
|
892
|
+
ip: "ip";
|
|
893
|
+
"ip+handler": "ip+handler";
|
|
894
|
+
tenant: "tenant";
|
|
895
|
+
"tenant+handler": "tenant+handler";
|
|
896
|
+
user: "user";
|
|
897
|
+
"user+handler": "user+handler";
|
|
898
|
+
}>;
|
|
899
|
+
limit: z.ZodNumber;
|
|
900
|
+
windowSeconds: z.ZodNumber;
|
|
901
|
+
cost: z.ZodOptional<z.ZodNumber>;
|
|
902
|
+
}, z.core.$strict>>, z.ZodObject<{
|
|
783
903
|
disabled: z.ZodLiteral<true>;
|
|
784
904
|
reason: z.ZodString;
|
|
785
905
|
}, z.core.$strict>]>, z.ZodObject<{
|
|
@@ -824,7 +944,31 @@ export declare const patternSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
824
944
|
limit: z.ZodNumber;
|
|
825
945
|
windowSeconds: z.ZodNumber;
|
|
826
946
|
cost: z.ZodOptional<z.ZodNumber>;
|
|
827
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
947
|
+
}, z.core.$strict>, z.ZodTuple<[z.ZodObject<{
|
|
948
|
+
per: z.ZodEnum<{
|
|
949
|
+
ip: "ip";
|
|
950
|
+
"ip+handler": "ip+handler";
|
|
951
|
+
tenant: "tenant";
|
|
952
|
+
"tenant+handler": "tenant+handler";
|
|
953
|
+
user: "user";
|
|
954
|
+
"user+handler": "user+handler";
|
|
955
|
+
}>;
|
|
956
|
+
limit: z.ZodNumber;
|
|
957
|
+
windowSeconds: z.ZodNumber;
|
|
958
|
+
cost: z.ZodOptional<z.ZodNumber>;
|
|
959
|
+
}, z.core.$strict>], z.ZodObject<{
|
|
960
|
+
per: z.ZodEnum<{
|
|
961
|
+
ip: "ip";
|
|
962
|
+
"ip+handler": "ip+handler";
|
|
963
|
+
tenant: "tenant";
|
|
964
|
+
"tenant+handler": "tenant+handler";
|
|
965
|
+
user: "user";
|
|
966
|
+
"user+handler": "user+handler";
|
|
967
|
+
}>;
|
|
968
|
+
limit: z.ZodNumber;
|
|
969
|
+
windowSeconds: z.ZodNumber;
|
|
970
|
+
cost: z.ZodOptional<z.ZodNumber>;
|
|
971
|
+
}, z.core.$strict>>, z.ZodObject<{
|
|
828
972
|
disabled: z.ZodLiteral<true>;
|
|
829
973
|
reason: z.ZodString;
|
|
830
974
|
}, z.core.$strict>]>, z.ZodObject<{
|
|
@@ -157,6 +157,7 @@ const rateLimitOptionSchema = z
|
|
|
157
157
|
cost: z.number().optional(),
|
|
158
158
|
})
|
|
159
159
|
.strict();
|
|
160
|
+
const rateLimitOptionListSchema = z.tuple([rateLimitOptionSchema], rateLimitOptionSchema);
|
|
160
161
|
const additionalRateLimitsSchema = z.array(z
|
|
161
162
|
.object({
|
|
162
163
|
per: z.object({ payloadField: z.string() }).strict(),
|
|
@@ -247,7 +248,7 @@ const WRITE_HANDLER_PATTERN_HEADER_SHAPE = {
|
|
|
247
248
|
...WRITE_HANDLER_HEADER_SHAPE,
|
|
248
249
|
access: orRawRef(accessRuleSchema),
|
|
249
250
|
agent: orRawRef(agentHandlerHintsSchema),
|
|
250
|
-
rateLimit: orRawRef(z.union([rateLimitOptionSchema, rateLimitDisabledSchema])),
|
|
251
|
+
rateLimit: orRawRef(z.union([rateLimitOptionSchema, rateLimitOptionListSchema, rateLimitDisabledSchema])),
|
|
251
252
|
additionalRateLimits: orRawRef(additionalRateLimitsSchema),
|
|
252
253
|
escapeHatch: orRawRef(escapeHatchSchema),
|
|
253
254
|
};
|
|
@@ -255,13 +256,13 @@ const QUERY_HANDLER_PATTERN_HEADER_SHAPE = {
|
|
|
255
256
|
...QUERY_HANDLER_HEADER_SHAPE,
|
|
256
257
|
access: orRawRef(accessRuleSchema),
|
|
257
258
|
agent: orRawRef(agentHandlerHintsSchema),
|
|
258
|
-
rateLimit: orRawRef(z.union([rateLimitOptionSchema, rateLimitDisabledSchema])),
|
|
259
|
+
rateLimit: orRawRef(z.union([rateLimitOptionSchema, rateLimitOptionListSchema, rateLimitDisabledSchema])),
|
|
259
260
|
escapeHatch: orRawRef(escapeHatchSchema),
|
|
260
261
|
};
|
|
261
262
|
const STREAM_HANDLER_PATTERN_HEADER_SHAPE = {
|
|
262
263
|
...STREAM_HANDLER_HEADER_SHAPE,
|
|
263
264
|
access: orRawRef(accessRuleSchema),
|
|
264
|
-
rateLimit: orRawRef(z.union([rateLimitOptionSchema, rateLimitDisabledSchema])),
|
|
265
|
+
rateLimit: orRawRef(z.union([rateLimitOptionSchema, rateLimitOptionListSchema, rateLimitDisabledSchema])),
|
|
265
266
|
escapeHatch: orRawRef(escapeHatchSchema),
|
|
266
267
|
};
|
|
267
268
|
function requireHandlerBody(val, ctx, headerKeys) {
|
|
@@ -31,9 +31,12 @@ function qualifySelectorPanel(featureName, panel) {
|
|
|
31
31
|
throw new Error(`[Feature ${featureName}] extensionSelector screen panel "${panel.id}" must not set a label — ` +
|
|
32
32
|
`the settings dashboard has no i18n namespace for it; the embedded screen carries its own title.`);
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
const qualify = (ref) => ref === "" || isValidQn(ref) ? ref : qualifyEntityName(featureName, "screen", ref);
|
|
35
|
+
const screen = qualify(panel.screen);
|
|
36
|
+
const fallback = panel.fallback === undefined ? undefined : qualify(panel.fallback);
|
|
37
|
+
if (screen === panel.screen && fallback === panel.fallback)
|
|
35
38
|
return panel;
|
|
36
|
-
return { ...panel, screen
|
|
39
|
+
return { ...panel, screen, ...(fallback !== undefined && { fallback }) };
|
|
37
40
|
}
|
|
38
41
|
throw new Error(`[Feature ${featureName}] extensionSelector panel ${JSON.stringify(panel)} has an unsupported kind — ` +
|
|
39
42
|
`only "custom" and "screen" panels are allowed.`);
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ export { i18nKey } from "./i18n-key.ts";
|
|
|
51
51
|
export { buildSessionRoles, findForbiddenMembershipRole, isForbiddenMembershipRole, stripForbiddenMembershipRoles, } from "./membership-roles.ts";
|
|
52
52
|
export type { OwnershipClause, OwnershipMap, OwnershipRef, OwnershipRule } from "./ownership.ts";
|
|
53
53
|
export { buildOwnershipClause, combineClauses, from, normalizeAccessEntry, userCanCreateFieldRow, userCanReadFieldRow, userCanWriteFieldRow, } from "./ownership.ts";
|
|
54
|
+
export { type PagedListRequest, type PagedListRowValue, pagedListSchema, pageRows, } from "./paged-list.ts";
|
|
54
55
|
export type { ParsedRefTarget } from "./parse-ref-target.ts";
|
|
55
56
|
export { parseRefTarget, parseRefTargetEntityName } from "./parse-ref-target.ts";
|
|
56
57
|
export { buildPipelineSteps, stepsPipeline } from "./pipeline.ts";
|
|
@@ -83,7 +84,7 @@ export { assertNotificationVariables, templateVariable, templateVariablesToZod,
|
|
|
83
84
|
export { type EffectiveFeaturesResolver, findTierResolverUsage, isTierResolverPlugin, requireTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, type TierResolverPlugin, type TrialGate, } from "./tier-resolver-extension.ts";
|
|
84
85
|
export { findOverDeepTransferChain, resolveTransferAdjacency as resolveEntityTransferAdjacency, type TransferAdjacency, type TransferEdge, type TransferEdgeLink, } from "./transfer-adjacency.ts";
|
|
85
86
|
export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.ts";
|
|
86
|
-
export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BeforeTransactionPhase, BeforeTransactionResult, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, BootOptionName, 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, StackBootRequirement, 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.ts";
|
|
87
|
+
export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BeforeTransactionPhase, BeforeTransactionResult, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, BootOptionName, 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, DashboardCurrencyFormat, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardProgressTone, DashboardRecordBinding, DashboardRefresh, DashboardRelativeTimeFormat, DashboardRelativeTimeParam, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextBlock, DashboardTextPanel, 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, StackBootRequirement, 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.ts";
|
|
87
88
|
export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, HookPhases, HTTP_ERROR_CODE_PATTERN, isAgentVisibleScreen, isDeliveryErrorCode, isOpenToAllGranted, resolveAgentExposure, } from "./types/index.ts";
|
|
88
89
|
export type { AwaitedEventType, PipelineBuildCtx, PipelineCtx, PipelineDef, StepBuilder, StepDef, StepFailureStrategy, StepInstance, StepKind, StepNamespace, StepResolver, } from "./types/step.ts";
|
|
89
90
|
export { runValidation } from "./validation.ts";
|
package/dist/engine/index.js
CHANGED
|
@@ -51,6 +51,7 @@ export { i18nKey } from "./i18n-key.js";
|
|
|
51
51
|
// surface.
|
|
52
52
|
export { buildSessionRoles, findForbiddenMembershipRole, isForbiddenMembershipRole, stripForbiddenMembershipRoles, } from "./membership-roles.js";
|
|
53
53
|
export { buildOwnershipClause, combineClauses, from, normalizeAccessEntry, userCanCreateFieldRow, userCanReadFieldRow, userCanWriteFieldRow, } from "./ownership.js";
|
|
54
|
+
export { pagedListSchema, pageRows, } from "./paged-list.js";
|
|
54
55
|
export { parseRefTarget, parseRefTargetEntityName } from "./parse-ref-target.js";
|
|
55
56
|
export { buildPipelineSteps, stepsPipeline } from "./pipeline.js";
|
|
56
57
|
export { projectAppSchemaForRoles } from "./project-app-schema-for-roles.js";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CursorResult } from "@cosmicdrift/kumiko-types/cursor-types";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
export type PagedListRowValue = string | number | boolean | null;
|
|
4
|
+
export declare function pagedListSchema<const TSortField extends string>(sortableFields: readonly [TSortField, ...TSortField[]]): z.ZodObject<{
|
|
5
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
6
|
+
offset: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
search: z.ZodOptional<z.ZodString>;
|
|
8
|
+
sort: z.ZodOptional<z.ZodEnum<{ [k in TSortField]: k; } extends infer T ? { [k in keyof T]: T[k]; } : never>>;
|
|
9
|
+
sortDirection: z.ZodOptional<z.ZodEnum<{
|
|
10
|
+
asc: "asc";
|
|
11
|
+
desc: "desc";
|
|
12
|
+
}>>;
|
|
13
|
+
totalCount: z.ZodOptional<z.ZodBoolean>;
|
|
14
|
+
cursor: z.ZodOptional<z.ZodNever>;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
export type PagedListRequest<TSortField extends string> = {
|
|
17
|
+
readonly limit: number;
|
|
18
|
+
readonly offset?: number | undefined;
|
|
19
|
+
readonly search?: string | undefined;
|
|
20
|
+
readonly sort?: TSortField | undefined;
|
|
21
|
+
readonly sortDirection?: "asc" | "desc" | undefined;
|
|
22
|
+
readonly totalCount?: boolean | undefined;
|
|
23
|
+
};
|
|
24
|
+
export declare function pageRows<TRow extends Readonly<Record<TSortField, PagedListRowValue>> & {
|
|
25
|
+
readonly id: string;
|
|
26
|
+
}, TSortField extends string>(rows: readonly TRow[], request: PagedListRequest<TSortField>, options: {
|
|
27
|
+
readonly searchFields: readonly TSortField[];
|
|
28
|
+
readonly defaultSort: TSortField;
|
|
29
|
+
}): CursorResult<TRow>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Search, sort and offset paging for projectionList queries whose rows are
|
|
2
|
+
// assembled in memory (merged from several tables, no single SQL query to
|
|
3
|
+
// page). The projectionList pager sends `offset` + `totalCount`, never a
|
|
4
|
+
// cursor, so `nextCursor` is always null here.
|
|
5
|
+
import * as z from "zod";
|
|
6
|
+
import { MAX_LIST_LIMIT } from "./entity-handlers.js";
|
|
7
|
+
export function pagedListSchema(sortableFields) {
|
|
8
|
+
return z.object({
|
|
9
|
+
limit: z.number().int().min(1).max(MAX_LIST_LIMIT).default(50),
|
|
10
|
+
offset: z.number().int().nonnegative().optional(),
|
|
11
|
+
search: z.string().optional(),
|
|
12
|
+
sort: z.enum(sortableFields).optional(),
|
|
13
|
+
sortDirection: z.enum(["asc", "desc"]).optional(),
|
|
14
|
+
totalCount: z.boolean().optional(),
|
|
15
|
+
// Declared so a caller still paging by cursor gets a 400 instead of page one.
|
|
16
|
+
cursor: z.never({ error: "cursor paging is not supported here; page with offset" }).optional(),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function isMissing(value) {
|
|
20
|
+
return value === null || value === undefined;
|
|
21
|
+
}
|
|
22
|
+
// Missing values sort last in both directions; numbers and booleans compare
|
|
23
|
+
// by value, everything else as a locale-aware string.
|
|
24
|
+
function compareRowValues(a, b, directionMultiplier) {
|
|
25
|
+
if (isMissing(a) && isMissing(b))
|
|
26
|
+
return 0;
|
|
27
|
+
if (isMissing(a))
|
|
28
|
+
return 1;
|
|
29
|
+
if (isMissing(b))
|
|
30
|
+
return -1;
|
|
31
|
+
if (typeof a === "number" && typeof b === "number")
|
|
32
|
+
return (a - b) * directionMultiplier;
|
|
33
|
+
if (typeof a === "boolean" && typeof b === "boolean") {
|
|
34
|
+
return (Number(a) - Number(b)) * directionMultiplier;
|
|
35
|
+
}
|
|
36
|
+
return String(a).localeCompare(String(b)) * directionMultiplier;
|
|
37
|
+
}
|
|
38
|
+
export function pageRows(rows, request, options) {
|
|
39
|
+
const needle = request.search?.trim().toLowerCase() ?? "";
|
|
40
|
+
const matching = needle === ""
|
|
41
|
+
? rows
|
|
42
|
+
: rows.filter((row) => options.searchFields.some((field) => String(row[field] ?? "")
|
|
43
|
+
.toLowerCase()
|
|
44
|
+
.includes(needle)));
|
|
45
|
+
const sortField = request.sort ?? options.defaultSort;
|
|
46
|
+
const directionMultiplier = request.sortDirection === "desc" ? -1 : 1;
|
|
47
|
+
// The source rows come unordered, so ties fall back to the id; otherwise a
|
|
48
|
+
// tied row could show up on two pages or on none.
|
|
49
|
+
const sorted = [...matching].sort((a, b) => compareRowValues(a[sortField], b[sortField], directionMultiplier) || a.id.localeCompare(b.id));
|
|
50
|
+
const offset = request.offset ?? 0;
|
|
51
|
+
return {
|
|
52
|
+
rows: sorted.slice(offset, offset + request.limit),
|
|
53
|
+
nextCursor: null,
|
|
54
|
+
...(request.totalCount === true && { total: matching.length }),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
// resolves app-wide via a global short-id map, mirroring
|
|
40
40
|
// resolveRowActionNavigateTarget; kind:"drawer" resolves same-feature
|
|
41
41
|
// only, mirroring validateDrawerTargetAction)
|
|
42
|
-
// - projectionDetail header actions + metrics[].navigate
|
|
43
|
-
// - entityEdit/actionForm/secretMint redirect + actionForm cancelTarget
|
|
42
|
+
// - projectionDetail and dashboard header actions + projectionDetail metrics[].navigate
|
|
43
|
+
// - entityEdit/actionForm/secretMint redirect + actionForm successLinks and cancelTarget
|
|
44
44
|
// (resolveScreenTargetQn: same-feature short id or cross-feature QN)
|
|
45
45
|
// - dashboard DashboardScreenPanel.screen (resolveScreenTargetQn)
|
|
46
46
|
// - every EditSectionSpec kind's own `actions` (fields/extension/writeForm/
|
|
@@ -418,8 +418,27 @@ function projectHeader(header, indices, keptScreenQns) {
|
|
|
418
418
|
return changed ? { ...header, subtitle: nextParts } : header;
|
|
419
419
|
}
|
|
420
420
|
function projectDashboardPanels(panels, feature, keptScreenQns) {
|
|
421
|
-
|
|
422
|
-
|
|
421
|
+
let changed = false;
|
|
422
|
+
const kept = [];
|
|
423
|
+
for (const panel of panels) {
|
|
424
|
+
if (panel.kind !== "screen") {
|
|
425
|
+
kept.push(panel);
|
|
426
|
+
}
|
|
427
|
+
else if (!isScreenTargetKept(feature.featureName, panel.screen, keptScreenQns)) {
|
|
428
|
+
changed = true;
|
|
429
|
+
}
|
|
430
|
+
else if (panel.fallback !== undefined &&
|
|
431
|
+
!isScreenTargetKept(feature.featureName, panel.fallback, keptScreenQns)) {
|
|
432
|
+
// The role cannot reach the stand-in, so the tile just hides.
|
|
433
|
+
const { fallback: _unreachable, ...withoutFallback } = panel;
|
|
434
|
+
kept.push(withoutFallback);
|
|
435
|
+
changed = true;
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
kept.push(panel);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return changed ? kept : panels;
|
|
423
442
|
}
|
|
424
443
|
// --- per-screen-type projection ---
|
|
425
444
|
function projectListScreenId(listScreenId, indices, keptScreenQns) {
|
|
@@ -512,12 +531,15 @@ function projectEntityEditScreen(screen, feature, indices, keptScreenQns) {
|
|
|
512
531
|
};
|
|
513
532
|
}
|
|
514
533
|
function projectActionFormScreen(screen, feature, indices, keptScreenQns) {
|
|
515
|
-
const { redirect, cancelTarget, layout, listScreenId, ...rest } = screen;
|
|
534
|
+
const { redirect, successLinks, cancelTarget, layout, listScreenId, ...rest } = screen;
|
|
516
535
|
const nextRedirect = projectRedirect(feature.featureName, redirect, keptScreenQns);
|
|
536
|
+
const keptSuccessLinks = successLinks?.filter((link) => isRedirectTargetKept(feature.featureName, link, keptScreenQns));
|
|
537
|
+
const nextSuccessLinks = keptSuccessLinks?.length === 0 ? undefined : keptSuccessLinks;
|
|
517
538
|
const nextCancelTarget = projectCancelTarget(feature.featureName, cancelTarget, keptScreenQns);
|
|
518
539
|
const nextLayout = projectEditLayout(layout, feature, indices, keptScreenQns);
|
|
519
540
|
const nextListScreenId = projectListScreenId(listScreenId, indices, keptScreenQns);
|
|
520
541
|
const unchanged = nextRedirect === redirect &&
|
|
542
|
+
nextSuccessLinks?.length === successLinks?.length &&
|
|
521
543
|
nextCancelTarget === cancelTarget &&
|
|
522
544
|
nextLayout === layout &&
|
|
523
545
|
nextListScreenId === listScreenId;
|
|
@@ -527,6 +549,7 @@ function projectActionFormScreen(screen, feature, indices, keptScreenQns) {
|
|
|
527
549
|
...rest,
|
|
528
550
|
layout: nextLayout,
|
|
529
551
|
...(nextRedirect !== undefined && { redirect: nextRedirect }),
|
|
552
|
+
...(nextSuccessLinks !== undefined && { successLinks: nextSuccessLinks }),
|
|
530
553
|
...(nextCancelTarget !== undefined && { cancelTarget: nextCancelTarget }),
|
|
531
554
|
...(nextListScreenId !== undefined && { listScreenId: nextListScreenId }),
|
|
532
555
|
};
|
|
@@ -565,8 +588,16 @@ function projectScreen(screen, feature, indices, keptScreenQns) {
|
|
|
565
588
|
case "projectionDetail":
|
|
566
589
|
return projectProjectionDetailScreen(screen, feature, indices, keptScreenQns);
|
|
567
590
|
case "dashboard": {
|
|
591
|
+
const { actions, ...rest } = screen;
|
|
568
592
|
const nextPanels = projectDashboardPanels(screen.panels, feature, keptScreenQns);
|
|
569
|
-
|
|
593
|
+
const nextActions = projectRowActionArray(actions, feature, indices, keptScreenQns);
|
|
594
|
+
if (nextPanels === screen.panels && nextActions === actions)
|
|
595
|
+
return screen;
|
|
596
|
+
return {
|
|
597
|
+
...rest,
|
|
598
|
+
panels: nextPanels,
|
|
599
|
+
...(nextActions !== undefined && { actions: nextActions }),
|
|
600
|
+
};
|
|
570
601
|
}
|
|
571
602
|
case "entityEdit":
|
|
572
603
|
return projectEntityEditScreen(screen, feature, indices, keptScreenQns);
|
|
@@ -7,8 +7,8 @@ export type { ExtensionOptionsArgs, ExtensionOptionsFor, KumikoExtensionOptionsM
|
|
|
7
7
|
export type { BootCheckContext, BootCheckFn, BootOptionName, EntityObjectDefinition, FeatureDefinition, FeatureMetricDef, FeatureMetricType, FeatureRegistrar, HookDefinition, MetricOptions, Registry, SecretKeyDefinition, SecretKeyHandle, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretOptions, StackBootRequirement, StoreTableDef, StoreTableEntry, StoreTableOptions, UiHintOption, UiHints, } from "@cosmicdrift/kumiko-types/feature";
|
|
8
8
|
export type { FieldIconKey } from "@cosmicdrift/kumiko-types/field-icon";
|
|
9
9
|
export type { AnyFileFieldDef, BigIntFieldDef, BooleanFieldDef, ClientDerivedFieldDef, DateFieldDef, DecimalFieldDef, DefaultCurrency, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EmbeddedDerivedCellDef, EmbeddedFieldDef, EmbeddedSubFieldDef, EntityDefinition, EntityEventMigration, EntityIndexDef, FieldAccess, FieldDefinition, FieldsMap, FileFieldDef, FilesFieldDef, Findability, ImageFieldDef, ImagesFieldDef, JsonbFieldDef, LocatedTimestampFieldDef, LongTextFieldDef, LongTextFindability, MoneyCurrencySource, MoneyFieldDef, MultiSelectFieldDef, NumberFieldDef, OptionsQueryPayload, OptionsQueryPayloadValue, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, ReferenceFieldDef, ResolvedPiiFlags, RetentionDef, SelectFieldDef, SelectOptionTone, TextFieldDef, TimestampFieldDef, TransitionMap, TzFieldDef, } from "@cosmicdrift/kumiko-types/fields";
|
|
10
|
-
export type { AccessRule, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AggregateStreamHandle, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, 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";
|
|
11
|
-
export { DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, HTTP_ERROR_CODE_PATTERN, isDeliveryErrorCode, isOpenToAllGranted, isRateLimitDisabled, isUiAccessGranted, } from "@cosmicdrift/kumiko-types/handlers";
|
|
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, RateLimitOptions, RateLimitPer, RoleAccessPersonalData, RoleAccessRule, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, TenantWriteTarget, TryAppendEventFn, TryAppendEventResult, UnsafeAppendEventFn, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "@cosmicdrift/kumiko-types/handlers";
|
|
11
|
+
export { DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, declaredRateLimitOptions, HTTP_ERROR_CODE_PATTERN, isDeliveryErrorCode, isOpenToAllGranted, isRateLimitDisabled, isRateLimitOption, 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";
|
|
14
14
|
export type { EntityId, TenantId } from "@cosmicdrift/kumiko-types/identifiers";
|
|
@@ -17,7 +17,7 @@ export type { ContentCollectionDefinition, NavDefinition, NavIconKey, } from "@c
|
|
|
17
17
|
export type { FromRule, FromRuleKind, OwnershipClause, OwnershipMap, OwnershipRef, OwnershipRule, SqlFragment, WhereRule, WhereRuleContext, } from "@cosmicdrift/kumiko-types/ownership";
|
|
18
18
|
export type { EntityProjectionExtension, MspErrorMode, MspErrorPolicy, MultiStreamApplyFn, MultiStreamProjectionDefinition, ProjectionDefinition, ProjectionRowIdOf, ProjectionTable, SingleStreamApplyFn, } from "@cosmicdrift/kumiko-types/projection";
|
|
19
19
|
export type { BelongsToRelation, EntityRelations, HasManyRelation, ManyToManyRelation, RelationDefinition, } from "@cosmicdrift/kumiko-types/relations";
|
|
20
|
-
export type { ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelGate, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardValueFormat, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EmbeddedListEditSpec, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormWidth, ListColumnSpec, ListEmptyStateSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, UnitKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
20
|
+
export type { ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardCurrencyFormat, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelGate, DashboardPanelVisibility, DashboardProgressListPanel, DashboardProgressTone, DashboardRecordBinding, DashboardRefresh, DashboardRelativeTimeFormat, DashboardRelativeTimeParam, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextBlock, DashboardTextPanel, DashboardTextParam, DashboardValueFormat, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EmbeddedListEditSpec, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormWidth, ListColumnSpec, ListEmptyStateSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, UnitKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
21
21
|
export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
22
22
|
export type { TargetRef } from "@cosmicdrift/kumiko-types/target-ref";
|
|
23
23
|
export type { Subscribe, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, } from "@cosmicdrift/kumiko-types/tree-node";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Barrel: re-exports all types from @cosmicdrift/kumiko-types, plus the
|
|
2
2
|
// runtime helpers below that stay framework-side.
|
|
3
3
|
export { isAgentVisibleScreen, resolveAgentExposure, } from "@cosmicdrift/kumiko-types/agent-exposure";
|
|
4
|
-
export { DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, HTTP_ERROR_CODE_PATTERN, isDeliveryErrorCode, isOpenToAllGranted, isRateLimitDisabled, isUiAccessGranted, } from "@cosmicdrift/kumiko-types/handlers";
|
|
4
|
+
export { DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, declaredRateLimitOptions, HTTP_ERROR_CODE_PATTERN, isDeliveryErrorCode, isOpenToAllGranted, isRateLimitDisabled, isRateLimitOption, isUiAccessGranted, } from "@cosmicdrift/kumiko-types/handlers";
|
|
5
5
|
export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-types/identifiers";
|
|
6
6
|
export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
7
7
|
export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, isFileField } from "../field-helpers.js";
|