Guida alla migrazione di AssemblyScript
Reading time: 10 min
Finora i subgraph utilizzavano una delle (v0.6). Finalmente abbiamo aggiunto il supporto per la (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.
TypedArray
possono ora essere costruiti daArrayBuffer
utilizzando il ()- Nuove funzioni di libreria standard:
String#toUpperCase
,String#toLowerCase
,String#localeCompare
eTypedArray#set
() - Aggiunto il supporto per x instanceof GenericClass ()
- Aggiunto
StaticArray<T>
, una variante di array più efficiente () - Aggiunto
Array<T>#flat
() - Implementato l'argomento
radix
suNumber#toString
() - Aggiunto il supporto per i separatori nei letterali in virgola mobile ()
- Aggiunto il supporto per le funzioni di prima classe ()
- Aggiunti i builtin:
i32/i64/f32/f64.add/sub/mul
() - Implementati
Array/TypedArray/String#at
() - Aggiunto il supporto per le stringhe letterali dei template ()
- Aggiunto
encodeURI(Component)
edecodeURI(Component)
() - Aggiunto
toString
,toDateString
etoTimeString
aDate
() - Aggiunto
toUTCString
perDate
() - Aggiunto il tipo builtin
nonnull/NonNullable
()
- Le funzioni
matematiche
comeexp
,exp2
,log
,log2
epow
sono state sostituite da varianti più rapide () - Ottimizzato leggermente
Math.mod
() - Cache di più accessi ai campi in std Map e Set ()
- Ottimizzato per le potenze di due in
ipow32/64
()
- Il tipo di un letterale di array può ora essere dedotto dal suo contenuto ()
- Aggiornato stdlib a Unicode 13.0.0 ()
- Modificare le mappature
apiVersion
insubgraph.yaml
a0.0.6
:
...dataSources:...mapping:...apiVersion: 0.0.6...
- Aggiornare il
graph-cli
in uso alla versioneultima
eseguendo:
# se è installato globalmentenpm install --global @graphprotocol/graph-cli@latest# o nel proprio subgraph, se è una dipendenza di devnpm install --save-dev @graphprotocol/graph-cli@latest
- Fare lo stesso per
graph-ts
, ma invece di installarlo globalmente, salvarlo nelle dipendenze principali:
npm install --save @graphprotocol/graph-ts@latest
- Seguire il resto della guida per correggere le modifiche alla lingua.
- Eseguire di nuovo
codegen
edeploy
.
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 nullmaybeValue.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.
Prima si poteva fare lo e il codice come questo funzionava:
let a = 10let b = 20let 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) {// orif (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 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
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 classiclass Bytes extends Uint8Array {}let uint8Array = new Uint8Array(2)// <Bytes>uint8Array // si interrompe in fase di esecuzione :(
// tra due tipi che condividono una superclasseclass 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 classiclass Bytes extends Uint8Array {}let uint8Array = new Uint8Array(2)changetype<Bytes>(uint8Array) // funziona :)
// tra due tipi che condividono una superclasseclass 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 | nullif (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 , 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
Per utilizzare la si possono usare le istruzioni if
oppure l'operatore ternario (?
e :
) come questo:
let something: string | null = 'data'let somethingOrElse = something ? something : 'else'// orlet somethingOrElseif (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.datalet somethingOrElse: string = data ? data : 'else' // viene compilato benissimo :)
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 = nullx + y // give compile time error about nullabilitylet 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
Se si dispone di codice come questo:
var value: Type // nullvalue.x = 10value.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() // initializedvalue.x = 10value.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)
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) {}}// orexport class Something {value: Thingconstructor(value: Thing) {this.value = value}}// orexport class Something {value!: Thing}
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
eSet#add
con le specifiche, restituendoquesto
() - Gli Array non ereditano più da ArrayBufferView, ma sono ora distinti ()
- Le classi inizializzate a partire da letterali di oggetti non possono più definire un costruttore ()
- 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 chiamasseMath/f.pow
() - Coerenzia
NaN
afalse
quando viene lanciato abool
() - Quando si sposta un piccolo valore intero di tipo
i8
/u8
oppurei16
/u16
, solo i 3 o 4 bit meno significativi del valore RHS influiscono sul risultato, analogamente al risultato di uni32.shl
che viene influenzato solo dai 5 bit meno significativi del valore RHS. Esempio:someI8 << 8
prima produceva il valore0
, ma ora producesomeI8
a causa del mascheramento del RHS come8 & 7 = 0
(3 bits) () - Correzione del bug dei confronti tra stringhe relazionali quando le dimensioni sono diverse ()