Skip to content

Commit

Permalink
feat(typegen): tuple return type for event.params (Joystream#358)
Browse files Browse the repository at this point in the history
* feat(hydra-typegen): use tuple-based return type for event parameters (vs type-based names)

affects: @dzlzv/hydra-typegen

* fix(hydra-cli): update mappings for new typegen event parameters format

affects: @dzlzv/hydra-cli, @dzlzv/hydra-typegen, sample, sample-mappings
  • Loading branch information
dzhelezov committed May 7, 2021
1 parent d343da1 commit bf16156
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 159 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ export async function balancesTransfer(
event: Balances.TransferEvent
) {
const transfer = new Transfer()
transfer.from = Buffer.from(event.data.accountIds[0].toHex())
transfer.to = Buffer.from(event.data.accountIds[1].toHex())
transfer.value = event.data.balance.toBn()
transfer.from = Buffer.from(event.params[0].toHex())
transfer.to = Buffer.from(event.params[1].toHex())
transfer.value = event.params[2].toBn()
transfer.block = event.ctx.blockNumber
transfer.comment = `Transferred ${transfer.value} from ${transfer.from} to ${transfer.to}`
transfer.insertedAt = new Date()
Expand Down
13 changes: 7 additions & 6 deletions packages/hydra-e2e-tests/fixtures/mappings/mappings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { DatabaseManager } from '@dzlzv/hydra-db-utils'
import BN from 'bn.js'
import { SubstrateEvent } from '@dzlzv/hydra-common'

import { Transfer } from '../generated/graphql-server/src/modules/transfer/transfer.model'
import { BlockTimestamp } from '../generated/graphql-server/src/modules/block-timestamp/block-timestamp.model'
import {
Transfer,
BlockTimestamp,
BlockHook,
HookType,
} from '../generated/graphql-server/src/modules/block-hook/block-hook.model'
} from '../generated/graphql-server/model'

// run 'NODE_URL=<RPC_ENDPOINT> EVENTS=<comma separated list of events> yarn codegen:mappings-types'
// to genenerate typescript classes for events, such as Balances.TransferEvent
Expand All @@ -19,9 +19,10 @@ export async function balancesTransfer(
event: Balances.TransferEvent
) {
const transfer = new Transfer()
transfer.from = Buffer.from(event.data.accountIds[0].toHex())
transfer.to = Buffer.from(event.data.accountIds[1].toHex())
transfer.value = event.data.balance.toBn()
const [from, to, value] = event.params
transfer.from = Buffer.from(from.toHex())
transfer.to = Buffer.from(to.toHex())
transfer.value = value.toBn()
transfer.block = event.ctx.blockNumber
transfer.comment = `Transferred ${transfer.value} from ${transfer.from} to ${transfer.to}`
transfer.insertedAt = new Date()
Expand Down
24 changes: 19 additions & 5 deletions packages/hydra-typegen/src/generators/helpers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
import { expect } from 'chai'
import { nameFromType } from '.'
import { helper } from './helpers'
import { compact as c } from '../util'

describe('helpers', () => {
it('should name from arg type', () => {
expect(nameFromType('Compact<Balance> & Codec')).to.equal('balance')
expect(nameFromType('MyLongType & Codec')).to.equal('myLongType')
expect(nameFromType('SimpleType')).to.equal('simpleType')
it('should render return type ', () => {
const prmsReturn = helper.paramsReturnType.bind({
args: ['Type1', 'Type2 & Codec', 'Option<Type3>'],
})
expect(c(prmsReturn())).to.equal(c(`[Type1, Type2 & Codec, Option<Type3>]`))
})

it('should render return type statement', () => {
const prmsReturnStmt = helper.paramsReturnStmt.bind({
args: ['Type1', 'Type2 & Balance'],
})
expect(c(prmsReturnStmt())).to.equal(
c(`return [createTypeUnsafe<Type1 & Codec>(
typeRegistry, 'Type1', [this.ctx.params[0].value]),
createTypeUnsafe<Type2 & Balance & Codec>(
typeRegistry, 'Type2 & Balance', [this.ctx.params[1].value])]`)
)
})
})
172 changes: 56 additions & 116 deletions packages/hydra-typegen/src/generators/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,16 @@
import Handlebars from 'handlebars'
import { ImportsDef } from './types'
import { kebabCase, countBy, last, camelCase, upperFirst } from 'lodash'
import { kebabCase, camelCase, upperFirst } from 'lodash'
import { Arg } from '../metadata'
import { warn } from '../log'

const debug = require('debug')('hydra-typegen:helpers')

export const handlebars = Handlebars.create()

const callArgValueGetter = (ctxIndex: number) =>
`[this.extrinsic.args[${ctxIndex}].value]`

const eventParamValueGetter = (ctxIndex: number) =>
`[this.ctx.params[${ctxIndex}].value]`

handlebars.registerHelper({
imports() {
const { imports } = (this as unknown) as { imports: ImportsDef }
return renderImports(imports)
},

kebabCase(s: string) {
return kebabCase(s)
},

camelCase(s: string) {
return camelCase(s)
},

pascalCase(s: string) {
return upperFirst(camelCase(s))
},

toString(s: any) {
if (s.toString !== undefined) {
return s.toString()
}
return JSON.stringify(s)
},

getters() {
const { args } = (this as unknown) as { args: string[] | Arg[] }
return isNamedArgs(args)
? renderNamedArgs(args, callArgValueGetter)
: renderTypeOnlyArgs(args, eventParamValueGetter)
},
})

function isNamedArgs(args: string[] | Arg[]): args is Arg[] {
if (args.length === 0) {
warn(`WARNING: empty arguments list`)
return true
}
return 'name' in (args[0] as any)
}

export function renderImports(imports: ImportsDef): string {
const defs = Object.keys(imports).map((loc: string) => ({
file: loc,
Expand Down Expand Up @@ -90,86 +46,70 @@ export function renderNamedArgs(
}, '')
}

export function renderTypeOnlyArgs(
argTypes: string[],
ctxValueGetter: (ctxIndex: number) => string
): string {
debug(`Rendering type only args: ${JSON.stringify(argTypes, null, 2)}`)

const grouped = countBy(argTypes)
const typeToIndices: Record<string, number[]> = {}

argTypes.forEach((a, i) => {
if (typeToIndices[a]) {
typeToIndices[a].push(i)
} else {
typeToIndices[a] = [i]
}
})

return argTypes.reduce((result, argType: string, index) => {
let getStmt = ''

if (grouped[argType] === 1) {
getStmt = `get ${nameFromType(argType)}(): ${argType} {
return ${renderCreateTypeStmt(argType, ctxValueGetter(index))}
}`
// once we at the last index of that type
} else if (index === last(typeToIndices[argType])) {
getStmt =
// prettier-ignore
`get ${nameFromType(argType)}s(): { [key: number]: ${argType} } {
return {
${renderCreateTypesArray(argType, typeToIndices[argType], ctxValueGetter)}
}
}`
}
return `${result}\n${getStmt}\n`
}, '')
}

function renderCreateTypesArray(
argType: string,
indices: number[],
ctxValueGetter: (ctxIndex: number) => string
) {
return indices.reduce(
(result, argIndex, i) =>
`${result}${i}: ${renderCreateTypeStmt(
argType,
ctxValueGetter(argIndex)
)},\n`,
''
export function renderTypedParams(argTypes: string[]): string {
const returnType = `[${argTypes.join(',')}]`
const returnObjects = argTypes.map((argType, index) =>
renderCreateTypeStmt(argType, eventParamValueGetter(index))
)
return `get params(): ${returnType} {
return [${returnObjects.join(',')}]
}`
}

function renderCreateTypeStmt(argType: string, ctxValueGetter: string) {
return `createTypeUnsafe<${argType} & Codec>(
typeRegistry, '${argType}', ${ctxValueGetter}) `
}

export function inferName(arg: string | Arg): string {
if (typeof arg === 'string') {
return nameFromType(arg)
}
export const helper: Handlebars.HelperDeclareSpec = {
imports() {
const { imports } = (this as unknown) as { imports: ImportsDef }
return renderImports(imports)
},

if (arg.name !== undefined) {
return arg.name.toString()
}
kebabCase(s: string) {
return kebabCase(s)
},

return nameFromType(arg.type.toRawType())
}
camelCase(s: string) {
return camelCase(s)
},

export function nameFromType(rawType: string): string {
let stripped = rawType.trim()
if (stripped.includes('&')) {
// if its a union type, take only the first part as it's
// probably most descriptive
stripped = stripped.split('&')[0].trim()
}
const match = /[^<]*<([^>]+)>.*/g.exec(stripped)
if (match && match[1]) {
stripped = match[1]
}
return camelCase(stripped)
pascalCase(s: string) {
return upperFirst(camelCase(s))
},

// eslint-disable-next-line @typescript-eslint/no-explicit-any
toString(s: any) {
if (s.toString !== undefined) {
return s.toString()
}
return JSON.stringify(s)
},

paramsReturnType() {
const { args } = (this as unknown) as { args: string[] }
return `[${args.join(',')}]`
},

paramsReturnStmt() {
const { args } = (this as unknown) as { args: string[] }
const returnObjects = args.map((argType, index) =>
renderCreateTypeStmt(argType, eventParamValueGetter(index))
)
return `return [${returnObjects.join(',\n')}]`
},

paramGetter() {
const { args } = (this as unknown) as { args: string[] }
return renderTypedParams(args)
},

namedGetters() {
const { args } = (this as unknown) as { args: Arg[] }
return renderNamedArgs(args, callArgValueGetter)
},
}

export const handlebars = Handlebars.create()
handlebars.registerHelper(helper)
10 changes: 2 additions & 8 deletions packages/hydra-typegen/src/metadata/default-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,10 @@ import * as genericClasses from '@polkadot/types/generic'
import * as primitiveClasses from '@polkadot/types/primitive'
import * as interfaceDefinitions from '@polkadot/types/interfaces/definitions'

import { Json, Raw } from '@polkadot/types/codec'

const primitive = {
...primitiveClasses,
Json,
Raw,
}
const primitive = [...Object.keys(primitiveClasses), 'Json', 'Raw']

export const builtInClasses = [
...Object.keys(primitive),
...primitive,
...Object.keys(codecClasses as Record<string, unknown>),
...Object.keys(genericClasses as Record<string, unknown>),
...Object.keys(extrinsicClasses as Record<string, unknown>),
Expand Down
1 change: 1 addition & 0 deletions packages/hydra-typegen/src/metadata/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ async function fromChain(
)
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
websocket.onmessage = (message: any): void => {
const data = JSON.parse(message.data)
if (data.error) {
Expand Down
12 changes: 3 additions & 9 deletions packages/hydra-typegen/src/templates/module.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ export namespace {{module.name}} {

constructor(public readonly ctx: SubstrateEvent) {}

get data(): {{name}}_Params {
get params(): {{{paramsReturnType}}} {

{{#if @root.validateArgs}}
if (!this.validateParams()) {
throw new Error(`Expected event parameters of types [${this.expectedParamTypes.join(',')}] but received [${this.ctx.params.map((p) => p.type).join(',')}]`)
}
{{/if}}

return new {{name}}_Params(this.ctx)
{{{paramsReturnStmt}}}
}

validateParams(): boolean {
Expand All @@ -47,13 +47,7 @@ export namespace {{module.name}} {

}

class {{name}}_Params {

constructor(public readonly ctx: SubstrateEvent) {}

{{{getters}}}

}
{{/each}}

{{#each calls}}
Expand Down Expand Up @@ -102,7 +96,7 @@ export namespace {{module.name}} {

constructor(public readonly extrinsic: SubstrateExtrinsic) {}

{{{getters}}}
{{{namedGetters}}}

}
{{/each}}
Expand Down
10 changes: 10 additions & 0 deletions packages/hydra-typegen/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,13 @@ export function pushToDictionary<V>(
dict[key].push(...values)
}
}

/**
* replace all whitespaces and carriage returns
*
* @param s
* @returns the same string with all whitecharacters removed
*/
export function compact(s: string): string {
return s.replace(/\s/g, '')
}
13 changes: 6 additions & 7 deletions packages/sample/mappings/mappings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,24 @@ import { SubstrateEvent } from '@dzlzv/hydra-common'

const start = Date.now()
let blockTime = 0
let blockStartTime = 0
let totalEvents = 0
let totalBlocks = 0

export async function balancesTransfer({
store,
event,
block,
}: {
store: DatabaseManager
event: SubstrateEvent
block: { blockNumber: number }
}) {
const transfer = new Transfer()
const _event = new Balances.TransferEvent(event)
transfer.from = Buffer.from(_event.data.accountIds[0].toHex())
transfer.to = Buffer.from(_event.data.accountIds[1].toHex())
transfer.value = _event.data.balance.toBn()
transfer.block = block.blockNumber
const [from, to, value] = new Balances.TransferEvent(event).params
transfer.from = Buffer.from(from.toHex())
transfer.to = Buffer.from(to.toHex())
transfer.value = value.toBn()

transfer.block = event.blockNumber
transfer.comment = `Transferred ${transfer.value} from ${transfer.from} to ${transfer.to}`
transfer.insertedAt = new Date()
await store.save<Transfer>(transfer)
Expand Down
Loading

0 comments on commit bf16156

Please sign in to comment.