发布说明&更新向导 > AssemblyScript 迁移指南

AssemblyScript 迁移指南

Reading time: 14 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 现在可以使用新的wrap静态方法 (v0.8.1)基于ArrayBuffers 构建
  • 新的标准库函数: String#toUpperCase, String#toLowerCase, String#localeCompareTypedArray#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)
  • toStringtoDateStringtoTimeString 添加到 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 函数,例如 expexp2loglog2pow 已替换为更快的变体 (v0.9.0)
  • 些许优化了Math.mod (v0.17.1)
  • 在 std Map 和 Set (v0.17.8) 中缓存更多字段访问
  • ipow32/64 (v0.18.2)中优化二的幂运算

其他

链到本节
  • 现在可以从数组内容中推断出数组文字的类型(v0.9.0
  • 将 stdlib 更新为 Unicode 13.0.0 (v0.10.0)

如何升级?

链到本节
  1. subgraph.yaml 中的映射 apiVersion 更改为 0.0.6
...
dataSources:
...
mapping:
...
apiVersion: 0.0.6
...
  1. 通过运行以下命令,将您正在使用的 graph-cli 更新为 latest 版本:
# 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. 再次运行 codegendeploy

重大变化

链到本节

可空性

链到本节

在旧版本的 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 null
maybeValue.aMethod()

如果您不确定选择哪个,我们建议始终使用安全的方式。 如果该值不存在,您可能只想在您的子图处理程序中,尽早执行一个带有 return 的 if 语句进行检查。

变量遮蔽

链到本节

在您可以进行 变量遮蔽 之前,这样的代码可以工作:

let 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

但是,这只适用于两种情况:

  • 原始类型转换(在u8, i32, bool等类型之间; 例如: 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 并不安全:

  • 在类继承时向下转换(超类 → 子类)
  • 在共享超类的两种类型之间
// 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 :)

如果您只想去掉可空性,您可以继续使用 as 运算符(或 <T>variable),但请确保您知道该值不会为空, 否则程序会出现问题。

// remove nullability
let previousBalance = AccountBalance.load(balanceId) // AccountBalance | null
if (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'
// or
let somethingOrElse
if (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.data
let 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 = 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

我们为此在 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 // null
value.x = 10
value.y = 'content'

代码将编译成功,但在运行时会出现问题,这是因为值尚未初始化,因此请确保您的子图已初始化变量的值,如下所示:

var value = new Type() // initialized
value.x = 10
value.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) {}
}
// 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 :(

根据您使用的类型,例如可以为空的类型,以及访问它们的方式,您可能会遇到类似下面这样的运行时错误:

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", "", "", "", ""]

GraphQL 模式

链到本节

这不是一个直接的 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#setSet#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 时强制 NaNfalse ([v0.14.9](https://github.com/AssemblyScript/assemblyscript/releases/tag /v0.14.9))
  • 当移动 i8/u8i16/u16 类型的小整数值时,只有 4 个 RHS 值的最低有效位中的 3 个会影响结果,类似于 i32.shl 的结果仅受 RHS 值的 5 个最低有效位影响。 示例:someI8 << 8 以前生成值 0,但现在由于将 RHS 屏蔽为8 & 7 = 0 (3 比特), 而生成 someI8v0.17.0
  • 大小不同时关系字符串比较的错误修复 (v0.17.8)
编辑

上页
将现有子图升级到Graph网络
下页
GraphQL验证迁移指南
编辑