@secondlayer/subgraphs 3.26.4 → 4.0.1

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,7 +2,7 @@
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>`.
5
+ Subgraphs have a visibility: deploys default `visibility: public` — anon-readable at `/v1/subgraphs/<name>/<table>`, in a single global public namespace (409 `PUBLIC_NAME_TAKEN` if the name is taken). Override with `--visibility public|private` at deploy.
6
6
 
7
7
  Subgraph rows fan out to HTTP subscribers through a post-flush outbox emitter — signed Standard Webhooks POSTs with retries, circuit breaker, and replay.
8
8
 
@@ -53,7 +53,7 @@ export default defineSubgraph({
53
53
  });
54
54
  ```
55
55
 
56
- Deploy via CLI (`sl subgraphs deploy path/to/definition.ts`), SDK (`sl.subgraphs.deploy({...})`), or MCP (`subgraphs_deploy`). The CLI can also scaffold from a deployed contract with `sl subgraphs scaffold <contract> -o subgraphs/name.ts`; scaffold writes or amends `package.json` and runs `bun install` unless `--no-install` is passed. The dashboard is read-only — creation always happens through an API surface.
56
+ Deploy via CLI (`secondlayer subgraphs deploy path/to/definition.ts`), SDK (`sl.subgraphs.deploy({...})`), or MCP (`subgraphs_deploy`). The CLI can also scaffold from a deployed contract with `secondlayer subgraphs scaffold <contract> -o subgraphs/name.ts`; scaffold writes or amends `package.json` and runs `bun install` unless `--no-install` is passed. The dashboard is read-only — creation always happens through an API surface.
57
57
 
58
58
  ## Exports
59
59
 
@@ -91,72 +91,19 @@ Delivery bodies and response previews land in `subscription_deliveries`. Rows wh
91
91
  | `SECONDLAYER_EMIT_OUTBOX` | `true` | Set `false` to bypass outbox emission on every block (kill-switch). |
92
92
  | `SECONDLAYER_ALLOW_PRIVATE_EGRESS` | `false` | Allow the emitter to deliver to private IP ranges (localhost, 10/8, 172.16/12, 192.168/16, link-local, v6 mapped). Leave off in production. |
93
93
  | `SECONDLAYER_SECRETS_KEY` | — | 32-byte hex key for the AES-GCM envelope around subscription signing secrets. OSS mode auto-generates + persists to `.env.local`. |
94
- | `TENANT_PLAN` | unset | Dedicated hosting plan injected by the provisioner. Unset and paid plans use standard batches. |
95
- | `SUBGRAPH_REINDEX_BATCH_SIZE` | plan-based | Override the default historical block batch size used by reindex/backfill. |
96
- | `SUBGRAPH_REINDEX_MIN_BATCH_SIZE` | plan-based | Override the adaptive lower bound for reindex/backfill batches. |
97
- | `SUBGRAPH_REINDEX_MAX_BATCH_SIZE` | plan-based | Override the adaptive upper bound for reindex/backfill batches. |
98
- | `DATABASE_MAX_POOLS` | `25` | Max cached connection pools. With BYO subgraphs (one pool per user DB) the least-recently-used pool is evicted past this cap; the source/target pools are never evicted. |
99
- | `DATABASE_IDLE_TIMEOUT` | `300` | Seconds before idle connections are closed (`0` = never). Keeps a fleet of BYO pools from pinning connections. |
94
+ | `SUBGRAPH_REINDEX_BATCH_SIZE` | adaptive | Override the default historical block batch size used by reindex/backfill. |
95
+ | `SUBGRAPH_REINDEX_MIN_BATCH_SIZE` | adaptive | Override the adaptive lower bound for reindex/backfill batches. |
96
+ | `SUBGRAPH_REINDEX_MAX_BATCH_SIZE` | adaptive | Override the adaptive upper bound for reindex/backfill batches. |
97
+ | `DATABASE_MAX_POOLS` | `25` | Max cached connection pools; the least-recently-used pool is evicted past this cap. The source/target pools are never evicted. |
98
+ | `DATABASE_IDLE_TIMEOUT` | `300` | Seconds before idle connections are closed (`0` = never). |
100
99
 
101
- ## Bring your own database (BYO data plane)
102
-
103
- There are three tiers, not two:
104
-
105
- | Tier | Indexer / decode | Handler exec | Database | Serving API |
106
- | --- | --- | --- | --- | --- |
107
- | Managed (default) | ours | ours | ours | ours |
108
- | **BYO data plane** | ours | ours | **yours** | **yours** |
109
- | Self-host | yours | yours | yours | yours |
110
-
111
- With BYO, the managed pipeline (ingest → decode → match → run your handler) is
112
- unchanged, but your handler's rows land in **your** Postgres and the serving API
113
- reads from there. Deploy with a connection string:
114
-
115
- ```bash
116
- sl subgraphs deploy subgraphs/my.ts --database-url "postgres://user:pass@your-host:5432/db"
117
- ```
118
-
119
- BYO deploys default `visibility: private` (managed deploys default public);
120
- pass `--visibility public` or `sl subgraphs publish <name>` to open reads.
121
-
122
- The connection string is stored encrypted at rest (AES-GCM, keyed by
123
- `SECONDLAYER_SECRETS_KEY`) and never returned in API responses. The server
124
- verifies the connection before deploying. Once deployed, query the
125
- `subgraph_…` schema directly with any ORM/GraphQL/REST — we're no longer in the
126
- serving path.
127
-
128
- **Preview before it touches your DB.** `POST /api/subgraphs` with
129
- `{"dryRun": true, "databaseUrl": "…"}` returns the exact DDL plus a grant
130
- script and verifies the connection — without writing anything.
131
-
132
- **Constraints (v1):**
133
-
134
- - **Idempotent handlers only.** A BYO block write can't share the managed
135
- transaction, so a crash replays the block (at-least-once). `ctx.insert` and
136
- `ctx.upsert` (with a unique key) are safe — flush is replace-per-height. A
137
- deploy with non-idempotent `ctx.update` / `ctx.patchOrInsert` is rejected.
138
- - **No reindex.** A breaking change (removed table/column, changed type, or a
139
- forced reindex) would drop + rebuild the schema in your DB, so the deploy is
140
- refused with HTTP `422` — nothing is touched. The refusal carries a migration
141
- plan: the SDK throws a typed `ByoBreakingChangeError` (`details.reasons` +
142
- `details.plan` with `dropStatement`/`statements`/`grantScript`) and the CLI
143
- prints the exact `DROP SCHEMA … CASCADE` + rebuild DDL to run yourself, then
144
- re-deploy. A destructive `--force` rebuild on your DB is not yet supported.
145
- - **Delete leaves your data.** Deleting the subgraph removes our registry row
146
- (and the stored connection) and pauses subscriptions, but never drops the
147
- schema in your database.
148
-
149
- **Recommended:** give Secondlayer a least-privilege role scoped to its own
150
- schema, and point `--database-url` at a session-mode pooler endpoint
151
- (PgBouncer/Neon/Supabase) rather than raw Postgres.
152
-
153
- ### ORM codegen
100
+ ## ORM codegen
154
101
 
155
102
  Once rows land in your DB, generate a typed schema for your ORM:
156
103
 
157
104
  ```bash
158
- sl subgraphs codegen subgraphs/my.ts --target prisma -o prisma/schema.prisma
159
- sl subgraphs codegen subgraphs/my.ts --target drizzle -o db/schema.ts
105
+ secondlayer subgraphs codegen subgraphs/my.ts --target prisma -o prisma/schema.prisma
106
+ secondlayer subgraphs codegen subgraphs/my.ts --target drizzle -o db/schema.ts
160
107
  ```
161
108
 
162
109
  Prisma and Drizzle have first-class generators (`generatePrismaSchema` /
@@ -474,10 +474,9 @@ declare function canSparseScan(subgraph: SubgraphDefinition): boolean;
474
474
  declare function sparseProbeTargets(subgraph: SubgraphDefinition): SparseProbeTarget[];
475
475
  interface ReindexOptions {
476
476
  /**
477
- * Policy floor on the walk's first block — NOT a caller-supplied range.
478
- * Free-tier accounts may not reindex below their registered `start_block`
479
- * (see `resolveGenesisPolicy` on the API side); this raises the walk start
480
- * and can never lower it. There is deliberately no `toBlock` counterpart:
477
+ * Floor on the walk's first block — NOT a caller-supplied range. When set
478
+ * (e.g. an op row carrying a `from_block`), it raises the walk start and
479
+ * can never lower it. There is deliberately no `toBlock` counterpart:
481
480
  * a reindex always runs to chain tip. See {@link resolveBlockRange}.
482
481
  */
483
482
  startBlockFloor?: number;
@@ -1103,7 +1102,6 @@ declare function generateIndexSchema(target: IndexCodegenTarget, opts?: IndexCod
1103
1102
  declare const INDEX_CODEGEN_TABLES: readonly string[];
1104
1103
  import { pgSchemaName } from "@secondlayer/shared/db/queries/subgraphs";
1105
1104
  import { Database as Database3 } from "@secondlayer/shared/db";
1106
- import { ByoBreakingChangeDetails } from "@secondlayer/shared/errors";
1107
1105
  import { Kysely as Kysely3 } from "kysely";
1108
1106
  type AnyDb = Kysely3<Database3>;
1109
1107
  interface TableDiff {
@@ -1163,36 +1161,16 @@ interface DeployDiff {
1163
1161
  /** uniqueKeys added in place via ALTER TABLE … ADD CONSTRAINT. */
1164
1162
  addedUniqueKeys?: Record<string, string[][]>;
1165
1163
  }
1166
- interface ByoMigrationPlan {
1167
- schemaName: string;
1168
- dropStatement: string;
1169
- statements: string[];
1170
- grantScript: string;
1171
- }
1172
- /**
1173
- * Thrown when a BYO subgraph deploy is refused for a breaking schema change.
1174
- * Plain `Error` with a literal `code` (not `SecondLayerError`) so the API
1175
- * middleware matches it by code across bundle boundaries — bunup duplicates
1176
- * classes per package, breaking cross-bundle `instanceof`. The refusal stands;
1177
- * `details` carries the reviewable DROP + rebuild the user must run manually.
1178
- */
1179
- declare class ByoBreakingChangeError extends Error {
1180
- readonly code: "BYO_BREAKING_CHANGE";
1181
- readonly details: ByoBreakingChangeDetails;
1182
- constructor(reasons: string[], diff: DeployDiff, plan: ByoMigrationPlan);
1183
- }
1184
1164
  interface DeployPlan {
1185
1165
  schemaName: string;
1186
- /** `DROP SCHEMA … CASCADE` a destructive rebuild would run first (shown, never auto-run on BYO). */
1166
+ /** `DROP SCHEMA … CASCADE` a destructive rebuild would run first. */
1187
1167
  dropStatement: string;
1188
- /** DDL Secondlayer will run against your database. */
1168
+ /** DDL the deploy will run. */
1189
1169
  statements: string[];
1190
- /** Least-privilege grant script to run once, before deploying. */
1191
- grantScript: string;
1192
1170
  }
1193
1171
  /**
1194
- * Render the DDL + grant script a BYO deploy would run, without executing.
1195
- * Powers `--dry-run`: the user reviews exactly what touches their DB first.
1172
+ * Render the DDL a deploy would run, without executing.
1173
+ * Powers `--dry-run`: the user reviews exactly what a deploy does first.
1196
1174
  */
1197
1175
  declare function renderDeployPlan(def: SubgraphDefinition, schemaName?: string): DeployPlan;
1198
1176
  /**
@@ -1210,14 +1188,6 @@ declare function deploySchema(db: AnyDb, def: SubgraphDefinition, handlerPath: s
1210
1188
  version?: string
1211
1189
  handlerCode?: string
1212
1190
  sourceCode?: string
1213
- /**
1214
- * BYO data plane: when set, schema DDL (CREATE/ALTER/index) runs against
1215
- * the user-owned DB while the subgraphs registry row stays on `db`
1216
- * (managed). Defaults to `db` — managed deploys are unchanged.
1217
- */
1218
- dataDb?: AnyDb
1219
- /** Encrypted user-DB connection string to persist on the registry row. */
1220
- databaseUrlEnc?: Buffer | null
1221
1191
  }): Promise<{
1222
1192
  action: "created" | "unchanged" | "handler_updated" | "updated" | "reindexed"
1223
1193
  subgraphId: string
@@ -1226,4 +1196,4 @@ declare function deploySchema(db: AnyDb, def: SubgraphDefinition, handlerPath: s
1226
1196
  /** Non-fatal caveats the caller should surface (e.g. handler grafts at tip). */
1227
1197
  warnings?: string[]
1228
1198
  }>;
1229
- export { validateSubgraphDefinition, validatePrintPayload, sparseProbeTargets, resumeReindex, renderDeployPlan, reindexSubgraph, readContractAt, pgSchemaName, inferPrintTopics, hasBreakingChanges, generateSubgraphSQL, generatePrismaSchema, generateKyselySchema, generateIndexSchema, generateDrizzleSchema, filterFieldsByType, diffSchema, deploySchema, defineSubgraph, defineSchema, 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, PrintValidationResult, PrintSample, PrintFieldToTS, PrintField, PrintEventPayload, PrintEventFor, PrintEventFilter, PrintDataOf, NftTransferPayload, NftTransferFilter, NftMintPayload, NftMintFilter, NftBurnPayload, NftBurnFilter, KyselyGenOptions, InferredTopicSchema, InferredPrintField, InferTableRow, InferSubgraphClient, InferContext, InferColumnType, IndexCodegenTarget, IndexCodegenOptions, INDEX_CODEGEN_TABLES, GeneratedSQL, FtTransferPayload, FtTransferFilter, FtMintPayload, FtMintFilter, FtBurnPayload, FtBurnFilter, FindManyOptions, FactoryScope, EventForFilter, ErasedChainReadClient, DrizzleGenOptions, DeployPlan, DeployDiff, ContractDeployPayload, ContractDeployFilter, ContractCallPayload, ContractCallFilter, ContractCallEvent, ComputedValue, ComparisonFilter, ColumnType, ColumnToTS, ColumnDiff, ChainReadOptions, ChainReadMethods, ChainReadError, ChainReadClient, ChainReadCacheMode, ByoMigrationPlan, ByoBreakingChangeError, AnyEvent, AggregateSpec, AggregateResult };
1199
+ export { validateSubgraphDefinition, validatePrintPayload, sparseProbeTargets, resumeReindex, renderDeployPlan, reindexSubgraph, readContractAt, pgSchemaName, inferPrintTopics, hasBreakingChanges, generateSubgraphSQL, generatePrismaSchema, generateKyselySchema, generateIndexSchema, generateDrizzleSchema, filterFieldsByType, diffSchema, deploySchema, defineSubgraph, defineSchema, 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, PrintValidationResult, PrintSample, PrintFieldToTS, PrintField, PrintEventPayload, PrintEventFor, PrintEventFilter, PrintDataOf, NftTransferPayload, NftTransferFilter, NftMintPayload, NftMintFilter, NftBurnPayload, NftBurnFilter, KyselyGenOptions, InferredTopicSchema, InferredPrintField, InferTableRow, InferSubgraphClient, InferContext, InferColumnType, IndexCodegenTarget, IndexCodegenOptions, INDEX_CODEGEN_TABLES, GeneratedSQL, FtTransferPayload, FtTransferFilter, FtMintPayload, FtMintFilter, FtBurnPayload, FtBurnFilter, FindManyOptions, FactoryScope, EventForFilter, ErasedChainReadClient, DrizzleGenOptions, DeployPlan, DeployDiff, ContractDeployPayload, ContractDeployFilter, ContractCallPayload, ContractCallFilter, ContractCallEvent, ComputedValue, ComparisonFilter, ColumnType, ColumnToTS, ColumnDiff, ChainReadOptions, ChainReadMethods, ChainReadError, ChainReadClient, ChainReadCacheMode, AnyEvent, AggregateSpec, AggregateResult };
package/dist/src/index.js CHANGED
@@ -538,16 +538,14 @@ class SubgraphContext {
538
538
  pgSchemaName;
539
539
  subgraphSchema;
540
540
  ops = [];
541
- byo;
542
541
  journal;
543
542
  _client;
544
- constructor(db, pgSchemaName2, subgraphSchema, block, tx, byo = false, journal = false) {
543
+ constructor(db, pgSchemaName2, subgraphSchema, block, tx, journal = false) {
545
544
  this.db = db;
546
545
  this.pgSchemaName = pgSchemaName2;
547
546
  this.subgraphSchema = subgraphSchema;
548
547
  this.block = block;
549
548
  this._tx = tx;
550
- this.byo = byo;
551
549
  this.journal = journal;
552
550
  }
553
551
  get client() {
@@ -916,15 +914,6 @@ class SubgraphContext {
916
914
  }
917
915
  buildStatements(ops) {
918
916
  const statements = [];
919
- if (this.byo) {
920
- const insertTables = new Set;
921
- for (const op of ops)
922
- if (op.kind === "insert")
923
- insertTables.add(op.table);
924
- for (const table of insertTables) {
925
- statements.push(`DELETE FROM "${this.pgSchemaName}"."${table}" WHERE "_block_height" = ${this.block.height}`);
926
- }
927
- }
928
917
  let currentBatch = null;
929
918
  let currentBatchKey = "";
930
919
  let incBatch = null;
@@ -1877,16 +1866,13 @@ function matchSources(sources, transactions, events, traitContracts = new Map, f
1877
1866
  }
1878
1867
 
1879
1868
  // src/runtime/block-processor.ts
1880
- import { secretsKeyAvailable } from "@secondlayer/shared/crypto/secrets";
1881
1869
  import { getTargetDb } from "@secondlayer/shared/db";
1882
1870
  import { resolveTraitContractIds } from "@secondlayer/shared/db/queries/contracts";
1883
1871
  import { advanceOperationCursor } from "@secondlayer/shared/db/queries/subgraph-operations";
1884
1872
  import {
1885
- isByoSubgraph,
1886
1873
  recordLiveError,
1887
1874
  recordLiveProgress,
1888
1875
  recordSubgraphProcessed,
1889
- resolveSubgraphDb,
1890
1876
  rewindLiveProgress,
1891
1877
  updateSubgraphStatus
1892
1878
  } from "@secondlayer/shared/db/queries/subgraphs";
@@ -2815,26 +2801,14 @@ async function refreshMatcher(db) {
2815
2801
 
2816
2802
  // src/runtime/block-processor.ts
2817
2803
  var routeCache = new Map;
2818
-
2819
- class ByoKeyUnavailableError extends Error {
2820
- constructor(subgraphName) {
2821
- super(`subgraph ${subgraphName} is BYO (database_url_enc set) but this process has no SECONDLAYER_SECRETS_KEY — the subgraph processor deliberately does not hold it (f072). Route BYO subgraphs to a process that does, or unset BYO.`);
2822
- this.name = "ByoKeyUnavailableError";
2823
- }
2824
- }
2825
2804
  async function resolveRoute(subgraphName, targetDb) {
2826
2805
  const cached = routeCache.get(subgraphName);
2827
2806
  if (cached)
2828
2807
  return cached;
2829
2808
  const row = await targetDb.selectFrom("subgraphs").selectAll().where("name", "=", subgraphName).executeTakeFirst();
2830
- const byo = row ? isByoSubgraph(row) : false;
2831
- if (row && byo && !secretsKeyAvailable()) {
2832
- throw new ByoKeyUnavailableError(subgraphName);
2833
- }
2834
2809
  const route = {
2835
2810
  schemaName: row?.schema_name ?? pgSchemaName(subgraphName),
2836
- dataDb: row && byo ? resolveSubgraphDb(row) : targetDb,
2837
- byo,
2811
+ dataDb: targetDb,
2838
2812
  sandboxWorkers: row?.sandbox_workers === true,
2839
2813
  handlerCode: row?.handler_code ?? null,
2840
2814
  version: row?.version ?? ""
@@ -3042,7 +3016,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
3042
3016
  await rewindLiveProgress(tx, subgraphName, blockHeight);
3043
3017
  } else {
3044
3018
  const advanced = await recordLiveProgress(tx, subgraphName, blockHeight);
3045
- if (!advanced && flushedWrites && !route.byo) {
3019
+ if (!advanced && flushedWrites) {
3046
3020
  throw new LiveCursorRaceLostError(subgraphName, blockHeight);
3047
3021
  }
3048
3022
  }
@@ -3051,122 +3025,79 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
3051
3025
  await recordSubgraphProcessed(tx, subgraphName, rr.processed, rr.errors, lastError);
3052
3026
  }
3053
3027
  };
3054
- if (route.byo) {
3055
- if (statusMode(opts)) {
3056
- const row = await targetDb.selectFrom("subgraphs").select("last_processed_block").where("name", "=", subgraphName).executeTakeFirst();
3057
- if (row && Number(row.last_processed_block) >= blockHeight) {
3058
- result.skipped = true;
3059
- return result;
3060
- }
3061
- } else if (opCursorMode(opts)) {
3062
- const om = opCursorMode(opts);
3063
- const row = await targetDb.selectFrom("subgraph_operations").select("cursor_block").where("id", "=", om.operationId).executeTakeFirst();
3064
- if (row?.cursor_block != null && Number(row.cursor_block) >= blockHeight) {
3065
- result.skipped = true;
3066
- return result;
3028
+ try {
3029
+ await targetDb.transaction().execute(async (tx) => {
3030
+ const opMode = opCursorMode(opts);
3031
+ const sm = statusMode(opts);
3032
+ const rewind = isReorgRewind(opts);
3033
+ if (sm) {
3034
+ const row = await tx.selectFrom("subgraphs").select("last_processed_block").where("name", "=", subgraphName).executeTakeFirst();
3035
+ if (row && Number(row.last_processed_block) >= blockHeight) {
3036
+ result.skipped = true;
3037
+ return;
3038
+ }
3039
+ } else if (opMode) {
3040
+ const row = await tx.selectFrom("subgraph_operations").select("cursor_block").where("id", "=", opMode.operationId).executeTakeFirst();
3041
+ if (row?.cursor_block != null && Number(row.cursor_block) >= blockHeight) {
3042
+ result.skipped = true;
3043
+ return;
3044
+ }
3045
+ } else if (!rewind) {
3046
+ const row = await tx.selectFrom("subgraphs").select("last_processed_block").where("name", "=", subgraphName).executeTakeFirst();
3047
+ if (row && Number(row.last_processed_block) >= blockHeight) {
3048
+ result.skipped = true;
3049
+ return;
3050
+ }
3067
3051
  }
3068
- }
3069
- let runResult = { processed: 0, errors: 0 };
3070
- let manifest;
3071
- await route.dataDb.transaction().execute(async (tx) => {
3072
- const ctx = new SubgraphContext(tx, schemaName, subgraph.schema, blockMeta, initialTx, true, journalEnabled(opts));
3052
+ const ctx = new SubgraphContext(tx, schemaName, subgraph.schema, blockMeta, initialTx, journalEnabled(opts));
3073
3053
  const handlerStart = performance.now();
3074
- runResult = await runHandlers(subgraph, matched, ctx);
3054
+ const runResult = sandboxEnabled({
3055
+ sandbox_workers: route.sandboxWorkers
3056
+ }) ? await runHandlersSandboxed({
3057
+ subgraphName,
3058
+ version: route.version,
3059
+ handlerCode: route.handlerCode,
3060
+ hostCtx: ctx,
3061
+ block: blockMeta,
3062
+ matched
3063
+ }) : await runHandlers(subgraph, matched, ctx);
3075
3064
  handlerMs = performance.now() - handlerStart;
3065
+ result.processed = runResult.processed;
3066
+ result.errors = runResult.errors;
3067
+ let flushedWrites = false;
3076
3068
  if (ctx.pendingOps > 0) {
3077
3069
  const flushStart = performance.now();
3078
- manifest = await ctx.flush();
3070
+ const manifest = await ctx.flush();
3071
+ flushedWrites = manifest.count > 0;
3072
+ if (manifest.count > 0) {
3073
+ await emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block.height);
3074
+ }
3079
3075
  flushMs = performance.now() - flushStart;
3080
3076
  }
3081
- });
3082
- result.processed = runResult.processed;
3083
- result.errors = runResult.errors;
3084
- await persistFactoryDiscoveries(schemaName, route.dataDb, blockHeight, discovered);
3085
- await targetDb.transaction().execute(async (tx) => {
3086
- if (manifest && manifest.count > 0) {
3087
- await emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block.height);
3088
- }
3089
- const byoSm = statusMode(opts);
3090
- const byoOm = opCursorMode(opts);
3091
- if (byoSm && manifest && manifest.count > 0) {
3092
- await updateSubgraphStatus(tx, subgraphName, byoSm.status, blockHeight);
3093
- } else if (byoOm && manifest && manifest.count > 0) {
3094
- await advanceOperationCursor(tx, byoOm.operationId, blockHeight);
3077
+ if (sm && flushedWrites) {
3078
+ await updateSubgraphStatus(tx, subgraphName, sm.status, blockHeight);
3079
+ } else if (opMode && flushedWrites) {
3080
+ const advanced = await advanceOperationCursor(tx, opMode.operationId, blockHeight);
3081
+ if (!advanced) {
3082
+ throw new CursorRaceLostError(opMode.operationId, blockHeight);
3083
+ }
3095
3084
  }
3096
- await applyProgress(tx, runResult, manifest ? manifest.count > 0 : false);
3085
+ await applyProgress(tx, runResult, flushedWrites);
3097
3086
  });
3098
- } else {
3099
- try {
3100
- await targetDb.transaction().execute(async (tx) => {
3101
- const opMode = opCursorMode(opts);
3102
- const sm = statusMode(opts);
3103
- const rewind = isReorgRewind(opts);
3104
- if (sm) {
3105
- const row = await tx.selectFrom("subgraphs").select("last_processed_block").where("name", "=", subgraphName).executeTakeFirst();
3106
- if (row && Number(row.last_processed_block) >= blockHeight) {
3107
- result.skipped = true;
3108
- return;
3109
- }
3110
- } else if (opMode) {
3111
- const row = await tx.selectFrom("subgraph_operations").select("cursor_block").where("id", "=", opMode.operationId).executeTakeFirst();
3112
- if (row?.cursor_block != null && Number(row.cursor_block) >= blockHeight) {
3113
- result.skipped = true;
3114
- return;
3115
- }
3116
- } else if (!rewind) {
3117
- const row = await tx.selectFrom("subgraphs").select("last_processed_block").where("name", "=", subgraphName).executeTakeFirst();
3118
- if (row && Number(row.last_processed_block) >= blockHeight) {
3119
- result.skipped = true;
3120
- return;
3121
- }
3122
- }
3123
- const ctx = new SubgraphContext(tx, schemaName, subgraph.schema, blockMeta, initialTx, false, journalEnabled(opts));
3124
- const handlerStart = performance.now();
3125
- const runResult = sandboxEnabled({
3126
- sandbox_workers: route.sandboxWorkers
3127
- }) ? await runHandlersSandboxed({
3128
- subgraphName,
3129
- version: route.version,
3130
- handlerCode: route.handlerCode,
3131
- hostCtx: ctx,
3132
- block: blockMeta,
3133
- matched
3134
- }) : await runHandlers(subgraph, matched, ctx);
3135
- handlerMs = performance.now() - handlerStart;
3136
- result.processed = runResult.processed;
3137
- result.errors = runResult.errors;
3138
- let flushedWrites = false;
3139
- if (ctx.pendingOps > 0) {
3140
- const flushStart = performance.now();
3141
- const manifest = await ctx.flush();
3142
- flushedWrites = manifest.count > 0;
3143
- if (manifest.count > 0) {
3144
- await emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block.height);
3145
- }
3146
- flushMs = performance.now() - flushStart;
3147
- }
3148
- if (sm && flushedWrites) {
3149
- await updateSubgraphStatus(tx, subgraphName, sm.status, blockHeight);
3150
- } else if (opMode && flushedWrites) {
3151
- const advanced = await advanceOperationCursor(tx, opMode.operationId, blockHeight);
3152
- if (!advanced) {
3153
- throw new CursorRaceLostError(opMode.operationId, blockHeight);
3154
- }
3155
- }
3156
- await applyProgress(tx, runResult, flushedWrites);
3087
+ } catch (err) {
3088
+ if (err instanceof CursorRaceLostError || err instanceof LiveCursorRaceLostError) {
3089
+ logger7.warn("cursor race lost — block already covered", {
3090
+ subgraph: subgraphName,
3091
+ blockHeight,
3092
+ error: err.message
3157
3093
  });
3158
- } catch (err) {
3159
- if (err instanceof CursorRaceLostError || err instanceof LiveCursorRaceLostError) {
3160
- logger7.warn("cursor race lost — block already covered", {
3161
- subgraph: subgraphName,
3162
- blockHeight,
3163
- error: err.message
3164
- });
3165
- result.skipped = true;
3166
- return result;
3167
- }
3168
- throw err;
3094
+ result.skipped = true;
3095
+ return result;
3169
3096
  }
3097
+ throw err;
3098
+ }
3099
+ if (!result.skipped) {
3100
+ await persistFactoryDiscoveries(schemaName, route.dataDb, blockHeight, discovered);
3170
3101
  }
3171
3102
  const totalMs = performance.now() - blockStart;
3172
3103
  if (!result.skipped) {
@@ -5055,16 +4986,6 @@ function bumpPatch(version) {
5055
4986
  const patch = Number.parseInt(parts[2] ?? "0", 10);
5056
4987
  return `${parts[0]}.${parts[1]}.${Number.isNaN(patch) ? 1 : patch + 1}`;
5057
4988
  }
5058
-
5059
- class ByoBreakingChangeError extends Error {
5060
- code = "BYO_BREAKING_CHANGE";
5061
- details;
5062
- constructor(reasons, diff, plan) {
5063
- super("Breaking schema change on a BYO subgraph would drop data in your " + "database. Review the plan and run the DROP + rebuild DDL manually.");
5064
- this.name = "ByoBreakingChangeError";
5065
- this.details = { reasons, diff, plan };
5066
- }
5067
- }
5068
4989
  function toDeployDiff(diff, reasons) {
5069
4990
  return {
5070
4991
  addedTables: diff?.addedTables ?? [],
@@ -5078,31 +4999,13 @@ function renderDeployPlan(def, schemaName) {
5078
4999
  const { statements } = generateSubgraphSQL(def, schemaName);
5079
5000
  const schema = schemaName ?? pgSchemaName(def.name);
5080
5001
  const dropStatement = `DROP SCHEMA IF EXISTS "${schema}" CASCADE;`;
5081
- const grantScript = [
5082
- "-- Run once on YOUR database as an owner/superuser, replacing <role>",
5083
- "-- with the role whose credentials you give Secondlayer.",
5084
- "-- Secondlayer then creates and owns only this one schema:",
5085
- "GRANT CREATE ON DATABASE current_database() TO <role>;",
5086
- `-- (after first deploy <role> owns "${schema}"; no further grants needed)`
5087
- ].join(`
5088
- `);
5089
- return { schemaName: schema, dropStatement, statements, grantScript };
5002
+ return { schemaName: schema, dropStatement, statements };
5090
5003
  }
5091
5004
  async function deploySchema(db, def, handlerPath, opts) {
5092
5005
  validateSubgraphDefinition(def);
5093
5006
  const { statements, hash } = generateSubgraphSQL(def, opts?.schemaName);
5094
5007
  const { getSubgraph: getSubgraph2, registerSubgraph } = await import("@secondlayer/shared/db/queries/subgraphs");
5095
- const ddlDb = opts?.dataDb ?? db;
5096
- const byo = opts?.dataDb != null;
5097
- const refuseDestructiveOnByo = (reasons, diff) => {
5098
- const plan = renderDeployPlan(def, opts?.schemaName);
5099
- throw new ByoBreakingChangeError(reasons, toDeployDiff(diff, reasons), {
5100
- schemaName: plan.schemaName,
5101
- dropStatement: plan.dropStatement,
5102
- statements: plan.statements,
5103
- grantScript: plan.grantScript
5104
- });
5105
- };
5008
+ const ddlDb = db;
5106
5009
  const existing = await getSubgraph2(db, def.name, opts?.accountId);
5107
5010
  const schemaName = opts?.schemaName ?? pgSchemaName(def.name);
5108
5011
  const newVersion = opts?.version ?? (existing ? bumpPatch(existing.version) : "1.0.0");
@@ -5124,8 +5027,7 @@ async function deploySchema(db, def, handlerPath, opts) {
5124
5027
  handlerCode: opts?.handlerCode,
5125
5028
  sourceCode: opts?.sourceCode,
5126
5029
  schemaName,
5127
- startBlock: def.startBlock,
5128
- databaseUrlEnc: opts?.databaseUrlEnc ?? null
5030
+ startBlock: def.startBlock
5129
5031
  };
5130
5032
  if (existing) {
5131
5033
  const schemaExists = await sql5`
@@ -5160,8 +5062,6 @@ async function deploySchema(db, def, handlerPath, opts) {
5160
5062
  };
5161
5063
  }
5162
5064
  if (existing.schema_hash === hash && opts?.forceReindex) {
5163
- if (byo)
5164
- refuseDestructiveOnByo(["force reindex"], null);
5165
5065
  await sql5.raw(`DROP SCHEMA IF EXISTS "${schemaName}" CASCADE`).execute(ddlDb);
5166
5066
  for (const stmt of statements) {
5167
5067
  await sql5.raw(stmt).execute(ddlDb);
@@ -5173,9 +5073,6 @@ async function deploySchema(db, def, handlerPath, opts) {
5173
5073
  const diff = diffSchema(existing.definition.schema, def.schema);
5174
5074
  const { breaking, reasons } = hasBreakingChanges(diff);
5175
5075
  if (breaking || opts?.forceReindex) {
5176
- if (byo) {
5177
- refuseDestructiveOnByo(reasons.length > 0 ? reasons : ["force reindex"], diff);
5178
- }
5179
5076
  await sql5.raw(`DROP SCHEMA IF EXISTS "${schemaName}" CASCADE`).execute(ddlDb);
5180
5077
  for (const stmt of statements) {
5181
5078
  await sql5.raw(stmt).execute(ddlDb);
@@ -5357,9 +5254,8 @@ export {
5357
5254
  camelizeDataKey,
5358
5255
  backfillSubgraph,
5359
5256
  INDEX_CODEGEN_TABLES,
5360
- ChainReadError,
5361
- ByoBreakingChangeError
5257
+ ChainReadError
5362
5258
  };
5363
5259
 
5364
- //# debugId=720FEFA20DA031E864756E2164756E21
5260
+ //# debugId=544EEADDD1FD235264756E2164756E21
5365
5261
  //# sourceMappingURL=index.js.map