AssemblyScript マイグレーションガイド
Reading time: 15 min
これまでサブグラフは、 (v0.6)を使用していました。 ついに(v0.19.10) のサポートを追加しました! 🎉
これにより、サブグラフの開発者は、AS 言語と標準ライブラリの新しい機能を使用できるようになります。
このガイドは、バージョン0.22.0
以下のgraph-cli
/graph-ts
をお使いの方に適用されます。 もしあなたがすでにそれ以上のバージョンにいるなら、あなたはすでに AssemblyScript のバージョン0.19.10
を使っています。
注:0.24.0
以降、graph-node
はサブグラフマニフェストで指定されたapiVersion
に応じて、両方のバージョンをサポートしています。
TypedArray
s can now be built fromArrayBuffer
s by using the ()- New standard library functions:
String#toUpperCase
,String#toLowerCase
,String#localeCompare
andTypedArray#set
() - Added support for x instanceof GenericClass ()
- Added
StaticArray<T>
, a more efficient array variant () - Added
Array<T>#flat
() - Implemented
radix
argument onNumber#toString
() - Added support for separators in floating point literals ()
- Added support for first class functions ()
- Add builtins:
i32/i64/f32/f64.add/sub/mul
() - Implement
Array/TypedArray/String#at
() - Added support for template literal strings ()
- Add
encodeURI(Component)
anddecodeURI(Component)
() - Add
toString
,toDateString
andtoTimeString
toDate
() - Add
toUTCString
forDate
() - Add
nonnull/NonNullable
builtin type ()
Math
functions such asexp
,exp2
,log
,log2
andpow
have been replaced by faster variants ()- Slightly optimize
Math.mod
() - Cache more field accesses in std Map and Set ()
- Optimize for powers of two in
ipow32/64
()
- The type of an array literal can now be inferred from its contents ()
- Updated stdlib to Unicode 13.0.0 ()
subgraph.yaml
のマッピングのapiVersion
を0.0.6
に変更してください。
...dataSources:...mapping:...apiVersion: 0.0.6...
- 使用している
graph-cli
を最新版
に更新するには、次のように実行します。
# if you have it globally installednpm install --global @graphprotocol/graph-cli@latest# or in your subgraph if you have it as a dev dependencynpm install --save-dev @graphprotocol/graph-cli@latest
graph-ts
についても同様ですが、グローバルにインストールするのではなく、メインの依存関係に保存します。
npm install --save @graphprotocol/graph-ts@latest
- ガイドの残りの部分に従って、言語の変更を修正します。
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 nullmaybeValue.aMethod()
どちらを選択すべきか迷った場合は、常に安全なバージョンを使用することをお勧めします。 値が存在しない場合は、サブグラフハンドラの中で return を伴う初期の if 文を実行するとよいでしょう。
以前は、を行うことができ、次のようなコードが動作していました。
et a = 10let b = 20let 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) {// orif (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 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
キャストしたくても、as
/<T>var
を使うと安全ではないというシナリオが 2 つあります。
- クラス継承のダウンキャスト(スーパークラス → サブクラス)
- スーパークラスを共有する 2 つの型の間
// downcasting on class inheritanceclass Bytes extends Uint8Array {}let uint8Array = new Uint8Array(2)// <Bytes>uint8Array // breaks in runtime :(
// between two types that share a superclassclass Bytes extends Uint8Array {}class ByteArray extends Uint8Array {}let bytes = new Bytes(2)// <ByteArray>bytes // breaks in runtime :(
このような場合には、changetype<T>
関数を使用します。
// downcasting on class inheritanceclass Bytes extends Uint8Array {}let uint8Array = new Uint8Array(2)changetype<Bytes>(uint8Array) // works :)
// between two types that share a superclassclass Bytes extends Uint8Array {}class ByteArray extends Uint8Array {}let bytes = new Bytes(2)changetype<ByteArray>(bytes) // works :)
単に null 性を除去したいだけなら、as
オペレーター(or <T>variable
)を使い続けることができますが、値が null ではないことを確認しておかないと壊れてしまいます。
// remove nullabilitylet previousBalance = AccountBalance.load(balanceId) // AccountBalance | nullif (previousBalance != null) {return previousBalance as AccountBalance // safe remove null}let newBalance = new AccountBalance(balanceId)
Nullability については、を利用することをお勧めします。それはあなたのコードをよりきれいにします🙂
また、キャストを容易にするために、いくつかの型にスタティックメソッドを追加しました。
- Bytes.fromByteArray
- Bytes.fromUint8Array
- BigInt.fromByteArray
- ByteArray.fromBigInt
を使用するには、次のようにif
文や三項演算子(?
and :
) を使用します。
let something: string | null = 'data'let somethingOrElse = something ? something : 'else'// orlet somethingOrElseif (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.datalet 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 = nullx + y // give compile time error about nullabilitylet 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 // nullvalue.x = 10value.y = 'content'
これは、値が初期化されていないために起こります。したがって、次のようにサブグラフが値を初期化していることを確認してください。
var value = new Type() // initializedvalue.x = 10value.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) {}}// orexport class Something {value: Thingconstructor(value: Thing) {this.value = value}}// orexport 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
andSet#add
with the spec, returningthis
() - Arrays no longer inherit from ArrayBufferView, but are now distinct ()
- Classes initialized from object literals can no longer define a constructor ()
- 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
() - Coerce
NaN
tofalse
when casting tobool
() - タイプ
i8
/u8
またはi16
/u16
の小さな整数値をシフトする場合、最小の 3 つ、それぞれ 4 つだけRHS 値の上位 5 ビットのみが影響を受けるi32.shl
の結果と同様に、RHS 値の有効ビットが結果に影響します。例:someI8 << 8
は以前は値0
を生成していましたが、RHS を8 & 7 = 0
としてマスクするため、someI8
を生成するようになりました。(3 ビット) () - Bug fix of relational string comparisons when sizes differ ()