10 minutos
Guía de Migración de AssemblyScript
Up until now, Subgraphs have been using one of the first versions of AssemblyScript (v0.6). Finally we’ve added support for the newest one available (v0.19.10)! 🎉
That will enable Subgraph developers to use newer features of the AS language and standard library.
Esta guía es aplicable para cualquiera que use graph-cli
/graph-ts
bajo la versión 0.22.0
. Si ya estás en una versión superior (o igual) a esa, has estado usando la versión 0.19.10
de AssemblyScript 🙂
Note: As of 0.24.0
, graph-node
can support both versions, depending on the apiVersion
specified in the Subgraph manifest.
Características
Nueva Funcionalidad
TypedArray
s ahora puede construirse desdeArrayBuffer
s usando el nuevowrap
método estático (v0.8.1)- Nuevas funciones de la biblioteca estándar:
String#toUpperCase
,String#toLowerCase
,String#localeCompare
andTypedArray#set
(v0.9.0) - Se agregó soporte para x instanceof GenericClass (v0.9.2)
- Se agregó
StaticArray<T>
, una más eficiente variante de array (v0.9.3) - Se agregó
Array<T>#flat
(v0.10.0) - Se implementó el argumento
radix
enNumber#toString
(v0.10.1) - Se agregó soporte para los separadores en los literales de punto flotante (v0.13.7)
- Se agregó soporte para las funciones de primera clase (v0.14.0)
- Se agregaron builtins:
i32/i64/f32/f64.add/sub/mul
(v0.14.13) - Se implementó
Array/TypedArray/String#at
(v0.18.2) - Se agregó soporte para las plantillas de strings literales (v0.18.17)
- Se agregó
encodeURI(Component)
ydecodeURI(Component)
(v0.18.27) - Se agregó
toString
,toDateString
andtoTimeString
toDate
(v0.18.29) - Se agregó
toUTCString
paraDate
(v0.18.30) - Se agregó
nonnull/NonNullable
builtin type (v0.19.2)
Optimizaciones
- Funciones
Math
comoexp
,exp2
,log
,log2
ypow
fueron reemplazadas por variantes más rápidas (v0.9.0) - Optimizar ligeramente
Math.mod
(v0.17.1) - Caché de más accesos a campos en std Map y Set (v0.17.8)
- Optimizar para potencias de dos en
ipow32/64
(v0.18.2)
Otros
- El tipo de un de array literal ahora puede inferirse a partir de su contenido (v0.9.0)
- Actualizado stdlib a Unicode 13.0.0 (v0.10.0)
¿Cómo actualizar?
- Change your mappings
apiVersion
insubgraph.yaml
to0.0.9
:
1...2dataSources:3 ...4 mapping:5 ...6 apiVersion: 0.0.97 ...
- Actualiza la
graph-cli
que usas a laúltima
versión:
1# si lo tiene instalada de forma global2npm install --global @graphprotocol/graph-cli@latest34# o desde su subgrafo si lo tiene como dependencia5npm install --save-dev @graphprotocol/graph-cli@latest
- Haz lo mismo con
graph-ts
, pero en lugar de instalarlo globalmente, guárdalo en tus dependencias principales:
1npm install --save @graphprotocol/graph-ts@latest
- Sigue el resto de la guía para arreglar los cambios que rompen el lenguaje.
- Ejecuta
codegen
ydeploy
nuevamente.
Rompiendo los esquemas
Anulabilidad
En la versión anterior de AssemblyScript, podías crear un código como el siguiente:
1function load(): Value | null { ... }23let maybeValue = load();4maybeValue.aMethod();
Sin embargo, en la versión más reciente, debido a que el valor es anulable, es necesario que lo compruebes, así:
1let maybeValue = load()23if (maybeValue) {4 maybeValue.aMethod() // `maybeValue` is not null anymore5}
O forzarlo así:
1let maybeValue = load()! // rompiendo el runtime si el valor es nulo23maybeValue.aMethod()
If you are unsure which to choose, we recommend always using the safe version. If the value doesn’t exist you might want to just do an early if statement with a return in you Subgraph handler.
Variable Shadowing
Antes podías hacer variable shadowing y un código como este funcionaría:
1let a = 102let b = 203let a = a + b
Sin embargo, ahora esto ya no es posible, y el compilador devuelve este error:
1ERROR TS2451: Cannot redeclare block-scoped variable 'a'23 let a = a + b;4 ~~~~~~~~~~~~~5in assembly/index.ts(4,3)
Tendrás que cambiar el nombre de las variables duplicadas si tienes una variable shadowing.
Comparaciones Nulas
By doing the upgrade on your Subgraph, sometimes you might get errors like these:
1ERROR TS2322: Type '~lib/@graphprotocol/graph-ts/common/numbers/BigInt | null' is not assignable to type '~lib/@graphprotocol/graph-ts/common/numbers/BigInt'.2 if (decimals == null) {3 ~~~~4 in src/mappings/file.ts(41,21)
Para solucionarlo puedes simplemente cambiar la declaración if
por algo así:
1if (!decimals) {23 // or45 if (decimals === null) {
Lo mismo ocurre si haces != en lugar de ==.
Casting
La forma común de hacer el casting antes era simplemente usar la palabra clave as
, de la siguiente forma:
1let byteArray = new ByteArray(10)2let uint8Array = byteArray as Uint8Array // equivalent to: <Uint8Array>byteArray
Sin embargo, esto solo funciona en dos casos:
- Casting de primitivas (entre tipos como
u8
,i32
,bool
; eg:let b: isize = 10; b as usize
); - Upcasting en la herencia de clases (subclase → superclase)
Ejemplos:
1// primitive casting2let a: usize = 103let b: isize = 54let c: usize = a + (b as usize)
1// upcasting on class inheritance2class Bytes extends Uint8Array {}34let bytes = new Bytes(2)5// <Uint8Array>bytes // same as: bytes as Uint8Array
Hay dos escenarios en los que puede querer cast, pero usando as
/<T>var
no es seguro:
- Downcasting en la herencia de clases (superclase → subclase)
- Entre dos tipos que comparten una superclase
1// downcasting on class inheritance2class Bytes extends Uint8Array {}34let uint8Array = new Uint8Array(2)5// <Bytes>uint8Array // breaks in runtime :(
1// between two types that share a superclass2class Bytes extends Uint8Array {}3class ByteArray extends Uint8Array {}45let bytes = new Bytes(2)6// <ByteArray>bytes // breaks in runtime :(
Para esos casos, puedes usar la función changetype<T>
:
1// downcasting on class inheritance2class Bytes extends Uint8Array {}34let uint8Array = new Uint8Array(2)5changetype<Bytes>(uint8Array) // works :)
1// entre dos tipos que comparten un superclass2class Bytes extends Uint8Array {}3class ByteArray extends Uint8Array {}45let bytes = new Bytes(2)6changetype<ByteArray>(bytes) // works :)
Si solo quieres eliminar la anulabilidad, puedes seguir usando el as
operador (o <T>variable
), pero asegúrate de que el valor no puede ser nulo, de lo contrario se romperá.
1// eliminar anulabilidad2let previousBalance = AccountBalance.load(balanceId) // AccountBalance | null34if (previousBalance != null) {5 return previousBalance as AccountBalance // sabe remove null6}78let newBalance = new AccountBalance(balanceId)
Para el caso de la anulabilidad se recomienda echar un vistazo al nullability check feature, hará que tu código sea más limpio 🙂
También hemos añadido algunos métodos estáticos en algunos tipos para facilitar el casting, son:
- Bytes.fromByteArray
- Bytes.fromUint8Array
- BigInt.fromByteArray
- ByteArray.fromBigInt
Comprobación de anulabilidad con acceso a la propiedad
Para usar el nullability check feature puedes usar la declaración if
o el operador ternario (?
and :
) asi:
1let something: string | null = 'data'23let somethingOrElse = something ? something : 'else'45// o67let somethingOrElse89if (something) {10 somethingOrElse = something11} else {12 somethingOrElse = 'else'13}
Sin embargo eso solo funciona cuando estás haciendo el if
/ ternario en una variable, no en un acceso a una propiedad, como este:
1class Container {2 data: string | null3}45let container = new Container()6container.data = 'data'78let somethingOrElse: string = container.data ? container.data : 'else' // doesn't compile
Lo que produce este error:
1ERROR TS2322: Type '~lib/string/String | null' is not assignable to type '~lib/string/String'.23 let somethingOrElse: string = container.data ? container.data : "else";4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Para solucionar este problema, puedes crear una variable para ese acceso a la propiedad de manera que el compilador pueda hacer la magia de la comprobación de nulidad:
1class Container {2 data: string | null3}45let container = new Container()6container.data = 'data'78let data = container.data910let somethingOrElse: string = data ? data : 'else' // compiles just fine :)
Sobrecarga de operadores con acceso a propiedades
Si intentas sumar (por ejemplo) un tipo anulable (desde un acceso a una propiedad) con otro no anulable, el compilador de AssemblyScript en lugar de dar un error en el tiempo de compilación advirtiendo que uno de los valores es anulable, simplemente compila en silencio, dando oportunidad a que el código se rompa en tiempo de ejecución.
1class BigInt extends Uint8Array {2 @operator('+')3 plus(other: BigInt): BigInt {4 // ...5 }6}78class Wrapper {9 public constructor(public n: BigInt | null) {}10}1112let x = BigInt.fromI32(2)13let y: BigInt | null = null1415x + y // give compile time error about nullability1617let wrapper = new Wrapper(y)1819wrapper.n = wrapper.n + x // doesn't give compile time errors as it should
We’ve opened a issue on the AssemblyScript compiler for this, but for now if you do these kind of operations in your Subgraph mappings, you should change them to do a null check before it.
1let wrapper = new Wrapper(y)23if (!wrapper.n) {4 wrapper.n = BigInt.fromI32(0)5}67wrapper.n = wrapper.n + x // now `n` is guaranteed to be a BigInt
Inicialización del valor
Si tienes algún código como este:
1var value: Type // null2value.x = 103value.y = 'content'
It will compile but break at runtime, that happens because the value hasn’t been initialized, so make sure your Subgraph has initialized their values, like this:
1var value = new Type() // initialized2value.x = 103value.y = 'content'
También si tienes propiedades anulables en una entidad GraphQL, como esta:
1type Total @entity {2 id: Bytes!3 amount: BigInt4}
Y tienes un código similar a este:
1let total = Total.load('latest')23if (total === null) {4 total = new Total('latest')5}67total.amount = total.amount + BigInt.fromI32(1)
Tendrás que asegurarte de inicializar el valor total.amount
, porque si intentas acceder como en la última línea para la suma, se bloqueará. Así que o bien la inicializas primero:
1let total = Total.load('latest')23if (total === null) {4 total = new Total('latest')5 total.amount = BigInt.fromI32(0)6}78total.tokens = total.tokens + BigInt.fromI32(1)
O simplemente puedes cambiar tu esquema GraphQL para no usar un tipo anulable para esta propiedad, entonces la inicializaremos como cero en el paso codegen
😉
1type Total @entity {2 id: Bytes!3 amount: BigInt!4}
1let total = Total.load('latest')23if (total === null) {4 total = new Total('latest') // justo luego de haber iniciado la propiedad de no-anulable5}67total.amount = total.amount + BigInt.fromI32(1)
Inicialización de las propiedades de la clase
Si exportas alguna clase con propiedades que son otras clases (declaradas por ti o por la librería estándar) así:
1class Thing {}23export class Something {4 value: Thing5}
El compilador dará un error porque tienes que añadir un inicializador para las propiedades que son clases, o añadir el operador !
:
1export class Something {2 constructor(public value: Thing) {}3}45// o67export class Something {8 value: Thing910 constructor(value: Thing) {11 this.value = value12 }13}1415// or1617export class Something {18 value!: Thing19}
Inicialización de Array
La clase Array
sigue aceptando un número para inicializar la longitud de la lista, sin embargo hay que tener cuidado porque operaciones como .push
en realidad aumentarán el tamaño en lugar de añadirlo al principio, por ejemplo:
1let arr = new Array<string>(5) // ["", "", "", "", ""]23arr.push('something') // ["", "", "", "", "", "something"] // size 6 :(
Dependiendo de los tipos que estés utilizando, por ejemplo los anulables, y de cómo estés accediendo a ellos, podrías encontrarte con un error de ejecución como este:
1ERRO Handler skipped due to execution failure, error: Mapping aborted at ~lib/array.ts, line 110, column 40, with message: Element type must be nullable if array is holey wasm backtrace: 0: 0x19c4 - <unknown>!~lib/@graphprotocol/graph-ts/index/format 1: 0x1e75 - <unknown>!~lib/@graphprotocol/graph-ts/common/collections/Entity#constructor 2: 0x30b9 - <unknown>!node_modules/@graphprotocol/graph-ts/global/global/id_of_type
Para realmente empujar al principio deberías o bien, inicializar el Array
con tamaño cero, así:
1let arr = new Array<string>(0) // []23arr.push('something') // ["something"]
O debería mutar a través de un índice:
1let arr = new Array<string>(5) // ["", "", "", "", ""]23arr[0] = 'something' // ["something", "", "", "", ""]
Esquema GraphQL
Esto no es un cambio directo de AssemblyScript, pero es posible que tengas que actualizar tu archivo schema.graphql
.
Ahora ya no puedes definir campos en tus tipos que sean Listas No Anulables. Si tienes un esquema como este:
1type Something @entity {2 id: Bytes!3}45type MyEntity @entity {6 id: Bytes!7 invalidField: [Something]! # no longer valid8}
Tendrás que añadir un !
al miembro del tipo Lista, así:
1type Something @entity {2 id: Bytes!3}45type MyEntity @entity {6 id: Bytes!7 invalidField: [Something!]! # valid8}
Esto cambió debido a las diferencias de anulabilidad entre las versiones de AssemblyScript, y está relacionado con el archivo src/generated/schema.ts
(ruta por defecto, puede que lo hayas cambiado).
Otros
- Alineado
Map#set
ySet#add
con el spec, devolviendothis
(v0.9.2) - Las arrays ya no heredan de ArrayBufferView, sino que son distintas (v0.10.0)
- Las clases inicializadas a partir de objetos literales ya no pueden definir un constructor (v0.10.0)
- El resultado de una operación binaria
**
es ahora el entero denominador común si ambos operandos son enteros. Anteriormente, el resultado era un flotante como si se llamara aMath/f.pow
(v0.11.0) - Coerción
NaN
afalse
cuando casting abool
(v0.14.9) - Al desplazar un valor entero pequeño de tipo
i8
/u8
oi16
/u16
, sólo los 3 o 4 bits menos significativos del valor RHS afectan al resultado, de forma análoga al resultado de uni32.shl
que sólo se ve afectado por los 5 bits menos significativos del valor RHS. Ejemplo:someI8 << 8
previamente producía el valor0
, pero ahora producesomeI8
debido a enmascarar el RHS como8 & 7 = 0
(3 bits) (v0.17.0) - Corrección de errores en las comparaciones de strings relacionales cuando los tamaños difieren (v0.17.8)