@secondlayer/subgraphs 3.21.0 → 3.23.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/src/index.d.ts +238 -17
- package/dist/src/index.js +389 -52
- package/dist/src/index.js.map +12 -11
- package/dist/src/runtime/block-processor.d.ts +111 -10
- package/dist/src/runtime/block-processor.js +263 -31
- package/dist/src/runtime/block-processor.js.map +9 -8
- package/dist/src/runtime/catchup.d.ts +111 -10
- package/dist/src/runtime/catchup.js +263 -31
- package/dist/src/runtime/catchup.js.map +9 -8
- package/dist/src/runtime/context.d.ts +111 -11
- package/dist/src/runtime/context.js +166 -12
- package/dist/src/runtime/context.js.map +7 -6
- package/dist/src/runtime/processor.js +272 -35
- package/dist/src/runtime/processor.js.map +10 -9
- package/dist/src/runtime/reindex.d.ts +111 -10
- package/dist/src/runtime/reindex.js +263 -31
- package/dist/src/runtime/reindex.js.map +9 -8
- package/dist/src/runtime/reorg.d.ts +111 -10
- package/dist/src/runtime/reorg.js +268 -31
- package/dist/src/runtime/reorg.js.map +10 -9
- package/dist/src/runtime/replay.d.ts +3 -3
- package/dist/src/runtime/replay.js +30 -6
- package/dist/src/runtime/replay.js.map +4 -4
- package/dist/src/runtime/runner.d.ts +191 -17
- package/dist/src/runtime/source-matcher.d.ts +68 -8
- package/dist/src/runtime/source-matcher.js +30 -6
- package/dist/src/runtime/source-matcher.js.map +3 -3
- package/dist/src/runtime/stats.d.ts +3 -3
- package/dist/src/schema/index.d.ts +114 -13
- package/dist/src/schema/index.js +133 -20
- package/dist/src/schema/index.js.map +5 -5
- package/dist/src/service.js +272 -35
- package/dist/src/service.js.map +10 -9
- package/dist/src/testing/index.d.ts +667 -0
- package/dist/src/testing/index.js +1463 -0
- package/dist/src/testing/index.js.map +17 -0
- package/dist/src/triggers/index.d.ts +57 -6
- package/dist/src/types.d.ts +112 -11
- package/dist/src/validate.d.ts +129 -11
- package/dist/src/validate.js +117 -18
- package/dist/src/validate.js.map +3 -3
- package/package.json +9 -3
|
@@ -1,3 +1,62 @@
|
|
|
1
|
+
import { UnwrapResponse } from "@secondlayer/stacks/actions";
|
|
2
|
+
import { AbiContract, ExtractFunctionArgs, ExtractFunctionOutput, ExtractReadOnlyFunctions, ToCamelCase } from "@secondlayer/stacks/clarity";
|
|
3
|
+
/**
|
|
4
|
+
* How long a read stays valid.
|
|
5
|
+
*
|
|
6
|
+
* - `per-block` (default): pinned to the block being processed, cached under
|
|
7
|
+
* its `index_block_hash`. Sound for any read-only function — reindexing the
|
|
8
|
+
* same block always returns the same value, and a reorg-replaced block has a
|
|
9
|
+
* different id so it can't inherit the orphaned answer.
|
|
10
|
+
* - `contract-constant`: the caller asserts the value cannot change for this
|
|
11
|
+
* contract and these args (SIP-010 `get-decimals`/`get-symbol`), so it is
|
|
12
|
+
* resolved ONCE instead of once per block. This is the mode that makes a
|
|
13
|
+
* backfill affordable — under `per-block` every block is a distinct key and
|
|
14
|
+
* therefore a distinct RPC. Declaring it on a value that does change pins the
|
|
15
|
+
* first answer forever, so it is opt-in and never inferred.
|
|
16
|
+
*/
|
|
17
|
+
type ChainReadCacheMode = "per-block" | "contract-constant";
|
|
18
|
+
type ChainReadOptions = {
|
|
19
|
+
cache?: ChainReadCacheMode
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The chain-read surface, fully typed by an ABI.
|
|
23
|
+
*
|
|
24
|
+
* Shaped like `getContract` from `@secondlayer/stacks` — the same ABI in, the
|
|
25
|
+
* same camelCased read methods out — minus `call`/`buildCall`: a handler
|
|
26
|
+
* indexes the chain, it never writes to it.
|
|
27
|
+
*
|
|
28
|
+
* Reached through {@link readContractAt} rather than off `ctx.client`
|
|
29
|
+
* directly: an ABI-generic method living on the context interface makes every
|
|
30
|
+
* structural comparison of a subgraph definition instantiate it, which exceeds
|
|
31
|
+
* TS's depth budget. At a call site the ABI is already concrete and it costs
|
|
32
|
+
* nothing.
|
|
33
|
+
*/
|
|
34
|
+
interface ChainReadClient {
|
|
35
|
+
/**
|
|
36
|
+
* Read-only methods of a contract, evaluated against the exact block being
|
|
37
|
+
* processed.
|
|
38
|
+
*
|
|
39
|
+
* Every call is pinned to the block's `index_block_hash`, so a handler is a
|
|
40
|
+
* pure function of its block: reindexing a range produces byte-identical
|
|
41
|
+
* rows. Reading at the node's tip instead would make the same reindex
|
|
42
|
+
* return different values — that is the corruption this refuses to allow,
|
|
43
|
+
* so a block whose id was never persisted throws rather than falling back.
|
|
44
|
+
*
|
|
45
|
+
* Results are memoized in Postgres, so a value is fetched once per block
|
|
46
|
+
* rather than once per event (or once per contract with
|
|
47
|
+
* `cache: "contract-constant"`).
|
|
48
|
+
*/
|
|
49
|
+
contract<const C extends AbiContract>(contractId: string, abi: C, options?: ChainReadOptions): {
|
|
50
|
+
read: ChainReadMethods<C>
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The camelCased read methods of an ABI. Spelled out rather than reused from
|
|
55
|
+
* `ContractInstance<C>["read"]`: that type is a conditional over the codegen
|
|
56
|
+
* brand, and evaluating it wherever a subgraph definition is structurally
|
|
57
|
+
* compared pushes TS past its instantiation depth.
|
|
58
|
+
*/
|
|
59
|
+
type ChainReadMethods<C extends AbiContract> = { [N in ExtractReadOnlyFunctions<C> as ToCamelCase<N>] : (args: ExtractFunctionArgs<C, N>) => Promise<UnwrapResponse<ExtractFunctionOutput<C, N>>> };
|
|
1
60
|
/** Supported column types for subgraph schemas */
|
|
2
61
|
type ColumnType = "text" | "uint" | "int" | "principal" | "boolean" | "timestamp" | "jsonb";
|
|
3
62
|
/** Column definition in a subgraph table */
|
|
@@ -24,15 +83,24 @@ interface SubgraphRelation {
|
|
|
24
83
|
/** Target column(s) the fields point at (a uniqueKeys entry on the target). */
|
|
25
84
|
referencedColumns: string[];
|
|
26
85
|
}
|
|
27
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Table definition within a subgraph schema.
|
|
88
|
+
*
|
|
89
|
+
* The array fields are `readonly` so a schema literal can be hoisted out of
|
|
90
|
+
* the `defineSubgraph()` call with `as const` (or {@link defineSchema}) and
|
|
91
|
+
* still typecheck — `as const` produces `readonly [readonly ["holder"]]`,
|
|
92
|
+
* which a mutable `string[][]` rejects. Without that, extracting a helper
|
|
93
|
+
* that takes `ctx` meant losing the entire typed surface, which is why the
|
|
94
|
+
* shipped starter reached for `ctx: any`.
|
|
95
|
+
*/
|
|
28
96
|
interface SubgraphTable {
|
|
29
97
|
columns: Record<string, SubgraphColumn>;
|
|
30
98
|
/** Composite indexes (each entry is an array of column names) */
|
|
31
|
-
indexes?: string[][];
|
|
99
|
+
indexes?: readonly (readonly string[])[];
|
|
32
100
|
/** Unique key constraints (each entry is an array of column names). Required for upsert. */
|
|
33
|
-
uniqueKeys?: string[][];
|
|
101
|
+
uniqueKeys?: readonly (readonly string[])[];
|
|
34
102
|
/** Foreign-key relations to other tables (for typed ORM joins). */
|
|
35
|
-
relations?: SubgraphRelation[];
|
|
103
|
+
relations?: readonly SubgraphRelation[];
|
|
36
104
|
}
|
|
37
105
|
/** Subgraph schema — maps table names to table definitions */
|
|
38
106
|
type SubgraphSchema = Record<string, SubgraphTable>;
|
|
@@ -40,12 +108,19 @@ type SubgraphSchema = Record<string, SubgraphTable>;
|
|
|
40
108
|
type RowValue = string | number | bigint | boolean | null | undefined | Record<string, unknown> | unknown[];
|
|
41
109
|
/** Value or computed function that receives existing row */
|
|
42
110
|
type ComputedValue = RowValue | ((existing: Record<string, unknown> | null) => unknown);
|
|
43
|
-
import { Database } from "@secondlayer/shared/db";
|
|
44
|
-
import { Kysely, Transaction } from "kysely";
|
|
45
|
-
type AnyDb =
|
|
111
|
+
import { Database as Database2 } from "@secondlayer/shared/db";
|
|
112
|
+
import { Kysely as Kysely2, Transaction } from "kysely";
|
|
113
|
+
type AnyDb = Kysely2<Database2> | Transaction<Database2>;
|
|
46
114
|
/** Reorg journal entries older than this many blocks are prunable — far past
|
|
47
115
|
* Stacks finality (observed reorg depth is single digits). */
|
|
48
116
|
declare const JOURNAL_RETENTION_BLOCKS = 300;
|
|
117
|
+
interface WriteOp {
|
|
118
|
+
kind: "insert" | "update" | "delete" | "increment";
|
|
119
|
+
table: string;
|
|
120
|
+
data: Record<string, unknown>;
|
|
121
|
+
/** For update: SET clause. For increment: column → signed delta. */
|
|
122
|
+
set?: Record<string, unknown>;
|
|
123
|
+
}
|
|
49
124
|
interface FlushWrite {
|
|
50
125
|
op: "insert" | "update" | "delete";
|
|
51
126
|
table: string;
|
|
@@ -67,6 +142,13 @@ interface BlockMeta {
|
|
|
67
142
|
hash: string;
|
|
68
143
|
timestamp: number;
|
|
69
144
|
burnBlockHeight: number;
|
|
145
|
+
/**
|
|
146
|
+
* Nakamoto StacksBlockId — the identifier `?tip=` accepts, and the only way
|
|
147
|
+
* to pin a read-only call to THIS block. Null on rows ingested before it
|
|
148
|
+
* was persisted; `ctx.client.readOnly` refuses to run rather than silently
|
|
149
|
+
* reading at the node's tip.
|
|
150
|
+
*/
|
|
151
|
+
indexBlockHash?: string | null;
|
|
70
152
|
}
|
|
71
153
|
interface TxMeta2 {
|
|
72
154
|
txId: string;
|
|
@@ -92,7 +174,10 @@ declare class SubgraphContext2 {
|
|
|
92
174
|
private readonly db;
|
|
93
175
|
private readonly pgSchemaName;
|
|
94
176
|
private readonly subgraphSchema;
|
|
95
|
-
|
|
177
|
+
/** Pending writes for this block. `protected` so the in-memory test
|
|
178
|
+
* context (`@secondlayer/subgraphs/testing`) can materialize them through
|
|
179
|
+
* the SAME overlay this class uses — never a second implementation. */
|
|
180
|
+
protected readonly ops: WriteOp[];
|
|
96
181
|
/**
|
|
97
182
|
* BYO data plane: handler writes land in a user-owned DB whose flush can't
|
|
98
183
|
* share the managed block transaction, so a crash replays the block. When
|
|
@@ -108,7 +193,15 @@ declare class SubgraphContext2 {
|
|
|
108
193
|
* journal would just be churn the pruner deletes.
|
|
109
194
|
*/
|
|
110
195
|
private readonly journal;
|
|
196
|
+
/** Memoized per block; see the `client` getter. */
|
|
197
|
+
protected _client?: ChainReadClient;
|
|
111
198
|
constructor(db: AnyDb, pgSchemaName: string, subgraphSchema: SubgraphSchema, block: BlockMeta, tx: TxMeta2, byo?: boolean, journal?: boolean);
|
|
199
|
+
/**
|
|
200
|
+
* Chain reads pinned to THIS block. Lazily built so a handler that never
|
|
201
|
+
* reads never touches the node config, and so the in-memory test context
|
|
202
|
+
* can override it without a node.
|
|
203
|
+
*/
|
|
204
|
+
get client(): ChainReadClient;
|
|
112
205
|
get tx(): TxMeta2;
|
|
113
206
|
/** Update the current transaction context (used by runner between events) */
|
|
114
207
|
setTx(tx: TxMeta2): void;
|
|
@@ -143,6 +236,13 @@ declare class SubgraphContext2 {
|
|
|
143
236
|
patchOrInsert(table: string, key: Record<string, unknown>, row: Record<string, ComputedValue>): Promise<void>;
|
|
144
237
|
/** Format a bigint amount with decimal places */
|
|
145
238
|
formatUnits(value: bigint, decimals: number): string;
|
|
239
|
+
/**
|
|
240
|
+
* Committed rows matching `where`, BEFORE the pending-ops overlay. The one
|
|
241
|
+
* seam a different backing store replaces (the in-memory test context);
|
|
242
|
+
* everything above it — validation, overlay, op queueing, flush semantics
|
|
243
|
+
* — stays here so the two can't diverge.
|
|
244
|
+
*/
|
|
245
|
+
protected readRows(table: string, where: Record<string, unknown>, limit?: number): Promise<Record<string, unknown>[]>;
|
|
146
246
|
findOne(table: string, where: Record<string, unknown>): Promise<Record<string, unknown> | null>;
|
|
147
247
|
findMany(table: string, where: Record<string, unknown>): Promise<Record<string, unknown>[]>;
|
|
148
248
|
/**
|
|
@@ -152,10 +252,10 @@ declare class SubgraphContext2 {
|
|
|
152
252
|
* apply by where-match. Overlaid rows synthesized from pending inserts
|
|
153
253
|
* lack DB-generated columns (_id, _created_at).
|
|
154
254
|
*/
|
|
155
|
-
|
|
156
|
-
|
|
255
|
+
protected overlayOne(table: string, where: Record<string, unknown>, dbRow: Record<string, unknown> | null): Record<string, unknown> | null;
|
|
256
|
+
protected overlayMany(table: string, where: Record<string, unknown>, dbRows: Record<string, unknown>[]): Record<string, unknown>[];
|
|
157
257
|
/** Apply one pending op to a candidate row state (null = row absent). */
|
|
158
|
-
|
|
258
|
+
protected applyOpToRow(op: WriteOp, row: Record<string, unknown> | null, where: Record<string, unknown>): Record<string, unknown> | null;
|
|
159
259
|
count(table: string, where?: Record<string, unknown>): Promise<number>;
|
|
160
260
|
sum(table: string, column: string, where?: Record<string, unknown>): Promise<bigint>;
|
|
161
261
|
min(table: string, column: string, where?: Record<string, unknown>): Promise<bigint | null>;
|
|
@@ -100,6 +100,18 @@ function emitJournalDDL(schemaName) {
|
|
|
100
100
|
`CREATE INDEX IF NOT EXISTS idx_${schemaName}_journal_height ON ${schemaName}._journal (block_height)`
|
|
101
101
|
];
|
|
102
102
|
}
|
|
103
|
+
function emitFactoryDDL(schemaName) {
|
|
104
|
+
return [
|
|
105
|
+
`CREATE TABLE IF NOT EXISTS ${schemaName}._factory_addresses (
|
|
106
|
+
source_name TEXT NOT NULL,
|
|
107
|
+
address TEXT NOT NULL,
|
|
108
|
+
block_height BIGINT NOT NULL,
|
|
109
|
+
_created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
110
|
+
PRIMARY KEY (source_name, address)
|
|
111
|
+
)`,
|
|
112
|
+
`CREATE INDEX IF NOT EXISTS idx_${schemaName}_factory_height ON ${schemaName}._factory_addresses (block_height)`
|
|
113
|
+
];
|
|
114
|
+
}
|
|
103
115
|
function emitForeignKeyDDL(schemaName, tableName, tableDef) {
|
|
104
116
|
return (tableDef.relations ?? []).map((rel) => {
|
|
105
117
|
const constraintName = `fk_${schemaName}_${tableName}_${rel.name}`;
|
|
@@ -118,6 +130,9 @@ function generateSubgraphSQL(def, schemaNameOverride) {
|
|
|
118
130
|
statements.push(...emitTableDDL(schemaName, tableName, tableDef));
|
|
119
131
|
}
|
|
120
132
|
statements.push(...emitJournalDDL(schemaName));
|
|
133
|
+
if (Object.values(def.sources ?? {}).some((src) => src.factory !== undefined)) {
|
|
134
|
+
statements.push(...emitFactoryDDL(schemaName));
|
|
135
|
+
}
|
|
121
136
|
for (const [tableName, tableDef] of Object.entries(def.schema)) {
|
|
122
137
|
statements.push(...emitForeignKeyDDL(schemaName, tableName, tableDef));
|
|
123
138
|
}
|
|
@@ -130,6 +145,139 @@ function generateSubgraphSQL(def, schemaNameOverride) {
|
|
|
130
145
|
return { statements, hash };
|
|
131
146
|
}
|
|
132
147
|
|
|
148
|
+
// src/runtime/chain-read.ts
|
|
149
|
+
import { getSourceDb } from "@secondlayer/shared/db";
|
|
150
|
+
import { http, createPublicClient } from "@secondlayer/stacks";
|
|
151
|
+
import {
|
|
152
|
+
ContractResponseError,
|
|
153
|
+
buildFunctionArgs,
|
|
154
|
+
isResponseOutput,
|
|
155
|
+
readContract
|
|
156
|
+
} from "@secondlayer/stacks/actions";
|
|
157
|
+
import {
|
|
158
|
+
clarityValueToJSUntyped,
|
|
159
|
+
deserializeCV,
|
|
160
|
+
normalizeAbi,
|
|
161
|
+
serializeCV,
|
|
162
|
+
toCamelCase
|
|
163
|
+
} from "@secondlayer/stacks/clarity";
|
|
164
|
+
function readContractAt(ctx, contractId, abi, options) {
|
|
165
|
+
return ctx.client.contract(contractId, abi, options);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
class ChainReadError extends Error {
|
|
169
|
+
name = "ChainReadError";
|
|
170
|
+
}
|
|
171
|
+
function nodeClient(rpcUrl) {
|
|
172
|
+
return createPublicClient({
|
|
173
|
+
transport: http(rpcUrl, { retryCount: 0, timeout: 1e4 })
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
class ConcurrencyGate {
|
|
178
|
+
limit;
|
|
179
|
+
active = 0;
|
|
180
|
+
queue = [];
|
|
181
|
+
constructor(limit) {
|
|
182
|
+
this.limit = limit;
|
|
183
|
+
}
|
|
184
|
+
async run(fn) {
|
|
185
|
+
if (this.active >= this.limit) {
|
|
186
|
+
await new Promise((resolve) => this.queue.push(resolve));
|
|
187
|
+
}
|
|
188
|
+
this.active++;
|
|
189
|
+
try {
|
|
190
|
+
return await fn();
|
|
191
|
+
} finally {
|
|
192
|
+
this.active--;
|
|
193
|
+
this.queue.shift()?.();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
var DEFAULT_MAX_CONCURRENT_READS = 4;
|
|
198
|
+
function maxConcurrentReads() {
|
|
199
|
+
const raw = process.env.SUBGRAPH_CHAIN_READ_CONCURRENCY;
|
|
200
|
+
const parsed = raw ? Number.parseInt(raw, 10) : Number.NaN;
|
|
201
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_CONCURRENT_READS;
|
|
202
|
+
}
|
|
203
|
+
var gate = new ConcurrencyGate(maxConcurrentReads());
|
|
204
|
+
function hashArgs(args) {
|
|
205
|
+
const serialized = args.map((arg) => serializeCV(arg)).join(",");
|
|
206
|
+
return Bun.hash(serialized).toString(16);
|
|
207
|
+
}
|
|
208
|
+
function findFunction(abi, name) {
|
|
209
|
+
const fn = abi.functions.find((f) => f.name === name);
|
|
210
|
+
if (!fn) {
|
|
211
|
+
throw new ChainReadError(`No function "${name}" in the supplied ABI.`);
|
|
212
|
+
}
|
|
213
|
+
if (fn.access !== "read-only") {
|
|
214
|
+
throw new ChainReadError(`"${name}" is ${fn.access}, not read-only — a handler cannot call it.`);
|
|
215
|
+
}
|
|
216
|
+
return fn;
|
|
217
|
+
}
|
|
218
|
+
function createChainReadClient(ctx) {
|
|
219
|
+
const readValue = async (contractId, abi, functionName, args, options) => {
|
|
220
|
+
const rpcUrl = ctx.rpcUrl ?? process.env.STACKS_NODE_RPC_URL;
|
|
221
|
+
if (!rpcUrl) {
|
|
222
|
+
throw new ChainReadError("ctx.client needs a Stacks node: set STACKS_NODE_RPC_URL on the subgraph processor.");
|
|
223
|
+
}
|
|
224
|
+
const constant = options.cache === "contract-constant";
|
|
225
|
+
if (!constant && !ctx.indexBlockHash) {
|
|
226
|
+
throw new ChainReadError(`Block ${ctx.blockHeight} has no index_block_hash, so a read cannot be pinned to it. Reading at the node's tip would make this handler non-deterministic (the same reindex would produce different rows). Re-ingest the block, or pass cache: "contract-constant" if the value genuinely cannot change.`);
|
|
227
|
+
}
|
|
228
|
+
const fn = findFunction(abi, functionName);
|
|
229
|
+
const clarityArgs = buildFunctionArgs(fn, args);
|
|
230
|
+
const db = ctx.db ?? getSourceDb();
|
|
231
|
+
const argsHash = hashArgs(clarityArgs);
|
|
232
|
+
const pinnedTo = constant ? null : ctx.indexBlockHash;
|
|
233
|
+
const cached = await db.selectFrom("chain_read_cache").select("result_hex").where("contract_id", "=", contractId).where("function_name", "=", fn.name).where("args_hash", "=", argsHash).where((eb) => pinnedTo === null ? eb("index_block_hash", "is", null) : eb("index_block_hash", "=", pinnedTo)).executeTakeFirst();
|
|
234
|
+
const resultHex = cached?.result_hex ?? await gate.run(async () => {
|
|
235
|
+
const value2 = await readContract(nodeClient(rpcUrl), {
|
|
236
|
+
contract: contractId,
|
|
237
|
+
functionName: fn.name,
|
|
238
|
+
args: clarityArgs,
|
|
239
|
+
tip: ctx.indexBlockHash ?? undefined
|
|
240
|
+
});
|
|
241
|
+
return serializeCV(value2);
|
|
242
|
+
});
|
|
243
|
+
if (!cached) {
|
|
244
|
+
await db.insertInto("chain_read_cache").values({
|
|
245
|
+
contract_id: contractId,
|
|
246
|
+
function_name: fn.name,
|
|
247
|
+
args_hash: argsHash,
|
|
248
|
+
index_block_hash: pinnedTo,
|
|
249
|
+
block_height: constant ? null : ctx.blockHeight,
|
|
250
|
+
result_hex: resultHex
|
|
251
|
+
}).onConflict((oc) => oc.doNothing()).execute();
|
|
252
|
+
}
|
|
253
|
+
const value = deserializeCV(resultHex);
|
|
254
|
+
const js = clarityValueToJSUntyped(fn.outputs, value);
|
|
255
|
+
if (isResponseOutput(fn.outputs)) {
|
|
256
|
+
const response = js;
|
|
257
|
+
if ("ok" in response)
|
|
258
|
+
return response.ok;
|
|
259
|
+
throw new ContractResponseError(`${contractId}::${fn.name} returned an err response.`, response.err);
|
|
260
|
+
}
|
|
261
|
+
return js;
|
|
262
|
+
};
|
|
263
|
+
return {
|
|
264
|
+
contract(contractId, rawAbi, options = {}) {
|
|
265
|
+
const abi = normalizeAbi(rawAbi);
|
|
266
|
+
const camelToKebab = new Map;
|
|
267
|
+
for (const fn of abi.functions) {
|
|
268
|
+
camelToKebab.set(toCamelCase(fn.name), fn.name);
|
|
269
|
+
}
|
|
270
|
+
const read = new Proxy({}, {
|
|
271
|
+
get(_target, prop) {
|
|
272
|
+
const fnName = camelToKebab.get(prop) ?? prop;
|
|
273
|
+
return (args = {}) => readValue(contractId, abi, fnName, args, options);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
return { read };
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
133
281
|
// src/runtime/context.ts
|
|
134
282
|
var JOURNAL_RETENTION_BLOCKS = 300;
|
|
135
283
|
var journalEnsured = new Set;
|
|
@@ -148,6 +296,7 @@ class SubgraphContext {
|
|
|
148
296
|
ops = [];
|
|
149
297
|
byo;
|
|
150
298
|
journal;
|
|
299
|
+
_client;
|
|
151
300
|
constructor(db, pgSchemaName2, subgraphSchema, block, tx, byo = false, journal = false) {
|
|
152
301
|
this.db = db;
|
|
153
302
|
this.pgSchemaName = pgSchemaName2;
|
|
@@ -157,6 +306,13 @@ class SubgraphContext {
|
|
|
157
306
|
this.byo = byo;
|
|
158
307
|
this.journal = journal;
|
|
159
308
|
}
|
|
309
|
+
get client() {
|
|
310
|
+
this._client ??= createChainReadClient({
|
|
311
|
+
blockHeight: this.block.height,
|
|
312
|
+
indexBlockHash: this.block.indexBlockHash
|
|
313
|
+
});
|
|
314
|
+
return this._client;
|
|
315
|
+
}
|
|
160
316
|
get tx() {
|
|
161
317
|
return this._tx;
|
|
162
318
|
}
|
|
@@ -262,23 +418,21 @@ class SubgraphContext {
|
|
|
262
418
|
formatUnits(value, decimals) {
|
|
263
419
|
return formatUnits(value, decimals);
|
|
264
420
|
}
|
|
265
|
-
async
|
|
266
|
-
this.validateTable(table);
|
|
421
|
+
async readRows(table, where, limit) {
|
|
267
422
|
const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
|
|
268
423
|
const { clause } = buildWhereClause(where);
|
|
269
|
-
const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause} LIMIT
|
|
424
|
+
const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause}${limit !== undefined ? ` LIMIT ${limit}` : ""}`;
|
|
270
425
|
const { rows } = await sql.raw(query).execute(this.db);
|
|
271
|
-
|
|
272
|
-
|
|
426
|
+
return rows.map((r) => this.coerceRow(table, r));
|
|
427
|
+
}
|
|
428
|
+
async findOne(table, where) {
|
|
429
|
+
this.validateTable(table);
|
|
430
|
+
const row = (await this.readRows(table, where, 1))[0] ?? null;
|
|
431
|
+
return this.overlayOne(table, where, row);
|
|
273
432
|
}
|
|
274
433
|
async findMany(table, where) {
|
|
275
434
|
this.validateTable(table);
|
|
276
|
-
|
|
277
|
-
const { clause } = buildWhereClause(where);
|
|
278
|
-
const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause}`;
|
|
279
|
-
const { rows } = await sql.raw(query).execute(this.db);
|
|
280
|
-
const dbRows = rows.map((r) => this.coerceRow(table, r));
|
|
281
|
-
return this.overlayMany(table, where, dbRows);
|
|
435
|
+
return this.overlayMany(table, where, await this.readRows(table, where));
|
|
282
436
|
}
|
|
283
437
|
overlayOne(table, where, dbRow) {
|
|
284
438
|
if (this.ops.length === 0)
|
|
@@ -723,5 +877,5 @@ export {
|
|
|
723
877
|
JOURNAL_RETENTION_BLOCKS
|
|
724
878
|
};
|
|
725
879
|
|
|
726
|
-
//# debugId=
|
|
880
|
+
//# debugId=943E34F234EEBFC564756E2164756E21
|
|
727
881
|
//# sourceMappingURL=context.js.map
|