Skip to content

Commit 2946073

Browse files
authored
fix: require immutable flag on entities (#1994)
1 parent c9fe1c4 commit 2946073

File tree

58 files changed

+221
-69
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+221
-69
lines changed

.changeset/tangy-pants-buy.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphprotocol/graph-cli': minor
3+
---
4+
5+
require immutable flag on entities

examples/arweave-blocks-transactions/schema.graphql

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
type Block @entity {
1+
type Block @entity(immutable: true) {
22
id: ID!
33

44
timestamp: BigInt!
@@ -22,7 +22,7 @@ type Block @entity {
2222
poa: Poa
2323
}
2424

25-
type Transaction @entity {
25+
type Transaction @entity(immutable: true) {
2626
id: ID!
2727

2828
block: Block!
@@ -39,7 +39,7 @@ type Transaction @entity {
3939
reward: Bytes!
4040
}
4141

42-
type Poa @entity {
42+
type Poa @entity(immutable: true) {
4343
id: ID!
4444

4545
option: String!
@@ -48,7 +48,7 @@ type Poa @entity {
4848
chunk: Bytes!
4949
}
5050

51-
type Tag @entity {
51+
type Tag @entity(immutable: true) {
5252
id: ID!
5353

5454
name: Bytes!

examples/cosmos-block-filtering/schema.graphql

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
type Block @entity {
1+
type Block @entity(immutable: true) {
22
id: ID!
33
number: BigInt
44
timestamp: BigInt

examples/cosmos-osmosis-token-swaps/schema.graphql

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
type Token @entity {
1+
type Token @entity(immutable: true) {
22
id: ID!
33
amount: String
44
denom: String
55
}
66

7-
type TokenSwap @entity {
7+
type TokenSwap @entity(immutable: true) {
88
id: ID!
99
sender: String
1010
poolId: String

examples/cosmos-validator-delegations/schema.graphql

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
type Delegation @entity {
1+
type Delegation @entity(immutable: true) {
22
id: ID!
33
delegatorAddress: String
44
validatorAddress: String
55
amount: Coin
66
}
77

8-
type Coin @entity {
8+
type Coin @entity(immutable: true) {
99
id: ID!
1010
denom: String
1111
amount: String

examples/cosmos-validator-rewards/schema.graphql

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
type Reward @entity {
1+
type Reward @entity(immutable: true) {
22
id: ID!
33
amount: String
44
validator: String

examples/ethereum-basic-event-handlers/schema.graphql

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
type NewGravatar @entity {
1+
type NewGravatar @entity(immutable: false) {
22
id: ID!
33
owner: Bytes!
44
displayName: String!
55
imageUrl: String!
66
}
77

8-
type UpdatedGravatar @entity {
8+
type UpdatedGravatar @entity(immutable: false) {
99
id: ID!
1010
owner: Bytes!
1111
displayName: String!

examples/ethereum-gravatar/schema.graphql

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
type Gravatar @entity {
1+
type Gravatar @entity(immutable: false) {
22
id: ID!
33
owner: Bytes!
44
displayName: String!

examples/example-subgraph/schema.graphql

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
type ExampleEntity @entity {
1+
type ExampleEntity @entity(immutable: false) {
22
id: ID!
33

44
optionalBoolean: Boolean
@@ -32,6 +32,6 @@ type ExampleEntity @entity {
3232
requiredReferenceList: [OtherEntity!]!
3333
}
3434

35-
type OtherEntity @entity {
35+
type OtherEntity @entity(immutable: true) {
3636
id: ID!
3737
}

examples/near-blocks/schema.graphql

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
type BlockEvent @entity {
1+
type BlockEvent @entity(immutable: true) {
22
id: ID!
33
number: BigInt
44
hash: Bytes

examples/near-receipts/schema.graphql

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
type Greeter @entity {
1+
type Greeter @entity(immutable: true) {
22
id: ID!
33
name: String!
44
greetings: [Greeting!] @derivedFrom(field: "greeter")
55
}
66

7-
type Greeting @entity {
7+
type Greeting @entity(immutable: true) {
88
id: ID!
99
greeter: Greeter!
1010
timestamp: BigInt!

examples/substreams-powered-subgraph/schema.graphql

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
type Contract @entity {
1+
type Contract @entity(immutable: true) {
22
id: ID!
33

44
"The timestamp when the contract was deployed"

packages/cli/src/scaffold/__snapshots__/cosmos.test.ts.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export function handleBlock(block: cosmos.Block): void {
6060
`;
6161

6262
exports[`Cosmos subgraph scaffolding > Schema (default) 1`] = `
63-
"type ExampleEntity @entity {
63+
"type ExampleEntity @entity(immutable: true) {
6464
id: ID!
6565
block: Bytes!
6666
count: BigInt!

packages/cli/src/scaffold/__snapshots__/ethereum.test.ts.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ export function handleTupleArrayEvent(event: TupleArrayEvent): void {}
227227
`;
228228

229229
exports[`Ethereum subgraph scaffolding > Schema (default) 1`] = `
230-
"type ExampleEntity @entity {
230+
"type ExampleEntity @entity(immutable: true) {
231231
id: Bytes!
232232
count: BigInt!
233233
a: BigInt! # uint256

packages/cli/src/scaffold/__snapshots__/near.test.ts.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export function handleReceipt(
6262
`;
6363

6464
exports[`NEAR subgraph scaffolding > Schema (default) 1`] = `
65-
"type ExampleEntity @entity {
65+
"type ExampleEntity @entity(immutable: true) {
6666
id: ID!
6767
block: Bytes!
6868
count: BigInt!

packages/cli/src/scaffold/schema.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export const generateEventType = (
8585

8686
export const generateExampleEntityType = (protocol: Protocol, events: any[]) => {
8787
if (protocol.hasABIs() && events.length > 0) {
88-
return `type ExampleEntity @entity {
88+
return `type ExampleEntity @entity(immutable: true) {
8989
id: Bytes!
9090
count: BigInt!
9191
${events[0].inputs
@@ -97,7 +97,7 @@ export const generateExampleEntityType = (protocol: Protocol, events: any[]) =>
9797
.join('\n')}
9898
}`;
9999
}
100-
return `type ExampleEntity @entity {
100+
return `type ExampleEntity @entity(immutable: true) {
101101
id: ID!
102102
block: Bytes!
103103
count: BigInt!

packages/cli/src/schema.ts

+27
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,31 @@ export default class Schema {
4242
)
4343
.map(entity => (entity as graphql.ObjectTypeDefinitionNode).name.value);
4444
}
45+
46+
immutableEntitiesCount(): number {
47+
const isImmutable = (entity: graphql.ConstDirectiveNode) => {
48+
return (
49+
entity.arguments?.find(arg => {
50+
return (
51+
(arg.name.value === 'immutable' || arg.name.value === 'timeseries') &&
52+
arg.value.kind === 'BooleanValue' &&
53+
arg.value.value
54+
);
55+
}) !== undefined
56+
);
57+
};
58+
59+
return this.ast.definitions.filter(def => {
60+
if (def.kind !== 'ObjectTypeDefinition') {
61+
return false;
62+
}
63+
64+
const entity = def.directives?.find(directive => directive.name.value === 'entity');
65+
if (entity === undefined) {
66+
return false;
67+
}
68+
69+
return isImmutable(entity);
70+
}).length;
71+
}
4572
}

packages/cli/src/type-generator.ts

+8
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,14 @@ export default class TypeGenerator {
102102
typeGenDebug.extend('generateTypes')('Generating types for schema');
103103
await this.generateTypesForSchema({ schema });
104104

105+
if (schema.immutableEntitiesCount() === 0) {
106+
toolbox.print.warning(
107+
'\nThe GraphQL schema does not take advantage of using immutable entities. ' +
108+
'Immutable entities can significantly improve subgraph performance.\n' +
109+
'Learn more about using immutable entities: https://thegraph.com/docs/en/subgraphs/best-practices/immutable-entities-bytes-as-ids/#immutable-entities',
110+
);
111+
}
112+
105113
if (this.options.subgraphSources.length > 0) {
106114
const ipfsClient = createIpfsClient({
107115
url: appendApiVersionForGraph(this.options.ipfsUrl.toString()),

packages/cli/src/validation/schema.ts

+32
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fs from 'node:fs';
2+
import { print } from 'gluegun';
23
import * as graphql from 'graphql/language/index.js';
34
import immutable from 'immutable';
45
import debugFactory from '../debug.js';
@@ -99,6 +100,36 @@ const validateEntityDirective = (def: any) => {
99100
]);
100101
};
101102

103+
const validateEntityDirectiveImmutableArgument = (def: graphql.ObjectTypeDefinitionNode) => {
104+
validateDebugger('Validating immutable argument on entity directive for %s', def?.name?.value);
105+
106+
const entity = def.directives?.find(directive => directive.name.value === 'entity');
107+
if (entity === undefined) {
108+
return List();
109+
}
110+
111+
const hasImmutableArg =
112+
entity.arguments?.find(arg => arg.name.value === 'immutable') !== undefined;
113+
const hasTimeseriesArg =
114+
entity.arguments?.find(arg => arg.name.value === 'timeseries') !== undefined;
115+
116+
if (hasImmutableArg || hasTimeseriesArg) {
117+
return List();
118+
}
119+
120+
return immutable.fromJS([
121+
{
122+
loc: def.loc,
123+
entity: def.name?.value,
124+
message:
125+
`@entity directive requires \`immutable\` argument\n ` +
126+
print.colors.yellow(
127+
`Hint: Try updating the entity definition with: @entity(immutable: true)`,
128+
),
129+
},
130+
]);
131+
};
132+
102133
const validateEntityID = (def: any) => {
103134
validateDebugger('Validating entity ID for %s', def?.name?.value);
104135
const idField = def.fields.find((field: any) => field.name.value === 'id');
@@ -986,6 +1017,7 @@ const typeDefinitionValidators = {
9861017
? List.of(...validateSubgraphSchemaDirectives(def), ...validateTypeHasNoFields(def))
9871018
: List.of(
9881019
...validateEntityDirective(def),
1020+
...validateEntityDirectiveImmutableArgument(def),
9891021
...validateEntityID(def),
9901022
...validateEntityFields(defs, def),
9911023
...validateNoImportDirective(def),

packages/cli/tests/cli/__snapshots__/validation.test.ts.snap

+14
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,20 @@ Types generated successfully
932932
"
933933
`;
934934
935+
exports[`Validation > Should require immutable argument on entity directive 1`] = `
936+
"- Load subgraph from subgraph.yaml
937+
✖ Failed to load subgraph from subgraph.yaml: Error in schema.graphql:
938+
939+
EntityA:
940+
- @entity directive requires \`immutable\` argument
941+
Hint: Try updating the entity definition with: @entity(immutable: true)
942+
"
943+
`;
944+
945+
exports[`Validation > Should require immutable argument on entity directive 2`] = `1`;
946+
947+
exports[`Validation > Should require immutable argument on entity directive 3`] = `""`;
948+
935949
exports[`Validation > Source without address is valid 1`] = `
936950
"- Load subgraph from subgraph.yaml
937951
✔ Load subgraph from subgraph.yaml

packages/cli/tests/cli/add/subgraph/schema.graphql

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
type NewGravatar @entity {
1+
type NewGravatar @entity(immutable: true) {
22
id: ID!
33
owner: Bytes!
44
displayName: String!
55
imageUrl: String!
66
}
77

8-
type UpdatedGravatar @entity {
8+
type UpdatedGravatar @entity(immutable: true) {
99
id: ID!
1010
owner: Bytes!
1111
displayName: String!

packages/cli/tests/cli/validation.test.ts

+9
Original file line numberDiff line numberDiff line change
@@ -247,4 +247,13 @@ describe('Validation', { concurrent: true, timeout: 60_000 }, () => {
247247
exitCode: 0,
248248
},
249249
);
250+
251+
cliTest(
252+
'Should require immutable argument on entity directive',
253+
['codegen', '--skip-migrations'],
254+
'validation/require-immutable-argument',
255+
{
256+
exitCode: 1,
257+
},
258+
);
250259
});
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
type MyEntity @entity {
1+
type MyEntity @entity(immutable: true) {
22
id: ID!
33
childTokenURIs: [[String!]!]! # string[][]
44
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
type MyEntity @entity {
1+
type MyEntity @entity(immutable: true) {
22
id: ID!
33
childTokenURIs: [[[String!]!]!]! # string[][][]
44
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
type MyEntity @entity {
1+
type MyEntity @entity(immutable: true) {
22
id: ID!
33
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
type MyEntity @entity {
1+
type MyEntity @entity(immutable: true) {
22
id: ID!
33
x: BigDecimal!
44
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
type A @entity {
1+
type A @entity(immutable: true) {
22
id: ID!
3-
}
3+
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
type MyEntity @entity {
1+
type MyEntity @entity(immutable: true) {
22
id: ID!
33
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
type MyEntity @entity {
1+
type MyEntity @entity(immutable: true) {
22
id: ID!
33
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
type MyEntity @entity {
1+
type MyEntity @entity(immutable: true) {
22
id: ID!
33
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
type MyEntity @entity {
1+
type MyEntity @entity(immutable: true) {
22
id: ID!
33
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
type Foo @entity {
1+
type Foo @entity(immutable: true) {
22
id: ID!
33
bars: [Bars!]! @derivedFrom(field: "foo")
44
}

packages/cli/tests/cli/validation/derived-from-with-interface/schema.graphql

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
type Gravatar @entity {
1+
type Gravatar @entity(immutable: false) {
22
id: ID!
33
# This field points at an interface; we allow that a derived field
44
# in a type that implements the interface derives from this field
@@ -12,7 +12,7 @@ interface Account {
1212
gravatars: [Gravatar!]! @derivedFrom(field: "owner")
1313
}
1414

15-
type UserAccount implements Account @entity {
15+
type UserAccount implements Account @entity(immutable: true) {
1616
id: ID!
1717
name: String!
1818
gravatars: [Gravatar!]! @derivedFrom(field: "owner")

0 commit comments

Comments
 (0)