リリースノート&アップグレードガイド > AssemblyScript マイグレーションガイド

AssemblyScript マイグレーションガイド

Reading time: 15 min

これまでサブグラフは、AssemblyScript の最初のバージョン (v0.6)を使用していました。 ついに最新のバージョン(v0.19.10) のサポートを追加しました! 🎉

これにより、サブグラフの開発者は、AS 言語と標準ライブラリの新しい機能を使用できるようになります。

このガイドは、バージョン0.22.0以下のgraph-cli/graph-ts をお使いの方に適用されます。 もしあなたがすでにそれ以上のバージョンにいるなら、あなたはすでに AssemblyScript のバージョン0.19.10 を使っています。

注:0.24.0以降、graph-nodeはサブグラフマニフェストで指定されたapiVersionに応じて、両方のバージョンをサポートしています。

特徴

このセクションへのリンク
  • TypedArrays can now be built from ArrayBuffers by using the new wrap static method (v0.8.1)
  • New standard library functions: String#toUpperCase, String#toLowerCase, String#localeCompareand TypedArray#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 on Number#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) and decodeURI(Component) (v0.18.27)
  • Add toString, toDateString and toTimeString to Date (v0.18.29)
  • Add toUTCString for Date (v0.18.30)
  • Add nonnull/NonNullable builtin type (v0.19.2)
  • Math functions such as exp, exp2, log, log2 and pow 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)

アップグレードの方法

このセクションへのリンク
  1. subgraph.yamlのマッピングのapiVersion0.0.6に変更してください。
...
dataSources:
...
mapping:
...
apiVersion: 0.0.6
...
  1. 使用しているgraph-cli最新版に更新するには、次のように実行します。
# if you have it globally installed
npm install --global @graphprotocol/graph-cli@latest
# or in your subgraph if you have it as a dev dependency
npm install --save-dev @graphprotocol/graph-cli@latest
  1. graph-tsについても同様ですが、グローバルにインストールするのではなく、メインの依存関係に保存します。
npm install --save @graphprotocol/graph-ts@latest
  1. ガイドの残りの部分に従って、言語の変更を修正します。
  2. codegenを実行し、再度deployします。

古いバージョンの AssemblyScript では、以下のようなコードを作ることができました:

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

しかし、新しいバージョンでは、値が nullable であるため、次のようにチェックする必要があります:

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

あるいは、次のように強制します:

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

どちらを選択すべきか迷った場合は、常に安全なバージョンを使用することをお勧めします。 値が存在しない場合は、サブグラフハンドラの中で return を伴う初期の if 文を実行するとよいでしょう。

変数シャドウイング

このセクションへのリンク

以前は、変数のシャドウイングを行うことができ、次のようなコードが動作していました。

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

しかし、現在はこれができなくなり、コンパイラは次のようなエラーを返します。

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

変数シャドウイングを行っていた場合は、重複する変数の名前を変更する必要があります。

サブグラフのアップグレードを行うと、時々以下のようなエラーが発生することがあります。

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)

解決するには、 if 文を以下のように変更するだけです。

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

== の代わりに != を実行している場合も同様です。

以前の一般的なキャストの方法は、次のようにasキーワードを使うだけでした。

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

しかし、これは 2 つのシナリオでしか機能しません。

  • プリミティブなキャスト(between types such as u8, i32, bool; eg: let b: isize = 10; b as usize);
  • クラス継承のアップキャスティング(サブクラス → スーパークラス)

// 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

キャストしたくても、as/<T>varを使うと安全ではないというシナリオが 2 つあります。

  • クラス継承のダウンキャスト(スーパークラス → サブクラス)
  • スーパークラスを共有する 2 つの型の間
// downcasting on class inheritance
class Bytes extends Uint8Array {}
let uint8Array = new Uint8Array(2)
// <Bytes>uint8Array // breaks in runtime :(
// between two types that share a superclass
class Bytes extends Uint8Array {}
class ByteArray extends Uint8Array {}
let bytes = new Bytes(2)
// <ByteArray>bytes // breaks in runtime :(

このような場合には、changetype<T>関数を使用します。

// downcasting on class inheritance
class Bytes extends Uint8Array {}
let uint8Array = new Uint8Array(2)
changetype<Bytes>(uint8Array) // works :)
// between two types that share a superclass
class Bytes extends Uint8Array {}
class ByteArray extends Uint8Array {}
let bytes = new Bytes(2)
changetype<ByteArray>(bytes) // works :)

単に null 性を除去したいだけなら、as オペレーター(or <T>variable)を使い続けることができますが、値が null ではないことを確認しておかないと壊れてしまいます。

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

Nullability については、nullability check featureを利用することをお勧めします。それはあなたのコードをよりきれいにします🙂

また、キャストを容易にするために、いくつかの型にスタティックメソッドを追加しました。

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

プロパティアクセスによる Nullability チェック

このセクションへのリンク

nullability check featureを使用するには、次のようにif文や三項演算子(? and :) を使用します。

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

しかし、これは、以下のように、プロパティのアクセスではなく、変数に対してif/ternary を行っている場合にのみ機能します。

class Container {
data: string | null
}
let container = new Container()
container.data = 'data'
let somethingOrElse: string = container.data ?

すると、このようなエラーが出力されます。

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

この問題を解決するには、そのプロパティアクセスのための変数を作成して、コンパイラが nullability check のマジックを行うようにします。

class Container {
data: string | null
}
let container = new Container()
container.data = 'data'
let data = container.data
let somethingOrElse: string = data :)

プロパティアクセスによるオペレーターオーバーロード

このセクションへのリンク

たとえば、(プロパティ アクセスからの) null 許容型と null 非許容型を合計しようとすると、AssemblyScript コンパイラは、値の 1 つが null 許容であるというコンパイル時のエラー警告を表示する代わりに、黙ってコンパイルします。実行時にコードが壊れる可能性を与えます。

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 // doesn't give compile time errors as it should

この件に関して、アセンブリ・スクリプト・コンパイラーに問題を提起しましたが、 今のところ、もしサブグラフ・マッピングでこの種の操作を行う場合には、 その前に NULL チェックを行うように変更してください。

let wrapper = new Wrapper(y)
if (!wrapper.n) {
wrapper.n = BigInt.fromI32(0)
}
wrapper.n = wrapper.n + x // now `n` is guaranteed to be a BigInt

もし、このようなコードがあった場合:

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

これは、値が初期化されていないために起こります。したがって、次のようにサブグラフが値を初期化していることを確認してください。

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

また、以下のように GraphQL のエンティティに Nullable なプロパティがある場合も同様です。

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

そして、以下のようなコードになります:

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

total.amountの値を確実に初期化する必要があります。なぜなら、最後の行の sum のようにアクセスしようとすると、クラッシュしてしまうからです。 そのため、最初に初期化する必要があります。

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

あるいは、このプロパティに nullable 型を使用しないように GraphQL スキーマを変更することもできます。そうすれば、コード生成の段階でゼロとして初期化されます。😉

type Total @entity {
id: Bytes!
amount: BigInt!
}
let total = Total.load('latest')
if (total === null) {
total = new Total('latest') // already initializes non-nullable properties
}
total.amount = total.amount + BigInt.fromI32(1)

クラスのプロパティの初期化

このセクションへのリンク

以下のように、他のクラス(自分で宣言したものや標準ライブラリで宣言したもの)のプロパティを持つクラスをエクスポートした場合、そのクラスのプロパティを初期化します:

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

コンパイラがエラーになるのは、クラスであるプロパティにイニシャライザを追加するか、! オペレーターを追加する必要があるからです。

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
}

Arrayクラスは、リストの長さを初期化するための数値を依然として受け取ります。しかし、例えば.pushのような操作は、先頭に追加するのではなく、実際にサイズを大きくするので、注意が必要です。

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

使用している型(例えば null 可能な型) とそのアクセス方法によっては、次のようなランタイムエラーに遭遇する可能性があります。

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

実際に最初にプッシュするには、以下のように、サイズゼロの Arrayを初期化する必要があります:

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

あるいは、インデックス経由で変異させるべきでしょう:

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

これは直接のAssemblyScriptの変更ではありませんが、schema.graphql ファイルを更新する必要があるかもしれません。

この変更により、Non-Nullable Listのフィールドを型に定義することができなくなりました。仮に、以下のようなスキーマがあった場合:

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

List タイプのメンバーには、以下のように! を付ける必要があります:

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

これはAssemblyScriptのバージョンによるNullabilityの違いで変わったもので、src/generated/schema.tsファイル(デフォルトパス、変更されているかもしれません)に関連しています。

  • Aligned Map#set and Set#add with the spec, returning this (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 calling Math/f.pow (v0.11.0)
  • Coerce NaN to false when casting to bool (v0.14.9)
  • タイプ i8/u8 または i16/u16 の小さな整数値をシフトする場合、最小の 3 つ、それぞれ 4 つだけRHS 値の上位 5 ビットのみが影響を受ける i32.shl の結果と同様に、RHS 値の有効ビットが結果に影響します。例: someI8 << 8 は以前は値 0 を生成していましたが、RHS を 8 & 7 = 0 としてマスクするため、someI8 を生成するようになりました。(3 ビット) (v0.17.0)
  • Bug fix of relational string comparisons when sizes differ (v0.17.8)
ページを編集

既存のサブグラフをThe Graph Networkにアップグレードする方法
GraphQL 検証移行ガイド
ページを編集