@secondlayer/subgraphs 3.2.1 → 3.3.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.
Files changed (36) hide show
  1. package/README.md +81 -0
  2. package/dist/src/index.d.ts +71 -9
  3. package/dist/src/index.js +461 -54
  4. package/dist/src/index.js.map +11 -9
  5. package/dist/src/runtime/block-processor.d.ts +37 -9
  6. package/dist/src/runtime/block-processor.js +127 -37
  7. package/dist/src/runtime/block-processor.js.map +5 -5
  8. package/dist/src/runtime/catchup.d.ts +34 -8
  9. package/dist/src/runtime/catchup.js +125 -36
  10. package/dist/src/runtime/catchup.js.map +5 -5
  11. package/dist/src/runtime/context.d.ts +27 -1
  12. package/dist/src/runtime/context.js +13 -2
  13. package/dist/src/runtime/context.js.map +3 -3
  14. package/dist/src/runtime/processor.js +143 -39
  15. package/dist/src/runtime/processor.js.map +8 -8
  16. package/dist/src/runtime/reindex.d.ts +34 -8
  17. package/dist/src/runtime/reindex.js +131 -36
  18. package/dist/src/runtime/reindex.js.map +6 -6
  19. package/dist/src/runtime/reorg.d.ts +34 -8
  20. package/dist/src/runtime/reorg.js +131 -39
  21. package/dist/src/runtime/reorg.js.map +6 -6
  22. package/dist/src/runtime/runner.d.ts +43 -9
  23. package/dist/src/runtime/source-matcher.d.ts +19 -10
  24. package/dist/src/runtime/source-matcher.js +26 -6
  25. package/dist/src/runtime/source-matcher.js.map +3 -3
  26. package/dist/src/schema/index.d.ts +42 -8
  27. package/dist/src/schema/index.js +57 -19
  28. package/dist/src/schema/index.js.map +5 -5
  29. package/dist/src/service.js +143 -39
  30. package/dist/src/service.js.map +8 -8
  31. package/dist/src/triggers/index.d.ts +16 -8
  32. package/dist/src/types.d.ts +35 -9
  33. package/dist/src/validate.d.ts +34 -8
  34. package/dist/src/validate.js +10 -3
  35. package/dist/src/validate.js.map +3 -3
  36. package/package.json +3 -3
package/README.md CHANGED
@@ -93,6 +93,87 @@ Delivery bodies and response previews land in `subscription_deliveries`. Rows wh
93
93
  | `SUBGRAPH_REINDEX_BATCH_SIZE` | plan-based | Override the default historical block batch size used by reindex/backfill. |
94
94
  | `SUBGRAPH_REINDEX_MIN_BATCH_SIZE` | plan-based | Override the adaptive lower bound for reindex/backfill batches. |
95
95
  | `SUBGRAPH_REINDEX_MAX_BATCH_SIZE` | plan-based | Override the adaptive upper bound for reindex/backfill batches. |
96
+ | `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. |
97
+ | `DATABASE_IDLE_TIMEOUT` | `300` | Seconds before idle connections are closed (`0` = never). Keeps a fleet of BYO pools from pinning connections. |
98
+
99
+ ## Bring your own database (BYO data plane)
100
+
101
+ There are three tiers, not two:
102
+
103
+ | Tier | Indexer / decode | Handler exec | Database | Serving API |
104
+ | --- | --- | --- | --- | --- |
105
+ | Managed (default) | ours | ours | ours | ours |
106
+ | **BYO data plane** | ours | ours | **yours** | **yours** |
107
+ | Self-host | yours | yours | yours | yours |
108
+
109
+ With BYO, the managed pipeline (ingest → decode → match → run your handler) is
110
+ unchanged, but your handler's rows land in **your** Postgres and the serving API
111
+ reads from there. Deploy with a connection string:
112
+
113
+ ```bash
114
+ sl subgraphs deploy subgraphs/my.ts --database-url "postgres://user:pass@your-host:5432/db"
115
+ ```
116
+
117
+ The connection string is stored encrypted at rest (AES-GCM, keyed by
118
+ `SECONDLAYER_SECRETS_KEY`) and never returned in API responses. The server
119
+ verifies the connection before deploying. Once deployed, query the
120
+ `subgraph_…` schema directly with any ORM/GraphQL/REST — we're no longer in the
121
+ serving path.
122
+
123
+ **Preview before it touches your DB.** `POST /api/subgraphs` with
124
+ `{"dryRun": true, "databaseUrl": "…"}` returns the exact DDL plus a grant
125
+ script and verifies the connection — without writing anything.
126
+
127
+ **Constraints (v1):**
128
+
129
+ - **Idempotent handlers only.** A BYO block write can't share the managed
130
+ transaction, so a crash replays the block (at-least-once). `ctx.insert` and
131
+ `ctx.upsert` (with a unique key) are safe — flush is replace-per-height. A
132
+ deploy with non-idempotent `ctx.update` / `ctx.patchOrInsert` is rejected.
133
+ - **No reindex.** Reindex would drop + rebuild the schema in your DB from a
134
+ background job; instead, re-deploy to rebuild (or drop the schema yourself).
135
+ - **Delete leaves your data.** Deleting the subgraph removes our registry row
136
+ (and the stored connection) and pauses subscriptions, but never drops the
137
+ schema in your database.
138
+
139
+ **Recommended:** give Secondlayer a least-privilege role scoped to its own
140
+ schema, and point `--database-url` at a session-mode pooler endpoint
141
+ (PgBouncer/Neon/Supabase) rather than raw Postgres.
142
+
143
+ ### ORM codegen
144
+
145
+ Once rows land in your DB, generate a typed schema for your ORM:
146
+
147
+ ```bash
148
+ sl subgraphs codegen subgraphs/my.ts --target prisma -o prisma/schema.prisma
149
+ sl subgraphs codegen subgraphs/my.ts --target drizzle -o db/schema.ts
150
+ ```
151
+
152
+ Prisma and Drizzle have first-class generators (`generatePrismaSchema` /
153
+ `generateDrizzleSchema` are exported); both emit relations/`@relation` from the
154
+ schema's `relations` metadata. For Kysely, run `kysely-codegen` against the DB.
155
+ Output mirrors the deployed DDL — `prisma db pull` should be a no-op; treat the
156
+ tables as read-only (the processor owns them) and never `migrate`/`push`.
157
+ `uint`→`Decimal`/`numeric` and the `BigInt` id need `.toString()` for JSON.
158
+
159
+ ## Trait-scoped sources
160
+
161
+ A source can target a SIP standard instead of a fixed contract — it indexes
162
+ every contract the registry classifies as that standard (incl. ones deployed
163
+ later):
164
+
165
+ ```ts
166
+ sources: {
167
+ tokens: { type: "ft_transfer", trait: "sip-010" }, // all SIP-010 tokens
168
+ }
169
+ ```
170
+
171
+ `trait` (`sip-009` | `sip-010` | `sip-013`) is supported on FT/NFT/`contract_call`/
172
+ `print_event` filters and composes (AND) with other fields. Token filters match
173
+ the asset-identifier's contract; `contract_call`/`print` match `contract_id`.
174
+ Resolution is as-of-block, so a reindex backfills a contract's full history even
175
+ if it was classified after deploy. Requires the contract registry to be
176
+ populated. Discover the set via `GET /v1/contracts?trait=sip-010`.
96
177
 
97
178
  ## Postgres + pool mode
98
179
 
@@ -9,6 +9,22 @@ interface SubgraphColumn {
9
9
  search?: boolean;
10
10
  default?: string | number | boolean;
11
11
  }
12
+ /**
13
+ * A foreign-key relation to another table in the same subgraph. Drives DDL FK
14
+ * constraints and ORM codegen (`@relation` in Prisma, `relations()` in Drizzle)
15
+ * so generated clients get typed joins. The referenced columns must form a
16
+ * `uniqueKeys` entry on the target table.
17
+ */
18
+ interface SubgraphRelation {
19
+ /** Relation field name on this table's generated model (e.g. "pool"). */
20
+ name: string;
21
+ /** Target table name in this subgraph. */
22
+ references: string;
23
+ /** Local column(s) holding the foreign key. */
24
+ fields: string[];
25
+ /** Target column(s) the fields point at (a uniqueKeys entry on the target). */
26
+ referencedColumns: string[];
27
+ }
12
28
  /** Table definition within a subgraph schema */
13
29
  interface SubgraphTable {
14
30
  columns: Record<string, SubgraphColumn>;
@@ -16,6 +32,8 @@ interface SubgraphTable {
16
32
  indexes?: string[][];
17
33
  /** Unique key constraints (each entry is an array of column names). Required for upsert. */
18
34
  uniqueKeys?: string[][];
35
+ /** Foreign-key relations to other tables (for typed ORM joins). */
36
+ relations?: SubgraphRelation[];
19
37
  }
20
38
  /** Subgraph schema — maps table names to table definitions */
21
39
  type SubgraphSchema = Record<string, SubgraphTable>;
@@ -42,39 +60,47 @@ interface StxLockFilter {
42
60
  lockedAddress?: string;
43
61
  minAmount?: bigint;
44
62
  }
63
+ /**
64
+ * Restrict a source to contracts conforming to a trait/standard (e.g. "sip-010")
65
+ * instead of a fixed contract — resolved from the contract registry at match time,
66
+ * as-of each processed block. Lets a source index "all SIP-010 tokens" etc.
67
+ */
68
+ type TraitScope = {
69
+ trait?: string
70
+ };
45
71
  /** FT event filters */
46
- interface FtTransferFilter {
72
+ interface FtTransferFilter extends TraitScope {
47
73
  type: "ft_transfer";
48
74
  assetIdentifier?: string;
49
75
  sender?: string;
50
76
  recipient?: string;
51
77
  minAmount?: bigint;
52
78
  }
53
- interface FtMintFilter {
79
+ interface FtMintFilter extends TraitScope {
54
80
  type: "ft_mint";
55
81
  assetIdentifier?: string;
56
82
  recipient?: string;
57
83
  minAmount?: bigint;
58
84
  }
59
- interface FtBurnFilter {
85
+ interface FtBurnFilter extends TraitScope {
60
86
  type: "ft_burn";
61
87
  assetIdentifier?: string;
62
88
  sender?: string;
63
89
  minAmount?: bigint;
64
90
  }
65
91
  /** NFT event filters */
66
- interface NftTransferFilter {
92
+ interface NftTransferFilter extends TraitScope {
67
93
  type: "nft_transfer";
68
94
  assetIdentifier?: string;
69
95
  sender?: string;
70
96
  recipient?: string;
71
97
  }
72
- interface NftMintFilter {
98
+ interface NftMintFilter extends TraitScope {
73
99
  type: "nft_mint";
74
100
  assetIdentifier?: string;
75
101
  recipient?: string;
76
102
  }
77
- interface NftBurnFilter {
103
+ interface NftBurnFilter extends TraitScope {
78
104
  type: "nft_burn";
79
105
  assetIdentifier?: string;
80
106
  sender?: string;
@@ -121,7 +147,7 @@ interface ContractCallEvent {
121
147
  };
122
148
  }
123
149
  /** Contract event filters */
124
- interface ContractCallFilter {
150
+ interface ContractCallFilter extends TraitScope {
125
151
  type: "contract_call";
126
152
  contractId?: string;
127
153
  functionName?: string;
@@ -139,7 +165,7 @@ interface ContractDeployFilter {
139
165
  deployer?: string;
140
166
  contractName?: string;
141
167
  }
142
- interface PrintEventFilter {
168
+ interface PrintEventFilter extends TraitScope {
143
169
  type: "print_event";
144
170
  contractId?: string;
145
171
  topic?: string;
@@ -559,6 +585,22 @@ interface GeneratedSQL {
559
585
  * each with auto-columns and indexes.
560
586
  */
561
587
  declare function generateSubgraphSQL(def: SubgraphDefinition, schemaNameOverride?: string): GeneratedSQL;
588
+ interface PrismaGenOptions {
589
+ /** Postgres schema the tables live in (account-scoped). */
590
+ schemaName?: string;
591
+ /** env var the datasource url reads from. */
592
+ datasourceEnv?: string;
593
+ /**
594
+ * Emit only the `model` blocks (no datasource/generator). Lets users compose
595
+ * these models with their own schema via Prisma's `prismaSchemaFolder`.
596
+ */
597
+ modelsOnly?: boolean;
598
+ }
599
+ declare function generatePrismaSchema(def: SubgraphDefinition, opts?: PrismaGenOptions): string;
600
+ interface DrizzleGenOptions {
601
+ schemaName?: string;
602
+ }
603
+ declare function generateDrizzleSchema(def: SubgraphDefinition, opts?: DrizzleGenOptions): string;
562
604
  import { pgSchemaName } from "@secondlayer/shared/db/queries/subgraphs";
563
605
  import { Database } from "@secondlayer/shared/db";
564
606
  import { Kysely } from "kysely";
@@ -586,6 +628,18 @@ interface DeployDiff {
586
628
  addedColumns: Record<string, string[]>;
587
629
  breakingChanges: string[];
588
630
  }
631
+ interface DeployPlan {
632
+ schemaName: string;
633
+ /** DDL Secondlayer will run against your database. */
634
+ statements: string[];
635
+ /** Least-privilege grant script to run once, before deploying. */
636
+ grantScript: string;
637
+ }
638
+ /**
639
+ * Render the DDL + grant script a BYO deploy would run, without executing.
640
+ * Powers `--dry-run`: the user reviews exactly what touches their DB first.
641
+ */
642
+ declare function renderDeployPlan(def: SubgraphDefinition, schemaName?: string): DeployPlan;
589
643
  /**
590
644
  * Deploy a subgraph schema to the database.
591
645
  * - New subgraph → CREATE SCHEMA + tables + register
@@ -601,10 +655,18 @@ declare function deploySchema(db: AnyDb, def: SubgraphDefinition, handlerPath: s
601
655
  version?: string
602
656
  handlerCode?: string
603
657
  sourceCode?: string
658
+ /**
659
+ * BYO data plane: when set, schema DDL (CREATE/ALTER/index) runs against
660
+ * the user-owned DB while the subgraphs registry row stays on `db`
661
+ * (managed). Defaults to `db` — managed deploys are unchanged.
662
+ */
663
+ dataDb?: AnyDb
664
+ /** Encrypted user-DB connection string to persist on the registry row. */
665
+ databaseUrlEnc?: Buffer | null
604
666
  }): Promise<{
605
667
  action: "created" | "unchanged" | "handler_updated" | "updated" | "reindexed"
606
668
  subgraphId: string
607
669
  version: string
608
670
  diff?: DeployDiff
609
671
  }>;
610
- export { validateSubgraphDefinition, resumeReindex, reindexSubgraph, pgSchemaName, generateSubgraphSQL, diffSchema, deploySchema, defineSubgraph, backfillSubgraph, WriteRow, WhereInput, TypedSubgraphDefinition, TypedSubgraphContext, TypedHandlers, TxMeta, TableDiff, SystemRow, SubgraphTableClient, SubgraphTable, SubgraphSchema, SubgraphHandler, SubgraphFilter, SubgraphDefinition, SubgraphContext, SubgraphColumn, StxTransferPayload, StxTransferFilter, StxMintPayload, StxMintFilter, StxLockPayload, StxLockFilter, StxBurnPayload, StxBurnFilter, RowValue, ReindexOptions, PrintEventPayload, PrintEventFor, PrintEventFilter, NftTransferPayload, NftTransferFilter, NftMintPayload, NftMintFilter, NftBurnPayload, NftBurnFilter, InferTableRow, InferSubgraphClient, InferColumnType, GeneratedSQL, FtTransferPayload, FtTransferFilter, FtMintPayload, FtMintFilter, FtBurnPayload, FtBurnFilter, FindManyOptions, EventForFilter, ContractDeployPayload, ContractDeployFilter, ContractCallPayload, ContractCallFilter, ContractCallEvent, ComputedValue, ComparisonFilter, ColumnType, ColumnToTS, ColumnDiff, AnyEvent };
672
+ export { validateSubgraphDefinition, resumeReindex, renderDeployPlan, reindexSubgraph, pgSchemaName, generateSubgraphSQL, generatePrismaSchema, generateDrizzleSchema, diffSchema, deploySchema, defineSubgraph, backfillSubgraph, WriteRow, WhereInput, TypedSubgraphDefinition, TypedSubgraphContext, TypedHandlers, TxMeta, TableDiff, SystemRow, SubgraphTableClient, SubgraphTable, SubgraphSchema, SubgraphHandler, SubgraphFilter, SubgraphDefinition, SubgraphContext, SubgraphColumn, StxTransferPayload, StxTransferFilter, StxMintPayload, StxMintFilter, StxLockPayload, StxLockFilter, StxBurnPayload, StxBurnFilter, RowValue, ReindexOptions, PrismaGenOptions, PrintEventPayload, PrintEventFor, PrintEventFilter, NftTransferPayload, NftTransferFilter, NftMintPayload, NftMintFilter, NftBurnPayload, NftBurnFilter, InferTableRow, InferSubgraphClient, InferColumnType, GeneratedSQL, FtTransferPayload, FtTransferFilter, FtMintPayload, FtMintFilter, FtBurnPayload, FtBurnFilter, FindManyOptions, EventForFilter, DrizzleGenOptions, DeployPlan, ContractDeployPayload, ContractDeployFilter, ContractCallPayload, ContractCallFilter, ContractCallEvent, ComputedValue, ComparisonFilter, ColumnType, ColumnToTS, ColumnDiff, AnyEvent };