@secondlayer/subgraphs 3.26.3 → 4.0.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 +10 -63
- package/dist/src/index.d.ts +8 -38
- package/dist/src/index.js +70 -174
- package/dist/src/index.js.map +8 -8
- package/dist/src/runtime/block-processor.d.ts +2 -13
- package/dist/src/runtime/block-processor.js +66 -136
- package/dist/src/runtime/block-processor.js.map +5 -5
- package/dist/src/runtime/catchup.d.ts +4 -4
- package/dist/src/runtime/catchup.js +66 -135
- package/dist/src/runtime/catchup.js.map +6 -6
- package/dist/src/runtime/context.d.ts +1 -9
- package/dist/src/runtime/context.js +2 -13
- package/dist/src/runtime/context.js.map +3 -3
- package/dist/src/runtime/processor.js +69 -145
- package/dist/src/runtime/processor.js.map +9 -9
- package/dist/src/runtime/reindex.d.ts +3 -4
- package/dist/src/runtime/reindex.js +66 -135
- package/dist/src/runtime/reindex.js.map +7 -7
- package/dist/src/runtime/reorg.js +69 -141
- package/dist/src/runtime/reorg.js.map +7 -7
- package/dist/src/runtime/runner.d.ts +1 -9
- package/dist/src/runtime/runner.js.map +2 -2
- package/dist/src/schema/index.d.ts +0 -8
- package/dist/src/schema/index.js +4 -38
- package/dist/src/schema/index.js.map +3 -3
- package/dist/src/service.js +69 -145
- package/dist/src/service.js.map +9 -9
- package/dist/src/testing/index.js +3 -14
- package/dist/src/testing/index.js.map +5 -5
- package/package.json +2 -2
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:
|
|
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 (`
|
|
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
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
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
|
-
##
|
|
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
|
-
|
|
159
|
-
|
|
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` /
|
package/dist/src/index.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
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
|
|
1166
|
+
/** `DROP SCHEMA … CASCADE` a destructive rebuild would run first. */
|
|
1187
1167
|
dropStatement: string;
|
|
1188
|
-
/** DDL
|
|
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
|
|
1195
|
-
* Powers `--dry-run`: the user reviews exactly what
|
|
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,
|
|
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,
|
|
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:
|
|
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
|
|
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
|
-
|
|
3055
|
-
|
|
3056
|
-
const
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
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 =
|
|
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
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
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,
|
|
3085
|
+
await applyProgress(tx, runResult, flushedWrites);
|
|
3097
3086
|
});
|
|
3098
|
-
}
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
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
|
-
|
|
3159
|
-
|
|
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
|
-
|
|
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 =
|
|
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=
|
|
5260
|
+
//# debugId=544EEADDD1FD235264756E2164756E21
|
|
5365
5261
|
//# sourceMappingURL=index.js.map
|