@forestadmin/agent-bff 1.31.0 → 1.32.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.
Files changed (38) hide show
  1. package/dist/action/action-routes-middleware.d.ts +3 -1
  2. package/dist/action/action-routes-middleware.js +48 -13
  3. package/dist/activity-log/activity-log-drainer.d.ts +24 -0
  4. package/dist/activity-log/activity-log-drainer.js +63 -0
  5. package/dist/activity-log/activity-log-writer.d.ts +26 -0
  6. package/dist/activity-log/activity-log-writer.js +23 -0
  7. package/dist/activity-log/activity-logs-creator.d.ts +42 -0
  8. package/dist/activity-log/activity-logs-creator.js +209 -0
  9. package/dist/activity-log/activity-logs-service.d.ts +18 -0
  10. package/dist/activity-log/activity-logs-service.js +19 -0
  11. package/dist/activity-log/with-activity-log.d.ts +25 -0
  12. package/dist/activity-log/with-activity-log.js +63 -0
  13. package/dist/api-key/api-key-authenticator.d.ts +11 -0
  14. package/dist/api-key/api-key-authenticator.js +12 -2
  15. package/dist/api-key/api-key-client.d.ts +6 -0
  16. package/dist/api-key/api-key-client.js +2 -1
  17. package/dist/api-key/api-key-middleware.d.ts +10 -1
  18. package/dist/api-key/api-key-middleware.js +18 -1
  19. package/dist/api-key/resolve-cache.d.ts +12 -0
  20. package/dist/api-key/resolve-cache.js +38 -6
  21. package/dist/auth/auth-mode.d.ts +1 -0
  22. package/dist/auth/auth-mode.js +10 -3
  23. package/dist/auth/forest-server-token-middleware.d.ts +25 -0
  24. package/dist/auth/forest-server-token-middleware.js +90 -0
  25. package/dist/build-bff.d.ts +8 -0
  26. package/dist/build-bff.js +29 -10
  27. package/dist/cli-core.d.ts +9 -0
  28. package/dist/cli-core.js +38 -3
  29. package/dist/data/agent-query.d.ts +12 -0
  30. package/dist/data/agent-query.js +19 -1
  31. package/dist/data/data-routes-middleware.d.ts +3 -1
  32. package/dist/data/data-routes-middleware.js +41 -4
  33. package/dist/http/bff-http-server.d.ts +22 -0
  34. package/dist/http/bff-http-server.js +50 -5
  35. package/dist/http/bff-local-errors.d.ts +15 -0
  36. package/dist/http/bff-local-errors.js +29 -3
  37. package/dist/openapi/openapi-document.js +3 -3
  38. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import type { AgentActionClient, AgentActionClientOptions } from './agent-action-client';
2
+ import type { ActivityLogWriter } from '../activity-log/activity-log-writer';
2
3
  import type { AgentTransport } from '../agent/agent-transport';
3
4
  import type { Logger } from '../ports/logger-port';
4
5
  import type ReadModelStore from '../read-model/read-model-store';
@@ -7,7 +8,8 @@ export interface ActionRoutesMiddlewareOptions {
7
8
  store: ReadModelStore;
8
9
  transport: AgentTransport;
9
10
  logger: Logger;
11
+ activityLogs: ActivityLogWriter;
10
12
  createClient?: (options: AgentActionClientOptions) => AgentActionClient;
11
13
  }
12
- export default function createActionRoutesMiddleware({ store, transport, logger, createClient, }: ActionRoutesMiddlewareOptions): Middleware;
14
+ export default function createActionRoutesMiddleware({ store, transport, logger, activityLogs, createClient, }: ActionRoutesMiddlewareOptions): Middleware;
13
15
  //# sourceMappingURL=action-routes-middleware.d.ts.map
@@ -45,8 +45,19 @@ const agent_action_client_1 = __importStar(require("./agent-action-client"));
45
45
  const sanitize_action_html_1 = __importDefault(require("./sanitize-action-html"));
46
46
  const agent_error_mapper_1 = require("../http/agent-error-mapper");
47
47
  const agent_route_helpers_1 = require("../http/agent-route-helpers");
48
+ const bff_http_error_1 = require("../http/bff-http-error");
48
49
  const bff_local_errors_1 = require("../http/bff-local-errors");
49
50
  const ACTION_ROUTE = /^\/agent\/v1\/([^/]+)\/actions\/([^/]+)\/(form|execute)$/;
51
+ const EXECUTE_VERB = 'execute';
52
+ /**
53
+ * An approval request is a business outcome, not a failure: the action was routed for review. The
54
+ * BFF answers it with a 403, but recording the entry as `failed` would make the same event count
55
+ * differently here and in mcp-server, which records it as a success — and action-failure statistics
56
+ * would mix refusals with runs that never happened.
57
+ */
58
+ function isApprovalRequest(error) {
59
+ return error instanceof bff_http_error_1.BffHttpError && error.type === bff_local_errors_1.ACTION_REQUIRES_APPROVAL_TYPE;
60
+ }
50
61
  // recordIds are opaque BFF ids; only presence and array-ness are checked. An empty array is allowed
51
62
  // on purpose (global/bulk actions have no selected records); the spec AC only covers a missing
52
63
  // recordIds, so `[]` is an explicit BFF choice, not a spec requirement.
@@ -148,7 +159,7 @@ async function handleExecute({ ctx, action, values, logger, }) {
148
159
  ctx.status = status;
149
160
  ctx.body = body;
150
161
  }
151
- function createActionRoutesMiddleware({ store, transport, logger, createClient = agent_action_client_1.default, }) {
162
+ function createActionRoutesMiddleware({ store, transport, logger, activityLogs, createClient = agent_action_client_1.default, }) {
152
163
  return async function actionRoutesMiddleware(ctx, next) {
153
164
  const match = ACTION_ROUTE.exec(ctx.path);
154
165
  if (!match || ctx.method !== 'POST') {
@@ -162,12 +173,15 @@ function createActionRoutesMiddleware({ store, transport, logger, createClient =
162
173
  const readModel = await (0, agent_route_helpers_1.resolveReadModel)(store);
163
174
  // The read-model's action map IS the allow-list, so an absent action cannot be told from a
164
175
  // known-but-disallowed one — every non-exposed action maps to 404 here; `action_not_allowed`
165
- // (403) has no local trigger, mirroring `collection_not_allowed`/`relation_not_allowed`. The
166
- // URL identity is resolved before the body, so a bad action 404s before its payload is read.
176
+ // (403) has no local trigger, mirroring `collection_not_allowed`/`relation_not_allowed`.
167
177
  // TODO(PRD-673): distinguish disallowed from unknown when a separate exposure source exists.
168
- if (!readModel.isActionAllowed(collection, actionName)) {
178
+ const allowed = readModel.isActionAllowed(collection, actionName);
179
+ const refuseUnknownAction = () => {
169
180
  throw (0, bff_local_errors_1.unknownAction)(`Unknown action: ${collection}.${actionName}`);
170
- }
181
+ };
182
+ // The form is unaudited, so it keeps refusing before the body is read.
183
+ if (!allowed && verb !== EXECUTE_VERB)
184
+ refuseUnknownAction();
171
185
  const body = (ctx.request.body ?? {});
172
186
  assertKnownBodyKeys(body);
173
187
  const recordIds = parseRecordIds(body.recordIds);
@@ -177,19 +191,40 @@ function createActionRoutesMiddleware({ store, transport, logger, createClient =
177
191
  token,
178
192
  actionEndpoints: readModel.getActionEndpoints(),
179
193
  });
180
- const action = await (0, agent_route_helpers_1.callAgent)(() => client.loadAction({
194
+ const loadAction = () => (0, agent_route_helpers_1.callAgent)(() => client.loadAction({
181
195
  collection,
182
196
  actionName,
183
197
  recordIds,
184
198
  timezone: ctx.state.timezone,
185
199
  }), logger);
186
- const handlerArgs = { ctx, action, values, logger };
187
- if (verb === 'execute') {
188
- await handleExecute(handlerArgs);
189
- }
190
- else {
191
- await handleForm(handlerArgs);
200
+ // The form is not audited, mirroring mcp-server, whose get-action-form tool writes no log
201
+ // either: the record-touching event the trail records is the execution.
202
+ if (verb !== EXECUTE_VERB) {
203
+ const action = await loadAction();
204
+ await handleForm({ ctx, action, values, logger });
205
+ return;
192
206
  }
207
+ // The whole sequence is audited, loadAction and setFields included, so the intent is recorded
208
+ // even when the attempt never reaches the agent's execute. The allow-list refusal is inside
209
+ // too, mirroring mcp-server, whose execute-action tool resolves the action within its own
210
+ // wrapper: an attempt on an action the caller may not trigger is exactly what the trail is
211
+ // for, and the record ids it names are only known once the body is read.
212
+ await activityLogs.record({
213
+ ctx,
214
+ action: 'action',
215
+ context: {
216
+ collectionName: collection,
217
+ recordIds,
218
+ label: `triggered the action "${actionName}"`,
219
+ },
220
+ isCompletedDespite: isApprovalRequest,
221
+ operation: async () => {
222
+ if (!allowed)
223
+ refuseUnknownAction();
224
+ const action = await loadAction();
225
+ await handleExecute({ ctx, action, values, logger });
226
+ },
227
+ });
193
228
  };
194
229
  }
195
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWN0aW9uLXJvdXRlcy1taWRkbGV3YXJlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FjdGlvbi9hY3Rpb24tcm91dGVzLW1pZGRsZXdhcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUE0TEEsK0NBNERDO0FBN09ELDREQUttQztBQUVuQyxtRUFBaUU7QUFDakUsNkRBQXFEO0FBQ3JELHdGQUFxRTtBQUNyRSw2RUFBeUY7QUFDekYsa0ZBQXdEO0FBQ3hELG1FQUEyRDtBQUMzRCxxRUFLcUM7QUFDckMsK0RBS2tDO0FBRWxDLE1BQU0sWUFBWSxHQUFHLDBEQUEwRCxDQUFDO0FBT2hGLG9HQUFvRztBQUNwRywrRkFBK0Y7QUFDL0Ysd0VBQXdFO0FBQ3hFLHFHQUFxRztBQUNyRyxxR0FBcUc7QUFDckcsU0FBUyxjQUFjLENBQUMsR0FBWTtJQUNsQyxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQ3hCLE1BQU0sSUFBQSxpQ0FBYyxFQUFDLDRDQUE0QyxDQUFDLENBQUM7SUFDckUsQ0FBQztJQUVELE9BQU8sR0FBRyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ25DLENBQUM7QUFFRCxNQUFNLGdCQUFnQixHQUFHLENBQUMsV0FBVyxFQUFFLFFBQVEsRUFBRSxVQUFVLENBQUMsQ0FBQztBQUU3RCxnR0FBZ0c7QUFDaEcsMEVBQTBFO0FBQzFFLFNBQVMsbUJBQW1CLENBQUMsSUFBNkI7SUFDeEQsTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBRTdFLElB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
230
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWN0aW9uLXJvdXRlcy1taWRkbGV3YXJlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FjdGlvbi9hY3Rpb24tcm91dGVzLW1pZGRsZXdhcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUE0TUEsK0NBMkZDO0FBM1JELDREQUttQztBQUVuQyxtRUFBaUU7QUFDakUsNkRBQXFEO0FBQ3JELHdGQUFxRTtBQUNyRSw2RUFBeUY7QUFDekYsa0ZBQXdEO0FBQ3hELG1FQUEyRDtBQUMzRCxxRUFLcUM7QUFDckMsMkRBQXNEO0FBQ3RELCtEQU1rQztBQUVsQyxNQUFNLFlBQVksR0FBRywwREFBMEQsQ0FBQztBQUVoRixNQUFNLFlBQVksR0FBRyxTQUFTLENBQUM7QUFFL0I7Ozs7O0dBS0c7QUFDSCxTQUFTLGlCQUFpQixDQUFDLEtBQWM7SUFDdkMsT0FBTyxLQUFLLFlBQVksNkJBQVksSUFBSSxLQUFLLENBQUMsSUFBSSxLQUFLLGdEQUE2QixDQUFDO0FBQ3ZGLENBQUM7QUFPRCxvR0FBb0c7QUFDcEcsK0ZBQStGO0FBQy9GLHdFQUF3RTtBQUN4RSxxR0FBcUc7QUFDckcscUdBQXFHO0FBQ3JHLFNBQVMsY0FBYyxDQUFDLEdBQVk7SUFDbEMsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUN4QixNQUFNLElBQUEsaUNBQWMsRUFBQyw0Q0FBNEMsQ0FBQyxDQUFDO0lBQ3JFLENBQUM7SUFFRCxPQUFPLEdBQUcsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztBQUNuQyxDQUFDO0FBRUQsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLFdBQVcsRUFBRSxRQUFRLEVBQUUsVUFBVSxDQUFDLENBQUM7QUFFN0QsZ0dBQWdHO0FBQ2hHLDBFQUEwRTtBQUMxRSxTQUFT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
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Holds the audited requests and the status transitions they fire without `await`. Nothing else
3
+ * keeps the transitions alive: `server.close()` waits for connections, and one sent after the
4
+ * response is attached to none — without this, every deploy would leave entries stuck in `pending`.
5
+ *
6
+ * The requests are tracked too, and not only their transitions, for the embedded deployment: there
7
+ * the host owns the connections, so `stop()` returns while requests are still running and their
8
+ * transitions are not registered yet.
9
+ */
10
+ export default class ActivityLogDrainer {
11
+ private readonly inFlight;
12
+ track<T>(operation: () => Promise<T>, description: string): Promise<T>;
13
+ /**
14
+ * Loops rather than settling one snapshot: a transition is registered only once the request it
15
+ * audits has finished, so a single pass would return before the work that outlives it.
16
+ *
17
+ * `timeoutMs` is the shutdown deadline the caller shares: a stalled audit store would otherwise
18
+ * hold the process past the grace its orchestrator gives it, and be SIGKILLed mid-drain. Returns
19
+ * what the deadline left unfinished, empty when everything settled.
20
+ */
21
+ drain(timeoutMs?: number): Promise<string[]>;
22
+ private settle;
23
+ }
24
+ //# sourceMappingURL=activity-log-drainer.d.ts.map
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * Holds the audited requests and the status transitions they fire without `await`. Nothing else
5
+ * keeps the transitions alive: `server.close()` waits for connections, and one sent after the
6
+ * response is attached to none — without this, every deploy would leave entries stuck in `pending`.
7
+ *
8
+ * The requests are tracked too, and not only their transitions, for the embedded deployment: there
9
+ * the host owns the connections, so `stop()` returns while requests are still running and their
10
+ * transitions are not registered yet.
11
+ */
12
+ class ActivityLogDrainer {
13
+ constructor() {
14
+ this.inFlight = new Set();
15
+ }
16
+ track(operation, description) {
17
+ const promise = operation();
18
+ const entry = { promise, description };
19
+ this.inFlight.add(entry);
20
+ promise.finally(() => this.inFlight.delete(entry)).catch(() => { });
21
+ return promise;
22
+ }
23
+ /**
24
+ * Loops rather than settling one snapshot: a transition is registered only once the request it
25
+ * audits has finished, so a single pass would return before the work that outlives it.
26
+ *
27
+ * `timeoutMs` is the shutdown deadline the caller shares: a stalled audit store would otherwise
28
+ * hold the process past the grace its orchestrator gives it, and be SIGKILLed mid-drain. Returns
29
+ * what the deadline left unfinished, empty when everything settled.
30
+ */
31
+ async drain(timeoutMs) {
32
+ const deadline = timeoutMs === undefined ? undefined : Date.now() + timeoutMs;
33
+ while (this.inFlight.size > 0) {
34
+ const remainingMs = deadline === undefined ? undefined : deadline - Date.now();
35
+ if (remainingMs !== undefined && remainingMs <= 0)
36
+ break;
37
+ // eslint-disable-next-line no-await-in-loop
38
+ await this.settle(remainingMs);
39
+ }
40
+ return [...this.inFlight].map(entry => entry.description);
41
+ }
42
+ async settle(timeoutMs) {
43
+ const settled = Promise.allSettled([...this.inFlight].map(entry => entry.promise));
44
+ if (timeoutMs === undefined) {
45
+ await settled;
46
+ return;
47
+ }
48
+ let timer;
49
+ try {
50
+ await Promise.race([
51
+ settled,
52
+ new Promise(resolve => {
53
+ timer = setTimeout(resolve, timeoutMs);
54
+ }),
55
+ ]);
56
+ }
57
+ finally {
58
+ clearTimeout(timer);
59
+ }
60
+ }
61
+ }
62
+ exports.default = ActivityLogDrainer;
63
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWN0aXZpdHktbG9nLWRyYWluZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYWN0aXZpdHktbG9nL2FjdGl2aXR5LWxvZy1kcmFpbmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBTUE7Ozs7Ozs7O0dBUUc7QUFDSCxNQUFxQixrQkFBa0I7SUFBdkM7UUFDbUIsYUFBUSxHQUFHLElBQUksR0FBRyxFQUFxQixDQUFDO0lBd0QzRCxDQUFDO0lBdERDLEtBQUssQ0FBSSxTQUEyQixFQUFFLFdBQW1CO1FBQ3ZELE1BQU0sT0FBTyxHQUFHLFNBQVMsRUFBRSxDQUFDO1FBQzVCLE1BQU0sS0FBSyxHQUFzQixFQUFFLE9BQU8sRUFBRSxXQUFXLEVBQUUsQ0FBQztRQUMxRCxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN6QixPQUFPLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRSxHQUFFLENBQUMsQ0FBQyxDQUFDO1FBRW5FLE9BQU8sT0FBTyxDQUFDO0lBQ2pCLENBQUM7SUFFRDs7Ozs7OztPQU9HO0lBQ0gsS0FBSyxDQUFDLEtBQUssQ0FBQyxTQUFrQjtRQUM1QixNQUFNLFFBQVEsR0FBRyxTQUFTLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxTQUFTLENBQUM7UUFFOUUsT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUM5QixNQUFNLFdBQVcsR0FBRyxRQUFRLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7WUFFL0UsSUFBSSxXQUFXLEtBQUssU0FBUyxJQUFJLFdBQVcsSUFBSSxDQUFDO2dCQUFFLE1BQU07WUFFekQsNENBQTRDO1lBQzVDLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUNqQyxDQUFDO1FBRUQsT0FBTyxDQUFDLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxXQUFXLENBQUMsQ0FBQztJQUM1RCxDQUFDO0lBRU8sS0FBSyxDQUFDLE1BQU0sQ0FBQyxTQUFrQjtRQUNyQyxNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsVUFBVSxDQUFDLENBQUMsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7UUFFbkYsSUFBSSxTQUFTLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDNUIsTUFBTSxPQUFPLENBQUM7WUFFZCxPQUFPO1FBQ1QsQ0FBQztRQUVELElBQUksS0FBaUMsQ0FBQztRQUV0QyxJQUFJLENBQUM7WUFDSCxNQUFNLE9BQU8sQ0FBQyxJQUFJLENBQUM7Z0JBQ2pCLE9BQU87Z0JBQ1AsSUFBSSxPQUFPLENBQU8sT0FBTyxDQUFDLEVBQUU7b0JBQzFCLEtBQUssR0FBRyxVQUFVLENBQUMsT0FBTyxFQUFFLFNBQVMsQ0FBQyxDQUFDO2dCQUN6QyxDQUFDLENBQUM7YUFDSCxDQUFDLENBQUM7UUFDTCxDQUFDO2dCQUFTLENBQUM7WUFDVCxZQUFZLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDdEIsQ0FBQztJQUNILENBQUM7Q0FDRjtBQXpERCxxQ0F5REMifQ==
@@ -0,0 +1,26 @@
1
+ import type { ActivityLogContext, BffActivityLogAction } from './activity-logs-creator';
2
+ import type { ActivityLogsWriter } from './activity-logs-service';
3
+ import type { Logger } from '../ports/logger-port';
4
+ import type { Context } from 'koa';
5
+ export interface RecordActivityLogOptions<T> {
6
+ ctx: Context;
7
+ action: BffActivityLogAction;
8
+ context?: ActivityLogContext;
9
+ operation: () => Promise<T>;
10
+ isCompletedDespite?: (error: unknown) => boolean;
11
+ }
12
+ export interface ActivityLogWriter {
13
+ record<T>(options: RecordActivityLogOptions<T>): Promise<T>;
14
+ /**
15
+ * Waits for the audited requests still running and for the status transitions no connection
16
+ * holds. Called when the server stops, which shares its deadline through `timeoutMs`; returns
17
+ * the operations that deadline left unfinished.
18
+ */
19
+ drain(timeoutMs?: number): Promise<string[]>;
20
+ }
21
+ export interface ActivityLogWriterOptions {
22
+ service: ActivityLogsWriter;
23
+ logger: Logger;
24
+ }
25
+ export default function createActivityLogWriter({ service, logger, }: ActivityLogWriterOptions): ActivityLogWriter;
26
+ //# sourceMappingURL=activity-log-writer.d.ts.map
@@ -0,0 +1,23 @@
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 = createActivityLogWriter;
7
+ const activity_log_drainer_1 = __importDefault(require("./activity-log-drainer"));
8
+ const with_activity_log_1 = __importDefault(require("./with-activity-log"));
9
+ function describeRequest(action, collectionName) {
10
+ return collectionName ? `'${action}' request on '${collectionName}'` : `'${action}' request`;
11
+ }
12
+ function createActivityLogWriter({ service, logger, }) {
13
+ const drainer = new activity_log_drainer_1.default();
14
+ return {
15
+ record(options) {
16
+ return drainer.track(() => (0, with_activity_log_1.default)({ ...options, service, drainer, logger }), describeRequest(options.action, options.context?.collectionName));
17
+ },
18
+ drain(timeoutMs) {
19
+ return drainer.drain(timeoutMs);
20
+ },
21
+ };
22
+ }
23
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWN0aXZpdHktbG9nLXdyaXRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hY3Rpdml0eS1sb2cvYWN0aXZpdHktbG9nLXdyaXRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQW1DQSwwQ0FrQkM7QUFoREQsa0ZBQXdEO0FBQ3hELDRFQUFrRDtBQXlCbEQsU0FBUyxlQUFlLENBQUMsTUFBNEIsRUFBRSxjQUF1QjtJQUM1RSxPQUFPLGNBQWMsQ0FBQyxDQUFDLENBQUMsSUFBSSxNQUFNLGlCQUFpQixjQUFjLEdBQUcsQ0FBQyxDQUFDLENBQUMsSUFBSSxNQUFNLFdBQVcsQ0FBQztBQUMvRixDQUFDO0FBRUQsU0FBd0IsdUJBQXVCLENBQUMsRUFDOUMsT0FBTyxFQUNQLE1BQU0sR0FDbUI7SUFDekIsTUFBTSxPQUFPLEdBQUcsSUFBSSw4QkFBa0IsRUFBRSxDQUFDO0lBRXpDLE9BQU87UUFDTCxNQUFNLENBQUksT0FBb0M7WUFDNUMsT0FBTyxPQUFPLENBQUMsS0FBSyxDQUNsQixHQUFHLEVBQUUsQ0FBQyxJQUFBLDJCQUFlLEVBQUMsRUFBRSxHQUFHLE9BQU8sRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxDQUFDLEVBQy9ELGVBQWUsQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFLE9BQU8sQ0FBQyxPQUFPLEVBQUUsY0FBYyxDQUFDLENBQ2pFLENBQUM7UUFDSixDQUFDO1FBRUQsS0FBSyxDQUFDLFNBQWtCO1lBQ3RCLE9BQU8sT0FBTyxDQUFDLEtBQUssQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUNsQyxDQUFDO0tBQ0YsQ0FBQztBQUNKLENBQUMifQ==
@@ -0,0 +1,42 @@
1
+ import type ActivityLogDrainer from './activity-log-drainer';
2
+ import type { ActivityLogsWriter } from './activity-logs-service';
3
+ import type { Logger } from '../ports/logger-port';
4
+ import type { ActivityLogAction, ActivityLogResponse } from '@forestadmin/forestadmin-client';
5
+ import type { Context } from 'koa';
6
+ /** The actions the BFF writes: its data routes read, and its action route writes. */
7
+ export type BffActivityLogAction = Extract<ActivityLogAction, 'index' | 'search' | 'filter' | 'listRelatedData' | 'action'>;
8
+ export interface ActivityLogContext {
9
+ collectionName?: string;
10
+ recordId?: string | number;
11
+ recordIds?: string[] | number[];
12
+ label?: string;
13
+ }
14
+ /**
15
+ * The token that created the log is kept for the status transition: the transition is fired after
16
+ * the response, when the session it came from may already be unreachable.
17
+ */
18
+ export interface PendingActivityLog {
19
+ activityLog: ActivityLogResponse;
20
+ forestServerToken: string;
21
+ }
22
+ export interface CreatePendingActivityLogOptions {
23
+ ctx: Context;
24
+ service: ActivityLogsWriter;
25
+ action: BffActivityLogAction;
26
+ context?: ActivityLogContext;
27
+ logger: Logger;
28
+ }
29
+ export default function createPendingActivityLog({ ctx, service, action, context, logger, }: CreatePendingActivityLogOptions): Promise<PendingActivityLog | null>;
30
+ export interface MarkActivityLogOptions {
31
+ service: ActivityLogsWriter;
32
+ drainer: ActivityLogDrainer;
33
+ pending: PendingActivityLog;
34
+ status: 'completed' | 'failed';
35
+ logger: Logger;
36
+ }
37
+ /**
38
+ * Fire-and-forget on purpose: the caller's response must not wait for the audit store. The drainer
39
+ * holds the promise so a shutdown can wait for it instead.
40
+ */
41
+ export declare function markActivityLog(options: MarkActivityLogOptions): void;
42
+ //# sourceMappingURL=activity-logs-creator.d.ts.map
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = createPendingActivityLog;
4
+ exports.markActivityLog = markActivityLog;
5
+ const forestadmin_client_1 = require("@forestadmin/forestadmin-client");
6
+ const api_key_middleware_1 = require("../api-key/api-key-middleware");
7
+ const forest_server_token_middleware_1 = require("../auth/forest-server-token-middleware");
8
+ const bff_http_error_1 = require("../http/bff-http-error");
9
+ const bff_local_errors_1 = require("../http/bff-local-errors");
10
+ /**
11
+ * Fail policy for the audit trail, keyed by action type: a write whose activity log cannot be
12
+ * created is blocked (no unaudited side effect), while a read proceeds with a warning (an audit
13
+ * store outage must not take down the read surface).
14
+ *
15
+ * One case is arbitrated by the cause instead of the action type: an authorization refusal (403)
16
+ * propagates for reads too — the read itself is not authorized either.
17
+ */
18
+ const ACTION_TO_TYPE = {
19
+ index: 'read',
20
+ search: 'read',
21
+ filter: 'read',
22
+ listRelatedData: 'read',
23
+ action: 'write',
24
+ };
25
+ const MAX_STATUS_ATTEMPTS = 5;
26
+ const STATUS_RETRY_DELAY_MS = 500;
27
+ const NO_RENDERING_MESSAGE = 'This request carries no usable rendering';
28
+ function describeCause(error) {
29
+ return error instanceof Error ? `${error.name}: ${error.message}` : String(error);
30
+ }
31
+ const FORBIDDEN = 403;
32
+ const UNAUTHORIZED = 401;
33
+ const SERVER_ERROR = 500;
34
+ /** Not found: the document may not be visible yet. 0: unreachable. 408: timeout. 429: throttled. */
35
+ const RETRYABLE_TRANSITION_STATUSES = new Set([0, 404, 408, 429]);
36
+ const AUDIT_ENDPOINT_ABSENT_STATUSES = new Set([404, 501]);
37
+ const NO_AUDIT_ENDPOINT_MESSAGE = 'The Forest server does not expose the endpoint the activity log is written through, so the ' +
38
+ 'operation was not performed';
39
+ /**
40
+ * A 403 only. A 401 is not the caller being refused: the bearer the BFF audits with is minted by
41
+ * the Forest server and cached, so a 401 says that token expired — answering the caller with
42
+ * `audit_not_authorized` would refuse a read the fail-open policy lets through.
43
+ */
44
+ function isAuthorizationRefusal(error) {
45
+ return error instanceof forestadmin_client_1.HttpError && error.status === FORBIDDEN;
46
+ }
47
+ function isExpiredAuditCredential(error) {
48
+ return error instanceof forestadmin_client_1.HttpError && error.status === UNAUTHORIZED;
49
+ }
50
+ /**
51
+ * A Forest server that does not serve the activity-log endpoint at all, rather than one failing to
52
+ * answer it. No retry can make the route appear, so the caller must not be handed a `Retry-After`
53
+ * it would keep honouring on every write.
54
+ */
55
+ function isAuditEndpointAbsent(error) {
56
+ return error instanceof forestadmin_client_1.HttpError && AUDIT_ENDPOINT_ABSENT_STATUSES.has(error.status);
57
+ }
58
+ /**
59
+ * What locates the failure for support, and nothing else: the credential, the record ids and the
60
+ * label are the payload this must never carry.
61
+ */
62
+ function auditIdentifiers(ctx, context) {
63
+ const renderingId = (0, forest_server_token_middleware_1.resolveRenderingId)(ctx);
64
+ return {
65
+ ...(renderingId === undefined ? {} : { renderingId }),
66
+ ...(context?.collectionName === undefined ? {} : { collectionName: context.collectionName }),
67
+ };
68
+ }
69
+ /** An empty string is an answer, not a value: it locates no document, so it fails the guard. */
70
+ function isPresent(value) {
71
+ return value !== null && value !== undefined && value !== '';
72
+ }
73
+ /**
74
+ * The status transition reads both the id and the index, so an answer carrying only an id strands
75
+ * the entry `pending`: the fail-closed policy has to engage here, not asynchronously afterwards.
76
+ */
77
+ function isTransitionable(activityLog) {
78
+ return isPresent(activityLog?.id) && isPresent(activityLog?.attributes?.index);
79
+ }
80
+ /**
81
+ * A credential this deployment never mints is a degradation the Forest server declares by sending
82
+ * none (`api-key/api-key-client.ts`), so it warns; only a resolution that actually failed is an
83
+ * error. The single report for either: `with-activity-log` states nothing of its own, which used to
84
+ * double every line of the read path.
85
+ */
86
+ function reportUnresolvedCredentials({ ctx, action, context, logger, error, }) {
87
+ const identifiers = { ...auditIdentifiers(ctx, context), cause: describeCause(error) };
88
+ if ((0, bff_local_errors_1.isUnretryableAuditFailure)(error)) {
89
+ logger('Warn', `Activity log for '${action}' was not created: this deployment has no credential to write ` +
90
+ 'it with', identifiers);
91
+ return;
92
+ }
93
+ logger('Error', `Activity log for '${action}' has no credentials to be created with`, identifiers);
94
+ }
95
+ async function resolveCredentials(ctx) {
96
+ const renderingId = (0, forest_server_token_middleware_1.resolveRenderingId)(ctx);
97
+ if (renderingId === undefined)
98
+ throw (0, bff_http_error_1.sessionExpired)(NO_RENDERING_MESSAGE);
99
+ return {
100
+ forestServerToken: await (0, forest_server_token_middleware_1.resolveForestServerToken)(ctx),
101
+ renderingId: String(renderingId),
102
+ };
103
+ }
104
+ async function createPendingActivityLog({ ctx, service, action, context, logger, }) {
105
+ const type = ACTION_TO_TYPE[action];
106
+ let credentials;
107
+ try {
108
+ credentials = await resolveCredentials(ctx);
109
+ }
110
+ catch (error) {
111
+ reportUnresolvedCredentials({ ctx, action, context, logger, error });
112
+ if (type === 'write')
113
+ throw error;
114
+ return null;
115
+ }
116
+ const { forestServerToken, renderingId } = credentials;
117
+ let activityLog;
118
+ try {
119
+ activityLog = await service.createMcpActivityLog({
120
+ forestServerToken,
121
+ renderingId,
122
+ action,
123
+ type,
124
+ collectionName: context?.collectionName,
125
+ recordId: context?.recordId,
126
+ recordIds: context?.recordIds,
127
+ label: context?.label,
128
+ });
129
+ }
130
+ catch (error) {
131
+ logger('Error', `Activity log for '${action}' could not be created`, {
132
+ ...auditIdentifiers(ctx, context),
133
+ cause: describeCause(error),
134
+ });
135
+ if (isAuthorizationRefusal(error))
136
+ throw (0, bff_local_errors_1.auditNotAuthorized)();
137
+ if (isExpiredAuditCredential(error))
138
+ (0, api_key_middleware_1.invalidateApiKeyIdentity)(ctx);
139
+ if (type === 'write') {
140
+ throw isAuditEndpointAbsent(error)
141
+ ? (0, bff_local_errors_1.auditUnavailable)(undefined, NO_AUDIT_ENDPOINT_MESSAGE)
142
+ : (0, bff_local_errors_1.auditUnavailable)(bff_local_errors_1.AUDIT_RETRY_AFTER_SECONDS);
143
+ }
144
+ return null;
145
+ }
146
+ if (!isTransitionable(activityLog)) {
147
+ logger('Error', `Activity log for '${action}' could not be created: the server answered with no activity ` +
148
+ 'log id or index, so the audit store dropped the write', auditIdentifiers(ctx, context));
149
+ if (type === 'write')
150
+ throw (0, bff_local_errors_1.auditUnavailable)(bff_local_errors_1.AUDIT_RETRY_AFTER_SECONDS);
151
+ return null;
152
+ }
153
+ return { activityLog, forestServerToken };
154
+ }
155
+ /**
156
+ * The transition is fired after the response, so nothing retries it downstream: an entry whose
157
+ * transition is dropped stays `pending` for good and skews the action-failure statistics. Retried
158
+ * are the failures a later attempt can land — the document not existing yet, a timeout, a throttle,
159
+ * an unreachable server (status 0) and any 5xx — plus a transport failure the client rethrows raw,
160
+ * which carries no status at all. A refusal or a malformed request is left to fail at once.
161
+ */
162
+ function isRetryableTransitionFailure(error) {
163
+ if (!(error instanceof forestadmin_client_1.HttpError))
164
+ return true;
165
+ return error.status >= SERVER_ERROR || RETRYABLE_TRANSITION_STATUSES.has(error.status);
166
+ }
167
+ async function updateStatus(options, attempt = 1) {
168
+ const { service, pending, status, logger } = options;
169
+ try {
170
+ await service.updateActivityLogStatus({
171
+ forestServerToken: pending.forestServerToken,
172
+ activityLog: pending.activityLog,
173
+ status,
174
+ });
175
+ }
176
+ catch (error) {
177
+ if (isRetryableTransitionFailure(error) && attempt < MAX_STATUS_ATTEMPTS) {
178
+ logger('Debug', `Activity log status transition failed, retrying it`, {
179
+ attempt,
180
+ attempts: MAX_STATUS_ATTEMPTS,
181
+ cause: describeCause(error),
182
+ });
183
+ await new Promise(resolve => {
184
+ // Unreferenced: a pending retry must not outlive the shutdown grace the drainer enforces.
185
+ setTimeout(resolve, STATUS_RETRY_DELAY_MS).unref();
186
+ });
187
+ await updateStatus(options, attempt + 1);
188
+ return;
189
+ }
190
+ throw error;
191
+ }
192
+ }
193
+ /**
194
+ * Fire-and-forget on purpose: the caller's response must not wait for the audit store. The drainer
195
+ * holds the promise so a shutdown can wait for it instead.
196
+ */
197
+ function markActivityLog(options) {
198
+ const { drainer, status, logger } = options;
199
+ drainer
200
+ .track(() => updateStatus(options), `'${status}' transition of the activity log ${options.pending.activityLog.id}`)
201
+ .catch(error => {
202
+ logger('Error', `Failed to mark the activity log as '${status}'`, {
203
+ activityLogId: options.pending.activityLog.id,
204
+ index: options.pending.activityLog.attributes?.index,
205
+ cause: describeCause(error),
206
+ });
207
+ });
208
+ }
209
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWN0aXZpdHktbG9ncy1jcmVhdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FjdGl2aXR5LWxvZy9hY3Rpdml0eS1sb2dzLWNyZWF0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUF1TUEsMkNBb0VDO0FBMERELDBDQWVDO0FBMVVELHdFQUE0RDtBQUU1RCxzRUFBeUU7QUFDekUsMkZBR2dEO0FBQ2hELDJEQUF3RDtBQUN4RCwrREFLa0M7QUFRbEM7Ozs7Ozs7R0FPRztBQUNILE1BQU0sY0FBYyxHQUFrRDtJQUNwRSxLQUFLLEVBQUUsTUFBTTtJQUNiLE1BQU0sRUFBRSxNQUFNO0lBQ2QsTUFBTSxFQUFFLE1BQU07SUFDZCxlQUFlLEVBQUUsTUFBTTtJQUN2QixNQUFNLEVBQUUsT0FBTztDQUNoQixDQUFDO0FBRUYsTUFBTSxtQkFBbUIsR0FBRyxDQUFDLENBQUM7QUFDOUIsTUFBTSxxQkFBcUIsR0FBRyxHQUFHLENBQUM7QUFFbEMsTUFBTSxvQkFBb0IsR0FBRywwQ0FBMEMsQ0FBQztBQStCeEUsU0FBUyxhQUFhLENBQUMsS0FBYztJQUNuQyxPQUFPLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsS0FBSyxDQUFDLElBQUksS0FBSyxLQUFLLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUNwRixDQUFDO0FBRUQsTUFBTSxTQUFTLEdBQUcsR0FBRyxDQUFDO0FBQ3RCLE1BQU0sWUFBWSxHQUFHLEdBQUcsQ0FBQztBQUN6QixNQUFNLFlBQVksR0FBRyxHQUFHLENBQUM7QUFDekIsb0dBQW9HO0FBQ3BHLE1BQU0sNkJBQTZCLEdBQUcsSUFBSSxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQyxDQUFDO0FBQ2xFLE1BQU0sOEJBQThCLEdBQUcsSUFBSSxHQUFHLENBQUMsQ0FBQyxHQUFH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
@@ -0,0 +1,18 @@
1
+ import type { ActivityLogResponse, CreateActivityLogParams, UpdateActivityLogStatusParams } from '@forestadmin/forestadmin-client';
2
+ export declare const APPLICATION_SOURCE_HEADER = "Forest-Application-Source";
3
+ export declare const BFF_APPLICATION_SOURCE = "BFF";
4
+ /**
5
+ * The slice of `ActivityLogsService` the BFF uses. Named so a fake can stand in for the two calls
6
+ * without carrying the rest of the Forest client.
7
+ */
8
+ export interface ActivityLogsWriter {
9
+ createMcpActivityLog(params: CreateActivityLogParams): Promise<ActivityLogResponse>;
10
+ updateActivityLogStatus(params: UpdateActivityLogStatusParams): Promise<void>;
11
+ }
12
+ /**
13
+ * Its own instance rather than the client `oauth/forest-server-client.ts` already holds:
14
+ * `ForestAdminClientOptions` carries no `headers`, so that one cannot tell the server which channel
15
+ * wrote the log.
16
+ */
17
+ export default function createBffActivityLogsService(forestServerUrl: string): ActivityLogsWriter;
18
+ //# sourceMappingURL=activity-logs-service.d.ts.map
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BFF_APPLICATION_SOURCE = exports.APPLICATION_SOURCE_HEADER = void 0;
4
+ exports.default = createBffActivityLogsService;
5
+ const forestadmin_client_1 = require("@forestadmin/forestadmin-client");
6
+ exports.APPLICATION_SOURCE_HEADER = 'Forest-Application-Source';
7
+ exports.BFF_APPLICATION_SOURCE = 'BFF';
8
+ /**
9
+ * Its own instance rather than the client `oauth/forest-server-client.ts` already holds:
10
+ * `ForestAdminClientOptions` carries no `headers`, so that one cannot tell the server which channel
11
+ * wrote the log.
12
+ */
13
+ function createBffActivityLogsService(forestServerUrl) {
14
+ return new forestadmin_client_1.ActivityLogsService(new forestadmin_client_1.ForestHttpApi(), {
15
+ forestServerUrl,
16
+ headers: { [exports.APPLICATION_SOURCE_HEADER]: exports.BFF_APPLICATION_SOURCE },
17
+ });
18
+ }
19
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWN0aXZpdHktbG9ncy1zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FjdGl2aXR5LWxvZy9hY3Rpdml0eS1sb2dzLXNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBeUJBLCtDQUtDO0FBeEJELHdFQUFxRjtBQUV4RSxRQUFBLHlCQUF5QixHQUFHLDJCQUEyQixDQUFDO0FBQ3hELFFBQUEsc0JBQXNCLEdBQUcsS0FBSyxDQUFDO0FBVzVDOzs7O0dBSUc7QUFDSCxTQUF3Qiw0QkFBNEIsQ0FBQyxlQUF1QjtJQUMxRSxPQUFPLElBQUksd0NBQW1CLENBQUMsSUFBSSxrQ0FBYSxFQUFFLEVBQUU7UUFDbEQsZUFBZTtRQUNmLE9BQU8sRUFBRSxFQUFFLENBQUMsaUNBQXlCLENBQUMsRUFBRSw4QkFBc0IsRUFBRTtLQUNqRSxDQUFDLENBQUM7QUFDTCxDQUFDIn0=
@@ -0,0 +1,25 @@
1
+ import type ActivityLogDrainer from './activity-log-drainer';
2
+ import type { ActivityLogContext, BffActivityLogAction } from './activity-logs-creator';
3
+ import type { ActivityLogsWriter } from './activity-logs-service';
4
+ import type { Logger } from '../ports/logger-port';
5
+ import type { Context } from 'koa';
6
+ export interface WithActivityLogOptions<T> {
7
+ ctx: Context;
8
+ service: ActivityLogsWriter;
9
+ drainer: ActivityLogDrainer;
10
+ action: BffActivityLogAction;
11
+ context?: ActivityLogContext;
12
+ logger: Logger;
13
+ operation: () => Promise<T>;
14
+ /**
15
+ * Errors the log records as `completed` rather than `failed` — the operation reached a business
16
+ * outcome the BFF answers with an error status.
17
+ */
18
+ isCompletedDespite?: (error: unknown) => boolean;
19
+ }
20
+ /**
21
+ * Runs an operation under an activity log: the pending log is awaited before the operation starts,
22
+ * so nothing runs unaudited, and the status transition is fired without `await` afterwards.
23
+ */
24
+ export default function withActivityLog<T>(options: WithActivityLogOptions<T>): Promise<T>;
25
+ //# sourceMappingURL=with-activity-log.d.ts.map