Guía de Migración de AssemblyScript
Reading time: 10 min
Hasta ahora, los subgrafos han utilizado una de las (v0.6). Finalmente, hemos añadido soporte para la (v0.19.10)! 🎉! 🎉
Esto permitirá a los desarrolladores de subgrafos utilizar las nuevas características del lenguaje AS y la librería estándar.
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 🙂
Nota: A partir de 0.24.0
, graph-node
puede soportar ambas versiones, dependiendo del apiVersion
especificado en el manifiesto del subgrafo.
TypedArray
s ahora puede construirse desdeArrayBuffer
s usando el ()- Nuevas funciones de la biblioteca estándar:
String#toUpperCase
,String#toLowerCase
,String#localeCompare
andTypedArray#set
() - Se agregó soporte para x instanceof GenericClass ()
- Se agregó
StaticArray<T>
, una más eficiente variante de array () - Se agregó
Array<T>#flat
() - Se implementó el argumento
radix
enNumber#toString
() - Se agregó soporte para los separadores en los literales de punto flotante ()
- Se agregó soporte para las funciones de primera clase ()
- Se agregaron builtins:
i32/i64/f32/f64.add/sub/mul
() - Se implementó
Array/TypedArray/String#at
() - Se agregó soporte para las plantillas de strings literales ()
- Se agregó
encodeURI(Component)
ydecodeURI(Component)
() - Se agregó
toString
,toDateString
andtoTimeString
toDate
() - Se agregó
toUTCString
paraDate
() - Se agregó
nonnull/NonNullable
builtin type ()
- Funciones
Math
comoexp
,exp2
,log
,log2
ypow
fueron reemplazadas por variantes más rápidas () - Optimizar ligeramente
Math.mod
() - Caché de más accesos a campos en std Map y Set ()
- Optimizar para potencias de dos en
ipow32/64
()
- El tipo de un de array literal ahora puede inferirse a partir de su contenido ()
- Actualizado stdlib a Unicode 13.0.0 ()
- Cambiar tus mappings
apiVersion
ensubgraph.yaml
a0.0.6
:
...dataSources:...mapping:...apiVersion: 0.0.6...
- Actualiza la
graph-cli
que usas a laúltima
versión:
# si lo tiene instalada de forma globalnpm install --global @graphprotocol/graph-cli@latest# o desde su subgrafo si lo tiene como dependencianpm 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:
npm 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.
En la versión anterior de AssemblyScript, podías crear un código como el siguiente:
function load(): Value | null { ... }let maybeValue = load();maybeValue.aMethod();
Sin embargo, en la versión más reciente, debido a que el valor es anulable, es necesario que lo compruebes, así:
let maybeValue = load()if (maybeValue) {maybeValue.aMethod() // `maybeValue` is not null anymore}
O forzarlo así:
let maybeValue = load()! // rompiendo el runtime si el valor es nulomaybeValue.aMethod()
Si no estás seguro de cuál elegir, te recomendamos que utilices siempre la versión segura. Si el valor no existe, es posible que quieras hacer una declaración if temprana con un retorno en tu handler de subgrafo.
Antes podías hacer y un código como este funcionaría:
let a = 10let b = 20let a = a + b
Sin embargo, ahora esto ya no es posible, y el compilador devuelve este error:
ERROR TS2451: Cannot redeclare block-scoped variable 'a'let a = a + b;~~~~~~~~~~~~~in assembly/index.ts(4,3)
Tendrás que cambiar el nombre de las variables duplicadas si tienes una variable shadowing.
Al hacer la actualización en un subgrafo, a veces pueden aparecer errores como estos:
ERROR TS2322: Type '~lib/@graphprotocol/graph-ts/common/numbers/BigInt | null' is not assignable to type '~lib/@graphprotocol/graph-ts/common/numbers/BigInt'.if (decimals == null) {~~~~in src/mappings/file.ts(41,21)
Para solucionarlo puedes simplemente cambiar la declaración if
por algo así:
if (!decimals) {// orif (decimals === null) {
Lo mismo ocurre si haces != en lugar de ==.
La forma común de hacer el casting antes era simplemente usar la palabra clave as
, de la siguiente forma:
let byteArray = new ByteArray(10)let 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:
// primitive castinglet a: usize = 10let b: isize = 5let c: usize = a + (b as usize)
// upcasting on class inheritanceclass Bytes extends Uint8Array {}let bytes = new Bytes(2)// <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
// downcasting on class inheritanceclass Bytes extends Uint8Array {}let uint8Array = new Uint8Array(2)// <Bytes>uint8Array // breaks in runtime :(
// between two types that share a superclassclass Bytes extends Uint8Array {}class ByteArray extends Uint8Array {}let bytes = new Bytes(2)// <ByteArray>bytes // breaks in runtime :(
Para esos casos, puedes usar la función changetype<T>
:
// downcasting on class inheritanceclass Bytes extends Uint8Array {}let uint8Array = new Uint8Array(2)changetype<Bytes>(uint8Array) // works :)
// entre dos tipos que comparten un superclassclass Bytes extends Uint8Array {}class ByteArray extends Uint8Array {}let bytes = new Bytes(2)changetype<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á.
// eliminar anulabilidadlet previousBalance = AccountBalance.load(balanceId) // AccountBalance | nullif (previousBalance != null) {return previousBalance as AccountBalance // sabe remove null}let newBalance = new AccountBalance(balanceId)
Para el caso de la anulabilidad se recomienda echar un vistazo al , 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
Para usar el puedes usar la declaración if
o el operador ternario (?
and :
) asi:
let something: string | null = 'data'let somethingOrElse = something ? something : 'else'// olet somethingOrElseif (something) {somethingOrElse = something} else {somethingOrElse = 'else'}
Sin embargo eso solo funciona cuando estás haciendo el if
/ ternario en una variable, no en un acceso a una propiedad, como este:
class Container {data: string | null}let container = new Container()container.data = 'data'let somethingOrElse: string = container.data ? container.data : 'else' // doesn't compile
Lo que produce este error:
ERROR TS2322: Type '~lib/string/String | null' is not assignable to type '~lib/string/String'.let somethingOrElse: string = container.data ? container.data : "else";~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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:
class Container {data: string | null}let container = new Container()container.data = 'data'let data = container.datalet somethingOrElse: string = data ? data : 'else' // compiles just fine :)
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.
class BigInt extends Uint8Array {@operator('+')plus(other: BigInt): BigInt {// ...}}class Wrapper {public constructor(public n: BigInt | null) {}}let x = BigInt.fromI32(2)let y: BigInt | null = nullx + y // give compile time error about nullabilitylet wrapper = new Wrapper(y)wrapper.n = wrapper.n + x // doesn't give compile time errors as it should
Hemos abierto un tema en el compilador de AssemblyScript para esto, pero por ahora si haces este tipo de operaciones en tus mapeos de subgrafos, deberías cambiarlos para hacer una comprobación de nulos antes de ello.
let wrapper = new Wrapper(y)if (!wrapper.n) {wrapper.n = BigInt.fromI32(0)}wrapper.n = wrapper.n + x // now `n` is guaranteed to be a BigInt
Si tienes algún código como este:
var value: Type // nullvalue.x = 10value.y = 'content'
Compilará pero se romperá en tiempo de ejecución, eso ocurre porque el valor no ha sido inicializado, así que asegúrate de que tu subgrafo ha inicializado sus valores, así:
var value = new Type() // initializedvalue.x = 10value.y = 'content'
También si tienes propiedades anulables en una entidad GraphQL, como esta:
type Total @entity {id: Bytes!amount: BigInt}
Y tienes un código similar a este:
let total = Total.load('latest')if (total === null) {total = new Total('latest')}total.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:
let total = Total.load('latest')if (total === null) {total = new Total('latest')total.amount = BigInt.fromI32(0)}total.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
😉
type Total @entity {id: Bytes!amount: BigInt!}
let total = Total.load('latest')if (total === null) {total = new Total('latest') // justo luego de haber iniciado la propiedad de no-anulable}total.amount = total.amount + BigInt.fromI32(1)
Si exportas alguna clase con propiedades que son otras clases (declaradas por ti o por la librería estándar) así:
class Thing {}export class Something {value: Thing}
El compilador dará un error porque tienes que añadir un inicializador para las propiedades que son clases, o añadir el operador !
:
export class Something {constructor(public value: Thing) {}}// oexport class Something {value: Thingconstructor(value: Thing) {this.value = value}}// orexport class Something {value!: Thing}
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:
let arr = new Array<string>(5) // ["", "", "", "", ""]arr.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:
ERRO 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í:
let arr = new Array<string>(0) // []arr.push('something') // ["something"]
O debería mutar a través de un índice:
let arr = new Array<string>(5) // ["", "", "", "", ""]arr[0] = 'something' // ["something", "", "", "", ""]
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:
type Something @entity {id: Bytes!}type MyEntity @entity {id: Bytes!invalidField: [Something]! # no longer valid}
Tendrás que añadir un !
al miembro del tipo Lista, así:
type Something @entity {id: Bytes!}type MyEntity @entity {id: Bytes!invalidField: [Something!]! # valid}
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).
- Alineado
Map#set
ySet#add
con el spec, devolviendothis
() - Las arrays ya no heredan de ArrayBufferView, sino que son distintas ()
- Las clases inicializadas a partir de objetos literales ya no pueden definir un constructor ()
- 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
() - Coerción
NaN
afalse
cuando casting abool
() - 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) () - Corrección de errores en las comparaciones de strings relacionales cuando los tamaños difieren ()