@friggframework/core 2.0.0-next.111 → 2.0.0-next.113

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 (74) hide show
  1. package/.eslintrc.json +18 -1
  2. package/CLAUDE.md +101 -13
  3. package/README.md +19 -22
  4. package/core/CLAUDE.md +28 -13
  5. package/core/Worker.js +32 -37
  6. package/core/create-handler.js +97 -197
  7. package/core/index.js +8 -1
  8. package/core/invocation-scope.js +225 -0
  9. package/core/secrets-to-env.js +7 -6
  10. package/database/config.js +0 -7
  11. package/database/documentdb-encryption-service.js +6 -2
  12. package/database/documentdb-utils.js +2 -1
  13. package/database/encryption/README.md +25 -1
  14. package/database/encryption/encryption-schema-registry.js +3 -0
  15. package/database/encryption/integration-mapping-encryption.js +97 -0
  16. package/database/encryption/logger.js +5 -2
  17. package/database/encryption/prisma-encryption-extension.js +20 -9
  18. package/errors/fetch-error.js +48 -65
  19. package/handlers/app-definition-loader.js +38 -2
  20. package/handlers/app-handler-helpers.js +18 -4
  21. package/handlers/backend-utils.js +10 -13
  22. package/handlers/database-migration-handler.js +10 -1
  23. package/handlers/integration-event-dispatcher.js +2 -2
  24. package/handlers/routers/integration-webhook-routers.js +13 -6
  25. package/handlers/routers/websocket.js +27 -7
  26. package/handlers/workers/db-migration.js +12 -1
  27. package/handlers/workers/dlq-processor.js +64 -38
  28. package/index.js +19 -1
  29. package/integrations/integration-base.js +56 -38
  30. package/integrations/integration-router.js +21 -20
  31. package/integrations/repositories/integration-mapping-query-pipeline.js +152 -0
  32. package/integrations/repositories/integration-mapping-query.js +193 -0
  33. package/integrations/repositories/integration-mapping-repository-documentdb.js +53 -0
  34. package/integrations/repositories/integration-mapping-repository-interface.js +48 -0
  35. package/integrations/repositories/integration-mapping-repository-mongo.js +72 -0
  36. package/integrations/repositories/integration-mapping-repository-postgres.js +118 -0
  37. package/integrations/use-cases/delete-integration-for-user.js +24 -13
  38. package/jest.config.js +2 -0
  39. package/logs/.eslintrc.json +26 -1
  40. package/logs/__fixtures__/events.js +82 -0
  41. package/logs/__fixtures__/matchers.js +45 -0
  42. package/logs/__fixtures__/secrets.js +59 -0
  43. package/logs/__fixtures__/vectors.js +329 -0
  44. package/logs/__fixtures__/with-env.js +45 -0
  45. package/logs/context.js +109 -0
  46. package/logs/contract-keys.js +67 -0
  47. package/logs/debug-shims.js +46 -0
  48. package/logs/denied-keys.js +21 -0
  49. package/logs/index.js +14 -3
  50. package/logs/jest-logger-setup.js +25 -0
  51. package/logs/levels.js +71 -0
  52. package/logs/logger-runtime.js +137 -0
  53. package/logs/logger.js +134 -42
  54. package/logs/record.js +276 -0
  55. package/logs/redact.js +328 -0
  56. package/logs/serialize.js +329 -0
  57. package/logs/sinks.js +93 -0
  58. package/logs/summarize-event.js +163 -0
  59. package/modules/module.js +32 -19
  60. package/modules/requester/oauth-2.js +28 -22
  61. package/modules/requester/requester.js +69 -6
  62. package/package.json +5 -6
  63. package/queues/queue-delivery.js +41 -0
  64. package/queues/queuer-util.js +1 -15
  65. package/telemetry/bind-telemetry-context.js +4 -0
  66. package/telemetry/instrument-handler.js +19 -2
  67. package/telemetry/no-op-telemetry.js +4 -0
  68. package/telemetry/otel-telemetry.js +25 -6
  69. package/telemetry/telemetry-context.js +19 -8
  70. package/telemetry/telemetry-runtime.js +17 -0
  71. package/telemetry/telemetry-service.js +1 -0
  72. package/types/errors/index.d.ts +24 -10
  73. package/types/logs/index.d.ts +61 -2
  74. package/user/repositories/user-repository-documentdb.js +10 -42
@@ -2,7 +2,16 @@
2
2
  // REMOVING FOR NOW UNTIL WE ADD WEBPACK BACK IN
3
3
  // require('source-map-support').install();
4
4
 
5
- const { initDebugLog, flushDebugLog } = require('../logs');
5
+ const { getLogger, toSanitizedSurrogate } = require('../logs');
6
+ const {
7
+ summarizeLambdaEvent,
8
+ toScopeInvocation,
9
+ toRequestDetails,
10
+ } = require('../logs/summarize-event');
11
+ const {
12
+ runInvocationScope,
13
+ DEFAULT_FLUSH_TIMEOUT_MS,
14
+ } = require('./invocation-scope');
6
15
  const { secretsToEnv } = require('./secrets-to-env');
7
16
  const { parametersToEnv } = require('./parameters-to-env');
8
17
  const {
@@ -11,123 +20,7 @@ const {
11
20
  getPluginTelemetrySubscribers,
12
21
  } = require('../telemetry/telemetry-runtime');
13
22
 
14
- // Bounds the tail latency telemetry adds to every warm invocation. Kept low so
15
- // an unreachable OTLP endpoint (e.g. a VPC Lambda with no NAT/egress) costs at
16
- // most this, not multiple seconds. Override with OTEL_FLUSH_TIMEOUT_MS.
17
- const DEFAULT_FLUSH_TIMEOUT_MS =
18
- Number(process.env.OTEL_FLUSH_TIMEOUT_MS) || 500;
19
-
20
- /**
21
- * Fold the invocation's buffered usage counters into the durable store, then
22
- * clear the buffer. On an SQS redelivery (ApproximateReceiveCount > 1) we
23
- * DISCARD rather than flush — the prior delivery already counted, and the usage
24
- * accuracy contract is "approximate, skip obvious redeliveries". Fully guarded.
25
- */
26
- async function flushUsageRollup(subscriber, eventSummary, shouldUseDatabase) {
27
- if (!subscriber) return;
28
- try {
29
- // Persisting usage requires a DB connection. DB-free handlers (e.g. the
30
- // webhook-receipt route) never called connectPrisma, so drop the buffer
31
- // instead of issuing a connectionless Prisma write.
32
- if (!shouldUseDatabase) {
33
- subscriber.discard();
34
- return;
35
- }
36
- // Discard only when EVERY record in the batch is a redelivery. The buffer
37
- // is invocation-scoped (not per-message), so discarding on *any*
38
- // redelivery would drop the fresh records' counts too (silent
39
- // under-count). For a mixed batch we flush: preserving fresh counts and
40
- // at worst re-counting the one redelivered record is strictly better than
41
- // losing fresh data for an approximate store. (Integration queue workers
42
- // are batchSize:1 today, so a batch is all-or-nothing; this keeps it
43
- // correct if batchSize is ever raised.)
44
- const records = Array.isArray(eventSummary?.records)
45
- ? eventSummary.records
46
- : [];
47
- const allRedelivered =
48
- records.length > 0 &&
49
- records.every((r) => Number(r.receiveCount) > 1);
50
- if (allRedelivered) {
51
- subscriber.discard();
52
- } else {
53
- await subscriber.flush();
54
- }
55
- } catch (_) {
56
- // Usage rollup must never break the handler.
57
- }
58
- }
59
-
60
- /**
61
- * Flush telemetry before the Lambda container freezes. Because
62
- * `callbackWaitsForEmptyEventLoop=false` (below) stops the event loop the moment
63
- * the handler returns, OTel's timer-driven batch processors would never fire —
64
- * so spans/metrics must be flushed synchronously here. Bounded by a timeout so a
65
- * stalled exporter can never block the response, and fully guarded so a flush
66
- * failure never breaks the handler.
67
- */
68
- async function flushTelemetry(telemetry, timeoutMs) {
69
- try {
70
- if (
71
- !telemetry ||
72
- typeof telemetry.isEnabled !== 'function' ||
73
- !telemetry.isEnabled()
74
- ) {
75
- return;
76
- }
77
- let timer;
78
- const deadline = new Promise((resolve) => {
79
- timer = setTimeout(resolve, timeoutMs);
80
- });
81
- try {
82
- await Promise.race([
83
- Promise.resolve(telemetry.forceFlush()),
84
- deadline,
85
- ]);
86
- } finally {
87
- clearTimeout(timer);
88
- }
89
- } catch (_) {
90
- // Telemetry flush must never break the handler.
91
- }
92
- }
93
-
94
- // Best-effort extraction of correlation identifiers from a Lambda event.
95
- // For SQS: pulls messageIds + parsed event/processId/integrationId from each
96
- // record body. For HTTP: pulls method+path. Never throws.
97
- const summarizeLambdaEvent = (event) => {
98
- if (!event) return {};
99
- if (Array.isArray(event.Records)) {
100
- return {
101
- source: 'sqs',
102
- records: event.Records.map((r) => {
103
- let parsed = {};
104
- try {
105
- const body = JSON.parse(r.body);
106
- parsed = {
107
- event: body?.event,
108
- processId: body?.data?.processId,
109
- integrationId: body?.data?.integrationId,
110
- };
111
- } catch {
112
- // ignore unparseable bodies
113
- }
114
- return {
115
- messageId: r.messageId,
116
- receiveCount: r.attributes?.ApproximateReceiveCount,
117
- ...parsed,
118
- };
119
- }),
120
- };
121
- }
122
- if (event.httpMethod || event.requestContext?.http) {
123
- return {
124
- source: 'http',
125
- method: event.httpMethod || event.requestContext?.http?.method,
126
- path: event.path || event.rawPath,
127
- };
128
- }
129
- return { source: 'other' };
130
- };
23
+ const log = getLogger('frigg.handler');
131
24
 
132
25
  const createHandler = (optionByName = {}) => {
133
26
  const {
@@ -157,95 +50,102 @@ const createHandler = (optionByName = {}) => {
157
50
  // no-op after the first invocation.
158
51
  getPluginTelemetrySubscribers();
159
52
 
160
- try {
161
- console.info(`[createHandler] ${eventName}: handler entry`, {
162
- eventName,
163
- awsRequestId: context?.awsRequestId,
164
- ...eventSummary,
165
- });
166
-
167
- initDebugLog(eventName, event);
53
+ const scopeFields = {
54
+ requestId: context?.awsRequestId,
55
+ handlerName: eventName,
56
+ method: eventSummary.method,
57
+ route: eventSummary.route,
58
+ routeKey: eventSummary.routeKey,
59
+ invocation: toScopeInvocation(eventSummary),
60
+ };
61
+ // Path, query keys and header names go only on the entry and failure
62
+ // records; the logger adds them to the scope's invocation.
63
+ const requestDetails = toRequestDetails(eventSummary);
168
64
 
169
- const requestMethod = event.httpMethod;
170
- const requestPath = event.path;
171
- if (requestMethod && requestPath) {
172
- console.info(`${requestMethod} ${requestPath}`);
173
- }
65
+ return runInvocationScope(
66
+ scopeFields,
67
+ async () => {
68
+ try {
69
+ log.info('Handler invoked', {
70
+ eventName: 'frigg.handler.invoked',
71
+ invocation: requestDetails,
72
+ });
174
73
 
175
- // If enabled (i.e. if SECRET_ARN is set in process.env) Fetch secrets from AWS Secrets Manager, and set them as environment variables.
176
- await secretsToEnv();
74
+ // If enabled (i.e. if SECRET_ARN is set in process.env) Fetch secrets from AWS Secrets Manager, and set them as environment variables.
75
+ await secretsToEnv();
177
76
 
178
- // If enabled (i.e. if SSM_PARAMETER_PREFIX and FRIGG_SSM_OFFLOADED_KEYS are set) fetch offloaded params from SSM Parameter Store into process.env.
179
- await parametersToEnv();
77
+ // If enabled (i.e. if SSM_PARAMETER_PREFIX and FRIGG_SSM_OFFLOADED_KEYS are set) fetch offloaded params from SSM Parameter Store into process.env.
78
+ await parametersToEnv();
180
79
 
181
- // Lazy-required so DB-free handlers never load the Prisma client.
182
- if (shouldUseDatabase) {
183
- const { connectPrisma } = require('../database/prisma');
184
- await connectPrisma();
185
- }
80
+ // Lazy-required so DB-free handlers never load the Prisma client.
81
+ if (shouldUseDatabase) {
82
+ const { connectPrisma } = require('../database/prisma');
83
+ await connectPrisma();
84
+ }
186
85
 
187
- // Helps reuse the database connection. Lowers response times.
188
- context.callbackWaitsForEmptyEventLoop = false;
189
-
190
- // Run the Lambda
191
- return await method(event, context);
192
- } catch (error) {
193
- flushDebugLog(error);
194
-
195
- // Don't leak implementation details to end users.
196
- if (isUserFacingResponse) {
197
- // Allow client-safe errors to pass through with their actual message
198
- if (error.isClientSafe === true) {
199
- const statusCode = error.statusCode || 400;
200
- return {
201
- statusCode,
202
- body: JSON.stringify({
203
- error: error.message,
204
- }),
205
- };
206
- }
86
+ // Helps reuse the database connection. Lowers response times.
87
+ context.callbackWaitsForEmptyEventLoop = false;
88
+
89
+ // Run the Lambda
90
+ return await method(event, context);
91
+ } catch (error) {
92
+ // Allow client-safe errors to pass through with their actual message
93
+ if (isUserFacingResponse && error.isClientSafe === true) {
94
+ const statusCode = error.statusCode || 400;
95
+ log.warn('Request rejected', {
96
+ eventName: 'frigg.handler.rejected',
97
+ statusCode,
98
+ error,
99
+ });
100
+ return {
101
+ statusCode,
102
+ body: JSON.stringify({
103
+ error: error.message,
104
+ }),
105
+ };
106
+ }
207
107
 
208
- // Hide other errors with generic message
209
- return {
210
- statusCode: 500,
211
- body: JSON.stringify({
212
- error: 'An Internal Error Occurred',
213
- }),
214
- };
215
- }
108
+ // Server-to-server: halt errors succeed and won't be
109
+ // retried, so the halt itself is the failure record.
110
+ if (!isUserFacingResponse && error.isHaltError === true) {
111
+ log.error('Handler halted', {
112
+ eventName: 'frigg.handler.halted',
113
+ invocation: requestDetails,
114
+ error,
115
+ });
116
+ return;
117
+ }
216
118
 
217
- // Handle server-to-server responses.
218
-
219
- // Halt errors are logged but suceed and won't be retried.
220
- // Log explicitly — silent suppression here previously made stuck
221
- // messages invisible to observability tooling. Include
222
- // eventSummary so operators can correlate across concurrent
223
- // invocations (processId / messageIds / HTTP path).
224
- if (error.isHaltError === true) {
225
- console.warn(
226
- `[createHandler] ${eventName}: halt error suppressed (no retry)`,
227
- {
228
- eventName,
229
- errorName: error.name,
230
- errorMessage: error.message,
231
- statusCode: error.statusCode,
232
- ...eventSummary,
119
+ log.error('Handler failed', {
120
+ eventName: 'frigg.handler.failed',
121
+ invocation: requestDetails,
122
+ error,
123
+ });
124
+
125
+ // Don't leak implementation details to end users.
126
+ if (isUserFacingResponse) {
127
+ return {
128
+ statusCode: 500,
129
+ body: JSON.stringify({
130
+ error: 'An Internal Error Occurred',
131
+ }),
132
+ };
233
133
  }
234
- );
235
- return;
236
- }
237
134
 
238
- // Here we can just rethrow and let AWS build the response.
239
- throw error;
240
- } finally {
241
- // Flush telemetry + usage before the container freezes.
242
- await flushTelemetry(activeTelemetry, flushTimeoutMs);
243
- await flushUsageRollup(
244
- activeUsageRollup,
135
+ // The Lambda runtime writes a rethrown error itself, so
136
+ // rethrow a sanitized copy (ADR-048 §6 item 11).
137
+ throw toSanitizedSurrogate(error);
138
+ }
139
+ },
140
+ {
141
+ telemetry: activeTelemetry,
142
+ usageRollup: activeUsageRollup,
245
143
  eventSummary,
246
- shouldUseDatabase
247
- );
248
- }
144
+ shouldUseDatabase,
145
+ flushTimeoutMs,
146
+ context,
147
+ }
148
+ );
249
149
  };
250
150
  };
251
151
 
package/core/index.js CHANGED
@@ -2,5 +2,12 @@ const { Delegate } = require('./Delegate');
2
2
  const { Worker } = require('./Worker');
3
3
  const { loadInstalledModules } = require('./load-installed-modules');
4
4
  const { createHandler } = require('./create-handler');
5
+ const { runInvocationScope } = require('./invocation-scope');
5
6
 
6
- module.exports = { Delegate, Worker, loadInstalledModules, createHandler };
7
+ module.exports = {
8
+ Delegate,
9
+ Worker,
10
+ loadInstalledModules,
11
+ createHandler,
12
+ runInvocationScope,
13
+ };
@@ -0,0 +1,225 @@
1
+ const { runInContext, LOGGER_SCOPE_KEY } = require('../logs/context');
2
+ const { summarizeMessageBody } = require('../logs/summarize-event');
3
+ const { flushSinks, hasFlushableSinks } = require('../logs/logger-runtime');
4
+
5
+ // Bounds the tail latency telemetry adds to every warm invocation. Kept low so
6
+ // an unreachable OTLP endpoint (e.g. a VPC Lambda with no NAT/egress) costs at
7
+ // most this, not multiple seconds. Override with OTEL_FLUSH_TIMEOUT_MS.
8
+ const DEFAULT_FLUSH_TIMEOUT_MS =
9
+ Number(process.env.OTEL_FLUSH_TIMEOUT_MS) || 500;
10
+
11
+ // Time kept back from the Lambda deadline, so a slow flush never becomes a
12
+ // Lambda timeout.
13
+ const FLUSH_MARGIN_MS = 50;
14
+
15
+ // setTimeout fires at once for a delay above this.
16
+ const MAX_TIMER_MS = 2 ** 31 - 1;
17
+
18
+ /**
19
+ * Fold the invocation's buffered usage counters into the durable store, then
20
+ * clear the buffer. On an SQS redelivery (ApproximateReceiveCount > 1) we
21
+ * DISCARD rather than flush — the prior delivery already counted, and the usage
22
+ * accuracy contract is "approximate, skip obvious redeliveries". Fully guarded.
23
+ */
24
+ async function flushUsageRollup(subscriber, eventSummary, shouldUseDatabase) {
25
+ if (!subscriber) return;
26
+ try {
27
+ // Persisting usage requires a DB connection. DB-free handlers (e.g. the
28
+ // webhook-receipt route) never called connectPrisma, so drop the buffer
29
+ // instead of issuing a connectionless Prisma write.
30
+ if (!shouldUseDatabase) {
31
+ subscriber.discard();
32
+ return;
33
+ }
34
+ // Discard only when EVERY record in the batch is a redelivery. The buffer
35
+ // is invocation-scoped (not per-message), so discarding on *any*
36
+ // redelivery would drop the fresh records' counts too (silent
37
+ // under-count). For a mixed batch we flush: preserving fresh counts and
38
+ // at worst re-counting the one redelivered record is strictly better than
39
+ // losing fresh data for an approximate store. (Integration queue workers
40
+ // are batchSize:1 today, so a batch is all-or-nothing; this keeps it
41
+ // correct if batchSize is ever raised.)
42
+ const records = Array.isArray(eventSummary?.records)
43
+ ? eventSummary.records
44
+ : [];
45
+ const allRedelivered =
46
+ records.length > 0 &&
47
+ records.every((r) => Number(r.receiveCount) > 1);
48
+ if (allRedelivered) {
49
+ subscriber.discard();
50
+ } else {
51
+ await subscriber.flush();
52
+ }
53
+ } catch (_) {
54
+ // Usage rollup must never break the handler.
55
+ }
56
+ }
57
+
58
+ function isTelemetryEnabled(telemetry) {
59
+ try {
60
+ return Boolean(
61
+ telemetry &&
62
+ typeof telemetry.isEnabled === 'function' &&
63
+ telemetry.isEnabled()
64
+ );
65
+ } catch (_) {
66
+ return false;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Flush telemetry before the Lambda container freezes. Because
72
+ * `callbackWaitsForEmptyEventLoop=false` stops the event loop the moment the
73
+ * handler returns, OTel's timer-driven batch processors would never fire — so
74
+ * spans/metrics must be flushed here. No timer of its own: the caller bounds it
75
+ * with `withDeadline`. Fully guarded, so a flush failure never breaks the
76
+ * handler.
77
+ */
78
+ async function flushTelemetry(telemetry, { signal } = {}) {
79
+ if (!isTelemetryEnabled(telemetry) || signal?.aborted) return;
80
+ try {
81
+ await telemetry.forceFlush();
82
+ } catch (_) {
83
+ // Telemetry flush must never break the handler.
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Run `fn(signal)` for at most `ms`. Owns the only timer and clears it when the
89
+ * work settles. Aborts the signal at the deadline. Never rejects, and a late
90
+ * rejection of the work is swallowed.
91
+ */
92
+ function withDeadline(ms, fn) {
93
+ const controller = new AbortController();
94
+ return new Promise((resolve) => {
95
+ let timer;
96
+ const finish = () => {
97
+ clearTimeout(timer);
98
+ resolve();
99
+ };
100
+ timer = setTimeout(() => {
101
+ controller.abort();
102
+ finish();
103
+ }, Math.min(ms, MAX_TIMER_MS));
104
+ let work;
105
+ try {
106
+ work = Promise.resolve(fn(controller.signal));
107
+ } catch (_) {
108
+ work = Promise.resolve();
109
+ }
110
+ work.then(finish, finish);
111
+ });
112
+ }
113
+
114
+ function remainingTimeMs(context) {
115
+ try {
116
+ const remaining = context?.getRemainingTimeInMillis?.();
117
+ return typeof remaining === 'number' ? remaining : Infinity;
118
+ } catch (_) {
119
+ return Infinity;
120
+ }
121
+ }
122
+
123
+ async function flushInvocation({
124
+ telemetry,
125
+ usageRollup,
126
+ eventSummary,
127
+ shouldUseDatabase,
128
+ flushTimeoutMs,
129
+ context,
130
+ }) {
131
+ // ADR-048 §11: the deadline is fixed at flush start. The usage rollup
132
+ // emits no telemetry, so it runs in parallel with no bound of its own.
133
+ const deadline = Math.min(
134
+ flushTimeoutMs,
135
+ remainingTimeMs(context) - FLUSH_MARGIN_MS
136
+ );
137
+ await Promise.all([
138
+ flushUsageRollup(usageRollup, eventSummary, shouldUseDatabase),
139
+ flushBounded(telemetry, deadline),
140
+ ]);
141
+ }
142
+
143
+ function flushBounded(telemetry, deadline) {
144
+ const flushes = [];
145
+ if (isTelemetryEnabled(telemetry)) {
146
+ flushes.push((signal) => flushTelemetry(telemetry, { signal }));
147
+ }
148
+ if (hasFlushableSinks()) {
149
+ flushes.push((signal) => flushSinks({ signal }));
150
+ }
151
+ if (!flushes.length || !(deadline > 0)) return undefined;
152
+ return withDeadline(deadline, (signal) =>
153
+ Promise.allSettled(flushes.map((flush) => flush(signal)))
154
+ );
155
+ }
156
+
157
+ /**
158
+ * Open the logger scope for one invocation, run `fn` inside it, then flush.
159
+ * `fields` (requestId, handlerName, method, route, routeKey, invocation, ...)
160
+ * go into the scope's logger sub-object and never reach the telemetry bus.
161
+ */
162
+ async function runInvocationScope(fields, fn, opts = {}) {
163
+ const {
164
+ telemetry,
165
+ usageRollup,
166
+ eventSummary,
167
+ shouldUseDatabase = true,
168
+ flushTimeoutMs = DEFAULT_FLUSH_TIMEOUT_MS,
169
+ context,
170
+ } = opts;
171
+ return runInContext({ [LOGGER_SCOPE_KEY]: { ...(fields || {}) } }, async () => {
172
+ try {
173
+ return await fn();
174
+ } finally {
175
+ // Every step is guarded, so a flush never changes the result.
176
+ await flushInvocation({
177
+ telemetry,
178
+ usageRollup,
179
+ eventSummary,
180
+ shouldUseDatabase,
181
+ flushTimeoutMs,
182
+ context,
183
+ });
184
+ }
185
+ });
186
+ }
187
+
188
+ function toCount(value) {
189
+ const count = Number(value);
190
+ return value !== undefined && value !== null && Number.isFinite(count)
191
+ ? count
192
+ : undefined;
193
+ }
194
+
195
+ /**
196
+ * Open the logger scope for one SQS message: messageId, receiveCount and the
197
+ * Frigg ids the body carries. No flush; the invocation scope owns that.
198
+ */
199
+ function runMessageScope(record, fn) {
200
+ const { event, processId, integrationId } = summarizeMessageBody(
201
+ record?.body
202
+ );
203
+ return runInContext(
204
+ {
205
+ [LOGGER_SCOPE_KEY]: {
206
+ messageId: record?.messageId,
207
+ receiveCount: toCount(record?.attributes?.ApproximateReceiveCount),
208
+ integrationEvent: event,
209
+ processId,
210
+ integrationId,
211
+ },
212
+ },
213
+ fn
214
+ );
215
+ }
216
+
217
+ module.exports = {
218
+ runInvocationScope,
219
+ runMessageScope,
220
+ withDeadline,
221
+ flushTelemetry,
222
+ flushUsageRollup,
223
+ FLUSH_MARGIN_MS,
224
+ DEFAULT_FLUSH_TIMEOUT_MS,
225
+ };
@@ -1,5 +1,9 @@
1
+ const { getLogger } = require('../logs');
2
+
3
+ const log = getLogger('frigg.core.secrets');
4
+
1
5
  const getSecretValue = async () => {
2
- console.log('Fetching secrets...');
6
+ log.debug('Fetching secrets', { eventName: 'frigg.core.secrets.fetching' });
3
7
 
4
8
  const httpPort = process.env.PARAMETERS_SECRETS_EXTENSION_HTTP_PORT || 2773;
5
9
  const url = `http://localhost:${httpPort}/secretsmanager/get?secretId=${encodeURIComponent(
@@ -15,10 +19,7 @@ const getSecretValue = async () => {
15
19
  const response = await fetch(url, options);
16
20
 
17
21
  if (!response.ok) {
18
- const json = await response.json().catch((err) => err.message);
19
- console.error('Invalid response - response:', JSON.stringify(response));
20
- console.error('Invalid response - json:', json);
21
- throw new Error(`Invalid ${response.status} response`);
22
+ throw new Error(`Secrets fetch failed with ${response.status}`);
22
23
  }
23
24
 
24
25
  const result = await response.json();
@@ -44,7 +45,7 @@ const secretsToEnv = async () => {
44
45
  if (!process.env.SECRET_ARN) {
45
46
  return;
46
47
  }
47
- console.log('Secrets to env');
48
+ log.debug('Secrets to env', { eventName: 'frigg.core.secrets.to_env' });
48
49
 
49
50
  try {
50
51
  const secrets = await getSecretValue();
@@ -125,16 +125,9 @@ let cachedDbType = null;
125
125
  */
126
126
  const PRISMA_LOG_LEVEL = process.env.PRISMA_LOG_LEVEL || 'error,warn';
127
127
 
128
- /**
129
- * Enable Prisma query logging for performance monitoring
130
- * @type {boolean}
131
- */
132
- const PRISMA_QUERY_LOGGING = process.env.PRISMA_QUERY_LOGGING === 'true';
133
-
134
128
  module.exports = {
135
129
  getDatabaseType, // Export for testing and direct use
136
130
  PRISMA_LOG_LEVEL,
137
- PRISMA_QUERY_LOGGING,
138
131
  };
139
132
 
140
133
  /**
@@ -1,5 +1,9 @@
1
1
  const { Cryptor } = require('../encrypt/Cryptor');
2
- const { getEncryptedFields, loadCustomEncryptionSchema } = require('./encryption/encryption-schema-registry');
2
+ const {
3
+ getEncryptedFields,
4
+ getFieldsToEncryptOnWrite,
5
+ loadCustomEncryptionSchema,
6
+ } = require('./encryption/encryption-schema-registry');
3
7
 
4
8
  /**
5
9
  * Encryption service specifically for DocumentDB repositories
@@ -107,7 +111,7 @@ class DocumentDBEncryptionService {
107
111
  }
108
112
 
109
113
  // Get encrypted fields from registry
110
- const encryptedFieldsConfig = getEncryptedFields(modelName);
114
+ const encryptedFieldsConfig = getFieldsToEncryptOnWrite(modelName);
111
115
  if (!encryptedFieldsConfig || encryptedFieldsConfig.length === 0) {
112
116
  return document;
113
117
  }
@@ -166,11 +166,12 @@ async function findManyDrained(client, collection, filter = {}, options = {}) {
166
166
  return drainCursor(client, collection, first);
167
167
  }
168
168
 
169
- async function aggregateDrained(client, collection, pipeline) {
169
+ async function aggregateDrained(client, collection, pipeline, options = {}) {
170
170
  const first = await client.$runCommandRaw({
171
171
  aggregate: collection,
172
172
  pipeline,
173
173
  cursor: { batchSize: DRAIN_BATCH_SIZE },
174
+ ...options,
174
175
  });
175
176
  return drainCursor(client, collection, first);
176
177
  }