@secondlayer/subgraphs 3.20.0 → 3.21.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/dist/src/index.d.ts +68 -8
- package/dist/src/index.js +141 -5
- package/dist/src/index.js.map +6 -5
- package/dist/src/runtime/block-processor.d.ts +32 -5
- package/dist/src/runtime/block-processor.js +100 -3
- package/dist/src/runtime/block-processor.js.map +5 -4
- package/dist/src/runtime/catchup.d.ts +32 -5
- package/dist/src/runtime/catchup.js +100 -3
- package/dist/src/runtime/catchup.js.map +5 -4
- package/dist/src/runtime/processor.js +100 -3
- package/dist/src/runtime/processor.js.map +5 -4
- package/dist/src/runtime/reindex.d.ts +32 -5
- package/dist/src/runtime/reindex.js +100 -3
- package/dist/src/runtime/reindex.js.map +5 -4
- package/dist/src/runtime/reorg.d.ts +32 -5
- package/dist/src/runtime/reorg.js +100 -3
- package/dist/src/runtime/reorg.js.map +5 -4
- package/dist/src/runtime/runner.d.ts +36 -5
- package/dist/src/runtime/runner.js +100 -3
- package/dist/src/runtime/runner.js.map +5 -4
- package/dist/src/runtime/source-matcher.d.ts +32 -5
- package/dist/src/schema/index.d.ts +32 -5
- package/dist/src/schema/index.js +41 -3
- package/dist/src/schema/index.js.map +3 -3
- package/dist/src/service.js +100 -3
- package/dist/src/service.js.map +5 -4
- package/dist/src/triggers/index.d.ts +32 -5
- package/dist/src/types.d.ts +33 -6
- package/dist/src/validate.d.ts +51 -6
- package/dist/src/validate.js +44 -4
- package/dist/src/validate.js.map +3 -3
- package/package.json +3 -5
|
@@ -124,18 +124,45 @@ interface ContractDeployFilter {
|
|
|
124
124
|
deployer?: string;
|
|
125
125
|
contractName?: string;
|
|
126
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* One declared field of a print payload.
|
|
129
|
+
*
|
|
130
|
+
* A bare {@link ColumnType} is a required scalar (`amount: "uint"`). The
|
|
131
|
+
* composite forms exist because real print payloads are not flat: BNS-V2
|
|
132
|
+
* emits `name` as a NESTED tuple, and a `prints` vocabulary that could only
|
|
133
|
+
* say `"jsonb"` is why a handler reading flat `data.name` type-checked,
|
|
134
|
+
* deployed, and then decoded to null on every event.
|
|
135
|
+
*
|
|
136
|
+
* - `{ type: F, optional: true }` — the KEY becomes optional on `event.data`
|
|
137
|
+
* (mirrors `always_present: false` in the inferred schema).
|
|
138
|
+
* - `{ tuple: { … } }` — a nested tuple, typed all the way down.
|
|
139
|
+
* - `{ list: F }` — a list of `F`.
|
|
140
|
+
*/
|
|
141
|
+
type PrintField = ColumnType | {
|
|
142
|
+
type: PrintField
|
|
143
|
+
optional: true
|
|
144
|
+
} | {
|
|
145
|
+
tuple: Record<string, PrintField>
|
|
146
|
+
} | {
|
|
147
|
+
list: PrintField
|
|
148
|
+
};
|
|
127
149
|
interface PrintEventFilter extends TraitScope {
|
|
128
150
|
type: "print_event";
|
|
129
151
|
contractId?: string;
|
|
130
152
|
topic?: string;
|
|
131
153
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
154
|
+
* Per-topic field schema. When declared, the handler's `event` is a
|
|
155
|
+
* discriminated union keyed by `topic` and `event.data` is typed per topic
|
|
134
156
|
* (e.g. `{ "completed-deposit": { amount: "uint", sender: "principal" } }`).
|
|
135
|
-
*
|
|
136
|
-
*
|
|
157
|
+
* Nested tuples, lists, and optional fields are expressible — see
|
|
158
|
+
* {@link PrintField}.
|
|
159
|
+
*
|
|
160
|
+
* Declaring `prints` also opts the source into RUNTIME validation: an
|
|
161
|
+
* event whose decoded payload does not match is skipped and logged rather
|
|
162
|
+
* than written as nulls (and the deploy-time field lint is promoted from
|
|
163
|
+
* a warning to an error).
|
|
137
164
|
*/
|
|
138
|
-
prints?: Record<string, Record<string,
|
|
165
|
+
prints?: Record<string, Record<string, PrintField>>;
|
|
139
166
|
}
|
|
140
167
|
/** All subgraph filter types — discriminated on `type` */
|
|
141
168
|
type SubgraphFilter = StxTransferFilter | StxMintFilter | StxBurnFilter | StxLockFilter | FtTransferFilter | FtMintFilter | FtBurnFilter | NftTransferFilter | NftMintFilter | NftBurnFilter | ContractCallFilter | ContractDeployFilter | PrintEventFilter;
|
|
@@ -758,6 +758,87 @@ import {
|
|
|
758
758
|
deserializeCV as deserializeCV2,
|
|
759
759
|
toCamelCase
|
|
760
760
|
} from "@secondlayer/stacks/clarity";
|
|
761
|
+
|
|
762
|
+
// src/runtime/print-validate.ts
|
|
763
|
+
var COLUMN_TYPES = new Set([
|
|
764
|
+
"text",
|
|
765
|
+
"uint",
|
|
766
|
+
"int",
|
|
767
|
+
"principal",
|
|
768
|
+
"boolean",
|
|
769
|
+
"timestamp",
|
|
770
|
+
"jsonb"
|
|
771
|
+
]);
|
|
772
|
+
function isColumnType(field) {
|
|
773
|
+
return typeof field === "string" && COLUMN_TYPES.has(field);
|
|
774
|
+
}
|
|
775
|
+
function matchesField(field, value) {
|
|
776
|
+
if (isColumnType(field)) {
|
|
777
|
+
switch (field) {
|
|
778
|
+
case "uint":
|
|
779
|
+
case "int":
|
|
780
|
+
return typeof value === "bigint" || typeof value === "number";
|
|
781
|
+
case "boolean":
|
|
782
|
+
return typeof value === "boolean";
|
|
783
|
+
case "text":
|
|
784
|
+
case "principal":
|
|
785
|
+
case "timestamp":
|
|
786
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "bigint";
|
|
787
|
+
case "jsonb":
|
|
788
|
+
return value !== undefined;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
if (typeof field !== "object" || field === null)
|
|
792
|
+
return false;
|
|
793
|
+
if ("optional" in field) {
|
|
794
|
+
return value === undefined || value === null || matchesField(field.type, value);
|
|
795
|
+
}
|
|
796
|
+
if ("tuple" in field) {
|
|
797
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
return matchesTopic(field.tuple, value).ok;
|
|
801
|
+
}
|
|
802
|
+
if ("list" in field) {
|
|
803
|
+
return Array.isArray(value) && value.every((item) => matchesField(field.list, item));
|
|
804
|
+
}
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
function matchesTopic(declared, data) {
|
|
808
|
+
for (const [name, field] of Object.entries(declared)) {
|
|
809
|
+
const present = name in data;
|
|
810
|
+
const optional = typeof field === "object" && field !== null && "optional" in field;
|
|
811
|
+
if (!present) {
|
|
812
|
+
if (optional)
|
|
813
|
+
continue;
|
|
814
|
+
return { ok: false, reason: `missing declared field "${name}"` };
|
|
815
|
+
}
|
|
816
|
+
if (!matchesField(field, data[name])) {
|
|
817
|
+
return {
|
|
818
|
+
ok: false,
|
|
819
|
+
reason: `field "${name}" does not match its declared type (got ${describe(data[name])})`
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return { ok: true };
|
|
824
|
+
}
|
|
825
|
+
function describe(value) {
|
|
826
|
+
if (value === null)
|
|
827
|
+
return "null";
|
|
828
|
+
if (Array.isArray(value))
|
|
829
|
+
return "array";
|
|
830
|
+
return typeof value;
|
|
831
|
+
}
|
|
832
|
+
function validatePrintPayload(prints, topic, data) {
|
|
833
|
+
if (!prints)
|
|
834
|
+
return { ok: true };
|
|
835
|
+
const declared = prints[topic];
|
|
836
|
+
if (!declared)
|
|
837
|
+
return { ok: true };
|
|
838
|
+
return matchesTopic(declared, data);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// src/runtime/runner.ts
|
|
761
842
|
var DEFAULT_ERROR_THRESHOLD = 50;
|
|
762
843
|
function camelCase(str) {
|
|
763
844
|
return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
@@ -1002,6 +1083,7 @@ function buildEventPayload(filter, tx, event) {
|
|
|
1002
1083
|
async function runHandlers(subgraph, matched, ctx, opts) {
|
|
1003
1084
|
let processed = 0;
|
|
1004
1085
|
let errors = 0;
|
|
1086
|
+
let skipped = 0;
|
|
1005
1087
|
const threshold = opts?.errorThreshold ?? DEFAULT_ERROR_THRESHOLD;
|
|
1006
1088
|
const filterLookup = new Map;
|
|
1007
1089
|
if (!Array.isArray(subgraph.sources)) {
|
|
@@ -1026,7 +1108,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1026
1108
|
errors,
|
|
1027
1109
|
threshold
|
|
1028
1110
|
});
|
|
1029
|
-
return { processed, errors };
|
|
1111
|
+
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
1030
1112
|
}
|
|
1031
1113
|
const handler = subgraph.handlers[sourceName] ?? subgraph.handlers["*"] ?? null;
|
|
1032
1114
|
if (!handler) {
|
|
@@ -1082,6 +1164,21 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1082
1164
|
if (event !== null && filter?.type === "print_event" && filter.topic && payload.topic !== filter.topic) {
|
|
1083
1165
|
continue;
|
|
1084
1166
|
}
|
|
1167
|
+
if (event !== null && filter?.type === "print_event" && filter.prints) {
|
|
1168
|
+
const printPayload = payload;
|
|
1169
|
+
const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
|
|
1170
|
+
if (!verdict.ok) {
|
|
1171
|
+
skipped++;
|
|
1172
|
+
logger2.warn("Print payload does not match declared prints schema", {
|
|
1173
|
+
subgraph: subgraph.name,
|
|
1174
|
+
sourceName,
|
|
1175
|
+
topic: printPayload.topic,
|
|
1176
|
+
txId: tx.tx_id,
|
|
1177
|
+
reason: verdict.reason
|
|
1178
|
+
});
|
|
1179
|
+
continue;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1085
1182
|
await handler(payload, ctx);
|
|
1086
1183
|
processed++;
|
|
1087
1184
|
} catch (err) {
|
|
@@ -1096,7 +1193,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1096
1193
|
});
|
|
1097
1194
|
}
|
|
1098
1195
|
}
|
|
1099
|
-
return { processed, errors };
|
|
1196
|
+
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
1100
1197
|
}
|
|
1101
1198
|
|
|
1102
1199
|
// src/runtime/source-matcher.ts
|
|
@@ -2858,5 +2955,5 @@ export {
|
|
|
2858
2955
|
backfillSubgraph
|
|
2859
2956
|
};
|
|
2860
2957
|
|
|
2861
|
-
//# debugId=
|
|
2958
|
+
//# debugId=829E2A0E177E90D964756E2164756E21
|
|
2862
2959
|
//# sourceMappingURL=reindex.js.map
|