@forestadmin/agent-bff 1.6.0 → 1.7.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/cli-core.js +17 -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 +159 -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/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
package/dist/cli-core.js
CHANGED
|
@@ -16,6 +16,7 @@ const auth_mode_middleware_1 = __importDefault(require("./auth/auth-mode-middlew
|
|
|
16
16
|
const env_config_1 = require("./config/env-config");
|
|
17
17
|
const cors_middleware_1 = __importDefault(require("./cors/cors-middleware"));
|
|
18
18
|
const per_key_origin_1 = __importDefault(require("./cors/per-key-origin"));
|
|
19
|
+
const data_routes_middleware_1 = __importDefault(require("./data/data-routes-middleware"));
|
|
19
20
|
const errors_1 = require("./errors");
|
|
20
21
|
const bff_http_error_1 = require("./http/bff-http-error");
|
|
21
22
|
const bff_http_server_1 = __importDefault(require("./http/bff-http-server"));
|
|
@@ -24,6 +25,7 @@ const forest_server_client_1 = __importDefault(require("./oauth/forest-server-cl
|
|
|
24
25
|
const oauth_routes_1 = __importDefault(require("./oauth/oauth-routes"));
|
|
25
26
|
const session_store_1 = __importDefault(require("./oauth/session-store"));
|
|
26
27
|
const token_cipher_1 = __importDefault(require("./oauth/token-cipher"));
|
|
28
|
+
const create_read_model_1 = __importDefault(require("./read-model/create-read-model"));
|
|
27
29
|
const timezone_middleware_1 = __importDefault(require("./timezone/timezone-middleware"));
|
|
28
30
|
const version_1 = __importDefault(require("./version"));
|
|
29
31
|
const BODY_LIMIT = '16kb';
|
|
@@ -109,6 +111,19 @@ function buildApiKeyMiddleware(config, logger) {
|
|
|
109
111
|
});
|
|
110
112
|
return (0, api_key_middleware_1.default)({ authenticator, logger });
|
|
111
113
|
}
|
|
114
|
+
function buildDataRoutesMiddleware(config, logger) {
|
|
115
|
+
const apiKeyConfig = resolveApiKeyConfig(config);
|
|
116
|
+
if (!apiKeyConfig || !config.agentUrl) {
|
|
117
|
+
logger('Warn', 'Data endpoints disabled: AGENT_URL or read-model configuration is missing');
|
|
118
|
+
return (0, agent_stub_1.default)();
|
|
119
|
+
}
|
|
120
|
+
const { store } = (0, create_read_model_1.default)({
|
|
121
|
+
forestServerUrl: apiKeyConfig.forestServerUrl,
|
|
122
|
+
envSecret: apiKeyConfig.forestEnvSecret,
|
|
123
|
+
logger,
|
|
124
|
+
});
|
|
125
|
+
return (0, data_routes_middleware_1.default)({ store, agentUrl: config.agentUrl, logger });
|
|
126
|
+
}
|
|
112
127
|
function buildAgentMiddlewares(config, logger) {
|
|
113
128
|
const { forestAuthSecret, defaultTimezone } = config;
|
|
114
129
|
if (!forestAuthSecret) {
|
|
@@ -121,7 +136,7 @@ function buildAgentMiddlewares(config, logger) {
|
|
|
121
136
|
apiKeyStep,
|
|
122
137
|
(0, per_key_origin_1.default)(),
|
|
123
138
|
(0, timezone_middleware_1.default)({ defaultTimezone }),
|
|
124
|
-
(
|
|
139
|
+
buildDataRoutesMiddleware(config, logger),
|
|
125
140
|
];
|
|
126
141
|
return chain.map(agentScoped);
|
|
127
142
|
}
|
|
@@ -156,4 +171,4 @@ function reportFatalError(err) {
|
|
|
156
171
|
process.stderr.write(`Error: ${(0, errors_1.extractErrorMessage)(err)}\n`);
|
|
157
172
|
process.exitCode = 1;
|
|
158
173
|
}
|
|
159
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
174
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLWNvcmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY2xpLWNvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFvTUEseUJBa0NDO0FBRUQsNENBR0M7QUF2T0QsZ0RBQTZDO0FBRTdDLCtFQUE0RDtBQUM1RCxvRUFBMkQ7QUFDM0QsNEZBQXdFO0FBQ3hFLDhFQUFvRDtBQUNwRCxzRkFBa0U7QUFDbEUsNEVBQXlEO0FBQ3pELHVGQUFtRTtBQUNuRSxvREFBa0Q7QUFDbEQsNkVBQTBEO0FBQzFELDJFQUFpRTtBQUNqRSwyRkFBdUU7QUFDdkUscUNBQStDO0FBQy9DLDBEQUFxRDtBQUNyRCw2RUFBbUQ7QUFDbkQsK0VBQTREO0FBQzVELHdGQUE4RDtBQUM5RCx3RUFBcUQ7QUFDckQsMEVBQStEO0FBQy9ELHdFQUFxRDtBQUNyRCx1RkFBNkQ7QUFDN0QseUZBQXNFO0FBQ3RFLHdEQUFnQztBQUVoQyxNQUFNLFVBQVUsR0FBRyxNQUFNLENBQUM7QUFDMUIsTUFBTSxtQkFBbUIsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsQ0FBQztBQUV6QyxTQUFTLFdBQVcsQ0FBQyxJQUFZO0lBQy9CLE9BQU8sSUFBSSxLQUFLLFFBQVEsSUFBSSxJQUFJLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQ3pELENBQUM7QUFFRCxTQUFTLFdBQVcsQ0FBQyxVQUFzQjtJQUN6QyxPQUFPLEtBQUssVUFBVSxNQUFNLENBQUMsR0FBRyxFQUFFLElBQUk7UUFDcEMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztZQUMzQixNQUFNLElBQUksRUFBRSxDQUFDO1lBRWIsT0FBTztRQUNULENBQUM7UUFFRCxNQUFNLFVBQVUsQ0FBQyxHQUFHLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDOUIsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQUVELFNBQVMsNEJBQTRCLENBQUMsTUFBYztJQUNsRCxP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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,159 @@
|
|
|
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_error_mapper_1 = require("../http/agent-error-mapper");
|
|
11
|
+
const bff_http_error_1 = require("../http/bff-http-error");
|
|
12
|
+
const bff_local_errors_1 = require("../http/bff-local-errors");
|
|
13
|
+
const errors_1 = __importDefault(require("../read-model/errors"));
|
|
14
|
+
const relation_field_guard_1 = __importDefault(require("../validation/relation-field-guard"));
|
|
15
|
+
const DATA_ROUTE = /^\/agent\/v1\/([^/]+)\/(list|count)$/;
|
|
16
|
+
const RELATION_ROUTE = /^\/agent\/v1\/([^/]+)\/relations\/([^/]+)\/(list|count)$/;
|
|
17
|
+
// Only to-many relations expose list/count on the agent; to-one relations get update-relation only.
|
|
18
|
+
// A polymorphic relation carrying multiple targets is always the to-one (PolymorphicManyToOne) side.
|
|
19
|
+
function resolveForeignCollection(target) {
|
|
20
|
+
if (!target || !('target' in target))
|
|
21
|
+
return null;
|
|
22
|
+
if (target.type !== 'HasMany' && target.type !== 'BelongsToMany')
|
|
23
|
+
return null;
|
|
24
|
+
return target.target;
|
|
25
|
+
}
|
|
26
|
+
function decodeSegment(raw, label) {
|
|
27
|
+
try {
|
|
28
|
+
return decodeURIComponent(raw);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw (0, bff_local_errors_1.invalidRequest)(`Malformed ${label} in path`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function resolveReadModel(store) {
|
|
35
|
+
try {
|
|
36
|
+
return await store.getReadModel();
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error instanceof errors_1.default)
|
|
40
|
+
throw (0, bff_local_errors_1.schemaUnavailable)();
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Only the agent call is wrapped: guard, query-building and mapping errors are BFF-origin and must
|
|
45
|
+
// keep their own type, not be recategorized as agent errors.
|
|
46
|
+
async function callAgent(fn, logger) {
|
|
47
|
+
try {
|
|
48
|
+
return await fn();
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
throw (0, agent_error_mapper_1.mapAgentError)(error, { logger });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function handleList(ctx, body, deps) {
|
|
55
|
+
(0, relation_field_guard_1.default)((0, agent_query_1.collectListFieldPaths)(body));
|
|
56
|
+
const query = (0, agent_query_1.buildListAgentQuery)(deps.collection, deps.timezone, body);
|
|
57
|
+
const records = await callAgent(() => deps.client.list(deps.collection, query), deps.logger);
|
|
58
|
+
ctx.status = 200;
|
|
59
|
+
ctx.body = (0, response_mappers_1.mapListResponse)(deps.collection, records, deps.primaryKeys);
|
|
60
|
+
}
|
|
61
|
+
async function handleCount(ctx, body, deps) {
|
|
62
|
+
(0, relation_field_guard_1.default)((0, agent_query_1.collectCountFieldPaths)(body));
|
|
63
|
+
const query = (0, agent_query_1.buildCountAgentQuery)(deps.timezone, body);
|
|
64
|
+
const raw = await callAgent(() => deps.client.countRaw(deps.collection, query), deps.logger);
|
|
65
|
+
ctx.status = 200;
|
|
66
|
+
ctx.body = (0, response_mappers_1.mapCountResponse)(raw);
|
|
67
|
+
}
|
|
68
|
+
async function handleRelationList(ctx, body, deps) {
|
|
69
|
+
// The nested-relation guard IS wired here: the agent's list-related asserts browse only on the
|
|
70
|
+
// immediate foreign collection, so a nested `:`-path would traverse to a third collection whose
|
|
71
|
+
// browse is never checked. Plain foreign fields (no `:`) are unaffected.
|
|
72
|
+
(0, relation_field_guard_1.default)((0, agent_query_1.collectListFieldPaths)(body));
|
|
73
|
+
const query = (0, agent_query_1.buildListAgentQuery)(deps.foreignCollection, deps.timezone, body);
|
|
74
|
+
const records = await callAgent(() => deps.client.listRelation(deps.collection, body.parentId, deps.relation, query), deps.logger);
|
|
75
|
+
ctx.status = 200;
|
|
76
|
+
ctx.body = (0, response_mappers_1.mapListResponse)(deps.foreignCollection, records, deps.primaryKeys);
|
|
77
|
+
}
|
|
78
|
+
async function handleRelationCount(ctx, body, deps) {
|
|
79
|
+
(0, relation_field_guard_1.default)((0, agent_query_1.collectCountFieldPaths)(body));
|
|
80
|
+
const query = (0, agent_query_1.buildCountAgentQuery)(deps.timezone, body);
|
|
81
|
+
const raw = await callAgent(() => deps.client.countRelationRaw(deps.collection, body.parentId, deps.relation, query), deps.logger);
|
|
82
|
+
ctx.status = 200;
|
|
83
|
+
ctx.body = (0, response_mappers_1.mapCountResponse)(raw);
|
|
84
|
+
}
|
|
85
|
+
async function handleRelation(ctx, rawBody, match, { deps, readModel }) {
|
|
86
|
+
const relation = decodeSegment(match[2], 'relation name');
|
|
87
|
+
const operation = match[3];
|
|
88
|
+
// The URL identity (does this listable relation exist?) is resolved before the body,
|
|
89
|
+
// so a bad path 404s before its payload is inspected.
|
|
90
|
+
const foreignCollection = resolveForeignCollection(readModel.getRelationTarget(deps.collection, relation));
|
|
91
|
+
// TODO(PRD-672): the read-model's relation map is the allow-list, so an absent/to-one/polymorphic
|
|
92
|
+
// relation cannot be told from a disallowed one — every non-listable relation maps to 404 here;
|
|
93
|
+
// `relation_not_allowed` (403) has no local trigger, mirroring `collection_not_allowed`.
|
|
94
|
+
if (!foreignCollection) {
|
|
95
|
+
throw (0, bff_local_errors_1.unknownRelation)(`Unknown relation: ${deps.collection}.${relation}`);
|
|
96
|
+
}
|
|
97
|
+
// The agent's count-related authorizes only the parent, so an unguarded foreign collection would
|
|
98
|
+
// leak a hidden collection's rows/count. Guard it here, mirroring the parent allow-list check.
|
|
99
|
+
if (!readModel.isCollectionAllowed(foreignCollection)) {
|
|
100
|
+
throw (0, bff_local_errors_1.unknownCollection)();
|
|
101
|
+
}
|
|
102
|
+
const relationDeps = { ...deps, relation, foreignCollection };
|
|
103
|
+
if (operation === 'list') {
|
|
104
|
+
await handleRelationList(ctx, (0, agent_query_1.parseRelationListRequest)(rawBody), {
|
|
105
|
+
...relationDeps,
|
|
106
|
+
primaryKeys: readModel.getPrimaryKeys(foreignCollection),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
await handleRelationCount(ctx, (0, agent_query_1.parseRelationCountRequest)(rawBody), relationDeps);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function createDataRoutesMiddleware({ store, agentUrl, logger, createClient = agent_data_client_1.default, }) {
|
|
114
|
+
return async function dataRoutesMiddleware(ctx, next) {
|
|
115
|
+
const dataMatch = DATA_ROUTE.exec(ctx.path);
|
|
116
|
+
const relationMatch = dataMatch ? null : RELATION_ROUTE.exec(ctx.path);
|
|
117
|
+
const match = dataMatch ?? relationMatch;
|
|
118
|
+
if (!match || ctx.method !== 'POST') {
|
|
119
|
+
await next();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const collection = decodeSegment(match[1], 'collection name');
|
|
123
|
+
// The agent token is minted only on the API-key path; the OAuth path sets a principal but no
|
|
124
|
+
// agent token yet. Fail closed instead of calling the agent with `Bearer undefined`.
|
|
125
|
+
// TODO(PRD-637): mint an agent token from the OAuth principal so data routes work in OAuth mode.
|
|
126
|
+
const token = ctx.state.agentToken;
|
|
127
|
+
if (!token)
|
|
128
|
+
throw (0, bff_http_error_1.unauthorized)('No agent credentials for this request');
|
|
129
|
+
const readModel = await resolveReadModel(store);
|
|
130
|
+
if (!readModel.isCollectionAllowed(collection)) {
|
|
131
|
+
// TODO(PRD-671): the read-model exposes a single allow-list (the exposed collections), so it
|
|
132
|
+
// cannot tell an unknown collection from a known-but-disallowed one. Every absent name maps
|
|
133
|
+
// to 404 here; `collection_not_allowed` (403) has no local trigger until a distinct exposure
|
|
134
|
+
// source exists. Escalated on the ticket — revisit when Anthony answers.
|
|
135
|
+
throw (0, bff_local_errors_1.unknownCollection)(`Unknown collection: ${collection}`);
|
|
136
|
+
}
|
|
137
|
+
const deps = {
|
|
138
|
+
collection,
|
|
139
|
+
client: createClient({ agentUrl, token }),
|
|
140
|
+
timezone: ctx.state.timezone,
|
|
141
|
+
logger,
|
|
142
|
+
};
|
|
143
|
+
const rawBody = ctx.request.body ?? {};
|
|
144
|
+
if (relationMatch) {
|
|
145
|
+
await handleRelation(ctx, rawBody, relationMatch, { deps, readModel });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const operation = match[2];
|
|
149
|
+
if (operation === 'list') {
|
|
150
|
+
const body = (0, agent_query_1.parseListRequest)(rawBody);
|
|
151
|
+
await handleList(ctx, body, { ...deps, primaryKeys: readModel.getPrimaryKeys(collection) });
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
const body = (0, agent_query_1.parseCountRequest)(rawBody);
|
|
155
|
+
await handleCount(ctx, body, deps);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGF0YS1yb3V0ZXMtbWlkZGxld2FyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9kYXRhL2RhdGEtcm91dGVzLW1pZGRsZXdhcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFxTUEsNkNBMkRDO0FBblBELDRFQUErRDtBQUMvRCwrQ0FTdUI7QUFDdkIseURBQXVFO0FBQ3ZFLG1FQUEyRDtBQUMzRCwyREFBc0Q7QUFDdEQsK0RBS2tDO0FBQ2xDLGtFQUEwRDtBQUMxRCw4RkFBNEU7QUFFNUUsTUFBTSxVQUFVLEdBQUcsc0NBQXNDLENBQUM7QUFDMUQsTUFBTSxjQUFjLEdBQUcsMERBQTBELENBQUM7QUFFbEYsb0dBQW9HO0FBQ3BHLHFHQUFxRztBQUNyRyxTQUFTLHdCQUF3QixDQUFDLE1BQWtDO0lBQ2xFLElBQUksQ0FBQyxNQUFNLElBQUksQ0FBQyxDQUFDLFFBQVEsSUFBSSxNQUFNLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUNsRCxJQUFJLE1BQU0sQ0FBQyxJQUFJLEtBQUssU0FBUyxJQUFJLE1BQU0sQ0FBQyxJQUFJLEtBQUssZUFBZTtRQUFFLE9BQU8sSUFBSSxDQUFDO0lBRTlFLE9BQU8sTUFBTSxDQUFDLE1BQU0sQ0FBQztBQUN2QixDQUFDO0FBa0JELFNBQVMsYUFBYSxDQUFDLEdBQVcsRUFBRSxLQUFhO0lBQy9DLElBQUksQ0FBQztRQUNILE9BQU8sa0JBQWtCLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDakMsQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUNQLE1BQU0sSUFBQSxpQ0FBYyxFQUFDLGFBQWEsS0FBSyxVQUFVLENBQUMsQ0FBQztJQUNyRCxDQUFDO0FBQ0gsQ0FBQztBQUVELEtBQUssVUFBVSxnQkFBZ0IsQ0FBQyxLQUFxQjtJQUNuRCxJQUFJLENBQUM7UUFDSCxPQUFPLE1BQU0sS0FBSyxDQUFDLFlBQVksRUFBRSxDQUFDO0lBQ3BDLENBQUM7SUFBQyxPQUFPLEtBQUssRUFBRSxDQUFD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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,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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
|