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..cb56c0929 100644 --- a/packages/cli/src/codegen/schema.js +++ b/packages/cli/src/codegen/schema.js @@ -1,25 +1,22 @@ -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 + 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,19 +24,19 @@ 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) { @@ -48,7 +45,7 @@ class IdField { } static fromTypeDef(def) { - return IdField.fromFields(def.get("fields")) + return IdField.fromFields(def.get('fields')) } } @@ -117,7 +114,7 @@ module.exports = class SchemaCodeGenerator { .get('fields') .reduce( (methods, field) => methods.concat(this._generateEntityFieldMethods(def, field)), - List(), + [], ) .forEach(method => klass.addMethod(method)) @@ -138,7 +135,7 @@ module.exports = class SchemaCodeGenerator { } _generateStoreMethods(entityName, idField) { - return List.of( + return [ tsCodegen.method( 'save', [], @@ -162,14 +159,14 @@ 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) { @@ -206,17 +203,13 @@ module.exports = class SchemaCodeGenerator { 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 = ` @@ -246,8 +239,13 @@ Suggestion: add an '!' to the member type of the List, change from '[${baseType} // 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 + .get('definitions') + .find( + def => + (this._isEntityTypeDefinition(def) || this._isInterfaceDefinition(def)) && + def.getIn(['name', 'value']) === typeName, + ) if (typeDef) { return IdField.fromTypeDef(typeDef).typeName() } else { diff --git a/packages/cli/src/codegen/schema.test.js b/packages/cli/src/codegen/schema.test.js index 1a35dcdbd..4269a80cc 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, @@ -20,7 +19,7 @@ const formatTS = code => const createSchemaCodeGen = schema => new SchemaCodeGenerator({ - ast: immutable.fromJS(graphql.parse(schema)), + ast: graphql.parse(schema), }) const testEntity = (generatedTypes, expectedEntity) => { 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..b38cd39f1 100644 --- a/packages/cli/src/codegen/types/index.js +++ b/packages/cli/src/codegen/types/index.js @@ -1,5 +1,3 @@ -const immutable = require('immutable') - const TYPE_CONVERSIONS = require('./conversions') // Conversion utilities @@ -15,7 +13,7 @@ const conversionsForTypeSystems = (fromTypeSystem, toTypeSystem) => { } const objectifyConversion = (fromTypeSystem, toTypeSystem, conversion) => { - return immutable.fromJS({ + return Object.freeze({ from: { typeSystem: fromTypeSystem, type: conversion.get(0), @@ -72,8 +70,7 @@ const ascTypeForProtocol = (protocol, protocolType) => findConversionFromType(protocol, 'AssemblyScript', protocolType).getIn(['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']) 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..6cbd7baf4 100644 --- a/packages/cli/src/commands/add.js +++ b/packages/cli/src/commands/add.js @@ -152,8 +152,8 @@ module.exports = { } const getEntities = (manifest) => { - let dataSources = manifest.result.get('dataSources', immutable.List()) - let templates = manifest.result.get('templates', immutable.List()) + let dataSources = manifest.result.get('dataSources', []) + let templates = manifest.result.get('templates', []) return dataSources .concat(templates) @@ -162,8 +162,8 @@ const getEntities = (manifest) => { } const getContractNames = (manifest) => { - let dataSources = manifest.result.get('dataSources', immutable.List()) - let templates = manifest.result.get('templates', immutable.List()) + let dataSources = manifest.result.get('dataSources', []) + let templates = manifest.result.get('templates', []) return dataSources .concat(templates) @@ -190,7 +190,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 +200,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..186e6824f 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') @@ -598,7 +597,7 @@ 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()) { updates.push({ @@ -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..59303b021 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,7 +28,7 @@ module.exports = class EthereumSubgraph { dataSourceOrTemplate.get('path'), ), ), - immutable.List(), + [], ) } @@ -36,8 +38,8 @@ module.exports = class EthereumSubgraph { let abiName = dataSource.getIn(['source', 'abi']) let abiNames = dataSource.getIn(['mapping', 'abis']).map(abi => abi.get('name')) let nameErrors = abiNames.includes(abiName) - ? immutable.List() - : immutable.fromJS([ + ? [] + : [ { path: [...path, 'source', 'abi'], message: `\ @@ -48,7 +50,7 @@ ${abiNames .map(name => `- ${name}`) .join('\n')}`, }, - ]) + ] // Validate that all ABI files are valid let fileErrors = dataSource @@ -58,30 +60,30 @@ ${abiNames 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, - }), - ) + return errors.push({ + path: [...path, 'mapping', 'abis', abiIndex, 'file'], + message: e.message, + }) } - }, immutable.List()) + }, []) 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) { @@ -96,12 +98,12 @@ ${abiNames } 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()) + .getIn(['mapping', 'eventHandlers'], []) .map(handler => handler.get('event')) // Obtain event signatures from the ABI @@ -113,19 +115,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(), + }), + [], ) } @@ -152,7 +152,7 @@ ${abiEvents // Obtain event signatures from the mapping let manifestFunctions = dataSource - .getIn(['mapping', 'callHandlers'], immutable.List()) + .getIn(['mapping', 'callHandlers'], []) .map(handler => handler.get('function')) // Obtain event signatures from the ABI @@ -164,28 +164,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..be519da27 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') @@ -38,7 +37,7 @@ module.exports = class EthereumTypeGenerator { ), abis, ), - immutable.List(), + [], ) } catch (e) { throw Error(`Failed to load contract ABIs: ${e.message}`) @@ -68,7 +67,7 @@ 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 template of subgraph.get('templates', [])) { for (let abi of template.getIn(['mapping', 'abis'])) { abis.push( this._loadDataSourceTemplateABI( 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..39452eca2 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 @@ -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..579571f88 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') @@ -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'), { diff --git a/packages/cli/src/validation/contract.js b/packages/cli/src/validation/contract.js index 53df01f70..12fe33c73 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) @@ -33,21 +31,18 @@ const validateContractValues = (manifest, protocol) => { let contractValue = dataSource.getIn(['source', fieldName]) - const { valid, error } = validateContract(contractValue, ProtocolContract) // Validate whether the contract is valid for the 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..3c04b5bdb 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 @@ -49,7 +45,7 @@ const resolveType = (schema, type) => /** * A map of supported validators. */ -const validators = immutable.fromJS({ +const validators = Object.freeze({ ScalarTypeDefinition: (value, ctx) => validators.get(ctx.getIn(['type', 'name', 'value']))(value, ctx), @@ -83,12 +79,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,12 +100,12 @@ 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) @@ -131,93 +127,93 @@ 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) => { + const enumValues = ctx.getIn(['type', 'values']).map(v => { return v.getIn(['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) => { @@ -229,17 +225,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 +247,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 +258,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 +276,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 +287,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 +318,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'}