@aws-blocks/bb-metrics 0.1.0

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.
@@ -0,0 +1,87 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { MetricsErrors } from './errors.js';
4
+ function blocksError(name, message) {
5
+ const err = new Error(`${name}: ${message}`);
6
+ err.name = name;
7
+ return err;
8
+ }
9
+ /**
10
+ * Validate a metric name against CloudWatch constraints.
11
+ * - Must be non-empty
12
+ * - Max 1024 characters
13
+ */
14
+ export function validateMetricName(name) {
15
+ if (!name || name.length === 0) {
16
+ throw blocksError(MetricsErrors.InvalidMetricName, 'Metric name must not be empty');
17
+ }
18
+ if (name.length > 1024) {
19
+ throw blocksError(MetricsErrors.InvalidMetricName, `Metric name exceeds 1024 characters (got ${name.length})`);
20
+ }
21
+ }
22
+ /**
23
+ * Validate dimensions against CloudWatch constraints.
24
+ * - Max 30 dimension key-value pairs
25
+ * - Keys and values must be non-empty
26
+ * - Keys and values max 1024 characters each
27
+ */
28
+ export function validateDimensions(dimensions) {
29
+ const entries = Object.entries(dimensions);
30
+ if (entries.length > 30) {
31
+ throw blocksError(MetricsErrors.InvalidDimensions, `Dimensions exceed 30 entries (got ${entries.length})`);
32
+ }
33
+ for (const [key, value] of entries) {
34
+ if (!key || key.length === 0) {
35
+ throw blocksError(MetricsErrors.InvalidDimensions, 'Dimension key must not be empty');
36
+ }
37
+ if (key.length > 1024) {
38
+ throw blocksError(MetricsErrors.InvalidDimensions, `Dimension key exceeds 1024 characters: '${key.substring(0, 50)}...'`);
39
+ }
40
+ if (value === undefined || value === null || value.length === 0) {
41
+ throw blocksError(MetricsErrors.InvalidDimensions, `Dimension value for key '${key}' must not be empty`);
42
+ }
43
+ if (value.length > 1024) {
44
+ throw blocksError(MetricsErrors.InvalidDimensions, `Dimension value for key '${key}' exceeds 1024 characters`);
45
+ }
46
+ }
47
+ }
48
+ /**
49
+ * Validate batch size (max 100 metrics per EMF document).
50
+ */
51
+ export function validateBatchSize(count) {
52
+ if (count > 100) {
53
+ throw blocksError(MetricsErrors.BatchTooLarge, `Batch exceeds 100 metrics (got ${count})`);
54
+ }
55
+ }
56
+ /** Valid characters for a CloudWatch namespace: alphanumeric, dot, underscore, hash, colon, slash, hyphen, space. */
57
+ const NAMESPACE_PATTERN = /^[a-zA-Z0-9._#:/ -]+$/;
58
+ /**
59
+ * Validate a namespace against CloudWatch constraints.
60
+ * - Must be non-empty (at least one non-whitespace character)
61
+ * - Max 256 characters
62
+ * - Only valid chars: [a-zA-Z0-9._#:/ -]
63
+ * - Must not start with "AWS/" (reserved for AWS services)
64
+ */
65
+ export function validateNamespace(namespace) {
66
+ if (!namespace || namespace.trim().length === 0) {
67
+ throw blocksError(MetricsErrors.InvalidNamespace, 'Namespace must not be empty');
68
+ }
69
+ if (namespace.length > 256) {
70
+ throw blocksError(MetricsErrors.InvalidNamespace, `Namespace exceeds 256 characters (got ${namespace.length})`);
71
+ }
72
+ if (!NAMESPACE_PATTERN.test(namespace)) {
73
+ throw blocksError(MetricsErrors.InvalidNamespace, `Namespace contains invalid characters: '${namespace.substring(0, 50)}'. Valid: [a-zA-Z0-9._#:/ -]`);
74
+ }
75
+ if (namespace.startsWith('AWS/')) {
76
+ throw blocksError(MetricsErrors.InvalidNamespace, `Namespace must not start with 'AWS/' (reserved for AWS services): '${namespace}'`);
77
+ }
78
+ }
79
+ /**
80
+ * Merge default dimensions with per-emit dimensions.
81
+ * Per-emit dimensions take precedence on key conflict.
82
+ */
83
+ export function mergeDimensions(defaults, overrides) {
84
+ if (!overrides || Object.keys(overrides).length === 0)
85
+ return { ...defaults };
86
+ return { ...defaults, ...overrides };
87
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@aws-blocks/bb-metrics",
3
+ "version": "0.1.0",
4
+ "author": "Amazon Web Services",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "files": [
8
+ "dist",
9
+ "README.md",
10
+ "src",
11
+ "LICENSE"
12
+ ],
13
+ "exports": {
14
+ ".": {
15
+ "browser": "./dist/index.browser.js",
16
+ "cdk": {
17
+ "types": "./dist/index.cdk.d.ts",
18
+ "default": "./dist/index.cdk.js"
19
+ },
20
+ "aws-runtime": "./dist/index.aws.js",
21
+ "types": "./dist/index.mock.d.ts",
22
+ "default": "./dist/index.mock.js"
23
+ }
24
+ },
25
+ "scripts": {
26
+ "build": "tsc --build",
27
+ "test": "node --test dist/**/*.test.js"
28
+ },
29
+ "dependencies": {
30
+ "@aws-blocks/core": "^0.1.0",
31
+ "@aws-blocks/bb-logger": "^0.1.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^20.0.0",
35
+ "typescript": "^5.3.0"
36
+ },
37
+ "peerDependencies": {
38
+ "aws-cdk-lib": "^2.257.0",
39
+ "constructs": "^10.6.0"
40
+ }
41
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,30 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /**
5
+ * Typed error constants for Metrics. Use with `isBlocksError()` in catch blocks.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { isBlocksError } from '@aws-blocks/core';
10
+ * import { MetricsErrors } from '@aws-blocks/bb-metrics';
11
+ *
12
+ * try {
13
+ * metrics.emit('', 1);
14
+ * } catch (e) {
15
+ * if (isBlocksError(e, MetricsErrors.InvalidMetricName)) {
16
+ * // handle invalid metric name
17
+ * }
18
+ * }
19
+ * ```
20
+ */
21
+ export const MetricsErrors = {
22
+ /** Metric name is empty or exceeds 1024 characters. */
23
+ InvalidMetricName: 'InvalidMetricNameException',
24
+ /** Dimensions exceed 30 entries, or contain empty keys/values, or key/value exceeds 1024 chars. */
25
+ InvalidDimensions: 'InvalidDimensionsException',
26
+ /** Batch contains more than 100 metrics. */
27
+ BatchTooLarge: 'BatchTooLargeException',
28
+ /** Namespace is empty, too long, contains invalid characters, or uses reserved AWS/ prefix. */
29
+ InvalidNamespace: 'InvalidNamespaceException',
30
+ } as const;
@@ -0,0 +1,351 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { Scope } from '@aws-blocks/core';
5
+ import type { ScopeParent } from '@aws-blocks/core';
6
+ import type {
7
+ MetricsOptions,
8
+ EmitOptions,
9
+ MetricDatum,
10
+ MetricUnit,
11
+ MetricResolution,
12
+ ExternalMetricsRef,
13
+ MetricsEmitter,
14
+ } from './types.js';
15
+ import { MetricsErrors } from './errors.js';
16
+ import {
17
+ validateMetricName,
18
+ validateDimensions,
19
+ validateBatchSize,
20
+ validateNamespace,
21
+ mergeDimensions,
22
+ } from './validation.js';
23
+ import { Logger } from '@aws-blocks/bb-logger';
24
+ import type { ChildLogger } from '@aws-blocks/bb-logger';
25
+
26
+ export { MetricsErrors } from './errors.js';
27
+ export type {
28
+ MetricsOptions,
29
+ EmitOptions,
30
+ MetricDatum,
31
+ MetricUnit,
32
+ MetricResolution,
33
+ ExternalMetricsRef,
34
+ MetricsEmitter,
35
+ } from './types.js';
36
+
37
+ // ── Metrics (AWS runtime via EMF) ───────────────────────────────────────────
38
+
39
+ /**
40
+ * Custom application metrics backed by Amazon CloudWatch (via EMF).
41
+ *
42
+ * Metrics emitted from this Building Block appear in CloudWatch under the
43
+ * configured namespace. Use for dashboards, alarms, and operational visibility.
44
+ *
45
+ * Uses CloudWatch Embedded Metric Format (EMF) — writes metric data as
46
+ * structured JSON to stdout. Lambda captures these and CloudWatch extracts
47
+ * metrics automatically.
48
+ *
49
+ * **When to use:** You need to track numeric measurements over time — request
50
+ * counts, error rates, latency, queue depths, business KPIs.
51
+ *
52
+ * **When NOT to use:** If you need structured log output only, use `Logging`.
53
+ * If you need distributed request tracing, use `Tracing`. If you need to store
54
+ * time-series data for querying, use `Database` or `DistributedTable`.
55
+ *
56
+ * **Best practices:**
57
+ * - Keep dimension cardinality low (avoid user IDs or request IDs as dimensions)
58
+ * - Use consistent metric names across your application
59
+ * - Use `defaultDimensions` for shared context (service name, environment)
60
+ * - Prefer `emitBatch` when recording multiple metrics in a single request
61
+ * - Use units to enable automatic conversions in CloudWatch dashboards
62
+ *
63
+ * **Scaling:** CloudWatch accepts unlimited metrics via EMF. Standard resolution
64
+ * metrics (60s) are retained for 15 days; high-resolution (1s) for 3 hours,
65
+ * then aggregated. Costs scale with unique metric name + dimension combinations.
66
+ *
67
+ * **Local development:** Metrics are written as EMF JSON to stdout (same as AWS).
68
+ * No disk persistence — metrics are ephemeral in local dev.
69
+ *
70
+ * **⚠️ Synchronous:** All metric methods are synchronous (void, not Promise).
71
+ * EMF writes to stdout which Lambda captures asynchronously. Returning a Promise
72
+ * would add overhead for zero benefit.
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * import { Metrics } from '@aws-blocks/bb-metrics';
77
+ *
78
+ * const metrics = new Metrics(scope, 'appMetrics', {
79
+ * namespace: 'MyApp/Orders',
80
+ * defaultDimensions: { service: 'orders' },
81
+ * });
82
+ *
83
+ * metrics.emit('RequestCount', 1, { unit: 'Count' });
84
+ * metrics.emit('Latency', 42, { unit: 'Milliseconds' });
85
+ * ```
86
+ */
87
+ export class Metrics extends Scope implements MetricsEmitter {
88
+ /** The resolved CloudWatch namespace for metrics emitted by this instance. */
89
+ readonly namespace: string;
90
+ /** Dimensions applied to every metric emitted by this instance. */
91
+ readonly defaultDimensions: Readonly<Record<string, string>>;
92
+
93
+ /** @internal Logger for internal operations. Defaults to error-level when not provided. */
94
+ protected log: ChildLogger;
95
+
96
+ constructor(scope: ScopeParent, id: string, options?: MetricsOptions) {
97
+ super(id, { parent: scope });
98
+ this.log = options?.logger ?? new Logger(this, 'logger', { level: 'error' });
99
+ this.namespace = options?.metrics?.namespace
100
+ ?? options?.namespace
101
+ ?? this.fullId;
102
+ this.defaultDimensions = options?.defaultDimensions ?? {};
103
+ validateNamespace(this.namespace);
104
+ }
105
+
106
+ /**
107
+ * Record a single metric data point via EMF.
108
+ *
109
+ * @param name - Metric name (e.g., 'RequestCount', 'Latency'). Non-empty, max 1024 chars.
110
+ * @param value - Numeric value to record.
111
+ * @param options - Unit, dimensions, timestamp, and resolution.
112
+ * @throws {MetricsErrors.InvalidMetricName} If the metric name is empty or exceeds 1024 characters.
113
+ * @throws {MetricsErrors.InvalidDimensions} If dimensions exceed 30 entries or contain empty keys/values.
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * metrics.emit('RequestCount', 1);
118
+ * metrics.emit('Latency', 42, { unit: 'Milliseconds' });
119
+ * metrics.emit('ErrorRate', 0.05, {
120
+ * unit: 'Percent',
121
+ * dimensions: { endpoint: '/api/orders' },
122
+ * });
123
+ * ```
124
+ */
125
+ emit(name: string, value: number, options?: EmitOptions): void {
126
+ validateMetricName(name);
127
+ const dims = mergeDimensions(this.defaultDimensions, options?.dimensions);
128
+ validateDimensions(dims);
129
+
130
+ writeEmf(this.namespace, [{
131
+ name,
132
+ value,
133
+ unit: options?.unit ?? 'None',
134
+ dimensions: dims,
135
+ timestamp: options?.timestamp,
136
+ resolution: options?.resolution ?? 'standard',
137
+ }]);
138
+ }
139
+
140
+ /**
141
+ * Record multiple metric data points in a single EMF document.
142
+ * Metrics with the same dimension set are grouped into one EMF entry.
143
+ *
144
+ * @param metrics - Array of metric data points (max 100 per call).
145
+ * @throws {MetricsErrors.InvalidMetricName} If any metric name is invalid.
146
+ * @throws {MetricsErrors.InvalidDimensions} If any metric's dimensions are invalid.
147
+ * @throws {MetricsErrors.BatchTooLarge} If the batch exceeds 100 metrics.
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * metrics.emitBatch([
152
+ * { name: 'RequestCount', value: 1, unit: 'Count' },
153
+ * { name: 'Latency', value: 42, unit: 'Milliseconds' },
154
+ * { name: 'ErrorCount', value: 0, unit: 'Count' },
155
+ * ]);
156
+ * ```
157
+ */
158
+ emitBatch(metrics: MetricDatum[]): void {
159
+ validateBatchSize(metrics.length);
160
+
161
+ for (const m of metrics) {
162
+ validateMetricName(m.name);
163
+ const dims = mergeDimensions(this.defaultDimensions, m.dimensions);
164
+ validateDimensions(dims);
165
+ }
166
+
167
+ const resolved = metrics.map(m => ({
168
+ name: m.name,
169
+ value: m.value,
170
+ unit: (m.unit ?? 'None') as MetricUnit,
171
+ dimensions: mergeDimensions(this.defaultDimensions, m.dimensions),
172
+ timestamp: m.timestamp,
173
+ resolution: (m.resolution ?? 'standard') as MetricResolution,
174
+ }));
175
+
176
+ writeEmf(this.namespace, resolved);
177
+ }
178
+
179
+ /**
180
+ * No-op. EMF writes are synchronous stdout writes — no buffering to flush.
181
+ */
182
+ flush(): void {}
183
+
184
+ /**
185
+ * Create a child Metrics emitter with inherited namespace and dimensions.
186
+ * The child merges the provided dimensions on top of the parent's defaults.
187
+ *
188
+ * @param dimensions - Additional dimensions for the child instance.
189
+ * @returns A MetricsEmitter with merged dimensions.
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * const requestMetrics = metrics.child({ endpoint: '/api/users', method: 'GET' });
194
+ * requestMetrics.emit('RequestCount', 1);
195
+ * ```
196
+ */
197
+ child(dimensions: Record<string, string>): MetricsEmitter {
198
+ return new ChildMetrics(
199
+ this.namespace,
200
+ { ...this.defaultDimensions, ...dimensions },
201
+ );
202
+ }
203
+
204
+ /**
205
+ * Reference an existing CloudWatch namespace not managed by this Building Block.
206
+ *
207
+ * @param namespace - The CloudWatch namespace string.
208
+ *
209
+ * @example
210
+ * ```typescript
211
+ * const metrics = new Metrics(scope, 'legacy', {
212
+ * metrics: Metrics.fromExisting('MyOrg/SharedMetrics'),
213
+ * });
214
+ * ```
215
+ */
216
+ static fromExisting(namespace: string): ExternalMetricsRef {
217
+ return { __brand: 'ExternalMetricsRef' as const, namespace };
218
+ }
219
+ }
220
+
221
+ // ── ChildMetrics ────────────────────────────────────────────────────────────
222
+
223
+ class ChildMetrics implements MetricsEmitter {
224
+ constructor(
225
+ private namespace: string,
226
+ readonly defaultDimensions: Readonly<Record<string, string>>,
227
+ ) {}
228
+
229
+ emit(name: string, value: number, options?: EmitOptions): void {
230
+ validateMetricName(name);
231
+ const dims = mergeDimensions(this.defaultDimensions, options?.dimensions);
232
+ validateDimensions(dims);
233
+
234
+ writeEmf(this.namespace, [{
235
+ name,
236
+ value,
237
+ unit: options?.unit ?? 'None',
238
+ dimensions: dims,
239
+ timestamp: options?.timestamp,
240
+ resolution: options?.resolution ?? 'standard',
241
+ }]);
242
+ }
243
+
244
+ emitBatch(metrics: MetricDatum[]): void {
245
+ validateBatchSize(metrics.length);
246
+
247
+ for (const m of metrics) {
248
+ validateMetricName(m.name);
249
+ const dims = mergeDimensions(this.defaultDimensions, m.dimensions);
250
+ validateDimensions(dims);
251
+ }
252
+
253
+ const resolved = metrics.map(m => ({
254
+ name: m.name,
255
+ value: m.value,
256
+ unit: (m.unit ?? 'None') as MetricUnit,
257
+ dimensions: mergeDimensions(this.defaultDimensions, m.dimensions),
258
+ timestamp: m.timestamp,
259
+ resolution: (m.resolution ?? 'standard') as MetricResolution,
260
+ }));
261
+
262
+ writeEmf(this.namespace, resolved);
263
+ }
264
+
265
+ flush(): void {}
266
+
267
+ child(dimensions: Record<string, string>): MetricsEmitter {
268
+ return new ChildMetrics(
269
+ this.namespace,
270
+ { ...this.defaultDimensions, ...dimensions },
271
+ );
272
+ }
273
+ }
274
+
275
+ // ── EMF Writer ──────────────────────────────────────────────────────────────
276
+
277
+ interface ResolvedMetric {
278
+ name: string;
279
+ value: number;
280
+ unit: string;
281
+ dimensions: Record<string, string>;
282
+ timestamp?: Date;
283
+ resolution: MetricResolution;
284
+ }
285
+
286
+ interface MetricGroup {
287
+ dimensions: Record<string, string>;
288
+ metrics: ResolvedMetric[];
289
+ }
290
+
291
+ /**
292
+ * Write one or more metrics as EMF-formatted JSON lines to stdout.
293
+ * Groups metrics by dimension set (EMF requires same dimensions per entry).
294
+ */
295
+ function writeEmf(namespace: string, metrics: ResolvedMetric[]): void {
296
+ if (metrics.length === 0) return;
297
+
298
+ const groups = groupByDimensions(metrics);
299
+
300
+ for (const group of groups) {
301
+ const dimKeys = Object.keys(group.dimensions);
302
+ const timestamp = group.metrics[0].timestamp?.getTime() ?? Date.now();
303
+
304
+ const emfPayload: Record<string, unknown> = {
305
+ _aws: {
306
+ Timestamp: timestamp,
307
+ CloudWatchMetrics: [{
308
+ Namespace: namespace,
309
+ Dimensions: dimKeys.length > 0 ? [dimKeys] : [[]],
310
+ Metrics: group.metrics.map(m => ({
311
+ Name: m.name,
312
+ Unit: m.unit,
313
+ StorageResolution: m.resolution === 'high' ? 1 : 60,
314
+ })),
315
+ }],
316
+ },
317
+ ...group.dimensions,
318
+ };
319
+
320
+ for (const m of group.metrics) {
321
+ emfPayload[m.name] = m.value;
322
+ }
323
+
324
+ process.stdout.write(JSON.stringify(emfPayload) + '\n');
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Group metrics by their dimension set. EMF requires metrics in the same
330
+ * CloudWatchMetrics entry to share the same dimension keys and values.
331
+ */
332
+ function groupByDimensions(metrics: ResolvedMetric[]): MetricGroup[] {
333
+ const groups = new Map<string, MetricGroup>();
334
+
335
+ for (const m of metrics) {
336
+ const key = dimensionKey(m.dimensions);
337
+ let group = groups.get(key);
338
+ if (!group) {
339
+ group = { dimensions: m.dimensions, metrics: [] };
340
+ groups.set(key, group);
341
+ }
342
+ group.metrics.push(m);
343
+ }
344
+
345
+ return Array.from(groups.values());
346
+ }
347
+
348
+ function dimensionKey(dims: Record<string, string>): string {
349
+ const sorted = Object.entries(dims).sort(([a], [b]) => a.localeCompare(b));
350
+ return sorted.map(([k, v]) => `${k}=${v}`).join('&');
351
+ }
@@ -0,0 +1,29 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ // Browser stub — Metrics is server-side only.
5
+ // Provides a no-op implementation that silently drops all metric emissions.
6
+
7
+ import type { EmitOptions, MetricDatum, ExternalMetricsRef, MetricsEmitter } from './types.js';
8
+
9
+ export { MetricsErrors } from './errors.js';
10
+ export type {
11
+ MetricsOptions,
12
+ EmitOptions,
13
+ MetricDatum,
14
+ MetricUnit,
15
+ MetricResolution,
16
+ ExternalMetricsRef,
17
+ MetricsEmitter,
18
+ } from './types.js';
19
+
20
+ export class Metrics implements MetricsEmitter {
21
+ constructor(..._args: any[]) {}
22
+ emit(_name: string, _value: number, _options?: EmitOptions): void {}
23
+ emitBatch(_metrics: MetricDatum[]): void {}
24
+ flush(): void {}
25
+ child(_dimensions: Record<string, string>): MetricsEmitter { return new Metrics(); }
26
+ static fromExisting(namespace: string): ExternalMetricsRef {
27
+ return { __brand: 'ExternalMetricsRef' as const, namespace };
28
+ }
29
+ }
@@ -0,0 +1,40 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { Scope } from '@aws-blocks/core/cdk';
5
+ import type { ScopeParent } from '@aws-blocks/core';
6
+ import type { MetricsOptions } from './types.js';
7
+
8
+ export { MetricsErrors } from './errors.js';
9
+ export type {
10
+ MetricsOptions,
11
+ EmitOptions,
12
+ MetricDatum,
13
+ MetricUnit,
14
+ MetricResolution,
15
+ ExternalMetricsRef,
16
+ MetricsEmitter,
17
+ } from './types.js';
18
+
19
+ /**
20
+ * CDK construct for Metrics.
21
+ *
22
+ * EMF writes structured JSON to stdout which Lambda captures in CloudWatch Logs.
23
+ * CloudWatch automatically extracts metrics — no additional IAM permissions or
24
+ * environment variables are needed.
25
+ */
26
+ export class Metrics extends Scope {
27
+ /** The resolved CloudWatch namespace for metrics emitted by this instance. */
28
+ readonly namespace: string;
29
+
30
+ /** Default dimensions applied to every metric emitted by this instance. */
31
+ readonly defaultDimensions: Readonly<Record<string, string>>;
32
+
33
+ constructor(scope: ScopeParent, id: string, options?: MetricsOptions) {
34
+ super(id, { parent: scope });
35
+ this.namespace = options?.metrics?.namespace
36
+ ?? options?.namespace
37
+ ?? this.fullId;
38
+ this.defaultDimensions = options?.defaultDimensions ?? {};
39
+ }
40
+ }
@@ -0,0 +1,20 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /**
5
+ * Mock (local development) runtime for Metrics.
6
+ *
7
+ * Behavior is identical to the AWS runtime — both write EMF-formatted JSON to
8
+ * stdout. In local dev the output is visible in the terminal; in Lambda it is
9
+ * captured by CloudWatch Logs. No separate mock implementation is needed.
10
+ */
11
+ export { Metrics, MetricsErrors } from './index.aws.js';
12
+ export type {
13
+ MetricsOptions,
14
+ EmitOptions,
15
+ MetricDatum,
16
+ MetricUnit,
17
+ MetricResolution,
18
+ ExternalMetricsRef,
19
+ MetricsEmitter,
20
+ } from './types.js';