@sinio/sdk 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +117 -2
- package/dist/cjs/client.d.ts +69 -0
- package/dist/cjs/client.js +176 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/errors.d.ts +34 -0
- package/dist/cjs/errors.js +60 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/http.d.ts +22 -0
- package/dist/cjs/http.js +139 -0
- package/dist/cjs/http.js.map +1 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.js +18 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/serialize.d.ts +11 -0
- package/dist/cjs/serialize.js +111 -0
- package/dist/cjs/serialize.js.map +1 -0
- package/dist/cjs/types.d.ts +114 -0
- package/dist/cjs/types.js +3 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/esm/client.d.ts +69 -0
- package/dist/esm/client.js +172 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/errors.d.ts +34 -0
- package/dist/esm/errors.js +49 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/http.d.ts +22 -0
- package/dist/esm/http.js +134 -0
- package/dist/esm/http.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/serialize.d.ts +11 -0
- package/dist/esm/serialize.js +102 -0
- package/dist/esm/serialize.js.map +1 -0
- package/dist/esm/types.d.ts +114 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/package.json +37 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ternatek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,118 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @sinio/sdk — Node.js
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official client for the sinio.ai ingestion API. No runtime dependencies; Node 18+
|
|
4
|
+
(uses the global `fetch`). ESM and CommonJS, typed end to end.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm install @sinio/sdk
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Quick start
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { SinioClient } from "@sinio/sdk";
|
|
14
|
+
|
|
15
|
+
const sinio = new SinioClient({
|
|
16
|
+
apiKey: process.env.SINIO_API_KEY!,
|
|
17
|
+
baseUrl: "https://api.sinio.ai",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const result = await sinio.sendEvents([
|
|
21
|
+
{
|
|
22
|
+
sourceEventId: "evt_88213",
|
|
23
|
+
type: "badge_in",
|
|
24
|
+
occurredAt: "2026-08-28T07:58:12+02:00",
|
|
25
|
+
roles: { device: "BORNE-A12", subject: "P-4582", location: "SITE-A.HALL" },
|
|
26
|
+
payload: { badge: "04A2B9C1", reader_firmware: "2.4.1" },
|
|
27
|
+
},
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
// The four roles (device, subject, location, actor) are read from `roles` only;
|
|
31
|
+
// any other key is refused by the API. `payload` is free and optional ({} by
|
|
32
|
+
// default); readings such as a battery level are read from it as the
|
|
33
|
+
// source declares them. `count` (1 by default) says how many units one event stands for.
|
|
34
|
+
|
|
35
|
+
console.log(result.accepted, result.duplicates, result.rejections);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## What the client does for you
|
|
39
|
+
|
|
40
|
+
- **Chunking.** Pass 50 000 events; the SDK splits them into calls of 500 (the API
|
|
41
|
+
limit) and merges the counters and rejections into one result.
|
|
42
|
+
- **Retries.** 429, 5xx and dropped connections are retried with exponential
|
|
43
|
+
backoff and full jitter, honouring `Retry-After`. This is safe because
|
|
44
|
+
a resend with the same `source_event_id` and the same content is counted in
|
|
45
|
+
`duplicates`, never stored twice.
|
|
46
|
+
- **Local validation.** A timestamp without a UTC offset is rejected before any
|
|
47
|
+
call, with the API's own rule, instead of coming back as an `invalid_occurred_at` rejection.
|
|
48
|
+
- **Typed errors.** `SinioAuthError` (401/403, do not replay),
|
|
49
|
+
`SinioRequestError` (400/404/413, fix the request), `SinioRateLimitError`
|
|
50
|
+
(429, carries `retryAfterSeconds`), `SinioServerError` (5xx, replayable),
|
|
51
|
+
`SinioConnectionError`, `SinioValidationError`.
|
|
52
|
+
|
|
53
|
+
## Partial acceptance
|
|
54
|
+
|
|
55
|
+
A 200 means the batch was **received**, not derived — derivation is asynchronous.
|
|
56
|
+
Within a batch, valid events are accepted even when others are rejected, so always
|
|
57
|
+
read `rejections`:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
for (const rejection of result.rejections) {
|
|
61
|
+
console.warn(rejection.sourceEventId, rejection.code, rejection.message);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`duplicates` and `revisions` are successes, not failures.
|
|
66
|
+
|
|
67
|
+
## Reference data
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
await sinio.upsertLocations([
|
|
71
|
+
{ externalId: "SITE-A", kind: "site", name: "Site A" },
|
|
72
|
+
{ externalId: "SITE-A.HALL", kind: "zone", name: "Hall", parent: "SITE-A" },
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
await sinio.upsertEntities([
|
|
76
|
+
{ externalId: "BORNE-A12", kind: "device", subkind: "nfc_kiosk", location: "SITE-A.HALL",
|
|
77
|
+
attributes: { firmware: "2.4.1" } },
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
await sinio.createAssignments([
|
|
81
|
+
{ credential: "04A2B9C1", subject: "P-4582", relation: "assigned_to",
|
|
82
|
+
validFrom: "2026-01-01T00:00:00+01:00" },
|
|
83
|
+
]);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The reference is never a condition of acceptance: an event pointing at an unknown
|
|
87
|
+
device is still accepted. Upserting is how you enrich it, not how you gate it.
|
|
88
|
+
|
|
89
|
+
## Backfill
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
await sinio.backfillEvents(history);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`mode: "backfill"` persists history without raising alerts; once it has arrived, the
|
|
96
|
+
project's processes replay it in a canonical order. Live catch-up after an outage should stay on
|
|
97
|
+
`sendEvents` — the declared mode decides alerting, not the age of the events.
|
|
98
|
+
|
|
99
|
+
## Webhook key
|
|
100
|
+
|
|
101
|
+
For systems that can only be pointed at a URL and cannot set a header:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
const sinio = new SinioClient({ webhookKey: process.env.SINIO_WEBHOOK_KEY! });
|
|
105
|
+
await sinio.sendEvents(events); // POST /v1/webhooks/<key>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
In this mode only event ingestion is available; the reference endpoints require an
|
|
109
|
+
API key.
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
## Development
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npm install
|
|
116
|
+
npm test # typecheck + node:test
|
|
117
|
+
npm run build
|
|
118
|
+
```
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type FetchLike } from "./http.ts";
|
|
2
|
+
import type { AssignmentInput, AssignmentsResult, EntityInput, LocationInput, SendEventsOptions, SendEventsResult, SinioEvent, ProjectOptions, UpsertResult } from "./types.ts";
|
|
3
|
+
export declare const SDK_VERSION = "0.1.0";
|
|
4
|
+
export interface SinioClientOptions {
|
|
5
|
+
/** Machine credential, sent as `Authorization: Bearer`. Mutually exclusive with `webhookKey`. */
|
|
6
|
+
readonly apiKey?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Key carried in the URL instead of a header, for systems that can only be
|
|
9
|
+
* pointed at a webhook URL. Only `sendEvents` is available in this mode.
|
|
10
|
+
*/
|
|
11
|
+
readonly webhookKey?: string;
|
|
12
|
+
/** Defaults to `https://api.sinio.ai`. */
|
|
13
|
+
readonly baseUrl?: string;
|
|
14
|
+
/** Sent as `X-Source-Code`; optional when the key is already bound to a source. */
|
|
15
|
+
readonly sourceCode?: string;
|
|
16
|
+
/** Default project `external_id`, for publisher-level keys covering several projects. */
|
|
17
|
+
readonly project?: string;
|
|
18
|
+
/** Per-request timeout, default 30 s. */
|
|
19
|
+
readonly timeoutMs?: number;
|
|
20
|
+
/** Retries on 429, 5xx and dropped connections. Default 3. */
|
|
21
|
+
readonly maxRetries?: number;
|
|
22
|
+
/** First backoff ceiling, doubled per attempt with full jitter. Default 500 ms. */
|
|
23
|
+
readonly retryBaseDelayMs?: number;
|
|
24
|
+
/** Injectable transport, for tests or for Node < 18. */
|
|
25
|
+
readonly fetch?: FetchLike;
|
|
26
|
+
/** Injectable sleep, so tests do not wait on retries. */
|
|
27
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Client for the sinio.ai ingestion API.
|
|
31
|
+
*
|
|
32
|
+
* Every method accepts an arbitrarily long list and splits it into calls of
|
|
33
|
+
* 500 items, the API limit, merging the counters and rejections back into a
|
|
34
|
+
* single result. Sending the same events twice is safe: idempotency is keyed
|
|
35
|
+
* on `(project, source, source_event_id, revision)`, so a replay is counted as
|
|
36
|
+
* a duplicate rather than stored again.
|
|
37
|
+
*/
|
|
38
|
+
export declare class SinioClient {
|
|
39
|
+
private readonly transport;
|
|
40
|
+
private readonly authHeaders;
|
|
41
|
+
private readonly eventsPath;
|
|
42
|
+
private readonly webhookMode;
|
|
43
|
+
private readonly sourceCode;
|
|
44
|
+
private readonly project;
|
|
45
|
+
constructor(options: SinioClientOptions);
|
|
46
|
+
/** `POST /v1/events` — live ingestion. Derivation is asynchronous: a 200 confirms receipt only. */
|
|
47
|
+
sendEvents(events: readonly SinioEvent[], options?: SendEventsOptions): Promise<SendEventsResult>;
|
|
48
|
+
/**
|
|
49
|
+
* Same endpoint with `mode: "backfill"`: history is persisted but nothing is
|
|
50
|
+
* alerted, and derivation is replayed later in a canonical order.
|
|
51
|
+
*/
|
|
52
|
+
backfillEvents(events: readonly SinioEvent[], options?: Omit<SendEventsOptions, "mode">): Promise<SendEventsResult>;
|
|
53
|
+
/**
|
|
54
|
+
* `POST /v1/entities` — bulk upsert keyed on `(kind, external_id)`. An unknown
|
|
55
|
+
* `subkind` never fails the call: it is created undeclared, for someone
|
|
56
|
+
* to qualify in the console.
|
|
57
|
+
*/
|
|
58
|
+
upsertEntities(entities: readonly EntityInput[], options?: ProjectOptions): Promise<UpsertResult>;
|
|
59
|
+
/** `POST /v1/locations` — bulk upsert of the location tree, keyed on `external_id`. */
|
|
60
|
+
upsertLocations(locations: readonly LocationInput[], options?: ProjectOptions): Promise<UpsertResult>;
|
|
61
|
+
/**
|
|
62
|
+
* `POST /v1/assignments` — `credential -> subject` with a validity range. An
|
|
63
|
+
* overlap is rejected with `overlapping_assignment`, never merged silently.
|
|
64
|
+
*/
|
|
65
|
+
createAssignments(assignments: readonly AssignmentInput[], options?: ProjectOptions): Promise<AssignmentsResult>;
|
|
66
|
+
private upsertReference;
|
|
67
|
+
private withProject;
|
|
68
|
+
private assertBearerMode;
|
|
69
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SinioClient = exports.SDK_VERSION = void 0;
|
|
4
|
+
const errors_ts_1 = require("./errors.js");
|
|
5
|
+
const http_ts_1 = require("./http.js");
|
|
6
|
+
const serialize_ts_1 = require("./serialize.js");
|
|
7
|
+
exports.SDK_VERSION = "0.1.0";
|
|
8
|
+
/**
|
|
9
|
+
* Client for the sinio.ai ingestion API.
|
|
10
|
+
*
|
|
11
|
+
* Every method accepts an arbitrarily long list and splits it into calls of
|
|
12
|
+
* 500 items, the API limit, merging the counters and rejections back into a
|
|
13
|
+
* single result. Sending the same events twice is safe: idempotency is keyed
|
|
14
|
+
* on `(project, source, source_event_id, revision)`, so a replay is counted as
|
|
15
|
+
* a duplicate rather than stored again.
|
|
16
|
+
*/
|
|
17
|
+
class SinioClient {
|
|
18
|
+
transport;
|
|
19
|
+
authHeaders;
|
|
20
|
+
eventsPath;
|
|
21
|
+
webhookMode;
|
|
22
|
+
sourceCode;
|
|
23
|
+
project;
|
|
24
|
+
constructor(options) {
|
|
25
|
+
const hasApiKey = typeof options.apiKey === "string" && options.apiKey.length > 0;
|
|
26
|
+
const hasWebhookKey = typeof options.webhookKey === "string" && options.webhookKey.length > 0;
|
|
27
|
+
if (hasApiKey === hasWebhookKey) {
|
|
28
|
+
throw new errors_ts_1.SinioValidationError("Provide exactly one of `apiKey` or `webhookKey`.");
|
|
29
|
+
}
|
|
30
|
+
this.webhookMode = hasWebhookKey;
|
|
31
|
+
this.authHeaders = hasApiKey ? { authorization: `Bearer ${options.apiKey}` } : {};
|
|
32
|
+
this.eventsPath = hasWebhookKey ? `/v1/webhooks/${encodeURIComponent(options.webhookKey)}` : "/v1/events";
|
|
33
|
+
this.sourceCode = options.sourceCode;
|
|
34
|
+
this.project = options.project;
|
|
35
|
+
this.transport = new http_ts_1.Transport({
|
|
36
|
+
baseUrl: options.baseUrl ?? "https://api.sinio.ai",
|
|
37
|
+
timeoutMs: options.timeoutMs ?? 30_000,
|
|
38
|
+
maxRetries: options.maxRetries ?? 3,
|
|
39
|
+
retryBaseDelayMs: options.retryBaseDelayMs ?? 500,
|
|
40
|
+
userAgent: `sinio-node/${exports.SDK_VERSION}`,
|
|
41
|
+
fetch: options.fetch,
|
|
42
|
+
sleep: options.sleep,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/** `POST /v1/events` — live ingestion. Derivation is asynchronous: a 200 confirms receipt only. */
|
|
46
|
+
async sendEvents(events, options = {}) {
|
|
47
|
+
const wire = events.map((event, index) => (0, serialize_ts_1.toWireEvent)(this.project === undefined || event.project !== undefined ? event : { ...event, project: this.project }, index));
|
|
48
|
+
const sourceCode = options.sourceCode ?? this.sourceCode;
|
|
49
|
+
const headers = sourceCode === undefined ? {} : { "x-source-code": sourceCode };
|
|
50
|
+
const merged = {
|
|
51
|
+
batchIds: [],
|
|
52
|
+
accepted: 0,
|
|
53
|
+
pendingClassification: 0,
|
|
54
|
+
duplicates: 0,
|
|
55
|
+
revisions: 0,
|
|
56
|
+
rejected: 0,
|
|
57
|
+
rejections: [],
|
|
58
|
+
testMode: false,
|
|
59
|
+
};
|
|
60
|
+
for (const part of (0, serialize_ts_1.chunk)(wire, serialize_ts_1.MAX_ITEMS_PER_CALL)) {
|
|
61
|
+
const body = { events: part, mode: options.mode ?? "webhook" };
|
|
62
|
+
const response = asRecord(await this.transport.post(this.eventsPath, body, { ...this.authHeaders, ...headers }));
|
|
63
|
+
const batchId = asString(response["batch_id"]);
|
|
64
|
+
if (batchId !== null) {
|
|
65
|
+
merged.batchIds.push(batchId);
|
|
66
|
+
}
|
|
67
|
+
Object.assign(merged, {
|
|
68
|
+
accepted: merged.accepted + asNumber(response["accepted"]),
|
|
69
|
+
pendingClassification: merged.pendingClassification + asNumber(response["pending_classification"]),
|
|
70
|
+
duplicates: merged.duplicates + asNumber(response["duplicates"]),
|
|
71
|
+
revisions: merged.revisions + asNumber(response["revisions"]),
|
|
72
|
+
rejected: merged.rejected + asNumber(response["rejected"]),
|
|
73
|
+
testMode: merged.testMode || response["test_mode"] === true,
|
|
74
|
+
});
|
|
75
|
+
for (const rejection of asArray(response["rejections"])) {
|
|
76
|
+
const item = asRecord(rejection);
|
|
77
|
+
merged.rejections.push({
|
|
78
|
+
sourceEventId: asString(item["source_event_id"]) ?? "",
|
|
79
|
+
code: asString(item["code"]) ?? "unknown",
|
|
80
|
+
message: asString(item["message"]) ?? "",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return merged;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Same endpoint with `mode: "backfill"`: history is persisted but nothing is
|
|
88
|
+
* alerted, and derivation is replayed later in a canonical order.
|
|
89
|
+
*/
|
|
90
|
+
async backfillEvents(events, options = {}) {
|
|
91
|
+
return this.sendEvents(events, { ...options, mode: "backfill" });
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* `POST /v1/entities` — bulk upsert keyed on `(kind, external_id)`. An unknown
|
|
95
|
+
* `subkind` never fails the call: it is created undeclared, for someone
|
|
96
|
+
* to qualify in the console.
|
|
97
|
+
*/
|
|
98
|
+
async upsertEntities(entities, options = {}) {
|
|
99
|
+
return this.upsertReference("/v1/entities", "entities", entities.map(serialize_ts_1.toWireEntity), options);
|
|
100
|
+
}
|
|
101
|
+
/** `POST /v1/locations` — bulk upsert of the location tree, keyed on `external_id`. */
|
|
102
|
+
async upsertLocations(locations, options = {}) {
|
|
103
|
+
return this.upsertReference("/v1/locations", "locations", locations.map(serialize_ts_1.toWireLocation), options);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* `POST /v1/assignments` — `credential -> subject` with a validity range. An
|
|
107
|
+
* overlap is rejected with `overlapping_assignment`, never merged silently.
|
|
108
|
+
*/
|
|
109
|
+
async createAssignments(assignments, options = {}) {
|
|
110
|
+
this.assertBearerMode("createAssignments");
|
|
111
|
+
const wire = assignments.map(serialize_ts_1.toWireAssignment);
|
|
112
|
+
const merged = { accepted: 0, rejected: 0, rejections: [] };
|
|
113
|
+
for (const part of (0, serialize_ts_1.chunk)(wire, serialize_ts_1.MAX_ITEMS_PER_CALL)) {
|
|
114
|
+
const response = asRecord(await this.transport.post("/v1/assignments", this.withProject({ assignments: part }, options), this.authHeaders));
|
|
115
|
+
Object.assign(merged, {
|
|
116
|
+
accepted: merged.accepted + asNumber(response["accepted"]),
|
|
117
|
+
rejected: merged.rejected + asNumber(response["rejected"]),
|
|
118
|
+
});
|
|
119
|
+
for (const rejection of asArray(response["rejections"])) {
|
|
120
|
+
const item = asRecord(rejection);
|
|
121
|
+
merged.rejections.push({
|
|
122
|
+
credential: asString(item["credential"]) ?? "",
|
|
123
|
+
subject: asString(item["subject"]) ?? "",
|
|
124
|
+
code: asString(item["code"]) ?? "unknown",
|
|
125
|
+
message: asString(item["message"]) ?? "",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return merged;
|
|
130
|
+
}
|
|
131
|
+
async upsertReference(path, key, wire, options) {
|
|
132
|
+
this.assertBearerMode(path);
|
|
133
|
+
const merged = { accepted: 0, rejected: 0, rejections: [] };
|
|
134
|
+
for (const part of (0, serialize_ts_1.chunk)(wire, serialize_ts_1.MAX_ITEMS_PER_CALL)) {
|
|
135
|
+
const response = asRecord(await this.transport.post(path, this.withProject({ [key]: part }, options), this.authHeaders));
|
|
136
|
+
Object.assign(merged, {
|
|
137
|
+
accepted: merged.accepted + asNumber(response["accepted"]),
|
|
138
|
+
rejected: merged.rejected + asNumber(response["rejected"]),
|
|
139
|
+
});
|
|
140
|
+
for (const rejection of asArray(response["rejections"])) {
|
|
141
|
+
const item = asRecord(rejection);
|
|
142
|
+
merged.rejections.push({
|
|
143
|
+
externalId: asString(item["external_id"]) ?? "",
|
|
144
|
+
code: asString(item["code"]) ?? "unknown",
|
|
145
|
+
message: asString(item["message"]) ?? "",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return merged;
|
|
150
|
+
}
|
|
151
|
+
withProject(body, options) {
|
|
152
|
+
const project = options.project ?? this.project;
|
|
153
|
+
return project === undefined ? body : { project, ...body };
|
|
154
|
+
}
|
|
155
|
+
assertBearerMode(operation) {
|
|
156
|
+
if (this.webhookMode) {
|
|
157
|
+
throw new errors_ts_1.SinioValidationError(`${operation} requires an API key: a webhook key only authenticates event ingestion.`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
exports.SinioClient = SinioClient;
|
|
162
|
+
function asRecord(value) {
|
|
163
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
164
|
+
? value
|
|
165
|
+
: {};
|
|
166
|
+
}
|
|
167
|
+
function asArray(value) {
|
|
168
|
+
return Array.isArray(value) ? value : [];
|
|
169
|
+
}
|
|
170
|
+
function asNumber(value) {
|
|
171
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
172
|
+
}
|
|
173
|
+
function asString(value) {
|
|
174
|
+
return typeof value === "string" ? value : null;
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":";;;AAAA,2CAAmD;AACnD,uCAAsD;AACtD,iDAOwB;AAgBX,QAAA,WAAW,GAAG,OAAO,CAAC;AA4BnC;;;;;;;;GAQG;AACH,MAAa,WAAW;IACL,SAAS,CAAY;IACrB,WAAW,CAAyB;IACpC,UAAU,CAAS;IACnB,WAAW,CAAU;IACrB,UAAU,CAAqB;IAC/B,OAAO,CAAqB;IAE7C,YAAY,OAA2B;QACrC,MAAM,SAAS,GAAG,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;QAClF,MAAM,aAAa,GAAG,OAAO,OAAO,CAAC,UAAU,KAAK,QAAQ,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;QAC9F,IAAI,SAAS,KAAK,aAAa,EAAE,CAAC;YAChC,MAAM,IAAI,gCAAoB,CAAC,kDAAkD,CAAC,CAAC;QACrF,CAAC;QAED,IAAI,CAAC,WAAW,GAAG,aAAa,CAAC;QACjC,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,UAAU,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClF,IAAI,CAAC,UAAU,GAAG,aAAa,CAAC,CAAC,CAAC,gBAAgB,kBAAkB,CAAC,OAAO,CAAC,UAAW,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC;QAC3G,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,SAAS,GAAG,IAAI,mBAAS,CAAC;YAC7B,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,sBAAsB;YAClD,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,MAAM;YACtC,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,CAAC;YACnC,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,IAAI,GAAG;YACjD,SAAS,EAAE,cAAc,mBAAW,EAAE;YACtC,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,KAAK,EAAE,OAAO,CAAC,KAAK;SACrB,CAAC,CAAC;IACL,CAAC;IAED,mGAAmG;IACnG,KAAK,CAAC,UAAU,CAAC,MAA6B,EAAE,UAA6B,EAAE;QAC7E,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CACvC,IAAA,0BAAW,EAAC,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,CAC5H,CAAC;QAEF,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC;QACzD,MAAM,OAAO,GAA2B,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC;QACxG,MAAM,MAAM,GAAqB;YAC/B,QAAQ,EAAE,EAAE;YACZ,QAAQ,EAAE,CAAC;YACX,qBAAqB,EAAE,CAAC;YACxB,UAAU,EAAE,CAAC;YACb,SAAS,EAAE,CAAC;YACZ,QAAQ,EAAE,CAAC;YACX,UAAU,EAAE,EAAE;YACd,QAAQ,EAAE,KAAK;SAChB,CAAC;QAEF,KAAK,MAAM,IAAI,IAAI,IAAA,oBAAK,EAAC,IAAI,EAAE,iCAAkB,CAAC,EAAE,CAAC;YACnD,MAAM,IAAI,GAA4B,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,SAAS,EAAE,CAAC;YACxF,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC;YACjH,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;YAC/C,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrB,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAChC,CAAC;YACD,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE;gBACpB,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;gBAC1D,qBAAqB,EAAE,MAAM,CAAC,qBAAqB,GAAG,QAAQ,CAAC,QAAQ,CAAC,wBAAwB,CAAC,CAAC;gBAClG,UAAU,EAAE,MAAM,CAAC,UAAU,GAAG,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;gBAChE,SAAS,EAAE,MAAM,CAAC,SAAS,GAAG,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;gBAC7D,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;gBAC1D,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,QAAQ,CAAC,WAAW,CAAC,KAAK,IAAI;aAC5D,CAAC,CAAC;YACH,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;gBACxD,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;gBACjC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC;oBACrB,aAAa,EAAE,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,IAAI,EAAE;oBACtD,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,SAAS;oBACzC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE;iBAChB,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,cAAc,CAClB,MAA6B,EAC7B,UAA2C,EAAE;QAE7C,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;IACnE,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,cAAc,CAAC,QAAgC,EAAE,UAA0B,EAAE;QACjF,OAAO,IAAI,CAAC,eAAe,CAAC,cAAc,EAAE,UAAU,EAAE,QAAQ,CAAC,GAAG,CAAC,2BAAY,CAAC,EAAE,OAAO,CAAC,CAAC;IAC/F,CAAC;IAED,uFAAuF;IACvF,KAAK,CAAC,eAAe,CAAC,SAAmC,EAAE,UAA0B,EAAE;QACrF,OAAO,IAAI,CAAC,eAAe,CAAC,eAAe,EAAE,WAAW,EAAE,SAAS,CAAC,GAAG,CAAC,6BAAc,CAAC,EAAE,OAAO,CAAC,CAAC;IACpG,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,iBAAiB,CACrB,WAAuC,EACvC,UAA0B,EAAE;QAE5B,IAAI,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,CAAC;QAC3C,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,+BAAgB,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAsB,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;QAE/E,KAAK,MAAM,IAAI,IAAI,IAAA,oBAAK,EAAC,IAAI,EAAE,iCAAkB,CAAC,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,QAAQ,CACvB,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,iBAAiB,EAAE,IAAI,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,CACjH,CAAC;YACF,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE;gBACpB,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;gBAC1D,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;aAC3D,CAAC,CAAC;YACH,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;gBACxD,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;gBACjC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC;oBACrB,UAAU,EAAE,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE;oBAC9C,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE;oBACxC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,SAAS;oBACzC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE;iBACX,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAEO,KAAK,CAAC,eAAe,CAC3B,IAAY,EACZ,GAAW,EACX,IAAwC,EACxC,OAAuB;QAEvB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAC5B,MAAM,MAAM,GAAiB,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;QAE1E,KAAK,MAAM,IAAI,IAAI,IAAA,oBAAK,EAAC,IAAI,EAAE,iCAAkB,CAAC,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,QAAQ,CACvB,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,CAC9F,CAAC;YACF,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE;gBACpB,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;gBAC1D,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;aAC3D,CAAC,CAAC;YACH,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;gBACxD,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;gBACjC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC;oBACrB,UAAU,EAAE,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,IAAI,EAAE;oBAC/C,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,SAAS;oBACzC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE;iBACZ,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAEO,WAAW,CAAC,IAA6B,EAAE,OAAuB;QACxE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC;QAChD,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC;IAC7D,CAAC;IAEO,gBAAgB,CAAC,SAAiB;QACxC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,MAAM,IAAI,gCAAoB,CAC5B,GAAG,SAAS,yEAAyE,CACtF,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AAnLD,kCAmLC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACzE,CAAC,CAAE,KAAiC;QACpC,CAAC,CAAC,EAAE,CAAC;AACT,CAAC;AAED,SAAS,OAAO,CAAC,KAAc;IAC7B,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAC3C,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Base class for every error the SDK throws. */
|
|
2
|
+
export declare class SinioError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
/** The request never got a usable HTTP response (DNS, TCP, TLS, timeout). */
|
|
6
|
+
export declare class SinioConnectionError extends SinioError {
|
|
7
|
+
readonly reason: unknown;
|
|
8
|
+
constructor(message: string, reason?: unknown);
|
|
9
|
+
}
|
|
10
|
+
/** A 4xx or 5xx response. */
|
|
11
|
+
export declare class SinioApiError extends SinioError {
|
|
12
|
+
readonly status: number;
|
|
13
|
+
readonly body: unknown;
|
|
14
|
+
/** Rejection code returned by the API, when the body carries one. */
|
|
15
|
+
readonly code: string | null;
|
|
16
|
+
constructor(message: string, status: number, body: unknown, code?: string | null);
|
|
17
|
+
}
|
|
18
|
+
/** 401 / 403 — invalid, revoked, or insufficiently scoped key. Never replay. */
|
|
19
|
+
export declare class SinioAuthError extends SinioApiError {
|
|
20
|
+
}
|
|
21
|
+
/** 400 / 404 / 413 — the request itself is wrong. Fix it before replaying. */
|
|
22
|
+
export declare class SinioRequestError extends SinioApiError {
|
|
23
|
+
}
|
|
24
|
+
/** 429 — quota exceeded. `retryAfterSeconds` mirrors the `Retry-After` header. */
|
|
25
|
+
export declare class SinioRateLimitError extends SinioApiError {
|
|
26
|
+
readonly retryAfterSeconds: number | null;
|
|
27
|
+
constructor(message: string, status: number, body: unknown, retryAfterSeconds: number | null);
|
|
28
|
+
}
|
|
29
|
+
/** 5xx — temporary. Safe to replay verbatim: ingestion is idempotent. */
|
|
30
|
+
export declare class SinioServerError extends SinioApiError {
|
|
31
|
+
}
|
|
32
|
+
/** Raised before any network call, when the input cannot be a valid request. */
|
|
33
|
+
export declare class SinioValidationError extends SinioError {
|
|
34
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SinioValidationError = exports.SinioServerError = exports.SinioRateLimitError = exports.SinioRequestError = exports.SinioAuthError = exports.SinioApiError = exports.SinioConnectionError = exports.SinioError = void 0;
|
|
4
|
+
/** Base class for every error the SDK throws. */
|
|
5
|
+
class SinioError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = new.target.name;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.SinioError = SinioError;
|
|
12
|
+
/** The request never got a usable HTTP response (DNS, TCP, TLS, timeout). */
|
|
13
|
+
class SinioConnectionError extends SinioError {
|
|
14
|
+
reason;
|
|
15
|
+
constructor(message, reason) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.reason = reason;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.SinioConnectionError = SinioConnectionError;
|
|
21
|
+
/** A 4xx or 5xx response. */
|
|
22
|
+
class SinioApiError extends SinioError {
|
|
23
|
+
status;
|
|
24
|
+
body;
|
|
25
|
+
/** Rejection code returned by the API, when the body carries one. */
|
|
26
|
+
code;
|
|
27
|
+
constructor(message, status, body, code = null) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.status = status;
|
|
30
|
+
this.body = body;
|
|
31
|
+
this.code = code;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.SinioApiError = SinioApiError;
|
|
35
|
+
/** 401 / 403 — invalid, revoked, or insufficiently scoped key. Never replay. */
|
|
36
|
+
class SinioAuthError extends SinioApiError {
|
|
37
|
+
}
|
|
38
|
+
exports.SinioAuthError = SinioAuthError;
|
|
39
|
+
/** 400 / 404 / 413 — the request itself is wrong. Fix it before replaying. */
|
|
40
|
+
class SinioRequestError extends SinioApiError {
|
|
41
|
+
}
|
|
42
|
+
exports.SinioRequestError = SinioRequestError;
|
|
43
|
+
/** 429 — quota exceeded. `retryAfterSeconds` mirrors the `Retry-After` header. */
|
|
44
|
+
class SinioRateLimitError extends SinioApiError {
|
|
45
|
+
retryAfterSeconds;
|
|
46
|
+
constructor(message, status, body, retryAfterSeconds) {
|
|
47
|
+
super(message, status, body);
|
|
48
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.SinioRateLimitError = SinioRateLimitError;
|
|
52
|
+
/** 5xx — temporary. Safe to replay verbatim: ingestion is idempotent. */
|
|
53
|
+
class SinioServerError extends SinioApiError {
|
|
54
|
+
}
|
|
55
|
+
exports.SinioServerError = SinioServerError;
|
|
56
|
+
/** Raised before any network call, when the input cannot be a valid request. */
|
|
57
|
+
class SinioValidationError extends SinioError {
|
|
58
|
+
}
|
|
59
|
+
exports.SinioValidationError = SinioValidationError;
|
|
60
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":";;;AAAA,iDAAiD;AACjD,MAAa,UAAW,SAAQ,KAAK;IACnC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;IAC9B,CAAC;CACF;AALD,gCAKC;AAED,6EAA6E;AAC7E,MAAa,oBAAqB,SAAQ,UAAU;IACzC,MAAM,CAAU;IAEzB,YAAY,OAAe,EAAE,MAAgB;QAC3C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAPD,oDAOC;AAED,6BAA6B;AAC7B,MAAa,aAAc,SAAQ,UAAU;IAClC,MAAM,CAAS;IACf,IAAI,CAAU;IACvB,qEAAqE;IAC5D,IAAI,CAAgB;IAE7B,YAAY,OAAe,EAAE,MAAc,EAAE,IAAa,EAAE,OAAsB,IAAI;QACpF,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAZD,sCAYC;AAED,gFAAgF;AAChF,MAAa,cAAe,SAAQ,aAAa;CAAG;AAApD,wCAAoD;AAEpD,8EAA8E;AAC9E,MAAa,iBAAkB,SAAQ,aAAa;CAAG;AAAvD,8CAAuD;AAEvD,kFAAkF;AAClF,MAAa,mBAAoB,SAAQ,aAAa;IAC3C,iBAAiB,CAAgB;IAE1C,YAAY,OAAe,EAAE,MAAc,EAAE,IAAa,EAAE,iBAAgC;QAC1F,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC,iBAAiB,GAAG,iBAAiB,CAAC;IAC7C,CAAC;CACF;AAPD,kDAOC;AAED,yEAAyE;AACzE,MAAa,gBAAiB,SAAQ,aAAa;CAAG;AAAtD,4CAAsD;AAEtD,gFAAgF;AAChF,MAAa,oBAAqB,SAAQ,UAAU;CAAG;AAAvD,oDAAuD"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type FetchLike = (input: string, init: RequestInit) => Promise<Response>;
|
|
2
|
+
export interface TransportOptions {
|
|
3
|
+
readonly baseUrl: string;
|
|
4
|
+
readonly timeoutMs: number;
|
|
5
|
+
readonly maxRetries: number;
|
|
6
|
+
readonly retryBaseDelayMs: number;
|
|
7
|
+
readonly userAgent: string;
|
|
8
|
+
readonly fetch?: FetchLike;
|
|
9
|
+
/** Injected in tests so retries do not actually wait. */
|
|
10
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
/** `Retry-After` is either a delay in seconds or an HTTP date. Both are accepted. */
|
|
13
|
+
export declare function parseRetryAfter(header: string | null, now?: number): number | null;
|
|
14
|
+
export declare class Transport {
|
|
15
|
+
private readonly options;
|
|
16
|
+
private readonly fetchImpl;
|
|
17
|
+
private readonly sleep;
|
|
18
|
+
constructor(options: TransportOptions);
|
|
19
|
+
post(path: string, body: unknown, headers: Record<string, string>): Promise<unknown>;
|
|
20
|
+
/** Exponential backoff with full jitter, unless the server told us how long to wait. */
|
|
21
|
+
private backoffMs;
|
|
22
|
+
}
|
package/dist/cjs/http.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Transport = void 0;
|
|
4
|
+
exports.parseRetryAfter = parseRetryAfter;
|
|
5
|
+
const errors_ts_1 = require("./errors.js");
|
|
6
|
+
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
/** `Retry-After` is either a delay in seconds or an HTTP date. Both are accepted. */
|
|
8
|
+
function parseRetryAfter(header, now = Date.now()) {
|
|
9
|
+
if (header === null) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const seconds = Number(header);
|
|
13
|
+
if (Number.isFinite(seconds)) {
|
|
14
|
+
return Math.max(0, seconds);
|
|
15
|
+
}
|
|
16
|
+
const date = Date.parse(header);
|
|
17
|
+
if (Number.isNaN(date)) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return Math.max(0, (date - now) / 1000);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A 429 or a 5xx is retried, and so is a dropped connection: ingestion is
|
|
24
|
+
* idempotent, so a replay can only ever be counted as a duplicate. A 4xx that
|
|
25
|
+
* is not a 429 is the caller's fault and is raised immediately.
|
|
26
|
+
*/
|
|
27
|
+
function isRetriableStatus(status) {
|
|
28
|
+
return status === 429 || status >= 500;
|
|
29
|
+
}
|
|
30
|
+
class Transport {
|
|
31
|
+
options;
|
|
32
|
+
fetchImpl;
|
|
33
|
+
sleep;
|
|
34
|
+
constructor(options) {
|
|
35
|
+
this.options = options;
|
|
36
|
+
const provided = options.fetch ?? globalThis.fetch;
|
|
37
|
+
if (provided === undefined) {
|
|
38
|
+
throw new Error("No global fetch available; pass `fetch` in the client options (Node < 18).");
|
|
39
|
+
}
|
|
40
|
+
this.fetchImpl = provided;
|
|
41
|
+
this.sleep = options.sleep ?? defaultSleep;
|
|
42
|
+
}
|
|
43
|
+
async post(path, body, headers) {
|
|
44
|
+
const url = `${this.options.baseUrl.replace(/\/+$/, "")}${path}`;
|
|
45
|
+
const payload = JSON.stringify(body);
|
|
46
|
+
let attempt = 0;
|
|
47
|
+
for (;;) {
|
|
48
|
+
let response;
|
|
49
|
+
try {
|
|
50
|
+
response = await this.fetchImpl(url, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: {
|
|
53
|
+
"content-type": "application/json",
|
|
54
|
+
accept: "application/json",
|
|
55
|
+
"user-agent": this.options.userAgent,
|
|
56
|
+
...headers,
|
|
57
|
+
},
|
|
58
|
+
body: payload,
|
|
59
|
+
signal: AbortSignal.timeout(this.options.timeoutMs),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (cause) {
|
|
63
|
+
if (attempt >= this.options.maxRetries) {
|
|
64
|
+
throw new errors_ts_1.SinioConnectionError(`Request to ${url} failed: ${String(cause)}`, cause);
|
|
65
|
+
}
|
|
66
|
+
await this.sleep(this.backoffMs(attempt, null));
|
|
67
|
+
attempt += 1;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (response.ok) {
|
|
71
|
+
return await readJson(response);
|
|
72
|
+
}
|
|
73
|
+
const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
|
|
74
|
+
if (isRetriableStatus(response.status) && attempt < this.options.maxRetries) {
|
|
75
|
+
await this.sleep(this.backoffMs(attempt, retryAfter));
|
|
76
|
+
attempt += 1;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
throw await toApiError(response, retryAfter);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Exponential backoff with full jitter, unless the server told us how long to wait. */
|
|
83
|
+
backoffMs(attempt, retryAfterSeconds) {
|
|
84
|
+
if (retryAfterSeconds !== null) {
|
|
85
|
+
return retryAfterSeconds * 1000;
|
|
86
|
+
}
|
|
87
|
+
const ceiling = Math.min(this.options.retryBaseDelayMs * 2 ** attempt, 30_000);
|
|
88
|
+
return Math.random() * ceiling;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.Transport = Transport;
|
|
92
|
+
async function readJson(response) {
|
|
93
|
+
const text = await response.text();
|
|
94
|
+
if (text.length === 0) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
return JSON.parse(text);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return text;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function toApiError(response, retryAfterSeconds) {
|
|
105
|
+
const body = await readJson(response);
|
|
106
|
+
const code = extractCode(body);
|
|
107
|
+
const message = `${response.status} ${response.statusText}: ${extractMessage(body) ?? "no message"}`;
|
|
108
|
+
if (response.status === 401 || response.status === 403) {
|
|
109
|
+
return new errors_ts_1.SinioAuthError(message, response.status, body, code);
|
|
110
|
+
}
|
|
111
|
+
if (response.status === 429) {
|
|
112
|
+
return new errors_ts_1.SinioRateLimitError(message, response.status, body, retryAfterSeconds);
|
|
113
|
+
}
|
|
114
|
+
if (response.status >= 500) {
|
|
115
|
+
return new errors_ts_1.SinioServerError(message, response.status, body, code);
|
|
116
|
+
}
|
|
117
|
+
return new errors_ts_1.SinioRequestError(message, response.status, body, code);
|
|
118
|
+
}
|
|
119
|
+
function extractCode(body) {
|
|
120
|
+
if (typeof body === "object" && body !== null && "code" in body && typeof body.code === "string") {
|
|
121
|
+
return body.code;
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
function extractMessage(body) {
|
|
126
|
+
if (typeof body === "string") {
|
|
127
|
+
return body;
|
|
128
|
+
}
|
|
129
|
+
if (typeof body !== "object" || body === null || !("message" in body)) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const { message } = body;
|
|
133
|
+
// NestJS's ValidationPipe returns an array of messages, one per failed rule.
|
|
134
|
+
if (Array.isArray(message)) {
|
|
135
|
+
return message.join("; ");
|
|
136
|
+
}
|
|
137
|
+
return typeof message === "string" ? message : null;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=http.js.map
|