@graphprotocol/graph-cli
Version:
CLI for building for and deploying to The Graph
649 lines (636 loc) • 26.8 kB
JavaScript
"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 {
id: ID!
# two non primitive types
description: String
name: String!
# two primitive types (i32)
age: Int
count: Int!
isActive: Boolean
# derivedFrom
wallets: [Wallet!]
# New scalars
int8: Int8!
}
type Wallet {
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 {
id: Bytes!
name: String!
tasks: [Task!]
}
type Task {
id: Bytes!
employee: Employee!
workers: [Worker!]
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 {
id: Bytes!
related: [RelatedBytes!]!
}
type RelatedBytes {
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 {
id: ID!
main: Main!
num: Int!
}
type Main {
id: ID!
examples: [IExample!]!
}
`);
const generateDerivedLoaders = codegen.generateDerivedLoaders().filter(Boolean);
(0, assert_1.default)(generateDerivedLoaders.length === 0);
});
});