@ciq-dev/neoiq-foundation-node 1.0.1-beta.1 → 1.0.1-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,143 +1,262 @@
1
1
  "use strict";
2
- //#region rolldown:runtime
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
- key = keys[i];
12
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
- get: ((k) => from[k]).bind(null, key),
14
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
- });
16
- }
17
- return to;
18
- };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
- value: mod,
21
- enumerable: true
22
- }) : target, mod));
2
+ const require_tracing = require('./tracing-DM5OFo7l.js');
3
+ const __opentelemetry_resources = require_tracing.__toESM(require("@opentelemetry/resources"));
4
+ const __opentelemetry_semantic_conventions = require_tracing.__toESM(require("@opentelemetry/semantic-conventions"));
5
+ const __opentelemetry_api = require_tracing.__toESM(require("@opentelemetry/api"));
6
+ const async_hooks = require_tracing.__toESM(require("async_hooks"));
7
+ const pino = require_tracing.__toESM(require("pino"));
8
+ const __opentelemetry_exporter_metrics_otlp_grpc = require_tracing.__toESM(require("@opentelemetry/exporter-metrics-otlp-grpc"));
9
+ const __opentelemetry_sdk_metrics = require_tracing.__toESM(require("@opentelemetry/sdk-metrics"));
10
+ const fastify_plugin = require_tracing.__toESM(require("fastify-plugin"));
11
+ const crypto = require_tracing.__toESM(require("crypto"));
12
+ const axios = require_tracing.__toESM(require("axios"));
13
+ const axios_retry = require_tracing.__toESM(require("axios-retry"));
14
+ const opossum = require_tracing.__toESM(require("opossum"));
15
+ const node_crypto = require_tracing.__toESM(require("node:crypto"));
16
+ const node_stream = require_tracing.__toESM(require("node:stream"));
23
17
 
24
- //#endregion
25
- const zod = __toESM(require("zod"));
26
- const async_hooks = __toESM(require("async_hooks"));
27
- const pino = __toESM(require("pino"));
28
- const __opentelemetry_api = __toESM(require("@opentelemetry/api"));
29
- const __opentelemetry_sdk_node = __toESM(require("@opentelemetry/sdk-node"));
30
- const __opentelemetry_auto_instrumentations_node = __toESM(require("@opentelemetry/auto-instrumentations-node"));
31
- const __opentelemetry_exporter_trace_otlp_grpc = __toESM(require("@opentelemetry/exporter-trace-otlp-grpc"));
32
- const __opentelemetry_resources = __toESM(require("@opentelemetry/resources"));
33
- const __opentelemetry_semantic_conventions = __toESM(require("@opentelemetry/semantic-conventions"));
34
- const __opentelemetry_exporter_metrics_otlp_grpc = __toESM(require("@opentelemetry/exporter-metrics-otlp-grpc"));
35
- const __opentelemetry_sdk_metrics = __toESM(require("@opentelemetry/sdk-metrics"));
36
- const fastify_plugin = __toESM(require("fastify-plugin"));
37
- const crypto = __toESM(require("crypto"));
38
- const axios = __toESM(require("axios"));
39
- const axios_retry = __toESM(require("axios-retry"));
40
- const opossum = __toESM(require("opossum"));
41
-
42
- //#region src/config.ts
43
- const AutoInstrumentationConfigSchema = zod.z.object({
44
- http: zod.z.boolean().default(true),
45
- fastify: zod.z.boolean().default(true),
46
- express: zod.z.boolean().default(true),
47
- mongodb: zod.z.boolean().default(true),
48
- pg: zod.z.boolean().default(true),
49
- mysql: zod.z.boolean().default(true),
50
- redis: zod.z.boolean().default(true),
51
- ioredis: zod.z.boolean().default(true),
52
- grpc: zod.z.boolean().default(true),
53
- fs: zod.z.boolean().default(false),
54
- dns: zod.z.boolean().default(false)
55
- }).partial();
56
- const FeaturesConfigSchema = zod.z.object({
57
- tracing: zod.z.boolean().default(true),
58
- metrics: zod.z.boolean().default(true),
59
- logging: zod.z.boolean().default(true),
60
- autoInstrumentation: AutoInstrumentationConfigSchema.default({})
61
- }).partial();
62
- const DEFAULT_OTEL_ENDPOINT = "http://otel-stack-deployment-collector.observability.svc.cluster.local:4317";
63
- const OtelConfigSchema = zod.z.object({
64
- endpoint: zod.z.string().default(DEFAULT_OTEL_ENDPOINT),
65
- metricsIntervalMs: zod.z.number().min(1e3).default(5e3),
66
- traceSampleRate: zod.z.number().min(0).max(1).default(1)
67
- }).partial();
68
- const LoggingConfigSchema = zod.z.object({
69
- level: zod.z.enum([
70
- "debug",
71
- "info",
72
- "warn",
73
- "error"
74
- ]).default("info"),
75
- prettyPrint: zod.z.boolean().optional()
76
- }).partial();
77
- const RequestLoggingConfigSchema = zod.z.object({
78
- logHeaders: zod.z.boolean().default(true),
79
- logBody: zod.z.boolean().default(false),
80
- logResponseBody: zod.z.boolean().default(false),
81
- maxBodySize: zod.z.number().default(10 * 1024),
82
- redactHeaders: zod.z.array(zod.z.string()).optional()
83
- }).partial();
84
- const FoundationConfigSchema = zod.z.object({
85
- serviceName: zod.z.string().min(1, "serviceName is required"),
86
- serviceVersion: zod.z.string().default(process.env.SERVICE_VERSION || "1.0.0"),
87
- environment: zod.z.enum([
88
- "development",
89
- "staging",
90
- "qa",
91
- "production"
92
- ]).default(process.env.NODE_ENV || "development"),
93
- features: FeaturesConfigSchema.default({}),
94
- otel: OtelConfigSchema.default({}),
95
- logging: LoggingConfigSchema.default({}),
96
- requestLogging: RequestLoggingConfigSchema.default({})
97
- });
98
- /** Parse and validate configuration */
99
- function parseConfig(input) {
100
- const result = FoundationConfigSchema.safeParse(input);
101
- if (!result.success) {
102
- const errors = result.error.errors.map((e) => ` - ${e.path.join(".")}: ${e.message}`).join("\n");
103
- throw new Error(`Invalid foundation configuration:\n${errors}`);
104
- }
105
- return result.data;
18
+ //#region src/features/context.ts
19
+ const BAGGAGE_CORRELATION_KEY = "correlation.id";
20
+ function setBaggageCorrelationId(correlationId) {
21
+ const currentBaggage = __opentelemetry_api.propagation.getBaggage(__opentelemetry_api.context.active());
22
+ const baggage = (currentBaggage ?? __opentelemetry_api.propagation.createBaggage()).setEntry(BAGGAGE_CORRELATION_KEY, { value: correlationId });
23
+ return __opentelemetry_api.propagation.setBaggage(__opentelemetry_api.context.active(), baggage);
106
24
  }
107
- /** Get default OTEL endpoint */
108
- function getDefaultOtelEndpoint() {
109
- return process.env.OTEL_EXPORTER_OTLP_ENDPOINT || DEFAULT_OTEL_ENDPOINT;
25
+ function getBaggageCorrelationId() {
26
+ const baggage = __opentelemetry_api.propagation.getBaggage(__opentelemetry_api.context.active());
27
+ return baggage?.getEntry(BAGGAGE_CORRELATION_KEY)?.value;
110
28
  }
111
-
112
- //#endregion
113
- //#region src/features/context.ts
114
29
  /** Create a new context manager instance */
115
30
  function createContextManager() {
116
31
  const als = new async_hooks.AsyncLocalStorage();
117
32
  return {
118
- getContext: () => als.getStore(),
119
- run(context$3, fn) {
120
- return als.run(context$3, fn);
33
+ getContext() {
34
+ const alsCtx = als.getStore();
35
+ if (!alsCtx) return void 0;
36
+ const baggageCorrelationId = getBaggageCorrelationId();
37
+ if (baggageCorrelationId && baggageCorrelationId !== alsCtx.correlationId) return {
38
+ ...alsCtx,
39
+ correlationId: baggageCorrelationId
40
+ };
41
+ return alsCtx;
42
+ },
43
+ run(context$2, fn) {
44
+ if (context$2.correlationId) {
45
+ const otelCtx = setBaggageCorrelationId(context$2.correlationId);
46
+ return __opentelemetry_api.context.with(otelCtx, () => als.run(context$2, fn));
47
+ }
48
+ return als.run(context$2, fn);
121
49
  },
122
50
  get(key) {
51
+ if (key === "correlationId") return getBaggageCorrelationId() ?? als.getStore()?.correlationId;
123
52
  return als.getStore()?.[key];
124
53
  },
125
54
  update(updates) {
126
55
  const current = als.getStore();
127
56
  if (!current) return void 0;
57
+ if (updates.correlationId) setBaggageCorrelationId(updates.correlationId);
128
58
  Object.assign(current, updates);
129
59
  return current;
60
+ },
61
+ setContextValue(key, value) {
62
+ const current = als.getStore();
63
+ if (!current) return;
64
+ if (!current.contextData) current.contextData = {};
65
+ current.contextData[key] = value;
66
+ },
67
+ getContextValue(key) {
68
+ return als.getStore()?.contextData?.[key];
69
+ },
70
+ getContextData() {
71
+ return als.getStore()?.contextData ?? {};
72
+ },
73
+ setContextData(data) {
74
+ const current = als.getStore();
75
+ if (!current) return;
76
+ current.contextData = {
77
+ ...current.contextData ?? {},
78
+ ...data
79
+ };
80
+ },
81
+ clearContextData() {
82
+ const current = als.getStore();
83
+ if (!current) return;
84
+ current.contextData = {};
130
85
  }
131
86
  };
132
87
  }
133
88
 
89
+ //#endregion
90
+ //#region src/features/redaction.ts
91
+ /**
92
+ * Log Redaction & PII Sanitization
93
+ *
94
+ * Two-layer approach:
95
+ * - Layer A: Pino native `redact` (fast-redact) for key-based redaction on every log call.
96
+ * Compiled at init, near-zero runtime cost.
97
+ * - Layer B: Deep-traverse sanitizer for value-pattern detection (JWTs, AWS keys, etc.).
98
+ * Only used for request/response body logging — NOT on every log call.
99
+ */
100
+ const PLACEHOLDER = "[REDACTED]";
101
+ /**
102
+ * Key names that Pino's fast-redact will censor automatically.
103
+ * Supports wildcards: '*.password' matches nested keys one level deep.
104
+ */
105
+ const REDACT_PATHS = [
106
+ "password",
107
+ "passwd",
108
+ "pass",
109
+ "pwd",
110
+ "secret",
111
+ "secretKey",
112
+ "secret_key",
113
+ "token",
114
+ "accessToken",
115
+ "access_token",
116
+ "refreshToken",
117
+ "refresh_token",
118
+ "idToken",
119
+ "id_token",
120
+ "apiKey",
121
+ "api_key",
122
+ "apiSecret",
123
+ "api_secret",
124
+ "authorization",
125
+ "auth",
126
+ "credentials",
127
+ "privateKey",
128
+ "private_key",
129
+ "cookie",
130
+ "setCookie",
131
+ "set_cookie",
132
+ "creditCard",
133
+ "credit_card",
134
+ "cardNumber",
135
+ "card_number",
136
+ "ccNumber",
137
+ "cc_number",
138
+ "cvv",
139
+ "cvc",
140
+ "securityCode",
141
+ "security_code",
142
+ "accountNumber",
143
+ "account_number",
144
+ "ssn",
145
+ "socialSecurity",
146
+ "social_security",
147
+ "dateOfBirth",
148
+ "date_of_birth",
149
+ "dob",
150
+ "*.password",
151
+ "*.secret",
152
+ "*.token",
153
+ "*.apiKey",
154
+ "*.api_key",
155
+ "*.authorization",
156
+ "*.cookie",
157
+ "*.credentials",
158
+ "*.creditCard",
159
+ "*.cardNumber",
160
+ "*.cvv",
161
+ "*.ssn",
162
+ "*.privateKey",
163
+ "*.private_key"
164
+ ];
165
+ /**
166
+ * Value patterns that indicate a secret regardless of the key name.
167
+ * Used only for body sanitization (Layer B).
168
+ */
169
+ const VALUE_PATTERNS = [
170
+ {
171
+ label: "jwt",
172
+ pattern: /^eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]+$/
173
+ },
174
+ {
175
+ label: "aws_access_key",
176
+ pattern: /(?:^|[^A-Za-z0-9])AKIA[0-9A-Z]{16}(?:$|[^A-Za-z0-9])/
177
+ },
178
+ {
179
+ label: "stripe_key",
180
+ pattern: /^[sr]k_(live|test)_[A-Za-z0-9]{10,}$/
181
+ },
182
+ {
183
+ label: "openai_key",
184
+ pattern: /^sk-[A-Za-z0-9_-]{20,}$/
185
+ },
186
+ {
187
+ label: "github_token",
188
+ pattern: /^gh[ps]_[A-Za-z0-9]{36,}$/
189
+ },
190
+ {
191
+ label: "pem_private_key",
192
+ pattern: /-----BEGIN\s+(RSA\s+|EC\s+)?PRIVATE\s+KEY-----/
193
+ },
194
+ {
195
+ label: "connection_string",
196
+ pattern: /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^:]+:[^@]+@/
197
+ },
198
+ {
199
+ label: "bearer_token",
200
+ pattern: /^Bearer\s+\S{10,}$/i
201
+ }
202
+ ];
203
+ const SENSITIVE_KEYS = new Set(REDACT_PATHS.filter((p) => !p.includes("*")).map((k) => k.toLowerCase()));
204
+ const MAX_DEPTH = 10;
205
+ const MAX_KEYS = 200;
206
+ function mask(value) {
207
+ if (typeof value !== "string" || value.length <= 8) return PLACEHOLDER;
208
+ return `${value.slice(0, 4)}${"*".repeat(Math.min(value.length - 8, 20))}${value.slice(-4)}`;
209
+ }
210
+ function isSensitiveValue(value) {
211
+ return VALUE_PATTERNS.some((p) => p.pattern.test(value));
212
+ }
213
+ function sanitizeValue(value, depth) {
214
+ if (depth > MAX_DEPTH) return value;
215
+ if (typeof value === "string" && isSensitiveValue(value)) return mask(value);
216
+ if (Array.isArray(value)) return value.map((item) => sanitizeValue(item, depth + 1));
217
+ if (value !== null && typeof value === "object") return sanitizeObject(value, depth + 1);
218
+ return value;
219
+ }
220
+ function sanitizeObject(obj, depth) {
221
+ if (depth > MAX_DEPTH) return obj;
222
+ const keys = Object.keys(obj);
223
+ if (keys.length > MAX_KEYS) return obj;
224
+ const result = {};
225
+ for (const key of keys) {
226
+ const value = obj[key];
227
+ if (SENSITIVE_KEYS.has(key.toLowerCase())) result[key] = typeof value === "string" ? mask(value) : PLACEHOLDER;
228
+ else result[key] = sanitizeValue(value, depth);
229
+ }
230
+ return result;
231
+ }
232
+ /**
233
+ * Deep-traverse sanitizer for request/response bodies.
234
+ * Checks both key names (deny-list) and value patterns (JWT, AWS keys, etc.).
235
+ * NOT intended for every log call — use Pino native `redact` for that.
236
+ */
237
+ function sanitizeBody(body) {
238
+ if (body === null || body === void 0) return body;
239
+ if (typeof body === "string") return isSensitiveValue(body) ? mask(body) : body;
240
+ if (typeof body !== "object") return body;
241
+ if (Array.isArray(body)) return body.map((item) => sanitizeValue(item, 0));
242
+ return sanitizeObject(body, 0);
243
+ }
244
+ /** Build the Pino `redact` config object for fast-redact integration */
245
+ function buildPinoRedactConfig(additionalPaths = []) {
246
+ return {
247
+ paths: [...REDACT_PATHS, ...additionalPaths],
248
+ censor: PLACEHOLDER
249
+ };
250
+ }
251
+
134
252
  //#endregion
135
253
  //#region src/features/logging.ts
136
254
  /** Create a structured logger with automatic trace context injection */
137
255
  function createLogger(options) {
138
- const { serviceName, serviceVersion, environment, level, prettyPrint, contextManager } = options;
256
+ const { serviceName, serviceVersion, environment, level, prettyPrint, contextManager, additionalRedactPaths = [] } = options;
139
257
  const pinoLogger = (0, pino.default)({
140
258
  level,
259
+ redact: buildPinoRedactConfig(additionalRedactPaths),
141
260
  base: {
142
261
  service: serviceName,
143
262
  version: serviceVersion,
@@ -150,11 +269,14 @@ function createLogger(options) {
150
269
  const traceId = spanContext?.traceId || ctx?.traceId;
151
270
  const spanId = spanContext?.spanId || ctx?.spanId;
152
271
  const correlationId = ctx?.correlationId;
153
- return {
272
+ const contextData = ctx?.contextData;
273
+ const result = {
154
274
  trace_id: traceId,
155
275
  span_id: spanId,
156
276
  correlation_id: correlationId
157
277
  };
278
+ if (contextData && Object.keys(contextData).length > 0) result.context = contextData;
279
+ return result;
158
280
  },
159
281
  formatters: { level: (label) => ({ level: label }) },
160
282
  transport: prettyPrint ? {
@@ -179,12 +301,13 @@ function wrapPinoLogger(pinoLogger) {
179
301
  };
180
302
  }
181
303
  /** Fallback logger when Pino is not available */
182
- function createFallbackLogger(serviceName = "unknown") {
304
+ function createFallbackLogger(serviceName = "unknown", baseBindings = {}) {
183
305
  const log = (level, obj, msg) => {
184
306
  const logObj = {
185
307
  timestamp: new Date().toISOString(),
186
308
  level,
187
309
  service: serviceName,
310
+ ...baseBindings,
188
311
  ...obj,
189
312
  msg
190
313
  };
@@ -198,7 +321,10 @@ function createFallbackLogger(serviceName = "unknown") {
198
321
  info: (obj, msg) => log("info", obj, msg),
199
322
  warn: (obj, msg) => log("warn", obj, msg),
200
323
  error: (obj, msg) => log("error", obj, msg),
201
- child: () => fallback,
324
+ child: (bindings) => createFallbackLogger(serviceName, {
325
+ ...baseBindings,
326
+ ...bindings
327
+ }),
202
328
  pino: null
203
329
  };
204
330
  return fallback;
@@ -211,84 +337,6 @@ function getGlobalLogger() {
211
337
  return globalLogger || createFallbackLogger();
212
338
  }
213
339
 
214
- //#endregion
215
- //#region src/features/tracing.ts
216
- let sdk = null;
217
- let isInitialized$1 = false;
218
- /** Initialize OpenTelemetry tracing */
219
- function setupTracing(options) {
220
- if (isInitialized$1) {
221
- console.warn("[neoiq-foundation] Tracing already initialized");
222
- return;
223
- }
224
- const { serviceName, serviceVersion, environment, endpoint = getDefaultOtelEndpoint(), autoInstrumentation = {} } = options;
225
- const resource = (0, __opentelemetry_resources.resourceFromAttributes)({
226
- [__opentelemetry_semantic_conventions.ATTR_SERVICE_NAME]: serviceName,
227
- [__opentelemetry_semantic_conventions.ATTR_SERVICE_VERSION]: serviceVersion,
228
- "deployment.environment": environment
229
- });
230
- const traceExporter = new __opentelemetry_exporter_trace_otlp_grpc.OTLPTraceExporter({ url: endpoint });
231
- const instrumentationConfig = buildInstrumentationConfig(autoInstrumentation);
232
- sdk = new __opentelemetry_sdk_node.NodeSDK({
233
- resource,
234
- traceExporter,
235
- instrumentations: [(0, __opentelemetry_auto_instrumentations_node.getNodeAutoInstrumentations)(instrumentationConfig)]
236
- });
237
- sdk.start();
238
- isInitialized$1 = true;
239
- }
240
- function buildInstrumentationConfig(config) {
241
- const mapping = {
242
- http: "@opentelemetry/instrumentation-http",
243
- fastify: "@opentelemetry/instrumentation-fastify",
244
- express: "@opentelemetry/instrumentation-express",
245
- mongodb: "@opentelemetry/instrumentation-mongodb",
246
- pg: "@opentelemetry/instrumentation-pg",
247
- mysql: "@opentelemetry/instrumentation-mysql",
248
- redis: "@opentelemetry/instrumentation-redis",
249
- ioredis: "@opentelemetry/instrumentation-ioredis",
250
- grpc: "@opentelemetry/instrumentation-grpc",
251
- fs: "@opentelemetry/instrumentation-fs",
252
- dns: "@opentelemetry/instrumentation-dns"
253
- };
254
- const result = {};
255
- for (const [key, instrumentationName] of Object.entries(mapping)) {
256
- const userSetting = config[key];
257
- const defaultValue = key !== "fs" && key !== "dns";
258
- result[instrumentationName] = { enabled: userSetting ?? defaultValue };
259
- }
260
- return result;
261
- }
262
- /** Shutdown tracing gracefully */
263
- async function shutdownTracing() {
264
- if (!sdk) return;
265
- try {
266
- await sdk.shutdown();
267
- isInitialized$1 = false;
268
- sdk = null;
269
- } catch (error) {
270
- console.error("[neoiq-foundation] Error shutting down tracing:", error);
271
- }
272
- }
273
- function getTracer(name) {
274
- return __opentelemetry_api.trace.getTracer(name);
275
- }
276
- function getActiveSpan() {
277
- return __opentelemetry_api.trace.getActiveSpan();
278
- }
279
- function getTraceContext() {
280
- const span = __opentelemetry_api.trace.getActiveSpan();
281
- if (!span) return {};
282
- const ctx = span.spanContext();
283
- return {
284
- traceId: ctx.traceId,
285
- spanId: ctx.spanId
286
- };
287
- }
288
- function isTracingEnabled() {
289
- return isInitialized$1;
290
- }
291
-
292
340
  //#endregion
293
341
  //#region src/features/metrics.ts
294
342
  let meterProvider = null;
@@ -299,7 +347,7 @@ function setupMetrics(options) {
299
347
  console.warn("[neoiq-foundation] Metrics already initialized");
300
348
  return;
301
349
  }
302
- const { serviceName, serviceVersion, environment, endpoint = getDefaultOtelEndpoint(), intervalMs = 5e3 } = options;
350
+ const { serviceName, serviceVersion, environment, endpoint = require_tracing.getDefaultOtelEndpoint(), intervalMs = 5e3 } = options;
303
351
  const resource = (0, __opentelemetry_resources.resourceFromAttributes)({
304
352
  [__opentelemetry_semantic_conventions.ATTR_SERVICE_NAME]: serviceName,
305
353
  [__opentelemetry_semantic_conventions.ATTR_SERVICE_VERSION]: serviceVersion,
@@ -326,6 +374,7 @@ async function shutdownMetrics() {
326
374
  meterProvider = null;
327
375
  } catch (error) {
328
376
  console.error("[neoiq-foundation] Error shutting down metrics:", error);
377
+ throw error;
329
378
  }
330
379
  }
331
380
  function getMeter(name, version = "1.0.0") {
@@ -377,7 +426,6 @@ function createObservabilityPlugin(options) {
377
426
  const maxBodySize = requestLogging.maxBodySize ?? 10 * 1024;
378
427
  const headersToRedact = requestLogging.redactHeaders ?? DEFAULT_REDACT_HEADERS;
379
428
  const runInContext = contextManager ? (ctx, fn) => contextManager.run(ctx, fn) : (_ctx, fn) => fn();
380
- const tracer = tracingEnabled ? __opentelemetry_api.trace.getTracer("neoiq-foundation-node") : null;
381
429
  let requestCounter;
382
430
  let requestDuration;
383
431
  let requestErrors;
@@ -394,22 +442,12 @@ function createObservabilityPlugin(options) {
394
442
  }
395
443
  const correlationId = request.headers["x-request-id"] || (0, crypto.randomUUID)();
396
444
  reply.header("x-request-id", correlationId);
397
- let span;
398
445
  let traceId = "";
399
446
  let spanId = "";
400
- if (tracer) {
401
- const parentContext = __opentelemetry_api.propagation.extract(__opentelemetry_api.context.active(), request.headers);
402
- span = tracer.startSpan(`${request.method} ${request.routeOptions?.url || request.url}`, {
403
- kind: 1,
404
- attributes: {
405
- "http.method": request.method,
406
- "http.url": request.url,
407
- "http.route": request.routeOptions?.url || request.url,
408
- "http.user_agent": request.headers["user-agent"] || "",
409
- "http.correlation_id": correlationId
410
- }
411
- }, parentContext);
412
- const spanContext = span.spanContext();
447
+ const activeSpan = tracingEnabled ? __opentelemetry_api.trace.getActiveSpan() : void 0;
448
+ if (activeSpan) {
449
+ activeSpan.setAttribute("http.correlation_id", correlationId);
450
+ const spanContext = activeSpan.spanContext();
413
451
  traceId = spanContext.traceId;
414
452
  spanId = spanContext.spanId;
415
453
  }
@@ -419,7 +457,6 @@ function createObservabilityPlugin(options) {
419
457
  spanId,
420
458
  startTime: Date.now()
421
459
  };
422
- request.__span = span;
423
460
  request.__requestContext = requestContext;
424
461
  runInContext(requestContext, () => {
425
462
  const logData = {
@@ -445,7 +482,7 @@ function createObservabilityPlugin(options) {
445
482
  runInContext(ctx, () => {
446
483
  logger.debug({
447
484
  correlation_id: ctx.correlationId,
448
- body: truncateBody(request.body, maxBodySize)
485
+ body: sanitizeBody(truncateBody(request.body, maxBodySize))
449
486
  }, "Request body");
450
487
  done();
451
488
  });
@@ -460,14 +497,13 @@ function createObservabilityPlugin(options) {
460
497
  logger.debug({
461
498
  correlation_id: ctx.correlationId,
462
499
  statusCode: reply.statusCode,
463
- body: truncateBody(payload, maxBodySize)
500
+ body: sanitizeBody(truncateBody(payload, maxBodySize))
464
501
  }, "Response body");
465
502
  done(null, payload);
466
503
  });
467
504
  });
468
505
  fastify.addHook("onResponse", (request, reply, done) => {
469
506
  const ctx = request.__requestContext;
470
- const span = request.__span;
471
507
  if (!ctx) {
472
508
  done();
473
509
  return;
@@ -491,18 +527,15 @@ function createObservabilityPlugin(options) {
491
527
  requestDuration.record(durationMs, labels);
492
528
  if (reply.statusCode >= 400) requestErrors.add(1, labels);
493
529
  }
494
- if (span) {
495
- span.setStatus({ code: reply.statusCode < 400 ? __opentelemetry_api.SpanStatusCode.OK : __opentelemetry_api.SpanStatusCode.ERROR });
496
- span.setAttribute("http.status_code", reply.statusCode);
497
- span.setAttribute("http.response_time_ms", durationMs);
498
- span.end();
530
+ if (tracingEnabled) {
531
+ const activeSpan = __opentelemetry_api.trace.getActiveSpan();
532
+ if (activeSpan) activeSpan.setAttribute("http.response_time_ms", durationMs);
499
533
  }
500
534
  done();
501
535
  });
502
536
  });
503
537
  fastify.addHook("onError", (request, _reply, error, done) => {
504
538
  const ctx = request.__requestContext;
505
- const span = request.__span;
506
539
  if (!ctx) {
507
540
  done();
508
541
  return;
@@ -514,12 +547,15 @@ function createObservabilityPlugin(options) {
514
547
  url: request.url,
515
548
  error: error.message
516
549
  }, "Request failed");
517
- if (span) {
518
- span.setStatus({
519
- code: __opentelemetry_api.SpanStatusCode.ERROR,
520
- message: error.message
521
- });
522
- span.recordException(error);
550
+ if (tracingEnabled) {
551
+ const activeSpan = __opentelemetry_api.trace.getActiveSpan();
552
+ if (activeSpan) {
553
+ activeSpan.setStatus({
554
+ code: __opentelemetry_api.SpanStatusCode.ERROR,
555
+ message: error.message
556
+ });
557
+ activeSpan.recordException(error);
558
+ }
523
559
  }
524
560
  done();
525
561
  });
@@ -616,31 +652,34 @@ function createHttpClient(options) {
616
652
  }
617
653
  return Promise.reject(error);
618
654
  });
619
- const retryConfig = {
620
- retries: retry.retries ?? 3,
621
- retryDelay: (retryCount) => (retry.retryDelay ?? 1e3) * Math.pow(2, retryCount - 1),
622
- retryCondition: (error) => {
623
- const retryStatusCodes = retry.retryStatusCodes ?? [
624
- 408,
625
- 429,
626
- 500,
627
- 502,
628
- 503,
629
- 504
630
- ];
631
- return !error.response || retryStatusCodes.includes(error.response?.status || 0);
632
- },
633
- onRetry: (retryCount, error, requestConfig) => {
634
- logger.warn({
635
- retryCount,
636
- url: `${requestConfig.baseURL || ""}${requestConfig.url}`,
637
- error: error.message
638
- }, "Retrying request");
639
- }
640
- };
641
- (0, axios_retry.default)(client, retryConfig);
642
- if (cbOptions.enabled !== false) {
643
- const breaker = new opossum.default(async (config) => client.request(config), {
655
+ if (retry.enabled !== false) {
656
+ const retryConfig = {
657
+ retries: retry.retries ?? 3,
658
+ retryDelay: (retryCount) => (retry.retryDelay ?? 1e3) * Math.pow(2, retryCount - 1),
659
+ retryCondition: (error) => {
660
+ const retryStatusCodes = retry.retryStatusCodes ?? [
661
+ 408,
662
+ 429,
663
+ 500,
664
+ 502,
665
+ 503,
666
+ 504
667
+ ];
668
+ return !error.response || retryStatusCodes.includes(error.response?.status || 0);
669
+ },
670
+ onRetry: (retryCount, error, requestConfig) => {
671
+ logger.warn({
672
+ retryCount,
673
+ url: `${requestConfig.baseURL || ""}${requestConfig.url}`,
674
+ error: error.message
675
+ }, "Retrying request");
676
+ }
677
+ };
678
+ (0, axios_retry.default)(client, retryConfig);
679
+ }
680
+ if (cbOptions.enabled === true) {
681
+ const originalRequest = client.request.bind(client);
682
+ const breaker = new opossum.default(async (config) => originalRequest(config), {
644
683
  timeout,
645
684
  resetTimeout: cbOptions.resetTimeout ?? 3e4,
646
685
  errorThresholdPercentage: cbOptions.errorThresholdPercentage ?? 50,
@@ -649,17 +688,58 @@ function createHttpClient(options) {
649
688
  breaker.on("open", () => logger.warn({ targetService: serviceName }, "Circuit breaker OPEN"));
650
689
  breaker.on("halfOpen", () => logger.info({ targetService: serviceName }, "Circuit breaker HALF-OPEN"));
651
690
  breaker.on("close", () => logger.info({ targetService: serviceName }, "Circuit breaker CLOSED"));
691
+ client.request = (config) => breaker.fire(config);
692
+ client.get = (url, config) => breaker.fire({
693
+ ...config,
694
+ method: "GET",
695
+ url
696
+ });
697
+ client.post = (url, data, config) => breaker.fire({
698
+ ...config,
699
+ method: "POST",
700
+ url,
701
+ data
702
+ });
703
+ client.put = (url, data, config) => breaker.fire({
704
+ ...config,
705
+ method: "PUT",
706
+ url,
707
+ data
708
+ });
709
+ client.delete = (url, config) => breaker.fire({
710
+ ...config,
711
+ method: "DELETE",
712
+ url
713
+ });
714
+ client.patch = (url, data, config) => breaker.fire({
715
+ ...config,
716
+ method: "PATCH",
717
+ url,
718
+ data
719
+ });
720
+ client.__originalRequest = originalRequest;
652
721
  }
653
722
  return client;
654
723
  }
655
724
 
656
725
  //#endregion
657
726
  //#region src/foundation.ts
727
+ const deprecationWarnings = new Set();
728
+ function warnDeprecation(oldPath, newPath, logger) {
729
+ if (deprecationWarnings.has(oldPath)) return;
730
+ deprecationWarnings.add(oldPath);
731
+ const msg = `foundation.${oldPath}() is deprecated. Use foundation.${newPath}() instead. This alias will be removed in the next major version.`;
732
+ if (logger) logger.warn({
733
+ deprecated: oldPath,
734
+ replacement: newPath
735
+ }, msg);
736
+ else console.warn(`[neoiq-foundation] DEPRECATED: ${msg}`);
737
+ }
658
738
  /** Create a fully configured observability foundation */
659
739
  function createFoundation(input) {
660
740
  const startTime = Date.now();
661
- const config = parseConfig(input);
662
- const { serviceName, serviceVersion, environment, features: featuresConfig, otel, logging: loggingConfig, requestLogging: requestLoggingConfig } = config;
741
+ const config = require_tracing.parseConfig(input);
742
+ const { serviceName, serviceVersion, environment, features: featuresConfig, otel, logging: loggingConfig, requestLogging: requestLoggingConfig, redaction: redactionConfig, shutdown: shutdownConfig } = config;
663
743
  const features = {
664
744
  tracing: featuresConfig.tracing ?? true,
665
745
  metrics: featuresConfig.metrics ?? true,
@@ -675,7 +755,8 @@ function createFoundation(input) {
675
755
  environment,
676
756
  level: loggingConfig.level ?? "info",
677
757
  prettyPrint: loggingConfig.prettyPrint ?? environment === "development",
678
- contextManager
758
+ contextManager,
759
+ additionalRedactPaths: redactionConfig.additionalPaths
679
760
  });
680
761
  setGlobalLogger(logger);
681
762
  } catch (err) {
@@ -705,7 +786,7 @@ function createFoundation(input) {
705
786
  let tracerInstance = null;
706
787
  let tracingError;
707
788
  if (features.tracing) try {
708
- setupTracing({
789
+ require_tracing.setupTracing({
709
790
  serviceName,
710
791
  serviceVersion,
711
792
  environment,
@@ -713,7 +794,7 @@ function createFoundation(input) {
713
794
  sampleRate: otel.traceSampleRate,
714
795
  autoInstrumentation: featuresConfig.autoInstrumentation
715
796
  });
716
- tracerInstance = getTracer(serviceName);
797
+ tracerInstance = require_tracing.getTracer(serviceName);
717
798
  logger.info({
718
799
  feature: "tracing",
719
800
  endpoint: otel.endpoint
@@ -747,17 +828,139 @@ function createFoundation(input) {
747
828
  environment,
748
829
  features
749
830
  }, "Foundation initialized");
750
- const foundation = {
751
- config,
831
+ if (shutdownConfig.flushOnCrash) {
832
+ const flushTimeoutMs = shutdownConfig.flushTimeoutMs ?? 5e3;
833
+ const crashFlush = (origin, err) => {
834
+ const error = err instanceof Error ? err : new Error(String(err));
835
+ logger.error({
836
+ error: error.message,
837
+ stack: error.stack,
838
+ origin
839
+ }, "Process crash detected, flushing telemetry");
840
+ const flushPromises = [];
841
+ if (features.tracing && require_tracing.isTracingEnabled()) flushPromises.push(require_tracing.shutdownTracing());
842
+ if (features.metrics && isMetricsEnabled()) flushPromises.push(shutdownMetrics());
843
+ const timeout = new Promise((resolve) => setTimeout(resolve, flushTimeoutMs));
844
+ Promise.race([Promise.allSettled(flushPromises), timeout]).finally(() => {
845
+ process.exit(1);
846
+ });
847
+ };
848
+ process.on("uncaughtException", (err) => crashFlush("uncaughtException", err));
849
+ process.on("unhandledRejection", (reason) => crashFlush("unhandledRejection", reason));
850
+ logger.info({ flushTimeoutMs }, "Crash-flush handlers registered");
851
+ }
852
+ const traceInSpan = async (name, fn) => {
853
+ const tracer = tracerInstance || require_tracing.getTracer(serviceName);
854
+ return new Promise((resolve, reject) => {
855
+ tracer.startActiveSpan(name, async (span) => {
856
+ try {
857
+ const result = await fn();
858
+ span.setStatus({ code: __opentelemetry_api.SpanStatusCode.OK });
859
+ span.end();
860
+ resolve(result);
861
+ } catch (err) {
862
+ const error = err;
863
+ span.setStatus({
864
+ code: __opentelemetry_api.SpanStatusCode.ERROR,
865
+ message: error.message
866
+ });
867
+ span.recordException(error);
868
+ span.end();
869
+ logger.error({
870
+ span: name,
871
+ error: error.message
872
+ }, "Span failed");
873
+ reject(error);
874
+ }
875
+ });
876
+ });
877
+ };
878
+ const observability = {
752
879
  logger,
753
- context: contextManager,
754
880
  tracer: tracerInstance,
755
881
  meter: meterInstance,
756
- features,
757
- getTracer: (name) => getTracer(name || serviceName),
882
+ getTracer: (name) => require_tracing.getTracer(name || serviceName),
758
883
  getMeter: (name, version) => getMeter(name, version),
759
- getTraceContext,
760
- getActiveSpan,
884
+ getTraceContext: require_tracing.getTraceContext,
885
+ getActiveSpan: require_tracing.getActiveSpan,
886
+ trace: traceInSpan
887
+ };
888
+ const httpModule = { createClient: (options) => createHttpClient({
889
+ ...options,
890
+ foundation
891
+ }) };
892
+ const buildHealthStatus = () => {
893
+ const tracingUp = !features.tracing || !tracingError && require_tracing.isTracingEnabled();
894
+ const metricsUp = !features.metrics || !metricsError && isMetricsEnabled();
895
+ const loggingUp = !loggingError;
896
+ const allUp = tracingUp && metricsUp && loggingUp;
897
+ const allDown = (!tracingUp || !features.tracing) && (!metricsUp || !features.metrics) && !loggingUp;
898
+ let status = "healthy";
899
+ if (!allUp) status = allDown ? "unhealthy" : "degraded";
900
+ return {
901
+ status,
902
+ timestamp: new Date().toISOString(),
903
+ service: serviceName,
904
+ version: serviceVersion,
905
+ uptime: Math.floor((Date.now() - startTime) / 1e3),
906
+ components: {
907
+ tracing: {
908
+ enabled: features.tracing,
909
+ status: !features.tracing ? "disabled" : tracingError ? "down" : "up",
910
+ message: tracingError
911
+ },
912
+ metrics: {
913
+ enabled: features.metrics,
914
+ status: !features.metrics ? "disabled" : metricsError ? "down" : "up",
915
+ message: metricsError
916
+ },
917
+ logging: {
918
+ enabled: features.logging,
919
+ status: loggingError ? "down" : "up",
920
+ message: loggingError
921
+ }
922
+ }
923
+ };
924
+ };
925
+ const shutdownFn = async () => {
926
+ logger.info({}, "Shutting down foundation...");
927
+ const promises = [];
928
+ if (features.tracing && require_tracing.isTracingEnabled()) promises.push(require_tracing.shutdownTracing());
929
+ if (features.metrics && isMetricsEnabled()) promises.push(shutdownMetrics());
930
+ await Promise.all(promises);
931
+ logger.info({}, "Foundation shutdown complete");
932
+ };
933
+ const isReadyFn = () => {
934
+ if (features.tracing && !tracingError && !require_tracing.isTracingEnabled()) return false;
935
+ if (features.metrics && !metricsError && !isMetricsEnabled()) return false;
936
+ return true;
937
+ };
938
+ const safeRunFn = async (fn, fallback) => {
939
+ try {
940
+ return await fn();
941
+ } catch (err) {
942
+ const error = err;
943
+ logger.error({
944
+ error: error.message,
945
+ stack: error.stack
946
+ }, "safeRun caught error");
947
+ return fallback;
948
+ }
949
+ };
950
+ const lifecycle = {
951
+ health: buildHealthStatus,
952
+ isReady: isReadyFn,
953
+ shutdown: shutdownFn,
954
+ safeRun: safeRunFn
955
+ };
956
+ const foundation = {
957
+ config,
958
+ features,
959
+ observability,
960
+ http: httpModule,
961
+ lifecycle,
962
+ logger,
963
+ context: contextManager,
761
964
  fastifyPlugin: createObservabilityPlugin({
762
965
  serviceName,
763
966
  logger,
@@ -766,91 +969,47 @@ function createFoundation(input) {
766
969
  metricsEnabled: features.metrics && !metricsError,
767
970
  requestLogging: requestLoggingConfig
768
971
  }),
769
- createHttpClient: (options) => createHttpClient({
770
- ...options,
771
- foundation
772
- }),
773
- shutdown: async () => {
774
- logger.info({}, "Shutting down foundation...");
775
- const promises = [];
776
- if (features.tracing && isTracingEnabled()) promises.push(shutdownTracing());
777
- if (features.metrics && isMetricsEnabled()) promises.push(shutdownMetrics());
778
- await Promise.all(promises);
779
- logger.info({}, "Foundation shutdown complete");
972
+ tracer: tracerInstance,
973
+ meter: meterInstance,
974
+ getTracer: (name) => {
975
+ warnDeprecation("getTracer", "observability.getTracer", logger);
976
+ return observability.getTracer(name);
977
+ },
978
+ getMeter: (name, version) => {
979
+ warnDeprecation("getMeter", "observability.getMeter", logger);
980
+ return observability.getMeter(name, version);
981
+ },
982
+ getTraceContext: () => {
983
+ warnDeprecation("getTraceContext", "observability.getTraceContext", logger);
984
+ return observability.getTraceContext();
985
+ },
986
+ getActiveSpan: () => {
987
+ warnDeprecation("getActiveSpan", "observability.getActiveSpan", logger);
988
+ return observability.getActiveSpan();
989
+ },
990
+ createHttpClient: (options) => {
991
+ warnDeprecation("createHttpClient", "http.createClient", logger);
992
+ return httpModule.createClient(options);
993
+ },
994
+ shutdown: () => {
995
+ warnDeprecation("shutdown", "lifecycle.shutdown", logger);
996
+ return lifecycle.shutdown();
780
997
  },
781
998
  isReady: () => {
782
- if (features.tracing && !tracingError && !isTracingEnabled()) return false;
783
- if (features.metrics && !metricsError && !isMetricsEnabled()) return false;
784
- return true;
999
+ warnDeprecation("isReady", "lifecycle.isReady", logger);
1000
+ return lifecycle.isReady();
785
1001
  },
786
1002
  health: () => {
787
- const tracingUp = !features.tracing || !tracingError && isTracingEnabled();
788
- const metricsUp = !features.metrics || !metricsError && isMetricsEnabled();
789
- const loggingUp = !loggingError;
790
- const allUp = tracingUp && metricsUp && loggingUp;
791
- const anyDown = features.tracing && tracingError || features.metrics && metricsError || loggingError;
792
- return {
793
- status: allUp ? "healthy" : anyDown ? "degraded" : "unhealthy",
794
- timestamp: new Date().toISOString(),
795
- service: serviceName,
796
- version: serviceVersion,
797
- uptime: Math.floor((Date.now() - startTime) / 1e3),
798
- components: {
799
- tracing: {
800
- enabled: features.tracing,
801
- status: !features.tracing ? "disabled" : tracingError ? "down" : "up",
802
- message: tracingError
803
- },
804
- metrics: {
805
- enabled: features.metrics,
806
- status: !features.metrics ? "disabled" : metricsError ? "down" : "up",
807
- message: metricsError
808
- },
809
- logging: {
810
- enabled: features.logging,
811
- status: loggingError ? "down" : "up",
812
- message: loggingError
813
- }
814
- }
815
- };
1003
+ warnDeprecation("health", "lifecycle.health", logger);
1004
+ return lifecycle.health();
816
1005
  },
817
- trace: async (name, fn) => {
818
- const tracer = tracerInstance || getTracer(serviceName);
819
- return new Promise((resolve, reject) => {
820
- tracer.startActiveSpan(name, async (span) => {
821
- try {
822
- const result = await fn();
823
- span.setStatus({ code: __opentelemetry_api.SpanStatusCode.OK });
824
- span.end();
825
- resolve(result);
826
- } catch (err) {
827
- const error = err;
828
- span.setStatus({
829
- code: __opentelemetry_api.SpanStatusCode.ERROR,
830
- message: error.message
831
- });
832
- span.recordException(error);
833
- span.end();
834
- logger.error({
835
- span: name,
836
- error: error.message
837
- }, "Span failed");
838
- reject(error);
839
- }
840
- });
841
- });
1006
+ trace: (name, fn) => {
1007
+ warnDeprecation("trace", "observability.trace", logger);
1008
+ return observability.trace(name, fn);
842
1009
  },
843
- safeRun: async (fn, fallback) => {
844
- try {
845
- return await fn();
846
- } catch (err) {
847
- const error = err;
848
- logger.error({
849
- error: error.message,
850
- stack: error.stack
851
- }, "safeRun caught error");
852
- return fallback;
853
- }
1010
+ safeRun: (fn, fallback) => {
1011
+ warnDeprecation("safeRun", "lifecycle.safeRun", logger);
1012
+ return lifecycle.safeRun(fn, fallback);
854
1013
  }
855
1014
  };
856
1015
  return foundation;
@@ -859,13 +1018,303 @@ function createFoundation(input) {
859
1018
  const setupObservability = createFoundation;
860
1019
 
861
1020
  //#endregion
862
- exports.AutoInstrumentationConfigSchema = AutoInstrumentationConfigSchema
863
- exports.FeaturesConfigSchema = FeaturesConfigSchema
864
- exports.FoundationConfigSchema = FoundationConfigSchema
865
- exports.LoggingConfigSchema = LoggingConfigSchema
866
- exports.OtelConfigSchema = OtelConfigSchema
867
- exports.RequestLoggingConfigSchema = RequestLoggingConfigSchema
1021
+ //#region src/integrations/object-store/aws-s3.ts
1022
+ function isAwsError(err) {
1023
+ return typeof err === "object" && err !== null;
1024
+ }
1025
+ async function loadAwsS3() {
1026
+ try {
1027
+ const clientMod = await import("@aws-sdk/client-s3");
1028
+ const presignerMod = await import("@aws-sdk/s3-request-presigner");
1029
+ return {
1030
+ S3Client: clientMod.S3Client,
1031
+ PutObjectCommand: clientMod.PutObjectCommand,
1032
+ GetObjectCommand: clientMod.GetObjectCommand,
1033
+ HeadObjectCommand: clientMod.HeadObjectCommand,
1034
+ DeleteObjectCommand: clientMod.DeleteObjectCommand,
1035
+ ListObjectsV2Command: clientMod.ListObjectsV2Command,
1036
+ getSignedUrl: presignerMod.getSignedUrl
1037
+ };
1038
+ } catch (err) {
1039
+ const e = err instanceof Error ? err : new Error(String(err));
1040
+ throw new Error(`AWS SDK not available. Install optional peer deps: @aws-sdk/client-s3 and @aws-sdk/s3-request-presigner. Original error: ${e.message}`);
1041
+ }
1042
+ }
1043
+ /**
1044
+ * AwsS3ObjectStore - wraps AWS S3 behind the provider-agnostic `ObjectStore` interface.
1045
+ *
1046
+ * This implementation uses dynamic imports so `neoiq-foundation-node` can be used without AWS SDK.
1047
+ */
1048
+ var AwsS3ObjectStore = class {
1049
+ provider = "aws-s3";
1050
+ clientPromise;
1051
+ awsPromise = loadAwsS3();
1052
+ constructor(options = {}) {
1053
+ if (options.client) {
1054
+ this.clientPromise = Promise.resolve(options.client);
1055
+ return;
1056
+ }
1057
+ this.clientPromise = (async () => {
1058
+ const aws = await this.awsPromise;
1059
+ return new aws.S3Client(options.clientOptions ?? {});
1060
+ })();
1061
+ }
1062
+ async client() {
1063
+ return this.clientPromise;
1064
+ }
1065
+ async putObject(ref, body, options = {}) {
1066
+ const aws = await this.awsPromise;
1067
+ const s3 = await this.client();
1068
+ const res = await s3.send(new aws.PutObjectCommand({
1069
+ Bucket: ref.bucket,
1070
+ Key: ref.key,
1071
+ Body: body,
1072
+ ContentType: options.contentType,
1073
+ CacheControl: options.cacheControl,
1074
+ Metadata: options.metadata
1075
+ }));
1076
+ return {
1077
+ etag: res?.ETag,
1078
+ versionId: res?.VersionId
1079
+ };
1080
+ }
1081
+ async getObject(ref) {
1082
+ const aws = await this.awsPromise;
1083
+ const s3 = await this.client();
1084
+ const res = await s3.send(new aws.GetObjectCommand({
1085
+ Bucket: ref.bucket,
1086
+ Key: ref.key
1087
+ }));
1088
+ if (!res?.Body) {
1089
+ const err = new Error(`S3 GetObject returned empty body: ${ref.bucket}/${ref.key}`);
1090
+ err.code = "EMPTY_BODY";
1091
+ throw err;
1092
+ }
1093
+ return {
1094
+ body: res.Body,
1095
+ contentType: res.ContentType,
1096
+ contentLength: res.ContentLength,
1097
+ etag: res.ETag,
1098
+ versionId: res.VersionId,
1099
+ metadata: res.Metadata,
1100
+ lastModified: res.LastModified
1101
+ };
1102
+ }
1103
+ async headObject(ref) {
1104
+ const aws = await this.awsPromise;
1105
+ const s3 = await this.client();
1106
+ try {
1107
+ const res = await s3.send(new aws.HeadObjectCommand({
1108
+ Bucket: ref.bucket,
1109
+ Key: ref.key
1110
+ }));
1111
+ return {
1112
+ exists: true,
1113
+ contentType: res.ContentType,
1114
+ contentLength: res.ContentLength,
1115
+ etag: res.ETag,
1116
+ versionId: res.VersionId,
1117
+ metadata: res.Metadata,
1118
+ lastModified: res.LastModified
1119
+ };
1120
+ } catch (err) {
1121
+ if (isAwsError(err)) {
1122
+ const name = String(err.name ?? "");
1123
+ const httpStatus = err.$metadata?.httpStatusCode;
1124
+ if (httpStatus === 404 || name.includes("NotFound") || name.includes("NoSuchKey")) return { exists: false };
1125
+ }
1126
+ throw err;
1127
+ }
1128
+ }
1129
+ async deleteObject(ref) {
1130
+ const aws = await this.awsPromise;
1131
+ const s3 = await this.client();
1132
+ await s3.send(new aws.DeleteObjectCommand({
1133
+ Bucket: ref.bucket,
1134
+ Key: ref.key
1135
+ }));
1136
+ }
1137
+ async listObjects(options) {
1138
+ const aws = await this.awsPromise;
1139
+ const s3 = await this.client();
1140
+ const res = await s3.send(new aws.ListObjectsV2Command({
1141
+ Bucket: options.bucket,
1142
+ Prefix: options.prefix,
1143
+ ContinuationToken: options.continuationToken,
1144
+ MaxKeys: options.maxKeys
1145
+ }));
1146
+ const contents = res?.Contents ?? [];
1147
+ return {
1148
+ objects: contents.filter((o) => typeof o.Key === "string").map((o) => ({
1149
+ key: o.Key,
1150
+ size: o.Size,
1151
+ etag: o.ETag,
1152
+ lastModified: o.LastModified
1153
+ })),
1154
+ nextContinuationToken: res?.NextContinuationToken
1155
+ };
1156
+ }
1157
+ async presignGetObject(ref, options) {
1158
+ const aws = await this.awsPromise;
1159
+ const s3 = await this.client();
1160
+ return aws.getSignedUrl(s3, new aws.GetObjectCommand({
1161
+ Bucket: ref.bucket,
1162
+ Key: ref.key,
1163
+ ResponseContentType: options.responseContentType
1164
+ }), { expiresIn: options.expiresInSeconds });
1165
+ }
1166
+ async presignPutObject(ref, options) {
1167
+ const aws = await this.awsPromise;
1168
+ const s3 = await this.client();
1169
+ return aws.getSignedUrl(s3, new aws.PutObjectCommand({
1170
+ Bucket: ref.bucket,
1171
+ Key: ref.key,
1172
+ ContentType: options.contentType
1173
+ }), { expiresIn: options.expiresInSeconds });
1174
+ }
1175
+ };
1176
+
1177
+ //#endregion
1178
+ //#region src/integrations/object-store/in-memory.ts
1179
+ const DEFAULT_MAX_OBJECTS = 1e4;
1180
+ const STREAM_TIMEOUT_MS = 3e4;
1181
+ function toBuffer(body) {
1182
+ if (typeof body === "string") return Buffer.from(body);
1183
+ if (Buffer.isBuffer(body)) return body;
1184
+ if (body instanceof Uint8Array) return Buffer.from(body);
1185
+ return new Promise((resolve, reject) => {
1186
+ const chunks = [];
1187
+ const timer = setTimeout(() => {
1188
+ body.destroy(new Error("InMemoryObjectStore: stream read timed out"));
1189
+ reject(new Error("InMemoryObjectStore: stream read timed out"));
1190
+ }, STREAM_TIMEOUT_MS);
1191
+ body.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
1192
+ body.on("end", () => {
1193
+ clearTimeout(timer);
1194
+ resolve(Buffer.concat(chunks));
1195
+ });
1196
+ body.on("error", (err) => {
1197
+ clearTimeout(timer);
1198
+ reject(err);
1199
+ });
1200
+ });
1201
+ }
1202
+ function computeEtag(buf) {
1203
+ return `"${(0, node_crypto.createHash)("md5").update(buf).digest("hex")}"`;
1204
+ }
1205
+ /**
1206
+ * InMemoryObjectStore - useful for local dev, unit tests, and as a safe default.
1207
+ * NOTE: presign* methods are not meaningful here and will throw.
1208
+ */
1209
+ var InMemoryObjectStore = class {
1210
+ provider = "in-memory";
1211
+ buckets = new Map();
1212
+ maxObjects;
1213
+ objectCount = 0;
1214
+ constructor(options = {}) {
1215
+ this.maxObjects = options.maxObjects ?? DEFAULT_MAX_OBJECTS;
1216
+ }
1217
+ bucketMap(bucket) {
1218
+ let m = this.buckets.get(bucket);
1219
+ if (!m) {
1220
+ m = new Map();
1221
+ this.buckets.set(bucket, m);
1222
+ }
1223
+ return m;
1224
+ }
1225
+ async putObject(ref, body, options = {}) {
1226
+ const map = this.bucketMap(ref.bucket);
1227
+ const isNew = !map.has(ref.key);
1228
+ if (isNew && this.objectCount >= this.maxObjects) throw new Error(`InMemoryObjectStore: max object limit reached (${this.maxObjects})`);
1229
+ const buf = await toBuffer(body);
1230
+ const obj = {
1231
+ body: buf,
1232
+ contentType: options.contentType,
1233
+ cacheControl: options.cacheControl,
1234
+ metadata: options.metadata,
1235
+ etag: computeEtag(buf),
1236
+ lastModified: new Date()
1237
+ };
1238
+ map.set(ref.key, obj);
1239
+ if (isNew) this.objectCount++;
1240
+ return { etag: obj.etag };
1241
+ }
1242
+ async getObject(ref) {
1243
+ const obj = this.bucketMap(ref.bucket).get(ref.key);
1244
+ if (!obj) {
1245
+ const err = new Error(`Object not found: ${ref.bucket}/${ref.key}`);
1246
+ err.code = "OBJECT_NOT_FOUND";
1247
+ throw err;
1248
+ }
1249
+ return {
1250
+ body: node_stream.Readable.from(obj.body),
1251
+ contentType: obj.contentType,
1252
+ contentLength: obj.body.length,
1253
+ etag: obj.etag,
1254
+ metadata: obj.metadata,
1255
+ lastModified: obj.lastModified
1256
+ };
1257
+ }
1258
+ async headObject(ref) {
1259
+ const obj = this.bucketMap(ref.bucket).get(ref.key);
1260
+ if (!obj) return { exists: false };
1261
+ return {
1262
+ exists: true,
1263
+ contentType: obj.contentType,
1264
+ contentLength: obj.body.length,
1265
+ etag: obj.etag,
1266
+ metadata: obj.metadata,
1267
+ lastModified: obj.lastModified
1268
+ };
1269
+ }
1270
+ async deleteObject(ref) {
1271
+ const map = this.bucketMap(ref.bucket);
1272
+ if (map.delete(ref.key)) this.objectCount--;
1273
+ }
1274
+ async listObjects(options) {
1275
+ const { bucket, prefix = "", continuationToken, maxKeys = 1e3 } = options;
1276
+ const map = this.bucketMap(bucket);
1277
+ const all = [...map.entries()].filter(([key]) => key.startsWith(prefix)).map(([key, obj]) => ({
1278
+ key,
1279
+ size: obj.body.length,
1280
+ etag: obj.etag,
1281
+ lastModified: obj.lastModified
1282
+ })).sort((a, b) => a.key.localeCompare(b.key));
1283
+ let startIndex = 0;
1284
+ if (continuationToken) {
1285
+ const idx = all.findIndex((o) => o.key === continuationToken);
1286
+ if (idx === -1) throw new Error(`InMemoryObjectStore: invalid continuationToken "${continuationToken}"`);
1287
+ startIndex = idx + 1;
1288
+ }
1289
+ const page = all.slice(startIndex, startIndex + maxKeys);
1290
+ const hasMore = startIndex + maxKeys < all.length;
1291
+ return {
1292
+ objects: page,
1293
+ nextContinuationToken: hasMore ? page[page.length - 1]?.key : void 0
1294
+ };
1295
+ }
1296
+ async presignGetObject(_ref, _options) {
1297
+ throw new Error("InMemoryObjectStore does not support presigned URLs");
1298
+ }
1299
+ async presignPutObject(_ref, _options) {
1300
+ throw new Error("InMemoryObjectStore does not support presigned URLs");
1301
+ }
1302
+ };
1303
+
1304
+ //#endregion
1305
+ exports.AutoInstrumentationConfigSchema = require_tracing.AutoInstrumentationConfigSchema
1306
+ exports.AwsS3ObjectStore = AwsS3ObjectStore
1307
+ exports.FeaturesConfigSchema = require_tracing.FeaturesConfigSchema
1308
+ exports.FoundationConfigSchema = require_tracing.FoundationConfigSchema
1309
+ exports.InMemoryObjectStore = InMemoryObjectStore
1310
+ exports.LoggingConfigSchema = require_tracing.LoggingConfigSchema
1311
+ exports.OtelConfigSchema = require_tracing.OtelConfigSchema
1312
+ exports.REDACT_PATHS = REDACT_PATHS
1313
+ exports.RedactionConfigSchema = require_tracing.RedactionConfigSchema
1314
+ exports.RequestLoggingConfigSchema = require_tracing.RequestLoggingConfigSchema
1315
+ exports.ShutdownConfigSchema = require_tracing.ShutdownConfigSchema
868
1316
  exports.SpanStatusCode = __opentelemetry_api.SpanStatusCode
1317
+ exports.buildPinoRedactConfig = buildPinoRedactConfig
869
1318
  exports.context = __opentelemetry_api.context
870
1319
  exports.createContextManager = createContextManager
871
1320
  exports.createFallbackLogger = createFallbackLogger
@@ -873,22 +1322,23 @@ exports.createFoundation = createFoundation
873
1322
  exports.createHttpClient = createHttpClient
874
1323
  exports.createLogger = createLogger
875
1324
  exports.createObservabilityPlugin = createObservabilityPlugin
876
- exports.getActiveSpan = getActiveSpan
877
- exports.getDefaultOtelEndpoint = getDefaultOtelEndpoint
1325
+ exports.getActiveSpan = require_tracing.getActiveSpan
1326
+ exports.getDefaultOtelEndpoint = require_tracing.getDefaultOtelEndpoint
878
1327
  exports.getGlobalLogger = getGlobalLogger
879
1328
  exports.getMeter = getMeter
880
- exports.getTraceContext = getTraceContext
881
- exports.getTracer = getTracer
1329
+ exports.getTraceContext = require_tracing.getTraceContext
1330
+ exports.getTracer = require_tracing.getTracer
882
1331
  exports.isMetricsEnabled = isMetricsEnabled
883
- exports.isTracingEnabled = isTracingEnabled
1332
+ exports.isTracingEnabled = require_tracing.isTracingEnabled
884
1333
  exports.metrics = __opentelemetry_api.metrics
885
- exports.parseConfig = parseConfig
1334
+ exports.parseConfig = require_tracing.parseConfig
886
1335
  exports.propagation = __opentelemetry_api.propagation
1336
+ exports.sanitizeBody = sanitizeBody
887
1337
  exports.setGlobalLogger = setGlobalLogger
888
1338
  exports.setupMetrics = setupMetrics
889
1339
  exports.setupObservability = setupObservability
890
- exports.setupTracing = setupTracing
1340
+ exports.setupTracing = require_tracing.setupTracing
891
1341
  exports.shutdownMetrics = shutdownMetrics
892
- exports.shutdownTracing = shutdownTracing
1342
+ exports.shutdownTracing = require_tracing.shutdownTracing
893
1343
  exports.trace = __opentelemetry_api.trace
894
1344
  //# sourceMappingURL=index.js.map