Release Notes & Upgrade Guides > Guida alla migrazione di AssemblyScript

Guida alla migrazione di AssemblyScript

Reading time: 10 min

Finora i subgraph utilizzavano una delle prime versioni di AssemblyScript (v0.6). Finalmente abbiamo aggiunto il supporto per la più recente disponibile (v0.19.10)! 🎉

Ciò consentirà agli sviluppatori di subgraph di utilizzare le nuove caratteristiche del linguaggio AS e della libreria standard.

Questa guida si applica a chiunque utilizzi graph-cli/ graph-ts al di sotto della versione 0.22.0. Se siete già a una versione superiore (o uguale) a questa, avete già utilizzato la versione 0.19.10 di AssemblyScript 🙂

Nota: A partire da 0.24.0, graph-node può supportare entrambe le versioni, a seconda della apiVersion specificata nel manifest del subgraph.

Caratteristiche

Collegamento a questa sezione

Nuova funzionalità

Collegamento a questa sezione
  • TypedArray possono ora essere costruiti da ArrayBuffer utilizzando il nuovo metodo statico wrap (v0.8.1)
  • Nuove funzioni di libreria standard: String#toUpperCase, String#toLowerCase, String#localeCompare e TypedArray#set (v0.9.0)
  • Aggiunto il supporto per x instanceof GenericClass (v0.9.2)
  • Aggiunto StaticArray<T>, una variante di array più efficiente (v0.9.3)
  • Aggiunto Array<T>#flat (v0.10.0)
  • Implementato l'argomento radix su Number#toString (v0.10.1)
  • Aggiunto il supporto per i separatori nei letterali in virgola mobile (v0.13.7)
  • Aggiunto il supporto per le funzioni di prima classe (v0.14.0)
  • Aggiunti i builtin: i32/i64/f32/f64.add/sub/mul (v0.14.13)
  • Implementati Array/TypedArray/String#at (v0.18.2)
  • Aggiunto il supporto per le stringhe letterali dei template (v0.18.17)
  • Aggiunto encodeURI(Component) e decodeURI(Component) (v0.18.27)
  • Aggiunto toString, toDateString e toTimeString a Date (v0.18.29)
  • Aggiunto toUTCString per Date (v0.18.30)
  • Aggiunto il tipo builtin nonnull/NonNullable (v0.19.2)
  • Le funzioni matematiche come exp, exp2, log, log2 e pow sono state sostituite da varianti più rapide (v0.9.0)
  • Ottimizzato leggermente Math.mod (v0.17.1)
  • Cache di più accessi ai campi in std Map e Set (v0.17.8)
  • Ottimizzato per le potenze di due in ipow32/64 (v0.18.2)
  • Il tipo di un letterale di array può ora essere dedotto dal suo contenuto (v0.9.0)
  • Aggiornato stdlib a Unicode 13.0.0 (v0.10.0)
  1. Modificare le mappature apiVersion in subgraph.yaml a 0.0.6:
...
dataSources:
...
mapping:
...
apiVersion: 0.0.6
...
  1. Aggiornare il graph-cli in uso alla versione ultima eseguendo:
# se è installato globalmente
npm install --global @graphprotocol/graph-cli@latest
# o nel proprio subgraph, se è una dipendenza di dev
npm install --save-dev @graphprotocol/graph-cli@latest
  1. Fare lo stesso per graph-ts, ma invece di installarlo globalmente, salvarlo nelle dipendenze principali:
npm install --save @graphprotocol/graph-ts@latest
  1. Seguire il resto della guida per correggere le modifiche alla lingua.
  2. Eseguire di nuovo codegen e deploy.

Cambiamenti di rottura

Collegamento a questa sezione

Nella versione precedente di AssemblyScript, era possibile creare codice come questo:

function load(): Value | null { ... }
let maybeValue = load();
maybeValue.aMethod();

Tuttavia, nella versione più recente, poiché il valore è nullable, è necessario effettuare un controllo, come questo:

let maybeValue = load()
if (maybeValue) {
maybeValue.aMethod() // `maybeValue` is not null anymore
}

Oppure forzarla in questo modo:

let maybeValue = load()! // breaks in runtime if value is null
maybeValue.aMethod()

Se non si è sicuri di quale scegliere, si consiglia di utilizzare sempre la versione sicura. Se il valore non esiste, si potrebbe fare una dichiarazione if anticipata con un ritorno nel gestore del subgraph.

Shadowing della variabile

Collegamento a questa sezione

Prima si poteva fare lo shadowing della variabile e il codice come questo funzionava:

let a = 10
let b = 20
let a = a + b

Tuttavia ora questo non è più possibile e il compilatore restituisce questo errore:

ERROR TS2451: Cannot redeclare block-scoped variable 'a'
let a = a + b;
~~~~~~~~~~~~~
in assembly/index.ts(4,3)

È necessario rinominare le variabili duplicate se si dispone di un'ombreggiatura delle variabili.

Eseguendo l'aggiornamento sul subgraph, a volte si possono ottenere errori come questi:

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)

Per risolvere il problema è sufficiente modificare l'istruzione if in qualcosa di simile:

if (!decimals) {
// or
if (decimals === null) {

Lo stesso vale se si fa != invece di ==.

Il modo più comune per effettuare il casting era quello di utilizzare la parola chiave as, come in questo caso:

let byteArray = new ByteArray(10)
let uint8Array = byteArray as Uint8Array // equivalent to: <Uint8Array>byteArray

Tuttavia, questo funziona solo in due scenari:

  • Casting primitivo (tra tipi come u8, i32, bool; ad esempio: let b: isize = 10; b as usize);
  • Upcasting sull'ereditarietà delle classi (subclasse → superclasse)

Esempi:

// primitive casting
let a: usize = 10
let b: isize = 5
let c: usize = a + (b as usize)
// upcasting on class inheritance
class Bytes extends Uint8Array {}
let bytes = new Bytes(2)
// <Uint8Array>bytes // same as: bytes as Uint8Array

Ci sono due scenari in cui potreste voler effettuare casting, ma l'uso as/<T>var non è sicuro:

  • Downcasting sull'ereditarietà delle classi (superclasse → subclasse)
  • Tra due tipi che condividono una superclasse
// downcasting sull'ereditarietà delle classi
class Bytes extends Uint8Array {}
let uint8Array = new Uint8Array(2)
// <Bytes>uint8Array // si interrompe in fase di esecuzione :(
// tra due tipi che condividono una superclasse
class Bytes extends Uint8Array {}
class ByteArray extends Uint8Array {}
let bytes = new Bytes(2)
// <ByteArray>bytes // si interrompe in fase di esecuzione :(

In questi casi, si può usare la funzione changetype<T>:

// downcasting sull'ereditarietà delle classi
class Bytes extends Uint8Array {}
let uint8Array = new Uint8Array(2)
changetype<Bytes>(uint8Array) // funziona :)
// tra due tipi che condividono una superclasse
class Bytes extends Uint8Array {}
class ByteArray extends Uint8Array {}
let bytes = new Bytes(2)
changetype<ByteArray>(bytes) // funziona :)

Se si vuole solo rimuovere la nullità, si può continuare a usare l'operatore as (oppure <T>variabile), ma assicurarsi di sapere che il valore non può essere nullo, altrimenti si interromperà.

// rimuovere la nullità
let previousBalance = AccountBalance.load(balanceId) // AccountBalance | null
if (previousBalance != null) {
return previousBalance as AccountBalance // safe remove null
}
let newBalance = new AccountBalance(balanceId)

Per il caso della nullità si consiglia di dare un'occhiata alla funzione verifica della nullità, che renderà il codice più pulito 🙂

Inoltre abbiamo aggiunto alcuni metodi statici in alcuni tipi per facilitare il casting, che sono:

  • Bytes.fromByteArray
  • Bytes.fromUint8Array
  • BigInt.fromByteArray
  • ByteArray.fromBigInt

Verifica della nullità con accesso alle proprietà

Collegamento a questa sezione

Per utilizzare la di funzione controllo della nullità si possono usare le istruzioni if oppure l'operatore ternario (? e :) come questo:

let something: string | null = 'data'
let somethingOrElse = something ? something : 'else'
// or
let somethingOrElse
if (something) {
somethingOrElse = something
} else {
somethingOrElse = 'else'
}

Tuttavia, questo funziona solo quando si esegue il if / ternario su una variabile, non sull'accesso a una proprietà, come in questo caso:

class Container {
data: string | null
}
let container = new Container()
container.data = 'data'
let somethingOrElse: string = container.data ? container.data : 'else' // non viene compilato

Che produce questo errore:

ERROR TS2322: Type '~lib/string/String | null' is not assignable to type '~lib/string/String'.
let somethingOrElse: string = container.data ? container.data : "else";
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Per risolvere questo problema, si può creare una variabile per l'accesso alla proprietà, in modo che il compilatore possa fare la magia del controllo di annullabilità:

class Container {
data: string | null
}
let container = new Container()
container.data = 'data'
let data = container.data
let somethingOrElse: string = data ? data : 'else' // viene compilato benissimo :)

Sovraccarico dell'operatore con accesso alle proprietà

Collegamento a questa sezione

Se si tenta di sommare (ad esempio) un tipo nullable (da un accesso a una proprietà) con uno non nullable, il compilatore di AssemblyScript, invece di dare un avviso di errore in fase di compilazione sul fatto che uno dei valori è nullable, si limita a compilare in silenzio, dando la possibilità che il codice si interrompa in fase di esecuzione.

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 = null
x + y // give compile time error about nullability
let wrapper = new Wrapper(y)
wrapper.n = wrapper.n + x // non dà errori in fase di compilazione come dovrebbe

Abbiamo aperto un problema sul compilatore AssemblyScript per questo, ma per il momento se fate questo tipo di operazioni nelle vostre mappature di subgraph, dovreste modificarle in modo da fare un controllo di null prima di esse.

let wrapper = new Wrapper(y)
if (!wrapper.n) {
wrapper.n = BigInt.fromI32(0)
}
wrapper.n = wrapper.n + x // ora `n` è garantito essere un BigInt

Inizializzazione del valore

Collegamento a questa sezione

Se si dispone di codice come questo:

var value: Type // null
value.x = 10
value.y = 'content'

Verrà compilato ma si interromperà in fase di esecuzione, perché il valore non è stato inizializzato, quindi assicuratevi che il vostro subgraph abbia inizializzato i suoi valori, in questo modo:

var value = new Type() // initialized
value.x = 10
value.y = 'content'

Inoltre, se si hanno proprietà nullable in un'entità GraphQL, come questa:

type Total @entity {
id: Bytes!
amount: BigInt
}

E avete un codice simile a questo:

let total = Total.load('latest')
if (total === null) {
total = new Total('latest')
}
total.amount = total.amount + BigInt.fromI32(1)

È necessario assicurarsi di inizializzare il valore total.amount, perché se si tenta di accedervi come nell'ultima riga per la somma, il programma si blocca. Quindi bisogna inizializzarlo prima:

let total = Total.load('latest')
if (total === null) {
total = new Total('latest')
total.amount = BigInt.fromI32(0)
}
total.tokens = total.tokens + BigInt.fromI32(1)

Oppure si può semplicemente modificare lo schema GraphQL per non utilizzare un tipo nullable per questa proprietà, quindi la inizializzeremo come zero nel passaggio codegen 😉

type Total @entity {
id: Bytes!
amount: BigInt!
}
let total = Total.load('latest')
if (total === null) {
total = new Total('latest') // inizializza già le proprietà non nulle
}
total.amount = total.amount + BigInt.fromI32(1)

Inizializzazione delle proprietà della classe

Collegamento a questa sezione

Se si esportano classi con proprietà che sono altre classi (dichiarate dall'utente o dalla libreria standard) come questa:

class Thing {}
export class Something {
value: Thing
}

Il compilatore darà un errore perché è necessario aggiungere un initializer per le proprietà che sono classi, oppure aggiungere l'operatore !:

export class Something {
constructor(public value: Thing) {}
}
// or
export class Something {
value: Thing
constructor(value: Thing) {
this.value = value
}
}
// or
export class Something {
value!: Thing
}

Inizializzazione del Array

Collegamento a questa sezione

La classe Array accetta ancora un numero per inizializzare la lunghezza dell'elenco, ma bisogna fare attenzione perché operazioni come .push aumentano effettivamente la dimensione invece di aggiungere all'inizio, ad esempio:

let arr = new Array<string>(5) // ["", "", "", "", ""]
arr.push('something') // ["", "", "", "", "", "something"] // size 6 :(

A seconda dei tipi utilizzati, ad esempio quelli nullable, e del modo in cui vi si accede, si potrebbe verificare un errore di runtime come questo:

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

Per spingere effettivamente all'inizio, si dovrebbe inizializzare l'Array con dimensione zero, in questo modo:

let arr = new Array<string>(0) // []
arr.push('something') // ["something"]

Oppure si dovrebbe mutare tramite indice:

let arr = new Array<string>(5) // ["", "", "", "", ""]
arr[0] = 'something' // ["something", "", "", "", ""]

Non si tratta di una modifica diretta di AssemblyScript, ma potrebbe essere necessario aggiornare il file schema.graphql.

Ora non è più possibile definire campi nei tipi che sono elenchi non nulli. Se si ha uno schema come questo:

type Something @entity {
id: Bytes!
}
type MyEntity @entity {
id: Bytes!
invalidField: [Something]! # no longer valid
}

Si dovrà aggiungere un ! al membro del tipo List, in questo modo:

type Something @entity {
id: Bytes!
}
type MyEntity @entity {
id: Bytes!
invalidField: [Something!]! # valid
}

Questo è cambiato a causa delle differenze di nullabilità tra le versioni di AssemblyScript ed è legato al file src/generated/schema.ts (percorso predefinito, potrebbe essere stato modificato).

  • Allinea Map#set e Set#add con le specifiche, restituendo questo (v0.9.2)
  • Gli Array non ereditano più da ArrayBufferView, ma sono ora distinti (v0.10.0)
  • Le classi inizializzate a partire da letterali di oggetti non possono più definire un costruttore (v0.10.0)
  • Il risultato di un'operazione binaria ** è ora l'intero a denominatore comune se entrambi gli operandi sono interi. In precedenza, il risultato era un float, come se si chiamasse Math/f.pow (v0.11.0)
  • Coerenzia NaN a false quando viene lanciato a bool (v0.14.9)
  • Quando si sposta un piccolo valore intero di tipo i8/u8 oppure i16/u16, solo i 3 o 4 bit meno significativi del valore RHS influiscono sul risultato, analogamente al risultato di un i32.shl che viene influenzato solo dai 5 bit meno significativi del valore RHS. Esempio: someI8 << 8 prima produceva il valore 0, ma ora produce someI8 a causa del mascheramento del RHS come 8 & 7 = 0 (3 bits) (v0.17.0)
  • Correzione del bug dei confronti tra stringhe relazionali quando le dimensioni sono diverse (v0.17.8)
Modifica pagina

Precedente
Guida alla migrazione delle validazione GraphQL
Successivo
Substreams
Modifica pagina