@secondlayer/subgraphs 3.10.0 → 3.11.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 CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Typed on-chain indexing for Stacks. Declare event filters + column schema with `defineSubgraph()`; the runtime decodes blocks, matches filters, runs your handlers inside a transactional context, and exposes the result as a Postgres schema you query over REST or SQL.
4
4
 
5
+ Subgraphs have a visibility: managed deploys default `visibility: public` — anon-readable at `/v1/subgraphs/<name>/<table>`, in a single global public namespace claimed on publish (409 `PUBLIC_NAME_TAKEN` if the name is taken). BYO-database deploys default private. Override with `--visibility public|private` at deploy, or flip later with `sl subgraphs publish|unpublish <name>`.
6
+
5
7
  Subgraph rows fan out to HTTP subscribers through a post-flush outbox emitter — signed Standard Webhooks POSTs with retries, circuit breaker, and replay.
6
8
 
7
9
  ## Install
@@ -114,6 +116,9 @@ reads from there. Deploy with a connection string:
114
116
  sl subgraphs deploy subgraphs/my.ts --database-url "postgres://user:pass@your-host:5432/db"
115
117
  ```
116
118
 
119
+ BYO deploys default `visibility: private` (managed deploys default public);
120
+ pass `--visibility public` or `sl subgraphs publish <name>` to open reads.
121
+
117
122
  The connection string is stored encrypted at rest (AES-GCM, keyed by
118
123
  `SECONDLAYER_SECRETS_KEY`) and never returned in API responses. The server
119
124
  verifies the connection before deploying. Once deployed, query the
package/dist/src/index.js CHANGED
@@ -1195,6 +1195,28 @@ function reconstructEvent(e) {
1195
1195
  }
1196
1196
 
1197
1197
  // src/runtime/block-source.ts
1198
+ function canSparseScan(subgraph) {
1199
+ if (Array.isArray(subgraph.sources))
1200
+ return false;
1201
+ const filters = sourceFilters(subgraph);
1202
+ if (filters.length === 0)
1203
+ return false;
1204
+ return filters.every((f) => Boolean(EVENT_FILTER_TO_INDEX_TYPE[f.type]));
1205
+ }
1206
+ function sparseProbeTargets(subgraph) {
1207
+ const targets = new Map;
1208
+ for (const f of sourceFilters(subgraph)) {
1209
+ const eventType = EVENT_FILTER_TO_INDEX_TYPE[f.type];
1210
+ if (!eventType)
1211
+ continue;
1212
+ const scoped = f;
1213
+ const contractId = scoped.contractId ?? scoped.assetIdentifier?.split("::")[0];
1214
+ const key = `${eventType}|${contractId ?? ""}`;
1215
+ targets.set(key, { eventType, ...contractId ? { contractId } : {} });
1216
+ }
1217
+ return [...targets.values()];
1218
+ }
1219
+
1198
1220
  class PostgresBlockSource {
1199
1221
  async getTip() {
1200
1222
  const progress = await getSourceDb().selectFrom("index_progress").selectAll().where("network", "=", process.env.NETWORK ?? "mainnet").executeTakeFirst();
@@ -1257,9 +1279,18 @@ function isStreamsIndexEligible(subgraph) {
1257
1279
  class PublicApiBlockSource {
1258
1280
  http;
1259
1281
  eventTypes;
1260
- constructor(http, eventTypes) {
1282
+ probeTargets;
1283
+ constructor(http, eventTypes, probeTargets) {
1261
1284
  this.http = http;
1262
1285
  this.eventTypes = eventTypes;
1286
+ this.probeTargets = probeTargets;
1287
+ }
1288
+ async nextDataHeight(afterHeight, untilHeight) {
1289
+ if (!this.probeTargets?.length)
1290
+ return afterHeight + 1;
1291
+ const hits = await Promise.all(this.probeTargets.map((t) => this.http.firstEventHeight(t.eventType, afterHeight + 1, untilHeight, t.contractId)));
1292
+ const found = hits.filter((h) => h !== null);
1293
+ return found.length ? Math.min(...found) : null;
1263
1294
  }
1264
1295
  getTip() {
1265
1296
  return this.http.getIndexTip();
@@ -1323,6 +1354,15 @@ class FallbackBlockSource {
1323
1354
  return this.fallback.loadBlockRange(fromHeight, toHeight);
1324
1355
  }
1325
1356
  }
1357
+ async nextDataHeight(afterHeight, untilHeight) {
1358
+ if (!this.primary.nextDataHeight)
1359
+ return afterHeight + 1;
1360
+ try {
1361
+ return await this.primary.nextDataHeight(afterHeight, untilHeight);
1362
+ } catch {
1363
+ return afterHeight + 1;
1364
+ }
1365
+ }
1326
1366
  }
1327
1367
  var postgresBlockSource = new PostgresBlockSource;
1328
1368
  function buildHttpClient() {
@@ -1335,7 +1375,7 @@ function buildHttpClient() {
1335
1375
  }
1336
1376
  function resolveBlockSource(subgraph) {
1337
1377
  if (process.env.SUBGRAPH_SOURCE === "streams-index" && subgraph && isStreamsIndexEligible(subgraph)) {
1338
- return new FallbackBlockSource(new PublicApiBlockSource(buildHttpClient(), referencedIndexEventTypes(subgraph)), postgresBlockSource);
1378
+ return new FallbackBlockSource(new PublicApiBlockSource(buildHttpClient(), referencedIndexEventTypes(subgraph), canSparseScan(subgraph) ? sparseProbeTargets(subgraph) : undefined), postgresBlockSource);
1339
1379
  }
1340
1380
  if (process.env.SUBGRAPH_SOURCE === "streams-index" && subgraph) {
1341
1381
  logger3.debug("Subgraph not streams-index eligible, using DB tap", {
@@ -2005,6 +2045,7 @@ async function processBlockRange(def, opts) {
2005
2045
  let batchSize = batchConfig.defaultBatchSize;
2006
2046
  let currentHeight = fromBlock;
2007
2047
  let aborted = false;
2048
+ const sparse = Boolean(source.nextDataHeight && canSparseScan(def));
2008
2049
  const flushHealth = async () => {
2009
2050
  if (pendingEventsProcessed === 0 && pendingErrors === 0)
2010
2051
  return;
@@ -2035,6 +2076,7 @@ async function processBlockRange(def, opts) {
2035
2076
  nextBatchPromise = source.loadBlockRange(nextStart, nextBatchEnd);
2036
2077
  }
2037
2078
  const batchFailedBlocks = [];
2079
+ let batchMatched = 0;
2038
2080
  for (let height = currentHeight;height <= batchEnd; height++) {
2039
2081
  const blockData = batch.get(height);
2040
2082
  if (!blockData) {
@@ -2064,6 +2106,7 @@ async function processBlockRange(def, opts) {
2064
2106
  continue;
2065
2107
  }
2066
2108
  blocksProcessed++;
2109
+ batchMatched += result.matched;
2067
2110
  totalEventsProcessed += result.processed;
2068
2111
  totalErrors += result.errors;
2069
2112
  pendingEventsProcessed += result.processed;
@@ -2109,6 +2152,27 @@ async function processBlockRange(def, opts) {
2109
2152
  });
2110
2153
  });
2111
2154
  }
2155
+ if (sparse && batchMatched === 0 && batchEnd < toBlock && source.nextDataHeight) {
2156
+ const next = await source.nextDataHeight(batchEnd, toBlock);
2157
+ const jumpTo = next === null ? toBlock + 1 : Math.max(next, batchEnd + 1);
2158
+ if (jumpTo > batchEnd + 1) {
2159
+ const skipped = Math.min(jumpTo, toBlock + 1) - (batchEnd + 1);
2160
+ blocksProcessed += skipped;
2161
+ await updateSubgraphStatus2(targetDb, subgraphName, status, jumpTo - 1);
2162
+ logger6.info("Sparse skip", {
2163
+ subgraph: subgraphName,
2164
+ from: batchEnd + 1,
2165
+ to: jumpTo - 1,
2166
+ skipped
2167
+ });
2168
+ currentHeight = jumpTo;
2169
+ if (currentHeight <= toBlock) {
2170
+ nextBatchEnd = Math.min(currentHeight + batchSize - 1, toBlock);
2171
+ nextBatchPromise = source.loadBlockRange(currentHeight, nextBatchEnd);
2172
+ }
2173
+ continue;
2174
+ }
2175
+ }
2112
2176
  const avg = avgEventsPerBlock(batch);
2113
2177
  if (avg > 50)
2114
2178
  batchSize = Math.max(Math.round(batchSize * 0.5), batchConfig.minBatchSize);
@@ -3191,5 +3255,5 @@ export {
3191
3255
  ByoBreakingChangeError
3192
3256
  };
3193
3257
 
3194
- //# debugId=44829C409B3F962864756E2164756E21
3258
+ //# debugId=9A94DC6E9FCF6CD964756E2164756E21
3195
3259
  //# sourceMappingURL=index.js.map