@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/README.md +40 -1
- package/dist/bootstrap.d.mts +1 -0
- package/dist/bootstrap.d.ts +1 -0
- package/dist/bootstrap.js +31 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/bootstrap.mjs +30 -0
- package/dist/bootstrap.mjs.map +1 -0
- package/dist/index.d.mts +332 -54
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +332 -54
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +806 -356
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +768 -302
- package/dist/index.mjs.map +1 -1
- package/dist/tracing-Cv-Y3fZx.mjs +196 -0
- package/dist/tracing-Cv-Y3fZx.mjs.map +1 -0
- package/dist/tracing-DM5OFo7l.js +316 -0
- package/dist/tracing-DM5OFo7l.js.map +1 -0
- package/package.json +24 -3
package/dist/index.js
CHANGED
|
@@ -1,143 +1,262 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
//#
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
return
|
|
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
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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: () =>
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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 (
|
|
495
|
-
|
|
496
|
-
|
|
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 (
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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
|
-
|
|
751
|
-
|
|
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
|
-
|
|
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
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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
|
-
|
|
783
|
-
|
|
784
|
-
return true;
|
|
999
|
+
warnDeprecation("isReady", "lifecycle.isReady", logger);
|
|
1000
|
+
return lifecycle.isReady();
|
|
785
1001
|
},
|
|
786
1002
|
health: () => {
|
|
787
|
-
|
|
788
|
-
|
|
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:
|
|
818
|
-
|
|
819
|
-
return
|
|
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:
|
|
844
|
-
|
|
845
|
-
|
|
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
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
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
|