From d38eddcb2933cdd5ad203bdad1dc7b734a002e71 Mon Sep 17 00:00:00 2001 From: incrypto32 Date: Thu, 27 Mar 2025 19:19:49 +0400 Subject: [PATCH 1/6] Generate only immutable entities when using graph init with a source subgraph --- packages/cli/src/commands/init.ts | 6 ++-- packages/cli/src/schema.test.ts | 49 +++++++++++++++++++++++++++++++ packages/cli/src/schema.ts | 20 +++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 packages/cli/src/schema.test.ts diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 78f9c305e..ba0d18238 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -1253,7 +1253,7 @@ async function initSubgraphFromContract( } } - let entities: string[] | undefined; + let immutableEntities: string[] | undefined; if (isComposedSubgraph) { try { @@ -1274,7 +1274,7 @@ async function initSubgraphFromContract( startBlock ||= getMinStartBlock(manifestYaml)?.toString(); const schemaString = await loadSubgraphSchemaFromIPFS(ipfsClient, source); const schema = await Schema.loadFromString(schemaString); - entities = schema.getEntityNames(); + immutableEntities = schema.getImmutableEntityNames(); } catch (e) { this.error(`Failed to load and parse subgraph schema: ${e.message}`, { exit: 1 }); } @@ -1316,7 +1316,7 @@ async function initSubgraphFromContract( startBlock, node, spkgPath, - entities, + entities: immutableEntities, }, spinner, ); diff --git a/packages/cli/src/schema.test.ts b/packages/cli/src/schema.test.ts new file mode 100644 index 000000000..1fd361d3e --- /dev/null +++ b/packages/cli/src/schema.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, test } from 'vitest'; +import Schema from './schema.js'; + +describe('Schema', () => { + const schemaDocument = ` + type Entity1 @entity { + id: ID! + } + + type Entity2 @entity(immutable: true) { + id: ID! + } + + type Entity3 @entity(immutable: false) { + id: ID! + } + `; + + test('getEntityNames returns all entity types', async () => { + const schema = await Schema.loadFromString(schemaDocument); + const entityNames = schema.getEntityNames(); + expect(entityNames).toEqual(['Entity1', 'Entity2', 'Entity3']); + }); + + test('getImmutableEntityNames returns only immutable entity types', async () => { + const schema = await Schema.loadFromString(schemaDocument); + const immutableEntityNames = schema.getImmutableEntityNames(); + expect(immutableEntityNames).toEqual(['Entity2']); + }); + + test('getImmutableEntityNames handles entities without immutable flag', async () => { + const schema = await Schema.loadFromString(schemaDocument); + const immutableEntityNames = schema.getImmutableEntityNames(); + expect(immutableEntityNames).not.toContain('Entity1'); + }); + + test('getImmutableEntityNames handles explicitly non-immutable entities', async () => { + const schema = await Schema.loadFromString(schemaDocument); + const immutableEntityNames = schema.getImmutableEntityNames(); + expect(immutableEntityNames).not.toContain('Entity3'); + }); + + test('getImmutableEntityNames ignores non-entity types', async () => { + const schema = await Schema.loadFromString(schemaDocument); + const immutableEntityNames = schema.getImmutableEntityNames(); + expect(immutableEntityNames).not.toContain('Entity1'); + expect(immutableEntityNames).not.toContain('Entity3'); + }); +}); diff --git a/packages/cli/src/schema.ts b/packages/cli/src/schema.ts index 9f935fe05..12ae99bed 100644 --- a/packages/cli/src/schema.ts +++ b/packages/cli/src/schema.ts @@ -69,4 +69,24 @@ export default class Schema { return isImmutable(entity); }).length; } + + getImmutableEntityNames(): string[] { + return this.ast.definitions + .filter( + def => + def.kind === 'ObjectTypeDefinition' && + def.directives?.find( + directive => + directive.name.value === 'entity' && + directive.arguments?.find(arg => { + return ( + arg.name.value === 'immutable' && + arg.value.kind === 'BooleanValue' && + arg.value.value === true + ); + }), + ) !== undefined, + ) + .map(entity => (entity as graphql.ObjectTypeDefinitionNode).name.value); + } } From 76e65ddb7584f9e652dcffd95d2a2ce760b0ed98 Mon Sep 17 00:00:00 2001 From: incrypto32 Date: Mon, 31 Mar 2025 19:29:17 +0400 Subject: [PATCH 2/6] remove EntityHandler from codegen for composed subgraphs --- .../protocols/subgraph/scaffold/mapping.ts | 3 +-- packages/ts/common/collections.ts | 22 ------------------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/packages/cli/src/protocols/subgraph/scaffold/mapping.ts b/packages/cli/src/protocols/subgraph/scaffold/mapping.ts index a2241259a..cff158d30 100644 --- a/packages/cli/src/protocols/subgraph/scaffold/mapping.ts +++ b/packages/cli/src/protocols/subgraph/scaffold/mapping.ts @@ -7,12 +7,11 @@ export const generatePlaceholderHandlers = ({ }) => ` import { ExampleEntity } from '../generated/schema' import {${entities.join(', ')}} from '../generated/subgraph-${contract}' -import { EntityTrigger } from '@graphprotocol/graph-ts' ${entities .map( entityName => ` -export function handle${entityName}(entity: EntityTrigger<${entityName}>): void { +export function handle${entityName}(entity: ${entityName}): void { // Empty handler for ${entityName} }`, ) diff --git a/packages/ts/common/collections.ts b/packages/ts/common/collections.ts index f12f98ba0..706b92ec8 100644 --- a/packages/ts/common/collections.ts +++ b/packages/ts/common/collections.ts @@ -457,28 +457,6 @@ export class Entity extends TypedMap { } } -/** - * Common representation for entity triggers, this wraps the entity - * and has fields for the operation type and the entity type. - */ -export class EntityTrigger { - constructor( - public operation: EntityOp, - public type: string, - public data: T, // T is a specific type that extends Entity - ) {} -} - -/** - * Enum for entity operations. - * Create, Modify, Remove - */ -export enum EntityOp { - Create, - Modify, - Remove, -} - /** * The result of an operation, with a corresponding value and error type. */ From 6bf1e05ac9745dca2fe9774e100f5e510571fd9b Mon Sep 17 00:00:00 2001 From: incrypto32 Date: Mon, 31 Mar 2025 20:13:41 +0400 Subject: [PATCH 3/6] fix: clarify error message for source subgraph immutable entities requirement --- packages/cli/src/commands/init.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index ba0d18238..8a5e23ab0 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -1275,6 +1275,13 @@ async function initSubgraphFromContract( const schemaString = await loadSubgraphSchemaFromIPFS(ipfsClient, source); const schema = await Schema.loadFromString(schemaString); immutableEntities = schema.getImmutableEntityNames(); + + if (immutableEntities.length === 0) { + this.error( + 'Source subgraph must have at least one immutable entity. This subgraph cannot be used as a source subgraph since it has no immutable entities.', + { exit: 1 }, + ); + } } catch (e) { this.error(`Failed to load and parse subgraph schema: ${e.message}`, { exit: 1 }); } From 1db6d32fccb6bb768e19b07be8848a9864cc9e1f Mon Sep 17 00:00:00 2001 From: incrypto32 Date: Tue, 1 Apr 2025 16:22:11 +0400 Subject: [PATCH 4/6] Add changeset --- .changeset/short-coins-deny.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/short-coins-deny.md diff --git a/.changeset/short-coins-deny.md b/.changeset/short-coins-deny.md new file mode 100644 index 000000000..f5557efd6 --- /dev/null +++ b/.changeset/short-coins-deny.md @@ -0,0 +1,6 @@ +--- +'@graphprotocol/graph-cli': minor +'@graphprotocol/graph-ts': minor +--- + +Composed subgraphs are modified to only accept immutable entites as triggers from a source subgraph From 804aed5189a9e1f49775072edc27bee662a6ffc0 Mon Sep 17 00:00:00 2001 From: incrypto32 Date: Tue, 1 Apr 2025 19:26:46 +0400 Subject: [PATCH 5/6] remove non entity type test for getImmutableEntityNames --- packages/cli/src/schema.test.ts | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/packages/cli/src/schema.test.ts b/packages/cli/src/schema.test.ts index 1fd361d3e..668267510 100644 --- a/packages/cli/src/schema.test.ts +++ b/packages/cli/src/schema.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from 'vitest'; +import { beforeEach, describe, expect, test } from 'vitest'; import Schema from './schema.js'; describe('Schema', () => { @@ -16,34 +16,29 @@ describe('Schema', () => { } `; - test('getEntityNames returns all entity types', async () => { - const schema = await Schema.loadFromString(schemaDocument); + let schema: Schema; + + beforeEach(async () => { + schema = await Schema.loadFromString(schemaDocument); + }); + + test('getEntityNames returns all entity types', () => { const entityNames = schema.getEntityNames(); expect(entityNames).toEqual(['Entity1', 'Entity2', 'Entity3']); }); - test('getImmutableEntityNames returns only immutable entity types', async () => { - const schema = await Schema.loadFromString(schemaDocument); + test('getImmutableEntityNames returns only immutable entity types', () => { const immutableEntityNames = schema.getImmutableEntityNames(); expect(immutableEntityNames).toEqual(['Entity2']); }); - test('getImmutableEntityNames handles entities without immutable flag', async () => { - const schema = await Schema.loadFromString(schemaDocument); + test('getImmutableEntityNames handles entities without immutable flag', () => { const immutableEntityNames = schema.getImmutableEntityNames(); expect(immutableEntityNames).not.toContain('Entity1'); }); - test('getImmutableEntityNames handles explicitly non-immutable entities', async () => { - const schema = await Schema.loadFromString(schemaDocument); - const immutableEntityNames = schema.getImmutableEntityNames(); - expect(immutableEntityNames).not.toContain('Entity3'); - }); - - test('getImmutableEntityNames ignores non-entity types', async () => { - const schema = await Schema.loadFromString(schemaDocument); + test('getImmutableEntityNames handles explicitly non-immutable entities', () => { const immutableEntityNames = schema.getImmutableEntityNames(); - expect(immutableEntityNames).not.toContain('Entity1'); expect(immutableEntityNames).not.toContain('Entity3'); }); }); From 60b4f7148086fb1a4a6b4bb9067084bc0e55e18b Mon Sep 17 00:00:00 2001 From: incrypto32 Date: Wed, 2 Apr 2025 22:21:35 +0400 Subject: [PATCH 6/6] remove graph-ts from changeset --- .changeset/short-coins-deny.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/short-coins-deny.md b/.changeset/short-coins-deny.md index f5557efd6..20e8f440c 100644 --- a/.changeset/short-coins-deny.md +++ b/.changeset/short-coins-deny.md @@ -1,6 +1,5 @@ --- '@graphprotocol/graph-cli': minor -'@graphprotocol/graph-ts': minor --- Composed subgraphs are modified to only accept immutable entites as triggers from a source subgraph