12 分
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.
This guide is applicable for anyone using graph-cli
/graph-ts
below version 0.22.0
. If you’re already at a higher than (or equal) version to that, you’ve already been using version 0.19.10
of AssemblyScript 🙂
Note: As of 0.24.0
, graph-node
can support both versions, depending on the apiVersion
specified in the Subgraph manifest.
特徴
新機能
TypedArray
s can now be built fromArrayBuffer
s by using the newwrap
static method (v0.8.1)- New standard library functions:
String#toUpperCase
,String#toLowerCase
,String#localeCompare
andTypedArray#set
(v0.9.0) - Added support for x instanceof GenericClass (v0.9.2)
- Added
StaticArray<T>
, a more efficient array variant (v0.9.3) - Added
Array<T>#flat
(v0.10.0) - Implemented
radix
argument onNumber#toString
(v0.10.1) - Added support for separators in floating point literals (v0.13.7)
- Added support for first class functions (v0.14.0)
- Add builtins:
i32/i64/f32/f64.add/sub/mul
(v0.14.13) - Implement
Array/TypedArray/String#at
(v0.18.2) - Added support for template literal strings (v0.18.17)
- Add
encodeURI(Component)
anddecodeURI(Component)
(v0.18.27) - Add
toString
,toDateString
andtoTimeString
toDate
(v0.18.29) - Add
toUTCString
forDate
(v0.18.30) - Add
nonnull/NonNullable
builtin type (v0.19.2)
最適化
Math
functions such asexp
,exp2
,log
,log2
andpow
have been replaced by faster variants (v0.9.0)- Slightly optimize
Math.mod
(v0.17.1) - Cache more field accesses in std Map and Set (v0.17.8)
- Optimize for powers of two in
ipow32/64
(v0.18.2)
その他
- The type of an array literal can now be inferred from its contents (v0.9.0)
- Updated stdlib to Unicode 13.0.0 (v0.10.0)
アップグレードの方法
- Change your mappings
apiVersion
insubgraph.yaml
to0.0.9
:
1...2dataSources:3 ...4 mapping:5 ...6 apiVersion: 0.0.97 ...
- Update the
graph-cli
you’re using to thelatest
version by running:
1# if you have it globally installed2npm install --global @graphprotocol/graph-cli@latest34# or in your subgraph if you have it as a dev dependency5npm install --save-dev @graphprotocol/graph-cli@latest
- Do the same for
graph-ts
, but instead of installing globally, save it in your main dependencies:
1npm install --save @graphprotocol/graph-ts@latest
- ガイドの残りの部分に従って、言語の変更を修正します。
- Run
codegen
anddeploy
again.
変更点
ヌル可能性
古いバージョンの AssemblyScript では、以下のようなコードを作ることができました:
1function load(): Value | null { ... }23let maybeValue = load();4maybeValue.aMethod();
しかし、新しいバージョンでは、値が nullable であるため、次のようにチェックする必要があります:
1let maybeValue = load()23if (maybeValue) {4 maybeValue.aMethod() // `maybeValue` is not null anymore5}
あるいは、次のように強制します:
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.
変数シャドウイング
Before you could do variable shadowing and code like this would work:
1et a = 102let b = 203let a = a + b
しかし、現在はこれができなくなり、コンパイラは次のようなエラーを返します。
1ERROR TS2451: Cannot redeclare block-scoped variable 'a'23 let a = a + b;4 ~~~~~~~~~~~~~5in assembly/index.ts(4,3)
変数シャドウイングを行っていた場合は、重複する変数の名前を変更する必要があります。
Null 比較
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)
To solve you can simply change the if
statement to something like this:
1if (!decimals) {23 // or45 if (decimals === null) {
== の代わりに != を実行している場合も同様です。
鋳造
The common way to do casting before was to just use the as
keyword, like this:
1let byteArray = new ByteArray(10)2let uint8Array = byteArray as Uint8Array // equivalent to: <Uint8Array>byteArray
しかし、これは 2 つのシナリオでしか機能しません。
- Primitive casting (between types such as
u8
,i32
,bool
; eg:let b: isize = 10; b as usize
); - クラス継承のアップキャスティング(サブクラス → スーパークラス)
例
1// primitive casting2let a: usize = 103let b: isize = 54let c: usize = a + (b as usize)
1// upcasting on class inheritance2class Bytes extends Uint8Array {}34let bytes = new Bytes(2)5// <Uint8Array>bytes // same as: bytes as Uint8Array
There are two scenarios where you may want to cast, but using as
/<T>var
isn’t safe:
- クラス継承のダウンキャスト(スーパークラス → サブクラス)
- スーパークラスを共有する 2 つの型の間
1// downcasting on class inheritance2class Bytes extends Uint8Array {}34let uint8Array = new Uint8Array(2)5// <Bytes>uint8Array // breaks in runtime :(
1// between two types that share a superclass2class Bytes extends Uint8Array {}3class ByteArray extends Uint8Array {}45let bytes = new Bytes(2)6// <ByteArray>bytes // breaks in runtime :(
For those cases, you can use the changetype<T>
function:
1// downcasting on class inheritance2class Bytes extends Uint8Array {}34let uint8Array = new Uint8Array(2)5changetype<Bytes>(uint8Array) // works :)
1// between two types that share a superclass2class Bytes extends Uint8Array {}3class ByteArray extends Uint8Array {}45let bytes = new Bytes(2)6changetype<ByteArray>(bytes) // works :)
If you just want to remove nullability, you can keep using the as
operator (or <T>variable
), but make sure you know that value can’t be null, otherwise it will break.
1// remove nullability2let previousBalance = AccountBalance.load(balanceId) // AccountBalance | null34if (previousBalance != null) {5 return previousBalance as AccountBalance // safe remove null6}78let newBalance = new AccountBalance(balanceId)
For the nullability case we recommend taking a look at the nullability check feature, it will make your code cleaner 🙂
また、キャストを容易にするために、いくつかの型にスタティックメソッドを追加しました。
- Bytes.fromByteArray
- Bytes.fromUint8Array
- BigInt.fromByteArray
- ByteArray.fromBigInt
プロパティアクセスによる Nullability チェック
To use the nullability check feature you can use either if
statements or the ternary operator (?
and :
) like this:
1let something: string | null = 'data'23let somethingOrElse = something ? something : 'else'45// or67let somethingOrElse89if (something) {10 somethingOrElse = something11} else {12 somethingOrElse = 'else'13}
However that only works when you’re doing the if
/ ternary on a variable, not on a property access, like this:
1class Container {2 data: string | null3}45let container = new Container()6container.data = 'data'78let somethingOrElse: string = container.data ?
すると、このようなエラーが出力されます。
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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
この問題を解決するには、そのプロパティアクセスのための変数を作成して、コンパイラが nullability check のマジックを行うようにします。
1class Container {2 data: string | null3}45let container = new Container()6container.data = 'data'78let data = container.data910let somethingOrElse: string = data :)
プロパティアクセスによるオペレーターオーバーロード
たとえば、(プロパティ アクセスからの) null 許容型と null 非許容型を合計しようとすると、AssemblyScript コンパイラは、値の 1 つが null 許容であるというコンパイル時のエラー警告を表示する代わりに、黙ってコンパイルします。実行時にコードが壊れる可能性を与えます。
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 // give compile time error about nullability1617let wrapper = new Wrapper(y)1819wrapper.n = wrapper.n + x // doesn't give compile time errors as it should
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 // now `n` is guaranteed to be a BigInt
値の初期化
もし、このようなコードがあった場合:
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'
また、以下のように GraphQL のエンティティに Nullable なプロパティがある場合も同様です。
1type Total @entity {2 id: Bytes!3 amount: BigInt4}
そして、以下のようなコードになります:
1let total = Total.load('latest')23if (total === null) {4 total = new Total('latest')5}67total.amount = total.amount + BigInt.fromI32(1)
You’ll need to make sure to initialize the total.amount
value, because if you try to access like in the last line for the sum, it will crash. So you either initialize it first:
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)
Or you can just change your GraphQL schema to not use a nullable type for this property, then we’ll initialize it as zero on the codegen
step 😉
1type Total @entity {2 id: Bytes!3 amount: BigInt!4}
1let total = Total.load('latest')23if (total === null) {4 total = new Total('latest') // already initializes non-nullable properties5}67total.amount = total.amount + BigInt.fromI32(1)
クラスのプロパティの初期化
以下のように、他のクラス(自分で宣言したものや標準ライブラリで宣言したもの)のプロパティを持つクラスをエクスポートした場合、そのクラスのプロパティを初期化します:
1class Thing {}23export class Something {4 value: Thing5}
The compiler will error because you either need to add an initializer for the properties that are classes, or add the !
operator:
1export class Something {2 constructor(public value: Thing) {}3}45// or67export class Something {8 value: Thing910 constructor(value: Thing) {11 this.value = value12 }13}1415// or1617export class Something {18 value!: Thing19}
配列の初期化
The Array
class still accepts a number to initialize the length of the list, however you should take care because operations like .push
will actually increase the size instead of adding to the beginning, for example:
1let arr = new Array<string>(5) // ["", "", "", "", ""]23arr.push('something') // ["", "", "", "", "", "something"] // size 6 :(
使用している型(例えば null 可能な型) とそのアクセス方法によっては、次のようなランタイムエラーに遭遇する可能性があります。
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
To actually push at the beginning you should either, initialize the Array
with size zero, like this:
1let arr = new Array<string>(0) // []23arr.push('something') // ["something"]
あるいは、インデックス経由で変異させるべきでしょう:
1let arr = new Array<string>(5) // ["", "", "", "", ""]23arr[0] = 'something' // ["something", "", "", "", ""]
GraphQLスキーマ
This is not a direct AssemblyScript change, but you may have to update your schema.graphql
file.
この変更により、Non-Nullable Listのフィールドを型に定義することができなくなりました。仮に、以下のようなスキーマがあった場合:
1type Something @entity {2 id: Bytes!3}45type MyEntity @entity {6 id: Bytes!7 invalidField: [Something]! # no longer valid8}
You’ll have to add an !
to the member of the List type, like this:
1type Something @entity {2 id: Bytes!3}45type MyEntity @entity {6 id: Bytes!7 invalidField: [Something!]! # valid8}
This changed because of nullability differences between AssemblyScript versions, and it’s related to the src/generated/schema.ts
file (default path, you might have changed this).
その他
- Aligned
Map#set
andSet#add
with the spec, returningthis
(v0.9.2) - Arrays no longer inherit from ArrayBufferView, but are now distinct (v0.10.0)
- Classes initialized from object literals can no longer define a constructor (v0.10.0)
- The result of a
**
binary operation is now the common denominator integer if both operands are integers. Previously, the result was a float as if callingMath/f.pow
(v0.11.0) - Coerce
NaN
tofalse
when casting tobool
(v0.14.9) - When shifting a small integer value of type
i8
/u8
ori16
/u16
, only the 3 respectively 4 least significant bits of the RHS value affect the result, analogous to the result of ani32.shl
only being affected by the 5 least significant bits of the RHS value. Example:someI8 << 8
previously produced the value0
, but now producessomeI8
due to masking the RHS as8 & 7 = 0
(3 bits) (v0.17.0) - Bug fix of relational string comparisons when sizes differ (v0.17.8)