AssemblyScript 迁移指南
Reading time: 14 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 现在可以使用 ()基于ArrayBuffer
s 构建- 新的标准库函数:
String#toUpperCase
,String#toLowerCase
,String#localeCompare
和TypedArray#set
() - 增加了对 x instanceof GenericClass ()的支持
- 添加了
StaticArray<T>
, 一种更高效的数组变体 () - 增加了
Array<T>#flat
() - 在
Number#toString
()上实现了radix
参数 - 添加了对浮点文字中的分隔符的支持 ()
- 添加了对一级函数的支持 ()
- 添加内置函数:
i32/i64/f32/f64.add/sub/mul
() - 实现了
Array/TypedArray/String#at
() - 添加了对模板文字字符串的支持()
- 添加了
encodeURI(Component)
和decodeURI(Component)
() - 将
toString
、toDateString
和toTimeString
添加到Date
([v0.18.29]( AssemblyScript/assemblyscript/releases/tag/v0.18.29)) - 为
Date
添加了toUTCString
() - 添加
nonnull/NonNullable
内置类型()
Math
函数,例如exp
、exp2
、log
、log2
和pow
已替换为更快的变体 ()- 些许优化了
Math.mod
() - 在 std Map 和 Set () 中缓存更多字段访问
- 在
ipow32/64
()中优化二的幂运算
- 将
subgraph.yaml
中的映射apiVersion
更改为0.0.6
:
...dataSources:...mapping:...apiVersion: 0.0.6...
- 通过运行以下命令,将您正在使用的
graph-cli
更新为latest
版本:
# 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();
但是在较新的版本中,由于该值可以为空,因此需要您进行检查,如下所示:
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 语句进行检查。
let 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
但是,这只适用于两种情况:
- 原始类型转换(在
u8
,i32
,bool
等类型之间; 例如: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
并不安全:
- 在类继承时向下转换(超类 → 子类)
- 在共享超类的两种类型之间
// 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 :)
如果您只想去掉可空性,您可以继续使用 as
运算符(或 <T>variable
),但请确保您知道该值不会为空, 否则程序会出现问题。
// remove nullabilitylet previousBalance = AccountBalance.load(balanceId) // AccountBalance | nullif (previousBalance != null) {return previousBalance as AccountBalance // safe remove null}let newBalance = new AccountBalance(balanceId)
我们还在某些类型中添加了一些静态方法来简化转换,它们是:
- Bytes.fromByteArray
- Bytes.fromUint8Array
- BigInt.fromByteArray
- ByteArray.fromBigInt
要使用 ,您可以使用 if
语句或三元运算符(?
和 :
),如下所示:
let something: string | null = 'data'let somethingOrElse = something ? something : 'else'// orlet somethingOrElseif (something) {somethingOrElse = something} else {somethingOrElse = 'else'}
但是,这仅在您对变量执行 if
/ 三元组而不是属性访问时才有效,如下所示:
class Container {data: string | null}let container = new Container()container.data = 'data'let somethingOrElse: string = container.data ? container.data : 'else' // doesn't compile
输出此错误:
ERROR TS2322: Type '~lib/string/String | null' is not assignable to type '~lib/string/String'.let somethingOrElse: string = container.data ? container.data : "else";~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
要解决此问题,您可以为该属性访问创建一个变量,以便编译器可以执行可空性检查:
class Container {data: string | null}let container = new Container()container.data = 'data'let data = container.datalet somethingOrElse: string = data ? data : 'else' // compiles just fine :)
如果您尝试将可空类型(来自属性访问)与不可空类型相加,AssemblyScript 编译器不会给出编译时错误警告其中一个值可以为空,它只是静默编译,这会导致代码在运行时可能出现问题。
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
我们为此在 AssemblyScript 编译器上提出了一个 issue,但现在如果您在子图映射中执行此类操作,您应该在之前进行空值检查。
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 实体中有可为空的属性,如下所示:
type Total @entity {id: Id!amount: BigInt}
同时,您有类似这样的代码:
let total = Total.load('latest')if (total === null) {total = new Total('latest')}total.amount = total.amount + BigInt.fromI32(1)
您需要确保初始化 total.amount
值,因为如果您尝试像最后一行代码一样求和,程序将崩溃。 所以你要么先初始化它:
let total = Total.load('latest')if (total === null) {total = new Total('latest')total.amount = BigInt.fromI32(0)}total.tokens = total.tokens + BigInt.fromI32(1)
或者您可以更改您的 GraphQL 模式,不给此属性赋予可为空的类型,然后您在 codegen
步骤中将其初始化为零 😉
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 :(
根据您使用的类型,例如可以为空的类型,以及访问它们的方式,您可能会遇到类似下面这样的运行时错误:
ERRO 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
初始化为大小为零,如下所示:
let arr = new Array<string>(0) // []arr.push('something') // ["something"]
或者,你可以通过索引对其进行改变:
let arr = new Array<string>(5) // ["", "", "", "", ""]arr[0] = 'something' // ["something", "", "", "", ""]
这不是一个直接的 AssemblyScript 更改,但是您可能需要更新 schema.Graphql
文件。
现在,您不再能够在类型中定义属于非空列表的字段。如果您有这样的模式:
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 版本之间的可空性差异导致了这种改变, 并且这也与 src/generated/schema.ts
文件(默认路径,您可能已更改)有关。
- 将
Map#set
和Set#add
与规范对齐,返回this
([v0.9.2]( /assemblyscript/releases/tag/v0.9.2)) - 数组不再继承自 ArrayBufferView,并且现在是完全不同的 ()
- 从对象字面初始化的类不能再定义构造函数()
- 如果两个操作数都是整数,则
**
二元运算的结果现在是公分母整数。 以前,结果是一个浮点数,就像调用Math/f.pow
() - 在转换为
bool
时强制NaN
为false
([v0.14.9]( /v0.14.9)) - 当移动
i8
/u8
或i16
/u16
类型的小整数值时,只有 4 个 RHS 值的最低有效位中的 3 个会影响结果,类似于i32.shl
的结果仅受 RHS 值的 5 个最低有效位影响。 示例:someI8 << 8
以前生成值0
,但现在由于将 RHS 屏蔽为8 & 7 = 0
(3 比特), 而生成someI8
() - 大小不同时关系字符串比较的错误修复 ()