@secondlayer/subgraphs 4.1.4 → 4.2.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.
Files changed (41) hide show
  1. package/dist/src/index.d.ts +91 -31
  2. package/dist/src/index.js +3319 -3067
  3. package/dist/src/index.js.map +11 -9
  4. package/dist/src/runtime/block-processor.d.ts +72 -23
  5. package/dist/src/runtime/block-processor.js +867 -400
  6. package/dist/src/runtime/block-processor.js.map +9 -6
  7. package/dist/src/runtime/catchup.d.ts +72 -23
  8. package/dist/src/runtime/catchup.js +878 -411
  9. package/dist/src/runtime/catchup.js.map +9 -6
  10. package/dist/src/runtime/processor.js +943 -475
  11. package/dist/src/runtime/processor.js.map +10 -7
  12. package/dist/src/runtime/reindex.d.ts +72 -23
  13. package/dist/src/runtime/reindex.js +911 -444
  14. package/dist/src/runtime/reindex.js.map +9 -6
  15. package/dist/src/runtime/reorg.d.ts +72 -23
  16. package/dist/src/runtime/reorg.js +890 -422
  17. package/dist/src/runtime/reorg.js.map +10 -7
  18. package/dist/src/runtime/replay.js +12 -6
  19. package/dist/src/runtime/replay.js.map +4 -4
  20. package/dist/src/runtime/runner.d.ts +72 -23
  21. package/dist/src/runtime/runner.js +757 -28
  22. package/dist/src/runtime/runner.js.map +9 -5
  23. package/dist/src/runtime/source-matcher.d.ts +80 -24
  24. package/dist/src/runtime/source-matcher.js +12 -5
  25. package/dist/src/runtime/source-matcher.js.map +3 -3
  26. package/dist/src/schema/index.d.ts +72 -23
  27. package/dist/src/schema/index.js +115 -1
  28. package/dist/src/schema/index.js.map +3 -3
  29. package/dist/src/service.js +983 -515
  30. package/dist/src/service.js.map +10 -7
  31. package/dist/src/testing/index.d.ts +195 -24
  32. package/dist/src/testing/index.js +1049 -30
  33. package/dist/src/testing/index.js.map +10 -5
  34. package/dist/src/triggers/index.d.ts +83 -24
  35. package/dist/src/triggers/index.js +5 -2
  36. package/dist/src/triggers/index.js.map +3 -3
  37. package/dist/src/types.d.ts +73 -24
  38. package/dist/src/validate.d.ts +72 -23
  39. package/dist/src/validate.js +115 -1
  40. package/dist/src/validate.js.map +3 -3
  41. package/package.json +2 -2
@@ -32,15 +32,679 @@ function decodeFunctionArgs(args) {
32
32
  return args.map(decodeClarityValue);
33
33
  }
34
34
 
35
+ // src/runtime/source-matcher.ts
36
+ var patternCache = new Map;
37
+ function matchAny(value, pattern) {
38
+ return Array.isArray(pattern) ? pattern.some((p) => matchPattern(value, p)) : matchPattern(value, pattern);
39
+ }
40
+ function matchPattern(value, pattern) {
41
+ if (!pattern.includes("*"))
42
+ return value === pattern;
43
+ let re = patternCache.get(pattern);
44
+ if (!re) {
45
+ const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
46
+ re = new RegExp(`^${regex}$`);
47
+ patternCache.set(pattern, re);
48
+ }
49
+ return re.test(value);
50
+ }
51
+ var EMPTY_SET = new Set;
52
+ function factoryAllows(filter, contractId, factoryContracts) {
53
+ if (!filter.factory)
54
+ return true;
55
+ if (!contractId)
56
+ return false;
57
+ return (factoryContracts.get(filter.factory.from) ?? EMPTY_SET).has(contractId);
58
+ }
59
+ function readPath(payload, path) {
60
+ let cur = payload;
61
+ for (const key of path.split(".")) {
62
+ if (cur === null || typeof cur !== "object")
63
+ return;
64
+ cur = cur[key];
65
+ }
66
+ return cur;
67
+ }
68
+ function traitAllows(filter, contractId, traitContracts) {
69
+ const trait = filter.trait;
70
+ if (!trait)
71
+ return true;
72
+ if (!contractId)
73
+ return false;
74
+ return (traitContracts.get(trait) ?? EMPTY_SET).has(contractId);
75
+ }
76
+ function assetContract(assetId) {
77
+ return assetId?.split("::")[0];
78
+ }
79
+ function printContractId(data) {
80
+ if (!data)
81
+ return;
82
+ const id = (typeof data.contract_identifier === "string" ? data.contract_identifier : undefined) ?? (typeof data.contract_id === "string" ? data.contract_id : undefined);
83
+ return id || undefined;
84
+ }
85
+ function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map) {
86
+ const results = [];
87
+ switch (filter.type) {
88
+ case "stx_transfer":
89
+ case "stx_mint":
90
+ case "stx_burn":
91
+ case "stx_lock": {
92
+ const eventType = `${filter.type}_event`;
93
+ for (const tx of transactions) {
94
+ const txEvents = eventsByTx.get(tx.tx_id) ?? [];
95
+ const matched = txEvents.filter((e) => e.type === eventType);
96
+ if (matched.length === 0)
97
+ continue;
98
+ const filtered = matched.filter((e) => {
99
+ const data = e.data;
100
+ if (!data)
101
+ return false;
102
+ if ("sender" in filter && filter.sender) {
103
+ if (!matchPattern(data.sender, filter.sender))
104
+ return false;
105
+ }
106
+ if ("recipient" in filter && filter.recipient) {
107
+ if (!matchPattern(data.recipient, filter.recipient))
108
+ return false;
109
+ }
110
+ if ("lockedAddress" in filter && filter.lockedAddress) {
111
+ if (!matchPattern(data.locked_address, filter.lockedAddress))
112
+ return false;
113
+ }
114
+ if ("minAmount" in filter && filter.minAmount !== undefined) {
115
+ const amount = BigInt(data.amount ?? data.locked_amount ?? "0");
116
+ if (amount < filter.minAmount)
117
+ return false;
118
+ }
119
+ if ("maxAmount" in filter && filter.maxAmount !== undefined) {
120
+ const amount = BigInt(data.amount ?? "0");
121
+ if (amount > filter.maxAmount)
122
+ return false;
123
+ }
124
+ return true;
125
+ });
126
+ if (filtered.length > 0) {
127
+ results.push({ tx, events: filtered });
128
+ }
129
+ }
130
+ break;
131
+ }
132
+ case "ft_transfer":
133
+ case "ft_mint":
134
+ case "ft_burn": {
135
+ const eventType = `${filter.type}_event`;
136
+ for (const tx of transactions) {
137
+ const txEvents = eventsByTx.get(tx.tx_id) ?? [];
138
+ const matched = txEvents.filter((e) => {
139
+ if (e.type !== eventType)
140
+ return false;
141
+ const data = e.data;
142
+ if (!data)
143
+ return false;
144
+ if (filter.assetIdentifier) {
145
+ const assetId = data.asset_identifier;
146
+ if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
147
+ return false;
148
+ }
149
+ if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
150
+ return false;
151
+ if ("sender" in filter && filter.sender) {
152
+ if (!matchPattern(data.sender, filter.sender))
153
+ return false;
154
+ }
155
+ if ("recipient" in filter && filter.recipient) {
156
+ if (!matchPattern(data.recipient, filter.recipient))
157
+ return false;
158
+ }
159
+ if (filter.minAmount !== undefined) {
160
+ const amount = BigInt(data.amount ?? "0");
161
+ if (amount < filter.minAmount)
162
+ return false;
163
+ }
164
+ return true;
165
+ });
166
+ if (matched.length > 0) {
167
+ results.push({ tx, events: matched });
168
+ }
169
+ }
170
+ break;
171
+ }
172
+ case "nft_transfer":
173
+ case "nft_mint":
174
+ case "nft_burn": {
175
+ const eventType = `${filter.type}_event`;
176
+ for (const tx of transactions) {
177
+ const txEvents = eventsByTx.get(tx.tx_id) ?? [];
178
+ const matched = txEvents.filter((e) => {
179
+ if (e.type !== eventType)
180
+ return false;
181
+ const data = e.data;
182
+ if (!data)
183
+ return false;
184
+ if (filter.assetIdentifier) {
185
+ const assetId = data.asset_identifier;
186
+ if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
187
+ return false;
188
+ }
189
+ if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
190
+ return false;
191
+ if ("sender" in filter && filter.sender) {
192
+ if (!matchPattern(data.sender, filter.sender))
193
+ return false;
194
+ }
195
+ if ("recipient" in filter && filter.recipient) {
196
+ if (!matchPattern(data.recipient, filter.recipient))
197
+ return false;
198
+ }
199
+ return true;
200
+ });
201
+ if (matched.length > 0) {
202
+ results.push({ tx, events: matched });
203
+ }
204
+ }
205
+ break;
206
+ }
207
+ case "contract_call": {
208
+ for (const tx of transactions) {
209
+ if (tx.type !== "contract_call")
210
+ continue;
211
+ if (filter.contractId) {
212
+ if (!tx.contract_id || !matchAny(tx.contract_id, filter.contractId))
213
+ continue;
214
+ }
215
+ if (!factoryAllows(filter, tx.contract_id, factoryContracts))
216
+ continue;
217
+ if (filter.functionName) {
218
+ if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
219
+ continue;
220
+ }
221
+ if (filter.caller) {
222
+ if (!matchPattern(tx.sender, filter.caller))
223
+ continue;
224
+ }
225
+ if (!traitAllows(filter, tx.contract_id, traitContracts))
226
+ continue;
227
+ const txEvents = eventsByTx.get(tx.tx_id) ?? [];
228
+ results.push({ tx, events: txEvents });
229
+ }
230
+ break;
231
+ }
232
+ case "contract_deploy": {
233
+ for (const tx of transactions) {
234
+ if (tx.type !== "smart_contract")
235
+ continue;
236
+ if (filter.deployer) {
237
+ if (!matchPattern(tx.sender, filter.deployer))
238
+ continue;
239
+ }
240
+ if (filter.contractName) {
241
+ const name = tx.contract_id?.split(".")[1] ?? "";
242
+ if (!matchPattern(name, filter.contractName))
243
+ continue;
244
+ }
245
+ const txEvents = eventsByTx.get(tx.tx_id) ?? [];
246
+ results.push({ tx, events: txEvents });
247
+ }
248
+ break;
249
+ }
250
+ case "print_event": {
251
+ for (const tx of transactions) {
252
+ const txEvents = eventsByTx.get(tx.tx_id) ?? [];
253
+ const matched = txEvents.filter((e) => {
254
+ if (e.type !== "smart_contract_event" && e.type !== "contract_event")
255
+ return false;
256
+ const data = e.data;
257
+ if (!data)
258
+ return false;
259
+ if (data.topic !== "print")
260
+ return false;
261
+ const contractId = printContractId(data);
262
+ if (filter.contractId) {
263
+ if (!contractId || !matchAny(contractId, filter.contractId))
264
+ return false;
265
+ }
266
+ if (!traitAllows(filter, contractId, traitContracts))
267
+ return false;
268
+ if (!factoryAllows(filter, contractId, factoryContracts))
269
+ return false;
270
+ return true;
271
+ });
272
+ if (matched.length > 0) {
273
+ results.push({ tx, events: matched });
274
+ }
275
+ }
276
+ break;
277
+ }
278
+ }
279
+ return results;
280
+ }
281
+ function matchSources(sources, transactions, events, traitContracts = new Map, factoryContracts = new Map) {
282
+ const eventsByTx = new Map;
283
+ for (const event of events) {
284
+ const list = eventsByTx.get(event.tx_id) ?? [];
285
+ list.push(event);
286
+ eventsByTx.set(event.tx_id, list);
287
+ }
288
+ const seen = new Set;
289
+ const results = [];
290
+ for (const [sourceName, filter] of Object.entries(sources)) {
291
+ const matches = matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts);
292
+ for (const match of matches) {
293
+ const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
294
+ if (!seen.has(dedupeKey)) {
295
+ seen.add(dedupeKey);
296
+ results.push({ ...match, sourceName });
297
+ }
298
+ }
299
+ }
300
+ return results;
301
+ }
302
+
35
303
  // src/runtime/runner.ts
36
304
  import { getErrorMessage } from "@secondlayer/shared";
37
- import { logger } from "@secondlayer/shared/logger";
305
+ import { logger as logger2 } from "@secondlayer/shared/logger";
38
306
  import {
39
307
  clarityValueToJS,
40
- deserializeCV as deserializeCV2,
308
+ deserializeCV as deserializeCV3,
41
309
  toCamelCase
42
310
  } from "@secondlayer/stacks/clarity";
43
311
 
312
+ // src/print-schema.ts
313
+ import {
314
+ deserializeCV as deserializeCV2
315
+ } from "@secondlayer/stacks/clarity";
316
+ function camelizeDataKey(str) {
317
+ return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
318
+ }
319
+ function camelizeKeys(obj) {
320
+ if (obj === null || obj === undefined)
321
+ return obj;
322
+ if (typeof obj !== "object")
323
+ return obj;
324
+ if (Array.isArray(obj))
325
+ return obj.map(camelizeKeys);
326
+ const result = {};
327
+ for (const [k, v] of Object.entries(obj)) {
328
+ result[camelizeDataKey(k)] = camelizeKeys(v);
329
+ }
330
+ return result;
331
+ }
332
+ function cvToTree(cv) {
333
+ switch (cv.type) {
334
+ case "uint":
335
+ case "int":
336
+ return { kind: cv.type };
337
+ case "true":
338
+ case "false":
339
+ return { kind: "bool" };
340
+ case "address":
341
+ case "contract":
342
+ return { kind: "principal" };
343
+ case "buffer":
344
+ return { kind: "buffer", len: cv.value.length / 2 };
345
+ case "ascii":
346
+ return { kind: "ascii", len: cv.value.length };
347
+ case "utf8":
348
+ return { kind: "utf8", len: new TextEncoder().encode(cv.value).length };
349
+ case "none":
350
+ return { kind: "optional", inner: null };
351
+ case "some":
352
+ return { kind: "optional", inner: cvToTree(cv.value) };
353
+ case "ok":
354
+ return { kind: "response", ok: cvToTree(cv.value), err: null };
355
+ case "err":
356
+ return { kind: "response", ok: null, err: cvToTree(cv.value) };
357
+ case "list": {
358
+ let inner = null;
359
+ for (const el of cv.value) {
360
+ const t = cvToTree(el);
361
+ inner = inner ? unify(inner, t) : t;
362
+ }
363
+ return { kind: "list", inner };
364
+ }
365
+ case "tuple": {
366
+ const entries = new Map;
367
+ for (const [k, v] of Object.entries(cv.value)) {
368
+ entries.set(k, { tree: cvToTree(v), present: 1 });
369
+ }
370
+ return { kind: "tuple", count: 1, entries };
371
+ }
372
+ }
373
+ }
374
+ function unifyNullable(a, b) {
375
+ if (!a)
376
+ return b;
377
+ if (!b)
378
+ return a;
379
+ return unify(a, b);
380
+ }
381
+ var UNION_KIND_ORDER = {
382
+ uint: 0,
383
+ int: 1,
384
+ bool: 2,
385
+ principal: 3,
386
+ buffer: 4,
387
+ ascii: 5,
388
+ utf8: 6,
389
+ list: 7,
390
+ tuple: 8,
391
+ response: 9
392
+ };
393
+ function makeUnion(members) {
394
+ const sorted = [...members].sort((a, b) => (UNION_KIND_ORDER[a.kind] ?? 99) - (UNION_KIND_ORDER[b.kind] ?? 99));
395
+ return { kind: "union", members: sorted };
396
+ }
397
+ function unify(a, b) {
398
+ if (a.kind === "optional" || b.kind === "optional") {
399
+ const ai = a.kind === "optional" ? a.inner : a;
400
+ const bi = b.kind === "optional" ? b.inner : b;
401
+ return { kind: "optional", inner: unifyNullable(ai, bi) };
402
+ }
403
+ if (a.kind === "union")
404
+ return unionAdd(a.members, b);
405
+ if (b.kind === "union")
406
+ return unionAdd(b.members, a);
407
+ if (a.kind !== b.kind)
408
+ return makeUnion([a, b]);
409
+ switch (a.kind) {
410
+ case "uint":
411
+ case "int":
412
+ case "bool":
413
+ case "principal":
414
+ return a;
415
+ case "buffer":
416
+ case "ascii":
417
+ case "utf8":
418
+ return { kind: a.kind, len: Math.max(a.len, b.len) };
419
+ case "list":
420
+ return {
421
+ kind: "list",
422
+ inner: unifyNullable(a.inner, b.inner)
423
+ };
424
+ case "response": {
425
+ const rb = b;
426
+ return {
427
+ kind: "response",
428
+ ok: unifyNullable(a.ok, rb.ok),
429
+ err: unifyNullable(a.err, rb.err)
430
+ };
431
+ }
432
+ case "tuple": {
433
+ const tb = b;
434
+ const entries = new Map([...a.entries].map(([k, e]) => [k, { ...e }]));
435
+ for (const [k, e] of tb.entries) {
436
+ const existing = entries.get(k);
437
+ entries.set(k, existing ? {
438
+ tree: unify(existing.tree, e.tree),
439
+ present: existing.present + e.present
440
+ } : { ...e });
441
+ }
442
+ return { kind: "tuple", count: a.count + tb.count, entries };
443
+ }
444
+ }
445
+ }
446
+ function unionAdd(members, t) {
447
+ if (t.kind === "union") {
448
+ let acc = { kind: "union", members };
449
+ for (const m of t.members)
450
+ acc = unify(acc, m);
451
+ return acc;
452
+ }
453
+ const next = [...members];
454
+ for (let i = 0;i < next.length; i++) {
455
+ const member = next[i];
456
+ if (!member)
457
+ continue;
458
+ const merged = unify(member, t);
459
+ if (merged.kind !== "union") {
460
+ next[i] = merged;
461
+ return makeUnion(next);
462
+ }
463
+ }
464
+ next.push(t);
465
+ return makeUnion(next);
466
+ }
467
+ function wrapOptional(t) {
468
+ return t.kind === "optional" ? t : { kind: "optional", inner: t };
469
+ }
470
+ function renderClarity(t) {
471
+ if (!t)
472
+ return "?";
473
+ switch (t.kind) {
474
+ case "uint":
475
+ case "int":
476
+ case "bool":
477
+ case "principal":
478
+ return t.kind;
479
+ case "buffer":
480
+ return `(buff ${t.len})`;
481
+ case "ascii":
482
+ return `(string-ascii ${t.len})`;
483
+ case "utf8":
484
+ return `(string-utf8 ${t.len})`;
485
+ case "optional":
486
+ return `(optional ${renderClarity(t.inner)})`;
487
+ case "list":
488
+ return `(list ${renderClarity(t.inner)})`;
489
+ case "response":
490
+ return `(response ${renderClarity(t.ok)} ${renderClarity(t.err)})`;
491
+ case "tuple": {
492
+ const parts = [...t.entries].map(([k, e]) => {
493
+ const tree = e.present < t.count ? wrapOptional(e.tree) : e.tree;
494
+ return `(${k} ${renderClarity(tree)})`;
495
+ });
496
+ return `(tuple ${parts.join(" ")})`;
497
+ }
498
+ case "union":
499
+ return t.members.map(renderClarity).join(" | ");
500
+ }
501
+ }
502
+ function renderTs(t) {
503
+ if (!t)
504
+ return "unknown";
505
+ switch (t.kind) {
506
+ case "uint":
507
+ case "int":
508
+ return "bigint";
509
+ case "bool":
510
+ return "boolean";
511
+ case "principal":
512
+ case "buffer":
513
+ case "ascii":
514
+ case "utf8":
515
+ return "string";
516
+ case "optional":
517
+ return t.inner ? `${renderTs(t.inner)} | null` : "unknown | null";
518
+ case "list": {
519
+ const inner = renderTs(t.inner);
520
+ return inner.includes(" | ") ? `(${inner})[]` : `${inner}[]`;
521
+ }
522
+ case "response": {
523
+ const sides = [...new Set([renderTs(t.ok), renderTs(t.err)])];
524
+ return sides.join(" | ");
525
+ }
526
+ case "tuple": {
527
+ const parts = [...t.entries].map(([k, e]) => {
528
+ const opt = e.present < t.count ? "?" : "";
529
+ return `${camelizeDataKey(k)}${opt}: ${renderTs(e.tree)}`;
530
+ });
531
+ return `{ ${parts.join("; ")} }`;
532
+ }
533
+ case "union":
534
+ return [...new Set(t.members.map((m) => renderTs(m)))].join(" | ");
535
+ }
536
+ }
537
+ function toColumnType(t) {
538
+ if (!t)
539
+ return "jsonb";
540
+ switch (t.kind) {
541
+ case "uint":
542
+ return "uint";
543
+ case "int":
544
+ return "int";
545
+ case "bool":
546
+ return "boolean";
547
+ case "principal":
548
+ return "principal";
549
+ case "buffer":
550
+ case "ascii":
551
+ case "utf8":
552
+ return "text";
553
+ case "list":
554
+ case "tuple":
555
+ return "jsonb";
556
+ case "optional":
557
+ return toColumnType(t.inner);
558
+ case "response":
559
+ return t.ok ? toColumnType(t.ok) : "jsonb";
560
+ case "union":
561
+ return "jsonb";
562
+ }
563
+ }
564
+ var MAX_DECODED_PER_TOPIC_NEWEST = 75;
565
+ var MAX_DECODED_PER_TOPIC_OLDEST = 25;
566
+ function inferPrintTopics(samples) {
567
+ const groups = new Map;
568
+ for (const s of samples) {
569
+ const group = groups.get(s.topic);
570
+ if (group)
571
+ group.push(s);
572
+ else
573
+ groups.set(s.topic, [s]);
574
+ }
575
+ const out = [];
576
+ for (const [topic, rows] of groups) {
577
+ let first = Number.POSITIVE_INFINITY;
578
+ let last = Number.NEGATIVE_INFINITY;
579
+ for (const r of rows) {
580
+ if (r.blockHeight < first)
581
+ first = r.blockHeight;
582
+ if (r.blockHeight > last)
583
+ last = r.blockHeight;
584
+ }
585
+ const withHex = [...rows].filter((r) => r.rawHex !== null).sort((a, b) => b.blockHeight - a.blockHeight);
586
+ const budget = MAX_DECODED_PER_TOPIC_NEWEST + MAX_DECODED_PER_TOPIC_OLDEST;
587
+ const picked = withHex.length <= budget ? withHex : [
588
+ ...withHex.slice(0, MAX_DECODED_PER_TOPIC_NEWEST),
589
+ ...withHex.slice(-MAX_DECODED_PER_TOPIC_OLDEST)
590
+ ];
591
+ const tuples = [];
592
+ let decoded = 0;
593
+ for (const p of picked) {
594
+ try {
595
+ const cv = deserializeCV2(p.rawHex);
596
+ decoded++;
597
+ if (cv.type === "tuple")
598
+ tuples.push(cv);
599
+ } catch {}
600
+ }
601
+ const nonTuple = decoded > 0 && tuples.length === 0;
602
+ const fields = [];
603
+ if (!nonTuple) {
604
+ const stats = new Map;
605
+ for (const t of tuples) {
606
+ for (const [key, value] of Object.entries(t.value)) {
607
+ if (key === "topic")
608
+ continue;
609
+ const tree = cvToTree(value);
610
+ const existing = stats.get(key);
611
+ if (existing) {
612
+ existing.present++;
613
+ existing.tree = unify(existing.tree, tree);
614
+ if (value.type === "none")
615
+ existing.noneCount++;
616
+ if (value.type === "none" || value.type === "some") {
617
+ existing.optionalSeen = true;
618
+ }
619
+ } else {
620
+ stats.set(key, {
621
+ tree,
622
+ present: 1,
623
+ noneCount: value.type === "none" ? 1 : 0,
624
+ optionalSeen: value.type === "none" || value.type === "some"
625
+ });
626
+ }
627
+ }
628
+ }
629
+ for (const [name, st] of [...stats].sort(([a], [b]) => a.localeCompare(b))) {
630
+ const field = {
631
+ name,
632
+ camel_name: camelizeDataKey(name),
633
+ clarity_type: renderClarity(st.tree),
634
+ ts_type: renderTs(st.tree),
635
+ column_type: toColumnType(st.tree),
636
+ always_present: st.present === tuples.length
637
+ };
638
+ if (st.optionalSeen) {
639
+ field.optional_some_rate = (st.present - st.noneCount) / st.present;
640
+ }
641
+ fields.push(field);
642
+ }
643
+ }
644
+ out.push({
645
+ topic,
646
+ count: rows.length,
647
+ first_height: first,
648
+ last_height: last,
649
+ non_tuple: nonTuple,
650
+ fields
651
+ });
652
+ }
653
+ return out.sort((a, b) => b.count - a.count);
654
+ }
655
+
656
+ // src/runtime/materialize.ts
657
+ function resolveColumn(col, event, tx, block) {
658
+ if ("fromTx" in col) {
659
+ return { ok: true, value: tx[col.fromTx] };
660
+ }
661
+ if ("fromBlock" in col) {
662
+ return { ok: true, value: block[col.fromBlock] };
663
+ }
664
+ const data = event.data;
665
+ const dataObj = data !== null && typeof data === "object" && !Array.isArray(data) ? data : undefined;
666
+ if (dataObj && Object.prototype.hasOwnProperty.call(dataObj, col.from)) {
667
+ return { ok: true, value: dataObj[col.from] };
668
+ }
669
+ if (col.from === "topic" && "topic" in event) {
670
+ return { ok: true, value: event.topic };
671
+ }
672
+ if (Object.prototype.hasOwnProperty.call(event, col.from)) {
673
+ return { ok: true, value: event[col.from] };
674
+ }
675
+ return { ok: false, missing: col.from };
676
+ }
677
+ function buildMaterializeRow(spec, event, ctx, tableColumns) {
678
+ const row = {};
679
+ for (const [colName, mapping] of Object.entries(spec.columns)) {
680
+ const resolved = resolveColumn(mapping, event, ctx.tx, ctx.block);
681
+ if (!resolved.ok) {
682
+ const colDef = tableColumns[colName];
683
+ if (colDef?.nullable) {
684
+ row[colName] = null;
685
+ continue;
686
+ }
687
+ return {
688
+ ok: false,
689
+ reason: `required materialize field "${resolved.missing}" missing for column "${colName}"`
690
+ };
691
+ }
692
+ row[colName] = resolved.value ?? null;
693
+ }
694
+ return { ok: true, row };
695
+ }
696
+ function applyMaterializeInsert(spec, event, ctx, schema) {
697
+ const table = schema[spec.table];
698
+ if (!table) {
699
+ return { ok: false, reason: `materialize table "${spec.table}" missing` };
700
+ }
701
+ const built = buildMaterializeRow(spec, event, ctx, table.columns);
702
+ if (!built.ok)
703
+ return built;
704
+ ctx.insert(spec.table, built.row);
705
+ return { ok: true };
706
+ }
707
+
44
708
  // src/runtime/print-validate.ts
45
709
  var COLUMN_TYPES = new Set([
46
710
  "text",
@@ -120,24 +784,62 @@ function validatePrintPayload(prints, topic, data) {
120
784
  return matchesTopic(declared, data);
121
785
  }
122
786
 
123
- // src/runtime/runner.ts
124
- var DEFAULT_ERROR_THRESHOLD = 50;
125
- function camelCase(str) {
126
- return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
787
+ // src/runtime/violations.ts
788
+ import { getTargetDb } from "@secondlayer/shared/db";
789
+ import { logger } from "@secondlayer/shared/logger";
790
+ import { sql } from "kysely";
791
+ var VIOLATIONS_CAP_PER_SUBGRAPH = 100;
792
+ function jsonSafe(value) {
793
+ if (typeof value === "bigint")
794
+ return value.toString();
795
+ if (value === null || value === undefined)
796
+ return value;
797
+ if (Array.isArray(value))
798
+ return value.map(jsonSafe);
799
+ if (typeof value === "object") {
800
+ const out = {};
801
+ for (const [k, v] of Object.entries(value)) {
802
+ out[k] = jsonSafe(v);
803
+ }
804
+ return out;
805
+ }
806
+ return value;
127
807
  }
128
- function camelizeKeys(obj) {
129
- if (obj === null || obj === undefined)
130
- return obj;
131
- if (typeof obj !== "object")
132
- return obj;
133
- if (Array.isArray(obj))
134
- return obj.map(camelizeKeys);
135
- const result = {};
136
- for (const [k, v] of Object.entries(obj)) {
137
- result[camelCase(k)] = camelizeKeys(v);
808
+ async function defaultRecordPrintViolation(input) {
809
+ try {
810
+ const db = getTargetDb();
811
+ const sample = jsonSafe(input.samplePayload);
812
+ await db.insertInto("subgraph_violations").values({
813
+ subgraph_name: input.subgraphName,
814
+ source_name: input.sourceName,
815
+ block_height: input.blockHeight,
816
+ tx_id: input.txId,
817
+ reason: input.reason,
818
+ sample_payload: sample
819
+ }).execute();
820
+ await sql`
821
+ DELETE FROM subgraph_violations
822
+ WHERE id IN (
823
+ SELECT id FROM subgraph_violations
824
+ WHERE subgraph_name = ${input.subgraphName}
825
+ ORDER BY seen_at DESC
826
+ OFFSET ${VIOLATIONS_CAP_PER_SUBGRAPH}
827
+ )
828
+ `.execute(db);
829
+ } catch (err) {
830
+ logger.warn("Failed to record print violation", {
831
+ subgraph: input.subgraphName,
832
+ error: err instanceof Error ? err.message : String(err)
833
+ });
138
834
  }
139
- return result;
140
835
  }
836
+ var recorder = defaultRecordPrintViolation;
837
+ async function recordPrintViolation(input) {
838
+ await recorder(input);
839
+ }
840
+
841
+ // src/runtime/runner.ts
842
+ var DEFAULT_ERROR_THRESHOLD = 50;
141
843
  function decodeFunctionArgs2(args) {
142
844
  let parsed = args;
143
845
  if (typeof parsed === "string") {
@@ -201,7 +903,7 @@ function buildContractCallInput(filter, tx) {
201
903
  return;
202
904
  try {
203
905
  const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
204
- input[toCamelCase(arg.name)] = decodeArg(arg.type, deserializeCV2(clean));
906
+ input[toCamelCase(arg.name)] = decodeArg(arg.type, deserializeCV3(clean));
205
907
  } catch {}
206
908
  });
207
909
  return input;
@@ -324,7 +1026,7 @@ function buildEventPayload(filter, tx, event) {
324
1026
  const { topic: _, ...rest } = clarityObj ?? {};
325
1027
  const data = Object.keys(rest).length > 0 ? camelizeKeys(rest) : rawValue && typeof rawValue !== "object" ? rawValue : {};
326
1028
  return {
327
- contractId: decoded.contract_identifier ?? tx.contract_id ?? "",
1029
+ contractId: printContractId(decoded) ?? tx.contract_id ?? "",
328
1030
  topic,
329
1031
  data: data ?? {},
330
1032
  tx: txMeta
@@ -385,16 +1087,18 @@ async function runHandlers(subgraph, matched, ctx, opts) {
385
1087
  units.sort((a, b) => (a.tx.tx_index ?? 0) - (b.tx.tx_index ?? 0) || (a.event?.event_index ?? -1) - (b.event?.event_index ?? -1));
386
1088
  for (const { tx, event, sourceName } of units) {
387
1089
  if (errors >= threshold) {
388
- logger.error("Subgraph error threshold reached, skipping remaining events", {
1090
+ logger2.error("Subgraph error threshold reached, skipping remaining events", {
389
1091
  subgraph: subgraph.name,
390
1092
  errors,
391
1093
  threshold
392
1094
  });
393
1095
  return { processed, errors, ...skipped > 0 ? { skipped } : {} };
394
1096
  }
1097
+ const filter = filterLookup.get(sourceName);
1098
+ const materialize = filter && "materialize" in filter && filter.materialize !== undefined ? filter.materialize : undefined;
395
1099
  const handler = subgraph.handlers[sourceName] ?? subgraph.handlers["*"] ?? null;
396
- if (!handler) {
397
- logger.warn("No handler found for source", {
1100
+ if (!handler && !materialize) {
1101
+ logger2.warn("No handler found for source", {
398
1102
  subgraph: subgraph.name,
399
1103
  sourceName,
400
1104
  txId: tx.tx_id
@@ -409,7 +1113,6 @@ async function runHandlers(subgraph, matched, ctx, opts) {
409
1113
  contractId: tx.contract_id ?? null,
410
1114
  functionName: tx.function_name ?? null
411
1115
  });
412
- const filter = filterLookup.get(sourceName);
413
1116
  const checkpoint = ctx.opsCheckpoint();
414
1117
  try {
415
1118
  let payload;
@@ -451,22 +1154,48 @@ async function runHandlers(subgraph, matched, ctx, opts) {
451
1154
  const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
452
1155
  if (!verdict.ok) {
453
1156
  skipped++;
454
- logger.warn("Print payload does not match declared prints schema", {
1157
+ logger2.warn("Print payload does not match declared prints schema", {
455
1158
  subgraph: subgraph.name,
456
1159
  sourceName,
457
1160
  topic: printPayload.topic,
458
1161
  txId: tx.tx_id,
459
1162
  reason: verdict.reason
460
1163
  });
1164
+ recordPrintViolation({
1165
+ subgraphName: subgraph.name,
1166
+ sourceName,
1167
+ blockHeight: ctx.block.height,
1168
+ txId: tx.tx_id,
1169
+ reason: verdict.reason,
1170
+ samplePayload: {
1171
+ topic: printPayload.topic ?? "",
1172
+ data: printPayload.data ?? {}
1173
+ }
1174
+ });
1175
+ continue;
1176
+ }
1177
+ }
1178
+ if (materialize) {
1179
+ const result = applyMaterializeInsert(materialize, payload, ctx, subgraph.schema);
1180
+ if (!result.ok) {
1181
+ skipped++;
1182
+ logger2.warn("Materialize skipped: required field missing", {
1183
+ subgraph: subgraph.name,
1184
+ sourceName,
1185
+ txId: tx.tx_id,
1186
+ reason: result.reason
1187
+ });
461
1188
  continue;
462
1189
  }
1190
+ processed++;
1191
+ } else if (handler) {
1192
+ await handler(payload, ctx);
1193
+ processed++;
463
1194
  }
464
- await handler(payload, ctx);
465
- processed++;
466
1195
  } catch (err) {
467
1196
  ctx.rollbackTo(checkpoint);
468
1197
  errors++;
469
- logger.error("Subgraph handler error", {
1198
+ logger2.error("Subgraph handler error", {
470
1199
  subgraph: subgraph.name,
471
1200
  sourceName,
472
1201
  txId: tx.tx_id,
@@ -483,5 +1212,5 @@ export {
483
1212
  buildContractCallInput
484
1213
  };
485
1214
 
486
- //# debugId=5FE61480B3DD60C064756E2164756E21
1215
+ //# debugId=B6BA5F58B8903CEC64756E2164756E21
487
1216
  //# sourceMappingURL=runner.js.map