@secondlayer/subgraphs 4.1.5 → 4.2.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 (37) hide show
  1. package/dist/src/index.d.ts +87 -29
  2. package/dist/src/index.js +376 -128
  3. package/dist/src/index.js.map +9 -7
  4. package/dist/src/runtime/block-processor.d.ts +72 -23
  5. package/dist/src/runtime/block-processor.js +186 -53
  6. package/dist/src/runtime/block-processor.js.map +7 -5
  7. package/dist/src/runtime/catchup.d.ts +72 -23
  8. package/dist/src/runtime/catchup.js +201 -68
  9. package/dist/src/runtime/catchup.js.map +7 -5
  10. package/dist/src/runtime/processor.js +281 -147
  11. package/dist/src/runtime/processor.js.map +8 -6
  12. package/dist/src/runtime/reindex.d.ts +72 -23
  13. package/dist/src/runtime/reindex.js +238 -105
  14. package/dist/src/runtime/reindex.js.map +7 -5
  15. package/dist/src/runtime/reorg.d.ts +72 -23
  16. package/dist/src/runtime/reorg.js +215 -81
  17. package/dist/src/runtime/reorg.js.map +8 -6
  18. package/dist/src/runtime/runner.d.ts +72 -23
  19. package/dist/src/runtime/runner.js +156 -23
  20. package/dist/src/runtime/runner.js.map +8 -6
  21. package/dist/src/runtime/source-matcher.d.ts +72 -23
  22. package/dist/src/schema/index.d.ts +72 -23
  23. package/dist/src/schema/index.js +115 -1
  24. package/dist/src/schema/index.js.map +3 -3
  25. package/dist/src/service.js +286 -152
  26. package/dist/src/service.js.map +8 -6
  27. package/dist/src/testing/index.d.ts +195 -24
  28. package/dist/src/testing/index.js +448 -25
  29. package/dist/src/testing/index.js.map +9 -6
  30. package/dist/src/triggers/index.d.ts +83 -24
  31. package/dist/src/triggers/index.js +5 -2
  32. package/dist/src/triggers/index.js.map +3 -3
  33. package/dist/src/types.d.ts +73 -24
  34. package/dist/src/validate.d.ts +72 -23
  35. package/dist/src/validate.js +115 -1
  36. package/dist/src/validate.js.map +3 -3
  37. package/package.json +2 -2
@@ -46,7 +46,12 @@ type TraitScope = {
46
46
  *
47
47
  * ```ts
48
48
  * sources: {
49
- * registry: { type: "print_event", contractId: REGISTRY, topic: "pool-created" },
49
+ * registry: {
50
+ * type: "print_event",
51
+ * contractId: REGISTRY,
52
+ * topic: "pool-created",
53
+ * prints: { "pool-created": { pool: "principal" } },
54
+ * },
50
55
  * swaps: {
51
56
  * type: "print_event",
52
57
  * topic: "swap",
@@ -130,6 +135,9 @@ interface ContractCallFilter extends TraitScope, FactoryScope {
130
135
  * decoded function arguments. Dev-provided; serialized into the deployed
131
136
  * definition and used at runtime to decode args by name. Omit to keep
132
137
  * `event.args` as a positional `unknown[]`.
138
+ *
139
+ * Deploy validation requires `abi` whenever `functionName` is set
140
+ * (`normalizeAbi()` from `@secondlayer/stacks/clarity`).
133
141
  */
134
142
  abi?: AbiContract2;
135
143
  }
@@ -160,29 +168,70 @@ type PrintField = ColumnType | {
160
168
  } | {
161
169
  list: PrintField
162
170
  };
163
- interface PrintEventFilter extends TraitScope, FactoryScope {
164
- type: "print_event";
165
- /** One contract id, or a set of them (max 20). Supports `*` wildcards.
166
- * COMPOSES with `trait` — the matcher ANDs them, so the pair means
167
- * "contracts conforming to this trait, narrowed to these ids". (The Index
168
- * read API refuses the combination, but that is its query planner's
169
- * constraint, not a semantic one.) */
170
- contractId?: string | readonly string[];
171
- topic?: string;
172
- /**
173
- * Per-topic field schema. When declared, the handler's `event` is a
174
- * discriminated union keyed by `topic` and `event.data` is typed per topic
175
- * (e.g. `{ "completed-deposit": { amount: "uint", sender: "principal" } }`).
176
- * Nested tuples, lists, and optional fields are expressible — see
177
- * {@link PrintField}.
178
- *
179
- * Declaring `prints` also opts the source into RUNTIME validation: an
180
- * event whose decoded payload does not match is skipped and logged rather
181
- * than written as nulls (and the deploy-time field lint is promoted from
182
- * a warning to an error).
183
- */
184
- prints?: Record<string, Record<string, PrintField>>;
171
+ /**
172
+ * Per-topic field schema for a print_event source. When declared, the handler's
173
+ * `event` is a discriminated union keyed by `topic` and `event.data` is typed
174
+ * per topic. Nested tuples, lists, and optional fields — see {@link PrintField}.
175
+ *
176
+ * Also opts the source into RUNTIME validation: a payload that does not match
177
+ * is skipped and logged rather than written as nulls.
178
+ */
179
+ type PrintEventPrints = Record<string, Record<string, PrintField>>;
180
+ /**
181
+ * One column in a static identity map. Literals only — the bundler never
182
+ * executes user code, so `{ from: "tokenX" }` is valid and `from("tokenX")`
183
+ * is not.
184
+ */
185
+ type MaterializeColumn = {
186
+ from: string
187
+ } | {
188
+ fromTx: "sender" | "txId"
189
+ } | {
190
+ fromBlock: "height" | "hash" | "timestamp"
191
+ };
192
+ /** Static insert from a source event — replaces a 1:1 handler. */
193
+ interface MaterializeSpec {
194
+ table: string;
195
+ /** Keys = schema column names (snake). */
196
+ columns: Record<string, MaterializeColumn>;
185
197
  }
198
+ type PrintEventFields = FactoryScope & {
199
+ type: "print_event"
200
+ topic?: string
201
+ /** Identity map: insert without a handler function. XOR with handlers[name]. */
202
+ materialize?: MaterializeSpec
203
+ };
204
+ /**
205
+ * Pinned to one or more contract ids with no trait — `prints` is required so
206
+ * `event.data` is typed and empty-table handlers cannot typecheck.
207
+ */
208
+ type PinnedPrintEventFilter = PrintEventFields & {
209
+ contractId: string | readonly string[]
210
+ trait?: undefined
211
+ prints: PrintEventPrints
212
+ };
213
+ /**
214
+ * Trait-scoped print source (optionally narrowed by contractId). No single
215
+ * schema — `prints` stays optional.
216
+ */
217
+ type TraitPrintEventFilter = PrintEventFields & {
218
+ contractId?: string | readonly string[]
219
+ trait: string
220
+ prints?: PrintEventPrints
221
+ };
222
+ /**
223
+ * Unpinned / factory-only print source (no contractId). `prints` optional.
224
+ */
225
+ type UnpinnedPrintEventFilter = PrintEventFields & {
226
+ contractId?: undefined
227
+ trait?: string
228
+ prints?: PrintEventPrints
229
+ };
230
+ /**
231
+ * Print-event filter. Pinned sources (contractId set, no trait) require
232
+ * `prints`; trait and unpinned sources may omit it.
233
+ */
234
+ type PrintEventFilter = PinnedPrintEventFilter | TraitPrintEventFilter | UnpinnedPrintEventFilter;
186
235
  /** All subgraph filter types — discriminated on `type` */
187
236
  type SubgraphFilter = StxTransferFilter | StxMintFilter | StxBurnFilter | StxLockFilter | FtTransferFilter | FtMintFilter | FtBurnFilter | NftTransferFilter | NftMintFilter | NftBurnFilter | ContractCallFilter | ContractDeployFilter | PrintEventFilter;
188
237
  interface EventTrigger {
@@ -297,7 +346,17 @@ interface TriggerHelpers {
297
346
  nftBurn: (f?: Omit<NftBurnFilter, "type">) => TypedEventTrigger<NftBurnEvent>;
298
347
  contractCall: (f?: Omit<ContractCallFilter, "type">) => TypedEventTrigger<ContractCallEvent2>;
299
348
  contractDeploy: (f?: Omit<ContractDeployFilter, "type">) => TypedEventTrigger<ContractDeployEvent>;
300
- printEvent: (f?: Omit<PrintEventFilter, "type">) => TypedEventTrigger<PrintEventEvent>;
349
+ /** Subscription triggers are not subgraph deploys — prints not required. */
350
+ printEvent: (f?: {
351
+ contractId?: string | readonly string[]
352
+ topic?: string
353
+ trait?: string
354
+ factory?: {
355
+ from: string
356
+ field: string
357
+ }
358
+ prints?: PrintEventPrints
359
+ }) => TypedEventTrigger<PrintEventEvent>;
301
360
  }
302
361
  declare const on: TriggerHelpers;
303
362
  /** Extract the event payload type from a trigger. */
@@ -18,11 +18,14 @@ var on = {
18
18
  nftBurn: (f = {}) => make({ type: "nft_burn", ...f }),
19
19
  contractCall: (f = {}) => make({ type: "contract_call", ...f }),
20
20
  contractDeploy: (f = {}) => make({ type: "contract_deploy", ...f }),
21
- printEvent: (f = {}) => make({ type: "print_event", ...f })
21
+ printEvent: (f = {}) => make({
22
+ type: "print_event",
23
+ ...f
24
+ })
22
25
  };
23
26
  export {
24
27
  on
25
28
  };
26
29
 
27
- //# debugId=C92F67573451324664756E2164756E21
30
+ //# debugId=E8A9BFB4916AE4AF64756E2164756E21
28
31
  //# sourceMappingURL=index.js.map
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/triggers/index.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * Typed event-trigger helpers. Each helper returns a standard `EventTrigger`\n * plus a phantom `__event` type used for inference at consumer sites. The\n * phantom carries no runtime value.\n */\n\nimport type {\n\tContractCallFilter,\n\tContractDeployFilter,\n\tFtBurnFilter,\n\tFtMintFilter,\n\tFtTransferFilter,\n\tNftBurnFilter,\n\tNftMintFilter,\n\tNftTransferFilter,\n\tPrintEventFilter,\n\tStxBurnFilter,\n\tStxLockFilter,\n\tStxMintFilter,\n\tStxTransferFilter,\n} from \"../types.ts\";\nimport type { EventTrigger } from \"./types.ts\";\n\n// --- Per-filter event payload shapes ---\n\n/** Metadata shared by every event. */\nexport interface TxMeta {\n\ttxId: string;\n\tsender: string;\n\tblockHeight: number;\n\tblockTime: number;\n}\n\nexport interface StxTransferEvent {\n\tsender: string;\n\trecipient: string;\n\tamount: bigint;\n\tmemo: string | null;\n\ttx: TxMeta;\n}\n\nexport interface StxMintEvent {\n\trecipient: string;\n\tamount: bigint;\n\ttx: TxMeta;\n}\n\nexport interface StxBurnEvent {\n\tsender: string;\n\tamount: bigint;\n\ttx: TxMeta;\n}\n\nexport interface StxLockEvent {\n\tlockedAddress: string;\n\tamount: bigint;\n\tunlockHeight: number;\n\ttx: TxMeta;\n}\n\nexport interface FtTransferEvent {\n\tassetIdentifier: string;\n\tsender: string;\n\trecipient: string;\n\tamount: bigint;\n\ttx: TxMeta;\n}\n\nexport interface FtMintEvent {\n\tassetIdentifier: string;\n\trecipient: string;\n\tamount: bigint;\n\ttx: TxMeta;\n}\n\nexport interface FtBurnEvent {\n\tassetIdentifier: string;\n\tsender: string;\n\tamount: bigint;\n\ttx: TxMeta;\n}\n\nexport interface NftTransferEvent {\n\tassetIdentifier: string;\n\tsender: string;\n\trecipient: string;\n\ttokenId: string;\n\ttx: TxMeta;\n}\n\nexport interface NftMintEvent {\n\tassetIdentifier: string;\n\trecipient: string;\n\ttokenId: string;\n\ttx: TxMeta;\n}\n\nexport interface NftBurnEvent {\n\tassetIdentifier: string;\n\tsender: string;\n\ttokenId: string;\n\ttx: TxMeta;\n}\n\nexport interface ContractCallEvent {\n\tcontractId: string;\n\tfunctionName: string;\n\targs: unknown[];\n\tcaller: string;\n\ttx: TxMeta;\n}\n\nexport interface ContractDeployEvent {\n\tcontractId: string;\n\tdeployer: string;\n\tcontractName: string;\n\ttx: TxMeta;\n}\n\nexport interface PrintEventEvent {\n\tcontractId: string;\n\ttopic: string | null;\n\tvalue: unknown;\n\ttx: TxMeta;\n}\n\n// --- Typed trigger wrapper ---\n\n/**\n * Phantom-typed EventTrigger. Runtime shape is identical to `EventTrigger`;\n * the `__event` field only exists at the type level, letting subscription\n * definitions infer the handler's `event` parameter from the filter.\n */\nexport type TypedEventTrigger<TEvent> = EventTrigger & {\n\treadonly __event?: TEvent;\n};\n\nfunction make<TEvent>(\n\tfilter: EventTrigger[\"filter\"],\n): TypedEventTrigger<TEvent> {\n\treturn { type: \"event\", filter };\n}\n\n// --- `on.*` helpers, 13 total ---\n\nexport interface TriggerHelpers {\n\tstxTransfer: (\n\t\tf?: Omit<StxTransferFilter, \"type\">,\n\t) => TypedEventTrigger<StxTransferEvent>;\n\tstxMint: (f?: Omit<StxMintFilter, \"type\">) => TypedEventTrigger<StxMintEvent>;\n\tstxBurn: (f?: Omit<StxBurnFilter, \"type\">) => TypedEventTrigger<StxBurnEvent>;\n\tstxLock: (f?: Omit<StxLockFilter, \"type\">) => TypedEventTrigger<StxLockEvent>;\n\tftTransfer: (\n\t\tf?: Omit<FtTransferFilter, \"type\">,\n\t) => TypedEventTrigger<FtTransferEvent>;\n\tftMint: (f?: Omit<FtMintFilter, \"type\">) => TypedEventTrigger<FtMintEvent>;\n\tftBurn: (f?: Omit<FtBurnFilter, \"type\">) => TypedEventTrigger<FtBurnEvent>;\n\tnftTransfer: (\n\t\tf?: Omit<NftTransferFilter, \"type\">,\n\t) => TypedEventTrigger<NftTransferEvent>;\n\tnftMint: (f?: Omit<NftMintFilter, \"type\">) => TypedEventTrigger<NftMintEvent>;\n\tnftBurn: (f?: Omit<NftBurnFilter, \"type\">) => TypedEventTrigger<NftBurnEvent>;\n\tcontractCall: (\n\t\tf?: Omit<ContractCallFilter, \"type\">,\n\t) => TypedEventTrigger<ContractCallEvent>;\n\tcontractDeploy: (\n\t\tf?: Omit<ContractDeployFilter, \"type\">,\n\t) => TypedEventTrigger<ContractDeployEvent>;\n\tprintEvent: (\n\t\tf?: Omit<PrintEventFilter, \"type\">,\n\t) => TypedEventTrigger<PrintEventEvent>;\n}\n\nexport const on: TriggerHelpers = {\n\tstxTransfer: (f = {}) =>\n\t\tmake<StxTransferEvent>({ type: \"stx_transfer\", ...f }),\n\n\tstxMint: (f = {}) => make<StxMintEvent>({ type: \"stx_mint\", ...f }),\n\n\tstxBurn: (f = {}) => make<StxBurnEvent>({ type: \"stx_burn\", ...f }),\n\n\tstxLock: (f = {}) => make<StxLockEvent>({ type: \"stx_lock\", ...f }),\n\n\tftTransfer: (f = {}) => make<FtTransferEvent>({ type: \"ft_transfer\", ...f }),\n\n\tftMint: (f = {}) => make<FtMintEvent>({ type: \"ft_mint\", ...f }),\n\n\tftBurn: (f = {}) => make<FtBurnEvent>({ type: \"ft_burn\", ...f }),\n\n\tnftTransfer: (f = {}) =>\n\t\tmake<NftTransferEvent>({ type: \"nft_transfer\", ...f }),\n\n\tnftMint: (f = {}) => make<NftMintEvent>({ type: \"nft_mint\", ...f }),\n\n\tnftBurn: (f = {}) => make<NftBurnEvent>({ type: \"nft_burn\", ...f }),\n\n\tcontractCall: (f = {}) =>\n\t\tmake<ContractCallEvent>({ type: \"contract_call\", ...f }),\n\n\tcontractDeploy: (f = {}) =>\n\t\tmake<ContractDeployEvent>({ type: \"contract_deploy\", ...f }),\n\n\tprintEvent: (f = {}) => make<PrintEventEvent>({ type: \"print_event\", ...f }),\n};\n\n/** Extract the event payload type from a trigger. */\nexport type EventOf<T> = T extends TypedEventTrigger<infer E> ? E : never;\n"
5
+ "/**\n * Typed event-trigger helpers. Each helper returns a standard `EventTrigger`\n * plus a phantom `__event` type used for inference at consumer sites. The\n * phantom carries no runtime value.\n */\n\nimport type {\n\tContractCallFilter,\n\tContractDeployFilter,\n\tFtBurnFilter,\n\tFtMintFilter,\n\tFtTransferFilter,\n\tNftBurnFilter,\n\tNftMintFilter,\n\tNftTransferFilter,\n\tPrintEventFilter,\n\tPrintEventPrints,\n\tStxBurnFilter,\n\tStxLockFilter,\n\tStxMintFilter,\n\tStxTransferFilter,\n} from \"../types.ts\";\nimport type { EventTrigger } from \"./types.ts\";\n\n// --- Per-filter event payload shapes ---\n\n/** Metadata shared by every event. */\nexport interface TxMeta {\n\ttxId: string;\n\tsender: string;\n\tblockHeight: number;\n\tblockTime: number;\n}\n\nexport interface StxTransferEvent {\n\tsender: string;\n\trecipient: string;\n\tamount: bigint;\n\tmemo: string | null;\n\ttx: TxMeta;\n}\n\nexport interface StxMintEvent {\n\trecipient: string;\n\tamount: bigint;\n\ttx: TxMeta;\n}\n\nexport interface StxBurnEvent {\n\tsender: string;\n\tamount: bigint;\n\ttx: TxMeta;\n}\n\nexport interface StxLockEvent {\n\tlockedAddress: string;\n\tamount: bigint;\n\tunlockHeight: number;\n\ttx: TxMeta;\n}\n\nexport interface FtTransferEvent {\n\tassetIdentifier: string;\n\tsender: string;\n\trecipient: string;\n\tamount: bigint;\n\ttx: TxMeta;\n}\n\nexport interface FtMintEvent {\n\tassetIdentifier: string;\n\trecipient: string;\n\tamount: bigint;\n\ttx: TxMeta;\n}\n\nexport interface FtBurnEvent {\n\tassetIdentifier: string;\n\tsender: string;\n\tamount: bigint;\n\ttx: TxMeta;\n}\n\nexport interface NftTransferEvent {\n\tassetIdentifier: string;\n\tsender: string;\n\trecipient: string;\n\ttokenId: string;\n\ttx: TxMeta;\n}\n\nexport interface NftMintEvent {\n\tassetIdentifier: string;\n\trecipient: string;\n\ttokenId: string;\n\ttx: TxMeta;\n}\n\nexport interface NftBurnEvent {\n\tassetIdentifier: string;\n\tsender: string;\n\ttokenId: string;\n\ttx: TxMeta;\n}\n\nexport interface ContractCallEvent {\n\tcontractId: string;\n\tfunctionName: string;\n\targs: unknown[];\n\tcaller: string;\n\ttx: TxMeta;\n}\n\nexport interface ContractDeployEvent {\n\tcontractId: string;\n\tdeployer: string;\n\tcontractName: string;\n\ttx: TxMeta;\n}\n\nexport interface PrintEventEvent {\n\tcontractId: string;\n\ttopic: string | null;\n\tvalue: unknown;\n\ttx: TxMeta;\n}\n\n// --- Typed trigger wrapper ---\n\n/**\n * Phantom-typed EventTrigger. Runtime shape is identical to `EventTrigger`;\n * the `__event` field only exists at the type level, letting subscription\n * definitions infer the handler's `event` parameter from the filter.\n */\nexport type TypedEventTrigger<TEvent> = EventTrigger & {\n\treadonly __event?: TEvent;\n};\n\nfunction make<TEvent>(\n\tfilter: EventTrigger[\"filter\"],\n): TypedEventTrigger<TEvent> {\n\treturn { type: \"event\", filter };\n}\n\n// --- `on.*` helpers, 13 total ---\n\nexport interface TriggerHelpers {\n\tstxTransfer: (\n\t\tf?: Omit<StxTransferFilter, \"type\">,\n\t) => TypedEventTrigger<StxTransferEvent>;\n\tstxMint: (f?: Omit<StxMintFilter, \"type\">) => TypedEventTrigger<StxMintEvent>;\n\tstxBurn: (f?: Omit<StxBurnFilter, \"type\">) => TypedEventTrigger<StxBurnEvent>;\n\tstxLock: (f?: Omit<StxLockFilter, \"type\">) => TypedEventTrigger<StxLockEvent>;\n\tftTransfer: (\n\t\tf?: Omit<FtTransferFilter, \"type\">,\n\t) => TypedEventTrigger<FtTransferEvent>;\n\tftMint: (f?: Omit<FtMintFilter, \"type\">) => TypedEventTrigger<FtMintEvent>;\n\tftBurn: (f?: Omit<FtBurnFilter, \"type\">) => TypedEventTrigger<FtBurnEvent>;\n\tnftTransfer: (\n\t\tf?: Omit<NftTransferFilter, \"type\">,\n\t) => TypedEventTrigger<NftTransferEvent>;\n\tnftMint: (f?: Omit<NftMintFilter, \"type\">) => TypedEventTrigger<NftMintEvent>;\n\tnftBurn: (f?: Omit<NftBurnFilter, \"type\">) => TypedEventTrigger<NftBurnEvent>;\n\tcontractCall: (\n\t\tf?: Omit<ContractCallFilter, \"type\">,\n\t) => TypedEventTrigger<ContractCallEvent>;\n\tcontractDeploy: (\n\t\tf?: Omit<ContractDeployFilter, \"type\">,\n\t) => TypedEventTrigger<ContractDeployEvent>;\n\t/** Subscription triggers are not subgraph deploys — prints not required. */\n\tprintEvent: (f?: {\n\t\tcontractId?: string | readonly string[];\n\t\ttopic?: string;\n\t\ttrait?: string;\n\t\tfactory?: { from: string; field: string };\n\t\tprints?: PrintEventPrints;\n\t}) => TypedEventTrigger<PrintEventEvent>;\n}\n\nexport const on: TriggerHelpers = {\n\tstxTransfer: (f = {}) =>\n\t\tmake<StxTransferEvent>({ type: \"stx_transfer\", ...f }),\n\n\tstxMint: (f = {}) => make<StxMintEvent>({ type: \"stx_mint\", ...f }),\n\n\tstxBurn: (f = {}) => make<StxBurnEvent>({ type: \"stx_burn\", ...f }),\n\n\tstxLock: (f = {}) => make<StxLockEvent>({ type: \"stx_lock\", ...f }),\n\n\tftTransfer: (f = {}) => make<FtTransferEvent>({ type: \"ft_transfer\", ...f }),\n\n\tftMint: (f = {}) => make<FtMintEvent>({ type: \"ft_mint\", ...f }),\n\n\tftBurn: (f = {}) => make<FtBurnEvent>({ type: \"ft_burn\", ...f }),\n\n\tnftTransfer: (f = {}) =>\n\t\tmake<NftTransferEvent>({ type: \"nft_transfer\", ...f }),\n\n\tnftMint: (f = {}) => make<NftMintEvent>({ type: \"nft_mint\", ...f }),\n\n\tnftBurn: (f = {}) => make<NftBurnEvent>({ type: \"nft_burn\", ...f }),\n\n\tcontractCall: (f = {}) =>\n\t\tmake<ContractCallEvent>({ type: \"contract_call\", ...f }),\n\n\tcontractDeploy: (f = {}) =>\n\t\tmake<ContractDeployEvent>({ type: \"contract_deploy\", ...f }),\n\n\tprintEvent: (f = {}) =>\n\t\tmake<PrintEventEvent>({\n\t\t\ttype: \"print_event\",\n\t\t\t...f,\n\t\t} as PrintEventFilter),\n};\n\n/** Extract the event payload type from a trigger. */\nexport type EventOf<T> = T extends TypedEventTrigger<infer E> ? E : never;\n"
6
6
  ],
7
- "mappings": ";;;;AAyIA,SAAS,IAAY,CACpB,QAC4B;AAAA,EAC5B,OAAO,EAAE,MAAM,SAAS,OAAO;AAAA;AAiCzB,IAAM,KAAqB;AAAA,EACjC,aAAa,CAAC,IAAI,CAAC,MAClB,KAAuB,EAAE,MAAM,mBAAmB,EAAE,CAAC;AAAA,EAEtD,SAAS,CAAC,IAAI,CAAC,MAAM,KAAmB,EAAE,MAAM,eAAe,EAAE,CAAC;AAAA,EAElE,SAAS,CAAC,IAAI,CAAC,MAAM,KAAmB,EAAE,MAAM,eAAe,EAAE,CAAC;AAAA,EAElE,SAAS,CAAC,IAAI,CAAC,MAAM,KAAmB,EAAE,MAAM,eAAe,EAAE,CAAC;AAAA,EAElE,YAAY,CAAC,IAAI,CAAC,MAAM,KAAsB,EAAE,MAAM,kBAAkB,EAAE,CAAC;AAAA,EAE3E,QAAQ,CAAC,IAAI,CAAC,MAAM,KAAkB,EAAE,MAAM,cAAc,EAAE,CAAC;AAAA,EAE/D,QAAQ,CAAC,IAAI,CAAC,MAAM,KAAkB,EAAE,MAAM,cAAc,EAAE,CAAC;AAAA,EAE/D,aAAa,CAAC,IAAI,CAAC,MAClB,KAAuB,EAAE,MAAM,mBAAmB,EAAE,CAAC;AAAA,EAEtD,SAAS,CAAC,IAAI,CAAC,MAAM,KAAmB,EAAE,MAAM,eAAe,EAAE,CAAC;AAAA,EAElE,SAAS,CAAC,IAAI,CAAC,MAAM,KAAmB,EAAE,MAAM,eAAe,EAAE,CAAC;AAAA,EAElE,cAAc,CAAC,IAAI,CAAC,MACnB,KAAwB,EAAE,MAAM,oBAAoB,EAAE,CAAC;AAAA,EAExD,gBAAgB,CAAC,IAAI,CAAC,MACrB,KAA0B,EAAE,MAAM,sBAAsB,EAAE,CAAC;AAAA,EAE5D,YAAY,CAAC,IAAI,CAAC,MAAM,KAAsB,EAAE,MAAM,kBAAkB,EAAE,CAAC;AAC5E;",
8
- "debugId": "C92F67573451324664756E2164756E21",
7
+ "mappings": ";;;;AA0IA,SAAS,IAAY,CACpB,QAC4B;AAAA,EAC5B,OAAO,EAAE,MAAM,SAAS,OAAO;AAAA;AAsCzB,IAAM,KAAqB;AAAA,EACjC,aAAa,CAAC,IAAI,CAAC,MAClB,KAAuB,EAAE,MAAM,mBAAmB,EAAE,CAAC;AAAA,EAEtD,SAAS,CAAC,IAAI,CAAC,MAAM,KAAmB,EAAE,MAAM,eAAe,EAAE,CAAC;AAAA,EAElE,SAAS,CAAC,IAAI,CAAC,MAAM,KAAmB,EAAE,MAAM,eAAe,EAAE,CAAC;AAAA,EAElE,SAAS,CAAC,IAAI,CAAC,MAAM,KAAmB,EAAE,MAAM,eAAe,EAAE,CAAC;AAAA,EAElE,YAAY,CAAC,IAAI,CAAC,MAAM,KAAsB,EAAE,MAAM,kBAAkB,EAAE,CAAC;AAAA,EAE3E,QAAQ,CAAC,IAAI,CAAC,MAAM,KAAkB,EAAE,MAAM,cAAc,EAAE,CAAC;AAAA,EAE/D,QAAQ,CAAC,IAAI,CAAC,MAAM,KAAkB,EAAE,MAAM,cAAc,EAAE,CAAC;AAAA,EAE/D,aAAa,CAAC,IAAI,CAAC,MAClB,KAAuB,EAAE,MAAM,mBAAmB,EAAE,CAAC;AAAA,EAEtD,SAAS,CAAC,IAAI,CAAC,MAAM,KAAmB,EAAE,MAAM,eAAe,EAAE,CAAC;AAAA,EAElE,SAAS,CAAC,IAAI,CAAC,MAAM,KAAmB,EAAE,MAAM,eAAe,EAAE,CAAC;AAAA,EAElE,cAAc,CAAC,IAAI,CAAC,MACnB,KAAwB,EAAE,MAAM,oBAAoB,EAAE,CAAC;AAAA,EAExD,gBAAgB,CAAC,IAAI,CAAC,MACrB,KAA0B,EAAE,MAAM,sBAAsB,EAAE,CAAC;AAAA,EAE5D,YAAY,CAAC,IAAI,CAAC,MACjB,KAAsB;AAAA,IACrB,MAAM;AAAA,OACH;AAAA,EACJ,CAAqB;AACvB;",
8
+ "debugId": "E8A9BFB4916AE4AF64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -119,7 +119,12 @@ type TraitScope = {
119
119
  *
120
120
  * ```ts
121
121
  * sources: {
122
- * registry: { type: "print_event", contractId: REGISTRY, topic: "pool-created" },
122
+ * registry: {
123
+ * type: "print_event",
124
+ * contractId: REGISTRY,
125
+ * topic: "pool-created",
126
+ * prints: { "pool-created": { pool: "principal" } },
127
+ * },
123
128
  * swaps: {
124
129
  * type: "print_event",
125
130
  * topic: "swap",
@@ -244,6 +249,9 @@ interface ContractCallFilter extends TraitScope, FactoryScope {
244
249
  * decoded function arguments. Dev-provided; serialized into the deployed
245
250
  * definition and used at runtime to decode args by name. Omit to keep
246
251
  * `event.args` as a positional `unknown[]`.
252
+ *
253
+ * Deploy validation requires `abi` whenever `functionName` is set
254
+ * (`normalizeAbi()` from `@secondlayer/stacks/clarity`).
247
255
  */
248
256
  abi?: AbiContract2;
249
257
  }
@@ -274,29 +282,70 @@ type PrintField = ColumnType | {
274
282
  } | {
275
283
  list: PrintField
276
284
  };
277
- interface PrintEventFilter extends TraitScope, FactoryScope {
278
- type: "print_event";
279
- /** One contract id, or a set of them (max 20). Supports `*` wildcards.
280
- * COMPOSES with `trait` — the matcher ANDs them, so the pair means
281
- * "contracts conforming to this trait, narrowed to these ids". (The Index
282
- * read API refuses the combination, but that is its query planner's
283
- * constraint, not a semantic one.) */
284
- contractId?: string | readonly string[];
285
- topic?: string;
286
- /**
287
- * Per-topic field schema. When declared, the handler's `event` is a
288
- * discriminated union keyed by `topic` and `event.data` is typed per topic
289
- * (e.g. `{ "completed-deposit": { amount: "uint", sender: "principal" } }`).
290
- * Nested tuples, lists, and optional fields are expressible — see
291
- * {@link PrintField}.
292
- *
293
- * Declaring `prints` also opts the source into RUNTIME validation: an
294
- * event whose decoded payload does not match is skipped and logged rather
295
- * than written as nulls (and the deploy-time field lint is promoted from
296
- * a warning to an error).
297
- */
298
- prints?: Record<string, Record<string, PrintField>>;
285
+ /**
286
+ * Per-topic field schema for a print_event source. When declared, the handler's
287
+ * `event` is a discriminated union keyed by `topic` and `event.data` is typed
288
+ * per topic. Nested tuples, lists, and optional fields — see {@link PrintField}.
289
+ *
290
+ * Also opts the source into RUNTIME validation: a payload that does not match
291
+ * is skipped and logged rather than written as nulls.
292
+ */
293
+ type PrintEventPrints = Record<string, Record<string, PrintField>>;
294
+ /**
295
+ * One column in a static identity map. Literals only — the bundler never
296
+ * executes user code, so `{ from: "tokenX" }` is valid and `from("tokenX")`
297
+ * is not.
298
+ */
299
+ type MaterializeColumn = {
300
+ from: string
301
+ } | {
302
+ fromTx: "sender" | "txId"
303
+ } | {
304
+ fromBlock: "height" | "hash" | "timestamp"
305
+ };
306
+ /** Static insert from a source event — replaces a 1:1 handler. */
307
+ interface MaterializeSpec {
308
+ table: string;
309
+ /** Keys = schema column names (snake). */
310
+ columns: Record<string, MaterializeColumn>;
299
311
  }
312
+ type PrintEventFields = FactoryScope & {
313
+ type: "print_event"
314
+ topic?: string
315
+ /** Identity map: insert without a handler function. XOR with handlers[name]. */
316
+ materialize?: MaterializeSpec
317
+ };
318
+ /**
319
+ * Pinned to one or more contract ids with no trait — `prints` is required so
320
+ * `event.data` is typed and empty-table handlers cannot typecheck.
321
+ */
322
+ type PinnedPrintEventFilter = PrintEventFields & {
323
+ contractId: string | readonly string[]
324
+ trait?: undefined
325
+ prints: PrintEventPrints
326
+ };
327
+ /**
328
+ * Trait-scoped print source (optionally narrowed by contractId). No single
329
+ * schema — `prints` stays optional.
330
+ */
331
+ type TraitPrintEventFilter = PrintEventFields & {
332
+ contractId?: string | readonly string[]
333
+ trait: string
334
+ prints?: PrintEventPrints
335
+ };
336
+ /**
337
+ * Unpinned / factory-only print source (no contractId). `prints` optional.
338
+ */
339
+ type UnpinnedPrintEventFilter = PrintEventFields & {
340
+ contractId?: undefined
341
+ trait?: string
342
+ prints?: PrintEventPrints
343
+ };
344
+ /**
345
+ * Print-event filter. Pinned sources (contractId set, no trait) require
346
+ * `prints`; trait and unpinned sources may omit it.
347
+ */
348
+ type PrintEventFilter = PinnedPrintEventFilter | TraitPrintEventFilter | UnpinnedPrintEventFilter;
300
349
  /** All subgraph filter types — discriminated on `type` */
301
350
  type SubgraphFilter = StxTransferFilter | StxMintFilter | StxBurnFilter | StxLockFilter | FtTransferFilter | FtMintFilter | FtBurnFilter | NftTransferFilter | NftMintFilter | NftBurnFilter | ContractCallFilter | ContractDeployFilter | PrintEventFilter;
302
351
  /** Transaction metadata available in handlers */
@@ -386,4 +435,4 @@ interface SubgraphDefinition {
386
435
  /** Handler functions — keys must match source names (or "*" for catch-all) */
387
436
  handlers: Record<string, SubgraphHandler>;
388
437
  }
389
- export { TxMeta, SubgraphTable, SubgraphSchema, SubgraphRelation, SubgraphHandler, SubgraphFilter, SubgraphDefinition, SubgraphContext, SubgraphColumn, StxTransferFilter, StxMintFilter, StxLockFilter, StxBurnFilter, RowValue, PrintField, PrintEventFilter, NftTransferFilter, NftMintFilter, NftBurnFilter, FtTransferFilter, FtMintFilter, FtBurnFilter, FactoryScope, ContractDeployFilter, ContractCallFilter, ContractCallEvent, ComputedValue, ColumnType };
438
+ export { UnpinnedPrintEventFilter, TxMeta, TraitPrintEventFilter, SubgraphTable, SubgraphSchema, SubgraphRelation, SubgraphHandler, SubgraphFilter, SubgraphDefinition, SubgraphContext, SubgraphColumn, StxTransferFilter, StxMintFilter, StxLockFilter, StxBurnFilter, RowValue, PrintField, PrintEventPrints, PrintEventFilter, PinnedPrintEventFilter, NftTransferFilter, NftMintFilter, NftBurnFilter, MaterializeSpec, MaterializeColumn, FtTransferFilter, FtMintFilter, FtBurnFilter, FactoryScope, ContractDeployFilter, ContractCallFilter, ContractCallEvent, ComputedValue, ColumnType };
@@ -119,7 +119,12 @@ type TraitScope = {
119
119
  *
120
120
  * ```ts
121
121
  * sources: {
122
- * registry: { type: "print_event", contractId: REGISTRY, topic: "pool-created" },
122
+ * registry: {
123
+ * type: "print_event",
124
+ * contractId: REGISTRY,
125
+ * topic: "pool-created",
126
+ * prints: { "pool-created": { pool: "principal" } },
127
+ * },
123
128
  * swaps: {
124
129
  * type: "print_event",
125
130
  * topic: "swap",
@@ -203,6 +208,9 @@ interface ContractCallFilter extends TraitScope, FactoryScope {
203
208
  * decoded function arguments. Dev-provided; serialized into the deployed
204
209
  * definition and used at runtime to decode args by name. Omit to keep
205
210
  * `event.args` as a positional `unknown[]`.
211
+ *
212
+ * Deploy validation requires `abi` whenever `functionName` is set
213
+ * (`normalizeAbi()` from `@secondlayer/stacks/clarity`).
206
214
  */
207
215
  abi?: AbiContract2;
208
216
  }
@@ -233,29 +241,70 @@ type PrintField = ColumnType | {
233
241
  } | {
234
242
  list: PrintField
235
243
  };
236
- interface PrintEventFilter extends TraitScope, FactoryScope {
237
- type: "print_event";
238
- /** One contract id, or a set of them (max 20). Supports `*` wildcards.
239
- * COMPOSES with `trait` — the matcher ANDs them, so the pair means
240
- * "contracts conforming to this trait, narrowed to these ids". (The Index
241
- * read API refuses the combination, but that is its query planner's
242
- * constraint, not a semantic one.) */
243
- contractId?: string | readonly string[];
244
- topic?: string;
245
- /**
246
- * Per-topic field schema. When declared, the handler's `event` is a
247
- * discriminated union keyed by `topic` and `event.data` is typed per topic
248
- * (e.g. `{ "completed-deposit": { amount: "uint", sender: "principal" } }`).
249
- * Nested tuples, lists, and optional fields are expressible — see
250
- * {@link PrintField}.
251
- *
252
- * Declaring `prints` also opts the source into RUNTIME validation: an
253
- * event whose decoded payload does not match is skipped and logged rather
254
- * than written as nulls (and the deploy-time field lint is promoted from
255
- * a warning to an error).
256
- */
257
- prints?: Record<string, Record<string, PrintField>>;
244
+ /**
245
+ * Per-topic field schema for a print_event source. When declared, the handler's
246
+ * `event` is a discriminated union keyed by `topic` and `event.data` is typed
247
+ * per topic. Nested tuples, lists, and optional fields — see {@link PrintField}.
248
+ *
249
+ * Also opts the source into RUNTIME validation: a payload that does not match
250
+ * is skipped and logged rather than written as nulls.
251
+ */
252
+ type PrintEventPrints = Record<string, Record<string, PrintField>>;
253
+ /**
254
+ * One column in a static identity map. Literals only — the bundler never
255
+ * executes user code, so `{ from: "tokenX" }` is valid and `from("tokenX")`
256
+ * is not.
257
+ */
258
+ type MaterializeColumn = {
259
+ from: string
260
+ } | {
261
+ fromTx: "sender" | "txId"
262
+ } | {
263
+ fromBlock: "height" | "hash" | "timestamp"
264
+ };
265
+ /** Static insert from a source event — replaces a 1:1 handler. */
266
+ interface MaterializeSpec {
267
+ table: string;
268
+ /** Keys = schema column names (snake). */
269
+ columns: Record<string, MaterializeColumn>;
258
270
  }
271
+ type PrintEventFields = FactoryScope & {
272
+ type: "print_event"
273
+ topic?: string
274
+ /** Identity map: insert without a handler function. XOR with handlers[name]. */
275
+ materialize?: MaterializeSpec
276
+ };
277
+ /**
278
+ * Pinned to one or more contract ids with no trait — `prints` is required so
279
+ * `event.data` is typed and empty-table handlers cannot typecheck.
280
+ */
281
+ type PinnedPrintEventFilter = PrintEventFields & {
282
+ contractId: string | readonly string[]
283
+ trait?: undefined
284
+ prints: PrintEventPrints
285
+ };
286
+ /**
287
+ * Trait-scoped print source (optionally narrowed by contractId). No single
288
+ * schema — `prints` stays optional.
289
+ */
290
+ type TraitPrintEventFilter = PrintEventFields & {
291
+ contractId?: string | readonly string[]
292
+ trait: string
293
+ prints?: PrintEventPrints
294
+ };
295
+ /**
296
+ * Unpinned / factory-only print source (no contractId). `prints` optional.
297
+ */
298
+ type UnpinnedPrintEventFilter = PrintEventFields & {
299
+ contractId?: undefined
300
+ trait?: string
301
+ prints?: PrintEventPrints
302
+ };
303
+ /**
304
+ * Print-event filter. Pinned sources (contractId set, no trait) require
305
+ * `prints`; trait and unpinned sources may omit it.
306
+ */
307
+ type PrintEventFilter = PinnedPrintEventFilter | TraitPrintEventFilter | UnpinnedPrintEventFilter;
259
308
  /** All subgraph filter types — discriminated on `type` */
260
309
  type SubgraphFilter = StxTransferFilter | StxMintFilter | StxBurnFilter | StxLockFilter | FtTransferFilter | FtMintFilter | FtBurnFilter | NftTransferFilter | NftMintFilter | NftBurnFilter | ContractCallFilter | ContractDeployFilter | PrintEventFilter;
261
310
  /** Transaction metadata available in handlers */
@@ -98,6 +98,15 @@ var amountRange = {
98
98
  minAmount: z.bigint().optional(),
99
99
  maxAmount: z.bigint().optional()
100
100
  };
101
+ var MaterializeColumnSchema = z.union([
102
+ z.object({ from: z.string().min(1) }).strict(),
103
+ z.object({ fromTx: z.enum(["sender", "txId"]) }).strict(),
104
+ z.object({ fromBlock: z.enum(["height", "hash", "timestamp"]) }).strict()
105
+ ]);
106
+ var MaterializeSpecSchema = z.object({
107
+ table: z.string().min(1),
108
+ columns: z.record(z.string(), MaterializeColumnSchema)
109
+ }).strict();
101
110
  var SubgraphFilterUnion = z.discriminatedUnion("type", [
102
111
  z.object({
103
112
  type: z.literal("stx_transfer"),
@@ -180,11 +189,114 @@ var SubgraphFilterUnion = z.discriminatedUnion("type", [
180
189
  contractId: contractIdField.optional(),
181
190
  topic: z.string().optional(),
182
191
  prints: z.record(z.string(), z.record(z.string(), PrintFieldSchema)).optional(),
192
+ materialize: MaterializeSpecSchema.optional(),
183
193
  ...traitScope,
184
194
  ...factoryScope
185
195
  }).strict()
186
196
  ]);
187
197
  var SubgraphFilterSchema = SubgraphFilterUnion;
198
+ function hasPinnedContractId(contractId) {
199
+ if (typeof contractId === "string")
200
+ return contractId.length > 0;
201
+ if (Array.isArray(contractId))
202
+ return contractId.length > 0;
203
+ return false;
204
+ }
205
+ function printMaterializeFromKeys(prints) {
206
+ const keys = new Set(["topic"]);
207
+ if (prints) {
208
+ for (const fields of Object.values(prints)) {
209
+ for (const k of Object.keys(fields))
210
+ keys.add(k);
211
+ }
212
+ }
213
+ return [...keys].sort();
214
+ }
215
+ function refineMaterialize(name, filter, schema, ctx) {
216
+ if (!("materialize" in filter) || filter.materialize === undefined)
217
+ return;
218
+ const spec = filter.materialize;
219
+ const table = schema[spec.table];
220
+ if (!table) {
221
+ ctx.addIssue({
222
+ code: "custom",
223
+ path: ["sources", name, "materialize", "table"],
224
+ message: `materialize table "${spec.table}" is not in schema`
225
+ });
226
+ return;
227
+ }
228
+ for (const col of Object.keys(spec.columns)) {
229
+ if (!(col in table.columns)) {
230
+ ctx.addIssue({
231
+ code: "custom",
232
+ path: ["sources", name, "materialize", "columns", col],
233
+ message: `materialize column "${col}" is not on schema table "${spec.table}"`
234
+ });
235
+ }
236
+ }
237
+ if (filter.type === "print_event") {
238
+ const known = printMaterializeFromKeys(filter.prints);
239
+ const knownSet = new Set(known);
240
+ for (const [col, mapping] of Object.entries(spec.columns)) {
241
+ if (!("from" in mapping))
242
+ continue;
243
+ if (!knownSet.has(mapping.from)) {
244
+ ctx.addIssue({
245
+ code: "custom",
246
+ path: ["sources", name, "materialize", "columns", col, "from"],
247
+ message: `materialize from "${mapping.from}" is not a known print field — known: ${known.join(", ")}`
248
+ });
249
+ }
250
+ }
251
+ }
252
+ }
253
+ function refineDeployGates(def, ctx) {
254
+ for (const [name, filter] of Object.entries(def.sources)) {
255
+ if (filter.type === "print_event") {
256
+ const pinned = hasPinnedContractId(filter.contractId) && !filter.trait;
257
+ if (pinned) {
258
+ const prints = filter.prints;
259
+ if (prints === undefined || typeof prints !== "object" || Object.keys(prints).length === 0) {
260
+ ctx.addIssue({
261
+ code: "custom",
262
+ path: ["sources", name, "prints"],
263
+ message: `print_event source "${name}" has no prints map — field names would be untyped and empty tables would typecheck. Declare prints from index_print_schema / create --from-contract.`
264
+ });
265
+ }
266
+ }
267
+ }
268
+ if (filter.type === "contract_call" && filter.functionName !== undefined) {
269
+ if (filter.abi === undefined) {
270
+ ctx.addIssue({
271
+ code: "custom",
272
+ path: ["sources", name, "abi"],
273
+ message: `contract_call source "${name}" has functionName but no abi — pass a canonical ABI (normalizeAbi() from @secondlayer/stacks/clarity).`
274
+ });
275
+ }
276
+ }
277
+ refineMaterialize(name, filter, def.schema, ctx);
278
+ }
279
+ const hasCatchAll = def.handlers["*"] != null;
280
+ for (const [name, filter] of Object.entries(def.sources)) {
281
+ const hasMaterialize = "materialize" in filter && filter.materialize !== undefined;
282
+ const hasHandler = def.handlers[name] != null;
283
+ if (hasMaterialize && hasHandler) {
284
+ ctx.addIssue({
285
+ code: "custom",
286
+ path: ["handlers", name],
287
+ message: `source "${name}" has both materialize and a handler — use one or the other`
288
+ });
289
+ continue;
290
+ }
291
+ if (!hasHandler && !hasMaterialize && !hasCatchAll) {
292
+ ctx.addIssue({
293
+ code: "custom",
294
+ path: ["handlers", name],
295
+ message: `source "${name}" has no handler (and no "*") — events will be skipped and the table stay empty.`
296
+ });
297
+ }
298
+ }
299
+ }
188
300
  var SubgraphDefinitionSchema = z.object({
189
301
  name: SubgraphNameSchema,
190
302
  version: z.string().optional(),
@@ -194,6 +306,8 @@ var SubgraphDefinitionSchema = z.object({
194
306
  sources: z.record(z.string(), SubgraphFilterSchema).refine((s) => Object.keys(s).length > 0, "Must have at least one source"),
195
307
  schema: SubgraphSchemaSchema,
196
308
  handlers: z.record(z.string(), z.any())
309
+ }).superRefine((def, ctx) => {
310
+ refineDeployGates(def, ctx);
197
311
  });
198
312
  function validateSubgraphDefinition(def) {
199
313
  return SubgraphDefinitionSchema.parse(def);
@@ -231,5 +345,5 @@ export {
231
345
  AbiContractSchema
232
346
  };
233
347
 
234
- //# debugId=BAF10EBE9E88C37664756E2164756E21
348
+ //# debugId=F1208BA1BE21EB0E64756E2164756E21
235
349
  //# sourceMappingURL=validate.js.map