@secondlayer/subgraphs 3.19.10 → 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 +107 -10
- package/dist/src/index.js +245 -16
- package/dist/src/index.js.map +7 -6
- 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 +58 -5
- package/dist/src/schema/index.js +145 -14
- package/dist/src/schema/index.js.map +4 -4
- 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 +2 -2
package/dist/src/index.d.ts
CHANGED
|
@@ -165,18 +165,45 @@ interface ContractDeployFilter {
|
|
|
165
165
|
deployer?: string;
|
|
166
166
|
contractName?: string;
|
|
167
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* One declared field of a print payload.
|
|
170
|
+
*
|
|
171
|
+
* A bare {@link ColumnType} is a required scalar (`amount: "uint"`). The
|
|
172
|
+
* composite forms exist because real print payloads are not flat: BNS-V2
|
|
173
|
+
* emits `name` as a NESTED tuple, and a `prints` vocabulary that could only
|
|
174
|
+
* say `"jsonb"` is why a handler reading flat `data.name` type-checked,
|
|
175
|
+
* deployed, and then decoded to null on every event.
|
|
176
|
+
*
|
|
177
|
+
* - `{ type: F, optional: true }` — the KEY becomes optional on `event.data`
|
|
178
|
+
* (mirrors `always_present: false` in the inferred schema).
|
|
179
|
+
* - `{ tuple: { … } }` — a nested tuple, typed all the way down.
|
|
180
|
+
* - `{ list: F }` — a list of `F`.
|
|
181
|
+
*/
|
|
182
|
+
type PrintField = ColumnType | {
|
|
183
|
+
type: PrintField
|
|
184
|
+
optional: true
|
|
185
|
+
} | {
|
|
186
|
+
tuple: Record<string, PrintField>
|
|
187
|
+
} | {
|
|
188
|
+
list: PrintField
|
|
189
|
+
};
|
|
168
190
|
interface PrintEventFilter extends TraitScope {
|
|
169
191
|
type: "print_event";
|
|
170
192
|
contractId?: string;
|
|
171
193
|
topic?: string;
|
|
172
194
|
/**
|
|
173
|
-
*
|
|
174
|
-
*
|
|
195
|
+
* Per-topic field schema. When declared, the handler's `event` is a
|
|
196
|
+
* discriminated union keyed by `topic` and `event.data` is typed per topic
|
|
175
197
|
* (e.g. `{ "completed-deposit": { amount: "uint", sender: "principal" } }`).
|
|
176
|
-
*
|
|
177
|
-
*
|
|
198
|
+
* Nested tuples, lists, and optional fields are expressible — see
|
|
199
|
+
* {@link PrintField}.
|
|
200
|
+
*
|
|
201
|
+
* Declaring `prints` also opts the source into RUNTIME validation: an
|
|
202
|
+
* event whose decoded payload does not match is skipped and logged rather
|
|
203
|
+
* than written as nulls (and the deploy-time field lint is promoted from
|
|
204
|
+
* a warning to an error).
|
|
178
205
|
*/
|
|
179
|
-
prints?: Record<string, Record<string,
|
|
206
|
+
prints?: Record<string, Record<string, PrintField>>;
|
|
180
207
|
}
|
|
181
208
|
/** All subgraph filter types — discriminated on `type` */
|
|
182
209
|
type SubgraphFilter = StxTransferFilter | StxMintFilter | StxBurnFilter | StxLockFilter | FtTransferFilter | FtMintFilter | FtBurnFilter | NftTransferFilter | NftMintFilter | NftBurnFilter | ContractCallFilter | ContractDeployFilter | PrintEventFilter;
|
|
@@ -411,7 +438,16 @@ type AggregateResult<
|
|
|
411
438
|
max: Record<ColsOf<A, "max">, string | null>
|
|
412
439
|
} : unknown);
|
|
413
440
|
interface SubgraphTableClient<TRow> {
|
|
414
|
-
|
|
441
|
+
/**
|
|
442
|
+
* List rows. `fields` narrows BOTH the wire payload and the return type:
|
|
443
|
+
* the server projects the SELECT to exactly those columns, so an
|
|
444
|
+
* unrequested field is physically absent — reading it is a compile error,
|
|
445
|
+
* not `undefined`. The `const` type parameter keeps the literal field
|
|
446
|
+
* names without `as const` at the call site (same pattern as `aggregate`).
|
|
447
|
+
*/
|
|
448
|
+
findMany<const F extends readonly (keyof TRow & string)[] = readonly (keyof TRow & string)[]>(options?: Omit<FindManyOptions<TRow>, "fields"> & {
|
|
449
|
+
fields?: F
|
|
450
|
+
}): Promise<Pick<TRow, F[number]>[]>;
|
|
415
451
|
count(where?: WhereInput<TRow> & SystemWhereAliases): Promise<number>;
|
|
416
452
|
/**
|
|
417
453
|
* Scalar aggregates over the filtered set. SUM/MIN/MAX accept numeric columns
|
|
@@ -595,13 +631,34 @@ type ContractCallPayload<F> = F extends {
|
|
|
595
631
|
* per topic; otherwise it falls back to the untyped {@link PrintEventPayload}.
|
|
596
632
|
*/
|
|
597
633
|
type PrintEventFor<F> = F extends {
|
|
598
|
-
prints: infer P extends Record<string, Record<string,
|
|
634
|
+
prints: infer P extends Record<string, Record<string, PrintField>>
|
|
599
635
|
} ? { [K in keyof P] : {
|
|
600
636
|
contractId: string
|
|
601
637
|
topic: K
|
|
602
|
-
data:
|
|
638
|
+
data: PrintDataOf<P[K]>
|
|
603
639
|
tx: TxMeta
|
|
604
640
|
} }[keyof P] : PrintEventPayload;
|
|
641
|
+
/** TS type of one declared print field (nested tuples and lists included). */
|
|
642
|
+
type PrintFieldToTS<F> = F extends ColumnType ? ColumnToTS<F> : F extends {
|
|
643
|
+
type: infer Inner
|
|
644
|
+
optional: true
|
|
645
|
+
} ? PrintFieldToTS<Inner> : F extends {
|
|
646
|
+
tuple: infer T extends Record<string, PrintField>
|
|
647
|
+
} ? PrintDataOf<T> : F extends {
|
|
648
|
+
list: infer Item
|
|
649
|
+
} ? PrintFieldToTS<Item>[] : unknown;
|
|
650
|
+
type OptionalFieldKeys<T> = { [K in keyof T] : T[K] extends {
|
|
651
|
+
optional: true
|
|
652
|
+
} ? K : never }[keyof T];
|
|
653
|
+
/** Collapse an intersection into one object literal, so hovering `event.data`
|
|
654
|
+
* shows the fields rather than `A & B`. */
|
|
655
|
+
type Flatten<T> = { [K in keyof T] : T[K] } & {};
|
|
656
|
+
/**
|
|
657
|
+
* `event.data` for one topic: declared fields typed, with
|
|
658
|
+
* `{ optional: true }` fields made optional KEYS (a field observed on only
|
|
659
|
+
* some events must not be typed as always present).
|
|
660
|
+
*/
|
|
661
|
+
type PrintDataOf<T extends Record<string, PrintField>> = Flatten<{ [K in Exclude<keyof T, OptionalFieldKeys<T>>] : PrintFieldToTS<T[K]> } & { [K in OptionalFieldKeys<T>]? : PrintFieldToTS<T[K]> }>;
|
|
605
662
|
/** The event payload a handler receives for a given source filter. */
|
|
606
663
|
type EventForFilter<F extends SubgraphFilter> = F extends {
|
|
607
664
|
type: "print_event"
|
|
@@ -680,6 +737,18 @@ declare function defineSubgraph<
|
|
|
680
737
|
const Sources extends Record<string, SubgraphFilter>,
|
|
681
738
|
const S extends SubgraphSchema
|
|
682
739
|
>(def: TypedSubgraphDefinition<Sources, S>): TypedSubgraphDefinition<Sources, S>;
|
|
740
|
+
type PrintValidationResult = {
|
|
741
|
+
ok: true
|
|
742
|
+
} | {
|
|
743
|
+
ok: false
|
|
744
|
+
reason: string
|
|
745
|
+
};
|
|
746
|
+
/**
|
|
747
|
+
* Validate one decoded print event against a source's `prints` declaration.
|
|
748
|
+
* Returns `{ ok: true }` when no declaration covers this topic (undeclared
|
|
749
|
+
* topics are not the developer's stated contract, so they pass through).
|
|
750
|
+
*/
|
|
751
|
+
declare function validatePrintPayload(prints: Record<string, Record<string, PrintField>> | undefined, topic: string, data: Record<string, unknown>): PrintValidationResult;
|
|
683
752
|
/**
|
|
684
753
|
* Empirical print-payload schema inference. Callers (the index print-schema
|
|
685
754
|
* endpoint) extract topic + raw Clarity hex per row; this module deserializes
|
|
@@ -793,19 +862,41 @@ interface TableDiff {
|
|
|
793
862
|
removedTables: string[];
|
|
794
863
|
/** Per-table column diffs (only for tables present in both) */
|
|
795
864
|
tables: Record<string, ColumnDiff>;
|
|
865
|
+
/** Per-table uniqueKeys / composite-index diffs (tables present in both) */
|
|
866
|
+
constraints: Record<string, ConstraintDiff>;
|
|
796
867
|
}
|
|
797
868
|
interface ColumnDiff {
|
|
798
869
|
added: string[];
|
|
799
870
|
removed: string[];
|
|
871
|
+
/** Data-shape changes (type/nullable/default) — breaking. */
|
|
800
872
|
changed: string[];
|
|
873
|
+
/** Only the `indexed`/`search` flags differ — index DDL, never a rebuild. */
|
|
874
|
+
indexChanged: string[];
|
|
875
|
+
}
|
|
876
|
+
interface ConstraintDiff {
|
|
877
|
+
/** New uniqueKeys entries → `ALTER TABLE … ADD CONSTRAINT` (additive). */
|
|
878
|
+
addedUniqueKeys: string[][];
|
|
879
|
+
/** Removed uniqueKeys entries — breaking: handler upserts may target them. */
|
|
880
|
+
removedUniqueKeys: string[][];
|
|
881
|
+
/** New composite `indexes` entries → CREATE INDEX (additive). */
|
|
882
|
+
addedIndexes: string[][];
|
|
883
|
+
/** Removed composite `indexes` entries → DROP INDEX (additive). */
|
|
884
|
+
removedIndexes: string[][];
|
|
801
885
|
}
|
|
802
886
|
/**
|
|
803
887
|
* Compare two multi-table subgraph schemas and return differences.
|
|
888
|
+
*
|
|
889
|
+
* Index-only changes (`indexed`/`search` flags, composite `indexes`) are
|
|
890
|
+
* reported separately from data-shape changes: an index never requires a
|
|
891
|
+
* rebuild, so it must never land in `changed`/breaking — flipping
|
|
892
|
+
* `indexed: true` on a populated column used to cost a full reindex.
|
|
804
893
|
*/
|
|
805
894
|
declare function diffSchema(existing: SubgraphSchema, incoming: SubgraphSchema): TableDiff;
|
|
806
895
|
/**
|
|
807
896
|
* Returns true if the diff contains any breaking changes
|
|
808
|
-
* (removed tables, removed columns,
|
|
897
|
+
* (removed tables, removed columns, changed column types, or removed
|
|
898
|
+
* uniqueKeys — handler upserts `ON CONFLICT` against those).
|
|
899
|
+
* Index-flag and composite-index changes are never breaking.
|
|
809
900
|
*/
|
|
810
901
|
declare function hasBreakingChanges(diff: TableDiff): {
|
|
811
902
|
breaking: boolean
|
|
@@ -816,6 +907,10 @@ interface DeployDiff {
|
|
|
816
907
|
removedTables: string[];
|
|
817
908
|
addedColumns: Record<string, string[]>;
|
|
818
909
|
breakingChanges: string[];
|
|
910
|
+
/** Columns whose only change was the `indexed`/`search` flag (index DDL applied in place). */
|
|
911
|
+
indexChanges?: Record<string, string[]>;
|
|
912
|
+
/** uniqueKeys added in place via ALTER TABLE … ADD CONSTRAINT. */
|
|
913
|
+
addedUniqueKeys?: Record<string, string[][]>;
|
|
819
914
|
}
|
|
820
915
|
interface ByoMigrationPlan {
|
|
821
916
|
schemaName: string;
|
|
@@ -877,6 +972,8 @@ declare function deploySchema(db: AnyDb, def: SubgraphDefinition, handlerPath: s
|
|
|
877
972
|
subgraphId: string
|
|
878
973
|
version: string
|
|
879
974
|
diff?: DeployDiff
|
|
975
|
+
/** Non-fatal caveats the caller should surface (e.g. handler grafts at tip). */
|
|
976
|
+
warnings?: string[]
|
|
880
977
|
}>;
|
|
881
978
|
/** A (decoded event type, optional contract scope) pair the sparse probe
|
|
882
979
|
* checks. Contract scoping is what makes token-subgraph reindexes leap over
|
|
@@ -891,4 +988,4 @@ type SparseProbeTarget = {
|
|
|
891
988
|
declare function canSparseScan(subgraph: SubgraphDefinition): boolean;
|
|
892
989
|
/** Probe targets for a subgraph's filters: decoded type + contract scope when\\n* the filter pins one (assetIdentifier "SP….contract::asset" or contractId). */
|
|
893
990
|
declare function sparseProbeTargets(subgraph: SubgraphDefinition): SparseProbeTarget[];
|
|
894
|
-
export { validateSubgraphDefinition, sparseProbeTargets, resumeReindex, renderDeployPlan, reindexSubgraph, pgSchemaName, inferPrintTopics, hasBreakingChanges, generateSubgraphSQL, generatePrismaSchema, generateKyselySchema, generateIndexSchema, generateDrizzleSchema, diffSchema, deploySchema, defineSubgraph, 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, PrintSample, PrintEventPayload, PrintEventFor, PrintEventFilter, NftTransferPayload, NftTransferFilter, NftMintPayload, NftMintFilter, NftBurnPayload, NftBurnFilter, KyselyGenOptions, InferredTopicSchema, InferredPrintField, InferTableRow, InferSubgraphClient, InferColumnType, IndexCodegenTarget, IndexCodegenOptions, INDEX_CODEGEN_TABLES, GeneratedSQL, FtTransferPayload, FtTransferFilter, FtMintPayload, FtMintFilter, FtBurnPayload, FtBurnFilter, FindManyOptions, EventForFilter, DrizzleGenOptions, DeployPlan, DeployDiff, ContractDeployPayload, ContractDeployFilter, ContractCallPayload, ContractCallFilter, ContractCallEvent, ComputedValue, ComparisonFilter, ColumnType, ColumnToTS, ColumnDiff, ByoMigrationPlan, ByoBreakingChangeError, AnyEvent, AggregateSpec, AggregateResult };
|
|
991
|
+
export { validateSubgraphDefinition, validatePrintPayload, sparseProbeTargets, resumeReindex, renderDeployPlan, reindexSubgraph, pgSchemaName, inferPrintTopics, hasBreakingChanges, generateSubgraphSQL, generatePrismaSchema, generateKyselySchema, generateIndexSchema, generateDrizzleSchema, diffSchema, deploySchema, defineSubgraph, 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, InferColumnType, IndexCodegenTarget, IndexCodegenOptions, INDEX_CODEGEN_TABLES, GeneratedSQL, FtTransferPayload, FtTransferFilter, FtMintPayload, FtMintFilter, FtBurnPayload, FtBurnFilter, FindManyOptions, EventForFilter, DrizzleGenOptions, DeployPlan, DeployDiff, ContractDeployPayload, ContractDeployFilter, ContractCallPayload, ContractCallFilter, ContractCallEvent, ComputedValue, ComparisonFilter, ColumnType, ColumnToTS, ColumnDiff, ByoMigrationPlan, ByoBreakingChangeError, AnyEvent, AggregateSpec, AggregateResult };
|
package/dist/src/index.js
CHANGED
|
@@ -14,6 +14,44 @@ var ColumnTypeSchema = z.enum([
|
|
|
14
14
|
"timestamp",
|
|
15
15
|
"jsonb"
|
|
16
16
|
]);
|
|
17
|
+
var PrintFieldSchema = z.lazy(() => z.union([
|
|
18
|
+
ColumnTypeSchema,
|
|
19
|
+
z.object({ type: PrintFieldSchema, optional: z.literal(true) }),
|
|
20
|
+
z.object({ tuple: z.record(z.string(), PrintFieldSchema) }),
|
|
21
|
+
z.object({ list: PrintFieldSchema })
|
|
22
|
+
]));
|
|
23
|
+
var AbiTypeSchema = z.lazy(() => z.union([
|
|
24
|
+
z.string(),
|
|
25
|
+
z.object({ buff: z.object({ length: z.number() }) }),
|
|
26
|
+
z.object({ "string-ascii": z.object({ length: z.number() }) }),
|
|
27
|
+
z.object({ "string-utf8": z.object({ length: z.number() }) }),
|
|
28
|
+
z.object({ optional: AbiTypeSchema }),
|
|
29
|
+
z.object({
|
|
30
|
+
list: z.object({ type: AbiTypeSchema, length: z.number() })
|
|
31
|
+
}),
|
|
32
|
+
z.object({
|
|
33
|
+
tuple: z.array(z.object({ name: z.string(), type: AbiTypeSchema }))
|
|
34
|
+
}),
|
|
35
|
+
z.object({
|
|
36
|
+
response: z.object({ ok: AbiTypeSchema, error: AbiTypeSchema })
|
|
37
|
+
})
|
|
38
|
+
]));
|
|
39
|
+
var AbiContractSchema = z.object({
|
|
40
|
+
functions: z.array(z.object({
|
|
41
|
+
name: z.string(),
|
|
42
|
+
access: z.enum(["public", "read-only", "private"], {
|
|
43
|
+
message: "ABI access must be public | read-only | private — a raw Hiro/Clarinet ABI uses `read_only`. Normalize it first: normalizeAbi() from @secondlayer/stacks/clarity."
|
|
44
|
+
}),
|
|
45
|
+
args: z.array(z.object({ name: z.string(), type: AbiTypeSchema })),
|
|
46
|
+
outputs: AbiTypeSchema
|
|
47
|
+
})),
|
|
48
|
+
maps: z.array(z.unknown()).optional(),
|
|
49
|
+
variables: z.array(z.unknown()).optional(),
|
|
50
|
+
fungible_tokens: z.array(z.unknown()).optional(),
|
|
51
|
+
non_fungible_tokens: z.array(z.unknown()).optional(),
|
|
52
|
+
implemented_traits: z.array(z.unknown()).optional(),
|
|
53
|
+
defined_traits: z.array(z.unknown()).optional()
|
|
54
|
+
});
|
|
17
55
|
var SubgraphColumnSchema = z.object({
|
|
18
56
|
type: ColumnTypeSchema,
|
|
19
57
|
nullable: z.boolean().optional(),
|
|
@@ -62,9 +100,9 @@ var SubgraphFilterSchema = z.object({
|
|
|
62
100
|
contractName: z.string().optional(),
|
|
63
101
|
topic: z.string().optional(),
|
|
64
102
|
lockedAddress: z.string().optional(),
|
|
65
|
-
abi:
|
|
103
|
+
abi: AbiContractSchema.optional(),
|
|
66
104
|
trait: z.string().optional(),
|
|
67
|
-
prints: z.record(z.string(), z.record(z.string(),
|
|
105
|
+
prints: z.record(z.string(), z.record(z.string(), PrintFieldSchema)).optional()
|
|
68
106
|
}).strict();
|
|
69
107
|
var SubgraphDefinitionSchema = z.object({
|
|
70
108
|
name: SubgraphNameSchema,
|
|
@@ -837,6 +875,87 @@ import {
|
|
|
837
875
|
deserializeCV as deserializeCV2,
|
|
838
876
|
toCamelCase
|
|
839
877
|
} from "@secondlayer/stacks/clarity";
|
|
878
|
+
|
|
879
|
+
// src/runtime/print-validate.ts
|
|
880
|
+
var COLUMN_TYPES = new Set([
|
|
881
|
+
"text",
|
|
882
|
+
"uint",
|
|
883
|
+
"int",
|
|
884
|
+
"principal",
|
|
885
|
+
"boolean",
|
|
886
|
+
"timestamp",
|
|
887
|
+
"jsonb"
|
|
888
|
+
]);
|
|
889
|
+
function isColumnType(field) {
|
|
890
|
+
return typeof field === "string" && COLUMN_TYPES.has(field);
|
|
891
|
+
}
|
|
892
|
+
function matchesField(field, value) {
|
|
893
|
+
if (isColumnType(field)) {
|
|
894
|
+
switch (field) {
|
|
895
|
+
case "uint":
|
|
896
|
+
case "int":
|
|
897
|
+
return typeof value === "bigint" || typeof value === "number";
|
|
898
|
+
case "boolean":
|
|
899
|
+
return typeof value === "boolean";
|
|
900
|
+
case "text":
|
|
901
|
+
case "principal":
|
|
902
|
+
case "timestamp":
|
|
903
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "bigint";
|
|
904
|
+
case "jsonb":
|
|
905
|
+
return value !== undefined;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
if (typeof field !== "object" || field === null)
|
|
909
|
+
return false;
|
|
910
|
+
if ("optional" in field) {
|
|
911
|
+
return value === undefined || value === null || matchesField(field.type, value);
|
|
912
|
+
}
|
|
913
|
+
if ("tuple" in field) {
|
|
914
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
915
|
+
return false;
|
|
916
|
+
}
|
|
917
|
+
return matchesTopic(field.tuple, value).ok;
|
|
918
|
+
}
|
|
919
|
+
if ("list" in field) {
|
|
920
|
+
return Array.isArray(value) && value.every((item) => matchesField(field.list, item));
|
|
921
|
+
}
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
function matchesTopic(declared, data) {
|
|
925
|
+
for (const [name, field] of Object.entries(declared)) {
|
|
926
|
+
const present = name in data;
|
|
927
|
+
const optional = typeof field === "object" && field !== null && "optional" in field;
|
|
928
|
+
if (!present) {
|
|
929
|
+
if (optional)
|
|
930
|
+
continue;
|
|
931
|
+
return { ok: false, reason: `missing declared field "${name}"` };
|
|
932
|
+
}
|
|
933
|
+
if (!matchesField(field, data[name])) {
|
|
934
|
+
return {
|
|
935
|
+
ok: false,
|
|
936
|
+
reason: `field "${name}" does not match its declared type (got ${describe(data[name])})`
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
return { ok: true };
|
|
941
|
+
}
|
|
942
|
+
function describe(value) {
|
|
943
|
+
if (value === null)
|
|
944
|
+
return "null";
|
|
945
|
+
if (Array.isArray(value))
|
|
946
|
+
return "array";
|
|
947
|
+
return typeof value;
|
|
948
|
+
}
|
|
949
|
+
function validatePrintPayload(prints, topic, data) {
|
|
950
|
+
if (!prints)
|
|
951
|
+
return { ok: true };
|
|
952
|
+
const declared = prints[topic];
|
|
953
|
+
if (!declared)
|
|
954
|
+
return { ok: true };
|
|
955
|
+
return matchesTopic(declared, data);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// src/runtime/runner.ts
|
|
840
959
|
var DEFAULT_ERROR_THRESHOLD = 50;
|
|
841
960
|
function camelCase(str) {
|
|
842
961
|
return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
@@ -1081,6 +1200,7 @@ function buildEventPayload(filter, tx, event) {
|
|
|
1081
1200
|
async function runHandlers(subgraph, matched, ctx, opts) {
|
|
1082
1201
|
let processed = 0;
|
|
1083
1202
|
let errors = 0;
|
|
1203
|
+
let skipped = 0;
|
|
1084
1204
|
const threshold = opts?.errorThreshold ?? DEFAULT_ERROR_THRESHOLD;
|
|
1085
1205
|
const filterLookup = new Map;
|
|
1086
1206
|
if (!Array.isArray(subgraph.sources)) {
|
|
@@ -1105,7 +1225,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1105
1225
|
errors,
|
|
1106
1226
|
threshold
|
|
1107
1227
|
});
|
|
1108
|
-
return { processed, errors };
|
|
1228
|
+
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
1109
1229
|
}
|
|
1110
1230
|
const handler = subgraph.handlers[sourceName] ?? subgraph.handlers["*"] ?? null;
|
|
1111
1231
|
if (!handler) {
|
|
@@ -1161,6 +1281,21 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1161
1281
|
if (event !== null && filter?.type === "print_event" && filter.topic && payload.topic !== filter.topic) {
|
|
1162
1282
|
continue;
|
|
1163
1283
|
}
|
|
1284
|
+
if (event !== null && filter?.type === "print_event" && filter.prints) {
|
|
1285
|
+
const printPayload = payload;
|
|
1286
|
+
const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
|
|
1287
|
+
if (!verdict.ok) {
|
|
1288
|
+
skipped++;
|
|
1289
|
+
logger2.warn("Print payload does not match declared prints schema", {
|
|
1290
|
+
subgraph: subgraph.name,
|
|
1291
|
+
sourceName,
|
|
1292
|
+
topic: printPayload.topic,
|
|
1293
|
+
txId: tx.tx_id,
|
|
1294
|
+
reason: verdict.reason
|
|
1295
|
+
});
|
|
1296
|
+
continue;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1164
1299
|
await handler(payload, ctx);
|
|
1165
1300
|
processed++;
|
|
1166
1301
|
} catch (err) {
|
|
@@ -1175,7 +1310,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1175
1310
|
});
|
|
1176
1311
|
}
|
|
1177
1312
|
}
|
|
1178
|
-
return { processed, errors };
|
|
1313
|
+
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
1179
1314
|
}
|
|
1180
1315
|
|
|
1181
1316
|
// src/runtime/source-matcher.ts
|
|
@@ -3843,31 +3978,53 @@ import { sql as sql4 } from "kysely";
|
|
|
3843
3978
|
function toJsonSafe(obj) {
|
|
3844
3979
|
return JSON.parse(JSON.stringify(obj, (_key, value) => typeof value === "bigint" ? value.toString() : value));
|
|
3845
3980
|
}
|
|
3981
|
+
function columnDataShape(col) {
|
|
3982
|
+
const { indexed, search, ...rest } = col ?? {};
|
|
3983
|
+
return JSON.stringify(rest, Object.keys(rest).sort());
|
|
3984
|
+
}
|
|
3985
|
+
function diffKeyLists(existing, incoming) {
|
|
3986
|
+
const key2 = (cols) => JSON.stringify(cols);
|
|
3987
|
+
const existingSet = new Set((existing ?? []).map(key2));
|
|
3988
|
+
const incomingSet = new Set((incoming ?? []).map(key2));
|
|
3989
|
+
return {
|
|
3990
|
+
added: (incoming ?? []).filter((cols) => !existingSet.has(key2(cols))),
|
|
3991
|
+
removed: (existing ?? []).filter((cols) => !incomingSet.has(key2(cols)))
|
|
3992
|
+
};
|
|
3993
|
+
}
|
|
3846
3994
|
function diffSchema(existing, incoming) {
|
|
3847
3995
|
const existingTables = new Set(Object.keys(existing));
|
|
3848
3996
|
const incomingTables = new Set(Object.keys(incoming));
|
|
3849
3997
|
const addedTables = [...incomingTables].filter((t) => !existingTables.has(t));
|
|
3850
3998
|
const removedTables = [...existingTables].filter((t) => !incomingTables.has(t));
|
|
3851
3999
|
const tables = {};
|
|
4000
|
+
const constraints = {};
|
|
3852
4001
|
for (const tableName of incomingTables) {
|
|
3853
4002
|
if (!existingTables.has(tableName))
|
|
3854
4003
|
continue;
|
|
3855
|
-
const
|
|
3856
|
-
const
|
|
4004
|
+
const existingTable = existing[tableName];
|
|
4005
|
+
const incomingTable = incoming[tableName];
|
|
4006
|
+
const existingCols = existingTable?.columns;
|
|
4007
|
+
const incomingCols = incomingTable?.columns;
|
|
3857
4008
|
const existingKeys = new Set(Object.keys(existingCols));
|
|
3858
4009
|
const incomingKeys = new Set(Object.keys(incomingCols));
|
|
4010
|
+
const common = [...incomingKeys].filter((k) => existingKeys.has(k));
|
|
4011
|
+
const sortedStringify = (o) => JSON.stringify(o, Object.keys(o).sort());
|
|
3859
4012
|
tables[tableName] = {
|
|
3860
4013
|
added: [...incomingKeys].filter((k) => !existingKeys.has(k)),
|
|
3861
4014
|
removed: [...existingKeys].filter((k) => !incomingKeys.has(k)),
|
|
3862
|
-
changed:
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
4015
|
+
changed: common.filter((k) => columnDataShape(existingCols[k]) !== columnDataShape(incomingCols[k])),
|
|
4016
|
+
indexChanged: common.filter((k) => columnDataShape(existingCols[k]) === columnDataShape(incomingCols[k]) && sortedStringify(existingCols[k]) !== sortedStringify(incomingCols[k]))
|
|
4017
|
+
};
|
|
4018
|
+
const uq = diffKeyLists(existingTable?.uniqueKeys, incomingTable?.uniqueKeys);
|
|
4019
|
+
const ix = diffKeyLists(existingTable?.indexes, incomingTable?.indexes);
|
|
4020
|
+
constraints[tableName] = {
|
|
4021
|
+
addedUniqueKeys: uq.added,
|
|
4022
|
+
removedUniqueKeys: uq.removed,
|
|
4023
|
+
addedIndexes: ix.added,
|
|
4024
|
+
removedIndexes: ix.removed
|
|
3868
4025
|
};
|
|
3869
4026
|
}
|
|
3870
|
-
return { addedTables, removedTables, tables };
|
|
4027
|
+
return { addedTables, removedTables, tables, constraints };
|
|
3871
4028
|
}
|
|
3872
4029
|
function hasBreakingChanges(diff) {
|
|
3873
4030
|
const reasons = [];
|
|
@@ -3882,6 +4039,11 @@ function hasBreakingChanges(diff) {
|
|
|
3882
4039
|
reasons.push(`${table}: changed columns [${colDiff.changed.join(", ")}]`);
|
|
3883
4040
|
}
|
|
3884
4041
|
}
|
|
4042
|
+
for (const [table, conDiff] of Object.entries(diff.constraints ?? {})) {
|
|
4043
|
+
if (conDiff.removedUniqueKeys.length > 0) {
|
|
4044
|
+
reasons.push(`${table}: removed uniqueKeys [${conDiff.removedUniqueKeys.map((cols) => `(${cols.join(", ")})`).join(", ")}]`);
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
3885
4047
|
return { breaking: reasons.length > 0, reasons };
|
|
3886
4048
|
}
|
|
3887
4049
|
function bumpPatch(version) {
|
|
@@ -3987,7 +4149,12 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
3987
4149
|
return {
|
|
3988
4150
|
action: handlerChanged ? "handler_updated" : "unchanged",
|
|
3989
4151
|
subgraphId: existing.id,
|
|
3990
|
-
version: existing.version
|
|
4152
|
+
version: existing.version,
|
|
4153
|
+
...handlerChanged ? {
|
|
4154
|
+
warnings: [
|
|
4155
|
+
"handler changed but schema did not: new logic applies from the current tip only. Rows already indexed were computed by the previous handler. To recompute history, redeploy with --reindex (or backfill a range)."
|
|
4156
|
+
]
|
|
4157
|
+
} : {}
|
|
3991
4158
|
};
|
|
3992
4159
|
}
|
|
3993
4160
|
if (existing.schema_hash === hash && opts?.forceReindex) {
|
|
@@ -4058,6 +4225,65 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
4058
4225
|
}
|
|
4059
4226
|
}
|
|
4060
4227
|
}
|
|
4228
|
+
const existingSchema = existing.definition.schema;
|
|
4229
|
+
const flagFlips = {};
|
|
4230
|
+
const needsTrgmForFlips = Object.entries(diff.tables).some(([tableName, colDiff]) => colDiff.indexChanged.some((colName) => def.schema[tableName]?.columns[colName]?.search && !existingSchema[tableName]?.columns[colName]?.search));
|
|
4231
|
+
if (needsTrgmForFlips) {
|
|
4232
|
+
await sql4.raw("CREATE EXTENSION IF NOT EXISTS pg_trgm").execute(ddlDb);
|
|
4233
|
+
}
|
|
4234
|
+
for (const [tableName, colDiff] of Object.entries(diff.tables)) {
|
|
4235
|
+
if (colDiff.indexChanged.length === 0)
|
|
4236
|
+
continue;
|
|
4237
|
+
flagFlips[tableName] = colDiff.indexChanged;
|
|
4238
|
+
const qualifiedName = `${schemaName}.${tableName}`;
|
|
4239
|
+
for (const colName of colDiff.indexChanged) {
|
|
4240
|
+
const was = existingSchema[tableName]?.columns[colName];
|
|
4241
|
+
const now = def.schema[tableName]?.columns[colName];
|
|
4242
|
+
if (!now)
|
|
4243
|
+
continue;
|
|
4244
|
+
const plainIdx = `idx_${schemaName}_${tableName}_${colName}`;
|
|
4245
|
+
const trgmIdx = `idx_${schemaName}_${tableName}_${colName}_trgm`;
|
|
4246
|
+
if (now.indexed && !was?.indexed) {
|
|
4247
|
+
await sql4.raw(`CREATE INDEX CONCURRENTLY IF NOT EXISTS ${plainIdx} ON ${qualifiedName} (${colName})`).execute(ddlDb);
|
|
4248
|
+
} else if (!now.indexed && was?.indexed) {
|
|
4249
|
+
await sql4.raw(`DROP INDEX CONCURRENTLY IF EXISTS ${schemaName}.${plainIdx}`).execute(ddlDb);
|
|
4250
|
+
}
|
|
4251
|
+
if (now.search && !was?.search) {
|
|
4252
|
+
await sql4.raw(`CREATE INDEX CONCURRENTLY IF NOT EXISTS ${trgmIdx} ON ${qualifiedName} USING gin (${colName} gin_trgm_ops)`).execute(ddlDb);
|
|
4253
|
+
} else if (!now.search && was?.search) {
|
|
4254
|
+
await sql4.raw(`DROP INDEX CONCURRENTLY IF EXISTS ${schemaName}.${trgmIdx}`).execute(ddlDb);
|
|
4255
|
+
}
|
|
4256
|
+
}
|
|
4257
|
+
}
|
|
4258
|
+
const addedUq = {};
|
|
4259
|
+
for (const [tableName, conDiff] of Object.entries(diff.constraints)) {
|
|
4260
|
+
const qualifiedName = `${schemaName}.${tableName}`;
|
|
4261
|
+
for (const cols of conDiff.addedUniqueKeys) {
|
|
4262
|
+
const constraintName = `uq_${schemaName}_${tableName}_${cols.join("_")}`;
|
|
4263
|
+
const exists = await sql4`
|
|
4264
|
+
SELECT EXISTS (
|
|
4265
|
+
SELECT 1 FROM pg_constraint
|
|
4266
|
+
WHERE conname = ${constraintName}
|
|
4267
|
+
) AS "exists"
|
|
4268
|
+
`.execute(ddlDb).then((r) => r.rows[0]?.exists ?? false);
|
|
4269
|
+
if (!exists) {
|
|
4270
|
+
await sql4.raw(`ALTER TABLE ${qualifiedName} ADD CONSTRAINT ${constraintName} UNIQUE (${cols.join(", ")})`).execute(ddlDb);
|
|
4271
|
+
}
|
|
4272
|
+
addedUq[tableName] = [...addedUq[tableName] ?? [], cols];
|
|
4273
|
+
}
|
|
4274
|
+
for (const cols of conDiff.addedIndexes) {
|
|
4275
|
+
const i = (def.schema[tableName]?.indexes ?? []).findIndex((entry) => JSON.stringify(entry) === JSON.stringify(cols));
|
|
4276
|
+
const idxName = `idx_${schemaName}_${tableName}_composite_${i}`;
|
|
4277
|
+
await sql4.raw(`CREATE INDEX CONCURRENTLY IF NOT EXISTS ${idxName} ON ${qualifiedName} (${cols.join(", ")})`).execute(ddlDb);
|
|
4278
|
+
}
|
|
4279
|
+
for (const cols of conDiff.removedIndexes) {
|
|
4280
|
+
const i = (existingSchema[tableName]?.indexes ?? []).findIndex((entry) => JSON.stringify(entry) === JSON.stringify(cols));
|
|
4281
|
+
if (i < 0)
|
|
4282
|
+
continue;
|
|
4283
|
+
const idxName = `idx_${schemaName}_${tableName}_composite_${i}`;
|
|
4284
|
+
await sql4.raw(`DROP INDEX CONCURRENTLY IF EXISTS ${schemaName}.${idxName}`).execute(ddlDb);
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4061
4287
|
const sg2 = await registerSubgraph(db, regData);
|
|
4062
4288
|
const addedCols = {};
|
|
4063
4289
|
for (const [t, colDiff] of Object.entries(diff.tables)) {
|
|
@@ -4068,7 +4294,9 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
4068
4294
|
addedTables: diff.addedTables,
|
|
4069
4295
|
removedTables: [],
|
|
4070
4296
|
addedColumns: addedCols,
|
|
4071
|
-
breakingChanges: []
|
|
4297
|
+
breakingChanges: [],
|
|
4298
|
+
...Object.keys(flagFlips).length > 0 ? { indexChanges: flagFlips } : {},
|
|
4299
|
+
...Object.keys(addedUq).length > 0 ? { addedUniqueKeys: addedUq } : {}
|
|
4072
4300
|
};
|
|
4073
4301
|
return {
|
|
4074
4302
|
action: "updated",
|
|
@@ -4104,6 +4332,7 @@ function getDefault(type) {
|
|
|
4104
4332
|
}
|
|
4105
4333
|
export {
|
|
4106
4334
|
validateSubgraphDefinition,
|
|
4335
|
+
validatePrintPayload,
|
|
4107
4336
|
sparseProbeTargets,
|
|
4108
4337
|
resumeReindex,
|
|
4109
4338
|
renderDeployPlan,
|
|
@@ -4126,5 +4355,5 @@ export {
|
|
|
4126
4355
|
ByoBreakingChangeError
|
|
4127
4356
|
};
|
|
4128
4357
|
|
|
4129
|
-
//# debugId=
|
|
4358
|
+
//# debugId=1014943AF0D5A26B64756E2164756E21
|
|
4130
4359
|
//# sourceMappingURL=index.js.map
|