@forestadmin/agent-bff 1.17.0 → 1.19.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/README.md +1 -1
- package/dist/action/action-form-mapper.d.ts +2 -1
- package/dist/action/action-form-mapper.js +1 -1
- package/dist/action/agent-action-client.d.ts +2 -1
- package/dist/action/agent-action-client.js +1 -1
- package/dist/cli-core.js +44 -2
- package/dist/docs/docs-page.d.ts +2 -0
- package/dist/docs/docs-page.js +171 -0
- package/dist/docs/docs-routes.d.ts +34 -0
- package/dist/docs/docs-routes.js +86 -0
- package/dist/docs/docs-samples.d.ts +25 -0
- package/dist/docs/docs-samples.js +329 -0
- package/dist/docs/docs-theme.d.ts +84 -0
- package/dist/docs/docs-theme.js +108 -0
- package/dist/docs/redoc.standalone.js +1838 -0
- package/dist/openapi/openapi-document.js +9 -2
- package/dist/openapi/unfolded-paths.js +8 -1
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @forestadmin/agent-bff
|
|
2
2
|
|
|
3
|
-
Standalone REST BFF (Backend-For-Frontend) that lets a trusted third-party UI call a Forest
|
|
3
|
+
Standalone REST BFF (Backend-For-Frontend) that lets a trusted third-party UI call a Forest
|
|
4
4
|
agent from a browser without learning MCP or JSON:API.
|
|
5
5
|
|
|
6
6
|
It is a bootable Koa 3 server with a `/health` endpoint, a version header, env-driven config
|
|
@@ -2,7 +2,8 @@ import type { ActionForm } from './agent-action-client';
|
|
|
2
2
|
import type { ForestServerActionFormLayoutElement } from '@forestadmin/forestadmin-client';
|
|
3
3
|
export interface ActionFormFieldResponse {
|
|
4
4
|
name: string;
|
|
5
|
-
type
|
|
5
|
+
/** Verbatim from the agent, so a list type is `['String']` rather than `'StringList'`. */
|
|
6
|
+
type: string | [string];
|
|
6
7
|
value: unknown;
|
|
7
8
|
isRequired: boolean;
|
|
8
9
|
enumValues?: string[] | null;
|
|
@@ -30,4 +30,4 @@ function mapActionForm(action, skippedFields, layout) {
|
|
|
30
30
|
layout,
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
33
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWN0aW9uLWZvcm0tbWFwcGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FjdGlvbi9hY3Rpb24tZm9ybS1tYXBwZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUF3QkEsc0NBaUNDO0FBdERELE1BQU0sU0FBUyxHQUFHLE1BQU0sQ0FBQztBQW1CekIsa0dBQWtHO0FBQ2xHLDhGQUE4RjtBQUM5RixTQUFnQixhQUFhLENBQzNCLE1BQWtCLEVBQ2xCLGFBQXVCLEVBQ3ZCLE1BQTZDO0lBRTdDLE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxTQUFTLEVBQUUsQ0FBQztJQUVsQyxNQUFNLGNBQWMsR0FBRyxNQUFNO1NBQzFCLE1BQU0sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxVQUFVLEVBQUUsQ0FBQztTQUNuQyxNQUFNLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsUUFBUSxFQUFFLEtBQUssU0FBUyxJQUFJLEtBQUssQ0FBQyxRQUFRLEVBQUUsS0FBSyxJQUFJLENBQUM7U0FDNUUsR0FBRyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsS0FBSyxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUM7SUFFakMsT0FBTztRQUNMLE1BQU0sRUFBRSxNQUFNLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxFQUFFO1lBQ3pCLE1BQU0sSUFBSSxHQUE0QjtnQkFDcEMsSUFBSSxFQUFFLEtBQUssQ0FBQyxPQUFPLEVBQUU7Z0JBQ3JCLElBQUksRUFBRSxLQUFLLENBQUMsT0FBTyxFQUFFO2dCQUNyQixLQUFLLEVBQUUsS0FBSyxDQUFDLFFBQVEsRUFBRTtnQkFDdkIsVUFBVSxFQUFFLEtBQUssQ0FBQyxVQUFVLEVBQUUsSUFBSSxLQUFLO2FBQ3hDLENBQUM7WUFFRixxRUFBcUU7WUFDckUsSUFBSSxLQUFLLENBQUMsT0FBTyxFQUFFLEtBQUssU0FBUyxFQUFFLENBQUM7Z0JBQ2xDLE9BQU8sRUFBRSxHQUFHLElBQUksRUFBRSxVQUFVLEVBQUUsTUFBTSxDQUFDLFlBQVksQ0FBQyxLQUFLLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxVQUFVLEVBQUUsSUFBSSxJQUFJLEVBQUUsQ0FBQztZQUM1RixDQUFDO1lBRUQsT0FBTyxJQUFJLENBQUM7UUFDZCxDQUFDLENBQUM7UUFDRixVQUFVLEVBQUUsY0FBYyxDQUFDLE1BQU0sS0FBSyxDQUFDO1FBQ3ZDLGNBQWM7UUFDZCxhQUFhO1FBQ2IsTUFBTTtLQUNQLENBQUM7QUFDSixDQUFDIn0=
|
|
@@ -2,7 +2,8 @@ import type { ActionEndpointsByCollection } from '@forestadmin/agent-client';
|
|
|
2
2
|
import type { ForestServerActionFormLayoutElement } from '@forestadmin/forestadmin-client';
|
|
3
3
|
export interface ActionFormField {
|
|
4
4
|
getName(): string;
|
|
5
|
-
|
|
5
|
+
/** A list type is the array the agent sent, `['String']`, not `'StringList'`. */
|
|
6
|
+
getType(): string | [string];
|
|
6
7
|
getValue(): unknown;
|
|
7
8
|
isRequired(): boolean | undefined;
|
|
8
9
|
}
|
|
@@ -32,4 +32,4 @@ function createAgentActionClient({ agentUrl, token, actionEndpoints, timeoutMs,
|
|
|
32
32
|
loadAction: (collection, action, recordIds) => client.collection(collection).action(action, { recordIds }),
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
35
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWdlbnQtYWN0aW9uLWNsaWVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hY3Rpb24vYWdlbnQtYWN0aW9uLWNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQStDQSw0Q0FJQztBQU9ELDBDQWlCQztBQXhFRCw0REFBb0U7QUFFcEUsdUdBQTRFO0FBcUM1RSxtR0FBbUc7QUFDbkcscUdBQXFHO0FBQ3JHLGdHQUFnRztBQUNoRyxrR0FBa0c7QUFDbEcsMEZBQTBGO0FBQzFGLFNBQWdCLGdCQUFnQixDQUFDLE1BQWtCO0lBQ2pELE1BQU0sSUFBSSxHQUFHLE1BQU0sQ0FBQyxTQUFTLEVBQXdELENBQUM7SUFFdEYsT0FBTyxJQUFJLENBQUMsTUFBTSxJQUFJLEVBQUUsQ0FBQztBQUMzQixDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILFNBQXdCLHVCQUF1QixDQUFDLEVBQzlDLFFBQVEsRUFDUixLQUFLLEVBQ0wsZUFBZSxFQUNmLFNBQVMsR0FDZ0I7SUFDekIsTUFBTSxNQUFNLEdBQUcsSUFBQSxzQ0FBdUIsRUFBQztRQUNyQyxHQUFHLEVBQUUsUUFBUTtRQUNiLEtBQUs7UUFDTCxlQUFlO1FBQ2YsYUFBYSxFQUFFLElBQUEscUNBQXdCLEVBQUMsS0FBSyxFQUFFLFFBQVEsRUFBRSxTQUFTLENBQUM7S0FDcEUsQ0FBQyxDQUFDO0lBRUgsT0FBTztRQUNMLFVBQVUsRUFBRSxDQUFDLFVBQVUsRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLEVBQUUsQ0FDNUMsTUFBTSxDQUFDLFVBQVUsQ0FBQyxVQUFVLENBQUMsQ0FBQyxNQUFNLENBQUMsTUFBTSxFQUFFLEVBQUUsU0FBUyxFQUFFLENBQUM7S0FDOUQsQ0FBQztBQUNKLENBQUMifQ==
|
package/dist/cli-core.js
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
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
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
@@ -19,6 +52,7 @@ const env_config_1 = require("./config/env-config");
|
|
|
19
52
|
const cors_middleware_1 = __importDefault(require("./cors/cors-middleware"));
|
|
20
53
|
const per_key_origin_1 = __importDefault(require("./cors/per-key-origin"));
|
|
21
54
|
const data_routes_middleware_1 = __importDefault(require("./data/data-routes-middleware"));
|
|
55
|
+
const docs_routes_1 = __importDefault(require("./docs/docs-routes"));
|
|
22
56
|
const errors_1 = require("./errors");
|
|
23
57
|
const bff_http_error_1 = require("./http/bff-http-error");
|
|
24
58
|
const bff_http_server_1 = __importDefault(require("./http/bff-http-server"));
|
|
@@ -28,7 +62,7 @@ const forest_server_client_1 = __importDefault(require("./oauth/forest-server-cl
|
|
|
28
62
|
const oauth_routes_1 = __importDefault(require("./oauth/oauth-routes"));
|
|
29
63
|
const session_store_1 = __importDefault(require("./oauth/session-store"));
|
|
30
64
|
const token_cipher_1 = __importDefault(require("./oauth/token-cipher"));
|
|
31
|
-
const openapi_routes_1 =
|
|
65
|
+
const openapi_routes_1 = __importStar(require("./openapi/openapi-routes"));
|
|
32
66
|
const permissions_cache_1 = __importDefault(require("./permissions/permissions-cache"));
|
|
33
67
|
const permissions_client_1 = __importDefault(require("./permissions/permissions-client"));
|
|
34
68
|
const permissions_routes_middleware_1 = __importDefault(require("./permissions/permissions-routes-middleware"));
|
|
@@ -223,6 +257,14 @@ async function runCli(env, logger = (0, console_logger_1.default)()) {
|
|
|
223
257
|
...agentErrorMiddleware,
|
|
224
258
|
(0, bodyparser_1.bodyParser)({ jsonLimit: body_limit_1.default }),
|
|
225
259
|
...oauthMiddlewares,
|
|
260
|
+
// Outside the agent-scoped chain on purpose: the viewer is a public page, the document it fetches
|
|
261
|
+
// is not. Gated on the edge being mounted too, like the error middleware above: with no agent
|
|
262
|
+
// chain there is no document to fetch, and the page would only ever reach a bare Koa 404.
|
|
263
|
+
(0, docs_routes_1.default)({
|
|
264
|
+
enabled: config.openapiEnabled && agentMiddlewares.length > 0,
|
|
265
|
+
documentPath: openapi_routes_1.OPENAPI_PATH,
|
|
266
|
+
logger,
|
|
267
|
+
}),
|
|
226
268
|
...agentMiddlewares,
|
|
227
269
|
];
|
|
228
270
|
const server = new bff_http_server_1.default({
|
|
@@ -239,4 +281,4 @@ function reportFatalError(err) {
|
|
|
239
281
|
process.stderr.write(`Error: ${(0, errors_1.extractErrorMessage)(err)}\n`);
|
|
240
282
|
process.exitCode = 1;
|
|
241
283
|
}
|
|
242
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
284
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLWNvcmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY2xpLWNvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUErTkEsa0RBRUM7QUFzRUQseUJBMENDO0FBRUQsNENBR0M7QUEvVUQsZ0RBQTZDO0FBRTdDLGlHQUE2RTtBQUM3RSwrRUFBNEQ7QUFDNUQsb0VBQTJEO0FBQzNELDRGQUF3RTtBQUN4RSw4RUFBb0Q7QUFDcEQsc0ZBQWtFO0FBQ2xFLDRFQUF5RDtBQUN6RCx1RkFBbUU7QUFDbkUsb0RBQWtEO0FBQ2xELDZFQUEwRDtBQUMxRCwyRUFBaUU7QUFDakUsMkZBQXVFO0FBQ3ZFLHFFQUFrRDtBQUNsRCxxQ0FBK0M7QUFDL0MsMERBQXFEO0FBQ3JELDZFQUFtRDtBQUNuRCxtRUFBMkM7QUFDM0MsK0VBQTREO0FBQzVELHdGQUE4RDtBQUM5RCx3RUFBcUQ7QUFDckQsMEVBQStEO0FBQy9ELHdFQUFxRDtBQUNyRCwyRUFBNkU7QUFDN0Usd0ZBQStEO0FBQy9ELDBGQUFpRTtBQUNqRSxnSEFBNEY7QUFDNUYsdUZBQTZEO0FBQzdELHlGQUFzRTtBQUN0RSx3REFBZ0M7QUFFaEMsTUFBTSxtQkFBbUIsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsQ0FBQztBQUV6QyxTQUFTLFdBQVcsQ0FBQyxJQUFZO0lBQy9CLE9BQU8sSUFBSSxLQUFLLFFBQVEsSUFBSSxJQUFJLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQ3pELENBQUM7QUFFRCxTQUFTLFdBQVcsQ0FBQyxVQUFzQjtJQUN6QyxPQUFPLEtBQUssVUFBVSxNQUFNLENBQUMsR0FBRyxFQUFFLElBQUk7UUFDcEMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztZQUMzQixNQUFNLElBQUksRUFBRSxDQUFDO1lBRWIsT0FBTztRQUNULENBQUM7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
|
|
@@ -0,0 +1,171 @@
|
|
|
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 = renderDocsPage;
|
|
7
|
+
/**
|
|
8
|
+
* The page is served WITHOUT credentials, so it must carry no schema: it is an empty shell that asks
|
|
9
|
+
* the caller for a BFF API key, fetches the document with it, and hands the parsed object to Redoc.
|
|
10
|
+
* That is the only design that is both openable in a browser — which sends no header when it
|
|
11
|
+
* navigates — and compatible with a document that is never reachable unauthenticated.
|
|
12
|
+
*
|
|
13
|
+
* The key is never persisted: it is read from the input, passed down as an argument, and the input is
|
|
14
|
+
* cleared. Once the document is fetched the page has no further use for it.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately NOT a `<form>`. A form with no `action` navigates to `/docs?key=<the key>` the moment
|
|
17
|
+
* its default submit is not prevented — a CSP that blocks this inline script is enough — which would
|
|
18
|
+
* put the key in the browser history and in every access log on the way. A form submit is also what
|
|
19
|
+
* Chrome reads as a login, and it then offers to save the key whatever `autocomplete` says. With no
|
|
20
|
+
* form there is no default action to prevent and no submit to observe: without this script the button
|
|
21
|
+
* does nothing at all.
|
|
22
|
+
*/
|
|
23
|
+
const docs_samples_1 = __importDefault(require("./docs-samples"));
|
|
24
|
+
const docs_theme_1 = require("./docs-theme");
|
|
25
|
+
/**
|
|
26
|
+
* `untrustedSpec` because the descriptions in the document come from the agent's own schema, which is
|
|
27
|
+
* customer-authored, and Redoc renders their markdown as HTML unsanitized otherwise.
|
|
28
|
+
*/
|
|
29
|
+
const REDOC_OPTIONS = { hideDownloadButton: true, untrustedSpec: true, theme: docs_theme_1.REDOC_THEME };
|
|
30
|
+
function renderDocsPage(documentPath, bundlePath) {
|
|
31
|
+
return `<!doctype html>
|
|
32
|
+
<html lang="en">
|
|
33
|
+
<head>
|
|
34
|
+
<meta charset="utf-8" />
|
|
35
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
36
|
+
<meta name="robots" content="noindex" />
|
|
37
|
+
<title>Forest BFF API</title>
|
|
38
|
+
<link rel="icon" href="data:image/svg+xml,${encodeURIComponent(docs_theme_1.FAVICON_SVG)}" />
|
|
39
|
+
<style>${docs_theme_1.PAGE_STYLES} </style>
|
|
40
|
+
</head>
|
|
41
|
+
<body>
|
|
42
|
+
<div id="unlock">
|
|
43
|
+
<strong>Forest<span>.</span></strong>
|
|
44
|
+
<label for="key">BFF API key</label>
|
|
45
|
+
<input id="key" type="password" autocomplete="off" spellcheck="false" />
|
|
46
|
+
<button id="load" type="button">Load the API document</button>
|
|
47
|
+
</div>
|
|
48
|
+
<div id="error"></div>
|
|
49
|
+
<div id="redoc"></div>
|
|
50
|
+
<script src="${bundlePath}"></script>
|
|
51
|
+
<script>
|
|
52
|
+
(function () {
|
|
53
|
+
var DOCUMENT_PATH = ${JSON.stringify(documentPath)};
|
|
54
|
+
var BUNDLE_PATH = ${JSON.stringify(bundlePath)};
|
|
55
|
+
var REDOC_OPTIONS = ${JSON.stringify(REDOC_OPTIONS)};
|
|
56
|
+
var unlock = document.getElementById('unlock');
|
|
57
|
+
var input = document.getElementById('key');
|
|
58
|
+
var button = document.getElementById('load');
|
|
59
|
+
var errorBox = document.getElementById('error');
|
|
60
|
+
var attempts = 0;
|
|
61
|
+
${docs_samples_1.default}
|
|
62
|
+
function show(message) {
|
|
63
|
+
errorBox.textContent = message;
|
|
64
|
+
errorBox.setAttribute('data-shown', '');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function hide() {
|
|
68
|
+
errorBox.removeAttribute('data-shown');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function describe(status, body) {
|
|
72
|
+
var error = body && body.error;
|
|
73
|
+
|
|
74
|
+
if (error && error.type) {
|
|
75
|
+
return 'The BFF answered ' + status + ' ' + error.type + ': ' + (error.message || '');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return 'The BFF answered ' + status + ': ' + JSON.stringify(body);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Kept out of the fetch chain: a throw from here is a viewer problem, and reporting it as
|
|
83
|
+
* "could not reach the document" would point the reader at the wrong thing.
|
|
84
|
+
*/
|
|
85
|
+
function render(spec) {
|
|
86
|
+
if (typeof Redoc === 'undefined') {
|
|
87
|
+
show('The Redoc viewer did not load from ' + BUNDLE_PATH + ', so the document cannot be rendered.');
|
|
88
|
+
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
unlock.style.display = 'none';
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
Redoc.init(withSamples(spec), REDOC_OPTIONS, document.getElementById('redoc'));
|
|
96
|
+
} catch (initError) {
|
|
97
|
+
unlock.style.display = '';
|
|
98
|
+
show('The Redoc viewer could not render the document: ' + initError);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Every completion is checked against \`attempt\`: two submissions in quick succession — a
|
|
104
|
+
* mistyped key corrected straight away — resolve in whatever order the network gives, and a
|
|
105
|
+
* late answer from the abandoned one would otherwise render its document or report its error
|
|
106
|
+
* over the current attempt's result.
|
|
107
|
+
*/
|
|
108
|
+
function load(key) {
|
|
109
|
+
hide();
|
|
110
|
+
|
|
111
|
+
var attempt = ++attempts;
|
|
112
|
+
var current = function () {
|
|
113
|
+
return attempt === attempts;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
fetch(DOCUMENT_PATH, {
|
|
117
|
+
cache: 'no-store',
|
|
118
|
+
headers: { 'X-Forest-Bff-Key': key },
|
|
119
|
+
})
|
|
120
|
+
.then(function (response) {
|
|
121
|
+
return response.text().then(function (text) {
|
|
122
|
+
try {
|
|
123
|
+
return { ok: response.ok, status: response.status, body: JSON.parse(text) };
|
|
124
|
+
} catch (parseError) {
|
|
125
|
+
// Never successful, whatever the status said: a body we cannot parse is not a
|
|
126
|
+
// document, and handing this placeholder to Redoc would hide why.
|
|
127
|
+
return {
|
|
128
|
+
ok: false,
|
|
129
|
+
status: response.status,
|
|
130
|
+
body: { error: { type: 'unreadable_response', message: text.slice(0, 200) } },
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
})
|
|
135
|
+
.then(function (result) {
|
|
136
|
+
if (!current()) return;
|
|
137
|
+
|
|
138
|
+
if (!result.ok) {
|
|
139
|
+
show(describe(result.status, result.body));
|
|
140
|
+
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
render(result.body);
|
|
145
|
+
})
|
|
146
|
+
.catch(function (fetchError) {
|
|
147
|
+
if (!current()) return;
|
|
148
|
+
|
|
149
|
+
show('Could not reach ' + DOCUMENT_PATH + ': ' + fetchError);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function unlockDocument() {
|
|
154
|
+
var key = input.value.trim();
|
|
155
|
+
input.value = '';
|
|
156
|
+
|
|
157
|
+
if (key) load(key);
|
|
158
|
+
else show('A BFF API key is required: the document is never served unauthenticated.');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
button.addEventListener('click', unlockDocument);
|
|
162
|
+
input.addEventListener('keydown', function (event) {
|
|
163
|
+
if (event.key === 'Enter') unlockDocument();
|
|
164
|
+
});
|
|
165
|
+
})();
|
|
166
|
+
</script>
|
|
167
|
+
</body>
|
|
168
|
+
</html>
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZG9jcy1wYWdlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2RvY3MvZG9jcy1wYWdlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBeUJBLGlDQTRJQztBQXJLRDs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFDSCxrRUFBNEM7QUFDNUMsNkNBQXFFO0FBRXJFOzs7R0FHRztBQUNILE1BQU0sYUFBYSxHQUFHLEVBQUUsa0JBQWtCLEVBQUUsSUFBSSxFQUFFLGFBQWEsRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLHdCQUFXLEVBQUUsQ0FBQztBQUU1RixTQUF3QixjQUFjLENBQUMsWUFBb0IsRUFBRSxVQUFrQjtJQUM3RSxPQUFPOzs7Ozs7O2dEQU91QyxrQkFBa0IsQ0FBQyx3QkFBVyxDQUFDO2FBQ2xFLHdCQUFXOzs7Ozs7Ozs7OzttQkFXTCxVQUFVOzs7OEJBR0MsSUFBSSxDQUFDLFNBQVMsQ0FBQyxZQUFZLENBQUM7NEJBQzlCLElBQUksQ0FBQyxTQUFTLENBQUMsVUFBVSxDQUFDOzhCQUN4QixJQUFJLENBQUMsU0FBUyxDQUFDLGFBQWEsQ0FBQzs7Ozs7O0VBTXpELHNCQUFjOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Q0E0R2YsQ0FBQztBQUNGLENBQUMifQ==
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Logger } from '../ports/logger-port';
|
|
2
|
+
import type { Middleware } from 'koa';
|
|
3
|
+
export declare const DOCS_PATH = "/docs";
|
|
4
|
+
export declare const DOCS_BUNDLE_PATH = "/docs/redoc.standalone.js";
|
|
5
|
+
export interface DocsRoutesOptions {
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
/** Where the shell fetches the document. Passed in so this module never reaches into `src/openapi`. */
|
|
8
|
+
documentPath: string;
|
|
9
|
+
logger: Logger;
|
|
10
|
+
/** The bundle lookup, as a seam: an install that shipped without the asset is a real state to serve. */
|
|
11
|
+
resolveBundlePath?: () => string | undefined;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The bundle is copied next to this module at build time (`build:copy`), which is what a published
|
|
15
|
+
* install serves. Running from `src` — tests, `build:watch` — there is nothing to copy to, so the
|
|
16
|
+
* `redoc` devDependency is resolved instead: the same file, from the package that pins its version.
|
|
17
|
+
*
|
|
18
|
+
* The directory is a parameter so both branches are reachable from a test. Running from `src` only
|
|
19
|
+
* ever takes the fallback, which would leave the branch a published install actually uses untested.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveBundle(directory?: string): string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Serves the Redoc viewer OUTSIDE `/agent`, deliberately: the agent prefix answers 401 to a request
|
|
24
|
+
* with no credential (`auth-mode.ts`), and a browser navigating to a page sends none. Both routes are
|
|
25
|
+
* public, and both are inert — the shell carries no schema and the bundle is a third-party asset.
|
|
26
|
+
* The document itself stays gated.
|
|
27
|
+
*
|
|
28
|
+
* Disabled, or unable to find its bundle, the middleware falls through rather than throwing: `/docs`
|
|
29
|
+
* is not covered by the agent-scoped error middleware, so a thrown error would surface as a bare 500
|
|
30
|
+
* instead of the BFF error contract. A 404 also keeps a disabled deployment from advertising a page
|
|
31
|
+
* it does not serve.
|
|
32
|
+
*/
|
|
33
|
+
export default function createDocsRoutes({ enabled, documentPath, logger, resolveBundlePath, }: DocsRoutesOptions): Middleware;
|
|
34
|
+
//# sourceMappingURL=docs-routes.d.ts.map
|
|
@@ -0,0 +1,86 @@
|
|
|
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.DOCS_BUNDLE_PATH = exports.DOCS_PATH = void 0;
|
|
7
|
+
exports.resolveBundle = resolveBundle;
|
|
8
|
+
exports.default = createDocsRoutes;
|
|
9
|
+
const fs_1 = require("fs");
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const docs_page_1 = __importDefault(require("./docs-page"));
|
|
12
|
+
exports.DOCS_PATH = '/docs';
|
|
13
|
+
exports.DOCS_BUNDLE_PATH = '/docs/redoc.standalone.js';
|
|
14
|
+
const BUNDLE_FILE = 'redoc.standalone.js';
|
|
15
|
+
const READ_METHODS = new Set(['GET', 'HEAD']);
|
|
16
|
+
/**
|
|
17
|
+
* The bundle is copied next to this module at build time (`build:copy`), which is what a published
|
|
18
|
+
* install serves. Running from `src` — tests, `build:watch` — there is nothing to copy to, so the
|
|
19
|
+
* `redoc` devDependency is resolved instead: the same file, from the package that pins its version.
|
|
20
|
+
*
|
|
21
|
+
* The directory is a parameter so both branches are reachable from a test. Running from `src` only
|
|
22
|
+
* ever takes the fallback, which would leave the branch a published install actually uses untested.
|
|
23
|
+
*/
|
|
24
|
+
function resolveBundle(directory = __dirname) {
|
|
25
|
+
const copied = path_1.default.join(directory, BUNDLE_FILE);
|
|
26
|
+
if ((0, fs_1.existsSync)(copied))
|
|
27
|
+
return copied;
|
|
28
|
+
try {
|
|
29
|
+
return require.resolve(`redoc/bundles/${BUNDLE_FILE}`);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
/* istanbul ignore next — `redoc` is a devDependency of this package, so the lookup only fails in
|
|
33
|
+
a published install whose `build:copy` did not run. */
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Serves the Redoc viewer OUTSIDE `/agent`, deliberately: the agent prefix answers 401 to a request
|
|
39
|
+
* with no credential (`auth-mode.ts`), and a browser navigating to a page sends none. Both routes are
|
|
40
|
+
* public, and both are inert — the shell carries no schema and the bundle is a third-party asset.
|
|
41
|
+
* The document itself stays gated.
|
|
42
|
+
*
|
|
43
|
+
* Disabled, or unable to find its bundle, the middleware falls through rather than throwing: `/docs`
|
|
44
|
+
* is not covered by the agent-scoped error middleware, so a thrown error would surface as a bare 500
|
|
45
|
+
* instead of the BFF error contract. A 404 also keeps a disabled deployment from advertising a page
|
|
46
|
+
* it does not serve.
|
|
47
|
+
*/
|
|
48
|
+
function createDocsRoutes({ enabled, documentPath, logger, resolveBundlePath = resolveBundle, }) {
|
|
49
|
+
const bundle = enabled ? resolveBundlePath() : undefined;
|
|
50
|
+
if (enabled && !bundle) {
|
|
51
|
+
logger('Warn', `API documentation page disabled: ${BUNDLE_FILE} is missing from this install`);
|
|
52
|
+
}
|
|
53
|
+
const page = bundle ? (0, docs_page_1.default)(documentPath, exports.DOCS_BUNDLE_PATH) : undefined;
|
|
54
|
+
let script;
|
|
55
|
+
return async function docsRoutes(ctx, next) {
|
|
56
|
+
const isDocsPath = ctx.path === exports.DOCS_PATH || ctx.path === exports.DOCS_BUNDLE_PATH;
|
|
57
|
+
if (!bundle || !isDocsPath || !READ_METHODS.has(ctx.method)) {
|
|
58
|
+
await next();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (ctx.path === exports.DOCS_BUNDLE_PATH) {
|
|
62
|
+
// Read once and kept in memory: ~1 MB, served on every page load. A file that resolved at boot
|
|
63
|
+
// and is unreadable now falls through like a missing one: no error middleware covers this path.
|
|
64
|
+
if (script === undefined) {
|
|
65
|
+
try {
|
|
66
|
+
script = (0, fs_1.readFileSync)(bundle, 'utf8');
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
logger('Warn', `API documentation bundle unreadable: ${bundle}`, { error });
|
|
70
|
+
await next();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
ctx.status = 200;
|
|
75
|
+
ctx.type = 'application/javascript';
|
|
76
|
+
ctx.set('Cache-Control', 'public, max-age=3600');
|
|
77
|
+
ctx.body = script;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
ctx.status = 200;
|
|
81
|
+
ctx.type = 'text/html';
|
|
82
|
+
ctx.set('Cache-Control', 'no-store');
|
|
83
|
+
ctx.body = page;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZG9jcy1yb3V0ZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZG9jcy9kb2NzLXJvdXRlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUErQkEsc0NBWUM7QUFhRCxtQ0FvREM7QUF6R0QsMkJBQThDO0FBQzlDLGdEQUF3QjtBQUV4Qiw0REFBeUM7QUFFNUIsUUFBQSxTQUFTLEdBQUcsT0FBTyxDQUFDO0FBQ3BCLFFBQUEsZ0JBQWdCLEdBQUcsMkJBQTJCLENBQUM7QUFFNUQsTUFBTSxXQUFXLEdBQUcscUJBQXFCLENBQUM7QUFDMUMsTUFBTSxZQUFZLEdBQUcsSUFBSSxHQUFHLENBQUMsQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQztBQVc5Qzs7Ozs7OztHQU9HO0FBQ0gsU0FBZ0IsYUFBYSxDQUFDLFlBQW9CLFNBQVM7SUFDekQsTUFBTSxNQUFNLEdBQUcsY0FBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsV0FBVyxDQUFDLENBQUM7SUFFakQsSUFBSSxJQUFBLGVBQVUsRUFBQyxNQUFNLENBQUM7UUFBRSxPQUFPLE1BQU0sQ0FBQztJQUV0QyxJQUFJLENBQUM7UUFDSCxPQUFPLE9BQU8sQ0FBQyxPQUFPLENBQUMsaUJBQWlCLFdBQVcsRUFBRSxDQUFDLENBQUM7SUFDekQsQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUNQO2lFQUN5RDtRQUN6RCxPQUFPLFNBQVMsQ0FBQztJQUNuQixDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7Ozs7Ozs7O0dBVUc7QUFDSCxTQUF3QixnQkFBZ0IsQ0FBQyxFQUN2QyxPQUFPLEVBQ1AsWUFBWSxFQUNaLE1BQU0sRUFDTixpQkFBaUIsR0FBRyxhQUFhLEdBQ2Y7SUFDbEIsTUFBTSxNQUFNLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxpQkFBaUIsRUFBRSxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7SUFFekQsSUFBSSxPQUFPLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUN2QixNQUFNLENBQUMsTUFBTSxFQUFFLG9DQUFvQyxXQUFXLCtCQUErQixDQUFDLENBQUM7SUFDakcsQ0FBQztJQUVELE1BQU0sSUFBSSxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBQSxtQkFBYyxFQUFDLFlBQVksRUFBRSx3QkFBZ0IsQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7SUFDakYsSUFBSSxNQUEwQixDQUFDO0lBRS9CLE9BQU8sS0FBSyxVQUFVLFVBQVUsQ0FBQyxHQUFHLEVBQUUsSUFBSTtRQUN4QyxNQUFNLFVBQVUsR0FBRyxHQUFHLENBQUMsSUFBSSxLQUFLLGlCQUFTLElBQUksR0FBRyxDQUFDLElBQUksS0FBSyx3QkFBZ0IsQ0FBQztRQUUzRSxJQUFJLENBQUMsTUFBTSxJQUFJLENBQUMsVUFBVSxJQUFJLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQztZQUM1RCxNQUFNLElBQUksRUFBRSxDQUFDO1lBRWIsT0FBTztRQUNULENBQUM7UUFFRCxJQUFJLEdBQUcsQ0FBQyxJQUFJLEtBQUssd0JBQWdCLEVBQUUsQ0FBQztZQUNsQywrRkFBK0Y7WUFDL0YsZ0dBQWdHO1lBQ2hHLElBQUksTUFBTSxLQUFLLFNBQVMsRUFBRSxDQUFDO2dCQUN6QixJQUFJLENBQUM7b0JBQ0gsTUFBTSxHQUFHLElBQUEsaUJBQVksRUFBQyxNQUFNLEVBQUUsTUFBTSxDQUFDLENBQUM7Z0JBQ3hDLENBQUM7Z0JBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztvQkFDZixNQUFNLENBQUMsTUFBTSxFQUFFLHdDQUF3QyxNQUFNLEVBQUUsRUFBRSxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUM7b0JBRTVFLE1BQU0sSUFBSSxFQUFFLENBQUM7b0JBRWIsT0FBTztnQkFDVCxDQUFDO1lBQ0gsQ0FBQztZQUVELEdBQUcsQ0FBQyxNQUFNLEdBQUcsR0FBRyxDQUFDO1lBQ2pCLEdBQUcsQ0FBQyxJQUFJLEdBQUcsd0JBQXdCLENBQUM7WUFDcEMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxlQUFlLEVBQUUsc0JBQXNCLENBQUMsQ0FBQztZQUNqRCxHQUFHLENBQUMsSUFBSSxHQUFHLE1BQU0sQ0FBQztZQUVsQixPQUFPO1FBQ1QsQ0FBQztRQUVELEdBQUcsQ0FBQyxNQUFNLEdBQUcsR0FBRyxDQUFDO1FBQ2pCLEdBQUcsQ0FBQyxJQUFJLEdBQUcsV0FBVyxDQUFDO1FBQ3ZCLEdBQUcsQ0FBQyxHQUFHLENBQUMsZUFBZSxFQUFFLFVBQVUsQ0FBQyxDQUFDO1FBQ3JDLEdBQUcsQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO0lBQ2xCLENBQUMsQ0FBQztBQUNKLENBQUMifQ==
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser source, injected into the page: it decorates the fetched document with `x-codeSamples`,
|
|
3
|
+
* which Redoc renders as one tab per language, then hands it to `Redoc.init`.
|
|
4
|
+
*
|
|
5
|
+
* In the page rather than in the document, deliberately. Three samples per operation weigh ~73 KB on
|
|
6
|
+
* a 16-collection schema and several hundred KB on a large one, which every consumer of
|
|
7
|
+
* `/agent/openapi.json` would pay for an extension only a viewer reads — where this costs one
|
|
8
|
+
* function whatever the operation count. It also lets a sample carry the REAL origin: the document
|
|
9
|
+
* declares `servers: [{ url: '/' }]`, so a sample built into it could only hold a placeholder host,
|
|
10
|
+
* while the page knows where it is served from and emits a command that runs as pasted.
|
|
11
|
+
*
|
|
12
|
+
* The key is never inlined: each language reads it from the environment, so a copied sample cannot
|
|
13
|
+
* carry a credential into a shell history or a paste.
|
|
14
|
+
*
|
|
15
|
+
* Everything else is read from the document rather than assumed: the auth header comes from the
|
|
16
|
+
* security scheme the operation names, and the body carries exactly the properties its request
|
|
17
|
+
* schema makes required — `parentId` for a relation, `recordIds` for an action, nothing at all for a
|
|
18
|
+
* list, whose body is optional. The one header that cannot be read off an operation is the timezone,
|
|
19
|
+
* a component-level parameter reference; resolving it would buy nothing, since omitting it is a 400
|
|
20
|
+
* (`resolveTimezone` throws `missing_timezone` when header, body field and deployment default are
|
|
21
|
+
* all absent) and that is precisely what a hand-written sample forgets.
|
|
22
|
+
*/
|
|
23
|
+
declare const SAMPLES_SCRIPT: string;
|
|
24
|
+
export default SAMPLES_SCRIPT;
|
|
25
|
+
//# sourceMappingURL=docs-samples.d.ts.map
|