UNPKG

@graphprotocol/graph-cli

Version:

CLI for building for and deploying to The Graph

649 lines (636 loc) • 26.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const assert_1 = __importDefault(require("assert")); const graphql = __importStar(require("graphql/language")); const prettier_1 = __importDefault(require("prettier")); const vitest_1 = require("vitest"); const schema_1 = __importDefault(require("../schema")); const schema_2 = __importDefault(require("./schema")); const typescript_1 = require("./typescript"); const formatTS = async (code) => await prettier_1.default.format(code, { parser: 'typescript', semi: false }); const createSchemaCodeGen = (schema) => new schema_2.default(new schema_1.default('', schema, graphql.parse(schema))); const testEntity = async (generatedTypes, expectedEntity) => { const entity = generatedTypes.find(type => type.name === expectedEntity.name); (0, vitest_1.expect)(entity instanceof typescript_1.Class).toBe(true); (0, vitest_1.expect)(entity.extends).toBe('Entity'); (0, vitest_1.expect)(entity.export).toBe(true); const { members, methods } = entity; (0, vitest_1.expect)(members).toStrictEqual(expectedEntity.members); for (const expectedMethod of expectedEntity.methods) { const method = methods.find((method) => method.name === expectedMethod.name); // eslint-disable-next-line @typescript-eslint/no-unused-expressions expectedMethod.static ? (0, vitest_1.expect)(method instanceof typescript_1.StaticMethod).toBe(true) : (0, vitest_1.expect)(method instanceof typescript_1.Method).toBe(true); (0, vitest_1.expect)(method.params).toStrictEqual(expectedMethod.params); (0, vitest_1.expect)(method.returnType).toStrictEqual(expectedMethod.returnType); (0, vitest_1.expect)(await formatTS(method.body)).toBe(await formatTS(expectedMethod.body)); } (0, vitest_1.expect)(methods.length).toBe(expectedEntity.methods.length); }; vitest_1.describe.concurrent('Schema code generator', () => { (0, vitest_1.test)('Should generate nothing for non entity types', () => { const codegen = createSchemaCodeGen(` type Foo { foobar: Int } type Bar { barfoo: Int } `); (0, vitest_1.expect)(codegen.generateTypes().length).toBe(0); }); (0, vitest_1.describe)('Should generate correct classes for each entity', () => { const codegen = createSchemaCodeGen(` # just to be sure nothing will be generated from non-entity types alongside regular ones type Foo { foobar: Int } type Account @entity { id: ID! # two non primitive types description: String name: String! # two primitive types (i32) age: Int count: Int! isActive: Boolean # derivedFrom wallets: [Wallet!] @derivedFrom(field: "account") # New scalars int8: Int8! } type Wallet @entity { id: ID! amount: BigInt! account: Account! } `); const generatedTypes = codegen.generateTypes(); (0, vitest_1.test)('Foo is NOT an entity', () => { const foo = generatedTypes.find((type) => type.name === 'Foo'); (0, vitest_1.expect)(foo).toBe(undefined); // Account and Wallet (0, vitest_1.expect)(generatedTypes.length).toBe(2); }); (0, vitest_1.test)('Account is an entity with the correct methods', async () => { await testEntity(generatedTypes, { name: 'Account', members: [], methods: [ { name: 'constructor', params: [new typescript_1.Param('id', new typescript_1.NamedType('string'))], returnType: undefined, body: ` super() this.set('id', Value.fromString(id)) `, }, { name: 'save', params: [], returnType: new typescript_1.NamedType('void'), body: ` let id = this.get('id') assert(id != null, 'Cannot save Account entity without an ID') if (id) { assert( id.kind == ValueKind.STRING, \`Entities of type Account must have an ID of type String but the id '\${id.displayData()}' is of type \${id.displayKind()}\`) store.set('Account', id.toString(), this) } `, }, { name: 'loadInBlock', static: true, params: [new typescript_1.Param('id', new typescript_1.NamedType('string'))], returnType: new typescript_1.NullableType(new typescript_1.NamedType('Account')), body: ` return changetype<Account | null>(store.get_in_block('Account', id)) `, }, { name: 'load', static: true, params: [new typescript_1.Param('id', new typescript_1.NamedType('string'))], returnType: new typescript_1.NullableType(new typescript_1.NamedType('Account')), body: ` return changetype<Account | null>(store.get('Account', id)) `, }, { name: 'get id', params: [], returnType: new typescript_1.NamedType('string'), body: `let value = this.get('id') if (!value || value.kind == ValueKind.NULL) { throw new Error("Cannot return null for a required field.") } else { return value.toString() } `, }, { name: 'set id', params: [new typescript_1.Param('value', new typescript_1.NamedType('string'))], returnType: undefined, body: ` this.set('id', Value.fromString(value)) `, }, { name: 'get description', params: [], returnType: new typescript_1.NullableType(new typescript_1.NamedType('string')), body: ` let value = this.get('description') if (!value || value.kind == ValueKind.NULL) { return null } else { return value.toString() } `, }, { name: 'set description', params: [new typescript_1.Param('value', new typescript_1.NullableType(new typescript_1.NamedType('string')))], returnType: undefined, body: ` if (!value) { this.unset('description') } else { this.set('description', Value.fromString(<string>value)) } `, }, { name: 'get name', params: [], returnType: new typescript_1.NamedType('string'), body: `let value = this.get('name') if (!value || value.kind == ValueKind.NULL) { throw new Error("Cannot return null for a required field.") } else { return value.toString() } `, }, { name: 'set name', params: [new typescript_1.Param('value', new typescript_1.NamedType('string'))], returnType: undefined, body: ` this.set('name', Value.fromString(value)) `, }, { name: 'get age', params: [], returnType: new typescript_1.NamedType('i32'), body: `let value = this.get('age') if (!value || value.kind == ValueKind.NULL) { return 0 } else { return value.toI32() } `, }, { name: 'set age', params: [new typescript_1.Param('value', new typescript_1.NamedType('i32'))], returnType: undefined, body: ` this.set('age', Value.fromI32(value)) `, }, { name: 'get count', params: [], returnType: new typescript_1.NamedType('i32'), body: `let value = this.get('count') if (!value || value.kind == ValueKind.NULL) { return 0 } else { return value.toI32() } `, }, { name: 'set count', params: [new typescript_1.Param('value', new typescript_1.NamedType('i32'))], returnType: undefined, body: ` this.set('count', Value.fromI32(value)) `, }, { name: 'get isActive', params: [], returnType: new typescript_1.NamedType('boolean'), body: `let value = this.get('isActive') if (!value || value.kind == ValueKind.NULL) { return false } else { return value.toBoolean() } `, }, { name: 'set isActive', params: [new typescript_1.Param('value', new typescript_1.NamedType('boolean'))], returnType: undefined, body: ` this.set('isActive', Value.fromBoolean(value)) `, }, { name: 'get int8', params: [], returnType: new typescript_1.NamedType('i64'), body: `let value = this.get('int8') if (!value || value.kind == ValueKind.NULL) { return 0 } else { return value.toI64() } `, }, { name: 'set int8', params: [new typescript_1.Param('value', new typescript_1.NamedType('i64'))], returnType: undefined, body: ` this.set('int8', Value.fromI64(value)) `, }, { name: 'get wallets', params: [], returnType: new typescript_1.NamedType('WalletLoader'), body: ` return new WalletLoader("Account", this.get('id')!.toString(), "wallets") `, }, ], }); }); (0, vitest_1.test)('Wallet is an entity with the correct methods', async () => { await testEntity(generatedTypes, { name: 'Wallet', members: [], methods: [ { name: 'constructor', params: [new typescript_1.Param('id', new typescript_1.NamedType('string'))], returnType: undefined, body: ` super() this.set('id', Value.fromString(id)) `, }, { name: 'save', params: [], returnType: new typescript_1.NamedType('void'), body: ` let id = this.get('id') assert(id != null, 'Cannot save Wallet entity without an ID') if (id) { assert( id.kind == ValueKind.STRING, \`Entities of type Wallet must have an ID of type String but the id '\${id.displayData()}' is of type \${id.displayKind()}\`) store.set('Wallet', id.toString(), this) } `, }, { name: 'loadInBlock', static: true, params: [new typescript_1.Param('id', new typescript_1.NamedType('string'))], returnType: new typescript_1.NullableType(new typescript_1.NamedType('Wallet')), body: ` return changetype<Wallet | null>(store.get_in_block('Wallet', id)) `, }, { name: 'load', static: true, params: [new typescript_1.Param('id', new typescript_1.NamedType('string'))], returnType: new typescript_1.NullableType(new typescript_1.NamedType('Wallet')), body: ` return changetype<Wallet | null>(store.get('Wallet', id)) `, }, { name: 'get id', params: [], returnType: new typescript_1.NamedType('string'), body: `let value = this.get('id') if (!value || value.kind == ValueKind.NULL) { throw new Error("Cannot return null for a required field.") } else { return value.toString() } `, }, { name: 'set id', params: [new typescript_1.Param('value', new typescript_1.NamedType('string'))], returnType: undefined, body: ` this.set('id', Value.fromString(value)) `, }, { name: 'get amount', params: [], returnType: new typescript_1.NamedType('BigInt'), body: `let value = this.get('amount') if (!value || value.kind == ValueKind.NULL) { throw new Error("Cannot return null for a required field.") } else { return value.toBigInt() } `, }, { name: 'set amount', params: [new typescript_1.Param('value', new typescript_1.NamedType('BigInt'))], returnType: undefined, body: ` this.set('amount', Value.fromBigInt(value)) `, }, { name: 'get account', params: [], returnType: new typescript_1.NamedType('string'), body: `let value = this.get('account') if (!value || value.kind == ValueKind.NULL) { throw new Error("Cannot return null for a required field.") } else { return value.toString() } `, }, { name: 'set account', params: [new typescript_1.Param('value', new typescript_1.NamedType('string'))], returnType: undefined, body: ` this.set('account', Value.fromString(value)) `, }, ], }); }); }); (0, vitest_1.test)('Should handle references with Bytes id types', async () => { const codegen = createSchemaCodeGen(` interface Employee { id: Bytes! name: String! } type Worker implements Employee @entity { id: Bytes! name: String! tasks: [Task!] } type Task @entity { id: Bytes! employee: Employee! workers: [Worker!] @derivedFrom(field: "tasks") worker: Worker! } `); const generatedTypes = codegen.generateTypes(); await testEntity(generatedTypes, { name: 'Task', members: [], methods: [ { name: 'constructor', params: [new typescript_1.Param('id', new typescript_1.NamedType('Bytes'))], returnType: undefined, body: "\n super()\n this.set('id', Value.fromBytes(id))\n ", }, { name: 'save', params: [], returnType: new typescript_1.NamedType('void'), body: '\n' + " let id = this.get('id')\n" + ' assert(id != null,\n' + " 'Cannot save Task entity without an ID')\n" + ' if (id) {\n' + ' assert(id.kind == ValueKind.BYTES,\n' + " `Entities of type Task must have an ID of type Bytes but the id '${id.displayData()}' is of type ${id.displayKind()}`)\n" + " store.set('Task', id.toBytes().toHexString(), this)\n" + ' }', }, { name: 'loadInBlock', static: true, params: [new typescript_1.Param('id', new typescript_1.NamedType('Bytes'))], returnType: new typescript_1.NullableType(new typescript_1.NamedType('Task')), body: '\n' + " return changetype<Task | null>(store.get_in_block('Task', id.toHexString()))\n" + ' ', }, { name: 'load', static: true, params: [new typescript_1.Param('id', new typescript_1.NamedType('Bytes'))], returnType: new typescript_1.NullableType(new typescript_1.NamedType('Task')), body: '\n' + " return changetype<Task | null>(store.get('Task', id.toHexString()))\n" + ' ', }, { name: 'get id', params: [], returnType: new typescript_1.NamedType('Bytes'), body: `let value = this.get("id") if (!value || value.kind == ValueKind.NULL) { throw new Error("Cannot return null for a required field.") } else { return value.toBytes() }`, }, { name: 'set id', params: [new typescript_1.Param('value', new typescript_1.NamedType('Bytes'))], returnType: undefined, body: "\n this.set('id', Value.fromBytes(value))\n ", }, { name: 'get employee', params: [], returnType: new typescript_1.NamedType('Bytes'), body: `let value = this.get('employee') if (!value || value.kind == ValueKind.NULL) { throw new Error("Cannot return null for a required field.") } else { return value.toBytes() }`, }, { name: 'set employee', params: [new typescript_1.Param('value', new typescript_1.NamedType('Bytes'))], returnType: undefined, body: "\n this.set('employee', Value.fromBytes(value))\n ", }, { name: 'get worker', params: [], returnType: new typescript_1.NamedType('Bytes'), body: `let value = this.get('worker') if (!value || value.kind == ValueKind.NULL) { throw new Error("Cannot return null for a required field.") } else { return value.toBytes() }`, }, { name: 'set worker', params: [new typescript_1.Param('value', new typescript_1.NamedType('Bytes'))], returnType: undefined, body: "\n this.set('worker', Value.fromBytes(value))\n ", }, { name: 'get workers', params: [], returnType: new typescript_1.NamedType('WorkerLoader'), body: "\n return new WorkerLoader('Task', this.get('id')!.toBytes().toHexString(), 'workers')\n ", }, ], }); }); (0, vitest_1.test)('get related method for WithBytes entity', async () => { const codegen = createSchemaCodeGen(` type WithBytes @entity { id: Bytes! related: [RelatedBytes!]! @derivedFrom(field: "related") } type RelatedBytes @entity { id: ID! related: WithBytes! } `); const generatedTypes = codegen.generateTypes(); await testEntity(generatedTypes, { name: 'WithBytes', members: [], methods: [ { name: 'constructor', params: [new typescript_1.Param('id', new typescript_1.NamedType('Bytes'))], returnType: undefined, body: ` super() this.set('id', Value.fromBytes(id));`, }, { name: 'save', params: [], returnType: new typescript_1.NamedType('void'), body: ` let id = this.get('id'); assert(id != null, 'Cannot save WithBytes entity without an ID'); if (id) { assert(id.kind == ValueKind.BYTES, \`Entities of type WithBytes must have an ID of type Bytes but the id '\${id.displayData()}' is of type \${id.displayKind()}\`); store.set('WithBytes', id.toBytes().toHexString(), this); } `, }, { name: 'load', static: true, params: [new typescript_1.Param('id', new typescript_1.NamedType('Bytes'))], returnType: new typescript_1.NullableType(new typescript_1.NamedType('WithBytes')), body: `return changetype<WithBytes | null>(store.get('WithBytes', id.toHexString()));`, }, { name: 'loadInBlock', static: true, params: [new typescript_1.Param('id', new typescript_1.NamedType('Bytes'))], returnType: new typescript_1.NullableType(new typescript_1.NamedType('WithBytes')), body: `return changetype<WithBytes | null>(store.get_in_block('WithBytes', id.toHexString()));`, }, { name: 'get id', params: [], returnType: new typescript_1.NamedType('Bytes'), body: `let value = this.get("id") if (!value || value.kind == ValueKind.NULL) { throw new Error("Cannot return null for a required field.") } else { return value.toBytes() } `, }, { name: 'set id', params: [new typescript_1.Param('value', new typescript_1.NamedType('Bytes'))], returnType: undefined, body: `this.set('id', Value.fromBytes(value));`, }, { name: 'get related', params: [], returnType: new typescript_1.NamedType('RelatedBytesLoader'), body: `return new RelatedBytesLoader('WithBytes', this.get('id')!.toBytes().toHexString(), 'related');`, }, // Add any additional getters and setters for other fields if necessary ], }); }); (0, vitest_1.test)('no derived loader for interface', () => { const codegen = createSchemaCodeGen(` interface IExample { id: ID! main: Main! num: Int! } type Example1 implements IExample @entity { id: ID! main: Main! num: Int! } type Main @entity { id: ID! examples: [IExample!]! @derivedFrom(field: "main") } `); const generateDerivedLoaders = codegen.generateDerivedLoaders().filter(Boolean); (0, assert_1.default)(generateDerivedLoaders.length === 0); }); });