@secondlayer/subgraphs 3.13.0 → 3.14.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.
@@ -664,6 +664,52 @@ declare function defineSubgraph<
664
664
  const Sources extends Record<string, SubgraphFilter>,
665
665
  const S extends SubgraphSchema
666
666
  >(def: TypedSubgraphDefinition<Sources, S>): TypedSubgraphDefinition<Sources, S>;
667
+ /**
668
+ * Empirical print-payload schema inference. Callers (the index print-schema
669
+ * endpoint) extract topic + raw Clarity hex per row; this module deserializes
670
+ * a bounded sample per topic, unifies the observed value shapes into a single
671
+ * structural tree, and renders Clarity/TS/ColumnType views of each field.
672
+ */
673
+ interface PrintSample {
674
+ blockHeight: number;
675
+ topic: string;
676
+ rawHex: string | null;
677
+ }
678
+ interface InferredPrintField {
679
+ /** Original kebab-case tuple key (the `topic` discriminant is excluded) */
680
+ name: string;
681
+ /** What handlers see on `e.data` — exact runner camelization */
682
+ camel_name: string;
683
+ /** Rendered Clarity type; buff/string lengths are the max observed */
684
+ clarity_type: string;
685
+ /** Decoded handler value type (uint→bigint, buffer→string, …) */
686
+ ts_type: string;
687
+ column_type: ColumnType;
688
+ /** Present in 100% of this topic's decoded samples */
689
+ always_present: boolean;
690
+ /** Only when optional observed: share of present samples that were some */
691
+ optional_some_rate?: number;
692
+ }
693
+ interface InferredTopicSchema {
694
+ topic: string;
695
+ count: number;
696
+ first_height: number;
697
+ last_height: number;
698
+ non_tuple: boolean;
699
+ fields: InferredPrintField[];
700
+ }
701
+ /**
702
+ * Kebab-case → camelCase using the exact runner regex, so `camel_name`
703
+ * matches what handlers see on `e.data` (runner.ts camelizeKeys).
704
+ */
705
+ declare function camelizeDataKey(str: string): string;
706
+ /**
707
+ * Infers per-topic field schemas from sampled print events. Counts and
708
+ * height bounds cover ALL rows of a topic (cheap); only a bounded subset is
709
+ * deserialized for typing. Rows with missing/undecodable hex still count but
710
+ * contribute nothing to typing.
711
+ */
712
+ declare function inferPrintTopics(samples: PrintSample[]): InferredTopicSchema[];
667
713
  interface GeneratedSQL {
668
714
  statements: string[];
669
715
  hash: string;
@@ -829,4 +875,4 @@ type SparseProbeTarget = {
829
875
  declare function canSparseScan(subgraph: SubgraphDefinition): boolean;
830
876
  /** Probe targets for a subgraph's filters: decoded type + contract scope when\\n* the filter pins one (assetIdentifier "SP….contract::asset" or contractId). */
831
877
  declare function sparseProbeTargets(subgraph: SubgraphDefinition): SparseProbeTarget[];
832
- export { validateSubgraphDefinition, sparseProbeTargets, resumeReindex, renderDeployPlan, reindexSubgraph, pgSchemaName, hasBreakingChanges, generateSubgraphSQL, generatePrismaSchema, generateKyselySchema, generateIndexSchema, generateDrizzleSchema, diffSchema, deploySchema, defineSubgraph, canSparseScan, backfillSubgraph, WriteRow, WhereInput, TypedSubgraphDefinition, TypedSubgraphContext, TypedHandlers, TxMeta, TableDiff, SystemRow, SubscribeOptions, SubgraphTableClient, SubgraphTable, SubgraphSchema, SubgraphHandler, SubgraphFilter, SubgraphDefinition, SubgraphContext, SubgraphColumn, StxTransferPayload, StxTransferFilter, StxMintPayload, StxMintFilter, StxLockPayload, StxLockFilter, StxBurnPayload, StxBurnFilter, SparseProbeTarget, RowValue, ReindexOptions, PrismaGenOptions, PrintEventPayload, PrintEventFor, PrintEventFilter, NftTransferPayload, NftTransferFilter, NftMintPayload, NftMintFilter, NftBurnPayload, NftBurnFilter, KyselyGenOptions, InferTableRow, InferSubgraphClient, InferColumnType, IndexCodegenTarget, IndexCodegenOptions, INDEX_CODEGEN_TABLES, GeneratedSQL, FtTransferPayload, FtTransferFilter, FtMintPayload, FtMintFilter, FtBurnPayload, FtBurnFilter, FindManyOptions, EventForFilter, DrizzleGenOptions, DeployPlan, DeployDiff, ContractDeployPayload, ContractDeployFilter, ContractCallPayload, ContractCallFilter, ContractCallEvent, ComputedValue, ComparisonFilter, ColumnType, ColumnToTS, ColumnDiff, ByoMigrationPlan, ByoBreakingChangeError, AnyEvent, AggregateSpec, AggregateResult };
878
+ export { validateSubgraphDefinition, sparseProbeTargets, resumeReindex, renderDeployPlan, reindexSubgraph, pgSchemaName, inferPrintTopics, hasBreakingChanges, generateSubgraphSQL, generatePrismaSchema, generateKyselySchema, generateIndexSchema, generateDrizzleSchema, diffSchema, deploySchema, defineSubgraph, canSparseScan, camelizeDataKey, backfillSubgraph, WriteRow, WhereInput, TypedSubgraphDefinition, TypedSubgraphContext, TypedHandlers, TxMeta, TableDiff, SystemRow, SubscribeOptions, SubgraphTableClient, SubgraphTable, SubgraphSchema, SubgraphHandler, SubgraphFilter, SubgraphDefinition, SubgraphContext, SubgraphColumn, StxTransferPayload, StxTransferFilter, StxMintPayload, StxMintFilter, StxLockPayload, StxLockFilter, StxBurnPayload, StxBurnFilter, SparseProbeTarget, RowValue, ReindexOptions, PrismaGenOptions, PrintSample, PrintEventPayload, PrintEventFor, PrintEventFilter, NftTransferPayload, NftTransferFilter, NftMintPayload, NftMintFilter, NftBurnPayload, NftBurnFilter, KyselyGenOptions, InferredTopicSchema, InferredPrintField, InferTableRow, InferSubgraphClient, InferColumnType, IndexCodegenTarget, IndexCodegenOptions, INDEX_CODEGEN_TABLES, GeneratedSQL, FtTransferPayload, FtTransferFilter, FtMintPayload, FtMintFilter, FtBurnPayload, FtBurnFilter, FindManyOptions, EventForFilter, DrizzleGenOptions, DeployPlan, DeployDiff, ContractDeployPayload, ContractDeployFilter, ContractCallPayload, ContractCallFilter, ContractCallEvent, ComputedValue, ComparisonFilter, ColumnType, ColumnToTS, ColumnDiff, ByoMigrationPlan, ByoBreakingChangeError, AnyEvent, AggregateSpec, AggregateResult };
package/dist/src/index.js CHANGED
@@ -62,7 +62,8 @@ var SubgraphFilterSchema = z.object({
62
62
  topic: z.string().optional(),
63
63
  lockedAddress: z.string().optional(),
64
64
  abi: z.record(z.string(), z.any()).optional(),
65
- trait: z.string().optional()
65
+ trait: z.string().optional(),
66
+ prints: z.record(z.string(), z.record(z.string(), ColumnTypeSchema)).optional()
66
67
  }).strict();
67
68
  var SubgraphDefinitionSchema = z.object({
68
69
  name: SubgraphNameSchema,
@@ -1409,6 +1410,7 @@ function matchSources(sources, transactions, events, traitContracts = new Map) {
1409
1410
  // src/runtime/block-processor.ts
1410
1411
  import { getTargetDb } from "@secondlayer/shared/db";
1411
1412
  import { resolveTraitContractIds } from "@secondlayer/shared/db/queries/contracts";
1413
+ import { advanceOperationCursor } from "@secondlayer/shared/db/queries/subgraph-operations";
1412
1414
  import {
1413
1415
  isByoSubgraph,
1414
1416
  recordSubgraphProcessed,
@@ -2028,6 +2030,21 @@ async function resolveTraitContracts(subgraph, blockHeight, db) {
2028
2030
  }
2029
2031
  return resolved;
2030
2032
  }
2033
+
2034
+ class CursorRaceLostError extends Error {
2035
+ constructor(operationId, height) {
2036
+ super(`op ${operationId} lost cursor race at block ${height}`);
2037
+ this.name = "CursorRaceLostError";
2038
+ }
2039
+ }
2040
+ function opCursorMode(opts) {
2041
+ const ap = opts?.atomicProgress;
2042
+ return ap && "operationId" in ap ? ap : undefined;
2043
+ }
2044
+ function statusMode(opts) {
2045
+ const ap = opts?.atomicProgress;
2046
+ return ap && "status" in ap ? ap : undefined;
2047
+ }
2031
2048
  var BLOCK_RETRY_DELAYS_MS = [500, 2000, 5000];
2032
2049
  function journalEnabled(opts) {
2033
2050
  return !opts?.skipProgressUpdate;
@@ -2121,12 +2138,19 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2121
2138
  }
2122
2139
  };
2123
2140
  if (route.byo) {
2124
- if (opts?.atomicProgress) {
2141
+ if (statusMode(opts)) {
2125
2142
  const row = await targetDb.selectFrom("subgraphs").select("last_processed_block").where("name", "=", subgraphName).executeTakeFirst();
2126
2143
  if (row && Number(row.last_processed_block) >= blockHeight) {
2127
2144
  result.skipped = true;
2128
2145
  return result;
2129
2146
  }
2147
+ } else if (opCursorMode(opts)) {
2148
+ const om = opCursorMode(opts);
2149
+ const row = await targetDb.selectFrom("subgraph_operations").select("cursor_block").where("id", "=", om.operationId).executeTakeFirst();
2150
+ if (row?.cursor_block != null && Number(row.cursor_block) >= blockHeight) {
2151
+ result.skipped = true;
2152
+ return result;
2153
+ }
2130
2154
  }
2131
2155
  let runResult = { processed: 0, errors: 0 };
2132
2156
  let manifest;
@@ -2147,41 +2171,71 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2147
2171
  if (manifest && manifest.count > 0) {
2148
2172
  await emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block.height);
2149
2173
  }
2150
- if (opts?.atomicProgress && manifest && manifest.count > 0) {
2151
- await updateSubgraphStatus(tx, subgraphName, opts.atomicProgress.status, blockHeight);
2174
+ const byoSm = statusMode(opts);
2175
+ const byoOm = opCursorMode(opts);
2176
+ if (byoSm && manifest && manifest.count > 0) {
2177
+ await updateSubgraphStatus(tx, subgraphName, byoSm.status, blockHeight);
2178
+ } else if (byoOm && manifest && manifest.count > 0) {
2179
+ await advanceOperationCursor(tx, byoOm.operationId, blockHeight);
2152
2180
  }
2153
2181
  await applyProgress(tx, runResult);
2154
2182
  });
2155
2183
  } else {
2156
- await targetDb.transaction().execute(async (tx) => {
2157
- if (opts?.atomicProgress) {
2158
- const row = await tx.selectFrom("subgraphs").select("last_processed_block").where("name", "=", subgraphName).executeTakeFirst();
2159
- if (row && Number(row.last_processed_block) >= blockHeight) {
2160
- result.skipped = true;
2161
- return;
2184
+ try {
2185
+ await targetDb.transaction().execute(async (tx) => {
2186
+ const opMode = opCursorMode(opts);
2187
+ if (statusMode(opts)) {
2188
+ const row = await tx.selectFrom("subgraphs").select("last_processed_block").where("name", "=", subgraphName).executeTakeFirst();
2189
+ if (row && Number(row.last_processed_block) >= blockHeight) {
2190
+ result.skipped = true;
2191
+ return;
2192
+ }
2193
+ } else if (opMode) {
2194
+ const row = await tx.selectFrom("subgraph_operations").select("cursor_block").where("id", "=", opMode.operationId).executeTakeFirst();
2195
+ if (row?.cursor_block != null && Number(row.cursor_block) >= blockHeight) {
2196
+ result.skipped = true;
2197
+ return;
2198
+ }
2162
2199
  }
2163
- }
2164
- const ctx = new SubgraphContext(tx, schemaName, subgraph.schema, blockMeta, initialTx, false, journalEnabled(opts));
2165
- const handlerStart = performance.now();
2166
- const runResult = await runHandlers(subgraph, matched, ctx);
2167
- handlerMs = performance.now() - handlerStart;
2168
- result.processed = runResult.processed;
2169
- result.errors = runResult.errors;
2170
- let flushedWrites = false;
2171
- if (ctx.pendingOps > 0) {
2172
- const flushStart = performance.now();
2173
- const manifest = await ctx.flush();
2174
- flushedWrites = manifest.count > 0;
2175
- if (manifest.count > 0) {
2176
- await emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block.height);
2200
+ const ctx = new SubgraphContext(tx, schemaName, subgraph.schema, blockMeta, initialTx, false, journalEnabled(opts));
2201
+ const handlerStart = performance.now();
2202
+ const runResult = await runHandlers(subgraph, matched, ctx);
2203
+ handlerMs = performance.now() - handlerStart;
2204
+ result.processed = runResult.processed;
2205
+ result.errors = runResult.errors;
2206
+ let flushedWrites = false;
2207
+ if (ctx.pendingOps > 0) {
2208
+ const flushStart = performance.now();
2209
+ const manifest = await ctx.flush();
2210
+ flushedWrites = manifest.count > 0;
2211
+ if (manifest.count > 0) {
2212
+ await emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block.height);
2213
+ }
2214
+ flushMs = performance.now() - flushStart;
2177
2215
  }
2178
- flushMs = performance.now() - flushStart;
2179
- }
2180
- if (opts?.atomicProgress && flushedWrites) {
2181
- await updateSubgraphStatus(tx, subgraphName, opts.atomicProgress.status, blockHeight);
2216
+ const sm = statusMode(opts);
2217
+ if (sm && flushedWrites) {
2218
+ await updateSubgraphStatus(tx, subgraphName, sm.status, blockHeight);
2219
+ } else if (opMode && flushedWrites) {
2220
+ const advanced = await advanceOperationCursor(tx, opMode.operationId, blockHeight);
2221
+ if (!advanced) {
2222
+ throw new CursorRaceLostError(opMode.operationId, blockHeight);
2223
+ }
2224
+ }
2225
+ await applyProgress(tx, runResult);
2226
+ });
2227
+ } catch (err) {
2228
+ if (err instanceof CursorRaceLostError) {
2229
+ logger5.warn("cursor race lost — block already covered", {
2230
+ subgraph: subgraphName,
2231
+ blockHeight,
2232
+ error: err.message
2233
+ });
2234
+ result.skipped = true;
2235
+ return result;
2182
2236
  }
2183
- await applyProgress(tx, runResult);
2184
- });
2237
+ throw err;
2238
+ }
2185
2239
  }
2186
2240
  const totalMs = performance.now() - blockStart;
2187
2241
  result.timing = {
@@ -2286,7 +2340,10 @@ import {
2286
2340
  recordGapBatch,
2287
2341
  resolveGaps
2288
2342
  } from "@secondlayer/shared/db/queries/subgraph-gaps";
2289
- import { updateOperationProcessedEvents } from "@secondlayer/shared/db/queries/subgraph-operations";
2343
+ import {
2344
+ advanceOperationCursor as advanceOperationCursor2,
2345
+ updateOperationProcessedEvents
2346
+ } from "@secondlayer/shared/db/queries/subgraph-operations";
2290
2347
  import {
2291
2348
  recordSubgraphProcessed as recordSubgraphProcessed2,
2292
2349
  updateSubgraphStatus as updateSubgraphStatus2
@@ -2359,6 +2416,7 @@ async function processBlockRange(def, opts) {
2359
2416
  const totalBlocks = toBlock - fromBlock + 1;
2360
2417
  const stats = new StatsAccumulator(subgraphName, opts.isCatchup);
2361
2418
  let blocksProcessed = 0;
2419
+ let blocksSkippedByCursor = 0;
2362
2420
  let totalEventsProcessed = 0;
2363
2421
  let totalErrors = 0;
2364
2422
  let pendingEventsProcessed = 0;
@@ -2419,7 +2477,8 @@ async function processBlockRange(def, opts) {
2419
2477
  }
2420
2478
  const batchFailedBlocks = [];
2421
2479
  let batchMatched = 0;
2422
- const atomicProgress = status === "reindexing" ? { status } : undefined;
2480
+ const opCursor = status === "active" && opts.operationId ? { operationId: opts.operationId } : undefined;
2481
+ const atomicProgress = status === "reindexing" ? { status } : opCursor;
2423
2482
  for (let height = currentHeight;height <= batchEnd; height++) {
2424
2483
  let blockData = batch.get(height);
2425
2484
  if (!blockData) {
@@ -2459,6 +2518,8 @@ async function processBlockRange(def, opts) {
2459
2518
  continue;
2460
2519
  }
2461
2520
  blocksProcessed++;
2521
+ if (result.skipped)
2522
+ blocksSkippedByCursor++;
2462
2523
  batchMatched += result.matched;
2463
2524
  totalEventsProcessed += result.processed;
2464
2525
  totalErrors += result.errors;
@@ -2476,7 +2537,11 @@ async function processBlockRange(def, opts) {
2476
2537
  const now = Date.now();
2477
2538
  const shouldFlushProgress = blocksProcessed % 100 === 0 || now - lastProgressFlushAt >= PROGRESS_FLUSH_INTERVAL_MS;
2478
2539
  if (shouldFlushProgress) {
2479
- await updateSubgraphStatus2(targetDb, subgraphName, status, height);
2540
+ if (opCursor) {
2541
+ await advanceOperationCursor2(targetDb, opCursor.operationId, height);
2542
+ } else {
2543
+ await updateSubgraphStatus2(targetDb, subgraphName, status, height);
2544
+ }
2480
2545
  if (opts.operationId) {
2481
2546
  await updateOperationProcessedEvents(targetDb, opts.operationId, totalEventsProcessed).catch(() => {});
2482
2547
  }
@@ -2488,7 +2553,8 @@ async function processBlockRange(def, opts) {
2488
2553
  processed: blocksProcessed,
2489
2554
  total: totalBlocks,
2490
2555
  currentBlock: height,
2491
- pct: Math.round(blocksProcessed / totalBlocks * 100)
2556
+ pct: Math.round(blocksProcessed / totalBlocks * 100),
2557
+ ...blocksSkippedByCursor > 0 ? { skippedByCursor: blocksSkippedByCursor } : {}
2492
2558
  });
2493
2559
  }
2494
2560
  }
@@ -2514,7 +2580,11 @@ async function processBlockRange(def, opts) {
2514
2580
  if (jumpTo > batchEnd + 1) {
2515
2581
  const skipped = Math.min(jumpTo, toBlock + 1) - (batchEnd + 1);
2516
2582
  blocksProcessed += skipped;
2517
- await updateSubgraphStatus2(targetDb, subgraphName, status, jumpTo - 1);
2583
+ if (opCursor) {
2584
+ await advanceOperationCursor2(targetDb, opCursor.operationId, jumpTo - 1);
2585
+ } else {
2586
+ await updateSubgraphStatus2(targetDb, subgraphName, status, jumpTo - 1);
2587
+ }
2518
2588
  logger6.info("Sparse skip", {
2519
2589
  subgraph: subgraphName,
2520
2590
  from: batchEnd + 1,
@@ -2756,6 +2826,336 @@ async function backfillSubgraph(def, opts) {
2756
2826
  function defineSubgraph(def) {
2757
2827
  return def;
2758
2828
  }
2829
+ // src/print-schema.ts
2830
+ import {
2831
+ deserializeCV as deserializeCV3
2832
+ } from "@secondlayer/stacks/clarity";
2833
+ function camelizeDataKey(str) {
2834
+ return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
2835
+ }
2836
+ function cvToTree(cv) {
2837
+ switch (cv.type) {
2838
+ case "uint":
2839
+ case "int":
2840
+ return { kind: cv.type };
2841
+ case "true":
2842
+ case "false":
2843
+ return { kind: "bool" };
2844
+ case "address":
2845
+ case "contract":
2846
+ return { kind: "principal" };
2847
+ case "buffer":
2848
+ return { kind: "buffer", len: cv.value.length / 2 };
2849
+ case "ascii":
2850
+ return { kind: "ascii", len: cv.value.length };
2851
+ case "utf8":
2852
+ return { kind: "utf8", len: new TextEncoder().encode(cv.value).length };
2853
+ case "none":
2854
+ return { kind: "optional", inner: null };
2855
+ case "some":
2856
+ return { kind: "optional", inner: cvToTree(cv.value) };
2857
+ case "ok":
2858
+ return { kind: "response", ok: cvToTree(cv.value), err: null };
2859
+ case "err":
2860
+ return { kind: "response", ok: null, err: cvToTree(cv.value) };
2861
+ case "list": {
2862
+ let inner = null;
2863
+ for (const el of cv.value) {
2864
+ const t = cvToTree(el);
2865
+ inner = inner ? unify(inner, t) : t;
2866
+ }
2867
+ return { kind: "list", inner };
2868
+ }
2869
+ case "tuple": {
2870
+ const entries = new Map;
2871
+ for (const [k, v] of Object.entries(cv.value)) {
2872
+ entries.set(k, { tree: cvToTree(v), present: 1 });
2873
+ }
2874
+ return { kind: "tuple", count: 1, entries };
2875
+ }
2876
+ }
2877
+ }
2878
+ function unifyNullable(a, b) {
2879
+ if (!a)
2880
+ return b;
2881
+ if (!b)
2882
+ return a;
2883
+ return unify(a, b);
2884
+ }
2885
+ var UNION_KIND_ORDER = {
2886
+ uint: 0,
2887
+ int: 1,
2888
+ bool: 2,
2889
+ principal: 3,
2890
+ buffer: 4,
2891
+ ascii: 5,
2892
+ utf8: 6,
2893
+ list: 7,
2894
+ tuple: 8,
2895
+ response: 9
2896
+ };
2897
+ function makeUnion(members) {
2898
+ const sorted = [...members].sort((a, b) => (UNION_KIND_ORDER[a.kind] ?? 99) - (UNION_KIND_ORDER[b.kind] ?? 99));
2899
+ return { kind: "union", members: sorted };
2900
+ }
2901
+ function unify(a, b) {
2902
+ if (a.kind === "optional" || b.kind === "optional") {
2903
+ const ai = a.kind === "optional" ? a.inner : a;
2904
+ const bi = b.kind === "optional" ? b.inner : b;
2905
+ return { kind: "optional", inner: unifyNullable(ai, bi) };
2906
+ }
2907
+ if (a.kind === "union")
2908
+ return unionAdd(a.members, b);
2909
+ if (b.kind === "union")
2910
+ return unionAdd(b.members, a);
2911
+ if (a.kind !== b.kind)
2912
+ return makeUnion([a, b]);
2913
+ switch (a.kind) {
2914
+ case "uint":
2915
+ case "int":
2916
+ case "bool":
2917
+ case "principal":
2918
+ return a;
2919
+ case "buffer":
2920
+ case "ascii":
2921
+ case "utf8":
2922
+ return { kind: a.kind, len: Math.max(a.len, b.len) };
2923
+ case "list":
2924
+ return {
2925
+ kind: "list",
2926
+ inner: unifyNullable(a.inner, b.inner)
2927
+ };
2928
+ case "response": {
2929
+ const rb = b;
2930
+ return {
2931
+ kind: "response",
2932
+ ok: unifyNullable(a.ok, rb.ok),
2933
+ err: unifyNullable(a.err, rb.err)
2934
+ };
2935
+ }
2936
+ case "tuple": {
2937
+ const tb = b;
2938
+ const entries = new Map([...a.entries].map(([k, e]) => [k, { ...e }]));
2939
+ for (const [k, e] of tb.entries) {
2940
+ const existing = entries.get(k);
2941
+ entries.set(k, existing ? {
2942
+ tree: unify(existing.tree, e.tree),
2943
+ present: existing.present + e.present
2944
+ } : { ...e });
2945
+ }
2946
+ return { kind: "tuple", count: a.count + tb.count, entries };
2947
+ }
2948
+ }
2949
+ }
2950
+ function unionAdd(members, t) {
2951
+ if (t.kind === "union") {
2952
+ let acc = { kind: "union", members };
2953
+ for (const m of t.members)
2954
+ acc = unify(acc, m);
2955
+ return acc;
2956
+ }
2957
+ const next = [...members];
2958
+ for (let i = 0;i < next.length; i++) {
2959
+ const member = next[i];
2960
+ if (!member)
2961
+ continue;
2962
+ const merged = unify(member, t);
2963
+ if (merged.kind !== "union") {
2964
+ next[i] = merged;
2965
+ return makeUnion(next);
2966
+ }
2967
+ }
2968
+ next.push(t);
2969
+ return makeUnion(next);
2970
+ }
2971
+ function wrapOptional(t) {
2972
+ return t.kind === "optional" ? t : { kind: "optional", inner: t };
2973
+ }
2974
+ function renderClarity(t) {
2975
+ if (!t)
2976
+ return "?";
2977
+ switch (t.kind) {
2978
+ case "uint":
2979
+ case "int":
2980
+ case "bool":
2981
+ case "principal":
2982
+ return t.kind;
2983
+ case "buffer":
2984
+ return `(buff ${t.len})`;
2985
+ case "ascii":
2986
+ return `(string-ascii ${t.len})`;
2987
+ case "utf8":
2988
+ return `(string-utf8 ${t.len})`;
2989
+ case "optional":
2990
+ return `(optional ${renderClarity(t.inner)})`;
2991
+ case "list":
2992
+ return `(list ${renderClarity(t.inner)})`;
2993
+ case "response":
2994
+ return `(response ${renderClarity(t.ok)} ${renderClarity(t.err)})`;
2995
+ case "tuple": {
2996
+ const parts = [...t.entries].map(([k, e]) => {
2997
+ const tree = e.present < t.count ? wrapOptional(e.tree) : e.tree;
2998
+ return `(${k} ${renderClarity(tree)})`;
2999
+ });
3000
+ return `(tuple ${parts.join(" ")})`;
3001
+ }
3002
+ case "union":
3003
+ return t.members.map(renderClarity).join(" | ");
3004
+ }
3005
+ }
3006
+ function renderTs(t) {
3007
+ if (!t)
3008
+ return "unknown";
3009
+ switch (t.kind) {
3010
+ case "uint":
3011
+ case "int":
3012
+ return "bigint";
3013
+ case "bool":
3014
+ return "boolean";
3015
+ case "principal":
3016
+ case "buffer":
3017
+ case "ascii":
3018
+ case "utf8":
3019
+ return "string";
3020
+ case "optional":
3021
+ return t.inner ? `${renderTs(t.inner)} | null` : "unknown | null";
3022
+ case "list": {
3023
+ const inner = renderTs(t.inner);
3024
+ return inner.includes(" | ") ? `(${inner})[]` : `${inner}[]`;
3025
+ }
3026
+ case "response": {
3027
+ const sides = [...new Set([renderTs(t.ok), renderTs(t.err)])];
3028
+ return sides.join(" | ");
3029
+ }
3030
+ case "tuple": {
3031
+ const parts = [...t.entries].map(([k, e]) => {
3032
+ const opt = e.present < t.count ? "?" : "";
3033
+ return `${camelizeDataKey(k)}${opt}: ${renderTs(e.tree)}`;
3034
+ });
3035
+ return `{ ${parts.join("; ")} }`;
3036
+ }
3037
+ case "union":
3038
+ return [...new Set(t.members.map((m) => renderTs(m)))].join(" | ");
3039
+ }
3040
+ }
3041
+ function toColumnType(t) {
3042
+ if (!t)
3043
+ return "jsonb";
3044
+ switch (t.kind) {
3045
+ case "uint":
3046
+ return "uint";
3047
+ case "int":
3048
+ return "int";
3049
+ case "bool":
3050
+ return "boolean";
3051
+ case "principal":
3052
+ return "principal";
3053
+ case "buffer":
3054
+ case "ascii":
3055
+ case "utf8":
3056
+ return "text";
3057
+ case "list":
3058
+ case "tuple":
3059
+ return "jsonb";
3060
+ case "optional":
3061
+ return toColumnType(t.inner);
3062
+ case "response":
3063
+ return t.ok ? toColumnType(t.ok) : "jsonb";
3064
+ case "union":
3065
+ return "jsonb";
3066
+ }
3067
+ }
3068
+ var MAX_DECODED_PER_TOPIC_NEWEST = 75;
3069
+ var MAX_DECODED_PER_TOPIC_OLDEST = 25;
3070
+ function inferPrintTopics(samples) {
3071
+ const groups = new Map;
3072
+ for (const s of samples) {
3073
+ const group = groups.get(s.topic);
3074
+ if (group)
3075
+ group.push(s);
3076
+ else
3077
+ groups.set(s.topic, [s]);
3078
+ }
3079
+ const out = [];
3080
+ for (const [topic, rows] of groups) {
3081
+ let first = Number.POSITIVE_INFINITY;
3082
+ let last = Number.NEGATIVE_INFINITY;
3083
+ for (const r of rows) {
3084
+ if (r.blockHeight < first)
3085
+ first = r.blockHeight;
3086
+ if (r.blockHeight > last)
3087
+ last = r.blockHeight;
3088
+ }
3089
+ const withHex = [...rows].filter((r) => r.rawHex !== null).sort((a, b) => b.blockHeight - a.blockHeight);
3090
+ const budget = MAX_DECODED_PER_TOPIC_NEWEST + MAX_DECODED_PER_TOPIC_OLDEST;
3091
+ const picked = withHex.length <= budget ? withHex : [
3092
+ ...withHex.slice(0, MAX_DECODED_PER_TOPIC_NEWEST),
3093
+ ...withHex.slice(-MAX_DECODED_PER_TOPIC_OLDEST)
3094
+ ];
3095
+ const tuples = [];
3096
+ let decoded = 0;
3097
+ for (const p of picked) {
3098
+ try {
3099
+ const cv = deserializeCV3(p.rawHex);
3100
+ decoded++;
3101
+ if (cv.type === "tuple")
3102
+ tuples.push(cv);
3103
+ } catch {}
3104
+ }
3105
+ const nonTuple = decoded > 0 && tuples.length === 0;
3106
+ const fields = [];
3107
+ if (!nonTuple) {
3108
+ const stats = new Map;
3109
+ for (const t of tuples) {
3110
+ for (const [key2, value] of Object.entries(t.value)) {
3111
+ if (key2 === "topic")
3112
+ continue;
3113
+ const tree = cvToTree(value);
3114
+ const existing = stats.get(key2);
3115
+ if (existing) {
3116
+ existing.present++;
3117
+ existing.tree = unify(existing.tree, tree);
3118
+ if (value.type === "none")
3119
+ existing.noneCount++;
3120
+ if (value.type === "none" || value.type === "some") {
3121
+ existing.optionalSeen = true;
3122
+ }
3123
+ } else {
3124
+ stats.set(key2, {
3125
+ tree,
3126
+ present: 1,
3127
+ noneCount: value.type === "none" ? 1 : 0,
3128
+ optionalSeen: value.type === "none" || value.type === "some"
3129
+ });
3130
+ }
3131
+ }
3132
+ }
3133
+ for (const [name, st] of [...stats].sort(([a], [b]) => a.localeCompare(b))) {
3134
+ const field = {
3135
+ name,
3136
+ camel_name: camelizeDataKey(name),
3137
+ clarity_type: renderClarity(st.tree),
3138
+ ts_type: renderTs(st.tree),
3139
+ column_type: toColumnType(st.tree),
3140
+ always_present: st.present === tuples.length
3141
+ };
3142
+ if (st.optionalSeen) {
3143
+ field.optional_some_rate = (st.present - st.noneCount) / st.present;
3144
+ }
3145
+ fields.push(field);
3146
+ }
3147
+ }
3148
+ out.push({
3149
+ topic,
3150
+ count: rows.length,
3151
+ first_height: first,
3152
+ last_height: last,
3153
+ non_tuple: nonTuple,
3154
+ fields
3155
+ });
3156
+ }
3157
+ return out.sort((a, b) => b.count - a.count);
3158
+ }
2759
3159
  // src/schema/prisma.ts
2760
3160
  var PRISMA_TYPE = {
2761
3161
  uint: { type: "Decimal", db: "@db.Numeric" },
@@ -3602,6 +4002,7 @@ export {
3602
4002
  renderDeployPlan,
3603
4003
  reindexSubgraph,
3604
4004
  pgSchemaName,
4005
+ inferPrintTopics,
3605
4006
  hasBreakingChanges,
3606
4007
  generateSubgraphSQL,
3607
4008
  generatePrismaSchema,
@@ -3612,10 +4013,11 @@ export {
3612
4013
  deploySchema,
3613
4014
  defineSubgraph,
3614
4015
  canSparseScan,
4016
+ camelizeDataKey,
3615
4017
  backfillSubgraph,
3616
4018
  INDEX_CODEGEN_TABLES,
3617
4019
  ByoBreakingChangeError
3618
4020
  };
3619
4021
 
3620
- //# debugId=A7DC4F802366B29864756E2164756E21
4022
+ //# debugId=4F9187B8B213FB0764756E2164756E21
3621
4023
  //# sourceMappingURL=index.js.map