@adaptic/backend-legacy 0.0.48 → 0.0.50

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,88 @@
1
+ import { Request, Response, Router } from 'express';
2
+ import { Registry, Counter, Histogram, Gauge } from 'prom-client';
3
+ /**
4
+ * Dedicated Prometheus registry for backend-legacy metrics.
5
+ * Using a dedicated registry avoids conflicts with default Node.js metrics
6
+ * that might be registered by other libraries.
7
+ */
8
+ export declare const metricsRegistry: Registry<"text/plain; version=0.0.4; charset=utf-8">;
9
+ /** Counts total HTTP requests by method, route, and status code */
10
+ export declare const httpRequestsTotal: Counter<"method" | "route" | "status_code">;
11
+ /** Histogram of HTTP request durations in seconds */
12
+ export declare const httpRequestDuration: Histogram<"method" | "route" | "status_code">;
13
+ /** Counts total GraphQL operations by type and name */
14
+ export declare const graphqlOperationsTotal: Counter<"status" | "operation_type" | "operation_name">;
15
+ /** Histogram of GraphQL operation durations in seconds */
16
+ export declare const graphqlOperationDuration: Histogram<"operation_type" | "operation_name">;
17
+ /** Counts GraphQL errors by operation and error code */
18
+ export declare const graphqlErrorsTotal: Counter<"operation_type" | "operation_name" | "error_code">;
19
+ /** Histogram of Prisma query durations in seconds */
20
+ export declare const dbQueryDuration: Histogram<"operation" | "model">;
21
+ /** Gauge for active database connections */
22
+ export declare const dbConnectionsActive: Gauge<string>;
23
+ /** Gauge for active GraphQL subscriptions */
24
+ export declare const activeSubscriptions: Gauge<string>;
25
+ /** Gauge for active WebSocket connections */
26
+ export declare const activeWebSocketConnections: Gauge<string>;
27
+ /**
28
+ * Initializes Prometheus metrics collection.
29
+ *
30
+ * Registers default Node.js metrics (CPU, memory, event loop, GC) and
31
+ * application-specific metrics for HTTP, GraphQL, and database operations.
32
+ *
33
+ * Environment variables:
34
+ * - PROMETHEUS_METRICS_ENABLED: Explicit on/off ('true'/'false'). Defaults to on in production/staging.
35
+ *
36
+ * @returns true if metrics were initialized, false if disabled
37
+ */
38
+ export declare function initMetrics(): boolean;
39
+ /**
40
+ * Express middleware that records HTTP request metrics.
41
+ * Tracks request count and duration for each method/route/status combination.
42
+ *
43
+ * Should be mounted early in the middleware chain to capture all requests.
44
+ */
45
+ export declare function metricsMiddleware(req: Request, res: Response, next: () => void): void;
46
+ /**
47
+ * Creates an Apollo Server plugin that records GraphQL operation metrics.
48
+ *
49
+ * Tracks:
50
+ * - Operation count by type (query/mutation/subscription) and name
51
+ * - Operation duration
52
+ * - Error count by operation and error code
53
+ *
54
+ * @returns Apollo Server plugin configuration
55
+ */
56
+ export declare function createMetricsPlugin(): {
57
+ requestDidStart: () => Promise<{
58
+ willSendResponse: (requestContext: {
59
+ operationName?: string | null;
60
+ operation?: {
61
+ operation: string;
62
+ } | null;
63
+ response: {
64
+ body: {
65
+ kind: string;
66
+ singleResult?: {
67
+ errors?: ReadonlyArray<{
68
+ extensions?: {
69
+ code?: string;
70
+ };
71
+ }>;
72
+ };
73
+ };
74
+ };
75
+ }) => Promise<void>;
76
+ }>;
77
+ };
78
+ /**
79
+ * Creates an Express router that exposes Prometheus metrics at GET /metrics.
80
+ *
81
+ * The endpoint returns metrics in Prometheus text exposition format.
82
+ * This should be scraped by a Prometheus server at regular intervals.
83
+ *
84
+ * Note: This endpoint should be protected in production (e.g., by firewall rules
85
+ * or by requiring a bearer token). It is excluded from rate limiting by default.
86
+ */
87
+ export declare function createMetricsRouter(): Router;
88
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../src/config/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EACL,QAAQ,EACR,OAAO,EACP,SAAS,EACT,KAAK,EAEN,MAAM,aAAa,CAAC;AAKrB;;;;GAIG;AACH,eAAO,MAAM,eAAe,sDAAiB,CAAC;AAO9C,mEAAmE;AACnE,eAAO,MAAM,iBAAiB,6CAK5B,CAAC;AAEH,qDAAqD;AACrD,eAAO,MAAM,mBAAmB,+CAM9B,CAAC;AAEH,uDAAuD;AACvD,eAAO,MAAM,sBAAsB,yDAKjC,CAAC;AAEH,0DAA0D;AAC1D,eAAO,MAAM,wBAAwB,gDAMnC,CAAC;AAEH,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,6DAK7B,CAAC;AAMH,qDAAqD;AACrD,eAAO,MAAM,eAAe,kCAM1B,CAAC;AAEH,4CAA4C;AAC5C,eAAO,MAAM,mBAAmB,eAI9B,CAAC;AAMH,6CAA6C;AAC7C,eAAO,MAAM,mBAAmB,eAI9B,CAAC;AAEH,6CAA6C;AAC7C,eAAO,MAAM,0BAA0B,eAIrC,CAAC;AAiCH;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,IAAI,OAAO,CAmBrC;AAMD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,IAAI,GAAG,IAAI,CAoBrF;AAMD;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,IAAI;IACrC,eAAe,EAAE,MAAM,OAAO,CAAC;QAC7B,gBAAgB,EAAE,CAAC,cAAc,EAAE;YACjC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;YAC9B,SAAS,CAAC,EAAE;gBAAE,SAAS,EAAE,MAAM,CAAA;aAAE,GAAG,IAAI,CAAC;YACzC,QAAQ,EAAE;gBAAE,IAAI,EAAE;oBAAE,IAAI,EAAE,MAAM,CAAC;oBAAC,YAAY,CAAC,EAAE;wBAAE,MAAM,CAAC,EAAE,aAAa,CAAC;4BAAE,UAAU,CAAC,EAAE;gCAAE,IAAI,CAAC,EAAE,MAAM,CAAA;6BAAE,CAAA;yBAAE,CAAC,CAAA;qBAAE,CAAA;iBAAE,CAAA;aAAE,CAAC;SACrH,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACrB,CAAC,CAAC;CACJ,CAiDA;AAMD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAiB5C"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../../src/config/metrics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,248 @@
1
+ import { Router } from 'express';
2
+ import { Registry, Counter, Histogram, Gauge, collectDefaultMetrics, } from 'prom-client';
3
+ import { logger } from '../utils/logger.mjs';
4
+ const SERVICE_NAME = 'backend-legacy';
5
+ /**
6
+ * Dedicated Prometheus registry for backend-legacy metrics.
7
+ * Using a dedicated registry avoids conflicts with default Node.js metrics
8
+ * that might be registered by other libraries.
9
+ */
10
+ export const metricsRegistry = new Registry();
11
+ metricsRegistry.setDefaultLabels({ service: SERVICE_NAME });
12
+ // ---------------------------------------------------------------------------
13
+ // HTTP / GraphQL request metrics
14
+ // ---------------------------------------------------------------------------
15
+ /** Counts total HTTP requests by method, route, and status code */
16
+ export const httpRequestsTotal = new Counter({
17
+ name: 'http_requests_total',
18
+ help: 'Total number of HTTP requests',
19
+ labelNames: ['method', 'route', 'status_code'],
20
+ registers: [metricsRegistry],
21
+ });
22
+ /** Histogram of HTTP request durations in seconds */
23
+ export const httpRequestDuration = new Histogram({
24
+ name: 'http_request_duration_seconds',
25
+ help: 'Duration of HTTP requests in seconds',
26
+ labelNames: ['method', 'route', 'status_code'],
27
+ buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
28
+ registers: [metricsRegistry],
29
+ });
30
+ /** Counts total GraphQL operations by type and name */
31
+ export const graphqlOperationsTotal = new Counter({
32
+ name: 'graphql_operations_total',
33
+ help: 'Total number of GraphQL operations',
34
+ labelNames: ['operation_type', 'operation_name', 'status'],
35
+ registers: [metricsRegistry],
36
+ });
37
+ /** Histogram of GraphQL operation durations in seconds */
38
+ export const graphqlOperationDuration = new Histogram({
39
+ name: 'graphql_operation_duration_seconds',
40
+ help: 'Duration of GraphQL operations in seconds',
41
+ labelNames: ['operation_type', 'operation_name'],
42
+ buckets: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30],
43
+ registers: [metricsRegistry],
44
+ });
45
+ /** Counts GraphQL errors by operation and error code */
46
+ export const graphqlErrorsTotal = new Counter({
47
+ name: 'graphql_errors_total',
48
+ help: 'Total number of GraphQL errors',
49
+ labelNames: ['operation_type', 'operation_name', 'error_code'],
50
+ registers: [metricsRegistry],
51
+ });
52
+ // ---------------------------------------------------------------------------
53
+ // Database metrics
54
+ // ---------------------------------------------------------------------------
55
+ /** Histogram of Prisma query durations in seconds */
56
+ export const dbQueryDuration = new Histogram({
57
+ name: 'db_query_duration_seconds',
58
+ help: 'Duration of database queries in seconds',
59
+ labelNames: ['operation', 'model'],
60
+ buckets: [0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 5],
61
+ registers: [metricsRegistry],
62
+ });
63
+ /** Gauge for active database connections */
64
+ export const dbConnectionsActive = new Gauge({
65
+ name: 'db_connections_active',
66
+ help: 'Number of active database connections (approximate)',
67
+ registers: [metricsRegistry],
68
+ });
69
+ // ---------------------------------------------------------------------------
70
+ // WebSocket / Subscriptions metrics
71
+ // ---------------------------------------------------------------------------
72
+ /** Gauge for active GraphQL subscriptions */
73
+ export const activeSubscriptions = new Gauge({
74
+ name: 'graphql_active_subscriptions',
75
+ help: 'Number of active GraphQL subscriptions',
76
+ registers: [metricsRegistry],
77
+ });
78
+ /** Gauge for active WebSocket connections */
79
+ export const activeWebSocketConnections = new Gauge({
80
+ name: 'websocket_active_connections',
81
+ help: 'Number of active WebSocket connections',
82
+ registers: [metricsRegistry],
83
+ });
84
+ // ---------------------------------------------------------------------------
85
+ // Application metrics
86
+ // ---------------------------------------------------------------------------
87
+ /** Gauge for application uptime in seconds */
88
+ const uptimeGauge = new Gauge({
89
+ name: 'app_uptime_seconds',
90
+ help: 'Application uptime in seconds',
91
+ registers: [metricsRegistry],
92
+ });
93
+ const startedAt = Date.now();
94
+ // ---------------------------------------------------------------------------
95
+ // Initialization
96
+ // ---------------------------------------------------------------------------
97
+ /**
98
+ * Determines if metrics collection is enabled.
99
+ * Enabled by default in production/staging; can be explicitly controlled
100
+ * via PROMETHEUS_METRICS_ENABLED env var.
101
+ */
102
+ function isMetricsEnabled() {
103
+ const explicitSetting = process.env.PROMETHEUS_METRICS_ENABLED;
104
+ if (explicitSetting !== undefined) {
105
+ return explicitSetting === 'true' || explicitSetting === '1';
106
+ }
107
+ const env = process.env.NODE_ENV || 'development';
108
+ return env === 'production' || env === 'staging';
109
+ }
110
+ /**
111
+ * Initializes Prometheus metrics collection.
112
+ *
113
+ * Registers default Node.js metrics (CPU, memory, event loop, GC) and
114
+ * application-specific metrics for HTTP, GraphQL, and database operations.
115
+ *
116
+ * Environment variables:
117
+ * - PROMETHEUS_METRICS_ENABLED: Explicit on/off ('true'/'false'). Defaults to on in production/staging.
118
+ *
119
+ * @returns true if metrics were initialized, false if disabled
120
+ */
121
+ export function initMetrics() {
122
+ if (!isMetricsEnabled()) {
123
+ logger.info('Prometheus metrics collection is disabled', {
124
+ reason: 'PROMETHEUS_METRICS_ENABLED not set or environment is development',
125
+ });
126
+ return false;
127
+ }
128
+ // Register default Node.js metrics (memory, CPU, event loop lag, etc.)
129
+ collectDefaultMetrics({ register: metricsRegistry });
130
+ // Update uptime gauge periodically
131
+ const UPTIME_UPDATE_INTERVAL_MS = 15000;
132
+ setInterval(() => {
133
+ uptimeGauge.set((Date.now() - startedAt) / 1000);
134
+ }, UPTIME_UPDATE_INTERVAL_MS);
135
+ logger.info('Prometheus metrics initialized');
136
+ return true;
137
+ }
138
+ // ---------------------------------------------------------------------------
139
+ // Express middleware
140
+ // ---------------------------------------------------------------------------
141
+ /**
142
+ * Express middleware that records HTTP request metrics.
143
+ * Tracks request count and duration for each method/route/status combination.
144
+ *
145
+ * Should be mounted early in the middleware chain to capture all requests.
146
+ */
147
+ export function metricsMiddleware(req, res, next) {
148
+ if (!isMetricsEnabled()) {
149
+ next();
150
+ return;
151
+ }
152
+ const start = process.hrtime.bigint();
153
+ res.on('finish', () => {
154
+ const durationNs = Number(process.hrtime.bigint() - start);
155
+ const durationSeconds = durationNs / 1e9;
156
+ const route = req.route?.path || req.path || 'unknown';
157
+ const method = req.method;
158
+ const statusCode = String(res.statusCode);
159
+ httpRequestsTotal.inc({ method, route, status_code: statusCode });
160
+ httpRequestDuration.observe({ method, route, status_code: statusCode }, durationSeconds);
161
+ });
162
+ next();
163
+ }
164
+ // ---------------------------------------------------------------------------
165
+ // Apollo Server plugin
166
+ // ---------------------------------------------------------------------------
167
+ /**
168
+ * Creates an Apollo Server plugin that records GraphQL operation metrics.
169
+ *
170
+ * Tracks:
171
+ * - Operation count by type (query/mutation/subscription) and name
172
+ * - Operation duration
173
+ * - Error count by operation and error code
174
+ *
175
+ * @returns Apollo Server plugin configuration
176
+ */
177
+ export function createMetricsPlugin() {
178
+ return {
179
+ async requestDidStart() {
180
+ const start = process.hrtime.bigint();
181
+ return {
182
+ async willSendResponse(requestContext) {
183
+ const durationNs = Number(process.hrtime.bigint() - start);
184
+ const durationSeconds = durationNs / 1e9;
185
+ const operationName = requestContext.operationName || 'anonymous';
186
+ const operationType = requestContext.operation?.operation || 'unknown';
187
+ graphqlOperationDuration.observe({ operation_type: operationType, operation_name: operationName }, durationSeconds);
188
+ const responseBody = requestContext.response.body;
189
+ const hasErrors = responseBody.kind === 'single' &&
190
+ responseBody.singleResult?.errors &&
191
+ responseBody.singleResult.errors.length > 0;
192
+ if (hasErrors && responseBody.kind === 'single' && responseBody.singleResult?.errors) {
193
+ for (const error of responseBody.singleResult.errors) {
194
+ const errorCode = error.extensions?.code || 'INTERNAL_SERVER_ERROR';
195
+ graphqlErrorsTotal.inc({
196
+ operation_type: operationType,
197
+ operation_name: operationName,
198
+ error_code: String(errorCode),
199
+ });
200
+ }
201
+ graphqlOperationsTotal.inc({
202
+ operation_type: operationType,
203
+ operation_name: operationName,
204
+ status: 'error',
205
+ });
206
+ }
207
+ else {
208
+ graphqlOperationsTotal.inc({
209
+ operation_type: operationType,
210
+ operation_name: operationName,
211
+ status: 'success',
212
+ });
213
+ }
214
+ },
215
+ };
216
+ },
217
+ };
218
+ }
219
+ // ---------------------------------------------------------------------------
220
+ // Metrics endpoint
221
+ // ---------------------------------------------------------------------------
222
+ /**
223
+ * Creates an Express router that exposes Prometheus metrics at GET /metrics.
224
+ *
225
+ * The endpoint returns metrics in Prometheus text exposition format.
226
+ * This should be scraped by a Prometheus server at regular intervals.
227
+ *
228
+ * Note: This endpoint should be protected in production (e.g., by firewall rules
229
+ * or by requiring a bearer token). It is excluded from rate limiting by default.
230
+ */
231
+ export function createMetricsRouter() {
232
+ const router = Router();
233
+ router.get('/metrics', async (_req, res) => {
234
+ try {
235
+ const metrics = await metricsRegistry.metrics();
236
+ res.set('Content-Type', metricsRegistry.contentType);
237
+ res.status(200).send(metrics);
238
+ }
239
+ catch (metricsError) {
240
+ logger.error('Failed to generate metrics', {
241
+ error: metricsError instanceof Error ? metricsError.message : String(metricsError),
242
+ });
243
+ res.status(500).send('Failed to generate metrics');
244
+ }
245
+ });
246
+ return router;
247
+ }
248
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1,40 @@
1
+ import { ApolloServerPluginCacheControlDisabled } from '@apollo/server/plugin/disabled';
2
+ /**
3
+ * Simple in-memory key-value cache implementing Apollo's KeyValueCache interface.
4
+ * Provides LRU eviction when the cache reaches maximum capacity.
5
+ */
6
+ export declare class InMemoryAPQCache {
7
+ private cache;
8
+ private readonly maxSize;
9
+ constructor(maxSize?: number);
10
+ get(key: string): Promise<string | undefined>;
11
+ set(key: string, value: string, options?: {
12
+ ttl?: number | null;
13
+ }): Promise<void>;
14
+ delete(key: string): Promise<boolean>;
15
+ /** Returns the current number of cached entries */
16
+ get size(): number;
17
+ /** Clears all cached entries */
18
+ clear(): void;
19
+ }
20
+ /**
21
+ * Determines if APQ (Automatic Persisted Queries) is enabled.
22
+ * Enabled by default. Disable via APQ_ENABLED=false.
23
+ */
24
+ export declare function isAPQEnabled(): boolean;
25
+ /**
26
+ * Creates and returns an APQ cache instance for use with Apollo Server.
27
+ *
28
+ * Apollo Server 5 enables APQ by default when a cache is available.
29
+ * To disable APQ entirely, set APQ_ENABLED=false.
30
+ *
31
+ * @returns The configured APQ cache, or undefined if APQ is disabled
32
+ */
33
+ export declare function createAPQCache(): InMemoryAPQCache | undefined;
34
+ /**
35
+ * Returns the Apollo Server cache control plugin configuration.
36
+ * When APQ is disabled, this returns the cache control disabled plugin
37
+ * to explicitly disable caching behavior.
38
+ */
39
+ export declare function getCacheControlPlugin(): ReturnType<typeof ApolloServerPluginCacheControlDisabled> | undefined;
40
+ //# sourceMappingURL=persisted-queries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persisted-queries.d.ts","sourceRoot":"","sources":["../../../src/config/persisted-queries.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sCAAsC,EAAE,MAAM,gCAAgC,CAAC;AAwCxF;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,OAAO,CAAC,EAAE,MAAM;IAKtB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAgB7C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAcjF,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI3C,mDAAmD;IACnD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,gCAAgC;IAChC,KAAK,IAAI,IAAI;CAGd;AAED;;;GAGG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAMtC;AAcD;;;;;;;GAOG;AACH,wBAAgB,cAAc,IAAI,gBAAgB,GAAG,SAAS,CAW7D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,UAAU,CAAC,OAAO,sCAAsC,CAAC,GAAG,SAAS,CAK7G"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persisted-queries.js","sourceRoot":"","sources":["../../../src/config/persisted-queries.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,114 @@
1
+ import { ApolloServerPluginCacheControlDisabled } from '@apollo/server/plugin/disabled';
2
+ import { logger } from '../utils/logger.mjs';
3
+ /**
4
+ * Default cache TTL for persisted query hashes in seconds.
5
+ * Set to 0 to disable TTL (hashes persist indefinitely in memory).
6
+ */
7
+ const DEFAULT_CACHE_TTL_SECONDS = 0;
8
+ /**
9
+ * Maximum size of the in-memory persisted query cache.
10
+ * Once exceeded, oldest entries are evicted (LRU).
11
+ */
12
+ const DEFAULT_MAX_CACHE_SIZE = 1000;
13
+ /**
14
+ * Simple in-memory key-value cache implementing Apollo's KeyValueCache interface.
15
+ * Provides LRU eviction when the cache reaches maximum capacity.
16
+ */
17
+ export class InMemoryAPQCache {
18
+ cache;
19
+ maxSize;
20
+ constructor(maxSize) {
21
+ this.maxSize = maxSize || DEFAULT_MAX_CACHE_SIZE;
22
+ this.cache = new Map();
23
+ }
24
+ async get(key) {
25
+ const entry = this.cache.get(key);
26
+ if (!entry)
27
+ return undefined;
28
+ // Check TTL expiration
29
+ if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {
30
+ this.cache.delete(key);
31
+ return undefined;
32
+ }
33
+ // Move to end for LRU ordering (Map preserves insertion order)
34
+ this.cache.delete(key);
35
+ this.cache.set(key, entry);
36
+ return entry.value;
37
+ }
38
+ async set(key, value, options) {
39
+ // Evict oldest entry if at capacity
40
+ if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
41
+ const oldestKey = this.cache.keys().next().value;
42
+ if (oldestKey !== undefined) {
43
+ this.cache.delete(oldestKey);
44
+ }
45
+ }
46
+ const ttlSeconds = options?.ttl ?? DEFAULT_CACHE_TTL_SECONDS;
47
+ const expiresAt = ttlSeconds > 0 ? Date.now() + ttlSeconds * 1000 : null;
48
+ this.cache.set(key, { value, expiresAt });
49
+ }
50
+ async delete(key) {
51
+ return this.cache.delete(key);
52
+ }
53
+ /** Returns the current number of cached entries */
54
+ get size() {
55
+ return this.cache.size;
56
+ }
57
+ /** Clears all cached entries */
58
+ clear() {
59
+ this.cache.clear();
60
+ }
61
+ }
62
+ /**
63
+ * Determines if APQ (Automatic Persisted Queries) is enabled.
64
+ * Enabled by default. Disable via APQ_ENABLED=false.
65
+ */
66
+ export function isAPQEnabled() {
67
+ const setting = process.env.APQ_ENABLED;
68
+ if (setting !== undefined) {
69
+ return setting !== 'false' && setting !== '0';
70
+ }
71
+ return true;
72
+ }
73
+ /**
74
+ * Resolves the maximum APQ cache size from environment variables.
75
+ */
76
+ function getMaxCacheSize() {
77
+ const envValue = process.env.APQ_MAX_CACHE_SIZE;
78
+ if (envValue) {
79
+ const parsed = parseInt(envValue, 10);
80
+ if (!isNaN(parsed) && parsed > 0)
81
+ return parsed;
82
+ }
83
+ return DEFAULT_MAX_CACHE_SIZE;
84
+ }
85
+ /**
86
+ * Creates and returns an APQ cache instance for use with Apollo Server.
87
+ *
88
+ * Apollo Server 5 enables APQ by default when a cache is available.
89
+ * To disable APQ entirely, set APQ_ENABLED=false.
90
+ *
91
+ * @returns The configured APQ cache, or undefined if APQ is disabled
92
+ */
93
+ export function createAPQCache() {
94
+ if (!isAPQEnabled()) {
95
+ logger.info('Automatic Persisted Queries (APQ) disabled');
96
+ return undefined;
97
+ }
98
+ const maxSize = getMaxCacheSize();
99
+ const cache = new InMemoryAPQCache(maxSize);
100
+ logger.info('Automatic Persisted Queries (APQ) enabled', { maxCacheSize: maxSize });
101
+ return cache;
102
+ }
103
+ /**
104
+ * Returns the Apollo Server cache control plugin configuration.
105
+ * When APQ is disabled, this returns the cache control disabled plugin
106
+ * to explicitly disable caching behavior.
107
+ */
108
+ export function getCacheControlPlugin() {
109
+ if (!isAPQEnabled()) {
110
+ return ApolloServerPluginCacheControlDisabled();
111
+ }
112
+ return undefined;
113
+ }
114
+ //# sourceMappingURL=persisted-queries.js.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Initializes OpenTelemetry tracing for the backend-legacy service.
3
+ *
4
+ * Configures:
5
+ * - HTTP instrumentation for incoming/outgoing HTTP requests
6
+ * - Express instrumentation for route-level spans
7
+ * - GraphQL instrumentation for resolver-level spans
8
+ * - OTLP HTTP exporter for sending traces to a collector (e.g., Jaeger, Grafana Tempo)
9
+ *
10
+ * Environment variables:
11
+ * - OTEL_TRACING_ENABLED: Explicit on/off ('true'/'false'). Defaults to on in production/staging.
12
+ * - OTEL_EXPORTER_OTLP_ENDPOINT: Collector endpoint (default: http://localhost:4318/v1/traces)
13
+ * - OTEL_SERVICE_NAME: Override service name (default: 'backend-legacy')
14
+ *
15
+ * Call this function before any other imports that need instrumentation (e.g., before Express/Apollo setup).
16
+ */
17
+ export declare function initTracing(): void;
18
+ /**
19
+ * Gracefully shuts down the OpenTelemetry SDK.
20
+ * Should be called during application shutdown (SIGTERM/SIGINT handlers).
21
+ * Flushes any pending spans before shutting down.
22
+ */
23
+ export declare function shutdownTracing(): Promise<void>;
24
+ //# sourceMappingURL=tracing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../../../src/config/tracing.ts"],"names":[],"mappings":"AAiDA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAgDlC;AAED;;;;GAIG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAWrD"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracing.js","sourceRoot":"","sources":["../../../src/config/tracing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,122 @@
1
+ import { NodeSDK } from '@opentelemetry/sdk-node';
2
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
3
+ import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
4
+ import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
5
+ import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql';
6
+ import { resourceFromAttributes } from '@opentelemetry/resources';
7
+ import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
8
+ import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node';
9
+ import { logger } from '../utils/logger.mjs';
10
+ const SERVICE_NAME = 'backend-legacy';
11
+ /**
12
+ * Reads the package version for trace resource attributes.
13
+ * Falls back to 'unknown' if the version cannot be determined.
14
+ */
15
+ function getPackageVersion() {
16
+ try {
17
+ const pkg = require('../../package.json');
18
+ return pkg.version || 'unknown';
19
+ }
20
+ catch {
21
+ return 'unknown';
22
+ }
23
+ }
24
+ /**
25
+ * Resolves the OTLP endpoint from environment variables.
26
+ * Defaults to http://localhost:4318/v1/traces (standard OTLP HTTP endpoint).
27
+ */
28
+ function getOtlpEndpoint() {
29
+ return process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318/v1/traces';
30
+ }
31
+ /**
32
+ * Determines if tracing is enabled.
33
+ * Disabled by default in development; enabled in production and staging.
34
+ * Can be explicitly controlled via OTEL_TRACING_ENABLED env var.
35
+ */
36
+ function isTracingEnabled() {
37
+ const explicitSetting = process.env.OTEL_TRACING_ENABLED;
38
+ if (explicitSetting !== undefined) {
39
+ return explicitSetting === 'true' || explicitSetting === '1';
40
+ }
41
+ const env = process.env.NODE_ENV || 'development';
42
+ return env === 'production' || env === 'staging';
43
+ }
44
+ let sdk = null;
45
+ /**
46
+ * Initializes OpenTelemetry tracing for the backend-legacy service.
47
+ *
48
+ * Configures:
49
+ * - HTTP instrumentation for incoming/outgoing HTTP requests
50
+ * - Express instrumentation for route-level spans
51
+ * - GraphQL instrumentation for resolver-level spans
52
+ * - OTLP HTTP exporter for sending traces to a collector (e.g., Jaeger, Grafana Tempo)
53
+ *
54
+ * Environment variables:
55
+ * - OTEL_TRACING_ENABLED: Explicit on/off ('true'/'false'). Defaults to on in production/staging.
56
+ * - OTEL_EXPORTER_OTLP_ENDPOINT: Collector endpoint (default: http://localhost:4318/v1/traces)
57
+ * - OTEL_SERVICE_NAME: Override service name (default: 'backend-legacy')
58
+ *
59
+ * Call this function before any other imports that need instrumentation (e.g., before Express/Apollo setup).
60
+ */
61
+ export function initTracing() {
62
+ if (!isTracingEnabled()) {
63
+ logger.info('OpenTelemetry tracing is disabled', {
64
+ reason: 'OTEL_TRACING_ENABLED not set or environment is development',
65
+ });
66
+ return;
67
+ }
68
+ const endpoint = getOtlpEndpoint();
69
+ const serviceName = process.env.OTEL_SERVICE_NAME || SERVICE_NAME;
70
+ const serviceVersion = getPackageVersion();
71
+ const traceExporter = new OTLPTraceExporter({
72
+ url: endpoint,
73
+ });
74
+ const resource = resourceFromAttributes({
75
+ [ATTR_SERVICE_NAME]: serviceName,
76
+ [ATTR_SERVICE_VERSION]: serviceVersion,
77
+ 'deployment.environment': process.env.NODE_ENV || 'development',
78
+ });
79
+ sdk = new NodeSDK({
80
+ resource,
81
+ spanProcessors: [new BatchSpanProcessor(traceExporter)],
82
+ instrumentations: [
83
+ new HttpInstrumentation({
84
+ ignoreIncomingRequestHook: (req) => {
85
+ // Skip health check endpoints to reduce trace noise
86
+ return req.url === '/health' || req.url === '/metrics';
87
+ },
88
+ }),
89
+ new ExpressInstrumentation(),
90
+ new GraphQLInstrumentation({
91
+ mergeItems: true,
92
+ allowValues: true,
93
+ depth: 5,
94
+ }),
95
+ ],
96
+ });
97
+ sdk.start();
98
+ logger.info('OpenTelemetry tracing initialized', {
99
+ endpoint,
100
+ serviceName,
101
+ serviceVersion,
102
+ });
103
+ }
104
+ /**
105
+ * Gracefully shuts down the OpenTelemetry SDK.
106
+ * Should be called during application shutdown (SIGTERM/SIGINT handlers).
107
+ * Flushes any pending spans before shutting down.
108
+ */
109
+ export async function shutdownTracing() {
110
+ if (sdk) {
111
+ try {
112
+ await sdk.shutdown();
113
+ logger.info('OpenTelemetry tracing shut down successfully');
114
+ }
115
+ catch (shutdownError) {
116
+ logger.error('Error shutting down OpenTelemetry tracing', {
117
+ error: shutdownError instanceof Error ? shutdownError.message : String(shutdownError),
118
+ });
119
+ }
120
+ }
121
+ }
122
+ //# sourceMappingURL=tracing.js.map
@@ -0,0 +1,56 @@
1
+ import { GraphQLSchema, DocumentNode } from 'graphql';
2
+ /**
3
+ * Result of a query complexity check.
4
+ */
5
+ interface ComplexityCheckResult {
6
+ /** The computed complexity score */
7
+ complexity: number;
8
+ /** Whether the query exceeds the maximum allowed complexity */
9
+ exceeded: boolean;
10
+ /** The maximum allowed complexity for this request */
11
+ maxComplexity: number;
12
+ }
13
+ /**
14
+ * Computes the complexity of a GraphQL document against a schema.
15
+ *
16
+ * Uses two estimators:
17
+ * 1. fieldExtensionsEstimator: Reads complexity from field extensions (set via schema directives)
18
+ * 2. simpleEstimator: Assigns a default cost of 1 per field as fallback
19
+ *
20
+ * @param schema - The GraphQL schema
21
+ * @param document - The parsed GraphQL document (AST)
22
+ * @param variables - Query variables (used for list size estimation)
23
+ * @param isAuthenticated - Whether the request is authenticated (affects limits)
24
+ * @returns The complexity check result
25
+ */
26
+ export declare function checkQueryComplexity(schema: GraphQLSchema, document: DocumentNode, variables: Record<string, unknown>, isAuthenticated: boolean): ComplexityCheckResult;
27
+ /**
28
+ * Creates an Apollo Server plugin that enforces query complexity and depth limits.
29
+ *
30
+ * The plugin runs before query execution and rejects queries that exceed
31
+ * configured complexity or depth thresholds.
32
+ *
33
+ * Environment variables:
34
+ * - GRAPHQL_MAX_COMPLEXITY_AUTH: Max complexity for authenticated users (default: 1000)
35
+ * - GRAPHQL_MAX_COMPLEXITY_UNAUTH: Max complexity for unauthenticated users (default: 200)
36
+ * - GRAPHQL_MAX_DEPTH: Max query depth (default: 10)
37
+ * - GRAPHQL_COMPLEXITY_ENABLED: Explicit on/off ('true'/'false'). Defaults to on in production/staging.
38
+ *
39
+ * @param schema - The GraphQL schema to validate against
40
+ * @returns Apollo Server plugin configuration
41
+ */
42
+ export declare function createQueryComplexityPlugin(schema: GraphQLSchema): {
43
+ requestDidStart: () => Promise<{
44
+ didResolveOperation: (requestContext: {
45
+ document: DocumentNode;
46
+ request: {
47
+ variables?: Record<string, unknown> | null;
48
+ };
49
+ contextValue: {
50
+ user?: unknown;
51
+ };
52
+ }) => Promise<void>;
53
+ }>;
54
+ };
55
+ export {};
56
+ //# sourceMappingURL=query-complexity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-complexity.d.ts","sourceRoot":"","sources":["../../../src/middleware/query-complexity.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AA+CtD;;GAEG;AACH,UAAU,qBAAqB;IAC7B,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,QAAQ,EAAE,OAAO,CAAC;IAClB,sDAAsD;IACtD,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,YAAY,EACtB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,eAAe,EAAE,OAAO,GACvB,qBAAqB,CA8BvB;AA6BD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,aAAa,GAAG;IAClE,eAAe,EAAE,MAAM,OAAO,CAAC;QAC7B,mBAAmB,EAAE,CAAC,cAAc,EAAE;YACpC,QAAQ,EAAE,YAAY,CAAC;YACvB,OAAO,EAAE;gBAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;aAAE,CAAC;YACxD,YAAY,EAAE;gBAAE,IAAI,CAAC,EAAE,OAAO,CAAA;aAAE,CAAC;SAClC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACrB,CAAC,CAAC;CACJ,CAwDA"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-complexity.js","sourceRoot":"","sources":["../../../src/middleware/query-complexity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,176 @@
1
+ import { getComplexity, simpleEstimator, fieldExtensionsEstimator, } from 'graphql-query-complexity';
2
+ import { logger } from '../utils/logger.mjs';
3
+ /**
4
+ * Default query complexity limits per authentication level.
5
+ * These can be overridden via environment variables.
6
+ */
7
+ const DEFAULT_MAX_COMPLEXITY_AUTHENTICATED = 1000;
8
+ const DEFAULT_MAX_COMPLEXITY_UNAUTHENTICATED = 200;
9
+ const DEFAULT_MAX_DEPTH = 10;
10
+ /**
11
+ * Resolves the maximum allowed query complexity from environment variables or defaults.
12
+ *
13
+ * @param isAuthenticated - Whether the request is from an authenticated user
14
+ * @returns The maximum allowed complexity score
15
+ */
16
+ function getMaxComplexity(isAuthenticated) {
17
+ if (isAuthenticated) {
18
+ const envValue = process.env.GRAPHQL_MAX_COMPLEXITY_AUTH;
19
+ if (envValue) {
20
+ const parsed = parseInt(envValue, 10);
21
+ if (!isNaN(parsed) && parsed > 0)
22
+ return parsed;
23
+ }
24
+ return DEFAULT_MAX_COMPLEXITY_AUTHENTICATED;
25
+ }
26
+ const envValue = process.env.GRAPHQL_MAX_COMPLEXITY_UNAUTH;
27
+ if (envValue) {
28
+ const parsed = parseInt(envValue, 10);
29
+ if (!isNaN(parsed) && parsed > 0)
30
+ return parsed;
31
+ }
32
+ return DEFAULT_MAX_COMPLEXITY_UNAUTHENTICATED;
33
+ }
34
+ /**
35
+ * Resolves the maximum allowed query depth.
36
+ */
37
+ function getMaxDepth() {
38
+ const envValue = process.env.GRAPHQL_MAX_DEPTH;
39
+ if (envValue) {
40
+ const parsed = parseInt(envValue, 10);
41
+ if (!isNaN(parsed) && parsed > 0)
42
+ return parsed;
43
+ }
44
+ return DEFAULT_MAX_DEPTH;
45
+ }
46
+ /**
47
+ * Computes the complexity of a GraphQL document against a schema.
48
+ *
49
+ * Uses two estimators:
50
+ * 1. fieldExtensionsEstimator: Reads complexity from field extensions (set via schema directives)
51
+ * 2. simpleEstimator: Assigns a default cost of 1 per field as fallback
52
+ *
53
+ * @param schema - The GraphQL schema
54
+ * @param document - The parsed GraphQL document (AST)
55
+ * @param variables - Query variables (used for list size estimation)
56
+ * @param isAuthenticated - Whether the request is authenticated (affects limits)
57
+ * @returns The complexity check result
58
+ */
59
+ export function checkQueryComplexity(schema, document, variables, isAuthenticated) {
60
+ const maxComplexity = getMaxComplexity(isAuthenticated);
61
+ try {
62
+ const complexity = getComplexity({
63
+ schema,
64
+ query: document,
65
+ variables,
66
+ estimators: [
67
+ fieldExtensionsEstimator(),
68
+ simpleEstimator({ defaultComplexity: 1 }),
69
+ ],
70
+ });
71
+ return {
72
+ complexity,
73
+ exceeded: complexity > maxComplexity,
74
+ maxComplexity,
75
+ };
76
+ }
77
+ catch (estimationError) {
78
+ logger.warn('Failed to estimate query complexity', {
79
+ error: estimationError instanceof Error ? estimationError.message : String(estimationError),
80
+ });
81
+ // On failure, allow the query to proceed to avoid blocking legitimate requests
82
+ return {
83
+ complexity: 0,
84
+ exceeded: false,
85
+ maxComplexity,
86
+ };
87
+ }
88
+ }
89
+ /**
90
+ * Checks query depth by counting nested selections.
91
+ *
92
+ * @param document - The parsed GraphQL document (AST)
93
+ * @returns The maximum depth found in the query
94
+ */
95
+ function computeQueryDepth(document) {
96
+ let maxDepth = 0;
97
+ function traverse(node, depth) {
98
+ if (depth > maxDepth) {
99
+ maxDepth = depth;
100
+ }
101
+ if (node.selectionSet) {
102
+ for (const selection of node.selectionSet.selections) {
103
+ traverse(selection, depth + 1);
104
+ }
105
+ }
106
+ }
107
+ for (const definition of document.definitions) {
108
+ traverse(definition, 0);
109
+ }
110
+ return maxDepth;
111
+ }
112
+ /**
113
+ * Creates an Apollo Server plugin that enforces query complexity and depth limits.
114
+ *
115
+ * The plugin runs before query execution and rejects queries that exceed
116
+ * configured complexity or depth thresholds.
117
+ *
118
+ * Environment variables:
119
+ * - GRAPHQL_MAX_COMPLEXITY_AUTH: Max complexity for authenticated users (default: 1000)
120
+ * - GRAPHQL_MAX_COMPLEXITY_UNAUTH: Max complexity for unauthenticated users (default: 200)
121
+ * - GRAPHQL_MAX_DEPTH: Max query depth (default: 10)
122
+ * - GRAPHQL_COMPLEXITY_ENABLED: Explicit on/off ('true'/'false'). Defaults to on in production/staging.
123
+ *
124
+ * @param schema - The GraphQL schema to validate against
125
+ * @returns Apollo Server plugin configuration
126
+ */
127
+ export function createQueryComplexityPlugin(schema) {
128
+ const isEnabled = () => {
129
+ const explicitSetting = process.env.GRAPHQL_COMPLEXITY_ENABLED;
130
+ if (explicitSetting !== undefined) {
131
+ return explicitSetting === 'true' || explicitSetting === '1';
132
+ }
133
+ const env = process.env.NODE_ENV || 'development';
134
+ return env === 'production' || env === 'staging';
135
+ };
136
+ return {
137
+ async requestDidStart() {
138
+ return {
139
+ async didResolveOperation(requestContext) {
140
+ if (!isEnabled())
141
+ return;
142
+ const { document, request, contextValue } = requestContext;
143
+ const variables = (request.variables || {});
144
+ const isAuthenticated = Boolean(contextValue.user);
145
+ // Check depth limit
146
+ const maxDepth = getMaxDepth();
147
+ const depth = computeQueryDepth(document);
148
+ if (depth > maxDepth) {
149
+ logger.warn('Query depth exceeded', {
150
+ depth,
151
+ maxDepth,
152
+ isAuthenticated,
153
+ });
154
+ throw new Error(`Query depth of ${depth} exceeds maximum allowed depth of ${maxDepth}`);
155
+ }
156
+ // Check complexity limit
157
+ const result = checkQueryComplexity(schema, document, variables, isAuthenticated);
158
+ if (result.exceeded) {
159
+ logger.warn('Query complexity exceeded', {
160
+ complexity: result.complexity,
161
+ maxComplexity: result.maxComplexity,
162
+ isAuthenticated,
163
+ });
164
+ throw new Error(`Query complexity of ${result.complexity} exceeds maximum allowed complexity of ${result.maxComplexity}`);
165
+ }
166
+ logger.debug('Query complexity check passed', {
167
+ complexity: result.complexity,
168
+ maxComplexity: result.maxComplexity,
169
+ depth,
170
+ });
171
+ },
172
+ };
173
+ },
174
+ };
175
+ }
176
+ //# sourceMappingURL=query-complexity.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adaptic/backend-legacy",
3
- "version": "0.0.48",
3
+ "version": "0.0.50",
4
4
  "description": "Backend executable CRUD functions with dynamic variables construction, and type definitions for the Adaptic AI platform.",
5
5
  "type": "module",
6
6
  "types": "index.d.ts",