@dataworks-technology/data 0.1.9 → 0.2.2

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,4 +1,24 @@
1
- // node_modules/@dataworks/sdk/dist/client/subscription-manager.js
1
+ // ../../../Dataworks-SDK/dist/client/events-auth.js
2
+ var INGEST_AUTH_ERROR = "Authentication failed: username, password, and clientId are required for ingest.";
3
+ function resolveEventsAuth(input) {
4
+ const userToken = input.userToken?.trim();
5
+ if (userToken) {
6
+ return { mode: "userCredentials", token: userToken };
7
+ }
8
+ const apiKey = input.apiKey?.trim();
9
+ if (apiKey) {
10
+ return { mode: "apiKey", apiKey };
11
+ }
12
+ throw new Error(INGEST_AUTH_ERROR);
13
+ }
14
+ function buildEventsAuthHeader(resolved, host) {
15
+ if (resolved.mode === "userCredentials") {
16
+ return { Authorization: resolved.token, host };
17
+ }
18
+ return { "x-api-key": resolved.apiKey, host };
19
+ }
20
+
21
+ // ../../../Dataworks-SDK/dist/client/subscription-manager.js
2
22
  function createAutoRefreshingSubscription(options) {
3
23
  const maxReconnectAttempts = options.maxReconnectAttempts ?? 1;
4
24
  let reconnectAttempts = 0;
@@ -40,7 +60,7 @@ function createAutoRefreshingSubscription(options) {
40
60
  };
41
61
  }
42
62
 
43
- // node_modules/@dataworks/sdk/dist/client/validation.js
63
+ // ../../../Dataworks-SDK/dist/client/validation.js
44
64
  function validateMetric(m) {
45
65
  if (typeof m.metric !== "string" || m.metric.trim() === "")
46
66
  return "metric must be a non-empty string";
@@ -89,7 +109,7 @@ function getMetricValidationResult(metrics) {
89
109
  return { valid, dropped };
90
110
  }
91
111
 
92
- // node_modules/@dataworks/sdk/dist/client/base64url.js
112
+ // ../../../Dataworks-SDK/dist/client/base64url.js
93
113
  var toBase64Url = (input) => {
94
114
  if (typeof Buffer !== "undefined") {
95
115
  return Buffer.from(input).toString("base64url");
@@ -104,7 +124,7 @@ var fromBase64Url = (b64url) => {
104
124
  return atob(padded);
105
125
  };
106
126
 
107
- // node_modules/@dataworks/sdk/dist/client/auth.js
127
+ // ../../../Dataworks-SDK/dist/client/auth.js
108
128
  async function loginWithCredentials(username, password, config) {
109
129
  const authParams = {
110
130
  USERNAME: username,
@@ -200,8 +220,22 @@ async function computeSecretHashAsync(username, clientId, clientSecret) {
200
220
  // src/validation.ts
201
221
  var validateMetric2 = validateMetric;
202
222
  var filterValidMetrics2 = filterValidMetrics;
223
+ var getMetricValidationResult2 = getMetricValidationResult;
224
+
225
+ // src/types.ts
226
+ var MetricValidationError = class extends Error {
227
+ constructor(message, dropped) {
228
+ super(message);
229
+ this.code = "METRIC_VALIDATION_FAILED";
230
+ this.name = "MetricValidationError";
231
+ this.dropped = dropped;
232
+ }
233
+ };
203
234
 
204
235
  // src/data-client.ts
236
+ function createSubscriptionError(message, code, cause) {
237
+ return { message, code, cause };
238
+ }
205
239
  var DataClient = class {
206
240
  constructor(config) {
207
241
  this.credentials = null;
@@ -229,7 +263,7 @@ var DataClient = class {
229
263
  }
230
264
  /**
231
265
  * Ingest metric data points into the Dataworks Data Engine.
232
- * Validates each metric before sending — invalid metrics are silently dropped.
266
+ * Validates each metric before sending — invalid metrics are dropped with warnings.
233
267
  *
234
268
  * @param metrics - Array of metric data points
235
269
  * @param eventId - Event identifier
@@ -238,12 +272,53 @@ var DataClient = class {
238
272
  * @see https://data-sdk-docs.dataworks.live/ingesting-data
239
273
  */
240
274
  async ingest(metrics, eventId, datasetDatasourceId) {
241
- this.requireAuth();
242
- const valid = filterValidMetrics2(
243
- metrics,
244
- (msg) => console.warn(`[@dataworks-technology/data] ${msg}`)
245
- );
246
- if (valid.length === 0) return;
275
+ await this.ingestWithResult(metrics, eventId, datasetDatasourceId);
276
+ }
277
+ /**
278
+ * Ingest metrics with structured validation diagnostics.
279
+ *
280
+ * By default (`validationMode: "best-effort"`), invalid metrics are dropped and
281
+ * valid metrics continue. In strict mode, invalid metrics cause a
282
+ * MetricValidationError before any network request is sent.
283
+ *
284
+ * @returns IngestResult with accepted/dropped counts and dropped reasons.
285
+ */
286
+ async ingestWithResult(metrics, eventId, datasetDatasourceId, options = {}) {
287
+ this.requireIngestAuth();
288
+ const validationMode = options.validationMode ?? "best-effort";
289
+ const validation = getMetricValidationResult2(metrics);
290
+ const valid = validation.valid;
291
+ const dropped = validation.dropped.map((item) => ({
292
+ index: item.index,
293
+ reason: item.reason,
294
+ metric: item.metric
295
+ }));
296
+ dropped.forEach((item) => {
297
+ options.onDroppedMetric?.(item);
298
+ console.warn(
299
+ `[@dataworks-technology/data] Dropping invalid metric [${String(item.metric.metric)}=${String(item.metric.value)}]: ${item.reason}`
300
+ );
301
+ });
302
+ if (validationMode === "strict" && dropped.length > 0) {
303
+ throw new MetricValidationError(
304
+ "[@dataworks-technology/data] validation failed: one or more metrics are invalid",
305
+ dropped
306
+ );
307
+ }
308
+ if (valid.length === 0) {
309
+ if (options.requireAtLeastOneValidMetric) {
310
+ throw new MetricValidationError(
311
+ "[@dataworks-technology/data] validation failed: no valid metrics to ingest",
312
+ dropped
313
+ );
314
+ }
315
+ return {
316
+ acceptedCount: 0,
317
+ droppedCount: dropped.length,
318
+ dropped,
319
+ requestSent: false
320
+ };
321
+ }
247
322
  const payload = {
248
323
  eventId,
249
324
  datasetDatasourceId,
@@ -265,6 +340,12 @@ var DataClient = class {
265
340
  `[@dataworks-technology/data] ingest failed: ${resp.status} \u2014 ${body}`
266
341
  );
267
342
  }
343
+ return {
344
+ acceptedCount: valid.length,
345
+ droppedCount: dropped.length,
346
+ dropped,
347
+ requestSent: true
348
+ };
268
349
  }
269
350
  /**
270
351
  * Report an error to the Dataworks Data Engine.
@@ -274,7 +355,7 @@ var DataClient = class {
274
355
  * @see https://data-sdk-docs.dataworks.live/error-reporting
275
356
  */
276
357
  async reportError(error) {
277
- this.requireAuth();
358
+ this.requireIngestAuth();
278
359
  const resp = await this.fetchWithAutoRefresh(
279
360
  (accessToken) => fetch(this.config.errorUrl, {
280
361
  method: "POST",
@@ -313,7 +394,10 @@ var DataClient = class {
313
394
  * @see https://data-sdk-docs.dataworks.live/real-time-subscriptions
314
395
  */
315
396
  subscribe(channel, onEvent, onError) {
316
- this.requireAuth();
397
+ const apiKey = this.config.apiKey?.trim();
398
+ if (!this.credentials && !apiKey) {
399
+ this.requireAuth();
400
+ }
317
401
  if (typeof WebSocket === "undefined") {
318
402
  throw new Error(
319
403
  "[@dataworks-technology/data] WebSocket is not available. Use Node >= 22, Bun, or a browser environment. For older Node versions, install a WebSocket polyfill (e.g. ws) and assign it to globalThis.WebSocket."
@@ -327,20 +411,32 @@ var DataClient = class {
327
411
  const channelPath = channel.startsWith("/") ? channel : `/${channel}`;
328
412
  return createAutoRefreshingSubscription({
329
413
  refreshAuth: async () => {
330
- await this.refreshSession();
414
+ if (this.credentials) {
415
+ await this.refreshSession();
416
+ }
331
417
  },
332
418
  onReconnectError: (error) => {
333
- onError?.(error);
419
+ onError?.(
420
+ createSubscriptionError(error.message, "RECONNECT_FAILED", error)
421
+ );
334
422
  },
335
423
  connect: ({ onUnexpectedClose }) => {
336
424
  const url = new URL(this.config.realtimeUrl);
337
425
  url.pathname = "/event/realtime";
338
426
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
427
+ if (url.host.includes(".appsync-api.")) {
428
+ url.host = url.host.replace(
429
+ ".appsync-api.",
430
+ ".appsync-realtime-api."
431
+ );
432
+ }
339
433
  const host = new URL(this.config.realtimeUrl).host;
340
- const authPayload = JSON.stringify({
341
- Authorization: this.credentials.accessToken,
342
- host
434
+ const resolvedAuth = resolveEventsAuth({
435
+ userToken: this.credentials?.accessToken,
436
+ apiKey: this.config.apiKey?.trim()
343
437
  });
438
+ const realtimeAuthHeader = buildEventsAuthHeader(resolvedAuth, host);
439
+ const authPayload = JSON.stringify(realtimeAuthHeader);
344
440
  const authHeader = toBase64Url(authPayload);
345
441
  const ws = new WebSocket(url.toString(), [
346
442
  "aws-appsync-event-ws",
@@ -358,8 +454,9 @@ var DataClient = class {
358
454
  });
359
455
  ws.addEventListener("error", () => {
360
456
  onError?.(
361
- new Error(
362
- "[@dataworks-technology/data] WebSocket connection error"
457
+ createSubscriptionError(
458
+ "[@dataworks-technology/data] WebSocket connection error",
459
+ "CONNECTION_ERROR"
363
460
  )
364
461
  );
365
462
  });
@@ -369,8 +466,9 @@ var DataClient = class {
369
466
  msg = JSON.parse(String(event.data));
370
467
  } catch {
371
468
  onError?.(
372
- new Error(
373
- "[@dataworks-technology/data] Received malformed message from AppSync"
469
+ createSubscriptionError(
470
+ "[@dataworks-technology/data] Received malformed message from AppSync",
471
+ "PARSE_ERROR"
374
472
  )
375
473
  );
376
474
  return;
@@ -381,10 +479,7 @@ var DataClient = class {
381
479
  type: "subscribe",
382
480
  id: crypto.randomUUID(),
383
481
  channel: channelPath,
384
- authorization: {
385
- Authorization: this.credentials.accessToken,
386
- host
387
- }
482
+ authorization: realtimeAuthHeader
388
483
  })
389
484
  );
390
485
  } else if (msg.type === "data") {
@@ -394,8 +489,9 @@ var DataClient = class {
394
489
  onEvent(JSON.parse(String(raw)));
395
490
  } catch {
396
491
  onError?.(
397
- new Error(
398
- "[@dataworks-technology/data] Received malformed event payload from AppSync"
492
+ createSubscriptionError(
493
+ "[@dataworks-technology/data] Received malformed event payload from AppSync",
494
+ "PARSE_ERROR"
399
495
  )
400
496
  );
401
497
  }
@@ -404,8 +500,9 @@ var DataClient = class {
404
500
  const msgStr = JSON.stringify(msg);
405
501
  if (msgStr.toLowerCase().includes("unauthor") && !unexpectedCloseHandled) {
406
502
  onError?.(
407
- new Error(
408
- "[@dataworks-technology/data] Unauthorized \u2014 session may have expired, attempting to reconnect"
503
+ createSubscriptionError(
504
+ "[@dataworks-technology/data] Unauthorized \u2014 session may have expired, attempting to reconnect",
505
+ "UNAUTHORIZED"
409
506
  )
410
507
  );
411
508
  handleUnexpectedCloseOnce();
@@ -413,7 +510,10 @@ var DataClient = class {
413
510
  const errors = msg.errors;
414
511
  const errorMessage = msg.message ?? errors?.[0]?.message ?? errors?.[0]?.errorType ?? "AppSync subscription error";
415
512
  onError?.(
416
- new Error(`[@dataworks-technology/data] ${errorMessage}`)
513
+ createSubscriptionError(
514
+ `[@dataworks-technology/data] ${errorMessage}`,
515
+ "SUBSCRIPTION_ERROR"
516
+ )
417
517
  );
418
518
  }
419
519
  }
@@ -448,6 +548,16 @@ var DataClient = class {
448
548
  );
449
549
  }
450
550
  }
551
+ /**
552
+ * @internal Guard for ingest-only operations (ingest, reportError). Throws the
553
+ * canonical SDK ingest auth error so the message is consistent with the lower-level
554
+ * `@dataworks/sdk/client` HttpClient guard.
555
+ */
556
+ requireIngestAuth() {
557
+ if (!this.credentials) {
558
+ throw new Error(`[@dataworks-technology/data] ${INGEST_AUTH_ERROR}`);
559
+ }
560
+ }
451
561
  async fetchWithAutoRefresh(makeRequest) {
452
562
  this.requireAuth();
453
563
  let response = await makeRequest(this.credentials.accessToken);
@@ -497,6 +607,7 @@ DataClient.validateMetric = validateMetric2;
497
607
  DataClient.filterValidMetrics = filterValidMetrics2;
498
608
  export {
499
609
  DataClient,
610
+ MetricValidationError,
500
611
  filterValidMetrics2 as filterValidMetrics,
501
612
  validateMetric2 as validateMetric
502
613
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../node_modules/@dataworks/sdk/dist/client/subscription-manager.js","../node_modules/@dataworks/sdk/dist/client/validation.js","../node_modules/@dataworks/sdk/dist/client/base64url.js","../node_modules/@dataworks/sdk/dist/client/auth.js","../src/validation.ts","../src/data-client.ts"],"sourcesContent":["/**\n * Generic subscription lifecycle manager with auth-refresh reconnect support.\n *\n * The protocol-specific client provides a `connect` function and calls\n * `onUnexpectedClose` when auth expiry or unexpected socket termination occurs.\n */\nexport function createAutoRefreshingSubscription(options) {\n const maxReconnectAttempts = options.maxReconnectAttempts ?? 1;\n let reconnectAttempts = 0;\n let reconnectInFlight = false;\n let closedByUser = false;\n let activeConnection = null;\n const hooks = {\n onUnexpectedClose: () => {\n if (closedByUser || reconnectInFlight) {\n return;\n }\n if (reconnectAttempts >= maxReconnectAttempts) {\n return;\n }\n reconnectInFlight = true;\n reconnectAttempts += 1;\n void (async () => {\n try {\n await options.refreshAuth();\n if (closedByUser) {\n return;\n }\n activeConnection = options.connect(hooks);\n }\n catch {\n options.onReconnectError?.(new Error(\"Subscription reconnect failed. Please retry or reauthenticate.\"));\n }\n finally {\n reconnectInFlight = false;\n }\n })();\n },\n };\n activeConnection = options.connect(hooks);\n return {\n close() {\n closedByUser = true;\n activeConnection?.close();\n activeConnection = null;\n },\n };\n}\n","/**\n * Metric validation for Dataworks payloads.\n *\n * Ported from Dataworks-Data packages/adaptors/shared-ts/src/validation.ts\n * to serve as the single source of truth for all Dataworks engine SDKs.\n *\n * @module @dataworks/sdk/client\n */\n/**\n * Returns null if the metric is valid, or a human-readable reason string if invalid.\n */\nexport function validateMetric(m) {\n if (typeof m.metric !== \"string\" || m.metric.trim() === \"\")\n return \"metric must be a non-empty string\";\n if (typeof m.athleteId !== \"string\" || m.athleteId.trim() === \"\")\n return \"athleteId must be a non-empty string\";\n if (!Number.isInteger(m.timestamp) || m.timestamp <= 0)\n return `timestamp must be a positive integer, got ${m.timestamp}`;\n if (m.value === null || m.value === undefined)\n return \"value is null/undefined\";\n if (typeof m.value === \"number\" && !isFinite(m.value))\n return `value is non-finite number (${m.value})`;\n if (typeof m.value !== \"number\" && typeof m.value !== \"string\")\n return `value type ${typeof m.value} not allowed (must be number or string)`;\n if (typeof m.value === \"string\" && m.value === \"\")\n return \"value is empty string\";\n return null;\n}\n/**\n * Filters an array of metrics, returning only valid ones.\n * Logs a warning for each dropped item via the provided warn function.\n */\nexport function filterValidMetrics(metrics, warn) {\n const { valid, dropped } = getMetricValidationResult(metrics);\n for (const item of dropped) {\n let valueStr;\n try {\n valueStr = JSON.stringify(item.metric.value);\n }\n catch {\n valueStr = String(item.metric.value);\n }\n warn(`Dropping invalid metric [${String(item.metric.metric)}=${valueStr}]: ${item.reason}`);\n }\n return valid;\n}\n/**\n * Validates metrics and returns both accepted and dropped items (with reasons).\n * This is useful for consumers that need structured diagnostics.\n */\nexport function getMetricValidationResult(metrics) {\n const valid = [];\n const dropped = [];\n for (const [index, m] of metrics.entries()) {\n const reason = validateMetric(m);\n if (reason) {\n dropped.push({\n index,\n reason,\n metric: m,\n });\n }\n else {\n valid.push(m);\n }\n }\n return { valid, dropped };\n}\n","/**\n * Runtime-agnostic base64url encode/decode.\n *\n * Prefers Node.js Buffer when available, falls back to btoa/atob for\n * browser and edge runtimes. Used for AppSync auth subprotocol headers\n * and JWT payload parsing.\n *\n * @module @dataworks/sdk/client\n */\n/** Encode a UTF-8 string to base64url. */\nexport const toBase64Url = (input) => {\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(input).toString(\"base64url\");\n }\n return btoa(input).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n};\n/** Decode a base64url string to UTF-8. */\nexport const fromBase64Url = (b64url) => {\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(b64url, \"base64url\").toString(\"utf-8\");\n }\n const padded = b64url.replace(/-/g, \"+\").replace(/_/g, \"/\") +\n \"=\".repeat(((-b64url.length % 4) + 4) % 4);\n return atob(padded);\n};\n","/**\n * Cognito authentication helpers.\n *\n * Provides M2M (client_credentials) and user (USER_PASSWORD_AUTH) flows.\n * Refactored from @dataworks/sdk e2e-utils.ts — uses native fetch instead\n * of axios so the client sub-path has zero heavy dependencies.\n *\n * @module @dataworks/sdk/client\n */\nimport { fromBase64Url } from \"./base64url.js\";\n/**\n * Fetch an M2M access token using the client_credentials grant.\n *\n * @param clientId - Cognito app client ID\n * @param clientSecret - Cognito app client secret\n * @param tokenUrl - Cognito token endpoint (e.g. https://<domain>.auth.<region>.amazoncognito.com/oauth2/token)\n * @returns Access token string\n */\nexport async function getClientToken(clientId, clientSecret, tokenUrl) {\n const resp = await fetch(tokenUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"client_credentials\",\n client_id: clientId,\n client_secret: clientSecret,\n }),\n });\n if (!resp.ok) {\n throw new Error(`getClientToken: Cognito token request failed: ${resp.status} ${resp.statusText}`);\n }\n const data = (await resp.json());\n if (!data.access_token) {\n throw new Error(\"getClientToken: response missing access_token\");\n }\n return data.access_token;\n}\n/**\n * Authenticate a user via Cognito USER_PASSWORD_AUTH flow.\n * Returns access token, ID token, refresh token, and tenant (extracted from JWT).\n *\n * Used by external developer clients — no client secret required when the\n * Cognito app client is configured without one.\n *\n * @param username - Cognito username\n * @param password - Cognito password\n * @param config - Cognito endpoint and client configuration\n * @returns Login result with tokens and tenant\n */\nexport async function loginWithCredentials(username, password, config) {\n const authParams = {\n USERNAME: username,\n PASSWORD: password,\n };\n // If a client secret is configured, compute SECRET_HASH\n if (config.clientSecret) {\n authParams.SECRET_HASH = await computeSecretHashAsync(username, config.clientId, config.clientSecret);\n }\n const resp = await fetch(config.cognitoEndpoint, {\n method: \"POST\",\n headers: {\n \"X-Amz-Target\": \"AWSCognitoIdentityProviderService.InitiateAuth\",\n \"Content-Type\": \"application/x-amz-json-1.1\",\n },\n body: JSON.stringify({\n AuthFlow: \"USER_PASSWORD_AUTH\",\n ClientId: config.clientId,\n AuthParameters: authParams,\n }),\n });\n if (!resp.ok) {\n const body = await resp.text().catch(() => \"\");\n throw new Error(`loginWithCredentials: Cognito auth failed: ${resp.status} — ${body}`);\n }\n const data = (await resp.json());\n const result = data.AuthenticationResult;\n if (!result?.AccessToken || !result?.IdToken) {\n throw new Error(\"loginWithCredentials: response missing AccessToken or IdToken\");\n }\n // Extract tenant from JWT claims (ID token payload)\n const tenant = extractTenantFromJwt(result.IdToken);\n return {\n accessToken: result.AccessToken,\n idToken: result.IdToken,\n refreshToken: result.RefreshToken ?? \"\",\n tenant,\n };\n}\n/**\n * Refreshes Cognito user tokens using REFRESH_TOKEN_AUTH.\n *\n * Returns updated tokens and tenant, preserving previous tenant/idToken when\n * Cognito omits fields in the refresh response.\n *\n * @param refreshToken - The refresh token string\n * @param config - Cognito endpoint and client configuration, plus optional username/previousTenant/previousIdToken\n */\nexport async function refreshWithToken(refreshToken, config) {\n const authParams = {\n REFRESH_TOKEN: refreshToken,\n };\n if (config.clientSecret) {\n if (!config.username) {\n throw new Error(\"refreshWithToken: username is required when clientSecret is configured\");\n }\n authParams.SECRET_HASH = await computeSecretHashAsync(config.username, config.clientId, config.clientSecret);\n }\n const resp = await fetch(config.cognitoEndpoint, {\n method: \"POST\",\n headers: {\n \"X-Amz-Target\": \"AWSCognitoIdentityProviderService.InitiateAuth\",\n \"Content-Type\": \"application/x-amz-json-1.1\",\n },\n body: JSON.stringify({\n AuthFlow: \"REFRESH_TOKEN_AUTH\",\n ClientId: config.clientId,\n AuthParameters: authParams,\n }),\n });\n if (!resp.ok) {\n const body = await resp.text().catch(() => \"\");\n throw new Error(`refreshWithToken: Cognito refresh failed: ${resp.status} — ${body}`);\n }\n const data = (await resp.json());\n const result = data.AuthenticationResult;\n if (!result?.AccessToken) {\n throw new Error(\"refreshWithToken: response missing AccessToken\");\n }\n const idToken = result.IdToken ?? config.previousIdToken ?? \"\";\n const extractedTenant = idToken ? extractTenantFromJwt(idToken) : \"\";\n const tenant = extractedTenant || config.previousTenant || \"\";\n return {\n accessToken: result.AccessToken,\n idToken,\n refreshToken: result.RefreshToken ?? refreshToken,\n tenant,\n };\n}\n/**\n * Extracts the tenant claim from a JWT ID token without verifying the signature.\n * The token is validated server-side by AppSync/API Gateway — this is a client-side\n * convenience for reading the tenant value.\n */\nfunction extractTenantFromJwt(idToken) {\n try {\n const payload = idToken.split(\".\")[1];\n const decoded = JSON.parse(fromBase64Url(payload));\n // Cognito custom attributes use \"custom:tenant\" claim\n return (decoded[\"custom:tenants\"] ??\n decoded[\"custom:tenant\"] ??\n decoded.tenant ??\n decoded[\"cognito:groups\"]?.[0] ??\n \"\");\n }\n catch {\n return \"\";\n }\n}\nasync function computeSecretHashAsync(username, clientId, clientSecret) {\n // Dynamic import to keep this module usable in environments without crypto.\n const crypto = await import(\"crypto\");\n return crypto\n .createHmac(\"sha256\", clientSecret)\n .update(username + clientId)\n .digest(\"base64\");\n}\n","/**\n * Validation re-exports — thin wrappers around @dataworks/sdk/client validation.\n * Declared locally so the published .d.ts is fully self-contained.\n *\n * @module @dataworks-technology/data\n */\n\nimport {\n validateMetric as _validateMetric,\n filterValidMetrics as _filterValidMetrics,\n} from \"@dataworks/sdk/client\";\nimport type { RawMetricItem, MetricItem } from \"./types.js\";\n\n/**\n * Check whether a metric is valid.\n * Returns null if valid, or a human-readable reason string if invalid.\n */\nexport const validateMetric: (m: RawMetricItem) => string | null =\n _validateMetric;\n\n/**\n * Filter an array of metrics, keeping only valid ones.\n * Logs a warning for each dropped item via the provided warn function.\n */\nexport const filterValidMetrics: (\n metrics: RawMetricItem[],\n warn: (msg: string) => void,\n) => MetricItem[] = _filterValidMetrics;\n","/**\n * DataClient — the public entry point for external developers using the Dataworks Data Engine.\n *\n * Handles authentication, metric ingestion, error reporting, and real-time subscriptions.\n * All SDK foundation code (@dataworks/sdk) is bundled at build time — consumers install\n * only @dataworks-technology/data with zero transitive dependencies.\n *\n * @example\n * ```ts\n * import { DataClient } from \"@dataworks-technology/data\";\n *\n * const dataworks = new DataClient({\n * cognitoEndpoint: \"https://cognito-idp.eu-west-1.amazonaws.com/\",\n * clientId: \"abc123\",\n * ingestUrl: \"https://dev-realtime.dataworks.live\",\n * errorUrl: \"https://dev-realtime-errors.dataworks.live\",\n * realtimeUrl: \"https://dev-event-api.dataworks.live\",\n * });\n *\n * await dataworks.login(\"graeme\", \"password123\");\n * await dataworks.ingest([{ athleteId: \"1\", metric: \"heartrate_calculated\", value: 172, timestamp: Date.now() / 1000 }], \"evt1\", \"ds1\");\n * ```\n *\n * @module @dataworks-technology/data\n */\n\nimport {\n createAutoRefreshingSubscription,\n loginWithCredentials,\n refreshWithToken,\n toBase64Url,\n} from \"@dataworks/sdk/client\";\nimport { validateMetric, filterValidMetrics } from \"./validation.js\";\nimport type {\n DataClientConfig,\n LoginResult,\n Metric,\n ErrorReport,\n SubscriptionHandler,\n ErrorHandler,\n Subscription,\n} from \"./types.js\";\n\nexport class DataClient {\n private readonly config: DataClientConfig;\n private credentials: LoginResult | null = null;\n private lastLoginUsername: string | null = null;\n private refreshInFlight: Promise<void> | null = null;\n\n constructor(config: DataClientConfig) {\n this.config = config;\n }\n\n /**\n * Authenticate with the Dataworks platform using Cognito USER_PASSWORD_AUTH.\n * Must be called before ingest(), reportError(), or subscribe().\n *\n * @param username - Cognito username\n * @param password - Cognito password\n * @returns Login result containing tokens and tenant\n * @see https://data-sdk-docs.dataworks.live/authentication\n */\n async login(username: string, password: string): Promise<LoginResult> {\n this.lastLoginUsername = username;\n this.credentials = await loginWithCredentials(username, password, {\n cognitoEndpoint: this.config.cognitoEndpoint,\n clientId: this.config.clientId,\n clientSecret: this.config.clientSecret,\n });\n return this.credentials;\n }\n\n /**\n * Ingest metric data points into the Dataworks Data Engine.\n * Validates each metric before sending — invalid metrics are silently dropped.\n *\n * @param metrics - Array of metric data points\n * @param eventId - Event identifier\n * @param datasetDatasourceId - Dataset-datasource identifier\n * @throws Error if not authenticated or if the request fails\n * @see https://data-sdk-docs.dataworks.live/ingesting-data\n */\n async ingest(\n metrics: Metric[],\n eventId: string,\n datasetDatasourceId: string,\n ): Promise<void> {\n this.requireAuth();\n\n const valid = filterValidMetrics(metrics, (msg) =>\n console.warn(`[@dataworks-technology/data] ${msg}`),\n );\n\n if (valid.length === 0) return;\n\n const payload = {\n eventId,\n datasetDatasourceId,\n metrics: valid,\n };\n\n const resp = await this.fetchWithAutoRefresh((accessToken) =>\n fetch(this.config.ingestUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${accessToken}`,\n },\n body: JSON.stringify(payload),\n }),\n );\n\n if (!resp.ok) {\n const body = await resp.text().catch(() => \"\");\n throw new Error(\n `[@dataworks-technology/data] ingest failed: ${resp.status} — ${body}`,\n );\n }\n }\n\n /**\n * Report an error to the Dataworks Data Engine.\n *\n * @param error - Error report details\n * @throws Error if not authenticated or if the request fails\n * @see https://data-sdk-docs.dataworks.live/error-reporting\n */\n async reportError(error: ErrorReport): Promise<void> {\n this.requireAuth();\n\n const resp = await this.fetchWithAutoRefresh((accessToken) =>\n fetch(this.config.errorUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${accessToken}`,\n },\n body: JSON.stringify({\n client_id: String(error.clientId),\n dataset_datasource_id: error.datasetDatasourceId,\n athlete_id: error.athleteId,\n error_title: error.errorTitle,\n error_description: error.errorDescription,\n }),\n }),\n );\n\n if (!resp.ok) {\n const body = await resp.text().catch(() => \"\");\n throw new Error(\n `[@dataworks-technology/data] reportError failed: ${resp.status} — ${body}`,\n );\n }\n }\n\n /**\n * Subscribe to a real-time data channel on the AppSync Events API.\n * Uses the Cognito JWT for authentication.\n *\n * Channel format: dataworks/{datasetDatasourceId}/{eventId}/{metric}\n * Use \"*\" for metric to receive all metrics for a dataset-datasource/event pair.\n * Examples: \"dataworks/1/1/heartrate\", \"dataworks/1/1/*\".\n *\n * @param channel - Channel path to subscribe to\n * @param onEvent - Callback invoked for each received event\n * @returns Subscription handle with a close() method\n * @throws Error if not authenticated\n * @see https://data-sdk-docs.dataworks.live/real-time-subscriptions\n */\n subscribe(\n channel: string,\n onEvent: SubscriptionHandler,\n onError?: ErrorHandler,\n ): Subscription {\n this.requireAuth();\n\n if (typeof WebSocket === \"undefined\") {\n throw new Error(\n \"[@dataworks-technology/data] WebSocket is not available. \" +\n \"Use Node >= 22, Bun, or a browser environment. \" +\n \"For older Node versions, install a WebSocket polyfill (e.g. ws) and assign it to globalThis.WebSocket.\",\n );\n }\n if (typeof crypto?.randomUUID !== \"function\") {\n throw new Error(\n \"[@dataworks-technology/data] crypto.randomUUID() is not available. \" +\n \"Use Node >= 19, Bun, or a browser with Crypto API support.\",\n );\n }\n\n const channelPath = channel.startsWith(\"/\") ? channel : `/${channel}`;\n\n return createAutoRefreshingSubscription({\n refreshAuth: async () => {\n await this.refreshSession();\n },\n onReconnectError: (error) => {\n onError?.(error);\n },\n connect: ({ onUnexpectedClose }) => {\n const url = new URL(this.config.realtimeUrl);\n // AppSync Events API uses the /event/realtime path for WebSocket connections\n url.pathname = \"/event/realtime\";\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n\n const host = new URL(this.config.realtimeUrl).host;\n // AWS AppSync Events API requires base64URL-encoded auth in the subprotocol.\n const authPayload = JSON.stringify({\n Authorization: this.credentials!.accessToken,\n host,\n });\n const authHeader = toBase64Url(authPayload);\n\n const ws = new WebSocket(url.toString(), [\n \"aws-appsync-event-ws\",\n `header-${authHeader}`,\n ]);\n\n let closedByUser = false;\n let unexpectedCloseHandled = false;\n\n const handleUnexpectedCloseOnce = () => {\n if (unexpectedCloseHandled) return;\n unexpectedCloseHandled = true;\n onUnexpectedClose();\n };\n\n ws.addEventListener(\"open\", () => {\n // Send connection_init — required for AppSync to send connection_ack\n ws.send(JSON.stringify({ type: \"connection_init\" }));\n });\n\n ws.addEventListener(\"error\", () => {\n onError?.(\n new Error(\n \"[@dataworks-technology/data] WebSocket connection error\",\n ),\n );\n });\n\n ws.addEventListener(\"message\", (event) => {\n let msg: Record<string, unknown>;\n try {\n msg = JSON.parse(String(event.data));\n } catch {\n onError?.(\n new Error(\n \"[@dataworks-technology/data] Received malformed message from AppSync\",\n ),\n );\n return;\n }\n\n if (msg.type === \"connection_ack\") {\n ws.send(\n JSON.stringify({\n type: \"subscribe\",\n id: crypto.randomUUID(),\n channel: channelPath,\n authorization: {\n Authorization: this.credentials!.accessToken,\n host,\n },\n }),\n );\n } else if (msg.type === \"data\") {\n const events = Array.isArray(msg.event) ? msg.event : [msg.event];\n for (const raw of events) {\n try {\n onEvent(JSON.parse(String(raw)));\n } catch {\n onError?.(\n new Error(\n \"[@dataworks-technology/data] Received malformed event payload from AppSync\",\n ),\n );\n }\n }\n } else if (\n msg.type === \"error\" ||\n msg.type === \"connection_error\" ||\n msg.type === \"subscribe_error\" ||\n msg.type === \"broadcast_error\" ||\n msg.type === \"unsubscribe_error\"\n ) {\n const msgStr = JSON.stringify(msg);\n if (msgStr.toLowerCase().includes(\"unauthor\") && !unexpectedCloseHandled) {\n onError?.(\n new Error(\n \"[@dataworks-technology/data] Unauthorized — session may have expired, attempting to reconnect\",\n ),\n );\n handleUnexpectedCloseOnce();\n } else {\n const errors = msg.errors as Array<Record<string, unknown>> | undefined;\n const errorMessage =\n msg.message ??\n errors?.[0]?.message ??\n errors?.[0]?.errorType ??\n \"AppSync subscription error\";\n onError?.(\n new Error(`[@dataworks-technology/data] ${errorMessage}`),\n );\n }\n }\n });\n\n ws.addEventListener(\"close\", () => {\n if (!closedByUser) {\n handleUnexpectedCloseOnce();\n }\n });\n\n return {\n close() {\n closedByUser = true;\n ws.close();\n },\n };\n },\n });\n }\n\n /**\n * Check whether a metric is valid before ingesting.\n * Returns null if valid, or a human-readable reason string if invalid.\n */\n static validateMetric = validateMetric;\n\n /**\n * Filter an array of metrics, keeping only valid ones.\n * Logs a warning for each dropped item.\n */\n static filterValidMetrics = filterValidMetrics;\n\n /** Returns true if the client has been authenticated via login(). */\n get isAuthenticated(): boolean {\n return this.credentials !== null;\n }\n\n /** Returns the tenant from the last successful login, or null. */\n get tenant(): string | null {\n return this.credentials?.tenant ?? null;\n }\n\n /** @internal Throws if not authenticated. */\n private requireAuth(): void {\n if (!this.credentials) {\n throw new Error(\n \"[@dataworks-technology/data] Not authenticated — call login() first\",\n );\n }\n }\n\n private async fetchWithAutoRefresh(\n makeRequest: (accessToken: string) => Promise<Response>,\n ): Promise<Response> {\n this.requireAuth();\n\n let response = await makeRequest(this.credentials!.accessToken);\n if (response.status !== 401) {\n return response;\n }\n\n await this.refreshSession();\n response = await makeRequest(this.credentials!.accessToken);\n return response;\n }\n\n private async refreshSession(): Promise<void> {\n this.requireAuth();\n\n if (!this.credentials!.refreshToken) {\n throw new Error(\n \"[@dataworks-technology/data] Session expired and no refresh token is available — call login() again\",\n );\n }\n\n if (!this.refreshInFlight) {\n this.refreshInFlight = (async () => {\n this.credentials = await refreshWithToken(\n this.credentials!.refreshToken,\n {\n cognitoEndpoint: this.config.cognitoEndpoint,\n clientId: this.config.clientId,\n clientSecret: this.config.clientSecret,\n username: this.lastLoginUsername ?? undefined,\n previousTenant: this.credentials!.tenant,\n previousIdToken: this.credentials!.idToken,\n },\n );\n })().finally(() => {\n this.refreshInFlight = null;\n });\n }\n\n await this.refreshInFlight;\n }\n}\n"],"mappings":"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","names":["crypto","validateMetric","filterValidMetrics","filterValidMetrics","validateMetric"]}
1
+ {"version":3,"sources":["../../../../Dataworks-SDK/dist/client/events-auth.js","../../../../Dataworks-SDK/dist/client/subscription-manager.js","../../../../Dataworks-SDK/dist/client/validation.js","../../../../Dataworks-SDK/dist/client/base64url.js","../../../../Dataworks-SDK/dist/client/auth.js","../src/validation.ts","../src/types.ts","../src/data-client.ts"],"sourcesContent":["/**\n * Authentication selection helpers for AppSync Events subscriptions and ingest.\n */\nexport const INGEST_AUTH_ERROR = \"Authentication failed: username, password, and clientId are required for ingest.\";\nexport function getEventsAuthMode(config) {\n if (config.username && config.password && config.clientId) {\n return \"userCredentials\";\n }\n // Treat empty/whitespace apiKey as absent\n const apiKey = config.apiKey?.trim();\n if (apiKey) {\n return \"apiKey\";\n }\n return \"none\";\n}\nexport function getAppSyncEventsSubscriptionHeaders(config) {\n const mode = getEventsAuthMode(config);\n if (mode === \"userCredentials\") {\n if (!config.userToken) {\n throw new Error(\"Authentication failed: missing userToken (Cognito JWT) for username/password/clientId auth.\");\n }\n return { Authorization: config.userToken };\n }\n if (mode === \"apiKey\") {\n // apiKey is guaranteed non-empty by getEventsAuthMode, but trim for safety\n const apiKey = config.apiKey?.trim();\n if (!apiKey) {\n throw new Error(INGEST_AUTH_ERROR);\n }\n return { \"x-api-key\": apiKey };\n }\n throw new Error(INGEST_AUTH_ERROR);\n}\nexport function assertIngestAuth(config) {\n if (getEventsAuthMode(config) !== \"userCredentials\") {\n throw new Error(INGEST_AUTH_ERROR);\n }\n}\n/**\n * Resolve which auth mode an AppSync Events subscription should use given the\n * tokens/keys currently available. User token always wins over apiKey.\n *\n * @throws Error (INGEST_AUTH_ERROR) when neither a user token nor an apiKey is available.\n */\nexport function resolveEventsAuth(input) {\n // Treat empty/whitespace userToken as absent\n const userToken = input.userToken?.trim();\n if (userToken) {\n return { mode: \"userCredentials\", token: userToken };\n }\n // Treat empty/whitespace apiKey as absent\n const apiKey = input.apiKey?.trim();\n if (apiKey) {\n return { mode: \"apiKey\", apiKey };\n }\n throw new Error(INGEST_AUTH_ERROR);\n}\n/**\n * Build the AppSync Events websocket auth header payload (the object that gets\n * JSON-stringified + base64URL-encoded into the `header-…` subprotocol entry,\n * and re-sent as the `authorization` field of `subscribe` messages).\n */\nexport function buildEventsAuthHeader(resolved, host) {\n if (resolved.mode === \"userCredentials\") {\n return { Authorization: resolved.token, host };\n }\n return { \"x-api-key\": resolved.apiKey, host };\n}\n","/**\n * Generic subscription lifecycle manager with auth-refresh reconnect support.\n *\n * The protocol-specific client provides a `connect` function and calls\n * `onUnexpectedClose` when auth expiry or unexpected socket termination occurs.\n */\nexport function createAutoRefreshingSubscription(options) {\n const maxReconnectAttempts = options.maxReconnectAttempts ?? 1;\n let reconnectAttempts = 0;\n let reconnectInFlight = false;\n let closedByUser = false;\n let activeConnection = null;\n const hooks = {\n onUnexpectedClose: () => {\n if (closedByUser || reconnectInFlight) {\n return;\n }\n if (reconnectAttempts >= maxReconnectAttempts) {\n return;\n }\n reconnectInFlight = true;\n reconnectAttempts += 1;\n void (async () => {\n try {\n await options.refreshAuth();\n if (closedByUser) {\n return;\n }\n activeConnection = options.connect(hooks);\n }\n catch {\n options.onReconnectError?.(new Error(\"Subscription reconnect failed. Please retry or reauthenticate.\"));\n }\n finally {\n reconnectInFlight = false;\n }\n })();\n },\n };\n activeConnection = options.connect(hooks);\n return {\n close() {\n closedByUser = true;\n activeConnection?.close();\n activeConnection = null;\n },\n };\n}\n","/**\n * Metric validation for Dataworks payloads.\n *\n * Ported from Dataworks-Data packages/adaptors/shared-ts/src/validation.ts\n * to serve as the single source of truth for all Dataworks engine SDKs.\n *\n * @module @dataworks/sdk/client\n */\n/**\n * Returns null if the metric is valid, or a human-readable reason string if invalid.\n */\nexport function validateMetric(m) {\n if (typeof m.metric !== \"string\" || m.metric.trim() === \"\")\n return \"metric must be a non-empty string\";\n if (typeof m.athleteId !== \"string\" || m.athleteId.trim() === \"\")\n return \"athleteId must be a non-empty string\";\n if (!Number.isInteger(m.timestamp) || m.timestamp <= 0)\n return `timestamp must be a positive integer, got ${m.timestamp}`;\n if (m.value === null || m.value === undefined)\n return \"value is null/undefined\";\n if (typeof m.value === \"number\" && !isFinite(m.value))\n return `value is non-finite number (${m.value})`;\n if (typeof m.value !== \"number\" && typeof m.value !== \"string\")\n return `value type ${typeof m.value} not allowed (must be number or string)`;\n if (typeof m.value === \"string\" && m.value === \"\")\n return \"value is empty string\";\n return null;\n}\n/**\n * Filters an array of metrics, returning only valid ones.\n * Logs a warning for each dropped item via the provided warn function.\n */\nexport function filterValidMetrics(metrics, warn) {\n const { valid, dropped } = getMetricValidationResult(metrics);\n for (const item of dropped) {\n let valueStr;\n try {\n valueStr = JSON.stringify(item.metric.value);\n }\n catch {\n valueStr = String(item.metric.value);\n }\n warn(`Dropping invalid metric [${String(item.metric.metric)}=${valueStr}]: ${item.reason}`);\n }\n return valid;\n}\n/**\n * Validates metrics and returns both accepted and dropped items (with reasons).\n * This is useful for consumers that need structured diagnostics.\n */\nexport function getMetricValidationResult(metrics) {\n const valid = [];\n const dropped = [];\n for (const [index, m] of metrics.entries()) {\n const reason = validateMetric(m);\n if (reason) {\n dropped.push({\n index,\n reason,\n metric: m,\n });\n }\n else {\n valid.push(m);\n }\n }\n return { valid, dropped };\n}\n","/**\n * Runtime-agnostic base64url encode/decode.\n *\n * Prefers Node.js Buffer when available, falls back to btoa/atob for\n * browser and edge runtimes. Used for AppSync auth subprotocol headers\n * and JWT payload parsing.\n *\n * @module @dataworks/sdk/client\n */\n/** Encode a UTF-8 string to base64url. */\nexport const toBase64Url = (input) => {\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(input).toString(\"base64url\");\n }\n return btoa(input).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n};\n/** Decode a base64url string to UTF-8. */\nexport const fromBase64Url = (b64url) => {\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(b64url, \"base64url\").toString(\"utf-8\");\n }\n const padded = b64url.replace(/-/g, \"+\").replace(/_/g, \"/\") +\n \"=\".repeat(((-b64url.length % 4) + 4) % 4);\n return atob(padded);\n};\n","/**\n * Cognito authentication helpers.\n *\n * Provides M2M (client_credentials) and user (USER_PASSWORD_AUTH) flows.\n * Refactored from @dataworks/sdk e2e-utils.ts — uses native fetch instead\n * of axios so the client sub-path has zero heavy dependencies.\n *\n * @module @dataworks/sdk/client\n */\nimport { fromBase64Url } from \"./base64url.js\";\n/**\n * Fetch an M2M access token using the client_credentials grant.\n *\n * @param clientId - Cognito app client ID\n * @param clientSecret - Cognito app client secret\n * @param tokenUrl - Cognito token endpoint (e.g. https://<domain>.auth.<region>.amazoncognito.com/oauth2/token)\n * @returns Access token string\n */\nexport async function getClientToken(clientId, clientSecret, tokenUrl) {\n const resp = await fetch(tokenUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"client_credentials\",\n client_id: clientId,\n client_secret: clientSecret,\n }),\n });\n if (!resp.ok) {\n throw new Error(`getClientToken: Cognito token request failed: ${resp.status} ${resp.statusText}`);\n }\n const data = (await resp.json());\n if (!data.access_token) {\n throw new Error(\"getClientToken: response missing access_token\");\n }\n return data.access_token;\n}\n/**\n * Authenticate a user via Cognito USER_PASSWORD_AUTH flow.\n * Returns access token, ID token, refresh token, and tenant (extracted from JWT).\n *\n * Used by external developer clients — no client secret required when the\n * Cognito app client is configured without one.\n *\n * @param username - Cognito username\n * @param password - Cognito password\n * @param config - Cognito endpoint and client configuration\n * @returns Login result with tokens and tenant\n */\nexport async function loginWithCredentials(username, password, config) {\n const authParams = {\n USERNAME: username,\n PASSWORD: password,\n };\n // If a client secret is configured, compute SECRET_HASH\n if (config.clientSecret) {\n authParams.SECRET_HASH = await computeSecretHashAsync(username, config.clientId, config.clientSecret);\n }\n const resp = await fetch(config.cognitoEndpoint, {\n method: \"POST\",\n headers: {\n \"X-Amz-Target\": \"AWSCognitoIdentityProviderService.InitiateAuth\",\n \"Content-Type\": \"application/x-amz-json-1.1\",\n },\n body: JSON.stringify({\n AuthFlow: \"USER_PASSWORD_AUTH\",\n ClientId: config.clientId,\n AuthParameters: authParams,\n }),\n });\n if (!resp.ok) {\n const body = await resp.text().catch(() => \"\");\n throw new Error(`loginWithCredentials: Cognito auth failed: ${resp.status} — ${body}`);\n }\n const data = (await resp.json());\n const result = data.AuthenticationResult;\n if (!result?.AccessToken || !result?.IdToken) {\n throw new Error(\"loginWithCredentials: response missing AccessToken or IdToken\");\n }\n // Extract tenant from JWT claims (ID token payload)\n const tenant = extractTenantFromJwt(result.IdToken);\n return {\n accessToken: result.AccessToken,\n idToken: result.IdToken,\n refreshToken: result.RefreshToken ?? \"\",\n tenant,\n };\n}\n/**\n * Refreshes Cognito user tokens using REFRESH_TOKEN_AUTH.\n *\n * Returns updated tokens and tenant, preserving previous tenant/idToken when\n * Cognito omits fields in the refresh response.\n *\n * @param refreshToken - The refresh token string\n * @param config - Cognito endpoint and client configuration, plus optional username/previousTenant/previousIdToken\n */\nexport async function refreshWithToken(refreshToken, config) {\n const authParams = {\n REFRESH_TOKEN: refreshToken,\n };\n if (config.clientSecret) {\n if (!config.username) {\n throw new Error(\"refreshWithToken: username is required when clientSecret is configured\");\n }\n authParams.SECRET_HASH = await computeSecretHashAsync(config.username, config.clientId, config.clientSecret);\n }\n const resp = await fetch(config.cognitoEndpoint, {\n method: \"POST\",\n headers: {\n \"X-Amz-Target\": \"AWSCognitoIdentityProviderService.InitiateAuth\",\n \"Content-Type\": \"application/x-amz-json-1.1\",\n },\n body: JSON.stringify({\n AuthFlow: \"REFRESH_TOKEN_AUTH\",\n ClientId: config.clientId,\n AuthParameters: authParams,\n }),\n });\n if (!resp.ok) {\n const body = await resp.text().catch(() => \"\");\n throw new Error(`refreshWithToken: Cognito refresh failed: ${resp.status} — ${body}`);\n }\n const data = (await resp.json());\n const result = data.AuthenticationResult;\n if (!result?.AccessToken) {\n throw new Error(\"refreshWithToken: response missing AccessToken\");\n }\n const idToken = result.IdToken ?? config.previousIdToken ?? \"\";\n const extractedTenant = idToken ? extractTenantFromJwt(idToken) : \"\";\n const tenant = extractedTenant || config.previousTenant || \"\";\n return {\n accessToken: result.AccessToken,\n idToken,\n refreshToken: result.RefreshToken ?? refreshToken,\n tenant,\n };\n}\n/**\n * Extracts the tenant claim from a JWT ID token without verifying the signature.\n * The token is validated server-side by AppSync/API Gateway — this is a client-side\n * convenience for reading the tenant value.\n */\nfunction extractTenantFromJwt(idToken) {\n try {\n const payload = idToken.split(\".\")[1];\n const decoded = JSON.parse(fromBase64Url(payload));\n // Cognito custom attributes use \"custom:tenant\" claim\n return (decoded[\"custom:tenants\"] ??\n decoded[\"custom:tenant\"] ??\n decoded.tenant ??\n decoded[\"cognito:groups\"]?.[0] ??\n \"\");\n }\n catch {\n return \"\";\n }\n}\nasync function computeSecretHashAsync(username, clientId, clientSecret) {\n // Dynamic import to keep this module usable in environments without crypto.\n const crypto = await import(\"crypto\");\n return crypto\n .createHmac(\"sha256\", clientSecret)\n .update(username + clientId)\n .digest(\"base64\");\n}\n","/**\n * Validation re-exports — thin wrappers around @dataworks/sdk/client validation.\n * Declared locally so the published .d.ts is fully self-contained.\n *\n * @module @dataworks-technology/data\n */\n\nimport {\n validateMetric as _validateMetric,\n filterValidMetrics as _filterValidMetrics,\n getMetricValidationResult as _getMetricValidationResult,\n} from \"@dataworks/sdk/client\";\nimport type { RawMetricItem, MetricItem } from \"./types.js\";\n\n/**\n * Check whether a metric is valid.\n * Returns null if valid, or a human-readable reason string if invalid.\n */\nexport const validateMetric: (m: RawMetricItem) => string | null =\n _validateMetric;\n\n/**\n * Filter an array of metrics, keeping only valid ones.\n * Logs a warning for each dropped item via the provided warn function.\n */\nexport const filterValidMetrics: (\n metrics: RawMetricItem[],\n warn: (msg: string) => void,\n) => MetricItem[] = _filterValidMetrics;\n\n/**\n * Validate metrics and return both valid metrics and dropped-metric diagnostics.\n */\nexport const getMetricValidationResult: (metrics: RawMetricItem[]) => {\n valid: MetricItem[];\n dropped: Array<{ index: number; reason: string; metric: RawMetricItem }>;\n} = _getMetricValidationResult;\n","/**\n * Public types for @dataworks-technology/data.\n *\n * @module @dataworks-technology/data\n */\n\n/** Configuration for connecting to the Dataworks Data Engine. */\nexport interface DataClientConfig {\n /** Cognito User Pool endpoint (e.g. \"https://cognito-idp.eu-west-1.amazonaws.com/\") */\n cognitoEndpoint: string;\n /** Cognito app client ID */\n clientId: string;\n /** Optional Cognito app client secret (if configured on the app client) */\n clientSecret?: string;\n /** Optional AppSync Events API key used for subscription-only auth */\n apiKey?: string;\n /** Data Engine ingest URL (API Gateway endpoint) */\n ingestUrl: string;\n /** Data Engine error reporting URL (API Gateway endpoint) */\n errorUrl: string;\n /** AppSync Events API endpoint for real-time subscriptions */\n realtimeUrl: string;\n}\n\n/** Credentials returned from a successful login. */\nexport interface LoginResult {\n accessToken: string;\n idToken: string;\n refreshToken: string;\n /** Tenant extracted from the JWT claims. */\n tenant: string;\n}\n\n/** A single metric data point to ingest. */\nexport interface Metric {\n /** Unique athlete identifier */\n athleteId: string;\n /** Metric name (e.g. \"heartrate\", \"speed\", \"power\") */\n metric: string;\n /** Metric value — number or string */\n value: number | string;\n /** Unix timestamp in seconds */\n timestamp: number;\n}\n\n/** Validated metric item (all fields confirmed present and correct). */\nexport interface MetricItem {\n athleteId: string;\n metric: string;\n value: unknown;\n timestamp: number;\n}\n\n/** Loosely-typed input for validation — all fields are unknown. */\nexport interface RawMetricItem {\n metric: unknown;\n athleteId: unknown;\n value: unknown;\n timestamp: unknown;\n}\n\n/** Payload shape for metric ingestion. */\nexport interface MetricsPayload {\n eventId: string;\n datasetDatasourceId: string;\n metrics: MetricItem[];\n}\n\n/** Error report to send to the Data Engine. */\nexport interface ErrorReport {\n /** Dataset-datasource identifier */\n datasetDatasourceId: string;\n /** Athlete identifier */\n athleteId: string;\n /** Client identifier */\n clientId: number;\n /** Short error title */\n errorTitle: string;\n /** Detailed error description */\n errorDescription: string;\n}\n\n/**\n * A single metric in a subscription event.\n */\nexport interface SubscriptionMetric {\n /** Metric name (e.g. \"current\", \"heartrate\", \"speed\") */\n metric: string;\n /** Metric value */\n value: number | string;\n}\n\n/**\n * Event received from a real-time subscription.\n *\n * This is the standard Data Engine event shape. If your channel publishes\n * a different format, use the generic type parameter on subscribe().\n *\n * @example\n * ```ts\n * dataworks.subscribe(\"dataworks/*\", (event: SubscriptionEvent) => {\n * console.log(event.athleteId, event.metrics);\n * });\n * ```\n */\nexport interface SubscriptionEvent {\n /** Event name (e.g. \"heartrateingested\", \"speedingested\") */\n name: string;\n /** Unix timestamp in milliseconds */\n timestamp: number;\n /** Unix timestamp in seconds */\n timestampSec: number;\n /** Event identifier */\n eventId: string;\n /** Dataset-datasource identifier */\n datasetDatasourceId: string;\n /** Athlete identifier */\n athleteId: string;\n /** Array of metrics in this event */\n metrics: SubscriptionMetric[];\n}\n\n/**\n * Error passed to subscription error callback.\n *\n * Wraps standard Error with additional context about the failure.\n */\nexport interface SubscriptionError {\n /** Error message */\n message: string;\n /** Error category for programmatic handling */\n code:\n | \"CONNECTION_ERROR\"\n | \"UNAUTHORIZED\"\n | \"SUBSCRIPTION_ERROR\"\n | \"PARSE_ERROR\"\n | \"RECONNECT_FAILED\"\n | \"UNKNOWN\";\n /** Original Error object */\n cause?: Error;\n}\n\n/** Callback for subscription events. */\nexport type SubscriptionHandler<T = SubscriptionEvent> = (event: T) => void;\n\n/** Callback for subscription errors (WebSocket errors, AppSync errors, reconnect failures). */\nexport type ErrorHandler = (error: SubscriptionError) => void;\n\n/** Active subscription that can be closed. */\nexport interface Subscription {\n /** Close the subscription and disconnect. */\n close(): void;\n}\n\n/** Validation mode for metric ingestion. */\nexport type ValidationMode = \"best-effort\" | \"strict\";\n\n/** Details for one dropped metric during validation. */\nexport interface DroppedMetric {\n /** Index of the dropped metric in the original input array. */\n index: number;\n /** Human-readable validation failure reason. */\n reason: string;\n /** Original metric payload that failed validation. */\n metric: RawMetricItem;\n}\n\n/** Optional behavior controls for ingestWithResult(). */\nexport interface IngestOptions {\n /**\n * Validation behavior:\n * - best-effort (default): drop invalid metrics and continue.\n * - strict: throw MetricValidationError if any invalid metrics are found.\n */\n validationMode?: ValidationMode;\n /**\n * When true, throw if there are zero valid metrics after validation.\n * Default: false.\n */\n requireAtLeastOneValidMetric?: boolean;\n /**\n * Optional callback for each dropped metric.\n */\n onDroppedMetric?: (dropped: DroppedMetric) => void;\n}\n\n/** Structured result returned by ingestWithResult(). */\nexport interface IngestResult {\n acceptedCount: number;\n droppedCount: number;\n dropped: DroppedMetric[];\n requestSent: boolean;\n}\n\n/** Thrown by strict validation mode when invalid metrics are present. */\nexport class MetricValidationError extends Error {\n readonly code = \"METRIC_VALIDATION_FAILED\" as const;\n readonly dropped: DroppedMetric[];\n\n constructor(message: string, dropped: DroppedMetric[]) {\n super(message);\n this.name = \"MetricValidationError\";\n this.dropped = dropped;\n }\n}\n","/**\n * DataClient — the public entry point for external developers using the Dataworks Data Engine.\n *\n * Handles authentication, metric ingestion, error reporting, and real-time subscriptions.\n * All SDK foundation code (@dataworks/sdk) is bundled at build time — consumers install\n * only @dataworks-technology/data with zero transitive dependencies.\n *\n * @example\n * ```ts\n * import { DataClient } from \"@dataworks-technology/data\";\n *\n * const dataworks = new DataClient({\n * cognitoEndpoint: \"https://cognito-idp.eu-west-1.amazonaws.com/\",\n * clientId: \"abc123\",\n * ingestUrl: \"https://dev-realtime.dataworks.live\",\n * errorUrl: \"https://dev-realtime-errors.dataworks.live\",\n * realtimeUrl: \"https://dev-event-api.dataworks.live\",\n * });\n *\n * await dataworks.login(\"graeme\", \"password123\");\n * await dataworks.ingest([{ athleteId: \"1\", metric: \"heartrate_calculated\", value: 172, timestamp: Date.now() / 1000 }], \"evt1\", \"ds1\");\n * ```\n *\n * @module @dataworks-technology/data\n */\n\nimport {\n buildEventsAuthHeader,\n createAutoRefreshingSubscription,\n INGEST_AUTH_ERROR,\n loginWithCredentials,\n refreshWithToken,\n resolveEventsAuth,\n toBase64Url,\n} from \"@dataworks/sdk/client\";\nimport {\n getMetricValidationResult,\n validateMetric,\n filterValidMetrics,\n} from \"./validation.js\";\nimport type {\n DataClientConfig,\n LoginResult,\n Metric,\n RawMetricItem,\n ErrorReport,\n SubscriptionHandler,\n ErrorHandler,\n Subscription,\n IngestOptions,\n IngestResult,\n DroppedMetric,\n SubscriptionEvent,\n SubscriptionError,\n} from \"./types.js\";\nimport { MetricValidationError } from \"./types.js\";\n\n/** Helper to create typed SubscriptionError objects */\nfunction createSubscriptionError(\n message: string,\n code: SubscriptionError[\"code\"],\n cause?: Error,\n): SubscriptionError {\n return { message, code, cause };\n}\n\nexport class DataClient {\n private readonly config: DataClientConfig;\n private credentials: LoginResult | null = null;\n private lastLoginUsername: string | null = null;\n private refreshInFlight: Promise<void> | null = null;\n\n constructor(config: DataClientConfig) {\n this.config = config;\n }\n\n /**\n * Authenticate with the Dataworks platform using Cognito USER_PASSWORD_AUTH.\n * Must be called before ingest(), reportError(), or subscribe().\n *\n * @param username - Cognito username\n * @param password - Cognito password\n * @returns Login result containing tokens and tenant\n * @see https://data-sdk-docs.dataworks.live/authentication\n */\n async login(username: string, password: string): Promise<LoginResult> {\n this.lastLoginUsername = username;\n this.credentials = await loginWithCredentials(username, password, {\n cognitoEndpoint: this.config.cognitoEndpoint,\n clientId: this.config.clientId,\n clientSecret: this.config.clientSecret,\n });\n return this.credentials;\n }\n\n /**\n * Ingest metric data points into the Dataworks Data Engine.\n * Validates each metric before sending — invalid metrics are dropped with warnings.\n *\n * @param metrics - Array of metric data points\n * @param eventId - Event identifier\n * @param datasetDatasourceId - Dataset-datasource identifier\n * @throws Error if not authenticated or if the request fails\n * @see https://data-sdk-docs.dataworks.live/ingesting-data\n */\n async ingest(\n metrics: Metric[],\n eventId: string,\n datasetDatasourceId: string,\n ): Promise<void> {\n await this.ingestWithResult(metrics, eventId, datasetDatasourceId);\n }\n\n /**\n * Ingest metrics with structured validation diagnostics.\n *\n * By default (`validationMode: \"best-effort\"`), invalid metrics are dropped and\n * valid metrics continue. In strict mode, invalid metrics cause a\n * MetricValidationError before any network request is sent.\n *\n * @returns IngestResult with accepted/dropped counts and dropped reasons.\n */\n async ingestWithResult(\n metrics: Metric[],\n eventId: string,\n datasetDatasourceId: string,\n options: IngestOptions = {},\n ): Promise<IngestResult> {\n this.requireIngestAuth();\n\n const validationMode = options.validationMode ?? \"best-effort\";\n const validation = getMetricValidationResult(metrics as RawMetricItem[]);\n const valid: Metric[] = validation.valid as Metric[];\n const dropped: DroppedMetric[] = validation.dropped.map((item) => ({\n index: item.index,\n reason: item.reason,\n metric: item.metric,\n }));\n\n dropped.forEach((item) => {\n options.onDroppedMetric?.(item);\n console.warn(\n `[@dataworks-technology/data] Dropping invalid metric [${String(item.metric.metric)}=${String(item.metric.value)}]: ${item.reason}`,\n );\n });\n\n if (validationMode === \"strict\" && dropped.length > 0) {\n throw new MetricValidationError(\n \"[@dataworks-technology/data] validation failed: one or more metrics are invalid\",\n dropped,\n );\n }\n\n if (valid.length === 0) {\n if (options.requireAtLeastOneValidMetric) {\n throw new MetricValidationError(\n \"[@dataworks-technology/data] validation failed: no valid metrics to ingest\",\n dropped,\n );\n }\n\n return {\n acceptedCount: 0,\n droppedCount: dropped.length,\n dropped,\n requestSent: false,\n };\n }\n\n const payload = {\n eventId,\n datasetDatasourceId,\n metrics: valid,\n };\n\n const resp = await this.fetchWithAutoRefresh((accessToken) =>\n fetch(this.config.ingestUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${accessToken}`,\n },\n body: JSON.stringify(payload),\n }),\n );\n\n if (!resp.ok) {\n const body = await resp.text().catch(() => \"\");\n throw new Error(\n `[@dataworks-technology/data] ingest failed: ${resp.status} — ${body}`,\n );\n }\n\n return {\n acceptedCount: valid.length,\n droppedCount: dropped.length,\n dropped,\n requestSent: true,\n };\n }\n\n /**\n * Report an error to the Dataworks Data Engine.\n *\n * @param error - Error report details\n * @throws Error if not authenticated or if the request fails\n * @see https://data-sdk-docs.dataworks.live/error-reporting\n */\n async reportError(error: ErrorReport): Promise<void> {\n this.requireIngestAuth();\n\n const resp = await this.fetchWithAutoRefresh((accessToken) =>\n fetch(this.config.errorUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${accessToken}`,\n },\n body: JSON.stringify({\n client_id: String(error.clientId),\n dataset_datasource_id: error.datasetDatasourceId,\n athlete_id: error.athleteId,\n error_title: error.errorTitle,\n error_description: error.errorDescription,\n }),\n }),\n );\n\n if (!resp.ok) {\n const body = await resp.text().catch(() => \"\");\n throw new Error(\n `[@dataworks-technology/data] reportError failed: ${resp.status} — ${body}`,\n );\n }\n }\n\n /**\n * Subscribe to a real-time data channel on the AppSync Events API.\n * Uses the Cognito JWT for authentication.\n *\n * Channel format: dataworks/{datasetDatasourceId}/{eventId}/{metric}\n * Use \"*\" for metric to receive all metrics for a dataset-datasource/event pair.\n * Examples: \"dataworks/1/1/heartrate\", \"dataworks/1/1/*\".\n *\n * @param channel - Channel path to subscribe to\n * @param onEvent - Callback invoked for each received event\n * @returns Subscription handle with a close() method\n * @throws Error if not authenticated\n * @see https://data-sdk-docs.dataworks.live/real-time-subscriptions\n */\n subscribe<T = SubscriptionEvent>(\n channel: string,\n onEvent: SubscriptionHandler<T>,\n onError?: ErrorHandler,\n ): Subscription {\n // Gate: subscriptions need either a completed login or a configured apiKey.\n // Treat empty/whitespace apiKey as absent.\n const apiKey = this.config.apiKey?.trim();\n if (!this.credentials && !apiKey) {\n this.requireAuth();\n }\n\n if (typeof WebSocket === \"undefined\") {\n throw new Error(\n \"[@dataworks-technology/data] WebSocket is not available. \" +\n \"Use Node >= 22, Bun, or a browser environment. \" +\n \"For older Node versions, install a WebSocket polyfill (e.g. ws) and assign it to globalThis.WebSocket.\",\n );\n }\n if (typeof crypto?.randomUUID !== \"function\") {\n throw new Error(\n \"[@dataworks-technology/data] crypto.randomUUID() is not available. \" +\n \"Use Node >= 19, Bun, or a browser with Crypto API support.\",\n );\n }\n\n const channelPath = channel.startsWith(\"/\") ? channel : `/${channel}`;\n\n return createAutoRefreshingSubscription({\n refreshAuth: async () => {\n if (this.credentials) {\n await this.refreshSession();\n }\n },\n onReconnectError: (error) => {\n onError?.(\n createSubscriptionError(error.message, \"RECONNECT_FAILED\", error),\n );\n },\n connect: ({ onUnexpectedClose }) => {\n const url = new URL(this.config.realtimeUrl);\n // AppSync Events API uses the /event/realtime path for WebSocket connections\n url.pathname = \"/event/realtime\";\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n // Raw AppSync URLs use a separate realtime host for WebSocket connections.\n // Custom domains share the same host for both HTTP and WS.\n if (url.host.includes(\".appsync-api.\")) {\n url.host = url.host.replace(\n \".appsync-api.\",\n \".appsync-realtime-api.\",\n );\n }\n\n const host = new URL(this.config.realtimeUrl).host;\n // Resolve auth mode using shared SDK precedence rules (user token > apiKey)\n // and build the AppSync Events header payload from the resolved mode.\n // Trim apiKey to handle empty/whitespace values.\n const resolvedAuth = resolveEventsAuth({\n userToken: this.credentials?.accessToken,\n apiKey: this.config.apiKey?.trim(),\n });\n const realtimeAuthHeader = buildEventsAuthHeader(resolvedAuth, host);\n // AWS AppSync Events API requires base64URL-encoded auth in the subprotocol.\n const authPayload = JSON.stringify(realtimeAuthHeader);\n const authHeader = toBase64Url(authPayload);\n\n const ws = new WebSocket(url.toString(), [\n \"aws-appsync-event-ws\",\n `header-${authHeader}`,\n ]);\n\n let closedByUser = false;\n let unexpectedCloseHandled = false;\n\n const handleUnexpectedCloseOnce = () => {\n if (unexpectedCloseHandled) return;\n unexpectedCloseHandled = true;\n onUnexpectedClose();\n };\n\n ws.addEventListener(\"open\", () => {\n // Send connection_init — required for AppSync to send connection_ack\n ws.send(JSON.stringify({ type: \"connection_init\" }));\n });\n\n ws.addEventListener(\"error\", () => {\n onError?.(\n createSubscriptionError(\n \"[@dataworks-technology/data] WebSocket connection error\",\n \"CONNECTION_ERROR\",\n ),\n );\n });\n\n ws.addEventListener(\"message\", (event) => {\n let msg: Record<string, unknown>;\n try {\n msg = JSON.parse(String(event.data));\n } catch {\n onError?.(\n createSubscriptionError(\n \"[@dataworks-technology/data] Received malformed message from AppSync\",\n \"PARSE_ERROR\",\n ),\n );\n return;\n }\n\n if (msg.type === \"connection_ack\") {\n ws.send(\n JSON.stringify({\n type: \"subscribe\",\n id: crypto.randomUUID(),\n channel: channelPath,\n authorization: realtimeAuthHeader,\n }),\n );\n } else if (msg.type === \"data\") {\n const events = Array.isArray(msg.event) ? msg.event : [msg.event];\n for (const raw of events) {\n try {\n onEvent(JSON.parse(String(raw)) as T);\n } catch {\n onError?.(\n createSubscriptionError(\n \"[@dataworks-technology/data] Received malformed event payload from AppSync\",\n \"PARSE_ERROR\",\n ),\n );\n }\n }\n } else if (\n msg.type === \"error\" ||\n msg.type === \"connection_error\" ||\n msg.type === \"subscribe_error\" ||\n msg.type === \"broadcast_error\" ||\n msg.type === \"unsubscribe_error\"\n ) {\n const msgStr = JSON.stringify(msg);\n if (\n msgStr.toLowerCase().includes(\"unauthor\") &&\n !unexpectedCloseHandled\n ) {\n onError?.(\n createSubscriptionError(\n \"[@dataworks-technology/data] Unauthorized — session may have expired, attempting to reconnect\",\n \"UNAUTHORIZED\",\n ),\n );\n handleUnexpectedCloseOnce();\n } else {\n const errors = msg.errors as\n | Array<Record<string, unknown>>\n | undefined;\n const errorMessage =\n msg.message ??\n errors?.[0]?.message ??\n errors?.[0]?.errorType ??\n \"AppSync subscription error\";\n onError?.(\n createSubscriptionError(\n `[@dataworks-technology/data] ${errorMessage}`,\n \"SUBSCRIPTION_ERROR\",\n ),\n );\n }\n }\n });\n\n ws.addEventListener(\"close\", () => {\n if (!closedByUser) {\n handleUnexpectedCloseOnce();\n }\n });\n\n return {\n close() {\n closedByUser = true;\n ws.close();\n },\n };\n },\n });\n }\n\n /**\n * Check whether a metric is valid before ingesting.\n * Returns null if valid, or a human-readable reason string if invalid.\n */\n static validateMetric = validateMetric;\n\n /**\n * Filter an array of metrics, keeping only valid ones.\n * Logs a warning for each dropped item.\n */\n static filterValidMetrics = filterValidMetrics;\n\n /** Returns true if the client has been authenticated via login(). */\n get isAuthenticated(): boolean {\n return this.credentials !== null;\n }\n\n /** Returns the tenant from the last successful login, or null. */\n get tenant(): string | null {\n return this.credentials?.tenant ?? null;\n }\n\n /** @internal Throws if not authenticated. */\n private requireAuth(): void {\n if (!this.credentials) {\n throw new Error(\n \"[@dataworks-technology/data] Not authenticated — call login() first\",\n );\n }\n }\n\n /**\n * @internal Guard for ingest-only operations (ingest, reportError). Throws the\n * canonical SDK ingest auth error so the message is consistent with the lower-level\n * `@dataworks/sdk/client` HttpClient guard.\n */\n private requireIngestAuth(): void {\n if (!this.credentials) {\n throw new Error(`[@dataworks-technology/data] ${INGEST_AUTH_ERROR}`);\n }\n }\n\n private async fetchWithAutoRefresh(\n makeRequest: (accessToken: string) => Promise<Response>,\n ): Promise<Response> {\n this.requireAuth();\n\n let response = await makeRequest(this.credentials!.accessToken);\n if (response.status !== 401) {\n return response;\n }\n\n await this.refreshSession();\n response = await makeRequest(this.credentials!.accessToken);\n return response;\n }\n\n private async refreshSession(): Promise<void> {\n this.requireAuth();\n\n if (!this.credentials!.refreshToken) {\n throw new Error(\n \"[@dataworks-technology/data] Session expired and no refresh token is available — call login() again\",\n );\n }\n\n if (!this.refreshInFlight) {\n this.refreshInFlight = (async () => {\n this.credentials = await refreshWithToken(\n this.credentials!.refreshToken,\n {\n cognitoEndpoint: this.config.cognitoEndpoint,\n clientId: this.config.clientId,\n clientSecret: this.config.clientSecret,\n username: this.lastLoginUsername ?? undefined,\n previousTenant: this.credentials!.tenant,\n previousIdToken: this.credentials!.idToken,\n },\n );\n })().finally(() => {\n this.refreshInFlight = null;\n });\n }\n\n await this.refreshInFlight;\n }\n}\n"],"mappings":"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","names":["crypto","validateMetric","filterValidMetrics","getMetricValidationResult","getMetricValidationResult","validateMetric","filterValidMetrics"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dataworks-technology/data",
3
- "version": "0.1.9",
3
+ "version": "0.2.2",
4
4
  "description": "Dataworks Data Engine SDK — authenticate, ingest metrics, and subscribe to live data",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "homepage": "https://data-sdk-docs.dataworks.live",
47
47
  "devDependencies": {
48
- "@dataworks/sdk": "1.7.1",
48
+ "@dataworks/sdk": "1.8.0",
49
49
  "tsup": "^8.4.0",
50
50
  "typescript": "^5.8.3",
51
51
  "vitest": "^3.2.3"