@secondlayer/subgraphs 3.2.1 → 3.3.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/README.md +81 -0
- package/dist/src/index.d.ts +71 -9
- package/dist/src/index.js +461 -54
- package/dist/src/index.js.map +11 -9
- package/dist/src/runtime/block-processor.d.ts +37 -9
- package/dist/src/runtime/block-processor.js +127 -37
- package/dist/src/runtime/block-processor.js.map +5 -5
- package/dist/src/runtime/catchup.d.ts +34 -8
- package/dist/src/runtime/catchup.js +125 -36
- package/dist/src/runtime/catchup.js.map +5 -5
- package/dist/src/runtime/context.d.ts +27 -1
- package/dist/src/runtime/context.js +13 -2
- package/dist/src/runtime/context.js.map +3 -3
- package/dist/src/runtime/processor.js +143 -39
- package/dist/src/runtime/processor.js.map +8 -8
- package/dist/src/runtime/reindex.d.ts +34 -8
- package/dist/src/runtime/reindex.js +131 -36
- package/dist/src/runtime/reindex.js.map +6 -6
- package/dist/src/runtime/reorg.d.ts +34 -8
- package/dist/src/runtime/reorg.js +131 -39
- package/dist/src/runtime/reorg.js.map +6 -6
- package/dist/src/runtime/runner.d.ts +43 -9
- package/dist/src/runtime/source-matcher.d.ts +19 -10
- package/dist/src/runtime/source-matcher.js +26 -6
- package/dist/src/runtime/source-matcher.js.map +3 -3
- package/dist/src/schema/index.d.ts +42 -8
- package/dist/src/schema/index.js +57 -19
- package/dist/src/schema/index.js.map +5 -5
- package/dist/src/service.js +143 -39
- package/dist/src/service.js.map +8 -8
- package/dist/src/triggers/index.d.ts +16 -8
- package/dist/src/types.d.ts +35 -9
- package/dist/src/validate.d.ts +34 -8
- package/dist/src/validate.js +10 -3
- package/dist/src/validate.js.map +3 -3
- package/package.json +3 -3
|
@@ -24,45 +24,53 @@ interface StxLockFilter {
|
|
|
24
24
|
lockedAddress?: string;
|
|
25
25
|
minAmount?: bigint;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Restrict a source to contracts conforming to a trait/standard (e.g. "sip-010")
|
|
29
|
+
* instead of a fixed contract — resolved from the contract registry at match time,
|
|
30
|
+
* as-of each processed block. Lets a source index "all SIP-010 tokens" etc.
|
|
31
|
+
*/
|
|
32
|
+
type TraitScope = {
|
|
33
|
+
trait?: string
|
|
34
|
+
};
|
|
27
35
|
/** FT event filters */
|
|
28
|
-
interface FtTransferFilter {
|
|
36
|
+
interface FtTransferFilter extends TraitScope {
|
|
29
37
|
type: "ft_transfer";
|
|
30
38
|
assetIdentifier?: string;
|
|
31
39
|
sender?: string;
|
|
32
40
|
recipient?: string;
|
|
33
41
|
minAmount?: bigint;
|
|
34
42
|
}
|
|
35
|
-
interface FtMintFilter {
|
|
43
|
+
interface FtMintFilter extends TraitScope {
|
|
36
44
|
type: "ft_mint";
|
|
37
45
|
assetIdentifier?: string;
|
|
38
46
|
recipient?: string;
|
|
39
47
|
minAmount?: bigint;
|
|
40
48
|
}
|
|
41
|
-
interface FtBurnFilter {
|
|
49
|
+
interface FtBurnFilter extends TraitScope {
|
|
42
50
|
type: "ft_burn";
|
|
43
51
|
assetIdentifier?: string;
|
|
44
52
|
sender?: string;
|
|
45
53
|
minAmount?: bigint;
|
|
46
54
|
}
|
|
47
55
|
/** NFT event filters */
|
|
48
|
-
interface NftTransferFilter {
|
|
56
|
+
interface NftTransferFilter extends TraitScope {
|
|
49
57
|
type: "nft_transfer";
|
|
50
58
|
assetIdentifier?: string;
|
|
51
59
|
sender?: string;
|
|
52
60
|
recipient?: string;
|
|
53
61
|
}
|
|
54
|
-
interface NftMintFilter {
|
|
62
|
+
interface NftMintFilter extends TraitScope {
|
|
55
63
|
type: "nft_mint";
|
|
56
64
|
assetIdentifier?: string;
|
|
57
65
|
recipient?: string;
|
|
58
66
|
}
|
|
59
|
-
interface NftBurnFilter {
|
|
67
|
+
interface NftBurnFilter extends TraitScope {
|
|
60
68
|
type: "nft_burn";
|
|
61
69
|
assetIdentifier?: string;
|
|
62
70
|
sender?: string;
|
|
63
71
|
}
|
|
64
72
|
/** Contract event filters */
|
|
65
|
-
interface ContractCallFilter {
|
|
73
|
+
interface ContractCallFilter extends TraitScope {
|
|
66
74
|
type: "contract_call";
|
|
67
75
|
contractId?: string;
|
|
68
76
|
functionName?: string;
|
|
@@ -80,7 +88,7 @@ interface ContractDeployFilter {
|
|
|
80
88
|
deployer?: string;
|
|
81
89
|
contractName?: string;
|
|
82
90
|
}
|
|
83
|
-
interface PrintEventFilter {
|
|
91
|
+
interface PrintEventFilter extends TraitScope {
|
|
84
92
|
type: "print_event";
|
|
85
93
|
contractId?: string;
|
|
86
94
|
topic?: string;
|
|
@@ -118,9 +126,10 @@ type EventRecord = {
|
|
|
118
126
|
event_index: number
|
|
119
127
|
data: unknown
|
|
120
128
|
};
|
|
129
|
+
type TraitContracts = Map<string, ReadonlySet<string>>;
|
|
121
130
|
/**
|
|
122
131
|
* Match named filters against a block's transactions and events.
|
|
123
132
|
* Returns matches with sourceName = the object key from sources.
|
|
124
133
|
*/
|
|
125
|
-
declare function matchSources(sources: Record<string, SubgraphFilter>, transactions: TxRecord[], events: EventRecord[]): MatchedTx[];
|
|
126
|
-
export { matchSources, MatchedTx };
|
|
134
|
+
declare function matchSources(sources: Record<string, SubgraphFilter>, transactions: TxRecord[], events: EventRecord[], traitContracts?: TraitContracts): MatchedTx[];
|
|
135
|
+
export { matchSources, TraitContracts, MatchedTx };
|
|
@@ -14,7 +14,19 @@ function matchPattern(value, pattern) {
|
|
|
14
14
|
}
|
|
15
15
|
return re.test(value);
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
var EMPTY_SET = new Set;
|
|
18
|
+
function traitAllows(filter, contractId, traitContracts) {
|
|
19
|
+
const trait = filter.trait;
|
|
20
|
+
if (!trait)
|
|
21
|
+
return true;
|
|
22
|
+
if (!contractId)
|
|
23
|
+
return false;
|
|
24
|
+
return (traitContracts.get(trait) ?? EMPTY_SET).has(contractId);
|
|
25
|
+
}
|
|
26
|
+
function assetContract(assetId) {
|
|
27
|
+
return assetId?.split("::")[0];
|
|
28
|
+
}
|
|
29
|
+
function matchFilter(filter, transactions, eventsByTx, traitContracts) {
|
|
18
30
|
const results = [];
|
|
19
31
|
switch (filter.type) {
|
|
20
32
|
case "stx_transfer":
|
|
@@ -78,6 +90,8 @@ function matchFilter(filter, transactions, eventsByTx) {
|
|
|
78
90
|
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
79
91
|
return false;
|
|
80
92
|
}
|
|
93
|
+
if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
|
|
94
|
+
return false;
|
|
81
95
|
if ("sender" in filter && filter.sender) {
|
|
82
96
|
if (!matchPattern(data.sender, filter.sender))
|
|
83
97
|
return false;
|
|
@@ -116,6 +130,8 @@ function matchFilter(filter, transactions, eventsByTx) {
|
|
|
116
130
|
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
117
131
|
return false;
|
|
118
132
|
}
|
|
133
|
+
if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
|
|
134
|
+
return false;
|
|
119
135
|
if ("sender" in filter && filter.sender) {
|
|
120
136
|
if (!matchPattern(data.sender, filter.sender))
|
|
121
137
|
return false;
|
|
@@ -148,6 +164,8 @@ function matchFilter(filter, transactions, eventsByTx) {
|
|
|
148
164
|
if (!matchPattern(tx.sender, filter.caller))
|
|
149
165
|
continue;
|
|
150
166
|
}
|
|
167
|
+
if (!traitAllows(filter, tx.contract_id, traitContracts))
|
|
168
|
+
continue;
|
|
151
169
|
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
152
170
|
results.push({ tx, events: txEvents });
|
|
153
171
|
}
|
|
@@ -182,11 +200,13 @@ function matchFilter(filter, transactions, eventsByTx) {
|
|
|
182
200
|
return false;
|
|
183
201
|
if (data.topic !== "print")
|
|
184
202
|
return false;
|
|
203
|
+
const printContractId = data.contract_identifier ?? data.contract_id;
|
|
185
204
|
if (filter.contractId) {
|
|
186
|
-
|
|
187
|
-
if (!contractId || !matchPattern(contractId, filter.contractId))
|
|
205
|
+
if (!printContractId || !matchPattern(printContractId, filter.contractId))
|
|
188
206
|
return false;
|
|
189
207
|
}
|
|
208
|
+
if (!traitAllows(filter, printContractId, traitContracts))
|
|
209
|
+
return false;
|
|
190
210
|
return true;
|
|
191
211
|
});
|
|
192
212
|
if (matched.length > 0) {
|
|
@@ -198,7 +218,7 @@ function matchFilter(filter, transactions, eventsByTx) {
|
|
|
198
218
|
}
|
|
199
219
|
return results;
|
|
200
220
|
}
|
|
201
|
-
function matchSources(sources, transactions, events) {
|
|
221
|
+
function matchSources(sources, transactions, events, traitContracts = new Map) {
|
|
202
222
|
const eventsByTx = new Map;
|
|
203
223
|
for (const event of events) {
|
|
204
224
|
const list = eventsByTx.get(event.tx_id) ?? [];
|
|
@@ -208,7 +228,7 @@ function matchSources(sources, transactions, events) {
|
|
|
208
228
|
const seen = new Set;
|
|
209
229
|
const results = [];
|
|
210
230
|
for (const [sourceName, filter] of Object.entries(sources)) {
|
|
211
|
-
const matches = matchFilter(filter, transactions, eventsByTx);
|
|
231
|
+
const matches = matchFilter(filter, transactions, eventsByTx, traitContracts);
|
|
212
232
|
for (const match of matches) {
|
|
213
233
|
const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
|
|
214
234
|
if (!seen.has(dedupeKey)) {
|
|
@@ -223,5 +243,5 @@ export {
|
|
|
223
243
|
matchSources
|
|
224
244
|
};
|
|
225
245
|
|
|
226
|
-
//# debugId=
|
|
246
|
+
//# debugId=4C4140B5CFEF4A9764756E2164756E21
|
|
227
247
|
//# sourceMappingURL=source-matcher.js.map
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/runtime/source-matcher.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { SubgraphFilter } from \"../types.ts\";\n\nexport interface MatchedTx {\n\ttx: TxRecord;\n\tevents: EventRecord[];\n\t/** Source object key — used for handler dispatch */\n\tsourceName: string;\n}\n\ntype TxRecord = {\n\ttx_id: string;\n\ttype: string;\n\tsender: string;\n\tstatus: string;\n\tcontract_id?: string | null;\n\tfunction_name?: string | null;\n\tfunction_args?: unknown | null;\n\traw_result?: string | null;\n};\n\ntype EventRecord = {\n\tid: string;\n\ttx_id: string;\n\ttype: string;\n\tevent_index: number;\n\tdata: unknown;\n};\n\n// ── Wildcard matching (shared with v1) ──────────────────────────────\n\nconst patternCache = new Map<string, RegExp>();\n\nfunction matchPattern(value: string, pattern: string): boolean {\n\tif (!pattern.includes(\"*\")) return value === pattern;\n\tlet re = patternCache.get(pattern);\n\tif (!re) {\n\t\tconst regex = pattern\n\t\t\t.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\")\n\t\t\t.replace(/\\*/g, \".*\");\n\t\tre = new RegExp(`^${regex}$`);\n\t\tpatternCache.set(pattern, re);\n\t}\n\treturn re.test(value);\n}\n\n// ── Per-filter-type matchers ────────────────────────────────────────\n\nfunction matchFilter(\n\tfilter: SubgraphFilter,\n\ttransactions: TxRecord[],\n\teventsByTx: Map<string, EventRecord[]>,\n): { tx: TxRecord; events: EventRecord[] }[] {\n\tconst results: { tx: TxRecord; events: EventRecord[] }[] = [];\n\n\tswitch (filter.type) {\n\t\t// ── STX events ──\n\t\tcase \"stx_transfer\":\n\t\tcase \"stx_mint\":\n\t\tcase \"stx_burn\":\n\t\tcase \"stx_lock\": {\n\t\t\tconst eventType = `${filter.type}_event`;\n\t\t\tfor (const tx of transactions) {\n\t\t\t\tconst txEvents = eventsByTx.get(tx.tx_id) ?? [];\n\t\t\t\tconst matched = txEvents.filter((e) => e.type === eventType);\n\t\t\t\tif (matched.length === 0) continue;\n\n\t\t\t\t// Apply address filters\n\t\t\t\tconst filtered = matched.filter((e) => {\n\t\t\t\t\tconst data = e.data as Record<string, unknown> | null;\n\t\t\t\t\tif (!data) return false;\n\t\t\t\t\tif (\"sender\" in filter && filter.sender) {\n\t\t\t\t\t\tif (!matchPattern(data.sender as string, filter.sender))\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tif (\"recipient\" in filter && filter.recipient) {\n\t\t\t\t\t\tif (!matchPattern(data.recipient as string, filter.recipient))\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tif (\"lockedAddress\" in filter && filter.lockedAddress) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t!matchPattern(data.locked_address as string, filter.lockedAddress)\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\t// Amount filters\n\t\t\t\t\tif (\"minAmount\" in filter && filter.minAmount !== undefined) {\n\t\t\t\t\t\tconst amount = BigInt(\n\t\t\t\t\t\t\t(data.amount ?? data.locked_amount ?? \"0\") as string,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (amount < filter.minAmount) return false;\n\t\t\t\t\t}\n\t\t\t\t\tif (\n\t\t\t\t\t\t\"maxAmount\" in filter &&\n\t\t\t\t\t\t(filter as { maxAmount?: bigint }).maxAmount !== undefined\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst amount = BigInt((data.amount ?? \"0\") as string);\n\t\t\t\t\t\tif (amount > (filter as { maxAmount: bigint }).maxAmount)\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t});\n\n\t\t\t\tif (filtered.length > 0) {\n\t\t\t\t\tresults.push({ tx, events: filtered });\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\t// ── FT events ──\n\t\tcase \"ft_transfer\":\n\t\tcase \"ft_mint\":\n\t\tcase \"ft_burn\": {\n\t\t\tconst eventType = `${filter.type}_event`;\n\t\t\tfor (const tx of transactions) {\n\t\t\t\tconst txEvents = eventsByTx.get(tx.tx_id) ?? [];\n\t\t\t\tconst matched = txEvents.filter((e) => {\n\t\t\t\t\tif (e.type !== eventType) return false;\n\t\t\t\t\tconst data = e.data as Record<string, unknown> | null;\n\t\t\t\t\tif (!data) return false;\n\n\t\t\t\t\t// Asset identifier filter\n\t\t\t\t\tif (filter.assetIdentifier) {\n\t\t\t\t\t\tconst assetId = data.asset_identifier as string | undefined;\n\t\t\t\t\t\tif (!assetId || !matchPattern(assetId, filter.assetIdentifier))\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\t// Address filters\n\t\t\t\t\tif (\"sender\" in filter && filter.sender) {\n\t\t\t\t\t\tif (!matchPattern(data.sender as string, filter.sender))\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tif (\"recipient\" in filter && filter.recipient) {\n\t\t\t\t\t\tif (!matchPattern(data.recipient as string, filter.recipient))\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\t// Amount filter\n\t\t\t\t\tif (filter.minAmount !== undefined) {\n\t\t\t\t\t\tconst amount = BigInt((data.amount ?? \"0\") as string);\n\t\t\t\t\t\tif (amount < filter.minAmount) return false;\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t});\n\n\t\t\t\tif (matched.length > 0) {\n\t\t\t\t\tresults.push({ tx, events: matched });\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\t// ── NFT events ──\n\t\tcase \"nft_transfer\":\n\t\tcase \"nft_mint\":\n\t\tcase \"nft_burn\": {\n\t\t\tconst eventType = `${filter.type}_event`;\n\t\t\tfor (const tx of transactions) {\n\t\t\t\tconst txEvents = eventsByTx.get(tx.tx_id) ?? [];\n\t\t\t\tconst matched = txEvents.filter((e) => {\n\t\t\t\t\tif (e.type !== eventType) return false;\n\t\t\t\t\tconst data = e.data as Record<string, unknown> | null;\n\t\t\t\t\tif (!data) return false;\n\n\t\t\t\t\tif (filter.assetIdentifier) {\n\t\t\t\t\t\tconst assetId = data.asset_identifier as string | undefined;\n\t\t\t\t\t\tif (!assetId || !matchPattern(assetId, filter.assetIdentifier))\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tif (\"sender\" in filter && filter.sender) {\n\t\t\t\t\t\tif (!matchPattern(data.sender as string, filter.sender))\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tif (\"recipient\" in filter && filter.recipient) {\n\t\t\t\t\t\tif (!matchPattern(data.recipient as string, filter.recipient))\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t});\n\n\t\t\t\tif (matched.length > 0) {\n\t\t\t\t\tresults.push({ tx, events: matched });\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\t// ── Contract call ──\n\t\tcase \"contract_call\": {\n\t\t\tfor (const tx of transactions) {\n\t\t\t\tif (tx.type !== \"contract_call\") continue;\n\n\t\t\t\t// Contract filter\n\t\t\t\tif (filter.contractId) {\n\t\t\t\t\tif (\n\t\t\t\t\t\t!tx.contract_id ||\n\t\t\t\t\t\t!matchPattern(tx.contract_id, filter.contractId)\n\t\t\t\t\t)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// Function filter\n\t\t\t\tif (filter.functionName) {\n\t\t\t\t\tif (\n\t\t\t\t\t\t!tx.function_name ||\n\t\t\t\t\t\t!matchPattern(tx.function_name, filter.functionName)\n\t\t\t\t\t)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// Caller filter\n\t\t\t\tif (filter.caller) {\n\t\t\t\t\tif (!matchPattern(tx.sender, filter.caller)) continue;\n\t\t\t\t}\n\n\t\t\t\tconst txEvents = eventsByTx.get(tx.tx_id) ?? [];\n\t\t\t\tresults.push({ tx, events: txEvents });\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\t// ── Contract deploy ──\n\t\tcase \"contract_deploy\": {\n\t\t\tfor (const tx of transactions) {\n\t\t\t\tif (tx.type !== \"smart_contract\") continue;\n\n\t\t\t\tif (filter.deployer) {\n\t\t\t\t\tif (!matchPattern(tx.sender, filter.deployer)) continue;\n\t\t\t\t}\n\t\t\t\tif (filter.contractName) {\n\t\t\t\t\tconst name = tx.contract_id?.split(\".\")[1] ?? \"\";\n\t\t\t\t\tif (!matchPattern(name, filter.contractName)) continue;\n\t\t\t\t}\n\n\t\t\t\tconst txEvents = eventsByTx.get(tx.tx_id) ?? [];\n\t\t\t\tresults.push({ tx, events: txEvents });\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\t// ── Print event ──\n\t\tcase \"print_event\": {\n\t\t\tfor (const tx of transactions) {\n\t\t\t\tconst txEvents = eventsByTx.get(tx.tx_id) ?? [];\n\t\t\t\tconst matched = txEvents.filter((e) => {\n\t\t\t\t\tif (e.type !== \"smart_contract_event\" && e.type !== \"contract_event\")\n\t\t\t\t\t\treturn false;\n\t\t\t\t\tconst data = e.data as Record<string, unknown> | null;\n\t\t\t\t\tif (!data) return false;\n\t\t\t\t\tif (data.topic !== \"print\") return false;\n\n\t\t\t\t\t// Contract filter — events store the contract under either\n\t\t\t\t\t// `contract_identifier` (legacy smart_contract_event payload)\n\t\t\t\t\t// or `contract_id` (current contract_event payload). Mirror\n\t\t\t\t\t// the streams query which checks both shapes.\n\t\t\t\t\tif (filter.contractId) {\n\t\t\t\t\t\tconst contractId =\n\t\t\t\t\t\t\t(data.contract_identifier as string | undefined) ??\n\t\t\t\t\t\t\t(data.contract_id as string | undefined);\n\t\t\t\t\t\tif (!contractId || !matchPattern(contractId, filter.contractId))\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\t// Topic filter — check the decoded Clarity value's topic field\n\t\t\t\t\t// At this stage data.value is still raw hex; topic filtering happens\n\t\t\t\t\t// after decode in the runner. For now, skip topic filtering here.\n\t\t\t\t\t// The runner will filter by topic after decoding.\n\t\t\t\t\treturn true;\n\t\t\t\t});\n\n\t\t\t\tif (matched.length > 0) {\n\t\t\t\t\tresults.push({ tx, events: matched });\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn results;\n}\n\n/**\n * Match named filters against a block's transactions and events.\n * Returns matches with sourceName = the object key from sources.\n */\nexport function matchSources(\n\tsources: Record<string, SubgraphFilter>,\n\ttransactions: TxRecord[],\n\tevents: EventRecord[],\n): MatchedTx[] {\n\t// Index events by txId\n\tconst eventsByTx = new Map<string, EventRecord[]>();\n\tfor (const event of events) {\n\t\tconst list = eventsByTx.get(event.tx_id) ?? [];\n\t\tlist.push(event);\n\t\teventsByTx.set(event.tx_id, list);\n\t}\n\n\tconst seen = new Set<string>();\n\tconst results: MatchedTx[] = [];\n\n\tfor (const [sourceName, filter] of Object.entries(sources)) {\n\t\tconst matches = matchFilter(filter, transactions, eventsByTx);\n\t\tfor (const match of matches) {\n\t\t\tconst dedupeKey = `${match.tx.tx_id}:${sourceName}`;\n\t\t\tif (!seen.has(dedupeKey)) {\n\t\t\t\tseen.add(dedupeKey);\n\t\t\t\tresults.push({ ...match, sourceName });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results;\n}\n"
|
|
5
|
+
"import type { SubgraphFilter } from \"../types.ts\";\n\nexport interface MatchedTx {\n\ttx: TxRecord;\n\tevents: EventRecord[];\n\t/** Source object key — used for handler dispatch */\n\tsourceName: string;\n}\n\ntype TxRecord = {\n\ttx_id: string;\n\ttype: string;\n\tsender: string;\n\tstatus: string;\n\tcontract_id?: string | null;\n\tfunction_name?: string | null;\n\tfunction_args?: unknown | null;\n\traw_result?: string | null;\n};\n\ntype EventRecord = {\n\tid: string;\n\ttx_id: string;\n\ttype: string;\n\tevent_index: number;\n\tdata: unknown;\n};\n\n// ── Wildcard matching (shared with v1) ──────────────────────────────\n\nconst patternCache = new Map<string, RegExp>();\n\nfunction matchPattern(value: string, pattern: string): boolean {\n\tif (!pattern.includes(\"*\")) return value === pattern;\n\tlet re = patternCache.get(pattern);\n\tif (!re) {\n\t\tconst regex = pattern\n\t\t\t.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\")\n\t\t\t.replace(/\\*/g, \".*\");\n\t\tre = new RegExp(`^${regex}$`);\n\t\tpatternCache.set(pattern, re);\n\t}\n\treturn re.test(value);\n}\n\n// Trait → set of conforming contract IDs, resolved per block by the caller\n// (block-processor) from the contract registry. Kept as injected data so this\n// module stays pure/sync/DB-less.\nexport type TraitContracts = Map<string, ReadonlySet<string>>;\nconst EMPTY_SET: ReadonlySet<string> = new Set();\n\n/**\n * True when a filter's optional `trait` admits this contract: no trait → always\n * allowed; trait set → the contract must be in the resolved conforming set.\n */\nfunction traitAllows(\n\tfilter: SubgraphFilter,\n\tcontractId: string | undefined | null,\n\ttraitContracts: TraitContracts,\n): boolean {\n\tconst trait = (filter as { trait?: string }).trait;\n\tif (!trait) return true;\n\tif (!contractId) return false;\n\treturn (traitContracts.get(trait) ?? EMPTY_SET).has(contractId);\n}\n\n/** Extract the contract id from an asset identifier (`<contract>::<token>`). */\nfunction assetContract(assetId: string | undefined): string | undefined {\n\treturn assetId?.split(\"::\")[0];\n}\n\n// ── Per-filter-type matchers ────────────────────────────────────────\n\nfunction matchFilter(\n\tfilter: SubgraphFilter,\n\ttransactions: TxRecord[],\n\teventsByTx: Map<string, EventRecord[]>,\n\ttraitContracts: TraitContracts,\n): { tx: TxRecord; events: EventRecord[] }[] {\n\tconst results: { tx: TxRecord; events: EventRecord[] }[] = [];\n\n\tswitch (filter.type) {\n\t\t// ── STX events ──\n\t\tcase \"stx_transfer\":\n\t\tcase \"stx_mint\":\n\t\tcase \"stx_burn\":\n\t\tcase \"stx_lock\": {\n\t\t\tconst eventType = `${filter.type}_event`;\n\t\t\tfor (const tx of transactions) {\n\t\t\t\tconst txEvents = eventsByTx.get(tx.tx_id) ?? [];\n\t\t\t\tconst matched = txEvents.filter((e) => e.type === eventType);\n\t\t\t\tif (matched.length === 0) continue;\n\n\t\t\t\t// Apply address filters\n\t\t\t\tconst filtered = matched.filter((e) => {\n\t\t\t\t\tconst data = e.data as Record<string, unknown> | null;\n\t\t\t\t\tif (!data) return false;\n\t\t\t\t\tif (\"sender\" in filter && filter.sender) {\n\t\t\t\t\t\tif (!matchPattern(data.sender as string, filter.sender))\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tif (\"recipient\" in filter && filter.recipient) {\n\t\t\t\t\t\tif (!matchPattern(data.recipient as string, filter.recipient))\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tif (\"lockedAddress\" in filter && filter.lockedAddress) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t!matchPattern(data.locked_address as string, filter.lockedAddress)\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\t// Amount filters\n\t\t\t\t\tif (\"minAmount\" in filter && filter.minAmount !== undefined) {\n\t\t\t\t\t\tconst amount = BigInt(\n\t\t\t\t\t\t\t(data.amount ?? data.locked_amount ?? \"0\") as string,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (amount < filter.minAmount) return false;\n\t\t\t\t\t}\n\t\t\t\t\tif (\n\t\t\t\t\t\t\"maxAmount\" in filter &&\n\t\t\t\t\t\t(filter as { maxAmount?: bigint }).maxAmount !== undefined\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst amount = BigInt((data.amount ?? \"0\") as string);\n\t\t\t\t\t\tif (amount > (filter as { maxAmount: bigint }).maxAmount)\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t});\n\n\t\t\t\tif (filtered.length > 0) {\n\t\t\t\t\tresults.push({ tx, events: filtered });\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\t// ── FT events ──\n\t\tcase \"ft_transfer\":\n\t\tcase \"ft_mint\":\n\t\tcase \"ft_burn\": {\n\t\t\tconst eventType = `${filter.type}_event`;\n\t\t\tfor (const tx of transactions) {\n\t\t\t\tconst txEvents = eventsByTx.get(tx.tx_id) ?? [];\n\t\t\t\tconst matched = txEvents.filter((e) => {\n\t\t\t\t\tif (e.type !== eventType) return false;\n\t\t\t\t\tconst data = e.data as Record<string, unknown> | null;\n\t\t\t\t\tif (!data) return false;\n\n\t\t\t\t\t// Asset identifier filter\n\t\t\t\t\tif (filter.assetIdentifier) {\n\t\t\t\t\t\tconst assetId = data.asset_identifier as string | undefined;\n\t\t\t\t\t\tif (!assetId || !matchPattern(assetId, filter.assetIdentifier))\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\t// Trait scope — the asset's contract must conform.\n\t\t\t\t\tif (\n\t\t\t\t\t\t!traitAllows(\n\t\t\t\t\t\t\tfilter,\n\t\t\t\t\t\t\tassetContract(data.asset_identifier as string | undefined),\n\t\t\t\t\t\t\ttraitContracts,\n\t\t\t\t\t\t)\n\t\t\t\t\t)\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t// Address filters\n\t\t\t\t\tif (\"sender\" in filter && filter.sender) {\n\t\t\t\t\t\tif (!matchPattern(data.sender as string, filter.sender))\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tif (\"recipient\" in filter && filter.recipient) {\n\t\t\t\t\t\tif (!matchPattern(data.recipient as string, filter.recipient))\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\t// Amount filter\n\t\t\t\t\tif (filter.minAmount !== undefined) {\n\t\t\t\t\t\tconst amount = BigInt((data.amount ?? \"0\") as string);\n\t\t\t\t\t\tif (amount < filter.minAmount) return false;\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t});\n\n\t\t\t\tif (matched.length > 0) {\n\t\t\t\t\tresults.push({ tx, events: matched });\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\t// ── NFT events ──\n\t\tcase \"nft_transfer\":\n\t\tcase \"nft_mint\":\n\t\tcase \"nft_burn\": {\n\t\t\tconst eventType = `${filter.type}_event`;\n\t\t\tfor (const tx of transactions) {\n\t\t\t\tconst txEvents = eventsByTx.get(tx.tx_id) ?? [];\n\t\t\t\tconst matched = txEvents.filter((e) => {\n\t\t\t\t\tif (e.type !== eventType) return false;\n\t\t\t\t\tconst data = e.data as Record<string, unknown> | null;\n\t\t\t\t\tif (!data) return false;\n\n\t\t\t\t\tif (filter.assetIdentifier) {\n\t\t\t\t\t\tconst assetId = data.asset_identifier as string | undefined;\n\t\t\t\t\t\tif (!assetId || !matchPattern(assetId, filter.assetIdentifier))\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tif (\n\t\t\t\t\t\t!traitAllows(\n\t\t\t\t\t\t\tfilter,\n\t\t\t\t\t\t\tassetContract(data.asset_identifier as string | undefined),\n\t\t\t\t\t\t\ttraitContracts,\n\t\t\t\t\t\t)\n\t\t\t\t\t)\n\t\t\t\t\t\treturn false;\n\t\t\t\t\tif (\"sender\" in filter && filter.sender) {\n\t\t\t\t\t\tif (!matchPattern(data.sender as string, filter.sender))\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tif (\"recipient\" in filter && filter.recipient) {\n\t\t\t\t\t\tif (!matchPattern(data.recipient as string, filter.recipient))\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t});\n\n\t\t\t\tif (matched.length > 0) {\n\t\t\t\t\tresults.push({ tx, events: matched });\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\t// ── Contract call ──\n\t\tcase \"contract_call\": {\n\t\t\tfor (const tx of transactions) {\n\t\t\t\tif (tx.type !== \"contract_call\") continue;\n\n\t\t\t\t// Contract filter\n\t\t\t\tif (filter.contractId) {\n\t\t\t\t\tif (\n\t\t\t\t\t\t!tx.contract_id ||\n\t\t\t\t\t\t!matchPattern(tx.contract_id, filter.contractId)\n\t\t\t\t\t)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// Function filter\n\t\t\t\tif (filter.functionName) {\n\t\t\t\t\tif (\n\t\t\t\t\t\t!tx.function_name ||\n\t\t\t\t\t\t!matchPattern(tx.function_name, filter.functionName)\n\t\t\t\t\t)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// Caller filter\n\t\t\t\tif (filter.caller) {\n\t\t\t\t\tif (!matchPattern(tx.sender, filter.caller)) continue;\n\t\t\t\t}\n\t\t\t\t// Trait scope — the called contract must conform.\n\t\t\t\tif (!traitAllows(filter, tx.contract_id, traitContracts)) continue;\n\n\t\t\t\tconst txEvents = eventsByTx.get(tx.tx_id) ?? [];\n\t\t\t\tresults.push({ tx, events: txEvents });\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\t// ── Contract deploy ──\n\t\tcase \"contract_deploy\": {\n\t\t\tfor (const tx of transactions) {\n\t\t\t\tif (tx.type !== \"smart_contract\") continue;\n\n\t\t\t\tif (filter.deployer) {\n\t\t\t\t\tif (!matchPattern(tx.sender, filter.deployer)) continue;\n\t\t\t\t}\n\t\t\t\tif (filter.contractName) {\n\t\t\t\t\tconst name = tx.contract_id?.split(\".\")[1] ?? \"\";\n\t\t\t\t\tif (!matchPattern(name, filter.contractName)) continue;\n\t\t\t\t}\n\n\t\t\t\tconst txEvents = eventsByTx.get(tx.tx_id) ?? [];\n\t\t\t\tresults.push({ tx, events: txEvents });\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\t// ── Print event ──\n\t\tcase \"print_event\": {\n\t\t\tfor (const tx of transactions) {\n\t\t\t\tconst txEvents = eventsByTx.get(tx.tx_id) ?? [];\n\t\t\t\tconst matched = txEvents.filter((e) => {\n\t\t\t\t\tif (e.type !== \"smart_contract_event\" && e.type !== \"contract_event\")\n\t\t\t\t\t\treturn false;\n\t\t\t\t\tconst data = e.data as Record<string, unknown> | null;\n\t\t\t\t\tif (!data) return false;\n\t\t\t\t\tif (data.topic !== \"print\") return false;\n\n\t\t\t\t\t// Contract filter — events store the contract under either\n\t\t\t\t\t// `contract_identifier` (legacy smart_contract_event payload)\n\t\t\t\t\t// or `contract_id` (current contract_event payload). Mirror\n\t\t\t\t\t// the streams query which checks both shapes.\n\t\t\t\t\tconst printContractId =\n\t\t\t\t\t\t(data.contract_identifier as string | undefined) ??\n\t\t\t\t\t\t(data.contract_id as string | undefined);\n\t\t\t\t\tif (filter.contractId) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t!printContractId ||\n\t\t\t\t\t\t\t!matchPattern(printContractId, filter.contractId)\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tif (!traitAllows(filter, printContractId, traitContracts))\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t// Topic filter — check the decoded Clarity value's topic field\n\t\t\t\t\t// At this stage data.value is still raw hex; topic filtering happens\n\t\t\t\t\t// after decode in the runner. For now, skip topic filtering here.\n\t\t\t\t\t// The runner will filter by topic after decoding.\n\t\t\t\t\treturn true;\n\t\t\t\t});\n\n\t\t\t\tif (matched.length > 0) {\n\t\t\t\t\tresults.push({ tx, events: matched });\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn results;\n}\n\n/**\n * Match named filters against a block's transactions and events.\n * Returns matches with sourceName = the object key from sources.\n */\nexport function matchSources(\n\tsources: Record<string, SubgraphFilter>,\n\ttransactions: TxRecord[],\n\tevents: EventRecord[],\n\ttraitContracts: TraitContracts = new Map(),\n): MatchedTx[] {\n\t// Index events by txId\n\tconst eventsByTx = new Map<string, EventRecord[]>();\n\tfor (const event of events) {\n\t\tconst list = eventsByTx.get(event.tx_id) ?? [];\n\t\tlist.push(event);\n\t\teventsByTx.set(event.tx_id, list);\n\t}\n\n\tconst seen = new Set<string>();\n\tconst results: MatchedTx[] = [];\n\n\tfor (const [sourceName, filter] of Object.entries(sources)) {\n\t\tconst matches = matchFilter(\n\t\t\tfilter,\n\t\t\ttransactions,\n\t\t\teventsByTx,\n\t\t\ttraitContracts,\n\t\t);\n\t\tfor (const match of matches) {\n\t\t\tconst dedupeKey = `${match.tx.tx_id}:${sourceName}`;\n\t\t\tif (!seen.has(dedupeKey)) {\n\t\t\t\tseen.add(dedupeKey);\n\t\t\t\tresults.push({ ...match, sourceName });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results;\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;AA8BA,IAAM,eAAe,IAAI;AAEzB,SAAS,YAAY,CAAC,OAAe,SAA0B;AAAA,EAC9D,IAAI,CAAC,QAAQ,SAAS,GAAG;AAAA,IAAG,OAAO,UAAU;AAAA,EAC7C,IAAI,KAAK,aAAa,IAAI,OAAO;AAAA,EACjC,IAAI,CAAC,IAAI;AAAA,IACR,MAAM,QAAQ,QACZ,QAAQ,qBAAqB,MAAM,EACnC,QAAQ,OAAO,IAAI;AAAA,IACrB,KAAK,IAAI,OAAO,IAAI,QAAQ;AAAA,IAC5B,aAAa,IAAI,SAAS,EAAE;AAAA,EAC7B;AAAA,EACA,OAAO,GAAG,KAAK,KAAK;AAAA;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;AA8BA,IAAM,eAAe,IAAI;AAEzB,SAAS,YAAY,CAAC,OAAe,SAA0B;AAAA,EAC9D,IAAI,CAAC,QAAQ,SAAS,GAAG;AAAA,IAAG,OAAO,UAAU;AAAA,EAC7C,IAAI,KAAK,aAAa,IAAI,OAAO;AAAA,EACjC,IAAI,CAAC,IAAI;AAAA,IACR,MAAM,QAAQ,QACZ,QAAQ,qBAAqB,MAAM,EACnC,QAAQ,OAAO,IAAI;AAAA,IACrB,KAAK,IAAI,OAAO,IAAI,QAAQ;AAAA,IAC5B,aAAa,IAAI,SAAS,EAAE;AAAA,EAC7B;AAAA,EACA,OAAO,GAAG,KAAK,KAAK;AAAA;AAOrB,IAAM,YAAiC,IAAI;AAM3C,SAAS,WAAW,CACnB,QACA,YACA,gBACU;AAAA,EACV,MAAM,QAAS,OAA8B;AAAA,EAC7C,IAAI,CAAC;AAAA,IAAO,OAAO;AAAA,EACnB,IAAI,CAAC;AAAA,IAAY,OAAO;AAAA,EACxB,QAAQ,eAAe,IAAI,KAAK,KAAK,WAAW,IAAI,UAAU;AAAA;AAI/D,SAAS,aAAa,CAAC,SAAiD;AAAA,EACvE,OAAO,SAAS,MAAM,IAAI,EAAE;AAAA;AAK7B,SAAS,WAAW,CACnB,QACA,cACA,YACA,gBAC4C;AAAA,EAC5C,MAAM,UAAqD,CAAC;AAAA,EAE5D,QAAQ,OAAO;AAAA,SAET;AAAA,SACA;AAAA,SACA;AAAA,SACA,YAAY;AAAA,MAChB,MAAM,YAAY,GAAG,OAAO;AAAA,MAC5B,WAAW,MAAM,cAAc;AAAA,QAC9B,MAAM,WAAW,WAAW,IAAI,GAAG,KAAK,KAAK,CAAC;AAAA,QAC9C,MAAM,UAAU,SAAS,OAAO,CAAC,MAAM,EAAE,SAAS,SAAS;AAAA,QAC3D,IAAI,QAAQ,WAAW;AAAA,UAAG;AAAA,QAG1B,MAAM,WAAW,QAAQ,OAAO,CAAC,MAAM;AAAA,UACtC,MAAM,OAAO,EAAE;AAAA,UACf,IAAI,CAAC;AAAA,YAAM,OAAO;AAAA,UAClB,IAAI,YAAY,UAAU,OAAO,QAAQ;AAAA,YACxC,IAAI,CAAC,aAAa,KAAK,QAAkB,OAAO,MAAM;AAAA,cACrD,OAAO;AAAA,UACT;AAAA,UACA,IAAI,eAAe,UAAU,OAAO,WAAW;AAAA,YAC9C,IAAI,CAAC,aAAa,KAAK,WAAqB,OAAO,SAAS;AAAA,cAC3D,OAAO;AAAA,UACT;AAAA,UACA,IAAI,mBAAmB,UAAU,OAAO,eAAe;AAAA,YACtD,IACC,CAAC,aAAa,KAAK,gBAA0B,OAAO,aAAa;AAAA,cAEjE,OAAO;AAAA,UACT;AAAA,UAEA,IAAI,eAAe,UAAU,OAAO,cAAc,WAAW;AAAA,YAC5D,MAAM,SAAS,OACb,KAAK,UAAU,KAAK,iBAAiB,GACvC;AAAA,YACA,IAAI,SAAS,OAAO;AAAA,cAAW,OAAO;AAAA,UACvC;AAAA,UACA,IACC,eAAe,UACd,OAAkC,cAAc,WAChD;AAAA,YACD,MAAM,SAAS,OAAQ,KAAK,UAAU,GAAc;AAAA,YACpD,IAAI,SAAU,OAAiC;AAAA,cAC9C,OAAO;AAAA,UACT;AAAA,UACA,OAAO;AAAA,SACP;AAAA,QAED,IAAI,SAAS,SAAS,GAAG;AAAA,UACxB,QAAQ,KAAK,EAAE,IAAI,QAAQ,SAAS,CAAC;AAAA,QACtC;AAAA,MACD;AAAA,MACA;AAAA,IACD;AAAA,SAGK;AAAA,SACA;AAAA,SACA,WAAW;AAAA,MACf,MAAM,YAAY,GAAG,OAAO;AAAA,MAC5B,WAAW,MAAM,cAAc;AAAA,QAC9B,MAAM,WAAW,WAAW,IAAI,GAAG,KAAK,KAAK,CAAC;AAAA,QAC9C,MAAM,UAAU,SAAS,OAAO,CAAC,MAAM;AAAA,UACtC,IAAI,EAAE,SAAS;AAAA,YAAW,OAAO;AAAA,UACjC,MAAM,OAAO,EAAE;AAAA,UACf,IAAI,CAAC;AAAA,YAAM,OAAO;AAAA,UAGlB,IAAI,OAAO,iBAAiB;AAAA,YAC3B,MAAM,UAAU,KAAK;AAAA,YACrB,IAAI,CAAC,WAAW,CAAC,aAAa,SAAS,OAAO,eAAe;AAAA,cAC5D,OAAO;AAAA,UACT;AAAA,UAEA,IACC,CAAC,YACA,QACA,cAAc,KAAK,gBAAsC,GACzD,cACD;AAAA,YAEA,OAAO;AAAA,UAER,IAAI,YAAY,UAAU,OAAO,QAAQ;AAAA,YACxC,IAAI,CAAC,aAAa,KAAK,QAAkB,OAAO,MAAM;AAAA,cACrD,OAAO;AAAA,UACT;AAAA,UACA,IAAI,eAAe,UAAU,OAAO,WAAW;AAAA,YAC9C,IAAI,CAAC,aAAa,KAAK,WAAqB,OAAO,SAAS;AAAA,cAC3D,OAAO;AAAA,UACT;AAAA,UAEA,IAAI,OAAO,cAAc,WAAW;AAAA,YACnC,MAAM,SAAS,OAAQ,KAAK,UAAU,GAAc;AAAA,YACpD,IAAI,SAAS,OAAO;AAAA,cAAW,OAAO;AAAA,UACvC;AAAA,UACA,OAAO;AAAA,SACP;AAAA,QAED,IAAI,QAAQ,SAAS,GAAG;AAAA,UACvB,QAAQ,KAAK,EAAE,IAAI,QAAQ,QAAQ,CAAC;AAAA,QACrC;AAAA,MACD;AAAA,MACA;AAAA,IACD;AAAA,SAGK;AAAA,SACA;AAAA,SACA,YAAY;AAAA,MAChB,MAAM,YAAY,GAAG,OAAO;AAAA,MAC5B,WAAW,MAAM,cAAc;AAAA,QAC9B,MAAM,WAAW,WAAW,IAAI,GAAG,KAAK,KAAK,CAAC;AAAA,QAC9C,MAAM,UAAU,SAAS,OAAO,CAAC,MAAM;AAAA,UACtC,IAAI,EAAE,SAAS;AAAA,YAAW,OAAO;AAAA,UACjC,MAAM,OAAO,EAAE;AAAA,UACf,IAAI,CAAC;AAAA,YAAM,OAAO;AAAA,UAElB,IAAI,OAAO,iBAAiB;AAAA,YAC3B,MAAM,UAAU,KAAK;AAAA,YACrB,IAAI,CAAC,WAAW,CAAC,aAAa,SAAS,OAAO,eAAe;AAAA,cAC5D,OAAO;AAAA,UACT;AAAA,UACA,IACC,CAAC,YACA,QACA,cAAc,KAAK,gBAAsC,GACzD,cACD;AAAA,YAEA,OAAO;AAAA,UACR,IAAI,YAAY,UAAU,OAAO,QAAQ;AAAA,YACxC,IAAI,CAAC,aAAa,KAAK,QAAkB,OAAO,MAAM;AAAA,cACrD,OAAO;AAAA,UACT;AAAA,UACA,IAAI,eAAe,UAAU,OAAO,WAAW;AAAA,YAC9C,IAAI,CAAC,aAAa,KAAK,WAAqB,OAAO,SAAS;AAAA,cAC3D,OAAO;AAAA,UACT;AAAA,UACA,OAAO;AAAA,SACP;AAAA,QAED,IAAI,QAAQ,SAAS,GAAG;AAAA,UACvB,QAAQ,KAAK,EAAE,IAAI,QAAQ,QAAQ,CAAC;AAAA,QACrC;AAAA,MACD;AAAA,MACA;AAAA,IACD;AAAA,SAGK,iBAAiB;AAAA,MACrB,WAAW,MAAM,cAAc;AAAA,QAC9B,IAAI,GAAG,SAAS;AAAA,UAAiB;AAAA,QAGjC,IAAI,OAAO,YAAY;AAAA,UACtB,IACC,CAAC,GAAG,eACJ,CAAC,aAAa,GAAG,aAAa,OAAO,UAAU;AAAA,YAE/C;AAAA,QACF;AAAA,QAEA,IAAI,OAAO,cAAc;AAAA,UACxB,IACC,CAAC,GAAG,iBACJ,CAAC,aAAa,GAAG,eAAe,OAAO,YAAY;AAAA,YAEnD;AAAA,QACF;AAAA,QAEA,IAAI,OAAO,QAAQ;AAAA,UAClB,IAAI,CAAC,aAAa,GAAG,QAAQ,OAAO,MAAM;AAAA,YAAG;AAAA,QAC9C;AAAA,QAEA,IAAI,CAAC,YAAY,QAAQ,GAAG,aAAa,cAAc;AAAA,UAAG;AAAA,QAE1D,MAAM,WAAW,WAAW,IAAI,GAAG,KAAK,KAAK,CAAC;AAAA,QAC9C,QAAQ,KAAK,EAAE,IAAI,QAAQ,SAAS,CAAC;AAAA,MACtC;AAAA,MACA;AAAA,IACD;AAAA,SAGK,mBAAmB;AAAA,MACvB,WAAW,MAAM,cAAc;AAAA,QAC9B,IAAI,GAAG,SAAS;AAAA,UAAkB;AAAA,QAElC,IAAI,OAAO,UAAU;AAAA,UACpB,IAAI,CAAC,aAAa,GAAG,QAAQ,OAAO,QAAQ;AAAA,YAAG;AAAA,QAChD;AAAA,QACA,IAAI,OAAO,cAAc;AAAA,UACxB,MAAM,OAAO,GAAG,aAAa,MAAM,GAAG,EAAE,MAAM;AAAA,UAC9C,IAAI,CAAC,aAAa,MAAM,OAAO,YAAY;AAAA,YAAG;AAAA,QAC/C;AAAA,QAEA,MAAM,WAAW,WAAW,IAAI,GAAG,KAAK,KAAK,CAAC;AAAA,QAC9C,QAAQ,KAAK,EAAE,IAAI,QAAQ,SAAS,CAAC;AAAA,MACtC;AAAA,MACA;AAAA,IACD;AAAA,SAGK,eAAe;AAAA,MACnB,WAAW,MAAM,cAAc;AAAA,QAC9B,MAAM,WAAW,WAAW,IAAI,GAAG,KAAK,KAAK,CAAC;AAAA,QAC9C,MAAM,UAAU,SAAS,OAAO,CAAC,MAAM;AAAA,UACtC,IAAI,EAAE,SAAS,0BAA0B,EAAE,SAAS;AAAA,YACnD,OAAO;AAAA,UACR,MAAM,OAAO,EAAE;AAAA,UACf,IAAI,CAAC;AAAA,YAAM,OAAO;AAAA,UAClB,IAAI,KAAK,UAAU;AAAA,YAAS,OAAO;AAAA,UAMnC,MAAM,kBACJ,KAAK,uBACL,KAAK;AAAA,UACP,IAAI,OAAO,YAAY;AAAA,YACtB,IACC,CAAC,mBACD,CAAC,aAAa,iBAAiB,OAAO,UAAU;AAAA,cAEhD,OAAO;AAAA,UACT;AAAA,UACA,IAAI,CAAC,YAAY,QAAQ,iBAAiB,cAAc;AAAA,YACvD,OAAO;AAAA,UAKR,OAAO;AAAA,SACP;AAAA,QAED,IAAI,QAAQ,SAAS,GAAG;AAAA,UACvB,QAAQ,KAAK,EAAE,IAAI,QAAQ,QAAQ,CAAC;AAAA,QACrC;AAAA,MACD;AAAA,MACA;AAAA,IACD;AAAA;AAAA,EAGD,OAAO;AAAA;AAOD,SAAS,YAAY,CAC3B,SACA,cACA,QACA,iBAAiC,IAAI,KACvB;AAAA,EAEd,MAAM,aAAa,IAAI;AAAA,EACvB,WAAW,SAAS,QAAQ;AAAA,IAC3B,MAAM,OAAO,WAAW,IAAI,MAAM,KAAK,KAAK,CAAC;AAAA,IAC7C,KAAK,KAAK,KAAK;AAAA,IACf,WAAW,IAAI,MAAM,OAAO,IAAI;AAAA,EACjC;AAAA,EAEA,MAAM,OAAO,IAAI;AAAA,EACjB,MAAM,UAAuB,CAAC;AAAA,EAE9B,YAAY,YAAY,WAAW,OAAO,QAAQ,OAAO,GAAG;AAAA,IAC3D,MAAM,UAAU,YACf,QACA,cACA,YACA,cACD;AAAA,IACA,WAAW,SAAS,SAAS;AAAA,MAC5B,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS;AAAA,MACvC,IAAI,CAAC,KAAK,IAAI,SAAS,GAAG;AAAA,QACzB,KAAK,IAAI,SAAS;AAAA,QAClB,QAAQ,KAAK,KAAK,OAAO,WAAW,CAAC;AAAA,MACtC;AAAA,IACD;AAAA,EACD;AAAA,EAEA,OAAO;AAAA;",
|
|
8
|
+
"debugId": "4C4140B5CFEF4A9764756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -9,6 +9,22 @@ interface SubgraphColumn {
|
|
|
9
9
|
search?: boolean;
|
|
10
10
|
default?: string | number | boolean;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* A foreign-key relation to another table in the same subgraph. Drives DDL FK
|
|
14
|
+
* constraints and ORM codegen (`@relation` in Prisma, `relations()` in Drizzle)
|
|
15
|
+
* so generated clients get typed joins. The referenced columns must form a
|
|
16
|
+
* `uniqueKeys` entry on the target table.
|
|
17
|
+
*/
|
|
18
|
+
interface SubgraphRelation {
|
|
19
|
+
/** Relation field name on this table's generated model (e.g. "pool"). */
|
|
20
|
+
name: string;
|
|
21
|
+
/** Target table name in this subgraph. */
|
|
22
|
+
references: string;
|
|
23
|
+
/** Local column(s) holding the foreign key. */
|
|
24
|
+
fields: string[];
|
|
25
|
+
/** Target column(s) the fields point at (a uniqueKeys entry on the target). */
|
|
26
|
+
referencedColumns: string[];
|
|
27
|
+
}
|
|
12
28
|
/** Table definition within a subgraph schema */
|
|
13
29
|
interface SubgraphTable {
|
|
14
30
|
columns: Record<string, SubgraphColumn>;
|
|
@@ -16,6 +32,8 @@ interface SubgraphTable {
|
|
|
16
32
|
indexes?: string[][];
|
|
17
33
|
/** Unique key constraints (each entry is an array of column names). Required for upsert. */
|
|
18
34
|
uniqueKeys?: string[][];
|
|
35
|
+
/** Foreign-key relations to other tables (for typed ORM joins). */
|
|
36
|
+
relations?: SubgraphRelation[];
|
|
19
37
|
}
|
|
20
38
|
/** Subgraph schema — maps table names to table definitions */
|
|
21
39
|
type SubgraphSchema = Record<string, SubgraphTable>;
|
|
@@ -42,45 +60,53 @@ interface StxLockFilter {
|
|
|
42
60
|
lockedAddress?: string;
|
|
43
61
|
minAmount?: bigint;
|
|
44
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Restrict a source to contracts conforming to a trait/standard (e.g. "sip-010")
|
|
65
|
+
* instead of a fixed contract — resolved from the contract registry at match time,
|
|
66
|
+
* as-of each processed block. Lets a source index "all SIP-010 tokens" etc.
|
|
67
|
+
*/
|
|
68
|
+
type TraitScope = {
|
|
69
|
+
trait?: string
|
|
70
|
+
};
|
|
45
71
|
/** FT event filters */
|
|
46
|
-
interface FtTransferFilter {
|
|
72
|
+
interface FtTransferFilter extends TraitScope {
|
|
47
73
|
type: "ft_transfer";
|
|
48
74
|
assetIdentifier?: string;
|
|
49
75
|
sender?: string;
|
|
50
76
|
recipient?: string;
|
|
51
77
|
minAmount?: bigint;
|
|
52
78
|
}
|
|
53
|
-
interface FtMintFilter {
|
|
79
|
+
interface FtMintFilter extends TraitScope {
|
|
54
80
|
type: "ft_mint";
|
|
55
81
|
assetIdentifier?: string;
|
|
56
82
|
recipient?: string;
|
|
57
83
|
minAmount?: bigint;
|
|
58
84
|
}
|
|
59
|
-
interface FtBurnFilter {
|
|
85
|
+
interface FtBurnFilter extends TraitScope {
|
|
60
86
|
type: "ft_burn";
|
|
61
87
|
assetIdentifier?: string;
|
|
62
88
|
sender?: string;
|
|
63
89
|
minAmount?: bigint;
|
|
64
90
|
}
|
|
65
91
|
/** NFT event filters */
|
|
66
|
-
interface NftTransferFilter {
|
|
92
|
+
interface NftTransferFilter extends TraitScope {
|
|
67
93
|
type: "nft_transfer";
|
|
68
94
|
assetIdentifier?: string;
|
|
69
95
|
sender?: string;
|
|
70
96
|
recipient?: string;
|
|
71
97
|
}
|
|
72
|
-
interface NftMintFilter {
|
|
98
|
+
interface NftMintFilter extends TraitScope {
|
|
73
99
|
type: "nft_mint";
|
|
74
100
|
assetIdentifier?: string;
|
|
75
101
|
recipient?: string;
|
|
76
102
|
}
|
|
77
|
-
interface NftBurnFilter {
|
|
103
|
+
interface NftBurnFilter extends TraitScope {
|
|
78
104
|
type: "nft_burn";
|
|
79
105
|
assetIdentifier?: string;
|
|
80
106
|
sender?: string;
|
|
81
107
|
}
|
|
82
108
|
/** Contract event filters */
|
|
83
|
-
interface ContractCallFilter {
|
|
109
|
+
interface ContractCallFilter extends TraitScope {
|
|
84
110
|
type: "contract_call";
|
|
85
111
|
contractId?: string;
|
|
86
112
|
functionName?: string;
|
|
@@ -98,7 +124,7 @@ interface ContractDeployFilter {
|
|
|
98
124
|
deployer?: string;
|
|
99
125
|
contractName?: string;
|
|
100
126
|
}
|
|
101
|
-
interface PrintEventFilter {
|
|
127
|
+
interface PrintEventFilter extends TraitScope {
|
|
102
128
|
type: "print_event";
|
|
103
129
|
contractId?: string;
|
|
104
130
|
topic?: string;
|
|
@@ -228,6 +254,14 @@ declare function deploySchema(db: AnyDb, def: SubgraphDefinition, handlerPath: s
|
|
|
228
254
|
version?: string
|
|
229
255
|
handlerCode?: string
|
|
230
256
|
sourceCode?: string
|
|
257
|
+
/**
|
|
258
|
+
* BYO data plane: when set, schema DDL (CREATE/ALTER/index) runs against
|
|
259
|
+
* the user-owned DB while the subgraphs registry row stays on `db`
|
|
260
|
+
* (managed). Defaults to `db` — managed deploys are unchanged.
|
|
261
|
+
*/
|
|
262
|
+
dataDb?: AnyDb
|
|
263
|
+
/** Encrypted user-DB connection string to persist on the registry row. */
|
|
264
|
+
databaseUrlEnc?: Buffer | null
|
|
231
265
|
}): Promise<{
|
|
232
266
|
action: "created" | "unchanged" | "handler_updated" | "updated" | "reindexed"
|
|
233
267
|
subgraphId: string
|
package/dist/src/schema/index.js
CHANGED
|
@@ -23,7 +23,13 @@ var SubgraphColumnSchema = z.object({
|
|
|
23
23
|
var SubgraphTableSchema = z.object({
|
|
24
24
|
columns: z.record(z.string(), SubgraphColumnSchema).refine((c) => Object.keys(c).length > 0, "Table must have at least one column"),
|
|
25
25
|
indexes: z.array(z.array(z.string())).optional(),
|
|
26
|
-
uniqueKeys: z.array(z.array(z.string())).optional()
|
|
26
|
+
uniqueKeys: z.array(z.array(z.string())).optional(),
|
|
27
|
+
relations: z.array(z.object({
|
|
28
|
+
name: z.string(),
|
|
29
|
+
references: z.string(),
|
|
30
|
+
fields: z.array(z.string()).min(1),
|
|
31
|
+
referencedColumns: z.array(z.string()).min(1)
|
|
32
|
+
})).optional()
|
|
27
33
|
});
|
|
28
34
|
var SubgraphSchemaSchema = z.record(z.string(), SubgraphTableSchema).refine((s) => Object.keys(s).length > 0, "Schema must have at least one table");
|
|
29
35
|
var VALID_FILTER_TYPES = [
|
|
@@ -55,7 +61,8 @@ var SubgraphFilterSchema = z.object({
|
|
|
55
61
|
contractName: z.string().optional(),
|
|
56
62
|
topic: z.string().optional(),
|
|
57
63
|
lockedAddress: z.string().optional(),
|
|
58
|
-
abi: z.record(z.string(), z.any()).optional()
|
|
64
|
+
abi: z.record(z.string(), z.any()).optional(),
|
|
65
|
+
trait: z.string().optional()
|
|
59
66
|
}).strict();
|
|
60
67
|
var SubgraphDefinitionSchema = z.object({
|
|
61
68
|
name: SubgraphNameSchema,
|
|
@@ -151,6 +158,12 @@ function generateSubgraphSQL(def, schemaNameOverride) {
|
|
|
151
158
|
}
|
|
152
159
|
}
|
|
153
160
|
}
|
|
161
|
+
for (const [tableName, tableDef] of Object.entries(def.schema)) {
|
|
162
|
+
for (const rel of tableDef.relations ?? []) {
|
|
163
|
+
const constraintName = `fk_${schemaName}_${tableName}_${rel.name}`;
|
|
164
|
+
statements.push(`ALTER TABLE ${schemaName}.${tableName} ADD CONSTRAINT ${constraintName} ` + `FOREIGN KEY (${rel.fields.join(", ")}) ` + `REFERENCES ${schemaName}.${rel.references} (${rel.referencedColumns.join(", ")})`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
154
167
|
const hashInput = JSON.stringify({
|
|
155
168
|
name: def.name,
|
|
156
169
|
schema: def.schema,
|
|
@@ -212,10 +225,29 @@ function bumpPatch(version) {
|
|
|
212
225
|
const patch = Number.parseInt(parts[2] ?? "0", 10);
|
|
213
226
|
return `${parts[0]}.${parts[1]}.${Number.isNaN(patch) ? 1 : patch + 1}`;
|
|
214
227
|
}
|
|
228
|
+
function renderDeployPlan(def, schemaName) {
|
|
229
|
+
validateSubgraphDefinition(def);
|
|
230
|
+
const { statements } = generateSubgraphSQL(def, schemaName);
|
|
231
|
+
const schema = schemaName ?? pgSchemaName(def.name);
|
|
232
|
+
const grantScript = [
|
|
233
|
+
"-- Run once on YOUR database as an owner/superuser, replacing <role>",
|
|
234
|
+
"-- with the role whose credentials you give Secondlayer.",
|
|
235
|
+
"-- Secondlayer then creates and owns only this one schema:",
|
|
236
|
+
`GRANT CREATE ON DATABASE current_database() TO <role>;`,
|
|
237
|
+
`-- (after first deploy <role> owns "${schema}"; no further grants needed)`
|
|
238
|
+
].join(`
|
|
239
|
+
`);
|
|
240
|
+
return { schemaName: schema, statements, grantScript };
|
|
241
|
+
}
|
|
215
242
|
async function deploySchema(db, def, handlerPath, opts) {
|
|
216
243
|
validateSubgraphDefinition(def);
|
|
217
244
|
const { statements, hash } = generateSubgraphSQL(def, opts?.schemaName);
|
|
218
245
|
const { getSubgraph, registerSubgraph } = await import("@secondlayer/shared/db/queries/subgraphs");
|
|
246
|
+
const ddlDb = opts?.dataDb ?? db;
|
|
247
|
+
const byo = opts?.dataDb != null;
|
|
248
|
+
const refuseDestructiveOnByo = (reason) => {
|
|
249
|
+
throw new Error(`Breaking schema change on a BYO subgraph (${reason}) would drop data in your database. Drop the schema "${opts?.schemaName ?? pgSchemaName(def.name)}" manually and re-deploy to rebuild.`);
|
|
250
|
+
};
|
|
219
251
|
const existing = await getSubgraph(db, def.name, opts?.accountId);
|
|
220
252
|
const schemaName = opts?.schemaName ?? pgSchemaName(def.name);
|
|
221
253
|
const newVersion = opts?.version ?? (existing ? bumpPatch(existing.version) : "1.0.0");
|
|
@@ -237,7 +269,8 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
237
269
|
handlerCode: opts?.handlerCode,
|
|
238
270
|
sourceCode: opts?.sourceCode,
|
|
239
271
|
schemaName,
|
|
240
|
-
startBlock: def.startBlock
|
|
272
|
+
startBlock: def.startBlock,
|
|
273
|
+
databaseUrlEnc: opts?.databaseUrlEnc ?? null
|
|
241
274
|
};
|
|
242
275
|
if (existing) {
|
|
243
276
|
const schemaExists = await sql`
|
|
@@ -245,10 +278,10 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
245
278
|
SELECT 1 FROM information_schema.schemata
|
|
246
279
|
WHERE schema_name = ${schemaName}
|
|
247
280
|
) AS "exists"
|
|
248
|
-
`.execute(
|
|
281
|
+
`.execute(ddlDb).then((r) => r.rows[0]?.exists ?? false);
|
|
249
282
|
if (!schemaExists) {
|
|
250
283
|
for (const stmt of statements) {
|
|
251
|
-
await sql.raw(stmt).execute(
|
|
284
|
+
await sql.raw(stmt).execute(ddlDb);
|
|
252
285
|
}
|
|
253
286
|
const sg2 = await registerSubgraph(db, regData);
|
|
254
287
|
return { action: "reindexed", subgraphId: sg2.id, version: newVersion };
|
|
@@ -267,9 +300,11 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
267
300
|
};
|
|
268
301
|
}
|
|
269
302
|
if (existing.schema_hash === hash && opts?.forceReindex) {
|
|
270
|
-
|
|
303
|
+
if (byo)
|
|
304
|
+
refuseDestructiveOnByo("force reindex");
|
|
305
|
+
await sql.raw(`DROP SCHEMA IF EXISTS "${schemaName}" CASCADE`).execute(ddlDb);
|
|
271
306
|
for (const stmt of statements) {
|
|
272
|
-
await sql.raw(stmt).execute(
|
|
307
|
+
await sql.raw(stmt).execute(ddlDb);
|
|
273
308
|
}
|
|
274
309
|
const sg2 = await registerSubgraph(db, regData);
|
|
275
310
|
return { action: "reindexed", subgraphId: sg2.id, version: newVersion };
|
|
@@ -278,9 +313,12 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
278
313
|
const diff = diffSchema(existing.definition.schema, def.schema);
|
|
279
314
|
const { breaking, reasons } = hasBreakingChanges(diff);
|
|
280
315
|
if (breaking || opts?.forceReindex) {
|
|
281
|
-
|
|
316
|
+
if (byo) {
|
|
317
|
+
refuseDestructiveOnByo(reasons.length > 0 ? reasons.join("; ") : "force reindex");
|
|
318
|
+
}
|
|
319
|
+
await sql.raw(`DROP SCHEMA IF EXISTS "${schemaName}" CASCADE`).execute(ddlDb);
|
|
282
320
|
for (const stmt of statements) {
|
|
283
|
-
await sql.raw(stmt).execute(
|
|
321
|
+
await sql.raw(stmt).execute(ddlDb);
|
|
284
322
|
}
|
|
285
323
|
const sg3 = await registerSubgraph(db, regData);
|
|
286
324
|
const deployDiff2 = {
|
|
@@ -317,15 +355,15 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
317
355
|
await sql.raw(`CREATE TABLE IF NOT EXISTS ${qualifiedName} (
|
|
318
356
|
${colDefs.join(`,
|
|
319
357
|
`)}
|
|
320
|
-
)`).execute(
|
|
321
|
-
await sql.raw(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_block_height ON ${qualifiedName} (_block_height)`).execute(
|
|
322
|
-
await sql.raw(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_tx_id ON ${qualifiedName} (_tx_id)`).execute(
|
|
358
|
+
)`).execute(ddlDb);
|
|
359
|
+
await sql.raw(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_block_height ON ${qualifiedName} (_block_height)`).execute(ddlDb);
|
|
360
|
+
await sql.raw(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_tx_id ON ${qualifiedName} (_tx_id)`).execute(ddlDb);
|
|
323
361
|
for (const [colName, col] of Object.entries(tableDef.columns)) {
|
|
324
362
|
if (col.indexed) {
|
|
325
|
-
await sql.raw(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_${colName} ON ${qualifiedName} (${colName})`).execute(
|
|
363
|
+
await sql.raw(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_${colName} ON ${qualifiedName} (${colName})`).execute(ddlDb);
|
|
326
364
|
}
|
|
327
365
|
if (col.search) {
|
|
328
|
-
await sql.raw(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_${colName}_trgm ON ${qualifiedName} USING gin (${colName} gin_trgm_ops)`).execute(
|
|
366
|
+
await sql.raw(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_${colName}_trgm ON ${qualifiedName} USING gin (${colName} gin_trgm_ops)`).execute(ddlDb);
|
|
329
367
|
}
|
|
330
368
|
}
|
|
331
369
|
}
|
|
@@ -344,12 +382,12 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
344
382
|
if (!sqlType)
|
|
345
383
|
continue;
|
|
346
384
|
const nullable = col.nullable ? "" : ` NOT NULL DEFAULT ${getDefault(col.type)}`;
|
|
347
|
-
await sql.raw(`ALTER TABLE ${qualifiedName} ADD COLUMN ${colName} ${sqlType}${nullable}`).execute(
|
|
385
|
+
await sql.raw(`ALTER TABLE ${qualifiedName} ADD COLUMN ${colName} ${sqlType}${nullable}`).execute(ddlDb);
|
|
348
386
|
if (col.indexed) {
|
|
349
|
-
await sql.raw(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_${colName} ON ${qualifiedName} (${colName})`).execute(
|
|
387
|
+
await sql.raw(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_${colName} ON ${qualifiedName} (${colName})`).execute(ddlDb);
|
|
350
388
|
}
|
|
351
389
|
if (col.search) {
|
|
352
|
-
await sql.raw(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_${colName}_trgm ON ${qualifiedName} USING gin (${colName} gin_trgm_ops)`).execute(
|
|
390
|
+
await sql.raw(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_${colName}_trgm ON ${qualifiedName} USING gin (${colName} gin_trgm_ops)`).execute(ddlDb);
|
|
353
391
|
}
|
|
354
392
|
}
|
|
355
393
|
}
|
|
@@ -374,7 +412,7 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
374
412
|
}
|
|
375
413
|
}
|
|
376
414
|
for (const stmt of statements) {
|
|
377
|
-
await sql.raw(stmt).execute(
|
|
415
|
+
await sql.raw(stmt).execute(ddlDb);
|
|
378
416
|
}
|
|
379
417
|
const sg = await registerSubgraph(db, regData);
|
|
380
418
|
return { action: "created", subgraphId: sg.id, version: newVersion };
|
|
@@ -403,5 +441,5 @@ export {
|
|
|
403
441
|
deploySchema
|
|
404
442
|
};
|
|
405
443
|
|
|
406
|
-
//# debugId=
|
|
444
|
+
//# debugId=6726D9DC9065FAAD64756E2164756E21
|
|
407
445
|
//# sourceMappingURL=index.js.map
|