@secondlayer/sdk 9.0.1 → 10.0.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 +78 -17
- package/README.md +98 -31
- package/dist/index.d.ts +234 -113
- package/dist/index.js +782 -445
- package/dist/index.js.map +19 -19
- package/dist/sinks/bun-sqlite.d.ts +4 -1
- package/dist/sinks/bun-sqlite.js +18 -19
- package/dist/sinks/bun-sqlite.js.map +5 -5
- package/dist/sinks/core.d.ts +8 -1
- package/dist/sinks/core.js +15 -19
- package/dist/sinks/core.js.map +4 -4
- package/dist/sinks/drizzle.d.ts +4 -1
- package/dist/sinks/drizzle.js +19 -20
- package/dist/sinks/drizzle.js.map +5 -5
- package/dist/sinks/kysely.d.ts +4 -1
- package/dist/sinks/kysely.js +20 -20
- 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 +94 -24
- package/dist/streams/index.js +411 -194
- package/dist/streams/index.js.map +13 -13
- package/dist/subgraphs/index.d.ts +198 -92
- package/dist/subgraphs/index.js +778 -443
- package/dist/subgraphs/index.js.map +17 -17
- package/package.json +4 -4
package/dist/subgraphs/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);
|
|
278
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
|
+
}
|
|
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
|
-
|
|
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;
|
|
294
564
|
}
|
|
295
|
-
function serializeWhere(where) {
|
|
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
|
}
|
|
@@ -512,37 +834,28 @@ class Contracts extends BaseClient {
|
|
|
512
834
|
}
|
|
513
835
|
}
|
|
514
836
|
|
|
515
|
-
// src/streams/errors.ts
|
|
516
|
-
class StreamsServerError extends ApiError {
|
|
517
|
-
constructor(message, status, body) {
|
|
518
|
-
super(status, message, body, undefined, { retryable: true });
|
|
519
|
-
this.name = "StreamsServerError";
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
class StreamsSignatureError extends SecondLayerError {
|
|
524
|
-
constructor(message = "Streams response signature verification failed.") {
|
|
525
|
-
super(message);
|
|
526
|
-
this.name = "StreamsSignatureError";
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
837
|
// src/streams/cursor.ts
|
|
531
|
-
|
|
838
|
+
import {
|
|
839
|
+
blockEndCursor,
|
|
840
|
+
decodeStreamsCursor,
|
|
841
|
+
encodeStreamsCursor
|
|
842
|
+
} from "@secondlayer/shared";
|
|
532
843
|
var Cursor = {
|
|
533
844
|
atHeight(height) {
|
|
534
845
|
if (height <= 0)
|
|
535
|
-
return
|
|
536
|
-
return
|
|
846
|
+
return null;
|
|
847
|
+
return encodeStreamsCursor(blockEndCursor(height - 1));
|
|
537
848
|
},
|
|
538
849
|
parse(cursor) {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
850
|
+
try {
|
|
851
|
+
const decoded = decodeStreamsCursor(cursor);
|
|
852
|
+
return {
|
|
853
|
+
blockHeight: decoded.block_height,
|
|
854
|
+
eventIndex: decoded.event_index
|
|
855
|
+
};
|
|
856
|
+
} catch {
|
|
543
857
|
throw new ValidationError(`Invalid stream cursor "${cursor}"; expected "<block>:<index>" (e.g. "951475:3").`, 400);
|
|
544
858
|
}
|
|
545
|
-
return { blockHeight, eventIndex };
|
|
546
859
|
}
|
|
547
860
|
};
|
|
548
861
|
|
|
@@ -550,6 +863,39 @@ var Cursor = {
|
|
|
550
863
|
function reorgKey(reorg) {
|
|
551
864
|
return `${reorg.detected_at}|${reorg.fork_point_height}|${reorg.new_canonical_tip}`;
|
|
552
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
|
+
}
|
|
553
899
|
function assertFinalizedCheckpoint(returned, checkpoint) {
|
|
554
900
|
if (returned === null || returned === undefined)
|
|
555
901
|
return;
|
|
@@ -629,17 +975,19 @@ async function defaultSleep(ms, signal) {
|
|
|
629
975
|
if (signal?.aborted)
|
|
630
976
|
return;
|
|
631
977
|
await new Promise((resolve) => {
|
|
632
|
-
const
|
|
633
|
-
if (!signal)
|
|
634
|
-
return;
|
|
635
|
-
signal.addEventListener("abort", () => {
|
|
978
|
+
const onAbort = () => {
|
|
636
979
|
clearTimeout(timeout);
|
|
637
980
|
resolve();
|
|
638
|
-
}
|
|
981
|
+
};
|
|
982
|
+
const timeout = setTimeout(() => {
|
|
983
|
+
signal?.removeEventListener("abort", onAbort);
|
|
984
|
+
resolve();
|
|
985
|
+
}, ms);
|
|
986
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
639
987
|
});
|
|
640
988
|
}
|
|
641
989
|
async function consumeStreamsEvents(opts) {
|
|
642
|
-
const
|
|
990
|
+
const sleep3 = opts.sleep ?? defaultSleep;
|
|
643
991
|
const mode = opts.mode ?? "tail";
|
|
644
992
|
const finalizedOnly = opts.finalizedOnly ?? false;
|
|
645
993
|
const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
|
|
@@ -649,6 +997,7 @@ async function consumeStreamsEvents(opts) {
|
|
|
649
997
|
const committedCursor = await opts.sink?.loadCursor() ?? null;
|
|
650
998
|
let cursor = opts.fromCursor ?? committedCursor;
|
|
651
999
|
const handledReorgs = new Set;
|
|
1000
|
+
let reorgSince = null;
|
|
652
1001
|
let pages = 0;
|
|
653
1002
|
let emptyPolls = 0;
|
|
654
1003
|
let height = resumeHeight(cursor);
|
|
@@ -668,27 +1017,61 @@ async function consumeStreamsEvents(opts) {
|
|
|
668
1017
|
retryCount: opts.retryCount,
|
|
669
1018
|
retryDelay: opts.retryDelay,
|
|
670
1019
|
onError: opts.onError,
|
|
671
|
-
sleep,
|
|
1020
|
+
sleep: sleep3,
|
|
672
1021
|
signal: opts.signal
|
|
673
1022
|
});
|
|
674
1023
|
pages++;
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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;
|
|
687
1038
|
scanned = height;
|
|
688
1039
|
emptyPolls = 0;
|
|
689
1040
|
continue;
|
|
690
1041
|
}
|
|
691
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
|
+
}
|
|
692
1075
|
const emitted = finalizedOnly ? envelope.events.filter((event) => event.finalized) : envelope.events;
|
|
693
1076
|
const checkpoint = finalizedOnly ? emitted.at(-1)?.cursor ?? cursor : envelope.next_cursor;
|
|
694
1077
|
height = emitted.at(-1)?.block_height ?? height;
|
|
@@ -729,7 +1112,7 @@ async function consumeStreamsEvents(opts) {
|
|
|
729
1112
|
if (mode === "bounded") {
|
|
730
1113
|
return { cursor, pages, emptyPolls };
|
|
731
1114
|
}
|
|
732
|
-
await
|
|
1115
|
+
await sleep3(emptyBackoffMs, opts.signal);
|
|
733
1116
|
continue;
|
|
734
1117
|
}
|
|
735
1118
|
return { cursor, pages, emptyPolls };
|
|
@@ -737,7 +1120,7 @@ async function consumeStreamsEvents(opts) {
|
|
|
737
1120
|
return { cursor, pages, emptyPolls };
|
|
738
1121
|
}
|
|
739
1122
|
async function* iterateStreamsBatches(opts) {
|
|
740
|
-
const
|
|
1123
|
+
const sleep3 = opts.sleep ?? defaultSleep;
|
|
741
1124
|
let cursor = opts.fromCursor ?? null;
|
|
742
1125
|
while (!opts.signal?.aborted) {
|
|
743
1126
|
const envelope = await opts.fetchEvents({
|
|
@@ -765,12 +1148,12 @@ async function* iterateStreamsBatches(opts) {
|
|
|
765
1148
|
if (!advanced && envelope.events.length === 0) {
|
|
766
1149
|
if (opts.signal?.aborted)
|
|
767
1150
|
return;
|
|
768
|
-
await
|
|
1151
|
+
await sleep3(opts.intervalMs, opts.signal);
|
|
769
1152
|
}
|
|
770
1153
|
}
|
|
771
1154
|
}
|
|
772
1155
|
async function* streamStreamsEvents(opts) {
|
|
773
|
-
const
|
|
1156
|
+
const sleep3 = opts.sleep ?? defaultSleep;
|
|
774
1157
|
const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
|
|
775
1158
|
const maxPages = opts.maxPages ?? Number.POSITIVE_INFINITY;
|
|
776
1159
|
const maxEmptyPolls = opts.maxEmptyPolls ?? Number.POSITIVE_INFINITY;
|
|
@@ -805,7 +1188,7 @@ async function* streamStreamsEvents(opts) {
|
|
|
805
1188
|
emptyPolls++;
|
|
806
1189
|
if (emptyPolls >= maxEmptyPolls || pages >= maxPages)
|
|
807
1190
|
return;
|
|
808
|
-
await
|
|
1191
|
+
await sleep3(emptyBackoffMs, opts.signal);
|
|
809
1192
|
continue;
|
|
810
1193
|
}
|
|
811
1194
|
return;
|
|
@@ -814,7 +1197,7 @@ async function* streamStreamsEvents(opts) {
|
|
|
814
1197
|
|
|
815
1198
|
// src/index-api/consumer.ts
|
|
816
1199
|
async function consumeIndexFeed(opts) {
|
|
817
|
-
const
|
|
1200
|
+
const sleep3 = opts.sleep ?? defaultSleep;
|
|
818
1201
|
const mode = opts.mode ?? "tail";
|
|
819
1202
|
const finalizedOnly = opts.finalizedOnly ?? false;
|
|
820
1203
|
const batchSize = opts.batchSize ?? 200;
|
|
@@ -838,22 +1221,23 @@ async function consumeIndexFeed(opts) {
|
|
|
838
1221
|
retryCount: opts.retryCount,
|
|
839
1222
|
retryDelay: opts.retryDelay,
|
|
840
1223
|
onError: opts.onError,
|
|
841
|
-
sleep,
|
|
1224
|
+
sleep: sleep3,
|
|
842
1225
|
signal: opts.signal
|
|
843
1226
|
});
|
|
844
1227
|
pages++;
|
|
845
1228
|
if (!finalizedOnly && (opts.onReorg || opts.sink)) {
|
|
846
|
-
const
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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;
|
|
857
1241
|
scanned = height;
|
|
858
1242
|
emptyPolls = 0;
|
|
859
1243
|
continue;
|
|
@@ -900,7 +1284,7 @@ async function consumeIndexFeed(opts) {
|
|
|
900
1284
|
if (mode === "bounded") {
|
|
901
1285
|
return { cursor, pages, emptyPolls };
|
|
902
1286
|
}
|
|
903
|
-
await
|
|
1287
|
+
await sleep3(emptyBackoffMs, opts.signal);
|
|
904
1288
|
continue;
|
|
905
1289
|
}
|
|
906
1290
|
return { cursor, pages, emptyPolls };
|
|
@@ -909,6 +1293,7 @@ async function consumeIndexFeed(opts) {
|
|
|
909
1293
|
}
|
|
910
1294
|
|
|
911
1295
|
// src/index-api/client.ts
|
|
1296
|
+
var INDEX_MAX_PAGE_SIZE = 1000;
|
|
912
1297
|
function feedFilters(params) {
|
|
913
1298
|
const { fromCursor: _checkpoint, ...filters } = params;
|
|
914
1299
|
return filters;
|
|
@@ -958,7 +1343,8 @@ class Index extends BaseClient {
|
|
|
958
1343
|
...feedFilters(params),
|
|
959
1344
|
cursor,
|
|
960
1345
|
fromHeight,
|
|
961
|
-
limit
|
|
1346
|
+
limit,
|
|
1347
|
+
signal: params.signal
|
|
962
1348
|
}),
|
|
963
1349
|
itemsOf: (envelope) => envelope.events
|
|
964
1350
|
})
|
|
@@ -972,7 +1358,8 @@ class Index extends BaseClient {
|
|
|
972
1358
|
...feedFilters(params),
|
|
973
1359
|
cursor,
|
|
974
1360
|
fromHeight,
|
|
975
|
-
limit
|
|
1361
|
+
limit,
|
|
1362
|
+
signal: params.signal
|
|
976
1363
|
}),
|
|
977
1364
|
itemsOf: (envelope) => envelope.contract_calls
|
|
978
1365
|
})
|
|
@@ -1011,7 +1398,8 @@ class Index extends BaseClient {
|
|
|
1011
1398
|
...feedFilters(params),
|
|
1012
1399
|
cursor,
|
|
1013
1400
|
fromHeight,
|
|
1014
|
-
limit
|
|
1401
|
+
limit,
|
|
1402
|
+
signal: params.signal
|
|
1015
1403
|
}),
|
|
1016
1404
|
itemsOf: (envelope) => envelope.deposits
|
|
1017
1405
|
}),
|
|
@@ -1031,7 +1419,8 @@ class Index extends BaseClient {
|
|
|
1031
1419
|
...feedFilters(params),
|
|
1032
1420
|
cursor,
|
|
1033
1421
|
fromHeight,
|
|
1034
|
-
limit
|
|
1422
|
+
limit,
|
|
1423
|
+
signal: params.signal
|
|
1035
1424
|
}),
|
|
1036
1425
|
itemsOf: (envelope) => envelope.events
|
|
1037
1426
|
})
|
|
@@ -1058,7 +1447,7 @@ class Index extends BaseClient {
|
|
|
1058
1447
|
asset_identifier: params.assetIdentifier,
|
|
1059
1448
|
sender: params.sender,
|
|
1060
1449
|
recipient: params.recipient
|
|
1061
|
-
})}
|
|
1450
|
+
})}`, undefined, { signal: params.signal });
|
|
1062
1451
|
}
|
|
1063
1452
|
async listNftTransfers(params = {}) {
|
|
1064
1453
|
return this.request("GET", `/v1/index/nft-transfers${buildQuery({
|
|
@@ -1067,29 +1456,45 @@ class Index extends BaseClient {
|
|
|
1067
1456
|
asset_identifier: params.assetIdentifier,
|
|
1068
1457
|
sender: params.sender,
|
|
1069
1458
|
recipient: params.recipient
|
|
1070
|
-
})}
|
|
1459
|
+
})}`, undefined, { signal: params.signal });
|
|
1071
1460
|
}
|
|
1072
1461
|
async* keysetWalk(params, list, itemsOf) {
|
|
1073
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
|
+
}
|
|
1074
1466
|
let cursor = params.cursor ?? params.fromCursor ?? null;
|
|
1075
1467
|
let firstPage = true;
|
|
1076
|
-
|
|
1077
|
-
|
|
1468
|
+
const throwIfAborted = () => {
|
|
1469
|
+
if (params.signal?.aborted)
|
|
1470
|
+
throw params.signal.reason ?? abortError();
|
|
1471
|
+
};
|
|
1472
|
+
for (;; ) {
|
|
1473
|
+
throwIfAborted();
|
|
1474
|
+
const page = {
|
|
1078
1475
|
limit: batchSize,
|
|
1079
1476
|
cursor: firstPage ? params.cursor : cursor,
|
|
1080
1477
|
fromCursor: firstPage ? params.fromCursor : undefined,
|
|
1081
|
-
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
|
|
1082
1487
|
});
|
|
1083
1488
|
const items = itemsOf(envelope);
|
|
1084
1489
|
for (const item of items) {
|
|
1085
|
-
|
|
1086
|
-
return;
|
|
1490
|
+
throwIfAborted();
|
|
1087
1491
|
yield item;
|
|
1088
1492
|
}
|
|
1089
1493
|
const nextCursor = envelope.next_cursor;
|
|
1090
|
-
if (
|
|
1494
|
+
if (nextCursor === null || nextCursor === undefined)
|
|
1495
|
+
return;
|
|
1496
|
+
if (nextCursor === cursor)
|
|
1091
1497
|
return;
|
|
1092
|
-
}
|
|
1093
1498
|
cursor = nextCursor;
|
|
1094
1499
|
firstPage = false;
|
|
1095
1500
|
}
|
|
@@ -1110,7 +1515,7 @@ class Index extends BaseClient {
|
|
|
1110
1515
|
recipient: params.recipient,
|
|
1111
1516
|
trait: params.trait,
|
|
1112
1517
|
tx_context: params.txContext ? "true" : undefined
|
|
1113
|
-
})}
|
|
1518
|
+
})}`, undefined, { signal: params.signal });
|
|
1114
1519
|
}
|
|
1115
1520
|
walkEvents(params) {
|
|
1116
1521
|
return this.keysetWalk(params, (page) => this.listEvents({ ...params, ...page }), (e) => e.events);
|
|
@@ -1122,19 +1527,19 @@ class Index extends BaseClient {
|
|
|
1122
1527
|
function_name: params.functionName,
|
|
1123
1528
|
sender: params.sender,
|
|
1124
1529
|
trait: params.trait
|
|
1125
|
-
})}
|
|
1530
|
+
})}`, undefined, { signal: params.signal });
|
|
1126
1531
|
}
|
|
1127
1532
|
walkContractCalls(params = {}) {
|
|
1128
1533
|
return this.keysetWalk(params, (page) => this.listContractCalls({ ...params, ...page }), (e) => e.contract_calls);
|
|
1129
1534
|
}
|
|
1130
1535
|
async listCanonical(params = {}) {
|
|
1131
|
-
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 });
|
|
1132
1537
|
}
|
|
1133
1538
|
walkCanonical(params = {}) {
|
|
1134
1539
|
return this.keysetWalk(params, (page) => this.listCanonical({ ...params, ...page }), (e) => e.canonical);
|
|
1135
1540
|
}
|
|
1136
1541
|
async listBlocks(params = {}) {
|
|
1137
|
-
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 });
|
|
1138
1543
|
}
|
|
1139
1544
|
async getBlock(ref) {
|
|
1140
1545
|
return this.requestOrNull("GET", `/v1/index/blocks/${encodeURIComponent(String(ref))}`);
|
|
@@ -1148,7 +1553,7 @@ class Index extends BaseClient {
|
|
|
1148
1553
|
type: params.type,
|
|
1149
1554
|
sender: params.sender,
|
|
1150
1555
|
contract_id: params.contractId
|
|
1151
|
-
})}
|
|
1556
|
+
})}`, undefined, { signal: params.signal });
|
|
1152
1557
|
}
|
|
1153
1558
|
async getTransaction(txId) {
|
|
1154
1559
|
return this.requestOrNull("GET", `/v1/index/transactions/${encodeURIComponent(txId)}`);
|
|
@@ -1165,7 +1570,7 @@ class Index extends BaseClient {
|
|
|
1165
1570
|
function_name: params.functionName,
|
|
1166
1571
|
stacker: params.stacker,
|
|
1167
1572
|
caller: params.caller
|
|
1168
|
-
})}
|
|
1573
|
+
})}`, undefined, { signal: params.signal });
|
|
1169
1574
|
}
|
|
1170
1575
|
walkStacking(params = {}) {
|
|
1171
1576
|
return this.keysetWalk(params, (page) => this.listStacking({ ...params, ...page }), (e) => e.stacking);
|
|
@@ -1177,7 +1582,7 @@ class Index extends BaseClient {
|
|
|
1177
1582
|
type: params.type,
|
|
1178
1583
|
contract_id: params.contractId,
|
|
1179
1584
|
function_name: params.functionName
|
|
1180
|
-
})}
|
|
1585
|
+
})}`, undefined, { signal: params.signal });
|
|
1181
1586
|
}
|
|
1182
1587
|
async getMempoolTx(txId) {
|
|
1183
1588
|
return this.requestOrNull("GET", `/v1/index/mempool/${encodeURIComponent(txId)}`);
|
|
@@ -1191,7 +1596,7 @@ class Index extends BaseClient {
|
|
|
1191
1596
|
confirmed: params.confirmed,
|
|
1192
1597
|
sender: params.sender,
|
|
1193
1598
|
bitcoin_txid: params.bitcoinTxid
|
|
1194
|
-
})}
|
|
1599
|
+
})}`, undefined, { signal: params.signal });
|
|
1195
1600
|
}
|
|
1196
1601
|
walkSbtcDeposits(params = {}) {
|
|
1197
1602
|
return this.keysetWalk(params, (page) => this.listSbtcDeposits({ ...params, ...page }), (e) => e.deposits);
|
|
@@ -1207,7 +1612,7 @@ class Index extends BaseClient {
|
|
|
1207
1612
|
sender: params.sender,
|
|
1208
1613
|
request_id: params.requestId,
|
|
1209
1614
|
settlement_confirmed: params.settlementConfirmed
|
|
1210
|
-
})}
|
|
1615
|
+
})}`, undefined, { signal: params.signal });
|
|
1211
1616
|
}
|
|
1212
1617
|
walkSbtcWithdrawals(params = {}) {
|
|
1213
1618
|
return this.keysetWalk(params, (page) => this.listSbtcWithdrawals({ ...params, ...page }), (e) => e.withdrawals);
|
|
@@ -1223,7 +1628,7 @@ class Index extends BaseClient {
|
|
|
1223
1628
|
sender: params.sender,
|
|
1224
1629
|
request_id: params.requestId,
|
|
1225
1630
|
bitcoin_txid: params.bitcoinTxid
|
|
1226
|
-
})}
|
|
1631
|
+
})}`, undefined, { signal: params.signal });
|
|
1227
1632
|
}
|
|
1228
1633
|
walkSbtcEvents(params = {}) {
|
|
1229
1634
|
return this.keysetWalk(params, (page) => this.listSbtcEvents({ ...params, ...page }), (e) => e.events);
|
|
@@ -1241,7 +1646,7 @@ class Index extends BaseClient {
|
|
|
1241
1646
|
signer_manager: params.signerManager,
|
|
1242
1647
|
bond_index: params.bondIndex,
|
|
1243
1648
|
reward_cycle: params.rewardCycle
|
|
1244
|
-
})}
|
|
1649
|
+
})}`, undefined, { signal: params.signal });
|
|
1245
1650
|
}
|
|
1246
1651
|
walkPox5Events(params = {}) {
|
|
1247
1652
|
return this.keysetWalk(params, (page) => this.listPox5Events({ ...params, ...page }), (e) => e.events);
|
|
@@ -1250,24 +1655,14 @@ class Index extends BaseClient {
|
|
|
1250
1655
|
return this.request("GET", `/v1/index/pox/cycles${buildQuery({
|
|
1251
1656
|
cursor: params.cursor,
|
|
1252
1657
|
limit: params.limit
|
|
1253
|
-
})}
|
|
1658
|
+
})}`, undefined, { signal: params.signal });
|
|
1254
1659
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
if (params.signal?.aborted)
|
|
1262
|
-
return;
|
|
1263
|
-
yield cycle;
|
|
1264
|
-
}
|
|
1265
|
-
const nextCursor = envelope.next_cursor;
|
|
1266
|
-
if (nextCursor === null || nextCursor === cursor || envelope.cycles.length < batchSize) {
|
|
1267
|
-
return;
|
|
1268
|
-
}
|
|
1269
|
-
cursor = nextCursor;
|
|
1270
|
-
}
|
|
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);
|
|
1271
1666
|
}
|
|
1272
1667
|
async getPoxCycle(rewardCycle) {
|
|
1273
1668
|
return this.requestOrNull("GET", `/v1/index/pox/cycles/${rewardCycle}`);
|
|
@@ -1431,166 +1826,57 @@ function createStreamsDumps(opts) {
|
|
|
1431
1826
|
return manifest;
|
|
1432
1827
|
}
|
|
1433
1828
|
async function download(file) {
|
|
1829
|
+
return fetchPageWithRetry(() => downloadOnce(file), {
|
|
1830
|
+
sleep: defaultSleep
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
async function downloadOnce(file) {
|
|
1434
1834
|
const res = await opts.fetchImpl(fileUrl(file));
|
|
1435
1835
|
if (!res.ok) {
|
|
1436
1836
|
throw new StreamsServerError(`Could not download dump ${file.path} (${res.status}).`, res.status);
|
|
1437
1837
|
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
if (digest !== file.sha256) {
|
|
1441
|
-
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);
|
|
1442
1840
|
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
if (external.aborted)
|
|
1456
|
-
controller.abort();
|
|
1457
|
-
else
|
|
1458
|
-
external.addEventListener("abort", () => controller.abort(), {
|
|
1459
|
-
once: true
|
|
1460
|
-
});
|
|
1461
|
-
}
|
|
1462
|
-
let cursor = params.fromCursor ?? null;
|
|
1463
|
-
const reconnectDelayMs = opts.reconnectDelayMs ?? 1000;
|
|
1464
|
-
const run = async () => {
|
|
1465
|
-
while (!controller.signal.aborted) {
|
|
1466
|
-
try {
|
|
1467
|
-
const url = `${opts.baseUrl}/v1/streams/events/stream${buildQuery({
|
|
1468
|
-
from_cursor: cursor ?? undefined,
|
|
1469
|
-
types: params.types,
|
|
1470
|
-
not_types: params.notTypes,
|
|
1471
|
-
contract_id: params.contractId,
|
|
1472
|
-
sender: params.sender,
|
|
1473
|
-
recipient: params.recipient,
|
|
1474
|
-
asset_identifier: params.assetIdentifier,
|
|
1475
|
-
filters: params.filters ? JSON.stringify(params.filters) : undefined
|
|
1476
|
-
})}`;
|
|
1477
|
-
const res = await opts.fetchImpl(url, {
|
|
1478
|
-
headers: {
|
|
1479
|
-
...opts.apiKey ? { Authorization: `Bearer ${opts.apiKey}` } : {},
|
|
1480
|
-
Accept: "text/event-stream"
|
|
1481
|
-
},
|
|
1482
|
-
signal: controller.signal
|
|
1483
|
-
});
|
|
1484
|
-
if (!res.ok) {
|
|
1485
|
-
throw new StreamsServerError(`Streams SSE returned ${res.status}.`, res.status);
|
|
1486
|
-
}
|
|
1487
|
-
if (!res.body) {
|
|
1488
|
-
throw new StreamsServerError("Streams SSE response has no body.", 0);
|
|
1489
|
-
}
|
|
1490
|
-
for await (const frame of parseSseFrames(res.body, controller.signal)) {
|
|
1491
|
-
if (frame.event === "ping" || !frame.data)
|
|
1492
|
-
continue;
|
|
1493
|
-
let parsed;
|
|
1494
|
-
try {
|
|
1495
|
-
parsed = JSON.parse(frame.data);
|
|
1496
|
-
} catch {
|
|
1497
|
-
continue;
|
|
1498
|
-
}
|
|
1499
|
-
if (!parsed.event)
|
|
1500
|
-
continue;
|
|
1501
|
-
if (opts.verify !== "off") {
|
|
1502
|
-
if (!parsed.sig) {
|
|
1503
|
-
if (opts.verify === "strict") {
|
|
1504
|
-
throw new StreamsSignatureError("Streams SSE frame signature is missing.");
|
|
1505
|
-
}
|
|
1506
|
-
} else {
|
|
1507
|
-
const key = await opts.loadKey();
|
|
1508
|
-
if (!ed25519.verifyEd25519(JSON.stringify(parsed.event), parsed.sig, key.publicKey)) {
|
|
1509
|
-
throw new StreamsSignatureError("Streams SSE frame signature is invalid.");
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
cursor = parsed.event.cursor ?? cursor;
|
|
1514
|
-
await params.onEvent(parsed.event);
|
|
1515
|
-
}
|
|
1516
|
-
} catch (err) {
|
|
1517
|
-
if (controller.signal.aborted)
|
|
1518
|
-
return;
|
|
1519
|
-
params.onError?.(err);
|
|
1520
|
-
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;
|
|
1521
1853
|
}
|
|
1854
|
+
} catch {
|
|
1855
|
+
throw new StreamsServerError(`Download of dump ${file.path} was interrupted after ${total} bytes.`, 0);
|
|
1522
1856
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
}
|
|
1527
|
-
function sleep(ms, signal) {
|
|
1528
|
-
return new Promise((resolve) => {
|
|
1529
|
-
if (signal.aborted)
|
|
1530
|
-
return resolve();
|
|
1531
|
-
const onAbort = () => {
|
|
1532
|
-
clearTimeout(timer);
|
|
1533
|
-
resolve();
|
|
1534
|
-
};
|
|
1535
|
-
const timer = setTimeout(() => {
|
|
1536
|
-
signal.removeEventListener("abort", onAbort);
|
|
1537
|
-
resolve();
|
|
1538
|
-
}, ms);
|
|
1539
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1540
|
-
});
|
|
1541
|
-
}
|
|
1542
|
-
async function* parseSseFrames(body, signal) {
|
|
1543
|
-
const reader = body.getReader();
|
|
1544
|
-
const decoder = new TextDecoder;
|
|
1545
|
-
let buffer = "";
|
|
1546
|
-
try {
|
|
1547
|
-
while (!signal.aborted) {
|
|
1548
|
-
const { value, done } = await reader.read();
|
|
1549
|
-
if (done)
|
|
1550
|
-
break;
|
|
1551
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1552
|
-
let sep = buffer.indexOf(`
|
|
1553
|
-
|
|
1554
|
-
`);
|
|
1555
|
-
while (sep !== -1) {
|
|
1556
|
-
yield parseFrame(buffer.slice(0, sep));
|
|
1557
|
-
buffer = buffer.slice(sep + 2);
|
|
1558
|
-
sep = buffer.indexOf(`
|
|
1559
|
-
|
|
1560
|
-
`);
|
|
1561
|
-
}
|
|
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}).`);
|
|
1562
1860
|
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
let event;
|
|
1571
|
-
const data = [];
|
|
1572
|
-
for (const line of raw.split(`
|
|
1573
|
-
`)) {
|
|
1574
|
-
if (line.startsWith("data:")) {
|
|
1575
|
-
data.push(line.slice(line.startsWith("data: ") ? 6 : 5));
|
|
1576
|
-
} else if (line.startsWith("event:")) {
|
|
1577
|
-
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;
|
|
1578
1868
|
}
|
|
1869
|
+
return bytes;
|
|
1579
1870
|
}
|
|
1580
|
-
return {
|
|
1581
|
-
`) : undefined };
|
|
1871
|
+
return { list, fileUrl, download };
|
|
1582
1872
|
}
|
|
1583
1873
|
|
|
1584
1874
|
// src/streams/client.ts
|
|
1585
1875
|
function cursorTuple(cursor) {
|
|
1586
1876
|
if (!cursor)
|
|
1587
1877
|
return [-1, -1];
|
|
1588
|
-
const
|
|
1589
|
-
|
|
1590
|
-
if (parts.length !== 2 || !Number.isInteger(block) || !Number.isInteger(index)) {
|
|
1591
|
-
throw new ValidationError(`Invalid stream cursor "${cursor}"; expected "<block>:<index>" (e.g. "951475:3").`, 400);
|
|
1592
|
-
}
|
|
1593
|
-
return [block, index];
|
|
1878
|
+
const { blockHeight, eventIndex } = Cursor.parse(cursor);
|
|
1879
|
+
return [blockHeight, eventIndex];
|
|
1594
1880
|
}
|
|
1595
1881
|
function maxCursor(a, b) {
|
|
1596
1882
|
const [ah, ai] = cursorTuple(a);
|
|
@@ -1611,51 +1897,33 @@ function streamsFilters(params) {
|
|
|
1611
1897
|
function normalizeBaseUrl(baseUrl) {
|
|
1612
1898
|
return baseUrl.replace(/\/+$/, "");
|
|
1613
1899
|
}
|
|
1614
|
-
|
|
1615
|
-
const text = await response.text();
|
|
1616
|
-
if (text.length === 0)
|
|
1617
|
-
return;
|
|
1900
|
+
function isLoopbackUrl(baseUrl) {
|
|
1618
1901
|
try {
|
|
1619
|
-
|
|
1902
|
+
const { hostname } = new URL(baseUrl);
|
|
1903
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]" || hostname.startsWith("127.");
|
|
1620
1904
|
} catch {
|
|
1621
|
-
return
|
|
1622
|
-
}
|
|
1623
|
-
}
|
|
1624
|
-
function errorMessage(body, fallback) {
|
|
1625
|
-
if (body && typeof body === "object") {
|
|
1626
|
-
const record = body;
|
|
1627
|
-
const message = record.error ?? record.message;
|
|
1628
|
-
if (typeof message === "string" && message.length > 0)
|
|
1629
|
-
return message;
|
|
1630
|
-
}
|
|
1631
|
-
if (typeof body === "string" && body.length > 0)
|
|
1632
|
-
return body;
|
|
1633
|
-
return fallback;
|
|
1634
|
-
}
|
|
1635
|
-
async function mapStreamsError(response) {
|
|
1636
|
-
const body = await responseBody(response);
|
|
1637
|
-
if (response.status === 401) {
|
|
1638
|
-
throw new AuthError(errorMessage(body, "API key invalid or expired."));
|
|
1639
|
-
}
|
|
1640
|
-
if (response.status === 429) {
|
|
1641
|
-
const retryAfter = response.headers.get("Retry-After") ?? undefined;
|
|
1642
|
-
throw new RateLimitError(errorMessage(body, "Rate limited. Try again later."), retryAfter);
|
|
1643
|
-
}
|
|
1644
|
-
if (response.status >= 500) {
|
|
1645
|
-
throw new StreamsServerError(errorMessage(body, `Streams server returned ${response.status}.`), response.status, body);
|
|
1905
|
+
return false;
|
|
1646
1906
|
}
|
|
1647
|
-
throw new ValidationError(errorMessage(body, `Streams request returned ${response.status}.`), response.status, body);
|
|
1648
1907
|
}
|
|
1649
1908
|
function createStreamsClient(options) {
|
|
1650
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
|
+
});
|
|
1651
1916
|
const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
1652
1917
|
const verify = options.verify;
|
|
1653
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
|
+
}
|
|
1654
1922
|
let keyPromise = null;
|
|
1655
1923
|
function loadKey() {
|
|
1656
1924
|
if (keyPromise)
|
|
1657
1925
|
return keyPromise;
|
|
1658
|
-
|
|
1926
|
+
const pending = (async () => {
|
|
1659
1927
|
if (typeof verify === "object") {
|
|
1660
1928
|
return {
|
|
1661
1929
|
keyId: ed255192.ed25519KeyId(verify.publicKey),
|
|
@@ -1663,8 +1931,16 @@ function createStreamsClient(options) {
|
|
|
1663
1931
|
publicKey: ed255192.loadEd25519PublicKey(verify.publicKey)
|
|
1664
1932
|
};
|
|
1665
1933
|
}
|
|
1666
|
-
|
|
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
|
+
}
|
|
1667
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
|
+
}
|
|
1668
1944
|
throw new StreamsSignatureError(`Could not fetch signing key (${res.status}).`);
|
|
1669
1945
|
}
|
|
1670
1946
|
const body = await res.json();
|
|
@@ -1677,7 +1953,26 @@ function createStreamsClient(options) {
|
|
|
1677
1953
|
publicKey: ed255192.loadEd25519PublicKey(body.public_key_pem)
|
|
1678
1954
|
};
|
|
1679
1955
|
})();
|
|
1680
|
-
|
|
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;
|
|
1681
1976
|
}
|
|
1682
1977
|
const dumps = createStreamsDumps({
|
|
1683
1978
|
baseUrl: options.dumpsBaseUrl,
|
|
@@ -1687,7 +1982,7 @@ function createStreamsClient(options) {
|
|
|
1687
1982
|
});
|
|
1688
1983
|
async function request(path) {
|
|
1689
1984
|
const response = await fetchImpl(`${baseUrl}${path}`, {
|
|
1690
|
-
headers:
|
|
1985
|
+
headers: authHeaders()
|
|
1691
1986
|
});
|
|
1692
1987
|
if (!response.ok)
|
|
1693
1988
|
await mapStreamsError(response);
|
|
@@ -1699,18 +1994,7 @@ function createStreamsClient(options) {
|
|
|
1699
1994
|
throw new StreamsSignatureError("Response is missing X-Signature.");
|
|
1700
1995
|
}
|
|
1701
1996
|
} else {
|
|
1702
|
-
const
|
|
1703
|
-
let key = await loadKey();
|
|
1704
|
-
if (responseKeyId && responseKeyId !== key.keyId) {
|
|
1705
|
-
if (typeof verify === "object") {
|
|
1706
|
-
throw new StreamsSignatureError(`Response signed with key '${responseKeyId}', expected pinned key '${key.keyId}'.`);
|
|
1707
|
-
}
|
|
1708
|
-
keyPromise = null;
|
|
1709
|
-
key = await loadKey();
|
|
1710
|
-
if (responseKeyId !== key.keyId) {
|
|
1711
|
-
throw new StreamsSignatureError(`Response signed with key '${responseKeyId}' not served by the signing-key endpoint.`);
|
|
1712
|
-
}
|
|
1713
|
-
}
|
|
1997
|
+
const key = await loadKeyFor(response.headers.get("X-Signature-KeyId"));
|
|
1714
1998
|
if (!ed255192.verifyEd25519(text, signature, key.publicKey)) {
|
|
1715
1999
|
throw new StreamsSignatureError;
|
|
1716
2000
|
}
|
|
@@ -1719,6 +2003,13 @@ function createStreamsClient(options) {
|
|
|
1719
2003
|
return JSON.parse(text);
|
|
1720
2004
|
}
|
|
1721
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);
|
|
1722
2013
|
function encodeFilters(filters) {
|
|
1723
2014
|
return filters ? JSON.stringify(filters) : undefined;
|
|
1724
2015
|
}
|
|
@@ -1766,8 +2057,10 @@ function createStreamsClient(options) {
|
|
|
1766
2057
|
...streamsFilters(params),
|
|
1767
2058
|
batchSize: params.batchSize ?? 100,
|
|
1768
2059
|
fetchEvents,
|
|
2060
|
+
fetchReorgs,
|
|
1769
2061
|
onBatch,
|
|
1770
2062
|
onReorg: params.onReorg,
|
|
2063
|
+
maxRollbackDepth: params.maxRollbackDepth,
|
|
1771
2064
|
emptyBackoffMs: params.emptyBackoffMs,
|
|
1772
2065
|
maxPages: params.maxPages,
|
|
1773
2066
|
maxEmptyPolls: params.maxEmptyPolls,
|
|
@@ -1809,10 +2102,12 @@ function createStreamsClient(options) {
|
|
|
1809
2102
|
subscribe(params) {
|
|
1810
2103
|
return subscribeStreamsEvents({
|
|
1811
2104
|
baseUrl,
|
|
1812
|
-
|
|
2105
|
+
headers: authHeaders(),
|
|
1813
2106
|
fetchImpl,
|
|
1814
2107
|
verify: verifyMode,
|
|
1815
|
-
loadKey,
|
|
2108
|
+
loadKey: loadKeyFor,
|
|
2109
|
+
reconnectDelayMs: params.reconnectDelayMs,
|
|
2110
|
+
staleAfterMs: params.staleAfterMs,
|
|
1816
2111
|
params
|
|
1817
2112
|
});
|
|
1818
2113
|
},
|
|
@@ -1820,11 +2115,11 @@ function createStreamsClient(options) {
|
|
|
1820
2115
|
const fromCursor = params.from === "genesis" ? null : params.from ?? null;
|
|
1821
2116
|
const fromBlock = fromCursor ? cursorTuple(fromCursor)[0] : 0;
|
|
1822
2117
|
const manifest = await dumps.list();
|
|
1823
|
-
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);
|
|
1824
2119
|
for (const file of files) {
|
|
1825
2120
|
if (params.signal?.aborted)
|
|
1826
2121
|
break;
|
|
1827
|
-
await params.onDumpFile(file);
|
|
2122
|
+
await params.onDumpFile(file, { from: fromCursor });
|
|
1828
2123
|
}
|
|
1829
2124
|
const seam = maxCursor(fromCursor, manifest.latest_finalized_cursor);
|
|
1830
2125
|
return consumeStreamsEvents({
|
|
@@ -1849,14 +2144,7 @@ function createStreamsClient(options) {
|
|
|
1849
2144
|
return request(`/v1/streams/blocks/${encodeURIComponent(String(heightOrHash))}/events`);
|
|
1850
2145
|
}
|
|
1851
2146
|
},
|
|
1852
|
-
reorgs: {
|
|
1853
|
-
list(params) {
|
|
1854
|
-
return request(`/v1/streams/reorgs${buildQuery({
|
|
1855
|
-
since: params.since,
|
|
1856
|
-
limit: params.limit
|
|
1857
|
-
})}`);
|
|
1858
|
-
}
|
|
1859
|
-
},
|
|
2147
|
+
reorgs: { list: listReorgs },
|
|
1860
2148
|
dumps,
|
|
1861
2149
|
canonical(height) {
|
|
1862
2150
|
return request(`/v1/streams/canonical/${height}`);
|
|
@@ -1875,44 +2163,69 @@ class Subscriptions extends BaseClient {
|
|
|
1875
2163
|
return this.request("GET", "/api/subscriptions");
|
|
1876
2164
|
}
|
|
1877
2165
|
async get(id) {
|
|
1878
|
-
return this.request("GET", `/api/subscriptions/${id}`);
|
|
2166
|
+
return this.request("GET", `/api/subscriptions/${seg(id)}`);
|
|
1879
2167
|
}
|
|
1880
2168
|
async create(input) {
|
|
1881
2169
|
return this.request("POST", "/api/subscriptions", input);
|
|
1882
2170
|
}
|
|
1883
2171
|
async update(id, patch) {
|
|
1884
|
-
return this.request("PATCH", `/api/subscriptions/${id}`, patch);
|
|
2172
|
+
return this.request("PATCH", `/api/subscriptions/${seg(id)}`, patch);
|
|
1885
2173
|
}
|
|
1886
2174
|
async pause(id) {
|
|
1887
|
-
return this.request("POST", `/api/subscriptions/${id}/pause`);
|
|
2175
|
+
return this.request("POST", `/api/subscriptions/${seg(id)}/pause`);
|
|
1888
2176
|
}
|
|
1889
2177
|
async resume(id) {
|
|
1890
|
-
return this.request("POST", `/api/subscriptions/${id}/resume`);
|
|
2178
|
+
return this.request("POST", `/api/subscriptions/${seg(id)}/resume`);
|
|
1891
2179
|
}
|
|
1892
2180
|
async delete(id) {
|
|
1893
|
-
return this.request("DELETE", `/api/subscriptions/${id}`);
|
|
2181
|
+
return this.request("DELETE", `/api/subscriptions/${seg(id)}`);
|
|
1894
2182
|
}
|
|
1895
2183
|
async rotateSecret(id) {
|
|
1896
|
-
return this.request("POST", `/api/subscriptions/${id}/rotate-secret`);
|
|
2184
|
+
return this.request("POST", `/api/subscriptions/${seg(id)}/rotate-secret`);
|
|
1897
2185
|
}
|
|
1898
2186
|
async test(id) {
|
|
1899
|
-
return this.request("POST", `/api/subscriptions/${id}/test`);
|
|
2187
|
+
return this.request("POST", `/api/subscriptions/${seg(id)}/test`);
|
|
1900
2188
|
}
|
|
1901
2189
|
async deliveries(id) {
|
|
1902
|
-
return this.request("GET", `/api/subscriptions/${id}/deliveries`);
|
|
2190
|
+
return this.request("GET", `/api/subscriptions/${seg(id)}/deliveries`);
|
|
1903
2191
|
}
|
|
1904
2192
|
async replay(id, range) {
|
|
1905
|
-
return this.request("POST", `/api/subscriptions/${id}/replay`, range);
|
|
2193
|
+
return this.request("POST", `/api/subscriptions/${seg(id)}/replay`, range);
|
|
1906
2194
|
}
|
|
1907
2195
|
async dead(id) {
|
|
1908
|
-
return this.request("GET", `/api/subscriptions/${id}/dead`);
|
|
2196
|
+
return this.request("GET", `/api/subscriptions/${seg(id)}/dead`);
|
|
1909
2197
|
}
|
|
1910
2198
|
async requeue(id, outboxId) {
|
|
1911
|
-
return this.request("POST", `/api/subscriptions/${id}/dead/${outboxId}/requeue`);
|
|
2199
|
+
return this.request("POST", `/api/subscriptions/${seg(id)}/dead/${seg(outboxId)}/requeue`);
|
|
1912
2200
|
}
|
|
1913
2201
|
}
|
|
1914
2202
|
|
|
1915
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
|
+
|
|
1916
2229
|
class SecondLayer extends BaseClient {
|
|
1917
2230
|
streams;
|
|
1918
2231
|
index;
|
|
@@ -1922,10 +2235,13 @@ class SecondLayer extends BaseClient {
|
|
|
1922
2235
|
constructor(options = {}) {
|
|
1923
2236
|
super(options);
|
|
1924
2237
|
this.streams = createStreamsClient({
|
|
1925
|
-
apiKey:
|
|
2238
|
+
apiKey: this.apiKey,
|
|
2239
|
+
origin: this.origin,
|
|
1926
2240
|
baseUrl: options.baseUrl,
|
|
1927
2241
|
fetchImpl: options.fetchImpl,
|
|
1928
|
-
dumpsBaseUrl: options.dumpsBaseUrl
|
|
2242
|
+
dumpsBaseUrl: options.dumpsBaseUrl,
|
|
2243
|
+
verify: options.verify,
|
|
2244
|
+
verifyDumpsManifest: options.verifyDumpsManifest
|
|
1929
2245
|
});
|
|
1930
2246
|
this.index = new Index(options);
|
|
1931
2247
|
this.contracts = new Contracts(options);
|
|
@@ -1936,28 +2252,42 @@ class SecondLayer extends BaseClient {
|
|
|
1936
2252
|
return this.request("POST", "/v1/batch", { requests });
|
|
1937
2253
|
}
|
|
1938
2254
|
async context() {
|
|
1939
|
-
const safe = (p) => p.then((v) => v).catch(() => null);
|
|
1940
2255
|
const [account, streamsTip, indexEnv, subgraphsRes, subscriptionsRes] = await Promise.all([
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
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())
|
|
1946
2261
|
]);
|
|
1947
|
-
const subgraphs =
|
|
1948
|
-
|
|
1949
|
-
|
|
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) {
|
|
1950
2271
|
const byStatus = {};
|
|
1951
|
-
for (const s of subscriptionsRes.data) {
|
|
2272
|
+
for (const s of subscriptionsRes.value.data) {
|
|
1952
2273
|
byStatus[s.status] = (byStatus[s.status] ?? 0) + 1;
|
|
1953
2274
|
}
|
|
1954
|
-
subscriptions = {
|
|
2275
|
+
subscriptions.value = {
|
|
2276
|
+
count: subscriptionsRes.value.data.length,
|
|
2277
|
+
byStatus
|
|
2278
|
+
};
|
|
1955
2279
|
}
|
|
1956
|
-
let activeOperations =
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
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");
|
|
1961
2291
|
return op ? {
|
|
1962
2292
|
subgraph: s.name,
|
|
1963
2293
|
operationId: op.id,
|
|
@@ -1966,12 +2296,17 @@ class SecondLayer extends BaseClient {
|
|
|
1966
2296
|
progress: op.progress
|
|
1967
2297
|
} : null;
|
|
1968
2298
|
}));
|
|
1969
|
-
activeOperations =
|
|
2299
|
+
activeOperations = {
|
|
2300
|
+
value: probed.filter((o) => o !== null)
|
|
2301
|
+
};
|
|
1970
2302
|
}
|
|
1971
2303
|
return {
|
|
1972
2304
|
account,
|
|
1973
2305
|
streamsTip,
|
|
1974
|
-
indexTip:
|
|
2306
|
+
indexTip: {
|
|
2307
|
+
value: indexEnv.value?.tip ?? null,
|
|
2308
|
+
...indexEnv.error ? { error: indexEnv.error } : {}
|
|
2309
|
+
},
|
|
1975
2310
|
subgraphs,
|
|
1976
2311
|
subscriptions,
|
|
1977
2312
|
activeOperations
|
|
@@ -1994,5 +2329,5 @@ export {
|
|
|
1994
2329
|
Subgraphs
|
|
1995
2330
|
};
|
|
1996
2331
|
|
|
1997
|
-
//# debugId=
|
|
2332
|
+
//# debugId=10070F0E5755447564756E2164756E21
|
|
1998
2333
|
//# sourceMappingURL=index.js.map
|