@secondlayer/sdk 6.40.1 → 6.41.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 ADDED
@@ -0,0 +1,107 @@
1
+ # @secondlayer/sdk — notes for coding agents
2
+
3
+ Five facts that are load-bearing. Everything else you can infer from the types;
4
+ these you cannot, and getting them wrong produces code that looks correct and
5
+ silently loses or corrupts data.
6
+
7
+ ## 1. Reads need no key
8
+
9
+ `/v1/index/*` and the public subgraph reads are open. Construct the client with
10
+ no credentials and start reading:
11
+
12
+ ```ts
13
+ import { Index } from "@secondlayer/sdk";
14
+
15
+ const sl = new Index();
16
+ const { events } = await sl.events.list({ eventType: "ft_transfer" });
17
+ ```
18
+
19
+ Keys are for Streams, writes, and higher rate limits. Do not invent an
20
+ `apiKey` requirement or send a placeholder — an empty Bearer is worse than none.
21
+
22
+ ## 2. Cursors are opaque
23
+
24
+ A cursor is `"<block_height>:<event_index>"`, and that is an implementation
25
+ detail you may read but must never construct, parse into, or compare
26
+ arithmetically. Pass back exactly what you were handed:
27
+
28
+ ```ts
29
+ let cursor: string | null = null;
30
+ for (;;) {
31
+ const page = await sl.events.list({ eventType: "ft_transfer", cursor });
32
+ if (page.events.length === 0) break;
33
+ cursor = page.next_cursor; // ✓
34
+ // cursor = `${lastHeight}:0`; // ✗ — skips events, silently
35
+ }
36
+ ```
37
+
38
+ `Cursor.atHeight(h)` exists for "start at the foot of block h"; use it rather
39
+ than hand-rolling a sentinel.
40
+
41
+ ## 3. Reorg rollback is INCLUSIVE
42
+
43
+ `onReorg` hands you `reorg.fork_point_height`. Delete rows at
44
+ `_block_height >= forkHeight`, not `>`. The fork block itself is replaced, so an
45
+ exclusive delete leaves exactly one block of orphaned rows behind — the hardest
46
+ kind of corruption to notice, because everything downstream still looks
47
+ consistent.
48
+
49
+ ```ts
50
+ onReorg: async (reorg, ctx) => {
51
+ await tx.deleteFrom("my_rows").where("height", ">=", reorg.fork_point_height).execute();
52
+ await saveCursor(tx, ctx.cursor);
53
+ },
54
+ ```
55
+
56
+ ## 4. Rows and the cursor commit in ONE transaction
57
+
58
+ If they commit separately, a crash between them either replays a batch (double
59
+ counts) or skips it (missing rows). There is no ordering that avoids both.
60
+
61
+ Prefer a sink, which owns this by construction — you write rows, it commits them
62
+ with the checkpoint and handles the rollback:
63
+
64
+ ```ts
65
+ import { kyselySink } from "@secondlayer/sdk/sinks/kysely";
66
+
67
+ await sl.streams.events.consume({
68
+ sink: kyselySink(db, { id: "my-indexer", tables: ["transfers"], height: "height" }),
69
+ onBatch: async (events, _envelope, ctx) => {
70
+ for (const e of events) await ctx.tx.insertInto("transfers").values(...).execute();
71
+ },
72
+ });
73
+ ```
74
+
75
+ Hand-rolling it is allowed, but the checkpoint write must be inside the same
76
+ transaction as the row writes.
77
+
78
+ ## 5. `walk()` is not reorg-safe
79
+
80
+ `walk()` iterates a fixed range for backfills and analysis. It does not surface
81
+ reorgs and does not rewind. Anything writing durable state off the tip belongs
82
+ in `consume()` with `onReorg` (or a sink). Use `walk()` for history that is
83
+ already final.
84
+
85
+ ---
86
+
87
+ ## Shapes worth knowing
88
+
89
+ - **`decoded: true`** on `streams.events.consume` delivers the flat,
90
+ `event_type`-discriminated rows Index serves, so Streams and Index handlers
91
+ read identically. Prefer it over the individual `is*`/`decode*` helpers.
92
+ - **Labelled filters**: `filters: { <label>: {...} }` plus `on: { <label>: handler }`
93
+ runs two unrelated concerns through one loop, one cursor, one checkpoint. Each
94
+ label's declared `types` narrows its handler.
95
+ - **Field selection**: `fields: ["recipient", "amount"]` on Index reads projects
96
+ the SELECT server-side; unrequested columns are absent from the row *and* the
97
+ type. `cursor`, `block_height`, and `event_type` always come back.
98
+ - **Errors**: everything derives from `SecondLayerError` — `code`, `retryable`,
99
+ `retryAfterSeconds`, `docsUrl`, and `walk(predicate)` to find a cause. Retries
100
+ are built in (`retryCount`/`retryDelay`); `onError` is a void observer, not a
101
+ retry decision.
102
+
103
+ ## More
104
+
105
+ - Docs, full text: <https://secondlayer.tools/llms-full.txt>
106
+ - Any page as markdown: append `.md` (e.g. `/docs/streams.md`)
107
+ - Deeper agent skill: `bunx skills add ryanwaits/secondlayer`
package/dist/index.d.ts CHANGED
@@ -1375,7 +1375,7 @@ type EventsWalkParams<T extends IndexEventType = IndexEventType> = Omit<EventsLi
1375
1375
  type EventsConsumeParams<
1376
1376
  T extends IndexEventType = IndexEventType,
1377
1377
  TTx = never
1378
- > = Omit<EventsListParams<T>, "cursor" | "fromCursor" | "limit"> & IndexConsumeOptions<IndexEventOf<T>, EventsEnvelope<T>, TTx>;
1378
+ > = Omit<EventsListParams<T>, "cursor" | "fromCursor" | "limit" | "fields"> & IndexConsumeOptions<IndexEventOf<T>, EventsEnvelope<T>, TTx>;
1379
1379
  type IndexContractCall = {
1380
1380
  cursor: string
1381
1381
  block_height: number
@@ -2066,6 +2066,14 @@ interface NftTransfersResource {
2066
2066
  }
2067
2067
  /** `index.events` — callable shorthand for `.list()`; `eventType` is required. */
2068
2068
  interface IndexEventsResource {
2069
+ /** Narrowing overload for the callable shorthand — same wire path as
2070
+ * `list`, so it must narrow identically. */
2071
+ <
2072
+ T extends IndexEventType,
2073
+ const F extends keyof IndexEventOf<T> & string
2074
+ >(params: EventsListParams<T> & {
2075
+ fields: readonly F[]
2076
+ }): Promise<EventsEnvelope<T, IndexEventFields<T, F>>>;
2069
2077
  <T extends IndexEventType>(params: EventsListParams<T>): Promise<EventsEnvelope<T>>;
2070
2078
  /** Narrowing overload: `fields` shrinks the returned row type to exactly
2071
2079
  * the requested columns plus {@link IndexAlwaysFields}. `const F` means no
@@ -2077,6 +2085,15 @@ interface IndexEventsResource {
2077
2085
  fields: readonly F[]
2078
2086
  }): Promise<EventsEnvelope<T, IndexEventFields<T, F>>>;
2079
2087
  list<T extends IndexEventType>(params: EventsListParams<T>): Promise<EventsEnvelope<T>>;
2088
+ /** Narrowing overload, matching `list`: `walk` forwards `fields` to the
2089
+ * wire, so without this the yielded rows are stripped while the type
2090
+ * still promises every column. */
2091
+ walk<
2092
+ T extends IndexEventType,
2093
+ const F extends keyof IndexEventOf<T> & string
2094
+ >(params: EventsWalkParams<T> & {
2095
+ fields: readonly F[]
2096
+ }): AsyncIterable<IndexEventFields<T, F>>;
2080
2097
  walk<T extends IndexEventType>(params: EventsWalkParams<T>): AsyncIterable<IndexEventOf<T>>;
2081
2098
  consume<
2082
2099
  T extends IndexEventType,