@secondlayer/sdk 10.4.2 → 10.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39,6 +39,22 @@ interface SecondLayerOptions {
39
39
  * retry policy instead of stalling a walk or consume loop forever. `0`
40
40
  * disables the timeout. */
41
41
  requestTimeoutMs?: number;
42
+ /** Where GET responses carrying an `ETag` are kept, so a repeat read sends
43
+ * `If-None-Match` and a finalized page comes back as a free `304`.
44
+ * Defaults to an in-memory store of the last 64 pages. Pass your own to
45
+ * keep it across processes (a file, a KV), or `false` to turn it off. */
46
+ etagCache?: EtagCache | false;
47
+ }
48
+ /** One cached GET response: its validator and the body it validated. */
49
+ type EtagEntry = {
50
+ etag: string;
51
+ body: string;
52
+ };
53
+ /** Storage for `etagCache`. Keys are full request URLs. Either method may be
54
+ * async, so a disk or KV store works as well as a Map. */
55
+ interface EtagCache {
56
+ get(key: string): EtagEntry | undefined | Promise<EtagEntry | undefined>;
57
+ set(key: string, entry: EtagEntry): void | Promise<void>;
42
58
  }
43
59
  /** Per-call options every `request*` method accepts. */
44
60
  type RequestOptions = {
@@ -52,6 +68,7 @@ declare abstract class BaseClient {
52
68
  protected origin: "cli" | "mcp" | "session";
53
69
  protected fetchImpl: FetchLike;
54
70
  protected requestTimeoutMs: number;
71
+ protected etagCache: EtagCache | null;
55
72
  constructor(options?: Partial<SecondLayerOptions>);
56
73
  static authHeaders(apiKey?: string): Record<string, string>;
57
74
  protected request<T>(method: string, path: string, body?: unknown, opts?: RequestOptions): Promise<T>;
@@ -68,7 +85,7 @@ declare abstract class BaseClient {
68
85
  /** Issue the HTTP request and map non-2xx statuses onto the error family.
69
86
  * `signal` is the combined caller-plus-timeout signal from `request*`;
70
87
  * subclasses that stream a body call this directly and read it themselves. */
71
- protected fetchResponse(method: string, path: string, body?: unknown, signal?: AbortSignal): Promise<Response>;
88
+ protected fetchResponse(method: string, path: string, body?: unknown, signal?: AbortSignal, extraHeaders?: Record<string, string>): Promise<Response>;
72
89
  }
73
90
  interface SubgraphSource {
74
91
  name: string;
@@ -582,7 +599,8 @@ interface ConsumerSink<Tx = unknown> {
582
599
  type WithSinkTx<TTx> = [TTx] extends [never] ? unknown : {
583
600
  tx: TTx;
584
601
  };
585
- import { StreamsEvent, StreamsEventType } from "@secondlayer/shared/streams-rows";
602
+ import { VmEventType } from "@secondlayer/shared";
603
+ import { StreamsEvent, StreamsEventType, StreamsWireEvent, VmStreamsEvent } from "@secondlayer/shared/streams-rows";
586
604
  type StreamsTip = {
587
605
  block_height: number;
588
606
  block_hash: string;
@@ -678,9 +696,11 @@ type StreamsEventsListParams = {
678
696
  cursor?: string | null;
679
697
  fromHeight?: number;
680
698
  toHeight?: number;
681
- types?: readonly StreamsEventType[];
699
+ /** `vm` reads ordinal. Omit for Streams 1.0. */
700
+ clock?: "classic" | "vm";
701
+ types?: readonly (StreamsEventType | VmEventType)[];
682
702
  /** Event types to exclude (applied after `types`). */
683
- notTypes?: readonly StreamsEventType[];
703
+ notTypes?: readonly (StreamsEventType | VmEventType)[];
684
704
  contractId?: StreamsFilterValue;
685
705
  sender?: StreamsFilterValue;
686
706
  recipient?: StreamsFilterValue;
@@ -1071,11 +1091,26 @@ type StreamsClient = {
1071
1091
  }): AsyncIterableIterator<StreamsBatch<StreamsEventOfTypes<T>>>;
1072
1092
  consume(params?: StreamsConsumeParams): AsyncIterableIterator<StreamsBatch>;
1073
1093
  events: {
1074
- /** Narrowing overload, matching `consume`. */
1075
- list<const T extends readonly StreamsEventType[]>(params: StreamsEventsListParams & {
1094
+ /** `clock: "vm"` pages carry vm rows only (a parallel vocabulary,
1095
+ * `event_index` = `ordinal`). Never `StreamsEvent`. */
1096
+ list(params: StreamsEventsListParams & {
1097
+ clock: "vm";
1098
+ }): Promise<StreamsEventsEnvelope<VmStreamsEvent>>;
1099
+ /** Narrowing overload, matching `consume`. Classic clock only —
1100
+ * `clock: "vm"` is overload 1, and a general `StreamsEventsListParams`
1101
+ * variable (clock unresolved) is the wire-union fallback. */
1102
+ list<const T extends readonly StreamsEventType[]>(params: Omit<StreamsEventsListParams, "clock" | "types" | "notTypes"> & {
1076
1103
  types: T;
1104
+ notTypes?: readonly StreamsEventType[];
1105
+ clock?: "classic";
1077
1106
  }): Promise<StreamsEventsEnvelope<StreamsEventOfTypes<T>>>;
1078
- list(params?: StreamsEventsListParams): Promise<StreamsEventsEnvelope>;
1107
+ list(params?: Omit<StreamsEventsListParams, "clock" | "types" | "notTypes"> & {
1108
+ types?: readonly StreamsEventType[];
1109
+ notTypes?: readonly StreamsEventType[];
1110
+ clock?: "classic";
1111
+ }): Promise<StreamsEventsEnvelope>;
1112
+ /** Unresolved `clock` on a general params variable: the wire union. */
1113
+ list(params: StreamsEventsListParams): Promise<StreamsEventsEnvelope<StreamsWireEvent>>;
1079
1114
  byTxId(txId: string): Promise<StreamsEventsListEnvelope>;
1080
1115
  /**
1081
1116
  * Pull pages from Streams and call `onBatch` after each page.
@@ -1450,8 +1485,38 @@ type IndexPrint = IndexEventBase & {
1450
1485
  raw_value: string | null;
1451
1486
  };
1452
1487
  };
1488
+ type IndexNestedContractCall = IndexEventBase & {
1489
+ event_type: "nested_contract_call";
1490
+ sender: string | null;
1491
+ caller: string;
1492
+ function_name: string;
1493
+ function_args: unknown;
1494
+ raw_result: string | null;
1495
+ };
1496
+ type IndexVarSet = IndexEventBase & {
1497
+ event_type: "var_set";
1498
+ var_name: string;
1499
+ raw_value: string;
1500
+ };
1501
+ type IndexMapSet = IndexEventBase & {
1502
+ event_type: "map_set";
1503
+ map: string;
1504
+ raw_key: string;
1505
+ raw_value: string;
1506
+ };
1507
+ type IndexMapInsert = IndexEventBase & {
1508
+ event_type: "map_insert";
1509
+ map: string;
1510
+ raw_key: string;
1511
+ raw_value: string;
1512
+ };
1513
+ type IndexMapDelete = IndexEventBase & {
1514
+ event_type: "map_delete";
1515
+ map: string;
1516
+ raw_key: string;
1517
+ };
1453
1518
  /** Decoded chain event, discriminated by `event_type`. */
1454
- type IndexEvent = IndexFtTransfer | IndexNftTransfer | IndexStxTransfer | IndexStxMint | IndexStxBurn | IndexStxLock | IndexFtMint | IndexFtBurn | IndexNftMint | IndexNftBurn | IndexPrint;
1519
+ type IndexEvent = IndexFtTransfer | IndexNftTransfer | IndexStxTransfer | IndexStxMint | IndexStxBurn | IndexStxLock | IndexFtMint | IndexFtBurn | IndexNftMint | IndexNftBurn | IndexPrint | IndexNestedContractCall | IndexVarSet | IndexMapSet | IndexMapInsert | IndexMapDelete;
1455
1520
  type IndexEventType = IndexEvent["event_type"];
1456
1521
  /** Fields every read returns regardless of `fields` — the consume contract
1457
1522
  * (`cursor`, `block_height`) plus the union discriminant. */
@@ -1476,8 +1541,7 @@ type IndexEventOf<T extends IndexEventType> = Extract<IndexEvent, {
1476
1541
  event_type: T;
1477
1542
  }>;
1478
1543
  type EventsListParams<T extends IndexEventType = IndexEventType> = {
1479
- /** Required. One of the decoded event types. Passing a literal narrows the
1480
- * rows every surface hands back to that event's own shape. */
1544
+ /** Decoded types or VM types (`nested_contract_call`, `map_set`, …). */
1481
1545
  eventType: T;
1482
1546
  cursor?: string | null;
1483
1547
  fromCursor?: string | null;
@@ -1488,6 +1552,12 @@ type EventsListParams<T extends IndexEventType = IndexEventType> = {
1488
1552
  assetIdentifier?: string;
1489
1553
  sender?: string;
1490
1554
  recipient?: string;
1555
+ functionName?: string;
1556
+ caller?: string;
1557
+ map?: string;
1558
+ varName?: string;
1559
+ /** Restrict to one transaction. */
1560
+ txId?: string;
1491
1561
  fromHeight?: number;
1492
1562
  toHeight?: number;
1493
1563
  /** Restrict to contracts conforming to a trait/standard (e.g. "sip-010").
@@ -156,6 +156,30 @@ function parseRetryAfter(value) {
156
156
  }
157
157
 
158
158
  // src/base.ts
159
+ class MemoryEtagCache {
160
+ limit;
161
+ entries = new Map;
162
+ constructor(limit = 64) {
163
+ this.limit = limit;
164
+ }
165
+ get(key) {
166
+ const entry = this.entries.get(key);
167
+ if (entry) {
168
+ this.entries.delete(key);
169
+ this.entries.set(key, entry);
170
+ }
171
+ return entry;
172
+ }
173
+ set(key, entry) {
174
+ this.entries.delete(key);
175
+ this.entries.set(key, entry);
176
+ if (this.entries.size > this.limit) {
177
+ const oldest = this.entries.keys().next().value;
178
+ if (oldest !== undefined)
179
+ this.entries.delete(oldest);
180
+ }
181
+ }
182
+ }
159
183
  var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
160
184
  function abortError() {
161
185
  return new DOMException("This operation was aborted", "AbortError");
@@ -266,12 +290,14 @@ class BaseClient {
266
290
  origin;
267
291
  fetchImpl;
268
292
  requestTimeoutMs;
293
+ etagCache;
269
294
  constructor(options = {}) {
270
295
  this.baseUrl = resolveBaseUrl(options.baseUrl);
271
296
  this.apiKey = resolveApiKey(options.apiKey);
272
297
  this.origin = options.origin ?? "cli";
273
298
  this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
274
299
  this.fetchImpl = options.fetchImpl ?? ((...args) => fetch(...args));
300
+ this.etagCache = options.etagCache === false ? null : options.etagCache ?? new MemoryEtagCache;
275
301
  }
276
302
  static authHeaders(apiKey) {
277
303
  const headers = {
@@ -284,11 +310,25 @@ class BaseClient {
284
310
  }
285
311
  async request(method, path, body, opts = {}) {
286
312
  return this.withRequestBudget(opts.signal, async (signal) => {
287
- const response = await this.fetchResponse(method, path, body, signal);
313
+ const cacheKey = method === "GET" && this.etagCache ? `${this.baseUrl}${path}` : null;
314
+ const cached = cacheKey ? await this.etagCache?.get(cacheKey) : undefined;
315
+ const response = await this.fetchResponse(method, path, body, signal, cached ? { "If-None-Match": cached.etag } : undefined);
288
316
  if (response.status === 204) {
289
317
  return;
290
318
  }
291
- return await raceAbort(response.json(), signal);
319
+ if (response.status === 304) {
320
+ if (!cached) {
321
+ throw new ApiError(304, "Not Modified without a cached copy");
322
+ }
323
+ return JSON.parse(cached.body);
324
+ }
325
+ const etag = cacheKey ? response.headers.get("ETag") : null;
326
+ if (!cacheKey || !etag) {
327
+ return await raceAbort(response.json(), signal);
328
+ }
329
+ const text = await raceAbort(response.text(), signal);
330
+ await this.etagCache?.set(cacheKey, { etag, body: text });
331
+ return JSON.parse(text);
292
332
  });
293
333
  }
294
334
  async requestOrNull(method, path, body, opts = {}) {
@@ -331,9 +371,12 @@ class BaseClient {
331
371
  callerSignal?.removeEventListener("abort", forward);
332
372
  }
333
373
  }
334
- async fetchResponse(method, path, body, signal) {
374
+ async fetchResponse(method, path, body, signal, extraHeaders) {
335
375
  const url = `${this.baseUrl}${path}`;
336
- const headers = BaseClient.authHeaders(this.apiKey);
376
+ const headers = {
377
+ ...BaseClient.authHeaders(this.apiKey),
378
+ ...extraHeaders
379
+ };
337
380
  headers["x-sl-origin"] = this.origin;
338
381
  let serializedBody;
339
382
  if (body !== undefined && body !== null) {
@@ -358,6 +401,8 @@ class BaseClient {
358
401
  throw err;
359
402
  throw new ApiError(0, `Cannot reach API at ${this.baseUrl}. Check your connection or try again.`, undefined, undefined, { retryable: true, cause: err instanceof Error ? err : undefined });
360
403
  }
404
+ if (response.status === 304)
405
+ return response;
361
406
  if (!response.ok) {
362
407
  const errorBody = signal ? await raceAbort(response.text(), signal) : await response.text();
363
408
  const envelope = parseErrorEnvelope(errorBody);
@@ -1806,6 +1851,11 @@ class Index extends BaseClient {
1806
1851
  asset_identifier: params.assetIdentifier,
1807
1852
  sender: params.sender,
1808
1853
  recipient: params.recipient,
1854
+ function_name: params.functionName,
1855
+ caller: params.caller,
1856
+ map: params.map,
1857
+ var_name: params.varName,
1858
+ tx_id: params.txId,
1809
1859
  trait: params.trait,
1810
1860
  tx_context: params.txContext ? "true" : undefined
1811
1861
  })}`, undefined, { signal: params.signal });
@@ -2318,6 +2368,7 @@ function createStreamsClient(options) {
2318
2368
  asset_identifier: params.assetIdentifier,
2319
2369
  types: params.types,
2320
2370
  not_types: params.notTypes,
2371
+ clock: params.clock === "vm" ? "vm" : undefined,
2321
2372
  filters: encodeFilters(params.filters)
2322
2373
  })}`);
2323
2374
  }
@@ -2637,5 +2688,5 @@ export {
2637
2688
  Subgraphs
2638
2689
  };
2639
2690
 
2640
- //# debugId=EC50B5FEA0569DC264756E2164756E21
2691
+ //# debugId=455A5D582FCD1C3A64756E2164756E21
2641
2692
  //# sourceMappingURL=index.js.map