@secondlayer/sdk 10.4.3 → 10.5.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/dist/index.d.ts +28 -2
- package/dist/index.js +51 -5
- package/dist/index.js.map +3 -3
- package/dist/streams/index.js +50 -5
- package/dist/streams/index.js.map +3 -3
- package/dist/subgraphs/index.d.ts +18 -1
- package/dist/subgraphs/index.js +50 -5
- package/dist/subgraphs/index.js.map +3 -3
- package/package.json +3 -3
|
@@ -39,6 +39,22 @@ interface SecondLayerOptions {
|
|
|
39
39
|
* retry policy instead of stalling a walk or consume loop forever. `0`
|
|
40
40
|
* disables the timeout. */
|
|
41
41
|
requestTimeoutMs?: number;
|
|
42
|
+
/** Where GET responses carrying an `ETag` are kept, so a repeat read sends
|
|
43
|
+
* `If-None-Match` and a finalized page comes back as a free `304`.
|
|
44
|
+
* Defaults to an in-memory store of the last 64 pages. Pass your own to
|
|
45
|
+
* keep it across processes (a file, a KV), or `false` to turn it off. */
|
|
46
|
+
etagCache?: EtagCache | false;
|
|
47
|
+
}
|
|
48
|
+
/** One cached GET response: its validator and the body it validated. */
|
|
49
|
+
type EtagEntry = {
|
|
50
|
+
etag: string;
|
|
51
|
+
body: string;
|
|
52
|
+
};
|
|
53
|
+
/** Storage for `etagCache`. Keys are full request URLs. Either method may be
|
|
54
|
+
* async, so a disk or KV store works as well as a Map. */
|
|
55
|
+
interface EtagCache {
|
|
56
|
+
get(key: string): EtagEntry | undefined | Promise<EtagEntry | undefined>;
|
|
57
|
+
set(key: string, entry: EtagEntry): void | Promise<void>;
|
|
42
58
|
}
|
|
43
59
|
/** Per-call options every `request*` method accepts. */
|
|
44
60
|
type RequestOptions = {
|
|
@@ -52,6 +68,7 @@ declare abstract class BaseClient {
|
|
|
52
68
|
protected origin: "cli" | "mcp" | "session";
|
|
53
69
|
protected fetchImpl: FetchLike;
|
|
54
70
|
protected requestTimeoutMs: number;
|
|
71
|
+
protected etagCache: EtagCache | null;
|
|
55
72
|
constructor(options?: Partial<SecondLayerOptions>);
|
|
56
73
|
static authHeaders(apiKey?: string): Record<string, string>;
|
|
57
74
|
protected request<T>(method: string, path: string, body?: unknown, opts?: RequestOptions): Promise<T>;
|
|
@@ -68,7 +85,7 @@ declare abstract class BaseClient {
|
|
|
68
85
|
/** Issue the HTTP request and map non-2xx statuses onto the error family.
|
|
69
86
|
* `signal` is the combined caller-plus-timeout signal from `request*`;
|
|
70
87
|
* subclasses that stream a body call this directly and read it themselves. */
|
|
71
|
-
protected fetchResponse(method: string, path: string, body?: unknown, signal?: AbortSignal): Promise<Response>;
|
|
88
|
+
protected fetchResponse(method: string, path: string, body?: unknown, signal?: AbortSignal, extraHeaders?: Record<string, string>): Promise<Response>;
|
|
72
89
|
}
|
|
73
90
|
interface SubgraphSource {
|
|
74
91
|
name: string;
|
package/dist/subgraphs/index.js
CHANGED
|
@@ -156,6 +156,30 @@ function parseRetryAfter(value) {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
// src/base.ts
|
|
159
|
+
class MemoryEtagCache {
|
|
160
|
+
limit;
|
|
161
|
+
entries = new Map;
|
|
162
|
+
constructor(limit = 64) {
|
|
163
|
+
this.limit = limit;
|
|
164
|
+
}
|
|
165
|
+
get(key) {
|
|
166
|
+
const entry = this.entries.get(key);
|
|
167
|
+
if (entry) {
|
|
168
|
+
this.entries.delete(key);
|
|
169
|
+
this.entries.set(key, entry);
|
|
170
|
+
}
|
|
171
|
+
return entry;
|
|
172
|
+
}
|
|
173
|
+
set(key, entry) {
|
|
174
|
+
this.entries.delete(key);
|
|
175
|
+
this.entries.set(key, entry);
|
|
176
|
+
if (this.entries.size > this.limit) {
|
|
177
|
+
const oldest = this.entries.keys().next().value;
|
|
178
|
+
if (oldest !== undefined)
|
|
179
|
+
this.entries.delete(oldest);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
159
183
|
var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
160
184
|
function abortError() {
|
|
161
185
|
return new DOMException("This operation was aborted", "AbortError");
|
|
@@ -266,12 +290,14 @@ class BaseClient {
|
|
|
266
290
|
origin;
|
|
267
291
|
fetchImpl;
|
|
268
292
|
requestTimeoutMs;
|
|
293
|
+
etagCache;
|
|
269
294
|
constructor(options = {}) {
|
|
270
295
|
this.baseUrl = resolveBaseUrl(options.baseUrl);
|
|
271
296
|
this.apiKey = resolveApiKey(options.apiKey);
|
|
272
297
|
this.origin = options.origin ?? "cli";
|
|
273
298
|
this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
274
299
|
this.fetchImpl = options.fetchImpl ?? ((...args) => fetch(...args));
|
|
300
|
+
this.etagCache = options.etagCache === false ? null : options.etagCache ?? new MemoryEtagCache;
|
|
275
301
|
}
|
|
276
302
|
static authHeaders(apiKey) {
|
|
277
303
|
const headers = {
|
|
@@ -284,11 +310,25 @@ class BaseClient {
|
|
|
284
310
|
}
|
|
285
311
|
async request(method, path, body, opts = {}) {
|
|
286
312
|
return this.withRequestBudget(opts.signal, async (signal) => {
|
|
287
|
-
const
|
|
313
|
+
const cacheKey = method === "GET" && this.etagCache ? `${this.baseUrl}${path}` : null;
|
|
314
|
+
const cached = cacheKey ? await this.etagCache?.get(cacheKey) : undefined;
|
|
315
|
+
const response = await this.fetchResponse(method, path, body, signal, cached ? { "If-None-Match": cached.etag } : undefined);
|
|
288
316
|
if (response.status === 204) {
|
|
289
317
|
return;
|
|
290
318
|
}
|
|
291
|
-
|
|
319
|
+
if (response.status === 304) {
|
|
320
|
+
if (!cached) {
|
|
321
|
+
throw new ApiError(304, "Not Modified without a cached copy");
|
|
322
|
+
}
|
|
323
|
+
return JSON.parse(cached.body);
|
|
324
|
+
}
|
|
325
|
+
const etag = cacheKey ? response.headers.get("ETag") : null;
|
|
326
|
+
if (!cacheKey || !etag) {
|
|
327
|
+
return await raceAbort(response.json(), signal);
|
|
328
|
+
}
|
|
329
|
+
const text = await raceAbort(response.text(), signal);
|
|
330
|
+
await this.etagCache?.set(cacheKey, { etag, body: text });
|
|
331
|
+
return JSON.parse(text);
|
|
292
332
|
});
|
|
293
333
|
}
|
|
294
334
|
async requestOrNull(method, path, body, opts = {}) {
|
|
@@ -331,9 +371,12 @@ class BaseClient {
|
|
|
331
371
|
callerSignal?.removeEventListener("abort", forward);
|
|
332
372
|
}
|
|
333
373
|
}
|
|
334
|
-
async fetchResponse(method, path, body, signal) {
|
|
374
|
+
async fetchResponse(method, path, body, signal, extraHeaders) {
|
|
335
375
|
const url = `${this.baseUrl}${path}`;
|
|
336
|
-
const headers =
|
|
376
|
+
const headers = {
|
|
377
|
+
...BaseClient.authHeaders(this.apiKey),
|
|
378
|
+
...extraHeaders
|
|
379
|
+
};
|
|
337
380
|
headers["x-sl-origin"] = this.origin;
|
|
338
381
|
let serializedBody;
|
|
339
382
|
if (body !== undefined && body !== null) {
|
|
@@ -358,6 +401,8 @@ class BaseClient {
|
|
|
358
401
|
throw err;
|
|
359
402
|
throw new ApiError(0, `Cannot reach API at ${this.baseUrl}. Check your connection or try again.`, undefined, undefined, { retryable: true, cause: err instanceof Error ? err : undefined });
|
|
360
403
|
}
|
|
404
|
+
if (response.status === 304)
|
|
405
|
+
return response;
|
|
361
406
|
if (!response.ok) {
|
|
362
407
|
const errorBody = signal ? await raceAbort(response.text(), signal) : await response.text();
|
|
363
408
|
const envelope = parseErrorEnvelope(errorBody);
|
|
@@ -2643,5 +2688,5 @@ export {
|
|
|
2643
2688
|
Subgraphs
|
|
2644
2689
|
};
|
|
2645
2690
|
|
|
2646
|
-
//# debugId=
|
|
2691
|
+
//# debugId=455A5D582FCD1C3A64756E2164756E21
|
|
2647
2692
|
//# sourceMappingURL=index.js.map
|