@ciq-dev/neoiq-foundation-node 1.1.2-beta.0 → 1.1.2-beta.10
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 +209 -360
- package/dist/bootstrap.js +1 -1
- package/dist/bootstrap.mjs +1 -1
- package/dist/index.d.mts +50 -16
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +50 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +50 -42
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +51 -43
- package/dist/index.mjs.map +1 -1
- package/dist/{tracing-DkqL2DIz.mjs → tracing-BsVfI7bV.mjs} +50 -14
- package/dist/tracing-BsVfI7bV.mjs.map +1 -0
- package/dist/{tracing-BNI3GT0b.js → tracing-baHEbJSU.js} +50 -14
- package/dist/tracing-baHEbJSU.js.map +1 -0
- package/package.json +2 -1
- package/dist/tracing-BNI3GT0b.js.map +0 -1
- package/dist/tracing-DkqL2DIz.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,295 +1,136 @@
|
|
|
1
1
|
# @ciq-dev/neoiq-foundation-node
|
|
2
2
|
|
|
3
|
-
Node.js observability foundation for CommerceIQ services
|
|
3
|
+
Node.js observability foundation for CommerceIQ services — OpenTelemetry + Groundcover.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
│ │
|
|
11
|
-
├── Traces (OTLP) ───────┤
|
|
12
|
-
└── Metrics (OTLP) ──────┘
|
|
13
|
-
|
|
14
|
-
│
|
|
15
|
-
└── Logs (stdout) ──────▶ Groundcover Agent scrapes container logs
|
|
6
|
+
Service → OTEL Collector → Groundcover
|
|
7
|
+
├── Traces (OTLP gRPC)
|
|
8
|
+
├── Metrics (OTLP gRPC)
|
|
9
|
+
└── Logs (stdout → scraped by Groundcover Agent)
|
|
16
10
|
```
|
|
17
11
|
|
|
18
12
|
## Features
|
|
19
13
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
- **Object Store Adapter**: Provider-agnostic wrapper for cloud object storage (e.g. S3) to avoid direct SDK coupling
|
|
14
|
+
- Distributed tracing, metrics, structured logs via OpenTelemetry + Pino
|
|
15
|
+
- Fastify plugin for automatic request lifecycle instrumentation
|
|
16
|
+
- Axios HTTP client with retry, circuit breaker, and trace propagation
|
|
17
|
+
- AsyncLocalStorage context manager (correlationId across async calls)
|
|
18
|
+
- HashiCorp Vault secrets client
|
|
19
|
+
- Provider-agnostic object store (S3 + in-memory)
|
|
20
|
+
- Zod-validated config with helpful error messages
|
|
28
21
|
|
|
29
22
|
---
|
|
30
23
|
|
|
31
|
-
##
|
|
32
|
-
|
|
33
|
-
### 1. Install
|
|
24
|
+
## Install
|
|
34
25
|
|
|
35
26
|
```bash
|
|
36
27
|
npm install @ciq-dev/neoiq-foundation-node
|
|
37
28
|
|
|
38
|
-
# Peer
|
|
39
|
-
npm install zod
|
|
29
|
+
# Peer deps
|
|
30
|
+
npm install zod pino pino-pretty axios axios-retry opossum fastify fastify-plugin \
|
|
31
|
+
@opentelemetry/api @opentelemetry/sdk-node @opentelemetry/sdk-metrics \
|
|
40
32
|
@opentelemetry/exporter-trace-otlp-grpc @opentelemetry/exporter-metrics-otlp-grpc \
|
|
41
|
-
@opentelemetry/auto-instrumentations-node @opentelemetry/resources
|
|
42
|
-
|
|
33
|
+
@opentelemetry/auto-instrumentations-node @opentelemetry/resources \
|
|
34
|
+
@opentelemetry/semantic-conventions
|
|
43
35
|
|
|
44
|
-
# Optional
|
|
36
|
+
# Optional — AWS S3 object store
|
|
45
37
|
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
|
|
46
38
|
```
|
|
47
39
|
|
|
48
|
-
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
49
43
|
|
|
50
44
|
```typescript
|
|
45
|
+
// src/foundation.ts — import this FIRST in the app
|
|
51
46
|
import { createFoundation } from '@ciq-dev/neoiq-foundation-node';
|
|
52
47
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
export const foundation = createFoundation({
|
|
49
|
+
serviceName: 'my-service',
|
|
50
|
+
serviceVersion: '1.0.0', // default: $SERVICE_VERSION or '1.0.0'
|
|
51
|
+
environment: 'production', // 'development' | 'staging' | 'qa' | 'production'
|
|
57
52
|
});
|
|
58
|
-
|
|
59
|
-
// Export for use in other files
|
|
60
|
-
export { foundation };
|
|
61
53
|
```
|
|
62
54
|
|
|
63
|
-
### 3. Add Fastify Plugin
|
|
64
|
-
|
|
65
55
|
```typescript
|
|
56
|
+
// src/index.ts
|
|
66
57
|
import Fastify from 'fastify';
|
|
67
58
|
import { foundation } from './foundation';
|
|
68
59
|
|
|
69
60
|
const app = Fastify();
|
|
70
|
-
app.register(foundation.fastifyPlugin);
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### 4. Set Environment Variable in Kubernetes
|
|
74
|
-
|
|
75
|
-
```yaml
|
|
76
|
-
env:
|
|
77
|
-
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
|
78
|
-
value: 'http://otel-stack-deployment-collector.observability.svc.cluster.local:4317'
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
That's it! Traces and metrics will flow to Groundcover.
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
## Selective Feature Enablement
|
|
61
|
+
app.register(foundation.fastifyPlugin); // automatic request tracing/metrics/logging
|
|
86
62
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
import { createFoundation } from '@ciq-dev/neoiq-foundation-node';
|
|
91
|
-
|
|
92
|
-
// Full observability (default)
|
|
93
|
-
const foundation = createFoundation({
|
|
94
|
-
serviceName: 'auth-service',
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// Logging only (lightweight mode)
|
|
98
|
-
const foundation = createFoundation({
|
|
99
|
-
serviceName: 'simple-worker',
|
|
100
|
-
features: {
|
|
101
|
-
tracing: false,
|
|
102
|
-
metrics: false,
|
|
103
|
-
logging: true,
|
|
104
|
-
},
|
|
63
|
+
process.on('SIGTERM', async () => {
|
|
64
|
+
await app.close();
|
|
65
|
+
await foundation.lifecycle.shutdown();
|
|
105
66
|
});
|
|
67
|
+
```
|
|
106
68
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
tracing: true,
|
|
112
|
-
metrics: true,
|
|
113
|
-
autoInstrumentation: {
|
|
114
|
-
http: true,
|
|
115
|
-
fastify: true,
|
|
116
|
-
mongodb: false, // Not using MongoDB
|
|
117
|
-
redis: false, // Not using Redis
|
|
118
|
-
pg: true, // Using PostgreSQL
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
});
|
|
69
|
+
```yaml
|
|
70
|
+
# Kubernetes env
|
|
71
|
+
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
|
72
|
+
value: 'http://otel-stack-deployment-collector.observability.svc.cluster.local:4317'
|
|
122
73
|
```
|
|
123
74
|
|
|
124
75
|
---
|
|
125
76
|
|
|
126
|
-
## API
|
|
77
|
+
## API
|
|
127
78
|
|
|
128
|
-
|
|
79
|
+
The foundation instance exposes top-level shortcuts for the most common operations and sub-modules for everything else.
|
|
129
80
|
|
|
130
|
-
|
|
81
|
+
### Top-level shortcuts
|
|
131
82
|
|
|
132
83
|
```typescript
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// Optional
|
|
140
|
-
serviceVersion: '1.0.0', // Default: '1.0.0' or $SERVICE_VERSION
|
|
141
|
-
environment: 'production', // Default: 'development' or $NODE_ENV
|
|
142
|
-
|
|
143
|
-
// Feature toggles
|
|
144
|
-
features: {
|
|
145
|
-
tracing: true, // Enable distributed tracing
|
|
146
|
-
metrics: true, // Enable metrics collection
|
|
147
|
-
logging: true, // Enable structured logging
|
|
148
|
-
autoInstrumentation: { // Fine-grained control
|
|
149
|
-
http: true,
|
|
150
|
-
fastify: true,
|
|
151
|
-
express: true,
|
|
152
|
-
mongodb: true,
|
|
153
|
-
pg: true,
|
|
154
|
-
mysql: true,
|
|
155
|
-
redis: true,
|
|
156
|
-
ioredis: true,
|
|
157
|
-
fs: false, // Disabled by default (noisy)
|
|
158
|
-
dns: false, // Disabled by default
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
|
|
162
|
-
// OTEL configuration
|
|
163
|
-
otel: {
|
|
164
|
-
endpoint: 'http://...:4317', // Default: cluster OTEL Collector
|
|
165
|
-
metricsIntervalMs: 5000, // Default: 5000 (5 seconds)
|
|
166
|
-
traceSampleRate: 1.0, // Default: 1.0 (100%)
|
|
167
|
-
},
|
|
168
|
-
|
|
169
|
-
// Logging configuration
|
|
170
|
-
logging: {
|
|
171
|
-
level: 'info', // 'debug' | 'info' | 'warn' | 'error'
|
|
172
|
-
prettyPrint: false, // Auto-detect from environment
|
|
173
|
-
},
|
|
174
|
-
});
|
|
84
|
+
foundation.logger // Pino logger with trace context auto-injected
|
|
85
|
+
foundation.context // AsyncLocalStorage context manager
|
|
86
|
+
foundation.fastifyPlugin // Fastify plugin (register directly)
|
|
87
|
+
foundation.features // { tracing: boolean, metrics: boolean, logging: boolean }
|
|
88
|
+
foundation.config // Full resolved config
|
|
175
89
|
```
|
|
176
90
|
|
|
177
|
-
###
|
|
178
|
-
|
|
179
|
-
The foundation instance provides access to all observability features:
|
|
91
|
+
### `foundation.observability`
|
|
180
92
|
|
|
181
93
|
```typescript
|
|
182
|
-
//
|
|
94
|
+
// Structured logging — logs include traceId, spanId, correlationId automatically
|
|
183
95
|
foundation.logger.info({ userId: '123' }, 'User logged in');
|
|
184
|
-
foundation.logger.
|
|
96
|
+
const routeLogger = foundation.logger.child({ component: 'auth' });
|
|
185
97
|
|
|
186
|
-
//
|
|
187
|
-
const meter = foundation.getMeter('my-service');
|
|
188
|
-
const
|
|
189
|
-
|
|
98
|
+
// Custom metrics
|
|
99
|
+
const meter = foundation.observability.getMeter('my-service');
|
|
100
|
+
const loginCounter = meter.createCounter('auth.logins.total');
|
|
101
|
+
loginCounter.add(1, { provider: 'workos' });
|
|
190
102
|
|
|
191
|
-
|
|
192
|
-
|
|
103
|
+
const latency = meter.createHistogram('auth.token.duration');
|
|
104
|
+
latency.record(42, { status: 'success' });
|
|
105
|
+
|
|
106
|
+
// Custom spans
|
|
107
|
+
const tracer = foundation.observability.getTracer();
|
|
193
108
|
tracer.startActiveSpan('validateToken', (span) => {
|
|
194
|
-
// ...
|
|
109
|
+
// ... work
|
|
195
110
|
span.end();
|
|
196
111
|
});
|
|
197
112
|
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
serviceName: 'user-service',
|
|
113
|
+
// Convenience: span with auto error handling
|
|
114
|
+
await foundation.observability.trace('validateToken', async () => {
|
|
115
|
+
// ... work
|
|
202
116
|
});
|
|
203
117
|
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
// Shutdown
|
|
208
|
-
await foundation.shutdown();
|
|
209
|
-
|
|
210
|
-
// Check features
|
|
211
|
-
console.log(foundation.features);
|
|
212
|
-
// { tracing: true, metrics: true, logging: true }
|
|
118
|
+
// Get current trace context
|
|
119
|
+
const { traceId, spanId } = foundation.observability.getTraceContext();
|
|
213
120
|
```
|
|
214
121
|
|
|
215
|
-
### `foundation.
|
|
216
|
-
|
|
217
|
-
Structured logger with automatic trace context injection.
|
|
122
|
+
### `foundation.http`
|
|
218
123
|
|
|
219
124
|
```typescript
|
|
220
|
-
foundation.
|
|
221
|
-
foundation.logger.error({ error: err.message }, 'Failed to process');
|
|
222
|
-
foundation.logger.warn({ attempt: 3 }, 'Retry attempt');
|
|
223
|
-
foundation.logger.debug({ payload: data }, 'Processing request');
|
|
224
|
-
|
|
225
|
-
// Child logger for component-specific logging
|
|
226
|
-
const authLogger = foundation.logger.child({ component: 'auth' });
|
|
227
|
-
authLogger.info({}, 'Auth module initialized');
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
**Log Output:**
|
|
231
|
-
```json
|
|
232
|
-
{
|
|
233
|
-
"level": "info",
|
|
234
|
-
"time": 1703318400000,
|
|
235
|
-
"service": "auth-service",
|
|
236
|
-
"traceId": "abc123...",
|
|
237
|
-
"spanId": "def456...",
|
|
238
|
-
"correlationId": "req-789",
|
|
239
|
-
"userId": "123",
|
|
240
|
-
"msg": "User logged in"
|
|
241
|
-
}
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### `foundation.getMeter(name, version?)`
|
|
245
|
-
|
|
246
|
-
Get a meter for custom metrics.
|
|
247
|
-
|
|
248
|
-
```typescript
|
|
249
|
-
const meter = foundation.getMeter('auth-service');
|
|
250
|
-
|
|
251
|
-
// Counter
|
|
252
|
-
const counter = meter.createCounter('logins.total');
|
|
253
|
-
counter.add(1, { provider: 'workos' });
|
|
254
|
-
|
|
255
|
-
// Histogram
|
|
256
|
-
const histogram = meter.createHistogram('token.validation.duration');
|
|
257
|
-
histogram.record(150, { status: 'success' });
|
|
258
|
-
|
|
259
|
-
// Observable Gauge
|
|
260
|
-
meter.createObservableGauge('active_sessions', {}, (result) => {
|
|
261
|
-
result.observe(getActiveSessions());
|
|
262
|
-
});
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
### `foundation.fastifyPlugin`
|
|
266
|
-
|
|
267
|
-
Fastify plugin for automatic request handling.
|
|
268
|
-
|
|
269
|
-
```typescript
|
|
270
|
-
app.register(foundation.fastifyPlugin);
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
**Automatically:**
|
|
274
|
-
- Extracts/generates correlation ID (x-request-id header)
|
|
275
|
-
- Creates OpenTelemetry spans for each request
|
|
276
|
-
- Logs request received/completed with full context
|
|
277
|
-
- Records HTTP metrics (http.server.requests.total, http.server.request.duration)
|
|
278
|
-
- Skips health check endpoints (/health, /healthz, /ready, /live)
|
|
279
|
-
|
|
280
|
-
### `foundation.createHttpClient(options)`
|
|
281
|
-
|
|
282
|
-
Create an Axios client with full observability.
|
|
283
|
-
|
|
284
|
-
```typescript
|
|
285
|
-
const userClient = foundation.createHttpClient({
|
|
125
|
+
const userClient = foundation.http.createClient({
|
|
286
126
|
baseURL: 'http://user-service:3000',
|
|
287
127
|
serviceName: 'user-service',
|
|
288
128
|
timeout: 10000,
|
|
289
129
|
retry: {
|
|
290
130
|
retries: 3,
|
|
291
131
|
retryDelay: 1000,
|
|
292
|
-
retryStatusCodes: [
|
|
132
|
+
retryStatusCodes: [429, 500, 502, 503, 504],
|
|
133
|
+
retryNonIdempotent: false, // default: don't retry POST/PATCH
|
|
293
134
|
},
|
|
294
135
|
circuitBreaker: {
|
|
295
136
|
enabled: true,
|
|
@@ -298,192 +139,200 @@ const userClient = foundation.createHttpClient({
|
|
|
298
139
|
},
|
|
299
140
|
});
|
|
300
141
|
|
|
301
|
-
const
|
|
142
|
+
const { data } = await userClient.get('/api/users/123');
|
|
143
|
+
// Trace context (traceparent) and correlationId propagated automatically
|
|
302
144
|
```
|
|
303
145
|
|
|
304
|
-
|
|
305
|
-
- Propagates trace context (traceparent header)
|
|
306
|
-
- Propagates correlation ID (x-request-id header)
|
|
307
|
-
- Logs outbound requests/responses
|
|
308
|
-
- Records HTTP client metrics
|
|
309
|
-
- Retries on 5xx errors with exponential backoff
|
|
310
|
-
- Circuit breaker protection
|
|
311
|
-
|
|
312
|
-
### `foundation.shutdown()`
|
|
313
|
-
|
|
314
|
-
Gracefully shutdown OTEL providers. Call on application shutdown.
|
|
146
|
+
### `foundation.lifecycle`
|
|
315
147
|
|
|
316
148
|
```typescript
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
await foundation.shutdown(); // Flush telemetry
|
|
320
|
-
});
|
|
321
|
-
```
|
|
149
|
+
// Graceful shutdown — flushes OTEL providers
|
|
150
|
+
await foundation.lifecycle.shutdown();
|
|
322
151
|
|
|
323
|
-
|
|
152
|
+
// Readiness check — false if providers are still initializing
|
|
153
|
+
foundation.lifecycle.isReady();
|
|
324
154
|
|
|
325
|
-
|
|
155
|
+
// Structured health status
|
|
156
|
+
const health = foundation.lifecycle.health();
|
|
157
|
+
// { status: 'healthy'|'degraded'|'unhealthy', components: { tracing, metrics, logging } }
|
|
326
158
|
|
|
327
|
-
|
|
159
|
+
// Run with error capture (useful for background tasks)
|
|
160
|
+
const result = await foundation.lifecycle.safeRun(
|
|
161
|
+
() => riskyOperation(),
|
|
162
|
+
defaultValue, // returned on error
|
|
163
|
+
);
|
|
164
|
+
```
|
|
328
165
|
|
|
329
|
-
###
|
|
166
|
+
### `foundation.secrets` (Vault)
|
|
330
167
|
|
|
331
168
|
```typescript
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
169
|
+
// Enable in config:
|
|
170
|
+
const foundation = createFoundation({
|
|
171
|
+
serviceName: 'my-service',
|
|
172
|
+
vault: {
|
|
173
|
+
enabled: true,
|
|
174
|
+
address: 'https://vault.beta.commerceiq.ai',
|
|
175
|
+
authMethod: 'kubernetes', // or 'token'
|
|
176
|
+
role: 'my-service',
|
|
177
|
+
},
|
|
336
178
|
});
|
|
337
179
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
await
|
|
342
|
-
|
|
343
|
-
// Presigned PUT URL (browser upload)
|
|
344
|
-
const uploadUrl = await store.presignPutObject(ref, { expiresInSeconds: 60, contentType: 'application/json' });
|
|
180
|
+
// Use:
|
|
181
|
+
const secrets = foundation.secrets!;
|
|
182
|
+
const creds = await secrets.getSecret('path/to/secret');
|
|
183
|
+
const apiKey = await secrets.getSecretValue('path/to/secret', 'API_KEY');
|
|
345
184
|
```
|
|
346
185
|
|
|
347
|
-
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Full Config Reference
|
|
348
189
|
|
|
349
190
|
```typescript
|
|
350
|
-
|
|
191
|
+
createFoundation({
|
|
192
|
+
serviceName: 'my-service', // required
|
|
193
|
+
serviceVersion: '1.0.0', // default: $SERVICE_VERSION
|
|
194
|
+
environment: 'development', // 'development'|'staging'|'qa'|'production'
|
|
351
195
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
196
|
+
features: {
|
|
197
|
+
tracing: true,
|
|
198
|
+
metrics: true,
|
|
199
|
+
logging: true,
|
|
200
|
+
autoInstrumentation: {
|
|
201
|
+
http: true, fastify: true, express: true,
|
|
202
|
+
mongodb: true, pg: true, mysql: true,
|
|
203
|
+
redis: true, ioredis: true, grpc: true,
|
|
204
|
+
fs: false, // disabled by default (noisy)
|
|
205
|
+
dns: false,
|
|
206
|
+
},
|
|
207
|
+
},
|
|
356
208
|
|
|
357
|
-
|
|
209
|
+
otel: {
|
|
210
|
+
endpoint: 'http://...:4317', // default: $OTEL_EXPORTER_OTLP_ENDPOINT or cluster URL
|
|
211
|
+
metricsIntervalMs: 5000,
|
|
212
|
+
},
|
|
358
213
|
|
|
359
|
-
|
|
214
|
+
logging: {
|
|
215
|
+
level: 'info', // 'debug'|'info'|'warn'|'error'
|
|
216
|
+
prettyPrint: false, // default: true in development
|
|
217
|
+
},
|
|
360
218
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
219
|
+
requestLogging: {
|
|
220
|
+
logHeaders: true,
|
|
221
|
+
logBody: false,
|
|
222
|
+
logResponseBody: false,
|
|
223
|
+
maxBodySize: 10240,
|
|
224
|
+
redactHeaders: ['authorization', 'cookie'],
|
|
225
|
+
},
|
|
364
226
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
227
|
+
redaction: {
|
|
228
|
+
additionalPaths: ['req.body.password', 'res.body.token'],
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
shutdown: {
|
|
232
|
+
flushOnCrash: false, // flush telemetry on uncaughtException/unhandledRejection
|
|
233
|
+
flushTimeoutMs: 5000,
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
vault: {
|
|
237
|
+
enabled: false,
|
|
238
|
+
address: 'https://vault.beta.commerceiq.ai',
|
|
239
|
+
authMethod: 'kubernetes',
|
|
240
|
+
role: 'my-service',
|
|
241
|
+
mountPoint: 'secret',
|
|
242
|
+
tokenPath: '/var/run/secrets/kubernetes.io/serviceaccount/token',
|
|
243
|
+
k8sAuthMount: 'kubernetes',
|
|
244
|
+
},
|
|
368
245
|
});
|
|
369
246
|
```
|
|
370
247
|
|
|
371
|
-
|
|
372
|
-
// src/index.ts
|
|
373
|
-
import Fastify from 'fastify';
|
|
374
|
-
import { foundation } from './foundation';
|
|
248
|
+
---
|
|
375
249
|
|
|
376
|
-
|
|
250
|
+
## Object Store
|
|
377
251
|
|
|
378
|
-
|
|
379
|
-
app.register(foundation.fastifyPlugin);
|
|
252
|
+
Provider-agnostic abstraction over cloud object storage.
|
|
380
253
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
baseURL: process.env.USER_SERVICE_URL!,
|
|
384
|
-
serviceName: 'user-service',
|
|
385
|
-
});
|
|
254
|
+
```typescript
|
|
255
|
+
import { AwsS3ObjectStore, InMemoryObjectStore } from '@ciq-dev/neoiq-foundation-node';
|
|
386
256
|
|
|
387
|
-
//
|
|
388
|
-
const
|
|
389
|
-
const loginCounter = meter.createCounter('auth.logins.total');
|
|
257
|
+
// Production
|
|
258
|
+
const store = new AwsS3ObjectStore({ clientOptions: { region: 'us-east-1' } });
|
|
390
259
|
|
|
391
|
-
//
|
|
392
|
-
|
|
393
|
-
const { provider } = req.body as { provider: string };
|
|
394
|
-
|
|
395
|
-
loginCounter.add(1, { provider });
|
|
396
|
-
foundation.logger.info({ provider }, 'Login attempt');
|
|
397
|
-
|
|
398
|
-
// Call user service (trace context automatically propagated)
|
|
399
|
-
const { data: user } = await userClient.get('/api/users/me');
|
|
400
|
-
|
|
401
|
-
return { success: true, user };
|
|
402
|
-
});
|
|
260
|
+
// Tests
|
|
261
|
+
const store = new InMemoryObjectStore();
|
|
403
262
|
|
|
404
|
-
|
|
405
|
-
|
|
263
|
+
const ref = { bucket: 'my-bucket', key: 'path/to/file.json' };
|
|
264
|
+
await store.putObject(ref, JSON.stringify(data), { contentType: 'application/json' });
|
|
265
|
+
const obj = await store.getObject(ref);
|
|
406
266
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
await foundation.shutdown();
|
|
410
|
-
});
|
|
267
|
+
// Presigned upload URL
|
|
268
|
+
const url = await store.presignPutObject(ref, { expiresInSeconds: 60, contentType: 'application/json' });
|
|
411
269
|
```
|
|
412
270
|
|
|
413
271
|
---
|
|
414
272
|
|
|
415
|
-
##
|
|
273
|
+
## Fastify Plugin — What it does
|
|
416
274
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
275
|
+
`foundation.fastifyPlugin` automatically:
|
|
276
|
+
- Extracts/generates `correlationId` from `x-request-id`
|
|
277
|
+
- Creates an OTEL span per request
|
|
278
|
+
- Logs `request received` / `request completed` with full context
|
|
279
|
+
- Records `http.server.requests.total` and `http.server.request.duration` metrics
|
|
280
|
+
- Skips `/health`, `/healthz`, `/ready`, `/live`
|
|
423
281
|
|
|
424
282
|
---
|
|
425
283
|
|
|
426
|
-
##
|
|
427
|
-
|
|
428
|
-
### Traces
|
|
429
|
-
- Every HTTP request (incoming and outgoing)
|
|
430
|
-
- Correlation ID linking requests across services
|
|
431
|
-
- Span attributes: method, url, status_code, duration
|
|
284
|
+
## Metrics Reference
|
|
432
285
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
### Logs (via stdout)
|
|
442
|
-
- Structured JSON logs written to stdout (Pino)
|
|
443
|
-
- Include traceId, spanId, correlationId for correlation
|
|
444
|
-
- Groundcover Agent scrapes container logs
|
|
445
|
-
- Automatically correlated with traces in Groundcover dashboard
|
|
286
|
+
| Metric | Description |
|
|
287
|
+
|--------|-------------|
|
|
288
|
+
| `http.server.requests.total` | Incoming request count |
|
|
289
|
+
| `http.server.request.duration` | Incoming request latency (histogram) |
|
|
290
|
+
| `http.server.requests.errors` | Incoming request errors |
|
|
291
|
+
| `http.client.requests.total` | Outgoing request count |
|
|
292
|
+
| `http.client.request.duration` | Outgoing request latency (histogram) |
|
|
446
293
|
|
|
447
294
|
---
|
|
448
295
|
|
|
449
|
-
##
|
|
296
|
+
## Environment Variables
|
|
450
297
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
│ ├── context.ts # AsyncLocalStorage context manager
|
|
458
|
-
│ ├── logging.ts # Pino logger setup
|
|
459
|
-
│ ├── tracing.ts # OTEL trace provider
|
|
460
|
-
│ └── metrics.ts # OTEL meter provider
|
|
461
|
-
└── integrations/
|
|
462
|
-
├── fastify-plugin.ts # Fastify observability plugin
|
|
463
|
-
├── http-client.ts # Axios wrapper with observability
|
|
464
|
-
└── object-store/ # Cloud object store abstraction (S3, etc.)
|
|
465
|
-
```
|
|
298
|
+
| Variable | Default |
|
|
299
|
+
|----------|---------|
|
|
300
|
+
| `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://otel-stack-deployment-collector.observability.svc.cluster.local:4317` |
|
|
301
|
+
| `SERVICE_VERSION` | `1.0.0` |
|
|
302
|
+
| `NODE_ENV` | `development` |
|
|
303
|
+
| `LOG_LEVEL` | `info` |
|
|
466
304
|
|
|
467
305
|
---
|
|
468
306
|
|
|
469
307
|
## Development
|
|
470
308
|
|
|
471
309
|
```bash
|
|
472
|
-
#
|
|
473
|
-
npm
|
|
474
|
-
|
|
475
|
-
#
|
|
476
|
-
npm
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
npm run typecheck
|
|
310
|
+
npm run build # tsdown: CJS + ESM dual output → dist/
|
|
311
|
+
npm run typecheck # tsc --noEmit
|
|
312
|
+
npm run lint # ESLint
|
|
313
|
+
npm run prettier:write # Prettier format
|
|
314
|
+
npm test # Vitest
|
|
315
|
+
npm run test:watch # Vitest watch mode
|
|
316
|
+
```
|
|
480
317
|
|
|
481
|
-
|
|
482
|
-
npm run lint
|
|
318
|
+
---
|
|
483
319
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
320
|
+
## Deprecated API
|
|
321
|
+
|
|
322
|
+
The following flat methods on `foundation` still work but emit a deprecation warning. Migrate to sub-modules:
|
|
323
|
+
|
|
324
|
+
| Deprecated | Use instead |
|
|
325
|
+
|------------|-------------|
|
|
326
|
+
| `foundation.getMeter()` | `foundation.observability.getMeter()` |
|
|
327
|
+
| `foundation.getTracer()` | `foundation.observability.getTracer()` |
|
|
328
|
+
| `foundation.getTraceContext()` | `foundation.observability.getTraceContext()` |
|
|
329
|
+
| `foundation.getActiveSpan()` | `foundation.observability.getActiveSpan()` |
|
|
330
|
+
| `foundation.trace()` | `foundation.observability.trace()` |
|
|
331
|
+
| `foundation.createHttpClient()` | `foundation.http.createClient()` |
|
|
332
|
+
| `foundation.shutdown()` | `foundation.lifecycle.shutdown()` |
|
|
333
|
+
| `foundation.isReady()` | `foundation.lifecycle.isReady()` |
|
|
334
|
+
| `foundation.health()` | `foundation.lifecycle.health()` |
|
|
335
|
+
| `foundation.safeRun()` | `foundation.lifecycle.safeRun()` |
|
|
487
336
|
|
|
488
337
|
---
|
|
489
338
|
|