@forestadmin/agent-bff 1.8.6 → 1.9.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/action/action-execute-mapper.d.ts +30 -0
- package/dist/action/action-execute-mapper.js +52 -0
- package/dist/action/action-routes-middleware.js +82 -10
- package/dist/action/agent-action-client.d.ts +5 -1
- package/dist/action/agent-action-client.js +2 -2
- package/dist/data/data-routes-middleware.js +39 -2
- package/dist/http/bff-local-errors.d.ts +1 -0
- package/dist/http/bff-local-errors.js +5 -1
- package/dist/read-model/capabilities-cache.d.ts +1 -1
- package/dist/read-model/read-model-store.d.ts +9 -1
- package/dist/read-model/read-model-store.js +24 -9
- package/dist/validation/capabilities-validator.d.ts +22 -0
- package/dist/validation/capabilities-validator.js +110 -0
- package/dist/validation/operator-normalizer.d.ts +14 -0
- package/dist/validation/operator-normalizer.js +26 -0
- package/dist/validation/validation-errors.d.ts +6 -0
- package/dist/validation/validation-errors.js +22 -0
- package/package.json +3 -2
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface ActionExecuteSuccessBody {
|
|
2
|
+
type: 'success';
|
|
3
|
+
message: string | null;
|
|
4
|
+
invalidated: string[];
|
|
5
|
+
html: string | null;
|
|
6
|
+
}
|
|
7
|
+
export interface ActionExecuteWebhookBody {
|
|
8
|
+
type: 'webhook';
|
|
9
|
+
url: unknown;
|
|
10
|
+
method: unknown;
|
|
11
|
+
headers: unknown;
|
|
12
|
+
body: unknown;
|
|
13
|
+
}
|
|
14
|
+
export interface ActionExecuteRedirectBody {
|
|
15
|
+
type: 'redirect';
|
|
16
|
+
path: unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface ActionExecuteUnsupportedBody {
|
|
19
|
+
error: {
|
|
20
|
+
type: 'unsupported_action_result';
|
|
21
|
+
status: 501;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export type ActionExecuteMappedBody = ActionExecuteSuccessBody | ActionExecuteWebhookBody | ActionExecuteRedirectBody | ActionExecuteUnsupportedBody;
|
|
25
|
+
export interface ActionExecuteMapped {
|
|
26
|
+
status: number;
|
|
27
|
+
body: ActionExecuteMappedBody;
|
|
28
|
+
}
|
|
29
|
+
export declare function mapActionExecuteResult(raw: unknown): ActionExecuteMapped;
|
|
30
|
+
//# sourceMappingURL=action-execute-mapper.d.ts.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mapActionExecuteResult = mapActionExecuteResult;
|
|
4
|
+
// The agent always serializes the four webhook fields together
|
|
5
|
+
// (`agent/src/routes/modification/action/action.ts`), so a payload missing url/method is malformed
|
|
6
|
+
// and must reach the 501 path rather than surface as a 200 the client cannot act on.
|
|
7
|
+
function isWebhook(value) {
|
|
8
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
9
|
+
return false;
|
|
10
|
+
const { url, method } = value;
|
|
11
|
+
return typeof url === 'string' && typeof method === 'string';
|
|
12
|
+
}
|
|
13
|
+
// The agent serializes a Success refresh as `{ relationships: [...] }`, so anything else (an array,
|
|
14
|
+
// a bare object without relationships) is malformed and must not act as the Success discriminator.
|
|
15
|
+
function isRefresh(value) {
|
|
16
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
17
|
+
return false;
|
|
18
|
+
return Array.isArray(value.relationships);
|
|
19
|
+
}
|
|
20
|
+
// Normalizes the agent's 200 execute payload into the flat BFF wrapper. The execute result is
|
|
21
|
+
// untyped at the BFF boundary (`Action.execute(): Promise<unknown>`), so we discriminate on the
|
|
22
|
+
// agent HTTP payload shape. A File result streams a binary with no JSON marker, so any unrecognized
|
|
23
|
+
// 200 body falls through to a structured 501 rather than being mislabelled.
|
|
24
|
+
function mapActionExecuteResult(raw) {
|
|
25
|
+
const body = (typeof raw === 'object' && raw !== null ? raw : {});
|
|
26
|
+
// Each branch validates the value shape, not just key presence: a malformed payload
|
|
27
|
+
// (`{ webhook: null }`, `{ redirectTo: {} }`, `{ success: {} }`) must fall through to the 501
|
|
28
|
+
// path rather than be surfaced as a 200 with null fields the client cannot tell from a real one.
|
|
29
|
+
if (isWebhook(body.webhook)) {
|
|
30
|
+
const { url, method, headers, body: hookBody } = body.webhook;
|
|
31
|
+
return { status: 200, body: { type: 'webhook', url, method, headers, body: hookBody } };
|
|
32
|
+
}
|
|
33
|
+
if (typeof body.redirectTo === 'string') {
|
|
34
|
+
return { status: 200, body: { type: 'redirect', path: body.redirectTo } };
|
|
35
|
+
}
|
|
36
|
+
// A Success carries a string message and/or a well-formed refresh; the agent always sends the
|
|
37
|
+
// refresh, so a bare refresh (message absent) still normalizes to a success with a null message.
|
|
38
|
+
if (typeof body.success === 'string' || isRefresh(body.refresh)) {
|
|
39
|
+
const relationships = isRefresh(body.refresh) ? body.refresh.relationships : [];
|
|
40
|
+
return {
|
|
41
|
+
status: 200,
|
|
42
|
+
body: {
|
|
43
|
+
type: 'success',
|
|
44
|
+
message: typeof body.success === 'string' ? body.success : null,
|
|
45
|
+
invalidated: relationships.filter((name) => typeof name === 'string'),
|
|
46
|
+
html: typeof body.html === 'string' ? body.html : null,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return { status: 501, body: { error: { type: 'unsupported_action_result', status: 501 } } };
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWN0aW9uLWV4ZWN1dGUtbWFwcGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FjdGlvbi9hY3Rpb24tZXhlY3V0ZS1tYXBwZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFpRUEsd0RBaUNDO0FBeERELCtEQUErRDtBQUMvRCxtR0FBbUc7QUFDbkcscUZBQXFGO0FBQ3JGLFNBQVMsU0FBUyxDQUFDLEtBQWM7SUFDL0IsSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUksS0FBSyxLQUFLLElBQUksSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQztRQUFFLE9BQU8sS0FBSyxDQUFDO0lBRXRGLE1BQU0sRUFBRSxHQUFHLEVBQUUsTUFBTSxFQUFFLEdBQUcsS0FBZ0MsQ0FBQztJQUV6RCxPQUFPLE9BQU8sR0FBRyxLQUFLLFFBQVEsSUFBSSxPQUFPLE1BQU0sS0FBSyxRQUFRLENBQUM7QUFDL0QsQ0FBQztBQUVELG9HQUFvRztBQUNwRyxtR0FBbUc7QUFDbkcsU0FBUyxTQUFTLENBQUMsS0FBYztJQUMvQixJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSxLQUFLLEtBQUssSUFBSSxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFFdEYsT0FBTyxLQUFLLENBQUMsT0FBTyxDQUFFLEtBQXFDLENBQUMsYUFBYSxDQUFDLENBQUM7QUFDN0UsQ0FBQztBQUVELDhGQUE4RjtBQUM5RixnR0FBZ0c7QUFDaEcsb0dBQW9HO0FBQ3BHLDRFQUE0RTtBQUM1RSxTQUFnQixzQkFBc0IsQ0FBQyxHQUFZO0lBQ2pELE1BQU0sSUFBSSxHQUFHLENBQUMsT0FBTyxHQUFHLEtBQUssUUFBUSxJQUFJLEdBQUcsS0FBSyxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUE0QixDQUFDO0lBRTdGLG9GQUFvRjtJQUNwRiw4RkFBOEY7SUFDOUYsaUdBQWlHO0lBQ2pHLElBQUksU0FBUyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO1FBQzVCLE1BQU0sRUFBRSxHQUFHLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQztRQUU5RCxPQUFPLEVBQUUsTUFBTSxFQUFFLEdBQUcsRUFBRSxJQUFJLEVBQUUsRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLEdBQUcsRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUUsRUFBRSxDQUFDO0lBQzFGLENBQUM7SUFFRCxJQUFJLE9BQU8sSUFBSSxDQUFDLFVBQVUsS0FBSyxRQUFRLEVBQUUsQ0FBQztRQUN4QyxPQUFPLEVBQUUsTUFBTSxFQUFFLEdBQUcsRUFBRSxJQUFJLEVBQUUsRUFBRSxJQUFJLEVBQUUsVUFBVSxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsVUFBVSxFQUFFLEVBQUUsQ0FBQztJQUM1RSxDQUFDO0lBRUQsOEZBQThGO0lBQzlGLGlHQUFpRztJQUNqRyxJQUFJLE9BQU8sSUFBSSxDQUFDLE9BQU8sS0FBSyxRQUFRLElBQUksU0FBUyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO1FBQ2hFLE1BQU0sYUFBYSxHQUFHLFNBQVMsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFFaEYsT0FBTztZQUNMLE1BQU0sRUFBRSxHQUFHO1lBQ1gsSUFBSSxFQUFFO2dCQUNKLElBQUksRUFBRSxTQUFTO2dCQUNmLE9BQU8sRUFBRSxPQUFPLElBQUksQ0FBQyxPQUFPLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxJQUFJO2dCQUMvRCxXQUFXLEVBQUUsYUFBYSxDQUFDLE1BQU0sQ0FBQyxDQUFDLElBQUksRUFBa0IsRUFBRSxDQUFDLE9BQU8sSUFBSSxLQUFLLFFBQVEsQ0FBQztnQkFDckYsSUFBSSxFQUFFLE9BQU8sSUFBSSxDQUFDLElBQUksS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUk7YUFDdkQ7U0FDRixDQUFDO0lBQ0osQ0FBQztJQUVELE9BQU8sRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFLElBQUksRUFBRSxFQUFFLEtBQUssRUFBRSxFQUFFLElBQUksRUFBRSwyQkFBMkIsRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFLEVBQUUsRUFBRSxDQUFDO0FBQzlGLENBQUMifQ==
|
|
@@ -34,11 +34,14 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.default = createActionRoutesMiddleware;
|
|
37
|
+
const agent_client_1 = require("@forestadmin/agent-client");
|
|
38
|
+
const action_execute_mapper_1 = require("./action-execute-mapper");
|
|
37
39
|
const action_form_mapper_1 = require("./action-form-mapper");
|
|
38
40
|
const agent_action_client_1 = __importStar(require("./agent-action-client"));
|
|
41
|
+
const agent_error_mapper_1 = require("../http/agent-error-mapper");
|
|
39
42
|
const agent_route_helpers_1 = require("../http/agent-route-helpers");
|
|
40
43
|
const bff_local_errors_1 = require("../http/bff-local-errors");
|
|
41
|
-
const
|
|
44
|
+
const ACTION_ROUTE = /^\/agent\/v1\/([^/]+)\/actions\/([^/]+)\/(form|execute)$/;
|
|
42
45
|
// recordIds are opaque BFF ids; only presence and array-ness are checked. An empty array is allowed
|
|
43
46
|
// on purpose (global/bulk actions have no selected records); the spec AC only covers a missing
|
|
44
47
|
// recordIds, so `[]` is an explicit BFF choice, not a spec requirement.
|
|
@@ -58,15 +61,85 @@ function parseValues(raw) {
|
|
|
58
61
|
}
|
|
59
62
|
return raw;
|
|
60
63
|
}
|
|
64
|
+
// Only plain objects are enumerated: a File result arrives as a Buffer/stream whose every byte
|
|
65
|
+
// index is an enumerable key, so `Object.keys` on it would build a multi-million entry log.
|
|
66
|
+
function describePayloadShape(raw) {
|
|
67
|
+
if (raw === null)
|
|
68
|
+
return 'null';
|
|
69
|
+
if (typeof raw !== 'object')
|
|
70
|
+
return typeof raw;
|
|
71
|
+
if (Buffer.isBuffer(raw))
|
|
72
|
+
return `Buffer(${raw.length})`;
|
|
73
|
+
if (ArrayBuffer.isView(raw))
|
|
74
|
+
return `${raw.constructor.name}(${raw.byteLength})`;
|
|
75
|
+
if (Array.isArray(raw))
|
|
76
|
+
return `array(${raw.length})`;
|
|
77
|
+
if (Object.getPrototypeOf(raw) !== Object.prototype)
|
|
78
|
+
return raw.constructor?.name ?? 'object';
|
|
79
|
+
return `object{${Object.keys(raw).join(',')}}`;
|
|
80
|
+
}
|
|
81
|
+
async function handleForm(ctx, client, collection, actionName, recordIds, values, logger) {
|
|
82
|
+
// Each agent-hitting call is wrapped on its own; getFields/extractRawLayout/mapping stay outside
|
|
83
|
+
// callAgent so a local BFF bug surfaces as a 500, not a mislabelled agent error. Fields and
|
|
84
|
+
// layout are read AFTER tryToSetFields because a change hook rebuilds them in place.
|
|
85
|
+
const action = await (0, agent_route_helpers_1.callAgent)(() => client.loadAction(collection, actionName, recordIds), logger);
|
|
86
|
+
const skippedFields = await (0, agent_route_helpers_1.callAgent)(() => action.tryToSetFields(values), logger);
|
|
87
|
+
ctx.status = 200;
|
|
88
|
+
ctx.body = (0, action_form_mapper_1.mapActionForm)(action, skippedFields, (0, agent_action_client_1.extractRawLayout)(action));
|
|
89
|
+
}
|
|
90
|
+
async function handleExecute(ctx, client, collection, actionName, recordIds, values, logger) {
|
|
91
|
+
const action = await (0, agent_route_helpers_1.callAgent)(() => client.loadAction(collection, actionName, recordIds), logger);
|
|
92
|
+
// setFields is strict: an unknown submitted field is a client error (400), not a 500. A transport
|
|
93
|
+
// failure from the change-hook it triggers is a genuine agent error, so it goes to the mapper.
|
|
94
|
+
try {
|
|
95
|
+
await action.setFields(values);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (error instanceof agent_client_1.UnknownActionFieldError)
|
|
99
|
+
throw (0, bff_local_errors_1.invalidRequest)(error.message);
|
|
100
|
+
throw (0, agent_error_mapper_1.mapAgentError)(error, { logger });
|
|
101
|
+
}
|
|
102
|
+
// execute() cannot go through the generic callAgent: agent-client turns the native action Error
|
|
103
|
+
// (HTTP 400) into ActionFormValidationError, a non-AgentHttpError the mapper would mislabel as a
|
|
104
|
+
// transport 502. So the semantic outcomes are caught here, everything else falls to the mapper.
|
|
105
|
+
let raw;
|
|
106
|
+
try {
|
|
107
|
+
raw = await action.execute();
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (error instanceof agent_client_1.ActionRequiresApprovalError) {
|
|
111
|
+
throw (0, bff_local_errors_1.actionRequiresApproval)(error.message, error.roleIdsAllowedToApprove !== undefined
|
|
112
|
+
? { roleIdsAllowedToApprove: error.roleIdsAllowedToApprove }
|
|
113
|
+
: undefined);
|
|
114
|
+
}
|
|
115
|
+
if (error instanceof agent_client_1.ActionFormValidationError) {
|
|
116
|
+
ctx.status = 400;
|
|
117
|
+
ctx.body = { type: 'error', status: 400, message: error.message, html: error.html ?? null };
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
throw (0, agent_error_mapper_1.mapAgentError)(error, { logger });
|
|
121
|
+
}
|
|
122
|
+
const { status, body } = (0, action_execute_mapper_1.mapActionExecuteResult)(raw);
|
|
123
|
+
// An unrecognized payload (a File stream, or a new agent result type) maps to a generic 501 with
|
|
124
|
+
// no trace of what it was; log a short shape hint so the case can be diagnosed without the body.
|
|
125
|
+
if (status === 501) {
|
|
126
|
+
logger('Warn', 'Unrecognized action execute result mapped to 501', {
|
|
127
|
+
shape: describePayloadShape(raw),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
ctx.status = status;
|
|
131
|
+
ctx.body = body;
|
|
132
|
+
}
|
|
61
133
|
function createActionRoutesMiddleware({ store, agentUrl, logger, createClient = agent_action_client_1.default, }) {
|
|
62
134
|
return async function actionRoutesMiddleware(ctx, next) {
|
|
63
|
-
const match =
|
|
135
|
+
const match = ACTION_ROUTE.exec(ctx.path);
|
|
64
136
|
if (!match || ctx.method !== 'POST') {
|
|
65
137
|
await next();
|
|
66
138
|
return;
|
|
67
139
|
}
|
|
68
140
|
const collection = (0, agent_route_helpers_1.decodeSegment)(match[1], 'collection name');
|
|
69
141
|
const actionName = (0, agent_route_helpers_1.decodeSegment)(match[2], 'action name');
|
|
142
|
+
const verb = match[3];
|
|
70
143
|
const token = (0, agent_route_helpers_1.requireAgentToken)(ctx);
|
|
71
144
|
const readModel = await (0, agent_route_helpers_1.resolveReadModel)(store);
|
|
72
145
|
// The read-model's action map IS the allow-list, so an absent action cannot be told from a
|
|
@@ -85,13 +158,12 @@ function createActionRoutesMiddleware({ store, agentUrl, logger, createClient =
|
|
|
85
158
|
token,
|
|
86
159
|
actionEndpoints: readModel.getActionEndpoints(),
|
|
87
160
|
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
ctx.body = (0, action_form_mapper_1.mapActionForm)(action, skippedFields, (0, agent_action_client_1.extractRawLayout)(action));
|
|
161
|
+
if (verb === 'execute') {
|
|
162
|
+
await handleExecute(ctx, client, collection, actionName, recordIds, values, logger);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
await handleForm(ctx, client, collection, actionName, recordIds, values, logger);
|
|
166
|
+
}
|
|
95
167
|
};
|
|
96
168
|
}
|
|
97
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
169
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWN0aW9uLXJvdXRlcy1taWRkbGV3YXJlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FjdGlvbi9hY3Rpb24tcm91dGVzLW1pZGRsZXdhcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUErSkEsK0NBOENDO0FBeE1ELDREQUltQztBQUVuQyxtRUFBaUU7QUFDakUsNkRBQXFEO0FBQ3JELDZFQUF5RjtBQUN6RixtRUFBMkQ7QUFDM0QscUVBS3FDO0FBQ3JDLCtEQUFpRztBQUVqRyxNQUFNLFlBQVksR0FBRywwREFBMEQsQ0FBQztBQU9oRixvR0FBb0c7QUFDcEcsK0ZBQStGO0FBQy9GLHdFQUF3RTtBQUN4RSxxR0FBcUc7QUFDckcscUdBQXFHO0FBQ3JHLFNBQVMsY0FBYyxDQUFDLEdBQVk7SUFDbEMsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUN4QixNQUFNLElBQUEsaUNBQWMsRUFBQyw0Q0FBNEMsQ0FBQyxDQUFDO0lBQ3JFLENBQUM7SUFFRCxPQUFPLEdBQUcsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztBQUNuQyxDQUFDO0FBRUQsU0FBUyxXQUFXLENBQUMsR0FBWTtJQUMvQixJQUFJLEdBQUcsS0FBSyxTQUFTO1FBQUUsT0FBTyxFQUFFLENBQUM7SUFFakMsSUFBSSxPQUFPLEdBQUcsS0FBSyxRQUFRLElBQUksR0FBRyxLQUFLLElBQUksSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7UUFDbEUsTUFBTSxJQUFBLGlDQUFjLEVBQUMsMEJBQTBCLENBQUMsQ0FBQztJQUNuRCxDQUFDO0lBRUQsT0FBTyxHQUE4QixDQUFDO0FBQ3hDLENBQUM7QUFFRCwrRkFBK0Y7QUFDL0YsNEZBQTRGO0FBQzVGLFNB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
|
|
@@ -14,8 +14,12 @@ export interface ActionForm {
|
|
|
14
14
|
};
|
|
15
15
|
getLayout(): unknown;
|
|
16
16
|
}
|
|
17
|
+
export interface Action extends ActionForm {
|
|
18
|
+
setFields(values: Record<string, unknown>): Promise<void>;
|
|
19
|
+
execute(): Promise<unknown>;
|
|
20
|
+
}
|
|
17
21
|
export interface AgentActionClient {
|
|
18
|
-
|
|
22
|
+
loadAction(collection: string, action: string, recordIds: string[]): Promise<Action>;
|
|
19
23
|
}
|
|
20
24
|
export interface AgentActionClientOptions {
|
|
21
25
|
agentUrl: string;
|
|
@@ -20,7 +20,7 @@ function extractRawLayout(action) {
|
|
|
20
20
|
function createAgentActionClient({ agentUrl, token, actionEndpoints, }) {
|
|
21
21
|
const client = (0, agent_client_1.createRemoteAgentClient)({ url: agentUrl, token, actionEndpoints });
|
|
22
22
|
return {
|
|
23
|
-
|
|
23
|
+
loadAction: (collection, action, recordIds) => client.collection(collection).action(action, { recordIds }),
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
26
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWdlbnQtYWN0aW9uLWNsaWVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hY3Rpb24vYWdlbnQtYWN0aW9uLWNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQTJDQSw0Q0FJQztBQU9ELDBDQVdDO0FBOURELDREQUFvRTtBQW1DcEUsbUdBQW1HO0FBQ25HLHFHQUFxRztBQUNyRyxnR0FBZ0c7QUFDaEcsa0dBQWtHO0FBQ2xHLDBGQUEwRjtBQUMxRixTQUFnQixnQkFBZ0IsQ0FBQyxNQUFrQjtJQUNqRCxNQUFNLElBQUksR0FBRyxNQUFNLENBQUMsU0FBUyxFQUF3RCxDQUFDO0lBRXRGLE9BQU8sSUFBSSxDQUFDLE1BQU0sSUFBSSxFQUFFLENBQUM7QUFDM0IsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxTQUF3Qix1QkFBdUIsQ0FBQyxFQUM5QyxRQUFRLEVBQ1IsS0FBSyxFQUNMLGVBQWUsR0FDVTtJQUN6QixNQUFNLE1BQU0sR0FBRyxJQUFBLHNDQUF1QixFQUFDLEVBQUUsR0FBRyxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQUUsZUFBZSxFQUFFLENBQUMsQ0FBQztJQUVsRixPQUFPO1FBQ0wsVUFBVSxFQUFFLENBQUMsVUFBVSxFQUFFLE1BQU0sRUFBRSxTQUFTLEVBQUUsRUFBRSxDQUM1QyxNQUFNLENBQUMsVUFBVSxDQUFDLFVBQVUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsRUFBRSxTQUFTLEVBQUUsQ0FBQztLQUM5RCxDQUFDO0FBQ0osQ0FBQyJ9
|
|
@@ -9,6 +9,8 @@ const agent_query_1 = require("./agent-query");
|
|
|
9
9
|
const response_mappers_1 = require("./response-mappers");
|
|
10
10
|
const agent_route_helpers_1 = require("../http/agent-route-helpers");
|
|
11
11
|
const bff_local_errors_1 = require("../http/bff-local-errors");
|
|
12
|
+
const agent_capabilities_fetcher_1 = __importDefault(require("../read-model/agent-capabilities-fetcher"));
|
|
13
|
+
const capabilities_validator_1 = require("../validation/capabilities-validator");
|
|
12
14
|
const relation_field_guard_1 = __importDefault(require("../validation/relation-field-guard"));
|
|
13
15
|
const DATA_ROUTE = /^\/agent\/v1\/([^/]+)\/(list|count)$/;
|
|
14
16
|
const RELATION_ROUTE = /^\/agent\/v1\/([^/]+)\/relations\/([^/]+)\/(list|count)$/;
|
|
@@ -21,15 +23,47 @@ function resolveForeignCollection(target) {
|
|
|
21
23
|
return null;
|
|
22
24
|
return target.target;
|
|
23
25
|
}
|
|
26
|
+
// The route's allow-list check ran against the read-model captured before this request awaited the
|
|
27
|
+
// capabilities; a refresh in that window can drop the collection, so it is re-checked against the
|
|
28
|
+
// generation the capabilities belong to. The agent stays the authority (it asserts canBrowse itself)
|
|
29
|
+
// — this keeps the BFF from serving a collection its own current schema no longer exposes.
|
|
30
|
+
function assertCollectionStillAllowed(readModel, collection) {
|
|
31
|
+
if (!readModel.isCollectionAllowed(collection))
|
|
32
|
+
throw (0, bff_local_errors_1.unknownCollection)();
|
|
33
|
+
}
|
|
34
|
+
function resolveCapabilities(deps) {
|
|
35
|
+
return (0, agent_route_helpers_1.callAgent)(() => deps.store.getCapabilities(deps.collection, (0, agent_capabilities_fetcher_1.default)({ agentUrl: deps.agentUrl, token: deps.token })), deps.logger);
|
|
36
|
+
}
|
|
24
37
|
async function handleList(ctx, body, deps) {
|
|
25
38
|
(0, relation_field_guard_1.default)((0, agent_query_1.collectListFieldPaths)(body));
|
|
39
|
+
const validationInput = {
|
|
40
|
+
filter: body.filter,
|
|
41
|
+
sortFields: body.sort?.map(clause => clause.field),
|
|
42
|
+
projectionFields: body.projection,
|
|
43
|
+
};
|
|
44
|
+
// The store hands back the read-model the capabilities belong to; using it keeps the primary keys
|
|
45
|
+
// serialized below from a different schema generation than the fields just validated, and re-checks
|
|
46
|
+
// the allow-list in case that generation dropped the collection.
|
|
47
|
+
let { primaryKeys } = deps;
|
|
48
|
+
if ((0, capabilities_validator_1.hasCapabilityConstrainedInput)(validationInput)) {
|
|
49
|
+
const { capabilities, readModel } = await resolveCapabilities(deps);
|
|
50
|
+
assertCollectionStillAllowed(readModel, deps.collection);
|
|
51
|
+
(0, capabilities_validator_1.assertValidAgainstCapabilities)(validationInput, capabilities);
|
|
52
|
+
primaryKeys = readModel.getPrimaryKeys(deps.collection);
|
|
53
|
+
}
|
|
26
54
|
const query = (0, agent_query_1.buildListAgentQuery)(deps.collection, deps.timezone, body);
|
|
27
55
|
const records = await (0, agent_route_helpers_1.callAgent)(() => deps.client.list(deps.collection, query), deps.logger);
|
|
28
56
|
ctx.status = 200;
|
|
29
|
-
ctx.body = (0, response_mappers_1.mapListResponse)(deps.collection, records,
|
|
57
|
+
ctx.body = (0, response_mappers_1.mapListResponse)(deps.collection, records, primaryKeys);
|
|
30
58
|
}
|
|
31
59
|
async function handleCount(ctx, body, deps) {
|
|
32
60
|
(0, relation_field_guard_1.default)((0, agent_query_1.collectCountFieldPaths)(body));
|
|
61
|
+
// Count carries only a filter (no sort/projection), so that is all there is to validate.
|
|
62
|
+
if (body.filter !== undefined) {
|
|
63
|
+
const { capabilities, readModel } = await resolveCapabilities(deps);
|
|
64
|
+
assertCollectionStillAllowed(readModel, deps.collection);
|
|
65
|
+
(0, capabilities_validator_1.assertValidAgainstCapabilities)({ filter: body.filter }, capabilities);
|
|
66
|
+
}
|
|
33
67
|
const query = (0, agent_query_1.buildCountAgentQuery)(deps.timezone, body);
|
|
34
68
|
const raw = await (0, agent_route_helpers_1.callAgent)(() => deps.client.countRaw(deps.collection, query), deps.logger);
|
|
35
69
|
ctx.status = 200;
|
|
@@ -102,6 +136,9 @@ function createDataRoutesMiddleware({ store, agentUrl, logger, createClient = ag
|
|
|
102
136
|
const deps = {
|
|
103
137
|
collection,
|
|
104
138
|
client: createClient({ agentUrl, token }),
|
|
139
|
+
store,
|
|
140
|
+
agentUrl,
|
|
141
|
+
token,
|
|
105
142
|
timezone: ctx.state.timezone,
|
|
106
143
|
logger,
|
|
107
144
|
};
|
|
@@ -121,4 +158,4 @@ function createDataRoutesMiddleware({ store, agentUrl, logger, createClient = ag
|
|
|
121
158
|
}
|
|
122
159
|
};
|
|
123
160
|
}
|
|
124
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
161
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGF0YS1yb3V0ZXMtbWlkZGxld2FyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9kYXRhL2RhdGEtcm91dGVzLW1pZGRsZXdhcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUErTkEsNkNBd0RDO0FBelFELDRFQUErRDtBQUMvRCwrQ0FTdUI7QUFDdkIseURBQXVFO0FBQ3ZFLHFFQUtxQztBQUNyQywrREFBOEU7QUFDOUUsMEdBQXNGO0FBQ3RGLGlGQUc4QztBQUM5Qyw4RkFBNEU7QUFFNUUsTUFBTSxVQUFVLEdBQUcsc0NBQXNDLENBQUM7QUFDMUQsTUFBTSxjQUFjLEdBQUcsMERBQTBELENBQUM7QUFFbEYsb0dBQW9HO0FBQ3BHLHFHQUFxRztBQUNyRyxTQUFTLHdCQUF3QixDQUFDLE1BQWtDO0lBQ2xFLElBQUksQ0FBQyxNQUFNLElBQUksQ0FBQyxDQUFDLFFBQVEsSUFBSSxNQUFNLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUNsRCxJQUFJLE1BQU0sQ0FBQyxJQUFJLEtBQUssU0FBUyxJQUFJLE1BQU0sQ0FBQyxJQUFJLEtBQUssZUFBZTtRQUFFLE9BQU8sSUFBSSxDQUFDO0lBRTlFLE9BQU8sTUFBTSxDQUFDLE1BQU0sQ0FBQztBQUN2QixDQUFDO0FBcUJELG1HQUFtRztBQUNuRyxrR0FBa0c7QUFDbEcscUdBQXFHO0FBQ3JHLDJGQUEyRjtBQUMzRixTQUFTLDRCQUE0QixDQUFDLFNBQW9CLEVBQUUsVUFBa0I7SUFDNUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxtQkFBbUIsQ0FBQyxVQUFVLENBQUM7UUFBRSxNQUFNLElBQUEsb0NBQWlCLEdBQUUsQ0FBQztBQUM1RSxDQUFDO0FBRUQsU0FBUyxtQkFBbUIsQ0FDMUIsSUFBd0I7SUFFeEIsT0FBTyxJQUFBLCtCQUFTLEVBQ2QsR0FBRyxFQUFFLENBQ0gsSUFBSSxDQUFDLEtBQUssQ0FBQyxlQUFlLENBQ3hCLElBQUksQ0FBQyxVQUFVLEVBQ2YsSUFBQSxvQ0FBOEIsRUFBQyxFQUFFLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUSxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FDL0UsRUFDSCxJQUFJLENBQUMsTUFBTSxDQUNaLENBQUM7QUFDSixDQUFDO0FBRUQsS0FBSyxVQUFVLFVBQVUsQ0FBQyxHQUFZLEVBQUUsSUFBcUIsRUFBRSxJQUFxQjtJQUNsRixJQUFBLDhCQUEwQixFQUFDLElBQUEsbUNBQXFCLEVBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUV4RCxNQUFNLGVBQWUsR0FBRztRQUN0QixNQUFNLEVBQUUsSUFBSSxDQUFDLE1BQU07UUFDbkIsVUFBVSxFQUFFLElBQUksQ0FBQyxJQUFJLEVBQUUsR0FBRyxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQztRQUNsRCxnQkFBZ0IsRUFBRSxJQUFJLENBQUMsVUFBVTtLQUNsQyxDQUFDO0lBRUYsa0dBQWtHO0lBQ2xHLG9HQUFvRztJQUNwRyxpRUFBaUU7SUFDakUsSUFBSSxFQUFFLFdBQVcsRUFBRSxHQUFHLElBQUksQ0FBQztJQUUzQixJQUFJLElBQUEsc0RBQTZCLEVBQUMsZUFBZSxDQUFDLEVBQUUsQ0FBQztRQUNuRCxNQUFNLEVBQUUsWUFBWSxFQUFFLFNBQVMsRUFBRSxHQUFHLE1BQU0sbUJBQW1CLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDcEUsNEJBQTRCLENBQUMsU0FBUyxFQUFFLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUN6RCxJQUFBLHVEQUE4QixFQUFDLGVBQWUsRUFBRSxZQUFZLENBQUMsQ0FBQztRQUM5RCxXQUFXLEdBQUcsU0FBUyxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDMUQsQ0FBQztJQUVELE1BQU0sS0FBSyxHQUFHLElBQUEsaUNBQW1CLEVBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxJQUFJLENBQUMsUUFBUSxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQ3hFLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBQSwrQkFBUyxFQUFDLEdBQUcsRUFBRSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLEVBQUUsS0FBSyxDQUFDLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBRTdGLEdBQUcsQ0FBQyxNQUFNLEdBQUcsR0FBRyxDQUFDO0lBQ2pCLEdBQUcsQ0FBQyxJQUFJLEdBQUcsSUFBQSxrQ0FBZSxFQUFDLElBQUksQ0FBQyxVQUFVLEVBQUUsT0FBTyxFQUFFLFdBQVcsQ0FBQyxDQUFDO0FBQ3BFLENBQUM7QUFFRCxLQUFLLFVBQVUsV0FBVyxDQUFDLEdBQVksRUFBRSxJQUFzQixFQUFFLElBQXdCO0lBQ3ZGLElBQUEsOEJBQTBCLEVBQUMsSUFBQSxvQ0FBc0IsRUFBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBRXpELHlGQUF5RjtJQUN6RixJQUFJLElBQUksQ0FBQyxNQUFNLEtBQUssU0FBUyxFQUFFLENBQUM7UUFDOUIsTUFBTSxFQUFFLFlBQVksRUFBRSxTQUFTLEVBQUUsR0FBRyxNQUFNLG1CQUFtQixDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3BFLDRCQUE0QixDQUFDLFNBQVMsRUFBRSxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDekQsSUFBQSx1REFBOEIsRUFBQyxFQUFFLE1BQU0sRUFBRSxJQUFJLENBQUMsTUFBTSxFQUFFLEVBQUUsWUFBWSxDQUFDLENBQUM7SUFDeEUsQ0FBQztJQUVELE1BQU0sS0FBSyxHQUFHLElBQUEsa0NBQW9CLEVBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxJQUFJLENBQUMsQ0FBQztJQUN4RCxNQUFNLEdBQUcsR0FBRyxNQUFNLElBQUEsK0JBQVMsRUFBQyxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLEtBQUssQ0FBQyxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUU3RixHQUFHLENBQUMsTUFBTSxHQUFHLEdBQUcsQ0FBQztJQUNqQixHQUFHLENBQUMsSUFBSSxHQUFHLElBQUEsbUNBQWdCLEVBQUMsR0FBRyxDQUFDLENBQUM7QUFDbkMsQ0FBQztBQVdELEtBQUssVUFBVSxrQkFBa0IsQ0FDL0IsR0FBWSxFQUNaLElBQTZCLEVBQzdCLElBQTZCO0lBRTdCLCtGQUErRjtJQUMvRixnR0FBZ0c7SUFDaEcseUVBQXlFO0lBQ3pFLElBQUEsOEJBQTBCLEVBQUMsSUFBQSxtQ0FBcUIsRUFBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBRXhELE1BQU0sS0FBSyxHQUFHLElBQUEsaUNBQW1CLEVBQUMsSUFBSSxDQUFDLGlCQUFpQixFQUFFLElBQUksQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDL0UsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFBLCtCQUFTLEVBQzdCLEdBQUcsRUFBRSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUSxFQUFFLEtBQUssQ0FBQyxFQUNwRixJQUFJLENBQUMsTUFBTSxDQUNaLENBQUM7SUFFRixHQUFHLENBQUMsTUFBTSxHQUFHLEdBQUcsQ0FBQztJQUNqQixHQUFHLENBQUMsSUFBSSxHQUFHLElBQUEsa0NBQWUsRUFBQyxJQUFJLENBQUMsaUJBQWlCLEVBQUUsT0FBTyxFQUFFLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQztBQUNoRixDQUFDO0FBRUQsS0FBSyxVQUFVLG1CQUFtQixDQUNoQyxHQUFZLEVBQ1osSUFBOEIsRUFDOUIsSUFBeUI7SUFFekIsSUFBQSw4QkFBMEIsRUFBQyxJQUFBLG9DQUFzQixFQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7SUFFekQsTUFBTSxLQUFLLEdBQUcsSUFBQSxrQ0FBb0IsRUFBQyxJQUFJLENBQUMsUUFBUSxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQ3hELE1BQU0sR0FBRyxHQUFHLE1BQU0sSUFBQSwrQkFBUyxFQUN6QixHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLElBQUksQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUSxFQUFFLEtBQUssQ0FBQyxFQUN4RixJQUFJLENBQUMsTUFBTSxDQUNaLENBQUM7SUFFRixHQUFHLENBQUMsTUFBTSxHQUFHLEdBQUcsQ0FBQztJQUNqQixHQUFHLENBQUMsSUFBSSxHQUFHLElBQUEsbUNBQWdCLEVBQUMsR0FBRyxDQUFDLENBQUM7QUFDbkMsQ0FBQztBQUVELEtBQUssVUFBVSxjQUFjLENBQzNCLEdBQVksRUFDWixPQUFnQixFQUNoQixLQUFzQixFQUN0QixFQUFFLElBQUksRUFBRSxTQUFTLEVBQXNEO0lBRXZFLE1BQU0sUUFBUSxHQUFHLElBQUEsbUNBQWEsRUFBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEVBQUUsZUFBZSxDQUFDLENBQUM7SUFDMUQsTUFBTSxTQUFTLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBcUIsQ0FBQztJQUUvQyxxRkFBcUY7SUFDckYsc0RBQXNEO0lBQ3RELE1BQU0saUJBQWlCLEdBQUcsd0JBQXdCLENBQ2hELFNBQVMsQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLFFBQVEsQ0FBQyxDQUN2RCxDQUFDO0lBRUYsa0dBQWtHO0lBQ2xHLGdHQUFnRztJQUNoRyx5RkFBeUY7SUFDekYsSUFBSSxDQUFDLGlCQUFpQixFQUFFLENBQUM7UUFDdkIsTUFBTSxJQUFBLGtDQUFlLEVBQUMscUJBQXFCLElBQUksQ0FBQyxVQUFVLElBQUksUUFBUSxFQUFFLENBQUMsQ0FBQztJQUM1RSxDQUFDO0lBRUQsaUdBQWlHO0lBQ2pHLCtGQUErRjtJQUMvRixJQUFJLENBQUMsU0FBUyxDQUFDLG1CQUFtQixDQUFDLGlCQUFpQixDQUFDLEVBQUUsQ0FBQztRQUN0RCxNQUFNLElBQUEsb0NBQWlCLEdBQUUsQ0FBQztJQUM1QixDQUFDO0lBRUQsTUFBTSxZQUFZLEdBQXdCLEVBQUUsR0FBRyxJQUFJLEVBQUUsUUFBUSxFQUFFLGlCQUFpQixFQUFFLENBQUM7SUFFbkYsSUFBSSxTQUFTLEtBQUssTUFBTSxFQUFFLENBQUM7UUFDekIsTUFBTSxrQkFBa0IsQ0FBQyxHQUFHLEVBQUUsSUFBQSxzQ0FBd0IsRUFBQyxPQUFPLENBQUMsRUFBRTtZQUMvRCxHQUFHLFlBQVk7WUFDZixXQUFXLEVBQUUsU0FBUyxDQUFDLGNBQWMsQ0FBQyxpQkFBaUIsQ0FBQztTQUN6RCxDQUFDLENBQUM7SUFDTCxDQUFDO1NBQU0sQ0FBQztRQUNOLE1BQU0sbUJBQW1CLENBQUMsR0FBRyxFQUFFLElBQUEsdUNBQXlCLEVBQUMsT0FBTyxDQUFDLEVBQUUsWUFBWSxDQUFDLENBQUM7SUFDbkYsQ0FBQztBQUNILENBQUM7QUFFRCxTQUF3QiwwQkFBMEIsQ0FBQyxFQUNqRCxLQUFLLEVBQ0wsUUFBUSxFQUNSLE1BQU0sRUFDTixZQUFZLEdBQUcsMkJBQTRCLEdBQ2Y7SUFDNUIsT0FBTyxLQUFLLFVBQVUsb0JBQW9CLENBQUMsR0FBRyxFQUFFLElBQUk7UUFDbEQsTUFBTSxTQUFTLEdBQUcsVUFBVSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDNUMsTUFBTSxhQUFhLEdBQUcsU0FBUyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3ZFLE1BQU0sS0FBSyxHQUFHLFNBQVMsSUFBSSxhQUFhLENBQUM7UUFFekMsSUFBSSxDQUFDLEtBQUssSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLE1BQU0sRUFBRSxDQUFDO1lBQ3BDLE1BQU0sSUFBSSxFQUFFLENBQUM7WUFFYixPQUFPO1FBQ1QsQ0FBQztRQUVELE1BQU0sVUFBVSxHQUFHLElBQUEsbUNBQWEsRUFBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEVBQUUsaUJBQWlCLENBQUMsQ0FBQztRQUM5RCxNQUFNLEtBQUssR0FBRyxJQUFBLHVDQUFpQixFQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3JDLE1BQU0sU0FBUyxHQUFHLE1BQU0sSUFBQSxzQ0FBZ0IsRUFBQyxLQUFLLENBQUMsQ0FBQztRQUVoRCxJQUFJLENBQUMsU0FBUyxDQUFDLG1CQUFtQixDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7WUFDL0MsNkZBQTZGO1lBQzdGLDRGQUE0RjtZQUM1Riw2RkFBNkY7WUFDN0YseUVBQXlFO1lBQ3pFLE1BQU0sSUFBQSxvQ0FBaUIsRUFBQyx1QkFBdUIsVUFBVSxFQUFFLENBQUMsQ0FBQztRQUMvRCxDQUFDO1FBRUQsTUFBTSxJQUFJLEdBQXVCO1lBQy9CLFVBQVU7WUFDVixNQUFNLEVBQUUsWUFBWSxDQUFDLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxDQUFDO1lBQ3pDLEtBQUs7WUFDTCxRQUFRO1lBQ1IsS0FBSztZQUNMLFFBQVEsRUFBRSxHQUFHLENBQUMsS0FBSyxDQUFDLFFBQWtCO1lBQ3RDLE1BQU07U0FDUCxDQUFDO1FBQ0YsTUFBTSxPQUFPLEdBQUcsR0FBRyxDQUFDLE9BQU8sQ0FBQyxJQUFJLElBQUksRUFBRSxDQUFDO1FBRXZDLElBQUksYUFBYSxFQUFFLENBQUM7WUFDbEIsTUFBTSxjQUFjLENBQUMsR0FBRyxFQUFFLE9BQU8sRUFBRSxhQUFhLEVBQUUsRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQztZQUV2RSxPQUFPO1FBQ1QsQ0FBQztRQUVELE1BQU0sU0FBUyxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQXFCLENBQUM7UUFFL0MsSUFBSSxTQUFTLEtBQUssTUFBTSxFQUFFLENBQUM7WUFDekIsTUFBTSxJQUFJLEdBQUcsSUFBQSw4QkFBZ0IsRUFBQyxPQUFPLENBQUMsQ0FBQztZQUN2QyxNQUFNLFVBQVUsQ0FBQyxHQUFHLEVBQUUsSUFBSSxFQUFFLEVBQUUsR0FBRyxJQUFJLEVBQUUsV0FBVyxFQUFFLFNBQVMsQ0FBQyxjQUFjLENBQUMsVUFBVSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQzlGLENBQUM7YUFBTSxDQUFDO1lBQ04sTUFBTSxJQUFJLEdBQUcsSUFBQSwrQkFBaUIsRUFBQyxPQUFPLENBQUMsQ0FBQztZQUN4QyxNQUFNLFdBQVcsQ0FBQyxHQUFHLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQ3JDLENBQUM7SUFDSCxDQUFDLENBQUM7QUFDSixDQUFDIn0=
|
|
@@ -9,4 +9,5 @@ export declare function invalidRequest(message?: string, details?: unknown): Bff
|
|
|
9
9
|
export declare function mappingError(message?: string): BffHttpError;
|
|
10
10
|
export declare function schemaUnavailable(message?: string): BffHttpError;
|
|
11
11
|
export declare function unsupportedActionResult(message?: string): BffHttpError;
|
|
12
|
+
export declare function actionRequiresApproval(message?: string, details?: unknown): BffHttpError;
|
|
12
13
|
//# sourceMappingURL=bff-local-errors.d.ts.map
|
|
@@ -10,6 +10,7 @@ exports.invalidRequest = invalidRequest;
|
|
|
10
10
|
exports.mappingError = mappingError;
|
|
11
11
|
exports.schemaUnavailable = schemaUnavailable;
|
|
12
12
|
exports.unsupportedActionResult = unsupportedActionResult;
|
|
13
|
+
exports.actionRequiresApproval = actionRequiresApproval;
|
|
13
14
|
const bff_http_error_1 = require("./bff-http-error");
|
|
14
15
|
function unknownCollection(message = 'Unknown collection') {
|
|
15
16
|
return new bff_http_error_1.BffHttpError(404, 'unknown_collection', message);
|
|
@@ -41,4 +42,7 @@ function schemaUnavailable(message = 'The agent schema is unavailable') {
|
|
|
41
42
|
function unsupportedActionResult(message = 'Unsupported action result') {
|
|
42
43
|
return new bff_http_error_1.BffHttpError(501, 'unsupported_action_result', message);
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
+
function actionRequiresApproval(message = 'This action requires an approval before it can run', details) {
|
|
46
|
+
return new bff_http_error_1.BffHttpError(403, 'action_requires_approval', message, details);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmZmLWxvY2FsLWVycm9ycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9odHRwL2JmZi1sb2NhbC1lcnJvcnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFFQSw4Q0FFQztBQUVELDBDQUVDO0FBRUQsc0NBRUM7QUFFRCxvREFFQztBQUVELGdEQUVDO0FBRUQsNENBRUM7QUFFRCx3Q0FFQztBQUVELG9DQUVDO0FBRUQsOENBRUM7QUFFRCwwREFFQztBQUVELHdEQUtDO0FBL0NELHFEQUFnRDtBQUVoRCxTQUFnQixpQkFBaUIsQ0FBQyxPQUFPLEdBQUcsb0JBQW9CO0lBQzlELE9BQU8sSUFBSSw2QkFBWSxDQUFDLEdBQUcsRUFBRSxvQkFBb0IsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUM5RCxDQUFDO0FBRUQsU0FBZ0IsZUFBZSxDQUFDLE9BQU8sR0FBRyxrQkFBa0I7SUFDMUQsT0FBTyxJQUFJLDZCQUFZLENBQUMsR0FBRyxFQUFFLGtCQUFrQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQzVELENBQUM7QUFFRCxTQUFnQixhQUFhLENBQUMsT0FBTyxHQUFHLGdCQUFnQjtJQUN0RCxPQUFPLElBQUksNkJBQVksQ0FBQyxHQUFHLEVBQUUsZ0JBQWdCLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDMUQsQ0FBQztBQUVELFNBQWdCLG9CQUFvQixDQUFDLE9BQU8sR0FBRywyQkFBMkI7SUFDeEUsT0FBTyxJQUFJLDZCQUFZLENBQUMsR0FBRyxFQUFFLHdCQUF3QixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQ2xFLENBQUM7QUFFRCxTQUFnQixrQkFBa0IsQ0FBQyxPQUFPLEdBQUcseUJBQXlCO0lBQ3BFLE9BQU8sSUFBSSw2QkFBWSxDQUFDLEdBQUcsRUFBRSxzQkFBc0IsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUNoRSxDQUFDO0FBRUQsU0FBZ0IsZ0JBQWdCLENBQUMsT0FBTyxHQUFHLHVCQUF1QjtJQUNoRSxPQUFPLElBQUksNkJBQVksQ0FBQyxHQUFHLEVBQUUsb0JBQW9CLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDOUQsQ0FBQztBQUVELFNBQWdCLGNBQWMsQ0FBQyxPQUFPLEdBQUcsaUJBQWlCLEVBQUUsT0FBaUI7SUFDM0UsT0FBTyxJQUFJLDZCQUFZLENBQUMsR0FBRyxFQUFFLGlCQUFpQixFQUFFLE9BQU8sRUFBRSxPQUFPLENBQUMsQ0FBQztBQUNwRSxDQUFDO0FBRUQsU0FBZ0IsWUFBWSxDQUFDLE9BQU8sR0FBRyxrQ0FBa0M7SUFDdkUsT0FBTyxJQUFJLDZCQUFZLENBQUMsR0FBRyxFQUFFLGVBQWUsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUN6RCxDQUFDO0FBRUQsU0FBZ0IsaUJBQWlCLENBQUMsT0FBTyxHQUFHLGlDQUFpQztJQUMzRSxPQUFPLElBQUksNkJBQVksQ0FBQyxHQUFHLEVBQUUsb0JBQW9CLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDOUQsQ0FBQztBQUVELFNBQWdCLHVCQUF1QixDQUFDLE9BQU8sR0FBRywyQkFBMkI7SUFDM0UsT0FBTyxJQUFJLDZCQUFZLENBQUMsR0FBRyxFQUFFLDJCQUEyQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQ3JFLENBQUM7QUFFRCxTQUFnQixzQkFBc0IsQ0FDcEMsT0FBTyxHQUFHLG9EQUFvRCxFQUM5RCxPQUFpQjtJQUVqQixPQUFPLElBQUksNkJBQVksQ0FBQyxHQUFHLEVBQUUsMEJBQTBCLEVBQUUsT0FBTyxFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQzdFLENBQUMifQ==
|
|
@@ -14,7 +14,15 @@ export default class ReadModelStore {
|
|
|
14
14
|
private readModel;
|
|
15
15
|
constructor(schemaCache: SchemaCache, capabilitiesCache: CapabilitiesCache);
|
|
16
16
|
getReadModel(): Promise<ReadModel>;
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Returns the capabilities together with the read-model they belong to, so a caller cannot mix a
|
|
19
|
+
* read-model captured earlier (allow-list, primary keys) with capabilities from a newer schema
|
|
20
|
+
* generation. Callers must use the returned `readModel`, not the one they held before this call.
|
|
21
|
+
*/
|
|
22
|
+
getCapabilities(collection: string, fetcher: CapabilitiesFetcher, attemptsLeft?: number): Promise<{
|
|
23
|
+
capabilities: CapabilitiesResult;
|
|
24
|
+
readModel: ReadModel;
|
|
25
|
+
}>;
|
|
18
26
|
ageSeconds(): number | undefined;
|
|
19
27
|
}
|
|
20
28
|
//# sourceMappingURL=read-model-store.d.ts.map
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const read_model_1 = __importDefault(require("./read-model"));
|
|
7
|
+
const MAX_GENERATION_RETRIES = 3;
|
|
7
8
|
/**
|
|
8
9
|
* Single owner of the coupled schema + capabilities lifecycle. A successful schema refresh (a bump
|
|
9
10
|
* of the cache `revision`) rebuilds the read-model and clears capabilities atomically, so the
|
|
@@ -25,19 +26,33 @@ class ReadModelStore {
|
|
|
25
26
|
}
|
|
26
27
|
return this.readModel;
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Returns the capabilities together with the read-model they belong to, so a caller cannot mix a
|
|
31
|
+
* read-model captured earlier (allow-list, primary keys) with capabilities from a newer schema
|
|
32
|
+
* generation. Callers must use the returned `readModel`, not the one they held before this call.
|
|
33
|
+
*/
|
|
34
|
+
async getCapabilities(collection, fetcher, attemptsLeft = MAX_GENERATION_RETRIES) {
|
|
35
|
+
if (attemptsLeft <= 0) {
|
|
36
|
+
throw new Error(`Schema generation kept changing while reading capabilities for "${collection}"`);
|
|
37
|
+
}
|
|
29
38
|
// Ensure any pending schema refresh (and its capabilities invalidation) runs first.
|
|
30
|
-
await this.getReadModel();
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
39
|
+
const readModel = await this.getReadModel();
|
|
40
|
+
const { revision } = this.schemaCache;
|
|
41
|
+
const capabilities = await this.capabilitiesCache.get(collection, fetcher);
|
|
42
|
+
// A refresh can land while the fetch is in flight: the cache drops the stale write but still
|
|
43
|
+
// resolves with it, so both reads are retried until they observe one generation. The revision is
|
|
44
|
+
// the discriminator, not the read-model identity — `SchemaCache` bumps the revision inside its
|
|
45
|
+
// refresh, before an awaiting `getReadModel` caller resumes and installs the new model, so an
|
|
46
|
+
// identity comparison still sees the old object in that window. Bounded because a refresh is
|
|
47
|
+
// driven by the 24h schema TTL, not by request volume.
|
|
48
|
+
if (this.schemaCache.revision !== revision) {
|
|
49
|
+
return this.getCapabilities(collection, fetcher, attemptsLeft - 1);
|
|
50
|
+
}
|
|
51
|
+
return { capabilities, readModel };
|
|
37
52
|
}
|
|
38
53
|
ageSeconds() {
|
|
39
54
|
return this.schemaCache.ageSeconds();
|
|
40
55
|
}
|
|
41
56
|
}
|
|
42
57
|
exports.default = ReadModelStore;
|
|
43
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
58
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVhZC1tb2RlbC1zdG9yZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9yZWFkLW1vZGVsL3JlYWQtbW9kZWwtc3RvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFJQSw4REFBcUM7QUFFckMsTUFBTSxzQkFBc0IsR0FBRyxDQUFDLENBQUM7QUFFakM7Ozs7R0FJRztBQUNILE1BQXFCLGNBQWM7SUFPakMsWUFBWSxXQUF3QixFQUFFLGlCQUFvQztRQUhsRSxrQkFBYSxHQUFHLENBQUMsQ0FBQyxDQUFDO1FBQ25CLGNBQVMsR0FBcUIsSUFBSSxDQUFDO1FBR3pDLElBQUksQ0FBQyxXQUFXLEdBQUcsV0FBVyxDQUFDO1FBQy9CLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxpQkFBaUIsQ0FBQztJQUM3QyxDQUFDO0lBRUQsS0FBSyxDQUFDLFlBQVk7UUFDaEIsTUFBTSxXQUFXLEdBQUcsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBRWpELElBQUksSUFBSSxDQUFDLFdBQVcsQ0FBQyxRQUFRLEtBQUssSUFBSSxDQUFDLGFBQWEsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUN4RSxJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksb0JBQVMsQ0FBQyxXQUFXLENBQUMsQ0FBQztZQUM1QyxJQUFJLENBQUMsYUFBYSxHQUFHLElBQUksQ0FBQyxXQUFXLENBQUMsUUFBUSxDQUFDO1lBQy9DLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNqQyxDQUFDO1FBRUQsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFDO0lBQ3hCLENBQUM7SUFFRDs7OztPQUlHO0lBQ0gsS0FBSyxDQUFDLGVBQWUsQ0FDbkIsVUFBa0IsRUFDbEIsT0FBNEIsRUFDNUIsWUFBWSxHQUFHLHNCQUFzQjtRQUVyQyxJQUFJLFlBQVksSUFBSSxDQUFDLEVBQUUsQ0FBQztZQUN0QixNQUFNLElBQUksS0FBSyxDQUNiLG1FQUFtRSxVQUFVLEdBQUcsQ0FDakYsQ0FBQztRQUNKLENBQUM7UUFFRCxvRkFBb0Y7UUFDcEYsTUFBTSxTQUFTLEdBQUcsTUFBTSxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7UUFDNUMsTUFBTSxFQUFFLFFBQVEsRUFBRSxHQUFHLElBQUksQ0FBQyxXQUFXLENBQUM7UUFDdEMsTUFBTSxZQUFZLEdBQUcsTUFBTSxJQUFJLENBQUMsaUJBQWlCLENBQUMsR0FBRyxDQUFDLFVBQVUsRUFBRSxPQUFPLENBQUMsQ0FBQztRQUUzRSw2RkFBNkY7UUFDN0YsaUdBQWlHO1FBQ2pHLCtGQUErRjtRQUMvRiw4RkFBOEY7UUFDOUYsNkZBQTZGO1FBQzdGLHVEQUF1RDtRQUN2RCxJQUFJLElBQUksQ0FBQyxXQUFXLENBQUMsUUFBUSxLQUFLLFFBQVEsRUFBRSxDQUFDO1lBQzNDLE9BQU8sSUFBSSxDQUFDLGVBQWUsQ0FBQyxVQUFVLEVBQUUsT0FBTyxFQUFFLFlBQVksR0FBRyxDQUFDLENBQUMsQ0FBQztRQUNyRSxDQUFDO1FBRUQsT0FBTyxFQUFFLFlBQVksRUFBRSxTQUFTLEVBQUUsQ0FBQztJQUNyQyxDQUFDO0lBRUQsVUFBVTtRQUNSLE9BQU8sSUFBSSxDQUFDLFdBQVcsQ0FBQyxVQUFVLEVBQUUsQ0FBQztJQUN2QyxDQUFDO0NBQ0Y7QUE3REQsaUNBNkRDIn0=
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { BffHttpError } from '../http/bff-http-error';
|
|
2
|
+
import type { CapabilitiesResult } from '../read-model/capabilities-cache';
|
|
3
|
+
export interface ValidateParams {
|
|
4
|
+
filter?: unknown;
|
|
5
|
+
sortFields?: string[];
|
|
6
|
+
projectionFields?: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare const MAX_FILTER_DEPTH = 100;
|
|
9
|
+
/**
|
|
10
|
+
* True when the request carries something capabilities can invalidate. Callers use it to skip the
|
|
11
|
+
* capabilities fetch entirely, so a plain list/count still succeeds while that fetch is unavailable.
|
|
12
|
+
*/
|
|
13
|
+
export declare function hasCapabilityConstrainedInput(params: ValidateParams): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Validates a request's filter, sort, and projection fields against the target collection's
|
|
16
|
+
* capabilities. Returns every offending field as a structured error (empty = valid); the caller
|
|
17
|
+
* surfaces the whole list or just the first. Throws a 500 mapping_error when a capabilities
|
|
18
|
+
* operator has no canonical mapping (a version skew between agent and BFF).
|
|
19
|
+
*/
|
|
20
|
+
export declare function validateAgainstCapabilities(params: ValidateParams, capabilities: CapabilitiesResult): BffHttpError[];
|
|
21
|
+
export declare function assertValidAgainstCapabilities(params: ValidateParams, capabilities: CapabilitiesResult): void;
|
|
22
|
+
//# sourceMappingURL=capabilities-validator.d.ts.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_FILTER_DEPTH = void 0;
|
|
4
|
+
exports.hasCapabilityConstrainedInput = hasCapabilityConstrainedInput;
|
|
5
|
+
exports.validateAgainstCapabilities = validateAgainstCapabilities;
|
|
6
|
+
exports.assertValidAgainstCapabilities = assertValidAgainstCapabilities;
|
|
7
|
+
const operator_normalizer_1 = require("./operator-normalizer");
|
|
8
|
+
const validation_errors_1 = require("./validation-errors");
|
|
9
|
+
const bff_local_errors_1 = require("../http/bff-local-errors");
|
|
10
|
+
function isBranch(node) {
|
|
11
|
+
return (typeof node === 'object' &&
|
|
12
|
+
node !== null &&
|
|
13
|
+
Array.isArray(node.conditions));
|
|
14
|
+
}
|
|
15
|
+
function isLeaf(node) {
|
|
16
|
+
return (typeof node === 'object' &&
|
|
17
|
+
node !== null &&
|
|
18
|
+
typeof node.field === 'string');
|
|
19
|
+
}
|
|
20
|
+
exports.MAX_FILTER_DEPTH = 100;
|
|
21
|
+
function collectLeaves(node, acc, depth = 0) {
|
|
22
|
+
if (depth > exports.MAX_FILTER_DEPTH)
|
|
23
|
+
throw (0, validation_errors_1.filterTooDeep)(exports.MAX_FILTER_DEPTH);
|
|
24
|
+
if (isBranch(node)) {
|
|
25
|
+
node.conditions.forEach(condition => collectLeaves(condition, acc, depth + 1));
|
|
26
|
+
}
|
|
27
|
+
else if (isLeaf(node)) {
|
|
28
|
+
const { operator } = node;
|
|
29
|
+
acc.push({ field: node.field, operator: typeof operator === 'string' ? operator : undefined });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function indexOperators(capabilities) {
|
|
33
|
+
const index = new Map();
|
|
34
|
+
capabilities.fields.forEach(field => index.set(field.name, field.operators ?? []));
|
|
35
|
+
return index;
|
|
36
|
+
}
|
|
37
|
+
function normalizeFieldOperators(field, operators) {
|
|
38
|
+
return operators.map(operator => {
|
|
39
|
+
const normalized = (0, operator_normalizer_1.normalizeOperator)(operator);
|
|
40
|
+
if (!normalized) {
|
|
41
|
+
throw (0, bff_local_errors_1.mappingError)(`Capabilities operator "${operator}" on field "${field}" has no canonical mapping`);
|
|
42
|
+
}
|
|
43
|
+
return normalized;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function validateFilter(filter, index) {
|
|
47
|
+
const leaves = [];
|
|
48
|
+
collectLeaves(filter, leaves);
|
|
49
|
+
const errors = [];
|
|
50
|
+
for (const leaf of leaves) {
|
|
51
|
+
const operators = index.get(leaf.field);
|
|
52
|
+
if (operators === undefined) {
|
|
53
|
+
errors.push((0, validation_errors_1.unknownField)(leaf.field));
|
|
54
|
+
}
|
|
55
|
+
else if (operators.length === 0) {
|
|
56
|
+
errors.push((0, validation_errors_1.fieldNotFilterable)(leaf.field));
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const normalized = normalizeFieldOperators(leaf.field, operators);
|
|
60
|
+
if (leaf.operator === undefined || !normalized.includes(leaf.operator)) {
|
|
61
|
+
errors.push((0, validation_errors_1.invalidFilterOperator)(leaf.field, normalized));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return errors;
|
|
66
|
+
}
|
|
67
|
+
function validateExistence(fields, index) {
|
|
68
|
+
return fields.filter(field => !index.has(field)).map(validation_errors_1.unknownField);
|
|
69
|
+
}
|
|
70
|
+
function dedupe(errors) {
|
|
71
|
+
const seen = new Set();
|
|
72
|
+
const result = [];
|
|
73
|
+
for (const error of errors) {
|
|
74
|
+
const key = `${error.type}:${error.details.field}`;
|
|
75
|
+
if (!seen.has(key)) {
|
|
76
|
+
seen.add(key);
|
|
77
|
+
result.push(error);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* True when the request carries something capabilities can invalidate. Callers use it to skip the
|
|
84
|
+
* capabilities fetch entirely, so a plain list/count still succeeds while that fetch is unavailable.
|
|
85
|
+
*/
|
|
86
|
+
function hasCapabilityConstrainedInput(params) {
|
|
87
|
+
return (params.filter !== undefined ||
|
|
88
|
+
(params.sortFields?.length ?? 0) > 0 ||
|
|
89
|
+
(params.projectionFields?.length ?? 0) > 0);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Validates a request's filter, sort, and projection fields against the target collection's
|
|
93
|
+
* capabilities. Returns every offending field as a structured error (empty = valid); the caller
|
|
94
|
+
* surfaces the whole list or just the first. Throws a 500 mapping_error when a capabilities
|
|
95
|
+
* operator has no canonical mapping (a version skew between agent and BFF).
|
|
96
|
+
*/
|
|
97
|
+
function validateAgainstCapabilities(params, capabilities) {
|
|
98
|
+
const index = indexOperators(capabilities);
|
|
99
|
+
return dedupe([
|
|
100
|
+
...validateFilter(params.filter, index),
|
|
101
|
+
...validateExistence(params.sortFields ?? [], index),
|
|
102
|
+
...validateExistence(params.projectionFields ?? [], index),
|
|
103
|
+
]);
|
|
104
|
+
}
|
|
105
|
+
function assertValidAgainstCapabilities(params, capabilities) {
|
|
106
|
+
const [first] = validateAgainstCapabilities(params, capabilities);
|
|
107
|
+
if (first)
|
|
108
|
+
throw first;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2FwYWJpbGl0aWVzLXZhbGlkYXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy92YWxpZGF0aW9uL2NhcGFiaWxpdGllcy12YWxpZGF0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBMEhBLHNFQU1DO0FBUUQsa0VBV0M7QUFFRCx3RUFPQztBQXpKRCwrREFBMEQ7QUFDMUQsMkRBSzZCO0FBQzdCLCtEQUF3RDtBQWF4RCxTQUFTLFFBQVEsQ0FBQyxJQUFhO0lBQzdCLE9BQU8sQ0FDTCxPQUFPLElBQUksS0FBSyxRQUFRO1FBQ3hCLElBQUksS0FBSyxJQUFJO1FBQ2IsS0FBSyxDQUFDLE9BQU8sQ0FBRSxJQUFpQyxDQUFDLFVBQVUsQ0FBQyxDQUM3RCxDQUFDO0FBQ0osQ0FBQztBQUVELFNBQVMsTUFBTSxDQUFDLElBQWE7SUFDM0IsT0FBTyxDQUNMLE9BQU8sSUFBSSxLQUFLLFFBQVE7UUFDeEIsSUFBSSxLQUFLLElBQUk7UUFDYixPQUFRLElBQTRCLENBQUMsS0FBSyxLQUFLLFFBQVEsQ0FDeEQsQ0FBQztBQUNKLENBQUM7QUFFWSxRQUFBLGdCQUFnQixHQUFHLEdBQUcsQ0FBQztBQUVwQyxTQUFTLGFBQWEsQ0FBQyxJQUFhLEVBQUUsR0FBaUIsRUFBRSxLQUFLLEdBQUcsQ0FBQztJQUNoRSxJQUFJLEtBQUssR0FBRyx3QkFBZ0I7UUFBRSxNQUFNLElBQUEsaUNBQWEsRUFBQyx3QkFBZ0IsQ0FBQyxDQUFDO0lBRXBFLElBQUksUUFBUSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7UUFDbkIsSUFBSSxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxhQUFhLENBQUMsU0FBUyxFQUFFLEdBQUcsRUFBRSxLQUFLLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNqRixDQUFDO1NBQU0sSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztRQUN4QixNQUFNLEVBQUUsUUFB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
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Operator } from '@forestadmin/datasource-toolkit';
|
|
2
|
+
/**
|
|
3
|
+
* Mirrors the agent's capabilities serialization (`packages/agent/src/routes/capabilities.ts`),
|
|
4
|
+
* which converts each PascalCase operator to snake_case before returning it. Kept identical so the
|
|
5
|
+
* inverse map below round-trips every operator the agent can emit.
|
|
6
|
+
*/
|
|
7
|
+
export declare function toSnakeCaseOperator(operator: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Maps an agent snake_case capabilities operator to the canonical PascalCase operator from
|
|
10
|
+
* datasource-toolkit `allOperators`. Returns undefined when no canonical operator matches, which
|
|
11
|
+
* only happens when the agent runs a newer operator set than this package (a version skew).
|
|
12
|
+
*/
|
|
13
|
+
export declare function normalizeOperator(snakeCaseOperator: string): Operator | undefined;
|
|
14
|
+
//# sourceMappingURL=operator-normalizer.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toSnakeCaseOperator = toSnakeCaseOperator;
|
|
4
|
+
exports.normalizeOperator = normalizeOperator;
|
|
5
|
+
const datasource_toolkit_1 = require("@forestadmin/datasource-toolkit");
|
|
6
|
+
/**
|
|
7
|
+
* Mirrors the agent's capabilities serialization (`packages/agent/src/routes/capabilities.ts`),
|
|
8
|
+
* which converts each PascalCase operator to snake_case before returning it. Kept identical so the
|
|
9
|
+
* inverse map below round-trips every operator the agent can emit.
|
|
10
|
+
*/
|
|
11
|
+
function toSnakeCaseOperator(operator) {
|
|
12
|
+
return operator
|
|
13
|
+
.split(/\.?(?=[A-Z])/)
|
|
14
|
+
.join('_')
|
|
15
|
+
.toLowerCase();
|
|
16
|
+
}
|
|
17
|
+
const SNAKE_TO_PASCAL = new Map(datasource_toolkit_1.allOperators.map(operator => [toSnakeCaseOperator(operator), operator]));
|
|
18
|
+
/**
|
|
19
|
+
* Maps an agent snake_case capabilities operator to the canonical PascalCase operator from
|
|
20
|
+
* datasource-toolkit `allOperators`. Returns undefined when no canonical operator matches, which
|
|
21
|
+
* only happens when the agent runs a newer operator set than this package (a version skew).
|
|
22
|
+
*/
|
|
23
|
+
function normalizeOperator(snakeCaseOperator) {
|
|
24
|
+
return SNAKE_TO_PASCAL.get(snakeCaseOperator);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BlcmF0b3Itbm9ybWFsaXplci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy92YWxpZGF0aW9uL29wZXJhdG9yLW5vcm1hbGl6ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFTQSxrREFLQztBQVdELDhDQUVDO0FBekJELHdFQUErRDtBQUUvRDs7OztHQUlHO0FBQ0gsU0FBZ0IsbUJBQW1CLENBQUMsUUFBZ0I7SUFDbEQsT0FBTyxRQUFRO1NBQ1osS0FBSyxDQUFDLGNBQWMsQ0FBQztTQUNyQixJQUFJLENBQUMsR0FBRyxDQUFDO1NBQ1QsV0FBVyxFQUFFLENBQUM7QUFDbkIsQ0FBQztBQUVELE1BQU0sZUFBZSxHQUFHLElBQUksR0FBRyxDQUM3QixpQ0FBWSxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUMsbUJBQW1CLENBQUMsUUFBUSxDQUFDLEVBQUUsUUFBUSxDQUFDLENBQUMsQ0FDeEUsQ0FBQztBQUVGOzs7O0dBSUc7QUFDSCxTQUFnQixpQkFBaUIsQ0FBQyxpQkFBeUI7SUFDekQsT0FBTyxlQUFlLENBQUMsR0FBRyxDQUFDLGlCQUFpQixDQUFDLENBQUM7QUFDaEQsQ0FBQyJ9
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { BffHttpError } from '../http/bff-http-error';
|
|
2
|
+
export declare function unknownField(field: string): BffHttpError;
|
|
3
|
+
export declare function fieldNotFilterable(field: string): BffHttpError;
|
|
4
|
+
export declare function filterTooDeep(maxDepth: number): BffHttpError;
|
|
5
|
+
export declare function invalidFilterOperator(field: string, validOperators: string[]): BffHttpError;
|
|
6
|
+
//# sourceMappingURL=validation-errors.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.unknownField = unknownField;
|
|
4
|
+
exports.fieldNotFilterable = fieldNotFilterable;
|
|
5
|
+
exports.filterTooDeep = filterTooDeep;
|
|
6
|
+
exports.invalidFilterOperator = invalidFilterOperator;
|
|
7
|
+
const bff_http_error_1 = require("../http/bff-http-error");
|
|
8
|
+
function unknownField(field) {
|
|
9
|
+
return new bff_http_error_1.BffHttpError(422, 'unknown_field', `Unknown field: ${field}`, { field });
|
|
10
|
+
}
|
|
11
|
+
function fieldNotFilterable(field) {
|
|
12
|
+
return new bff_http_error_1.BffHttpError(422, 'field_not_filterable', `Field is not filterable: ${field}`, {
|
|
13
|
+
field,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function filterTooDeep(maxDepth) {
|
|
17
|
+
return new bff_http_error_1.BffHttpError(400, 'filter_too_deep', `Filter nesting exceeds the maximum depth of ${maxDepth}`, { maxDepth });
|
|
18
|
+
}
|
|
19
|
+
function invalidFilterOperator(field, validOperators) {
|
|
20
|
+
return new bff_http_error_1.BffHttpError(400, 'invalid_filter_operator', `Unsupported operator for field: ${field}`, { field, validOperators });
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdGlvbi1lcnJvcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdmFsaWRhdGlvbi92YWxpZGF0aW9uLWVycm9ycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUVBLG9DQUVDO0FBRUQsZ0RBSUM7QUFFRCxzQ0FPQztBQUVELHNEQU9DO0FBNUJELDJEQUFzRDtBQUV0RCxTQUFnQixZQUFZLENBQUMsS0FBYTtJQUN4QyxPQUFPLElBQUksNkJBQVksQ0FBQyxHQUFHLEVBQUUsZUFBZSxFQUFFLGtCQUFrQixLQUFLLEVBQUUsRUFBRSxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUM7QUFDdEYsQ0FBQztBQUVELFNBQWdCLGtCQUFrQixDQUFDLEtBQWE7SUFDOUMsT0FBTyxJQUFJLDZCQUFZLENBQUMsR0FBRyxFQUFFLHNCQUFzQixFQUFFLDRCQUE0QixLQUFLLEVBQUUsRUFBRTtRQUN4RixLQUFLO0tBQ04sQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQUVELFNBQWdCLGFBQWEsQ0FBQyxRQUFnQjtJQUM1QyxPQUFPLElBQUksNkJBQVksQ0FDckIsR0FBRyxFQUNILGlCQUFpQixFQUNqQiwrQ0FBK0MsUUFBUSxFQUFFLEVBQ3pELEVBQUUsUUFBUSxFQUFFLENBQ2IsQ0FBQztBQUNKLENBQUM7QUFFRCxTQUFnQixxQkFBcUIsQ0FBQyxLQUFhLEVBQUUsY0FBd0I7SUFDM0UsT0FBTyxJQUFJLDZCQUFZLENBQ3JCLEdBQUcsRUFDSCx5QkFBeUIsRUFDekIsbUNBQW1DLEtBQUssRUFBRSxFQUMxQyxFQUFFLEtBQUssRUFBRSxjQUFjLEVBQUUsQ0FDMUIsQ0FBQztBQUNKLENBQUMifQ==
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forestadmin/agent-bff",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"forest-bff": "dist/cli.js"
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"test": "jest"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@forestadmin/agent-client": "1.
|
|
34
|
+
"@forestadmin/agent-client": "1.13.0",
|
|
35
|
+
"@forestadmin/datasource-toolkit": "1.54.0",
|
|
35
36
|
"@forestadmin/forestadmin-client": "1.41.3",
|
|
36
37
|
"@koa/bodyparser": "^6.1.0",
|
|
37
38
|
"jsonwebtoken": "^9.0.3",
|