@sentico-labs/sdk 0.1.0-preview.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0-preview.1
4
+
5
+ - Converts the SDK into a publishable preview package.
6
+ - Adds `SenticoreClient` with explicit `public`, `trading`, `orderEntry`, `raw`, and `ws` planes.
7
+ - Adds typed `ApiResponse`, rate-limit metadata, request-id propagation, timeouts, and retries.
8
+ - Adds typed API errors for auth, validation, conflict, rate-limit, payload-too-large, and server failures.
9
+ - Adds Institutional Order Entry batch encoding for the current cold JSON payload over
10
+ `application/x-senticore-order-entry-batch`.
11
+ - Keeps `client.mm`, `mmApiKey`, and `SenticoreMarketMakerClient` as deprecated compatibility aliases.
12
+ - Keeps legacy `SenticoreHttpClient` and `SenticoreWsClient` exports for existing examples.
13
+ - Adds Node test coverage and npm packaging verification.
package/README.md ADDED
@@ -0,0 +1,190 @@
1
+ # Senticore TypeScript SDK
2
+
3
+ Official TypeScript SDK preview for Senticore HTTP, WebSocket, and
4
+ Institutional Order Entry.
5
+
6
+ Package name: `@sentico-labs/sdk`
7
+ Preview version: `0.1.0-preview.1`
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @sentico-labs/sdk@0.1.0-preview.1
13
+ ```
14
+
15
+ For local development from the repository:
16
+
17
+ ```bash
18
+ npm ci
19
+ npm run verify
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ```ts
25
+ import { SenticoreClient } from "@sentico-labs/sdk";
26
+
27
+ const client = new SenticoreClient({
28
+ publicHttpBaseUrl: "https://api.sentico-labs.xyz",
29
+ tradingHttpBaseUrl: "https://api.sentico-labs.xyz",
30
+ publicWsUrl: "wss://api.sentico-labs.xyz/api/v1/ws/public",
31
+ privateWsUrl: "wss://api.sentico-labs.xyz/api/v1/ws/private/{account}",
32
+ orderEntryHttpBaseUrl: "https://api.sentico-labs.xyz",
33
+ orderEntryBinaryPath: "/api/order-entry/binary",
34
+ bearerToken: process.env.SENTICORE_BEARER_TOKEN,
35
+ orderEntryApiKey: process.env.SENTICORE_ORDER_ENTRY_API_KEY
36
+ });
37
+
38
+ const markets = await client.public.listMarkets();
39
+ console.log(markets.data, markets.requestId, markets.rateLimit);
40
+ ```
41
+
42
+ ## Client Planes
43
+
44
+ - `client.public`: anonymous public HTTP reads.
45
+ - `client.trading`: authenticated private reads and trading writes.
46
+ - `client.orderEntry`: Institutional Order Entry.
47
+ - `client.mm`: deprecated alias for `client.orderEntry`.
48
+ - `client.raw`: escape hatch for endpoints not promoted to stable helpers yet.
49
+ - `client.ws`: public and private WebSocket helpers.
50
+
51
+ Legacy `SenticoreHttpClient` and `SenticoreWsClient` exports are still present
52
+ for existing integrations. New integrations should use `SenticoreClient`.
53
+
54
+ ## Public Market Data
55
+
56
+ ```ts
57
+ const markets = await client.public.listMarkets();
58
+ const book = await client.public.getMarketOrderbook(1, { book: "YES", depth: 20 });
59
+ const trades = await client.public.listMarketTrades(1, { limit: 50 });
60
+ ```
61
+
62
+ ## Trading HTTP
63
+
64
+ ```ts
65
+ const account = "0x00000000000000000000000000000000000000d3";
66
+ const nonce = await client.trading.reserveNonce(account, {
67
+ count: 1,
68
+ ttlMs: 30_000,
69
+ idempotencyKey: "nonce-1"
70
+ });
71
+
72
+ const signedAction = {
73
+ payload: {
74
+ account,
75
+ nonce: nonce.data.startNonce,
76
+ nonceReservationId: nonce.data.reservationId,
77
+ ts: Date.now(),
78
+ action: {
79
+ PlaceOrder: {
80
+ market: 1,
81
+ book: "YES",
82
+ side: "Bid",
83
+ price: 500000,
84
+ qty: 100000,
85
+ is_market: false,
86
+ reduce_only: false,
87
+ time_in_force: null,
88
+ expires_at: null,
89
+ stp_mode: null
90
+ }
91
+ }
92
+ },
93
+ signature: {
94
+ scheme: "EcdsaSecp256k1",
95
+ bytes: new Uint8Array(65)
96
+ }
97
+ };
98
+
99
+ const accepted = await client.trading.submitSignedAction(signedAction, {
100
+ idempotencyKey: "client-order-1"
101
+ });
102
+ console.log(accepted.data);
103
+ ```
104
+
105
+ ## Institutional Order Entry
106
+
107
+ The current Senticore cold binary path uses compact JSON bytes on the wire:
108
+
109
+ ```json
110
+ {"version":1,"actions":[...],"idempotencyKey":"..."}
111
+ ```
112
+
113
+ The SDK sends this payload with
114
+ `Content-Type: application/x-senticore-order-entry-batch` and
115
+ `X-Senticore-Order-Entry-Key`.
116
+
117
+ The canonical private-beta endpoint is `POST /api/order-entry/binary`. The live
118
+ edge maps that alias to the trading plane's `/api/v1/mm/orders/batch.bin`
119
+ handler. If you connect directly to a trading-plane service without the edge
120
+ alias, set `orderEntryBinaryPath: "/api/v1/mm/orders/batch.bin"`.
121
+
122
+ ```ts
123
+ const response = await client.orderEntry.submitActions([signedAction], {
124
+ idempotencyKey: "order-entry-batch-1",
125
+ responseMode: "summary"
126
+ });
127
+
128
+ console.log(response.data.ok, response.data.seqs, response.data.ackMode);
129
+ ```
130
+
131
+ Low-latency clients that already produce venue payload bytes can call:
132
+
133
+ ```ts
134
+ await client.orderEntry.submitEncoded(encodedPayload, {
135
+ idempotencyKey: "order-entry-raw-1"
136
+ });
137
+ ```
138
+
139
+ ## WebSocket
140
+
141
+ ```ts
142
+ const socket = client.ws.connectPublic((frame) => {
143
+ console.log(frame);
144
+ });
145
+
146
+ socket.addEventListener("open", () => {
147
+ client.ws.subscribe(socket, {
148
+ type: "l2Book",
149
+ marketId: 1,
150
+ depth: 20
151
+ });
152
+ });
153
+ ```
154
+
155
+ Private user streams:
156
+
157
+ ```ts
158
+ const token = await client.trading.issuePrivateWsToken(account, {
159
+ ttlMs: 60_000
160
+ });
161
+
162
+ const socket = client.ws.connectPrivate(account, token.data.token, (frame) => {
163
+ console.log(frame);
164
+ });
165
+ ```
166
+
167
+ Replay gaps raise `ReplayGapError` during frame parsing. Production clients
168
+ should resync from an HTTP snapshot and resubscribe from a known sequence.
169
+
170
+ ## Error Handling
171
+
172
+ ```ts
173
+ import { RateLimitError, SenticoreApiError } from "@sentico-labs/sdk";
174
+
175
+ try {
176
+ await client.public.listMarkets();
177
+ } catch (error) {
178
+ if (error instanceof RateLimitError) {
179
+ console.log("retry after", error.retryAfter);
180
+ } else if (error instanceof SenticoreApiError) {
181
+ console.log(error.status, error.requestId, error.payload);
182
+ }
183
+ }
184
+ ```
185
+
186
+ ## Preview Compatibility
187
+
188
+ `0.1.0-preview.1` is not a v1 stability promise. The stable preview shape is the
189
+ top-level namespace split: `public`, `trading`, `orderEntry`, `raw`, and `ws`.
190
+ `mm` remains as a deprecated compatibility alias.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,35 @@
1
+ import { SenticoreClient } from "../src/index.js";
2
+ const account = process.env.SENTICORE_ACCOUNT;
3
+ const client = new SenticoreClient({
4
+ publicHttpBaseUrl: process.env.SENTICORE_PUBLIC_HTTP_URL ?? "https://api.sentico-labs.xyz",
5
+ tradingHttpBaseUrl: process.env.SENTICORE_TRADING_HTTP_URL ?? "https://api.sentico-labs.xyz",
6
+ publicWsUrl: process.env.SENTICORE_PUBLIC_WS_URL ?? "wss://api.sentico-labs.xyz/api/v1/ws/public",
7
+ privateWsUrl: process.env.SENTICORE_PRIVATE_WS_URL ??
8
+ "wss://api.sentico-labs.xyz/api/v1/ws/private/{account}",
9
+ orderEntryHttpBaseUrl: process.env.SENTICORE_ORDER_ENTRY_HTTP_URL ?? "https://api.sentico-labs.xyz",
10
+ orderEntryBinaryPath: process.env.SENTICORE_ORDER_ENTRY_BINARY_PATH ?? "/api/order-entry/binary",
11
+ orderEntryApiKey: process.env.SENTICORE_ORDER_ENTRY_API_KEY ?? process.env.SENTICORE_MM_API_KEY
12
+ });
13
+ const action = {
14
+ payload: {
15
+ account,
16
+ nonce: Number(process.env.SENTICORE_NONCE ?? "0"),
17
+ nonceReservationId: null,
18
+ ts: Date.now(),
19
+ action: {
20
+ Cancel: {
21
+ order_id: process.env.SENTICORE_ORDER_ID ??
22
+ "0x0101010101010101010101010101010101010101010101010101010101010101"
23
+ }
24
+ }
25
+ },
26
+ signature: {
27
+ scheme: "EcdsaSecp256k1",
28
+ bytes: new Uint8Array(65)
29
+ }
30
+ };
31
+ const response = await client.orderEntry.submitActions([action], {
32
+ idempotencyKey: `order-entry-${Date.now()}`,
33
+ responseMode: "summary"
34
+ });
35
+ console.log(response.data);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ import { SenticoreClient } from "../src/index.js";
2
+ const account = process.env.SENTICORE_ACCOUNT;
3
+ const client = new SenticoreClient({
4
+ publicHttpBaseUrl: process.env.SENTICORE_PUBLIC_HTTP_URL ?? "https://api.sentico-labs.xyz",
5
+ tradingHttpBaseUrl: process.env.SENTICORE_TRADING_HTTP_URL ?? "https://api.sentico-labs.xyz",
6
+ publicWsUrl: process.env.SENTICORE_PUBLIC_WS_URL ?? "wss://api.sentico-labs.xyz/api/v1/ws/public",
7
+ privateWsUrl: process.env.SENTICORE_PRIVATE_WS_URL ??
8
+ "wss://api.sentico-labs.xyz/api/v1/ws/private/{account}",
9
+ bearerToken: process.env.SENTICORE_BEARER_TOKEN
10
+ });
11
+ const token = await client.trading.issuePrivateWsToken(account, {
12
+ ttlMs: 120_000,
13
+ clientId: "algo-desk-1"
14
+ });
15
+ const socket = client.ws.connectPrivate(account, token.data.token, (frame) => {
16
+ console.log("private frame", frame);
17
+ });
18
+ socket.addEventListener("open", () => {
19
+ client.ws.subscribe(socket, { type: "account" });
20
+ client.ws.subscribe(socket, { type: "userFills" });
21
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ import { SenticoreClient } from "../src/index.js";
2
+ const client = new SenticoreClient({
3
+ publicHttpBaseUrl: process.env.SENTICORE_PUBLIC_HTTP_URL ?? "https://api.sentico-labs.xyz",
4
+ tradingHttpBaseUrl: process.env.SENTICORE_TRADING_HTTP_URL ?? "https://api.sentico-labs.xyz",
5
+ publicWsUrl: process.env.SENTICORE_PUBLIC_WS_URL ?? "wss://api.sentico-labs.xyz/api/v1/ws/public",
6
+ privateWsUrl: process.env.SENTICORE_PRIVATE_WS_URL ??
7
+ "wss://api.sentico-labs.xyz/api/v1/ws/private/{account}"
8
+ });
9
+ const socket = client.ws.connectPublic((frame) => {
10
+ console.log("public frame", frame);
11
+ });
12
+ socket.addEventListener("open", () => {
13
+ client.ws.subscribe(socket, {
14
+ type: "marketSnapshot",
15
+ marketId: 1,
16
+ cursor: 0
17
+ });
18
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,35 @@
1
+ import { ReplayGapError, SenticoreClient } from "../src/index.js";
2
+ const client = new SenticoreClient({
3
+ publicHttpBaseUrl: process.env.SENTICORE_PUBLIC_HTTP_URL ?? "https://api.sentico-labs.xyz",
4
+ tradingHttpBaseUrl: process.env.SENTICORE_TRADING_HTTP_URL ?? "https://api.sentico-labs.xyz",
5
+ publicWsUrl: process.env.SENTICORE_PUBLIC_WS_URL ?? "wss://api.sentico-labs.xyz/api/v1/ws/public",
6
+ privateWsUrl: process.env.SENTICORE_PRIVATE_WS_URL ??
7
+ "wss://api.sentico-labs.xyz/api/v1/ws/private/{account}"
8
+ });
9
+ let lastCursor = 0;
10
+ function handle(frame) {
11
+ if (typeof frame.seq === "number") {
12
+ lastCursor = frame.seq;
13
+ }
14
+ console.log(frame);
15
+ }
16
+ const socket = client.ws.connectPublic((frame) => {
17
+ try {
18
+ handle(frame);
19
+ }
20
+ catch (error) {
21
+ if (error instanceof ReplayGapError) {
22
+ void client.public.getMarketSnapshot(1, { depth: 20 }).then((snapshot) => {
23
+ console.log("resynced snapshot", snapshot.data);
24
+ lastCursor = 0;
25
+ });
26
+ }
27
+ }
28
+ });
29
+ socket.addEventListener("open", () => {
30
+ client.ws.subscribe(socket, {
31
+ type: "marketSnapshot",
32
+ marketId: 1,
33
+ cursor: lastCursor
34
+ });
35
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,45 @@
1
+ import { SenticoreClient } from "../src/index.js";
2
+ const account = process.env.SENTICORE_ACCOUNT;
3
+ const client = new SenticoreClient({
4
+ publicHttpBaseUrl: process.env.SENTICORE_PUBLIC_HTTP_URL ?? "https://api.sentico-labs.xyz",
5
+ tradingHttpBaseUrl: process.env.SENTICORE_TRADING_HTTP_URL ?? "https://api.sentico-labs.xyz",
6
+ publicWsUrl: process.env.SENTICORE_PUBLIC_WS_URL ?? "wss://api.sentico-labs.xyz/api/v1/ws/public",
7
+ privateWsUrl: process.env.SENTICORE_PRIVATE_WS_URL ??
8
+ "wss://api.sentico-labs.xyz/api/v1/ws/private/{account}",
9
+ bearerToken: process.env.SENTICORE_BEARER_TOKEN
10
+ });
11
+ const nonce = await client.trading.reserveNonce(account, {
12
+ count: 1,
13
+ ttlMs: 30_000,
14
+ idempotencyKey: `nonce-${Date.now()}`
15
+ });
16
+ const signedAction = {
17
+ payload: {
18
+ account,
19
+ nonce: Number(nonce.data.startNonce),
20
+ nonceReservationId: nonce.data.reservationId,
21
+ ts: Date.now(),
22
+ action: {
23
+ PlaceOrder: {
24
+ market: 1,
25
+ book: "YES",
26
+ side: "Bid",
27
+ price: 500000,
28
+ qty: 100000,
29
+ is_market: false,
30
+ reduce_only: false,
31
+ time_in_force: null,
32
+ expires_at: null,
33
+ stp_mode: null
34
+ }
35
+ }
36
+ },
37
+ signature: {
38
+ scheme: "EcdsaSecp256k1",
39
+ bytes: new Uint8Array(65)
40
+ }
41
+ };
42
+ const response = await client.trading.submitSignedAction(signedAction, {
43
+ idempotencyKey: `order-${Date.now()}`
44
+ });
45
+ console.log(response.data);
@@ -0,0 +1,15 @@
1
+ import { SenticorePublicHttpClient, SenticoreRawHttpClient, SenticoreTradingHttpClient } from "./http.js";
2
+ import { SenticoreOrderEntryClient } from "./mm.js";
3
+ import type { NormalizedSenticoreSdkConfig, SenticoreSdkConfig } from "./types.js";
4
+ import { SenticoreWsClient } from "./ws.js";
5
+ export declare class SenticoreClient {
6
+ readonly config: NormalizedSenticoreSdkConfig;
7
+ readonly public: SenticorePublicHttpClient;
8
+ readonly trading: SenticoreTradingHttpClient;
9
+ readonly orderEntry: SenticoreOrderEntryClient;
10
+ /** @deprecated Use orderEntry. */
11
+ readonly mm: SenticoreOrderEntryClient;
12
+ readonly raw: SenticoreRawHttpClient;
13
+ readonly ws: SenticoreWsClient;
14
+ constructor(config: SenticoreSdkConfig);
15
+ }
@@ -0,0 +1,24 @@
1
+ import { normalizeConfig } from "./config.js";
2
+ import { SenticoreHttpTransport, SenticorePublicHttpClient, SenticoreRawHttpClient, SenticoreTradingHttpClient } from "./http.js";
3
+ import { SenticoreOrderEntryClient } from "./mm.js";
4
+ import { SenticoreWsClient } from "./ws.js";
5
+ export class SenticoreClient {
6
+ config;
7
+ public;
8
+ trading;
9
+ orderEntry;
10
+ /** @deprecated Use orderEntry. */
11
+ mm;
12
+ raw;
13
+ ws;
14
+ constructor(config) {
15
+ this.config = normalizeConfig(config);
16
+ const transport = new SenticoreHttpTransport(this.config);
17
+ this.public = new SenticorePublicHttpClient(transport);
18
+ this.trading = new SenticoreTradingHttpClient(transport);
19
+ this.orderEntry = new SenticoreOrderEntryClient(transport);
20
+ this.mm = this.orderEntry;
21
+ this.raw = new SenticoreRawHttpClient(transport);
22
+ this.ws = new SenticoreWsClient(this.config);
23
+ }
24
+ }
@@ -0,0 +1,2 @@
1
+ import type { NormalizedSenticoreSdkConfig, SenticoreSdkConfig } from "./types.js";
2
+ export declare function normalizeConfig(config: SenticoreSdkConfig): NormalizedSenticoreSdkConfig;
@@ -0,0 +1,41 @@
1
+ const DEFAULT_TIMEOUT_MS = 10_000;
2
+ const DEFAULT_MAX_RETRIES = 2;
3
+ const DEFAULT_RETRY_BACKOFF_MS = 250;
4
+ const DEFAULT_ORDER_ENTRY_BINARY_PATH = "/api/order-entry/binary";
5
+ const DEFAULT_USER_AGENT = "@sentico-labs/sdk/0.1.0-preview.1";
6
+ export function normalizeConfig(config) {
7
+ const fetchImpl = config.fetch ?? globalThis.fetch?.bind(globalThis);
8
+ if (!fetchImpl) {
9
+ throw new Error("Senticore SDK requires fetch or config.fetch");
10
+ }
11
+ const WebSocketCtor = config.WebSocketCtor ?? globalThis.WebSocket;
12
+ const orderEntryHttpBaseUrl = trimTrailingSlash(config.orderEntryHttpBaseUrl ?? config.mmHttpBaseUrl ?? config.tradingHttpBaseUrl);
13
+ const orderEntryBinaryPath = normalizePath(config.orderEntryBinaryPath ?? config.mmBinaryPath ?? DEFAULT_ORDER_ENTRY_BINARY_PATH);
14
+ const orderEntryApiKey = config.orderEntryApiKey ?? config.mmApiKey;
15
+ return {
16
+ publicHttpBaseUrl: trimTrailingSlash(config.publicHttpBaseUrl),
17
+ tradingHttpBaseUrl: trimTrailingSlash(config.tradingHttpBaseUrl),
18
+ publicWsUrl: config.publicWsUrl,
19
+ privateWsUrl: config.privateWsUrl,
20
+ orderEntryHttpBaseUrl,
21
+ orderEntryBinaryPath,
22
+ orderEntryApiKey,
23
+ mmHttpBaseUrl: orderEntryHttpBaseUrl,
24
+ mmBinaryPath: orderEntryBinaryPath,
25
+ bearerToken: config.bearerToken,
26
+ mmApiKey: orderEntryApiKey,
27
+ timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,
28
+ maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
29
+ retryBackoffMs: config.retryBackoffMs ?? DEFAULT_RETRY_BACKOFF_MS,
30
+ userAgent: config.userAgent ?? DEFAULT_USER_AGENT,
31
+ headers: config.headers ?? {},
32
+ fetch: fetchImpl,
33
+ WebSocketCtor
34
+ };
35
+ }
36
+ function trimTrailingSlash(value) {
37
+ return value.replace(/\/+$/, "");
38
+ }
39
+ function normalizePath(path) {
40
+ return path.startsWith("/") ? path : `/${path}`;
41
+ }
@@ -0,0 +1,38 @@
1
+ export declare class SenticoreError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare class TransportError extends SenticoreError {
5
+ readonly cause?: unknown;
6
+ constructor(message: string, cause?: unknown);
7
+ }
8
+ export declare class ReplayGapError extends SenticoreError {
9
+ readonly frame?: unknown;
10
+ constructor(message: string, frame?: unknown);
11
+ }
12
+ export declare class SenticoreApiError extends SenticoreError {
13
+ readonly status: number;
14
+ readonly payload: unknown;
15
+ readonly headers: Headers;
16
+ readonly requestId?: string | undefined;
17
+ constructor(message: string, status: number, payload: unknown, headers: Headers, requestId?: string | undefined);
18
+ }
19
+ export declare class AuthenticationError extends SenticoreApiError {
20
+ }
21
+ export declare class PermissionDeniedError extends SenticoreApiError {
22
+ }
23
+ export declare class NotFoundError extends SenticoreApiError {
24
+ }
25
+ export declare class ConflictError extends SenticoreApiError {
26
+ }
27
+ export declare class ValidationError extends SenticoreApiError {
28
+ }
29
+ export declare class PayloadTooLargeError extends SenticoreApiError {
30
+ }
31
+ export declare class ServerError extends SenticoreApiError {
32
+ }
33
+ export declare class RateLimitError extends SenticoreApiError {
34
+ readonly retryAfter?: number | undefined;
35
+ constructor(message: string, status: number, payload: unknown, headers: Headers, requestId?: string, retryAfter?: number | undefined);
36
+ }
37
+ export declare function requestIdFromHeaders(headers: Headers): string | undefined;
38
+ export declare function apiErrorFromStatus(response: Response, payload: unknown): SenticoreApiError;
@@ -0,0 +1,106 @@
1
+ export class SenticoreError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = new.target.name;
5
+ }
6
+ }
7
+ export class TransportError extends SenticoreError {
8
+ cause;
9
+ constructor(message, cause) {
10
+ super(message);
11
+ this.cause = cause;
12
+ }
13
+ }
14
+ export class ReplayGapError extends SenticoreError {
15
+ frame;
16
+ constructor(message, frame) {
17
+ super(message);
18
+ this.frame = frame;
19
+ }
20
+ }
21
+ export class SenticoreApiError extends SenticoreError {
22
+ status;
23
+ payload;
24
+ headers;
25
+ requestId;
26
+ constructor(message, status, payload, headers, requestId) {
27
+ super(message);
28
+ this.status = status;
29
+ this.payload = payload;
30
+ this.headers = headers;
31
+ this.requestId = requestId;
32
+ }
33
+ }
34
+ export class AuthenticationError extends SenticoreApiError {
35
+ }
36
+ export class PermissionDeniedError extends SenticoreApiError {
37
+ }
38
+ export class NotFoundError extends SenticoreApiError {
39
+ }
40
+ export class ConflictError extends SenticoreApiError {
41
+ }
42
+ export class ValidationError extends SenticoreApiError {
43
+ }
44
+ export class PayloadTooLargeError extends SenticoreApiError {
45
+ }
46
+ export class ServerError extends SenticoreApiError {
47
+ }
48
+ export class RateLimitError extends SenticoreApiError {
49
+ retryAfter;
50
+ constructor(message, status, payload, headers, requestId, retryAfter) {
51
+ super(message, status, payload, headers, requestId);
52
+ this.retryAfter = retryAfter;
53
+ }
54
+ }
55
+ export function requestIdFromHeaders(headers) {
56
+ return (headers.get("x-request-id") ??
57
+ headers.get("x-correlation-id") ??
58
+ headers.get("x-senticore-request-id") ??
59
+ undefined);
60
+ }
61
+ export function apiErrorFromStatus(response, payload) {
62
+ const requestId = requestIdFromHeaders(response.headers);
63
+ const message = messageFromPayload(response.status, payload);
64
+ if (response.status === 401) {
65
+ return new AuthenticationError(message, response.status, payload, response.headers, requestId);
66
+ }
67
+ if (response.status === 403) {
68
+ return new PermissionDeniedError(message, response.status, payload, response.headers, requestId);
69
+ }
70
+ if (response.status === 404) {
71
+ return new NotFoundError(message, response.status, payload, response.headers, requestId);
72
+ }
73
+ if (response.status === 409) {
74
+ return new ConflictError(message, response.status, payload, response.headers, requestId);
75
+ }
76
+ if (response.status === 400 || response.status === 422) {
77
+ return new ValidationError(message, response.status, payload, response.headers, requestId);
78
+ }
79
+ if (response.status === 413) {
80
+ return new PayloadTooLargeError(message, response.status, payload, response.headers, requestId);
81
+ }
82
+ if (response.status === 429) {
83
+ return new RateLimitError(message, response.status, payload, response.headers, requestId, parseRetryAfter(response.headers.get("retry-after")));
84
+ }
85
+ if (response.status >= 500) {
86
+ return new ServerError(message, response.status, payload, response.headers, requestId);
87
+ }
88
+ return new SenticoreApiError(message, response.status, payload, response.headers, requestId);
89
+ }
90
+ function messageFromPayload(status, payload) {
91
+ if (payload && typeof payload === "object") {
92
+ const record = payload;
93
+ for (const key of ["error", "message", "detail"]) {
94
+ if (typeof record[key] === "string" && record[key]) {
95
+ return `Senticore HTTP ${status}: ${record[key]}`;
96
+ }
97
+ }
98
+ }
99
+ return `Senticore HTTP ${status}`;
100
+ }
101
+ function parseRetryAfter(value) {
102
+ if (!value)
103
+ return undefined;
104
+ const parsed = Number(value);
105
+ return Number.isFinite(parsed) ? parsed : undefined;
106
+ }