Skip to content

Commit 3e04952

Browse files
authored
graph init should only generate entity handlers for immutable enitites (#1993)
* Generate only immutable entities when using graph init with a source subgraph * remove EntityHandler from codegen for composed subgraphs * fix: clarify error message for source subgraph immutable entities requirement * Add changeset * remove non entity type test for getImmutableEntityNames * remove graph-ts from changeset
1 parent 2946073 commit 3e04952

File tree

6 files changed

+80
-27
lines changed

6 files changed

+80
-27
lines changed

.changeset/short-coins-deny.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphprotocol/graph-cli': minor
3+
---
4+
5+
Composed subgraphs are modified to only accept immutable entites as triggers from a source subgraph

packages/cli/src/commands/init.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -1253,7 +1253,7 @@ async function initSubgraphFromContract(
12531253
}
12541254
}
12551255

1256-
let entities: string[] | undefined;
1256+
let immutableEntities: string[] | undefined;
12571257

12581258
if (isComposedSubgraph) {
12591259
try {
@@ -1274,7 +1274,14 @@ async function initSubgraphFromContract(
12741274
startBlock ||= getMinStartBlock(manifestYaml)?.toString();
12751275
const schemaString = await loadSubgraphSchemaFromIPFS(ipfsClient, source);
12761276
const schema = await Schema.loadFromString(schemaString);
1277-
entities = schema.getEntityNames();
1277+
immutableEntities = schema.getImmutableEntityNames();
1278+
1279+
if (immutableEntities.length === 0) {
1280+
this.error(
1281+
'Source subgraph must have at least one immutable entity. This subgraph cannot be used as a source subgraph since it has no immutable entities.',
1282+
{ exit: 1 },
1283+
);
1284+
}
12781285
} catch (e) {
12791286
this.error(`Failed to load and parse subgraph schema: ${e.message}`, { exit: 1 });
12801287
}
@@ -1316,7 +1323,7 @@ async function initSubgraphFromContract(
13161323
startBlock,
13171324
node,
13181325
spkgPath,
1319-
entities,
1326+
entities: immutableEntities,
13201327
},
13211328
spinner,
13221329
);

packages/cli/src/protocols/subgraph/scaffold/mapping.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@ export const generatePlaceholderHandlers = ({
77
}) => `
88
import { ExampleEntity } from '../generated/schema'
99
import {${entities.join(', ')}} from '../generated/subgraph-${contract}'
10-
import { EntityTrigger } from '@graphprotocol/graph-ts'
1110
1211
${entities
1312
.map(
1413
entityName => `
15-
export function handle${entityName}(entity: EntityTrigger<${entityName}>): void {
14+
export function handle${entityName}(entity: ${entityName}): void {
1615
// Empty handler for ${entityName}
1716
}`,
1817
)

packages/cli/src/schema.test.ts

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { beforeEach, describe, expect, test } from 'vitest';
2+
import Schema from './schema.js';
3+
4+
describe('Schema', () => {
5+
const schemaDocument = `
6+
type Entity1 @entity {
7+
id: ID!
8+
}
9+
10+
type Entity2 @entity(immutable: true) {
11+
id: ID!
12+
}
13+
14+
type Entity3 @entity(immutable: false) {
15+
id: ID!
16+
}
17+
`;
18+
19+
let schema: Schema;
20+
21+
beforeEach(async () => {
22+
schema = await Schema.loadFromString(schemaDocument);
23+
});
24+
25+
test('getEntityNames returns all entity types', () => {
26+
const entityNames = schema.getEntityNames();
27+
expect(entityNames).toEqual(['Entity1', 'Entity2', 'Entity3']);
28+
});
29+
30+
test('getImmutableEntityNames returns only immutable entity types', () => {
31+
const immutableEntityNames = schema.getImmutableEntityNames();
32+
expect(immutableEntityNames).toEqual(['Entity2']);
33+
});
34+
35+
test('getImmutableEntityNames handles entities without immutable flag', () => {
36+
const immutableEntityNames = schema.getImmutableEntityNames();
37+
expect(immutableEntityNames).not.toContain('Entity1');
38+
});
39+
40+
test('getImmutableEntityNames handles explicitly non-immutable entities', () => {
41+
const immutableEntityNames = schema.getImmutableEntityNames();
42+
expect(immutableEntityNames).not.toContain('Entity3');
43+
});
44+
});

packages/cli/src/schema.ts

+20
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,24 @@ export default class Schema {
6969
return isImmutable(entity);
7070
}).length;
7171
}
72+
73+
getImmutableEntityNames(): string[] {
74+
return this.ast.definitions
75+
.filter(
76+
def =>
77+
def.kind === 'ObjectTypeDefinition' &&
78+
def.directives?.find(
79+
directive =>
80+
directive.name.value === 'entity' &&
81+
directive.arguments?.find(arg => {
82+
return (
83+
arg.name.value === 'immutable' &&
84+
arg.value.kind === 'BooleanValue' &&
85+
arg.value.value === true
86+
);
87+
}),
88+
) !== undefined,
89+
)
90+
.map(entity => (entity as graphql.ObjectTypeDefinitionNode).name.value);
91+
}
7292
}

packages/ts/common/collections.ts

-22
Original file line numberDiff line numberDiff line change
@@ -457,28 +457,6 @@ export class Entity extends TypedMap<string, Value> {
457457
}
458458
}
459459

460-
/**
461-
* Common representation for entity triggers, this wraps the entity
462-
* and has fields for the operation type and the entity type.
463-
*/
464-
export class EntityTrigger<T extends Entity> {
465-
constructor(
466-
public operation: EntityOp,
467-
public type: string,
468-
public data: T, // T is a specific type that extends Entity
469-
) {}
470-
}
471-
472-
/**
473-
* Enum for entity operations.
474-
* Create, Modify, Remove
475-
*/
476-
export enum EntityOp {
477-
Create,
478-
Modify,
479-
Remove,
480-
}
481-
482460
/**
483461
* The result of an operation, with a corresponding value and error type.
484462
*/

0 commit comments

Comments
 (0)