11 minutes
Guide de migration de l'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.
Ce guide s’applique à tous ceux qui utilisent graph-cli
/graph-ts
en dessous de la version 0.22.0
. Si vous êtes déjà à une version supérieure (ou égale) à celle-ci, vous avez déjà utilisé la version 0.19.10
d’AssemblyScript 🙂
Note: As of 0.24.0
, graph-node
can support both versions, depending on the apiVersion
specified in the Subgraph manifest.
Fonctionnalités
Nouvelle fonctionnalité
- Les
TypedArray
s peuvent maintenant être construits à partir desArrayBuffer
s en utilisant la nouvelle méthode statiquewrap
(v0.8.1) - Nouvelles fonctions de la bibliothèque standard :
String#toUpperCase
,String#toLowerCase
,String#localeCompare
etTypedArray#set
(v0.9.0) - Ajout de la prise en charge de x instanceof GenericClass (v0.9.2)
- Ajout de
StaticArray<T>
, une variante de tableau plus efficace (v0.9.3) - Ajout de
Array<T>#flat
(v0.10.0) - Implémentation de l’argument
radix
surNumber#toString
(v0.10.1) - Ajout de la prise en charge des séparateurs dans les nombres à virgule flottante (v0.13.7)
- Prise en charge des fonctions de première classe (v0.14.0)
- Ajouter des éléments intégrés suivants:
i32/i64/f32/f64.add/sub/mul
(v0.14.13) - Implémentation de
Array/TypedArray/String#at
(v0.18.2) - Ajout de la prise en charge des modèles de chaînes de caractères (v0.18.17)
- Ajout de
encodeURI(Component)
etdecodeURI(Component)
(v0.18.27) - Ajout de
toString
,toDateString
ettoTimeString
àDate
(v0.18.29) - Ajout de
toUTCString
pourDate
(v0.18.30) - Ajout du type intégré
nonnull/NonNullable
(v0.19.2)
Optimizations
- Les fonctions mathématiques telles que
exp
,exp2
,log
,log2
etpow
ont été remplacées par des variantes plus rapides (v0.9.0) - Optimisation légère de
Math.mod
(v0.17.1) - Mise en cache de plus d’accès aux champs dans std Map et Set (v0.17.8)
- Optimisation pour les puissances de deux dans
ipow32/64
(v0.18.2)
Autre
- Le type d’un de tableau d’éléments peut maintenant être déduit de son contenu (v0.9.0)
- Mise à jour de la stdlib vers Unicode 13.0.0 (v0.10.0)
Comment mettre à niveau ?
- Change your mappings
apiVersion
insubgraph.yaml
to0.0.9
:
1...2dataSources:3 ...4 mapping:5 ...6 apiVersion: 0.0.97 ...
- Mettez à jour le
graph-cli
que vous utilisez avec la version la plus récente en exécutant :
1# si vous l'avez installé globalement2npm install --global @graphprotocol/graph-cli@latest34# ou dans votre subgraph si vous l'avez comme dépendance de développement5npm install --save-dev @graphprotocol/graph-cli@latest
- Faites la même chose pour
graph-ts
, mais au lieu de l’installer globalement, sauvegardez-le dans vos dépendances principales :
1npm install --save @graphprotocol/graph-ts@latest
- Suivez le reste du guide pour corriger les changements de langue.
- Exécutez
codegen
etdeploy
à nouveau.
Modifications radicales
Nullability
Sur l’ancienne version d’AssemblyScript, vous pouviez créer du code comme celui-ci :
1function load(): Value | null { ... }23let maybeValue = load();4maybeValue.aMethod();
Cependant, sur la version la plus récente, comme la valeur est nullable, vous devez vérifier, comme ceci :
1let maybeValue = load()23if (maybeValue) {4 maybeValue.aMethod() // `maybeValue` n'est plus nul5}
Ou forcez-le comme ceci :
1let maybeValue = load()! // breaks in runtime if value is null23maybeValue.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.
Ombrage variable
Auparavant, vous pouviez faire un variable shadowing et un code comme celui-ci fonctionnait :
1let a = 102let b = 203let a = a + b
Cependant, cela n’est plus possible et le compilateur renvoie cette erreur :
1ERROR TS2451: Cannot redeclare block-scoped variable 'a'23 let a = a + b;4 ~~~~~~~~~~~~~5in assembly/index.ts(4,3)
Vous devrez renommer vos variables en double si vous conservez une observation de variables.
Comparaisons nulles
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)
Pour résoudre ce problème, il suffit de modifier l’instruction if
en quelque chose comme ceci :
1if (!decimals) {23 // ou45 if (decimals === null) {
La même chose s’applique si vous faites != au lieu de ==.
Casting
Auparavant, la façon la plus courante d’effectuer une conversion de type était d’utiliser le mot-clé as
, comme ceci :
1let byteArray = new ByteArray(10)2let uint8Array = byteArray as Uint8Array // équivalent à : <Uint8Array>byteArray
Cependant, cela ne fonctionne que dans deux scénarios :
- Conversion des types primitifs (entre des types tels que
u8
,i32
,bool
; ex :let b : isize = 10 ; b as usize
) ; - Upcasting sur l’héritage de classe (sous-classe → superclasse)
Les Exemples:
1// primitive casting2let a: usize = 103let b: isize = 54let c: usize = a + (b as usize)
1// conversion vers le type parent dans l'héritage des classes2class Bytes extends Uint8Array {}34let bytes = new Bytes(2)5// <Uint8Array>bytes // idem que: bytes as Uint8Array
Il y a deux cas de figure où l’on peut vouloir faire une conversion de type, mais l’utilisation de as
/<T>var
n’est pas sûre :
- Downcasting sur l’héritage de classe (superclasse → sous-classe)
- Entre deux types qui partagent une superclasse
1// conversion vers le type enfant dans l'héritage des classes2class Bytes extends Uint8Array {}34let uint8Array = new Uint8Array(2)5// <Bytes>uint8Array // crash l'exécution :(
1// entre deux types qui partagent la même superclasse2class Bytes extends Uint8Array {}3class ByteArray extends Uint8Array {}45let bytes = new Bytes(2)6// <ByteArray>bytes // crash à l'exécution :(
Dans ce cas, vous pouvez utiliser la fonction changetype<T>
:
1// conversion vers le type enfant dans l'héritage des classes2class Bytes extends Uint8Array {}34let uint8Array = new Uint8Array(2)5changetype<Bytes>(uint8Array) // fonctionne :)
1// entre deux types qui partagent une même superclasse2class Bytes extends Uint8Array {}3class ByteArray extends Uint8Array {}45let bytes = new Bytes(2)6changetype<ByteArray>(bytes) // fonctionne :)
Si vous voulez juste supprimer la nullité, vous pouvez continuer à utiliser l’opérateur as
(ou <T>variable
), mais assurez-vous que vous savez que la valeur ne peut pas être nulle, sinon cela causera un crash.
1// supprimer la possibilité de valeur nulle (nullability)2let previousBalance = AccountBalance.load(balanceId) // AccountBalance | null34if (previousBalance != null) {5 return previousBalance as AccountBalance // suppression sûre de null6}78let newBalance = new AccountBalance(balanceId)
Pour le cas de la nullité, nous recommandons de jeter un coup d’œil à la fonction de vérification de la nullité, cela rendra votre code plus propre 🙂
Nous avons également ajouté quelques méthodes statiques supplémentaires dans certains types pour faciliter la diffusion, à savoir :
- Bytes.fromByteArray
- Bytes.fromUint8Array
- BigInt.fromByteArray
- ByteArray.fromBigInt
Vérification de nullité avec accès à la propriété
Pour utiliser la fonction de vérification de la nullité, vous pouvez utiliser les instructions if
ou l’opérateur ternaire (?
et :
) comme suit :
1let something: string | null = 'data'23let somethingOrElse = something ? something : 'else'45// ou67let somethingOrElse89if (something) {10 somethingOrElse = something11} else {12 somethingOrElse = 'else'13}
Cependant, cela ne fonctionne que lorsque vous faites le if
/ ternaire sur une variable, et non sur l’accès à une propriété, comme ceci :
1class Container {2 data: string | null3}45let container = new Container()6container.data = 'data'78let somethingOrElse: string = container.data ? container.data : 'else' // ne compile pas
Ce qui génère cette erreur :
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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pour résoudre ce problème, vous pouvez créer une variable pour l’accès à cette propriété afin que le compilateur puisse effectuer la vérification magique de la nullité :
1class Container {2 data: string | null3}45let container = new Container()6container.data = 'data'78let data = container.data910let somethingOrElse: string = data ? data : 'else' // compile sans problème :)
Surcharge de l’opérateur avec accès à la propriété
Si vous essayez de additionner (par exemple) un type nullable (à partir d’un accès à une propriété) avec un type non nullable, le compilateur AssemblyScript au lieu de donner une erreur de compilation avertissant que l’une des valeurs est nullable, il compile simplement silencieusement, donnant une chance pour que le code soit interrompu au moment de l’exécution.
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 // donne une erreur de compilation concernant la nullité16let wrapper = new Wrapper(y)1718wrapper.n = wrapper.n + x // ne donne pas d'erreurs de compilation comme il se doit
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 // maintenant `n` est garanti comme étant un BigInt
Initialisation de valeur
Si vous avez un code comme celui-ci :
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'
De plus, si vous avez des propriétés nullables dans une entité GraphQL, comme ceci :
1type Total @entity {2 id: Bytes!3 amount: BigInt4}
Et vous avez un code similaire à celui-ci :
1let total = Total.load('latest')23if (total === null) {4 total = new Total('latest')5}67total.amount = total.amount + BigInt.fromI32(1)
Vous devez vous assurer d’initialiser la valeur total.amount
, car si vous essayez d’y accéder comme dans la dernière ligne pour la somme, cela va planter. Il faut donc d’abord l’initialiser :
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)
Ou vous pouvez simplement changer votre schéma GraphQL pour ne pas utiliser un type nullable pour cette propriété, puis nous l’initialiserons à zéro à l’étape codegen
😉
1type Total @entity {2 id: Bytes!3 amount: BigInt!4}
1let total = Total.load('latest')23if (total === null) {4 total = new Total('latest') // initialise déjà les propriétés non-nullables5}67total.amount = total.amount + BigInt.fromI32(1)
Initialisation de la propriété de classe
Si vous exportez des classes avec des propriétés qui sont d’autres classes (déclarées par vous ou par la bibliothèque standard), comme ceci :
1class Thing {}23export class Something {4 value: Thing5}
Le compilateur génèrera une erreur car vous devez soit ajouter un initialisateur pour les propriétés qui sont des classes, soit ajouter l’opérateur !
:
1export class Something {2 constructor(public value: Thing) {}3}45// ou67export class Something {8 value: Thing910 constructor(value: Thing) {11 this.value = value12 }13}1415// ou1617export class Something {18 value!: Thing19}
Initialisation du tableau
La classe Array
accepte toujours un nombre pour initialiser la longueur de la liste, cependant vous devez faire attention car des opérations comme .push
vont en fait augmenter la taille au lieu d’ajouter au début, par exemple :
1let arr = new Array<string>(5) // ["", "", "", "", ""]23arr.push('something') // ["", "", "", "", "", "something"] // taille 6 :(
En fonction des types que vous utilisez, par exemple les types nullables, et de la manière dont vous y accédez, vous pourriez rencontrer une erreur d’exécution comme celle-ci :
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
Pour pousser au début, vous devez soit initialiser le Tableau
avec une taille de zéro, comme ceci :
1let arr = new Array<string>(0) // []23arr.push('something') // ["quelque chose"]
Ou vous devriez le muter via index :
1let arr = new Array<string>(5) // ["", "", "", "", ""]23arr[0] = 'something' // ["quelque chose", "", "", "", ""]
Schéma GraphQL
Il ne s’agit pas d’un changement direct d’AssemblyScript, mais vous devrez peut-être mettre à jour votre fichier schema.graphql
.
Vous ne pouvez désormais plus définir de champs dans vos types qui sont des listes non nullables. Si vous avez un schéma comme celui-ci :
1type Something @entity {2 id: Bytes!3}45type MyEntity @entity {6 id: Bytes!7 invalidField: [Something]! # n'est plus valide8}
Vous devrez ajouter un !
au membre du type List, comme ceci :
1type Something @entity {2 id: Bytes!3}45type MyEntity @entity {6 id: Bytes!7 invalidField: [Something!]! # valide8}
Cela a changé à cause des différences de nullité entre les versions d’AssemblyScript, et c’est lié au fichier src/generated/schema.ts
(chemin par défaut, vous pouvez l’avoir changé).
Autre
- Alignement de
Map#set
etSet#add
avec la spécification, retournantthis
(v0.9.2) - Les tableaux n’héritent plus de ArrayBufferView, mais sont désormais distincts (v0.10.0)
- Les classes initialisées à partir d’objets littéraux ne peuvent plus définir de constructeur (v0.10.0)
- Le résultat d’une opération binaire
**
est maintenant l’entier de dénominateur commun si les deux opérandes sont des entiers. Auparavant, le résultat était un flottant comme si l’on appelaitMath/f.pow
(v0.11.0) - Contraindre
NaN
àfalse
lors d’une conversion de type versbool
(v0.14.9) - Lors du déplacement d’une petite valeur entière de type
i8
/u8
oui16
/u16
, seuls les 3 ou 4 bits les moins significatifs de la valeur RHS affectent le résultat, de manière analogue au résultat d’uni32.shl
qui n’est affecté que par les 5 bits les moins significatifs de la valeur RHS. Exemple :someI8 << 8
produisait auparavant la valeur0
, mais produit maintenantsomeI8
à cause du masquage de la valeur RHS comme8 & 7 = 0
(3 bits) (v0.17.0) - Correction d’un bug dans les comparaisons de chaînes de caractères relationnelles lorsque les tailles sont différentes (v0.17.8)