@dataworks-technology/data 0.1.3
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 +289 -0
- package/dist/index.cjs +291 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +157 -0
- package/dist/index.d.ts +157 -0
- package/dist/index.js +252 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# @dataworks-technology/data
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@dataworks-technology/data)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
[](https://bundlephobia.com/package/@dataworks-technology/data)
|
|
6
|
+
|
|
7
|
+
Official SDK for the Dataworks Data Engine — authenticate, ingest live athlete metrics, subscribe to real-time data streams, and report errors.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Zero dependencies** — fully self-contained, no transitive installs
|
|
12
|
+
- **Dual format** — ESM and CommonJS bundles included
|
|
13
|
+
- **TypeScript-first** — complete type declarations shipped with the package
|
|
14
|
+
- **Built-in validation** — metrics are validated before ingestion
|
|
15
|
+
- **Real-time subscriptions** — WebSocket-based live data streaming
|
|
16
|
+
- **Cognito authentication** — secure login with automatic token management
|
|
17
|
+
|
|
18
|
+
## Prerequisites
|
|
19
|
+
|
|
20
|
+
You need a Dataworks developer account. Contact your Dataworks administrator to receive:
|
|
21
|
+
|
|
22
|
+
| Credential | Description |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `cognitoEndpoint` | Cognito User Pool endpoint URL |
|
|
25
|
+
| `clientId` | Cognito app client ID |
|
|
26
|
+
| `ingestUrl` | API Gateway endpoint for metric ingestion |
|
|
27
|
+
| `errorUrl` | API Gateway endpoint for error reporting |
|
|
28
|
+
| `realtimeUrl` | AppSync Events API endpoint for subscriptions |
|
|
29
|
+
| Username + password | Your developer login credentials |
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @dataworks-technology/data
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
yarn add @dataworks-technology/data
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pnpm add @dataworks-technology/data
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
bun add @dataworks-technology/data
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { DataClient } from "@dataworks-technology/data";
|
|
53
|
+
|
|
54
|
+
const client = new DataClient({
|
|
55
|
+
cognitoEndpoint: "https://cognito-idp.eu-west-1.amazonaws.com/",
|
|
56
|
+
clientId: "your-client-id",
|
|
57
|
+
ingestUrl: "https://your-ingest-endpoint.dataworks.live",
|
|
58
|
+
errorUrl: "https://your-error-endpoint.dataworks.live",
|
|
59
|
+
realtimeUrl: "https://your-realtime-endpoint.dataworks.live",
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Authenticate
|
|
63
|
+
await client.login("username", "password");
|
|
64
|
+
|
|
65
|
+
// Ingest metrics
|
|
66
|
+
await client.ingest(
|
|
67
|
+
[
|
|
68
|
+
{
|
|
69
|
+
athleteId: "athlete-1",
|
|
70
|
+
metric: "heartrate",
|
|
71
|
+
value: 172,
|
|
72
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
"event-id",
|
|
76
|
+
"dataset-datasource-id",
|
|
77
|
+
);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## API
|
|
81
|
+
|
|
82
|
+
### `new DataClient(config)`
|
|
83
|
+
|
|
84
|
+
Create a new client instance.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
const client = new DataClient({
|
|
88
|
+
cognitoEndpoint: "https://cognito-idp.eu-west-1.amazonaws.com/",
|
|
89
|
+
clientId: "abc123",
|
|
90
|
+
ingestUrl: "https://ingest.dataworks.live",
|
|
91
|
+
errorUrl: "https://errors.dataworks.live",
|
|
92
|
+
realtimeUrl: "https://realtime.dataworks.live",
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### `client.login(username, password)`
|
|
97
|
+
|
|
98
|
+
Authenticate with Cognito. Must be called before any other operation.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const result = await client.login("username", "password");
|
|
102
|
+
// result: { accessToken, idToken, refreshToken, tenant }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### `client.ingest(metrics, eventId, datasetDatasourceId)`
|
|
106
|
+
|
|
107
|
+
Send metric data points to the Data Engine. Invalid metrics are automatically filtered out.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
await client.ingest(
|
|
111
|
+
[
|
|
112
|
+
{ athleteId: "1", metric: "heartrate", value: 172, timestamp: 1700000000 },
|
|
113
|
+
{ athleteId: "1", metric: "speed", value: 4.2, timestamp: 1700000000 },
|
|
114
|
+
],
|
|
115
|
+
"event-123",
|
|
116
|
+
"ds-456",
|
|
117
|
+
);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### `client.subscribe(channel, onEvent)`
|
|
121
|
+
|
|
122
|
+
Subscribe to real-time data events via WebSocket.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const subscription = client.subscribe(
|
|
126
|
+
"/default/events/event-123",
|
|
127
|
+
(event) => {
|
|
128
|
+
console.log("Received:", event);
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// Later: close the subscription
|
|
133
|
+
subscription.close();
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### `client.reportError(error)`
|
|
137
|
+
|
|
138
|
+
Report an error to the Data Engine for monitoring and alerting.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
await client.reportError({
|
|
142
|
+
datasetDatasourceId: "ds-456",
|
|
143
|
+
eventId: "event-123",
|
|
144
|
+
clientId: 1,
|
|
145
|
+
errorTitle: "Sensor disconnected",
|
|
146
|
+
errorDescription: "BLE heart rate sensor lost connection at 00:42:15",
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### `client.isAuthenticated`
|
|
151
|
+
|
|
152
|
+
Check if the client has valid credentials.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
if (client.isAuthenticated) {
|
|
156
|
+
await client.ingest(metrics, eventId, dsId);
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### `client.tenant`
|
|
161
|
+
|
|
162
|
+
Get the tenant from the authenticated session (or `null`).
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
console.log(`Logged in as tenant: ${client.tenant}`);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Validation Utilities
|
|
169
|
+
|
|
170
|
+
Standalone validation functions are exported for pre-checking metrics before ingestion:
|
|
171
|
+
|
|
172
|
+
### `DataClient.validateMetric(metric)`
|
|
173
|
+
|
|
174
|
+
Returns `null` if valid, or a string describing the validation failure.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { validateMetric } from "@dataworks-technology/data";
|
|
178
|
+
|
|
179
|
+
const error = validateMetric({
|
|
180
|
+
metric: "heartrate",
|
|
181
|
+
athleteId: "1",
|
|
182
|
+
value: 172,
|
|
183
|
+
timestamp: 1700000000,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (error) {
|
|
187
|
+
console.warn("Invalid metric:", error);
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### `DataClient.filterValidMetrics(metrics, warnFn)`
|
|
192
|
+
|
|
193
|
+
Filters an array, keeping only valid metrics. Calls `warnFn` for each invalid item.
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import { filterValidMetrics } from "@dataworks-technology/data";
|
|
197
|
+
|
|
198
|
+
const valid = filterValidMetrics(rawMetrics, (msg) => console.warn(msg));
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Validation Rules
|
|
202
|
+
|
|
203
|
+
| Field | Rule |
|
|
204
|
+
|---|---|
|
|
205
|
+
| `metric` | Non-empty string |
|
|
206
|
+
| `athleteId` | Non-empty string |
|
|
207
|
+
| `value` | Number (finite, not NaN) or non-empty string |
|
|
208
|
+
| `timestamp` | Positive integer (Unix seconds) |
|
|
209
|
+
|
|
210
|
+
## Types
|
|
211
|
+
|
|
212
|
+
All types are exported for use in your application:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import type {
|
|
216
|
+
DataClientConfig,
|
|
217
|
+
LoginResult,
|
|
218
|
+
Metric,
|
|
219
|
+
MetricItem,
|
|
220
|
+
RawMetricItem,
|
|
221
|
+
MetricsPayload,
|
|
222
|
+
ErrorReport,
|
|
223
|
+
SubscriptionHandler,
|
|
224
|
+
Subscription,
|
|
225
|
+
} from "@dataworks-technology/data";
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Key Interfaces
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
interface Metric {
|
|
232
|
+
athleteId: string;
|
|
233
|
+
metric: string;
|
|
234
|
+
value: number | string;
|
|
235
|
+
timestamp: number; // Unix seconds
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
interface ErrorReport {
|
|
239
|
+
datasetDatasourceId: string;
|
|
240
|
+
eventId: string;
|
|
241
|
+
clientId: number;
|
|
242
|
+
errorTitle: string;
|
|
243
|
+
errorDescription: string;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
interface LoginResult {
|
|
247
|
+
accessToken: string;
|
|
248
|
+
idToken: string;
|
|
249
|
+
refreshToken: string;
|
|
250
|
+
tenant: string;
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Error Handling
|
|
255
|
+
|
|
256
|
+
All async methods throw on failure. Wrap calls in try/catch:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
try {
|
|
260
|
+
await client.login("user", "pass");
|
|
261
|
+
} catch (err) {
|
|
262
|
+
// Authentication failed (invalid credentials, network error, etc.)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
await client.ingest(metrics, eventId, dsId);
|
|
267
|
+
} catch (err) {
|
|
268
|
+
// Ingestion failed (401 expired token, 5xx server error, etc.)
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Calling `ingest()`, `reportError()`, or `subscribe()` before `login()` throws immediately.
|
|
273
|
+
|
|
274
|
+
## Requirements
|
|
275
|
+
|
|
276
|
+
- **Node.js** ≥ 18 (uses native `fetch`)
|
|
277
|
+
- **ESM or CommonJS** — both module formats are included
|
|
278
|
+
- **TypeScript** ≥ 5.0 (optional — works with plain JavaScript too)
|
|
279
|
+
- **Browser** — compatible with any environment that has `fetch` and `WebSocket`
|
|
280
|
+
|
|
281
|
+
## Documentation
|
|
282
|
+
|
|
283
|
+
Full documentation with guides, examples, and detailed API reference:
|
|
284
|
+
|
|
285
|
+
**[https://data-docs.dataworks.live](https://data-docs.dataworks.live)**
|
|
286
|
+
|
|
287
|
+
## License
|
|
288
|
+
|
|
289
|
+
MIT © [Dataworks Technology](https://github.com/Dataworks-Technology)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
DataClient: () => DataClient,
|
|
34
|
+
filterValidMetrics: () => filterValidMetrics2,
|
|
35
|
+
validateMetric: () => validateMetric2
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
|
|
39
|
+
// ../../node_modules/@dataworks/sdk/dist/client/validation.js
|
|
40
|
+
function validateMetric(m) {
|
|
41
|
+
if (typeof m.metric !== "string" || m.metric.trim() === "")
|
|
42
|
+
return "metric must be a non-empty string";
|
|
43
|
+
if (typeof m.athleteId !== "string" || m.athleteId.trim() === "")
|
|
44
|
+
return "athleteId must be a non-empty string";
|
|
45
|
+
if (!Number.isInteger(m.timestamp) || m.timestamp <= 0)
|
|
46
|
+
return `timestamp must be a positive integer, got ${m.timestamp}`;
|
|
47
|
+
if (m.value === null || m.value === void 0)
|
|
48
|
+
return "value is null/undefined";
|
|
49
|
+
if (typeof m.value === "number" && !isFinite(m.value))
|
|
50
|
+
return `value is non-finite number (${m.value})`;
|
|
51
|
+
if (typeof m.value !== "number" && typeof m.value !== "string")
|
|
52
|
+
return `value type ${typeof m.value} not allowed (must be number or string)`;
|
|
53
|
+
if (typeof m.value === "string" && m.value === "")
|
|
54
|
+
return "value is empty string";
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
function filterValidMetrics(metrics, warn) {
|
|
58
|
+
const valid = [];
|
|
59
|
+
for (const m of metrics) {
|
|
60
|
+
const reason = validateMetric(m);
|
|
61
|
+
if (reason) {
|
|
62
|
+
warn(`Dropping invalid metric [${String(m.metric)}=${JSON.stringify(m.value)}]: ${reason}`);
|
|
63
|
+
} else {
|
|
64
|
+
valid.push(m);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return valid;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ../../node_modules/@dataworks/sdk/dist/client/auth.js
|
|
71
|
+
async function loginWithCredentials(username, password, config) {
|
|
72
|
+
const authParams = {
|
|
73
|
+
USERNAME: username,
|
|
74
|
+
PASSWORD: password
|
|
75
|
+
};
|
|
76
|
+
if (config.clientSecret) {
|
|
77
|
+
const crypto2 = await import("crypto");
|
|
78
|
+
const hash = crypto2.createHmac("sha256", config.clientSecret).update(username + config.clientId).digest("base64");
|
|
79
|
+
authParams.SECRET_HASH = hash;
|
|
80
|
+
}
|
|
81
|
+
const resp = await fetch(config.cognitoEndpoint, {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: {
|
|
84
|
+
"X-Amz-Target": "AWSCognitoIdentityProviderService.InitiateAuth",
|
|
85
|
+
"Content-Type": "application/x-amz-json-1.1"
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
AuthFlow: "USER_PASSWORD_AUTH",
|
|
89
|
+
ClientId: config.clientId,
|
|
90
|
+
AuthParameters: authParams
|
|
91
|
+
})
|
|
92
|
+
});
|
|
93
|
+
if (!resp.ok) {
|
|
94
|
+
const body = await resp.text().catch(() => "");
|
|
95
|
+
throw new Error(`loginWithCredentials: Cognito auth failed: ${resp.status} \u2014 ${body}`);
|
|
96
|
+
}
|
|
97
|
+
const data = await resp.json();
|
|
98
|
+
const result = data.AuthenticationResult;
|
|
99
|
+
if (!result?.AccessToken || !result?.IdToken) {
|
|
100
|
+
throw new Error("loginWithCredentials: response missing AccessToken or IdToken");
|
|
101
|
+
}
|
|
102
|
+
const tenant = extractTenantFromJwt(result.IdToken);
|
|
103
|
+
return {
|
|
104
|
+
accessToken: result.AccessToken,
|
|
105
|
+
idToken: result.IdToken,
|
|
106
|
+
refreshToken: result.RefreshToken ?? "",
|
|
107
|
+
tenant
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function extractTenantFromJwt(idToken) {
|
|
111
|
+
try {
|
|
112
|
+
const payload = idToken.split(".")[1];
|
|
113
|
+
const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
|
|
114
|
+
return decoded["custom:tenant"] ?? decoded.tenant ?? decoded["cognito:groups"]?.[0] ?? "";
|
|
115
|
+
} catch {
|
|
116
|
+
return "";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/validation.ts
|
|
121
|
+
var validateMetric2 = validateMetric;
|
|
122
|
+
var filterValidMetrics2 = filterValidMetrics;
|
|
123
|
+
|
|
124
|
+
// src/data-client.ts
|
|
125
|
+
var DataClient = class {
|
|
126
|
+
constructor(config) {
|
|
127
|
+
this.credentials = null;
|
|
128
|
+
this.config = config;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Authenticate with the Dataworks platform using Cognito USER_PASSWORD_AUTH.
|
|
132
|
+
* Must be called before ingest(), reportError(), or subscribe().
|
|
133
|
+
*
|
|
134
|
+
* @param username - Cognito username
|
|
135
|
+
* @param password - Cognito password
|
|
136
|
+
* @returns Login result containing tokens and tenant
|
|
137
|
+
*/
|
|
138
|
+
async login(username, password) {
|
|
139
|
+
this.credentials = await loginWithCredentials(username, password, {
|
|
140
|
+
cognitoEndpoint: this.config.cognitoEndpoint,
|
|
141
|
+
clientId: this.config.clientId
|
|
142
|
+
});
|
|
143
|
+
return this.credentials;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Ingest metric data points into the Dataworks Data Engine.
|
|
147
|
+
* Validates each metric before sending — invalid metrics are silently dropped.
|
|
148
|
+
*
|
|
149
|
+
* @param metrics - Array of metric data points
|
|
150
|
+
* @param eventId - Event identifier
|
|
151
|
+
* @param datasetDatasourceId - Dataset-datasource identifier
|
|
152
|
+
* @throws Error if not authenticated or if the request fails
|
|
153
|
+
*/
|
|
154
|
+
async ingest(metrics, eventId, datasetDatasourceId) {
|
|
155
|
+
this.requireAuth();
|
|
156
|
+
const valid = filterValidMetrics2(
|
|
157
|
+
metrics,
|
|
158
|
+
(msg) => console.warn(`[@dataworks-technology/data] ${msg}`)
|
|
159
|
+
);
|
|
160
|
+
if (valid.length === 0) return;
|
|
161
|
+
const payload = {
|
|
162
|
+
eventId,
|
|
163
|
+
datasetDatasourceId,
|
|
164
|
+
metrics: valid
|
|
165
|
+
};
|
|
166
|
+
const resp = await fetch(this.config.ingestUrl, {
|
|
167
|
+
method: "POST",
|
|
168
|
+
headers: {
|
|
169
|
+
"Content-Type": "application/json",
|
|
170
|
+
Authorization: `Bearer ${this.credentials.accessToken}`
|
|
171
|
+
},
|
|
172
|
+
body: JSON.stringify(payload)
|
|
173
|
+
});
|
|
174
|
+
if (!resp.ok) {
|
|
175
|
+
const body = await resp.text().catch(() => "");
|
|
176
|
+
throw new Error(
|
|
177
|
+
`[@dataworks-technology/data] ingest failed: ${resp.status} \u2014 ${body}`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Report an error to the Dataworks Data Engine.
|
|
183
|
+
*
|
|
184
|
+
* @param error - Error report details
|
|
185
|
+
* @throws Error if not authenticated or if the request fails
|
|
186
|
+
*/
|
|
187
|
+
async reportError(error) {
|
|
188
|
+
this.requireAuth();
|
|
189
|
+
const resp = await fetch(this.config.errorUrl, {
|
|
190
|
+
method: "POST",
|
|
191
|
+
headers: {
|
|
192
|
+
"Content-Type": "application/json",
|
|
193
|
+
Authorization: `Bearer ${this.credentials.accessToken}`
|
|
194
|
+
},
|
|
195
|
+
body: JSON.stringify(error)
|
|
196
|
+
});
|
|
197
|
+
if (!resp.ok) {
|
|
198
|
+
const body = await resp.text().catch(() => "");
|
|
199
|
+
throw new Error(
|
|
200
|
+
`[@dataworks-technology/data] reportError failed: ${resp.status} \u2014 ${body}`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Subscribe to a real-time data channel on the AppSync Events API.
|
|
206
|
+
* Uses the Cognito JWT for authentication.
|
|
207
|
+
*
|
|
208
|
+
* The channel name maps to the AppSync Events API namespace (e.g. "dataworks/metrics").
|
|
209
|
+
*
|
|
210
|
+
* @param channel - Channel path to subscribe to
|
|
211
|
+
* @param onEvent - Callback invoked for each received event
|
|
212
|
+
* @returns Subscription handle with a close() method
|
|
213
|
+
* @throws Error if not authenticated
|
|
214
|
+
*/
|
|
215
|
+
subscribe(channel, onEvent) {
|
|
216
|
+
this.requireAuth();
|
|
217
|
+
const url = new URL(this.config.realtimeUrl);
|
|
218
|
+
url.pathname = "/event/realtime";
|
|
219
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
220
|
+
const ws = new WebSocket(url.toString(), [
|
|
221
|
+
"aws-appsync-event-ws",
|
|
222
|
+
// Encode auth as a base64 subprotocol header
|
|
223
|
+
`header-${btoa(
|
|
224
|
+
JSON.stringify({
|
|
225
|
+
Authorization: this.credentials.accessToken,
|
|
226
|
+
host: new URL(this.config.realtimeUrl).host
|
|
227
|
+
})
|
|
228
|
+
)}`
|
|
229
|
+
]);
|
|
230
|
+
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
|
+
);
|
|
242
|
+
});
|
|
243
|
+
ws.addEventListener("message", (event) => {
|
|
244
|
+
try {
|
|
245
|
+
const msg = JSON.parse(String(event.data));
|
|
246
|
+
if (msg.type === "data") {
|
|
247
|
+
onEvent(JSON.parse(msg.event));
|
|
248
|
+
}
|
|
249
|
+
} catch {
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
return {
|
|
253
|
+
close() {
|
|
254
|
+
ws.close();
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
/** Returns true if the client has been authenticated via login(). */
|
|
259
|
+
get isAuthenticated() {
|
|
260
|
+
return this.credentials !== null;
|
|
261
|
+
}
|
|
262
|
+
/** Returns the tenant from the last successful login, or null. */
|
|
263
|
+
get tenant() {
|
|
264
|
+
return this.credentials?.tenant ?? null;
|
|
265
|
+
}
|
|
266
|
+
/** @internal Throws if not authenticated. */
|
|
267
|
+
requireAuth() {
|
|
268
|
+
if (!this.credentials) {
|
|
269
|
+
throw new Error(
|
|
270
|
+
"[@dataworks-technology/data] Not authenticated \u2014 call login() first"
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* Check whether a metric is valid before ingesting.
|
|
277
|
+
* Returns null if valid, or a human-readable reason string if invalid.
|
|
278
|
+
*/
|
|
279
|
+
DataClient.validateMetric = validateMetric2;
|
|
280
|
+
/**
|
|
281
|
+
* Filter an array of metrics, keeping only valid ones.
|
|
282
|
+
* Logs a warning for each dropped item.
|
|
283
|
+
*/
|
|
284
|
+
DataClient.filterValidMetrics = filterValidMetrics2;
|
|
285
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
286
|
+
0 && (module.exports = {
|
|
287
|
+
DataClient,
|
|
288
|
+
filterValidMetrics,
|
|
289
|
+
validateMetric
|
|
290
|
+
});
|
|
291
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for @dataworks/data.
|
|
3
|
+
*
|
|
4
|
+
* @module @dataworks-technology/data
|
|
5
|
+
*/
|
|
6
|
+
/** Configuration for connecting to the Dataworks Data Engine. */
|
|
7
|
+
interface DataClientConfig {
|
|
8
|
+
/** Cognito User Pool endpoint (e.g. "https://cognito-idp.eu-west-1.amazonaws.com/") */
|
|
9
|
+
cognitoEndpoint: string;
|
|
10
|
+
/** Cognito app client ID */
|
|
11
|
+
clientId: string;
|
|
12
|
+
/** Data Engine ingest URL (API Gateway endpoint) */
|
|
13
|
+
ingestUrl: string;
|
|
14
|
+
/** Data Engine error reporting URL (API Gateway endpoint) */
|
|
15
|
+
errorUrl: string;
|
|
16
|
+
/** AppSync Events API endpoint for real-time subscriptions */
|
|
17
|
+
realtimeUrl: string;
|
|
18
|
+
}
|
|
19
|
+
/** Credentials returned from a successful login. */
|
|
20
|
+
interface LoginResult {
|
|
21
|
+
accessToken: string;
|
|
22
|
+
idToken: string;
|
|
23
|
+
refreshToken: string;
|
|
24
|
+
/** Tenant extracted from the JWT claims. */
|
|
25
|
+
tenant: string;
|
|
26
|
+
}
|
|
27
|
+
/** A single metric data point to ingest. */
|
|
28
|
+
interface Metric {
|
|
29
|
+
/** Unique athlete identifier */
|
|
30
|
+
athleteId: string;
|
|
31
|
+
/** Metric name (e.g. "heartrate", "speed", "power") */
|
|
32
|
+
metric: string;
|
|
33
|
+
/** Metric value — number or string */
|
|
34
|
+
value: number | string;
|
|
35
|
+
/** Unix timestamp in seconds */
|
|
36
|
+
timestamp: number;
|
|
37
|
+
}
|
|
38
|
+
/** Validated metric item (all fields confirmed present and correct). */
|
|
39
|
+
interface MetricItem {
|
|
40
|
+
athleteId: string;
|
|
41
|
+
metric: string;
|
|
42
|
+
value: unknown;
|
|
43
|
+
timestamp: number;
|
|
44
|
+
}
|
|
45
|
+
/** Loosely-typed input for validation — all fields are unknown. */
|
|
46
|
+
interface RawMetricItem {
|
|
47
|
+
metric: unknown;
|
|
48
|
+
athleteId: unknown;
|
|
49
|
+
value: unknown;
|
|
50
|
+
timestamp: unknown;
|
|
51
|
+
}
|
|
52
|
+
/** Payload shape for metric ingestion. */
|
|
53
|
+
interface MetricsPayload {
|
|
54
|
+
eventId: string;
|
|
55
|
+
datasetDatasourceId: string;
|
|
56
|
+
metrics: MetricItem[];
|
|
57
|
+
}
|
|
58
|
+
/** Error report to send to the Data Engine. */
|
|
59
|
+
interface ErrorReport {
|
|
60
|
+
/** Dataset-datasource identifier */
|
|
61
|
+
datasetDatasourceId: string;
|
|
62
|
+
/** Event identifier */
|
|
63
|
+
eventId: string;
|
|
64
|
+
/** Client identifier */
|
|
65
|
+
clientId: number;
|
|
66
|
+
/** Short error title */
|
|
67
|
+
errorTitle: string;
|
|
68
|
+
/** Detailed error description */
|
|
69
|
+
errorDescription: string;
|
|
70
|
+
}
|
|
71
|
+
/** Callback for subscription events. */
|
|
72
|
+
type SubscriptionHandler = (event: unknown) => void;
|
|
73
|
+
/** Active subscription that can be closed. */
|
|
74
|
+
interface Subscription {
|
|
75
|
+
/** Close the subscription and disconnect. */
|
|
76
|
+
close(): void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
declare class DataClient {
|
|
80
|
+
private readonly config;
|
|
81
|
+
private credentials;
|
|
82
|
+
constructor(config: DataClientConfig);
|
|
83
|
+
/**
|
|
84
|
+
* Authenticate with the Dataworks platform using Cognito USER_PASSWORD_AUTH.
|
|
85
|
+
* Must be called before ingest(), reportError(), or subscribe().
|
|
86
|
+
*
|
|
87
|
+
* @param username - Cognito username
|
|
88
|
+
* @param password - Cognito password
|
|
89
|
+
* @returns Login result containing tokens and tenant
|
|
90
|
+
*/
|
|
91
|
+
login(username: string, password: string): Promise<LoginResult>;
|
|
92
|
+
/**
|
|
93
|
+
* Ingest metric data points into the Dataworks Data Engine.
|
|
94
|
+
* Validates each metric before sending — invalid metrics are silently dropped.
|
|
95
|
+
*
|
|
96
|
+
* @param metrics - Array of metric data points
|
|
97
|
+
* @param eventId - Event identifier
|
|
98
|
+
* @param datasetDatasourceId - Dataset-datasource identifier
|
|
99
|
+
* @throws Error if not authenticated or if the request fails
|
|
100
|
+
*/
|
|
101
|
+
ingest(metrics: Metric[], eventId: string, datasetDatasourceId: string): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Report an error to the Dataworks Data Engine.
|
|
104
|
+
*
|
|
105
|
+
* @param error - Error report details
|
|
106
|
+
* @throws Error if not authenticated or if the request fails
|
|
107
|
+
*/
|
|
108
|
+
reportError(error: ErrorReport): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* Subscribe to a real-time data channel on the AppSync Events API.
|
|
111
|
+
* Uses the Cognito JWT for authentication.
|
|
112
|
+
*
|
|
113
|
+
* The channel name maps to the AppSync Events API namespace (e.g. "dataworks/metrics").
|
|
114
|
+
*
|
|
115
|
+
* @param channel - Channel path to subscribe to
|
|
116
|
+
* @param onEvent - Callback invoked for each received event
|
|
117
|
+
* @returns Subscription handle with a close() method
|
|
118
|
+
* @throws Error if not authenticated
|
|
119
|
+
*/
|
|
120
|
+
subscribe(channel: string, onEvent: SubscriptionHandler): Subscription;
|
|
121
|
+
/**
|
|
122
|
+
* Check whether a metric is valid before ingesting.
|
|
123
|
+
* Returns null if valid, or a human-readable reason string if invalid.
|
|
124
|
+
*/
|
|
125
|
+
static validateMetric: (m: RawMetricItem) => string | null;
|
|
126
|
+
/**
|
|
127
|
+
* Filter an array of metrics, keeping only valid ones.
|
|
128
|
+
* Logs a warning for each dropped item.
|
|
129
|
+
*/
|
|
130
|
+
static filterValidMetrics: (metrics: RawMetricItem[], warn: (msg: string) => void) => MetricItem[];
|
|
131
|
+
/** Returns true if the client has been authenticated via login(). */
|
|
132
|
+
get isAuthenticated(): boolean;
|
|
133
|
+
/** Returns the tenant from the last successful login, or null. */
|
|
134
|
+
get tenant(): string | null;
|
|
135
|
+
/** @internal Throws if not authenticated. */
|
|
136
|
+
private requireAuth;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Validation re-exports — thin wrappers around @dataworks/sdk/client validation.
|
|
141
|
+
* Declared locally so the published .d.ts is fully self-contained.
|
|
142
|
+
*
|
|
143
|
+
* @module @dataworks-technology/data
|
|
144
|
+
*/
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check whether a metric is valid.
|
|
148
|
+
* Returns null if valid, or a human-readable reason string if invalid.
|
|
149
|
+
*/
|
|
150
|
+
declare const validateMetric: (m: RawMetricItem) => string | null;
|
|
151
|
+
/**
|
|
152
|
+
* Filter an array of metrics, keeping only valid ones.
|
|
153
|
+
* Logs a warning for each dropped item via the provided warn function.
|
|
154
|
+
*/
|
|
155
|
+
declare const filterValidMetrics: (metrics: RawMetricItem[], warn: (msg: string) => void) => MetricItem[];
|
|
156
|
+
|
|
157
|
+
export { DataClient, type DataClientConfig, type ErrorReport, type LoginResult, type Metric, type MetricItem, type MetricsPayload, type RawMetricItem, type Subscription, type SubscriptionHandler, filterValidMetrics, validateMetric };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for @dataworks/data.
|
|
3
|
+
*
|
|
4
|
+
* @module @dataworks-technology/data
|
|
5
|
+
*/
|
|
6
|
+
/** Configuration for connecting to the Dataworks Data Engine. */
|
|
7
|
+
interface DataClientConfig {
|
|
8
|
+
/** Cognito User Pool endpoint (e.g. "https://cognito-idp.eu-west-1.amazonaws.com/") */
|
|
9
|
+
cognitoEndpoint: string;
|
|
10
|
+
/** Cognito app client ID */
|
|
11
|
+
clientId: string;
|
|
12
|
+
/** Data Engine ingest URL (API Gateway endpoint) */
|
|
13
|
+
ingestUrl: string;
|
|
14
|
+
/** Data Engine error reporting URL (API Gateway endpoint) */
|
|
15
|
+
errorUrl: string;
|
|
16
|
+
/** AppSync Events API endpoint for real-time subscriptions */
|
|
17
|
+
realtimeUrl: string;
|
|
18
|
+
}
|
|
19
|
+
/** Credentials returned from a successful login. */
|
|
20
|
+
interface LoginResult {
|
|
21
|
+
accessToken: string;
|
|
22
|
+
idToken: string;
|
|
23
|
+
refreshToken: string;
|
|
24
|
+
/** Tenant extracted from the JWT claims. */
|
|
25
|
+
tenant: string;
|
|
26
|
+
}
|
|
27
|
+
/** A single metric data point to ingest. */
|
|
28
|
+
interface Metric {
|
|
29
|
+
/** Unique athlete identifier */
|
|
30
|
+
athleteId: string;
|
|
31
|
+
/** Metric name (e.g. "heartrate", "speed", "power") */
|
|
32
|
+
metric: string;
|
|
33
|
+
/** Metric value — number or string */
|
|
34
|
+
value: number | string;
|
|
35
|
+
/** Unix timestamp in seconds */
|
|
36
|
+
timestamp: number;
|
|
37
|
+
}
|
|
38
|
+
/** Validated metric item (all fields confirmed present and correct). */
|
|
39
|
+
interface MetricItem {
|
|
40
|
+
athleteId: string;
|
|
41
|
+
metric: string;
|
|
42
|
+
value: unknown;
|
|
43
|
+
timestamp: number;
|
|
44
|
+
}
|
|
45
|
+
/** Loosely-typed input for validation — all fields are unknown. */
|
|
46
|
+
interface RawMetricItem {
|
|
47
|
+
metric: unknown;
|
|
48
|
+
athleteId: unknown;
|
|
49
|
+
value: unknown;
|
|
50
|
+
timestamp: unknown;
|
|
51
|
+
}
|
|
52
|
+
/** Payload shape for metric ingestion. */
|
|
53
|
+
interface MetricsPayload {
|
|
54
|
+
eventId: string;
|
|
55
|
+
datasetDatasourceId: string;
|
|
56
|
+
metrics: MetricItem[];
|
|
57
|
+
}
|
|
58
|
+
/** Error report to send to the Data Engine. */
|
|
59
|
+
interface ErrorReport {
|
|
60
|
+
/** Dataset-datasource identifier */
|
|
61
|
+
datasetDatasourceId: string;
|
|
62
|
+
/** Event identifier */
|
|
63
|
+
eventId: string;
|
|
64
|
+
/** Client identifier */
|
|
65
|
+
clientId: number;
|
|
66
|
+
/** Short error title */
|
|
67
|
+
errorTitle: string;
|
|
68
|
+
/** Detailed error description */
|
|
69
|
+
errorDescription: string;
|
|
70
|
+
}
|
|
71
|
+
/** Callback for subscription events. */
|
|
72
|
+
type SubscriptionHandler = (event: unknown) => void;
|
|
73
|
+
/** Active subscription that can be closed. */
|
|
74
|
+
interface Subscription {
|
|
75
|
+
/** Close the subscription and disconnect. */
|
|
76
|
+
close(): void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
declare class DataClient {
|
|
80
|
+
private readonly config;
|
|
81
|
+
private credentials;
|
|
82
|
+
constructor(config: DataClientConfig);
|
|
83
|
+
/**
|
|
84
|
+
* Authenticate with the Dataworks platform using Cognito USER_PASSWORD_AUTH.
|
|
85
|
+
* Must be called before ingest(), reportError(), or subscribe().
|
|
86
|
+
*
|
|
87
|
+
* @param username - Cognito username
|
|
88
|
+
* @param password - Cognito password
|
|
89
|
+
* @returns Login result containing tokens and tenant
|
|
90
|
+
*/
|
|
91
|
+
login(username: string, password: string): Promise<LoginResult>;
|
|
92
|
+
/**
|
|
93
|
+
* Ingest metric data points into the Dataworks Data Engine.
|
|
94
|
+
* Validates each metric before sending — invalid metrics are silently dropped.
|
|
95
|
+
*
|
|
96
|
+
* @param metrics - Array of metric data points
|
|
97
|
+
* @param eventId - Event identifier
|
|
98
|
+
* @param datasetDatasourceId - Dataset-datasource identifier
|
|
99
|
+
* @throws Error if not authenticated or if the request fails
|
|
100
|
+
*/
|
|
101
|
+
ingest(metrics: Metric[], eventId: string, datasetDatasourceId: string): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Report an error to the Dataworks Data Engine.
|
|
104
|
+
*
|
|
105
|
+
* @param error - Error report details
|
|
106
|
+
* @throws Error if not authenticated or if the request fails
|
|
107
|
+
*/
|
|
108
|
+
reportError(error: ErrorReport): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* Subscribe to a real-time data channel on the AppSync Events API.
|
|
111
|
+
* Uses the Cognito JWT for authentication.
|
|
112
|
+
*
|
|
113
|
+
* The channel name maps to the AppSync Events API namespace (e.g. "dataworks/metrics").
|
|
114
|
+
*
|
|
115
|
+
* @param channel - Channel path to subscribe to
|
|
116
|
+
* @param onEvent - Callback invoked for each received event
|
|
117
|
+
* @returns Subscription handle with a close() method
|
|
118
|
+
* @throws Error if not authenticated
|
|
119
|
+
*/
|
|
120
|
+
subscribe(channel: string, onEvent: SubscriptionHandler): Subscription;
|
|
121
|
+
/**
|
|
122
|
+
* Check whether a metric is valid before ingesting.
|
|
123
|
+
* Returns null if valid, or a human-readable reason string if invalid.
|
|
124
|
+
*/
|
|
125
|
+
static validateMetric: (m: RawMetricItem) => string | null;
|
|
126
|
+
/**
|
|
127
|
+
* Filter an array of metrics, keeping only valid ones.
|
|
128
|
+
* Logs a warning for each dropped item.
|
|
129
|
+
*/
|
|
130
|
+
static filterValidMetrics: (metrics: RawMetricItem[], warn: (msg: string) => void) => MetricItem[];
|
|
131
|
+
/** Returns true if the client has been authenticated via login(). */
|
|
132
|
+
get isAuthenticated(): boolean;
|
|
133
|
+
/** Returns the tenant from the last successful login, or null. */
|
|
134
|
+
get tenant(): string | null;
|
|
135
|
+
/** @internal Throws if not authenticated. */
|
|
136
|
+
private requireAuth;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Validation re-exports — thin wrappers around @dataworks/sdk/client validation.
|
|
141
|
+
* Declared locally so the published .d.ts is fully self-contained.
|
|
142
|
+
*
|
|
143
|
+
* @module @dataworks-technology/data
|
|
144
|
+
*/
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check whether a metric is valid.
|
|
148
|
+
* Returns null if valid, or a human-readable reason string if invalid.
|
|
149
|
+
*/
|
|
150
|
+
declare const validateMetric: (m: RawMetricItem) => string | null;
|
|
151
|
+
/**
|
|
152
|
+
* Filter an array of metrics, keeping only valid ones.
|
|
153
|
+
* Logs a warning for each dropped item via the provided warn function.
|
|
154
|
+
*/
|
|
155
|
+
declare const filterValidMetrics: (metrics: RawMetricItem[], warn: (msg: string) => void) => MetricItem[];
|
|
156
|
+
|
|
157
|
+
export { DataClient, type DataClientConfig, type ErrorReport, type LoginResult, type Metric, type MetricItem, type MetricsPayload, type RawMetricItem, type Subscription, type SubscriptionHandler, filterValidMetrics, validateMetric };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// ../../node_modules/@dataworks/sdk/dist/client/validation.js
|
|
2
|
+
function validateMetric(m) {
|
|
3
|
+
if (typeof m.metric !== "string" || m.metric.trim() === "")
|
|
4
|
+
return "metric must be a non-empty string";
|
|
5
|
+
if (typeof m.athleteId !== "string" || m.athleteId.trim() === "")
|
|
6
|
+
return "athleteId must be a non-empty string";
|
|
7
|
+
if (!Number.isInteger(m.timestamp) || m.timestamp <= 0)
|
|
8
|
+
return `timestamp must be a positive integer, got ${m.timestamp}`;
|
|
9
|
+
if (m.value === null || m.value === void 0)
|
|
10
|
+
return "value is null/undefined";
|
|
11
|
+
if (typeof m.value === "number" && !isFinite(m.value))
|
|
12
|
+
return `value is non-finite number (${m.value})`;
|
|
13
|
+
if (typeof m.value !== "number" && typeof m.value !== "string")
|
|
14
|
+
return `value type ${typeof m.value} not allowed (must be number or string)`;
|
|
15
|
+
if (typeof m.value === "string" && m.value === "")
|
|
16
|
+
return "value is empty string";
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
function filterValidMetrics(metrics, warn) {
|
|
20
|
+
const valid = [];
|
|
21
|
+
for (const m of metrics) {
|
|
22
|
+
const reason = validateMetric(m);
|
|
23
|
+
if (reason) {
|
|
24
|
+
warn(`Dropping invalid metric [${String(m.metric)}=${JSON.stringify(m.value)}]: ${reason}`);
|
|
25
|
+
} else {
|
|
26
|
+
valid.push(m);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return valid;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ../../node_modules/@dataworks/sdk/dist/client/auth.js
|
|
33
|
+
async function loginWithCredentials(username, password, config) {
|
|
34
|
+
const authParams = {
|
|
35
|
+
USERNAME: username,
|
|
36
|
+
PASSWORD: password
|
|
37
|
+
};
|
|
38
|
+
if (config.clientSecret) {
|
|
39
|
+
const crypto2 = await import("crypto");
|
|
40
|
+
const hash = crypto2.createHmac("sha256", config.clientSecret).update(username + config.clientId).digest("base64");
|
|
41
|
+
authParams.SECRET_HASH = hash;
|
|
42
|
+
}
|
|
43
|
+
const resp = await fetch(config.cognitoEndpoint, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: {
|
|
46
|
+
"X-Amz-Target": "AWSCognitoIdentityProviderService.InitiateAuth",
|
|
47
|
+
"Content-Type": "application/x-amz-json-1.1"
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify({
|
|
50
|
+
AuthFlow: "USER_PASSWORD_AUTH",
|
|
51
|
+
ClientId: config.clientId,
|
|
52
|
+
AuthParameters: authParams
|
|
53
|
+
})
|
|
54
|
+
});
|
|
55
|
+
if (!resp.ok) {
|
|
56
|
+
const body = await resp.text().catch(() => "");
|
|
57
|
+
throw new Error(`loginWithCredentials: Cognito auth failed: ${resp.status} \u2014 ${body}`);
|
|
58
|
+
}
|
|
59
|
+
const data = await resp.json();
|
|
60
|
+
const result = data.AuthenticationResult;
|
|
61
|
+
if (!result?.AccessToken || !result?.IdToken) {
|
|
62
|
+
throw new Error("loginWithCredentials: response missing AccessToken or IdToken");
|
|
63
|
+
}
|
|
64
|
+
const tenant = extractTenantFromJwt(result.IdToken);
|
|
65
|
+
return {
|
|
66
|
+
accessToken: result.AccessToken,
|
|
67
|
+
idToken: result.IdToken,
|
|
68
|
+
refreshToken: result.RefreshToken ?? "",
|
|
69
|
+
tenant
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function extractTenantFromJwt(idToken) {
|
|
73
|
+
try {
|
|
74
|
+
const payload = idToken.split(".")[1];
|
|
75
|
+
const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
|
|
76
|
+
return decoded["custom:tenant"] ?? decoded.tenant ?? decoded["cognito:groups"]?.[0] ?? "";
|
|
77
|
+
} catch {
|
|
78
|
+
return "";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/validation.ts
|
|
83
|
+
var validateMetric2 = validateMetric;
|
|
84
|
+
var filterValidMetrics2 = filterValidMetrics;
|
|
85
|
+
|
|
86
|
+
// src/data-client.ts
|
|
87
|
+
var DataClient = class {
|
|
88
|
+
constructor(config) {
|
|
89
|
+
this.credentials = null;
|
|
90
|
+
this.config = config;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Authenticate with the Dataworks platform using Cognito USER_PASSWORD_AUTH.
|
|
94
|
+
* Must be called before ingest(), reportError(), or subscribe().
|
|
95
|
+
*
|
|
96
|
+
* @param username - Cognito username
|
|
97
|
+
* @param password - Cognito password
|
|
98
|
+
* @returns Login result containing tokens and tenant
|
|
99
|
+
*/
|
|
100
|
+
async login(username, password) {
|
|
101
|
+
this.credentials = await loginWithCredentials(username, password, {
|
|
102
|
+
cognitoEndpoint: this.config.cognitoEndpoint,
|
|
103
|
+
clientId: this.config.clientId
|
|
104
|
+
});
|
|
105
|
+
return this.credentials;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Ingest metric data points into the Dataworks Data Engine.
|
|
109
|
+
* Validates each metric before sending — invalid metrics are silently dropped.
|
|
110
|
+
*
|
|
111
|
+
* @param metrics - Array of metric data points
|
|
112
|
+
* @param eventId - Event identifier
|
|
113
|
+
* @param datasetDatasourceId - Dataset-datasource identifier
|
|
114
|
+
* @throws Error if not authenticated or if the request fails
|
|
115
|
+
*/
|
|
116
|
+
async ingest(metrics, eventId, datasetDatasourceId) {
|
|
117
|
+
this.requireAuth();
|
|
118
|
+
const valid = filterValidMetrics2(
|
|
119
|
+
metrics,
|
|
120
|
+
(msg) => console.warn(`[@dataworks-technology/data] ${msg}`)
|
|
121
|
+
);
|
|
122
|
+
if (valid.length === 0) return;
|
|
123
|
+
const payload = {
|
|
124
|
+
eventId,
|
|
125
|
+
datasetDatasourceId,
|
|
126
|
+
metrics: valid
|
|
127
|
+
};
|
|
128
|
+
const resp = await fetch(this.config.ingestUrl, {
|
|
129
|
+
method: "POST",
|
|
130
|
+
headers: {
|
|
131
|
+
"Content-Type": "application/json",
|
|
132
|
+
Authorization: `Bearer ${this.credentials.accessToken}`
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify(payload)
|
|
135
|
+
});
|
|
136
|
+
if (!resp.ok) {
|
|
137
|
+
const body = await resp.text().catch(() => "");
|
|
138
|
+
throw new Error(
|
|
139
|
+
`[@dataworks-technology/data] ingest failed: ${resp.status} \u2014 ${body}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Report an error to the Dataworks Data Engine.
|
|
145
|
+
*
|
|
146
|
+
* @param error - Error report details
|
|
147
|
+
* @throws Error if not authenticated or if the request fails
|
|
148
|
+
*/
|
|
149
|
+
async reportError(error) {
|
|
150
|
+
this.requireAuth();
|
|
151
|
+
const resp = await fetch(this.config.errorUrl, {
|
|
152
|
+
method: "POST",
|
|
153
|
+
headers: {
|
|
154
|
+
"Content-Type": "application/json",
|
|
155
|
+
Authorization: `Bearer ${this.credentials.accessToken}`
|
|
156
|
+
},
|
|
157
|
+
body: JSON.stringify(error)
|
|
158
|
+
});
|
|
159
|
+
if (!resp.ok) {
|
|
160
|
+
const body = await resp.text().catch(() => "");
|
|
161
|
+
throw new Error(
|
|
162
|
+
`[@dataworks-technology/data] reportError failed: ${resp.status} \u2014 ${body}`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Subscribe to a real-time data channel on the AppSync Events API.
|
|
168
|
+
* Uses the Cognito JWT for authentication.
|
|
169
|
+
*
|
|
170
|
+
* The channel name maps to the AppSync Events API namespace (e.g. "dataworks/metrics").
|
|
171
|
+
*
|
|
172
|
+
* @param channel - Channel path to subscribe to
|
|
173
|
+
* @param onEvent - Callback invoked for each received event
|
|
174
|
+
* @returns Subscription handle with a close() method
|
|
175
|
+
* @throws Error if not authenticated
|
|
176
|
+
*/
|
|
177
|
+
subscribe(channel, onEvent) {
|
|
178
|
+
this.requireAuth();
|
|
179
|
+
const url = new URL(this.config.realtimeUrl);
|
|
180
|
+
url.pathname = "/event/realtime";
|
|
181
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
182
|
+
const ws = new WebSocket(url.toString(), [
|
|
183
|
+
"aws-appsync-event-ws",
|
|
184
|
+
// Encode auth as a base64 subprotocol header
|
|
185
|
+
`header-${btoa(
|
|
186
|
+
JSON.stringify({
|
|
187
|
+
Authorization: this.credentials.accessToken,
|
|
188
|
+
host: new URL(this.config.realtimeUrl).host
|
|
189
|
+
})
|
|
190
|
+
)}`
|
|
191
|
+
]);
|
|
192
|
+
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
|
+
);
|
|
204
|
+
});
|
|
205
|
+
ws.addEventListener("message", (event) => {
|
|
206
|
+
try {
|
|
207
|
+
const msg = JSON.parse(String(event.data));
|
|
208
|
+
if (msg.type === "data") {
|
|
209
|
+
onEvent(JSON.parse(msg.event));
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
return {
|
|
215
|
+
close() {
|
|
216
|
+
ws.close();
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/** Returns true if the client has been authenticated via login(). */
|
|
221
|
+
get isAuthenticated() {
|
|
222
|
+
return this.credentials !== null;
|
|
223
|
+
}
|
|
224
|
+
/** Returns the tenant from the last successful login, or null. */
|
|
225
|
+
get tenant() {
|
|
226
|
+
return this.credentials?.tenant ?? null;
|
|
227
|
+
}
|
|
228
|
+
/** @internal Throws if not authenticated. */
|
|
229
|
+
requireAuth() {
|
|
230
|
+
if (!this.credentials) {
|
|
231
|
+
throw new Error(
|
|
232
|
+
"[@dataworks-technology/data] Not authenticated \u2014 call login() first"
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* Check whether a metric is valid before ingesting.
|
|
239
|
+
* Returns null if valid, or a human-readable reason string if invalid.
|
|
240
|
+
*/
|
|
241
|
+
DataClient.validateMetric = validateMetric2;
|
|
242
|
+
/**
|
|
243
|
+
* Filter an array of metrics, keeping only valid ones.
|
|
244
|
+
* Logs a warning for each dropped item.
|
|
245
|
+
*/
|
|
246
|
+
DataClient.filterValidMetrics = filterValidMetrics2;
|
|
247
|
+
export {
|
|
248
|
+
DataClient,
|
|
249
|
+
filterValidMetrics2 as filterValidMetrics,
|
|
250
|
+
validateMetric2 as validateMetric
|
|
251
|
+
};
|
|
252
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dataworks-technology/data",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Dataworks Data Engine SDK — authenticate, ingest metrics, and subscribe to live data",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"prepublishOnly": "bun run build",
|
|
24
|
+
"publish:patch": "npm version patch && npm publish --@dataworks-technology:registry=https://registry.npmjs.org/",
|
|
25
|
+
"publish:minor": "npm version minor && npm publish --@dataworks-technology:registry=https://registry.npmjs.org/",
|
|
26
|
+
"publish:major": "npm version major && npm publish --@dataworks-technology:registry=https://registry.npmjs.org/"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"registry": "https://registry.npmjs.org/",
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"dataworks",
|
|
34
|
+
"sports",
|
|
35
|
+
"data",
|
|
36
|
+
"metrics",
|
|
37
|
+
"live",
|
|
38
|
+
"sdk"
|
|
39
|
+
],
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/Dataworks-Technology/Dataworks-Data"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@dataworks/sdk": "^1.6.0",
|
|
47
|
+
"tsup": "^8.4.0",
|
|
48
|
+
"typescript": "^5.8.3",
|
|
49
|
+
"vitest": "^3.2.3"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {}
|
|
52
|
+
}
|