@venturekit/runtime 0.0.0-dev.20260415182021 → 0.0.0-dev.20260427220257

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/env/memo.d.ts +39 -0
  2. package/dist/env/memo.d.ts.map +1 -0
  3. package/dist/env/memo.js +85 -0
  4. package/dist/env/memo.js.map +1 -0
  5. package/dist/handler/context.d.ts.map +1 -1
  6. package/dist/handler/context.js +12 -14
  7. package/dist/handler/context.js.map +1 -1
  8. package/dist/handler/data-loader.d.ts +50 -0
  9. package/dist/handler/data-loader.d.ts.map +1 -0
  10. package/dist/handler/data-loader.js +68 -0
  11. package/dist/handler/data-loader.js.map +1 -0
  12. package/dist/handler/errors.d.ts +10 -0
  13. package/dist/handler/errors.d.ts.map +1 -1
  14. package/dist/handler/errors.js +13 -0
  15. package/dist/handler/errors.js.map +1 -1
  16. package/dist/handler/handler.d.ts +18 -0
  17. package/dist/handler/handler.d.ts.map +1 -1
  18. package/dist/handler/handler.js +56 -11
  19. package/dist/handler/handler.js.map +1 -1
  20. package/dist/handler/queue-batch.d.ts +140 -0
  21. package/dist/handler/queue-batch.d.ts.map +1 -0
  22. package/dist/handler/queue-batch.js +180 -0
  23. package/dist/handler/queue-batch.js.map +1 -0
  24. package/dist/handler/response.d.ts +8 -1
  25. package/dist/handler/response.d.ts.map +1 -1
  26. package/dist/handler/response.js +18 -4
  27. package/dist/handler/response.js.map +1 -1
  28. package/dist/handler/task-handler.d.ts.map +1 -1
  29. package/dist/handler/task-handler.js +8 -12
  30. package/dist/handler/task-handler.js.map +1 -1
  31. package/dist/index.d.ts +5 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +4 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/logging/logger.d.ts +20 -1
  36. package/dist/logging/logger.d.ts.map +1 -1
  37. package/dist/logging/logger.js +66 -23
  38. package/dist/logging/logger.js.map +1 -1
  39. package/dist/logging/metrics.d.ts +105 -0
  40. package/dist/logging/metrics.d.ts.map +1 -0
  41. package/dist/logging/metrics.js +189 -0
  42. package/dist/logging/metrics.js.map +1 -0
  43. package/dist/logging/tracing.d.ts +64 -10
  44. package/dist/logging/tracing.d.ts.map +1 -1
  45. package/dist/logging/tracing.js +197 -15
  46. package/dist/logging/tracing.js.map +1 -1
  47. package/dist/middleware/middleware.d.ts +3 -0
  48. package/dist/middleware/middleware.d.ts.map +1 -1
  49. package/dist/middleware/middleware.js +11 -2
  50. package/dist/middleware/middleware.js.map +1 -1
  51. package/dist/middleware/rate-limit-store.d.ts +18 -0
  52. package/dist/middleware/rate-limit-store.d.ts.map +1 -1
  53. package/dist/middleware/rate-limit-store.js +24 -4
  54. package/dist/middleware/rate-limit-store.js.map +1 -1
  55. package/dist/patterns/circuit-breaker.d.ts +11 -0
  56. package/dist/patterns/circuit-breaker.d.ts.map +1 -1
  57. package/dist/patterns/circuit-breaker.js +26 -2
  58. package/dist/patterns/circuit-breaker.js.map +1 -1
  59. package/dist/patterns/invoke.d.ts.map +1 -1
  60. package/dist/patterns/invoke.js +60 -31
  61. package/dist/patterns/invoke.js.map +1 -1
  62. package/dist/patterns/saga.d.ts +42 -1
  63. package/dist/patterns/saga.d.ts.map +1 -1
  64. package/dist/patterns/saga.js +78 -20
  65. package/dist/patterns/saga.js.map +1 -1
  66. package/dist/security/internal-hmac.d.ts +59 -0
  67. package/dist/security/internal-hmac.d.ts.map +1 -0
  68. package/dist/security/internal-hmac.js +117 -0
  69. package/dist/security/internal-hmac.js.map +1 -0
  70. package/package.json +4 -4
@@ -11,56 +11,91 @@
11
11
  * - Lambda-optimized (no worker threads, synchronous output)
12
12
  */
13
13
  import pino from 'pino';
14
+ /**
15
+ * Parse an env-provided sample rate into [0, 1]. Returns `1` (unsampled) if
16
+ * the value is missing, unparseable, or out of range — failing open keeps
17
+ * observability working when the env is misconfigured.
18
+ */
19
+ function resolveSampleRate(raw) {
20
+ if (raw === undefined)
21
+ return 1;
22
+ const n = Number.parseFloat(raw);
23
+ if (!Number.isFinite(n))
24
+ return 1;
25
+ if (n <= 0)
26
+ return 0;
27
+ if (n >= 1)
28
+ return 1;
29
+ return n;
30
+ }
31
+ /**
32
+ * Build a fresh root pino logger with Lambda-friendly defaults.
33
+ *
34
+ * Centralized so that {@link Logger.constructor} and {@link Logger.clearContext}
35
+ * produce byte-for-byte identical loggers — otherwise, a future config tweak
36
+ * made in only one of the two call sites would silently drift.
37
+ */
38
+ function buildRootPino(config) {
39
+ return pino({
40
+ level: config.minLevel,
41
+ // Lambda-friendly: no worker threads, flat JSON, fast.
42
+ transport: undefined,
43
+ timestamp: pino.stdTimeFunctions.isoTime,
44
+ // Drop default `pid` and `hostname` fields. In Lambda they're constant
45
+ // per container and just bloat every log line with no operational value.
46
+ base: null,
47
+ formatters: {
48
+ level(label) {
49
+ return { level: label };
50
+ },
51
+ },
52
+ });
53
+ }
14
54
  /**
15
55
  * Logger instance — thin wrapper over pino
16
56
  */
17
57
  export class Logger {
58
+ /** The currently-bound pino logger (may be a child with request fields). */
18
59
  pinoLogger;
60
+ /**
61
+ * Unbound "root" pino logger kept so `clearContext()` can drop request-scoped
62
+ * fields without discarding the caller-supplied config (log level, formatters,
63
+ * etc.). Re-creating via `pino({...})` would silently lose any future tweaks
64
+ * to the constructor-time options.
65
+ */
66
+ rootPinoLogger;
19
67
  context = null;
20
68
  config;
21
69
  constructor(config = {}) {
22
70
  this.config = {
23
71
  minLevel: config.minLevel ?? 'info',
24
72
  includeContext: config.includeContext ?? true,
73
+ infoSampleRate: config.infoSampleRate ?? resolveSampleRate(process.env.LOG_SAMPLE_RATE),
25
74
  };
26
- this.pinoLogger = pino({
27
- level: this.config.minLevel,
28
- // Lambda-friendly: no worker threads, flat JSON, fast
29
- transport: undefined,
30
- timestamp: pino.stdTimeFunctions.isoTime,
31
- formatters: {
32
- level(label) {
33
- return { level: label };
34
- },
35
- },
36
- });
75
+ this.rootPinoLogger = buildRootPino(this.config);
76
+ this.pinoLogger = this.rootPinoLogger;
37
77
  }
38
78
  /**
39
79
  * Set request context for all subsequent logs
40
80
  */
41
81
  setContext(context) {
42
82
  this.context = context;
43
- // Rebind pino child with request-scoped fields
44
- this.pinoLogger = this.pinoLogger.child({
83
+ // Bind a child off the root (not off the previous binding) so we never
84
+ // accumulate fields from a stale request on warm-container reuse.
85
+ this.pinoLogger = this.rootPinoLogger.child({
45
86
  requestId: context.requestId,
46
87
  ...(context.user?.id && { userId: context.user.id }),
47
88
  ...(context.tenant?.id && { tenantId: context.tenant.id }),
48
89
  });
49
90
  }
50
91
  /**
51
- * Clear request context
92
+ * Clear request context. Restores the original unbound logger instead of
93
+ * minting a brand-new pino root — that way log-level / formatter tweaks
94
+ * from construction are preserved and `clearContext()` is cheap.
52
95
  */
53
96
  clearContext() {
54
97
  this.context = null;
55
- this.pinoLogger = pino({
56
- level: this.config.minLevel,
57
- timestamp: pino.stdTimeFunctions.isoTime,
58
- formatters: {
59
- level(label) {
60
- return { level: label };
61
- },
62
- },
63
- });
98
+ this.pinoLogger = this.rootPinoLogger;
64
99
  }
65
100
  /**
66
101
  * Create a child logger with additional default fields
@@ -75,6 +110,14 @@ export class Logger {
75
110
  data ? this.pinoLogger.debug(data, message) : this.pinoLogger.debug(message);
76
111
  }
77
112
  info(message, data) {
113
+ // Optional sampling: skip the emit call if the sampled coin flip lands
114
+ // below the threshold. `warn` / `error` are deliberately never sampled —
115
+ // they're low volume and always need to reach the operator.
116
+ const rate = this.config.infoSampleRate ?? 1;
117
+ if (rate < 1 && rate > 0 && Math.random() >= rate)
118
+ return;
119
+ if (rate === 0)
120
+ return;
78
121
  data ? this.pinoLogger.info(data, message) : this.pinoLogger.info(message);
79
122
  }
80
123
  warn(message, data) {
@@ -1 +1 @@
1
- {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logging/logger.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logging/logger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * VentureKit CloudWatch EMF Metrics
3
+ *
4
+ * CloudWatch Embedded Metric Format (EMF) is a JSON shape that AWS auto-parses
5
+ * into custom CloudWatch metrics when emitted to stdout from a Lambda. Using
6
+ * EMF instead of the PutMetricData API means:
7
+ *
8
+ * - Zero extra SDK calls per request (metrics go out with your logs).
9
+ * - No IAM permissions beyond the standard Lambda execution role.
10
+ * - No synchronous latency tax on the hot path.
11
+ *
12
+ * ## Usage
13
+ *
14
+ * ```typescript
15
+ * import { createMetrics } from '@venturekit/runtime';
16
+ *
17
+ * const metrics = createMetrics({ namespace: 'MyApp/API' });
18
+ *
19
+ * metrics.putDimensions({ Service: 'checkout', Stage: 'prod' });
20
+ * metrics.putMetric('RequestDurationMs', 142, 'Milliseconds');
21
+ * metrics.putMetric('OrdersCreated', 1, 'Count');
22
+ * metrics.flush(); // emits a single JSON line to stdout
23
+ * ```
24
+ *
25
+ * ## Spec
26
+ *
27
+ * Format reference: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html
28
+ *
29
+ * CloudWatch limits (enforced below):
30
+ * - Max 9 dimensions per metric set.
31
+ * - Max 100 distinct metric names per log entry.
32
+ * - Max 100 values per metric.
33
+ * - Dimension values and metric names must be ≤ 1024 chars.
34
+ *
35
+ * The module writes to `stdout` via `console.log` because that's where the
36
+ * Lambda runtime picks up structured logs. Writing to `stderr` would also
37
+ * work but mixes EMF with error logs in CloudWatch Insights.
38
+ */
39
+ /**
40
+ * CloudWatch-supported metric units. Unknown units fall back to `None` at
41
+ * emit time (CloudWatch rejects unknown unit strings otherwise).
42
+ */
43
+ export type MetricUnit = 'Seconds' | 'Microseconds' | 'Milliseconds' | 'Bytes' | 'Kilobytes' | 'Megabytes' | 'Gigabytes' | 'Terabytes' | 'Bits' | 'Kilobits' | 'Megabits' | 'Gigabits' | 'Terabits' | 'Percent' | 'Count' | 'Bytes/Second' | 'Kilobytes/Second' | 'Megabytes/Second' | 'Gigabytes/Second' | 'Terabytes/Second' | 'Bits/Second' | 'Kilobits/Second' | 'Megabits/Second' | 'Gigabits/Second' | 'Terabits/Second' | 'Count/Second' | 'None';
44
+ /**
45
+ * EMF `StorageResolution` — 60 s (standard, cheapest) or 1 s (high-res, 10×
46
+ * the cost). Defaults to 60 s; opt in to 1 s only for latency SLIs where
47
+ * per-second granularity actually matters.
48
+ */
49
+ export type StorageResolution = 1 | 60;
50
+ export interface MetricsOptions {
51
+ /** CloudWatch namespace (e.g. `"MyApp/API"`). Required. */
52
+ namespace: string;
53
+ /**
54
+ * Default dimensions applied to every metric set. Typically
55
+ * `{ Service, Stage, FunctionName }`. Merged with any per-call dimensions.
56
+ */
57
+ defaultDimensions?: Record<string, string>;
58
+ /**
59
+ * Where to write the EMF line. Defaults to `console.log`. Override for
60
+ * tests or to tee to a different destination.
61
+ */
62
+ writer?: (line: string) => void;
63
+ /**
64
+ * Storage resolution (seconds). Defaults to 60. Set to 1 only when you
65
+ * need sub-minute metric resolution.
66
+ */
67
+ storageResolution?: StorageResolution;
68
+ }
69
+ export interface MetricsClient {
70
+ /**
71
+ * Add/override dimensions for subsequent `putMetric` calls. Dimension
72
+ * values must be strings ≤ 1024 chars; empty values are dropped.
73
+ */
74
+ putDimensions(dims: Record<string, string | undefined>): void;
75
+ /**
76
+ * Attach a metadata property that appears on the EMF log line but is NOT
77
+ * turned into a CloudWatch metric. Useful for traceId, requestId, etc.
78
+ */
79
+ putMetadata(key: string, value: unknown): void;
80
+ /**
81
+ * Record a metric sample. Call multiple times for the same name to
82
+ * accumulate a statistic set (min/max/avg/count) per flush.
83
+ */
84
+ putMetric(name: string, value: number, unit?: MetricUnit): void;
85
+ /**
86
+ * Emit the accumulated metrics as a single EMF JSON line and reset the
87
+ * buffer. Safe to call multiple times per invocation — each flush is a
88
+ * fresh metric set.
89
+ */
90
+ flush(): void;
91
+ /**
92
+ * Convenience: wrap an async operation, record its duration on success
93
+ * **and** on failure, then re-throw any error. The metric unit is always
94
+ * `Milliseconds`. The returned promise resolves/rejects exactly like `fn`.
95
+ */
96
+ timeAsync<T>(name: string, fn: () => Promise<T>): Promise<T>;
97
+ }
98
+ /**
99
+ * Create an EMF metrics client.
100
+ *
101
+ * The client buffers metric samples in memory; call `flush()` (or let
102
+ * `timeAsync` do it) to emit a CloudWatch-compatible EMF log line.
103
+ */
104
+ export declare function createMetrics(options: MetricsOptions): MetricsClient;
105
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/logging/metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH;;;GAGG;AACH,MAAM,MAAM,UAAU,GAClB,SAAS,GAAG,cAAc,GAAG,cAAc,GAC3C,OAAO,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GAC/D,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,GAC1D,SAAS,GAAG,OAAO,GACnB,cAAc,GAAG,kBAAkB,GAAG,kBAAkB,GACxD,kBAAkB,GAAG,kBAAkB,GACvC,aAAa,GAAG,iBAAiB,GAAG,iBAAiB,GACrD,iBAAiB,GAAG,iBAAiB,GACrC,cAAc,GAAG,MAAM,CAAC;AAE5B;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE,CAAC;AAEvC,MAAM,WAAW,cAAc;IAC7B,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACvC;AAED,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC;IAE9D;;;OAGG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAE/C;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAEhE;;;;OAIG;IACH,KAAK,IAAI,IAAI,CAAC;IAEd;;;;OAIG;IACH,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC9D;AAwBD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,CAwIpE"}
@@ -0,0 +1,189 @@
1
+ /**
2
+ * VentureKit CloudWatch EMF Metrics
3
+ *
4
+ * CloudWatch Embedded Metric Format (EMF) is a JSON shape that AWS auto-parses
5
+ * into custom CloudWatch metrics when emitted to stdout from a Lambda. Using
6
+ * EMF instead of the PutMetricData API means:
7
+ *
8
+ * - Zero extra SDK calls per request (metrics go out with your logs).
9
+ * - No IAM permissions beyond the standard Lambda execution role.
10
+ * - No synchronous latency tax on the hot path.
11
+ *
12
+ * ## Usage
13
+ *
14
+ * ```typescript
15
+ * import { createMetrics } from '@venturekit/runtime';
16
+ *
17
+ * const metrics = createMetrics({ namespace: 'MyApp/API' });
18
+ *
19
+ * metrics.putDimensions({ Service: 'checkout', Stage: 'prod' });
20
+ * metrics.putMetric('RequestDurationMs', 142, 'Milliseconds');
21
+ * metrics.putMetric('OrdersCreated', 1, 'Count');
22
+ * metrics.flush(); // emits a single JSON line to stdout
23
+ * ```
24
+ *
25
+ * ## Spec
26
+ *
27
+ * Format reference: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html
28
+ *
29
+ * CloudWatch limits (enforced below):
30
+ * - Max 9 dimensions per metric set.
31
+ * - Max 100 distinct metric names per log entry.
32
+ * - Max 100 values per metric.
33
+ * - Dimension values and metric names must be ≤ 1024 chars.
34
+ *
35
+ * The module writes to `stdout` via `console.log` because that's where the
36
+ * Lambda runtime picks up structured logs. Writing to `stderr` would also
37
+ * work but mixes EMF with error logs in CloudWatch Insights.
38
+ */
39
+ // CloudWatch constraints we enforce locally so we don't emit rejected lines.
40
+ const MAX_DIMENSIONS = 9;
41
+ const MAX_METRIC_NAMES = 100;
42
+ const MAX_VALUES_PER_METRIC = 100;
43
+ const MAX_STRING_LEN = 1024;
44
+ const VALID_UNITS = new Set([
45
+ 'Seconds', 'Microseconds', 'Milliseconds',
46
+ 'Bytes', 'Kilobytes', 'Megabytes', 'Gigabytes', 'Terabytes',
47
+ 'Bits', 'Kilobits', 'Megabits', 'Gigabits', 'Terabits',
48
+ 'Percent', 'Count',
49
+ 'Bytes/Second', 'Kilobytes/Second', 'Megabytes/Second',
50
+ 'Gigabytes/Second', 'Terabytes/Second',
51
+ 'Bits/Second', 'Kilobits/Second', 'Megabits/Second',
52
+ 'Gigabits/Second', 'Terabits/Second',
53
+ 'Count/Second', 'None',
54
+ ]);
55
+ function truncate(s) {
56
+ return s.length <= MAX_STRING_LEN ? s : s.slice(0, MAX_STRING_LEN);
57
+ }
58
+ /**
59
+ * Create an EMF metrics client.
60
+ *
61
+ * The client buffers metric samples in memory; call `flush()` (or let
62
+ * `timeAsync` do it) to emit a CloudWatch-compatible EMF log line.
63
+ */
64
+ export function createMetrics(options) {
65
+ const { namespace, defaultDimensions = {}, writer = (line) => {
66
+ // Using console.log because Lambda routes stdout to CloudWatch; stderr
67
+ // would mix with error logs and break CloudWatch Logs Insights filters.
68
+ console.log(line);
69
+ }, storageResolution = 60, } = options;
70
+ if (!namespace) {
71
+ throw new Error('createMetrics: namespace is required');
72
+ }
73
+ // Strip empties from defaults up-front.
74
+ const baseDims = {};
75
+ for (const [k, v] of Object.entries(defaultDimensions)) {
76
+ if (v && v.length > 0)
77
+ baseDims[k] = truncate(v);
78
+ }
79
+ let dimensions = { ...baseDims };
80
+ let metadata = {};
81
+ // Metric name → array of samples (CloudWatch accepts an array per metric).
82
+ let samples = {};
83
+ function reset() {
84
+ dimensions = { ...baseDims };
85
+ metadata = {};
86
+ samples = {};
87
+ }
88
+ function buildEmf() {
89
+ const dimKeys = Object.keys(dimensions).slice(0, MAX_DIMENSIONS);
90
+ const metricNames = Object.keys(samples).slice(0, MAX_METRIC_NAMES);
91
+ const body = {
92
+ _aws: {
93
+ Timestamp: Date.now(),
94
+ CloudWatchMetrics: [
95
+ {
96
+ Namespace: namespace,
97
+ // A single Dimensions set means "all of these dimensions apply
98
+ // to every metric in this log entry". Multi-set support is
99
+ // niche and easy to add if callers need it.
100
+ Dimensions: dimKeys.length > 0 ? [dimKeys] : [[]],
101
+ Metrics: metricNames.map((name) => ({
102
+ Name: name,
103
+ Unit: samples[name].unit,
104
+ StorageResolution: storageResolution,
105
+ })),
106
+ },
107
+ ],
108
+ },
109
+ ...metadata,
110
+ };
111
+ // Flatten dimensions and metric samples onto the top-level object — EMF
112
+ // identifies values by property name matching `Dimensions[*]` or
113
+ // `Metrics[*].Name`.
114
+ for (const k of dimKeys)
115
+ body[k] = dimensions[k];
116
+ for (const name of metricNames) {
117
+ const entry = samples[name];
118
+ // CloudWatch expects either a scalar or an array of up to 100 values.
119
+ body[name] = entry.values.length === 1 ? entry.values[0] : entry.values;
120
+ }
121
+ return JSON.stringify(body);
122
+ }
123
+ return {
124
+ putDimensions(dims) {
125
+ for (const [k, v] of Object.entries(dims)) {
126
+ if (!k)
127
+ continue;
128
+ if (v === undefined || v === '') {
129
+ delete dimensions[k];
130
+ continue;
131
+ }
132
+ if (Object.keys(dimensions).length >= MAX_DIMENSIONS && !(k in dimensions)) {
133
+ // Silently drop to respect CloudWatch's 9-dimension hard cap. We
134
+ // deliberately don't throw because metrics should never crash the
135
+ // caller's hot path.
136
+ continue;
137
+ }
138
+ dimensions[truncate(k)] = truncate(String(v));
139
+ }
140
+ },
141
+ putMetadata(key, value) {
142
+ if (!key)
143
+ return;
144
+ metadata[truncate(key)] = value;
145
+ },
146
+ putMetric(name, value, unit = 'Count') {
147
+ if (!name || !Number.isFinite(value))
148
+ return;
149
+ const resolvedUnit = VALID_UNITS.has(unit) ? unit : 'None';
150
+ const k = truncate(name);
151
+ let entry = samples[k];
152
+ if (!entry) {
153
+ if (Object.keys(samples).length >= MAX_METRIC_NAMES) {
154
+ // Dropping silently again — respect CW caps without crashing.
155
+ return;
156
+ }
157
+ entry = samples[k] = { values: [], unit: resolvedUnit };
158
+ }
159
+ if (entry.values.length >= MAX_VALUES_PER_METRIC)
160
+ return;
161
+ entry.values.push(value);
162
+ },
163
+ flush() {
164
+ if (Object.keys(samples).length === 0) {
165
+ // Nothing to emit — avoid polluting logs with empty EMF lines.
166
+ return;
167
+ }
168
+ writer(buildEmf());
169
+ reset();
170
+ },
171
+ async timeAsync(name, fn) {
172
+ const start = Date.now();
173
+ try {
174
+ const result = await fn();
175
+ // Record success before flushing so the timing and outcome arrive
176
+ // on the same EMF line.
177
+ this.putMetric(name, Date.now() - start, 'Milliseconds');
178
+ this.putMetric(`${name}.Success`, 1, 'Count');
179
+ return result;
180
+ }
181
+ catch (err) {
182
+ this.putMetric(name, Date.now() - start, 'Milliseconds');
183
+ this.putMetric(`${name}.Error`, 1, 'Count');
184
+ throw err;
185
+ }
186
+ },
187
+ };
188
+ }
189
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/logging/metrics.ts"],"names":[],"mappings":"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"}
@@ -6,41 +6,95 @@
6
6
  */
7
7
  import type { RequestContext } from '../handler/context.js';
8
8
  /**
9
- * Trace context propagated across service boundaries
9
+ * Trace context propagated across service boundaries.
10
+ *
11
+ * The runtime is wire-compatible with both:
12
+ * - W3C Trace Context (`traceparent` / `tracestate` / `baggage`) — the
13
+ * OpenTelemetry standard; use this to interop with external tracers.
14
+ * - Legacy `x-trace-id` / `x-span-id` headers — VentureKit's original
15
+ * format, still emitted for backward-compat so older services keep
16
+ * working.
10
17
  */
11
18
  export interface TraceContext {
12
- /** Root trace ID (created at the entry point) */
19
+ /** Root trace ID (32 hex chars in W3C mode; opaque otherwise). */
13
20
  traceId: string;
14
- /** Current span ID */
21
+ /** Current span ID (16 hex chars in W3C mode; opaque otherwise). */
15
22
  spanId: string;
16
- /** Parent span ID (undefined for root span) */
23
+ /** Parent span ID (undefined for root span). */
17
24
  parentSpanId?: string;
18
- /** Baggage — arbitrary key-value pairs propagated across all spans */
25
+ /**
26
+ * W3C trace flags byte (e.g. `01` = sampled). Defaults to `01` for the
27
+ * root span. Propagated end-to-end.
28
+ */
29
+ traceFlags?: string;
30
+ /**
31
+ * W3C `tracestate` header value, opaque to us — preserved verbatim so
32
+ * downstream tracing vendors (DataDog, Honeycomb, etc.) see the same
33
+ * state they sent.
34
+ */
35
+ traceState?: string;
36
+ /** Baggage — arbitrary key-value pairs propagated across all spans. */
19
37
  baggage: Record<string, string>;
20
38
  }
21
39
  /**
22
- * Trace headers used for propagation
40
+ * Trace headers used for propagation.
41
+ *
42
+ * W3C headers are the canonical modern format; VK_* headers are emitted
43
+ * alongside for the transition period so older services that only look at
44
+ * `x-trace-id` keep correlating.
23
45
  */
24
46
  export declare const TRACE_HEADERS: {
47
+ /** W3C Trace Context primary header. */
48
+ readonly TRACEPARENT: "traceparent";
49
+ /** W3C Trace Context vendor state. */
50
+ readonly TRACESTATE: "tracestate";
51
+ /** W3C Baggage header (RFC). */
52
+ readonly W3C_BAGGAGE: "baggage";
53
+ /** Legacy VentureKit trace ID (kept for backward compat). */
25
54
  readonly TRACE_ID: "x-trace-id";
55
+ /** Legacy VentureKit span ID. */
26
56
  readonly SPAN_ID: "x-span-id";
57
+ /** Legacy VentureKit parent-span ID. */
27
58
  readonly PARENT_SPAN_ID: "x-parent-span-id";
59
+ /** Legacy VentureKit baggage (JSON-encoded). */
28
60
  readonly BAGGAGE: "x-trace-baggage";
29
61
  };
30
62
  /**
31
- * Generate a random ID (16 hex chars)
63
+ * Generate a random 16-hex-char span ID (8 bytes) — matches W3C `parent_id`.
32
64
  */
33
65
  export declare function generateId(): string;
34
66
  /**
35
- * Extract trace context from request headers
67
+ * Generate a random 32-hex-char trace ID (16 bytes) — matches W3C `trace_id`.
68
+ *
69
+ * Kept separate from {@link generateId} because traceparent requires 128 bits
70
+ * of entropy, while span IDs only need 64 bits.
71
+ */
72
+ export declare function generateTraceId(): string;
73
+ /**
74
+ * Extract trace context from request headers.
75
+ *
76
+ * Priority order (highest → lowest) when multiple headers are present:
77
+ * 1. W3C `traceparent` / `tracestate` / `baggage`
78
+ * 2. Legacy `x-trace-id` / `x-span-id` / `x-trace-baggage`
79
+ * 3. Freshly generated (new root trace)
80
+ *
81
+ * All IDs are validated against a safe regex; baggage is capped at 4 KB so
82
+ * untrusted headers cannot cause log injection or memory blowup.
36
83
  */
37
84
  export declare function extractTraceContext(headers: Record<string, string | undefined>): TraceContext;
38
85
  /**
39
- * Serialize trace context into headers for outbound requests
86
+ * Serialize trace context into headers for outbound requests.
87
+ *
88
+ * Emits both the W3C headers (`traceparent` / `tracestate` / `baggage`)
89
+ * and the legacy `x-trace-*` headers, so callers speaking either dialect
90
+ * stay correlated with us.
40
91
  */
41
92
  export declare function injectTraceHeaders(trace: TraceContext): Record<string, string>;
42
93
  /**
43
- * Create a child trace context (for outbound calls)
94
+ * Create a child trace context (for outbound calls).
95
+ *
96
+ * Preserves trace ID + flags + state + baggage, mints a fresh span, records
97
+ * the caller as parent — standard W3C / OTel parent-child semantics.
44
98
  */
45
99
  export declare function createChildTrace(parent: TraceContext): TraceContext;
46
100
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../../src/logging/tracing.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;GAEG;AACH,eAAO,MAAM,aAAa;;;;;CAKhB,CAAC;AAEX;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAInC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,YAAY,CAgB7F;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAe9E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAOnE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY,CAElE"}
1
+ {"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../../src/logging/tracing.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,aAAa;IACxB,wCAAwC;;IAExC,sCAAsC;;IAEtC,gCAAgC;;IAGhC,6DAA6D;;IAE7D,iCAAiC;;IAEjC,wCAAwC;;IAExC,gDAAgD;;CAExC,CAAC;AAEX;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAInC;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAIxC;AA0FD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,YAAY,CA2D7F;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgC9E;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CASnE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY,CAElE"}