diff --git a/.changeset/@graphprotocol_graph-cli-1021-dependencies.md b/.changeset/@graphprotocol_graph-cli-1021-dependencies.md new file mode 100644 index 000000000..9bb046808 --- /dev/null +++ b/.changeset/@graphprotocol_graph-cli-1021-dependencies.md @@ -0,0 +1,5 @@ +--- +'@graphprotocol/graph-cli': patch +--- +dependencies updates: + - Removed dependency [`immutable@3.8.2` ↗︎](https://www.npmjs.com/package/immutable/v/3.8.2) (from `dependencies`) diff --git a/packages/cli/package.json b/packages/cli/package.json index c2a2d438a..7d984459f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -16,7 +16,6 @@ "glob": "7.1.6", "gluegun": "https://github.com/edgeandnode/gluegun#v4.3.1-pin-colors-dep", "graphql": "15.5.0", - "immutable": "3.8.2", "ipfs-http-client": "34.0.0", "jayson": "3.6.6", "js-yaml": "3.13.1", diff --git a/packages/cli/src/codegen/schema.js b/packages/cli/src/codegen/schema.js index e5ca92fd9..b9e2c3aba 100644 --- a/packages/cli/src/codegen/schema.js +++ b/packages/cli/src/codegen/schema.js @@ -1,25 +1,21 @@ -const immutable = require('immutable') - const tsCodegen = require('./typescript') const typesCodegen = require('./types') -const List = immutable.List - class IdField { - static BYTES = Symbol("Bytes") - static STRING = Symbol("String") + static BYTES = Symbol('Bytes') + static STRING = Symbol('String') constructor(idField) { - const typeName = idField.getIn(['type', 'type', 'name', 'value']) - this.kind = typeName === "Bytes" ? IdField.BYTES : IdField.STRING + const typeName = idField.type?.type?.name?.value + this.kind = typeName === 'Bytes' ? IdField.BYTES : IdField.STRING } typeName() { - return this.kind === IdField.BYTES ? "Bytes" : "string" + return this.kind === IdField.BYTES ? 'Bytes' : 'string' } gqlTypeName() { - return this.kind === IdField.BYTES ? "Bytes" : "String" + return this.kind === IdField.BYTES ? 'Bytes' : 'String' } tsNamedType() { @@ -27,28 +23,28 @@ class IdField { } tsValueFrom() { - return this.kind === IdField.BYTES ? "Value.fromBytes(id)" : "Value.fromString(id)" + return this.kind === IdField.BYTES ? 'Value.fromBytes(id)' : 'Value.fromString(id)' } tsValueKind() { - return this.kind === IdField.BYTES ? "ValueKind.BYTES" : "ValueKind.STRING" + return this.kind === IdField.BYTES ? 'ValueKind.BYTES' : 'ValueKind.STRING' } tsValueToString() { - return this.kind == IdField.BYTES ? "id.toBytes().toHexString()" : "id.toString()" + return this.kind == IdField.BYTES ? 'id.toBytes().toHexString()' : 'id.toString()' } tsToString() { - return this.kind == IdField.BYTES ? "id.toHexString()" : "id" + return this.kind == IdField.BYTES ? 'id.toHexString()' : 'id' } static fromFields(fields) { - const idField = fields.find(field => field.getIn(['name', 'value']) === 'id') + const idField = fields.find(field => field.name?.value === 'id') return new IdField(idField) } static fromTypeDef(def) { - return IdField.fromFields(def.get("fields")) + return IdField.fromFields(def.fields) } } @@ -81,29 +77,26 @@ module.exports = class SchemaCodeGenerator { } generateTypes() { - return this.schema.ast - .get('definitions') + return this.schema.ast.definitions .filter(def => this._isEntityTypeDefinition(def)) .map(def => this._generateEntityType(def)) } _isEntityTypeDefinition(def) { return ( - def.get('kind') === 'ObjectTypeDefinition' && - def - .get('directives') - .find(directive => directive.getIn(['name', 'value']) === 'entity') !== undefined + def.kind === 'ObjectTypeDefinition' && + def.directives.find(directive => directive.name?.value === 'entity') !== undefined ) } _isInterfaceDefinition(def) { - return def.get('kind') === 'InterfaceTypeDefinition' + return def.kind === 'InterfaceTypeDefinition' } _generateEntityType(def) { - let name = def.getIn(['name', 'value']) + let name = def.name?.value let klass = tsCodegen.klass(name, { export: true, extends: 'Entity' }) - const fields = def.get('fields') + const fields = def.fields const idField = IdField.fromFields(fields) // Generate and add a constructor @@ -113,11 +106,10 @@ module.exports = class SchemaCodeGenerator { this._generateStoreMethods(name, idField).forEach(method => klass.addMethod(method)) // Generate and add entity field getters and setters - def - .get('fields') + def.fields .reduce( (methods, field) => methods.concat(this._generateEntityFieldMethods(def, field)), - List(), + [], ) .forEach(method => klass.addMethod(method)) @@ -138,7 +130,7 @@ module.exports = class SchemaCodeGenerator { } _generateStoreMethods(entityName, idField) { - return List.of( + return [ tsCodegen.method( 'save', [], @@ -162,19 +154,19 @@ module.exports = class SchemaCodeGenerator { return changetype<${entityName} | null>(store.get('${entityName}', ${idField.tsToString()})) `, ), - ) + ] } _generateEntityFieldMethods(entityDef, fieldDef) { - return List([ + return [ this._generateEntityFieldGetter(entityDef, fieldDef), this._generateEntityFieldSetter(entityDef, fieldDef), - ]) + ] } _generateEntityFieldGetter(entityDef, fieldDef) { - let name = fieldDef.getIn(['name', 'value']) - let gqlType = fieldDef.get('type') + let name = fieldDef.name?.value + let gqlType = fieldDef.type let fieldValueType = this._valueTypeFromGraphQl(gqlType) let returnType = this._typeFromGraphQl(gqlType) let isNullable = returnType instanceof tsCodegen.NullableType @@ -198,25 +190,21 @@ module.exports = class SchemaCodeGenerator { } _generateEntityFieldSetter(entityDef, fieldDef) { - let name = fieldDef.getIn(['name', 'value']) - let gqlType = fieldDef.get('type') + let name = fieldDef.name?.value + let gqlType = fieldDef.type let fieldValueType = this._valueTypeFromGraphQl(gqlType) let paramType = this._typeFromGraphQl(gqlType) let isNullable = paramType instanceof tsCodegen.NullableType let paramTypeString = isNullable ? paramType.inner.toString() : paramType.toString() let isArray = paramType instanceof tsCodegen.ArrayType - if ( - isArray && - paramType.inner instanceof tsCodegen.NullableType - ) { + if (isArray && paramType.inner instanceof tsCodegen.NullableType) { let baseType = this._baseType(gqlType) throw new Error(` GraphQL schema can't have List's with Nullable members. Error in '${name}' field of type '[${baseType}]'. -Suggestion: add an '!' to the member type of the List, change from '[${baseType}]' to '[${baseType}!]'` - ) +Suggestion: add an '!' to the member type of the List, change from '[${baseType}]' to '[${baseType}!]'`) } let setNonNullable = ` @@ -242,12 +230,15 @@ Suggestion: add an '!' to the member type of the List, change from '[${baseType} } _resolveFieldType(gqlType) { - let typeName = gqlType.getIn(['name', 'value']) + let typeName = gqlType.name?.value // If this is a reference to another type, the field has the type of // the referred type's id field - const typeDef = this.schema.ast.get("definitions"). - find(def => (this._isEntityTypeDefinition(def) || this._isInterfaceDefinition(def)) && def.getIn(["name", "value"]) === typeName) + const typeDef = this.schema.ast.definitions.find( + def => + (this._isEntityTypeDefinition(def) || this._isInterfaceDefinition(def)) && + def.name?.value === typeName, + ) if (typeDef) { return IdField.fromTypeDef(typeDef).typeName() } else { @@ -260,10 +251,10 @@ Suggestion: add an '!' to the member type of the List, change from '[${baseType} * other entity types, this is the same as the type of the id of the * referred type, i.e., `string` or `Bytes`*/ _valueTypeFromGraphQl(gqlType) { - if (gqlType.get('kind') === 'NonNullType') { - return this._valueTypeFromGraphQl(gqlType.get('type'), false) - } else if (gqlType.get('kind') === 'ListType') { - return '[' + this._valueTypeFromGraphQl(gqlType.get('type')) + ']' + if (gqlType.kind === 'NonNullType') { + return this._valueTypeFromGraphQl(gqlType.type, false) + } else if (gqlType.kind === 'ListType') { + return '[' + this._valueTypeFromGraphQl(gqlType.type) + ']' } else { return this._resolveFieldType(gqlType) } @@ -272,20 +263,20 @@ Suggestion: add an '!' to the member type of the List, change from '[${baseType} /** Determine the base type of `gqlType` by removing any non-null * constraints and using the type of elements of lists */ _baseType(gqlType) { - if (gqlType.get('kind') === 'NonNullType') { - return this._baseType(gqlType.get('type')) - } else if (gqlType.get('kind') === 'ListType') { - return this._baseType(gqlType.get('type')) + if (gqlType.kind === 'NonNullType') { + return this._baseType(gqlType.type) + } else if (gqlType.kind === 'ListType') { + return this._baseType(gqlType.type) } else { - return gqlType.getIn(['name', 'value']) + return gqlType.name?.value } } _typeFromGraphQl(gqlType, nullable = true) { - if (gqlType.get('kind') === 'NonNullType') { - return this._typeFromGraphQl(gqlType.get('type'), false) - } else if (gqlType.get('kind') === 'ListType') { - let type = tsCodegen.arrayType(this._typeFromGraphQl(gqlType.get('type'))) + if (gqlType.kind === 'NonNullType') { + return this._typeFromGraphQl(gqlType.type, false) + } else if (gqlType.kind === 'ListType') { + let type = tsCodegen.arrayType(this._typeFromGraphQl(gqlType.type)) return nullable ? tsCodegen.nullableType(type) : type } else { // NamedType diff --git a/packages/cli/src/codegen/schema.test.js b/packages/cli/src/codegen/schema.test.js index 1a35dcdbd..922835fab 100644 --- a/packages/cli/src/codegen/schema.test.js +++ b/packages/cli/src/codegen/schema.test.js @@ -1,6 +1,5 @@ const prettier = require('prettier') const graphql = require('graphql/language') -const immutable = require('immutable') const SchemaCodeGenerator = require('./schema') const { Class, @@ -12,15 +11,11 @@ const { ArrayType, } = require('./typescript') -const formatTS = code => - prettier.format( - code, - { parser: 'typescript', semi: false } - ) +const formatTS = code => prettier.format(code, { parser: 'typescript', semi: false }) const createSchemaCodeGen = schema => new SchemaCodeGenerator({ - ast: immutable.fromJS(graphql.parse(schema)), + ast: graphql.parse(schema), }) const testEntity = (generatedTypes, expectedEntity) => { @@ -61,7 +56,7 @@ describe('Schema code generator', () => { } `) - expect(codegen.generateTypes().size).toBe(0) + expect(codegen.generateTypes().length).toBe(0) }) describe('Should generate correct classes for each entity', () => { @@ -99,7 +94,7 @@ describe('Schema code generator', () => { const foo = generatedTypes.find(type => type.name === 'Foo') expect(foo).toBe(undefined) // Account and Wallet - expect(generatedTypes.size).toBe(2) + expect(generatedTypes.length).toBe(2) }) test('Account is an entity with the correct methods', () => { @@ -248,7 +243,12 @@ describe('Schema code generator', () => { }, { name: 'set wallets', - params: [new Param('value', new NullableType(new ArrayType(new NamedType('string'))))], + params: [ + new Param( + 'value', + new NullableType(new ArrayType(new NamedType('string'))), + ), + ], returnType: undefined, body: ` if (!value) { @@ -377,20 +377,21 @@ describe('Schema code generator', () => { const generatedTypes = codegen.generateTypes() testEntity(generatedTypes, { - name: "Task", + name: 'Task', members: [], methods: [ { name: 'constructor', params: [new Param('id', new NamedType('Bytes'))], returnType: undefined, - body: "\n super()\n this.set('id', Value.fromBytes(id))\n " + body: "\n super()\n this.set('id', Value.fromBytes(id))\n ", }, { name: 'save', params: [], returnType: new NamedType('void'), - body: '\n' + + body: + '\n' + " let id = this.get('id')\n" + ' assert(id != null,\n' + " 'Cannot save Task entity without an ID')\n" + @@ -398,63 +399,67 @@ describe('Schema code generator', () => { ' 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: 'load', static: true, params: [new Param('id', new NamedType('Bytes'))], returnType: new NullableType(new NamedType('Task')), - body: '\n' + + body: + '\n' + " return changetype(store.get('Task', id.toHexString()))\n" + - ' ' + ' ', }, { name: 'get id', params: [], returnType: new NamedType('Bytes'), - body: '\n' + + body: + '\n' + " let value = this.get('id')\n" + ' return value!.toBytes()\n' + - ' ' + ' ', }, { name: 'set id', params: [new Param('value', new NamedType('Bytes'))], returnType: undefined, - body: "\n this.set('id', Value.fromBytes(value))\n " + body: "\n this.set('id', Value.fromBytes(value))\n ", }, { name: 'get employee', params: [], returnType: new NamedType('Bytes'), - body: '\n' + + body: + '\n' + " let value = this.get('employee')\n" + ' return value!.toBytes()\n' + - ' ' + ' ', }, { name: 'set employee', params: [new Param('value', new NamedType('Bytes'))], returnType: undefined, - body: "\n this.set('employee', Value.fromBytes(value))\n " + body: "\n this.set('employee', Value.fromBytes(value))\n ", }, { name: 'get worker', params: [], returnType: new NamedType('Bytes'), - body: '\n' + + body: + '\n' + " let value = this.get('worker')\n" + ' return value!.toBytes()\n' + - ' ' + ' ', }, { name: 'set worker', params: [new Param('value', new NamedType('Bytes'))], returnType: undefined, - body: "\n this.set('worker', Value.fromBytes(value))\n " - } - ] + body: "\n this.set('worker', Value.fromBytes(value))\n ", + }, + ], }) }) }) diff --git a/packages/cli/src/codegen/template.js b/packages/cli/src/codegen/template.js index 4a73f183a..53f4cff9a 100644 --- a/packages/cli/src/codegen/template.js +++ b/packages/cli/src/codegen/template.js @@ -1,4 +1,3 @@ -const immutable = require('immutable') const IpfsFileTemplateCodeGen = require('../protocols/ipfs/codegen/file_template') const tsCodegen = require('./typescript') @@ -29,7 +28,7 @@ module.exports = class DataSourceTemplateCodeGenerator { } generateTypes() { - return immutable.List([this._generateTemplateType()]) + return [this._generateTemplateType()] } _generateTemplateType() { diff --git a/packages/cli/src/codegen/types/conversions.js b/packages/cli/src/codegen/types/conversions.js index 0cbd2264b..72a4da48f 100644 --- a/packages/cli/src/codegen/types/conversions.js +++ b/packages/cli/src/codegen/types/conversions.js @@ -1,5 +1,3 @@ -const immutable = require('immutable') - /** * ethereum.Value -> AssemblyScript conversions */ @@ -55,29 +53,56 @@ const ETHEREUM_VALUE_TO_ASSEMBLYSCRIPT = [ // Multi dimensional arrays - [/^address\[([0-9]+)?\]\[([0-9]+)?\]$/, 'Array>', code => `${code}.toAddressMatrix()`], - [/^bool\[([0-9]+)?\]\[([0-9]+)?\]$/, 'Array>', code => `${code}.toBooleanMatrix()`], - [/^byte\[([0-9]+)?\]\[([0-9]+)?\]$/, 'Array>', code => `${code}.toBytesMatrix()`], + [ + /^address\[([0-9]+)?\]\[([0-9]+)?\]$/, + 'Array>', + code => `${code}.toAddressMatrix()`, + ], + [ + /^bool\[([0-9]+)?\]\[([0-9]+)?\]$/, + 'Array>', + code => `${code}.toBooleanMatrix()`, + ], + [ + /^byte\[([0-9]+)?\]\[([0-9]+)?\]$/, + 'Array>', + code => `${code}.toBytesMatrix()`, + ], [ /^bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32)?\[([0-9]+)?\]\[([0-9]+)?\]$/, 'Array>', code => `${code}.toBytesMatrix()`, ], - [/^int(8|16|24|32)\[([0-9]+)?\]\[([0-9]+)?\]$/, 'Array>', code => `${code}.toI32Matrix()`], - [/^uint(8|16|24)\[([0-9]+)?\]\[([0-9]+)?\]$/, 'Array>', code => `${code}.toI32Matrix()`], - [/^uint32\[([0-9]+)?\]\[([0-9]+)?\]$/, 'Array>', code => `${code}.toBigIntMatrix()`], + [ + /^int(8|16|24|32)\[([0-9]+)?\]\[([0-9]+)?\]$/, + 'Array>', + code => `${code}.toI32Matrix()`, + ], + [ + /^uint(8|16|24)\[([0-9]+)?\]\[([0-9]+)?\]$/, + 'Array>', + code => `${code}.toI32Matrix()`, + ], + [ + /^uint32\[([0-9]+)?\]\[([0-9]+)?\]$/, + 'Array>', + code => `${code}.toBigIntMatrix()`, + ], [ /^u?int(40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)\[([0-9]+)?\]\[([0-9]+)?\]$/, 'Array>', code => `${code}.toBigIntMatrix()`, ], - [/^string\[([0-9]+)?\]\[([0-9]+)?\]$/, 'Array>', code => `${code}.toStringMatrix()`], + [ + /^string\[([0-9]+)?\]\[([0-9]+)?\]$/, + 'Array>', + code => `${code}.toStringMatrix()`, + ], [ /^tuple\[([0-9]+)?\]\[([0-9]+)?\]$/, 'Array>', (code, type) => `${code}.toTupleMatrix<${type}>()`, ], - ] /** @@ -178,7 +203,7 @@ const ASSEMBLYSCRIPT_TO_ETHEREUM_VALUE = [ /^tuple\[([0-9]+)?\]$/, code => `ethereum.Value.fromTupleArray(${code})`, ], - + // Multi dimentional arrays [ @@ -282,9 +307,13 @@ const ASSEMBLYSCRIPT_TO_VALUE = [ ['Array>', '[[BigInt]]', code => `Value.fromBigIntMatrix(${code})`], ['Array>', '[[String]]', code => `Value.fromStringMatrix(${code})`], ['Array>', '[[ID]]', code => `Value.fromStringMatrix(${code})`], - ['Array>', '[[BigDecimal]]', code => `Value.fromBigDecimalMatrix(${code})`], + [ + 'Array>', + '[[BigDecimal]]', + code => `Value.fromBigDecimalMatrix(${code})`, + ], ['Array>', /\[\[.*\]\]/, code => `Value.fromStringMatrix(${code})`], - ['Array>', null, code => `Value.fromStringMatrix(${code})`],// is this overwriting the Array null below? + ['Array>', null, code => `Value.fromStringMatrix(${code})`], // is this overwriting the Array null below? // Arrays @@ -315,7 +344,7 @@ const ASSEMBLYSCRIPT_TO_VALUE = [ /** * Type conversions */ -module.exports = immutable.fromJS({ +module.exports = Object.freeze({ ethereum: { AssemblyScript: ETHEREUM_VALUE_TO_ASSEMBLYSCRIPT, }, diff --git a/packages/cli/src/codegen/types/index.js b/packages/cli/src/codegen/types/index.js index a6de197e2..e9d296e8d 100644 --- a/packages/cli/src/codegen/types/index.js +++ b/packages/cli/src/codegen/types/index.js @@ -1,11 +1,9 @@ -const immutable = require('immutable') - const TYPE_CONVERSIONS = require('./conversions') // Conversion utilities const conversionsForTypeSystems = (fromTypeSystem, toTypeSystem) => { - let conversions = TYPE_CONVERSIONS.getIn([fromTypeSystem, toTypeSystem]) + let conversions = TYPE_CONVERSIONS[fromTypeSystem]?.[toTypeSystem] if (conversions === undefined) { throw new Error( `Conversions from '${fromTypeSystem}' to '${toTypeSystem}' are not supported`, @@ -15,16 +13,16 @@ const conversionsForTypeSystems = (fromTypeSystem, toTypeSystem) => { } const objectifyConversion = (fromTypeSystem, toTypeSystem, conversion) => { - return immutable.fromJS({ + return Object.freeze({ from: { typeSystem: fromTypeSystem, - type: conversion.get(0), + type: conversion[0], }, to: { typeSystem: toTypeSystem, - type: conversion.get(1), + type: conversion[1], }, - convert: conversion.get(2), + convert: conversion[2], }) } @@ -32,9 +30,9 @@ const findConversionFromType = (fromTypeSystem, toTypeSystem, fromType) => { let conversions = conversionsForTypeSystems(fromTypeSystem, toTypeSystem) let conversion = conversions.find(conversion => - typeof conversion.get(0) === 'string' - ? conversion.get(0) === fromType - : fromType.match(conversion.get(0)), + typeof conversion[0] === 'string' + ? conversion[0] === fromType + : fromType.match(conversion[0]), ) if (conversion === undefined) { @@ -51,9 +49,9 @@ const findConversionToType = (fromTypeSystem, toTypeSystem, toType) => { let conversions = conversionsForTypeSystems(fromTypeSystem, toTypeSystem) let conversion = conversions.find(conversion => - typeof conversion.get(1) === 'string' - ? conversion.get(1) === toType - : toType.match(conversion.get(1)), + typeof conversion[1] === 'string' + ? conversion[1] === toType + : toType.match(conversion[1]), ) if (conversion === undefined) { @@ -69,35 +67,34 @@ const findConversionToType = (fromTypeSystem, toTypeSystem, toType) => { // High-level type system API const ascTypeForProtocol = (protocol, protocolType) => - findConversionFromType(protocol, 'AssemblyScript', protocolType).getIn(['to', 'type']) + findConversionFromType(protocol, 'AssemblyScript', protocolType).to?.type // TODO: this can be removed/replaced by the function above -const ascTypeForEthereum = ethereumType => - ascTypeForProtocol('ethereum', ethereumType) +const ascTypeForEthereum = ethereumType => ascTypeForProtocol('ethereum', ethereumType) const ethereumTypeForAsc = ascType => - findConversionFromType('AssemblyScript', 'ethereum', ascType).getIn(['to', 'type']) + findConversionFromType('AssemblyScript', 'ethereum', ascType).to?.type const ethereumToAsc = (code, ethereumType, internalType) => - findConversionFromType('ethereum', 'AssemblyScript', ethereumType).get('convert')( + findConversionFromType('ethereum', 'AssemblyScript', ethereumType).convert( code, internalType, ) const ethereumFromAsc = (code, ethereumType) => - findConversionToType('AssemblyScript', 'ethereum', ethereumType).get('convert')(code) + findConversionToType('AssemblyScript', 'ethereum', ethereumType).convert(code) const ascTypeForValue = valueType => - findConversionFromType('Value', 'AssemblyScript', valueType).getIn(['to', 'type']) + findConversionFromType('Value', 'AssemblyScript', valueType).to?.type const valueTypeForAsc = ascType => - findConversionFromType('AssemblyScript', 'Value', ascType).getIn(['to', 'type']) + findConversionFromType('AssemblyScript', 'Value', ascType).to?.type const valueToAsc = (code, valueType) => - findConversionFromType('Value', 'AssemblyScript', valueType).get('convert')(code) + findConversionFromType('Value', 'AssemblyScript', valueType).convert(code) const valueFromAsc = (code, valueType) => - findConversionToType('AssemblyScript', 'Value', valueType).get('convert')(code) + findConversionToType('AssemblyScript', 'Value', valueType).convert(code) module.exports = { // protocol <-> AssemblyScript diff --git a/packages/cli/src/codegen/typescript.js b/packages/cli/src/codegen/typescript.js index 487871a3a..616da5f3c 100644 --- a/packages/cli/src/codegen/typescript.js +++ b/packages/cli/src/codegen/typescript.js @@ -1,6 +1,3 @@ -let immutable = require('immutable') -let Map = immutable.Map - class Param { constructor(name, type) { this.name = name diff --git a/packages/cli/src/command-helpers/abi.js b/packages/cli/src/command-helpers/abi.js index b80c3ffc9..ce330244c 100644 --- a/packages/cli/src/command-helpers/abi.js +++ b/packages/cli/src/command-helpers/abi.js @@ -1,6 +1,5 @@ const { withSpinner } = require('./spinner') const fetch = require('node-fetch') -const immutable = require('immutable') const loadAbiFromEtherscan = async (ABI, network, address) => await withSpinner( @@ -18,7 +17,7 @@ const loadAbiFromEtherscan = async (ABI, network, address) => // a `result` field. The `status` is '0' in case of errors and '1' in // case of success if (json.status === '1') { - return new ABI('Contract', undefined, immutable.fromJS(JSON.parse(json.result))) + return new ABI('Contract', undefined, JSON.parse(json.result)) } else { throw new Error('ABI not found, try loading it from a local file') } @@ -42,7 +41,7 @@ const loadAbiFromBlockScout = async (ABI, network, address) => // a `result` field. The `status` is '0' in case of errors and '1' in // case of success if (json.status === '1') { - return new ABI('Contract', undefined, immutable.fromJS(JSON.parse(json.result))) + return new ABI('Contract', undefined, JSON.parse(json.result)) } else { throw new Error('ABI not found, try loading it from a local file') } diff --git a/packages/cli/src/command-helpers/data-sources.js b/packages/cli/src/command-helpers/data-sources.js index 859481d70..3ebd6a29c 100644 --- a/packages/cli/src/command-helpers/data-sources.js +++ b/packages/cli/src/command-helpers/data-sources.js @@ -1,4 +1,3 @@ -const immutable = require('immutable') const { loadManifest } = require('../migrations/util/load-manifest') // Loads manifest from file path and returns all: @@ -13,15 +12,15 @@ const fromFilePath = async manifestPath => { const extractDataSourceByType = (manifest, dataSourceType, protocol) => manifest - .get(dataSourceType, immutable.List()) + .get(dataSourceType, []) .reduce( (dataSources, dataSource, dataSourceIndex) => protocol.isValidKindName(dataSource.get('kind')) ? dataSources.push( - immutable.Map({ path: [dataSourceType, dataSourceIndex], dataSource }), + { path: [dataSourceType, dataSourceIndex], dataSource }, ) : dataSources, - immutable.List(), + [] ) // Extracts data sources and templates from a immutable manifest data structure diff --git a/packages/cli/src/command-helpers/scaffold.js b/packages/cli/src/command-helpers/scaffold.js index 890fa63b2..bc35580a3 100644 --- a/packages/cli/src/command-helpers/scaffold.js +++ b/packages/cli/src/command-helpers/scaffold.js @@ -9,7 +9,6 @@ const { generateEventIndexingHandlers } = require('../scaffold/mapping') const { generateEventType, abiEvents } = require('../scaffold/schema') const { generateTestsFiles } = require('../scaffold/tests') const { strings } = require('gluegun') -const { Map } = require('immutable') const generateDataSource = async ( protocol, @@ -20,15 +19,22 @@ const generateDataSource = async ( ) => { const protocolManifest = protocol.getManifestScaffold() - return Map.of( - 'kind', protocol.name, - 'name', contractName, - 'network', network, - 'source', yaml.parse(prettier.format(protocolManifest.source({contract: contractAddress, contractName}), - {parser: 'yaml'})), - 'mapping', yaml.parse(prettier.format(protocolManifest.mapping({abi, contractName}), - {parser: 'yaml'})) - ).asMutable() + return { + kind: protocol.name, + name: contractName, + network: network, + source: yaml.parse( + prettier.format( + protocolManifest.source({ contract: contractAddress, contractName }), + { parser: 'yaml' }, + ), + ), + mapping: yaml.parse( + prettier.format(protocolManifest.mapping({ abi, contractName }), { + parser: 'yaml', + }), + ), + } } const generateScaffold = async ( @@ -130,11 +136,9 @@ const writeMapping = async (abi, protocol, contractName, entities) => { const writeTestsFiles = async (abi, protocol, contractName) => { const hasEvents = protocol.hasEvents() - const events = hasEvents - ? abiEvents(abi).toJS() - : [] + const events = hasEvents ? abiEvents(abi).toJS() : [] - if(events.length > 0) { + if (events.length > 0) { // If a contract is added to a subgraph that has no tests folder await fs.ensureDir('./tests/') diff --git a/packages/cli/src/command-helpers/spinner.js b/packages/cli/src/command-helpers/spinner.js index 922f25b8f..2b8546872 100644 --- a/packages/cli/src/command-helpers/spinner.js +++ b/packages/cli/src/command-helpers/spinner.js @@ -1,5 +1,4 @@ const chalk = require('chalk') -const immutable = require('immutable') const toolbox = require('gluegun/toolbox') const step = (spinner, subject, text) => { diff --git a/packages/cli/src/commands/add.js b/packages/cli/src/commands/add.js index 3d5af3302..a20558f56 100644 --- a/packages/cli/src/commands/add.js +++ b/packages/cli/src/commands/add.js @@ -1,11 +1,16 @@ const chalk = require('chalk') const toolbox = require('gluegun/toolbox') -const immutable = require('immutable') const { withSpinner } = require('../command-helpers/spinner') const Subgraph = require('../subgraph') const Protocol = require('../protocols') const DataSourcesExtractor = require('../command-helpers/data-sources') -const { generateDataSource, writeABI, writeSchema, writeMapping, writeTestsFiles } = require('../command-helpers/scaffold') +const { + generateDataSource, + writeABI, + writeSchema, + writeMapping, + writeTestsFiles, +} = require('../command-helpers/scaffold') const { loadAbiFromEtherscan, loadAbiFromBlockScout } = require('../command-helpers/abi') const EthereumABI = require('../protocols/ethereum/abi') const { fixParameters } = require('../command-helpers/gluegun') @@ -36,7 +41,7 @@ module.exports = { h, help, mergeEntities, - networkFile + networkFile, } = toolbox.parameters.options contractName = contractName || 'Contract' @@ -54,7 +59,8 @@ module.exports = { } let address = toolbox.parameters.first || toolbox.parameters.array[0] - let manifestPath = toolbox.parameters.second || toolbox.parameters.array[1] || './subgraph.yaml' + let manifestPath = + toolbox.parameters.second || toolbox.parameters.array[1] || './subgraph.yaml' // Show help text if requested if (help || h) { @@ -72,7 +78,10 @@ module.exports = { const dataSourcesAndTemplates = await DataSourcesExtractor.fromFilePath(manifestPath) let protocol = Protocol.fromDataSources(dataSourcesAndTemplates) let manifest = await Subgraph.load(manifestPath, { protocol }) - let network = manifest.result.get('dataSources').get(0).get('network') + let network = manifest.result + .get('dataSources') + .get(0) + .get('network') let result = manifest.result.asMutable() let entities = getEntities(manifest) @@ -96,17 +105,27 @@ module.exports = { } } - let { collisionEntities, onlyCollisions, abiData } = updateEventNamesOnCollision(ethabi, entities, contractName, mergeEntities) + let { collisionEntities, onlyCollisions, abiData } = updateEventNamesOnCollision( + ethabi, + entities, + contractName, + mergeEntities, + ) ethabi.data = abiData await writeABI(ethabi, contractName) - await writeSchema(ethabi, protocol, result.getIn(['schema', 'file']), collisionEntities) + await writeSchema(ethabi, protocol, result.schema?.file, collisionEntities) await writeMapping(ethabi, protocol, contractName, collisionEntities) await writeTestsFiles(ethabi, protocol, contractName) let dataSources = result.get('dataSources') - let dataSource = await generateDataSource(protocol, - contractName, network, address, ethabi) + let dataSource = await generateDataSource( + protocol, + contractName, + network, + address, + ethabi, + ) // Handle the collisions edge case by copying another data source yaml data if (mergeEntities && onlyCollisions) { @@ -126,7 +145,7 @@ module.exports = { await Subgraph.write(result, manifestPath) // Update networks.json - const networksFile = networkFile || "./networks.json" + const networksFile = networkFile || './networks.json' await updateNetworksFile(toolbox, network, contractName, address, networksFile) // Detect Yarn and/or NPM @@ -146,28 +165,26 @@ module.exports = { 'Warning during codegen', async spinner => { await system.run(yarn ? 'yarn codegen' : 'npm run codegen') - } + }, ) - } + }, } -const getEntities = (manifest) => { - let dataSources = manifest.result.get('dataSources', immutable.List()) - let templates = manifest.result.get('templates', immutable.List()) +const getEntities = manifest => { + let dataSources = manifest.result.get('dataSources', []) + let templates = manifest.result.get('templates', []) return dataSources .concat(templates) - .map(dataSource => dataSource.getIn(['mapping', 'entities'])) + .map(dataSource => dataSource.mapping?.entities) .flatten() } -const getContractNames = (manifest) => { - let dataSources = manifest.result.get('dataSources', immutable.List()) - let templates = manifest.result.get('templates', immutable.List()) +const getContractNames = manifest => { + let dataSources = manifest.result.get('dataSources', []) + let templates = manifest.result.get('templates', []) - return dataSources - .concat(templates) - .map(dataSource => dataSource.get('name')) + return dataSources.concat(templates).map(dataSource => dataSource.get('name')) } const updateEventNamesOnCollision = (ethabi, entities, contractName, mergeEntities) => { @@ -179,7 +196,7 @@ const updateEventNamesOnCollision = (ethabi, entities, contractName, mergeEntiti for (let i = 0; i < abiData.size; i++) { let dataRow = abiData.get(i).asMutable() - if (dataRow.get('type') === 'event'){ + if (dataRow.get('type') === 'event') { if (entities.indexOf(dataRow.get('name')) !== -1) { if (entities.indexOf(`${contractName}${dataRow.get('name')}`) !== -1) { print.error(`Contract name ('${contractName}') @@ -190,7 +207,7 @@ const updateEventNamesOnCollision = (ethabi, entities, contractName, mergeEntiti if (mergeEntities) { collisionEntities.push(dataRow.get('name')) - abiData = abiData.asImmutable().delete(i) // needs to be immutable when deleting, yes you read that right - https://github.com/immutable-js/immutable-js/issues/1901 + abiData = abiData.delete(i) i-- // deletion also shifts values to the left continue } else { @@ -200,7 +217,7 @@ const updateEventNamesOnCollision = (ethabi, entities, contractName, mergeEntiti onlyCollisions = false } } - abiData = abiData.asMutable().set(i, dataRow) + abiData = abiData.set(i, dataRow) } return { abiData, collisionEntities, onlyCollisions } diff --git a/packages/cli/src/compiler/index.js b/packages/cli/src/compiler/index.js index bed99630e..40c398fbb 100644 --- a/packages/cli/src/compiler/index.js +++ b/packages/cli/src/compiler/index.js @@ -1,7 +1,6 @@ const chalk = require('chalk') const crypto = require('crypto') const fs = require('fs-extra') -const immutable = require('immutable') const path = require('path') const yaml = require('js-yaml') const toolbox = require('gluegun/toolbox') @@ -141,12 +140,12 @@ class Compiler { files.push(this.options.subgraphManifest) // Add all file paths specified in manifest - files.push(path.resolve(subgraph.getIn(['schema', 'file']))) + files.push(path.resolve(subgraph.schema?.file)) subgraph.get('dataSources').map(dataSource => { - files.push(dataSource.getIn(['mapping', 'file'])) + files.push(dataSource.mapping?.file) // Only watch ABI related files if the target protocol has support/need for them. if (this.protocol.hasABIs()) { - dataSource.getIn(['mapping', 'abis']).map(abi => { + dataSource.mapping?.abis.map(abi => { files.push(abi.get('file')) }) } @@ -258,7 +257,7 @@ class Compiler { } try { - let dataSourceName = dataSource.getIn(['name']) + let dataSourceName = dataSource.name let baseDir = this.sourceDir let absoluteMappingPath = path.resolve(baseDir, mappingPath) @@ -552,7 +551,7 @@ class Compiler { updates.push({ keyPath: ['schema', 'file'], value: await this._uploadFileToIPFS( - subgraph.getIn(['schema', 'file']), + subgraph.schema?.file, uploadedFiles, spinner, ), @@ -560,7 +559,7 @@ class Compiler { if (this.protocol.hasABIs()) { for (let [i, dataSource] of subgraph.get('dataSources').entries()) { - for (let [j, abi] of dataSource.getIn(['mapping', 'abis']).entries()) { + for (let [j, abi] of dataSource.mapping?.abis.entries()) { updates.push({ keyPath: ['dataSources', i, 'mapping', 'abis', j, 'file'], value: await this._uploadFileToIPFS( @@ -579,7 +578,7 @@ class Compiler { updates.push({ keyPath: ['dataSources', i, 'mapping', 'file'], value: await this._uploadFileToIPFS( - dataSource.getIn(['mapping', 'file']), + dataSource.mapping?.file, uploadedFiles, spinner, ), @@ -590,7 +589,7 @@ class Compiler { updates.push({ keyPath: ['dataSources', i, 'source', 'package', 'file'], value: await this._uploadFileToIPFS( - dataSource.getIn(['source', 'package', 'file']), + dataSource.source', 'package?.file, uploadedFiles, spinner, ), @@ -598,9 +597,9 @@ class Compiler { } } - for (let [i, template] of subgraph.get('templates', immutable.List()).entries()) { + for (let [i, template] of subgraph.get('templates', []).entries()) { if (this.protocol.hasABIs()) { - for (let [j, abi] of template.getIn(['mapping', 'abis']).entries()) { + for (let [j, abi] of template.mapping?.abis.entries()) { updates.push({ keyPath: ['templates', i, 'mapping', 'abis', j, 'file'], value: await this._uploadFileToIPFS( @@ -615,7 +614,7 @@ class Compiler { updates.push({ keyPath: ['templates', i, 'mapping', 'file'], value: await this._uploadFileToIPFS( - template.getIn(['mapping', 'file']), + template.mapping?.file, uploadedFiles, spinner, ), @@ -661,7 +660,7 @@ class Compiler { ' ..', `${hash}${alreadyUploaded ? ' (already uploaded)' : ''}`, ) - return immutable.fromJS({ '/': `/ipfs/${hash}` }) + return { '/': `/ipfs/${hash}` } } async _uploadSubgraphDefinitionToIPFS(subgraph) { diff --git a/packages/cli/src/protocols/arweave/subgraph.js b/packages/cli/src/protocols/arweave/subgraph.js index 4a738139f..c73c5f346 100644 --- a/packages/cli/src/protocols/arweave/subgraph.js +++ b/packages/cli/src/protocols/arweave/subgraph.js @@ -1,5 +1,3 @@ -const immutable = require('immutable') - module.exports = class ArweaveSubgraph { constructor(options = {}) { this.manifest = options.manifest @@ -8,13 +6,10 @@ module.exports = class ArweaveSubgraph { } validateManifest() { - return immutable.List() + return [] } handlerTypes() { - return immutable.List([ - 'blockHandlers', - 'transactionHandlers', - ]) + return ['blockHandlers', 'transactionHandlers'] } } diff --git a/packages/cli/src/protocols/cosmos/subgraph.js b/packages/cli/src/protocols/cosmos/subgraph.js index 5cf711eef..db2c36e31 100644 --- a/packages/cli/src/protocols/cosmos/subgraph.js +++ b/packages/cli/src/protocols/cosmos/subgraph.js @@ -1,5 +1,3 @@ -const immutable = require('immutable') - module.exports = class CosmosSubgraph { constructor(options = {}) { this.manifest = options.manifest @@ -8,15 +6,10 @@ module.exports = class CosmosSubgraph { } validateManifest() { - return immutable.List() + return [] } handlerTypes() { - return immutable.List([ - 'blockHandlers', - 'eventHandlers', - 'transactionHandlers', - 'messageHandlers', - ]) + return ['blockHandlers', 'eventHandlers', 'transactionHandlers', 'messageHandlers'] } } diff --git a/packages/cli/src/protocols/ethereum/abi.js b/packages/cli/src/protocols/ethereum/abi.js index 090800e25..a2ffcdb4e 100644 --- a/packages/cli/src/protocols/ethereum/abi.js +++ b/packages/cli/src/protocols/ethereum/abi.js @@ -1,5 +1,4 @@ const fs = require('fs-extra') -const immutable = require('immutable') const path = require('path') const AbiCodeGenerator = require('./codegen/abi') @@ -104,17 +103,17 @@ module.exports = class ABI { callFunctions() { // An entry is a function if its type is not set or if it is one of // 'constructor', 'function' or 'fallback' - let functionTypes = immutable.Set(['constructor', 'function', 'fallback']) + let functionTypes = new Set(['constructor', 'function', 'fallback']) let functions = this.data.filter( - entry => !entry.has('type') || functionTypes.includes(entry.get('type')), + entry => !entry.has('type') || functionTypes.has(entry.get('type')), ) // A function is a call function if it is nonpayable, payable or // not constant - let mutabilityTypes = immutable.Set(['nonpayable', 'payable']) + let mutabilityTypes =new Set(['nonpayable', 'payable']) return functions.filter( entry => - mutabilityTypes.includes(entry.get('stateMutability')) || + mutabilityTypes.has(entry.get('stateMutability')) || entry.get('constant') === false, ) } @@ -125,7 +124,7 @@ module.exports = class ABI { .map(entry => { const name = entry.get('name', '') const inputs = entry - .get('inputs', immutable.List()) + .get('inputs', []) .map(input => buildSignatureParameter(input)) return `${name}(${inputs.join(',')})` @@ -155,6 +154,6 @@ module.exports = class ABI { throw Error(`No valid ABI in file: ${path.relative(process.cwd(), file)}`) } - return new ABI(name, file, immutable.fromJS(abi)) + return new ABI(name, file, abi) } } diff --git a/packages/cli/src/protocols/ethereum/codegen/abi.js b/packages/cli/src/protocols/ethereum/codegen/abi.js index 984a31fb1..07861ea8c 100644 --- a/packages/cli/src/protocols/ethereum/codegen/abi.js +++ b/packages/cli/src/protocols/ethereum/codegen/abi.js @@ -1,14 +1,13 @@ -const immutable = require('immutable') const fs = require('fs') const yaml = require('yaml') const request = require('sync-request') -const Web3EthAbi = require('web3-eth-abi'); +const Web3EthAbi = require('web3-eth-abi') const tsCodegen = require('../../../codegen/typescript') const typesCodegen = require('../../../codegen/types') const util = require('../../../codegen/util') -const doFixtureCodegen = fs.existsSync('./fixtures.yaml'); +const doFixtureCodegen = fs.existsSync('./fixtures.yaml') module.exports = class AbiCodeGenerator { constructor(abi) { @@ -33,17 +32,12 @@ module.exports = class AbiCodeGenerator { 'BigInt', ], '@graphprotocol/graph-ts', - ) + ), ] if (doFixtureCodegen) { imports.push( - tsCodegen.moduleImports( - [ - 'newMockEvent', - ], - 'matchstick-as/assembly/index', - ) + tsCodegen.moduleImports(['newMockEvent'], 'matchstick-as/assembly/index'), ) } @@ -66,106 +60,104 @@ module.exports = class AbiCodeGenerator { setName: (fn, name) => fn.set('_alias', name), }) - callFunctions = callFunctions - .map(fn => { - let fnAlias = fn.get('_alias') - let fnClassName = `${fnAlias.charAt(0).toUpperCase()}${fnAlias.slice(1)}Call` - let tupleClasses = [] - - // First, generate a class with the input getters - let inputsClassName = fnClassName + '__Inputs' - let inputsClass = tsCodegen.klass(inputsClassName, { export: true }) - inputsClass.addMember(tsCodegen.klassMember('_call', fnClassName)) - inputsClass.addMethod( - tsCodegen.method( - `constructor`, - [tsCodegen.param(`call`, fnClassName)], - null, - `this._call = call`, - ), - ) + callFunctions = callFunctions.map(fn => { + let fnAlias = fn.get('_alias') + let fnClassName = `${fnAlias.charAt(0).toUpperCase()}${fnAlias.slice(1)}Call` + let tupleClasses = [] - // Generate getters and classes for function inputs - util - .disambiguateNames({ - values: fn.get('inputs', immutable.List()), - getName: (input, index) => input.get('name') || `value${index}`, - setName: (input, name) => input.set('name', name), - }) - .forEach((input, index) => { - let callInput = this._generateInputOrOutput( - input, - index, - fnClassName, - `call`, - `inputValues`, - ) - inputsClass.addMethod(callInput.getter) - tupleClasses.push(...callInput.classes) - }) - - // Second, generate a class with the output getters - let outputsClassName = fnClassName + '__Outputs' - let outputsClass = tsCodegen.klass(outputsClassName, { export: true }) - outputsClass.addMember(tsCodegen.klassMember('_call', fnClassName)) - outputsClass.addMethod( - tsCodegen.method( - `constructor`, - [tsCodegen.param(`call`, fnClassName)], - null, - `this._call = call`, - ), - ) + // First, generate a class with the input getters + let inputsClassName = fnClassName + '__Inputs' + let inputsClass = tsCodegen.klass(inputsClassName, { export: true }) + inputsClass.addMember(tsCodegen.klassMember('_call', fnClassName)) + inputsClass.addMethod( + tsCodegen.method( + `constructor`, + [tsCodegen.param(`call`, fnClassName)], + null, + `this._call = call`, + ), + ) - // Generate getters and classes for function outputs - util - .disambiguateNames({ - values: fn.get('outputs', immutable.List()), - getName: (output, index) => output.get('name') || `value${index}`, - setName: (output, name) => output.set('name', name), - }) - .forEach((output, index) => { - let callInput = this._generateInputOrOutput( - output, - index, - fnClassName, - `call`, - `outputValues`, - ) - outputsClass.addMethod(callInput.getter) - tupleClasses.push(...callInput.classes) - }) + // Generate getters and classes for function inputs + util + .disambiguateNames({ + values: fn.get('inputs', []), + getName: (input, index) => input.get('name') || `value${index}`, + setName: (input, name) => input.set('name', name), + }) + .forEach((input, index) => { + let callInput = this._generateInputOrOutput( + input, + index, + fnClassName, + `call`, + `inputValues`, + ) + inputsClass.addMethod(callInput.getter) + tupleClasses.push(...callInput.classes) + }) - // Then, generate the event class itself - let klass = tsCodegen.klass(fnClassName, { - export: true, - extends: 'ethereum.Call', + // Second, generate a class with the output getters + let outputsClassName = fnClassName + '__Outputs' + let outputsClass = tsCodegen.klass(outputsClassName, { export: true }) + outputsClass.addMember(tsCodegen.klassMember('_call', fnClassName)) + outputsClass.addMethod( + tsCodegen.method( + `constructor`, + [tsCodegen.param(`call`, fnClassName)], + null, + `this._call = call`, + ), + ) + + // Generate getters and classes for function outputs + util + .disambiguateNames({ + values: fn.get('outputs', []), + getName: (output, index) => output.get('name') || `value${index}`, + setName: (output, name) => output.set('name', name), + }) + .forEach((output, index) => { + let callInput = this._generateInputOrOutput( + output, + index, + fnClassName, + `call`, + `outputValues`, + ) + outputsClass.addMethod(callInput.getter) + tupleClasses.push(...callInput.classes) }) - klass.addMethod( - tsCodegen.method( - `get inputs`, - [], - tsCodegen.namedType(inputsClassName), - `return new ${inputsClassName}(this)`, - ), - ) - klass.addMethod( - tsCodegen.method( - `get outputs`, - [], - tsCodegen.namedType(outputsClassName), - `return new ${outputsClassName}(this)`, - ), - ) - return [klass, inputsClass, outputsClass, ...tupleClasses] - }) - return callFunctions - .reduce( - // flatten the array - (array, classes) => array.concat(classes), - [], + // Then, generate the event class itself + let klass = tsCodegen.klass(fnClassName, { + export: true, + extends: 'ethereum.Call', + }) + klass.addMethod( + tsCodegen.method( + `get inputs`, + [], + tsCodegen.namedType(inputsClassName), + `return new ${inputsClassName}(this)`, + ), + ) + klass.addMethod( + tsCodegen.method( + `get outputs`, + [], + tsCodegen.namedType(outputsClassName), + `return new ${outputsClassName}(this)`, + ), ) + return [klass, inputsClass, outputsClass, ...tupleClasses] + }) + + return callFunctions.reduce( + // flatten the array + (array, classes) => array.concat(classes), + [], + ) } _generateEventTypes() { @@ -176,122 +168,124 @@ module.exports = class AbiCodeGenerator { setName: (event, name) => event.set('_alias', name), }) - events = events - .map(event => { - let eventClassName = event.get('_alias') - let tupleClasses = [] + events = events.map(event => { + let eventClassName = event.get('_alias') + let tupleClasses = [] - // First, generate a class with the param getters - let paramsClassName = eventClassName + '__Params' - let paramsClass = tsCodegen.klass(paramsClassName, { export: true }) - paramsClass.addMember(tsCodegen.klassMember('_event', eventClassName)) - paramsClass.addMethod( - tsCodegen.method( - `constructor`, - [tsCodegen.param(`event`, eventClassName)], - null, - `this._event = event`, - ), - ) - - // Enumerate inputs with duplicate names - let inputs = util.disambiguateNames({ - values: event.get('inputs'), - getName: (input, index) => input.get('name') || `param${index}`, - setName: (input, name) => input.set('name', name), - }) - - let namesAndTypes = [] - inputs.forEach((input, index) => { - // Generate getters and classes for event params - let paramObject = this._generateInputOrOutput( - input, - index, - eventClassName, - `event`, - `parameters`, - ) - paramsClass.addMethod(paramObject.getter) - - // Fixture generation - if (doFixtureCodegen) { - let ethType = typesCodegen.ethereumTypeForAsc(paramObject.getter.returnType) - if (typeof ethType === typeof {} && (ethType.test("int256") || ethType.test("uint256"))) { - ethType = "int32" - } - namesAndTypes.push({name: paramObject.getter.name.slice(4), type: ethType}) - } + // First, generate a class with the param getters + let paramsClassName = eventClassName + '__Params' + let paramsClass = tsCodegen.klass(paramsClassName, { export: true }) + paramsClass.addMember(tsCodegen.klassMember('_event', eventClassName)) + paramsClass.addMethod( + tsCodegen.method( + `constructor`, + [tsCodegen.param(`event`, eventClassName)], + null, + `this._event = event`, + ), + ) - tupleClasses.push(...paramObject.classes) - }) + // Enumerate inputs with duplicate names + let inputs = util.disambiguateNames({ + values: event.get('inputs'), + getName: (input, index) => input.get('name') || `param${index}`, + setName: (input, name) => input.set('name', name), + }) - // Then, generate the event class itself - let klass = tsCodegen.klass(eventClassName, { - export: true, - extends: 'ethereum.Event', - }) - klass.addMethod( - tsCodegen.method( - `get params`, - [], - tsCodegen.namedType(paramsClassName), - `return new ${paramsClassName}(this)`, - ), + let namesAndTypes = [] + inputs.forEach((input, index) => { + // Generate getters and classes for event params + let paramObject = this._generateInputOrOutput( + input, + index, + eventClassName, + `event`, + `parameters`, ) + paramsClass.addMethod(paramObject.getter) // Fixture generation if (doFixtureCodegen) { - const args = yaml.parse(fs.readFileSync('./fixtures.yaml', 'utf8')) - const blockNumber = args['blockNumber'] - const contractAddr = args['contractAddr'] - const topic0 = args['topic0'] - const apiKey = args['apiKey'] - const url = `https://api.etherscan.io/api?module=logs&action=getLogs&fromBlock=${blockNumber}&toBlock=${blockNumber}&address=${contractAddr}&${topic0}=topic0&apikey=${apiKey}`; - - let resp = request("GET", url) - let body = JSON.parse(resp.getBody("utf8")) - if (body.status === '0') { - throw new Error(body.result) + let ethType = typesCodegen.ethereumTypeForAsc(paramObject.getter.returnType) + if ( + typeof ethType === typeof {} && + (ethType.test('int256') || ethType.test('uint256')) + ) { + ethType = 'int32' } + namesAndTypes.push({ name: paramObject.getter.name.slice(4), type: ethType }) + } + + tupleClasses.push(...paramObject.classes) + }) - let res = Web3EthAbi.decodeLog( - namesAndTypes, - body.result[0].data, - [] - ); - - let stmnts = "" - for (let i = 0; i < namesAndTypes.length; i++) { - let code = '"' + res[i] + '"' - if (namesAndTypes[i].type.toString() == "address") { - code = `Address.fromString(${code})` - } - stmnts = stmnts.concat(`event.parameters.push(new ethereum.EventParam(\"${namesAndTypes[i].name}\", ${typesCodegen.ethereumFromAsc(code, namesAndTypes[i].type)}));`, `\n`) + // Then, generate the event class itself + let klass = tsCodegen.klass(eventClassName, { + export: true, + extends: 'ethereum.Event', + }) + klass.addMethod( + tsCodegen.method( + `get params`, + [], + tsCodegen.namedType(paramsClassName), + `return new ${paramsClassName}(this)`, + ), + ) + + // Fixture generation + if (doFixtureCodegen) { + const args = yaml.parse(fs.readFileSync('./fixtures.yaml', 'utf8')) + const blockNumber = args['blockNumber'] + const contractAddr = args['contractAddr'] + const topic0 = args['topic0'] + const apiKey = args['apiKey'] + const url = `https://api.etherscan.io/api?module=logs&action=getLogs&fromBlock=${blockNumber}&toBlock=${blockNumber}&address=${contractAddr}&${topic0}=topic0&apikey=${apiKey}` + + let resp = request('GET', url) + let body = JSON.parse(resp.getBody('utf8')) + if (body.status === '0') { + throw new Error(body.result) + } + + let res = Web3EthAbi.decodeLog(namesAndTypes, body.result[0].data, []) + + let stmnts = '' + for (let i = 0; i < namesAndTypes.length; i++) { + let code = '"' + res[i] + '"' + if (namesAndTypes[i].type.toString() == 'address') { + code = `Address.fromString(${code})` } + stmnts = stmnts.concat( + `event.parameters.push(new ethereum.EventParam(\"${ + namesAndTypes[i].name + }\", ${typesCodegen.ethereumFromAsc(code, namesAndTypes[i].type)}));`, + `\n`, + ) + } - klass.addMethod( - tsCodegen.staticMethod( - `mock${eventClassName}`, - [], - tsCodegen.namedType(eventClassName), - ` + klass.addMethod( + tsCodegen.staticMethod( + `mock${eventClassName}`, + [], + tsCodegen.namedType(eventClassName), + ` let event = changetype<${eventClassName}>(newMockEvent()); ${stmnts} return event; `, - ) - ) - } + ), + ) + } - return [klass, paramsClass, ...tupleClasses] - }) + return [klass, paramsClass, ...tupleClasses] + }) - return events - .reduce( - // flatten the array - (array, classes) => array.concat(classes), - [], - ) + return events.reduce( + // flatten the array + (array, classes) => array.concat(classes), + [], + ) } _generateInputOrOutput(inputOrOutput, index, parentClass, parentType, parentField) { @@ -363,10 +357,10 @@ module.exports = class AbiCodeGenerator { `get ${name}`, [], util.isTupleMatrixType(type) - ? `Array>` - : util.isTupleArrayType(type) - ? `Array<${tupleClassName}>` - : tupleClassName, + ? `Array>` + : util.isTupleArrayType(type) + ? `Array<${tupleClassName}>` + : tupleClassName, ` return ${ isTupleType ? `changetype<${tupleClassName}>(${returnValue})` : `${returnValue}` @@ -408,14 +402,12 @@ module.exports = class AbiCodeGenerator { export: true, extends: 'ethereum.SmartContract', }) - let types = immutable.List() + let types = [] klass.addMethod( tsCodegen.staticMethod( 'bind', - immutable.List([ - tsCodegen.param('address', typesCodegen.ascTypeForEthereum('address')), - ]), + [tsCodegen.param('address', typesCodegen.ascTypeForEthereum('address'))], tsCodegen.namedType(this.abi.name), ` return new ${this.abi.name}('${this.abi.name}', address); @@ -445,12 +437,12 @@ module.exports = class AbiCodeGenerator { // Disambiguate outputs with duplicate names let outputs = util.disambiguateNames({ - values: member.get('outputs', immutable.List()), + values: member.get('outputs', []), getName: (input, index) => input.get('name') || `value${index}`, setName: (input, name) => input.set('name', name), }) - if (member.get('outputs', immutable.List()).size > 1) { + if (member.get('outputs', []).size > 1) { simpleReturnType = false // Create a type dedicated to holding the return values @@ -506,16 +498,18 @@ module.exports = class AbiCodeGenerator { ), ) .forEach(member => returnType.addMember(member)) - + // Add getters to the type outputs - .map((output, index) => - !!output.get('name') && tsCodegen.method( - `get${output.get('name')[0].toUpperCase()}${output.get('name').slice(1)}`, - [], - this._getTupleParamType(output, index, tupleResultParentType), - `return this.value${index};` - ) + .map( + (output, index) => + !!output.get('name') && + tsCodegen.method( + `get${output.get('name')[0].toUpperCase()}${output.get('name').slice(1)}`, + [], + this._getTupleParamType(output, index, tupleResultParentType), + `return this.value${index};`, + ), ) .forEach(method => !!method && returnType.addMethod(method)) @@ -560,7 +554,7 @@ module.exports = class AbiCodeGenerator { // Disambiguate inputs with duplicate names let inputs = util.disambiguateNames({ - values: member.get('inputs', immutable.List()), + values: member.get('inputs', []), getName: (input, index) => input.get('name') || `param${index}`, setName: (input, name) => input.set('name', name), }) @@ -729,7 +723,7 @@ module.exports = class AbiCodeGenerator { return this.abi.data.filter( member => member.get('type') === 'function' && - member.get('outputs', immutable.List()).size !== 0 && + member.get('outputs', []).size !== 0 && (allowedMutability.includes(member.get('stateMutability')) || (member.get('stateMutability') === undefined && !member.get('payable', false))), ) diff --git a/packages/cli/src/protocols/ethereum/codegen/abi.test.js b/packages/cli/src/protocols/ethereum/codegen/abi.test.js index 3502b0090..ccc26feb5 100644 --- a/packages/cli/src/protocols/ethereum/codegen/abi.test.js +++ b/packages/cli/src/protocols/ethereum/codegen/abi.test.js @@ -1,6 +1,5 @@ const fs = require('fs-extra') const path = require('path') -const immutable = require('immutable') const ABI = require('../abi') const ts = require('../../../codegen/typescript') @@ -216,31 +215,31 @@ describe('ABI code generation', () => { test('Have correct parameters', () => { let contract = generatedTypes.find(type => type.name === 'Contract') expect(contract.methods.map(method => [method.name, method.params])).toEqual([ - ['bind', immutable.List([ts.param('address', 'Address')])], - ['read', immutable.List()], - ['try_read', immutable.List()], + ['bind', [ts.param('address', 'Address')]], + ['read', []], + ['try_read', []], [ 'getProposal', - immutable.List([ + [ ts.param('proposalId', 'BigInt'), ts.param('param1', 'Contract__getProposalInputParam1Struct'), - ]), + ], ], [ 'try_getProposal', - immutable.List([ + [ ts.param('proposalId', 'BigInt'), ts.param('param1', 'Contract__getProposalInputParam1Struct'), - ]), + ], ], - ['getProposals', immutable.List()], - ['try_getProposals', immutable.List()], - ['overloaded', immutable.List([ts.param('param0', 'string')])], - ['try_overloaded', immutable.List([ts.param('param0', 'string')])], - ['overloaded1', immutable.List([ts.param('param0', 'BigInt')])], - ['try_overloaded1', immutable.List([ts.param('param0', 'BigInt')])], - ['overloaded2', immutable.List([ts.param('param0', 'Bytes')])], - ['try_overloaded2', immutable.List([ts.param('param0', 'Bytes')])], + ['getProposals', []], + ['try_getProposals', []], + ['overloaded', [ts.param('param0', 'string')]], + ['try_overloaded', [ts.param('param0', 'string')]], + ['overloaded1', [ts.param('param0', 'BigInt')]], + ['try_overloaded1', [ts.param('param0', 'BigInt')]], + ['overloaded2', [ts.param('param0', 'Bytes')]], + ['try_overloaded2', [ts.param('param0', 'Bytes')]], ]) }) diff --git a/packages/cli/src/protocols/ethereum/subgraph.js b/packages/cli/src/protocols/ethereum/subgraph.js index 8435c91f4..6bb388f36 100644 --- a/packages/cli/src/protocols/ethereum/subgraph.js +++ b/packages/cli/src/protocols/ethereum/subgraph.js @@ -1,4 +1,3 @@ -const immutable = require('immutable') const ABI = require('./abi') const DataSourcesExtractor = require('../../command-helpers/data-sources') @@ -16,7 +15,10 @@ module.exports = class EthereumSubgraph { } validateAbis() { - const dataSourcesAndTemplates = DataSourcesExtractor.fromManifest(this.manifest, this.protocol) + const dataSourcesAndTemplates = DataSourcesExtractor.fromManifest( + this.manifest, + this.protocol, + ) return dataSourcesAndTemplates.reduce( (errors, dataSourceOrTemplate) => @@ -26,18 +28,18 @@ module.exports = class EthereumSubgraph { dataSourceOrTemplate.get('path'), ), ), - immutable.List(), + [], ) } validateDataSourceAbis(dataSource, path) { // Validate that the the "source > abi" reference of all data sources // points to an existing ABI in the data source ABIs - let abiName = dataSource.getIn(['source', 'abi']) - let abiNames = dataSource.getIn(['mapping', 'abis']).map(abi => abi.get('name')) + let abiName = dataSource.source?.abi + let abiNames = dataSource.mapping?.abis.map(abi => abi.get('name')) let nameErrors = abiNames.includes(abiName) - ? immutable.List() - : immutable.fromJS([ + ? [] + : [ { path: [...path, 'source', 'abi'], message: `\ @@ -48,61 +50,57 @@ ${abiNames .map(name => `- ${name}`) .join('\n')}`, }, - ]) + ] // Validate that all ABI files are valid - let fileErrors = dataSource - .getIn(['mapping', 'abis']) - .reduce((errors, abi, abiIndex) => { - try { - ABI.load(abi.get('name'), this.resolveFile(abi.get('file'))) - return errors - } catch (e) { - return errors.push( - immutable.fromJS({ - path: [...path, 'mapping', 'abis', abiIndex, 'file'], - message: e.message, - }), - ) - } - }, immutable.List()) + let fileErrors = dataSource.mapping?.abis.reduce((errors, abi, abiIndex) => { + try { + ABI.load(abi.get('name'), this.resolveFile(abi.get('file'))) + return errors + } catch (e) { + return errors.push({ + path: [...path, 'mapping', 'abis', abiIndex, 'file'], + message: e.message, + }) + } + }, []) return nameErrors.concat(fileErrors) } validateEvents() { - const dataSourcesAndTemplates = DataSourcesExtractor.fromManifest(this.manifest, this.protocol) + const dataSourcesAndTemplates = DataSourcesExtractor.fromManifest( + this.manifest, + this.protocol, + ) - return dataSourcesAndTemplates - .reduce((errors, dataSourceOrTemplate) => { - return errors.concat( - this.validateDataSourceEvents( - dataSourceOrTemplate.get('dataSource'), - dataSourceOrTemplate.get('path'), - ), - ) - }, immutable.List()) + return dataSourcesAndTemplates.reduce((errors, dataSourceOrTemplate) => { + return errors.concat( + this.validateDataSourceEvents( + dataSourceOrTemplate.get('dataSource'), + dataSourceOrTemplate.get('path'), + ), + ) + }, []) } validateDataSourceEvents(dataSource, path) { let abi try { // Resolve the source ABI name into a real ABI object - let abiName = dataSource.getIn(['source', 'abi']) - let abiEntry = dataSource - .getIn(['mapping', 'abis']) - .find(abi => abi.get('name') === abiName) + let abiName = dataSource.source?.abi + let abiEntry = dataSource.mapping?.abis.find(abi => abi.get('name') === abiName) abi = ABI.load(abiEntry.get('name'), this.resolveFile(abiEntry.get('file'))) } catch (_) { // Ignore errors silently; we can't really say anything about // the events if the ABI can't even be loaded - return immutable.List() + return [] } // Obtain event signatures from the mapping - let manifestEvents = dataSource - .getIn(['mapping', 'eventHandlers'], immutable.List()) - .map(handler => handler.get('event')) + let manifestEvents = (dataSource.mapping?.eventHandlers || []).map(handler => + handler.get('event'), + ) // Obtain event signatures from the ABI let abiEvents = abi.eventSignatures() @@ -113,19 +111,17 @@ ${abiNames (errors, manifestEvent, index) => abiEvents.includes(manifestEvent) ? errors - : errors.push( - immutable.fromJS({ - path: [...path, 'eventHandlers', index], - message: `\ + : errors.push({ + path: [...path, 'eventHandlers', index], + message: `\ Event with signature '${manifestEvent}' not present in ABI '${abi.name}'. Available events: ${abiEvents .sort() .map(event => `- ${event}`) .join('\n')}`, - }), - ), - immutable.List(), + }), + [], ) } @@ -139,10 +135,8 @@ ${abiEvents let abi try { // Resolve the source ABI name into a real ABI object - let abiName = dataSource.getIn(['source', 'abi']) - let abiEntry = dataSource - .getIn(['mapping', 'abis']) - .find(abi => abi.get('name') === abiName) + let abiName = dataSource.source?.abi + let abiEntry = dataSource.mapping?.abis.find(abi => abi.get('name') === abiName) abi = ABI.load(abiEntry.get('name'), this.resolveFile(abiEntry.get('file'))) } catch (e) { // Ignore errors silently; we can't really say anything about @@ -151,9 +145,9 @@ ${abiEvents } // Obtain event signatures from the mapping - let manifestFunctions = dataSource - .getIn(['mapping', 'callHandlers'], immutable.List()) - .map(handler => handler.get('function')) + let manifestFunctions = (dataSource.mapping?.callHandlers || []).map(handler => + handler.get('function'), + ) // Obtain event signatures from the ABI let abiFunctions = abi.callFunctionSignatures() @@ -164,28 +158,22 @@ ${abiEvents (errors, manifestFunction, index) => abiFunctions.includes(manifestFunction) ? errors - : errors.push( - immutable.fromJS({ - path: [...path, index], - message: `\ + : errors.push({ + path: [...path, index], + message: `\ Call function with signature '${manifestFunction}' not present in ABI '${abi.name}'. Available call functions: ${abiFunctions .sort() .map(tx => `- ${tx}`) .join('\n')}`, - }), - ), + }), errors, ) - }, immutable.List()) + }, []) } handlerTypes() { - return immutable.List([ - 'blockHandlers', - 'callHandlers', - 'eventHandlers', - ]) + return ['blockHandlers', 'callHandlers', 'eventHandlers'] } } diff --git a/packages/cli/src/protocols/ethereum/type-generator.js b/packages/cli/src/protocols/ethereum/type-generator.js index bc0b3d71b..b87905afd 100644 --- a/packages/cli/src/protocols/ethereum/type-generator.js +++ b/packages/cli/src/protocols/ethereum/type-generator.js @@ -1,6 +1,5 @@ const fs = require('fs-extra') const path = require('path') -const immutable = require('immutable') const prettier = require('prettier') const ABI = require('./abi') const { step, withSpinner } = require('../../command-helpers/spinner') @@ -24,21 +23,19 @@ module.exports = class EthereumTypeGenerator { .get('dataSources') .reduce( (abis, dataSource) => - dataSource - .getIn(['mapping', 'abis']) - .reduce( - (abis, abi) => - abis.push( - this._loadABI( - dataSource, - abi.get('name'), - abi.get('file'), - spinner, - ), + dataSource.mapping?.abis.reduce( + (abis, abi) => + abis.push( + this._loadABI( + dataSource, + abi.get('name'), + abi.get('file'), + spinner, + ), + ), + abis, ), - abis, - ), - immutable.List(), + [], ) } catch (e) { throw Error(`Failed to load contract ABIs: ${e.message}`) @@ -68,8 +65,8 @@ module.exports = class EthereumTypeGenerator { `Warnings while loading data source template ABIs`, async spinner => { let abis = [] - for (let template of subgraph.get('templates', immutable.List())) { - for (let abi of template.getIn(['mapping', 'abis'])) { + for (let template of subgraph.get('templates', [])) { + for (let abi of template.mapping?.abis) { abis.push( this._loadDataSourceTemplateABI( template, @@ -89,11 +86,7 @@ module.exports = class EthereumTypeGenerator { try { if (this.sourceDir) { let absolutePath = path.resolve(this.sourceDir, maybeRelativePath) - step( - spinner, - `Load data source template ABI from`, - displayPath(absolutePath), - ) + step(spinner, `Load data source template ABI from`, displayPath(absolutePath)) return { template, abi: ABI.load(name, absolutePath) } } else { return { template, abi: ABI.load(name, maybeRelativePath) } @@ -170,9 +163,7 @@ module.exports = class EthereumTypeGenerator { step( spinner, `Generate types for data source template ABI:`, - `${abi.template.get('name')} > ${abi.abi.name} (${displayPath( - abi.abi.file, - )})`, + `${abi.template.get('name')} > ${abi.abi.name} (${displayPath(abi.abi.file)})`, ) let codeGenerator = abi.abi.codeGenerator() diff --git a/packages/cli/src/protocols/index.js b/packages/cli/src/protocols/index.js index eb03846ce..b5b86025a 100644 --- a/packages/cli/src/protocols/index.js +++ b/packages/cli/src/protocols/index.js @@ -1,4 +1,3 @@ -const immutable = require('immutable') const ArweaveSubgraph = require('./arweave/subgraph') const EthereumTypeGenerator = require('./ethereum/type-generator') const EthereumTemplateCodeGen = require('./ethereum/codegen/template') @@ -54,7 +53,7 @@ class Protocol { } static availableProtocols() { - return immutable.fromJS({ + return Object.freeze({ // `ethereum/contract` is kept for backwards compatibility. // New networks (or protocol perhaps) shouldn't have the `/contract` anymore (unless a new case makes use of it). arweave: ['arweave'], @@ -66,7 +65,7 @@ class Protocol { } static availableNetworks() { - let networks = immutable.fromJS({ + let networks = { arweave: ['arweave-mainnet'], ethereum: [ 'mainnet', @@ -106,14 +105,14 @@ class Protocol { 'juno-1', 'uni-3', // Juno testnet ], - }) + } let allNetworks = [] networks.forEach(value => { allNetworks.push(...value) }) - networks = networks.set('substreams', immutable.fromJS(allNetworks)) + networks['substreams'] = allNetworks return networks } @@ -132,7 +131,7 @@ class Protocol { // for the given protocol instance (this). isValidKindName(kind) { return Protocol.availableProtocols() - .get(this.name, immutable.List()) + .get(this.name, []) .includes(kind) } diff --git a/packages/cli/src/protocols/near/subgraph.js b/packages/cli/src/protocols/near/subgraph.js index 0e2163aee..4e49e9b9c 100644 --- a/packages/cli/src/protocols/near/subgraph.js +++ b/packages/cli/src/protocols/near/subgraph.js @@ -1,5 +1,3 @@ -const immutable = require('immutable') - module.exports = class NearSubgraph { constructor(options = {}) { this.manifest = options.manifest @@ -8,13 +6,13 @@ module.exports = class NearSubgraph { } validateManifest() { - return immutable.List() + return [] } handlerTypes() { - return immutable.List([ + return [ 'blockHandlers', 'receiptHandlers', - ]) + ] } } diff --git a/packages/cli/src/protocols/substreams/subgraph.js b/packages/cli/src/protocols/substreams/subgraph.js index da7831600..0a42951a9 100644 --- a/packages/cli/src/protocols/substreams/subgraph.js +++ b/packages/cli/src/protocols/substreams/subgraph.js @@ -1,5 +1,3 @@ -const immutable = require('immutable') - module.exports = class SubstreamsSubgraph { constructor(options = {}) { this.manifest = options.manifest @@ -8,10 +6,10 @@ module.exports = class SubstreamsSubgraph { } validateManifest() { - return immutable.List() + return [] } handlerTypes() { - return immutable.List([]) + return [] } } diff --git a/packages/cli/src/scaffold/cosmos.test.js b/packages/cli/src/scaffold/cosmos.test.js index 51b9b67d6..4198bc6d9 100644 --- a/packages/cli/src/scaffold/cosmos.test.js +++ b/packages/cli/src/scaffold/cosmos.test.js @@ -1,4 +1,3 @@ -const immutable = require('immutable') const Scaffold = require('./') const Protocol = require('../protocols') diff --git a/packages/cli/src/scaffold/ethereum.test.js b/packages/cli/src/scaffold/ethereum.test.js index 1a242c550..31e34cf2d 100644 --- a/packages/cli/src/scaffold/ethereum.test.js +++ b/packages/cli/src/scaffold/ethereum.test.js @@ -1,5 +1,4 @@ const ABI = require('../protocols/ethereum/abi') -const immutable = require('immutable') const Scaffold = require('./') const Protocol = require('../protocols') @@ -61,12 +60,12 @@ const TEST_CALLABLE_FUNCTIONS = [ const TEST_ABI = new ABI( 'Contract', undefined, - immutable.fromJS([ + [ TEST_EVENT, OVERLOADED_EVENT, TEST_CONTRACT, ...TEST_CALLABLE_FUNCTIONS, - ]), + ], ) const protocol = new Protocol('ethereum') diff --git a/packages/cli/src/scaffold/near.test.js b/packages/cli/src/scaffold/near.test.js index 6852a0cad..7c46cf5e2 100644 --- a/packages/cli/src/scaffold/near.test.js +++ b/packages/cli/src/scaffold/near.test.js @@ -1,4 +1,3 @@ -const immutable = require('immutable') const Scaffold = require('./') const Protocol = require('../protocols') diff --git a/packages/cli/src/schema.js b/packages/cli/src/schema.js index 1cc3d66d0..adf41a6f3 100644 --- a/packages/cli/src/schema.js +++ b/packages/cli/src/schema.js @@ -1,6 +1,5 @@ let fs = require('fs-extra') let graphql = require('graphql/language') -let immutable = require('immutable') let SchemaCodeGenerator = require('./codegen/schema') @@ -18,6 +17,6 @@ module.exports = class Schema { static async load(filename) { let document = await fs.readFile(filename, 'utf-8') let ast = graphql.parse(document) - return new Schema(filename, document, immutable.fromJS(ast)) + return new Schema(filename, document, ast) } } diff --git a/packages/cli/src/subgraph.js b/packages/cli/src/subgraph.js index 98c8b1190..99e7d045e 100644 --- a/packages/cli/src/subgraph.js +++ b/packages/cli/src/subgraph.js @@ -1,5 +1,4 @@ let fs = require('fs-extra') -let immutable = require('immutable') let path = require('path') let yaml = require('yaml') let { strOptions } = require('yaml/types') @@ -43,12 +42,12 @@ module.exports = class Subgraph { static async validate(data, protocol, { resolveFile }) { subgraphDebug(`Validating Subgraph with protocol "%s"`, protocol) if (protocol.name == null) { - return immutable.fromJS([ + return [ { path: [], message: `Unable to determine for which protocol manifest file is built for. Ensure you have at least one 'dataSources' and/or 'templates' elements defined in your subgraph.`, }, - ]) + ] } // Parse the default subgraph schema @@ -69,7 +68,7 @@ module.exports = class Subgraph { } static validateSchema(manifest, { resolveFile }) { - let filename = resolveFile(manifest.getIn(['schema', 'file'])) + let filename = resolveFile(manifest.schema?.file) let errors = validation.validateSchema(filename) if (errors.size > 0) { @@ -106,29 +105,29 @@ module.exports = class Subgraph { const repository = manifest.get('repository') return /^https:\/\/github\.com\/graphprotocol\/example-subgraphs?$/.test(repository) - ? immutable.List().push( - immutable.fromJS({ + ? [ + { path: ['repository'], message: `\ The repository is still set to ${repository}. Please replace it with a link to your subgraph source code.`, - }), - ) - : immutable.List() + }, + ] + : [] } static validateDescription(manifest, { resolveFile }) { // TODO: Maybe implement this in the future for each protocol example description return manifest.get('description', '').startsWith('Gravatar for ') - ? immutable.List().push( - immutable.fromJS({ + ? [ + { path: ['description'], message: `\ The description is still the one from the example subgraph. Please update it to tell users more about your subgraph.`, - }), - ) - : immutable.List() + }, + ] + : [] } static validateHandlers(manifest, protocol, protocolSubgraph) { @@ -151,27 +150,25 @@ Please update it to tell users more about your subgraph.`, } const areAllHandlersEmpty = handlerTypes - .map(handlerType => mapping.get(handlerType, immutable.List())) + .map(handlerType => mapping.get(handlerType, [])) .every(handlers => handlers.isEmpty()) const handlerNamesWithoutLast = handlerTypes.pop().join(', ') return areAllHandlersEmpty - ? errors.push( - immutable.fromJS({ - path: path, - message: `\ + ? errors.push({ + path: path, + message: `\ Mapping has no ${handlerNamesWithoutLast} or ${handlerTypes.get(-1)}. At least one such handler must be defined.`, - }), - ) + }) : errors - }, immutable.List()) + }, []) } static validateContractValues(manifest, protocol) { if (!protocol.hasContract()) { - return immutable.List() + return [] } return validation.validateContractValues(manifest, protocol) @@ -184,38 +181,32 @@ At least one such handler must be defined.`, let path = ['dataSources', dataSourceIndex, 'name'] let name = dataSource.get('name') if (names.includes(name)) { - errors = errors.push( - immutable.fromJS({ - path, - message: `\ + errors = errors.push({ + path, + message: `\ More than one data source named '${name}', data source names must be unique.`, - }), - ) + }) } names.push(name) return errors - }, immutable.List()) + }, []) } static validateUniqueTemplateNames(manifest) { let names = [] - return manifest - .get('templates', immutable.List()) - .reduce((errors, template, templateIndex) => { - let path = ['templates', templateIndex, 'name'] - let name = template.get('name') - if (names.includes(name)) { - errors = errors.push( - immutable.fromJS({ - path, - message: `\ + return manifest.get('templates', []).reduce((errors, template, templateIndex) => { + let path = ['templates', templateIndex, 'name'] + let name = template.get('name') + if (names.includes(name)) { + errors = errors.push({ + path, + message: `\ More than one template named '${name}', template names must be unique.`, - }), - ) - } - names.push(name) - return errors - }, immutable.List()) + }) + } + names.push(name) + return errors + }, []) } static dump(manifest) { @@ -250,7 +241,7 @@ More than one template named '${name}', template names must be unique.`, } } - let manifest = immutable.fromJS(data) + let manifest = data // Validate the schema Subgraph.validateSchema(manifest, { resolveFile }) @@ -262,14 +253,14 @@ More than one template named '${name}', template names must be unique.`, }) let errors = skipValidation - ? immutable.List() - : immutable.List.of( + ? [] + : [ ...protocolSubgraph.validateManifest(), ...Subgraph.validateContractValues(manifest, protocol), ...Subgraph.validateUniqueDataSourceNames(manifest), ...Subgraph.validateUniqueTemplateNames(manifest), ...Subgraph.validateHandlers(manifest, protocol, protocolSubgraph), - ) + ] if (errors.size > 0) { throwCombinedError(filename, errors) @@ -277,11 +268,11 @@ More than one template named '${name}', template names must be unique.`, // Perform warning validations let warnings = skipValidation - ? immutable.List() - : immutable.List.of( + ? [] + : [ ...Subgraph.validateRepository(manifest, { resolveFile }), ...Subgraph.validateDescription(manifest, { resolveFile }), - ) + ] return { result: manifest, diff --git a/packages/cli/src/type-generator.js b/packages/cli/src/type-generator.js index abde0de8b..2c54e515a 100644 --- a/packages/cli/src/type-generator.js +++ b/packages/cli/src/type-generator.js @@ -1,5 +1,4 @@ const fs = require('fs-extra') -const immutable = require('immutable') const path = require('path') const prettier = require('prettier') const graphql = require('graphql/language') @@ -120,14 +119,14 @@ module.exports = class TypeGenerator { } async loadSchema(subgraph) { - let maybeRelativePath = subgraph.getIn(['schema', 'file']) + let maybeRelativePath = subgraph.schema?.file let absolutePath = path.resolve(this.sourceDir, maybeRelativePath) return await withSpinner( `Load GraphQL schema from ${displayPath(absolutePath)}`, `Failed to load GraphQL schema from ${displayPath(absolutePath)}`, `Warnings while loading GraphQL schema from ${displayPath(absolutePath)}`, async spinner => { - let maybeRelativePath = subgraph.getIn(['schema', 'file']) + let maybeRelativePath = subgraph.schema?.file let absolutePath = path.resolve(this.sourceDir, maybeRelativePath) return Schema.load(absolutePath) }, @@ -169,7 +168,7 @@ module.exports = class TypeGenerator { async spinner => { // Combine the generated code for all templates let codeSegments = subgraph - .get('templates', immutable.List()) + .get('templates', []) .reduce((codeSegments, template) => { step( spinner, @@ -189,7 +188,7 @@ module.exports = class TypeGenerator { } return codeSegments.concat(codeGenerator.generateTypes()) - }, immutable.List()) + }, []) if (!codeSegments.isEmpty()) { let code = prettier.format([GENERATED_FILE_NOTE, ...codeSegments].join('\n'), { @@ -214,11 +213,11 @@ module.exports = class TypeGenerator { files.push(this.options.subgraphManifest) // Add the GraphQL schema to the watched files - files.push(subgraph.getIn(['schema', 'file'])) + files.push(subgraph.schema?.file) // Add all file paths specified in manifest subgraph.get('dataSources').map(dataSource => { - dataSource.getIn(['mapping', 'abis']).map(abi => { + dataSource.mapping?.abis.map(abi => { files.push(abi.get('file')) }) }) diff --git a/packages/cli/src/validation/contract.js b/packages/cli/src/validation/contract.js index 53df01f70..9444cdd8f 100644 --- a/packages/cli/src/validation/contract.js +++ b/packages/cli/src/validation/contract.js @@ -1,5 +1,3 @@ -const immutable = require('immutable') - const validateContract = (value, ProtocolContract) => { const contract = new ProtocolContract(value) @@ -31,8 +29,7 @@ const validateContractValues = (manifest, protocol) => { return errors } - let contractValue = dataSource.getIn(['source', fieldName]) - + let contractValue = dataSource.source[fieldName] const { valid, error } = validateContract(contractValue, ProtocolContract) @@ -40,14 +37,12 @@ const validateContractValues = (manifest, protocol) => { if (valid) { return errors } else { - return errors.push( - immutable.fromJS({ - path, - message: error, - }), - ) + return errors.push({ + path, + message: error, + }) } - }, immutable.List()) + }, []) } module.exports = { diff --git a/packages/cli/src/validation/manifest.js b/packages/cli/src/validation/manifest.js index ac911e35c..753e288f2 100644 --- a/packages/cli/src/validation/manifest.js +++ b/packages/cli/src/validation/manifest.js @@ -1,20 +1,16 @@ -const immutable = require('immutable') const yaml = require('js-yaml') const path = require('path') const Protocol = require('../protocols') -const List = immutable.List -const Map = immutable.Map - /** * Returns a user-friendly type name for a value. */ const typeName = value => - List.isList(value) ? 'list' : Map.isMap(value) ? 'map' : typeof value + Array.isArray(value) ? 'list' : typeof value === 'object' ? 'map' : typeof value /** - * Converts an immutable or plain JavaScript value to a YAML string. + * Converts a plain JavaScript value to a YAML string. */ const toYAML = x => yaml @@ -27,9 +23,7 @@ const toYAML = x => * Looks up the type of a field in a GraphQL object type. */ const getFieldType = (type, fieldName) => { - let fieldDef = type - .get('fields') - .find(field => field.getIn(['name', 'value']) === fieldName) + let fieldDef = type.get('fields').find(field => field.name?.value === fieldName) return fieldDef !== undefined ? fieldDef.get('type') : undefined } @@ -41,20 +35,17 @@ const resolveType = (schema, type) => type.has('type') ? resolveType(schema, type.get('type')) : type.get('kind') === 'NamedType' - ? schema - .get('definitions') - .find(def => def.getIn(['name', 'value']) === type.getIn(['name', 'value'])) + ? schema.get('definitions').find(def => def.name?.value === type.name?.value) : 'resolveType: unimplemented' /** * A map of supported validators. */ -const validators = immutable.fromJS({ - ScalarTypeDefinition: (value, ctx) => - validators.get(ctx.getIn(['type', 'name', 'value']))(value, ctx), +const validators = Object.freeze({ + ScalarTypeDefinition: (value, ctx) => validators.get(ctx.type?.name?.value)(value, ctx), UnionTypeDefinition: (value, ctx) => { - const unionVariants = ctx.getIn(['type', 'types']) + const unionVariants = ctx.type?.types let errors = List() @@ -83,12 +74,12 @@ const validators = immutable.fromJS({ value, ctx.update('type', type => type.get('type')), ) - : immutable.fromJS([ + : [ { path: ctx.get('path'), message: `No value provided`, }, - ]), + ], ListType: (value, ctx) => List.isList(value) @@ -104,18 +95,17 @@ const validators = immutable.fromJS({ ), List(), ) - : immutable.fromJS([ + : [ { path: ctx.get('path'), message: `Expected list, found ${typeName(value)}:\n${toYAML(value)}`, }, - ]), + ], ObjectTypeDefinition: (value, ctx) => { return Map.isMap(value) - ? ctx - .getIn(['type', 'fields']) - .map(fieldDef => fieldDef.getIn(['name', 'value'])) + ? ctx.type?.fields + .map(fieldDef => fieldDef.name?.value) .concat(value.keySeq()) .toSet() .reduce( @@ -131,97 +121,97 @@ const validators = immutable.fromJS({ ) : errors.push( key == 'templates' && ctx.get('protocol').hasTemplates() - ? immutable.fromJS({ + ? { path: ctx.get('path'), message: `The way to declare data source templates has changed, ` + `please move the templates from inside data sources to ` + `a \`templates:\` field at the top level of the manifest.`, - }) - : immutable.fromJS({ + } + : { path: ctx.get('path'), message: `Unexpected key in map: ${key}`, - }), + }, ), List(), ) - : immutable.fromJS([ + : [ { path: ctx.get('path'), message: `Expected map, found ${typeName(value)}:\n${toYAML(value)}`, }, - ]) + ] }, EnumTypeDefinition: (value, ctx) => { - const enumValues = ctx.getIn(['type', 'values']).map((v) => { - return v.getIn(['name', 'value']) + const enumValues = ctx.type?.values.map(v => { + return v.name?.value }) const allowedValues = enumValues.toArray().join(', ') return enumValues.includes(value) - ? List() - : immutable.fromJS([ - { - path: ctx.get('path'), - message: `Unexpected enum value: ${value}, allowed values: ${allowedValues}`, - }, - ]) + ? [] + : [ + { + path: ctx.get('path'), + message: `Unexpected enum value: ${value}, allowed values: ${allowedValues}`, + }, + ] }, String: (value, ctx) => typeof value === 'string' ? List() - : immutable.fromJS([ + : [ { path: ctx.get('path'), message: `Expected string, found ${typeName(value)}:\n${toYAML(value)}`, }, - ]), + ], BigInt: (value, ctx) => typeof value === 'number' - ? List() - : immutable.fromJS([ + ? [] + : [ { path: ctx.get('path'), message: `Expected BigInt, found ${typeName(value)}:\n${toYAML(value)}`, }, - ]), + ], File: (value, ctx) => typeof value === 'string' ? require('fs').existsSync(ctx.get('resolveFile')(value)) - ? List() - : immutable.fromJS([ + ? [] + : [ { path: ctx.get('path'), message: `File does not exist: ${path.relative(process.cwd(), value)}`, }, - ]) - : immutable.fromJS([ + ] + : [ { path: ctx.get('path'), message: `Expected filename, found ${typeName(value)}:\n${value}`, }, - ]), + ], Boolean: (value, ctx) => typeof value === 'boolean' - ? List() - : immutable.fromJS([ + ? [] + : [ { path: ctx.get('path'), message: `Expected true or false, found ${typeName(value)}:\n${toYAML( value, )}`, }, - ]), + ], }) const validateValue = (value, ctx) => { - let kind = ctx.getIn(['type', 'kind']) + let kind = ctx.type?.kind let validator = validators.get(kind) if (validator !== undefined) { @@ -229,17 +219,17 @@ const validateValue = (value, ctx) => { // type is wrapped in a `NonNullType`, the validator for that `NonNullType` // will catch the missing/unset value if (kind !== 'NonNullType' && (value === undefined || value === null)) { - return List() + return [] } else { return validator(value, ctx) } } else { - return immutable.fromJS([ + return [ { path: ctx.get('path'), message: `No validator for unsupported schema type: ${kind}`, }, - ]) + ] } } @@ -251,7 +241,7 @@ const validateValue = (value, ctx) => { // { name: 'contract3', kind: 'near', network: 'near-mainnet' }, // ] // -// Into Immutable JS structure like this (protocol kind is normalized): +// Into JS structure like this (protocol kind is normalized): // { // ethereum: { // mainnet: ['contract0', 'contract1'], @@ -262,15 +252,17 @@ const validateValue = (value, ctx) => { // }, // } const dataSourceListToMap = dataSources => - dataSources - .reduce( - (protocolKinds, dataSource) => - protocolKinds.update(Protocol.normalizeName(dataSource.kind), networks => - (networks || immutable.OrderedMap()).update(dataSource.network, dataSourceNames => - (dataSourceNames || immutable.OrderedSet()).add(dataSource.name)), - ), - immutable.OrderedMap(), - ) + dataSources.reduce((protocolKinds, dataSource) => { + const dataSourceName = Protocol.normalizeName(dataSource.kind) + if (!protocolKinds[dataSourceName]) { + protocolKinds[dataSourceName] = {} + } + if (!protocolKinds[dataSourceName][dataSource.network]) { + protocolKinds[dataSourceName][dataSource.network] = [] + } + protocolKinds[dataSourceName][dataSource.network].push(dataSource.name) + return protocolKinds + }, {}) const validateDataSourceProtocolAndNetworks = value => { const dataSources = [...value.dataSources, ...(value.templates || [])] @@ -278,7 +270,7 @@ const validateDataSourceProtocolAndNetworks = value => { const protocolNetworkMap = dataSourceListToMap(dataSources) if (protocolNetworkMap.size > 1) { - return immutable.fromJS([ + return [ { path: [], message: `Conflicting protocol kinds used in data sources and templates: @@ -289,18 +281,22 @@ ${protocolNetworkMap protocolKind === undefined ? 'Data sources and templates having no protocol kind set' : `Data sources and templates using '${protocolKind}'` - }:\n${dataSourceNames.valueSeq().flatten().map(ds => ` - ${ds}`).join('\n')}`, + }:\n${dataSourceNames + .valueSeq() + .flatten() + .map(ds => ` - ${ds}`) + .join('\n')}`, ) .join('\n')} Recommendation: Make all data sources and templates use the same protocol kind.`, }, - ]) + ] } const networks = protocolNetworkMap.first() if (networks.size > 1) { - return immutable.fromJS([ + return [ { path: [], message: `Conflicting networks used in data sources and templates: @@ -316,33 +312,30 @@ ${networks .join('\n')} Recommendation: Make all data sources and templates use the same network name.`, }, - ]) + ] } - return List() + return [] } const validateManifest = (value, type, schema, protocol, { resolveFile }) => { // Validate manifest using the GraphQL schema that defines its structure let errors = value !== null && value !== undefined - ? validateValue( - immutable.fromJS(value), - immutable.fromJS({ - schema: schema, - type: type, - path: [], - errors: [], - resolveFile, - protocol, - }), - ) - : immutable.fromJS([ + ? validateValue(value, { + schema: schema, + type: type, + path: [], + errors: [], + resolveFile, + protocol, + }) + : [ { path: [], message: `Expected non-empty value, found ${typeName(value)}:\n ${value}`, }, - ]) + ] // Fail early because a broken manifest prevents us from performing // additional validation steps diff --git a/packages/cli/src/validation/schema.js b/packages/cli/src/validation/schema.js index c8b61178e..44eb8ff51 100644 --- a/packages/cli/src/validation/schema.js +++ b/packages/cli/src/validation/schema.js @@ -1,9 +1,5 @@ const fs = require('fs') const graphql = require('graphql/language') -const immutable = require('immutable') - -const List = immutable.List -const Set = immutable.Set // Builtin scalar types const BUILTIN_SCALAR_TYPES = [ @@ -76,26 +72,26 @@ const parseSchema = doc => { const validateEntityDirective = def => def.directives.find(directive => directive.name.value === 'entity') - ? List() - : immutable.fromJS([ - { - loc: def.loc, - entity: def.name.value, - message: `Defined without @entity directive`, - }, - ]) + ? [] + : [ + { + loc: def.loc, + entity: def.name.value, + message: `Defined without @entity directive`, + }, + ] const validateEntityID = def => { let idField = def.fields.find(field => field.name.value === 'id') if (idField === undefined) { - return immutable.fromJS([ + return [ { loc: def.loc, entity: def.name.value, message: `Missing field: id: ID!`, }, - ]) + ] } if ( @@ -105,36 +101,36 @@ const validateEntityID = def => { idField.type.type.name.value === 'Bytes' || idField.type.type.name.value === 'String') ) { - return List() + return [] } else { - return immutable.fromJS([ + return [ { loc: idField.loc, entity: def.name.value, message: `Field 'id': Entity ids must be of type Bytes! or String!`, }, - ]) + ] } } const validateListFieldType = (def, field) => field.type.kind === 'NonNullType' && - field.type.kind === 'ListType' && - field.type.type.kind !== 'NonNullType' - ? immutable.fromJS([ - { - loc: field.loc, - entity: def.name.value, - message: `\ + field.type.kind === 'ListType' && + field.type.type.kind !== 'NonNullType' + ? [ + { + loc: field.loc, + entity: def.name.value, + message: `\ Field '${field.name.value}': Field has type [${field.type.type.name.value}]! but must have type [${field.type.type.name.value}!]! Reason: Lists with null elements are not supported.`, - }, - ]) + }, + ] : field.type.kind === 'ListType' && field.type.type.kind !== 'NonNullType' - ? immutable.fromJS([ + ? [ { loc: field.loc, entity: def.name.value, @@ -145,8 +141,8 @@ must have type [${field.type.type.name.value}!] Reason: Lists with null elements are not supported.`, }, - ]) - : List() + ] + : [] const unwrapType = type => { let innerTypeFromList = listType => @@ -163,8 +159,8 @@ const unwrapType = type => { return type.kind === 'NonNullType' ? innerTypeFromNonNull(type) : type.kind === 'ListType' - ? innerTypeFromList(type) - : type + ? innerTypeFromList(type) + : type } const gatherLocalTypes = defs => @@ -210,8 +206,8 @@ const gatherImportedTypes = defs => type.fields.find( field => field.name.value == 'as' && field.value.kind == 'StringValue', ) - ? type.fields.find(field => field.name.value == 'as').value.value - : undefined, + ? type.fields.find(field => field.name.value == 'as').value.value + : undefined, ), ), ) @@ -231,7 +227,8 @@ const entityTypeByName = (defs, name) => .filter( def => def.kind === 'InterfaceTypeDefinition' || - (def.kind === 'ObjectTypeDefinition' && def.directives.find(directive => directive.name.value === 'entity')) + (def.kind === 'ObjectTypeDefinition' && + def.directives.find(directive => directive.name.value === 'entity')), ) .find(def => def.name.value === name) @@ -258,17 +255,18 @@ const validateInnerFieldType = (defs, def, field) => { // Check whether the type name is available, otherwise return an error return availableTypes.includes(typeName) - ? List() - : immutable.fromJS([ - { - loc: field.loc, - entity: def.name.value, - message: `\ + ? [] + : [ + { + loc: field.loc, + entity: def.name.value, + message: `\ Field '${field.name.value}': \ -Unknown type '${typeName}'.${suggestion !== undefined ? ` Did you mean '${suggestion}'?` : '' +Unknown type '${typeName}'.${ + suggestion !== undefined ? ` Did you mean '${suggestion}'?` : '' }`, - }, - ]) + }, + ] } const validateEntityFieldType = (defs, def, field) => @@ -279,16 +277,16 @@ const validateEntityFieldType = (defs, def, field) => const validateEntityFieldArguments = (defs, def, field) => field.arguments.length > 0 - ? immutable.fromJS([ - { - loc: field.loc, - entity: def.name.value, - message: `\ + ? [ + { + loc: field.loc, + entity: def.name.value, + message: `\ Field '${field.name.value}': \ Field arguments are not supported.`, - }, - ]) - : List() + }, + ] + : [] const entityFieldExists = (entityDef, name) => entityDef.fields.find(field => field.name.value === name) !== undefined @@ -296,7 +294,7 @@ const entityFieldExists = (entityDef, name) => const validateDerivedFromDirective = (defs, def, field, directive) => { // Validate that there is a `field` argument and nothing else if (directive.arguments.length !== 1 || directive.arguments[0].name.value !== 'field') { - return immutable.fromJS([ + return [ { loc: directive.loc, entity: def.name.value, @@ -304,12 +302,12 @@ const validateDerivedFromDirective = (defs, def, field, directive) => { Field '${field.name.value}': \ @derivedFrom directive must have a 'field' argument`, }, - ]) + ] } // Validate that the "field" argument value is a string if (directive.arguments[0].value.kind !== 'StringValue') { - return immutable.fromJS([ + return [ { loc: directive.loc, entity: def.name.value, @@ -317,7 +315,7 @@ Field '${field.name.value}': \ Field '${field.name.value}': \ Value of the @derivedFrom 'field' argument must be a string`, }, - ]) + ] } let targetEntity = fieldTargetEntity(defs, field) @@ -325,7 +323,7 @@ Value of the @derivedFrom 'field' argument must be a string`, // This is handled in `validateInnerFieldType` but if we don't catch // the undefined case here, the code below will throw, as it assumes // the target entity exists - return immutable.fromJS([]) + return [] } let derivedFromField = targetEntity.fields.find( @@ -333,7 +331,7 @@ Value of the @derivedFrom 'field' argument must be a string`, ) if (derivedFromField === undefined) { - return immutable.fromJS([ + return [ { loc: directive.loc, entity: def.name.value, @@ -342,7 +340,7 @@ Field '${field.name.value}': \ @derivedFrom field '${directive.arguments[0].value.value}' \ does not exist on type '${targetEntity.name.value}'`, }, - ]) + ] } let backrefTypeName = unwrapType(derivedFromField.type) @@ -350,8 +348,12 @@ does not exist on type '${targetEntity.name.value}'`, // The field we are deriving from must either have type 'def' or one of the // interface types that 'def' is implementing - if (!backRefEntity || (backRefEntity.name.value !== def.name.value && !def.interfaces.find(intf => intf.name.value === backRefEntity.name.value))) { - return immutable.fromJS([ + if ( + !backRefEntity || + (backRefEntity.name.value !== def.name.value && + !def.interfaces.find(intf => intf.name.value === backRefEntity.name.value)) + ) { + return [ { loc: directive.loc, entity: def.name.value, @@ -362,22 +364,22 @@ on type '${targetEntity.name.value}' must have the type \ '${def.name.value}', '${def.name.value}!', '[${def.name.value}!]!', \ or one of the interface types that '${def.name.value}' implements`, }, - ]) + ] } - return List() + return [] } const validateEntityFieldDirective = (defs, def, field, directive) => directive.name.value === 'derivedFrom' ? validateDerivedFromDirective(defs, def, field, directive) - : List() + : [] const validateEntityFieldDirectives = (defs, def, field) => field.directives.reduce( (errors, directive) => errors.concat(validateEntityFieldDirective(defs, def, field, directive)), - List(), + [], ) const validateEntityFields = (defs, def) => @@ -387,73 +389,73 @@ const validateEntityFields = (defs, def) => .concat(validateEntityFieldType(defs, def, field)) .concat(validateEntityFieldArguments(defs, def, field)) .concat(validateEntityFieldDirectives(defs, def, field)), - List(), + [], ) const validateNoImportDirective = def => def.directives.find(directive => directive.name.value == 'import') - ? List([ - immutable.fromJS({ - loc: def.name.loc, - entity: def.name.value, - message: `@import directive only allowed on '${RESERVED_TYPE}' type`, - }), - ]) - : List() + ? [ + { + loc: def.name.loc, + entity: def.name.value, + message: `@import directive only allowed on '${RESERVED_TYPE}' type`, + }, + ] + : [] const validateNoFulltext = def => def.directives.find(directive => directive.name.value == 'fulltext') - ? List([ - immutable.fromJS({ - loc: def.name.loc, - entity: def.name.value, - message: `@fulltext directive only allowed on '${RESERVED_TYPE}' type`, - }), - ]) - : List() + ? [ + { + loc: def.name.loc, + entity: def.name.value, + message: `@fulltext directive only allowed on '${RESERVED_TYPE}' type`, + }, + ] + : [] const validateFulltextFields = (def, directive) => { return directive.arguments.reduce((errors, argument) => { return errors.concat( ['name', 'language', 'algorithm', 'include'].includes(argument.name.value) - ? List([]) - : List([ - immutable.fromJS({ - loc: directive.name.loc, - entity: def.name.value, - directive: fulltextDirectiveName(directive), - message: `found invalid argument: '${argument.name.value}', @fulltext directives only allow 'name', 'language', 'algorithm', and 'includes' arguments`, - }), - ]), + ? [] + : [ + { + loc: directive.name.loc, + entity: def.name.value, + directive: fulltextDirectiveName(directive), + message: `found invalid argument: '${argument.name.value}', @fulltext directives only allow 'name', 'language', 'algorithm', and 'includes' arguments`, + }, + ], ) - }, List([])) + }, []) } const validateFulltextName = (def, directive) => { let name = directive.arguments.find(argument => argument.name.value == 'name') return name ? validateFulltextArgumentName(def, directive, name) - : List([ - immutable.fromJS({ - loc: directive.name.loc, - entity: def.name.value, - directive: fulltextDirectiveName(directive), - message: `@fulltext argument 'name' must be specified`, - }), - ]) + : [ + { + loc: directive.name.loc, + entity: def.name.value, + directive: fulltextDirectiveName(directive), + message: `@fulltext argument 'name' must be specified`, + }, + ] } const validateFulltextArgumentName = (def, directive, argument) => { return argument.value.kind != 'StringValue' - ? List([ - immutable.fromJS({ - loc: directive.name.loc, - entity: def.name.value, - directive: fulltextDirectiveName(directive), - message: `@fulltext argument 'name' must be a string`, - }), - ]) - : List([]) + ? [ + { + loc: directive.name.loc, + entity: def.name.value, + directive: fulltextDirectiveName(directive), + message: `@fulltext argument 'name' must be a string`, + }, + ] + : [] } const fulltextDirectiveName = directive => { @@ -465,14 +467,14 @@ const validateFulltextLanguage = (def, directive) => { let language = directive.arguments.find(argument => argument.name.value == 'language') return language ? validateFulltextArgumentLanguage(def, directive, language) - : List([ - immutable.fromJS({ - loc: directive.name.loc, - entity: def.name.value, - directive: fulltextDirectiveName(directive), - message: `@fulltext argument 'language' must be specified`, - }), - ]) + : [ + { + loc: directive.name.loc, + entity: def.name.value, + directive: fulltextDirectiveName(directive), + message: `@fulltext argument 'language' must be specified`, + }, + ] } const validateFulltextArgumentLanguage = (def, directive, argument) => { @@ -495,27 +497,27 @@ const validateFulltextArgumentLanguage = (def, directive, argument) => { 'tr', ] if (argument.value.kind != 'EnumValue') { - return List([ - immutable.fromJS({ + return [ + { loc: directive.name.loc, entity: def.name.value, directive: fulltextDirectiveName(directive), message: `@fulltext 'language' value must be one of: ${languages.join(', ')}`, - }), - ]) + }, + ] } else if (!languages.includes(argument.value.value)) { - return List([ - immutable.fromJS({ + return [ + { loc: directive.name.loc, entity: def.name.value, directive: fulltextDirectiveName(directive), message: `@fulltext directive 'language' value must be one of: ${languages.join( ', ', )}`, - }), - ]) + }, + ] } else { - return List([]) + return [] } } @@ -523,35 +525,35 @@ const validateFulltextAlgorithm = (def, directive) => { let algorithm = directive.arguments.find(argument => argument.name.value == 'algorithm') return algorithm ? validateFulltextArgumentAlgorithm(def, directive, algorithm) - : List([ - immutable.fromJS({ - loc: directive.name.loc, - entity: def.name.value, - directive: fulltextDirectiveName(directive), - message: `@fulltext argument 'algorithm' must be specified`, - }), - ]) + : [ + { + loc: directive.name.loc, + entity: def.name.value, + directive: fulltextDirectiveName(directive), + message: `@fulltext argument 'algorithm' must be specified`, + }, + ] } const validateFulltextArgumentAlgorithm = (def, directive, argument) => { if (argument.value.kind != 'EnumValue') { - return List([ - immutable.fromJS({ + return [ + { loc: directive.name.loc, entity: def.name.value, directive: fulltextDirectiveName(directive), message: `@fulltext argument 'algorithm' must be one of: rank, proximityRank`, - }), - ]) + }, + ] } else if (!['rank', 'proximityRank'].includes(argument.value.value)) { - return List([ - immutable.fromJS({ + return [ + { loc: directive.name.loc, entity: def.name.value, directive: fulltextDirectiveName(directive), message: `@fulltext 'algorithm' value, '${argument.value.value}', must be one of: rank, proximityRank`, - }), - ]) + }, + ] } else { return List([]) } @@ -561,52 +563,52 @@ const validateFulltextInclude = (def, directive) => { let include = directive.arguments.find(argument => argument.name.value == 'include') if (include) { if (include.value.kind != 'ListValue') { - return List([ - immutable.fromJS({ + return [ + { loc: directive.name.loc, entity: def.name.value, directive: fulltextDirectiveName(directive), message: `@fulltext argument 'include' must be a list`, - }), - ]) + }, + ] } return include.value.values.reduce( (errors, type) => errors.concat(validateFulltextArgumentInclude(def, directive, type)), - List(), + [], ) } else { - return List([ - immutable.fromJS({ + return [ + { loc: directive.name.loc, entity: def.name.value, directive: fulltextDirectiveName(directive), message: `@fulltext argument 'include' must be specified`, - }), - ]) + }, + ] } } const validateFulltextArgumentInclude = (def, directive, argument) => { if (argument.kind != 'ObjectValue') { - return List([ - immutable.fromJS({ + return [ + { loc: directive.name.loc, entity: def.name.value, directive: fulltextDirectiveName(directive), message: `@fulltext argument 'include' must have the form '[{entity: "entityName", fields: [{name: "fieldName"}, ...]} ...]`, - }), - ]) + }, + ] } if (argument.fields.length != 2) { - return List([ - immutable.fromJS({ + return [ + { loc: directive.name.loc, entity: def.name.value, directive: fulltextDirectiveName(directive), message: `@fulltext argument include must have two fields, 'entity' and 'fields'`, - }), - ]) + }, + ] } return argument.fields.reduce( (errors, field) => @@ -617,33 +619,33 @@ const validateFulltextArgumentInclude = (def, directive, argument) => { const validateFulltextArgumentIncludeFields = (def, directive, field) => { if (!['entity', 'fields'].includes(field.name.value)) { - return List([ - immutable.fromJS({ + return [ + { loc: directive.name.loc, entity: def.name.value, directive: fulltextDirectiveName(directive), message: `@fulltext argument 'include > ${field.name.value}' must be be one of: entity, fields`, - }), - ]) + }, + ] } if (field.name.value == 'entity' && field.value.kind != 'StringValue') { - return List([ - immutable.fromJS({ + return [ + { loc: directive.name.loc, entity: def.name.value, directive: fulltextDirectiveName(directive), message: `@fulltext argument 'include > entity' must be the name of an entity in the schema enclosed in double quotes`, - }), - ]) + }, + ] } else if (field.name.value == 'fields' && field.value.kind != 'ListValue') { - return List([ - immutable.fromJS({ + return [ + { loc: directive.name.loc, entity: def.name.value, directive: fulltextDirectiveName(directive), message: `@fulltext argument 'include > fields' must be a list`, - }), - ]) + }, + ] } else if (field.name.value == 'fields' && field.value.kind == 'ListValue') { return field.value.values.reduce( (errors, field) => @@ -659,80 +661,74 @@ const validateFulltextArgumentIncludeFields = (def, directive, field) => { const validateFulltextArgumentIncludeFieldsObjects = (def, directive, argument) => { if (argument.kind != 'ObjectValue') { - return List([ - immutable.fromJS({ + return [ + { loc: directive.name.loc, entity: def.name.value, directive: fulltextDirectiveName(directive), message: `@fulltext argument 'include > fields' must have the form '[{ name: "fieldName" }, ...]`, - }), - ]) + }, + ] } else { return argument.fields.reduce( (errors, field) => errors.concat( validateFulltextArgumentIncludeArgumentFieldsObject(def, directive, field), ), - List(), + [], ) } } const validateFulltextArgumentIncludeArgumentFieldsObject = (def, directive, field) => { if (!['name'].includes(field.name.value)) { - return List([ - immutable.fromJS({ + return [ + { loc: directive.name.loc, entity: def.name.value, directive: fulltextDirectiveName(directive), message: `@fulltext argument 'include > fields' has invalid member '${field.name.value}', must be one of: name`, - }), - ]) + }, + ] } else if (field.name.value == 'name' && field.value.kind != 'StringValue') { - return List([ - immutable.fromJS({ + return [ + { loc: directive.name.loc, entity: def.name.value, directive: fulltextDirectiveName(directive), message: `@fulltext argument 'include > fields > name' must be the name of an entity field enclosed in double quotes`, - }), - ]) + }, + ] } else { return List([]) } } const importDirectiveTypeValidators = { - StringValue: (_def, _directive, _type) => List(), + StringValue: (_def, _directive, _type) => [], ObjectValue: (def, directive, type) => { - let errors = List() + let errors = [] if (type.fields.length != 2) { - return errors.push( - immutable.fromJS({ - loc: directive.name.loc, - entity: def.name.value, - message: `Import must be one of "Name" or { name: "Name", as: "Alias" }`, - }), - ) + return errors.push({ + loc: directive.name.loc, + entity: def.name.value, + message: `Import must be one of "Name" or { name: "Name", as: "Alias" }`, + }) } return type.fields.reduce((errors, field) => { if (!['name', 'as'].includes(field.name.value)) { - return errors.push( - immutable.fromJS({ - loc: directive.name.loc, - entity: def.name.value, - message: `@import field '${field.name.value}' invalid, may only be one of: name, as`, - }), - ) + return errors.push({ + loc: directive.name.loc, + entity: def.name.value, + message: `@import field '${field.name.value}' invalid, may only be one of: name, as`, + }) } if (field.value.kind != 'StringValue') { - return errors.push( - immutable.fromJS({ - loc: directive.name.loc, - entity: def.name.value, - message: `@import fields [name, as] must be strings`, - }), - ) + return errors.push({ + loc: directive.name.loc, + entity: def.name.value, + message: `@import fields [name, as] must be strings`, + }) } return errors }, errors) @@ -742,116 +738,112 @@ const importDirectiveTypeValidators = { const validateImportDirectiveType = (def, directive, type) => { return importDirectiveTypeValidators[type.kind] ? importDirectiveTypeValidators[type.kind](def, directive, type) - : List([ - immutable.fromJS({ - loc: directive.name.loc, - entity: def.name.value, - message: `Import must be one of "Name" or { name: "Name", as: "Alias" }`, - }), - ]) + : [ + { + loc: directive.name.loc, + entity: def.name.value, + message: `Import must be one of "Name" or { name: "Name", as: "Alias" }`, + }, + ] } const validateImportDirectiveArgumentTypes = (def, directive, argument) => { if (argument.value.kind != 'ListValue') { - return List([ - immutable.fromJS({ + return [ + { loc: directive.name.loc, entity: def.name.value, message: `@import argument 'types' must be an list`, - }), - ]) + }, + ] } return argument.value.values.reduce( (errors, type) => errors.concat(validateImportDirectiveType(def, directive, type)), - List(), + [], ) } const validateImportDirectiveArgumentFrom = (def, directive, argument) => { if (argument.value.kind != 'ObjectValue') { - return List([ - immutable.fromJS({ + return [ + { loc: directive.name.loc, entity: def.name.value, message: `@import argument 'from' must be an object`, - }), - ]) + }, + ] } if (argument.value.fields.length != 1) { - return List([ - immutable.fromJS({ + return [ + { loc: directive.name.loc, entity: def.name.value, message: `@import argument 'from' must have an 'id' or 'name' field`, - }), - ]) + }, + ] } return argument.value.fields.reduce((errors, field) => { if (!['name', 'id'].includes(field.name.value)) { - return errors.push( - immutable.fromJS({ - loc: field.name.loc, - entity: def.name.value, - message: `@import argument 'from' must be one of { name: "Name" } or { id: "ID" }`, - }), - ) + return errors.push({ + loc: field.name.loc, + entity: def.name.value, + message: `@import argument 'from' must be one of { name: "Name" } or { id: "ID" }`, + }) } if (field.value.kind != 'StringValue') { - return errors.push( - immutable.fromJS({ - loc: field.name.loc, - entity: def.name.value, - message: `@import argument 'from' must be one of { name: "Name" } or { id: "ID" } with string values`, - }), - ) + return errors.push({ + loc: field.name.loc, + entity: def.name.value, + message: `@import argument 'from' must be one of { name: "Name" } or { id: "ID" } with string values`, + }) } return errors - }, List()) + }, []) } const validateImportDirectiveFields = (def, directive) => { return directive.arguments.reduce((errors, argument) => { return errors.concat( ['types', 'from'].includes(argument.name.value) - ? List([]) - : List([ - immutable.fromJS({ - loc: directive.name.loc, - entity: def.name.value, - message: `found invalid argument: '${argument.name.value}', @import directives only allow 'types' and 'from' arguments`, - }), - ]), + ? [] + : [ + { + loc: directive.name.loc, + entity: def.name.value, + message: `found invalid argument: '${argument.name.value}', @import directives only allow 'types' and 'from' arguments`, + }, + ], ) - }, List([])) + }, []) } const validateImportDirectiveTypes = (def, directive) => { let types = directive.arguments.find(argument => argument.name.value == 'types') return types ? validateImportDirectiveArgumentTypes(def, directive, types) - : List([ - immutable.fromJS({ - loc: directive.name.loc, - entity: def.name.value, - message: `@import argument 'types' must be specified`, - }), - ]) + : [ + { + loc: directive.name.loc, + entity: def.name.value, + message: `@import argument 'types' must be specified`, + }, + ] } const validateImportDirectiveFrom = (def, directive) => { let from = directive.arguments.find(argument => argument.name.value == 'from') return from ? validateImportDirectiveArgumentFrom(def, directive, from) - : List([ - immutable.fromJS({ - loc: directive.name.loc, - entity: def.name.value, - message: `@import argument 'from' must be specified`, - }), - ]) + : [ + { + loc: directive.name.loc, + entity: def.name.value, + message: `@import argument 'from' must be specified`, + }, + ] } const validateImportDirective = (def, directive) => @@ -876,75 +868,73 @@ const validateSubgraphSchemaDirective = (def, directive) => { } else if (directive.name.value == 'fulltext') { return validateFulltext(def, directive) } else { - return List([ - immutable.fromJS({ + return [ + { loc: directive.name.loc, entity: def.name.value, message: `${RESERVED_TYPE} type only allows @import and @fulltext directives`, - }), - ]) + }, + ] } } const validateSubgraphSchemaDirectives = def => def.directives.reduce( (errors, directive) => errors.concat(validateSubgraphSchemaDirective(def, directive)), - List(), + [], ) const validateTypeHasNoFields = def => def.fields.length - ? List([ - immutable.fromJS({ - loc: def.name.loc, - entity: def.name.value, - message: `${def.name.value} type is not allowed any fields by convention`, - }), - ]) - : List() + ? [ + { + loc: def.name.loc, + entity: def.name.value, + message: `${def.name.value} type is not allowed any fields by convention`, + }, + ] + : [] -const validateAtLeastOneExtensionField = def => List() +const validateAtLeastOneExtensionField = def => [] const typeDefinitionValidators = { ObjectTypeDefinition: (defs, def) => def.name && def.name.value == RESERVED_TYPE ? List.of(...validateSubgraphSchemaDirectives(def), ...validateTypeHasNoFields(def)) : List.of( - ...validateEntityDirective(def), - ...validateEntityID(def), - ...validateEntityFields(defs, def), - ...validateNoImportDirective(def), - ...validateNoFulltext(def), - ), + ...validateEntityDirective(def), + ...validateEntityID(def), + ...validateEntityFields(defs, def), + ...validateNoImportDirective(def), + ...validateNoFulltext(def), + ), ObjectTypeExtension: (_defs, def) => validateAtLeastOneExtensionField(def), } const validateTypeDefinition = (defs, def) => typeDefinitionValidators[def.kind] !== undefined ? typeDefinitionValidators[def.kind](defs, def) - : List() + : [] const validateTypeDefinitions = defs => - defs.reduce((errors, def) => errors.concat(validateTypeDefinition(defs, def)), List()) + defs.reduce((errors, def) => errors.concat(validateTypeDefinition(defs, def)), []) const validateNamingCollisionsInTypes = types => { let seen = Set() let conflicting = Set() return types.reduce((errors, type) => { if (seen.has(type) && !conflicting.has(type)) { - errors = errors.push( - immutable.fromJS({ - loc: { start: 1, end: 1 }, - entity: type, - message: `Type '${type}' is defined more than once`, - }), - ) + errors = errors.push({ + loc: { start: 1, end: 1 }, + entity: type, + message: `Type '${type}' is defined more than once`, + }) conflicting = conflicting.add(type) } else { seen = seen.add(type) } return errors - }, List()) + }, []) } const validateNamingCollisions = (local, imported) => diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 70ab3bfd2..730cf6fe9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,7 +52,6 @@ importers: glob: 7.1.6 gluegun: https://github.com/edgeandnode/gluegun#v4.3.1-pin-colors-dep graphql: 15.5.0 - immutable: 3.8.2 ipfs-http-client: 34.0.0 jayson: 3.6.6 jest: 26.0.0 @@ -83,7 +82,6 @@ importers: glob: 7.1.6 gluegun: github.com/edgeandnode/gluegun/b34b9003d7bf556836da41b57ef36eb21570620a_debug@4.3.1 graphql: 15.5.0 - immutable: 3.8.2 ipfs-http-client: 34.0.0 jayson: 3.6.6 js-yaml: 3.13.1 @@ -3939,11 +3937,6 @@ packages: engines: {node: '>= 4'} dev: true - /immutable/3.8.2: - resolution: {integrity: sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==} - engines: {node: '>=0.10.0'} - dev: false - /import-fresh/3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'}