@rawdash/connector-aws-cloudwatch 0.15.0 → 0.16.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.
package/dist/index.js CHANGED
@@ -1,61 +1,6 @@
1
- // ../../connector-shared/dist/index.js
2
- var HttpClientError = class extends Error {
3
- response;
4
- constructor(message, response) {
5
- super(message);
6
- this.name = new.target.name;
7
- this.response = response;
8
- }
9
- };
10
- var TransientError = class extends HttpClientError {
11
- kind = "transient";
12
- };
13
- var RateLimitError = class extends HttpClientError {
14
- kind = "rate_limit";
15
- retryAfter;
16
- constructor(message, response, retryAfter) {
17
- super(message, response);
18
- this.retryAfter = retryAfter;
19
- }
20
- };
21
- var AuthError = class extends HttpClientError {
22
- kind = "auth";
23
- };
24
- var HTTP_CLIENT_VERSION = "0.0.0";
25
- var DEFAULT_USER_AGENT = `rawdash-connector/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;
26
- function connectorUserAgent(connectorId) {
27
- return `rawdash-connector-${connectorId}/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;
28
- }
29
- function parseEpoch(value, unit) {
30
- if (value === null || value === void 0) {
31
- return null;
32
- }
33
- if (unit === "iso") {
34
- if (typeof value !== "string") {
35
- return null;
36
- }
37
- const ms = new Date(value).getTime();
38
- return Number.isFinite(ms) ? ms : null;
39
- }
40
- if (typeof value === "string" && value.trim() === "") {
41
- return null;
42
- }
43
- const n = typeof value === "number" ? value : Number(value);
44
- if (!Number.isFinite(n)) {
45
- return null;
46
- }
47
- const result = unit === "s" ? n * 1e3 : n;
48
- return Number.isFinite(result) ? result : null;
49
- }
50
-
51
- // src/aws-cloudwatch.ts
52
- import {
53
- BaseConnector,
54
- defineConfigFields
55
- } from "@rawdash/core";
1
+ // ../aws-shared/dist/index.js
2
+ import { BaseConnector } from "@rawdash/core";
56
3
  import { z } from "zod";
57
-
58
- // src/sigv4.ts
59
4
  var encoder = new TextEncoder();
60
5
  var ALGORITHM = "AWS4-HMAC-SHA256";
61
6
  function u8(data) {
@@ -126,8 +71,6 @@ async function createAuthorizationHeader(params) {
126
71
  const signature = toHex(await hmac(signingKey, stringToSign));
127
72
  return `${ALGORITHM} Credential=${params.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
128
73
  }
129
-
130
- // src/xml.ts
131
74
  function decodeEntities(value) {
132
75
  return value.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&apos;/g, "'").replace(/&amp;/g, "&");
133
76
  }
@@ -208,74 +151,55 @@ function parseAssumeRole(xml) {
208
151
  function parseErrorCode(xml) {
209
152
  return firstText(xml, "Code");
210
153
  }
211
-
212
- // src/aws-cloudwatch.ts
213
- function readEnv(name) {
214
- const env = globalThis.process?.env;
215
- return env?.[name];
154
+ var HttpClientError = class extends Error {
155
+ response;
156
+ constructor(message, response) {
157
+ super(message);
158
+ this.name = new.target.name;
159
+ this.response = response;
160
+ }
161
+ };
162
+ var TransientError = class extends HttpClientError {
163
+ kind = "transient";
164
+ };
165
+ var RateLimitError = class extends HttpClientError {
166
+ kind = "rate_limit";
167
+ retryAfter;
168
+ constructor(message, response, retryAfter) {
169
+ super(message, response);
170
+ this.retryAfter = retryAfter;
171
+ }
172
+ };
173
+ var AuthError = class extends HttpClientError {
174
+ kind = "auth";
175
+ };
176
+ var HTTP_CLIENT_VERSION = "0.0.0";
177
+ var DEFAULT_USER_AGENT = `rawdash-connector/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;
178
+ function connectorUserAgent(connectorId) {
179
+ return `rawdash-connector-${connectorId}/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;
216
180
  }
217
- var metricQuerySchema = z.object({
218
- id: z.string().regex(
219
- /^[a-z][a-zA-Z0-9_]*$/,
220
- "CloudWatch query id must start with a lowercase letter and contain only letters, digits, and underscores"
221
- ),
222
- namespace: z.string().min(1),
223
- metric: z.string().min(1),
224
- stat: z.string().min(1),
225
- periodSeconds: z.number().int().min(60).refine((n) => n % 60 === 0, {
226
- message: "periodSeconds must be a multiple of 60 (1 minute)"
227
- }),
228
- dimensions: z.record(z.string(), z.string()).optional()
229
- });
230
- var configFields = defineConfigFields(
231
- z.object({
232
- region: z.string().regex(
233
- /^[a-z0-9-]+$/,
234
- "region must look like an AWS region, e.g. us-east-1"
235
- ).meta({
236
- label: "AWS Region",
237
- description: "The AWS region whose CloudWatch metrics you want to read, e.g. us-east-1.",
238
- placeholder: "us-east-1"
239
- }),
240
- accessKeyId: z.object({ $secret: z.string() }).optional().meta({
241
- label: "Access Key ID",
242
- description: "AWS access key ID for an IAM principal with cloudwatch:GetMetricData. Use this together with the secret access key for static-credential auth.",
243
- secret: true
244
- }),
245
- secretAccessKey: z.object({ $secret: z.string() }).optional().meta({
246
- label: "Secret Access Key",
247
- description: "AWS secret access key paired with the access key ID above.",
248
- secret: true
249
- }),
250
- roleArn: z.string().regex(
251
- /^arn:aws:iam::\d{12}:role\/.+/,
252
- "roleArn must be a full IAM role ARN, e.g. arn:aws:iam::123456789012:role/rawdash"
253
- ).optional().meta({
254
- label: "Role ARN",
255
- description: "IAM role to assume via STS instead of using static keys. The base credentials (the access key above, or the ambient AWS environment) must be allowed to sts:AssumeRole this role.",
256
- placeholder: "arn:aws:iam::123456789012:role/rawdash-cloudwatch"
257
- }),
258
- externalId: z.string().min(1).optional().meta({
259
- label: "External ID",
260
- description: "External ID required by the trust policy of the role being assumed. Only used with Role ARN."
261
- }),
262
- metricQueries: z.array(metricQuerySchema).nonempty().meta({
263
- label: "Metric queries",
264
- description: "CloudWatch is too broad to mirror wholesale \u2014 declare the specific metrics to pull. Each query needs an id, namespace, metric name, statistic, and period (seconds, multiple of 60), with optional dimensions."
265
- }),
266
- lookbackMinutes: z.number().int().positive().max(40320).optional().meta({
267
- label: "Lookback (minutes)",
268
- description: "How far back to pull data points on a full sync when the host does not supply a since bound. Defaults to 180.",
269
- placeholder: "180"
270
- })
271
- }).refine(
272
- (val) => val.roleArn !== void 0 || val.accessKeyId !== void 0 && val.secretAccessKey !== void 0,
273
- {
274
- message: "Provide either accessKeyId + secretAccessKey (static credentials) or roleArn (role assumption)"
181
+ function parseEpoch(value, unit) {
182
+ if (value === null || value === void 0) {
183
+ return null;
184
+ }
185
+ if (unit === "iso") {
186
+ if (typeof value !== "string") {
187
+ return null;
275
188
  }
276
- )
277
- );
278
- var cloudWatchCredentials = {
189
+ const ms = new Date(value).getTime();
190
+ return Number.isFinite(ms) ? ms : null;
191
+ }
192
+ if (typeof value === "string" && value.trim() === "") {
193
+ return null;
194
+ }
195
+ const n = typeof value === "number" ? value : Number(value);
196
+ if (!Number.isFinite(n)) {
197
+ return null;
198
+ }
199
+ const result = unit === "s" ? n * 1e3 : n;
200
+ return Number.isFinite(result) ? result : null;
201
+ }
202
+ var awsCredentialsSchema = {
279
203
  accessKeyId: {
280
204
  description: "AWS access key ID",
281
205
  auth: "optional"
@@ -285,61 +209,18 @@ var cloudWatchCredentials = {
285
209
  auth: "optional"
286
210
  }
287
211
  };
288
- var metricDataResponseSchema = z.object({
289
- MetricDataResults: z.array(
290
- z.object({
291
- Id: z.string(),
292
- Label: z.string(),
293
- Timestamps: z.array(z.iso.datetime()),
294
- Values: z.array(z.number()),
295
- StatusCode: z.enum([
296
- "Complete",
297
- "InternalError",
298
- "PartialData",
299
- "Forbidden"
300
- ])
301
- })
302
- ),
303
- NextToken: z.string().optional()
304
- });
305
- var CLOUDWATCH_SERVICE = "monitoring";
306
- var CLOUDWATCH_API_VERSION = "2010-08-01";
307
212
  var STS_SERVICE = "sts";
308
213
  var STS_API_VERSION = "2011-06-15";
309
- var MAX_QUERIES_PER_CALL = 500;
310
- var DEFAULT_LOOKBACK_MINUTES = 180;
311
214
  var ASSUMED_ROLE_TTL_BUFFER_MS = 6e4;
312
215
  var ASSUME_ROLE_DURATION_SECONDS = 3600;
313
- var MS_PER_MINUTE = 6e4;
314
216
  var FORM_CONTENT_TYPE = "application/x-www-form-urlencoded; charset=utf-8";
315
- var CloudWatchConnector = class _CloudWatchConnector extends BaseConnector {
316
- static id = "aws-cloudwatch";
317
- static schemas = {
318
- metric_data: metricDataResponseSchema
319
- };
320
- static create(input, ctx) {
321
- const parsed = configFields.parse(input);
322
- return new _CloudWatchConnector(
323
- {
324
- region: parsed.region,
325
- roleArn: parsed.roleArn,
326
- externalId: parsed.externalId,
327
- metricQueries: parsed.metricQueries,
328
- lookbackMinutes: parsed.lookbackMinutes
329
- },
330
- {
331
- accessKeyId: parsed.accessKeyId,
332
- secretAccessKey: parsed.secretAccessKey
333
- },
334
- ctx
335
- );
336
- }
337
- id = "aws-cloudwatch";
338
- credentials = cloudWatchCredentials;
217
+ function readEnv(name) {
218
+ const env = globalThis.process?.env;
219
+ return env?.[name];
220
+ }
221
+ var BaseAWSConnector = class extends BaseConnector {
222
+ credentials = awsCredentialsSchema;
339
223
  assumedCreds = null;
340
- // -------------------------------------------------------------------------
341
- // Credential resolution
342
- // -------------------------------------------------------------------------
343
224
  baseCredentials() {
344
225
  const { accessKeyId, secretAccessKey } = this.creds;
345
226
  if (accessKeyId && secretAccessKey) {
@@ -355,7 +236,7 @@ var CloudWatchConnector = class _CloudWatchConnector extends BaseConnector {
355
236
  };
356
237
  }
357
238
  throw new AuthError(
358
- "aws-cloudwatch: no AWS credentials available \u2014 provide accessKeyId + secretAccessKey, or set them in the environment for role assumption"
239
+ `${this.id}: no AWS credentials available \u2014 provide accessKeyId + secretAccessKey, or set them in the environment for role assumption`
359
240
  );
360
241
  }
361
242
  async resolveSigningCredentials(signal) {
@@ -363,7 +244,7 @@ var CloudWatchConnector = class _CloudWatchConnector extends BaseConnector {
363
244
  const { accessKeyId, secretAccessKey } = this.creds;
364
245
  if (!accessKeyId || !secretAccessKey) {
365
246
  throw new AuthError(
366
- "aws-cloudwatch: static-credential auth requires both accessKeyId and secretAccessKey"
247
+ `${this.id}: static-credential auth requires both accessKeyId and secretAccessKey`
367
248
  );
368
249
  }
369
250
  return { accessKeyId, secretAccessKey };
@@ -371,15 +252,14 @@ var CloudWatchConnector = class _CloudWatchConnector extends BaseConnector {
371
252
  if (this.assumedCreds && Date.now() < this.assumedCreds.expiresAt) {
372
253
  return this.assumedCreds.value;
373
254
  }
374
- const assumed = await this.assumeRole(this.settings.roleArn, signal);
375
- return assumed;
255
+ return this.assumeRole(this.settings.roleArn, signal);
376
256
  }
377
257
  async assumeRole(roleArn, signal) {
378
258
  const params = new URLSearchParams();
379
259
  params.set("Action", "AssumeRole");
380
260
  params.set("Version", STS_API_VERSION);
381
261
  params.set("RoleArn", roleArn);
382
- params.set("RoleSessionName", "rawdash-aws-cloudwatch");
262
+ params.set("RoleSessionName", `rawdash-${this.id}`);
383
263
  params.set("DurationSeconds", String(ASSUME_ROLE_DURATION_SECONDS));
384
264
  if (this.settings.externalId !== void 0) {
385
265
  params.set("ExternalId", this.settings.externalId);
@@ -396,7 +276,7 @@ var CloudWatchConnector = class _CloudWatchConnector extends BaseConnector {
396
276
  const parsed = parseAssumeRole(xml);
397
277
  if (parsed === null) {
398
278
  throw new AuthError(
399
- "aws-cloudwatch: STS AssumeRole returned no usable credentials"
279
+ `${this.id}: STS AssumeRole returned no usable credentials`
400
280
  );
401
281
  }
402
282
  this.cacheAssumedCredentials(parsed);
@@ -418,9 +298,6 @@ var CloudWatchConnector = class _CloudWatchConnector extends BaseConnector {
418
298
  expiresAt
419
299
  };
420
300
  }
421
- // -------------------------------------------------------------------------
422
- // Signed transport
423
- // -------------------------------------------------------------------------
424
301
  async signedPost(args) {
425
302
  const { amzDate, dateStamp } = formatAmzDate(/* @__PURE__ */ new Date());
426
303
  const payloadHash = await sha256Hex(args.body);
@@ -451,7 +328,7 @@ var CloudWatchConnector = class _CloudWatchConnector extends BaseConnector {
451
328
  "content-type": FORM_CONTENT_TYPE,
452
329
  "x-amz-content-sha256": payloadHash,
453
330
  "x-amz-date": amzDate,
454
- "user-agent": connectorUserAgent("aws-cloudwatch"),
331
+ "user-agent": connectorUserAgent(this.id),
455
332
  Authorization: authorization
456
333
  };
457
334
  if (args.signingCredentials.sessionToken !== void 0) {
@@ -474,9 +351,6 @@ var CloudWatchConnector = class _CloudWatchConnector extends BaseConnector {
474
351
  throw this.classifyAwsError(err);
475
352
  }
476
353
  }
477
- // CloudWatch and STS return AWS error codes inside the (XML) body even on a
478
- // 400 — map the documented ones to the shared error taxonomy so the host
479
- // backs off / pauses / retries correctly.
480
354
  classifyAwsError(err) {
481
355
  if (!(err instanceof Error) || !("kind" in err)) {
482
356
  return err;
@@ -498,13 +372,229 @@ var CloudWatchConnector = class _CloudWatchConnector extends BaseConnector {
498
372
  }
499
373
  return err;
500
374
  }
501
- // -------------------------------------------------------------------------
502
- // GetMetricData request building
503
- // -------------------------------------------------------------------------
375
+ };
376
+ var awsAuthConfigShape = {
377
+ region: z.string().regex(
378
+ /^[a-z0-9-]+$/,
379
+ "region must look like an AWS region, e.g. us-east-1"
380
+ ).meta({
381
+ label: "AWS Region",
382
+ description: "The AWS region whose service endpoint you want to call, e.g. us-east-1.",
383
+ placeholder: "us-east-1"
384
+ }),
385
+ accessKeyId: z.object({ $secret: z.string() }).optional().meta({
386
+ label: "Access Key ID",
387
+ description: "AWS access key ID for an IAM principal with permission to call the relevant service. Use together with the secret access key for static-credential auth.",
388
+ secret: true
389
+ }),
390
+ secretAccessKey: z.object({ $secret: z.string() }).optional().meta({
391
+ label: "Secret Access Key",
392
+ description: "AWS secret access key paired with the access key ID above.",
393
+ secret: true
394
+ }),
395
+ roleArn: z.string().regex(
396
+ /^arn:aws:iam::\d{12}:role\/.+/,
397
+ "roleArn must be a full IAM role ARN, e.g. arn:aws:iam::123456789012:role/rawdash"
398
+ ).optional().meta({
399
+ label: "Role ARN",
400
+ description: "IAM role to assume via STS instead of using static keys. The base credentials (the access key above, or the ambient AWS environment) must be allowed to sts:AssumeRole this role.",
401
+ placeholder: "arn:aws:iam::123456789012:role/rawdash"
402
+ }),
403
+ externalId: z.string().min(1).optional().meta({
404
+ label: "External ID",
405
+ description: "External ID required by the trust policy of the role being assumed. Only used with Role ARN."
406
+ })
407
+ };
408
+ var awsAuthRefine = {
409
+ predicate: (val) => {
410
+ const hasRole = val.roleArn !== void 0;
411
+ const hasStatic = val.accessKeyId !== void 0 && val.secretAccessKey !== void 0;
412
+ if (val.externalId !== void 0 && !hasRole) {
413
+ return false;
414
+ }
415
+ return hasRole || hasStatic;
416
+ },
417
+ message: "Provide either accessKeyId + secretAccessKey (static credentials) or roleArn (role assumption). externalId requires roleArn."
418
+ };
419
+
420
+ // ../../connector-shared/dist/index.js
421
+ var HTTP_CLIENT_VERSION2 = "0.0.0";
422
+ var DEFAULT_USER_AGENT2 = `rawdash-connector/${HTTP_CLIENT_VERSION2} (+https://rawdash.dev)`;
423
+ function parseEpoch2(value, unit) {
424
+ if (value === null || value === void 0) {
425
+ return null;
426
+ }
427
+ if (unit === "iso") {
428
+ if (typeof value !== "string") {
429
+ return null;
430
+ }
431
+ const ms = new Date(value).getTime();
432
+ return Number.isFinite(ms) ? ms : null;
433
+ }
434
+ if (typeof value === "string" && value.trim() === "") {
435
+ return null;
436
+ }
437
+ const n = typeof value === "number" ? value : Number(value);
438
+ if (!Number.isFinite(n)) {
439
+ return null;
440
+ }
441
+ const result = unit === "s" ? n * 1e3 : n;
442
+ return Number.isFinite(result) ? result : null;
443
+ }
444
+
445
+ // src/aws-cloudwatch.ts
446
+ import {
447
+ defineConfigFields,
448
+ defineConnectorDoc,
449
+ defineResources,
450
+ schemasFromResources
451
+ } from "@rawdash/core";
452
+ import { z as z2 } from "zod";
453
+ var metricQuerySchema = z2.object({
454
+ id: z2.string().regex(
455
+ /^[a-z][a-zA-Z0-9_]*$/,
456
+ "CloudWatch query id must start with a lowercase letter and contain only letters, digits, and underscores"
457
+ ),
458
+ namespace: z2.string().min(1),
459
+ metric: z2.string().min(1),
460
+ stat: z2.string().min(1),
461
+ periodSeconds: z2.number().int().min(60).refine((n) => n % 60 === 0, {
462
+ message: "periodSeconds must be a multiple of 60 (1 minute)"
463
+ }),
464
+ dimensions: z2.record(z2.string(), z2.string()).optional()
465
+ });
466
+ var configFields = defineConfigFields(
467
+ z2.object({
468
+ ...awsAuthConfigShape,
469
+ metricQueries: z2.array(metricQuerySchema).nonempty().meta({
470
+ label: "Metric queries",
471
+ description: "CloudWatch is too broad to mirror wholesale; declare the specific metrics to pull. Each query needs an id, namespace, metric name, statistic, and period (seconds, multiple of 60), with optional dimensions."
472
+ }),
473
+ lookbackMinutes: z2.number().int().positive().max(40320).optional().meta({
474
+ label: "Lookback (minutes)",
475
+ description: "How far back to pull data points on a full sync when the host does not supply a since bound. Defaults to 180.",
476
+ placeholder: "180"
477
+ })
478
+ }).refine(awsAuthRefine.predicate, { message: awsAuthRefine.message }).refine(
479
+ (cfg) => new Set(cfg.metricQueries.map((q) => q.id)).size === cfg.metricQueries.length,
480
+ {
481
+ path: ["metricQueries"],
482
+ message: "Each metric query id must be unique"
483
+ }
484
+ )
485
+ );
486
+ var doc = defineConnectorDoc({
487
+ displayName: "AWS CloudWatch",
488
+ category: "infrastructure",
489
+ brandColor: "#FF4F8B",
490
+ tagline: "Pull declared CloudWatch metric time series (any namespace, statistic, and period) into a single metric series per query.",
491
+ rateLimit: "GetMetricData is batched at most 500 metrics per call with NextToken pagination; throttling (Throttling / RequestLimitExceeded / TooManyRequests) is retried with backoff.",
492
+ vendor: {
493
+ name: "Amazon Web Services",
494
+ apiDocs: "https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_GetMetricData.html",
495
+ website: "https://aws.amazon.com/cloudwatch/"
496
+ },
497
+ auth: {
498
+ summary: "Authenticate with either static IAM access keys or an assumed IAM role (STS). The principal needs cloudwatch:GetMetricData on the target region.",
499
+ setup: [
500
+ "Create an IAM user or role with a policy granting `cloudwatch:GetMetricData`.",
501
+ "For static credentials, generate an access key ID and secret access key for that IAM user and store them as secrets.",
502
+ "For role assumption, set `roleArn` to the role to assume (and `externalId` if its trust policy requires one); the base credentials must be allowed to `sts:AssumeRole` it.",
503
+ "Set `region` to the AWS region whose CloudWatch endpoint holds the metrics, e.g. `us-east-1`.",
504
+ 'Reference the keys from config, e.g. `accessKeyId: secret("AWS_ACCESS_KEY_ID")` and `secretAccessKey: secret("AWS_SECRET_ACCESS_KEY")`.'
505
+ ]
506
+ },
507
+ limitations: [
508
+ "CloudWatch is too broad to mirror wholesale; only the metrics declared in `metricQueries` are synced; there is no automatic metric discovery.",
509
+ "The series name is derived from the query namespace/metric, so two queries against the same metric with different statistics or dimensions share one series name and are distinguished only by sample attributes.",
510
+ "Each query period must be a multiple of 60 seconds; sub-minute resolution is not supported.",
511
+ "A full sync uses lookbackMinutes; a latest sync uses a short window covering the last few periods."
512
+ ]
513
+ });
514
+ var metricDataResponseSchema = z2.object({
515
+ MetricDataResults: z2.array(
516
+ z2.object({
517
+ Id: z2.string(),
518
+ Label: z2.string(),
519
+ Timestamps: z2.array(z2.iso.datetime()),
520
+ Values: z2.array(z2.number()),
521
+ StatusCode: z2.enum([
522
+ "Complete",
523
+ "InternalError",
524
+ "PartialData",
525
+ "Forbidden"
526
+ ])
527
+ })
528
+ ),
529
+ NextToken: z2.string().optional()
530
+ });
531
+ var awsCloudwatchResources = defineResources({
532
+ "<namespace>/<metric>": {
533
+ shape: "metric",
534
+ dynamic: true,
535
+ description: "One metric series per declared metric query. The series name is the query namespace/metric (e.g. `AWS/EC2/CPUUtilization`), so the actual keys depend on the configured `metricQueries`. Each sample carries the query statistic, period, query id, the upstream status code, and label as attributes.",
536
+ endpoint: "POST / (GetMetricData)",
537
+ granularity: "Per query period (periodSeconds, a multiple of 60)",
538
+ notes: "Each sync replaces the full set of samples for the metric names it owns (idempotent).",
539
+ dimensions: [
540
+ {
541
+ name: "stat",
542
+ description: "The CloudWatch statistic requested for the query, e.g. Average, Sum, or p99."
543
+ },
544
+ {
545
+ name: "period",
546
+ description: "The aggregation period in seconds for the data points."
547
+ },
548
+ {
549
+ name: "queryId",
550
+ description: "The configured id of the metric query that produced the sample."
551
+ },
552
+ {
553
+ name: "statusCode",
554
+ description: "GetMetricData result status for the series (Complete, PartialData, InternalError, or Forbidden)."
555
+ },
556
+ {
557
+ name: "label",
558
+ description: "The human-readable label CloudWatch returned for the series."
559
+ }
560
+ ],
561
+ responses: { metric_data: metricDataResponseSchema }
562
+ }
563
+ });
564
+ var CLOUDWATCH_SERVICE = "monitoring";
565
+ var CLOUDWATCH_API_VERSION = "2010-08-01";
566
+ var MAX_QUERIES_PER_CALL = 500;
567
+ var DEFAULT_LOOKBACK_MINUTES = 180;
568
+ var MS_PER_MINUTE = 6e4;
569
+ var CloudWatchConnector = class _CloudWatchConnector extends BaseAWSConnector {
570
+ static id = "aws-cloudwatch";
571
+ static resources = awsCloudwatchResources;
572
+ static schemas = schemasFromResources(awsCloudwatchResources);
573
+ static cost = {
574
+ warning: "CloudWatch GetMetricData is billed per metric requested on the paid tier; high-frequency syncs over many metrics add up."
575
+ };
576
+ static create(input, ctx) {
577
+ const parsed = configFields.parse(input);
578
+ return new _CloudWatchConnector(
579
+ {
580
+ region: parsed.region,
581
+ roleArn: parsed.roleArn,
582
+ externalId: parsed.externalId,
583
+ metricQueries: parsed.metricQueries,
584
+ lookbackMinutes: parsed.lookbackMinutes
585
+ },
586
+ {
587
+ accessKeyId: parsed.accessKeyId,
588
+ secretAccessKey: parsed.secretAccessKey
589
+ },
590
+ ctx
591
+ );
592
+ }
593
+ id = "aws-cloudwatch";
504
594
  computeWindow(options) {
505
595
  const endMs = Date.now();
506
596
  if (options.since) {
507
- const sinceMs = parseEpoch(options.since, "iso");
597
+ const sinceMs = parseEpoch2(options.since, "iso");
508
598
  if (sinceMs !== null) {
509
599
  return { startMs: Math.min(sinceMs, endMs), endMs };
510
600
  }
@@ -546,9 +636,6 @@ var CloudWatchConnector = class _CloudWatchConnector extends BaseConnector {
546
636
  });
547
637
  return params.toString();
548
638
  }
549
- // -------------------------------------------------------------------------
550
- // sync
551
- // -------------------------------------------------------------------------
552
639
  async sync(options, storage, signal) {
553
640
  const queries = this.settings.metricQueries;
554
641
  const names = new Set(queries.map((q) => `${q.namespace}/${q.metric}`));
@@ -557,7 +644,6 @@ var CloudWatchConnector = class _CloudWatchConnector extends BaseConnector {
557
644
  }
558
645
  const queriesById = new Map(queries.map((q) => [q.id, q]));
559
646
  const { startMs, endMs } = this.computeWindow(options);
560
- const signingCredentials = await this.resolveSigningCredentials(signal);
561
647
  const samples = [];
562
648
  const host = `${CLOUDWATCH_SERVICE}.${this.settings.region}.amazonaws.com`;
563
649
  for (let i = 0; i < queries.length; i += MAX_QUERIES_PER_CALL) {
@@ -574,6 +660,7 @@ var CloudWatchConnector = class _CloudWatchConnector extends BaseConnector {
574
660
  endMs,
575
661
  nextToken
576
662
  );
663
+ const signingCredentials = await this.resolveSigningCredentials(signal);
577
664
  const xml = await this.signedPost({
578
665
  host,
579
666
  service: CLOUDWATCH_SERVICE,
@@ -619,7 +706,7 @@ var CloudWatchConnector = class _CloudWatchConnector extends BaseConnector {
619
706
  };
620
707
  const count = Math.min(result.timestamps.length, result.values.length);
621
708
  for (let i = 0; i < count; i++) {
622
- const ts = parseEpoch(result.timestamps[i], "iso");
709
+ const ts = parseEpoch2(result.timestamps[i], "iso");
623
710
  const value = result.values[i];
624
711
  if (ts === null || !Number.isFinite(value)) {
625
712
  continue;
@@ -634,6 +721,7 @@ var index_default = CloudWatchConnector;
634
721
  export {
635
722
  CloudWatchConnector,
636
723
  configFields,
637
- index_default as default
724
+ index_default as default,
725
+ doc
638
726
  };
639
727
  //# sourceMappingURL=index.js.map