Guia de Migração do AssemblyScript
Reading time: 10 min
Até agora, os subgraphs têm usado uma das (v0.6). Finalmente, adicionamos apoio à versão (v.0.19.10)! 🎉
Isto permitirá que os programadores de subgraph usem recursos mais novos da linguagem AS e da sua biblioteca normal.
Este guia se aplica a quem usar o graph-cli
/graph-ts
antes da versão 0.22.0
. Se já está numa versão maior (ou igual) àquela, já está a usar a versão 0.19.10
do AssemblyScript 🙂
Nota: Desde a versão 0.24.0
, o graph-node
pode apoiar ambas as versões, dependente da apiVersion
especificada no manifest do subgraph.
TypedArray
s podem ser construídos deArrayBuffer
s com o novo método estático ()- Novas funções de biblioteca normais:
String#toUpperCase
,String#toLowerCase
,String#localeCompare
eTypedArray#set
() - Suporte para x GenericClass ()
StaticArray<T>
, uma variante de arranjo mais eficiente ()Array<T>#flat
()- Implementado o argumento
radix
noNumber#toString
() - Suporte para separadores em literais de ponto flutuante ()
- Suporte para funções de primeira classe ()
- Embutidos:
i32/i64/f32/f64.add/sub/mul
() Array/TypedArray/String#at
()- Suporte para strings literais de modelos ()
encodeURI(Component)
edecodeURI(Component)
()toString
,toDateString
etoTimeString
aoDate
()toUTCString
para aDate
()- Tipo embutido
nonnull/NonNullable
()
- Funções
Math
comoexp
,exp2
,log
,log2
epow
foram substituídas por variantes mais rápidas () - Otimizado levemente o
Math.mod
() - Cacheing de mais acessos de campos em Map e Set ()
- Otimização para poderes de dois no
ipow32/64
()
- O tipo de um literal de arranjos agora pode ser inferido dos seus conteúdos ()
- stdlib atualizado ao Unicode 13.0.0 ()
- Mude os seus mapeamentos de
apiVersion
nosubgraph.yaml
para0.0.6
:
...dataSources:...mapping:...apiVersion: 0.0.6...
- Atualize o
graph-cli
que usa à versão mais recente (latest
) com:
# caso o tenha instalado globalmentenpm install --global @graphprotocol/graph-cli@latest# ou no seu subgraph se o tiver como dependência de desenvolvimentonpm install --save-dev @graphprotocol/graph-cli@latest
- Faça o mesmo para o
graph-ts
, mas em vez de instalar globalmente, salve-o nas suas dependências principais:
npm install --save @graphprotocol/graph-ts@latest
- Siga o resto do guia para consertar as mudanças frágeis na linguagem.
- Execute
codegen
edeploy
novamente.
Na versão mais antiga do AssemblyScript, podias criar códigos assim:
function load(): Value | null { ... }let maybeValue = load();maybeValue.aMethod();
Mas na versão mais nova, como o valor é anulável, ele exige que confira, assim:
let maybeValue = load()if (maybeValue) {maybeValue.aMethod() // `maybeValue` is not null anymore}
...ou o force deste jeito:
let maybeValue = load()! // breaks in runtime if value is nullmaybeValue.aMethod()
Se não tiver certeza de qual escolher, é sempre bom usar a versão segura. Se o valor não existir, pode fazer uma declaração if
precoce com um retorno no seu handler de subgraph.
Antes, ao fazer , códigos assim funcionavam bem:
let a = 10let b = 20let a = a + b
Porém, isto não é mais possível, e o compilador retorna este erro:
ERROR TS2451: Cannot redeclare block-scoped variable 'a'let a = a + b;~~~~~~~~~~~~~in assembly/index.ts(4,3)
Renomeie as suas variáveis duplicadas, se tinha o sombreamento variável.
Ao fazer a atualização no seu subgraph, às vezes aparecem erros como este:
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 resolver isto, basta mudar a declaração if
para algo assim:
if (!decimals) {// orif (decimals === null) {
O mesmo acontece se fizer o != em vez de ==.
Antigamente, casting era normalmente feito com a palavra-chave as
, assim:
let byteArray = new ByteArray(10)let uint8Array = byteArray as Uint8Array // equivalent to: <Uint8Array>byteArray
Porém, isto só funciona em dois casos:
- Casting primitivo (entre tipos como
u8
,i32
,bool
; por ex.:let b: isize = 10; b as usize
); - Upcasting em herança de classe (subclass → superclass)
Exemplos:
// primitive castinglet a: usize = 10let b: isize = 5let c: usize = a + (b as usize)
// upcasting em herança de classeclass Bytes extends Uint8Array {}let bytes = new Bytes(2)// <Uint8Array>bytes // mesmo que: bytes como Uint8Array
Há dois cenários onde casting é possível, mas usar as
/<T>var
não é seguro:
- Downcasting em herança de classe (superclass → subclass)
- Entre dois tipos que compartilham uma superclasse
// downcasting em herança de classeclass Bytes extends Uint8Array {}let uint8Array = new Uint8Array(2)// <Bytes>uint8Array // quebra no runtime :(
// entre dois tipos que compartilham uma superclasseclass Bytes extends Uint8Array {}class ByteArray extends Uint8Array {}let bytes = new Bytes(2)// <ByteArray>bytes // quebra no runtime :(
Nestes casos, vale usar a função changetype<T>
:
// downcasting em herança de classeclass Bytes extends Uint8Array {}let uint8Array = new Uint8Array(2)changetype<Bytes>(uint8Array) // funciona :)
// entre dois tipos que compartilham uma superclasseclass Bytes extends Uint8Array {}class ByteArray extends Uint8Array {}let bytes = new Bytes(2)changetype<ByteArray>(bytes) // funciona :)
Se só quiser tirar a anulabilidade, pode continuar a usar o operador as
(ou <T>variable
), mas tenha ciência de que sabe que o valor não pode ser nulo, ou ele falhará.
// remove anulabilidadelet previousBalance = AccountBalance.load(balanceId) // AccountBalance | nullif (previousBalance != null) {return previousBalance as AccountBalance // safe remove null}let newBalance = new AccountBalance(balanceId)
Para o caso de anulabilidade, é bom dar uma olhada no , pois ele deixará o seu código mais limpinho 🙂
Também adicionamos alguns métodos estáticos em alguns tipos para facilitar o casting, sendo:
- Bytes.fromByteArray
- Bytes.fromUint8Array
- BigInt.fromByteArray
- ByteArray.fromBigInt
Para usar a , dá para usar declarações if
ou o operador ternário (?
e :
) assim:
let something: string | null = 'data'let somethingOrElse = something ? something : 'else'// oulet somethingOrElseif (something) {somethingOrElse = something} else {somethingOrElse = 'else'}
Mas isto só funciona ao fazer o ternário if
/ numa variável, e não num acesso de propriedade, assim:
class Container {data: string | null}let container = new Container()container.data = 'data'let somethingOrElse: string = container.data ? container.data : 'else' // doesn't compile
O que retorna este erro:
ERROR TS2322: Type '~lib/string/String | null' is not assignable to type '~lib/string/String'.let somethingOrElse: string = container.data ? container.data : "else";~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Para consertar este problema, vale criar uma variável para aquele acesso à propriedade, para que o compilador faça a magia da checagem de anulabilidade:
class Container {data: string | null}let container = new Container()container.data = 'data'let data = container.datalet somethingOrElse: string = data ? data : 'else' // compiles just fine :)
Se tentar somar (por exemplo) um tipo anulável (de um acesso de propriedade) com um não-anulável, em vez de soltar um aviso de erro de tempo de compilação a dizer que um dos valores é anulável, o compilador AssemblyScript só compilará em silêncio, o que abre chances para o código quebrar em meio ao tempo de execução.
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 // dá o erro de tempo de complação sobre anulabilidadelet wrapper = new Wrapper(y)wrapper.n = wrapper.n + x // não dá erros de tempo de compilação como deveria
Nós abrimos um problema no compilador AssemblyScript para isto, mas por enquanto, se fizer estes tipos de operações nos seus mapeamentos de subgraph, vale mudá-las para fazer uma checagem de anulação antes delas.
let wrapper = new Wrapper(y)if (!wrapper.n) {wrapper.n = BigInt.fromI32(0)}wrapper.n = wrapper.n + x // agora o `n` é garantido a ser um BigInt
Se tiver algum código como este:
var value: Type // nullvalue.x = 10value.y = 'content'
Ele fará a compilação, mas quebrará no tempo de execução porque o valor não foi inicializado. Tenha certeza de que o seu subgraph inicializou os seus valores, como assim:
var value = new Type() // initializedvalue.x = 10value.y = 'content'
E também se tiver propriedades anuláveis numa entidade GraphQL, como assim:
type Total @entity {id: Bytes!amount: BigInt}
E tiver código parecido com este:
let total = Total.load('latest')if (total === null) {total = new Total('latest')}total.amount = total.amount + BigInt.fromI32(1)
Inicialize o valor total.amount
, porque se tentar acessar como na última linha para a soma, ele irá travar. Então — ou inicializas primeiro:
let total = Total.load('latest')if (total === null) {total = new Total('latest')total.amount = BigInt.fromI32(0)}total.tokens = total.tokens + BigInt.fromI32(1)
Ou pode simplesmente mudar o seu schema GraphQL para que não use um tipo anulável para esta propriedade, e o inicialize como zero no passo codegen
😉
type Total @entity {id: Bytes!amount: BigInt!}
let total = Total.load('latest')if (total === null) {total = new Total('latest') // já inicializa propriedades não-anuláveis}total.amount = total.amount + BigInt.fromI32(1)
Se exportar quaisquer classes com propriedades que sejam outras classes (declaradas por você ou pela biblioteca padrão) como esta:
class Thing {}export class Something {value: Thing}
O compilador dará em erro, porque precisa adicionar um iniciador às propriedades que são classes, ou adicionar o operador !
:
export class Something {constructor(public value: Thing) {}}// ouexport class Something {value: Thingconstructor(value: Thing) {this.value = value}}// ouexport class Something {value!: Thing}
A classe Array
(arranjo) ainda aceita um número para iniciar o comprimento da lista, mas tome cuidado — porque operações como .push
aumentarão o tamanho em vez de adicionar ao começo, por exemplo:
let arr = new Array<string>(5) // ["", "", "", "", ""]arr.push('something') // ["", "", "", "", "", "something"] // size 6 :(
Dependendo dos tipos que usa, por ex., anuláveis, e como os acessa, pode encontrar um erro de tempo de execução 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 empurrar no começo, inicialize o Array
com o tamanho zero, assim:
let arr = new Array<string>(0) // []arr.push('something') // ["something"]
Ou pode mudá-lo através do index:
let arr = new Array<string>(5) // ["", "", "", "", ""]arr[0] = 'something' // ["something", "", "", "", ""]
Isto não é uma mudança direta no AssemblyScript, mas pode ser que precise atualizar o seu arquivo schema.graphql
.
Agora não há mais como definir campos nos seus tipos que são Listas Não Anuláveis. Se tiver um schema como este:
type Something @entity {id: Bytes!}type MyEntity @entity {id: Bytes!invalidField: [Something]! # no longer valid}
Adicione um !
ao membro do tipo de Lista, como:
type Something @entity {id: Bytes!}type MyEntity @entity {id: Bytes!invalidField: [Something!]! # valid}
Isto mudou por diferenças de anulabilidade entre versões do AssemblyScript, e tem relação ao arquivo src/generated/schema.ts
(caminho padrão, talvez tenha mudado).
- Alinhados
Map#set
eSet#add
, com retorno dethis
() - Os arranjos não herdam mais do ArrayBufferView, mas agora são distintos ()
- Classes inicializadas de literais de objeto não podem mais definir um construtor ()
- O resultado de uma operação binária, se ambos os operandos forem inteiros,
**
agora é o inteiro denominador comum. Antes, o resultado era um float, como se chamasse oMath/f.pow
() - Coagir o
NaN
aofalse
ao converter embool
() - Ao mudar um valor inteiro pequeno do tipo
i8
/u8
oui16
/u16
, apenas os 3 respectivamente 4 bits menos significantes do valor RHS afetarão o resultado, análogo ao resultado de umi32.shl
só a ser afetado pelos 5 bits menos significantes do valor RHS. Por exemplo:someI8 << 8
antes produzia o valor0
, mas agora produz osomel8
por mascarar o RHS como8 & 7 = 0
(3 bits) () - Consertado um erro de comparações relacionais de string quando os tamanhos diferem ()