@forestadmin/agent-bff 1.6.0 → 1.8.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-form-mapper.d.ts +18 -0
- package/dist/action/action-form-mapper.js +33 -0
- package/dist/action/action-routes-middleware.d.ts +12 -0
- package/dist/action/action-routes-middleware.js +97 -0
- package/dist/action/agent-action-client.d.ts +32 -0
- package/dist/action/agent-action-client.js +26 -0
- package/dist/cli-core.js +24 -2
- package/dist/data/agent-data-client.d.ts +17 -0
- package/dist/data/agent-data-client.js +32 -0
- package/dist/data/agent-query.d.ts +36 -0
- package/dist/data/agent-query.js +144 -0
- package/dist/data/data-routes-middleware.d.ts +12 -0
- package/dist/data/data-routes-middleware.js +124 -0
- package/dist/data/pack-id.d.ts +9 -0
- package/dist/data/pack-id.js +37 -0
- package/dist/data/response-mappers.d.ts +20 -0
- package/dist/data/response-mappers.js +38 -0
- package/dist/http/agent-error-mapper.d.ts +18 -0
- package/dist/http/agent-error-mapper.js +114 -0
- package/dist/http/agent-route-helpers.d.ts +9 -0
- package/dist/http/agent-route-helpers.js +51 -0
- package/dist/http/bff-local-errors.d.ts +12 -0
- package/dist/http/bff-local-errors.js +44 -0
- package/dist/read-model/read-model.d.ts +7 -0
- package/dist/read-model/read-model.js +15 -1
- package/package.json +1 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ActionForm } from './agent-action-client';
|
|
2
|
+
import type { ForestServerActionFormLayoutElement } from '@forestadmin/forestadmin-client';
|
|
3
|
+
export interface ActionFormFieldResponse {
|
|
4
|
+
name: string;
|
|
5
|
+
type: string;
|
|
6
|
+
value: unknown;
|
|
7
|
+
isRequired: boolean;
|
|
8
|
+
enumValues?: string[] | null;
|
|
9
|
+
}
|
|
10
|
+
export interface ActionFormResponse {
|
|
11
|
+
fields: ActionFormFieldResponse[];
|
|
12
|
+
canExecute: boolean;
|
|
13
|
+
requiredFields: string[];
|
|
14
|
+
skippedFields: string[];
|
|
15
|
+
layout: ForestServerActionFormLayoutElement[];
|
|
16
|
+
}
|
|
17
|
+
export declare function mapActionForm(action: ActionForm, skippedFields: string[], layout: ForestServerActionFormLayoutElement[]): ActionFormResponse;
|
|
18
|
+
//# sourceMappingURL=action-form-mapper.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mapActionForm = mapActionForm;
|
|
4
|
+
const ENUM_TYPE = 'Enum';
|
|
5
|
+
// Mirrors the MCP getActionForm tool, adding `layout`. A required field is "missing a value" only
|
|
6
|
+
// when its resolved value is null/undefined; an explicit empty string or 0 counts as present.
|
|
7
|
+
function mapActionForm(action, skippedFields, layout) {
|
|
8
|
+
const fields = action.getFields();
|
|
9
|
+
const requiredFields = fields
|
|
10
|
+
.filter(field => field.isRequired())
|
|
11
|
+
.filter(field => field.getValue() === undefined || field.getValue() === null)
|
|
12
|
+
.map(field => field.getName());
|
|
13
|
+
return {
|
|
14
|
+
fields: fields.map(field => {
|
|
15
|
+
const base = {
|
|
16
|
+
name: field.getName(),
|
|
17
|
+
type: field.getType(),
|
|
18
|
+
value: field.getValue(),
|
|
19
|
+
isRequired: field.isRequired() ?? false,
|
|
20
|
+
};
|
|
21
|
+
// enumValues is emitted only for Enum fields, matching the MCP tool.
|
|
22
|
+
if (field.getType() === ENUM_TYPE) {
|
|
23
|
+
return { ...base, enumValues: action.getEnumField(field.getName()).getOptions() ?? null };
|
|
24
|
+
}
|
|
25
|
+
return base;
|
|
26
|
+
}),
|
|
27
|
+
canExecute: requiredFields.length === 0,
|
|
28
|
+
requiredFields,
|
|
29
|
+
skippedFields,
|
|
30
|
+
layout,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWN0aW9uLWZvcm0tbWFwcGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FjdGlvbi9hY3Rpb24tZm9ybS1tYXBwZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUF1QkEsc0NBaUNDO0FBckRELE1BQU0sU0FBUyxHQUFHLE1BQU0sQ0FBQztBQWtCekIsa0dBQWtHO0FBQ2xHLDhGQUE4RjtBQUM5RixTQUFnQixhQUFhLENBQzNCLE1BQWtCLEVBQ2xCLGFBQXVCLEVBQ3ZCLE1BQTZDO0lBRTdDLE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxTQUFTLEVBQUUsQ0FBQztJQUVsQyxNQUFNLGNBQWMsR0FBRyxNQUFNO1NBQzFCLE1BQU0sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxVQUFVLEVBQUUsQ0FBQztTQUNuQyxNQUFNLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsUUFBUSxFQUFFLEtBQUssU0FBUyxJQUFJLEtBQUssQ0FBQyxRQUFRLEVBQUUsS0FBSyxJQUFJLENBQUM7U0FDNUUsR0FBRyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsS0FBSyxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUM7SUFFakMsT0FBTztRQUNMLE1BQU0sRUFBRSxNQUFNLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxFQUFFO1lBQ3pCLE1BQU0sSUFBSSxHQUE0QjtnQkFDcEMsSUFBSSxFQUFFLEtBQUssQ0FBQyxPQUFPLEVBQUU7Z0JBQ3JCLElBQUksRUFBRSxLQUFLLENBQUMsT0FBTyxFQUFFO2dCQUNyQixLQUFLLEVBQUUsS0FBSyxDQUFDLFFBQVEsRUFBRTtnQkFDdkIsVUFBVSxFQUFFLEtBQUssQ0FBQyxVQUFVLEVBQUUsSUFBSSxLQUFLO2FBQ3hDLENBQUM7WUFFRixxRUFBcUU7WUFDckUsSUFBSSxLQUFLLENBQUMsT0FBTyxFQUFFLEtBQUssU0FBUyxFQUFFLENBQUM7Z0JBQ2xDLE9BQU8sRUFBRSxHQUFHLElBQUksRUFBRSxVQUFVLEVBQUUsTUFBTSxDQUFDLFlBQVksQ0FBQyxLQUFLLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxVQUFVLEVBQUUsSUFBSSxJQUFJLEVBQUUsQ0FBQztZQUM1RixDQUFDO1lBRUQsT0FBTyxJQUFJLENBQUM7UUFDZCxDQUFDLENBQUM7UUFDRixVQUFVLEVBQUUsY0FBYyxDQUFDLE1BQU0sS0FBSyxDQUFDO1FBQ3ZDLGNBQWM7UUFDZCxhQUFhO1FBQ2IsTUFBTTtLQUNQLENBQUM7QUFDSixDQUFDIn0=
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AgentActionClient, AgentActionClientOptions } from './agent-action-client';
|
|
2
|
+
import type { Logger } from '../ports/logger-port';
|
|
3
|
+
import type ReadModelStore from '../read-model/read-model-store';
|
|
4
|
+
import type { Middleware } from 'koa';
|
|
5
|
+
export interface ActionRoutesMiddlewareOptions {
|
|
6
|
+
store: ReadModelStore;
|
|
7
|
+
agentUrl: string;
|
|
8
|
+
logger: Logger;
|
|
9
|
+
createClient?: (options: AgentActionClientOptions) => AgentActionClient;
|
|
10
|
+
}
|
|
11
|
+
export default function createActionRoutesMiddleware({ store, agentUrl, logger, createClient, }: ActionRoutesMiddlewareOptions): Middleware;
|
|
12
|
+
//# sourceMappingURL=action-routes-middleware.d.ts.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.default = createActionRoutesMiddleware;
|
|
37
|
+
const action_form_mapper_1 = require("./action-form-mapper");
|
|
38
|
+
const agent_action_client_1 = __importStar(require("./agent-action-client"));
|
|
39
|
+
const agent_route_helpers_1 = require("../http/agent-route-helpers");
|
|
40
|
+
const bff_local_errors_1 = require("../http/bff-local-errors");
|
|
41
|
+
const ACTION_FORM_ROUTE = /^\/agent\/v1\/([^/]+)\/actions\/([^/]+)\/form$/;
|
|
42
|
+
// recordIds are opaque BFF ids; only presence and array-ness are checked. An empty array is allowed
|
|
43
|
+
// on purpose (global/bulk actions have no selected records); the spec AC only covers a missing
|
|
44
|
+
// recordIds, so `[]` is an explicit BFF choice, not a spec requirement.
|
|
45
|
+
// Ids are coerced to strings so a valid `0` (numeric primary key) survives agent-client's downstream
|
|
46
|
+
// `.filter(Boolean)`, which would otherwise drop it and load the form as if no record were selected.
|
|
47
|
+
function parseRecordIds(raw) {
|
|
48
|
+
if (!Array.isArray(raw)) {
|
|
49
|
+
throw (0, bff_local_errors_1.invalidRequest)('recordIds is required and must be an array');
|
|
50
|
+
}
|
|
51
|
+
return raw.map(id => String(id));
|
|
52
|
+
}
|
|
53
|
+
function parseValues(raw) {
|
|
54
|
+
if (raw === undefined)
|
|
55
|
+
return {};
|
|
56
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
57
|
+
throw (0, bff_local_errors_1.invalidRequest)('values must be an object');
|
|
58
|
+
}
|
|
59
|
+
return raw;
|
|
60
|
+
}
|
|
61
|
+
function createActionRoutesMiddleware({ store, agentUrl, logger, createClient = agent_action_client_1.default, }) {
|
|
62
|
+
return async function actionRoutesMiddleware(ctx, next) {
|
|
63
|
+
const match = ACTION_FORM_ROUTE.exec(ctx.path);
|
|
64
|
+
if (!match || ctx.method !== 'POST') {
|
|
65
|
+
await next();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const collection = (0, agent_route_helpers_1.decodeSegment)(match[1], 'collection name');
|
|
69
|
+
const actionName = (0, agent_route_helpers_1.decodeSegment)(match[2], 'action name');
|
|
70
|
+
const token = (0, agent_route_helpers_1.requireAgentToken)(ctx);
|
|
71
|
+
const readModel = await (0, agent_route_helpers_1.resolveReadModel)(store);
|
|
72
|
+
// The read-model's action map IS the allow-list, so an absent action cannot be told from a
|
|
73
|
+
// known-but-disallowed one — every non-exposed action maps to 404 here; `action_not_allowed`
|
|
74
|
+
// (403) has no local trigger, mirroring `collection_not_allowed`/`relation_not_allowed`. The
|
|
75
|
+
// URL identity is resolved before the body, so a bad action 404s before its payload is read.
|
|
76
|
+
// TODO(PRD-673): distinguish disallowed from unknown when a separate exposure source exists.
|
|
77
|
+
if (!readModel.isActionAllowed(collection, actionName)) {
|
|
78
|
+
throw (0, bff_local_errors_1.unknownAction)(`Unknown action: ${collection}.${actionName}`);
|
|
79
|
+
}
|
|
80
|
+
const body = (ctx.request.body ?? {});
|
|
81
|
+
const recordIds = parseRecordIds(body.recordIds);
|
|
82
|
+
const values = parseValues(body.values);
|
|
83
|
+
const client = createClient({
|
|
84
|
+
agentUrl,
|
|
85
|
+
token,
|
|
86
|
+
actionEndpoints: readModel.getActionEndpoints(),
|
|
87
|
+
});
|
|
88
|
+
// Each agent-hitting call is wrapped on its own; getFields/extractRawLayout/mapping stay outside
|
|
89
|
+
// callAgent so a local BFF bug surfaces as a 500, not a mislabelled agent error. Fields and
|
|
90
|
+
// layout are read AFTER tryToSetFields because a change hook rebuilds them in place.
|
|
91
|
+
const action = await (0, agent_route_helpers_1.callAgent)(() => client.loadActionForm(collection, actionName, recordIds), logger);
|
|
92
|
+
const skippedFields = await (0, agent_route_helpers_1.callAgent)(() => action.tryToSetFields(values), logger);
|
|
93
|
+
ctx.status = 200;
|
|
94
|
+
ctx.body = (0, action_form_mapper_1.mapActionForm)(action, skippedFields, (0, agent_action_client_1.extractRawLayout)(action));
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWN0aW9uLXJvdXRlcy1taWRkbGV3YXJlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FjdGlvbi9hY3Rpb24tcm91dGVzLW1pZGRsZXdhcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFvREEsK0NBbURDO0FBbEdELDZEQUFxRDtBQUNyRCw2RUFBeUY7QUFDekYscUVBS3FDO0FBQ3JDLCtEQUF5RTtBQUV6RSxNQUFNLGlCQUFpQixHQUFHLGdEQUFnRCxDQUFDO0FBTzNFLG9HQUFvRztBQUNwRywrRkFBK0Y7QUFDL0Ysd0VBQXdFO0FBQ3hFLHFHQUFxRztBQUNyRyxxR0FBcUc7QUFDckcsU0FBUyxjQUFjLENBQUMsR0FBWTtJQUNsQyxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQ3hCLE1BQU0sSUFBQSxpQ0FBYyxFQUFDLDRDQUE0QyxDQUFDLENBQUM7SUFDckUsQ0FBQztJQUVELE9BQU8sR0FBRyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ25DLENBQUM7QUFFRCxTQUFTLFdBQVcsQ0FBQyxHQUFZO0lBQy9CLElBQUksR0FBRyxLQUFLLFNBQVM7UUFBRSxPQUFPLEVBQUUsQ0FBQztJQUVqQyxJQUFJLE9BQU8sR0FBRyxLQUFLLFFBQVEsSUFBSSxHQUFHLEtBQUssSUFBSSxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUNsRSxNQUFNLElBQUEsaUNBQWMsRUFBQywwQkFBMEIsQ0FBQyxDQUFDO0lBQ25ELENBQUM7SUFFRCxPQUFPLEdBQThCLENBQUM7QUFDeEMsQ0FBQztBQVNELFNBQXdCLDRCQUE0QixDQUFDLEVBQ25ELEtBQUssRUFDTCxRQUFRLEVBQ1IsTUFBTSxFQUNOLFlBQVksR0FBRyw2QkFBOEIsR0FDZjtJQUM5QixPQUFPLEtBQUssVUFBVSxzQkFBc0IsQ0FBQyxHQUFHLEVBQUUsSUFBSTtRQUNwRCxNQUFNLEtBQUssR0FBRyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBRS9DLElBQUksQ0FBQyxLQUFLLElBQUksR0FBRyxDQUFDLE1BQU0sS0FBSyxNQUFNLEVBQUUsQ0FBQztZQUNwQyxNQUFNLElBQUksRUFBRSxDQUFDO1lBRWIsT0FBTztRQUNULENBQUM7UUFFRCxNQUFNLFVBQVUsR0FBRyxJQUFBLG1DQUFhLEVBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLGlCQUFpQixDQUFDLENBQUM7UUFDOUQsTUFBTSxVQUFVLEdBQUcsSUFBQSxtQ0FBYSxFQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxhQUFhLENBQUMsQ0FBQztRQUMxRCxNQUFNLEtBQUssR0FBRyxJQUFBLHVDQUFpQixFQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3JDLE1BQU0sU0FBUyxHQUFHLE1BQU0sSUFBQSxzQ0FBZ0IsRUFBQyxLQUFLLENBQUMsQ0FBQztRQUVoRCwyRkFBMkY7UUFDM0YsNkZBQTZGO1FBQzdGLDZGQUE2RjtRQUM3Riw2RkFBNkY7UUFDN0YsNkZBQTZGO1FBQzdGLElBQUksQ0FBQyxTQUFTLENBQUMsZUFBZSxDQUFDLFVBQVUsRUFBRSxVQUFVLENBQUMsRUFBRSxDQUFDO1lBQ3ZELE1BQU0sSUFBQSxnQ0FBYSxFQUFDLG1CQUFtQixVQUFVLElBQUksVUFBVSxFQUFFLENBQUMsQ0FBQztRQUNyRSxDQUFDO1FBRUQsTUFBTSxJQUFJLEdBQUcsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLElBQUksSUFBSSxFQUFFLENBQTBCLENBQUM7UUFDL0QsTUFBTSxTQUFTLEdBQUcsY0FBYyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUNqRCxNQUFNLE1BQU0sR0FBRyxXQUFXLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBRXhDLE1BQU0sTUFBTSxHQUFHLFlBQVksQ0FBQztZQUMxQixRQUFRO1lBQ1IsS0FBSztZQUNMLGVBQWUsRUFBRSxTQUFTLENBQUMsa0JBQWtCLEVBQUU7U0FDaEQsQ0FBQyxDQUFDO1FBRUgsaUdBQWlHO1FBQ2pHLDRGQUE0RjtRQUM1RixxRkFBcUY7UUFDckYsTUFBTSxNQUFNLEdBQUcsTUFBTSxJQUFBLCtCQUFTLEVBQzVCLEdBQUcsRUFBRSxDQUFDLE1BQU0sQ0FBQyxjQUFjLENBQUMsVUFBVSxFQUFFLFVBQVUsRUFBRSxTQUFTLENBQUMsRUFDOUQsTUFBTSxDQUNQLENBQUM7UUFDRixNQUFNLGFBQWEsR0FBRyxNQUFNLElBQUEsK0JBQVMsRUFBQyxHQUFHLEVBQUUsQ0FBQyxNQUFNLENBQUMsY0FBYyxDQUFDLE1BQU0sQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBRW5GLEdBQUcsQ0FBQyxNQUFNLEdBQUcsR0FBRyxDQUFDO1FBQ2pCLEdBQUcsQ0FBQyxJQUFJLEdBQUcsSUFBQSxrQ0FBYSxFQUFDLE1BQU0sRUFBRSxhQUFhLEVBQUUsSUFBQSxzQ0FBZ0IsRUFBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO0lBQzVFLENBQUMsQ0FBQztBQUNKLENBQUMifQ==
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ActionEndpointsByCollection } from '@forestadmin/agent-client';
|
|
2
|
+
import type { ForestServerActionFormLayoutElement } from '@forestadmin/forestadmin-client';
|
|
3
|
+
export interface ActionFormField {
|
|
4
|
+
getName(): string;
|
|
5
|
+
getType(): string;
|
|
6
|
+
getValue(): unknown;
|
|
7
|
+
isRequired(): boolean | undefined;
|
|
8
|
+
}
|
|
9
|
+
export interface ActionForm {
|
|
10
|
+
tryToSetFields(values: Record<string, unknown>): Promise<string[]>;
|
|
11
|
+
getFields(): ActionFormField[];
|
|
12
|
+
getEnumField(name: string): {
|
|
13
|
+
getOptions(): string[] | undefined;
|
|
14
|
+
};
|
|
15
|
+
getLayout(): unknown;
|
|
16
|
+
}
|
|
17
|
+
export interface AgentActionClient {
|
|
18
|
+
loadActionForm(collection: string, action: string, recordIds: string[]): Promise<ActionForm>;
|
|
19
|
+
}
|
|
20
|
+
export interface AgentActionClientOptions {
|
|
21
|
+
agentUrl: string;
|
|
22
|
+
token: string;
|
|
23
|
+
actionEndpoints: ActionEndpointsByCollection;
|
|
24
|
+
}
|
|
25
|
+
export declare function extractRawLayout(action: ActionForm): ForestServerActionFormLayoutElement[];
|
|
26
|
+
/**
|
|
27
|
+
* Thin action-form client bound to a request's agent token. Reuses agent-client's stateful form
|
|
28
|
+
* loading (`/hooks/load` + `/hooks/change`, Ruby 404 fallback, dependent-field re-evaluation) rather
|
|
29
|
+
* than reimplementing it. The endpoint map from the read-model is the action allow-list.
|
|
30
|
+
*/
|
|
31
|
+
export default function createAgentActionClient({ agentUrl, token, actionEndpoints, }: AgentActionClientOptions): AgentActionClient;
|
|
32
|
+
//# sourceMappingURL=agent-action-client.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractRawLayout = extractRawLayout;
|
|
4
|
+
exports.default = createAgentActionClient;
|
|
5
|
+
const agent_client_1 = require("@forestadmin/agent-client");
|
|
6
|
+
// The raw layout must be read AFTER tryToSetFields: a change hook rebuilds fields+layout in place.
|
|
7
|
+
// agent-client's `Action.getLayout()` only returns an `ActionLayoutRoot` wrapper whose element array
|
|
8
|
+
// lives in a protected field. The rollback contract forbids agent-client changes, so we read it
|
|
9
|
+
// through a cast rather than adding a public accessor. `extract-raw-layout.test.ts` builds a real
|
|
10
|
+
// `ActionLayoutRoot` and asserts this unwraps it, so a rename of that field fails a test.
|
|
11
|
+
function extractRawLayout(action) {
|
|
12
|
+
const root = action.getLayout();
|
|
13
|
+
return root.layout ?? [];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Thin action-form client bound to a request's agent token. Reuses agent-client's stateful form
|
|
17
|
+
* loading (`/hooks/load` + `/hooks/change`, Ruby 404 fallback, dependent-field re-evaluation) rather
|
|
18
|
+
* than reimplementing it. The endpoint map from the read-model is the action allow-list.
|
|
19
|
+
*/
|
|
20
|
+
function createAgentActionClient({ agentUrl, token, actionEndpoints, }) {
|
|
21
|
+
const client = (0, agent_client_1.createRemoteAgentClient)({ url: agentUrl, token, actionEndpoints });
|
|
22
|
+
return {
|
|
23
|
+
loadActionForm: (collection, action, recordIds) => client.collection(collection).action(action, { recordIds }),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWdlbnQtYWN0aW9uLWNsaWVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hY3Rpb24vYWdlbnQtYWN0aW9uLWNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQW9DQSw0Q0FJQztBQU9ELDBDQVdDO0FBdkRELDREQUFvRTtBQTRCcEUsbUdBQW1HO0FBQ25HLHFHQUFxRztBQUNyRyxnR0FBZ0c7QUFDaEcsa0dBQWtHO0FBQ2xHLDBGQUEwRjtBQUMxRixTQUFnQixnQkFBZ0IsQ0FBQyxNQUFrQjtJQUNqRCxNQUFNLElBQUksR0FBRyxNQUFNLENBQUMsU0FBUyxFQUF3RCxDQUFDO0lBRXRGLE9BQU8sSUFBSSxDQUFDLE1BQU0sSUFBSSxFQUFFLENBQUM7QUFDM0IsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxTQUF3Qix1QkFBdUIsQ0FBQyxFQUM5QyxRQUFRLEVBQ1IsS0FBSyxFQUNMLGVBQWUsR0FDVTtJQUN6QixNQUFNLE1BQU0sR0FBRyxJQUFBLHNDQUF1QixFQUFDLEVBQUUsR0FBRyxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQUUsZUFBZSxFQUFFLENBQUMsQ0FBQztJQUVsRixPQUFPO1FBQ0wsY0FBYyxFQUFFLENBQUMsVUFBVSxFQUFFLE1BQU0sRUFBRSxTQUFTLEVBQUUsRUFBRSxDQUNoRCxNQUFNLENBQUMsVUFBVSxDQUFDLFVBQVUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsRUFBRSxTQUFTLEVBQUUsQ0FBQztLQUM5RCxDQUFDO0FBQ0osQ0FBQyJ9
|
package/dist/cli-core.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.default = runCli;
|
|
7
7
|
exports.reportFatalError = reportFatalError;
|
|
8
8
|
const bodyparser_1 = require("@koa/bodyparser");
|
|
9
|
+
const action_routes_middleware_1 = __importDefault(require("./action/action-routes-middleware"));
|
|
9
10
|
const console_logger_1 = __importDefault(require("./adapters/console-logger"));
|
|
10
11
|
const agent_stub_1 = __importDefault(require("./agent/agent-stub"));
|
|
11
12
|
const api_key_authenticator_1 = __importDefault(require("./api-key/api-key-authenticator"));
|
|
@@ -16,6 +17,7 @@ const auth_mode_middleware_1 = __importDefault(require("./auth/auth-mode-middlew
|
|
|
16
17
|
const env_config_1 = require("./config/env-config");
|
|
17
18
|
const cors_middleware_1 = __importDefault(require("./cors/cors-middleware"));
|
|
18
19
|
const per_key_origin_1 = __importDefault(require("./cors/per-key-origin"));
|
|
20
|
+
const data_routes_middleware_1 = __importDefault(require("./data/data-routes-middleware"));
|
|
19
21
|
const errors_1 = require("./errors");
|
|
20
22
|
const bff_http_error_1 = require("./http/bff-http-error");
|
|
21
23
|
const bff_http_server_1 = __importDefault(require("./http/bff-http-server"));
|
|
@@ -24,6 +26,7 @@ const forest_server_client_1 = __importDefault(require("./oauth/forest-server-cl
|
|
|
24
26
|
const oauth_routes_1 = __importDefault(require("./oauth/oauth-routes"));
|
|
25
27
|
const session_store_1 = __importDefault(require("./oauth/session-store"));
|
|
26
28
|
const token_cipher_1 = __importDefault(require("./oauth/token-cipher"));
|
|
29
|
+
const create_read_model_1 = __importDefault(require("./read-model/create-read-model"));
|
|
27
30
|
const timezone_middleware_1 = __importDefault(require("./timezone/timezone-middleware"));
|
|
28
31
|
const version_1 = __importDefault(require("./version"));
|
|
29
32
|
const BODY_LIMIT = '16kb';
|
|
@@ -109,6 +112,25 @@ function buildApiKeyMiddleware(config, logger) {
|
|
|
109
112
|
});
|
|
110
113
|
return (0, api_key_middleware_1.default)({ authenticator, logger });
|
|
111
114
|
}
|
|
115
|
+
// Data and action routes share one read-model store (a single cache, a single schema fetch). The
|
|
116
|
+
// data middleware falls through to the action middleware on a non-data path.
|
|
117
|
+
function buildAgentRouteMiddlewares(config, logger) {
|
|
118
|
+
const apiKeyConfig = resolveApiKeyConfig(config);
|
|
119
|
+
if (!apiKeyConfig || !config.agentUrl) {
|
|
120
|
+
logger('Warn', 'Data endpoints disabled: AGENT_URL or read-model configuration is missing');
|
|
121
|
+
return [(0, agent_stub_1.default)()];
|
|
122
|
+
}
|
|
123
|
+
const { store } = (0, create_read_model_1.default)({
|
|
124
|
+
forestServerUrl: apiKeyConfig.forestServerUrl,
|
|
125
|
+
envSecret: apiKeyConfig.forestEnvSecret,
|
|
126
|
+
logger,
|
|
127
|
+
});
|
|
128
|
+
const { agentUrl } = config;
|
|
129
|
+
return [
|
|
130
|
+
(0, data_routes_middleware_1.default)({ store, agentUrl, logger }),
|
|
131
|
+
(0, action_routes_middleware_1.default)({ store, agentUrl, logger }),
|
|
132
|
+
];
|
|
133
|
+
}
|
|
112
134
|
function buildAgentMiddlewares(config, logger) {
|
|
113
135
|
const { forestAuthSecret, defaultTimezone } = config;
|
|
114
136
|
if (!forestAuthSecret) {
|
|
@@ -121,7 +143,7 @@ function buildAgentMiddlewares(config, logger) {
|
|
|
121
143
|
apiKeyStep,
|
|
122
144
|
(0, per_key_origin_1.default)(),
|
|
123
145
|
(0, timezone_middleware_1.default)({ defaultTimezone }),
|
|
124
|
-
(
|
|
146
|
+
...buildAgentRouteMiddlewares(config, logger),
|
|
125
147
|
];
|
|
126
148
|
return chain.map(agentScoped);
|
|
127
149
|
}
|
|
@@ -156,4 +178,4 @@ function reportFatalError(err) {
|
|
|
156
178
|
process.stderr.write(`Error: ${(0, errors_1.extractErrorMessage)(err)}\n`);
|
|
157
179
|
process.exitCode = 1;
|
|
158
180
|
}
|
|
159
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
181
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLWNvcmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY2xpLWNvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUEyTUEseUJBa0NDO0FBRUQsNENBR0M7QUE5T0QsZ0RBQTZDO0FBRTdDLGlHQUE2RTtBQUM3RSwrRUFBNEQ7QUFDNUQsb0VBQTJEO0FBQzNELDRGQUF3RTtBQUN4RSw4RUFBb0Q7QUFDcEQsc0ZBQWtFO0FBQ2xFLDRFQUF5RDtBQUN6RCx1RkFBbUU7QUFDbkUsb0RBQWtEO0FBQ2xELDZFQUEwRDtBQUMxRCwyRUFBaUU7QUFDakUsMkZBQXVFO0FBQ3ZFLHFDQUErQztBQUMvQywwREFBcUQ7QUFDckQsNkVBQW1EO0FBQ25ELCtFQUE0RDtBQUM1RCx3RkFBOEQ7QUFDOUQsd0VBQXFEO0FBQ3JELDBFQUErRDtBQUMvRCx3RUFBcUQ7QUFDckQsdUZBQTZEO0FBQzdELHlGQUFzRTtBQUN0RSx3REFBZ0M7QUFFaEMsTUFBTSxVQUFVLEdBQUcsTUFBTSxDQUFDO0FBQzFCLE1BQU0sbUJBQW1CLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLENBQUM7QUFFekMsU0FBUyxXQUFXLENBQUMsSUFBWTtJQUMvQixPQUFPLElBQUksS0FBSyxRQUFRLElBQUksSUFBSSxDQUFDLFVBQVUsQ0FBQyxTQUFTLENBQUMsQ0FBQztBQUN6RCxDQUFDO0FBRUQsU0FBUyxXQUFXLENBQUMsVUFBc0I7SUFDekMsT0FBTyxLQUFLLFVBQVUsTUFBTSxDQUFDLEdBQUcsRUFBRSxJQUFJO1FBQ3BDLElBQUksQ0FBQyxXQUFXLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7WUFDM0IsTUFBTSxJQUFJLEVBQUUsQ0FBQztZQUViLE9BQU87UUFDVCxDQUFDO1FBRUQsTUFBTSxVQUFVLENBQUMsR0FBRyxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQzlCLENBQUMsQ0FBQztBQUNKLENBQUM7QUFFRCxTQUFTLDRCQUE0QixDQUFD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
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface AgentDataClientOptions {
|
|
2
|
+
agentUrl: string;
|
|
3
|
+
token: string;
|
|
4
|
+
}
|
|
5
|
+
export interface AgentDataClient {
|
|
6
|
+
list(collection: string, query: Record<string, unknown>): Promise<Record<string, unknown>[]>;
|
|
7
|
+
countRaw(collection: string, query: Record<string, unknown>): Promise<unknown>;
|
|
8
|
+
listRelation(collection: string, parentId: string, relation: string, query: Record<string, unknown>): Promise<Record<string, unknown>[]>;
|
|
9
|
+
countRelationRaw(collection: string, parentId: string, relation: string, query: Record<string, unknown>): Promise<unknown>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Thin data client bound to a request's agent token. Uses the raw `HttpRequester` (not `Collection`)
|
|
13
|
+
* so the BFF controls the query params — notably the resolved `timezone` — and can read the count
|
|
14
|
+
* endpoint's raw payload, which `collection.count()` coerces through `Number()` and loses.
|
|
15
|
+
*/
|
|
16
|
+
export default function createAgentDataClient({ agentUrl, token, }: AgentDataClientOptions): AgentDataClient;
|
|
17
|
+
//# sourceMappingURL=agent-data-client.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = createAgentDataClient;
|
|
4
|
+
const agent_client_1 = require("@forestadmin/agent-client");
|
|
5
|
+
/**
|
|
6
|
+
* Thin data client bound to a request's agent token. Uses the raw `HttpRequester` (not `Collection`)
|
|
7
|
+
* so the BFF controls the query params — notably the resolved `timezone` — and can read the count
|
|
8
|
+
* endpoint's raw payload, which `collection.count()` coerces through `Number()` and loses.
|
|
9
|
+
*/
|
|
10
|
+
function createAgentDataClient({ agentUrl, token, }) {
|
|
11
|
+
const requester = new agent_client_1.HttpRequester(token, { url: agentUrl });
|
|
12
|
+
// Segments are passed raw: HttpRequester.buildUrl already runs the whole path through
|
|
13
|
+
// escapeUrlSlug/encodeURI, so pre-encoding here would double-encode (`|` -> `%257C`).
|
|
14
|
+
const relationPath = (collection, parentId, relation) => `/forest/${collection}/${parentId}/relationships/${relation}`;
|
|
15
|
+
return {
|
|
16
|
+
list: (collection, query) => requester.query({ method: 'get', path: `/forest/${collection}`, query }),
|
|
17
|
+
countRaw: (collection, query) => requester.query({
|
|
18
|
+
method: 'get',
|
|
19
|
+
path: `/forest/${collection}/count`,
|
|
20
|
+
query,
|
|
21
|
+
skipDeserialization: true,
|
|
22
|
+
}),
|
|
23
|
+
listRelation: (collection, parentId, relation, query) => requester.query({ method: 'get', path: relationPath(collection, parentId, relation), query }),
|
|
24
|
+
countRelationRaw: (collection, parentId, relation, query) => requester.query({
|
|
25
|
+
method: 'get',
|
|
26
|
+
path: `${relationPath(collection, parentId, relation)}/count`,
|
|
27
|
+
query,
|
|
28
|
+
skipDeserialization: true,
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWdlbnQtZGF0YS1jbGllbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZGF0YS9hZ2VudC1kYXRhLWNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQTZCQSx3Q0ErQkM7QUE1REQsNERBQTBEO0FBd0IxRDs7OztHQUlHO0FBQ0gsU0FBd0IscUJBQXFCLENBQUMsRUFDNUMsUUFBUSxFQUNSLEtBQUssR0FDa0I7SUFDdkIsTUFBTSxTQUFTLEdBQUcsSUFBSSw0QkFBYSxDQUFDLEtBQUssRUFBRSxFQUFFLEdBQUcsRUFBRSxRQUFRLEVBQUUsQ0FBQyxDQUFDO0lBRTlELHNGQUFzRjtJQUN0RixzRkFBc0Y7SUFDdEYsTUFBTSxZQUFZLEdBQUcsQ0FBQyxVQUFrQixFQUFFLFFBQWdCLEVBQUUsUUFBZ0IsRUFBRSxFQUFFLENBQzlFLFdBQVcsVUFBVSxJQUFJLFFBQVEsa0JBQWtCLFFBQVEsRUFBRSxDQUFDO0lBRWhFLE9BQU87UUFDTCxJQUFJLEVBQUUsQ0FBQyxVQUFVLEVBQUUsS0FBSyxFQUFFLEVBQUUsQ0FDMUIsU0FBUyxDQUFDLEtBQUssQ0FBQyxFQUFFLE1BQU0sRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLFdBQVcsVUFBVSxFQUFFLEVBQUUsS0FBSyxFQUFFLENBQUM7UUFDMUUsUUFBUSxFQUFFLENBQUMsVUFBVSxFQUFFLEtBQUssRUFBRSxFQUFFLENBQzlCLFNBQVMsQ0FBQyxLQUFLLENBQUM7WUFDZCxNQUFNLEVBQUUsS0FBSztZQUNiLElBQUksRUFBRSxXQUFXLFVBQVUsUUFBUTtZQUNuQyxLQUFLO1lBQ0wsbUJBQW1CLEVBQUUsSUFBSTtTQUMxQixDQUFDO1FBQ0osWUFBWSxFQUFFLENBQUMsVUFBVSxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLEVBQUUsQ0FDdEQsU0FBUyxDQUFDLEtBQUssQ0FBQyxFQUFFLE1BQU0sRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLFlBQVksQ0FBQyxVQUFVLEVBQUUsUUFBUSxFQUFFLFFBQVEsQ0FBQyxFQUFFLEtBQUssRUFBRSxDQUFDO1FBQy9GLGdCQUFnQixFQUFFLENBQUMsVUFBVSxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLEVBQUUsQ0FDMUQsU0FBUyxDQUFDLEtBQUssQ0FBQztZQUNkLE1BQU0sRUFBRSxLQUFLO1lBQ2IsSUFBSSxFQUFFLEdBQUcsWUFBWSxDQUFDLFVBQVUsRUFBRSxRQUFRLEVBQUUsUUFBUSxDQUFDLFFBQVE7WUFDN0QsS0FBSztZQUNMLG1CQUFtQixFQUFFLElBQUk7U0FDMUIsQ0FBQztLQUNMLENBQUM7QUFDSixDQUFDIn0=
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface BffSortClause {
|
|
2
|
+
field: string;
|
|
3
|
+
direction?: 'asc' | 'desc';
|
|
4
|
+
}
|
|
5
|
+
export interface BffPage {
|
|
6
|
+
limit: number;
|
|
7
|
+
offset: number;
|
|
8
|
+
}
|
|
9
|
+
export interface ListRequestBody {
|
|
10
|
+
filter?: unknown;
|
|
11
|
+
projection?: string[];
|
|
12
|
+
sort?: BffSortClause[];
|
|
13
|
+
page?: BffPage;
|
|
14
|
+
}
|
|
15
|
+
export interface CountRequestBody {
|
|
16
|
+
filter?: unknown;
|
|
17
|
+
}
|
|
18
|
+
export type RelationListRequestBody = ListRequestBody & {
|
|
19
|
+
parentId: string;
|
|
20
|
+
};
|
|
21
|
+
export type RelationCountRequestBody = CountRequestBody & {
|
|
22
|
+
parentId: string;
|
|
23
|
+
};
|
|
24
|
+
export type AgentQuery = Record<string, unknown> & {
|
|
25
|
+
timezone: string;
|
|
26
|
+
};
|
|
27
|
+
export declare function parseListRequest(body: unknown): ListRequestBody;
|
|
28
|
+
export declare function parseCountRequest(body: unknown): CountRequestBody;
|
|
29
|
+
export declare function buildListAgentQuery(collection: string, timezone: string, body: ListRequestBody): AgentQuery;
|
|
30
|
+
export declare function buildCountAgentQuery(timezone: string, body: CountRequestBody): AgentQuery;
|
|
31
|
+
export declare function collectListFieldPaths(body: ListRequestBody): string[];
|
|
32
|
+
export declare function parseParentId(parentId: unknown): string;
|
|
33
|
+
export declare function parseRelationListRequest(body: unknown): RelationListRequestBody;
|
|
34
|
+
export declare function parseRelationCountRequest(body: unknown): RelationCountRequestBody;
|
|
35
|
+
export declare function collectCountFieldPaths(body: CountRequestBody): string[];
|
|
36
|
+
//# sourceMappingURL=agent-query.d.ts.map
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseListRequest = parseListRequest;
|
|
4
|
+
exports.parseCountRequest = parseCountRequest;
|
|
5
|
+
exports.buildListAgentQuery = buildListAgentQuery;
|
|
6
|
+
exports.buildCountAgentQuery = buildCountAgentQuery;
|
|
7
|
+
exports.collectListFieldPaths = collectListFieldPaths;
|
|
8
|
+
exports.parseParentId = parseParentId;
|
|
9
|
+
exports.parseRelationListRequest = parseRelationListRequest;
|
|
10
|
+
exports.parseRelationCountRequest = parseRelationCountRequest;
|
|
11
|
+
exports.collectCountFieldPaths = collectCountFieldPaths;
|
|
12
|
+
const bff_local_errors_1 = require("../http/bff-local-errors");
|
|
13
|
+
function isPlainObject(value) {
|
|
14
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
// Validate the untyped request body before it reaches the query builders, so malformed shapes
|
|
17
|
+
// (e.g. `projection` or `sort` as a string) surface as 400 invalid_request rather than a 500 from
|
|
18
|
+
// an array method blowing up downstream.
|
|
19
|
+
function parseListRequest(body) {
|
|
20
|
+
if (!isPlainObject(body))
|
|
21
|
+
throw (0, bff_local_errors_1.invalidRequest)('Request body must be an object');
|
|
22
|
+
const { filter, projection, sort, page } = body;
|
|
23
|
+
if (projection !== undefined) {
|
|
24
|
+
if (!Array.isArray(projection) || projection.some(field => typeof field !== 'string')) {
|
|
25
|
+
throw (0, bff_local_errors_1.invalidRequest)('projection must be an array of field names');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (sort !== undefined) {
|
|
29
|
+
const valid = Array.isArray(sort) &&
|
|
30
|
+
sort.every(clause => isPlainObject(clause) &&
|
|
31
|
+
typeof clause.field === 'string' &&
|
|
32
|
+
(clause.direction === undefined ||
|
|
33
|
+
clause.direction === 'asc' ||
|
|
34
|
+
clause.direction === 'desc'));
|
|
35
|
+
if (!valid)
|
|
36
|
+
throw (0, bff_local_errors_1.invalidRequest)('sort must be an array of { field, direction? }');
|
|
37
|
+
}
|
|
38
|
+
if (filter !== undefined && !isPlainObject(filter)) {
|
|
39
|
+
throw (0, bff_local_errors_1.invalidRequest)('filter must be an object');
|
|
40
|
+
}
|
|
41
|
+
if (page !== undefined) {
|
|
42
|
+
if (!isPlainObject(page)) {
|
|
43
|
+
throw (0, bff_local_errors_1.invalidRequest)('page must be an object with limit and offset');
|
|
44
|
+
}
|
|
45
|
+
if (!Number.isInteger(page.limit) || page.limit <= 0) {
|
|
46
|
+
throw (0, bff_local_errors_1.invalidRequest)('page.limit must be a positive integer');
|
|
47
|
+
}
|
|
48
|
+
if (!Number.isInteger(page.offset) || page.offset < 0) {
|
|
49
|
+
throw (0, bff_local_errors_1.invalidRequest)('page.offset must be a non-negative integer');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return body;
|
|
53
|
+
}
|
|
54
|
+
function parseCountRequest(body) {
|
|
55
|
+
if (!isPlainObject(body))
|
|
56
|
+
throw (0, bff_local_errors_1.invalidRequest)('Request body must be an object');
|
|
57
|
+
if (body.filter !== undefined && !isPlainObject(body.filter)) {
|
|
58
|
+
throw (0, bff_local_errors_1.invalidRequest)('filter must be an object');
|
|
59
|
+
}
|
|
60
|
+
return body;
|
|
61
|
+
}
|
|
62
|
+
function isBranch(node) {
|
|
63
|
+
return (typeof node === 'object' &&
|
|
64
|
+
node !== null &&
|
|
65
|
+
Array.isArray(node.conditions));
|
|
66
|
+
}
|
|
67
|
+
function isLeaf(node) {
|
|
68
|
+
return (typeof node === 'object' &&
|
|
69
|
+
node !== null &&
|
|
70
|
+
typeof node.field === 'string');
|
|
71
|
+
}
|
|
72
|
+
function collectFilterFields(filter, acc) {
|
|
73
|
+
if (isBranch(filter)) {
|
|
74
|
+
filter.conditions.forEach(condition => collectFilterFields(condition, acc));
|
|
75
|
+
}
|
|
76
|
+
else if (isLeaf(filter)) {
|
|
77
|
+
acc.push(filter.field);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function serializeSort(sort) {
|
|
81
|
+
return sort.map(({ field, direction }) => (direction === 'desc' ? `-${field}` : field)).join(',');
|
|
82
|
+
}
|
|
83
|
+
// `parseListRequest` guarantees `limit` > 0 and `offset` >= 0 are integers; this only enforces the
|
|
84
|
+
// page-model rule. The agent paginates by page number/size, so an arbitrary offset that is not a
|
|
85
|
+
// whole multiple of the limit cannot be expressed — reject it rather than return a shifted window.
|
|
86
|
+
function serializePage(page) {
|
|
87
|
+
const { limit, offset } = page;
|
|
88
|
+
if (offset % limit !== 0) {
|
|
89
|
+
throw (0, bff_local_errors_1.invalidRequest)(`page.offset (${offset}) must be a multiple of page.limit (${limit})`);
|
|
90
|
+
}
|
|
91
|
+
return { 'page[size]': limit, 'page[number]': offset / limit + 1 };
|
|
92
|
+
}
|
|
93
|
+
function buildListAgentQuery(collection, timezone, body) {
|
|
94
|
+
const query = { timezone };
|
|
95
|
+
if (body.filter !== undefined)
|
|
96
|
+
query.filters = JSON.stringify(body.filter);
|
|
97
|
+
if (body.projection?.length)
|
|
98
|
+
query[`fields[${collection}]`] = body.projection.join(',');
|
|
99
|
+
if (body.sort?.length)
|
|
100
|
+
query.sort = serializeSort(body.sort);
|
|
101
|
+
if (body.page)
|
|
102
|
+
Object.assign(query, serializePage(body.page));
|
|
103
|
+
return query;
|
|
104
|
+
}
|
|
105
|
+
function buildCountAgentQuery(timezone, body) {
|
|
106
|
+
const query = { timezone };
|
|
107
|
+
if (body.filter !== undefined)
|
|
108
|
+
query.filters = JSON.stringify(body.filter);
|
|
109
|
+
return query;
|
|
110
|
+
}
|
|
111
|
+
function collectListFieldPaths(body) {
|
|
112
|
+
const paths = [...(body.projection ?? [])];
|
|
113
|
+
collectFilterFields(body.filter, paths);
|
|
114
|
+
(body.sort ?? []).forEach(({ field }) => paths.push(field));
|
|
115
|
+
return paths;
|
|
116
|
+
}
|
|
117
|
+
// The parent record id is opaque: a packed/composite id must survive unchanged, so its content is
|
|
118
|
+
// never inspected — only presence and primitive type. A finite number (single numeric pk) is
|
|
119
|
+
// coerced to string; anything else is a BFF-local 400 with no agent call.
|
|
120
|
+
function parseParentId(parentId) {
|
|
121
|
+
if (typeof parentId === 'string') {
|
|
122
|
+
if (parentId.trim() === '')
|
|
123
|
+
throw (0, bff_local_errors_1.invalidRequest)('parentId must not be empty');
|
|
124
|
+
return parentId;
|
|
125
|
+
}
|
|
126
|
+
if (typeof parentId === 'number' && Number.isFinite(parentId)) {
|
|
127
|
+
return String(parentId);
|
|
128
|
+
}
|
|
129
|
+
throw (0, bff_local_errors_1.invalidRequest)('parentId is required and must be a non-empty string or a number');
|
|
130
|
+
}
|
|
131
|
+
function parseRelationListRequest(body) {
|
|
132
|
+
const parentId = parseParentId(body?.parentId);
|
|
133
|
+
return { ...parseListRequest(body), parentId };
|
|
134
|
+
}
|
|
135
|
+
function parseRelationCountRequest(body) {
|
|
136
|
+
const parentId = parseParentId(body?.parentId);
|
|
137
|
+
return { ...parseCountRequest(body), parentId };
|
|
138
|
+
}
|
|
139
|
+
function collectCountFieldPaths(body) {
|
|
140
|
+
const paths = [];
|
|
141
|
+
collectFilterFields(body.filter, paths);
|
|
142
|
+
return paths;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWdlbnQtcXVlcnkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZGF0YS9hZ2VudC1xdWVyeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQW9DQSw0Q0E0Q0M7QUFFRCw4Q0FRQztBQW1ERCxrREFhQztBQUVELG9EQU1DO0FBRUQsc0RBTUM7QUFLRCxzQ0FZQztBQUVELDREQUlDO0FBRUQsOERBSUM7QUFFRCx3REFLQztBQTlNRCwrREFBMEQ7QUE2QjFELFNBQVMsYUFBYSxDQUFDLEtBQWM7SUFDbkMsT0FBTyxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUksS0FBSyxLQUFLLElBQUksSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7QUFDOUUsQ0FBQztBQUVELDhGQUE4RjtBQUM5RixrR0FBa0c7QUFDbEcseUNBQXlDO0FBQ3pDLFNBQWdCLGdCQUFnQixDQUFDLElBQWE7SUFDNUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUM7UUFBRSxNQUFNLElBQUEsaUNBQWMsRUFBQyxnQ0FBZ0MsQ0FBQyxDQUFDO0lBRWpGLE1BQU0sRUFBRSxNQUFNLEVBQUUsVUFBVSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsR0FBRyxJQUFJLENBQUM7SUFFaEQsSUFBSSxVQUFVLEtBQUssU0FBUyxFQUFFLENBQUM7UUFDN0IsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLElBQUksVUFBVSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLE9BQU8sS0FBSyxLQUFLLFFBQVEsQ0FBQyxFQUFFLENBQUM7WUFDdEYsTUFBTSxJQUFBLGlDQUFjLEVBQUMsNENBQTRDLENBQUMsQ0FBQztRQUNyRSxDQUFDO0lBQ0gsQ0FBQztJQUVELElBQUksSUFBSSxLQUFLLFNBQVMsRUFBRSxDQUFDO1FBQ3ZCLE1BQU0sS0FBSyxHQUNULEtBQUssQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFD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
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AgentDataClient, AgentDataClientOptions } from './agent-data-client';
|
|
2
|
+
import type { Logger } from '../ports/logger-port';
|
|
3
|
+
import type ReadModelStore from '../read-model/read-model-store';
|
|
4
|
+
import type { Middleware } from 'koa';
|
|
5
|
+
export interface DataRoutesMiddlewareOptions {
|
|
6
|
+
store: ReadModelStore;
|
|
7
|
+
agentUrl: string;
|
|
8
|
+
logger: Logger;
|
|
9
|
+
createClient?: (options: AgentDataClientOptions) => AgentDataClient;
|
|
10
|
+
}
|
|
11
|
+
export default function createDataRoutesMiddleware({ store, agentUrl, logger, createClient, }: DataRoutesMiddlewareOptions): Middleware;
|
|
12
|
+
//# sourceMappingURL=data-routes-middleware.d.ts.map
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = createDataRoutesMiddleware;
|
|
7
|
+
const agent_data_client_1 = __importDefault(require("./agent-data-client"));
|
|
8
|
+
const agent_query_1 = require("./agent-query");
|
|
9
|
+
const response_mappers_1 = require("./response-mappers");
|
|
10
|
+
const agent_route_helpers_1 = require("../http/agent-route-helpers");
|
|
11
|
+
const bff_local_errors_1 = require("../http/bff-local-errors");
|
|
12
|
+
const relation_field_guard_1 = __importDefault(require("../validation/relation-field-guard"));
|
|
13
|
+
const DATA_ROUTE = /^\/agent\/v1\/([^/]+)\/(list|count)$/;
|
|
14
|
+
const RELATION_ROUTE = /^\/agent\/v1\/([^/]+)\/relations\/([^/]+)\/(list|count)$/;
|
|
15
|
+
// Only to-many relations expose list/count on the agent; to-one relations get update-relation only.
|
|
16
|
+
// A polymorphic relation carrying multiple targets is always the to-one (PolymorphicManyToOne) side.
|
|
17
|
+
function resolveForeignCollection(target) {
|
|
18
|
+
if (!target || !('target' in target))
|
|
19
|
+
return null;
|
|
20
|
+
if (target.type !== 'HasMany' && target.type !== 'BelongsToMany')
|
|
21
|
+
return null;
|
|
22
|
+
return target.target;
|
|
23
|
+
}
|
|
24
|
+
async function handleList(ctx, body, deps) {
|
|
25
|
+
(0, relation_field_guard_1.default)((0, agent_query_1.collectListFieldPaths)(body));
|
|
26
|
+
const query = (0, agent_query_1.buildListAgentQuery)(deps.collection, deps.timezone, body);
|
|
27
|
+
const records = await (0, agent_route_helpers_1.callAgent)(() => deps.client.list(deps.collection, query), deps.logger);
|
|
28
|
+
ctx.status = 200;
|
|
29
|
+
ctx.body = (0, response_mappers_1.mapListResponse)(deps.collection, records, deps.primaryKeys);
|
|
30
|
+
}
|
|
31
|
+
async function handleCount(ctx, body, deps) {
|
|
32
|
+
(0, relation_field_guard_1.default)((0, agent_query_1.collectCountFieldPaths)(body));
|
|
33
|
+
const query = (0, agent_query_1.buildCountAgentQuery)(deps.timezone, body);
|
|
34
|
+
const raw = await (0, agent_route_helpers_1.callAgent)(() => deps.client.countRaw(deps.collection, query), deps.logger);
|
|
35
|
+
ctx.status = 200;
|
|
36
|
+
ctx.body = (0, response_mappers_1.mapCountResponse)(raw);
|
|
37
|
+
}
|
|
38
|
+
async function handleRelationList(ctx, body, deps) {
|
|
39
|
+
// The nested-relation guard IS wired here: the agent's list-related asserts browse only on the
|
|
40
|
+
// immediate foreign collection, so a nested `:`-path would traverse to a third collection whose
|
|
41
|
+
// browse is never checked. Plain foreign fields (no `:`) are unaffected.
|
|
42
|
+
(0, relation_field_guard_1.default)((0, agent_query_1.collectListFieldPaths)(body));
|
|
43
|
+
const query = (0, agent_query_1.buildListAgentQuery)(deps.foreignCollection, deps.timezone, body);
|
|
44
|
+
const records = await (0, agent_route_helpers_1.callAgent)(() => deps.client.listRelation(deps.collection, body.parentId, deps.relation, query), deps.logger);
|
|
45
|
+
ctx.status = 200;
|
|
46
|
+
ctx.body = (0, response_mappers_1.mapListResponse)(deps.foreignCollection, records, deps.primaryKeys);
|
|
47
|
+
}
|
|
48
|
+
async function handleRelationCount(ctx, body, deps) {
|
|
49
|
+
(0, relation_field_guard_1.default)((0, agent_query_1.collectCountFieldPaths)(body));
|
|
50
|
+
const query = (0, agent_query_1.buildCountAgentQuery)(deps.timezone, body);
|
|
51
|
+
const raw = await (0, agent_route_helpers_1.callAgent)(() => deps.client.countRelationRaw(deps.collection, body.parentId, deps.relation, query), deps.logger);
|
|
52
|
+
ctx.status = 200;
|
|
53
|
+
ctx.body = (0, response_mappers_1.mapCountResponse)(raw);
|
|
54
|
+
}
|
|
55
|
+
async function handleRelation(ctx, rawBody, match, { deps, readModel }) {
|
|
56
|
+
const relation = (0, agent_route_helpers_1.decodeSegment)(match[2], 'relation name');
|
|
57
|
+
const operation = match[3];
|
|
58
|
+
// The URL identity (does this listable relation exist?) is resolved before the body,
|
|
59
|
+
// so a bad path 404s before its payload is inspected.
|
|
60
|
+
const foreignCollection = resolveForeignCollection(readModel.getRelationTarget(deps.collection, relation));
|
|
61
|
+
// TODO(PRD-672): the read-model's relation map is the allow-list, so an absent/to-one/polymorphic
|
|
62
|
+
// relation cannot be told from a disallowed one — every non-listable relation maps to 404 here;
|
|
63
|
+
// `relation_not_allowed` (403) has no local trigger, mirroring `collection_not_allowed`.
|
|
64
|
+
if (!foreignCollection) {
|
|
65
|
+
throw (0, bff_local_errors_1.unknownRelation)(`Unknown relation: ${deps.collection}.${relation}`);
|
|
66
|
+
}
|
|
67
|
+
// The agent's count-related authorizes only the parent, so an unguarded foreign collection would
|
|
68
|
+
// leak a hidden collection's rows/count. Guard it here, mirroring the parent allow-list check.
|
|
69
|
+
if (!readModel.isCollectionAllowed(foreignCollection)) {
|
|
70
|
+
throw (0, bff_local_errors_1.unknownCollection)();
|
|
71
|
+
}
|
|
72
|
+
const relationDeps = { ...deps, relation, foreignCollection };
|
|
73
|
+
if (operation === 'list') {
|
|
74
|
+
await handleRelationList(ctx, (0, agent_query_1.parseRelationListRequest)(rawBody), {
|
|
75
|
+
...relationDeps,
|
|
76
|
+
primaryKeys: readModel.getPrimaryKeys(foreignCollection),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
await handleRelationCount(ctx, (0, agent_query_1.parseRelationCountRequest)(rawBody), relationDeps);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function createDataRoutesMiddleware({ store, agentUrl, logger, createClient = agent_data_client_1.default, }) {
|
|
84
|
+
return async function dataRoutesMiddleware(ctx, next) {
|
|
85
|
+
const dataMatch = DATA_ROUTE.exec(ctx.path);
|
|
86
|
+
const relationMatch = dataMatch ? null : RELATION_ROUTE.exec(ctx.path);
|
|
87
|
+
const match = dataMatch ?? relationMatch;
|
|
88
|
+
if (!match || ctx.method !== 'POST') {
|
|
89
|
+
await next();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const collection = (0, agent_route_helpers_1.decodeSegment)(match[1], 'collection name');
|
|
93
|
+
const token = (0, agent_route_helpers_1.requireAgentToken)(ctx);
|
|
94
|
+
const readModel = await (0, agent_route_helpers_1.resolveReadModel)(store);
|
|
95
|
+
if (!readModel.isCollectionAllowed(collection)) {
|
|
96
|
+
// TODO(PRD-671): the read-model exposes a single allow-list (the exposed collections), so it
|
|
97
|
+
// cannot tell an unknown collection from a known-but-disallowed one. Every absent name maps
|
|
98
|
+
// to 404 here; `collection_not_allowed` (403) has no local trigger until a distinct exposure
|
|
99
|
+
// source exists. Escalated on the ticket — revisit when Anthony answers.
|
|
100
|
+
throw (0, bff_local_errors_1.unknownCollection)(`Unknown collection: ${collection}`);
|
|
101
|
+
}
|
|
102
|
+
const deps = {
|
|
103
|
+
collection,
|
|
104
|
+
client: createClient({ agentUrl, token }),
|
|
105
|
+
timezone: ctx.state.timezone,
|
|
106
|
+
logger,
|
|
107
|
+
};
|
|
108
|
+
const rawBody = ctx.request.body ?? {};
|
|
109
|
+
if (relationMatch) {
|
|
110
|
+
await handleRelation(ctx, rawBody, relationMatch, { deps, readModel });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const operation = match[2];
|
|
114
|
+
if (operation === 'list') {
|
|
115
|
+
const body = (0, agent_query_1.parseListRequest)(rawBody);
|
|
116
|
+
await handleList(ctx, body, { ...deps, primaryKeys: readModel.getPrimaryKeys(collection) });
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
const body = (0, agent_query_1.parseCountRequest)(rawBody);
|
|
120
|
+
await handleCount(ctx, body, deps);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGF0YS1yb3V0ZXMtbWlkZGxld2FyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9kYXRhL2RhdGEtcm91dGVzLW1pZGRsZXdhcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUF3S0EsNkNBcURDO0FBaE5ELDRFQUErRDtBQUMvRCwrQ0FTdUI7QUFDdkIseURBQXVFO0FBQ3ZFLHFFQUtxQztBQUNyQywrREFBOEU7QUFDOUUsOEZBQTRFO0FBRTVFLE1BQU0sVUFBVSxHQUFHLHNDQUFzQyxDQUFDO0FBQzFELE1BQU0sY0FBYyxHQUFHLDBEQUEwRCxDQUFDO0FBRWxGLG9HQUFvRztBQUNwRyxxR0FBcUc7QUFDckcsU0FBUyx3QkFBd0IsQ0FBQyxNQUFrQztJQUNsRSxJQUFJLENBQUMsTUFBTSxJQUFJLENBQUMsQ0FBQyxRQUFRLElBQUksTUFBTSxDQUFDO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDbEQsSUFBSSxNQUFNLENBQUMsSUFBSSxLQUFLLFNBQVMsSUFBSSxNQUFNLENBQUMsSUFBSSxLQUFLLGVBQWU7UUFBRSxPQUFPLElBQUksQ0FBQztJQUU5RSxPQUFPLE1BQU0sQ0FBQyxNQUFNLENBQUM7QUFDdkIsQ0FBQztBQWtCRCxLQUFLLFVBQVUsVUFBVSxDQUFDLEdBQVksRUFBRSxJQUFxQixFQUFFLElBQXFCO0lBQ2xGLElBQUEsOEJBQTBCLEVBQUMsSUFBQSxtQ0FBcUIsRUFBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBRXhELE1BQU0sS0FBSyxHQUFHLElBQUEsaUNBQW1CLEVBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxJQUFJLENBQUMsUUFBUSxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQ3hFLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBQSwrQkFBUyxFQUFDLEdBQUcsRUFBRSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLEVBQUUsS0FBSyxDQUFDLEVBQUUsSUFBSSxDQUFD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
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PrimaryKeyField } from '../read-model/read-model';
|
|
2
|
+
/**
|
|
3
|
+
* Rebuild the structured primary key of a record from its opaque packed id, mirroring the agent's
|
|
4
|
+
* `IdUtils.packId`/`unpackId` (`|`-joined values, `Number` columns cast back to numbers). Returns a
|
|
5
|
+
* `{ pkField: value }` map for `__forest.primaryKey`. Throws a mapping error rather than emitting a
|
|
6
|
+
* malformed key when the schema lacks key metadata or the packed id shape does not match it.
|
|
7
|
+
*/
|
|
8
|
+
export default function unpackPrimaryKey(packedId: string, primaryKeys: PrimaryKeyField[]): Record<string, string | number>;
|
|
9
|
+
//# sourceMappingURL=pack-id.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = unpackPrimaryKey;
|
|
4
|
+
const bff_local_errors_1 = require("../http/bff-local-errors");
|
|
5
|
+
const PACKED_ID_SEPARATOR = '|';
|
|
6
|
+
// The only column type unpacked to a number, mirroring the agent's `IdUtils.unpackId`.
|
|
7
|
+
const NUMBER_COLUMN_TYPE = 'Number';
|
|
8
|
+
/**
|
|
9
|
+
* Rebuild the structured primary key of a record from its opaque packed id, mirroring the agent's
|
|
10
|
+
* `IdUtils.packId`/`unpackId` (`|`-joined values, `Number` columns cast back to numbers). Returns a
|
|
11
|
+
* `{ pkField: value }` map for `__forest.primaryKey`. Throws a mapping error rather than emitting a
|
|
12
|
+
* malformed key when the schema lacks key metadata or the packed id shape does not match it.
|
|
13
|
+
*/
|
|
14
|
+
function unpackPrimaryKey(packedId, primaryKeys) {
|
|
15
|
+
if (primaryKeys.length === 0) {
|
|
16
|
+
throw (0, bff_local_errors_1.mappingError)('Cannot build primary key: the collection exposes no key metadata');
|
|
17
|
+
}
|
|
18
|
+
const values = packedId.split(PACKED_ID_SEPARATOR);
|
|
19
|
+
if (values.length !== primaryKeys.length) {
|
|
20
|
+
throw (0, bff_local_errors_1.mappingError)(`Cannot build primary key: expected ${primaryKeys.length} values, found ${values.length}`);
|
|
21
|
+
}
|
|
22
|
+
const result = {};
|
|
23
|
+
primaryKeys.forEach(({ name, type }, index) => {
|
|
24
|
+
const value = values[index];
|
|
25
|
+
if (type !== NUMBER_COLUMN_TYPE) {
|
|
26
|
+
result[name] = value;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const numeric = Number(value);
|
|
30
|
+
if (Number.isNaN(numeric)) {
|
|
31
|
+
throw (0, bff_local_errors_1.mappingError)(`Cannot build primary key: invalid numeric value "${value}" for "${name}"`);
|
|
32
|
+
}
|
|
33
|
+
result[name] = numeric;
|
|
34
|
+
});
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGFjay1pZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9kYXRhL3BhY2staWQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFlQSxtQ0F1Q0M7QUFwREQsK0RBQXdEO0FBRXhELE1BQU0sbUJBQW1CLEdBQUcsR0FBRyxDQUFDO0FBRWhDLHVGQUF1RjtBQUN2RixNQUFNLGtCQUFrQixHQUFHLFFBQVEsQ0FBQztBQUVwQzs7Ozs7R0FLRztBQUNILFNBQXdCLGdCQUFnQixDQUN0QyxRQUFnQixFQUNoQixXQUE4QjtJQUU5QixJQUFJLFdBQVcsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDN0IsTUFBTSxJQUFBLCtCQUFZLEVBQUMsa0VBQWtFLENBQUMsQ0FBQztJQUN6RixDQUFDO0lBRUQsTUFBTSxNQUFNLEdBQUcsUUFBUSxDQUFDLEtBQUssQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO0lBRW5ELElBQUksTUFBTSxDQUFDLE1BQU0sS0FBSyxXQUFXLENBQUMsTUFBTSxFQUFFLENBQUM7UUFDekMsTUFBTSxJQUFBLCtCQUFZLEVBQ2hCLHNDQUFzQyxXQUFXLENBQUMsTUFBTSxrQkFBa0IsTUFBTSxDQUFDLE1BQU0sRUFBRSxDQUMxRixDQUFDO0lBQ0osQ0FBQztJQUVELE1BQU0sTUFBTSxHQUFvQyxFQUFFLENBQUM7SUFFbkQsV0FBVyxDQUFDLE9BQU8sQ0FBQyxDQUFDLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxFQUFFLEtBQUssRUFBRSxFQUFFO1FBQzVDLE1BQU0sS0FBSyxHQUFHLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUU1QixJQUFJLElBQUksS0FBSyxrQkFBa0IsRUFBRSxDQUFDO1lBQ2hDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxLQUFLLENBQUM7WUFFckIsT0FBTztRQUNULENBQUM7UUFFRCxNQUFNLE9BQU8sR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7UUFFOUIsSUFBSSxNQUFNLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7WUFDMUIsTUFBTSxJQUFBLCtCQUFZLEVBQ2hCLG9EQUFvRCxLQUFLLFVBQVUsSUFBSSxHQUFHLENBQzNFLENBQUM7UUFDSixDQUFDO1FBRUQsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLE9BQU8sQ0FBQztJQUN6QixDQUFDLENBQUMsQ0FBQztJQUVILE9BQU8sTUFBTSxDQUFDO0FBQ2hCLENBQUMifQ==
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PrimaryKeyField } from '../read-model/read-model';
|
|
2
|
+
export interface ForestRecordMetadata {
|
|
3
|
+
collection: string;
|
|
4
|
+
primaryKey: Record<string, string | number>;
|
|
5
|
+
}
|
|
6
|
+
export interface ListResponse {
|
|
7
|
+
data: Array<Record<string, unknown> & {
|
|
8
|
+
__forest: ForestRecordMetadata;
|
|
9
|
+
}>;
|
|
10
|
+
meta: {
|
|
11
|
+
countStatus: 'not_requested';
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export interface CountResponse {
|
|
15
|
+
count: number | null;
|
|
16
|
+
countStatus: 'available' | 'deactivated';
|
|
17
|
+
}
|
|
18
|
+
export declare function mapListResponse(collection: string, records: Record<string, unknown>[], primaryKeys: PrimaryKeyField[]): ListResponse;
|
|
19
|
+
export declare function mapCountResponse(raw: unknown): CountResponse;
|
|
20
|
+
//# sourceMappingURL=response-mappers.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.mapListResponse = mapListResponse;
|
|
7
|
+
exports.mapCountResponse = mapCountResponse;
|
|
8
|
+
const pack_id_1 = __importDefault(require("./pack-id"));
|
|
9
|
+
const bff_local_errors_1 = require("../http/bff-local-errors");
|
|
10
|
+
const DEACTIVATED = 'deactivated';
|
|
11
|
+
function mapListResponse(collection, records, primaryKeys) {
|
|
12
|
+
const data = records.map(record => {
|
|
13
|
+
if (record.id === undefined || record.id === null) {
|
|
14
|
+
throw (0, bff_local_errors_1.mappingError)('Agent record is missing its id');
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
...record,
|
|
18
|
+
__forest: {
|
|
19
|
+
collection,
|
|
20
|
+
primaryKey: (0, pack_id_1.default)(String(record.id), primaryKeys),
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
return { data, meta: { countStatus: 'not_requested' } };
|
|
25
|
+
}
|
|
26
|
+
// meta.count is the only deactivation signal the agent exposes.
|
|
27
|
+
function mapCountResponse(raw) {
|
|
28
|
+
const body = (typeof raw === 'object' && raw !== null ? raw : {});
|
|
29
|
+
if (body.meta?.count === DEACTIVATED) {
|
|
30
|
+
return { count: null, countStatus: 'deactivated' };
|
|
31
|
+
}
|
|
32
|
+
if (typeof body.count === 'number' && Number.isFinite(body.count)) {
|
|
33
|
+
return { count: body.count, countStatus: 'available' };
|
|
34
|
+
}
|
|
35
|
+
// Neither schema metadata nor a usable raw payload: never silently report 0.
|
|
36
|
+
throw (0, bff_local_errors_1.mappingError)('Agent count payload has no numeric count and no deactivated marker');
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzcG9uc2UtbWFwcGVycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9kYXRhL3Jlc3BvbnNlLW1hcHBlcnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFzQkEsMENBb0JDO0FBR0QsNENBZ0JDO0FBM0RELHdEQUF5QztBQUN6QywrREFBd0Q7QUFpQnhELE1BQU0sV0FBVyxHQUFHLGFBQWEsQ0FBQztBQUVsQyxTQUFnQixlQUFlLENBQzdCLFVBQWtCLEVBQ2xCLE9BQWtDLEVBQ2xDLFdBQThCO0lBRTlCLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLEVBQUU7UUFDaEMsSUFBSSxNQUFNLENBQUMsRUFBRSxLQUFLLFNBQVMsSUFBSSxNQUFNLENBQUMsRUFBRSxLQUFLLElBQUksRUFBRSxDQUFDO1lBQ2xELE1BQU0sSUFBQSwrQkFBWSxFQUFDLGdDQUFnQyxDQUFDLENBQUM7UUFDdkQsQ0FBQztRQUVELE9BQU87WUFDTCxHQUFHLE1BQU07WUFDVCxRQUFRLEVBQUU7Z0JBQ1IsVUFBVTtnQkFDVixVQUFVLEVBQUUsSUFBQSxpQkFBZ0IsRUFBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxFQUFFLFdBQVcsQ0FBQzthQUM3RDtTQUNGLENBQUM7SUFDSixDQUFDLENBQUMsQ0FBQztJQUVILE9BQU8sRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLEVBQUUsV0FBVyxFQUFFLGVBQWUsRUFBRSxFQUFFLENBQUM7QUFDMUQsQ0FBQztBQUVELGdFQUFnRTtBQUNoRSxTQUFnQixnQkFBZ0IsQ0FBQyxHQUFZO0lBQzNDLE1BQU0sSUFBSSxHQUFHLENBQUMsT0FBTyxHQUFHLEtBQUssUUFBUSxJQUFJLEdBQUcsS0FBSyxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUcvRCxDQUFDO0lBRUYsSUFBSSxJQUFJLENBQUMsSUFBSSxFQUFFLEtBQUssS0FBSyxXQUFXLEVBQUUsQ0FBQztRQUNyQyxPQUFPLEVBQUUsS0FBSyxFQUFFLElBQUksRUFBRSxXQUFXLEVBQUUsYUFBYSxFQUFFLENBQUM7SUFDckQsQ0FBQztJQUVELElBQUksT0FBTyxJQUFJLENBQUMsS0FBSyxLQUFLLFFBQVEsSUFBSSxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ2xFLE9BQU8sRUFBRSxLQUFLLEVBQUUsSUFBSSxDQUFDLEtBQUssRUFBRSxXQUFXLEVBQUUsV0FBVyxFQUFFLENBQUM7SUFDekQsQ0FBQztJQUVELDZFQUE2RTtJQUM3RSxNQUFNLElBQUEsK0JBQVksRUFBQyxvRUFBb0UsQ0FBQyxDQUFDO0FBQzNGLENBQUMifQ==
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Logger } from '../ports/logger-port';
|
|
2
|
+
import { BffHttpError } from './bff-http-error';
|
|
3
|
+
declare const TYPE_INVALID_REQUEST = "invalid_request";
|
|
4
|
+
declare const TYPE_VALIDATION_ERROR = "validation_error";
|
|
5
|
+
declare const TYPE_UNAUTHORIZED = "unauthorized";
|
|
6
|
+
declare const TYPE_FORBIDDEN = "forbidden";
|
|
7
|
+
declare const TYPE_NOT_FOUND = "not_found";
|
|
8
|
+
declare const TYPE_UNPROCESSABLE = "unprocessable_entity";
|
|
9
|
+
declare const TYPE_TOO_MANY_REQUESTS = "too_many_requests";
|
|
10
|
+
declare const TYPE_NETWORK_ERROR = "network_error";
|
|
11
|
+
declare const TYPE_AGENT_UNAVAILABLE = "agent_unavailable";
|
|
12
|
+
type BffErrorType = typeof TYPE_INVALID_REQUEST | typeof TYPE_VALIDATION_ERROR | typeof TYPE_UNAUTHORIZED | typeof TYPE_FORBIDDEN | typeof TYPE_NOT_FOUND | typeof TYPE_UNPROCESSABLE | typeof TYPE_TOO_MANY_REQUESTS | typeof TYPE_NETWORK_ERROR | typeof TYPE_AGENT_UNAVAILABLE;
|
|
13
|
+
export declare const AGENT_ERROR_TYPE_MAP: Record<string, BffErrorType>;
|
|
14
|
+
export declare function mapAgentError(error: unknown, { logger }: {
|
|
15
|
+
logger: Logger;
|
|
16
|
+
}): BffHttpError;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=agent-error-mapper.d.ts.map
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AGENT_ERROR_TYPE_MAP = void 0;
|
|
4
|
+
exports.mapAgentError = mapAgentError;
|
|
5
|
+
const agent_client_1 = require("@forestadmin/agent-client");
|
|
6
|
+
const bff_http_error_1 = require("./bff-http-error");
|
|
7
|
+
const TYPE_INVALID_REQUEST = 'invalid_request';
|
|
8
|
+
const TYPE_VALIDATION_ERROR = 'validation_error';
|
|
9
|
+
const TYPE_UNAUTHORIZED = 'unauthorized';
|
|
10
|
+
const TYPE_FORBIDDEN = 'forbidden';
|
|
11
|
+
const TYPE_NOT_FOUND = 'not_found';
|
|
12
|
+
const TYPE_UNPROCESSABLE = 'unprocessable_entity';
|
|
13
|
+
const TYPE_TOO_MANY_REQUESTS = 'too_many_requests';
|
|
14
|
+
const TYPE_NETWORK_ERROR = 'network_error';
|
|
15
|
+
const TYPE_AGENT_UNAVAILABLE = 'agent_unavailable';
|
|
16
|
+
const DEFAULT_NETWORK_MESSAGE = 'The agent could not be reached';
|
|
17
|
+
const DEFAULT_UNAVAILABLE_MESSAGE = 'The agent is unavailable';
|
|
18
|
+
const DEFAULT_ERROR_MESSAGE = 'Unexpected error';
|
|
19
|
+
exports.AGENT_ERROR_TYPE_MAP = {
|
|
20
|
+
ValidationError: TYPE_VALIDATION_ERROR,
|
|
21
|
+
BadRequestError: TYPE_INVALID_REQUEST,
|
|
22
|
+
UnauthorizedError: TYPE_UNAUTHORIZED,
|
|
23
|
+
ForbiddenError: TYPE_FORBIDDEN,
|
|
24
|
+
NotFoundError: TYPE_NOT_FOUND,
|
|
25
|
+
UnprocessableError: TYPE_UNPROCESSABLE,
|
|
26
|
+
TooManyRequestsError: TYPE_TOO_MANY_REQUESTS,
|
|
27
|
+
};
|
|
28
|
+
const FALLBACK_TYPE_BY_STATUS = {
|
|
29
|
+
400: TYPE_INVALID_REQUEST,
|
|
30
|
+
401: TYPE_UNAUTHORIZED,
|
|
31
|
+
403: TYPE_FORBIDDEN,
|
|
32
|
+
404: TYPE_NOT_FOUND,
|
|
33
|
+
422: TYPE_UNPROCESSABLE,
|
|
34
|
+
429: TYPE_TOO_MANY_REQUESTS,
|
|
35
|
+
};
|
|
36
|
+
function coerceStatus(status, fallback) {
|
|
37
|
+
const parsed = typeof status === 'string' ? Number(status) : status;
|
|
38
|
+
return typeof parsed === 'number' && Number.isFinite(parsed) ? parsed : fallback;
|
|
39
|
+
}
|
|
40
|
+
function fallbackTypeByStatus(status) {
|
|
41
|
+
return FALLBACK_TYPE_BY_STATUS[status] ?? TYPE_INVALID_REQUEST;
|
|
42
|
+
}
|
|
43
|
+
function agentUnavailable() {
|
|
44
|
+
return new bff_http_error_1.BffHttpError(503, TYPE_AGENT_UNAVAILABLE, DEFAULT_UNAVAILABLE_MESSAGE);
|
|
45
|
+
}
|
|
46
|
+
function firstJsonApiError(body) {
|
|
47
|
+
if (typeof body !== 'object' || body === null)
|
|
48
|
+
return undefined;
|
|
49
|
+
const { errors } = body;
|
|
50
|
+
return Array.isArray(errors) ? errors[0] : undefined;
|
|
51
|
+
}
|
|
52
|
+
function mapJsonApiError(agentError, fallbackStatus, logger) {
|
|
53
|
+
const bodyStatus = coerceStatus(agentError.status, fallbackStatus);
|
|
54
|
+
// Never surface an upstream failure as a 2xx/3xx: ignore a non-error body status.
|
|
55
|
+
const status = bodyStatus >= 400 ? bodyStatus : fallbackStatus;
|
|
56
|
+
// The wrapped-message path skips the outer AgentHttpError 5xx check, so normalize here too.
|
|
57
|
+
if (status >= 500)
|
|
58
|
+
return agentUnavailable();
|
|
59
|
+
const message = agentError.detail ?? agentError.message ?? DEFAULT_ERROR_MESSAGE;
|
|
60
|
+
const mappedType = agentError.name ? exports.AGENT_ERROR_TYPE_MAP[agentError.name] : undefined;
|
|
61
|
+
if (agentError.name && mappedType === undefined) {
|
|
62
|
+
logger('Warn', 'Unmapped agent error name; using status-derived type', {
|
|
63
|
+
name: agentError.name,
|
|
64
|
+
status,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return new bff_http_error_1.BffHttpError(status, mappedType ?? fallbackTypeByStatus(status), message, agentError.data);
|
|
68
|
+
}
|
|
69
|
+
function parseJsonApiFromMessage(error) {
|
|
70
|
+
if (!(error instanceof Error))
|
|
71
|
+
return undefined;
|
|
72
|
+
try {
|
|
73
|
+
return firstJsonApiError(JSON.parse(error.message));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function mapFlatBody(status, body, responseText) {
|
|
80
|
+
const flat = (typeof body === 'object' && body !== null ? body : {});
|
|
81
|
+
const message = (typeof flat.error === 'string' ? flat.error : undefined) ??
|
|
82
|
+
(typeof flat.message === 'string' ? flat.message : undefined) ??
|
|
83
|
+
(typeof responseText === 'string' && responseText !== '' ? responseText : undefined) ??
|
|
84
|
+
DEFAULT_ERROR_MESSAGE;
|
|
85
|
+
return new bff_http_error_1.BffHttpError(status, fallbackTypeByStatus(status), message);
|
|
86
|
+
}
|
|
87
|
+
function mapAgentError(error, { logger }) {
|
|
88
|
+
// A BFF-origin error that reached here (e.g. thrown inside a caller's agent-call try/catch) keeps
|
|
89
|
+
// its own type — it must not be recategorized as a transport/agent error.
|
|
90
|
+
if (error instanceof bff_http_error_1.BffHttpError)
|
|
91
|
+
return error;
|
|
92
|
+
if (!(error instanceof agent_client_1.AgentHttpError)) {
|
|
93
|
+
const agentError = parseJsonApiFromMessage(error);
|
|
94
|
+
if (agentError)
|
|
95
|
+
return mapJsonApiError(agentError, 400, logger);
|
|
96
|
+
// No HTTP response: treated as a transport failure reaching the agent. Log the real cause but
|
|
97
|
+
// return a generic message — transport errors embed internal topology (hostnames/IPs) that must
|
|
98
|
+
// not leak to the client. Semantic agent-client errors (action form validation / approval
|
|
99
|
+
// outcomes) are NOT transport failures: the action endpoint catches and maps those before this
|
|
100
|
+
// fallback. Callers must scope their try/catch to the agent call so a local BFF bug surfaces as
|
|
101
|
+
// a 500 through the error middleware rather than being mislabelled here.
|
|
102
|
+
logger('Warn', 'Agent transport failure mapped to network_error', {
|
|
103
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
104
|
+
});
|
|
105
|
+
return new bff_http_error_1.BffHttpError(502, TYPE_NETWORK_ERROR, DEFAULT_NETWORK_MESSAGE);
|
|
106
|
+
}
|
|
107
|
+
if (error.status >= 500)
|
|
108
|
+
return agentUnavailable();
|
|
109
|
+
const agentError = firstJsonApiError(error.body);
|
|
110
|
+
if (agentError)
|
|
111
|
+
return mapJsonApiError(agentError, error.status, logger);
|
|
112
|
+
return mapFlatBody(error.status, error.body, error.responseText);
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWdlbnQtZXJyb3ItbWFwcGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2h0dHAvYWdlbnQtZXJyb3ItbWFwcGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQXNJQSxzQ0E0QkM7QUFoS0QsNERBQTJEO0FBRTNELHFEQUFnRDtBQUVoRCxNQUFNLG9CQUFvQixHQUFHLGlCQUFpQixDQUFDO0FBQy9DLE1BQU0scUJBQXFCLEdBQUcsa0JBQWtCLENBQUM7QUFDakQsTUFBTSxpQkFBaUIsR0FBRyxjQUFjLENBQUM7QUFDekMsTUFBTSxjQUFjLEdBQUcsV0FBVyxDQUFDO0FBQ25DLE1BQU0sY0FBYyxHQUFHLFdBQVcsQ0FBQztBQUNuQyxNQUFNLGtCQUFrQixHQUFHLHNCQUFzQixDQUFDO0FBQ2xELE1BQU0sc0JBQXNCLEdBQUcsbUJBQW1CLENBQUM7QUFDbkQsTUFBTSxrQkFBa0IsR0FBRyxlQUFlLENBQUM7QUFDM0MsTUFBTSxzQkFBc0IsR0FBRyxtQkFBbUIsQ0FBQztBQWFuRCxNQUFNLHVCQUF1QixHQUFHLGdDQUFnQyxDQUFDO0FBQ2pFLE1BQU0sMkJBQTJCLEdBQUcsMEJBQTBCLENBQUM7QUFDL0QsTUFBTSxxQkFBcUIsR0FBRyxrQkFBa0IsQ0FBQztBQUVwQyxRQUFBLG9CQUFvQixHQUFpQztJQUNoRSxlQUFlLEVBQUUscUJBQXFCO0lBQ3RDLGVBQWUsRUFBRSxvQkFBb0I7SUFDckMsaUJBQWlCLEVBQUUsaUJBQWlCO0lBQ3BDLGNBQWMsRUFBRSxjQUFjO0lBQzlCLGFBQWEsRUFBRSxjQUFjO0lBQzdCLGtCQUFrQixFQUFFLGtCQUFrQjtJQUN0QyxvQkFBb0IsRUFBRSxzQkFBc0I7Q0FDN0MsQ0FBQztBQUVGLE1BQU0sdUJBQXVCLEdBQWlDO0lBQzVELEdBQUcsRUFBRSxvQkFBb0I7SUFDekIsR0FBRyxFQUFFLGlCQUFpQjtJQUN0QixHQUFHLEVBQUUsY0FBYztJQUNuQixHQUFHLEVBQUUsY0FBYztJQUNuQixH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
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Logger } from '../ports/logger-port';
|
|
2
|
+
import type ReadModel from '../read-model/read-model';
|
|
3
|
+
import type ReadModelStore from '../read-model/read-model-store';
|
|
4
|
+
import type { Context } from 'koa';
|
|
5
|
+
export declare function decodeSegment(raw: string, label: string): string;
|
|
6
|
+
export declare function resolveReadModel(store: ReadModelStore): Promise<ReadModel>;
|
|
7
|
+
export declare function requireAgentToken(ctx: Context): string;
|
|
8
|
+
export declare function callAgent<T>(fn: () => Promise<T>, logger: Logger): Promise<T>;
|
|
9
|
+
//# sourceMappingURL=agent-route-helpers.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.decodeSegment = decodeSegment;
|
|
7
|
+
exports.resolveReadModel = resolveReadModel;
|
|
8
|
+
exports.requireAgentToken = requireAgentToken;
|
|
9
|
+
exports.callAgent = callAgent;
|
|
10
|
+
const agent_error_mapper_1 = require("./agent-error-mapper");
|
|
11
|
+
const bff_http_error_1 = require("./bff-http-error");
|
|
12
|
+
const bff_local_errors_1 = require("./bff-local-errors");
|
|
13
|
+
const errors_1 = __importDefault(require("../read-model/errors"));
|
|
14
|
+
function decodeSegment(raw, label) {
|
|
15
|
+
try {
|
|
16
|
+
return decodeURIComponent(raw);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw (0, bff_local_errors_1.invalidRequest)(`Malformed ${label} in path`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function resolveReadModel(store) {
|
|
23
|
+
try {
|
|
24
|
+
return await store.getReadModel();
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error instanceof errors_1.default)
|
|
28
|
+
throw (0, bff_local_errors_1.schemaUnavailable)();
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// The agent token is minted only on the API-key path; the OAuth path sets a principal but no agent
|
|
33
|
+
// token yet. Fail closed instead of calling the agent with `Bearer undefined`.
|
|
34
|
+
// TODO(PRD-637): mint an agent token from the OAuth principal so agent routes work in OAuth mode.
|
|
35
|
+
function requireAgentToken(ctx) {
|
|
36
|
+
const token = ctx.state.agentToken;
|
|
37
|
+
if (!token)
|
|
38
|
+
throw (0, bff_http_error_1.unauthorized)('No agent credentials for this request');
|
|
39
|
+
return token;
|
|
40
|
+
}
|
|
41
|
+
// Only the agent call is wrapped: guard, query-building and mapping errors are BFF-origin and must
|
|
42
|
+
// keep their own type, not be recategorized as agent errors.
|
|
43
|
+
async function callAgent(fn, logger) {
|
|
44
|
+
try {
|
|
45
|
+
return await fn();
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
throw (0, agent_error_mapper_1.mapAgentError)(error, { logger });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWdlbnQtcm91dGUtaGVscGVycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9odHRwL2FnZW50LXJvdXRlLWhlbHBlcnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFVQSxzQ0FNQztBQUVELDRDQU9DO0FBS0QsOENBS0M7QUFJRCw4QkFNQztBQXhDRCw2REFBcUQ7QUFDckQscURBQWdEO0FBQ2hELHlEQUF1RTtBQUN2RSxrRUFBMEQ7QUFFMUQsU0FBZ0IsYUFBYSxDQUFDLEdBQVcsRUFBRSxLQUFhO0lBQ3RELElBQUksQ0FBQztRQUNILE9BQU8sa0JBQWtCLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDakMsQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUNQLE1BQU0sSUFBQSxpQ0FBYyxFQUFDLGFBQWEsS0FBSyxVQUFVLENBQUMsQ0FBQztJQUNyRCxDQUFDO0FBQ0gsQ0FBQztBQUVNLEtBQUssVUFBVSxnQkFBZ0IsQ0FBQyxLQUFxQjtJQUMxRCxJQUFJLENBQUM7UUFDSCxPQUFPLE1BQU0sS0FBSyxDQUFDLFlBQVksRUFBRSxDQUFDO0lBQ3BDLENBQUM7SUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1FBQ2YsSUFBSSxLQUFLLFlBQVksZ0JBQXNCO1lBQUUsTUFBTSxJQUFBLG9DQUFpQixHQUFFLENBQUM7UUFDdkUsTUFBTSxLQUFLLENBQUM7SUFDZCxDQUFDO0FBQ0gsQ0FBQztBQUVELG1HQUFtRztBQUNuRywrRUFBK0U7QUFDL0Usa0dBQWtHO0FBQ2xHLFNBQWdCLGlCQUFpQixDQUFDLEdBQVk7SUFDNUMsTUFBTSxLQUFLLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxVQUFnQyxDQUFDO0lBQ3pELElBQUksQ0FBQyxLQUFLO1FBQUUsTUFBTSxJQUFBLDZCQUFZLEVBQUMsdUNBQXVDLENBQUMsQ0FBQztJQUV4RSxPQUFPLEtBQUssQ0FBQztBQUNmLENBQUM7QUFFRCxtR0FBbUc7QUFDbkcsNkRBQTZEO0FBQ3RELEtBQUssVUFBVSxTQUFTLENBQUksRUFBb0IsRUFBRSxNQUFjO0lBQ3JFLElBQUksQ0FBQztRQUNILE9BQU8sTUFBTSxFQUFFLEVBQUUsQ0FBQztJQUNwQixDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNmLE1BQU0sSUFBQSxrQ0FBYSxFQUFDLEtBQUssRUFBRSxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUM7SUFDekMsQ0FBQztBQUNILENBQUMifQ==
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BffHttpError } from './bff-http-error';
|
|
2
|
+
export declare function unknownCollection(message?: string): BffHttpError;
|
|
3
|
+
export declare function unknownRelation(message?: string): BffHttpError;
|
|
4
|
+
export declare function unknownAction(message?: string): BffHttpError;
|
|
5
|
+
export declare function collectionNotAllowed(message?: string): BffHttpError;
|
|
6
|
+
export declare function relationNotAllowed(message?: string): BffHttpError;
|
|
7
|
+
export declare function actionNotAllowed(message?: string): BffHttpError;
|
|
8
|
+
export declare function invalidRequest(message?: string, details?: unknown): BffHttpError;
|
|
9
|
+
export declare function mappingError(message?: string): BffHttpError;
|
|
10
|
+
export declare function schemaUnavailable(message?: string): BffHttpError;
|
|
11
|
+
export declare function unsupportedActionResult(message?: string): BffHttpError;
|
|
12
|
+
//# sourceMappingURL=bff-local-errors.d.ts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.unknownCollection = unknownCollection;
|
|
4
|
+
exports.unknownRelation = unknownRelation;
|
|
5
|
+
exports.unknownAction = unknownAction;
|
|
6
|
+
exports.collectionNotAllowed = collectionNotAllowed;
|
|
7
|
+
exports.relationNotAllowed = relationNotAllowed;
|
|
8
|
+
exports.actionNotAllowed = actionNotAllowed;
|
|
9
|
+
exports.invalidRequest = invalidRequest;
|
|
10
|
+
exports.mappingError = mappingError;
|
|
11
|
+
exports.schemaUnavailable = schemaUnavailable;
|
|
12
|
+
exports.unsupportedActionResult = unsupportedActionResult;
|
|
13
|
+
const bff_http_error_1 = require("./bff-http-error");
|
|
14
|
+
function unknownCollection(message = 'Unknown collection') {
|
|
15
|
+
return new bff_http_error_1.BffHttpError(404, 'unknown_collection', message);
|
|
16
|
+
}
|
|
17
|
+
function unknownRelation(message = 'Unknown relation') {
|
|
18
|
+
return new bff_http_error_1.BffHttpError(404, 'unknown_relation', message);
|
|
19
|
+
}
|
|
20
|
+
function unknownAction(message = 'Unknown action') {
|
|
21
|
+
return new bff_http_error_1.BffHttpError(404, 'unknown_action', message);
|
|
22
|
+
}
|
|
23
|
+
function collectionNotAllowed(message = 'Collection is not allowed') {
|
|
24
|
+
return new bff_http_error_1.BffHttpError(403, 'collection_not_allowed', message);
|
|
25
|
+
}
|
|
26
|
+
function relationNotAllowed(message = 'Relation is not allowed') {
|
|
27
|
+
return new bff_http_error_1.BffHttpError(403, 'relation_not_allowed', message);
|
|
28
|
+
}
|
|
29
|
+
function actionNotAllowed(message = 'Action is not allowed') {
|
|
30
|
+
return new bff_http_error_1.BffHttpError(403, 'action_not_allowed', message);
|
|
31
|
+
}
|
|
32
|
+
function invalidRequest(message = 'Invalid request', details) {
|
|
33
|
+
return new bff_http_error_1.BffHttpError(400, 'invalid_request', message, details);
|
|
34
|
+
}
|
|
35
|
+
function mappingError(message = 'Failed to map the agent response') {
|
|
36
|
+
return new bff_http_error_1.BffHttpError(500, 'mapping_error', message);
|
|
37
|
+
}
|
|
38
|
+
function schemaUnavailable(message = 'The agent schema is unavailable') {
|
|
39
|
+
return new bff_http_error_1.BffHttpError(503, 'schema_unavailable', message);
|
|
40
|
+
}
|
|
41
|
+
function unsupportedActionResult(message = 'Unsupported action result') {
|
|
42
|
+
return new bff_http_error_1.BffHttpError(501, 'unsupported_action_result', message);
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmZmLWxvY2FsLWVycm9ycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9odHRwL2JmZi1sb2NhbC1lcnJvcnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFFQSw4Q0FFQztBQUVELDBDQUVDO0FBRUQsc0NBRUM7QUFFRCxvREFFQztBQUVELGdEQUVDO0FBRUQsNENBRUM7QUFFRCx3Q0FFQztBQUVELG9DQUVDO0FBRUQsOENBRUM7QUFFRCwwREFFQztBQXhDRCxxREFBZ0Q7QUFFaEQsU0FBZ0IsaUJBQWlCLENBQUMsT0FBTyxHQUFHLG9CQUFvQjtJQUM5RCxPQUFPLElBQUksNkJBQVksQ0FBQyxHQUFHLEVBQUUsb0JBQW9CLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDOUQsQ0FBQztBQUVELFNBQWdCLGVBQWUsQ0FBQyxPQUFPLEdBQUcsa0JBQWtCO0lBQzFELE9BQU8sSUFBSSw2QkFBWSxDQUFDLEdBQUcsRUFBRSxrQkFBa0IsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUM1RCxDQUFDO0FBRUQsU0FBZ0IsYUFBYSxDQUFDLE9BQU8sR0FBRyxnQkFBZ0I7SUFDdEQsT0FBTyxJQUFJLDZCQUFZLENBQUMsR0FBRyxFQUFFLGdCQUFnQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQzFELENBQUM7QUFFRCxTQUFnQixvQkFBb0IsQ0FBQyxPQUFPLEdBQUcsMkJBQTJCO0lBQ3hFLE9BQU8sSUFBSSw2QkFBWSxDQUFDLEdBQUcsRUFBRSx3QkFBd0IsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUNsRSxDQUFDO0FBRUQsU0FBZ0Isa0JBQWtCLENBQUMsT0FBTyxHQUFHLHlCQUF5QjtJQUNwRSxPQUFPLElBQUksNkJBQVksQ0FBQyxHQUFHLEVBQUUsc0JBQXNCLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDaEUsQ0FBQztBQUVELFNBQWdCLGdCQUFnQixDQUFDLE9BQU8sR0FBRyx1QkFBdUI7SUFDaEUsT0FBTyxJQUFJLDZCQUFZLENBQUMsR0FBRyxFQUFFLG9CQUFvQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQzlELENBQUM7QUFFRCxTQUFnQixjQUFjLENBQUMsT0FBTyxHQUFHLGlCQUFpQixFQUFFLE9BQWlCO0lBQzNFLE9BQU8sSUFBSSw2QkFBWSxDQUFDLEdBQUcsRUFBRSxpQkFBaUIsRUFBRSxPQUFPLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDcEUsQ0FBQztBQUVELFNBQWdCLFlBQVksQ0FBQyxPQUFPLEdBQUcsa0NBQWtDO0lBQ3ZFLE9BQU8sSUFBSSw2QkFBWSxDQUFDLEdBQUcsRUFBRSxlQUFlLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDekQsQ0FBQztBQUVELFNBQWdCLGlCQUFpQixDQUFDLE9BQU8sR0FBRyxpQ0FBaUM7SUFDM0UsT0FBTyxJQUFJLDZCQUFZLENBQUMsR0FBRyxFQUFFLG9CQUFvQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQzlELENBQUM7QUFFRCxTQUFnQix1QkFBdUIsQ0FBQyxPQUFPLEdBQUcsMkJBQTJCO0lBQzNFLE9BQU8sSUFBSSw2QkFBWSxDQUFDLEdBQUcsRUFBRSwyQkFBMkIsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUNyRSxDQUFDIn0=
|
|
@@ -10,6 +10,10 @@ export type RelationTarget = {
|
|
|
10
10
|
polymorphic: true;
|
|
11
11
|
targets: string[];
|
|
12
12
|
};
|
|
13
|
+
export type PrimaryKeyField = {
|
|
14
|
+
name: string;
|
|
15
|
+
type: string;
|
|
16
|
+
};
|
|
13
17
|
/**
|
|
14
18
|
* The agent read-model derived from a schema. Names are collection-scoped: relations and actions
|
|
15
19
|
* are keyed by `(collection, name)` so the same relation/action name on two collections stays
|
|
@@ -20,13 +24,16 @@ export default class ReadModel {
|
|
|
20
24
|
private readonly collections;
|
|
21
25
|
private readonly relations;
|
|
22
26
|
private readonly actionEndpoints;
|
|
27
|
+
private readonly primaryKeys;
|
|
23
28
|
constructor(collections: ForestSchemaCollection[]);
|
|
24
29
|
private buildRelations;
|
|
25
30
|
private buildActionEndpoints;
|
|
31
|
+
private buildPrimaryKeys;
|
|
26
32
|
isCollectionAllowed(collection: string): boolean;
|
|
27
33
|
isRelationAllowed(collection: string, relation: string): boolean;
|
|
28
34
|
getRelationTarget(collection: string, relation: string): RelationTarget | undefined;
|
|
29
35
|
isActionAllowed(collection: string, action: string): boolean;
|
|
30
36
|
getActionEndpoints(): ActionEndpointsByCollection;
|
|
37
|
+
getPrimaryKeys(collection: string): PrimaryKeyField[];
|
|
31
38
|
}
|
|
32
39
|
//# sourceMappingURL=read-model.d.ts.map
|
|
@@ -34,14 +34,17 @@ class ReadModel {
|
|
|
34
34
|
// Null-prototype so an action/collection named like an Object.prototype member
|
|
35
35
|
// (`toString`, `constructor`, `__proto__`, …) can't falsely pass the allow-list.
|
|
36
36
|
this.actionEndpoints = Object.create(null);
|
|
37
|
+
this.primaryKeys = new Map();
|
|
37
38
|
for (const collection of collections) {
|
|
38
39
|
this.collections.add(collection.name);
|
|
39
40
|
this.buildRelations(collection);
|
|
40
41
|
this.buildActionEndpoints(collection);
|
|
42
|
+
this.buildPrimaryKeys(collection);
|
|
41
43
|
}
|
|
42
44
|
// Freeze the exposed structures so a consumer cannot mutate the shared cached read-model.
|
|
43
45
|
deepFreeze(this.actionEndpoints);
|
|
44
46
|
this.relations.forEach(byRelation => byRelation.forEach(deepFreeze));
|
|
47
|
+
this.primaryKeys.forEach(deepFreeze);
|
|
45
48
|
}
|
|
46
49
|
buildRelations(collection) {
|
|
47
50
|
const relations = new Map();
|
|
@@ -71,6 +74,14 @@ class ReadModel {
|
|
|
71
74
|
};
|
|
72
75
|
}
|
|
73
76
|
}
|
|
77
|
+
buildPrimaryKeys(collection) {
|
|
78
|
+
const keys = [];
|
|
79
|
+
for (const field of collection.fields ?? []) {
|
|
80
|
+
if (field.isPrimaryKey)
|
|
81
|
+
keys.push({ name: field.field, type: field.type });
|
|
82
|
+
}
|
|
83
|
+
this.primaryKeys.set(collection.name, keys);
|
|
84
|
+
}
|
|
74
85
|
isCollectionAllowed(collection) {
|
|
75
86
|
return this.collections.has(collection);
|
|
76
87
|
}
|
|
@@ -86,6 +97,9 @@ class ReadModel {
|
|
|
86
97
|
getActionEndpoints() {
|
|
87
98
|
return this.actionEndpoints;
|
|
88
99
|
}
|
|
100
|
+
getPrimaryKeys(collection) {
|
|
101
|
+
return this.primaryKeys.get(collection) ?? [];
|
|
102
|
+
}
|
|
89
103
|
}
|
|
90
104
|
exports.default = ReadModel;
|
|
91
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
105
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVhZC1tb2RlbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9yZWFkLW1vZGVsL3JlYWQtbW9kZWwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFXQSxTQUFTLGdCQUFnQixDQUFDLEtBQXdCO0lBQ2hELElBQUksQ0FBQyxLQUFLLENBQUMsWUFBWTtRQUFFLE9BQU8sSUFBSSxDQUFDO0lBRXJDLE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxZQUFZLENBQUM7SUFFaEMsSUFBSSxLQUFLLENBQUMsMkJBQTJCLElBQUksS0FBSyxDQUFDLDJCQUEyQixDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUN0RixPQUFPLEVBQUUsSUFBSSxFQUFFLFdBQVcsRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLENBQUMsR0FBRyxLQUFLLENBQUMsMkJBQTJCLENBQUMsRUFBRSxDQUFDO0lBQ3RGLENBQUM7SUFFRCxJQUFJLEtBQUssQ0FBQyxTQUFTLEVBQUUsQ0FBQztRQUNwQiwwRkFBMEY7UUFDMUYseUZBQXlGO1FBQ3pGLE9BQU8sRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsS0FBSyxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO0lBQ2pHLENBQUM7SUFFRCxPQUFPLElBQUksQ0FBQztBQUNkLENBQUM7QUFFRCxTQUFTLFVBQVUsQ0FBSSxLQUFRO0lBQzdCLElBQUksS0FBSyxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsRUFBRSxDQUFDO1FBQ3ZDLE1BQU0sQ0FBQyxNQUFNLENBQUMsS0FBZ0MsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUNwRSxNQUFNLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3ZCLENB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
|