@secondlayer/sdk 9.0.2 → 10.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/AGENTS.md +85 -17
- package/README.md +98 -31
- package/dist/index.d.ts +234 -113
- package/dist/index.js +768 -436
- package/dist/index.js.map +19 -19
- package/dist/sinks/bun-sqlite.d.ts +18 -4
- package/dist/sinks/bun-sqlite.js +21 -20
- package/dist/sinks/bun-sqlite.js.map +5 -5
- package/dist/sinks/core.d.ts +30 -7
- package/dist/sinks/core.js +16 -19
- package/dist/sinks/core.js.map +4 -4
- package/dist/sinks/drizzle.d.ts +23 -6
- package/dist/sinks/drizzle.js +22 -21
- package/dist/sinks/drizzle.js.map +5 -5
- package/dist/sinks/kysely.d.ts +26 -11
- package/dist/sinks/kysely.js +23 -21
- package/dist/sinks/kysely.js.map +5 -5
- package/dist/sinks/testing.d.ts +4 -1
- package/dist/sinks/testing.js +14 -1
- package/dist/sinks/testing.js.map +3 -3
- package/dist/streams/index.d.ts +92 -23
- package/dist/streams/index.js +395 -183
- package/dist/streams/index.js.map +12 -12
- package/dist/subgraphs/index.d.ts +202 -94
- package/dist/subgraphs/index.js +764 -434
- package/dist/subgraphs/index.js.map +17 -17
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -85,8 +85,8 @@ class ApiError extends SecondLayerError {
|
|
|
85
85
|
|
|
86
86
|
class AuthError extends ApiError {
|
|
87
87
|
status = 401;
|
|
88
|
-
constructor(message = "API key invalid or expired.") {
|
|
89
|
-
super(401, message);
|
|
88
|
+
constructor(message = "API key invalid or expired.", body, code) {
|
|
89
|
+
super(401, message, body, code);
|
|
90
90
|
this.name = "AuthError";
|
|
91
91
|
}
|
|
92
92
|
}
|
|
@@ -94,8 +94,8 @@ class AuthError extends ApiError {
|
|
|
94
94
|
class RateLimitError extends ApiError {
|
|
95
95
|
retryAfter;
|
|
96
96
|
status = 429;
|
|
97
|
-
constructor(message = "Rate limited. Try again later.", retryAfter) {
|
|
98
|
-
super(429, message,
|
|
97
|
+
constructor(message = "Rate limited. Try again later.", retryAfter, body, code) {
|
|
98
|
+
super(429, message, body, code, {
|
|
99
99
|
retryable: true,
|
|
100
100
|
retryAfterSeconds: parseRetryAfter(retryAfter)
|
|
101
101
|
});
|
|
@@ -105,8 +105,8 @@ class RateLimitError extends ApiError {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
class ValidationError extends ApiError {
|
|
108
|
-
constructor(message, status, body) {
|
|
109
|
-
super(status, message, body,
|
|
108
|
+
constructor(message, status, body, code) {
|
|
109
|
+
super(status, message, body, code, { retryable: false });
|
|
110
110
|
this.name = "ValidationError";
|
|
111
111
|
}
|
|
112
112
|
}
|
|
@@ -122,18 +122,22 @@ function parseRetryAfter(value) {
|
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
class VersionConflictError extends ApiError {
|
|
126
|
-
currentVersion;
|
|
127
|
-
expectedVersion;
|
|
128
|
-
constructor(currentVersion, expectedVersion, message = `Version conflict: expected ${expectedVersion}, current ${currentVersion}`) {
|
|
129
|
-
super(409, message, { currentVersion, expectedVersion });
|
|
130
|
-
this.currentVersion = currentVersion;
|
|
131
|
-
this.expectedVersion = expectedVersion;
|
|
132
|
-
this.name = "VersionConflictError";
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
125
|
// src/base.ts
|
|
126
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
127
|
+
function abortError() {
|
|
128
|
+
return new DOMException("This operation was aborted", "AbortError");
|
|
129
|
+
}
|
|
130
|
+
function raceAbort(promise, signal) {
|
|
131
|
+
if (signal.aborted)
|
|
132
|
+
return Promise.reject(signal.reason ?? abortError());
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
const onAbort = () => reject(signal.reason ?? abortError());
|
|
135
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
136
|
+
promise.then(resolve, reject).finally(() => {
|
|
137
|
+
signal.removeEventListener("abort", onAbort);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
137
141
|
var LOCAL_API_URL = "http://127.0.0.1:3800";
|
|
138
142
|
function resolveBaseUrl(explicit) {
|
|
139
143
|
if (explicit && explicit.length > 0)
|
|
@@ -163,6 +167,9 @@ function resolveApiKey(apiKey) {
|
|
|
163
167
|
}
|
|
164
168
|
return instanceToken ?? legacyKey;
|
|
165
169
|
}
|
|
170
|
+
function seg(value) {
|
|
171
|
+
return encodeURIComponent(String(value));
|
|
172
|
+
}
|
|
166
173
|
function buildQuery(params) {
|
|
167
174
|
const search = new URLSearchParams;
|
|
168
175
|
for (const [name, value] of Object.entries(params)) {
|
|
@@ -176,16 +183,39 @@ function buildQuery(params) {
|
|
|
176
183
|
const query = search.toString();
|
|
177
184
|
return query ? `?${query}` : "";
|
|
178
185
|
}
|
|
186
|
+
function parseErrorEnvelope(text) {
|
|
187
|
+
if (text.length === 0)
|
|
188
|
+
return { body: undefined };
|
|
189
|
+
try {
|
|
190
|
+
const json = JSON.parse(text);
|
|
191
|
+
let message;
|
|
192
|
+
let code;
|
|
193
|
+
if (json && typeof json === "object") {
|
|
194
|
+
const err = json.error ?? json.message;
|
|
195
|
+
if (typeof err === "string" && err.length > 0)
|
|
196
|
+
message = err;
|
|
197
|
+
else if (err && typeof err === "object")
|
|
198
|
+
message = JSON.stringify(err);
|
|
199
|
+
if (typeof json.code === "string")
|
|
200
|
+
code = json.code;
|
|
201
|
+
}
|
|
202
|
+
return { message, code, body: json };
|
|
203
|
+
} catch {
|
|
204
|
+
return { message: text, body: text };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
179
207
|
|
|
180
208
|
class BaseClient {
|
|
181
209
|
baseUrl;
|
|
182
210
|
apiKey;
|
|
183
211
|
origin;
|
|
184
212
|
fetchImpl;
|
|
213
|
+
requestTimeoutMs;
|
|
185
214
|
constructor(options = {}) {
|
|
186
215
|
this.baseUrl = resolveBaseUrl(options.baseUrl);
|
|
187
216
|
this.apiKey = resolveApiKey(options.apiKey);
|
|
188
217
|
this.origin = options.origin ?? "cli";
|
|
218
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
189
219
|
this.fetchImpl = options.fetchImpl ?? ((...args) => fetch(...args));
|
|
190
220
|
}
|
|
191
221
|
static authHeaders(apiKey) {
|
|
@@ -197,27 +227,56 @@ class BaseClient {
|
|
|
197
227
|
}
|
|
198
228
|
return headers;
|
|
199
229
|
}
|
|
200
|
-
async request(method, path, body) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
230
|
+
async request(method, path, body, opts = {}) {
|
|
231
|
+
return this.withRequestBudget(opts.signal, async (signal) => {
|
|
232
|
+
const response = await this.fetchResponse(method, path, body, signal);
|
|
233
|
+
if (response.status === 204) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
return await raceAbort(response.json(), signal);
|
|
237
|
+
});
|
|
206
238
|
}
|
|
207
|
-
async requestOrNull(method, path, body) {
|
|
239
|
+
async requestOrNull(method, path, body, opts = {}) {
|
|
208
240
|
try {
|
|
209
|
-
return await this.request(method, path, body);
|
|
241
|
+
return await this.request(method, path, body, opts);
|
|
210
242
|
} catch (err) {
|
|
211
243
|
if (err instanceof ApiError && err.status === 404)
|
|
212
244
|
return null;
|
|
213
245
|
throw err;
|
|
214
246
|
}
|
|
215
247
|
}
|
|
216
|
-
async requestText(method, path, body) {
|
|
217
|
-
|
|
218
|
-
|
|
248
|
+
async requestText(method, path, body, opts = {}) {
|
|
249
|
+
return this.withRequestBudget(opts.signal, async (signal) => {
|
|
250
|
+
const response = await this.fetchResponse(method, path, body, signal);
|
|
251
|
+
return raceAbort(response.text(), signal);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
async withRequestBudget(callerSignal, run) {
|
|
255
|
+
const controller = new AbortController;
|
|
256
|
+
let timedOut = false;
|
|
257
|
+
const forward = () => controller.abort(callerSignal?.reason ?? abortError());
|
|
258
|
+
if (callerSignal?.aborted)
|
|
259
|
+
forward();
|
|
260
|
+
else
|
|
261
|
+
callerSignal?.addEventListener("abort", forward, { once: true });
|
|
262
|
+
const timer = this.requestTimeoutMs > 0 ? setTimeout(() => {
|
|
263
|
+
timedOut = true;
|
|
264
|
+
controller.abort(abortError());
|
|
265
|
+
}, this.requestTimeoutMs) : undefined;
|
|
266
|
+
try {
|
|
267
|
+
return await run(controller.signal);
|
|
268
|
+
} catch (err) {
|
|
269
|
+
if (timedOut && !callerSignal?.aborted) {
|
|
270
|
+
throw new ApiError(0, `No response from ${this.baseUrl} within ${this.requestTimeoutMs}ms. Retryable. Raise requestTimeoutMs for slow filtered reads.`, undefined, "REQUEST_TIMEOUT", { retryable: true, cause: err instanceof Error ? err : undefined });
|
|
271
|
+
}
|
|
272
|
+
throw err;
|
|
273
|
+
} finally {
|
|
274
|
+
if (timer !== undefined)
|
|
275
|
+
clearTimeout(timer);
|
|
276
|
+
callerSignal?.removeEventListener("abort", forward);
|
|
277
|
+
}
|
|
219
278
|
}
|
|
220
|
-
async fetchResponse(method, path, body) {
|
|
279
|
+
async fetchResponse(method, path, body, signal) {
|
|
221
280
|
const url = `${this.baseUrl}${path}`;
|
|
222
281
|
const headers = BaseClient.authHeaders(this.apiKey);
|
|
223
282
|
headers["x-sl-origin"] = this.origin;
|
|
@@ -232,50 +291,254 @@ class BaseClient {
|
|
|
232
291
|
}
|
|
233
292
|
let response;
|
|
234
293
|
try {
|
|
235
|
-
|
|
294
|
+
const pending = this.fetchImpl(url, {
|
|
236
295
|
method,
|
|
237
296
|
headers,
|
|
238
|
-
body: serializedBody
|
|
297
|
+
body: serializedBody,
|
|
298
|
+
signal
|
|
239
299
|
});
|
|
300
|
+
response = signal ? await raceAbort(pending, signal) : await pending;
|
|
240
301
|
} catch (err) {
|
|
302
|
+
if (signal?.aborted)
|
|
303
|
+
throw err;
|
|
241
304
|
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 });
|
|
242
305
|
}
|
|
243
306
|
if (!response.ok) {
|
|
307
|
+
const errorBody = signal ? await raceAbort(response.text(), signal) : await response.text();
|
|
308
|
+
const envelope = parseErrorEnvelope(errorBody);
|
|
309
|
+
const retryAfter = response.headers.get("Retry-After") ?? undefined;
|
|
244
310
|
if (response.status === 401) {
|
|
245
|
-
throw new AuthError;
|
|
311
|
+
throw new AuthError(envelope.message ?? "API key invalid or expired.", envelope.body, envelope.code);
|
|
246
312
|
}
|
|
247
313
|
if (response.status === 429) {
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
throw new RateLimitError(msg, retryAfter ?? undefined);
|
|
314
|
+
const fallback = retryAfter ? `Rate limited. Wait ${retryAfter} seconds.` : "Rate limited. Try again later.";
|
|
315
|
+
throw new RateLimitError(envelope.message ?? fallback, retryAfter, envelope.body, envelope.code);
|
|
251
316
|
}
|
|
252
317
|
if (response.status >= 500) {
|
|
253
|
-
|
|
318
|
+
const retryAfterSeconds = parseRetryAfter(retryAfter);
|
|
319
|
+
throw new ApiError(response.status, envelope.message ?? `Server error. Try again or check status at ${this.baseUrl}/health`, envelope.body, envelope.code, retryAfterSeconds !== undefined ? { retryAfterSeconds } : {});
|
|
254
320
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
321
|
+
throw new ApiError(response.status, envelope.message ?? `HTTP ${response.status}`, envelope.body, envelope.code);
|
|
322
|
+
}
|
|
323
|
+
return response;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/streams/subscribe.ts
|
|
328
|
+
import { ed25519 } from "@secondlayer/shared";
|
|
329
|
+
|
|
330
|
+
// src/streams/errors.ts
|
|
331
|
+
class StreamsServerError extends ApiError {
|
|
332
|
+
constructor(message, status, body, code) {
|
|
333
|
+
super(status, message, body, code, { retryable: true });
|
|
334
|
+
this.name = "StreamsServerError";
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
class StreamsSignatureError extends SecondLayerError {
|
|
339
|
+
constructor(message = "Streams response signature verification failed.") {
|
|
340
|
+
super(message);
|
|
341
|
+
this.name = "StreamsSignatureError";
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async function mapStreamsError(response) {
|
|
345
|
+
const { message, code, body } = parseErrorEnvelope(await response.text());
|
|
346
|
+
if (response.status === 401) {
|
|
347
|
+
throw new AuthError(message ?? "API key invalid or expired.", body, code);
|
|
348
|
+
}
|
|
349
|
+
if (response.status === 429) {
|
|
350
|
+
const retryAfter = response.headers.get("Retry-After") ?? undefined;
|
|
351
|
+
throw new RateLimitError(message ?? "Rate limited. Try again later.", retryAfter, body, code);
|
|
352
|
+
}
|
|
353
|
+
if (response.status >= 500) {
|
|
354
|
+
throw new StreamsServerError(message ?? `Streams server returned ${response.status}.`, response.status, body, code);
|
|
355
|
+
}
|
|
356
|
+
throw new ValidationError(message ?? `Streams request returned ${response.status}.`, response.status, body, code);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/streams/subscribe.ts
|
|
360
|
+
var SUBSCRIBE_MAX_RECONNECT_DELAY_MS = 30000;
|
|
361
|
+
var SUBSCRIBE_DEFAULT_STALE_AFTER_MS = 60000;
|
|
362
|
+
function reconnectDelay(attempt, baseMs, retryAfterSeconds, random = Math.random) {
|
|
363
|
+
const capped = Math.min(baseMs * 2 ** Math.max(0, attempt), SUBSCRIBE_MAX_RECONNECT_DELAY_MS);
|
|
364
|
+
const jittered = capped * (0.5 + random() * 0.5);
|
|
365
|
+
const floor = (retryAfterSeconds ?? 0) * 1000;
|
|
366
|
+
return Math.max(jittered, floor);
|
|
367
|
+
}
|
|
368
|
+
function isTerminal(err) {
|
|
369
|
+
if (err instanceof StreamsSignatureError)
|
|
370
|
+
return true;
|
|
371
|
+
return err?.retryable === false;
|
|
372
|
+
}
|
|
373
|
+
function subscribeStreamsEvents(opts) {
|
|
374
|
+
const { params } = opts;
|
|
375
|
+
const controller = new AbortController;
|
|
376
|
+
const external = params.signal;
|
|
377
|
+
if (external) {
|
|
378
|
+
if (external.aborted)
|
|
379
|
+
controller.abort();
|
|
380
|
+
else
|
|
381
|
+
external.addEventListener("abort", () => controller.abort(), {
|
|
382
|
+
once: true
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
let cursor = params.fromCursor ?? null;
|
|
386
|
+
const baseDelayMs = opts.reconnectDelayMs ?? params.reconnectDelayMs ?? 1000;
|
|
387
|
+
const staleAfterMs = opts.staleAfterMs ?? params.staleAfterMs ?? SUBSCRIBE_DEFAULT_STALE_AFTER_MS;
|
|
388
|
+
let attempt = 0;
|
|
389
|
+
const run = async () => {
|
|
390
|
+
while (!controller.signal.aborted) {
|
|
259
391
|
try {
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
392
|
+
const url = `${opts.baseUrl}/v1/streams/events/stream${buildQuery({
|
|
393
|
+
from_cursor: cursor ?? undefined,
|
|
394
|
+
types: params.types,
|
|
395
|
+
not_types: params.notTypes,
|
|
396
|
+
contract_id: params.contractId,
|
|
397
|
+
sender: params.sender,
|
|
398
|
+
recipient: params.recipient,
|
|
399
|
+
asset_identifier: params.assetIdentifier,
|
|
400
|
+
filters: params.filters ? JSON.stringify(params.filters) : undefined
|
|
401
|
+
})}`;
|
|
402
|
+
await readSse({
|
|
403
|
+
url,
|
|
404
|
+
headers: opts.headers ?? {},
|
|
405
|
+
signal: controller.signal,
|
|
406
|
+
fetchImpl: opts.fetchImpl,
|
|
407
|
+
staleAfterMs,
|
|
408
|
+
mapError: mapStreamsError,
|
|
409
|
+
onFrame: async (frame) => {
|
|
410
|
+
attempt = 0;
|
|
411
|
+
if (frame.event === "ping" || !frame.data)
|
|
412
|
+
return;
|
|
413
|
+
let parsed;
|
|
414
|
+
try {
|
|
415
|
+
parsed = JSON.parse(frame.data);
|
|
416
|
+
} catch {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (!parsed.event)
|
|
420
|
+
return;
|
|
421
|
+
if (opts.verify !== "off") {
|
|
422
|
+
if (!parsed.sig) {
|
|
423
|
+
if (opts.verify === "strict") {
|
|
424
|
+
throw new StreamsSignatureError("Streams SSE frame signature is missing.");
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
const key = await opts.loadKey(parsed.key_id);
|
|
428
|
+
if (!ed25519.verifyEd25519(JSON.stringify(parsed.event), parsed.sig, key.publicKey)) {
|
|
429
|
+
throw new StreamsSignatureError("Streams SSE frame signature is invalid.");
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
await params.onEvent(parsed.event);
|
|
434
|
+
cursor = parsed.event.cursor ?? cursor;
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
await sleep(reconnectDelay(attempt, baseDelayMs, undefined, opts.random), controller.signal);
|
|
438
|
+
attempt += 1;
|
|
439
|
+
} catch (err) {
|
|
440
|
+
if (controller.signal.aborted)
|
|
441
|
+
return;
|
|
442
|
+
params.onError?.(err);
|
|
443
|
+
if (isTerminal(err))
|
|
444
|
+
throw err;
|
|
445
|
+
const retryAfterSeconds = err.retryAfterSeconds;
|
|
446
|
+
await sleep(reconnectDelay(attempt, baseDelayMs, retryAfterSeconds, opts.random), controller.signal);
|
|
447
|
+
attempt += 1;
|
|
274
448
|
}
|
|
275
|
-
throw new ApiError(response.status, message, parsedBody, code);
|
|
276
449
|
}
|
|
277
|
-
|
|
450
|
+
};
|
|
451
|
+
const done = run();
|
|
452
|
+
done.catch(() => {});
|
|
453
|
+
const unsubscribe = () => controller.abort();
|
|
454
|
+
return Object.assign(unsubscribe, { done });
|
|
455
|
+
}
|
|
456
|
+
async function readSse(opts) {
|
|
457
|
+
const res = await opts.fetchImpl(opts.url, {
|
|
458
|
+
headers: { ...opts.headers ?? {}, Accept: "text/event-stream" },
|
|
459
|
+
signal: opts.signal
|
|
460
|
+
});
|
|
461
|
+
if (!res.ok) {
|
|
462
|
+
if (opts.mapError)
|
|
463
|
+
await opts.mapError(res);
|
|
464
|
+
throw new StreamsServerError(`Streams SSE returned ${res.status}.`, res.status);
|
|
465
|
+
}
|
|
466
|
+
if (!res.body) {
|
|
467
|
+
throw new StreamsServerError("Streams SSE response has no body.", 0);
|
|
468
|
+
}
|
|
469
|
+
for await (const frame of parseSseFrames(res.body, opts.signal, opts.staleAfterMs)) {
|
|
470
|
+
await opts.onFrame(frame);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
function sleep(ms, signal) {
|
|
474
|
+
return new Promise((resolve) => {
|
|
475
|
+
if (signal.aborted)
|
|
476
|
+
return resolve();
|
|
477
|
+
const onAbort = () => {
|
|
478
|
+
clearTimeout(timer);
|
|
479
|
+
resolve();
|
|
480
|
+
};
|
|
481
|
+
const timer = setTimeout(() => {
|
|
482
|
+
signal.removeEventListener("abort", onAbort);
|
|
483
|
+
resolve();
|
|
484
|
+
}, ms);
|
|
485
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
async function readWithStaleTimer(reader, staleAfterMs) {
|
|
489
|
+
if (staleAfterMs === undefined || !Number.isFinite(staleAfterMs)) {
|
|
490
|
+
return reader.read();
|
|
491
|
+
}
|
|
492
|
+
let timer;
|
|
493
|
+
const stale = new Promise((_, reject) => {
|
|
494
|
+
timer = setTimeout(() => reject(new StreamsServerError(`Streams SSE went quiet for ${staleAfterMs} ms; reconnecting.`, 0, undefined, "STREAM_STALE")), staleAfterMs);
|
|
495
|
+
});
|
|
496
|
+
try {
|
|
497
|
+
return await Promise.race([reader.read(), stale]);
|
|
498
|
+
} finally {
|
|
499
|
+
clearTimeout(timer);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
async function* parseSseFrames(body, signal, staleAfterMs) {
|
|
503
|
+
const reader = body.getReader();
|
|
504
|
+
const decoder = new TextDecoder;
|
|
505
|
+
let buffer = "";
|
|
506
|
+
try {
|
|
507
|
+
while (!signal.aborted) {
|
|
508
|
+
const { value, done } = await readWithStaleTimer(reader, staleAfterMs);
|
|
509
|
+
if (done)
|
|
510
|
+
break;
|
|
511
|
+
buffer += decoder.decode(value, { stream: true });
|
|
512
|
+
let sep = buffer.indexOf(`
|
|
513
|
+
|
|
514
|
+
`);
|
|
515
|
+
while (sep !== -1) {
|
|
516
|
+
yield parseFrame(buffer.slice(0, sep));
|
|
517
|
+
buffer = buffer.slice(sep + 2);
|
|
518
|
+
sep = buffer.indexOf(`
|
|
519
|
+
|
|
520
|
+
`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
} finally {
|
|
524
|
+
try {
|
|
525
|
+
await reader.cancel();
|
|
526
|
+
} catch {}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
function parseFrame(raw) {
|
|
530
|
+
let event;
|
|
531
|
+
const data = [];
|
|
532
|
+
for (const line of raw.split(`
|
|
533
|
+
`)) {
|
|
534
|
+
if (line.startsWith("data:")) {
|
|
535
|
+
data.push(line.slice(line.startsWith("data: ") ? 6 : 5));
|
|
536
|
+
} else if (line.startsWith("event:")) {
|
|
537
|
+
event = line.slice(line.startsWith("event: ") ? 7 : 6).trim();
|
|
538
|
+
}
|
|
278
539
|
}
|
|
540
|
+
return { event, data: data.length > 0 ? data.join(`
|
|
541
|
+
`) : undefined };
|
|
279
542
|
}
|
|
280
543
|
|
|
281
544
|
// src/subgraphs/serialize.ts
|
|
@@ -283,21 +546,28 @@ var SYSTEM_COLUMN_MAP = {
|
|
|
283
546
|
_blockHeight: "_block_height",
|
|
284
547
|
_txId: "_tx_id",
|
|
285
548
|
_createdAt: "_created_at",
|
|
286
|
-
_id: "_id"
|
|
549
|
+
_id: "_id"
|
|
550
|
+
};
|
|
551
|
+
var SYSTEM_COLUMN_ALIASES = {
|
|
287
552
|
blockHeight: "_block_height",
|
|
288
553
|
txId: "_tx_id",
|
|
289
554
|
createdAt: "_created_at",
|
|
290
555
|
id: "_id"
|
|
291
556
|
};
|
|
292
|
-
function resolveColumn(col) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
557
|
+
function resolveColumn(col, columns) {
|
|
558
|
+
const canonical = SYSTEM_COLUMN_MAP[col];
|
|
559
|
+
if (canonical)
|
|
560
|
+
return canonical;
|
|
561
|
+
if (columns?.has(col))
|
|
562
|
+
return col;
|
|
563
|
+
return SYSTEM_COLUMN_ALIASES[col] ?? col;
|
|
564
|
+
}
|
|
565
|
+
function serializeWhere(where, columns) {
|
|
296
566
|
const filters = {};
|
|
297
567
|
for (const [column, value] of Object.entries(where)) {
|
|
298
568
|
if (value === null || value === undefined)
|
|
299
569
|
continue;
|
|
300
|
-
const col = resolveColumn(column);
|
|
570
|
+
const col = resolveColumn(column, columns);
|
|
301
571
|
if (typeof value === "object" && !Array.isArray(value)) {
|
|
302
572
|
const ops = value;
|
|
303
573
|
for (const [op, opValue] of Object.entries(ops)) {
|
|
@@ -318,11 +588,43 @@ function serializeWhere(where) {
|
|
|
318
588
|
}
|
|
319
589
|
return filters;
|
|
320
590
|
}
|
|
321
|
-
function resolveOrderByColumn(col) {
|
|
322
|
-
return resolveColumn(col);
|
|
591
|
+
function resolveOrderByColumn(col, columns) {
|
|
592
|
+
return resolveColumn(col, columns);
|
|
323
593
|
}
|
|
324
594
|
|
|
325
595
|
// src/subgraphs/client.ts
|
|
596
|
+
var SUBSCRIBE_RECONNECT_DELAY_MS = 1000;
|
|
597
|
+
function declaredColumns(table) {
|
|
598
|
+
if (!table || typeof table !== "object")
|
|
599
|
+
return;
|
|
600
|
+
const columns = table.columns;
|
|
601
|
+
if (!columns || typeof columns !== "object")
|
|
602
|
+
return;
|
|
603
|
+
return new Set(Object.keys(columns));
|
|
604
|
+
}
|
|
605
|
+
function rowBlockHeight(row) {
|
|
606
|
+
const r = row;
|
|
607
|
+
const raw = r._block_height ?? r._blockHeight;
|
|
608
|
+
if (typeof raw !== "number" && typeof raw !== "string")
|
|
609
|
+
return;
|
|
610
|
+
const height = Number(raw);
|
|
611
|
+
return Number.isFinite(height) ? height : undefined;
|
|
612
|
+
}
|
|
613
|
+
function sleep2(ms, signal) {
|
|
614
|
+
return new Promise((resolve) => {
|
|
615
|
+
if (signal.aborted)
|
|
616
|
+
return resolve();
|
|
617
|
+
const onAbort = () => {
|
|
618
|
+
clearTimeout(timer);
|
|
619
|
+
resolve();
|
|
620
|
+
};
|
|
621
|
+
const timer = setTimeout(() => {
|
|
622
|
+
signal.removeEventListener("abort", onAbort);
|
|
623
|
+
resolve();
|
|
624
|
+
}, ms);
|
|
625
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
626
|
+
});
|
|
627
|
+
}
|
|
326
628
|
function buildSubgraphQueryString(params) {
|
|
327
629
|
return buildQuery({
|
|
328
630
|
_sort: params.sort,
|
|
@@ -352,25 +654,25 @@ class Subgraphs extends BaseClient {
|
|
|
352
654
|
return this.request("GET", "/api/subgraphs");
|
|
353
655
|
}
|
|
354
656
|
async status(name) {
|
|
355
|
-
return this.request("GET", `/api/subgraphs/${name}`);
|
|
657
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}`);
|
|
356
658
|
}
|
|
357
659
|
async openapi(name, options) {
|
|
358
|
-
return this.request("GET", `/api/subgraphs/${name}/openapi.json${buildSpecQueryString(options)}`);
|
|
660
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/openapi.json${buildSpecQueryString(options)}`);
|
|
359
661
|
}
|
|
360
662
|
async schema(name, options) {
|
|
361
|
-
return this.request("GET", `/api/subgraphs/${name}/schema.json${buildSpecQueryString(options)}`);
|
|
663
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/schema.json${buildSpecQueryString(options)}`);
|
|
362
664
|
}
|
|
363
665
|
async markdown(name, options) {
|
|
364
|
-
return this.requestText("GET", `/api/subgraphs/${name}/docs.md${buildSpecQueryString(options)}`);
|
|
666
|
+
return this.requestText("GET", `/api/subgraphs/${seg(name)}/docs.md${buildSpecQueryString(options)}`);
|
|
365
667
|
}
|
|
366
668
|
async reindex(name) {
|
|
367
|
-
return this.request("POST", `/api/subgraphs/${name}/reindex`);
|
|
669
|
+
return this.request("POST", `/api/subgraphs/${seg(name)}/reindex`);
|
|
368
670
|
}
|
|
369
671
|
async stop(name) {
|
|
370
|
-
return this.request("POST", `/api/subgraphs/${name}/stop`);
|
|
672
|
+
return this.request("POST", `/api/subgraphs/${seg(name)}/stop`);
|
|
371
673
|
}
|
|
372
674
|
async backfill(name, options) {
|
|
373
|
-
return this.request("POST", `/api/subgraphs/${name}/backfill`, options);
|
|
675
|
+
return this.request("POST", `/api/subgraphs/${seg(name)}/backfill`, options);
|
|
374
676
|
}
|
|
375
677
|
async gaps(name, opts) {
|
|
376
678
|
const qs = buildQuery({
|
|
@@ -378,62 +680,62 @@ class Subgraphs extends BaseClient {
|
|
|
378
680
|
_offset: opts?.offset,
|
|
379
681
|
resolved: opts?.resolved
|
|
380
682
|
});
|
|
381
|
-
return this.request("GET", `/api/subgraphs/${name}/gaps${qs}`);
|
|
683
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/gaps${qs}`);
|
|
382
684
|
}
|
|
383
685
|
async delete(name, options) {
|
|
384
686
|
const qs = buildQuery({ force: options?.force ? true : undefined });
|
|
385
|
-
return this.request("DELETE", `/api/subgraphs/${name}${qs}`);
|
|
687
|
+
return this.request("DELETE", `/api/subgraphs/${seg(name)}${qs}`);
|
|
386
688
|
}
|
|
387
689
|
async rows(name, table, params = {}) {
|
|
388
690
|
const { cursor, ...rest } = params;
|
|
389
691
|
const qs = buildSubgraphQueryString(rest);
|
|
390
692
|
const sep = qs ? "&" : "?";
|
|
391
693
|
const cursorQs = cursor ? `${sep}cursor=${encodeURIComponent(cursor)}` : "";
|
|
392
|
-
return this.request("GET", `/v1/subgraphs/${name}/${table}${qs}${cursorQs}`);
|
|
694
|
+
return this.request("GET", `/v1/subgraphs/${seg(name)}/${seg(table)}${qs}${cursorQs}`);
|
|
393
695
|
}
|
|
394
696
|
async operations(name) {
|
|
395
|
-
return this.request("GET", `/api/subgraphs/${name}/operations`);
|
|
697
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/operations`);
|
|
396
698
|
}
|
|
397
699
|
async getOperation(name, operationId) {
|
|
398
|
-
return this.request("GET", `/api/subgraphs/${name}/operations/${operationId}`);
|
|
700
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/operations/${seg(operationId)}`);
|
|
399
701
|
}
|
|
400
702
|
async deploy(data) {
|
|
401
703
|
return this.request("POST", "/api/subgraphs", data);
|
|
402
704
|
}
|
|
403
705
|
async getSource(name) {
|
|
404
|
-
return this.request("GET", `/api/subgraphs/${name}/source`);
|
|
706
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/source`);
|
|
405
707
|
}
|
|
406
708
|
async bundle(data) {
|
|
407
709
|
return this.request("POST", "/api/subgraphs/bundle", data);
|
|
408
710
|
}
|
|
409
711
|
async queryTable(name, table, params = {}) {
|
|
410
|
-
const result = await this.request("GET", `/api/subgraphs/${name}/${table}${buildSubgraphQueryString(params)}`);
|
|
712
|
+
const result = await this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}${buildSubgraphQueryString(params)}`);
|
|
411
713
|
return Array.isArray(result) ? result : result.data;
|
|
412
714
|
}
|
|
413
715
|
async queryTableCount(name, table, params = {}) {
|
|
414
|
-
return this.request("GET", `/api/subgraphs/${name}/${table}/count${buildSubgraphQueryString(params)}`);
|
|
716
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}/count${buildSubgraphQueryString(params)}`);
|
|
415
717
|
}
|
|
416
718
|
async queryTableAggregate(name, table, params = {}) {
|
|
417
|
-
return this.request("GET", `/api/subgraphs/${name}/${table}/aggregate${buildAggregateQueryString(params)}`);
|
|
719
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}/aggregate${buildAggregateQueryString(params)}`);
|
|
418
720
|
}
|
|
419
721
|
typed(def) {
|
|
420
722
|
const result = {};
|
|
421
|
-
for (const tableName of Object.
|
|
422
|
-
result[tableName] = this.createTableClient(def.name, tableName);
|
|
723
|
+
for (const [tableName, table] of Object.entries(def.schema)) {
|
|
724
|
+
result[tableName] = this.createTableClient(def.name, tableName, declaredColumns(table));
|
|
423
725
|
}
|
|
424
726
|
return result;
|
|
425
727
|
}
|
|
426
|
-
createTableClient(subgraphName, tableName) {
|
|
728
|
+
createTableClient(subgraphName, tableName, columns) {
|
|
427
729
|
const self = this;
|
|
428
730
|
return {
|
|
429
731
|
async findMany(options = {}) {
|
|
430
|
-
const filters = options.where ? serializeWhere(options.where) : undefined;
|
|
732
|
+
const filters = options.where ? serializeWhere(options.where, columns) : undefined;
|
|
431
733
|
let sort;
|
|
432
734
|
let order;
|
|
433
735
|
if (options.orderBy) {
|
|
434
736
|
const entries = Array.isArray(options.orderBy) ? options.orderBy : Object.entries(options.orderBy);
|
|
435
737
|
if (entries.length > 0) {
|
|
436
|
-
sort = entries.map(([col]) => resolveOrderByColumn(col)).join(",");
|
|
738
|
+
sort = entries.map(([col]) => resolveOrderByColumn(col, columns)).join(",");
|
|
437
739
|
order = entries.map(([, dir]) => dir ?? "asc").join(",");
|
|
438
740
|
}
|
|
439
741
|
}
|
|
@@ -448,14 +750,14 @@ class Subgraphs extends BaseClient {
|
|
|
448
750
|
return self.queryTable(subgraphName, tableName, params);
|
|
449
751
|
},
|
|
450
752
|
async count(where) {
|
|
451
|
-
const filters = where ? serializeWhere(where) : undefined;
|
|
753
|
+
const filters = where ? serializeWhere(where, columns) : undefined;
|
|
452
754
|
const result = await self.queryTableCount(subgraphName, tableName, {
|
|
453
755
|
filters
|
|
454
756
|
});
|
|
455
757
|
return result.count;
|
|
456
758
|
},
|
|
457
759
|
async aggregate(spec) {
|
|
458
|
-
const filters = spec.where ? serializeWhere(spec.where) : undefined;
|
|
760
|
+
const filters = spec.where ? serializeWhere(spec.where, columns) : undefined;
|
|
459
761
|
const result = await self.queryTableAggregate(subgraphName, tableName, {
|
|
460
762
|
filters,
|
|
461
763
|
count: spec.count,
|
|
@@ -467,27 +769,47 @@ class Subgraphs extends BaseClient {
|
|
|
467
769
|
return result;
|
|
468
770
|
},
|
|
469
771
|
subscribe(onRow, options = {}) {
|
|
470
|
-
const filters = options.where ? serializeWhere(options.where) : {};
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
772
|
+
const filters = options.where ? serializeWhere(options.where, columns) : {};
|
|
773
|
+
const controller = new AbortController;
|
|
774
|
+
let since = options.since ?? undefined;
|
|
775
|
+
const run = async () => {
|
|
776
|
+
while (!controller.signal.aborted) {
|
|
777
|
+
try {
|
|
778
|
+
const qs = buildQuery({ ...filters, since });
|
|
779
|
+
await readSse({
|
|
780
|
+
url: `${self.baseUrl}/v1/subgraphs/${seg(subgraphName)}/${seg(tableName)}/stream${qs}`,
|
|
781
|
+
headers: {
|
|
782
|
+
...BaseClient.authHeaders(self.apiKey),
|
|
783
|
+
"x-sl-origin": self.origin
|
|
784
|
+
},
|
|
785
|
+
signal: controller.signal,
|
|
786
|
+
fetchImpl: self.fetchImpl,
|
|
787
|
+
onFrame: (frame) => {
|
|
788
|
+
if (frame.event === "ping" || !frame.data)
|
|
789
|
+
return;
|
|
790
|
+
let row;
|
|
791
|
+
try {
|
|
792
|
+
row = JSON.parse(frame.data);
|
|
793
|
+
} catch {
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
const height = rowBlockHeight(row);
|
|
797
|
+
if (height !== undefined)
|
|
798
|
+
since = height;
|
|
799
|
+
onRow(row);
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
await sleep2(SUBSCRIBE_RECONNECT_DELAY_MS, controller.signal);
|
|
803
|
+
} catch (err) {
|
|
804
|
+
if (controller.signal.aborted)
|
|
805
|
+
return;
|
|
806
|
+
options.onError?.(err);
|
|
807
|
+
await sleep2(SUBSCRIBE_RECONNECT_DELAY_MS, controller.signal);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
485
810
|
};
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
es.onerror = (ev) => handler(ev);
|
|
489
|
-
}
|
|
490
|
-
return () => es.close();
|
|
811
|
+
run();
|
|
812
|
+
return () => controller.abort();
|
|
491
813
|
}
|
|
492
814
|
};
|
|
493
815
|
}
|
|
@@ -518,28 +840,10 @@ import {
|
|
|
518
840
|
decodeStreamsCursor,
|
|
519
841
|
encodeStreamsCursor
|
|
520
842
|
} from "@secondlayer/shared";
|
|
521
|
-
|
|
522
|
-
// src/streams/errors.ts
|
|
523
|
-
class StreamsServerError extends ApiError {
|
|
524
|
-
constructor(message, status, body) {
|
|
525
|
-
super(status, message, body, undefined, { retryable: true });
|
|
526
|
-
this.name = "StreamsServerError";
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
class StreamsSignatureError extends SecondLayerError {
|
|
531
|
-
constructor(message = "Streams response signature verification failed.") {
|
|
532
|
-
super(message);
|
|
533
|
-
this.name = "StreamsSignatureError";
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// src/streams/cursor.ts
|
|
538
843
|
var Cursor = {
|
|
539
844
|
atHeight(height) {
|
|
540
|
-
if (height <= 0)
|
|
541
|
-
return
|
|
542
|
-
}
|
|
845
|
+
if (height <= 0)
|
|
846
|
+
return null;
|
|
543
847
|
return encodeStreamsCursor(blockEndCursor(height - 1));
|
|
544
848
|
},
|
|
545
849
|
parse(cursor) {
|
|
@@ -559,6 +863,39 @@ var Cursor = {
|
|
|
559
863
|
function reorgKey(reorg) {
|
|
560
864
|
return `${reorg.detected_at}|${reorg.fork_point_height}|${reorg.new_canonical_tip}`;
|
|
561
865
|
}
|
|
866
|
+
var DEFAULT_MAX_ROLLBACK_DEPTH = 1000;
|
|
867
|
+
async function applyReorgs(opts) {
|
|
868
|
+
const fresh = opts.reorgs.filter((reorg) => !opts.handled.has(opts.keyOf(reorg)));
|
|
869
|
+
if (fresh.length === 0)
|
|
870
|
+
return null;
|
|
871
|
+
for (const reorg of fresh) {
|
|
872
|
+
const fork = reorg.fork_point_height;
|
|
873
|
+
if (!Number.isSafeInteger(fork) || fork < 0) {
|
|
874
|
+
throw new ValidationError(`Reorg fork_point_height ${String(fork)} is not a non-negative integer. Rollback deletes every row at or above the fork from every declared table, so a malformed fork point is refused before the sink runs.`, 400);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
const position = resumeHeight(opts.cursor);
|
|
878
|
+
const applicable = fresh.filter((reorg) => position !== null && reorg.fork_point_height <= position).sort((a, b) => a.fork_point_height - b.fork_point_height);
|
|
879
|
+
for (const reorg of fresh) {
|
|
880
|
+
if (!applicable.includes(reorg))
|
|
881
|
+
opts.handled.add(opts.keyOf(reorg));
|
|
882
|
+
}
|
|
883
|
+
if (applicable.length === 0 || position === null)
|
|
884
|
+
return null;
|
|
885
|
+
const forkPoint = applicable[0]?.fork_point_height ?? position;
|
|
886
|
+
const depth = position - forkPoint;
|
|
887
|
+
const maxDepth = opts.maxRollbackDepth ?? DEFAULT_MAX_ROLLBACK_DEPTH;
|
|
888
|
+
if (depth > maxDepth) {
|
|
889
|
+
throw new ValidationError(`Reorg at fork ${forkPoint} rewinds ${depth} blocks below the checkpoint at height ${position}, past maxRollbackDepth (${maxDepth}). A rollback this deep deletes every row at or above the fork from every declared table. Confirm the source is trusted, then pass maxRollbackDepth: ${depth} or higher to allow it.`, 400);
|
|
890
|
+
}
|
|
891
|
+
const rewind = Cursor.atHeight(forkPoint);
|
|
892
|
+
for (const reorg of applicable) {
|
|
893
|
+
await opts.sink?.rollback(reorg.fork_point_height, rewind);
|
|
894
|
+
await opts.onReorg?.(reorg, { cursor: rewind });
|
|
895
|
+
opts.handled.add(opts.keyOf(reorg));
|
|
896
|
+
}
|
|
897
|
+
return { forkPoint, cursor: rewind };
|
|
898
|
+
}
|
|
562
899
|
function assertFinalizedCheckpoint(returned, checkpoint) {
|
|
563
900
|
if (returned === null || returned === undefined)
|
|
564
901
|
return;
|
|
@@ -638,17 +975,19 @@ async function defaultSleep(ms, signal) {
|
|
|
638
975
|
if (signal?.aborted)
|
|
639
976
|
return;
|
|
640
977
|
await new Promise((resolve) => {
|
|
641
|
-
const
|
|
642
|
-
if (!signal)
|
|
643
|
-
return;
|
|
644
|
-
signal.addEventListener("abort", () => {
|
|
978
|
+
const onAbort = () => {
|
|
645
979
|
clearTimeout(timeout);
|
|
646
980
|
resolve();
|
|
647
|
-
}
|
|
981
|
+
};
|
|
982
|
+
const timeout = setTimeout(() => {
|
|
983
|
+
signal?.removeEventListener("abort", onAbort);
|
|
984
|
+
resolve();
|
|
985
|
+
}, ms);
|
|
986
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
648
987
|
});
|
|
649
988
|
}
|
|
650
989
|
async function consumeStreamsEvents(opts) {
|
|
651
|
-
const
|
|
990
|
+
const sleep3 = opts.sleep ?? defaultSleep;
|
|
652
991
|
const mode = opts.mode ?? "tail";
|
|
653
992
|
const finalizedOnly = opts.finalizedOnly ?? false;
|
|
654
993
|
const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
|
|
@@ -658,6 +997,7 @@ async function consumeStreamsEvents(opts) {
|
|
|
658
997
|
const committedCursor = await opts.sink?.loadCursor() ?? null;
|
|
659
998
|
let cursor = opts.fromCursor ?? committedCursor;
|
|
660
999
|
const handledReorgs = new Set;
|
|
1000
|
+
let reorgSince = null;
|
|
661
1001
|
let pages = 0;
|
|
662
1002
|
let emptyPolls = 0;
|
|
663
1003
|
let height = resumeHeight(cursor);
|
|
@@ -677,27 +1017,61 @@ async function consumeStreamsEvents(opts) {
|
|
|
677
1017
|
retryCount: opts.retryCount,
|
|
678
1018
|
retryDelay: opts.retryDelay,
|
|
679
1019
|
onError: opts.onError,
|
|
680
|
-
sleep,
|
|
1020
|
+
sleep: sleep3,
|
|
681
1021
|
signal: opts.signal
|
|
682
1022
|
});
|
|
683
1023
|
pages++;
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
1024
|
+
const reorgsOn = !finalizedOnly && Boolean(opts.onReorg || opts.sink);
|
|
1025
|
+
if (reorgsOn) {
|
|
1026
|
+
const rewound = await applyReorgs({
|
|
1027
|
+
reorgs: envelope.reorgs,
|
|
1028
|
+
cursor,
|
|
1029
|
+
handled: handledReorgs,
|
|
1030
|
+
keyOf: reorgKey,
|
|
1031
|
+
sink: opts.sink,
|
|
1032
|
+
onReorg: opts.onReorg,
|
|
1033
|
+
maxRollbackDepth: opts.maxRollbackDepth
|
|
1034
|
+
});
|
|
1035
|
+
if (rewound) {
|
|
1036
|
+
cursor = rewound.cursor;
|
|
1037
|
+
height = rewound.forkPoint > 0 ? rewound.forkPoint - 1 : null;
|
|
696
1038
|
scanned = height;
|
|
697
1039
|
emptyPolls = 0;
|
|
698
1040
|
continue;
|
|
699
1041
|
}
|
|
700
1042
|
}
|
|
1043
|
+
if (reorgsOn && opts.fetchReorgs && envelope.events.length === 0) {
|
|
1044
|
+
reorgSince ??= cursor;
|
|
1045
|
+
if (reorgSince !== null) {
|
|
1046
|
+
const since = reorgSince;
|
|
1047
|
+
const fetchReorgs = opts.fetchReorgs;
|
|
1048
|
+
const listed = await fetchPageWithRetry(() => fetchReorgs({ since }), {
|
|
1049
|
+
retryCount: opts.retryCount,
|
|
1050
|
+
retryDelay: opts.retryDelay,
|
|
1051
|
+
onError: opts.onError,
|
|
1052
|
+
sleep: sleep3,
|
|
1053
|
+
signal: opts.signal
|
|
1054
|
+
});
|
|
1055
|
+
if (listed.next_since !== null)
|
|
1056
|
+
reorgSince = listed.next_since;
|
|
1057
|
+
const rewound = await applyReorgs({
|
|
1058
|
+
reorgs: listed.reorgs,
|
|
1059
|
+
cursor,
|
|
1060
|
+
handled: handledReorgs,
|
|
1061
|
+
keyOf: reorgKey,
|
|
1062
|
+
sink: opts.sink,
|
|
1063
|
+
onReorg: opts.onReorg,
|
|
1064
|
+
maxRollbackDepth: opts.maxRollbackDepth
|
|
1065
|
+
});
|
|
1066
|
+
if (rewound) {
|
|
1067
|
+
cursor = rewound.cursor;
|
|
1068
|
+
height = rewound.forkPoint > 0 ? rewound.forkPoint - 1 : null;
|
|
1069
|
+
scanned = height;
|
|
1070
|
+
emptyPolls = 0;
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
701
1075
|
const emitted = finalizedOnly ? envelope.events.filter((event) => event.finalized) : envelope.events;
|
|
702
1076
|
const checkpoint = finalizedOnly ? emitted.at(-1)?.cursor ?? cursor : envelope.next_cursor;
|
|
703
1077
|
height = emitted.at(-1)?.block_height ?? height;
|
|
@@ -738,7 +1112,7 @@ async function consumeStreamsEvents(opts) {
|
|
|
738
1112
|
if (mode === "bounded") {
|
|
739
1113
|
return { cursor, pages, emptyPolls };
|
|
740
1114
|
}
|
|
741
|
-
await
|
|
1115
|
+
await sleep3(emptyBackoffMs, opts.signal);
|
|
742
1116
|
continue;
|
|
743
1117
|
}
|
|
744
1118
|
return { cursor, pages, emptyPolls };
|
|
@@ -746,7 +1120,7 @@ async function consumeStreamsEvents(opts) {
|
|
|
746
1120
|
return { cursor, pages, emptyPolls };
|
|
747
1121
|
}
|
|
748
1122
|
async function* iterateStreamsBatches(opts) {
|
|
749
|
-
const
|
|
1123
|
+
const sleep3 = opts.sleep ?? defaultSleep;
|
|
750
1124
|
let cursor = opts.fromCursor ?? null;
|
|
751
1125
|
while (!opts.signal?.aborted) {
|
|
752
1126
|
const envelope = await opts.fetchEvents({
|
|
@@ -774,12 +1148,12 @@ async function* iterateStreamsBatches(opts) {
|
|
|
774
1148
|
if (!advanced && envelope.events.length === 0) {
|
|
775
1149
|
if (opts.signal?.aborted)
|
|
776
1150
|
return;
|
|
777
|
-
await
|
|
1151
|
+
await sleep3(opts.intervalMs, opts.signal);
|
|
778
1152
|
}
|
|
779
1153
|
}
|
|
780
1154
|
}
|
|
781
1155
|
async function* streamStreamsEvents(opts) {
|
|
782
|
-
const
|
|
1156
|
+
const sleep3 = opts.sleep ?? defaultSleep;
|
|
783
1157
|
const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
|
|
784
1158
|
const maxPages = opts.maxPages ?? Number.POSITIVE_INFINITY;
|
|
785
1159
|
const maxEmptyPolls = opts.maxEmptyPolls ?? Number.POSITIVE_INFINITY;
|
|
@@ -814,7 +1188,7 @@ async function* streamStreamsEvents(opts) {
|
|
|
814
1188
|
emptyPolls++;
|
|
815
1189
|
if (emptyPolls >= maxEmptyPolls || pages >= maxPages)
|
|
816
1190
|
return;
|
|
817
|
-
await
|
|
1191
|
+
await sleep3(emptyBackoffMs, opts.signal);
|
|
818
1192
|
continue;
|
|
819
1193
|
}
|
|
820
1194
|
return;
|
|
@@ -823,7 +1197,7 @@ async function* streamStreamsEvents(opts) {
|
|
|
823
1197
|
|
|
824
1198
|
// src/index-api/consumer.ts
|
|
825
1199
|
async function consumeIndexFeed(opts) {
|
|
826
|
-
const
|
|
1200
|
+
const sleep3 = opts.sleep ?? defaultSleep;
|
|
827
1201
|
const mode = opts.mode ?? "tail";
|
|
828
1202
|
const finalizedOnly = opts.finalizedOnly ?? false;
|
|
829
1203
|
const batchSize = opts.batchSize ?? 200;
|
|
@@ -847,22 +1221,23 @@ async function consumeIndexFeed(opts) {
|
|
|
847
1221
|
retryCount: opts.retryCount,
|
|
848
1222
|
retryDelay: opts.retryDelay,
|
|
849
1223
|
onError: opts.onError,
|
|
850
|
-
sleep,
|
|
1224
|
+
sleep: sleep3,
|
|
851
1225
|
signal: opts.signal
|
|
852
1226
|
});
|
|
853
1227
|
pages++;
|
|
854
1228
|
if (!finalizedOnly && (opts.onReorg || opts.sink)) {
|
|
855
|
-
const
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1229
|
+
const rewound = await applyReorgs({
|
|
1230
|
+
reorgs: envelope.reorgs,
|
|
1231
|
+
cursor,
|
|
1232
|
+
handled: handledReorgs,
|
|
1233
|
+
keyOf: (reorg) => reorg.id,
|
|
1234
|
+
sink: opts.sink,
|
|
1235
|
+
onReorg: opts.onReorg,
|
|
1236
|
+
maxRollbackDepth: opts.maxRollbackDepth
|
|
1237
|
+
});
|
|
1238
|
+
if (rewound) {
|
|
1239
|
+
cursor = rewound.cursor;
|
|
1240
|
+
height = rewound.forkPoint > 0 ? rewound.forkPoint - 1 : null;
|
|
866
1241
|
scanned = height;
|
|
867
1242
|
emptyPolls = 0;
|
|
868
1243
|
continue;
|
|
@@ -909,7 +1284,7 @@ async function consumeIndexFeed(opts) {
|
|
|
909
1284
|
if (mode === "bounded") {
|
|
910
1285
|
return { cursor, pages, emptyPolls };
|
|
911
1286
|
}
|
|
912
|
-
await
|
|
1287
|
+
await sleep3(emptyBackoffMs, opts.signal);
|
|
913
1288
|
continue;
|
|
914
1289
|
}
|
|
915
1290
|
return { cursor, pages, emptyPolls };
|
|
@@ -918,6 +1293,7 @@ async function consumeIndexFeed(opts) {
|
|
|
918
1293
|
}
|
|
919
1294
|
|
|
920
1295
|
// src/index-api/client.ts
|
|
1296
|
+
var INDEX_MAX_PAGE_SIZE = 1000;
|
|
921
1297
|
function feedFilters(params) {
|
|
922
1298
|
const { fromCursor: _checkpoint, ...filters } = params;
|
|
923
1299
|
return filters;
|
|
@@ -967,7 +1343,8 @@ class Index extends BaseClient {
|
|
|
967
1343
|
...feedFilters(params),
|
|
968
1344
|
cursor,
|
|
969
1345
|
fromHeight,
|
|
970
|
-
limit
|
|
1346
|
+
limit,
|
|
1347
|
+
signal: params.signal
|
|
971
1348
|
}),
|
|
972
1349
|
itemsOf: (envelope) => envelope.events
|
|
973
1350
|
})
|
|
@@ -981,7 +1358,8 @@ class Index extends BaseClient {
|
|
|
981
1358
|
...feedFilters(params),
|
|
982
1359
|
cursor,
|
|
983
1360
|
fromHeight,
|
|
984
|
-
limit
|
|
1361
|
+
limit,
|
|
1362
|
+
signal: params.signal
|
|
985
1363
|
}),
|
|
986
1364
|
itemsOf: (envelope) => envelope.contract_calls
|
|
987
1365
|
})
|
|
@@ -1020,7 +1398,8 @@ class Index extends BaseClient {
|
|
|
1020
1398
|
...feedFilters(params),
|
|
1021
1399
|
cursor,
|
|
1022
1400
|
fromHeight,
|
|
1023
|
-
limit
|
|
1401
|
+
limit,
|
|
1402
|
+
signal: params.signal
|
|
1024
1403
|
}),
|
|
1025
1404
|
itemsOf: (envelope) => envelope.deposits
|
|
1026
1405
|
}),
|
|
@@ -1040,7 +1419,8 @@ class Index extends BaseClient {
|
|
|
1040
1419
|
...feedFilters(params),
|
|
1041
1420
|
cursor,
|
|
1042
1421
|
fromHeight,
|
|
1043
|
-
limit
|
|
1422
|
+
limit,
|
|
1423
|
+
signal: params.signal
|
|
1044
1424
|
}),
|
|
1045
1425
|
itemsOf: (envelope) => envelope.events
|
|
1046
1426
|
})
|
|
@@ -1067,7 +1447,7 @@ class Index extends BaseClient {
|
|
|
1067
1447
|
asset_identifier: params.assetIdentifier,
|
|
1068
1448
|
sender: params.sender,
|
|
1069
1449
|
recipient: params.recipient
|
|
1070
|
-
})}
|
|
1450
|
+
})}`, undefined, { signal: params.signal });
|
|
1071
1451
|
}
|
|
1072
1452
|
async listNftTransfers(params = {}) {
|
|
1073
1453
|
return this.request("GET", `/v1/index/nft-transfers${buildQuery({
|
|
@@ -1076,29 +1456,45 @@ class Index extends BaseClient {
|
|
|
1076
1456
|
asset_identifier: params.assetIdentifier,
|
|
1077
1457
|
sender: params.sender,
|
|
1078
1458
|
recipient: params.recipient
|
|
1079
|
-
})}
|
|
1459
|
+
})}`, undefined, { signal: params.signal });
|
|
1080
1460
|
}
|
|
1081
1461
|
async* keysetWalk(params, list, itemsOf) {
|
|
1082
1462
|
const batchSize = params.batchSize ?? 200;
|
|
1463
|
+
if (!Number.isInteger(batchSize) || batchSize < 1 || batchSize > INDEX_MAX_PAGE_SIZE) {
|
|
1464
|
+
throw new ValidationError(`walk batchSize must be an integer from 1 to ${INDEX_MAX_PAGE_SIZE} (the largest page the Index serves); got ${batchSize}.`, 400);
|
|
1465
|
+
}
|
|
1083
1466
|
let cursor = params.cursor ?? params.fromCursor ?? null;
|
|
1084
1467
|
let firstPage = true;
|
|
1085
|
-
|
|
1086
|
-
|
|
1468
|
+
const throwIfAborted = () => {
|
|
1469
|
+
if (params.signal?.aborted)
|
|
1470
|
+
throw params.signal.reason ?? abortError();
|
|
1471
|
+
};
|
|
1472
|
+
for (;; ) {
|
|
1473
|
+
throwIfAborted();
|
|
1474
|
+
const page = {
|
|
1087
1475
|
limit: batchSize,
|
|
1088
1476
|
cursor: firstPage ? params.cursor : cursor,
|
|
1089
1477
|
fromCursor: firstPage ? params.fromCursor : undefined,
|
|
1090
|
-
fromHeight: firstPage ? firstWalkFromHeight(params) : undefined
|
|
1478
|
+
fromHeight: firstPage ? firstWalkFromHeight(params) : undefined,
|
|
1479
|
+
signal: params.signal
|
|
1480
|
+
};
|
|
1481
|
+
const envelope = await fetchPageWithRetry(() => list(page), {
|
|
1482
|
+
retryCount: params.retryCount,
|
|
1483
|
+
retryDelay: params.retryDelay,
|
|
1484
|
+
onError: params.onError,
|
|
1485
|
+
sleep: defaultSleep,
|
|
1486
|
+
signal: params.signal
|
|
1091
1487
|
});
|
|
1092
1488
|
const items = itemsOf(envelope);
|
|
1093
1489
|
for (const item of items) {
|
|
1094
|
-
|
|
1095
|
-
return;
|
|
1490
|
+
throwIfAborted();
|
|
1096
1491
|
yield item;
|
|
1097
1492
|
}
|
|
1098
1493
|
const nextCursor = envelope.next_cursor;
|
|
1099
|
-
if (
|
|
1494
|
+
if (nextCursor === null || nextCursor === undefined)
|
|
1495
|
+
return;
|
|
1496
|
+
if (nextCursor === cursor)
|
|
1100
1497
|
return;
|
|
1101
|
-
}
|
|
1102
1498
|
cursor = nextCursor;
|
|
1103
1499
|
firstPage = false;
|
|
1104
1500
|
}
|
|
@@ -1119,7 +1515,7 @@ class Index extends BaseClient {
|
|
|
1119
1515
|
recipient: params.recipient,
|
|
1120
1516
|
trait: params.trait,
|
|
1121
1517
|
tx_context: params.txContext ? "true" : undefined
|
|
1122
|
-
})}
|
|
1518
|
+
})}`, undefined, { signal: params.signal });
|
|
1123
1519
|
}
|
|
1124
1520
|
walkEvents(params) {
|
|
1125
1521
|
return this.keysetWalk(params, (page) => this.listEvents({ ...params, ...page }), (e) => e.events);
|
|
@@ -1131,19 +1527,19 @@ class Index extends BaseClient {
|
|
|
1131
1527
|
function_name: params.functionName,
|
|
1132
1528
|
sender: params.sender,
|
|
1133
1529
|
trait: params.trait
|
|
1134
|
-
})}
|
|
1530
|
+
})}`, undefined, { signal: params.signal });
|
|
1135
1531
|
}
|
|
1136
1532
|
walkContractCalls(params = {}) {
|
|
1137
1533
|
return this.keysetWalk(params, (page) => this.listContractCalls({ ...params, ...page }), (e) => e.contract_calls);
|
|
1138
1534
|
}
|
|
1139
1535
|
async listCanonical(params = {}) {
|
|
1140
|
-
return this.request("GET", `/v1/index/canonical${buildQuery(indexPageQuery(params))}
|
|
1536
|
+
return this.request("GET", `/v1/index/canonical${buildQuery(indexPageQuery(params))}`, undefined, { signal: params.signal });
|
|
1141
1537
|
}
|
|
1142
1538
|
walkCanonical(params = {}) {
|
|
1143
1539
|
return this.keysetWalk(params, (page) => this.listCanonical({ ...params, ...page }), (e) => e.canonical);
|
|
1144
1540
|
}
|
|
1145
1541
|
async listBlocks(params = {}) {
|
|
1146
|
-
return this.request("GET", `/v1/index/blocks${buildQuery(indexPageQuery(params))}
|
|
1542
|
+
return this.request("GET", `/v1/index/blocks${buildQuery(indexPageQuery(params))}`, undefined, { signal: params.signal });
|
|
1147
1543
|
}
|
|
1148
1544
|
async getBlock(ref) {
|
|
1149
1545
|
return this.requestOrNull("GET", `/v1/index/blocks/${encodeURIComponent(String(ref))}`);
|
|
@@ -1157,7 +1553,7 @@ class Index extends BaseClient {
|
|
|
1157
1553
|
type: params.type,
|
|
1158
1554
|
sender: params.sender,
|
|
1159
1555
|
contract_id: params.contractId
|
|
1160
|
-
})}
|
|
1556
|
+
})}`, undefined, { signal: params.signal });
|
|
1161
1557
|
}
|
|
1162
1558
|
async getTransaction(txId) {
|
|
1163
1559
|
return this.requestOrNull("GET", `/v1/index/transactions/${encodeURIComponent(txId)}`);
|
|
@@ -1174,7 +1570,7 @@ class Index extends BaseClient {
|
|
|
1174
1570
|
function_name: params.functionName,
|
|
1175
1571
|
stacker: params.stacker,
|
|
1176
1572
|
caller: params.caller
|
|
1177
|
-
})}
|
|
1573
|
+
})}`, undefined, { signal: params.signal });
|
|
1178
1574
|
}
|
|
1179
1575
|
walkStacking(params = {}) {
|
|
1180
1576
|
return this.keysetWalk(params, (page) => this.listStacking({ ...params, ...page }), (e) => e.stacking);
|
|
@@ -1186,7 +1582,7 @@ class Index extends BaseClient {
|
|
|
1186
1582
|
type: params.type,
|
|
1187
1583
|
contract_id: params.contractId,
|
|
1188
1584
|
function_name: params.functionName
|
|
1189
|
-
})}
|
|
1585
|
+
})}`, undefined, { signal: params.signal });
|
|
1190
1586
|
}
|
|
1191
1587
|
async getMempoolTx(txId) {
|
|
1192
1588
|
return this.requestOrNull("GET", `/v1/index/mempool/${encodeURIComponent(txId)}`);
|
|
@@ -1200,7 +1596,7 @@ class Index extends BaseClient {
|
|
|
1200
1596
|
confirmed: params.confirmed,
|
|
1201
1597
|
sender: params.sender,
|
|
1202
1598
|
bitcoin_txid: params.bitcoinTxid
|
|
1203
|
-
})}
|
|
1599
|
+
})}`, undefined, { signal: params.signal });
|
|
1204
1600
|
}
|
|
1205
1601
|
walkSbtcDeposits(params = {}) {
|
|
1206
1602
|
return this.keysetWalk(params, (page) => this.listSbtcDeposits({ ...params, ...page }), (e) => e.deposits);
|
|
@@ -1216,7 +1612,7 @@ class Index extends BaseClient {
|
|
|
1216
1612
|
sender: params.sender,
|
|
1217
1613
|
request_id: params.requestId,
|
|
1218
1614
|
settlement_confirmed: params.settlementConfirmed
|
|
1219
|
-
})}
|
|
1615
|
+
})}`, undefined, { signal: params.signal });
|
|
1220
1616
|
}
|
|
1221
1617
|
walkSbtcWithdrawals(params = {}) {
|
|
1222
1618
|
return this.keysetWalk(params, (page) => this.listSbtcWithdrawals({ ...params, ...page }), (e) => e.withdrawals);
|
|
@@ -1232,7 +1628,7 @@ class Index extends BaseClient {
|
|
|
1232
1628
|
sender: params.sender,
|
|
1233
1629
|
request_id: params.requestId,
|
|
1234
1630
|
bitcoin_txid: params.bitcoinTxid
|
|
1235
|
-
})}
|
|
1631
|
+
})}`, undefined, { signal: params.signal });
|
|
1236
1632
|
}
|
|
1237
1633
|
walkSbtcEvents(params = {}) {
|
|
1238
1634
|
return this.keysetWalk(params, (page) => this.listSbtcEvents({ ...params, ...page }), (e) => e.events);
|
|
@@ -1250,7 +1646,7 @@ class Index extends BaseClient {
|
|
|
1250
1646
|
signer_manager: params.signerManager,
|
|
1251
1647
|
bond_index: params.bondIndex,
|
|
1252
1648
|
reward_cycle: params.rewardCycle
|
|
1253
|
-
})}
|
|
1649
|
+
})}`, undefined, { signal: params.signal });
|
|
1254
1650
|
}
|
|
1255
1651
|
walkPox5Events(params = {}) {
|
|
1256
1652
|
return this.keysetWalk(params, (page) => this.listPox5Events({ ...params, ...page }), (e) => e.events);
|
|
@@ -1259,24 +1655,14 @@ class Index extends BaseClient {
|
|
|
1259
1655
|
return this.request("GET", `/v1/index/pox/cycles${buildQuery({
|
|
1260
1656
|
cursor: params.cursor,
|
|
1261
1657
|
limit: params.limit
|
|
1262
|
-
})}
|
|
1658
|
+
})}`, undefined, { signal: params.signal });
|
|
1263
1659
|
}
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
if (params.signal?.aborted)
|
|
1271
|
-
return;
|
|
1272
|
-
yield cycle;
|
|
1273
|
-
}
|
|
1274
|
-
const nextCursor = envelope.next_cursor;
|
|
1275
|
-
if (nextCursor === null || nextCursor === cursor || envelope.cycles.length < batchSize) {
|
|
1276
|
-
return;
|
|
1277
|
-
}
|
|
1278
|
-
cursor = nextCursor;
|
|
1279
|
-
}
|
|
1660
|
+
walkPoxCycles(params = {}) {
|
|
1661
|
+
return this.keysetWalk(params, (page) => this.listPoxCycles({
|
|
1662
|
+
cursor: page.cursor,
|
|
1663
|
+
limit: page.limit,
|
|
1664
|
+
signal: page.signal
|
|
1665
|
+
}), (e) => e.cycles);
|
|
1280
1666
|
}
|
|
1281
1667
|
async getPoxCycle(rewardCycle) {
|
|
1282
1668
|
return this.requestOrNull("GET", `/v1/index/pox/cycles/${rewardCycle}`);
|
|
@@ -1440,154 +1826,49 @@ function createStreamsDumps(opts) {
|
|
|
1440
1826
|
return manifest;
|
|
1441
1827
|
}
|
|
1442
1828
|
async function download(file) {
|
|
1829
|
+
return fetchPageWithRetry(() => downloadOnce(file), {
|
|
1830
|
+
sleep: defaultSleep
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
async function downloadOnce(file) {
|
|
1443
1834
|
const res = await opts.fetchImpl(fileUrl(file));
|
|
1444
1835
|
if (!res.ok) {
|
|
1445
1836
|
throw new StreamsServerError(`Could not download dump ${file.path} (${res.status}).`, res.status);
|
|
1446
1837
|
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
if (digest !== file.sha256) {
|
|
1450
|
-
throw new StreamsSignatureError(`Dump ${file.path} sha256 mismatch (expected ${file.sha256}, got ${digest}).`);
|
|
1838
|
+
if (!res.body) {
|
|
1839
|
+
throw new StreamsServerError(`Dump ${file.path} response has no body.`, 0);
|
|
1451
1840
|
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
if (external.aborted)
|
|
1465
|
-
controller.abort();
|
|
1466
|
-
else
|
|
1467
|
-
external.addEventListener("abort", () => controller.abort(), {
|
|
1468
|
-
once: true
|
|
1469
|
-
});
|
|
1470
|
-
}
|
|
1471
|
-
let cursor = params.fromCursor ?? null;
|
|
1472
|
-
const reconnectDelayMs = opts.reconnectDelayMs ?? 1000;
|
|
1473
|
-
const run = async () => {
|
|
1474
|
-
while (!controller.signal.aborted) {
|
|
1475
|
-
try {
|
|
1476
|
-
const url = `${opts.baseUrl}/v1/streams/events/stream${buildQuery({
|
|
1477
|
-
from_cursor: cursor ?? undefined,
|
|
1478
|
-
types: params.types,
|
|
1479
|
-
not_types: params.notTypes,
|
|
1480
|
-
contract_id: params.contractId,
|
|
1481
|
-
sender: params.sender,
|
|
1482
|
-
recipient: params.recipient,
|
|
1483
|
-
asset_identifier: params.assetIdentifier,
|
|
1484
|
-
filters: params.filters ? JSON.stringify(params.filters) : undefined
|
|
1485
|
-
})}`;
|
|
1486
|
-
const res = await opts.fetchImpl(url, {
|
|
1487
|
-
headers: {
|
|
1488
|
-
...opts.apiKey ? { Authorization: `Bearer ${opts.apiKey}` } : {},
|
|
1489
|
-
Accept: "text/event-stream"
|
|
1490
|
-
},
|
|
1491
|
-
signal: controller.signal
|
|
1492
|
-
});
|
|
1493
|
-
if (!res.ok) {
|
|
1494
|
-
throw new StreamsServerError(`Streams SSE returned ${res.status}.`, res.status);
|
|
1495
|
-
}
|
|
1496
|
-
if (!res.body) {
|
|
1497
|
-
throw new StreamsServerError("Streams SSE response has no body.", 0);
|
|
1498
|
-
}
|
|
1499
|
-
for await (const frame of parseSseFrames(res.body, controller.signal)) {
|
|
1500
|
-
if (frame.event === "ping" || !frame.data)
|
|
1501
|
-
continue;
|
|
1502
|
-
let parsed;
|
|
1503
|
-
try {
|
|
1504
|
-
parsed = JSON.parse(frame.data);
|
|
1505
|
-
} catch {
|
|
1506
|
-
continue;
|
|
1507
|
-
}
|
|
1508
|
-
if (!parsed.event)
|
|
1509
|
-
continue;
|
|
1510
|
-
if (opts.verify !== "off") {
|
|
1511
|
-
if (!parsed.sig) {
|
|
1512
|
-
if (opts.verify === "strict") {
|
|
1513
|
-
throw new StreamsSignatureError("Streams SSE frame signature is missing.");
|
|
1514
|
-
}
|
|
1515
|
-
} else {
|
|
1516
|
-
const key = await opts.loadKey();
|
|
1517
|
-
if (!ed25519.verifyEd25519(JSON.stringify(parsed.event), parsed.sig, key.publicKey)) {
|
|
1518
|
-
throw new StreamsSignatureError("Streams SSE frame signature is invalid.");
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1522
|
-
cursor = parsed.event.cursor ?? cursor;
|
|
1523
|
-
await params.onEvent(parsed.event);
|
|
1524
|
-
}
|
|
1525
|
-
} catch (err) {
|
|
1526
|
-
if (controller.signal.aborted)
|
|
1527
|
-
return;
|
|
1528
|
-
params.onError?.(err);
|
|
1529
|
-
await sleep(reconnectDelayMs, controller.signal);
|
|
1841
|
+
const hash = createHash("sha256");
|
|
1842
|
+
const chunks = [];
|
|
1843
|
+
let total = 0;
|
|
1844
|
+
const reader = res.body.getReader();
|
|
1845
|
+
try {
|
|
1846
|
+
while (true) {
|
|
1847
|
+
const { value, done } = await reader.read();
|
|
1848
|
+
if (done)
|
|
1849
|
+
break;
|
|
1850
|
+
hash.update(value);
|
|
1851
|
+
chunks.push(value);
|
|
1852
|
+
total += value.byteLength;
|
|
1530
1853
|
}
|
|
1854
|
+
} catch {
|
|
1855
|
+
throw new StreamsServerError(`Download of dump ${file.path} was interrupted after ${total} bytes.`, 0);
|
|
1531
1856
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
}
|
|
1536
|
-
function sleep(ms, signal) {
|
|
1537
|
-
return new Promise((resolve) => {
|
|
1538
|
-
if (signal.aborted)
|
|
1539
|
-
return resolve();
|
|
1540
|
-
const onAbort = () => {
|
|
1541
|
-
clearTimeout(timer);
|
|
1542
|
-
resolve();
|
|
1543
|
-
};
|
|
1544
|
-
const timer = setTimeout(() => {
|
|
1545
|
-
signal.removeEventListener("abort", onAbort);
|
|
1546
|
-
resolve();
|
|
1547
|
-
}, ms);
|
|
1548
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1549
|
-
});
|
|
1550
|
-
}
|
|
1551
|
-
async function* parseSseFrames(body, signal) {
|
|
1552
|
-
const reader = body.getReader();
|
|
1553
|
-
const decoder = new TextDecoder;
|
|
1554
|
-
let buffer = "";
|
|
1555
|
-
try {
|
|
1556
|
-
while (!signal.aborted) {
|
|
1557
|
-
const { value, done } = await reader.read();
|
|
1558
|
-
if (done)
|
|
1559
|
-
break;
|
|
1560
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1561
|
-
let sep = buffer.indexOf(`
|
|
1562
|
-
|
|
1563
|
-
`);
|
|
1564
|
-
while (sep !== -1) {
|
|
1565
|
-
yield parseFrame(buffer.slice(0, sep));
|
|
1566
|
-
buffer = buffer.slice(sep + 2);
|
|
1567
|
-
sep = buffer.indexOf(`
|
|
1568
|
-
|
|
1569
|
-
`);
|
|
1570
|
-
}
|
|
1857
|
+
const digest = hash.digest("hex");
|
|
1858
|
+
if (digest !== file.sha256) {
|
|
1859
|
+
throw new StreamsSignatureError(`Dump ${file.path} sha256 mismatch (expected ${file.sha256}, got ${digest}).`);
|
|
1571
1860
|
}
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
let event;
|
|
1580
|
-
const data = [];
|
|
1581
|
-
for (const line of raw.split(`
|
|
1582
|
-
`)) {
|
|
1583
|
-
if (line.startsWith("data:")) {
|
|
1584
|
-
data.push(line.slice(line.startsWith("data: ") ? 6 : 5));
|
|
1585
|
-
} else if (line.startsWith("event:")) {
|
|
1586
|
-
event = line.slice(line.startsWith("event: ") ? 7 : 6).trim();
|
|
1861
|
+
if (chunks.length === 1 && chunks[0])
|
|
1862
|
+
return chunks[0];
|
|
1863
|
+
const bytes = new Uint8Array(total);
|
|
1864
|
+
let offset = 0;
|
|
1865
|
+
for (const chunk of chunks) {
|
|
1866
|
+
bytes.set(chunk, offset);
|
|
1867
|
+
offset += chunk.byteLength;
|
|
1587
1868
|
}
|
|
1869
|
+
return bytes;
|
|
1588
1870
|
}
|
|
1589
|
-
return {
|
|
1590
|
-
`) : undefined };
|
|
1871
|
+
return { list, fileUrl, download };
|
|
1591
1872
|
}
|
|
1592
1873
|
|
|
1593
1874
|
// src/streams/client.ts
|
|
@@ -1616,51 +1897,33 @@ function streamsFilters(params) {
|
|
|
1616
1897
|
function normalizeBaseUrl(baseUrl) {
|
|
1617
1898
|
return baseUrl.replace(/\/+$/, "");
|
|
1618
1899
|
}
|
|
1619
|
-
|
|
1620
|
-
const text = await response.text();
|
|
1621
|
-
if (text.length === 0)
|
|
1622
|
-
return;
|
|
1900
|
+
function isLoopbackUrl(baseUrl) {
|
|
1623
1901
|
try {
|
|
1624
|
-
|
|
1902
|
+
const { hostname } = new URL(baseUrl);
|
|
1903
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]" || hostname.startsWith("127.");
|
|
1625
1904
|
} catch {
|
|
1626
|
-
return
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
function errorMessage(body, fallback) {
|
|
1630
|
-
if (body && typeof body === "object") {
|
|
1631
|
-
const record = body;
|
|
1632
|
-
const message = record.error ?? record.message;
|
|
1633
|
-
if (typeof message === "string" && message.length > 0)
|
|
1634
|
-
return message;
|
|
1635
|
-
}
|
|
1636
|
-
if (typeof body === "string" && body.length > 0)
|
|
1637
|
-
return body;
|
|
1638
|
-
return fallback;
|
|
1639
|
-
}
|
|
1640
|
-
async function mapStreamsError(response) {
|
|
1641
|
-
const body = await responseBody(response);
|
|
1642
|
-
if (response.status === 401) {
|
|
1643
|
-
throw new AuthError(errorMessage(body, "API key invalid or expired."));
|
|
1644
|
-
}
|
|
1645
|
-
if (response.status === 429) {
|
|
1646
|
-
const retryAfter = response.headers.get("Retry-After") ?? undefined;
|
|
1647
|
-
throw new RateLimitError(errorMessage(body, "Rate limited. Try again later."), retryAfter);
|
|
1648
|
-
}
|
|
1649
|
-
if (response.status >= 500) {
|
|
1650
|
-
throw new StreamsServerError(errorMessage(body, `Streams server returned ${response.status}.`), response.status, body);
|
|
1905
|
+
return false;
|
|
1651
1906
|
}
|
|
1652
|
-
throw new ValidationError(errorMessage(body, `Streams request returned ${response.status}.`), response.status, body);
|
|
1653
1907
|
}
|
|
1654
1908
|
function createStreamsClient(options) {
|
|
1655
1909
|
const baseUrl = normalizeBaseUrl(resolveBaseUrl(options.baseUrl));
|
|
1910
|
+
const apiKey = resolveApiKey(options.apiKey);
|
|
1911
|
+
const origin = options.origin ?? "cli";
|
|
1912
|
+
const authHeaders = () => ({
|
|
1913
|
+
"x-sl-origin": origin,
|
|
1914
|
+
...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
|
|
1915
|
+
});
|
|
1656
1916
|
const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
1657
1917
|
const verify = options.verify;
|
|
1658
1918
|
const verifyMode = verify === false ? "off" : verify === undefined ? "lenient" : "strict";
|
|
1919
|
+
if (verify === true && baseUrl.startsWith("http:") && !isLoopbackUrl(baseUrl)) {
|
|
1920
|
+
throw new ValidationError(`verify: true over plain http to ${baseUrl} fetches the signing key from the same unprotected path it verifies. Pin the key with verify: { publicKey } (see GET /public/streams/signing-key), or use https.`, 400);
|
|
1921
|
+
}
|
|
1659
1922
|
let keyPromise = null;
|
|
1660
1923
|
function loadKey() {
|
|
1661
1924
|
if (keyPromise)
|
|
1662
1925
|
return keyPromise;
|
|
1663
|
-
|
|
1926
|
+
const pending = (async () => {
|
|
1664
1927
|
if (typeof verify === "object") {
|
|
1665
1928
|
return {
|
|
1666
1929
|
keyId: ed255192.ed25519KeyId(verify.publicKey),
|
|
@@ -1668,8 +1931,16 @@ function createStreamsClient(options) {
|
|
|
1668
1931
|
publicKey: ed255192.loadEd25519PublicKey(verify.publicKey)
|
|
1669
1932
|
};
|
|
1670
1933
|
}
|
|
1671
|
-
|
|
1934
|
+
let res;
|
|
1935
|
+
try {
|
|
1936
|
+
res = await fetchImpl(`${baseUrl}/public/streams/signing-key`);
|
|
1937
|
+
} catch {
|
|
1938
|
+
throw new StreamsServerError(`Could not reach the signing key at ${baseUrl}/public/streams/signing-key. Retryable.`, 0, undefined, "SIGNING_KEY_UNAVAILABLE");
|
|
1939
|
+
}
|
|
1672
1940
|
if (!res.ok) {
|
|
1941
|
+
if (res.status >= 500 || res.status === 429) {
|
|
1942
|
+
throw new StreamsServerError(`Could not fetch signing key (${res.status}). Retryable.`, res.status, undefined, "SIGNING_KEY_UNAVAILABLE");
|
|
1943
|
+
}
|
|
1673
1944
|
throw new StreamsSignatureError(`Could not fetch signing key (${res.status}).`);
|
|
1674
1945
|
}
|
|
1675
1946
|
const body = await res.json();
|
|
@@ -1682,7 +1953,26 @@ function createStreamsClient(options) {
|
|
|
1682
1953
|
publicKey: ed255192.loadEd25519PublicKey(body.public_key_pem)
|
|
1683
1954
|
};
|
|
1684
1955
|
})();
|
|
1685
|
-
|
|
1956
|
+
keyPromise = pending;
|
|
1957
|
+
pending.catch(() => {
|
|
1958
|
+
if (keyPromise === pending)
|
|
1959
|
+
keyPromise = null;
|
|
1960
|
+
});
|
|
1961
|
+
return pending;
|
|
1962
|
+
}
|
|
1963
|
+
async function loadKeyFor(responseKeyId) {
|
|
1964
|
+
let key = await loadKey();
|
|
1965
|
+
if (!responseKeyId || responseKeyId === key.keyId)
|
|
1966
|
+
return key;
|
|
1967
|
+
if (typeof verify === "object") {
|
|
1968
|
+
throw new StreamsSignatureError(`Response signed with key '${responseKeyId}', expected pinned key '${key.keyId}'.`);
|
|
1969
|
+
}
|
|
1970
|
+
keyPromise = null;
|
|
1971
|
+
key = await loadKey();
|
|
1972
|
+
if (responseKeyId !== key.keyId) {
|
|
1973
|
+
throw new StreamsSignatureError(`Response signed with key '${responseKeyId}' not served by the signing-key endpoint.`);
|
|
1974
|
+
}
|
|
1975
|
+
return key;
|
|
1686
1976
|
}
|
|
1687
1977
|
const dumps = createStreamsDumps({
|
|
1688
1978
|
baseUrl: options.dumpsBaseUrl,
|
|
@@ -1692,7 +1982,7 @@ function createStreamsClient(options) {
|
|
|
1692
1982
|
});
|
|
1693
1983
|
async function request(path) {
|
|
1694
1984
|
const response = await fetchImpl(`${baseUrl}${path}`, {
|
|
1695
|
-
headers:
|
|
1985
|
+
headers: authHeaders()
|
|
1696
1986
|
});
|
|
1697
1987
|
if (!response.ok)
|
|
1698
1988
|
await mapStreamsError(response);
|
|
@@ -1704,18 +1994,7 @@ function createStreamsClient(options) {
|
|
|
1704
1994
|
throw new StreamsSignatureError("Response is missing X-Signature.");
|
|
1705
1995
|
}
|
|
1706
1996
|
} else {
|
|
1707
|
-
const
|
|
1708
|
-
let key = await loadKey();
|
|
1709
|
-
if (responseKeyId && responseKeyId !== key.keyId) {
|
|
1710
|
-
if (typeof verify === "object") {
|
|
1711
|
-
throw new StreamsSignatureError(`Response signed with key '${responseKeyId}', expected pinned key '${key.keyId}'.`);
|
|
1712
|
-
}
|
|
1713
|
-
keyPromise = null;
|
|
1714
|
-
key = await loadKey();
|
|
1715
|
-
if (responseKeyId !== key.keyId) {
|
|
1716
|
-
throw new StreamsSignatureError(`Response signed with key '${responseKeyId}' not served by the signing-key endpoint.`);
|
|
1717
|
-
}
|
|
1718
|
-
}
|
|
1997
|
+
const key = await loadKeyFor(response.headers.get("X-Signature-KeyId"));
|
|
1719
1998
|
if (!ed255192.verifyEd25519(text, signature, key.publicKey)) {
|
|
1720
1999
|
throw new StreamsSignatureError;
|
|
1721
2000
|
}
|
|
@@ -1724,6 +2003,13 @@ function createStreamsClient(options) {
|
|
|
1724
2003
|
return JSON.parse(text);
|
|
1725
2004
|
}
|
|
1726
2005
|
const fetchEvents = listEvents;
|
|
2006
|
+
function listReorgs(params) {
|
|
2007
|
+
return request(`/v1/streams/reorgs${buildQuery({
|
|
2008
|
+
since: params.since,
|
|
2009
|
+
limit: params.limit
|
|
2010
|
+
})}`);
|
|
2011
|
+
}
|
|
2012
|
+
const fetchReorgs = (params) => listReorgs(params);
|
|
1727
2013
|
function encodeFilters(filters) {
|
|
1728
2014
|
return filters ? JSON.stringify(filters) : undefined;
|
|
1729
2015
|
}
|
|
@@ -1771,8 +2057,10 @@ function createStreamsClient(options) {
|
|
|
1771
2057
|
...streamsFilters(params),
|
|
1772
2058
|
batchSize: params.batchSize ?? 100,
|
|
1773
2059
|
fetchEvents,
|
|
2060
|
+
fetchReorgs,
|
|
1774
2061
|
onBatch,
|
|
1775
2062
|
onReorg: params.onReorg,
|
|
2063
|
+
maxRollbackDepth: params.maxRollbackDepth,
|
|
1776
2064
|
emptyBackoffMs: params.emptyBackoffMs,
|
|
1777
2065
|
maxPages: params.maxPages,
|
|
1778
2066
|
maxEmptyPolls: params.maxEmptyPolls,
|
|
@@ -1814,10 +2102,12 @@ function createStreamsClient(options) {
|
|
|
1814
2102
|
subscribe(params) {
|
|
1815
2103
|
return subscribeStreamsEvents({
|
|
1816
2104
|
baseUrl,
|
|
1817
|
-
|
|
2105
|
+
headers: authHeaders(),
|
|
1818
2106
|
fetchImpl,
|
|
1819
2107
|
verify: verifyMode,
|
|
1820
|
-
loadKey,
|
|
2108
|
+
loadKey: loadKeyFor,
|
|
2109
|
+
reconnectDelayMs: params.reconnectDelayMs,
|
|
2110
|
+
staleAfterMs: params.staleAfterMs,
|
|
1821
2111
|
params
|
|
1822
2112
|
});
|
|
1823
2113
|
},
|
|
@@ -1825,11 +2115,11 @@ function createStreamsClient(options) {
|
|
|
1825
2115
|
const fromCursor = params.from === "genesis" ? null : params.from ?? null;
|
|
1826
2116
|
const fromBlock = fromCursor ? cursorTuple(fromCursor)[0] : 0;
|
|
1827
2117
|
const manifest = await dumps.list();
|
|
1828
|
-
const files = manifest.files.filter((file) => file.to_block >= fromBlock).sort((a, b) => a.from_block - b.from_block || a.to_block - b.to_block);
|
|
2118
|
+
const files = manifest.files.filter((file) => file.to_block >= fromBlock).filter((file) => fromCursor === null || file.max_cursor === null || maxCursor(fromCursor, file.max_cursor) !== fromCursor).sort((a, b) => a.from_block - b.from_block || a.to_block - b.to_block);
|
|
1829
2119
|
for (const file of files) {
|
|
1830
2120
|
if (params.signal?.aborted)
|
|
1831
2121
|
break;
|
|
1832
|
-
await params.onDumpFile(file);
|
|
2122
|
+
await params.onDumpFile(file, { from: fromCursor });
|
|
1833
2123
|
}
|
|
1834
2124
|
const seam = maxCursor(fromCursor, manifest.latest_finalized_cursor);
|
|
1835
2125
|
return consumeStreamsEvents({
|
|
@@ -1854,14 +2144,7 @@ function createStreamsClient(options) {
|
|
|
1854
2144
|
return request(`/v1/streams/blocks/${encodeURIComponent(String(heightOrHash))}/events`);
|
|
1855
2145
|
}
|
|
1856
2146
|
},
|
|
1857
|
-
reorgs: {
|
|
1858
|
-
list(params) {
|
|
1859
|
-
return request(`/v1/streams/reorgs${buildQuery({
|
|
1860
|
-
since: params.since,
|
|
1861
|
-
limit: params.limit
|
|
1862
|
-
})}`);
|
|
1863
|
-
}
|
|
1864
|
-
},
|
|
2147
|
+
reorgs: { list: listReorgs },
|
|
1865
2148
|
dumps,
|
|
1866
2149
|
canonical(height) {
|
|
1867
2150
|
return request(`/v1/streams/canonical/${height}`);
|
|
@@ -1880,44 +2163,69 @@ class Subscriptions extends BaseClient {
|
|
|
1880
2163
|
return this.request("GET", "/api/subscriptions");
|
|
1881
2164
|
}
|
|
1882
2165
|
async get(id) {
|
|
1883
|
-
return this.request("GET", `/api/subscriptions/${id}`);
|
|
2166
|
+
return this.request("GET", `/api/subscriptions/${seg(id)}`);
|
|
1884
2167
|
}
|
|
1885
2168
|
async create(input) {
|
|
1886
2169
|
return this.request("POST", "/api/subscriptions", input);
|
|
1887
2170
|
}
|
|
1888
2171
|
async update(id, patch) {
|
|
1889
|
-
return this.request("PATCH", `/api/subscriptions/${id}`, patch);
|
|
2172
|
+
return this.request("PATCH", `/api/subscriptions/${seg(id)}`, patch);
|
|
1890
2173
|
}
|
|
1891
2174
|
async pause(id) {
|
|
1892
|
-
return this.request("POST", `/api/subscriptions/${id}/pause`);
|
|
2175
|
+
return this.request("POST", `/api/subscriptions/${seg(id)}/pause`);
|
|
1893
2176
|
}
|
|
1894
2177
|
async resume(id) {
|
|
1895
|
-
return this.request("POST", `/api/subscriptions/${id}/resume`);
|
|
2178
|
+
return this.request("POST", `/api/subscriptions/${seg(id)}/resume`);
|
|
1896
2179
|
}
|
|
1897
2180
|
async delete(id) {
|
|
1898
|
-
return this.request("DELETE", `/api/subscriptions/${id}`);
|
|
2181
|
+
return this.request("DELETE", `/api/subscriptions/${seg(id)}`);
|
|
1899
2182
|
}
|
|
1900
2183
|
async rotateSecret(id) {
|
|
1901
|
-
return this.request("POST", `/api/subscriptions/${id}/rotate-secret`);
|
|
2184
|
+
return this.request("POST", `/api/subscriptions/${seg(id)}/rotate-secret`);
|
|
1902
2185
|
}
|
|
1903
2186
|
async test(id) {
|
|
1904
|
-
return this.request("POST", `/api/subscriptions/${id}/test`);
|
|
2187
|
+
return this.request("POST", `/api/subscriptions/${seg(id)}/test`);
|
|
1905
2188
|
}
|
|
1906
2189
|
async deliveries(id) {
|
|
1907
|
-
return this.request("GET", `/api/subscriptions/${id}/deliveries`);
|
|
2190
|
+
return this.request("GET", `/api/subscriptions/${seg(id)}/deliveries`);
|
|
1908
2191
|
}
|
|
1909
2192
|
async replay(id, range) {
|
|
1910
|
-
return this.request("POST", `/api/subscriptions/${id}/replay`, range);
|
|
2193
|
+
return this.request("POST", `/api/subscriptions/${seg(id)}/replay`, range);
|
|
1911
2194
|
}
|
|
1912
2195
|
async dead(id) {
|
|
1913
|
-
return this.request("GET", `/api/subscriptions/${id}/dead`);
|
|
2196
|
+
return this.request("GET", `/api/subscriptions/${seg(id)}/dead`);
|
|
1914
2197
|
}
|
|
1915
2198
|
async requeue(id, outboxId) {
|
|
1916
|
-
return this.request("POST", `/api/subscriptions/${id}/dead/${outboxId}/requeue`);
|
|
2199
|
+
return this.request("POST", `/api/subscriptions/${seg(id)}/dead/${seg(outboxId)}/requeue`);
|
|
1917
2200
|
}
|
|
1918
2201
|
}
|
|
1919
2202
|
|
|
1920
2203
|
// src/client.ts
|
|
2204
|
+
async function contextField(read) {
|
|
2205
|
+
try {
|
|
2206
|
+
return { value: await read };
|
|
2207
|
+
} catch (err) {
|
|
2208
|
+
return { value: null, error: describeContextError(err) };
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
function describeContextError(err) {
|
|
2212
|
+
if (err instanceof SecondLayerError) {
|
|
2213
|
+
return {
|
|
2214
|
+
message: err.shortMessage,
|
|
2215
|
+
...err.code !== undefined ? { code: err.code } : {},
|
|
2216
|
+
...err instanceof ApiError ? { status: err.status } : {},
|
|
2217
|
+
retryable: err.retryable
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
if (err instanceof TypeError) {
|
|
2221
|
+
return { message: err.message, status: 0, retryable: true };
|
|
2222
|
+
}
|
|
2223
|
+
return {
|
|
2224
|
+
message: err instanceof Error ? err.message : String(err),
|
|
2225
|
+
retryable: false
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
|
|
1921
2229
|
class SecondLayer extends BaseClient {
|
|
1922
2230
|
streams;
|
|
1923
2231
|
index;
|
|
@@ -1927,10 +2235,13 @@ class SecondLayer extends BaseClient {
|
|
|
1927
2235
|
constructor(options = {}) {
|
|
1928
2236
|
super(options);
|
|
1929
2237
|
this.streams = createStreamsClient({
|
|
1930
|
-
apiKey:
|
|
2238
|
+
apiKey: this.apiKey,
|
|
2239
|
+
origin: this.origin,
|
|
1931
2240
|
baseUrl: options.baseUrl,
|
|
1932
2241
|
fetchImpl: options.fetchImpl,
|
|
1933
|
-
dumpsBaseUrl: options.dumpsBaseUrl
|
|
2242
|
+
dumpsBaseUrl: options.dumpsBaseUrl,
|
|
2243
|
+
verify: options.verify,
|
|
2244
|
+
verifyDumpsManifest: options.verifyDumpsManifest
|
|
1934
2245
|
});
|
|
1935
2246
|
this.index = new Index(options);
|
|
1936
2247
|
this.contracts = new Contracts(options);
|
|
@@ -1941,28 +2252,42 @@ class SecondLayer extends BaseClient {
|
|
|
1941
2252
|
return this.request("POST", "/v1/batch", { requests });
|
|
1942
2253
|
}
|
|
1943
2254
|
async context() {
|
|
1944
|
-
const safe = (p) => p.then((v) => v).catch(() => null);
|
|
1945
2255
|
const [account, streamsTip, indexEnv, subgraphsRes, subscriptionsRes] = await Promise.all([
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
2256
|
+
contextField(this.request("GET", "/api/accounts/me")),
|
|
2257
|
+
contextField(this.streams.tip()),
|
|
2258
|
+
contextField(this.index.canonical.list({ limit: 1 })),
|
|
2259
|
+
contextField(this.subgraphs.list()),
|
|
2260
|
+
contextField(this.subscriptions.list())
|
|
1951
2261
|
]);
|
|
1952
|
-
const subgraphs =
|
|
1953
|
-
|
|
1954
|
-
|
|
2262
|
+
const subgraphs = {
|
|
2263
|
+
value: subgraphsRes.value?.data ?? null,
|
|
2264
|
+
...subgraphsRes.error ? { error: subgraphsRes.error } : {}
|
|
2265
|
+
};
|
|
2266
|
+
const subscriptions = {
|
|
2267
|
+
value: null,
|
|
2268
|
+
...subscriptionsRes.error ? { error: subscriptionsRes.error } : {}
|
|
2269
|
+
};
|
|
2270
|
+
if (subscriptionsRes.value) {
|
|
1955
2271
|
const byStatus = {};
|
|
1956
|
-
for (const s of subscriptionsRes.data) {
|
|
2272
|
+
for (const s of subscriptionsRes.value.data) {
|
|
1957
2273
|
byStatus[s.status] = (byStatus[s.status] ?? 0) + 1;
|
|
1958
2274
|
}
|
|
1959
|
-
subscriptions = {
|
|
2275
|
+
subscriptions.value = {
|
|
2276
|
+
count: subscriptionsRes.value.data.length,
|
|
2277
|
+
byStatus
|
|
2278
|
+
};
|
|
1960
2279
|
}
|
|
1961
|
-
let activeOperations =
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
2280
|
+
let activeOperations = {
|
|
2281
|
+
value: null,
|
|
2282
|
+
error: {
|
|
2283
|
+
message: "Not probed: in-flight operations are read per subgraph, and the subgraph list did not resolve.",
|
|
2284
|
+
retryable: subgraphs.error?.retryable ?? false
|
|
2285
|
+
}
|
|
2286
|
+
};
|
|
2287
|
+
if (subgraphs.value) {
|
|
2288
|
+
const probed = await Promise.all(subgraphs.value.filter((s) => s.status === "reindexing").map(async (s) => {
|
|
2289
|
+
const res = await contextField(this.subgraphs.operations(s.name));
|
|
2290
|
+
const op = res.value?.operations.find((o) => o.status === "queued" || o.status === "running");
|
|
1966
2291
|
return op ? {
|
|
1967
2292
|
subgraph: s.name,
|
|
1968
2293
|
operationId: op.id,
|
|
@@ -1971,12 +2296,17 @@ class SecondLayer extends BaseClient {
|
|
|
1971
2296
|
progress: op.progress
|
|
1972
2297
|
} : null;
|
|
1973
2298
|
}));
|
|
1974
|
-
activeOperations =
|
|
2299
|
+
activeOperations = {
|
|
2300
|
+
value: probed.filter((o) => o !== null)
|
|
2301
|
+
};
|
|
1975
2302
|
}
|
|
1976
2303
|
return {
|
|
1977
2304
|
account,
|
|
1978
2305
|
streamsTip,
|
|
1979
|
-
indexTip:
|
|
2306
|
+
indexTip: {
|
|
2307
|
+
value: indexEnv.value?.tip ?? null,
|
|
2308
|
+
...indexEnv.error ? { error: indexEnv.error } : {}
|
|
2309
|
+
},
|
|
1980
2310
|
subgraphs,
|
|
1981
2311
|
subscriptions,
|
|
1982
2312
|
activeOperations
|
|
@@ -2261,9 +2591,9 @@ export {
|
|
|
2261
2591
|
decodeChainWebhook,
|
|
2262
2592
|
decode,
|
|
2263
2593
|
createStreamsClient,
|
|
2594
|
+
contextField,
|
|
2264
2595
|
consumerHealth,
|
|
2265
2596
|
consumeIndexFeed,
|
|
2266
|
-
VersionConflictError,
|
|
2267
2597
|
ValidationError,
|
|
2268
2598
|
Subscriptions,
|
|
2269
2599
|
Subgraphs,
|
|
@@ -2274,6 +2604,8 @@ export {
|
|
|
2274
2604
|
RateLimitError,
|
|
2275
2605
|
LOCAL_API_URL,
|
|
2276
2606
|
Index,
|
|
2607
|
+
INDEX_MAX_PAGE_SIZE,
|
|
2608
|
+
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
2277
2609
|
Cursor,
|
|
2278
2610
|
Contracts,
|
|
2279
2611
|
CREDENTIAL_ENV_VARS,
|
|
@@ -2281,5 +2613,5 @@ export {
|
|
|
2281
2613
|
ApiError
|
|
2282
2614
|
};
|
|
2283
2615
|
|
|
2284
|
-
//# debugId=
|
|
2616
|
+
//# debugId=CDF488058C75012864756E2164756E21
|
|
2285
2617
|
//# sourceMappingURL=index.js.map
|