@narrative.io/data-collaboration-sdk-ts 3.3.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -0
- package/build/base-api.js +2 -0
- package/build/forecast/types.d.ts +1 -1
- package/build/http-client.d.ts +56 -0
- package/build/http-client.js +43 -1
- package/build/jobs/index.d.ts +1 -1
- package/build/jobs/types.d.ts +2 -0
- package/build/types.d.ts +18 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -311,6 +311,80 @@ try {
|
|
|
311
311
|
}
|
|
312
312
|
```
|
|
313
313
|
|
|
314
|
+
### Centralized Error Handling
|
|
315
|
+
|
|
316
|
+
Instead of repeating `try/catch` normalization at every call site, you can register a
|
|
317
|
+
single `errorTransformer` on the client. It runs once per failed request — for both
|
|
318
|
+
non-2xx responses (as an `HttpError`) and native fetch/network rejections — after the
|
|
319
|
+
`HttpError` (with its parsed `body`) has been built. Whatever it returns is thrown in
|
|
320
|
+
its place, so you can map transport failures onto your own application error types.
|
|
321
|
+
|
|
322
|
+
The SDK only supplies the mechanism; your application decides what a failure *means*.
|
|
323
|
+
The SDK does not log out, retry, redirect, show a toast, or treat any status as fatal
|
|
324
|
+
on your behalf. A `500`, for example, is not automatically application-fatal — that
|
|
325
|
+
policy is yours to define. This keeps the SDK framework-agnostic: no Vue, Nuxt, Pinia,
|
|
326
|
+
or other UI dependency is involved.
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
import {
|
|
330
|
+
NarrativeApi,
|
|
331
|
+
HttpError,
|
|
332
|
+
type HttpRequestContext,
|
|
333
|
+
} from '@narrative.io/data-collaboration-sdk-ts';
|
|
334
|
+
|
|
335
|
+
// Your application's own error types — the SDK does not define these.
|
|
336
|
+
class AuthenticationRequiredError extends Error {}
|
|
337
|
+
class ApiServerError extends Error {}
|
|
338
|
+
|
|
339
|
+
const narrative = new NarrativeApi({
|
|
340
|
+
apiKey: process.env.NARRATIVE_API_KEY!,
|
|
341
|
+
errorTransformer(error: unknown, context: HttpRequestContext) {
|
|
342
|
+
// Only HTTP responses carry a status; network rejections do not.
|
|
343
|
+
if (error instanceof HttpError) {
|
|
344
|
+
const status = error.response.status;
|
|
345
|
+
|
|
346
|
+
if (status === 401) {
|
|
347
|
+
return new AuthenticationRequiredError(
|
|
348
|
+
`Authentication required for ${context.method} ${context.url}`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (status >= 500) {
|
|
353
|
+
return new ApiServerError(`Server error (${status})`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Return `undefined` (or the original `error`) to rethrow it unchanged.
|
|
358
|
+
return undefined;
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Semantics:
|
|
364
|
+
|
|
365
|
+
- The transformer runs **exactly once** per failed request, always after the
|
|
366
|
+
`HttpError` and its parsed `body` are available.
|
|
367
|
+
- **Returning the original `error`** preserves its object identity.
|
|
368
|
+
- **Returning a replacement** causes that value to be thrown instead.
|
|
369
|
+
- **Returning `undefined`** means "rethrow the original error unchanged" — identical
|
|
370
|
+
to having no transformer.
|
|
371
|
+
- If the **transformer itself throws or rejects**, that error propagates and the
|
|
372
|
+
transformer is not invoked a second time.
|
|
373
|
+
- `context` intentionally exposes only `method` and `url`. Request bodies and headers
|
|
374
|
+
(including `Authorization`) are never passed to the transformer.
|
|
375
|
+
|
|
376
|
+
### Custom `fetch` Implementation
|
|
377
|
+
|
|
378
|
+
By default the SDK uses `globalThis.fetch`. You can supply your own implementation —
|
|
379
|
+
useful for tests, instrumentation, or non-browser runtimes:
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
const narrative = new NarrativeApi({
|
|
383
|
+
apiKey: process.env.NARRATIVE_API_KEY!,
|
|
384
|
+
fetch: myCustomFetch, // defaults to globalThis.fetch
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
314
388
|
## API Reference
|
|
315
389
|
|
|
316
390
|
For detailed API documentation, please visit the [Narrative.io API Documentation](https://api.narrative.dev).
|
package/build/base-api.js
CHANGED
|
@@ -23,6 +23,8 @@ export class BaseApi {
|
|
|
23
23
|
baseUrl: this.getBaseUrl(),
|
|
24
24
|
headers: this.constructHeaders(),
|
|
25
25
|
responseAs: config.responseAs ?? "json",
|
|
26
|
+
errorTransformer: config.errorTransformer,
|
|
27
|
+
fetch: config.fetch,
|
|
26
28
|
});
|
|
27
29
|
if (this.verbose) {
|
|
28
30
|
console.info(`Using environment: ${this.environment}`);
|
|
@@ -72,7 +72,7 @@ export interface ForecastDimensions {
|
|
|
72
72
|
distinct_counts: string[];
|
|
73
73
|
group_by?: string;
|
|
74
74
|
}
|
|
75
|
-
export type JobState = "pending" | "running" | "completed" | "pending_cancellation" | "cancelled" | "failed";
|
|
75
|
+
export type JobState = "pending" | "scheduled" | "running" | "completed" | "pending_cancellation" | "cancelled" | "failed";
|
|
76
76
|
export interface ForecastResult {
|
|
77
77
|
cost: number;
|
|
78
78
|
datasets: DatasetForecastResponse[];
|
package/build/http-client.d.ts
CHANGED
|
@@ -11,9 +11,40 @@
|
|
|
11
11
|
* resolves to `null`
|
|
12
12
|
* - throws on any non-2xx status, attaching the {@link Response} and the parsed
|
|
13
13
|
* body to the error
|
|
14
|
+
*
|
|
15
|
+
* An optional {@link HttpErrorTransformer} lets consumers observe or replace the
|
|
16
|
+
* error before it propagates. It runs for both non-2xx responses and network
|
|
17
|
+
* rejections, always after the {@link HttpError} (with its parsed body) has been
|
|
18
|
+
* built. When absent, the original error is thrown unchanged.
|
|
14
19
|
*/
|
|
15
20
|
/** How a response body should be returned from a request. */
|
|
16
21
|
export type ResponseAs = "json" | "text" | "response";
|
|
22
|
+
/** HTTP verbs the SDK issues. */
|
|
23
|
+
export type HttpRequestMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
24
|
+
/**
|
|
25
|
+
* Minimal, side-effect-free description of the request that produced an error.
|
|
26
|
+
*
|
|
27
|
+
* Intentionally excludes the request body and headers (including any
|
|
28
|
+
* `Authorization` header) so an {@link HttpErrorTransformer} cannot leak
|
|
29
|
+
* credentials or payloads.
|
|
30
|
+
*/
|
|
31
|
+
export interface HttpRequestContext {
|
|
32
|
+
/** The HTTP verb used for the request. */
|
|
33
|
+
method: HttpRequestMethod;
|
|
34
|
+
/** The fully-resolved request URL (base URL joined with the endpoint). */
|
|
35
|
+
url: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Hook invoked when a request fails, either with an {@link HttpError} (non-2xx)
|
|
39
|
+
* or a native fetch/network rejection.
|
|
40
|
+
*
|
|
41
|
+
* Return the value to throw in place of the original error. Returning the
|
|
42
|
+
* original `error` preserves its identity. Returning `undefined` means "rethrow
|
|
43
|
+
* the original error unchanged" — the same as having no transformer. If the
|
|
44
|
+
* transformer itself throws (or rejects), that error propagates; the transformer
|
|
45
|
+
* is never invoked a second time for the same request.
|
|
46
|
+
*/
|
|
47
|
+
export type HttpErrorTransformer = (error: unknown, context: HttpRequestContext) => unknown | Promise<unknown>;
|
|
17
48
|
/** Options for constructing an {@link HttpClient}. */
|
|
18
49
|
export interface HttpClientOptions {
|
|
19
50
|
/** Absolute base URL that every request is resolved against. */
|
|
@@ -22,6 +53,16 @@ export interface HttpClientOptions {
|
|
|
22
53
|
headers?: Record<string, string>;
|
|
23
54
|
/** How to interpret response bodies. Defaults to `"json"`. */
|
|
24
55
|
responseAs?: ResponseAs;
|
|
56
|
+
/**
|
|
57
|
+
* Optional hook to observe or transform request failures. See
|
|
58
|
+
* {@link HttpErrorTransformer}.
|
|
59
|
+
*/
|
|
60
|
+
errorTransformer?: HttpErrorTransformer;
|
|
61
|
+
/**
|
|
62
|
+
* Optional `fetch` implementation. Defaults to `globalThis.fetch`, resolved
|
|
63
|
+
* at call time so the ambient global is honored when this is omitted.
|
|
64
|
+
*/
|
|
65
|
+
fetch?: typeof globalThis.fetch;
|
|
25
66
|
}
|
|
26
67
|
/**
|
|
27
68
|
* Error thrown for any non-2xx response. Carries the originating
|
|
@@ -36,10 +77,25 @@ export declare class HttpClient {
|
|
|
36
77
|
private readonly baseUrl;
|
|
37
78
|
private readonly headers;
|
|
38
79
|
private readonly responseAs;
|
|
80
|
+
private readonly errorTransformer?;
|
|
81
|
+
private readonly fetchImpl?;
|
|
39
82
|
constructor(options: HttpClientOptions);
|
|
40
83
|
get<T>(url: string): Promise<T>;
|
|
41
84
|
post<T>(url: string, data?: unknown): Promise<T>;
|
|
42
85
|
put<T>(url: string, data?: unknown): Promise<T>;
|
|
43
86
|
delete<T>(url: string): Promise<T>;
|
|
44
87
|
private request;
|
|
88
|
+
/**
|
|
89
|
+
* Performs the request and parses the response. Throws {@link HttpError} on a
|
|
90
|
+
* non-2xx status; native fetch/network failures reject as they always have.
|
|
91
|
+
* All failures are funneled through {@link transformError} by {@link request}.
|
|
92
|
+
*/
|
|
93
|
+
private send;
|
|
94
|
+
/**
|
|
95
|
+
* Runs the configured {@link HttpErrorTransformer}, if any, and returns the
|
|
96
|
+
* error to throw. Without a transformer, or when the transformer returns
|
|
97
|
+
* `undefined`, the original error is returned unchanged. A transformer that
|
|
98
|
+
* throws/rejects propagates its own error (and is not retried).
|
|
99
|
+
*/
|
|
100
|
+
private transformError;
|
|
45
101
|
}
|
package/build/http-client.js
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
* resolves to `null`
|
|
12
12
|
* - throws on any non-2xx status, attaching the {@link Response} and the parsed
|
|
13
13
|
* body to the error
|
|
14
|
+
*
|
|
15
|
+
* An optional {@link HttpErrorTransformer} lets consumers observe or replace the
|
|
16
|
+
* error before it propagates. It runs for both non-2xx responses and network
|
|
17
|
+
* rejections, always after the {@link HttpError} (with its parsed body) has been
|
|
18
|
+
* built. When absent, the original error is thrown unchanged.
|
|
14
19
|
*/
|
|
15
20
|
/**
|
|
16
21
|
* Error thrown for any non-2xx response. Carries the originating
|
|
@@ -39,9 +44,13 @@ export class HttpClient {
|
|
|
39
44
|
baseUrl;
|
|
40
45
|
headers;
|
|
41
46
|
responseAs;
|
|
47
|
+
errorTransformer;
|
|
48
|
+
fetchImpl;
|
|
42
49
|
constructor(options) {
|
|
43
50
|
this.baseUrl = options.baseUrl;
|
|
44
51
|
this.responseAs = options.responseAs ?? "json";
|
|
52
|
+
this.errorTransformer = options.errorTransformer;
|
|
53
|
+
this.fetchImpl = options.fetch;
|
|
45
54
|
this.headers = {
|
|
46
55
|
Accept: "application/json",
|
|
47
56
|
"Content-Type": "application/json",
|
|
@@ -61,12 +70,33 @@ export class HttpClient {
|
|
|
61
70
|
return this.request("DELETE", url);
|
|
62
71
|
}
|
|
63
72
|
async request(method, url, data) {
|
|
73
|
+
const requestUrl = joinUrl(this.baseUrl, url);
|
|
74
|
+
try {
|
|
75
|
+
return await this.send(method, requestUrl, data);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
throw await this.transformError(error, {
|
|
79
|
+
method: method,
|
|
80
|
+
url: requestUrl,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Performs the request and parses the response. Throws {@link HttpError} on a
|
|
86
|
+
* non-2xx status; native fetch/network failures reject as they always have.
|
|
87
|
+
* All failures are funneled through {@link transformError} by {@link request}.
|
|
88
|
+
*/
|
|
89
|
+
async send(method, requestUrl, data) {
|
|
64
90
|
const init = { method, headers: this.headers };
|
|
65
91
|
// Only POST/PUT/PATCH carry a body.
|
|
66
92
|
if (method[0] === "P" && data !== undefined && data !== null) {
|
|
67
93
|
init.body = JSON.stringify(data);
|
|
68
94
|
}
|
|
69
|
-
|
|
95
|
+
// Resolve the fetch implementation at call time so the ambient global is
|
|
96
|
+
// honored when no custom `fetch` was supplied. Invoked as a bare call so
|
|
97
|
+
// `this` stays undefined, which native fetch requires.
|
|
98
|
+
const fetchImpl = this.fetchImpl ?? globalThis.fetch;
|
|
99
|
+
const response = await fetchImpl(requestUrl, init);
|
|
70
100
|
// Return the raw Response untouched when asked.
|
|
71
101
|
if (this.responseAs === "response") {
|
|
72
102
|
if (response.status >= 200 && response.status < 300) {
|
|
@@ -83,4 +113,16 @@ export class HttpClient {
|
|
|
83
113
|
}
|
|
84
114
|
throw new HttpError(response, body);
|
|
85
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Runs the configured {@link HttpErrorTransformer}, if any, and returns the
|
|
118
|
+
* error to throw. Without a transformer, or when the transformer returns
|
|
119
|
+
* `undefined`, the original error is returned unchanged. A transformer that
|
|
120
|
+
* throws/rejects propagates its own error (and is not retried).
|
|
121
|
+
*/
|
|
122
|
+
async transformError(error, context) {
|
|
123
|
+
if (!this.errorTransformer)
|
|
124
|
+
return error;
|
|
125
|
+
const transformed = await this.errorTransformer(error, context);
|
|
126
|
+
return transformed === undefined ? error : transformed;
|
|
127
|
+
}
|
|
86
128
|
}
|
package/build/jobs/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { BaseApi } from "../base-api";
|
|
|
2
2
|
import type { ApiRecordsV2 } from "../types";
|
|
3
3
|
import type { CalculateAffectedRowsInput, CalculateAffectedRowsJob, CollectAccessRulesBillingDataInput, CollectAccessRulesBillingDataJob, CollectAccessRulesBillingDataResult, ColumnDetails, ColumnStatDataType, CostsInput, CostsJob, CreateTableInput, DatasetsCalculateColumnStatsJob, DatasetsCreateTableJob, DatasetsDeleteTableJob, DatasetsDeliverDataJob, DatasetsEnforceRowTtlRetentionPolicyJob, DatasetsEnforceTableTtlRetentionPolicyJob, DatasetsExecuteDmlJob, DatasetsExecuteSelectJob, DatasetsSampleJob, DatasetsSuggestMappingsJob, DatasetsTruncateTableJob, DeleteInput, DeliverInput, EnabledColumnStatFlags, EnforceRowTtlRetentionPolicyInput, EnforceTableTtlRetentionPolicyInput, ExecuteDmlInput, ExecuteDmlResult, ExecuteSelectInput, ExecuteSelectResult, ExplainInput, ExplainJob, ExplainOutput, ForecastInput, ForecastInternalJob, ForecastJob, HealthCheckJob, Job, JobExecutionCluster, JobRequestSource, JobRequestSourceApiUser, JobRequestSourceProcess, JobType, KnownJob, MaterializedViewInput, MaterializedViewJob, MaterializedViewOutput, MaterializedViewRowStats, ModelInferenceRunInput, ModelInferenceRunJob, ModelInferenceRunJobResult, ModelsDeliverModelInput, ModelsDeliverModelJob, ModelsTrainClassifierJob, ModelTrainingRunInput, ModelTrainingRunJob, RefreshMaterializedViewResult, SampleInput, StatsInput, StatsInputV2, SuggestMappingsInput, SuggestMappingsResult, TruncateTableInput } from "./types";
|
|
4
4
|
import { isKnownJob, KNOWN_JOB_TYPES } from "./types";
|
|
5
|
-
type JobStateFilter = "cancelled" | "completed" | "failed" | "pending" | "pending_cancellation" | "running";
|
|
5
|
+
type JobStateFilter = "cancelled" | "completed" | "failed" | "pending" | "pending_cancellation" | "running" | "scheduled";
|
|
6
6
|
interface GetJobsParameters extends Record<string, string | number | boolean | string[] | undefined> {
|
|
7
7
|
dataset_id?: number;
|
|
8
8
|
per_page?: number;
|
package/build/jobs/types.d.ts
CHANGED
package/build/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ResponseAs } from "./http-client";
|
|
1
|
+
import type { HttpErrorTransformer, ResponseAs } from "./http-client";
|
|
2
2
|
/**
|
|
3
3
|
* Environment for the API.
|
|
4
4
|
*
|
|
@@ -18,6 +18,23 @@ export interface Config {
|
|
|
18
18
|
headers?: Record<string, string>;
|
|
19
19
|
verbose?: boolean;
|
|
20
20
|
responseAs?: ResponseAs;
|
|
21
|
+
/**
|
|
22
|
+
* Optional hook to centrally observe or transform request failures — both
|
|
23
|
+
* non-2xx responses (as an {@link HttpError}) and native fetch/network
|
|
24
|
+
* rejections. Return a replacement error to throw, return the original error
|
|
25
|
+
* to preserve it, or return `undefined` to rethrow it unchanged. With no
|
|
26
|
+
* transformer configured, errors propagate exactly as before.
|
|
27
|
+
*
|
|
28
|
+
* The SDK only supplies the mechanism; the consuming application decides
|
|
29
|
+
* whether a given failure is local, authentication-related, retryable, or
|
|
30
|
+
* fatal. A 500, for instance, is not treated as application-fatal here.
|
|
31
|
+
*/
|
|
32
|
+
errorTransformer?: HttpErrorTransformer;
|
|
33
|
+
/**
|
|
34
|
+
* Optional `fetch` implementation, useful for testing and non-browser
|
|
35
|
+
* runtimes. Defaults to `globalThis.fetch`.
|
|
36
|
+
*/
|
|
37
|
+
fetch?: typeof globalThis.fetch;
|
|
21
38
|
}
|
|
22
39
|
export interface ApiRecordsV2<T> extends PaginationMetadata {
|
|
23
40
|
records: T[];
|