@dataworks-technology/data 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +225 -2
- package/dist/index.cjs +27 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +27 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -140,7 +140,7 @@ Report an error to the Data Engine for monitoring and alerting.
|
|
|
140
140
|
```typescript
|
|
141
141
|
await client.reportError({
|
|
142
142
|
datasetDatasourceId: "ds-456",
|
|
143
|
-
|
|
143
|
+
athleteId: "athlete-123",
|
|
144
144
|
clientId: 1,
|
|
145
145
|
errorTitle: "Sensor disconnected",
|
|
146
146
|
errorDescription: "BLE heart rate sensor lost connection at 00:42:15",
|
|
@@ -237,7 +237,7 @@ interface Metric {
|
|
|
237
237
|
|
|
238
238
|
interface ErrorReport {
|
|
239
239
|
datasetDatasourceId: string;
|
|
240
|
-
|
|
240
|
+
athleteId: string;
|
|
241
241
|
clientId: number;
|
|
242
242
|
errorTitle: string;
|
|
243
243
|
errorDescription: string;
|
|
@@ -278,12 +278,235 @@ Calling `ingest()`, `reportError()`, or `subscribe()` before `login()` throws im
|
|
|
278
278
|
- **TypeScript** ≥ 5.0 (optional — works with plain JavaScript too)
|
|
279
279
|
- **Browser** — compatible with any environment that has `fetch` and `WebSocket`
|
|
280
280
|
|
|
281
|
+
## Derived Metrics — Round-Trip Example
|
|
282
|
+
|
|
283
|
+
A core use case: subscribe to live metrics, apply your own calculations externally, and push **derived metrics** back into the Data Engine. This creates a feedback loop where the platform stores both raw and enriched data.
|
|
284
|
+
|
|
285
|
+
### Concept
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
Data Engine → subscribe("heartrate") → Your App → calculate rolling avg → ingest("heartrate_rolling_avg") → Data Engine
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Any metric you ingest is treated the same as raw sensor data — it flows through the enrichment pipeline (avg/min/max/zones), gets stored, and is available via subscriptions and the GraphQL API.
|
|
292
|
+
|
|
293
|
+
### TypeScript Example
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
import { DataClient } from "@dataworks-technology/data";
|
|
297
|
+
|
|
298
|
+
const client = new DataClient({
|
|
299
|
+
cognitoEndpoint: "https://cognito-idp.eu-west-1.amazonaws.com/",
|
|
300
|
+
clientId: "your-client-id",
|
|
301
|
+
ingestUrl: "https://your-ingest-endpoint.dataworks.live",
|
|
302
|
+
errorUrl: "https://your-error-endpoint.dataworks.live",
|
|
303
|
+
realtimeUrl: "https://your-realtime-endpoint.dataworks.live",
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
await client.login("developer", "password");
|
|
307
|
+
|
|
308
|
+
// 1. Subscribe to raw heart rate data
|
|
309
|
+
const buffer: number[] = [];
|
|
310
|
+
|
|
311
|
+
client.subscribe("dataworks/metrics", (event) => {
|
|
312
|
+
if (event.metric !== "heartrate") return;
|
|
313
|
+
|
|
314
|
+
// 2. Calculate a rolling 3-point average
|
|
315
|
+
buffer.push(event.value);
|
|
316
|
+
if (buffer.length > 3) buffer.shift();
|
|
317
|
+
const avg = buffer.reduce((a, b) => a + b, 0) / buffer.length;
|
|
318
|
+
|
|
319
|
+
// 3. Push the derived metric back into the engine
|
|
320
|
+
client.ingest(
|
|
321
|
+
[
|
|
322
|
+
{
|
|
323
|
+
athleteId: event.athleteId,
|
|
324
|
+
metric: "heartrate_rolling_avg",
|
|
325
|
+
value: Math.round(avg * 10) / 10,
|
|
326
|
+
timestamp: event.timestamp,
|
|
327
|
+
},
|
|
328
|
+
],
|
|
329
|
+
event.eventId,
|
|
330
|
+
event.datasetDatasourceId,
|
|
331
|
+
);
|
|
332
|
+
});
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Python Example (Cross-Platform)
|
|
336
|
+
|
|
337
|
+
The same flow works from any language. A full working Python demo is included at `test/demo/round-trip.py` — it proves every SDK capability end-to-end:
|
|
338
|
+
|
|
339
|
+
| Phase | What it does | SDK equivalent |
|
|
340
|
+
|-------|-------------|----------------|
|
|
341
|
+
| 1. Authenticate | Cognito USER_PASSWORD_AUTH | `client.login()` |
|
|
342
|
+
| 2. Subscribe | WebSocket → AppSync Events API | `client.subscribe()` |
|
|
343
|
+
| 3. Receive | Catch events on the subscriber | `onEvent` callback |
|
|
344
|
+
| 4. Calculate | Write to CSV/Google Sheets, compute rolling avg + HR zones | Your business logic |
|
|
345
|
+
| 5. Re-ingest | POST enriched metrics back to the Data Engine | `client.ingest()` |
|
|
346
|
+
| 6. Report Error | Send error through the error pipeline | `client.reportError()` |
|
|
347
|
+
|
|
348
|
+
#### Run the demo
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
# Install Python dependencies
|
|
352
|
+
pip install websocket-client requests
|
|
353
|
+
|
|
354
|
+
# Run from the repo root (reads test.env for credentials)
|
|
355
|
+
python test/demo/round-trip.py
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
#### Output
|
|
359
|
+
|
|
360
|
+
```
|
|
361
|
+
PHASE 4: WRITE DATA + CALCULATE
|
|
362
|
+
Enriched data:
|
|
363
|
+
HR=130 → avg=130.0, zone=Zone 2 (Aerobic), delta=—
|
|
364
|
+
HR=140 → avg=135.0, zone=Zone 3 (Tempo), delta=+10
|
|
365
|
+
HR=150 → avg=140.0, zone=Zone 3 (Tempo), delta=+10
|
|
366
|
+
HR=160 → avg=150.0, zone=Zone 4 (Threshold), delta=+10
|
|
367
|
+
HR=170 → avg=160.0, zone=Zone 4 (Threshold), delta=+10
|
|
368
|
+
|
|
369
|
+
PHASE 5: RE-INGEST ENRICHED METRICS
|
|
370
|
+
→ heartrate_rolling_avg=130.0
|
|
371
|
+
→ heartrate_rolling_avg=135.0
|
|
372
|
+
→ heartrate_rolling_avg=140.0
|
|
373
|
+
→ heartrate_rolling_avg=150.0
|
|
374
|
+
→ heartrate_rolling_avg=160.0
|
|
375
|
+
✓ Ingested (200)
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
The raw `heartrate` values (130–170 bpm) are transformed into `heartrate_rolling_avg` derived metrics and pushed back into the platform. These derived metrics are then available to all consumers (Console, Visual, Moments triggers) just like any other metric.
|
|
379
|
+
|
|
380
|
+
#### Google Sheets (optional)
|
|
381
|
+
|
|
382
|
+
The demo can write to Google Sheets instead of CSV — formulas compute rolling averages, HR zone classification, and deltas in the spreadsheet before re-ingesting. See `test/demo/README.md` for setup.
|
|
383
|
+
|
|
384
|
+
### Ideas for Derived Metrics
|
|
385
|
+
|
|
386
|
+
| Derived metric | Calculation | Use case |
|
|
387
|
+
|---|---|---|
|
|
388
|
+
| `heartrate_rolling_avg` | 3-point moving average | Smooth noisy sensor data |
|
|
389
|
+
| `heartrate_zone` | Zone classification (1–5) | Training load analysis |
|
|
390
|
+
| `speed_efficiency` | Speed / heart rate ratio | Fatigue detection |
|
|
391
|
+
| `power_to_weight` | Power / athlete weight | Normalised performance |
|
|
392
|
+
| `pace_delta` | Current pace − target pace | Real-time coaching alerts |
|
|
393
|
+
| `fatigue_index` | Custom formula over time window | Trigger warnings via Moments engine |
|
|
394
|
+
|
|
281
395
|
## Documentation
|
|
282
396
|
|
|
283
397
|
Full documentation with guides, examples, and detailed API reference:
|
|
284
398
|
|
|
285
399
|
**[https://data-docs.dataworks.live](https://data-docs.dataworks.live)**
|
|
286
400
|
|
|
401
|
+
## Development
|
|
402
|
+
|
|
403
|
+
> Internal contributors only — this section covers building, testing, and publishing the package.
|
|
404
|
+
|
|
405
|
+
This package publishes to **public npm** (`registry.npmjs.org`), not to the internal CodeArtifact registry used by `@dataworks/sdk`. The `packages/sdk/.npmrc` and `publishConfig` in `package.json` enforce this.
|
|
406
|
+
|
|
407
|
+
### Build
|
|
408
|
+
|
|
409
|
+
```bash
|
|
410
|
+
bun run build # tsup → dist/ (ESM + CJS + DTS)
|
|
411
|
+
bun run test # vitest unit tests (mocked — no infra needed)
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Publish
|
|
415
|
+
|
|
416
|
+
Login to npm first (one-time):
|
|
417
|
+
```bash
|
|
418
|
+
npm login --registry https://registry.npmjs.org/
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Then from `packages/sdk/`:
|
|
422
|
+
```bash
|
|
423
|
+
bun run publish:patch # bump patch + publish
|
|
424
|
+
bun run publish:minor # bump minor + publish
|
|
425
|
+
bun run publish:major # bump major + publish
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### E2E Tests
|
|
429
|
+
|
|
430
|
+
The e2e test suite (`test/e2e-public-sdk.test.ts`) validates the full external developer flow against deployed infrastructure. It runs against live AWS services and proves the public SDK works end-to-end across all three stacks (Shared, Data, SDK).
|
|
431
|
+
|
|
432
|
+
#### Prerequisites
|
|
433
|
+
|
|
434
|
+
Deploy all three stacks first — `make deploy` generates `test.env` with the required env vars:
|
|
435
|
+
|
|
436
|
+
| Env var | Source | Purpose |
|
|
437
|
+
|---|---|---|
|
|
438
|
+
| `FRONTEND_CLIENT_ID` | Shared stack → Cognito | App client for USER_PASSWORD_AUTH |
|
|
439
|
+
| `API_URL` | Data stack → API Gateway | Metric ingestion endpoint |
|
|
440
|
+
| `ERROR_URL` | Data stack → API Gateway | Error reporting endpoint |
|
|
441
|
+
| `REALTIME_URL` | Data stack → AppSync Events API | WebSocket subscription endpoint |
|
|
442
|
+
| `COGNITO_BASE_DOMAIN` | Shared stack → Cognito | IDP endpoint for auth |
|
|
443
|
+
| `DEVELOPER_USERNAME` | Shared stack → Cognito | Test user in the `developer` group |
|
|
444
|
+
| `DEVELOPER_PASSWORD` | Shared stack → Cognito | Test user password |
|
|
445
|
+
|
|
446
|
+
#### Run
|
|
447
|
+
|
|
448
|
+
```bash
|
|
449
|
+
bun x vitest run test/e2e-public-sdk.test.ts
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
#### What each test proves
|
|
453
|
+
|
|
454
|
+
| # | Area | Test | What it validates | How to verify manually |
|
|
455
|
+
|---|---|---|---|---|
|
|
456
|
+
| 1 | **Login** | `should authenticate testdeveloper` | USER_PASSWORD_AUTH works with frontend client | AWS Console → Cognito → User Pool → Users → `testdeveloper` exists in `developer` group |
|
|
457
|
+
| 2 | **Login** | `should extract tenant from JWT` | `custom:tenant` claim is present in ID token | Decode the JWT at [jwt.io](https://jwt.io) — look for `custom:tenant` |
|
|
458
|
+
| 3 | **Login** | `should include resource server scopes` | Pre-token Lambda injects `dataworks.live/read` and `/write` | AWS Console → Cognito → User Pool → Lambda triggers → Pre token generation V2 is set |
|
|
459
|
+
| 4 | **Login** | `should reject invalid credentials` | Bad password returns error, not a token | Try `client.login("testdeveloper", "wrong")` — should throw |
|
|
460
|
+
| 5 | **Ingest** | `should ingest valid metrics` | JWT passes API Gateway authoriser, Lambda processes payload | AWS Console → CloudWatch → Log group for the ingest Lambda — look for the metric |
|
|
461
|
+
| 6 | **Ingest** | `should filter invalid metrics` | Client-side validation drops bad metrics before sending | The `stderr` output shows `Dropping invalid metric` — local validation, no server call for bad data |
|
|
462
|
+
| 7 | **Ingest** | `should throw when not authenticated` | `ingest()` before `login()` throws immediately | Client-side guard — no network call made |
|
|
463
|
+
| 8 | **Error** | `should reach error endpoint` | Auth + API Gateway routing work (500 = Lambda ran, FK failed) | AWS Console → CloudWatch → Log group for error processor Lambda |
|
|
464
|
+
| 9 | **Error** | `should throw when not authenticated` | `reportError()` before `login()` throws immediately | Client-side guard |
|
|
465
|
+
| 10 | **Subscribe** | `should throw when not authenticated` | `subscribe()` before `login()` throws immediately | Client-side guard |
|
|
466
|
+
| 11 | **Subscribe** | `should create a subscription` | **Skipped in Node.js** (no `WebSocket` global) — see below | Use the Python verification script |
|
|
467
|
+
| 12–14 | **Validation** | `validateMetric` / `filterValidMetrics` | Static validation utilities work correctly | Pure functions — no infra dependency |
|
|
468
|
+
|
|
469
|
+
#### Verifying WebSocket subscribe (test 11)
|
|
470
|
+
|
|
471
|
+
The subscribe test is skipped in the Node.js Vitest environment because `globalThis.WebSocket` is not available. To prove subscribe works end-to-end, use the standalone Python script:
|
|
472
|
+
|
|
473
|
+
```bash
|
|
474
|
+
pip install websocket-client requests
|
|
475
|
+
python test/verify-subscribe.py
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
This script:
|
|
479
|
+
1. Authenticates via Cognito USER_PASSWORD_AUTH (same as the SDK)
|
|
480
|
+
2. Opens a WebSocket to the AppSync Events API with base64URL-encoded JWT auth subprotocols
|
|
481
|
+
3. Sends `connection_init` and receives `connection_ack` from AppSync
|
|
482
|
+
4. Sends a `subscribe` message and receives `subscribe_success`
|
|
483
|
+
5. Closes cleanly
|
|
484
|
+
|
|
485
|
+
Expected output:
|
|
486
|
+
```
|
|
487
|
+
Loading config from test.env
|
|
488
|
+
|
|
489
|
+
1. Authenticating as 'testdeveloper'...
|
|
490
|
+
✓ Login successful
|
|
491
|
+
Scopes: aws.cognito.signin.user.admin https://dataworks.live/write https://dataworks.live/read
|
|
492
|
+
Tenant: 2x9AOzBvDBgllLB0CXcCrPjK03C
|
|
493
|
+
|
|
494
|
+
2. Connecting to wss://event-api-chris-dev.dataworks.live/event/realtime...
|
|
495
|
+
Protocol: aws-appsync-event-ws (manual)
|
|
496
|
+
Received: connection_ack
|
|
497
|
+
✓ WebSocket connection established
|
|
498
|
+
|
|
499
|
+
3. Subscribing to channel '/dataworks/e2e-verify'...
|
|
500
|
+
Received: subscribe_success
|
|
501
|
+
✓ Subscribed to /dataworks/e2e-verify
|
|
502
|
+
|
|
503
|
+
4. WebSocket closed cleanly
|
|
504
|
+
|
|
505
|
+
============================================================
|
|
506
|
+
ALL CHECKS PASSED — subscribe flow works end-to-end
|
|
507
|
+
============================================================
|
|
508
|
+
```
|
|
509
|
+
|
|
287
510
|
## License
|
|
288
511
|
|
|
289
512
|
MIT © [Dataworks Technology](https://github.com/Dataworks-Technology)
|
package/dist/index.cjs
CHANGED
|
@@ -192,7 +192,13 @@ var DataClient = class {
|
|
|
192
192
|
"Content-Type": "application/json",
|
|
193
193
|
Authorization: `Bearer ${this.credentials.accessToken}`
|
|
194
194
|
},
|
|
195
|
-
body: JSON.stringify(
|
|
195
|
+
body: JSON.stringify({
|
|
196
|
+
client_id: String(error.clientId),
|
|
197
|
+
dataset_datasource_id: error.datasetDatasourceId,
|
|
198
|
+
athlete_id: error.athleteId,
|
|
199
|
+
error_title: error.errorTitle,
|
|
200
|
+
error_description: error.errorDescription
|
|
201
|
+
})
|
|
196
202
|
});
|
|
197
203
|
if (!resp.ok) {
|
|
198
204
|
const body = await resp.text().catch(() => "");
|
|
@@ -217,33 +223,34 @@ var DataClient = class {
|
|
|
217
223
|
const url = new URL(this.config.realtimeUrl);
|
|
218
224
|
url.pathname = "/event/realtime";
|
|
219
225
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
226
|
+
const authPayload = JSON.stringify({
|
|
227
|
+
Authorization: this.credentials.accessToken,
|
|
228
|
+
host: new URL(this.config.realtimeUrl).host
|
|
229
|
+
});
|
|
230
|
+
const authHeader = btoa(authPayload).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
220
231
|
const ws = new WebSocket(url.toString(), [
|
|
221
232
|
"aws-appsync-event-ws",
|
|
222
|
-
|
|
223
|
-
`header-${btoa(
|
|
224
|
-
JSON.stringify({
|
|
225
|
-
Authorization: this.credentials.accessToken,
|
|
226
|
-
host: new URL(this.config.realtimeUrl).host
|
|
227
|
-
})
|
|
228
|
-
)}`
|
|
233
|
+
`header-${authHeader}`
|
|
229
234
|
]);
|
|
230
235
|
ws.addEventListener("open", () => {
|
|
231
|
-
ws.send(
|
|
232
|
-
JSON.stringify({
|
|
233
|
-
type: "subscribe",
|
|
234
|
-
id: crypto.randomUUID(),
|
|
235
|
-
channel: `/${channel}`,
|
|
236
|
-
authorization: {
|
|
237
|
-
Authorization: this.credentials.accessToken,
|
|
238
|
-
host: new URL(this.config.realtimeUrl).host
|
|
239
|
-
}
|
|
240
|
-
})
|
|
241
|
-
);
|
|
236
|
+
ws.send(JSON.stringify({ type: "connection_init" }));
|
|
242
237
|
});
|
|
243
238
|
ws.addEventListener("message", (event) => {
|
|
244
239
|
try {
|
|
245
240
|
const msg = JSON.parse(String(event.data));
|
|
246
|
-
if (msg.type === "
|
|
241
|
+
if (msg.type === "connection_ack") {
|
|
242
|
+
ws.send(
|
|
243
|
+
JSON.stringify({
|
|
244
|
+
type: "subscribe",
|
|
245
|
+
id: crypto.randomUUID(),
|
|
246
|
+
channel: `/${channel}`,
|
|
247
|
+
authorization: {
|
|
248
|
+
Authorization: this.credentials.accessToken,
|
|
249
|
+
host: new URL(this.config.realtimeUrl).host
|
|
250
|
+
}
|
|
251
|
+
})
|
|
252
|
+
);
|
|
253
|
+
} else if (msg.type === "data") {
|
|
247
254
|
onEvent(JSON.parse(msg.event));
|
|
248
255
|
}
|
|
249
256
|
} catch {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../../../node_modules/@dataworks/sdk/dist/client/validation.js","../../../node_modules/@dataworks/sdk/dist/client/auth.js","../src/validation.ts","../src/data-client.ts"],"sourcesContent":["/**\n * @dataworks-technology/data — Dataworks Data Engine SDK\n *\n * Public npm package for external developers to:\n * - Authenticate via Cognito\n * - Ingest live athlete metrics\n * - Subscribe to real-time data streams\n * - Report errors\n *\n * @example\n * ```ts\n * import { DataClient } from \"@dataworks-technology/data\";\n *\n * const client = new DataClient({ ... });\n * await client.login(\"username\", \"password\");\n * await client.ingest(metrics, eventId, dsId);\n * ```\n *\n * @module @dataworks-technology/data\n */\n\nexport { DataClient } from \"./data-client.js\";\n\nexport type {\n DataClientConfig,\n LoginResult,\n Metric,\n MetricItem,\n RawMetricItem,\n MetricsPayload,\n ErrorReport,\n SubscriptionHandler,\n Subscription,\n} from \"./types.js\";\n\n// Re-export validation utilities for standalone use\nexport { validateMetric, filterValidMetrics } from \"./validation.js\";\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 = [];\n for (const m of metrics) {\n const reason = validateMetric(m);\n if (reason) {\n warn(`Dropping invalid metric [${String(m.metric)}=${JSON.stringify(m.value)}]: ${reason}`);\n }\n else {\n valid.push(m);\n }\n }\n return valid;\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 */\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 // Dynamic import to keep this module usable in environments without crypto\n const crypto = await import(\"crypto\");\n const hash = crypto\n .createHmac(\"sha256\", config.clientSecret)\n .update(username + config.clientId)\n .digest(\"base64\");\n authParams.SECRET_HASH = hash;\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 * 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(Buffer.from(payload, \"base64url\").toString(\"utf-8\"));\n // Cognito custom attributes use \"custom:tenant\" claim\n return (decoded[\"custom:tenant\"] ?? decoded.tenant ?? decoded[\"cognito:groups\"]?.[0] ?? \"\");\n }\n catch {\n return \"\";\n }\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/data with zero transitive dependencies.\n *\n * @example\n * ```ts\n * import { DataClient } from \"@dataworks-technology/data\";\n *\n * const client = 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 client.login(\"graeme\", \"password123\");\n * await client.ingest([{ athleteId: \"1\", metric: \"heartrate\", value: 172, timestamp: Date.now() / 1000 }], \"evt1\", \"ds1\");\n * ```\n *\n * @module @dataworks-technology/data\n */\n\nimport { loginWithCredentials } from \"@dataworks/sdk/client\";\nimport { validateMetric, filterValidMetrics } from \"./validation.js\";\nimport type {\n DataClientConfig,\n LoginResult,\n Metric,\n ErrorReport,\n SubscriptionHandler,\n Subscription,\n} from \"./types.js\";\n\nexport class DataClient {\n private readonly config: DataClientConfig;\n private credentials: LoginResult | 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 */\n async login(username: string, password: string): Promise<LoginResult> {\n this.credentials = await loginWithCredentials(username, password, {\n cognitoEndpoint: this.config.cognitoEndpoint,\n clientId: this.config.clientId,\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 */\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 fetch(this.config.ingestUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.credentials!.accessToken}`,\n },\n body: JSON.stringify(payload),\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 */\n async reportError(error: ErrorReport): Promise<void> {\n this.requireAuth();\n\n const resp = await fetch(this.config.errorUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.credentials!.accessToken}`,\n },\n body: JSON.stringify(error),\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 * The channel name maps to the AppSync Events API namespace (e.g. \"dataworks/metrics\").\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 */\n subscribe(channel: string, onEvent: SubscriptionHandler): Subscription {\n this.requireAuth();\n\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 ws = new WebSocket(url.toString(), [\n \"aws-appsync-event-ws\",\n // Encode auth as a base64 subprotocol header\n `header-${btoa(\n JSON.stringify({\n Authorization: this.credentials!.accessToken,\n host: new URL(this.config.realtimeUrl).host,\n }),\n )}`,\n ]);\n\n ws.addEventListener(\"open\", () => {\n // Subscribe to the channel after connection is established\n ws.send(\n JSON.stringify({\n type: \"subscribe\",\n id: crypto.randomUUID(),\n channel: `/${channel}`,\n authorization: {\n Authorization: this.credentials!.accessToken,\n host: new URL(this.config.realtimeUrl).host,\n },\n }),\n );\n });\n\n ws.addEventListener(\"message\", (event) => {\n try {\n const msg = JSON.parse(String(event.data));\n if (msg.type === \"data\") {\n onEvent(JSON.parse(msg.event));\n }\n } catch {\n // Ignore non-data messages (connection_ack, subscribe_success, etc.)\n }\n });\n\n return {\n close() {\n ws.close();\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"],"mappings":"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","names":["filterValidMetrics","validateMetric","crypto","validateMetric","filterValidMetrics","filterValidMetrics","validateMetric"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../../../node_modules/@dataworks/sdk/dist/client/validation.js","../../../node_modules/@dataworks/sdk/dist/client/auth.js","../src/validation.ts","../src/data-client.ts"],"sourcesContent":["/**\n * @dataworks-technology/data — Dataworks Data Engine SDK\n *\n * Public npm package for external developers to:\n * - Authenticate via Cognito\n * - Ingest live athlete metrics\n * - Subscribe to real-time data streams\n * - Report errors\n *\n * @example\n * ```ts\n * import { DataClient } from \"@dataworks-technology/data\";\n *\n * const client = new DataClient({ ... });\n * await client.login(\"username\", \"password\");\n * await client.ingest(metrics, eventId, dsId);\n * ```\n *\n * @module @dataworks-technology/data\n */\n\nexport { DataClient } from \"./data-client.js\";\n\nexport type {\n DataClientConfig,\n LoginResult,\n Metric,\n MetricItem,\n RawMetricItem,\n MetricsPayload,\n ErrorReport,\n SubscriptionHandler,\n Subscription,\n} from \"./types.js\";\n\n// Re-export validation utilities for standalone use\nexport { validateMetric, filterValidMetrics } from \"./validation.js\";\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 = [];\n for (const m of metrics) {\n const reason = validateMetric(m);\n if (reason) {\n warn(`Dropping invalid metric [${String(m.metric)}=${JSON.stringify(m.value)}]: ${reason}`);\n }\n else {\n valid.push(m);\n }\n }\n return valid;\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 */\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 // Dynamic import to keep this module usable in environments without crypto\n const crypto = await import(\"crypto\");\n const hash = crypto\n .createHmac(\"sha256\", config.clientSecret)\n .update(username + config.clientId)\n .digest(\"base64\");\n authParams.SECRET_HASH = hash;\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 * 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(Buffer.from(payload, \"base64url\").toString(\"utf-8\"));\n // Cognito custom attributes use \"custom:tenant\" claim\n return (decoded[\"custom:tenant\"] ?? decoded.tenant ?? decoded[\"cognito:groups\"]?.[0] ?? \"\");\n }\n catch {\n return \"\";\n }\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/data with zero transitive dependencies.\n *\n * @example\n * ```ts\n * import { DataClient } from \"@dataworks-technology/data\";\n *\n * const client = 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 client.login(\"graeme\", \"password123\");\n * await client.ingest([{ athleteId: \"1\", metric: \"heartrate\", value: 172, timestamp: Date.now() / 1000 }], \"evt1\", \"ds1\");\n * ```\n *\n * @module @dataworks-technology/data\n */\n\nimport { loginWithCredentials } from \"@dataworks/sdk/client\";\nimport { validateMetric, filterValidMetrics } from \"./validation.js\";\nimport type {\n DataClientConfig,\n LoginResult,\n Metric,\n ErrorReport,\n SubscriptionHandler,\n Subscription,\n} from \"./types.js\";\n\nexport class DataClient {\n private readonly config: DataClientConfig;\n private credentials: LoginResult | 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 */\n async login(username: string, password: string): Promise<LoginResult> {\n this.credentials = await loginWithCredentials(username, password, {\n cognitoEndpoint: this.config.cognitoEndpoint,\n clientId: this.config.clientId,\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 */\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 fetch(this.config.ingestUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.credentials!.accessToken}`,\n },\n body: JSON.stringify(payload),\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 */\n async reportError(error: ErrorReport): Promise<void> {\n this.requireAuth();\n\n const resp = await fetch(this.config.errorUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.credentials!.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 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 * The channel name maps to the AppSync Events API namespace (e.g. \"dataworks/metrics\").\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 */\n subscribe(channel: string, onEvent: SubscriptionHandler): Subscription {\n this.requireAuth();\n\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 // AWS AppSync Events API requires base64URL-encoded auth in the subprotocol.\n // Standard base64 (btoa) contains +, /, = which violate RFC 6455 token rules\n // and are rejected by non-browser WebSocket implementations (Bun, Node).\n const authPayload = JSON.stringify({\n Authorization: this.credentials!.accessToken,\n host: new URL(this.config.realtimeUrl).host,\n });\n const authHeader = btoa(authPayload)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n\n const ws = new WebSocket(url.toString(), [\n \"aws-appsync-event-ws\",\n `header-${authHeader}`,\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(\"message\", (event) => {\n try {\n const msg = JSON.parse(String(event.data));\n\n if (msg.type === \"connection_ack\") {\n // Connection established — now subscribe to the channel\n ws.send(\n JSON.stringify({\n type: \"subscribe\",\n id: crypto.randomUUID(),\n channel: `/${channel}`,\n authorization: {\n Authorization: this.credentials!.accessToken,\n host: new URL(this.config.realtimeUrl).host,\n },\n }),\n );\n } else if (msg.type === \"data\") {\n onEvent(JSON.parse(msg.event));\n }\n } catch {\n // Ignore malformed or non-data messages (ka, subscribe_success, etc.)\n }\n });\n\n return {\n close() {\n ws.close();\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"],"mappings":"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","names":["filterValidMetrics","validateMetric","crypto","validateMetric","filterValidMetrics","filterValidMetrics","validateMetric"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -59,8 +59,8 @@ interface MetricsPayload {
|
|
|
59
59
|
interface ErrorReport {
|
|
60
60
|
/** Dataset-datasource identifier */
|
|
61
61
|
datasetDatasourceId: string;
|
|
62
|
-
/**
|
|
63
|
-
|
|
62
|
+
/** Athlete identifier */
|
|
63
|
+
athleteId: string;
|
|
64
64
|
/** Client identifier */
|
|
65
65
|
clientId: number;
|
|
66
66
|
/** Short error title */
|
package/dist/index.d.ts
CHANGED
|
@@ -59,8 +59,8 @@ interface MetricsPayload {
|
|
|
59
59
|
interface ErrorReport {
|
|
60
60
|
/** Dataset-datasource identifier */
|
|
61
61
|
datasetDatasourceId: string;
|
|
62
|
-
/**
|
|
63
|
-
|
|
62
|
+
/** Athlete identifier */
|
|
63
|
+
athleteId: string;
|
|
64
64
|
/** Client identifier */
|
|
65
65
|
clientId: number;
|
|
66
66
|
/** Short error title */
|
package/dist/index.js
CHANGED
|
@@ -154,7 +154,13 @@ var DataClient = class {
|
|
|
154
154
|
"Content-Type": "application/json",
|
|
155
155
|
Authorization: `Bearer ${this.credentials.accessToken}`
|
|
156
156
|
},
|
|
157
|
-
body: JSON.stringify(
|
|
157
|
+
body: JSON.stringify({
|
|
158
|
+
client_id: String(error.clientId),
|
|
159
|
+
dataset_datasource_id: error.datasetDatasourceId,
|
|
160
|
+
athlete_id: error.athleteId,
|
|
161
|
+
error_title: error.errorTitle,
|
|
162
|
+
error_description: error.errorDescription
|
|
163
|
+
})
|
|
158
164
|
});
|
|
159
165
|
if (!resp.ok) {
|
|
160
166
|
const body = await resp.text().catch(() => "");
|
|
@@ -179,33 +185,34 @@ var DataClient = class {
|
|
|
179
185
|
const url = new URL(this.config.realtimeUrl);
|
|
180
186
|
url.pathname = "/event/realtime";
|
|
181
187
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
188
|
+
const authPayload = JSON.stringify({
|
|
189
|
+
Authorization: this.credentials.accessToken,
|
|
190
|
+
host: new URL(this.config.realtimeUrl).host
|
|
191
|
+
});
|
|
192
|
+
const authHeader = btoa(authPayload).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
182
193
|
const ws = new WebSocket(url.toString(), [
|
|
183
194
|
"aws-appsync-event-ws",
|
|
184
|
-
|
|
185
|
-
`header-${btoa(
|
|
186
|
-
JSON.stringify({
|
|
187
|
-
Authorization: this.credentials.accessToken,
|
|
188
|
-
host: new URL(this.config.realtimeUrl).host
|
|
189
|
-
})
|
|
190
|
-
)}`
|
|
195
|
+
`header-${authHeader}`
|
|
191
196
|
]);
|
|
192
197
|
ws.addEventListener("open", () => {
|
|
193
|
-
ws.send(
|
|
194
|
-
JSON.stringify({
|
|
195
|
-
type: "subscribe",
|
|
196
|
-
id: crypto.randomUUID(),
|
|
197
|
-
channel: `/${channel}`,
|
|
198
|
-
authorization: {
|
|
199
|
-
Authorization: this.credentials.accessToken,
|
|
200
|
-
host: new URL(this.config.realtimeUrl).host
|
|
201
|
-
}
|
|
202
|
-
})
|
|
203
|
-
);
|
|
198
|
+
ws.send(JSON.stringify({ type: "connection_init" }));
|
|
204
199
|
});
|
|
205
200
|
ws.addEventListener("message", (event) => {
|
|
206
201
|
try {
|
|
207
202
|
const msg = JSON.parse(String(event.data));
|
|
208
|
-
if (msg.type === "
|
|
203
|
+
if (msg.type === "connection_ack") {
|
|
204
|
+
ws.send(
|
|
205
|
+
JSON.stringify({
|
|
206
|
+
type: "subscribe",
|
|
207
|
+
id: crypto.randomUUID(),
|
|
208
|
+
channel: `/${channel}`,
|
|
209
|
+
authorization: {
|
|
210
|
+
Authorization: this.credentials.accessToken,
|
|
211
|
+
host: new URL(this.config.realtimeUrl).host
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
);
|
|
215
|
+
} else if (msg.type === "data") {
|
|
209
216
|
onEvent(JSON.parse(msg.event));
|
|
210
217
|
}
|
|
211
218
|
} catch {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../node_modules/@dataworks/sdk/dist/client/validation.js","../../../node_modules/@dataworks/sdk/dist/client/auth.js","../src/validation.ts","../src/data-client.ts"],"sourcesContent":["/**\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 = [];\n for (const m of metrics) {\n const reason = validateMetric(m);\n if (reason) {\n warn(`Dropping invalid metric [${String(m.metric)}=${JSON.stringify(m.value)}]: ${reason}`);\n }\n else {\n valid.push(m);\n }\n }\n return valid;\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 */\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 // Dynamic import to keep this module usable in environments without crypto\n const crypto = await import(\"crypto\");\n const hash = crypto\n .createHmac(\"sha256\", config.clientSecret)\n .update(username + config.clientId)\n .digest(\"base64\");\n authParams.SECRET_HASH = hash;\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 * 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(Buffer.from(payload, \"base64url\").toString(\"utf-8\"));\n // Cognito custom attributes use \"custom:tenant\" claim\n return (decoded[\"custom:tenant\"] ?? decoded.tenant ?? decoded[\"cognito:groups\"]?.[0] ?? \"\");\n }\n catch {\n return \"\";\n }\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/data with zero transitive dependencies.\n *\n * @example\n * ```ts\n * import { DataClient } from \"@dataworks-technology/data\";\n *\n * const client = 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 client.login(\"graeme\", \"password123\");\n * await client.ingest([{ athleteId: \"1\", metric: \"heartrate\", value: 172, timestamp: Date.now() / 1000 }], \"evt1\", \"ds1\");\n * ```\n *\n * @module @dataworks-technology/data\n */\n\nimport { loginWithCredentials } from \"@dataworks/sdk/client\";\nimport { validateMetric, filterValidMetrics } from \"./validation.js\";\nimport type {\n DataClientConfig,\n LoginResult,\n Metric,\n ErrorReport,\n SubscriptionHandler,\n Subscription,\n} from \"./types.js\";\n\nexport class DataClient {\n private readonly config: DataClientConfig;\n private credentials: LoginResult | 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 */\n async login(username: string, password: string): Promise<LoginResult> {\n this.credentials = await loginWithCredentials(username, password, {\n cognitoEndpoint: this.config.cognitoEndpoint,\n clientId: this.config.clientId,\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 */\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 fetch(this.config.ingestUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.credentials!.accessToken}`,\n },\n body: JSON.stringify(payload),\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 */\n async reportError(error: ErrorReport): Promise<void> {\n this.requireAuth();\n\n const resp = await fetch(this.config.errorUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.credentials!.accessToken}`,\n },\n body: JSON.stringify(error),\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 * The channel name maps to the AppSync Events API namespace (e.g. \"dataworks/metrics\").\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 */\n subscribe(channel: string, onEvent: SubscriptionHandler): Subscription {\n this.requireAuth();\n\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 ws = new WebSocket(url.toString(), [\n \"aws-appsync-event-ws\",\n // Encode auth as a base64 subprotocol header\n `header-${btoa(\n JSON.stringify({\n Authorization: this.credentials!.accessToken,\n host: new URL(this.config.realtimeUrl).host,\n }),\n )}`,\n ]);\n\n ws.addEventListener(\"open\", () => {\n // Subscribe to the channel after connection is established\n ws.send(\n JSON.stringify({\n type: \"subscribe\",\n id: crypto.randomUUID(),\n channel: `/${channel}`,\n authorization: {\n Authorization: this.credentials!.accessToken,\n host: new URL(this.config.realtimeUrl).host,\n },\n }),\n );\n });\n\n ws.addEventListener(\"message\", (event) => {\n try {\n const msg = JSON.parse(String(event.data));\n if (msg.type === \"data\") {\n onEvent(JSON.parse(msg.event));\n }\n } catch {\n // Ignore non-data messages (connection_ack, subscribe_success, etc.)\n }\n });\n\n return {\n close() {\n ws.close();\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"],"mappings":"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","names":["crypto","validateMetric","filterValidMetrics","filterValidMetrics","validateMetric"]}
|
|
1
|
+
{"version":3,"sources":["../../../node_modules/@dataworks/sdk/dist/client/validation.js","../../../node_modules/@dataworks/sdk/dist/client/auth.js","../src/validation.ts","../src/data-client.ts"],"sourcesContent":["/**\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 = [];\n for (const m of metrics) {\n const reason = validateMetric(m);\n if (reason) {\n warn(`Dropping invalid metric [${String(m.metric)}=${JSON.stringify(m.value)}]: ${reason}`);\n }\n else {\n valid.push(m);\n }\n }\n return valid;\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 */\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 // Dynamic import to keep this module usable in environments without crypto\n const crypto = await import(\"crypto\");\n const hash = crypto\n .createHmac(\"sha256\", config.clientSecret)\n .update(username + config.clientId)\n .digest(\"base64\");\n authParams.SECRET_HASH = hash;\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 * 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(Buffer.from(payload, \"base64url\").toString(\"utf-8\"));\n // Cognito custom attributes use \"custom:tenant\" claim\n return (decoded[\"custom:tenant\"] ?? decoded.tenant ?? decoded[\"cognito:groups\"]?.[0] ?? \"\");\n }\n catch {\n return \"\";\n }\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/data with zero transitive dependencies.\n *\n * @example\n * ```ts\n * import { DataClient } from \"@dataworks-technology/data\";\n *\n * const client = 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 client.login(\"graeme\", \"password123\");\n * await client.ingest([{ athleteId: \"1\", metric: \"heartrate\", value: 172, timestamp: Date.now() / 1000 }], \"evt1\", \"ds1\");\n * ```\n *\n * @module @dataworks-technology/data\n */\n\nimport { loginWithCredentials } from \"@dataworks/sdk/client\";\nimport { validateMetric, filterValidMetrics } from \"./validation.js\";\nimport type {\n DataClientConfig,\n LoginResult,\n Metric,\n ErrorReport,\n SubscriptionHandler,\n Subscription,\n} from \"./types.js\";\n\nexport class DataClient {\n private readonly config: DataClientConfig;\n private credentials: LoginResult | 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 */\n async login(username: string, password: string): Promise<LoginResult> {\n this.credentials = await loginWithCredentials(username, password, {\n cognitoEndpoint: this.config.cognitoEndpoint,\n clientId: this.config.clientId,\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 */\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 fetch(this.config.ingestUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.credentials!.accessToken}`,\n },\n body: JSON.stringify(payload),\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 */\n async reportError(error: ErrorReport): Promise<void> {\n this.requireAuth();\n\n const resp = await fetch(this.config.errorUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.credentials!.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 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 * The channel name maps to the AppSync Events API namespace (e.g. \"dataworks/metrics\").\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 */\n subscribe(channel: string, onEvent: SubscriptionHandler): Subscription {\n this.requireAuth();\n\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 // AWS AppSync Events API requires base64URL-encoded auth in the subprotocol.\n // Standard base64 (btoa) contains +, /, = which violate RFC 6455 token rules\n // and are rejected by non-browser WebSocket implementations (Bun, Node).\n const authPayload = JSON.stringify({\n Authorization: this.credentials!.accessToken,\n host: new URL(this.config.realtimeUrl).host,\n });\n const authHeader = btoa(authPayload)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n\n const ws = new WebSocket(url.toString(), [\n \"aws-appsync-event-ws\",\n `header-${authHeader}`,\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(\"message\", (event) => {\n try {\n const msg = JSON.parse(String(event.data));\n\n if (msg.type === \"connection_ack\") {\n // Connection established — now subscribe to the channel\n ws.send(\n JSON.stringify({\n type: \"subscribe\",\n id: crypto.randomUUID(),\n channel: `/${channel}`,\n authorization: {\n Authorization: this.credentials!.accessToken,\n host: new URL(this.config.realtimeUrl).host,\n },\n }),\n );\n } else if (msg.type === \"data\") {\n onEvent(JSON.parse(msg.event));\n }\n } catch {\n // Ignore malformed or non-data messages (ka, subscribe_success, etc.)\n }\n });\n\n return {\n close() {\n ws.close();\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"],"mappings":"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","names":["crypto","validateMetric","filterValidMetrics","filterValidMetrics","validateMetric"]}
|
package/package.json
CHANGED