@sketchlog/client 1.2.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 +35 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +165 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# `@sketchlog/client`
|
|
2
|
+
|
|
3
|
+
TypeScript HTTP client for SketchLog.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @sketchlog/client
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { SketchLogClient } from "@sketchlog/client";
|
|
11
|
+
|
|
12
|
+
const client = new SketchLogClient({
|
|
13
|
+
endpoint: "https://metrics.example",
|
|
14
|
+
authTokenProvider: async () => obtainRotatedToken(),
|
|
15
|
+
timeoutMs: 5000,
|
|
16
|
+
maxRetries: 3,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
await client.ingestEvents("api", {
|
|
20
|
+
latencies: [42.5],
|
|
21
|
+
events: { request: 1 },
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await client.close();
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Static credentials can be supplied with `authToken`. Authentication is sent in
|
|
28
|
+
`X-SketchLog-Auth-Token`, never in the URL.
|
|
29
|
+
|
|
30
|
+
GET, PUT, and DELETE requests retry transport failures, HTTP 429, and 5xx
|
|
31
|
+
responses with bounded exponential backoff and jitter. Ingestion POSTs are not
|
|
32
|
+
retried automatically because replay could double-count events.
|
|
33
|
+
|
|
34
|
+
See the [SDK documentation](https://sbalavignesh123.github.io/sketchlog/sdks/)
|
|
35
|
+
for configuration and conformance details.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare class SketchLogError extends Error {
|
|
2
|
+
status: number;
|
|
3
|
+
constructor(status: number, message: string);
|
|
4
|
+
}
|
|
5
|
+
export interface ClientOptions {
|
|
6
|
+
endpoint: string;
|
|
7
|
+
maxRetries?: number;
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
authToken?: string;
|
|
10
|
+
authTokenProvider?: () => string | Promise<string>;
|
|
11
|
+
}
|
|
12
|
+
export interface EventBatch {
|
|
13
|
+
latencies?: number[];
|
|
14
|
+
uniques?: string[];
|
|
15
|
+
events?: Record<string, number>;
|
|
16
|
+
}
|
|
17
|
+
export declare class SketchLogClient {
|
|
18
|
+
private static readonly MAX_RESPONSE_BYTES;
|
|
19
|
+
private endpoint;
|
|
20
|
+
private maxRetries;
|
|
21
|
+
private timeoutMs;
|
|
22
|
+
private agent;
|
|
23
|
+
private authToken?;
|
|
24
|
+
private authTokenProvider?;
|
|
25
|
+
constructor(options: ClientOptions);
|
|
26
|
+
private readResponse;
|
|
27
|
+
private request;
|
|
28
|
+
private delay;
|
|
29
|
+
health(): Promise<any>;
|
|
30
|
+
ingestEvents(streamId: string, batch: EventBatch, signal?: AbortSignal): Promise<void>;
|
|
31
|
+
close(): Promise<void>;
|
|
32
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SketchLogClient = exports.SketchLogError = void 0;
|
|
4
|
+
const undici_1 = require("undici");
|
|
5
|
+
class SketchLogError extends Error {
|
|
6
|
+
status;
|
|
7
|
+
constructor(status, message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.status = status;
|
|
10
|
+
this.name = 'SketchLogError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.SketchLogError = SketchLogError;
|
|
14
|
+
class SketchLogClient {
|
|
15
|
+
static MAX_RESPONSE_BYTES = 1024 * 1024;
|
|
16
|
+
endpoint;
|
|
17
|
+
maxRetries;
|
|
18
|
+
timeoutMs;
|
|
19
|
+
agent;
|
|
20
|
+
authToken;
|
|
21
|
+
authTokenProvider;
|
|
22
|
+
constructor(options) {
|
|
23
|
+
const endpoint = new URL(options.endpoint);
|
|
24
|
+
if (!['http:', 'https:'].includes(endpoint.protocol)
|
|
25
|
+
|| endpoint.username || endpoint.password
|
|
26
|
+
|| (endpoint.pathname !== '' && endpoint.pathname !== '/')
|
|
27
|
+
|| endpoint.search || endpoint.hash) {
|
|
28
|
+
throw new TypeError('endpoint must be an HTTP(S) origin without credentials or a path');
|
|
29
|
+
}
|
|
30
|
+
if (options.maxRetries !== undefined
|
|
31
|
+
&& (!Number.isInteger(options.maxRetries)
|
|
32
|
+
|| options.maxRetries < 0 || options.maxRetries > 10)) {
|
|
33
|
+
throw new TypeError('maxRetries must be an integer in [0, 10]');
|
|
34
|
+
}
|
|
35
|
+
if (options.timeoutMs !== undefined
|
|
36
|
+
&& (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0)) {
|
|
37
|
+
throw new TypeError('timeoutMs must be a positive finite number');
|
|
38
|
+
}
|
|
39
|
+
this.endpoint = endpoint.origin;
|
|
40
|
+
this.maxRetries = options.maxRetries ?? 3;
|
|
41
|
+
this.timeoutMs = options.timeoutMs ?? 5000;
|
|
42
|
+
this.authToken = options.authToken;
|
|
43
|
+
this.authTokenProvider = options.authTokenProvider;
|
|
44
|
+
// Connection pooling
|
|
45
|
+
this.agent = new undici_1.Agent({
|
|
46
|
+
keepAliveTimeout: 10000,
|
|
47
|
+
keepAliveMaxTimeout: 10000,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async readResponse(res) {
|
|
51
|
+
const contentLength = Number(res.headers.get('content-length'));
|
|
52
|
+
if (Number.isFinite(contentLength)
|
|
53
|
+
&& contentLength > SketchLogClient.MAX_RESPONSE_BYTES) {
|
|
54
|
+
await res.body?.cancel();
|
|
55
|
+
throw new SketchLogError(502, 'Response body exceeds 1 MiB');
|
|
56
|
+
}
|
|
57
|
+
if (!res.body)
|
|
58
|
+
return '';
|
|
59
|
+
const reader = res.body.getReader();
|
|
60
|
+
const chunks = [];
|
|
61
|
+
let total = 0;
|
|
62
|
+
while (true) {
|
|
63
|
+
const { done, value } = await reader.read();
|
|
64
|
+
if (done)
|
|
65
|
+
break;
|
|
66
|
+
total += value.byteLength;
|
|
67
|
+
if (total > SketchLogClient.MAX_RESPONSE_BYTES) {
|
|
68
|
+
await reader.cancel();
|
|
69
|
+
throw new SketchLogError(502, 'Response body exceeds 1 MiB');
|
|
70
|
+
}
|
|
71
|
+
chunks.push(value);
|
|
72
|
+
}
|
|
73
|
+
const combined = new Uint8Array(total);
|
|
74
|
+
let offset = 0;
|
|
75
|
+
for (const chunk of chunks) {
|
|
76
|
+
combined.set(chunk, offset);
|
|
77
|
+
offset += chunk.byteLength;
|
|
78
|
+
}
|
|
79
|
+
return new TextDecoder().decode(combined);
|
|
80
|
+
}
|
|
81
|
+
async request(method, path, body, signal) {
|
|
82
|
+
const url = `${this.endpoint}${path}`;
|
|
83
|
+
const isIdempotent = method === 'GET' || method === 'PUT' || method === 'DELETE';
|
|
84
|
+
let attempt = 0;
|
|
85
|
+
while (true) {
|
|
86
|
+
attempt++;
|
|
87
|
+
let timeoutId;
|
|
88
|
+
try {
|
|
89
|
+
const controller = new AbortController();
|
|
90
|
+
timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
91
|
+
// Combine provided signal with our timeout signal so neither is ignored
|
|
92
|
+
const activeSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
93
|
+
const token = this.authTokenProvider
|
|
94
|
+
? await this.authTokenProvider()
|
|
95
|
+
: this.authToken;
|
|
96
|
+
const headers = {};
|
|
97
|
+
if (body)
|
|
98
|
+
headers['Content-Type'] = 'application/json';
|
|
99
|
+
if (token)
|
|
100
|
+
headers['X-SketchLog-Auth-Token'] = token;
|
|
101
|
+
const res = await (0, undici_1.fetch)(url, {
|
|
102
|
+
method,
|
|
103
|
+
headers: Object.keys(headers).length ? headers : undefined,
|
|
104
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
105
|
+
dispatcher: this.agent,
|
|
106
|
+
signal: activeSignal,
|
|
107
|
+
redirect: 'error',
|
|
108
|
+
});
|
|
109
|
+
if (res.status >= 200 && res.status < 300) {
|
|
110
|
+
if (res.status === 204)
|
|
111
|
+
return null;
|
|
112
|
+
const responseText = await this.readResponse(res);
|
|
113
|
+
try {
|
|
114
|
+
return responseText ? JSON.parse(responseText) : null;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
throw new SketchLogError(502, 'Server returned invalid JSON');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Retry on 5xx or 429
|
|
121
|
+
if ((res.status >= 500 || res.status === 429) && isIdempotent && attempt <= this.maxRetries) {
|
|
122
|
+
await this.readResponse(res); // Consume to free the pooled socket.
|
|
123
|
+
await this.delay(attempt);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
throw new SketchLogError(res.status, await this.readResponse(res));
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
if (err instanceof SketchLogError) {
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
if (err.name === 'AbortError') {
|
|
133
|
+
if (signal?.aborted) {
|
|
134
|
+
throw signal.reason || err;
|
|
135
|
+
}
|
|
136
|
+
throw new SketchLogError(408, 'Request Timeout');
|
|
137
|
+
}
|
|
138
|
+
if (isIdempotent && attempt <= this.maxRetries) {
|
|
139
|
+
await this.delay(attempt);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
throw new SketchLogError(0, err.message || 'Unknown network error');
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
if (timeoutId !== undefined)
|
|
146
|
+
clearTimeout(timeoutId);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
delay(attempt) {
|
|
151
|
+
const base = 100 * Math.pow(2, attempt - 1);
|
|
152
|
+
const jitter = Math.random() * 50;
|
|
153
|
+
return new Promise(resolve => setTimeout(resolve, base + jitter));
|
|
154
|
+
}
|
|
155
|
+
async health() {
|
|
156
|
+
return this.request('GET', '/health');
|
|
157
|
+
}
|
|
158
|
+
async ingestEvents(streamId, batch, signal) {
|
|
159
|
+
await this.request('POST', `/v1/streams/${encodeURIComponent(streamId)}/events`, batch, signal);
|
|
160
|
+
}
|
|
161
|
+
async close() {
|
|
162
|
+
await this.agent.close();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
exports.SketchLogClient = SketchLogClient;
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sketchlog/client",
|
|
3
|
+
"version": "1.2.3",
|
|
4
|
+
"description": "TypeScript SDK for SketchLog",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"require": "./dist/index.js",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/index.js",
|
|
16
|
+
"dist/index.d.ts"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"test": "npm run build && node --test test/*.test.cjs"
|
|
21
|
+
},
|
|
22
|
+
"keywords": ["sketchlog", "observability", "metrics", "streaming"],
|
|
23
|
+
"author": "Bala Vignesh S",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/SBALAVIGNESH123/sketchlog.git",
|
|
28
|
+
"directory": "clients/typescript"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/SBALAVIGNESH123/sketchlog/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/SBALAVIGNESH123/sketchlog/tree/main/clients/typescript",
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public",
|
|
36
|
+
"provenance": true
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.17"
|
|
40
|
+
},
|
|
41
|
+
"type": "commonjs",
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"undici": "^6.20.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^26.0.0",
|
|
47
|
+
"tsx": "^4.22.4",
|
|
48
|
+
"typescript": "^6.0.3"
|
|
49
|
+
}
|
|
50
|
+
}
|