Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/short-coins-deny.md
Original file line number Diff line number Diff line change
@@ -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
13 changes: 10 additions & 3 deletions packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1253,7 +1253,7 @@ async function initSubgraphFromContract(
}
}

let entities: string[] | undefined;
let immutableEntities: string[] | undefined;

if (isComposedSubgraph) {
try {
Expand All @@ -1274,7 +1274,14 @@ 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();

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 });
}
Expand Down Expand Up @@ -1316,7 +1323,7 @@ async function initSubgraphFromContract(
startBlock,
node,
spkgPath,
entities,
entities: immutableEntities,
},
spinner,
);
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/src/protocols/subgraph/scaffold/mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}
}`,
)
Expand Down
44 changes: 44 additions & 0 deletions packages/cli/src/schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { beforeEach, 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!
}
`;

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', () => {
const immutableEntityNames = schema.getImmutableEntityNames();
expect(immutableEntityNames).toEqual(['Entity2']);
});

test('getImmutableEntityNames handles entities without immutable flag', () => {
const immutableEntityNames = schema.getImmutableEntityNames();
expect(immutableEntityNames).not.toContain('Entity1');
});

test('getImmutableEntityNames handles explicitly non-immutable entities', () => {
const immutableEntityNames = schema.getImmutableEntityNames();
expect(immutableEntityNames).not.toContain('Entity3');
});
});
20 changes: 20 additions & 0 deletions packages/cli/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
22 changes: 0 additions & 22 deletions packages/ts/common/collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,28 +457,6 @@ export class Entity extends TypedMap<string, Value> {
}
}

/**
* Common representation for entity triggers, this wraps the entity
* and has fields for the operation type and the entity type.
*/
export class EntityTrigger<T extends Entity> {
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.
*/
Expand Down
Loading