Repository: graphprotocol/graph-tooling (ts)

Issue:
[BUG] camelCase to snake-case conversion issue during `graph init`
### Which packages are impacted by your issue?

@graphprotocol/graph-cli

### Describe the issue

I attempted to deploy a subgraph on Moonbeam on a live demo today and ran into this error:

```
✖ Failed to deploy to Graph node https://api.studio.thegraph.com/deploy/: Could not deploy subgraph on graph-node: subgraph deployment error: store error: The `id` field has type `BigInt` but only `String`, `Bytes`, `Int8`, and `ID` are allowed. Deployment: QmRkH3mWEwUxAtg7CHkuVi4sozH8boj2pxkiWFxKGBYryb
UNCAUGHT EXCEPTION: Error: EEXIT: 1
```
Smart contract address: `0xb89279DEd0AD7cD573D164b03424E1E2BbE1e2Be`

The subgraph was an init subgraph and all the schema entity ids are `Bytes`.

`graph-cli` is `0.73.0`.

I was able to deploy a subgraph that was indexing an ethereum smart contract right afterwards without issue.

@lutter provided context to this issue:

"I see the schema has fields like _id: BigInt (e.g. on EnteredGame) That causes the issue: we internally convert GraphQL field names from camel case to snake case (fieldName -> field_name) The library we use for that unfortunately translates _id to id and that makes graph-node think you have a id: BigInt in your schema. Try renaming those fields, and things should work."


### Reproduction

graph-cli

### Steps to Reproduce the Bug or Issue

See issue description ⬆️ 

### Expected behavior

graph-cli should continue through its `init` process.

If this error is unavoidable at this time, an error code could display such as:

```
Failed to deploy to Graph node: Could not deploy subgraph on graph-node: subgraph deployment error: store error: The `id` field has type `BigInt` but only `String`, `Bytes`, `Int8`, and `ID` are allowed.

Cause: Schema has fields like _id: BigInt, incorrectly converted to id by our internal library, making the graph-node think there is an id: BigInt.

Fix: Rename entity fields to avoid starting with an underscore (_). For example, rename _id to id.

UNCAUGHT EXCEPTION: Error: EEXIT: 1
```

### Screenshots or Videos

_No response_

### Platform

- OS: MacOS
- NodeJS: v20.13.1
- `@graphprotocol/graph-cli v0.73.0`


### Subgraph Manifest

_No response_

### Subgraph GraphQL Schema

_No response_

### Additional context

_No response_

Pull request:
Fix `_id` field clash
Fixes #1679 

- Added "_id" to the remap dictionary. 
- Simplified the remap logic since adding subgraph name to the field doesn't make much sense. Replacing `id` with `internal_id` is more logical and explicit
- Added `id` field to tests

Patch:
diff --git a/.changeset/short-keys-boil.md b/.changeset/short-keys-boil.md
new file mode 100644
index 00000000..f10fd68f
--- /dev/null
+++ b/.changeset/short-keys-boil.md
@@ -0,0 +1,5 @@
+---
+'@graphprotocol/graph-cli': patch
+---
+
+fix bug with clashing \_id field name in schema
diff --git a/packages/cli/src/scaffold/mapping.ts b/packages/cli/src/scaffold/mapping.ts
index 56c50dce..60244a92 100644
--- a/packages/cli/src/scaffold/mapping.ts
+++ b/packages/cli/src/scaffold/mapping.ts
@@ -13,29 +13,23 @@ export const generateFieldAssignments = ({ index, input }: { index: number; inpu
         [input.name || `param${index}`],
       );
 
-type BlacklistDictionary = Record<string, string>;
-
 /**
  * Map of input names that are reserved so we do not use them as field names to avoid conflicts
+ * see https://github.com/graphprotocol/graph-tooling/issues/710
+ * name => mappedName
  */
-export const INPUT_NAMES_BLACKLIST = {
-  /** Related to https://github.com/graphprotocol/graph-tooling/issues/710 */
-  id: 'id',
+const NAMES_REMAP_DICTIONARY: Record<string, string> = {
+  id: 'internal_id',
+  _id: 'internal__id',
 } as const;
 
-export const renameInput = (name: string, subgraphName: string) => {
-  const inputMap: BlacklistDictionary = {
-    [INPUT_NAMES_BLACKLIST.id]: `${subgraphName}_id`,
-  };
-
-  return inputMap?.[name] ?? name;
+export const renameNameIfNeeded = (name: string) => {
+  return NAMES_REMAP_DICTIONARY[name] ?? name;
 };
 
-export const generateEventFieldAssignments = (event: any, contractName: string) =>
+export const generateEventFieldAssignments = (event: any, _contractName: string) =>
   event.inputs.reduce((acc: any[], input: any, index: number) => {
-    if (Object.values(INPUT_NAMES_BLACKLIST).includes(input.name)) {
-      input.mappedName = renameInput(input.name, contractName ?? 'contract');
-    }
+    input.mappedName = renameNameIfNeeded(input.name);
     return acc.concat(generateFieldAssignments({ input, index }));
   }, []);
 
diff --git a/packages/cli/src/scaffold/schema.ts b/packages/cli/src/scaffold/schema.ts
index 8f2d4c82..e3b00b9b 100644
--- a/packages/cli/src/scaffold/schema.ts
+++ b/packages/cli/src/scaffold/schema.ts
@@ -2,7 +2,7 @@ import immutable from 'immutable';
 import { ascTypeForProtocol, valueTypeForAsc } from '../codegen/types/index.js';
 import * as util from '../codegen/util.js';
 import Protocol from '../protocols/index.js';
-import { INPUT_NAMES_BLACKLIST, renameInput } from './mapping.js';
+import { renameNameIfNeeded } from './mapping.js';
 
 export function abiEvents(abi: { data: immutable.Collection<any, any> }) {
   return util.disambiguateNames({
@@ -73,9 +73,7 @@ export const generateEventType = (
         id: Bytes!
         ${event.inputs
           .reduce((acc: any[], input: any, index: number) => {
-            if (Object.values(INPUT_NAMES_BLACKLIST).includes(input.name)) {
-              input.name = renameInput(input.name, contractName ?? 'contract');
-            }
+            input.name = renameNameIfNeeded(input.name);
             return acc.concat(generateEventFields({ input, index, protocolName }));
           }, [])
           .join('\n')}


Test patch:
diff --git a/packages/cli/src/scaffold/ethereum.test.ts b/packages/cli/src/scaffold/ethereum.test.ts
index 1f84f8e0..9c9eb2a6 100644
--- a/packages/cli/src/scaffold/ethereum.test.ts
+++ b/packages/cli/src/scaffold/ethereum.test.ts
@@ -26,6 +26,7 @@ const TEST_EVENT = {
       ],
     },
     { name: 'd', type: 'string', indexed: true },
+    { name: 'id', type: 'string' },
   ],
 };
 
@@ -106,7 +107,7 @@ dataSources:
         - name: Contract
           file: ./abis/Contract.json
       eventHandlers:
-        - event: ExampleEvent(indexed uint256,bytes[4],string,(uint256,bytes32,string,(uint96,string,bytes32)),indexed string)
+        - event: ExampleEvent(indexed uint256,bytes[4],string,(uint256,bytes32,string,(uint96,string,bytes32)),indexed string,string)
           handler: handleExampleEvent
         - event: ExampleEvent(bytes32)
           handler: handleExampleEvent1
@@ -139,6 +140,7 @@ type ExampleEvent @entity(immutable: true) {
   c_c3_value1: String! # string
   c_c3_value2: Bytes! # bytes32
   d: String! # string
+  internal_id: String! # string
   blockNumber: BigInt!
   blockTimestamp: BigInt!
   transactionHash: Bytes!
@@ -233,6 +235,7 @@ export function handleExampleEvent(event: ExampleEventEvent): void {
   entity.c_c3_value1 = event.params.c.c3.value1
   entity.c_c3_value2 = event.params.c.c3.value2
   entity.d = event.params.d
+  entity.internal_id = event.params.id
 
   entity.blockNumber = event.block.number
   entity.blockTimestamp = event.block.timestamp
@@ -285,7 +288,8 @@ describe("Describe entity assertions", () => {
     let param2 = "Example string value"
     let c = "ethereum.Tuple Not implemented"
     let d = "Example string value"
-    let newExampleEventEvent = createExampleEventEvent(a, b, param2, c, d)
+    let id = "Example string value"
+    let newExampleEventEvent = createExampleEventEvent(a, b, param2, c, d, id)
     handleExampleEvent(newExampleEventEvent)
   })
 
@@ -346,7 +350,8 @@ export function createExampleEventEvent(
   b: Array<Bytes>,
   param2: string,
   c: ethereum.Tuple,
-  d: string
+  d: string,
+  id: string
 ): ExampleEvent {
   let exampleEventEvent = changetype<ExampleEvent>(newMockEvent())
 
@@ -367,6 +372,9 @@ export function createExampleEventEvent(
   exampleEventEvent.parameters.push(
     new ethereum.EventParam("d", ethereum.Value.fromString(d))
   )
+  exampleEventEvent.parameters.push(
+    new ethereum.EventParam("id", ethereum.Value.fromString(id))
+  )
 
   return exampleEventEvent
 }
@@ -415,7 +423,8 @@ describe("Describe entity assertions", () => {
     let param2 = "Example string value"
     let c = "ethereum.Tuple Not implemented"
     let d = "Example string value"
-    let newExampleEventEvent = createExampleEventEvent(a, b, param2, c, d)
+    let id = "Example string value"
+    let newExampleEventEvent = createExampleEventEvent(a, b, param2, c, d, id)
     handleExampleEvent(newExampleEventEvent)
   })
 
@@ -476,7 +485,8 @@ export function createExampleEventEvent(
   b: Array<Bytes>,
   param2: string,
   c: ethereum.Tuple,
-  d: string
+  d: string,
+  id: string
 ): ExampleEvent {
   let exampleEventEvent = changetype<ExampleEvent>(newMockEvent())
 
@@ -497,6 +507,9 @@ export function createExampleEventEvent(
   exampleEventEvent.parameters.push(
     new ethereum.EventParam("d", ethereum.Value.fromString(d))
   )
+  exampleEventEvent.parameters.push(
+    new ethereum.EventParam("id", ethereum.Value.fromString(id))
+  )
 
   return exampleEventEvent
 }