@forestadmin/agent-bff 1.4.1 → 1.6.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.
@@ -0,0 +1,4 @@
1
+ import type { Logger } from '../ports/logger-port';
2
+ import type { Metrics } from '../ports/metrics-port';
3
+ export default function createConsoleMetrics(logger?: Logger): Metrics;
4
+ //# sourceMappingURL=console-metrics.d.ts.map
@@ -0,0 +1,18 @@
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 = createConsoleMetrics;
7
+ const console_logger_1 = __importDefault(require("./console-logger"));
8
+ function createConsoleMetrics(logger = (0, console_logger_1.default)()) {
9
+ return {
10
+ increment(name, tags) {
11
+ logger('Info', 'metric.increment', { metric: name, ...(tags ?? {}) });
12
+ },
13
+ gauge(name, value, tags) {
14
+ logger('Info', 'metric.gauge', { metric: name, value, ...(tags ?? {}) });
15
+ },
16
+ };
17
+ }
18
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uc29sZS1tZXRyaWNzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FkYXB0ZXJzL2NvbnNvbGUtbWV0cmljcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUtBLHVDQVNDO0FBWEQsc0VBQW1EO0FBRW5ELFNBQXdCLG9CQUFvQixDQUFDLFNBQWlCLElBQUEsd0JBQW1CLEdBQUU7SUFDakYsT0FBTztRQUNMLFNBQVMsQ0FBQyxJQUFZLEVBQUUsSUFBaUI7WUFDdkMsTUFBTSxDQUFDLE1BQU0sRUFBRSxrQkFBa0IsRUFBRSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsR0FBRyxDQUFDLElBQUksSUFBSSxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDeEUsQ0FBQztRQUNELEtBQUssQ0FBQyxJQUFZLEVBQUUsS0FBYSxFQUFFLElBQWlCO1lBQ2xELE1BQU0sQ0FBQyxNQUFNLEVBQUUsY0FBYyxFQUFFLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsR0FBRyxDQUFDLElBQUksSUFBSSxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDM0UsQ0FBQztLQUNGLENBQUM7QUFDSixDQUFDIn0=
@@ -0,0 +1,8 @@
1
+ import type { Metrics } from '../ports/metrics-port';
2
+ /**
3
+ * Wraps a Metrics port so emitting a metric can never throw into business logic. Metrics is a
4
+ * user-supplied implementation; a backend that throws must not break a request (e.g. a throwing
5
+ * gauge on the schema cache-hit path would otherwise degrade every read).
6
+ */
7
+ export default function safeMetrics(metrics: Metrics): Metrics;
8
+ //# sourceMappingURL=safe-metrics.d.ts.map
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = safeMetrics;
4
+ /**
5
+ * Wraps a Metrics port so emitting a metric can never throw into business logic. Metrics is a
6
+ * user-supplied implementation; a backend that throws must not break a request (e.g. a throwing
7
+ * gauge on the schema cache-hit path would otherwise degrade every read).
8
+ */
9
+ function safeMetrics(metrics) {
10
+ return {
11
+ increment(name, tags) {
12
+ try {
13
+ metrics.increment(name, tags);
14
+ }
15
+ catch {
16
+ // Swallow: a metrics-backend failure must never break business logic.
17
+ }
18
+ },
19
+ gauge(name, value, tags) {
20
+ try {
21
+ metrics.gauge(name, value, tags);
22
+ }
23
+ catch {
24
+ // Swallow: a metrics-backend failure must never break business logic.
25
+ }
26
+ },
27
+ };
28
+ }
29
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2FmZS1tZXRyaWNzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FkYXB0ZXJzL3NhZmUtbWV0cmljcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQU9BLDhCQWlCQztBQXRCRDs7OztHQUlHO0FBQ0gsU0FBd0IsV0FBVyxDQUFDLE9BQWdCO0lBQ2xELE9BQU87UUFDTCxTQUFTLENBQUMsSUFBWSxFQUFFLElBQWlCO1lBQ3ZDLElBQUksQ0FBQztnQkFDSCxPQUFPLENBQUMsU0FBUyxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztZQUNoQyxDQUFDO1lBQUMsTUFBTSxDQUFDO2dCQUNQLHNFQUFzRTtZQUN4RSxDQUFDO1FBQ0gsQ0FBQztRQUNELEtBQUssQ0FBQyxJQUFZLEVBQUUsS0FBYSxFQUFFLElBQWlCO1lBQ2xELElBQUksQ0FBQztnQkFDSCxPQUFPLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxLQUFLLEVBQUUsSUFBSSxDQUFDLENBQUM7WUFDbkMsQ0FBQztZQUFDLE1BQU0sQ0FBQztnQkFDUCxzRUFBc0U7WUFDeEUsQ0FBQztRQUNILENBQUM7S0FDRixDQUFDO0FBQ0osQ0FBQyJ9
@@ -31,4 +31,5 @@ export declare function sessionExpired(message?: string): BffHttpError;
31
31
  export declare function originNotAllowed(message?: string): BffHttpError;
32
32
  export declare function missingTimezone(message?: string): BffHttpError;
33
33
  export declare function invalidTimezone(value: string): BffHttpError;
34
+ export declare function relationFieldNotSupported(fields: string[]): BffHttpError;
34
35
  //# sourceMappingURL=bff-http-error.d.ts.map
@@ -9,6 +9,7 @@ exports.sessionExpired = sessionExpired;
9
9
  exports.originNotAllowed = originNotAllowed;
10
10
  exports.missingTimezone = missingTimezone;
11
11
  exports.invalidTimezone = invalidTimezone;
12
+ exports.relationFieldNotSupported = relationFieldNotSupported;
12
13
  class BffHttpError extends Error {
13
14
  constructor(status, type, message, details) {
14
15
  super(message);
@@ -52,4 +53,7 @@ function missingTimezone(message = 'A timezone is required but none was provided
52
53
  function invalidTimezone(value) {
53
54
  return new BffHttpError(400, 'invalid_timezone', `Invalid timezone: "${value}"`);
54
55
  }
55
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmZmLWh0dHAtZXJyb3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaHR0cC9iZmYtaHR0cC1lcnJvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUF1QkEsa0RBY0M7QUFFRCxrQ0FhQztBQUVELG9DQUVDO0FBRUQsb0RBSUM7QUFFRCx3Q0FFQztBQUVELDRDQUVDO0FBRUQsMENBSUM7QUFFRCwwQ0FFQztBQWhGRCxNQUFhLFlBQWEsU0FBUSxLQUFLO0lBS3JDLFlBQVksTUFBYyxFQUFFLElBQVksRUFBRSxPQUFlLEVBQUUsT0FBaUI7UUFDMUUsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ2YsSUFBSSxDQUFDLElBQUksR0FBRyxjQUFjLENBQUM7UUFDM0IsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFDckIsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7UUFDakIsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7SUFDekIsQ0FBQztDQUNGO0FBWkQsb0NBWUM7QUFXRCxTQUFnQixtQkFBbUIsQ0FBQyxLQUFjO0lBT2hELE9BQU8sQ0FDTCxPQUFPLEtBQUssS0FBSyxRQUFRO1FBQ3pCLEtBQUssS0FBSyxJQUFJO1FBQ2QsT0FBUSxLQUE4QixDQUFDLE1BQU0sS0FBSyxRQUFRO1FBQzFELE9BQVEsS0FBNEIsQ0FBQyxJQUFJLEtBQUssUUFBUTtRQUN0RCxPQUFRLEtBQStCLENBQUMsT0FBTyxLQUFLLFFBQVEsQ0FDN0QsQ0FBQztBQUNKLENBQUM7QUFFRCxTQUFnQixXQUFXLENBQUMsS0FLM0I7SUFDQyxNQUFNLElBQUksR0FBaUI7UUFDekIsS0FBSyxFQUFFLEVBQUUsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJLEVBQUUsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLEVBQUUsT0FBTyxFQUFFLEtBQUssQ0FBQyxPQUFPLEVBQUU7S0FDMUUsQ0FBQztJQUVGLElBQUksS0FBSyxDQUFDLE9BQU8sS0FBSyxTQUFTO1FBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQztJQUVwRSxPQUFPLElBQUksQ0FBQztBQUNkLENBQUM7QUFFRCxTQUFnQixZQUFZLENBQUMsT0FBTyxHQUFHLGdDQUFnQztJQUNyRSxPQUFPLElBQUksWUFBWSxDQUFDLEdBQUcsRUFBRSxjQUFjLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDeEQsQ0FBQztBQUVELFNBQWdCLG9CQUFvQixDQUNsQyxPQUFPLEdBQUcsdURBQXVEO0lBRWpFLE9BQU8sSUFBSSxZQUFZLENBQUMsR0FBRyxFQUFFLHVCQUF1QixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQ2pFLENBQUM7QUFFRCxTQUFnQixjQUFjLENBQUMsT0FBTyxHQUFHLDZCQUE2QjtJQUNwRSxPQUFPLElBQUksWUFBWSxDQUFDLEdBQUcsRUFBRSxpQkFBaUIsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUMzRCxDQUFDO0FBRUQsU0FBZ0IsZ0JBQWdCLENBQUMsT0FBTyxHQUFHLG9DQUFvQztJQUM3RSxPQUFPLElBQUksWUFBWSxDQUFDLEdBQUcsRUFBRSxvQkFBb0IsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUM5RCxDQUFDO0FBRUQsU0FBZ0IsZUFBZSxDQUM3QixPQUFPLEdBQUcsOENBQThDO0lBRXhELE9BQU8sSUFBSSxZQUFZLENBQUMsR0FBRyxFQUFFLGtCQUFrQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQzVELENBQUM7QUFFRCxTQUFnQixlQUFlLENBQUMsS0FBYTtJQUMzQyxPQUFPLElBQUksWUFBWSxDQUFDLEdBQUcsRUFBRSxrQkFBa0IsRUFBRSxzQkFBc0IsS0FBSyxHQUFHLENBQUMsQ0FBQztBQUNuRixDQUFDIn0=
56
+ function relationFieldNotSupported(fields) {
57
+ return new BffHttpError(422, 'relation_field_not_supported', 'Nested relation field paths are not supported on top-level list and count', { fields });
58
+ }
59
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmZmLWh0dHAtZXJyb3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaHR0cC9iZmYtaHR0cC1lcnJvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUF1QkEsa0RBY0M7QUFFRCxrQ0FhQztBQUVELG9DQUVDO0FBRUQsb0RBSUM7QUFFRCx3Q0FFQztBQUVELDRDQUVDO0FBRUQsMENBSUM7QUFFRCwwQ0FFQztBQUVELDhEQU9DO0FBekZELE1BQWEsWUFBYSxTQUFRLEtBQUs7SUFLckMsWUFBWSxNQUFjLEVBQUUsSUFBWSxFQUFFLE9BQWUsRUFBRSxPQUFpQjtRQUMxRSxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDZixJQUFJLENBQUMsSUFBSSxHQUFHLGNBQWMsQ0FBQztRQUMzQixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztRQUNyQixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztRQUNqQixJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztJQUN6QixDQUFDO0NBQ0Y7QUFaRCxvQ0FZQztBQVdELFNBQWdCLG1CQUFtQixDQUFDLEtBQWM7SUFPaEQsT0FBTyxDQUNMLE9BQU8sS0FBSyxLQUFLLFFBQVE7UUFDekIsS0FBSyxLQUFLLElBQUk7UUFDZCxPQUFRLEtBQThCLENBQUMsTUFBTSxLQUFLLFFBQVE7UUFDMUQsT0FBUSxLQUE0QixDQUFDLElBQUksS0FBSyxRQUFRO1FBQ3RELE9BQVEsS0FBK0IsQ0FBQyxPQUFPLEtBQUssUUFBUSxDQUM3RCxDQUFDO0FBQ0osQ0FBQztBQUVELFNBQWdCLFdBQVcsQ0FBQyxLQUszQjtJQUNDLE1BQU0sSUFBSSxHQUFpQjtRQUN6QixLQUFLLEVBQUUsRUFBRSxJQUFJLEVBQUUsS0FBSyxDQUFDLElBQUksRUFBRSxNQUFNLEVBQUUsS0FBSyxDQUFDLE1BQU0sRUFBRSxPQUFPLEVBQUUsS0FBSyxDQUFDLE9BQU8sRUFBRTtLQUMxRSxDQUFDO0lBRUYsSUFBSSxLQUFLLENBQUMsT0FBTyxLQUFLLFNBQVM7UUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDO0lBRXBFLE9BQU8sSUFBSSxDQUFDO0FBQ2QsQ0FBQztBQUVELFNBQWdCLFlBQVksQ0FBQyxPQUFPLEdBQUcsZ0NBQWdDO0lBQ3JFLE9BQU8sSUFBSSxZQUFZLENBQUMsR0FBRyxFQUFFLGNBQWMsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUN4RCxDQUFDO0FBRUQsU0FBZ0Isb0JBQW9CLENBQ2xDLE9BQU8sR0FBRyx1REFBdUQ7SUFFakUsT0FBTyxJQUFJLFlBQVksQ0FBQyxHQUFHLEVBQUUsdUJBQXVCLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDakUsQ0FBQztBQUVELFNBQWdCLGNBQWMsQ0FBQyxPQUFPLEdBQUcsNkJBQTZCO0lBQ3BFLE9BQU8sSUFBSSxZQUFZLENBQUMsR0FBRyxFQUFFLGlCQUFpQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQzNELENBQUM7QUFFRCxTQUFnQixnQkFBZ0IsQ0FBQyxPQUFPLEdBQUcsb0NBQW9DO0lBQzdFLE9BQU8sSUFBSSxZQUFZLENBQUMsR0FBRyxFQUFFLG9CQUFvQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQzlELENBQUM7QUFFRCxTQUFnQixlQUFlLENBQzdCLE9BQU8sR0FBRyw4Q0FBOEM7SUFFeEQsT0FBTyxJQUFJLFlBQVksQ0FBQyxHQUFHLEVBQUUsa0JBQWtCLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDNUQsQ0FBQztBQUVELFNBQWdCLGVBQWUsQ0FBQyxLQUFhO0lBQzNDLE9BQU8sSUFBSSxZQUFZLENBQUMsR0FBRyxFQUFFLGtCQUFrQixFQUFFLHNCQUFzQixLQUFLLEdBQUcsQ0FBQyxDQUFDO0FBQ25GLENBQUM7QUFFRCxTQUFnQix5QkFBeUIsQ0FBQyxNQUFnQjtJQUN4RCxPQUFPLElBQUksWUFBWSxDQUNyQixHQUFHLEVBQ0gsOEJBQThCLEVBQzlCLDJFQUEyRSxFQUMzRSxFQUFFLE1BQU0sRUFBRSxDQUNYLENBQUM7QUFDSixDQUFDIn0=
@@ -0,0 +1,6 @@
1
+ export type MetricTags = Record<string, string | number>;
2
+ export interface Metrics {
3
+ increment(name: string, tags?: MetricTags): void;
4
+ gauge(name: string, value: number, tags?: MetricTags): void;
5
+ }
6
+ //# sourceMappingURL=metrics-port.d.ts.map
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0cmljcy1wb3J0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3BvcnRzL21ldHJpY3MtcG9ydC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIn0=
@@ -0,0 +1,23 @@
1
+ import type ReadModel from './read-model';
2
+ import type { Metrics } from '../ports/metrics-port';
3
+ import type { ActionEndpointsByCollection } from '@forestadmin/agent-client';
4
+ export declare const ACTION_ENDPOINT_MISS = "action_endpoint_miss";
5
+ export declare const ACTION_ENDPOINT_ERROR = "action_endpoint_error";
6
+ export type ActionEndpointInfo = ActionEndpointsByCollection[string][string];
7
+ export type ReadModelProvider = () => Promise<ReadModel>;
8
+ export interface ResolveActionContext {
9
+ rendering: string | number;
10
+ }
11
+ /**
12
+ * Resolves an action to its endpoint against the current read-model. Never throws: an absent
13
+ * mapping emits the miss counter, a failure to obtain the read-model emits the error counter.
14
+ * Both counters carry `rendering`, `collection`, and `action` tags. In normal flow the action
15
+ * allow-list already excludes endpoint-less actions, so the miss path is a defensive guard.
16
+ */
17
+ export default class ActionEndpointResolver {
18
+ private readonly getReadModel;
19
+ private readonly metrics;
20
+ constructor(getReadModel: ReadModelProvider, metrics: Metrics);
21
+ resolve(collection: string, action: string, { rendering }: ResolveActionContext): Promise<ActionEndpointInfo | undefined>;
22
+ }
23
+ //# sourceMappingURL=action-endpoint-resolver.d.ts.map
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ACTION_ENDPOINT_ERROR = exports.ACTION_ENDPOINT_MISS = void 0;
4
+ exports.ACTION_ENDPOINT_MISS = 'action_endpoint_miss';
5
+ exports.ACTION_ENDPOINT_ERROR = 'action_endpoint_error';
6
+ /**
7
+ * Resolves an action to its endpoint against the current read-model. Never throws: an absent
8
+ * mapping emits the miss counter, a failure to obtain the read-model emits the error counter.
9
+ * Both counters carry `rendering`, `collection`, and `action` tags. In normal flow the action
10
+ * allow-list already excludes endpoint-less actions, so the miss path is a defensive guard.
11
+ */
12
+ class ActionEndpointResolver {
13
+ constructor(getReadModel, metrics) {
14
+ this.getReadModel = getReadModel;
15
+ this.metrics = metrics;
16
+ }
17
+ async resolve(collection, action, { rendering }) {
18
+ const tags = { rendering, collection, action };
19
+ let readModel;
20
+ try {
21
+ readModel = await this.getReadModel();
22
+ }
23
+ catch {
24
+ this.metrics.increment(exports.ACTION_ENDPOINT_ERROR, tags);
25
+ return undefined;
26
+ }
27
+ const info = readModel.getActionEndpoints()[collection]?.[action];
28
+ if (!info) {
29
+ this.metrics.increment(exports.ACTION_ENDPOINT_MISS, tags);
30
+ return undefined;
31
+ }
32
+ return info;
33
+ }
34
+ }
35
+ exports.default = ActionEndpointResolver;
36
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWN0aW9uLWVuZHBvaW50LXJlc29sdmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3JlYWQtbW9kZWwvYWN0aW9uLWVuZHBvaW50LXJlc29sdmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUlhLFFBQUEsb0JBQW9CLEdBQUcsc0JBQXNCLENBQUM7QUFDOUMsUUFBQSxxQkFBcUIsR0FBRyx1QkFBdUIsQ0FBQztBQVU3RDs7Ozs7R0FLRztBQUNILE1BQXFCLHNCQUFzQjtJQUN6QyxZQUNtQixZQUErQixFQUMvQixPQUFnQjtRQURoQixpQkFBWSxHQUFaLFlBQVksQ0FBbUI7UUFDL0IsWUFBTyxHQUFQLE9BQU8sQ0FBUztJQUNoQyxDQUFDO0lBRUosS0FBSyxDQUFDLE9BQU8sQ0FDWCxVQUFrQixFQUNsQixNQUFjLEVBQ2QsRUFBRSxTQUFTLEVBQXdCO1FBRW5DLE1BQU0sSUFBSSxHQUFHLEVBQUUsU0FBUyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsQ0FBQztRQUUvQyxJQUFJLFNBQW9CLENBQUM7UUFFekIsSUFBSSxDQUFDO1lBQ0gsU0FBUyxHQUFHLE1BQU0sSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO1FBQ3hDLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyw2QkFBcUIsRUFBRSxJQUFJLENBQUMsQ0FBQztZQUVwRCxPQUFPLFNBQVMsQ0FBQztRQUNuQixDQUFDO1FBRUQsTUFBTSxJQUFJLEdBQUcsU0FBUyxDQUFDLGtCQUFrQixFQUFFLENBQUMsVUFBVSxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUVsRSxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDVixJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyw0QkFBb0IsRUFBRSxJQUFJLENBQUMsQ0FBQztZQUVuRCxPQUFPLFNBQVMsQ0FBQztRQUNuQixDQUFDO1FBRUQsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0NBQ0Y7QUFqQ0QseUNBaUNDIn0=
@@ -0,0 +1,11 @@
1
+ import type { CapabilitiesFetcher } from './capabilities-cache';
2
+ export interface AgentCapabilitiesFetcherOptions {
3
+ agentUrl: string;
4
+ token: string;
5
+ }
6
+ /**
7
+ * Builds a capabilities fetcher bound to a request's agent token. The cache calls it only on a
8
+ * miss, so the token of whichever request first populates a collection is the one used.
9
+ */
10
+ export default function createAgentCapabilitiesFetcher({ agentUrl, token, }: AgentCapabilitiesFetcherOptions): CapabilitiesFetcher;
11
+ //# sourceMappingURL=agent-capabilities-fetcher.d.ts.map
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = createAgentCapabilitiesFetcher;
4
+ const agent_client_1 = require("@forestadmin/agent-client");
5
+ /**
6
+ * Builds a capabilities fetcher bound to a request's agent token. The cache calls it only on a
7
+ * miss, so the token of whichever request first populates a collection is the one used.
8
+ */
9
+ function createAgentCapabilitiesFetcher({ agentUrl, token, }) {
10
+ const client = (0, agent_client_1.createRemoteAgentClient)({ url: agentUrl, token });
11
+ return collection => client.collection(collection).capabilities();
12
+ }
13
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWdlbnQtY2FwYWJpbGl0aWVzLWZldGNoZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvcmVhZC1tb2RlbC9hZ2VudC1jYXBhYmlsaXRpZXMtZmV0Y2hlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQWFBLGlEQU9DO0FBbEJELDREQUFvRTtBQU9wRTs7O0dBR0c7QUFDSCxTQUF3Qiw4QkFBOEIsQ0FBQyxFQUNyRCxRQUFRLEVBQ1IsS0FBSyxHQUMyQjtJQUNoQyxNQUFNLE1BQU0sR0FBRyxJQUFBLHNDQUF1QixFQUFDLEVBQUUsR0FBRyxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDO0lBRWpFLE9BQU8sVUFBVSxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLFVBQVUsQ0FBQyxDQUFDLFlBQVksRUFBRSxDQUFDO0FBQ3BFLENBQUMifQ==
@@ -0,0 +1,27 @@
1
+ export interface CapabilitiesResult {
2
+ fields: {
3
+ name: string;
4
+ type: string;
5
+ operators: string[];
6
+ }[];
7
+ }
8
+ export type CapabilitiesFetcher = (collection: string) => Promise<CapabilitiesResult>;
9
+ export interface CapabilitiesCacheOptions {
10
+ now?: () => number;
11
+ ttlMs?: number;
12
+ }
13
+ /**
14
+ * Per-collection capabilities cache (24h). The fetcher is passed per call so it can be bound to
15
+ * the caller's request token. Invalidated together with the schema via `clear()`.
16
+ */
17
+ export default class CapabilitiesCache {
18
+ private readonly now;
19
+ private readonly ttlMs;
20
+ private readonly entries;
21
+ private readonly inFlight;
22
+ private generation;
23
+ constructor({ now, ttlMs }?: CapabilitiesCacheOptions);
24
+ get(collection: string, fetcher: CapabilitiesFetcher): Promise<CapabilitiesResult>;
25
+ clear(): void;
26
+ }
27
+ //# sourceMappingURL=capabilities-cache.d.ts.map
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const schema_cache_1 = require("./schema-cache");
4
+ /**
5
+ * Per-collection capabilities cache (24h). The fetcher is passed per call so it can be bound to
6
+ * the caller's request token. Invalidated together with the schema via `clear()`.
7
+ */
8
+ class CapabilitiesCache {
9
+ constructor({ now = Date.now, ttlMs = schema_cache_1.ONE_DAY_MS } = {}) {
10
+ this.entries = new Map();
11
+ this.inFlight = new Map();
12
+ this.generation = 0;
13
+ this.now = now;
14
+ this.ttlMs = ttlMs;
15
+ }
16
+ async get(collection, fetcher) {
17
+ const entry = this.entries.get(collection);
18
+ if (entry && this.now() - entry.fetchedAt < this.ttlMs)
19
+ return entry.result;
20
+ const pending = this.inFlight.get(collection);
21
+ if (pending)
22
+ return pending;
23
+ const { generation } = this;
24
+ const fetch = fetcher(collection)
25
+ .then(result => {
26
+ // Skip the write if a clear() (schema refresh) happened while this fetch was in flight —
27
+ // its result belongs to the old schema generation and must not repopulate the cache.
28
+ if (this.generation === generation) {
29
+ this.entries.set(collection, { result, fetchedAt: this.now() });
30
+ }
31
+ return result;
32
+ })
33
+ .finally(() => {
34
+ if (this.inFlight.get(collection) === fetch)
35
+ this.inFlight.delete(collection);
36
+ });
37
+ this.inFlight.set(collection, fetch);
38
+ return fetch;
39
+ }
40
+ clear() {
41
+ this.generation += 1;
42
+ this.entries.clear();
43
+ this.inFlight.clear();
44
+ }
45
+ }
46
+ exports.default = CapabilitiesCache;
47
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2FwYWJpbGl0aWVzLWNhY2hlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3JlYWQtbW9kZWwvY2FwYWJpbGl0aWVzLWNhY2hlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsaURBQTRDO0FBa0I1Qzs7O0dBR0c7QUFDSCxNQUFxQixpQkFBaUI7SUFRcEMsWUFBWSxFQUFFLEdBQUcsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLEtBQUssR0FBRyx5QkFBVSxLQUErQixFQUFFO1FBSmhFLFlBQU8sR0FBRyxJQUFJLEdBQUcsRUFBc0IsQ0FBQztRQUN4QyxhQUFRLEdBQUcsSUFBSSxHQUFHLEVBQXVDLENBQUM7UUFDbkUsZUFBVSxHQUFHLENBQUMsQ0FBQztRQUdyQixJQUFJLENBQUMsR0FBRyxHQUFHLEdBQUcsQ0FBQztRQUNmLElBQUksQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDO0lBQ3JCLENBQUM7SUFFRCxLQUFLLENBQUMsR0FBRyxDQUFDLFVBQWtCLEVBQUUsT0FBNEI7UUFDeEQsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDM0MsSUFBSSxLQUFLLElBQUksSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLEtBQUssQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDLEtBQUs7WUFBRSxPQUFPLEtBQUssQ0FBQyxNQUFNLENBQUM7UUFFNUUsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDOUMsSUFBSSxPQUFPO1lBQUUsT0FBTyxPQUFPLENBQUM7UUFFNUIsTUFBTSxFQUFFLFVBQVUsRUFBRSxHQUFHLElBQUksQ0FBQztRQUM1QixNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsVUFBVSxDQUFDO2FBQzlCLElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRTtZQUNiLHlGQUF5RjtZQUN6RixxRkFBcUY7WUFDckYsSUFBSSxJQUFJLENBQUMsVUFBVSxLQUFLLFVBQVUsRUFBRSxDQUFDO2dCQUNuQyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxVQUFVLEVBQUUsRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLElBQUksQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDbEUsQ0FBQztZQUVELE9BQU8sTUFBTSxDQUFDO1FBQ2hCLENBQUMsQ0FBQzthQUNELE9BQU8sQ0FBQyxHQUFHLEVBQUU7WUFDWixJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQyxLQUFLLEtBQUs7Z0JBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDaEYsQ0FBQyxDQUFDLENBQUM7UUFFTCxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxVQUFVLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFFckMsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBRUQsS0FBSztRQUNILElBQUksQ0FBQyxVQUFVLElBQUksQ0FBQyxDQUFDO1FBQ3JCLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDckIsSUFBSSxDQUFDLFFBQVEsQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUN4QixDQUFDO0NBQ0Y7QUE3Q0Qsb0NBNkNDIn0=
@@ -0,0 +1,17 @@
1
+ import type { Logger } from '../ports/logger-port';
2
+ import type { Metrics } from '../ports/metrics-port';
3
+ import ActionEndpointResolver from './action-endpoint-resolver';
4
+ import ReadModelStore from './read-model-store';
5
+ export interface CreateReadModelOptions {
6
+ forestServerUrl: string;
7
+ envSecret: string;
8
+ metrics?: Metrics;
9
+ logger?: Logger;
10
+ now?: () => number;
11
+ }
12
+ export interface ReadModelBundle {
13
+ store: ReadModelStore;
14
+ actionEndpointResolver: ActionEndpointResolver;
15
+ }
16
+ export default function createReadModel({ forestServerUrl, envSecret, metrics, logger, now, }: CreateReadModelOptions): ReadModelBundle;
17
+ //# sourceMappingURL=create-read-model.d.ts.map
@@ -0,0 +1,24 @@
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 = createReadModel;
7
+ const action_endpoint_resolver_1 = __importDefault(require("./action-endpoint-resolver"));
8
+ const capabilities_cache_1 = __importDefault(require("./capabilities-cache"));
9
+ const forest_schema_client_1 = __importDefault(require("./forest-schema-client"));
10
+ const read_model_store_1 = __importDefault(require("./read-model-store"));
11
+ const schema_cache_1 = __importDefault(require("./schema-cache"));
12
+ const console_metrics_1 = __importDefault(require("../adapters/console-metrics"));
13
+ const safe_metrics_1 = __importDefault(require("../adapters/safe-metrics"));
14
+ function createReadModel({ forestServerUrl, envSecret, metrics, logger, now, }) {
15
+ // Wrap once so a throwing metrics backend can never break business logic anywhere in the bundle.
16
+ const resolvedMetrics = (0, safe_metrics_1.default)(metrics ?? (0, console_metrics_1.default)(logger));
17
+ const fetcher = new forest_schema_client_1.default({ forestServerUrl, envSecret });
18
+ const schemaCache = new schema_cache_1.default({ fetcher, metrics: resolvedMetrics, now });
19
+ const capabilitiesCache = new capabilities_cache_1.default({ now });
20
+ const store = new read_model_store_1.default(schemaCache, capabilitiesCache);
21
+ const actionEndpointResolver = new action_endpoint_resolver_1.default(() => store.getReadModel(), resolvedMetrics);
22
+ return { store, actionEndpointResolver };
23
+ }
24
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlLXJlYWQtbW9kZWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvcmVhZC1tb2RlbC9jcmVhdGUtcmVhZC1tb2RlbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQXdCQSxrQ0FtQkM7QUF4Q0QsMEZBQWdFO0FBQ2hFLDhFQUFxRDtBQUNyRCxrRkFBd0Q7QUFDeEQsMEVBQWdEO0FBQ2hELGtFQUF5QztBQUN6QyxrRkFBK0Q7QUFDL0QsNEVBQW1EO0FBZW5ELFNBQXdCLGVBQWUsQ0FBQyxFQUN0QyxlQUFlLEVBQ2YsU0FBUyxFQUNULE9BQU8sRUFDUCxNQUFNLEVBQ04sR0FBRyxHQUNvQjtJQUN2QixpR0FBaUc7SUFDakcsTUFBTSxlQUFlLEdBQUcsSUFBQSxzQkFBVyxFQUFDLE9BQU8sSUFBSSxJQUFBLHlCQUFvQixFQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7SUFDN0UsTUFBTSxPQUFPLEdBQUcsSUFBSSw4QkFBa0IsQ0FBQyxFQUFFLGVBQWUsRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDO0lBQ3ZFLE1BQU0sV0FBVyxHQUFHLElBQUksc0JBQVcsQ0FBQyxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsZUFBZSxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUM7SUFDaEYsTUFBTSxpQkFBaUIsR0FBRyxJQUFJLDRCQUFpQixDQUFDLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQztJQUN6RCxNQUFNLEtBQUssR0FBRyxJQUFJLDBCQUFjLENBQUMsV0FBVyxFQUFFLGlCQUFpQixDQUFDLENBQUM7SUFDakUsTUFBTSxzQkFBc0IsR0FBRyxJQUFJLGtDQUFzQixDQUN2RCxHQUFHLEVBQUUsQ0FBQyxLQUFLLENBQUMsWUFBWSxFQUFFLEVBQzFCLGVBQWUsQ0FDaEIsQ0FBQztJQUVGLE9BQU8sRUFBRSxLQUFLLEVBQUUsc0JBQXNCLEVBQUUsQ0FBQztBQUMzQyxDQUFDIn0=
@@ -0,0 +1,5 @@
1
+ export default class SchemaUnavailableError extends Error {
2
+ readonly cause?: unknown;
3
+ constructor(cause?: unknown);
4
+ }
5
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class SchemaUnavailableError extends Error {
4
+ constructor(cause) {
5
+ super('The agent schema is unavailable');
6
+ this.name = 'SchemaUnavailableError';
7
+ this.cause = cause;
8
+ }
9
+ }
10
+ exports.default = SchemaUnavailableError;
11
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3JzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3JlYWQtbW9kZWwvZXJyb3JzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsTUFBcUIsc0JBQXVCLFNBQVEsS0FBSztJQUd2RCxZQUFZLEtBQWU7UUFDekIsS0FBSyxDQUFDLGlDQUFpQyxDQUFDLENBQUM7UUFDekMsSUFBSSxDQUFDLElBQUksR0FBRyx3QkFBd0IsQ0FBQztRQUNyQyxJQUFJLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FBQztJQUNyQixDQUFDO0NBQ0Y7QUFSRCx5Q0FRQyJ9
@@ -0,0 +1,14 @@
1
+ import type { ForestSchemaCollection } from '@forestadmin/forestadmin-client';
2
+ export interface ForestSchemaClientOptions {
3
+ forestServerUrl: string;
4
+ envSecret: string;
5
+ }
6
+ export interface SchemaFetcher {
7
+ fetchSchema(): Promise<ForestSchemaCollection[]>;
8
+ }
9
+ export default class ForestSchemaClient implements SchemaFetcher {
10
+ private readonly schemaService;
11
+ constructor({ forestServerUrl, envSecret }: ForestSchemaClientOptions);
12
+ fetchSchema(): Promise<ForestSchemaCollection[]>;
13
+ }
14
+ //# sourceMappingURL=forest-schema-client.d.ts.map
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const forestadmin_client_1 = require("@forestadmin/forestadmin-client");
4
+ class ForestSchemaClient {
5
+ constructor({ forestServerUrl, envSecret }) {
6
+ this.schemaService = new forestadmin_client_1.SchemaService(new forestadmin_client_1.ForestHttpApi(), { forestServerUrl, envSecret });
7
+ }
8
+ async fetchSchema() {
9
+ return this.schemaService.getSchema();
10
+ }
11
+ }
12
+ exports.default = ForestSchemaClient;
13
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9yZXN0LXNjaGVtYS1jbGllbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvcmVhZC1tb2RlbC9mb3Jlc3Qtc2NoZW1hLWNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUVBLHdFQUErRTtBQVcvRSxNQUFxQixrQkFBa0I7SUFHckMsWUFBWSxFQUFFLGVBQWUsRUFBRSxTQUFTLEVBQTZCO1FBQ25FLElBQUksQ0FBQyxhQUFhLEdBQUcsSUFBSSxrQ0FBYSxDQUFDLElBQUksa0NBQWEsRUFBRSxFQUFFLEVBQUUsZUFBZSxFQUFFLFNBQVMsRUFBRSxDQUFDLENBQUM7SUFDOUYsQ0FBQztJQUVELEtBQUssQ0FBQyxXQUFXO1FBQ2YsT0FBTyxJQUFJLENBQUMsYUFBYSxDQUFDLFNBQVMsRUFBRSxDQUFDO0lBQ3hDLENBQUM7Q0FDRjtBQVZELHFDQVVDIn0=
@@ -0,0 +1,20 @@
1
+ import type CapabilitiesCache from './capabilities-cache';
2
+ import type { CapabilitiesFetcher, CapabilitiesResult } from './capabilities-cache';
3
+ import type SchemaCache from './schema-cache';
4
+ import ReadModel from './read-model';
5
+ /**
6
+ * Single owner of the coupled schema + capabilities lifecycle. A successful schema refresh (a bump
7
+ * of the cache `revision`) rebuilds the read-model and clears capabilities atomically, so the
8
+ * allow-list and capabilities never split-brain across schema generations.
9
+ */
10
+ export default class ReadModelStore {
11
+ private readonly schemaCache;
12
+ private readonly capabilitiesCache;
13
+ private builtRevision;
14
+ private readModel;
15
+ constructor(schemaCache: SchemaCache, capabilitiesCache: CapabilitiesCache);
16
+ getReadModel(): Promise<ReadModel>;
17
+ getCapabilities(collection: string, fetcher: CapabilitiesFetcher): Promise<CapabilitiesResult>;
18
+ ageSeconds(): number | undefined;
19
+ }
20
+ //# sourceMappingURL=read-model-store.d.ts.map
@@ -0,0 +1,43 @@
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
+ const read_model_1 = __importDefault(require("./read-model"));
7
+ /**
8
+ * Single owner of the coupled schema + capabilities lifecycle. A successful schema refresh (a bump
9
+ * of the cache `revision`) rebuilds the read-model and clears capabilities atomically, so the
10
+ * allow-list and capabilities never split-brain across schema generations.
11
+ */
12
+ class ReadModelStore {
13
+ constructor(schemaCache, capabilitiesCache) {
14
+ this.builtRevision = -1;
15
+ this.readModel = null;
16
+ this.schemaCache = schemaCache;
17
+ this.capabilitiesCache = capabilitiesCache;
18
+ }
19
+ async getReadModel() {
20
+ const collections = await this.schemaCache.get();
21
+ if (this.schemaCache.revision !== this.builtRevision || !this.readModel) {
22
+ this.readModel = new read_model_1.default(collections);
23
+ this.builtRevision = this.schemaCache.revision;
24
+ this.capabilitiesCache.clear();
25
+ }
26
+ return this.readModel;
27
+ }
28
+ async getCapabilities(collection, fetcher) {
29
+ // Ensure any pending schema refresh (and its capabilities invalidation) runs first.
30
+ await this.getReadModel();
31
+ // TODO(wiring): possible TOCTOU once this is called from request handling. A concurrent schema
32
+ // refresh can clear capabilities while this fetch is in flight, so the caller could receive
33
+ // capabilities from the previous schema generation alongside the new allow-list. When wiring
34
+ // the data endpoints, re-check `schemaCache.revision` after the fetch resolves and retry on a
35
+ // mismatch so capabilities and schema stay atomically coupled.
36
+ return this.capabilitiesCache.get(collection, fetcher);
37
+ }
38
+ ageSeconds() {
39
+ return this.schemaCache.ageSeconds();
40
+ }
41
+ }
42
+ exports.default = ReadModelStore;
43
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVhZC1tb2RlbC1zdG9yZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9yZWFkLW1vZGVsL3JlYWQtbW9kZWwtc3RvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFJQSw4REFBcUM7QUFFckM7Ozs7R0FJRztBQUNILE1BQXFCLGNBQWM7SUFPakMsWUFBWSxXQUF3QixFQUFFLGlCQUFvQztRQUhsRSxrQkFBYSxHQUFHLENBQUMsQ0FBQyxDQUFDO1FBQ25CLGNBQVMsR0FBcUIsSUFBSSxDQUFDO1FBR3pDLElBQUksQ0FBQyxXQUFXLEdBQUcsV0FBVyxDQUFDO1FBQy9CLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxpQkFBaUIsQ0FBQztJQUM3QyxDQUFDO0lBRUQsS0FBSyxDQUFDLFlBQVk7UUFDaEIsTUFBTSxXQUFXLEdBQUcsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBRWpELElBQUksSUFBSSxDQUFDLFdBQVcsQ0FBQyxRQUFRLEtBQUssSUFBSSxDQUFDLGFBQWEsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUN4RSxJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksb0JBQVMsQ0FBQyxXQUFXLENBQUMsQ0FBQztZQUM1QyxJQUFJLENBQUMsYUFBYSxHQUFHLElBQUksQ0FBQyxXQUFXLENBQUMsUUFBUSxDQUFDO1lBQy9DLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNqQyxDQUFDO1FBRUQsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFDO0lBQ3hCLENBQUM7SUFFRCxLQUFLLENBQUMsZUFBZSxDQUNuQixVQUFrQixFQUNsQixPQUE0QjtRQUU1QixvRkFBb0Y7UUFDcEYsTUFBTSxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7UUFFMUIsK0ZBQStGO1FBQy9GLDRGQUE0RjtRQUM1Riw2RkFBNkY7UUFDN0YsOEZBQThGO1FBQzlGLCtEQUErRDtRQUMvRCxPQUFPLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxHQUFHLENBQUMsVUFBVSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ3pELENBQUM7SUFFRCxVQUFVO1FBQ1IsT0FBTyxJQUFJLENBQUMsV0FBVyxDQUFDLFVBQVUsRUFBRSxDQUFDO0lBQ3ZDLENBQUM7Q0FDRjtBQTFDRCxpQ0EwQ0MifQ==
@@ -0,0 +1,32 @@
1
+ import type { ActionEndpointsByCollection } from '@forestadmin/agent-client';
2
+ import type { ForestSchemaCollection } from '@forestadmin/forestadmin-client';
3
+ export type RelationshipType = 'BelongsTo' | 'HasOne' | 'HasMany' | 'BelongsToMany';
4
+ export type RelationTarget = {
5
+ type: RelationshipType;
6
+ polymorphic: false;
7
+ target: string;
8
+ } | {
9
+ type: RelationshipType;
10
+ polymorphic: true;
11
+ targets: string[];
12
+ };
13
+ /**
14
+ * The agent read-model derived from a schema. Names are collection-scoped: relations and actions
15
+ * are keyed by `(collection, name)` so the same relation/action name on two collections stays
16
+ * distinct. The action allow-list is exactly the endpoint-map keys — endpoint-less actions are
17
+ * not exposed.
18
+ */
19
+ export default class ReadModel {
20
+ private readonly collections;
21
+ private readonly relations;
22
+ private readonly actionEndpoints;
23
+ constructor(collections: ForestSchemaCollection[]);
24
+ private buildRelations;
25
+ private buildActionEndpoints;
26
+ isCollectionAllowed(collection: string): boolean;
27
+ isRelationAllowed(collection: string, relation: string): boolean;
28
+ getRelationTarget(collection: string, relation: string): RelationTarget | undefined;
29
+ isActionAllowed(collection: string, action: string): boolean;
30
+ getActionEndpoints(): ActionEndpointsByCollection;
31
+ }
32
+ //# sourceMappingURL=read-model.d.ts.map
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ function toRelationTarget(field) {
4
+ if (!field.relationship)
5
+ return null;
6
+ const type = field.relationship;
7
+ if (field.polymorphicReferencedModels && field.polymorphicReferencedModels.length > 0) {
8
+ return { type, polymorphic: true, targets: [...field.polymorphicReferencedModels] };
9
+ }
10
+ if (field.reference) {
11
+ // reference is `${foreignCollection}.${key}`; the collection name may itself contain dots
12
+ // (e.g. mongoose nested collections like `User.address`), so drop only the trailing key.
13
+ return { type, polymorphic: false, target: field.reference.split('.').slice(0, -1).join('.') };
14
+ }
15
+ return null;
16
+ }
17
+ function deepFreeze(value) {
18
+ if (value && typeof value === 'object') {
19
+ Object.values(value).forEach(deepFreeze);
20
+ Object.freeze(value);
21
+ }
22
+ return value;
23
+ }
24
+ /**
25
+ * The agent read-model derived from a schema. Names are collection-scoped: relations and actions
26
+ * are keyed by `(collection, name)` so the same relation/action name on two collections stays
27
+ * distinct. The action allow-list is exactly the endpoint-map keys — endpoint-less actions are
28
+ * not exposed.
29
+ */
30
+ class ReadModel {
31
+ constructor(collections) {
32
+ this.collections = new Set();
33
+ this.relations = new Map();
34
+ // Null-prototype so an action/collection named like an Object.prototype member
35
+ // (`toString`, `constructor`, `__proto__`, …) can't falsely pass the allow-list.
36
+ this.actionEndpoints = Object.create(null);
37
+ for (const collection of collections) {
38
+ this.collections.add(collection.name);
39
+ this.buildRelations(collection);
40
+ this.buildActionEndpoints(collection);
41
+ }
42
+ // Freeze the exposed structures so a consumer cannot mutate the shared cached read-model.
43
+ deepFreeze(this.actionEndpoints);
44
+ this.relations.forEach(byRelation => byRelation.forEach(deepFreeze));
45
+ }
46
+ buildRelations(collection) {
47
+ const relations = new Map();
48
+ for (const field of collection.fields ?? []) {
49
+ const target = toRelationTarget(field);
50
+ if (target)
51
+ relations.set(field.field, target);
52
+ }
53
+ this.relations.set(collection.name, relations);
54
+ }
55
+ buildActionEndpoints(collection) {
56
+ const actions = collection.actions ?? [];
57
+ const withEndpoint = actions.filter(action => Boolean(action.endpoint));
58
+ if (withEndpoint.length === 0)
59
+ return;
60
+ this.actionEndpoints[collection.name] = Object.create(null);
61
+ for (const action of withEndpoint) {
62
+ // Copy fields/hooks so a mutating consumer can't corrupt the shared cached schema, and
63
+ // default them defensively: the schema is cast from `Record<string, unknown>`, so a
64
+ // malformed action could omit them at runtime despite the declared type.
65
+ this.actionEndpoints[collection.name][action.name] = {
66
+ id: action.id,
67
+ name: action.name,
68
+ endpoint: action.endpoint,
69
+ hooks: { load: action.hooks?.load ?? false, change: [...(action.hooks?.change ?? [])] },
70
+ fields: (action.fields ?? []).map(field => ({ ...field })),
71
+ };
72
+ }
73
+ }
74
+ isCollectionAllowed(collection) {
75
+ return this.collections.has(collection);
76
+ }
77
+ isRelationAllowed(collection, relation) {
78
+ return this.relations.get(collection)?.has(relation) ?? false;
79
+ }
80
+ getRelationTarget(collection, relation) {
81
+ return this.relations.get(collection)?.get(relation);
82
+ }
83
+ isActionAllowed(collection, action) {
84
+ return this.actionEndpoints[collection]?.[action] !== undefined;
85
+ }
86
+ getActionEndpoints() {
87
+ return this.actionEndpoints;
88
+ }
89
+ }
90
+ exports.default = ReadModel;
91
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVhZC1tb2RlbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9yZWFkLW1vZGVsL3JlYWQtbW9kZWwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFTQSxTQUFTLGdCQUFnQixDQUFDLEtBQXdCO0lBQ2hELElBQUksQ0FBQyxLQUFLLENBQUMsWUFBWTtRQUFFLE9BQU8sSUFBSSxDQUFDO0lBRXJDLE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxZQUFZLENBQUM7SUFFaEMsSUFBSSxLQUFLLENBQUMsMkJBQTJCLElBQUksS0FBSyxDQUFDLDJCQUEyQixDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUN0RixPQUFPLEVBQUUsSUFBSSxFQUFFLFdBQVcsRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLENBQUMsR0FBRyxLQUFLLENBQUMsMkJBQTJCLENBQUMsRUFBRSxDQUFDO0lBQ3RGLENBQUM7SUFFRCxJQUFJLEtBQUssQ0FBQyxTQUFTLEVBQUUsQ0FBQztRQUNwQiwwRkFBMEY7UUFDMUYseUZBQXlGO1FBQ3pGLE9BQU8sRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsS0FBSyxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO0lBQ2pHLENBQUM7SUFFRCxPQUFPLElBQUksQ0FBQztBQUNkLENBQUM7QUFFRCxTQUFTLFVBQVUsQ0FBSSxLQUFRO0lBQzdCLElBQUksS0FBSyxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsRUFBRSxDQUFDO1FBQ3ZDLE1BQU0sQ0FBQyxNQUFNLENBQUMsS0FBZ0MsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUNwRSxNQUFNLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3ZCLENB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
@@ -0,0 +1,35 @@
1
+ import type { SchemaFetcher } from './forest-schema-client';
2
+ import type { Metrics } from '../ports/metrics-port';
3
+ import type { ForestSchemaCollection } from '@forestadmin/forestadmin-client';
4
+ export declare const ONE_DAY_MS: number;
5
+ export declare const SCHEMA_CACHE_REFRESH_ERROR = "schema_cache_refresh_error";
6
+ export declare const SCHEMA_CACHE_AGE_SECONDS = "schema_cache_age_seconds";
7
+ export interface SchemaCacheOptions {
8
+ fetcher: SchemaFetcher;
9
+ metrics: Metrics;
10
+ now?: () => number;
11
+ ttlMs?: number;
12
+ }
13
+ /**
14
+ * Caches the agent schema for 24h. The TTL only *triggers* a refresh attempt: the last good
15
+ * schema keeps being served until a refresh succeeds. A cold cache (no last good) surfaces a
16
+ * typed `SchemaUnavailableError`. A monotonic `revision` increments on each successful refresh so
17
+ * a consumer can detect a new schema generation explicitly.
18
+ */
19
+ export default class SchemaCache {
20
+ private readonly fetcher;
21
+ private readonly metrics;
22
+ private readonly now;
23
+ private readonly ttlMs;
24
+ private entry;
25
+ private inFlight;
26
+ private revisionValue;
27
+ constructor({ fetcher, metrics, now, ttlMs }: SchemaCacheOptions);
28
+ get(): Promise<ForestSchemaCollection[]>;
29
+ ageSeconds(): number | undefined;
30
+ get revision(): number;
31
+ private refresh;
32
+ private doRefresh;
33
+ private emitAge;
34
+ }
35
+ //# sourceMappingURL=schema-cache.d.ts.map
@@ -0,0 +1,82 @@
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.SCHEMA_CACHE_AGE_SECONDS = exports.SCHEMA_CACHE_REFRESH_ERROR = exports.ONE_DAY_MS = void 0;
7
+ const errors_1 = __importDefault(require("./errors"));
8
+ exports.ONE_DAY_MS = 24 * 60 * 60 * 1000;
9
+ exports.SCHEMA_CACHE_REFRESH_ERROR = 'schema_cache_refresh_error';
10
+ exports.SCHEMA_CACHE_AGE_SECONDS = 'schema_cache_age_seconds';
11
+ /**
12
+ * Caches the agent schema for 24h. The TTL only *triggers* a refresh attempt: the last good
13
+ * schema keeps being served until a refresh succeeds. A cold cache (no last good) surfaces a
14
+ * typed `SchemaUnavailableError`. A monotonic `revision` increments on each successful refresh so
15
+ * a consumer can detect a new schema generation explicitly.
16
+ */
17
+ class SchemaCache {
18
+ constructor({ fetcher, metrics, now = Date.now, ttlMs = exports.ONE_DAY_MS }) {
19
+ this.entry = null;
20
+ this.inFlight = null;
21
+ this.revisionValue = 0;
22
+ this.fetcher = fetcher;
23
+ this.metrics = metrics;
24
+ this.now = now;
25
+ this.ttlMs = ttlMs;
26
+ }
27
+ async get() {
28
+ if (this.entry && this.now() - this.entry.fetchedAt < this.ttlMs) {
29
+ this.emitAge();
30
+ return this.entry.collections;
31
+ }
32
+ return this.refresh();
33
+ }
34
+ ageSeconds() {
35
+ if (!this.entry)
36
+ return undefined;
37
+ return Math.floor((this.now() - this.entry.fetchedAt) / 1000);
38
+ }
39
+ get revision() {
40
+ return this.revisionValue;
41
+ }
42
+ async refresh() {
43
+ if (!this.inFlight) {
44
+ this.inFlight = this.doRefresh().finally(() => {
45
+ this.inFlight = null;
46
+ });
47
+ }
48
+ return this.inFlight;
49
+ }
50
+ async doRefresh() {
51
+ try {
52
+ const collections = await this.fetcher.fetchSchema();
53
+ // An agent always exposes collections, so an empty result is far likelier a broken response
54
+ // than a valid state. Caching it would silently deny everything for 24h — treat it as a
55
+ // failed fetch and fall through to the failure path below.
56
+ if (collections.length === 0)
57
+ throw new Error('Forest returned an empty schema');
58
+ this.entry = { collections, fetchedAt: this.now() };
59
+ this.revisionValue += 1;
60
+ this.emitAge();
61
+ return collections;
62
+ }
63
+ catch (error) {
64
+ this.metrics.increment(exports.SCHEMA_CACHE_REFRESH_ERROR);
65
+ // Warm cache: keep serving the last good schema (stale), do not poison — the next read
66
+ // re-attempts because `fetchedAt` is unchanged and the entry stays expired.
67
+ if (this.entry) {
68
+ this.emitAge();
69
+ return this.entry.collections;
70
+ }
71
+ // Cold cache: nothing to serve.
72
+ throw new errors_1.default(error);
73
+ }
74
+ }
75
+ emitAge() {
76
+ const age = this.ageSeconds();
77
+ if (age !== undefined)
78
+ this.metrics.gauge(exports.SCHEMA_CACHE_AGE_SECONDS, age);
79
+ }
80
+ }
81
+ exports.default = SchemaCache;
82
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2NoZW1hLWNhY2hlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3JlYWQtbW9kZWwvc2NoZW1hLWNhY2hlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7OztBQUlBLHNEQUE4QztBQUVqQyxRQUFBLFVBQVUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUM7QUFFakMsUUFBQSwwQkFBMEIsR0FBRyw0QkFBNEIsQ0FBQztBQUMxRCxRQUFBLHdCQUF3QixHQUFHLDBCQUEwQixDQUFDO0FBY25FOzs7OztHQUtHO0FBQ0gsTUFBcUIsV0FBVztJQVU5QixZQUFZLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxLQUFLLEdBQUcsa0JBQVUsRUFBc0I7UUFKaEYsVUFBSyxHQUFzQixJQUFJLENBQUM7UUFDaEMsYUFBUSxHQUE2QyxJQUFJLENBQUM7UUFDMUQsa0JBQWEsR0FBRyxDQUFDLENBQUM7UUFHeEIsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7UUFDdkIsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7UUFDdkIsSUFBSSxDQUFDLEdBQUcsR0FBRyxHQUFHLENBQUM7UUFDZixJQUFJLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FBQztJQUNyQixDQUFDO0lBRUQsS0FBSyxDQUFDLEdBQUc7UUFDUCxJQUFJLElBQUksQ0FBQyxLQUFLLElBQUksSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsU0FBUyxHQUFHLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUNqRSxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7WUFFZixPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsV0FBVyxDQUFDO1FBQ2hDLENBQUM7UUFFRCxPQUFPLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUN4QixDQUFDO0lBRUQsVUFBVTtRQUNSLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSztZQUFFLE9BQU8sU0FBUyxDQUFDO1FBRWxDLE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLFNBQVMsQ0FBQyxHQUFHLElBQUksQ0FBQyxDQUFDO0lBQ2hFLENBQUM7SUFFRCxJQUFJLFFBQVE7UUFDVixPQUFPLElBQUksQ0FBQyxhQUFhLENBQUM7SUFDNUIsQ0FBQztJQUVPLEtBQUssQ0FBQyxPQUFPO1FBQ25CLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDbkIsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUMsU0FBUyxFQUFFLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRTtnQkFDNUMsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUM7WUFDdkIsQ0FBQyxDQUFDLENBQUM7UUFDTCxDQUFDO1FBRUQsT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFDO0lBQ3ZCLENBQUM7SUFFTyxLQUFLLENBQUMsU0FBUztRQUNyQixJQUFJLENBQUM7WUFDSCxNQUFNLFdBQVcsR0FBRyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsV0FBVyxFQUFFLENBQUM7WUFFckQsNEZBQTRGO1lBQzVGLHdGQUF3RjtZQUN4RiwyREFBMkQ7WUFDM0QsSUFBSSxXQUFXLENBQUMsTUFBTSxLQUFLLENBQUM7Z0JBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyxpQ0FBaUMsQ0FBQyxDQUFDO1lBRWpGLElBQUksQ0FBQyxLQUFLLEdBQUcsRUFBRSxXQUFXLEVBQUUsU0FBUyxFQUFFLElBQUksQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDO1lBQ3BELElBQUksQ0FBQyxhQUFhLElBQUksQ0FBQyxDQUFDO1lBQ3hCLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUVmLE9BQU8sV0FBVyxDQUFDO1FBQ3JCLENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsa0NBQTBCLENBQUMsQ0FBQztZQUVuRCx1RkFBdUY7WUFDdkYsNEVBQTRFO1lBQzVFLElBQUksSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO2dCQUNmLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztnQkFFZixPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsV0FBVyxDQUFDO1lBQ2hDLENBQUM7WUFFRCxnQ0FBZ0M7WUFDaEMsTUFBTSxJQUFJLGdCQUFzQixDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQzFDLENBQUM7SUFDSCxDQUFDO0lBRU8sT0FBTztRQUNiLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUM5QixJQUFJLEdBQUcsS0FBSyxTQUFTO1lBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsZ0NBQXdCLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDM0UsQ0FBQztDQUNGO0FBakZELDhCQWlGQyJ9
@@ -0,0 +1,2 @@
1
+ export default function assertNoRelationFieldPaths(paths: string[]): void;
2
+ //# sourceMappingURL=relation-field-guard.d.ts.map
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = assertNoRelationFieldPaths;
4
+ const bff_http_error_1 = require("../http/bff-http-error");
5
+ const RELATION_SEPARATOR = ':';
6
+ function assertNoRelationFieldPaths(paths) {
7
+ const offending = [];
8
+ for (const path of paths) {
9
+ if (path.includes(RELATION_SEPARATOR) && !offending.includes(path)) {
10
+ offending.push(path);
11
+ }
12
+ }
13
+ if (offending.length > 0)
14
+ throw (0, bff_http_error_1.relationFieldNotSupported)(offending);
15
+ }
16
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVsYXRpb24tZmllbGQtZ3VhcmQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdmFsaWRhdGlvbi9yZWxhdGlvbi1maWVsZC1ndWFyZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUlBLDZDQVVDO0FBZEQsMkRBQW1FO0FBRW5FLE1BQU0sa0JBQWtCLEdBQUcsR0FBRyxDQUFDO0FBRS9CLFNBQXdCLDBCQUEwQixDQUFDLEtBQWU7SUFDaEUsTUFBTSxTQUFTLEdBQWEsRUFBRSxDQUFDO0lBRS9CLEtBQUssTUFBTSxJQUFJLElBQUksS0FBSyxFQUFFLENBQUM7UUFDekIsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLGtCQUFrQixDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7WUFDbkUsU0FBUyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUN2QixDQUFDO0lBQ0gsQ0FBQztJQUVELElBQUksU0FBUyxDQUFDLE1BQU0sR0FBRyxDQUFDO1FBQUUsTUFBTSxJQUFBLDBDQUF5QixFQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQ3ZFLENBQUMifQ==
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forestadmin/agent-bff",
3
- "version": "1.4.1",
3
+ "version": "1.6.0",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "forest-bff": "dist/cli.js"
@@ -31,6 +31,7 @@
31
31
  "test": "jest"
32
32
  },
33
33
  "dependencies": {
34
+ "@forestadmin/agent-client": "1.10.1",
34
35
  "@forestadmin/forestadmin-client": "1.40.4",
35
36
  "@koa/bodyparser": "^6.1.0",
36
37
  "jsonwebtoken": "^9.0.3",