@secondlayer/sdk 10.4.3 → 10.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +423 -398
- package/dist/index.js +770 -722
- package/dist/index.js.map +11 -11
- package/dist/sinks/bun-sqlite.d.ts +2 -2
- package/dist/sinks/core.d.ts +1 -1
- package/dist/sinks/core.js +4 -4
- package/dist/sinks/core.js.map +1 -1
- package/dist/sinks/drizzle.d.ts +2 -2
- package/dist/sinks/drizzle.js +6 -4
- package/dist/sinks/drizzle.js.map +5 -5
- package/dist/sinks/kysely.d.ts +2 -2
- package/dist/sinks/kysely.js +4 -2
- package/dist/sinks/kysely.js.map +5 -5
- package/dist/sinks/testing.d.ts +1 -1
- package/dist/sinks/testing.js +3 -3
- package/dist/sinks/testing.js.map +2 -2
- package/dist/streams/index.d.ts +1 -1
- package/dist/streams/index.js +80 -35
- package/dist/streams/index.js.map +3 -3
- package/dist/streams/rows.d.ts +1 -1
- package/dist/streams/rows.js +22 -22
- package/dist/streams/rows.js.map +1 -1
- package/dist/subgraphs/index.d.ts +19 -3
- package/dist/subgraphs/index.js +63 -18
- package/dist/subgraphs/index.js.map +4 -4
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
// src/archive/client.ts
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import {
|
|
4
|
+
checkSignature,
|
|
5
|
+
loadReference
|
|
6
|
+
} from "@secondlayer/shared/archive/reference";
|
|
7
|
+
|
|
1
8
|
// src/errors.ts
|
|
2
9
|
var DOCS_BASE = "https://www.secondlayer.tools";
|
|
3
10
|
|
|
@@ -156,6 +163,30 @@ function parseRetryAfter(value) {
|
|
|
156
163
|
}
|
|
157
164
|
|
|
158
165
|
// src/base.ts
|
|
166
|
+
class MemoryEtagCache {
|
|
167
|
+
limit;
|
|
168
|
+
entries = new Map;
|
|
169
|
+
constructor(limit = 64) {
|
|
170
|
+
this.limit = limit;
|
|
171
|
+
}
|
|
172
|
+
get(key) {
|
|
173
|
+
const entry = this.entries.get(key);
|
|
174
|
+
if (entry) {
|
|
175
|
+
this.entries.delete(key);
|
|
176
|
+
this.entries.set(key, entry);
|
|
177
|
+
}
|
|
178
|
+
return entry;
|
|
179
|
+
}
|
|
180
|
+
set(key, entry) {
|
|
181
|
+
this.entries.delete(key);
|
|
182
|
+
this.entries.set(key, entry);
|
|
183
|
+
if (this.entries.size > this.limit) {
|
|
184
|
+
const oldest = this.entries.keys().next().value;
|
|
185
|
+
if (oldest !== undefined)
|
|
186
|
+
this.entries.delete(oldest);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
159
190
|
var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
160
191
|
function abortError() {
|
|
161
192
|
return new DOMException("This operation was aborted", "AbortError");
|
|
@@ -266,12 +297,14 @@ class BaseClient {
|
|
|
266
297
|
origin;
|
|
267
298
|
fetchImpl;
|
|
268
299
|
requestTimeoutMs;
|
|
300
|
+
etagCache;
|
|
269
301
|
constructor(options = {}) {
|
|
270
302
|
this.baseUrl = resolveBaseUrl(options.baseUrl);
|
|
271
303
|
this.apiKey = resolveApiKey(options.apiKey);
|
|
272
304
|
this.origin = options.origin ?? "cli";
|
|
273
305
|
this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
274
306
|
this.fetchImpl = options.fetchImpl ?? ((...args) => fetch(...args));
|
|
307
|
+
this.etagCache = options.etagCache === false ? null : options.etagCache ?? new MemoryEtagCache;
|
|
275
308
|
}
|
|
276
309
|
static authHeaders(apiKey) {
|
|
277
310
|
const headers = {
|
|
@@ -284,11 +317,25 @@ class BaseClient {
|
|
|
284
317
|
}
|
|
285
318
|
async request(method, path, body, opts = {}) {
|
|
286
319
|
return this.withRequestBudget(opts.signal, async (signal) => {
|
|
287
|
-
const
|
|
320
|
+
const cacheKey = method === "GET" && this.etagCache ? `${this.baseUrl}${path}` : null;
|
|
321
|
+
const cached = cacheKey ? await this.etagCache?.get(cacheKey) : undefined;
|
|
322
|
+
const response = await this.fetchResponse(method, path, body, signal, cached ? { "If-None-Match": cached.etag } : undefined);
|
|
288
323
|
if (response.status === 204) {
|
|
289
324
|
return;
|
|
290
325
|
}
|
|
291
|
-
|
|
326
|
+
if (response.status === 304) {
|
|
327
|
+
if (!cached) {
|
|
328
|
+
throw new ApiError(304, "Not Modified without a cached copy");
|
|
329
|
+
}
|
|
330
|
+
return JSON.parse(cached.body);
|
|
331
|
+
}
|
|
332
|
+
const etag = cacheKey ? response.headers.get("ETag") : null;
|
|
333
|
+
if (!cacheKey || !etag) {
|
|
334
|
+
return await raceAbort(response.json(), signal);
|
|
335
|
+
}
|
|
336
|
+
const text = await raceAbort(response.text(), signal);
|
|
337
|
+
await this.etagCache?.set(cacheKey, { etag, body: text });
|
|
338
|
+
return JSON.parse(text);
|
|
292
339
|
});
|
|
293
340
|
}
|
|
294
341
|
async requestOrNull(method, path, body, opts = {}) {
|
|
@@ -331,9 +378,12 @@ class BaseClient {
|
|
|
331
378
|
callerSignal?.removeEventListener("abort", forward);
|
|
332
379
|
}
|
|
333
380
|
}
|
|
334
|
-
async fetchResponse(method, path, body, signal) {
|
|
381
|
+
async fetchResponse(method, path, body, signal, extraHeaders) {
|
|
335
382
|
const url = `${this.baseUrl}${path}`;
|
|
336
|
-
const headers =
|
|
383
|
+
const headers = {
|
|
384
|
+
...BaseClient.authHeaders(this.apiKey),
|
|
385
|
+
...extraHeaders
|
|
386
|
+
};
|
|
337
387
|
headers["x-sl-origin"] = this.origin;
|
|
338
388
|
let serializedBody;
|
|
339
389
|
if (body !== undefined && body !== null) {
|
|
@@ -358,6 +408,8 @@ class BaseClient {
|
|
|
358
408
|
throw err;
|
|
359
409
|
throw new ApiError(0, `Cannot reach API at ${this.baseUrl}. Check your connection or try again.`, undefined, undefined, { retryable: true, cause: err instanceof Error ? err : undefined });
|
|
360
410
|
}
|
|
411
|
+
if (response.status === 304)
|
|
412
|
+
return response;
|
|
361
413
|
if (!response.ok) {
|
|
362
414
|
const errorBody = signal ? await raceAbort(response.text(), signal) : await response.text();
|
|
363
415
|
const envelope = parseErrorEnvelope(errorBody);
|
|
@@ -379,604 +431,109 @@ class BaseClient {
|
|
|
379
431
|
}
|
|
380
432
|
}
|
|
381
433
|
|
|
382
|
-
// src/
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
434
|
+
// src/archive/client.ts
|
|
435
|
+
var OFFICIAL_ARCHIVE_HOST = "archive.secondlayer.tools";
|
|
436
|
+
var DEFAULT_ARCHIVE_BASE_URL = "https://archive.secondlayer.tools";
|
|
437
|
+
var DEFAULT_ARCHIVE_OPS_URL = "https://api.secondlayer.tools";
|
|
438
|
+
var MAX_FETCH_BATCH = 64;
|
|
439
|
+
var ARCHIVE_CREDENTIAL_RE = /^s[ks]-sl_/;
|
|
440
|
+
function quoteFromBody(body) {
|
|
441
|
+
return {
|
|
442
|
+
partitions: body.partitions,
|
|
443
|
+
bundles: body.bundles,
|
|
444
|
+
usdMicros: body.usd_micros,
|
|
445
|
+
usd: body.usd,
|
|
446
|
+
freeAllowanceAppliedMicros: body.free_allowance_applied_micros,
|
|
447
|
+
allowanceRemainingBundles: body.allowance_remaining_bundles,
|
|
448
|
+
balanceUsdMicros: body.balance_usd_micros,
|
|
449
|
+
sufficient: body.sufficient
|
|
450
|
+
};
|
|
391
451
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
constructor(message = "Streams response signature verification failed.") {
|
|
395
|
-
super(message);
|
|
396
|
-
this.name = "StreamsSignatureError";
|
|
397
|
-
}
|
|
452
|
+
function stripSlash(url) {
|
|
453
|
+
return url.replace(/\/+$/, "");
|
|
398
454
|
}
|
|
399
|
-
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
455
|
+
function createArchiveClient(options = {}) {
|
|
456
|
+
const accountKey = resolveAccountKey(options.accountKey ?? options.apiKey);
|
|
457
|
+
const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
458
|
+
const archiveBaseUrl = stripSlash(options.archiveBaseUrl ?? DEFAULT_ARCHIVE_BASE_URL);
|
|
459
|
+
const archiveOpsUrl = stripSlash(options.archiveOpsUrl ?? DEFAULT_ARCHIVE_OPS_URL);
|
|
460
|
+
const verifyManifest = options.verifyManifest ?? true;
|
|
461
|
+
const pinnedPem = options.publicKeyPem;
|
|
462
|
+
let keyPromise = null;
|
|
463
|
+
async function fetchSigningKey() {
|
|
464
|
+
if (pinnedPem)
|
|
465
|
+
return pinnedPem;
|
|
466
|
+
if (keyPromise)
|
|
467
|
+
return keyPromise;
|
|
468
|
+
const pending = (async () => {
|
|
469
|
+
const res = await fetchImpl(`${archiveOpsUrl}/public/streams/signing-key`);
|
|
470
|
+
if (!res.ok) {
|
|
471
|
+
throw new ArchiveSignatureError(`Could not fetch archive signing key (${res.status}).`);
|
|
472
|
+
}
|
|
473
|
+
const body = await res.json();
|
|
474
|
+
if (typeof body.public_key_pem !== "string") {
|
|
475
|
+
throw new ArchiveSignatureError("Signing key response missing key.");
|
|
476
|
+
}
|
|
477
|
+
return body.public_key_pem;
|
|
478
|
+
})();
|
|
479
|
+
keyPromise = pending;
|
|
480
|
+
pending.catch(() => {
|
|
481
|
+
if (keyPromise === pending)
|
|
482
|
+
keyPromise = null;
|
|
483
|
+
});
|
|
484
|
+
return pending;
|
|
410
485
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (err instanceof StreamsSignatureError)
|
|
425
|
-
return true;
|
|
426
|
-
return err?.retryable === false;
|
|
427
|
-
}
|
|
428
|
-
function subscribeStreamsEvents(opts) {
|
|
429
|
-
const { params } = opts;
|
|
430
|
-
const controller = new AbortController;
|
|
431
|
-
const external = params.signal;
|
|
432
|
-
if (external) {
|
|
433
|
-
if (external.aborted)
|
|
434
|
-
controller.abort();
|
|
435
|
-
else
|
|
436
|
-
external.addEventListener("abort", () => controller.abort(), {
|
|
437
|
-
once: true
|
|
438
|
-
});
|
|
486
|
+
async function throwOpsError(response) {
|
|
487
|
+
const envelope = parseErrorEnvelope(await response.text());
|
|
488
|
+
if (response.status === 401) {
|
|
489
|
+
throw new ArchiveAuthError(envelope.message ?? "Archive ops request was not authenticated.");
|
|
490
|
+
}
|
|
491
|
+
if (response.status === 402) {
|
|
492
|
+
const raw = envelope.body && typeof envelope.body === "object" && "shortfall_usd_micros" in envelope.body ? Number(envelope.body.shortfall_usd_micros) : undefined;
|
|
493
|
+
throw new InsufficientArchiveCreditsError(envelope.message ?? "Insufficient archive credits.", Number.isFinite(raw) ? raw : undefined);
|
|
494
|
+
}
|
|
495
|
+
if (response.status === 503) {
|
|
496
|
+
throw new ArchiveGateNotConfiguredError(envelope.message ?? "The archive gate is not configured on the server.");
|
|
497
|
+
}
|
|
498
|
+
throw new ApiError(response.status, envelope.message ?? `Archive ops returned ${response.status}.`, envelope.body, envelope.code);
|
|
439
499
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const url = `${opts.baseUrl}/v1/streams/events/stream${buildQuery({
|
|
448
|
-
from_cursor: cursor ?? undefined,
|
|
449
|
-
types: params.types,
|
|
450
|
-
not_types: params.notTypes,
|
|
451
|
-
contract_id: params.contractId,
|
|
452
|
-
sender: params.sender,
|
|
453
|
-
recipient: params.recipient,
|
|
454
|
-
asset_identifier: params.assetIdentifier,
|
|
455
|
-
filters: params.filters ? JSON.stringify(params.filters) : undefined
|
|
456
|
-
})}`;
|
|
457
|
-
await readSse({
|
|
458
|
-
url,
|
|
459
|
-
headers: opts.headers ?? {},
|
|
460
|
-
signal: controller.signal,
|
|
461
|
-
fetchImpl: opts.fetchImpl,
|
|
462
|
-
staleAfterMs,
|
|
463
|
-
mapError: mapStreamsError,
|
|
464
|
-
onFrame: async (frame) => {
|
|
465
|
-
attempt = 0;
|
|
466
|
-
if (frame.event === "ping" || !frame.data)
|
|
467
|
-
return;
|
|
468
|
-
let parsed;
|
|
469
|
-
try {
|
|
470
|
-
parsed = JSON.parse(frame.data);
|
|
471
|
-
} catch {
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
if (!parsed.event)
|
|
475
|
-
return;
|
|
476
|
-
if (opts.verify !== "off") {
|
|
477
|
-
if (!parsed.sig) {
|
|
478
|
-
if (opts.verify === "strict") {
|
|
479
|
-
throw new StreamsSignatureError("Streams SSE frame signature is missing.");
|
|
480
|
-
}
|
|
481
|
-
} else {
|
|
482
|
-
const key = await opts.loadKey(parsed.key_id);
|
|
483
|
-
if (!ed25519.verifyEd25519(JSON.stringify(parsed.event), parsed.sig, key.publicKey)) {
|
|
484
|
-
throw new StreamsSignatureError("Streams SSE frame signature is invalid.");
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
await params.onEvent(parsed.event);
|
|
489
|
-
cursor = parsed.event.cursor ?? cursor;
|
|
490
|
-
}
|
|
491
|
-
});
|
|
492
|
-
await sleep(reconnectDelay(attempt, baseDelayMs, undefined, opts.random), controller.signal);
|
|
493
|
-
attempt += 1;
|
|
494
|
-
} catch (err) {
|
|
495
|
-
if (controller.signal.aborted)
|
|
496
|
-
return;
|
|
497
|
-
params.onError?.(err);
|
|
498
|
-
if (isTerminal(err))
|
|
499
|
-
throw err;
|
|
500
|
-
const retryAfterSeconds = err.retryAfterSeconds;
|
|
501
|
-
await sleep(reconnectDelay(attempt, baseDelayMs, retryAfterSeconds, opts.random), controller.signal);
|
|
502
|
-
attempt += 1;
|
|
500
|
+
async function opsRequest(method, path, body, authed = true) {
|
|
501
|
+
const headers = {
|
|
502
|
+
"Content-Type": "application/json"
|
|
503
|
+
};
|
|
504
|
+
if (authed && accountKey) {
|
|
505
|
+
if (!ARCHIVE_CREDENTIAL_RE.test(accountKey)) {
|
|
506
|
+
throw new ArchiveAuthError("archive ops need SECONDLAYER_API_KEY (sk-sl_*), not INSTANCE_TOKEN");
|
|
503
507
|
}
|
|
508
|
+
headers.Authorization = `Bearer ${accountKey}`;
|
|
504
509
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
headers: { ...opts.headers ?? {}, Accept: "text/event-stream" },
|
|
514
|
-
signal: opts.signal
|
|
515
|
-
});
|
|
516
|
-
if (!res.ok) {
|
|
517
|
-
if (opts.mapError)
|
|
518
|
-
await opts.mapError(res);
|
|
519
|
-
throw new StreamsServerError(`Streams SSE returned ${res.status}.`, res.status);
|
|
510
|
+
const response = await fetchImpl(`${archiveOpsUrl}${path}`, {
|
|
511
|
+
method,
|
|
512
|
+
headers,
|
|
513
|
+
body: body !== undefined ? JSON.stringify(body) : undefined
|
|
514
|
+
});
|
|
515
|
+
if (!response.ok)
|
|
516
|
+
await throwOpsError(response);
|
|
517
|
+
return await response.json();
|
|
520
518
|
}
|
|
521
|
-
|
|
522
|
-
|
|
519
|
+
async function load(against, opts = {}) {
|
|
520
|
+
if (!/^https?:\/\//i.test(against)) {
|
|
521
|
+
throw new ValidationError("archive.load() accepts only http(s) URLs; local paths stay on the CLI.", 400);
|
|
522
|
+
}
|
|
523
|
+
const insecure = opts.insecure === true;
|
|
524
|
+
const publicKeyPem = pinnedPem ?? (verifyManifest && !insecure ? await fetchSigningKey() : undefined);
|
|
525
|
+
const ref = await loadReference(against, {
|
|
526
|
+
publicKeyPem,
|
|
527
|
+
fetchImpl
|
|
528
|
+
});
|
|
529
|
+
const signature = checkSignature(ref.manifest, publicKeyPem, insecure);
|
|
530
|
+
if (verifyManifest && !insecure && !signature.verified) {
|
|
531
|
+
throw new ArchiveSignatureError(signature.reason ?? "Archive manifest signature is missing or invalid.");
|
|
532
|
+
}
|
|
533
|
+
return { ...ref, signature };
|
|
523
534
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
function sleep(ms, signal) {
|
|
529
|
-
return new Promise((resolve) => {
|
|
530
|
-
if (signal.aborted)
|
|
531
|
-
return resolve();
|
|
532
|
-
const onAbort = () => {
|
|
533
|
-
clearTimeout(timer);
|
|
534
|
-
resolve();
|
|
535
|
-
};
|
|
536
|
-
const timer = setTimeout(() => {
|
|
537
|
-
signal.removeEventListener("abort", onAbort);
|
|
538
|
-
resolve();
|
|
539
|
-
}, ms);
|
|
540
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
async function readWithStaleTimer(reader, staleAfterMs) {
|
|
544
|
-
if (staleAfterMs === undefined || !Number.isFinite(staleAfterMs)) {
|
|
545
|
-
return reader.read();
|
|
546
|
-
}
|
|
547
|
-
let timer;
|
|
548
|
-
const stale = new Promise((_, reject) => {
|
|
549
|
-
timer = setTimeout(() => reject(new StreamsServerError(`Streams SSE went quiet for ${staleAfterMs} ms; reconnecting.`, 0, undefined, "STREAM_STALE")), staleAfterMs);
|
|
550
|
-
});
|
|
551
|
-
try {
|
|
552
|
-
return await Promise.race([reader.read(), stale]);
|
|
553
|
-
} finally {
|
|
554
|
-
clearTimeout(timer);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
async function* parseSseFrames(body, signal, staleAfterMs) {
|
|
558
|
-
const reader = body.getReader();
|
|
559
|
-
const decoder = new TextDecoder;
|
|
560
|
-
let buffer = "";
|
|
561
|
-
try {
|
|
562
|
-
while (!signal.aborted) {
|
|
563
|
-
const { value, done } = await readWithStaleTimer(reader, staleAfterMs);
|
|
564
|
-
if (done)
|
|
565
|
-
break;
|
|
566
|
-
buffer += decoder.decode(value, { stream: true });
|
|
567
|
-
let sep = buffer.indexOf(`
|
|
568
|
-
|
|
569
|
-
`);
|
|
570
|
-
while (sep !== -1) {
|
|
571
|
-
yield parseFrame(buffer.slice(0, sep));
|
|
572
|
-
buffer = buffer.slice(sep + 2);
|
|
573
|
-
sep = buffer.indexOf(`
|
|
574
|
-
|
|
575
|
-
`);
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
} finally {
|
|
579
|
-
try {
|
|
580
|
-
await reader.cancel();
|
|
581
|
-
} catch {}
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
function parseFrame(raw) {
|
|
585
|
-
let event;
|
|
586
|
-
const data = [];
|
|
587
|
-
for (const line of raw.split(`
|
|
588
|
-
`)) {
|
|
589
|
-
if (line.startsWith("data:")) {
|
|
590
|
-
data.push(line.slice(line.startsWith("data: ") ? 6 : 5));
|
|
591
|
-
} else if (line.startsWith("event:")) {
|
|
592
|
-
event = line.slice(line.startsWith("event: ") ? 7 : 6).trim();
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
return { event, data: data.length > 0 ? data.join(`
|
|
596
|
-
`) : undefined };
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// src/subgraphs/serialize.ts
|
|
600
|
-
var SYSTEM_COLUMN_MAP = {
|
|
601
|
-
_blockHeight: "_block_height",
|
|
602
|
-
_txId: "_tx_id",
|
|
603
|
-
_createdAt: "_created_at",
|
|
604
|
-
_id: "_id"
|
|
605
|
-
};
|
|
606
|
-
var SYSTEM_COLUMN_ALIASES = {
|
|
607
|
-
blockHeight: "_block_height",
|
|
608
|
-
txId: "_tx_id",
|
|
609
|
-
createdAt: "_created_at",
|
|
610
|
-
id: "_id"
|
|
611
|
-
};
|
|
612
|
-
function resolveColumn(col, columns) {
|
|
613
|
-
const canonical = SYSTEM_COLUMN_MAP[col];
|
|
614
|
-
if (canonical)
|
|
615
|
-
return canonical;
|
|
616
|
-
if (columns?.has(col))
|
|
617
|
-
return col;
|
|
618
|
-
return SYSTEM_COLUMN_ALIASES[col] ?? col;
|
|
619
|
-
}
|
|
620
|
-
function serializeWhere(where, columns) {
|
|
621
|
-
const filters = {};
|
|
622
|
-
for (const [column, value] of Object.entries(where)) {
|
|
623
|
-
if (value === null || value === undefined)
|
|
624
|
-
continue;
|
|
625
|
-
const col = resolveColumn(column, columns);
|
|
626
|
-
if (typeof value === "object" && !Array.isArray(value)) {
|
|
627
|
-
const ops = value;
|
|
628
|
-
for (const [op, opValue] of Object.entries(ops)) {
|
|
629
|
-
if (opValue === null || opValue === undefined)
|
|
630
|
-
continue;
|
|
631
|
-
if (op === "eq") {
|
|
632
|
-
filters[col] = String(opValue);
|
|
633
|
-
} else if (op === "in" || op === "notIn") {
|
|
634
|
-
const arr = Array.isArray(opValue) ? opValue : [opValue];
|
|
635
|
-
filters[`${col}.${op}`] = arr.map((v) => String(v)).join(",");
|
|
636
|
-
} else if (["neq", "gt", "gte", "lt", "lte", "like"].includes(op)) {
|
|
637
|
-
filters[`${col}.${op}`] = String(opValue);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
} else {
|
|
641
|
-
filters[col] = String(value);
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
return filters;
|
|
645
|
-
}
|
|
646
|
-
function resolveOrderByColumn(col, columns) {
|
|
647
|
-
return resolveColumn(col, columns);
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
// src/subgraphs/client.ts
|
|
651
|
-
var SUBSCRIBE_RECONNECT_DELAY_MS = 1000;
|
|
652
|
-
function declaredColumns(table) {
|
|
653
|
-
if (!table || typeof table !== "object")
|
|
654
|
-
return;
|
|
655
|
-
const columns = table.columns;
|
|
656
|
-
if (!columns || typeof columns !== "object")
|
|
657
|
-
return;
|
|
658
|
-
return new Set(Object.keys(columns));
|
|
659
|
-
}
|
|
660
|
-
function rowBlockHeight(row) {
|
|
661
|
-
const r = row;
|
|
662
|
-
const raw = r._block_height ?? r._blockHeight;
|
|
663
|
-
if (typeof raw !== "number" && typeof raw !== "string")
|
|
664
|
-
return;
|
|
665
|
-
const height = Number(raw);
|
|
666
|
-
return Number.isFinite(height) ? height : undefined;
|
|
667
|
-
}
|
|
668
|
-
function sleep2(ms, signal) {
|
|
669
|
-
return new Promise((resolve) => {
|
|
670
|
-
if (signal.aborted)
|
|
671
|
-
return resolve();
|
|
672
|
-
const onAbort = () => {
|
|
673
|
-
clearTimeout(timer);
|
|
674
|
-
resolve();
|
|
675
|
-
};
|
|
676
|
-
const timer = setTimeout(() => {
|
|
677
|
-
signal.removeEventListener("abort", onAbort);
|
|
678
|
-
resolve();
|
|
679
|
-
}, ms);
|
|
680
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
function buildSubgraphQueryString(params) {
|
|
684
|
-
return buildQuery({
|
|
685
|
-
_sort: params.sort,
|
|
686
|
-
_order: params.order,
|
|
687
|
-
_limit: params.limit,
|
|
688
|
-
_offset: params.offset,
|
|
689
|
-
_fields: params.fields,
|
|
690
|
-
...params.filters
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
function buildAggregateQueryString(params) {
|
|
694
|
-
return buildQuery({
|
|
695
|
-
...params.filters,
|
|
696
|
-
_count: params.count ? "true" : undefined,
|
|
697
|
-
_countDistinct: params.countDistinct,
|
|
698
|
-
_sum: params.sum,
|
|
699
|
-
_min: params.min,
|
|
700
|
-
_max: params.max
|
|
701
|
-
});
|
|
702
|
-
}
|
|
703
|
-
function buildSpecQueryString(options) {
|
|
704
|
-
return buildQuery({ server: options?.serverUrl });
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
class Subgraphs extends BaseClient {
|
|
708
|
-
async list() {
|
|
709
|
-
return this.request("GET", "/api/subgraphs");
|
|
710
|
-
}
|
|
711
|
-
async status(name) {
|
|
712
|
-
return this.request("GET", `/api/subgraphs/${seg(name)}`);
|
|
713
|
-
}
|
|
714
|
-
async openapi(name, options) {
|
|
715
|
-
return this.request("GET", `/api/subgraphs/${seg(name)}/openapi.json${buildSpecQueryString(options)}`);
|
|
716
|
-
}
|
|
717
|
-
async schema(name, options) {
|
|
718
|
-
return this.request("GET", `/api/subgraphs/${seg(name)}/schema.json${buildSpecQueryString(options)}`);
|
|
719
|
-
}
|
|
720
|
-
async markdown(name, options) {
|
|
721
|
-
return this.requestText("GET", `/api/subgraphs/${seg(name)}/docs.md${buildSpecQueryString(options)}`);
|
|
722
|
-
}
|
|
723
|
-
async reindex(name) {
|
|
724
|
-
return this.request("POST", `/api/subgraphs/${seg(name)}/reindex`);
|
|
725
|
-
}
|
|
726
|
-
async stop(name) {
|
|
727
|
-
return this.request("POST", `/api/subgraphs/${seg(name)}/stop`);
|
|
728
|
-
}
|
|
729
|
-
async backfill(name, options) {
|
|
730
|
-
return this.request("POST", `/api/subgraphs/${seg(name)}/backfill`, options);
|
|
731
|
-
}
|
|
732
|
-
async gaps(name, opts) {
|
|
733
|
-
const qs = buildQuery({
|
|
734
|
-
_limit: opts?.limit,
|
|
735
|
-
_offset: opts?.offset,
|
|
736
|
-
resolved: opts?.resolved
|
|
737
|
-
});
|
|
738
|
-
return this.request("GET", `/api/subgraphs/${seg(name)}/gaps${qs}`);
|
|
739
|
-
}
|
|
740
|
-
async delete(name, options) {
|
|
741
|
-
const qs = buildQuery({ force: options?.force ? true : undefined });
|
|
742
|
-
return this.request("DELETE", `/api/subgraphs/${seg(name)}${qs}`);
|
|
743
|
-
}
|
|
744
|
-
async rows(name, table, params = {}) {
|
|
745
|
-
const { cursor, ...rest } = params;
|
|
746
|
-
const qs = buildSubgraphQueryString(rest);
|
|
747
|
-
const sep = qs ? "&" : "?";
|
|
748
|
-
const cursorQs = cursor ? `${sep}cursor=${encodeURIComponent(cursor)}` : "";
|
|
749
|
-
return this.request("GET", `/v1/subgraphs/${seg(name)}/${seg(table)}${qs}${cursorQs}`);
|
|
750
|
-
}
|
|
751
|
-
async operations(name) {
|
|
752
|
-
return this.request("GET", `/api/subgraphs/${seg(name)}/operations`);
|
|
753
|
-
}
|
|
754
|
-
async getOperation(name, operationId) {
|
|
755
|
-
return this.request("GET", `/api/subgraphs/${seg(name)}/operations/${seg(operationId)}`);
|
|
756
|
-
}
|
|
757
|
-
async deploy(data) {
|
|
758
|
-
return this.request("POST", "/api/subgraphs", data);
|
|
759
|
-
}
|
|
760
|
-
async getSource(name) {
|
|
761
|
-
return this.request("GET", `/api/subgraphs/${seg(name)}/source`);
|
|
762
|
-
}
|
|
763
|
-
async bundle(data) {
|
|
764
|
-
return this.request("POST", "/api/subgraphs/bundle", data);
|
|
765
|
-
}
|
|
766
|
-
async queryTable(name, table, params = {}) {
|
|
767
|
-
const result = await this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}${buildSubgraphQueryString(params)}`);
|
|
768
|
-
return Array.isArray(result) ? result : result.data;
|
|
769
|
-
}
|
|
770
|
-
async queryTableCount(name, table, params = {}) {
|
|
771
|
-
return this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}/count${buildSubgraphQueryString(params)}`);
|
|
772
|
-
}
|
|
773
|
-
async queryTableAggregate(name, table, params = {}) {
|
|
774
|
-
return this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}/aggregate${buildAggregateQueryString(params)}`);
|
|
775
|
-
}
|
|
776
|
-
typed(def) {
|
|
777
|
-
const result = {};
|
|
778
|
-
for (const [tableName, table] of Object.entries(def.schema)) {
|
|
779
|
-
result[tableName] = this.createTableClient(def.name, tableName, declaredColumns(table));
|
|
780
|
-
}
|
|
781
|
-
return result;
|
|
782
|
-
}
|
|
783
|
-
createTableClient(subgraphName, tableName, columns) {
|
|
784
|
-
const self = this;
|
|
785
|
-
return {
|
|
786
|
-
async findMany(options = {}) {
|
|
787
|
-
const filters = options.where ? serializeWhere(options.where, columns) : undefined;
|
|
788
|
-
let sort;
|
|
789
|
-
let order;
|
|
790
|
-
if (options.orderBy) {
|
|
791
|
-
const entries = Array.isArray(options.orderBy) ? options.orderBy : Object.entries(options.orderBy);
|
|
792
|
-
if (entries.length > 0) {
|
|
793
|
-
sort = entries.map(([col]) => resolveOrderByColumn(col, columns)).join(",");
|
|
794
|
-
order = entries.map(([, dir]) => dir ?? "asc").join(",");
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
const params = {
|
|
798
|
-
sort,
|
|
799
|
-
order,
|
|
800
|
-
limit: options.limit,
|
|
801
|
-
offset: options.offset,
|
|
802
|
-
fields: options.fields?.join(","),
|
|
803
|
-
filters
|
|
804
|
-
};
|
|
805
|
-
return self.queryTable(subgraphName, tableName, params);
|
|
806
|
-
},
|
|
807
|
-
async count(where) {
|
|
808
|
-
const filters = where ? serializeWhere(where, columns) : undefined;
|
|
809
|
-
const result = await self.queryTableCount(subgraphName, tableName, {
|
|
810
|
-
filters
|
|
811
|
-
});
|
|
812
|
-
return result.count;
|
|
813
|
-
},
|
|
814
|
-
async aggregate(spec) {
|
|
815
|
-
const filters = spec.where ? serializeWhere(spec.where, columns) : undefined;
|
|
816
|
-
const result = await self.queryTableAggregate(subgraphName, tableName, {
|
|
817
|
-
filters,
|
|
818
|
-
count: spec.count,
|
|
819
|
-
countDistinct: spec.countDistinct,
|
|
820
|
-
sum: spec.sum,
|
|
821
|
-
min: spec.min,
|
|
822
|
-
max: spec.max
|
|
823
|
-
});
|
|
824
|
-
return result;
|
|
825
|
-
},
|
|
826
|
-
subscribe(onRow, options = {}) {
|
|
827
|
-
const filters = options.where ? serializeWhere(options.where, columns) : {};
|
|
828
|
-
const controller = new AbortController;
|
|
829
|
-
let since = options.since ?? undefined;
|
|
830
|
-
const run = async () => {
|
|
831
|
-
while (!controller.signal.aborted) {
|
|
832
|
-
try {
|
|
833
|
-
const qs = buildQuery({ ...filters, since });
|
|
834
|
-
await readSse({
|
|
835
|
-
url: `${self.baseUrl}/v1/subgraphs/${seg(subgraphName)}/${seg(tableName)}/stream${qs}`,
|
|
836
|
-
headers: {
|
|
837
|
-
...BaseClient.authHeaders(self.apiKey),
|
|
838
|
-
"x-sl-origin": self.origin
|
|
839
|
-
},
|
|
840
|
-
signal: controller.signal,
|
|
841
|
-
fetchImpl: self.fetchImpl,
|
|
842
|
-
onFrame: (frame) => {
|
|
843
|
-
if (frame.event === "ping" || !frame.data)
|
|
844
|
-
return;
|
|
845
|
-
let row;
|
|
846
|
-
try {
|
|
847
|
-
row = JSON.parse(frame.data);
|
|
848
|
-
} catch {
|
|
849
|
-
return;
|
|
850
|
-
}
|
|
851
|
-
const height = rowBlockHeight(row);
|
|
852
|
-
if (height !== undefined)
|
|
853
|
-
since = height;
|
|
854
|
-
onRow(row);
|
|
855
|
-
}
|
|
856
|
-
});
|
|
857
|
-
await sleep2(SUBSCRIBE_RECONNECT_DELAY_MS, controller.signal);
|
|
858
|
-
} catch (err) {
|
|
859
|
-
if (controller.signal.aborted)
|
|
860
|
-
return;
|
|
861
|
-
options.onError?.(err);
|
|
862
|
-
await sleep2(SUBSCRIBE_RECONNECT_DELAY_MS, controller.signal);
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
};
|
|
866
|
-
run();
|
|
867
|
-
return () => controller.abort();
|
|
868
|
-
}
|
|
869
|
-
};
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
// src/archive/client.ts
|
|
873
|
-
import { createHash } from "node:crypto";
|
|
874
|
-
import {
|
|
875
|
-
checkSignature,
|
|
876
|
-
loadReference
|
|
877
|
-
} from "@secondlayer/shared/archive/reference";
|
|
878
|
-
var OFFICIAL_ARCHIVE_HOST = "archive.secondlayer.tools";
|
|
879
|
-
var DEFAULT_ARCHIVE_BASE_URL = "https://archive.secondlayer.tools";
|
|
880
|
-
var DEFAULT_ARCHIVE_OPS_URL = "https://api.secondlayer.tools";
|
|
881
|
-
var MAX_FETCH_BATCH = 64;
|
|
882
|
-
var ARCHIVE_CREDENTIAL_RE = /^s[ks]-sl_/;
|
|
883
|
-
function quoteFromBody(body) {
|
|
884
|
-
return {
|
|
885
|
-
partitions: body.partitions,
|
|
886
|
-
bundles: body.bundles,
|
|
887
|
-
usdMicros: body.usd_micros,
|
|
888
|
-
usd: body.usd,
|
|
889
|
-
freeAllowanceAppliedMicros: body.free_allowance_applied_micros,
|
|
890
|
-
allowanceRemainingBundles: body.allowance_remaining_bundles,
|
|
891
|
-
balanceUsdMicros: body.balance_usd_micros,
|
|
892
|
-
sufficient: body.sufficient
|
|
893
|
-
};
|
|
894
|
-
}
|
|
895
|
-
function stripSlash(url) {
|
|
896
|
-
return url.replace(/\/+$/, "");
|
|
897
|
-
}
|
|
898
|
-
function createArchiveClient(options = {}) {
|
|
899
|
-
const accountKey = resolveAccountKey(options.accountKey ?? options.apiKey);
|
|
900
|
-
const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
901
|
-
const archiveBaseUrl = stripSlash(options.archiveBaseUrl ?? DEFAULT_ARCHIVE_BASE_URL);
|
|
902
|
-
const archiveOpsUrl = stripSlash(options.archiveOpsUrl ?? DEFAULT_ARCHIVE_OPS_URL);
|
|
903
|
-
const verifyManifest = options.verifyManifest ?? true;
|
|
904
|
-
const pinnedPem = options.publicKeyPem;
|
|
905
|
-
let keyPromise = null;
|
|
906
|
-
async function fetchSigningKey() {
|
|
907
|
-
if (pinnedPem)
|
|
908
|
-
return pinnedPem;
|
|
909
|
-
if (keyPromise)
|
|
910
|
-
return keyPromise;
|
|
911
|
-
const pending = (async () => {
|
|
912
|
-
const res = await fetchImpl(`${archiveOpsUrl}/public/streams/signing-key`);
|
|
913
|
-
if (!res.ok) {
|
|
914
|
-
throw new ArchiveSignatureError(`Could not fetch archive signing key (${res.status}).`);
|
|
915
|
-
}
|
|
916
|
-
const body = await res.json();
|
|
917
|
-
if (typeof body.public_key_pem !== "string") {
|
|
918
|
-
throw new ArchiveSignatureError("Signing key response missing key.");
|
|
919
|
-
}
|
|
920
|
-
return body.public_key_pem;
|
|
921
|
-
})();
|
|
922
|
-
keyPromise = pending;
|
|
923
|
-
pending.catch(() => {
|
|
924
|
-
if (keyPromise === pending)
|
|
925
|
-
keyPromise = null;
|
|
926
|
-
});
|
|
927
|
-
return pending;
|
|
928
|
-
}
|
|
929
|
-
async function throwOpsError(response) {
|
|
930
|
-
const envelope = parseErrorEnvelope(await response.text());
|
|
931
|
-
if (response.status === 401) {
|
|
932
|
-
throw new ArchiveAuthError(envelope.message ?? "Archive ops request was not authenticated.");
|
|
933
|
-
}
|
|
934
|
-
if (response.status === 402) {
|
|
935
|
-
const raw = envelope.body && typeof envelope.body === "object" && "shortfall_usd_micros" in envelope.body ? Number(envelope.body.shortfall_usd_micros) : undefined;
|
|
936
|
-
throw new InsufficientArchiveCreditsError(envelope.message ?? "Insufficient archive credits.", Number.isFinite(raw) ? raw : undefined);
|
|
937
|
-
}
|
|
938
|
-
if (response.status === 503) {
|
|
939
|
-
throw new ArchiveGateNotConfiguredError(envelope.message ?? "The archive gate is not configured on the server.");
|
|
940
|
-
}
|
|
941
|
-
throw new ApiError(response.status, envelope.message ?? `Archive ops returned ${response.status}.`, envelope.body, envelope.code);
|
|
942
|
-
}
|
|
943
|
-
async function opsRequest(method, path, body, authed = true) {
|
|
944
|
-
const headers = {
|
|
945
|
-
"Content-Type": "application/json"
|
|
946
|
-
};
|
|
947
|
-
if (authed && accountKey) {
|
|
948
|
-
if (!ARCHIVE_CREDENTIAL_RE.test(accountKey)) {
|
|
949
|
-
throw new ArchiveAuthError("archive ops need SECONDLAYER_API_KEY (sk-sl_*), not INSTANCE_TOKEN");
|
|
950
|
-
}
|
|
951
|
-
headers.Authorization = `Bearer ${accountKey}`;
|
|
952
|
-
}
|
|
953
|
-
const response = await fetchImpl(`${archiveOpsUrl}${path}`, {
|
|
954
|
-
method,
|
|
955
|
-
headers,
|
|
956
|
-
body: body !== undefined ? JSON.stringify(body) : undefined
|
|
957
|
-
});
|
|
958
|
-
if (!response.ok)
|
|
959
|
-
await throwOpsError(response);
|
|
960
|
-
return await response.json();
|
|
961
|
-
}
|
|
962
|
-
async function load(against, opts = {}) {
|
|
963
|
-
if (!/^https?:\/\//i.test(against)) {
|
|
964
|
-
throw new ValidationError("archive.load() accepts only http(s) URLs; local paths stay on the CLI.", 400);
|
|
965
|
-
}
|
|
966
|
-
const insecure = opts.insecure === true;
|
|
967
|
-
const publicKeyPem = pinnedPem ?? (verifyManifest && !insecure ? await fetchSigningKey() : undefined);
|
|
968
|
-
const ref = await loadReference(against, {
|
|
969
|
-
publicKeyPem,
|
|
970
|
-
fetchImpl
|
|
971
|
-
});
|
|
972
|
-
const signature = checkSignature(ref.manifest, publicKeyPem, insecure);
|
|
973
|
-
if (verifyManifest && !insecure && !signature.verified) {
|
|
974
|
-
throw new ArchiveSignatureError(signature.reason ?? "Archive manifest signature is missing or invalid.");
|
|
975
|
-
}
|
|
976
|
-
return { ...ref, signature };
|
|
977
|
-
}
|
|
978
|
-
async function latest(against, opts = {}) {
|
|
979
|
-
return load(against ?? `${archiveBaseUrl}/latest.json`, opts);
|
|
535
|
+
async function latest(against, opts = {}) {
|
|
536
|
+
return load(against ?? `${archiveBaseUrl}/latest.json`, opts);
|
|
980
537
|
}
|
|
981
538
|
async function status() {
|
|
982
539
|
const response = await fetchImpl(`${archiveBaseUrl}/status.json`);
|
|
@@ -1133,6 +690,37 @@ import {
|
|
|
1133
690
|
decodeStreamsCursor,
|
|
1134
691
|
encodeStreamsCursor
|
|
1135
692
|
} from "@secondlayer/shared";
|
|
693
|
+
|
|
694
|
+
// src/streams/errors.ts
|
|
695
|
+
class StreamsServerError extends ApiError {
|
|
696
|
+
constructor(message, status, body, code) {
|
|
697
|
+
super(status, message, body, code, { retryable: true });
|
|
698
|
+
this.name = "StreamsServerError";
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
class StreamsSignatureError extends SecondLayerError {
|
|
703
|
+
constructor(message = "Streams response signature verification failed.") {
|
|
704
|
+
super(message);
|
|
705
|
+
this.name = "StreamsSignatureError";
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
async function mapStreamsError(response) {
|
|
709
|
+
const { message, code, body } = parseErrorEnvelope(await response.text());
|
|
710
|
+
if (response.status === 401) {
|
|
711
|
+
throw new AuthError(message ?? "API key invalid or expired.", body, code);
|
|
712
|
+
}
|
|
713
|
+
if (response.status === 429) {
|
|
714
|
+
const retryAfter = response.headers.get("Retry-After") ?? undefined;
|
|
715
|
+
throw new RateLimitError(message ?? "Rate limited. Try again later.", retryAfter, body, code);
|
|
716
|
+
}
|
|
717
|
+
if (response.status >= 500) {
|
|
718
|
+
throw new StreamsServerError(message ?? `Streams server returned ${response.status}.`, response.status, body, code);
|
|
719
|
+
}
|
|
720
|
+
throw new ValidationError(message ?? `Streams request returned ${response.status}.`, response.status, body, code);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// src/streams/cursor.ts
|
|
1136
724
|
var Cursor = {
|
|
1137
725
|
atHeight(height) {
|
|
1138
726
|
if (height <= 0)
|
|
@@ -1280,7 +868,7 @@ async function defaultSleep(ms, signal) {
|
|
|
1280
868
|
});
|
|
1281
869
|
}
|
|
1282
870
|
async function consumeStreamsEvents(opts) {
|
|
1283
|
-
const
|
|
871
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
1284
872
|
const mode = opts.mode ?? "tail";
|
|
1285
873
|
const finalizedOnly = opts.finalizedOnly ?? false;
|
|
1286
874
|
const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
|
|
@@ -1310,7 +898,7 @@ async function consumeStreamsEvents(opts) {
|
|
|
1310
898
|
retryCount: opts.retryCount,
|
|
1311
899
|
retryDelay: opts.retryDelay,
|
|
1312
900
|
onError: opts.onError,
|
|
1313
|
-
sleep
|
|
901
|
+
sleep,
|
|
1314
902
|
signal: opts.signal
|
|
1315
903
|
});
|
|
1316
904
|
pages++;
|
|
@@ -1342,7 +930,7 @@ async function consumeStreamsEvents(opts) {
|
|
|
1342
930
|
retryCount: opts.retryCount,
|
|
1343
931
|
retryDelay: opts.retryDelay,
|
|
1344
932
|
onError: opts.onError,
|
|
1345
|
-
sleep
|
|
933
|
+
sleep,
|
|
1346
934
|
signal: opts.signal
|
|
1347
935
|
});
|
|
1348
936
|
if (listed.next_since !== null)
|
|
@@ -1405,7 +993,7 @@ async function consumeStreamsEvents(opts) {
|
|
|
1405
993
|
if (mode === "bounded") {
|
|
1406
994
|
return { cursor, pages, emptyPolls };
|
|
1407
995
|
}
|
|
1408
|
-
await
|
|
996
|
+
await sleep(emptyBackoffMs, opts.signal);
|
|
1409
997
|
continue;
|
|
1410
998
|
}
|
|
1411
999
|
return { cursor, pages, emptyPolls };
|
|
@@ -1413,7 +1001,7 @@ async function consumeStreamsEvents(opts) {
|
|
|
1413
1001
|
return { cursor, pages, emptyPolls };
|
|
1414
1002
|
}
|
|
1415
1003
|
async function* iterateStreamsBatches(opts) {
|
|
1416
|
-
const
|
|
1004
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
1417
1005
|
let cursor = opts.fromCursor ?? null;
|
|
1418
1006
|
while (!opts.signal?.aborted) {
|
|
1419
1007
|
const envelope = await opts.fetchEvents({
|
|
@@ -1441,12 +1029,12 @@ async function* iterateStreamsBatches(opts) {
|
|
|
1441
1029
|
if (!advanced && envelope.events.length === 0) {
|
|
1442
1030
|
if (opts.signal?.aborted)
|
|
1443
1031
|
return;
|
|
1444
|
-
await
|
|
1032
|
+
await sleep(opts.intervalMs, opts.signal);
|
|
1445
1033
|
}
|
|
1446
1034
|
}
|
|
1447
1035
|
}
|
|
1448
1036
|
async function* streamStreamsEvents(opts) {
|
|
1449
|
-
const
|
|
1037
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
1450
1038
|
const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
|
|
1451
1039
|
const maxPages = opts.maxPages ?? Number.POSITIVE_INFINITY;
|
|
1452
1040
|
const maxEmptyPolls = opts.maxEmptyPolls ?? Number.POSITIVE_INFINITY;
|
|
@@ -1481,7 +1069,7 @@ async function* streamStreamsEvents(opts) {
|
|
|
1481
1069
|
emptyPolls++;
|
|
1482
1070
|
if (emptyPolls >= maxEmptyPolls || pages >= maxPages)
|
|
1483
1071
|
return;
|
|
1484
|
-
await
|
|
1072
|
+
await sleep(emptyBackoffMs, opts.signal);
|
|
1485
1073
|
continue;
|
|
1486
1074
|
}
|
|
1487
1075
|
return;
|
|
@@ -1490,7 +1078,7 @@ async function* streamStreamsEvents(opts) {
|
|
|
1490
1078
|
|
|
1491
1079
|
// src/index-api/consumer.ts
|
|
1492
1080
|
async function consumeIndexFeed(opts) {
|
|
1493
|
-
const
|
|
1081
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
1494
1082
|
const mode = opts.mode ?? "tail";
|
|
1495
1083
|
const finalizedOnly = opts.finalizedOnly ?? false;
|
|
1496
1084
|
const batchSize = opts.batchSize ?? 200;
|
|
@@ -1514,7 +1102,7 @@ async function consumeIndexFeed(opts) {
|
|
|
1514
1102
|
retryCount: opts.retryCount,
|
|
1515
1103
|
retryDelay: opts.retryDelay,
|
|
1516
1104
|
onError: opts.onError,
|
|
1517
|
-
sleep
|
|
1105
|
+
sleep,
|
|
1518
1106
|
signal: opts.signal
|
|
1519
1107
|
});
|
|
1520
1108
|
pages++;
|
|
@@ -1577,7 +1165,7 @@ async function consumeIndexFeed(opts) {
|
|
|
1577
1165
|
if (mode === "bounded") {
|
|
1578
1166
|
return { cursor, pages, emptyPolls };
|
|
1579
1167
|
}
|
|
1580
|
-
await
|
|
1168
|
+
await sleep(emptyBackoffMs, opts.signal);
|
|
1581
1169
|
continue;
|
|
1582
1170
|
}
|
|
1583
1171
|
return { cursor, pages, emptyPolls };
|
|
@@ -2164,9 +1752,195 @@ function createStreamsDumps(opts) {
|
|
|
2164
1752
|
bytes.set(chunk, offset);
|
|
2165
1753
|
offset += chunk.byteLength;
|
|
2166
1754
|
}
|
|
2167
|
-
return bytes;
|
|
1755
|
+
return bytes;
|
|
1756
|
+
}
|
|
1757
|
+
return { list, fileUrl, download };
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
// src/streams/subscribe.ts
|
|
1761
|
+
import { ed25519 } from "@secondlayer/shared";
|
|
1762
|
+
var SUBSCRIBE_MAX_RECONNECT_DELAY_MS = 30000;
|
|
1763
|
+
var SUBSCRIBE_DEFAULT_STALE_AFTER_MS = 60000;
|
|
1764
|
+
function reconnectDelay(attempt, baseMs, retryAfterSeconds, random = Math.random) {
|
|
1765
|
+
const capped = Math.min(baseMs * 2 ** Math.max(0, attempt), SUBSCRIBE_MAX_RECONNECT_DELAY_MS);
|
|
1766
|
+
const jittered = capped * (0.5 + random() * 0.5);
|
|
1767
|
+
const floor = (retryAfterSeconds ?? 0) * 1000;
|
|
1768
|
+
return Math.max(jittered, floor);
|
|
1769
|
+
}
|
|
1770
|
+
function isTerminal(err) {
|
|
1771
|
+
if (err instanceof StreamsSignatureError)
|
|
1772
|
+
return true;
|
|
1773
|
+
return err?.retryable === false;
|
|
1774
|
+
}
|
|
1775
|
+
function subscribeStreamsEvents(opts) {
|
|
1776
|
+
const { params } = opts;
|
|
1777
|
+
const controller = new AbortController;
|
|
1778
|
+
const external = params.signal;
|
|
1779
|
+
if (external) {
|
|
1780
|
+
if (external.aborted)
|
|
1781
|
+
controller.abort();
|
|
1782
|
+
else
|
|
1783
|
+
external.addEventListener("abort", () => controller.abort(), {
|
|
1784
|
+
once: true
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
let cursor = params.fromCursor ?? null;
|
|
1788
|
+
const baseDelayMs = opts.reconnectDelayMs ?? params.reconnectDelayMs ?? 1000;
|
|
1789
|
+
const staleAfterMs = opts.staleAfterMs ?? params.staleAfterMs ?? SUBSCRIBE_DEFAULT_STALE_AFTER_MS;
|
|
1790
|
+
let attempt = 0;
|
|
1791
|
+
const run = async () => {
|
|
1792
|
+
while (!controller.signal.aborted) {
|
|
1793
|
+
try {
|
|
1794
|
+
const url = `${opts.baseUrl}/v1/streams/events/stream${buildQuery({
|
|
1795
|
+
from_cursor: cursor ?? undefined,
|
|
1796
|
+
types: params.types,
|
|
1797
|
+
not_types: params.notTypes,
|
|
1798
|
+
contract_id: params.contractId,
|
|
1799
|
+
sender: params.sender,
|
|
1800
|
+
recipient: params.recipient,
|
|
1801
|
+
asset_identifier: params.assetIdentifier,
|
|
1802
|
+
filters: params.filters ? JSON.stringify(params.filters) : undefined
|
|
1803
|
+
})}`;
|
|
1804
|
+
await readSse({
|
|
1805
|
+
url,
|
|
1806
|
+
headers: opts.headers ?? {},
|
|
1807
|
+
signal: controller.signal,
|
|
1808
|
+
fetchImpl: opts.fetchImpl,
|
|
1809
|
+
staleAfterMs,
|
|
1810
|
+
mapError: mapStreamsError,
|
|
1811
|
+
onFrame: async (frame) => {
|
|
1812
|
+
attempt = 0;
|
|
1813
|
+
if (frame.event === "ping" || !frame.data)
|
|
1814
|
+
return;
|
|
1815
|
+
let parsed;
|
|
1816
|
+
try {
|
|
1817
|
+
parsed = JSON.parse(frame.data);
|
|
1818
|
+
} catch {
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
if (!parsed.event)
|
|
1822
|
+
return;
|
|
1823
|
+
if (opts.verify !== "off") {
|
|
1824
|
+
if (!parsed.sig) {
|
|
1825
|
+
if (opts.verify === "strict") {
|
|
1826
|
+
throw new StreamsSignatureError("Streams SSE frame signature is missing.");
|
|
1827
|
+
}
|
|
1828
|
+
} else {
|
|
1829
|
+
const key = await opts.loadKey(parsed.key_id);
|
|
1830
|
+
if (!ed25519.verifyEd25519(JSON.stringify(parsed.event), parsed.sig, key.publicKey)) {
|
|
1831
|
+
throw new StreamsSignatureError("Streams SSE frame signature is invalid.");
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
await params.onEvent(parsed.event);
|
|
1836
|
+
cursor = parsed.event.cursor ?? cursor;
|
|
1837
|
+
}
|
|
1838
|
+
});
|
|
1839
|
+
await sleep(reconnectDelay(attempt, baseDelayMs, undefined, opts.random), controller.signal);
|
|
1840
|
+
attempt += 1;
|
|
1841
|
+
} catch (err) {
|
|
1842
|
+
if (controller.signal.aborted)
|
|
1843
|
+
return;
|
|
1844
|
+
params.onError?.(err);
|
|
1845
|
+
if (isTerminal(err))
|
|
1846
|
+
throw err;
|
|
1847
|
+
const retryAfterSeconds = err.retryAfterSeconds;
|
|
1848
|
+
await sleep(reconnectDelay(attempt, baseDelayMs, retryAfterSeconds, opts.random), controller.signal);
|
|
1849
|
+
attempt += 1;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
};
|
|
1853
|
+
const done = run();
|
|
1854
|
+
done.catch(() => {});
|
|
1855
|
+
const unsubscribe = () => controller.abort();
|
|
1856
|
+
return Object.assign(unsubscribe, { done });
|
|
1857
|
+
}
|
|
1858
|
+
async function readSse(opts) {
|
|
1859
|
+
const res = await opts.fetchImpl(opts.url, {
|
|
1860
|
+
headers: { ...opts.headers ?? {}, Accept: "text/event-stream" },
|
|
1861
|
+
signal: opts.signal
|
|
1862
|
+
});
|
|
1863
|
+
if (!res.ok) {
|
|
1864
|
+
if (opts.mapError)
|
|
1865
|
+
await opts.mapError(res);
|
|
1866
|
+
throw new StreamsServerError(`Streams SSE returned ${res.status}.`, res.status);
|
|
1867
|
+
}
|
|
1868
|
+
if (!res.body) {
|
|
1869
|
+
throw new StreamsServerError("Streams SSE response has no body.", 0);
|
|
1870
|
+
}
|
|
1871
|
+
for await (const frame of parseSseFrames(res.body, opts.signal, opts.staleAfterMs)) {
|
|
1872
|
+
await opts.onFrame(frame);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
function sleep(ms, signal) {
|
|
1876
|
+
return new Promise((resolve) => {
|
|
1877
|
+
if (signal.aborted)
|
|
1878
|
+
return resolve();
|
|
1879
|
+
const onAbort = () => {
|
|
1880
|
+
clearTimeout(timer);
|
|
1881
|
+
resolve();
|
|
1882
|
+
};
|
|
1883
|
+
const timer = setTimeout(() => {
|
|
1884
|
+
signal.removeEventListener("abort", onAbort);
|
|
1885
|
+
resolve();
|
|
1886
|
+
}, ms);
|
|
1887
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
async function readWithStaleTimer(reader, staleAfterMs) {
|
|
1891
|
+
if (staleAfterMs === undefined || !Number.isFinite(staleAfterMs)) {
|
|
1892
|
+
return reader.read();
|
|
1893
|
+
}
|
|
1894
|
+
let timer;
|
|
1895
|
+
const stale = new Promise((_, reject) => {
|
|
1896
|
+
timer = setTimeout(() => reject(new StreamsServerError(`Streams SSE went quiet for ${staleAfterMs} ms; reconnecting.`, 0, undefined, "STREAM_STALE")), staleAfterMs);
|
|
1897
|
+
});
|
|
1898
|
+
try {
|
|
1899
|
+
return await Promise.race([reader.read(), stale]);
|
|
1900
|
+
} finally {
|
|
1901
|
+
clearTimeout(timer);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
async function* parseSseFrames(body, signal, staleAfterMs) {
|
|
1905
|
+
const reader = body.getReader();
|
|
1906
|
+
const decoder = new TextDecoder;
|
|
1907
|
+
let buffer = "";
|
|
1908
|
+
try {
|
|
1909
|
+
while (!signal.aborted) {
|
|
1910
|
+
const { value, done } = await readWithStaleTimer(reader, staleAfterMs);
|
|
1911
|
+
if (done)
|
|
1912
|
+
break;
|
|
1913
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1914
|
+
let sep = buffer.indexOf(`
|
|
1915
|
+
|
|
1916
|
+
`);
|
|
1917
|
+
while (sep !== -1) {
|
|
1918
|
+
yield parseFrame(buffer.slice(0, sep));
|
|
1919
|
+
buffer = buffer.slice(sep + 2);
|
|
1920
|
+
sep = buffer.indexOf(`
|
|
1921
|
+
|
|
1922
|
+
`);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
} finally {
|
|
1926
|
+
try {
|
|
1927
|
+
await reader.cancel();
|
|
1928
|
+
} catch {}
|
|
2168
1929
|
}
|
|
2169
|
-
|
|
1930
|
+
}
|
|
1931
|
+
function parseFrame(raw) {
|
|
1932
|
+
let event;
|
|
1933
|
+
const data = [];
|
|
1934
|
+
for (const line of raw.split(`
|
|
1935
|
+
`)) {
|
|
1936
|
+
if (line.startsWith("data:")) {
|
|
1937
|
+
data.push(line.slice(line.startsWith("data: ") ? 6 : 5));
|
|
1938
|
+
} else if (line.startsWith("event:")) {
|
|
1939
|
+
event = line.slice(line.startsWith("event: ") ? 7 : 6).trim();
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
return { event, data: data.length > 0 ? data.join(`
|
|
1943
|
+
`) : undefined };
|
|
2170
1944
|
}
|
|
2171
1945
|
|
|
2172
1946
|
// src/streams/client.ts
|
|
@@ -2437,21 +2211,295 @@ function createStreamsClient(options) {
|
|
|
2437
2211
|
signal: params.signal
|
|
2438
2212
|
});
|
|
2439
2213
|
}
|
|
2440
|
-
},
|
|
2441
|
-
blocks: {
|
|
2442
|
-
events(heightOrHash) {
|
|
2443
|
-
return request(`/v1/streams/blocks/${encodeURIComponent(String(heightOrHash))}/events`);
|
|
2444
|
-
}
|
|
2445
|
-
},
|
|
2446
|
-
reorgs: { list: listReorgs },
|
|
2447
|
-
dumps,
|
|
2448
|
-
canonical(height) {
|
|
2449
|
-
return request(`/v1/streams/canonical/${height}`);
|
|
2450
|
-
},
|
|
2451
|
-
tip() {
|
|
2452
|
-
return request("/v1/streams/tip");
|
|
2453
|
-
}
|
|
2454
|
-
};
|
|
2214
|
+
},
|
|
2215
|
+
blocks: {
|
|
2216
|
+
events(heightOrHash) {
|
|
2217
|
+
return request(`/v1/streams/blocks/${encodeURIComponent(String(heightOrHash))}/events`);
|
|
2218
|
+
}
|
|
2219
|
+
},
|
|
2220
|
+
reorgs: { list: listReorgs },
|
|
2221
|
+
dumps,
|
|
2222
|
+
canonical(height) {
|
|
2223
|
+
return request(`/v1/streams/canonical/${height}`);
|
|
2224
|
+
},
|
|
2225
|
+
tip() {
|
|
2226
|
+
return request("/v1/streams/tip");
|
|
2227
|
+
}
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
// src/subgraphs/serialize.ts
|
|
2232
|
+
var SYSTEM_COLUMN_MAP = {
|
|
2233
|
+
_blockHeight: "_block_height",
|
|
2234
|
+
_txId: "_tx_id",
|
|
2235
|
+
_createdAt: "_created_at",
|
|
2236
|
+
_id: "_id"
|
|
2237
|
+
};
|
|
2238
|
+
var SYSTEM_COLUMN_ALIASES = {
|
|
2239
|
+
blockHeight: "_block_height",
|
|
2240
|
+
txId: "_tx_id",
|
|
2241
|
+
createdAt: "_created_at",
|
|
2242
|
+
id: "_id"
|
|
2243
|
+
};
|
|
2244
|
+
function resolveColumn(col, columns) {
|
|
2245
|
+
const canonical = SYSTEM_COLUMN_MAP[col];
|
|
2246
|
+
if (canonical)
|
|
2247
|
+
return canonical;
|
|
2248
|
+
if (columns?.has(col))
|
|
2249
|
+
return col;
|
|
2250
|
+
return SYSTEM_COLUMN_ALIASES[col] ?? col;
|
|
2251
|
+
}
|
|
2252
|
+
function serializeWhere(where, columns) {
|
|
2253
|
+
const filters = {};
|
|
2254
|
+
for (const [column, value] of Object.entries(where)) {
|
|
2255
|
+
if (value === null || value === undefined)
|
|
2256
|
+
continue;
|
|
2257
|
+
const col = resolveColumn(column, columns);
|
|
2258
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
2259
|
+
const ops = value;
|
|
2260
|
+
for (const [op, opValue] of Object.entries(ops)) {
|
|
2261
|
+
if (opValue === null || opValue === undefined)
|
|
2262
|
+
continue;
|
|
2263
|
+
if (op === "eq") {
|
|
2264
|
+
filters[col] = String(opValue);
|
|
2265
|
+
} else if (op === "in" || op === "notIn") {
|
|
2266
|
+
const arr = Array.isArray(opValue) ? opValue : [opValue];
|
|
2267
|
+
filters[`${col}.${op}`] = arr.map((v) => String(v)).join(",");
|
|
2268
|
+
} else if (["neq", "gt", "gte", "lt", "lte", "like"].includes(op)) {
|
|
2269
|
+
filters[`${col}.${op}`] = String(opValue);
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
} else {
|
|
2273
|
+
filters[col] = String(value);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
return filters;
|
|
2277
|
+
}
|
|
2278
|
+
function resolveOrderByColumn(col, columns) {
|
|
2279
|
+
return resolveColumn(col, columns);
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
// src/subgraphs/client.ts
|
|
2283
|
+
var SUBSCRIBE_RECONNECT_DELAY_MS = 1000;
|
|
2284
|
+
function declaredColumns(table) {
|
|
2285
|
+
if (!table || typeof table !== "object")
|
|
2286
|
+
return;
|
|
2287
|
+
const columns = table.columns;
|
|
2288
|
+
if (!columns || typeof columns !== "object")
|
|
2289
|
+
return;
|
|
2290
|
+
return new Set(Object.keys(columns));
|
|
2291
|
+
}
|
|
2292
|
+
function rowBlockHeight(row) {
|
|
2293
|
+
const r = row;
|
|
2294
|
+
const raw = r._block_height ?? r._blockHeight;
|
|
2295
|
+
if (typeof raw !== "number" && typeof raw !== "string")
|
|
2296
|
+
return;
|
|
2297
|
+
const height = Number(raw);
|
|
2298
|
+
return Number.isFinite(height) ? height : undefined;
|
|
2299
|
+
}
|
|
2300
|
+
function sleep2(ms, signal) {
|
|
2301
|
+
return new Promise((resolve) => {
|
|
2302
|
+
if (signal.aborted)
|
|
2303
|
+
return resolve();
|
|
2304
|
+
const onAbort = () => {
|
|
2305
|
+
clearTimeout(timer);
|
|
2306
|
+
resolve();
|
|
2307
|
+
};
|
|
2308
|
+
const timer = setTimeout(() => {
|
|
2309
|
+
signal.removeEventListener("abort", onAbort);
|
|
2310
|
+
resolve();
|
|
2311
|
+
}, ms);
|
|
2312
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2313
|
+
});
|
|
2314
|
+
}
|
|
2315
|
+
function buildSubgraphQueryString(params) {
|
|
2316
|
+
return buildQuery({
|
|
2317
|
+
_sort: params.sort,
|
|
2318
|
+
_order: params.order,
|
|
2319
|
+
_limit: params.limit,
|
|
2320
|
+
_offset: params.offset,
|
|
2321
|
+
_fields: params.fields,
|
|
2322
|
+
...params.filters
|
|
2323
|
+
});
|
|
2324
|
+
}
|
|
2325
|
+
function buildAggregateQueryString(params) {
|
|
2326
|
+
return buildQuery({
|
|
2327
|
+
...params.filters,
|
|
2328
|
+
_count: params.count ? "true" : undefined,
|
|
2329
|
+
_countDistinct: params.countDistinct,
|
|
2330
|
+
_sum: params.sum,
|
|
2331
|
+
_min: params.min,
|
|
2332
|
+
_max: params.max
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
function buildSpecQueryString(options) {
|
|
2336
|
+
return buildQuery({ server: options?.serverUrl });
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
class Subgraphs extends BaseClient {
|
|
2340
|
+
async list() {
|
|
2341
|
+
return this.request("GET", "/api/subgraphs");
|
|
2342
|
+
}
|
|
2343
|
+
async status(name) {
|
|
2344
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}`);
|
|
2345
|
+
}
|
|
2346
|
+
async openapi(name, options) {
|
|
2347
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/openapi.json${buildSpecQueryString(options)}`);
|
|
2348
|
+
}
|
|
2349
|
+
async schema(name, options) {
|
|
2350
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/schema.json${buildSpecQueryString(options)}`);
|
|
2351
|
+
}
|
|
2352
|
+
async markdown(name, options) {
|
|
2353
|
+
return this.requestText("GET", `/api/subgraphs/${seg(name)}/docs.md${buildSpecQueryString(options)}`);
|
|
2354
|
+
}
|
|
2355
|
+
async reindex(name) {
|
|
2356
|
+
return this.request("POST", `/api/subgraphs/${seg(name)}/reindex`);
|
|
2357
|
+
}
|
|
2358
|
+
async stop(name) {
|
|
2359
|
+
return this.request("POST", `/api/subgraphs/${seg(name)}/stop`);
|
|
2360
|
+
}
|
|
2361
|
+
async backfill(name, options) {
|
|
2362
|
+
return this.request("POST", `/api/subgraphs/${seg(name)}/backfill`, options);
|
|
2363
|
+
}
|
|
2364
|
+
async gaps(name, opts) {
|
|
2365
|
+
const qs = buildQuery({
|
|
2366
|
+
_limit: opts?.limit,
|
|
2367
|
+
_offset: opts?.offset,
|
|
2368
|
+
resolved: opts?.resolved
|
|
2369
|
+
});
|
|
2370
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/gaps${qs}`);
|
|
2371
|
+
}
|
|
2372
|
+
async delete(name, options) {
|
|
2373
|
+
const qs = buildQuery({ force: options?.force ? true : undefined });
|
|
2374
|
+
return this.request("DELETE", `/api/subgraphs/${seg(name)}${qs}`);
|
|
2375
|
+
}
|
|
2376
|
+
async rows(name, table, params = {}) {
|
|
2377
|
+
const { cursor, ...rest } = params;
|
|
2378
|
+
const qs = buildSubgraphQueryString(rest);
|
|
2379
|
+
const sep = qs ? "&" : "?";
|
|
2380
|
+
const cursorQs = cursor ? `${sep}cursor=${encodeURIComponent(cursor)}` : "";
|
|
2381
|
+
return this.request("GET", `/v1/subgraphs/${seg(name)}/${seg(table)}${qs}${cursorQs}`);
|
|
2382
|
+
}
|
|
2383
|
+
async operations(name) {
|
|
2384
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/operations`);
|
|
2385
|
+
}
|
|
2386
|
+
async getOperation(name, operationId) {
|
|
2387
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/operations/${seg(operationId)}`);
|
|
2388
|
+
}
|
|
2389
|
+
async deploy(data) {
|
|
2390
|
+
return this.request("POST", "/api/subgraphs", data);
|
|
2391
|
+
}
|
|
2392
|
+
async getSource(name) {
|
|
2393
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/source`);
|
|
2394
|
+
}
|
|
2395
|
+
async bundle(data) {
|
|
2396
|
+
return this.request("POST", "/api/subgraphs/bundle", data);
|
|
2397
|
+
}
|
|
2398
|
+
async queryTable(name, table, params = {}) {
|
|
2399
|
+
const result = await this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}${buildSubgraphQueryString(params)}`);
|
|
2400
|
+
return Array.isArray(result) ? result : result.data;
|
|
2401
|
+
}
|
|
2402
|
+
async queryTableCount(name, table, params = {}) {
|
|
2403
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}/count${buildSubgraphQueryString(params)}`);
|
|
2404
|
+
}
|
|
2405
|
+
async queryTableAggregate(name, table, params = {}) {
|
|
2406
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}/aggregate${buildAggregateQueryString(params)}`);
|
|
2407
|
+
}
|
|
2408
|
+
typed(def) {
|
|
2409
|
+
const result = {};
|
|
2410
|
+
for (const [tableName, table] of Object.entries(def.schema)) {
|
|
2411
|
+
result[tableName] = this.createTableClient(def.name, tableName, declaredColumns(table));
|
|
2412
|
+
}
|
|
2413
|
+
return result;
|
|
2414
|
+
}
|
|
2415
|
+
createTableClient(subgraphName, tableName, columns) {
|
|
2416
|
+
const self = this;
|
|
2417
|
+
return {
|
|
2418
|
+
async findMany(options = {}) {
|
|
2419
|
+
const filters = options.where ? serializeWhere(options.where, columns) : undefined;
|
|
2420
|
+
let sort;
|
|
2421
|
+
let order;
|
|
2422
|
+
if (options.orderBy) {
|
|
2423
|
+
const entries = Array.isArray(options.orderBy) ? options.orderBy : Object.entries(options.orderBy);
|
|
2424
|
+
if (entries.length > 0) {
|
|
2425
|
+
sort = entries.map(([col]) => resolveOrderByColumn(col, columns)).join(",");
|
|
2426
|
+
order = entries.map(([, dir]) => dir ?? "asc").join(",");
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
const params = {
|
|
2430
|
+
sort,
|
|
2431
|
+
order,
|
|
2432
|
+
limit: options.limit,
|
|
2433
|
+
offset: options.offset,
|
|
2434
|
+
fields: options.fields?.join(","),
|
|
2435
|
+
filters
|
|
2436
|
+
};
|
|
2437
|
+
return self.queryTable(subgraphName, tableName, params);
|
|
2438
|
+
},
|
|
2439
|
+
async count(where) {
|
|
2440
|
+
const filters = where ? serializeWhere(where, columns) : undefined;
|
|
2441
|
+
const result = await self.queryTableCount(subgraphName, tableName, {
|
|
2442
|
+
filters
|
|
2443
|
+
});
|
|
2444
|
+
return result.count;
|
|
2445
|
+
},
|
|
2446
|
+
async aggregate(spec) {
|
|
2447
|
+
const filters = spec.where ? serializeWhere(spec.where, columns) : undefined;
|
|
2448
|
+
const result = await self.queryTableAggregate(subgraphName, tableName, {
|
|
2449
|
+
filters,
|
|
2450
|
+
count: spec.count,
|
|
2451
|
+
countDistinct: spec.countDistinct,
|
|
2452
|
+
sum: spec.sum,
|
|
2453
|
+
min: spec.min,
|
|
2454
|
+
max: spec.max
|
|
2455
|
+
});
|
|
2456
|
+
return result;
|
|
2457
|
+
},
|
|
2458
|
+
subscribe(onRow, options = {}) {
|
|
2459
|
+
const filters = options.where ? serializeWhere(options.where, columns) : {};
|
|
2460
|
+
const controller = new AbortController;
|
|
2461
|
+
let since = options.since ?? undefined;
|
|
2462
|
+
const run = async () => {
|
|
2463
|
+
while (!controller.signal.aborted) {
|
|
2464
|
+
try {
|
|
2465
|
+
const qs = buildQuery({ ...filters, since });
|
|
2466
|
+
await readSse({
|
|
2467
|
+
url: `${self.baseUrl}/v1/subgraphs/${seg(subgraphName)}/${seg(tableName)}/stream${qs}`,
|
|
2468
|
+
headers: {
|
|
2469
|
+
...BaseClient.authHeaders(self.apiKey),
|
|
2470
|
+
"x-sl-origin": self.origin
|
|
2471
|
+
},
|
|
2472
|
+
signal: controller.signal,
|
|
2473
|
+
fetchImpl: self.fetchImpl,
|
|
2474
|
+
onFrame: (frame) => {
|
|
2475
|
+
if (frame.event === "ping" || !frame.data)
|
|
2476
|
+
return;
|
|
2477
|
+
let row;
|
|
2478
|
+
try {
|
|
2479
|
+
row = JSON.parse(frame.data);
|
|
2480
|
+
} catch {
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
2483
|
+
const height = rowBlockHeight(row);
|
|
2484
|
+
if (height !== undefined)
|
|
2485
|
+
since = height;
|
|
2486
|
+
onRow(row);
|
|
2487
|
+
}
|
|
2488
|
+
});
|
|
2489
|
+
await sleep2(SUBSCRIBE_RECONNECT_DELAY_MS, controller.signal);
|
|
2490
|
+
} catch (err) {
|
|
2491
|
+
if (controller.signal.aborted)
|
|
2492
|
+
return;
|
|
2493
|
+
options.onError?.(err);
|
|
2494
|
+
await sleep2(SUBSCRIBE_RECONNECT_DELAY_MS, controller.signal);
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
};
|
|
2498
|
+
run();
|
|
2499
|
+
return () => controller.abort();
|
|
2500
|
+
}
|
|
2501
|
+
};
|
|
2502
|
+
}
|
|
2455
2503
|
}
|
|
2456
2504
|
|
|
2457
2505
|
// src/webhooks/client.ts
|
|
@@ -2627,7 +2675,6 @@ class SecondLayer extends BaseClient {
|
|
|
2627
2675
|
};
|
|
2628
2676
|
}
|
|
2629
2677
|
}
|
|
2630
|
-
|
|
2631
2678
|
// src/subgraphs/get-subgraph.ts
|
|
2632
2679
|
function getSubgraph(def, options = {}) {
|
|
2633
2680
|
if (options instanceof Subgraphs) {
|
|
@@ -2638,36 +2685,6 @@ function getSubgraph(def, options = {}) {
|
|
|
2638
2685
|
}
|
|
2639
2686
|
return new Subgraphs(options).typed(def);
|
|
2640
2687
|
}
|
|
2641
|
-
// src/streams/index.ts
|
|
2642
|
-
import {
|
|
2643
|
-
decodeFtTransfer,
|
|
2644
|
-
isFtTransfer,
|
|
2645
|
-
decodeNftTransfer,
|
|
2646
|
-
isNftTransfer,
|
|
2647
|
-
decodeStxBurn,
|
|
2648
|
-
decodeStxLock,
|
|
2649
|
-
decodeStxMint,
|
|
2650
|
-
decodeStxTransfer,
|
|
2651
|
-
isStxBurn,
|
|
2652
|
-
isStxLock,
|
|
2653
|
-
isStxMint,
|
|
2654
|
-
isStxTransfer,
|
|
2655
|
-
decodeFtBurn,
|
|
2656
|
-
decodeFtMint,
|
|
2657
|
-
decodeNftBurn,
|
|
2658
|
-
decodeNftMint,
|
|
2659
|
-
isFtBurn,
|
|
2660
|
-
isFtMint,
|
|
2661
|
-
isNftBurn,
|
|
2662
|
-
isNftMint,
|
|
2663
|
-
decodePrint,
|
|
2664
|
-
isPrint
|
|
2665
|
-
} from "@secondlayer/shared/streams-rows";
|
|
2666
|
-
|
|
2667
|
-
// src/streams/types.ts
|
|
2668
|
-
import {
|
|
2669
|
-
STREAMS_EVENT_TYPES
|
|
2670
|
-
} from "@secondlayer/shared/streams-rows";
|
|
2671
2688
|
// src/consumer-ops.ts
|
|
2672
2689
|
function consumerHealth(options = {}) {
|
|
2673
2690
|
const staleAfterMs = options.staleAfterMs ?? 120000;
|
|
@@ -2719,12 +2736,12 @@ import {
|
|
|
2719
2736
|
} from "@secondlayer/shared/crypto/standard-webhooks";
|
|
2720
2737
|
function pickHeader(headers, name) {
|
|
2721
2738
|
if (typeof headers === "function") {
|
|
2722
|
-
const
|
|
2723
|
-
return typeof
|
|
2739
|
+
const v = headers(name);
|
|
2740
|
+
return typeof v === "string" ? v : undefined;
|
|
2724
2741
|
}
|
|
2725
2742
|
if (typeof headers.get === "function" && !(name in headers)) {
|
|
2726
|
-
const
|
|
2727
|
-
return typeof
|
|
2743
|
+
const v = headers.get(name);
|
|
2744
|
+
return typeof v === "string" ? v : undefined;
|
|
2728
2745
|
}
|
|
2729
2746
|
const bag = headers;
|
|
2730
2747
|
let v = bag[name] ?? bag[name.toLowerCase()];
|
|
@@ -2861,78 +2878,109 @@ function verifyTransactionProof(proof, opts) {
|
|
|
2861
2878
|
errors
|
|
2862
2879
|
};
|
|
2863
2880
|
}
|
|
2881
|
+
// src/streams/index.ts
|
|
2882
|
+
import {
|
|
2883
|
+
decodeFtTransfer,
|
|
2884
|
+
isFtTransfer,
|
|
2885
|
+
decodeNftTransfer,
|
|
2886
|
+
isNftTransfer,
|
|
2887
|
+
decodeStxBurn,
|
|
2888
|
+
decodeStxLock,
|
|
2889
|
+
decodeStxMint,
|
|
2890
|
+
decodeStxTransfer,
|
|
2891
|
+
isStxBurn,
|
|
2892
|
+
isStxLock,
|
|
2893
|
+
isStxMint,
|
|
2894
|
+
isStxTransfer,
|
|
2895
|
+
decodeFtBurn,
|
|
2896
|
+
decodeFtMint,
|
|
2897
|
+
decodeNftBurn,
|
|
2898
|
+
decodeNftMint,
|
|
2899
|
+
isFtBurn,
|
|
2900
|
+
isFtMint,
|
|
2901
|
+
isNftBurn,
|
|
2902
|
+
isNftMint,
|
|
2903
|
+
decodePrint,
|
|
2904
|
+
isPrint
|
|
2905
|
+
} from "@secondlayer/shared/streams-rows";
|
|
2906
|
+
|
|
2907
|
+
// src/streams/types.ts
|
|
2908
|
+
import {
|
|
2909
|
+
STREAMS_EVENT_TYPES
|
|
2910
|
+
} from "@secondlayer/shared/streams-rows";
|
|
2864
2911
|
// src/clarity.ts
|
|
2865
2912
|
import {
|
|
2866
2913
|
decodeClarityValue,
|
|
2867
2914
|
toJsonSafe
|
|
2868
2915
|
} from "@secondlayer/shared/streams-rows";
|
|
2869
2916
|
export {
|
|
2870
|
-
|
|
2871
|
-
verifyTransactionProof,
|
|
2872
|
-
verify2 as verifyStandardWebhooksHeaders,
|
|
2873
|
-
verifySecondlayerSignature,
|
|
2874
|
-
trigger,
|
|
2875
|
-
toJsonSafe,
|
|
2876
|
-
shutdownSignal,
|
|
2877
|
-
resolveBaseUrl,
|
|
2878
|
-
resolveApiKey,
|
|
2879
|
-
resolveAccountKey,
|
|
2880
|
-
parseRetryAfter,
|
|
2881
|
-
isStxTransfer,
|
|
2882
|
-
isStxMint,
|
|
2883
|
-
isStxLock,
|
|
2884
|
-
isStxBurn,
|
|
2885
|
-
isPrint,
|
|
2886
|
-
isNftTransfer,
|
|
2887
|
-
isNftMint,
|
|
2888
|
-
isNftBurn,
|
|
2889
|
-
isFtTransfer,
|
|
2890
|
-
isFtMint,
|
|
2891
|
-
isFtBurn,
|
|
2892
|
-
getSubgraph,
|
|
2893
|
-
fetchRewardSet,
|
|
2894
|
-
decodeStxTransfer,
|
|
2895
|
-
decodeStxMint,
|
|
2896
|
-
decodeStxLock,
|
|
2897
|
-
decodeStxBurn,
|
|
2898
|
-
decodePrint,
|
|
2899
|
-
decodeNftTransfer,
|
|
2900
|
-
decodeNftMint,
|
|
2901
|
-
decodeNftBurn,
|
|
2902
|
-
decodeFtTransfer,
|
|
2903
|
-
decodeFtMint,
|
|
2904
|
-
decodeFtBurn,
|
|
2905
|
-
decodeClarityValue,
|
|
2906
|
-
decodeChainWebhook,
|
|
2907
|
-
decode,
|
|
2908
|
-
createStreamsClient,
|
|
2909
|
-
createArchiveClient,
|
|
2910
|
-
contextField,
|
|
2911
|
-
consumerHealth,
|
|
2912
|
-
consumeIndexFeed,
|
|
2913
|
-
Webhooks,
|
|
2914
|
-
ValidationError,
|
|
2915
|
-
Subgraphs,
|
|
2916
|
-
StreamsSignatureError,
|
|
2917
|
-
StreamsServerError,
|
|
2918
|
-
SecondLayerError,
|
|
2919
|
-
SecondLayer,
|
|
2920
|
-
RateLimitError,
|
|
2921
|
-
LOCAL_API_URL,
|
|
2922
|
-
InsufficientArchiveCreditsError,
|
|
2923
|
-
Index,
|
|
2924
|
-
INSTANCE_TOKEN_ENV,
|
|
2925
|
-
INDEX_MAX_PAGE_SIZE,
|
|
2926
|
-
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
2927
|
-
Cursor,
|
|
2928
|
-
Contracts,
|
|
2929
|
-
AuthError,
|
|
2930
|
-
ArchiveSignatureError,
|
|
2931
|
-
ArchiveGateNotConfiguredError,
|
|
2932
|
-
ArchiveAuthError,
|
|
2917
|
+
ACCOUNT_KEY_ENV,
|
|
2933
2918
|
ApiError,
|
|
2934
|
-
|
|
2919
|
+
ArchiveAuthError,
|
|
2920
|
+
ArchiveGateNotConfiguredError,
|
|
2921
|
+
ArchiveSignatureError,
|
|
2922
|
+
AuthError,
|
|
2923
|
+
Contracts,
|
|
2924
|
+
Cursor,
|
|
2925
|
+
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
2926
|
+
INDEX_MAX_PAGE_SIZE,
|
|
2927
|
+
INSTANCE_TOKEN_ENV,
|
|
2928
|
+
Index,
|
|
2929
|
+
InsufficientArchiveCreditsError,
|
|
2930
|
+
LOCAL_API_URL,
|
|
2931
|
+
MemoryEtagCache,
|
|
2932
|
+
RateLimitError,
|
|
2933
|
+
SecondLayer,
|
|
2934
|
+
SecondLayerError,
|
|
2935
|
+
StreamsServerError,
|
|
2936
|
+
StreamsSignatureError,
|
|
2937
|
+
Subgraphs,
|
|
2938
|
+
ValidationError,
|
|
2939
|
+
Webhooks,
|
|
2940
|
+
consumeIndexFeed,
|
|
2941
|
+
consumerHealth,
|
|
2942
|
+
contextField,
|
|
2943
|
+
createArchiveClient,
|
|
2944
|
+
createStreamsClient,
|
|
2945
|
+
decode,
|
|
2946
|
+
decodeChainWebhook,
|
|
2947
|
+
decodeClarityValue,
|
|
2948
|
+
decodeFtBurn,
|
|
2949
|
+
decodeFtMint,
|
|
2950
|
+
decodeFtTransfer,
|
|
2951
|
+
decodeNftBurn,
|
|
2952
|
+
decodeNftMint,
|
|
2953
|
+
decodeNftTransfer,
|
|
2954
|
+
decodePrint,
|
|
2955
|
+
decodeStxBurn,
|
|
2956
|
+
decodeStxLock,
|
|
2957
|
+
decodeStxMint,
|
|
2958
|
+
decodeStxTransfer,
|
|
2959
|
+
fetchRewardSet,
|
|
2960
|
+
getSubgraph,
|
|
2961
|
+
isFtBurn,
|
|
2962
|
+
isFtMint,
|
|
2963
|
+
isFtTransfer,
|
|
2964
|
+
isNftBurn,
|
|
2965
|
+
isNftMint,
|
|
2966
|
+
isNftTransfer,
|
|
2967
|
+
isPrint,
|
|
2968
|
+
isStxBurn,
|
|
2969
|
+
isStxLock,
|
|
2970
|
+
isStxMint,
|
|
2971
|
+
isStxTransfer,
|
|
2972
|
+
parseRetryAfter,
|
|
2973
|
+
resolveAccountKey,
|
|
2974
|
+
resolveApiKey,
|
|
2975
|
+
resolveBaseUrl,
|
|
2976
|
+
shutdownSignal,
|
|
2977
|
+
toJsonSafe,
|
|
2978
|
+
trigger,
|
|
2979
|
+
verifySecondlayerSignature,
|
|
2980
|
+
verify2 as verifyStandardWebhooksHeaders,
|
|
2981
|
+
verifyTransactionProof,
|
|
2982
|
+
verifyWebhookSignature
|
|
2935
2983
|
};
|
|
2936
2984
|
|
|
2937
|
-
//# debugId=
|
|
2985
|
+
//# debugId=92280D2ABC3CFA6364756E2164756E21
|
|
2938
2986
|
//# sourceMappingURL=index.js.map
|