@secondlayer/subgraphs 3.26.1 → 3.26.3

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.
@@ -348,6 +348,39 @@ interface SubgraphDefinition {
348
348
  /** Run `fn` (a single block's write, or a reorg's whole rewind) holding the
349
349
  * per-subgraph block lock. Exported for reorg.ts. */
350
350
  declare function withSubgraphBlockLock<T>(name: string, fn: () => Promise<T>): Promise<T>;
351
+ /**
352
+ * Cross-process half of the block lock: a transaction-scoped Postgres advisory
353
+ * lock keyed `subgraph-block:<name>`, held for the duration of `fn`.
354
+ *
355
+ * Held, not merely acquired: each block's own write transaction is opened
356
+ * INSIDE `processBlock` (block-processor.ts — `targetDb.transaction()` for
357
+ * managed subgraphs, `route.dataDb.transaction()` for BYO), and that boundary
358
+ * is load-bearing for the f069 replay guard, so the lock cannot be taken on
359
+ * the writing connection. Instead this wraps the write in a separate
360
+ * lock-holding transaction on the target DB, exactly as the reorg lock does
361
+ * (reorg.ts): `pg_advisory_xact_lock` releases only when THIS transaction
362
+ * commits, regardless of which other connections `fn` awaits meanwhile.
363
+ * Transaction-scoped rather than session-scoped so a crashed walk can never
364
+ * strand the lock.
365
+ *
366
+ * The lock lives on the target DB even for BYO subgraphs — same as the reorg
367
+ * lock — because it coordinates control-plane walkers, not data-plane rows.
368
+ *
369
+ * Connection budget: this holds one pooled target-DB connection while `fn`
370
+ * takes another for the write, so an in-flight block costs 2 of
371
+ * `DATABASE_POOL_MAX` (default 20). Concurrent block writes are bounded by
372
+ * catch-up fan-out (5 subgraphs) plus operation concurrency (1) = 6 blocks =
373
+ * 12 connections. Raising either concurrency knob must be checked against the
374
+ * pool, or the wrapping transactions can starve the writes they wrap.
375
+ */
376
+ declare function withSubgraphBlockAdvisoryLock<T>(name: string, fn: () => Promise<T>): Promise<T>;
377
+ /**
378
+ * One block's write, holding BOTH halves of the block lock in the documented
379
+ * order: the in-process mutex, then the cross-process advisory lock. Every
380
+ * walk that writes a subgraph's schema — live catch-up, reindex, backfill —
381
+ * must go through this, or it serializes against nothing.
382
+ */
383
+ declare function withSubgraphBlockWriteLock<T>(name: string, fn: () => Promise<T>): Promise<T>;
351
384
  /** Bump a subgraph's reorg epoch. Call BEFORE acquiring the block lock so the
352
385
  * bump is visible to any concurrent catch-up immediately. Exported for
353
386
  * reorg.ts. */
@@ -371,4 +404,4 @@ declare function resolveCatchupBatchConfig(env?: CatchupBatchEnv): CatchupBatchC
371
404
  * plan-aware pipeline prefetching.
372
405
  */
373
406
  declare function catchUpSubgraph(subgraph: SubgraphDefinition, subgraphName: string): Promise<number>;
374
- export { withSubgraphBlockLock, resolveCatchupBatchConfig, catchUpSubgraph, bumpReorgEpoch };
407
+ export { withSubgraphBlockWriteLock, withSubgraphBlockLock, withSubgraphBlockAdvisoryLock, resolveCatchupBatchConfig, catchUpSubgraph, bumpReorgEpoch };
@@ -16,6 +16,13 @@ function quotePgIdent(name) {
16
16
  return name;
17
17
  return `"${name.replace(/"/g, '""')}"`;
18
18
  }
19
+ function snakeToCamel(name) {
20
+ return name.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
21
+ }
22
+ function pascalCase(name) {
23
+ const camel = snakeToCamel(name);
24
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
25
+ }
19
26
 
20
27
  // src/schema/generator.ts
21
28
  var TYPE_MAP = {
@@ -3045,7 +3052,7 @@ class StatsAccumulator {
3045
3052
  }
3046
3053
 
3047
3054
  // src/runtime/catchup.ts
3048
- import { getTargetDb as getTargetDb2 } from "@secondlayer/shared/db";
3055
+ import { getTargetDb as getTargetDb2, sql as sql4 } from "@secondlayer/shared/db";
3049
3056
  import { getSubgraph } from "@secondlayer/shared/db/queries/subgraphs";
3050
3057
  import { logger as logger8 } from "@secondlayer/shared/logger";
3051
3058
 
@@ -3116,6 +3123,15 @@ async function withSubgraphBlockLock(name, fn) {
3116
3123
  release();
3117
3124
  }
3118
3125
  }
3126
+ async function withSubgraphBlockAdvisoryLock(name, fn) {
3127
+ return getTargetDb2().transaction().execute(async (lockTx) => {
3128
+ await sql4`SELECT pg_advisory_xact_lock(hashtext(${`subgraph-block:${name}`}))`.execute(lockTx);
3129
+ return fn();
3130
+ });
3131
+ }
3132
+ async function withSubgraphBlockWriteLock(name, fn) {
3133
+ return withSubgraphBlockLock(name, () => withSubgraphBlockAdvisoryLock(name, fn));
3134
+ }
3119
3135
  function bumpReorgEpoch(name) {
3120
3136
  reorgEpoch.set(name, (reorgEpoch.get(name) ?? 0) + 1);
3121
3137
  }
@@ -3244,7 +3260,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
3244
3260
  const preloaded = blockData;
3245
3261
  let result;
3246
3262
  try {
3247
- result = await withSubgraphBlockLock(subgraphName, async () => {
3263
+ result = await withSubgraphBlockWriteLock(subgraphName, async () => {
3248
3264
  if (getReorgEpoch(subgraphName) !== walkEpoch) {
3249
3265
  throw new ReorgEpochChangedError(subgraphName);
3250
3266
  }
@@ -3304,11 +3320,13 @@ async function catchUpSubgraph(subgraph, subgraphName) {
3304
3320
  }
3305
3321
  }
3306
3322
  export {
3323
+ withSubgraphBlockWriteLock,
3307
3324
  withSubgraphBlockLock,
3325
+ withSubgraphBlockAdvisoryLock,
3308
3326
  resolveCatchupBatchConfig,
3309
3327
  catchUpSubgraph,
3310
3328
  bumpReorgEpoch
3311
3329
  };
3312
3330
 
3313
- //# debugId=1544B1119237E68264756E2164756E21
3331
+ //# debugId=90994D80368BB6A264756E2164756E21
3314
3332
  //# sourceMappingURL=catchup.js.map