14 分钟
AssemblyScript 迁移指南
到目前为止,子图一直在使用 AssemblyScript 的第一个版本 (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 现在可以使用新的wrap
静态方法 (v0.8.1)基于ArrayBuffer
s 构建- 新的标准库函数:
String#toUpperCase
,String#toLowerCase
,String#localeCompare
和TypedArray#set
(v0.9.0) - 增加了对 x instanceof GenericClass (v0.9.2)的支持
- 添加了
StaticArray<T>
, 一种更高效的数组变体 (v0.9.3) - 增加了
Array<T>#flat
(v0.10.0) - 在
Number#toString
(v0.10.1)上实现了radix
参数 - 添加了对浮点文字中的分隔符的支持 (v0.13.7)
- 添加了对一级函数的支持 (v0.14.0)
- 添加内置函数:
i32/i64/f32/f64.add/sub/mul
( v0.14.13) - 实现了
Array/TypedArray/String#at
(v0.18.2) - 添加了对模板文字字符串的支持(v0.18.17)
- 添加了
encodeURI(Component)
和decodeURI(Component)
(v0.18.27) - 将
toString
、toDateString
和toTimeString
添加到Date
([v0.18.29](https://github.com/ AssemblyScript/assemblyscript/releases/tag/v0.18.29)) - 为
Date
添加了toUTCString
(v0.18.30) - 添加
nonnull/NonNullable
内置类型(v0.19.2)
优化
Math
函数,例如exp
、exp2
、log
、log2
和pow
已替换为更快的变体 (v0.9.0)- 些许优化了
Math.mod
(v0.17.1) - 在 std Map 和 Set (v0.17.8) 中缓存更多字段访问
- 在
ipow32/64
(v0.18.2)中优化二的幂运算
其他
如何升级?
- 将
subgraph.yaml
中的映射apiVersion
更改为0.0.9
:
1...2dataSources:3 ...4 mapping:5 ...6 apiVersion: 0.0.97 ...
- 通过运行以下命令,将您正在使用的
graph-cli
更新为latest
版本:
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
- 对
graph-ts
执行相同的操作,但不是全局安装,而是将其保存在您的主要依赖项中:
1npm install --save @graphprotocol/graph-ts@latest
- 参考指南的其余部分修复语言更改带来的问题。
- 再次运行
codegen
和deploy
。
重大变化
可空性
在旧版本的 AssemblyScript 上,您可以创建如下代码:
1function load(): Value | null { ... }23let maybeValue = load();4maybeValue.aMethod();
但是在较新的版本中,由于该值可以为空,因此需要您进行检查,如下所示:
1let maybeValue = load()23if (maybeValue) {4 maybeValue.aMethod() // `maybeValue` is not null anymore5}
或者像这样编写代码:
1let maybeValue = load()! // breaks in runtime if value is null23maybeValue.aMethod()
如果您不确定选择哪个,我们建议始终使用安全的方式。 如果该值不存在,您可能只想在您的子图处理程序中,尽早执行一个带有 return 的 if 语句进行检查。
变量遮蔽
在您可以进行 变量遮蔽 之前,这样的代码可以工作:
1let 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)
如果您有变量遮蔽的情况,则需要重命名重名变量。
空值比较
对子图进行升级后,有时您可能会遇到如下错误:
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)
要解决此问题,您只需将 if
语句更改为如下所示代码:
1if (!decimals) {23 // or45 if (decimals === null) {
如果您使用 != 而不是 ==,这同样适用。
强制转换
以前,进行强制转换的常用方法是使用 as
关键字,如下所示:
1let byteArray = new ByteArray(10)2let uint8Array = byteArray as Uint8Array // equivalent to: <Uint8Array>byteArray
但是,这只适用于两种情况:
- 原始类型转换(在
u8
,i32
,bool
等类型之间; 例如: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
在两种情况下,您可能希望进行类型转换,但使用 as
/<T>var
并不安全:
- 在类继承时向下转换(超类 → 子类)
- 在共享超类的两种类型之间
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 :(
对于这些情况,您可以使用 changetype<T>
函数:
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 :)
如果您只想去掉可空性,您可以继续使用 as
运算符(或 <T>variable
),但请确保您知道该值不会为空, 否则程序会出现问题。
1// remove nullability2let previousBalance = AccountBalance.load(balanceId) // AccountBalance | null34if (previousBalance != null) {5 return previousBalance as AccountBalance // safe remove null6}78let newBalance = new AccountBalance(balanceId)
对于可空性情况,我们建议查看可空性检查功能,它会让您的代码更简洁 🙂
我们还在某些类型中添加了一些静态方法来简化转换,它们是:
- Bytes.fromByteArray
- Bytes.fromUint8Array
- BigInt.fromByteArray
- ByteArray.fromBigInt
使用属性访问进行可空性检查
要使用 可空性检查功能,您可以使用 if
语句或三元运算符(?
和 :
),如下所示:
1let something: string | null = 'data'23let somethingOrElse = something ? something : 'else'45// or67let somethingOrElse89if (something) {10 somethingOrElse = something11} else {12 somethingOrElse = 'else'13}
但是,这仅在您对变量执行 if
/ 三元组而不是属性访问时才有效,如下所示:
1class Container {2 data: string | null3}45let container = new Container()6container.data = 'data'78let somethingOrElse: string = container.data ? container.data : 'else' // doesn't compile
输出此错误:
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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
要解决此问题,您可以为该属性访问创建一个变量,以便编译器可以执行可空性检查:
1class Container {2 data: string | null3}45let container = new Container()6container.data = 'data'78let data = container.data910let somethingOrElse: string = data ? data : 'else' // compiles just fine :)
具有属性访问的运算符重载
如果您尝试将可空类型(来自属性访问)与不可空类型相加,AssemblyScript 编译器不会给出编译时错误警告其中一个值可以为空,它只是静默编译,这会导致代码在运行时可能出现问题。
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
我们为此在 AssemblyScript 编译器上提出了一个 issue,但现在如果您在子图映射中执行此类操作,您应该在之前进行空值检查。
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'
代码将编译成功,但在运行时会出现问题,这是因为值尚未初始化,因此请确保您的子图已初始化变量的值,如下所示:
1var value = new Type() // initialized2value.x = 103value.y = 'content'
此外,如果您在 GraphQL 实体中有可为空的属性,如下所示:
1type Total @entity {2 id: Id!3 amount: BigInt4}
同时,您有类似这样的代码:
1let total = Total.load('latest')23if (total === null) {4 total = new Total('latest')5}67total.amount = total.amount + BigInt.fromI32(1)
您需要确保初始化 total.amount
值,因为如果您尝试像最后一行代码一样求和,程序将崩溃。 所以你要么先初始化它:
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)
或者您可以更改您的 GraphQL 模式,不给此属性赋予可为空的类型,然后您在 codegen
步骤中将其初始化为零 😉
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}
编译器会报错,因为您需要为类属性添加初始化程序,或者添加 !
运算符:
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}
数组初始化
Array
类仍然接受一个数字来初始化列表的长度,但是您应该小心,因为像.push
的操作实际上会增加大小,而不是添加到开头,例如:
1let arr = new Array<string>(5) // ["", "", "", "", ""]23arr.push('something') // ["", "", "", "", "", "something"] // size 6 :(
根据您使用的类型,例如可以为空的类型,以及访问它们的方式,您可能会遇到类似下面这样的运行时错误:
1ERRO Handler 由于执行失败而跳过,错误: 映射在 ~ lib/array.ts,第110行,第40列中止,并且带有消息: 如果 array 是漏洞 wasm 反向跟踪,那么 Element type 必须为 null: 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
初始化为大小为零,如下所示:
1let arr = new Array<string>(0) // []23arr.push('something') // ["something"]
或者,你可以通过索引对其进行改变:
1let arr = new Array<string>(5) // ["", "", "", "", ""]23arr[0] = 'something' // ["something", "", "", "", ""]
GraphQL 模式
这不是一个直接的 AssemblyScript 更改,但是您可能需要更新 schema.Graphql
文件。
现在,您不再能够在类型中定义属于非空列表的字段。如果您有这样的模式:
1type Something @entity {2 id: Bytes!3}45type MyEntity @entity {6 id: Bytes!7 invalidField: [Something]! # no longer valid8}
您必须向 List 类型的成员添加一个!
,如下所示:
1type Something @entity {2 id: Bytes!3}45type MyEntity @entity {6 id: Bytes!7 invalidField: [Something!]! # valid8}
AssemblyScript 版本之间的可空性差异导致了这种改变, 并且这也与 src/generated/schema.ts
文件(默认路径,您可能已更改)有关。
其他
- 将
Map#set
和Set#add
与规范对齐,返回this
([v0.9.2](https://github.com/AssemblyScript /assemblyscript/releases/tag/v0.9.2)) - 数组不再继承自 ArrayBufferView,并且现在是完全不同的 (v0.10.0)
- 从对象字面初始化的类不能再定义构造函数(v0.10.0)
- 如果两个操作数都是整数,则
**
二元运算的结果现在是公分母整数。 以前,结果是一个浮点数,就像调用Math/f.pow
(v0.11.0) - 在转换为
bool
时强制NaN
为false
([v0.14.9](https://github.com/AssemblyScript/assemblyscript/releases/tag /v0.14.9)) - 当移动
i8
/u8
或i16
/u16
类型的小整数值时,只有 4 个 RHS 值的最低有效位中的 3 个会影响结果,类似于i32.shl
的结果仅受 RHS 值的 5 个最低有效位影响。 示例:someI8 << 8
以前生成值0
,但现在由于将 RHS 屏蔽为8 & 7 = 0
(3 比特), 而生成someI8
(v0.17.0) - 修复了大小不同时关系字符串比较的错误 (v0.17.8)