Руководство по миграции AssemblyScript
Reading time: 9 min
До сих пор для субграфов использовалась одна из (v0.6). Наконец, мы добавили поддержку (v0.19.10)! 🎉
Это позволит разработчикам субграфов использовать более новые возможности языка AS и стандартной библиотеки.
Это руководство применимо для всех, кто использует graph-cli
/graph-ts
версии ниже 0.22.0
. Если у Вас уже есть версия выше (или равная) этой, значит, Вы уже использовали версию 0.19.10
AssemblyScript 🙂
Примечание. Начиная с 0.24.0
, graph-node
может поддерживать обе версии, в зависимости от apiVersion
, указанного в манифесте субграфа.
- Теперь
TypedArray
можно создавать, используяArrayBuffer
6 с помощью () - Новые функции стандартной библиотеки:
String#toUpperCase
,String#toLowerCase
,String#localeCompare
иTypedArray#set
() - Добавлена поддержка x instanceof GenericClass ()
- Добавлен
StaticArray<T>
, более эффективный вариант массива () - Добавлен
Array<T>#flat
() - Реализован аргумент
radix
дляNumber#toString
() - Добавлена поддержка разделителей в литералах с плавающей точкой ()
- Добавлена поддержка функций первого класса ()
- Добавление встроенных модулей:
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)) - Добавление
toUTCString
дляDate
() - Добавление встроенного типа
nonnull/NonNullable
()
- Функции
Math
, такие какexp
,exp2
,log
,log2
иpow
были заменены более быстрыми вариантами () - Проведена небольшая оптимизация
Math.mod
() - Кэширование большего количества обращений к полям в std Map и Set ()
- Оптимизация по двум степеням в
ipow32/64
()
- Тип литерала массива теперь можно определить по его содержимому ()
- Стандартная библиотека обновлена до версии Unicode 13.0.0 ()
- Измените мэппинги
apiVersion
вsubgraph.yaml
на0.0.6
:
...dataSources:...mapping:...apiVersion: 0.0.6...
- Обновите используемый Вами
graph-cli
доlatest
версии, выполнив:
# если он у Вас установлен глобальноnpm install --global @graphprotocol/graph-cli@latest# или в Вашем субграфе, если он у Вас как зависимость devnpm 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()! // прерывается во время выполнения, если значение равно nullmaybeValue.aMethod()
Если Вы не уверены, что выбрать, мы рекомендуем всегда использовать безопасную версию. Если значение не существует, Вы можете просто выполнить раннее выражение 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
); - Укрупнение по наследованию классов (subclass → superclass)
Примеры:
// примитивный кастингlet a: usize = 10let b: isize = 5let c: usize = a + (b as usize)
// укрупнение по наследованию классовclass Bytes extends Uint8Array {}let bytes = new Bytes(2)// <Uint8Array>bytes // то же, что: bytes as Uint8Array
Есть два сценария, в которых Вы можете захотеть выполнить преобразование, но использовать as
/<T>var
небезопасно:
- Понижение уровня наследования классов (superclass → subclass)
- Между двумя типами, имеющими общий супер класс
// понижение уровня наследования классовclass Bytes extends Uint8Array {}let uint8Array = new Uint8Array(2)// <Bytes>uint8Array // перерывы в работе :(
// между двумя типами, имеющими общий суперклассclass Bytes extends Uint8Array {}class ByteArray extends Uint8Array {}let bytes = new Bytes(2)// <ByteArray>bytes // перерывы в работе :(
В таких случаях можно использовать функцию changetype<T>
:
// понижение уровня наследования классовclass Bytes extends Uint8Array {}let uint8Array = new Uint8Array(2)changetype<Bytes>(uint8Array) // работает :)
// между двумя типами, имеющими общий суперклассclass Bytes extends Uint8Array {}class ByteArray extends Uint8Array {}let bytes = new Bytes(2)changetype<ByteArray>(bytes) // работает :)
Если Вы просто хотите удалить значение NULL, Вы можете продолжать использовать оператор as
(или <T>variable
), но помните, что значение не может быть нулевым, иначе оно сломается.
// удалить значение NULLlet previousBalance = AccountBalance.load(balanceId) // AccountBalance | nullif (previousBalance != null) {return previousBalance as AccountBalance // безопасно удалить значение 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'// илиlet 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' // не компилируется
В результате чего выдается ошибка:
ERROR TS2322: Type '~lib/string/String | null' is not assignable to type '~lib/string/String'.let somethingOrElse: string = container.data ? container.data : "else";~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Чтобы решить эту проблему, Вы можете создать переменную для доступа к этому свойству, чтобы компилятор мог выполнять проверку допустимости значений NULL:
class Container {data: string | null}let container = new Container()container.data = 'data'let data = container.datalet somethingOrElse: string = data ? data : 'else' // компилируется просто отлично :)
Если Вы попытаетесь суммировать (например) тип, допускающий значение Null (из доступа к свойству), с типом, не допускающим значение Null, компилятор AssemblyScript вместо того, чтобы выдать предупреждение об ошибке компиляции, предупреждающую, что одно из значений допускает значение 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 // выдает ошибку времени компиляции о возможности обнуленияlet wrapper = new Wrapper(y)wrapper.n = wrapper.n + x // не выдает ошибок времени компиляции, как это должно быть
Мы открыли вопрос по этому поводу для компилятора AssemblyScript, но пока, если Вы выполняете подобные операции в своих мэппингах субграфов, Вам следует изменить их так, чтобы перед этим выполнялась проверка на нулевое значение.
let wrapper = new Wrapper(y)if (!wrapper.n) {wrapper.n = BigInt.fromI32(0)}wrapper.n = wrapper.n + x // теперь `n` гарантированно будет BigInt
Если у Вас есть такой код:
var value: Type // nullvalue.x = 10value.y = 'content'
Он будет скомпилирован, но сломается во время выполнения. Это происходит из-за того, что значение не было инициализировано, поэтому убедитесь, что Ваш субграф инициализировал свои значения, например так:
var value = new Type() // initializedvalue.x = 10value.y = 'content'
Также, если у Вас есть свойства, допускающие значение NULL, в объекте GraphQL, например:
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
инициализировано, потому что, если Вы попытаетесь получить доступ к сумме, как в последней строке, произойдет сбой. Таким образом, Вы либо инициализируете его первым:
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, чтобы не использовать тип, допускающий значение NULL для этого свойства. Тогда мы инициализируем его нулем на этапе codegen
😉
type Total @entity {id: Bytes!amount: BigInt!}
let total = Total.load('latest')if (total === null) {total = new Total('latest') // уже инициализирует свойства, не допускающие значения NULL}total.amount = total.amount + BigInt.fromI32(1)
Если Вы экспортируете какие-либо классы со свойствами, которые являются другими классами (декларированными Вами или стандартной библиотекой), то это выглядит следующим образом:
class Thing {}export class Something {value: Thing}
Компилятор выдаст ошибку, потому что Вам нужно либо добавить инициализатор для свойств, являющихся классами, либо добавить оператор !
:
export class Something {constructor(public value: Thing) {}}// илиexport class Something {value: Thingconstructor(value: Thing) {this.value = value}}// или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
.
Теперь Вы больше не можете определять поля в своих типах, которые являются списками, не допускающими значение NULL. Если у Вас такая схема:
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}
Изменение произошло из-за различий в допустимости значений NULL между версиями AssemblyScript и связано с файлом src/generated/schema.ts
(путь по умолчанию, возможно, Вы его изменили).
Map#set
иSet#add
согласованы со спецификацией, произведён возврат кthis
()- Массивы больше не наследуются от ArrayBufferView, а являются самостоятельными ()
- Классы, инициализируемые из объектных литералов, больше не могут определять конструктор ()
- Результатом бинарной операции
**
теперь является целое число с общим знаменателем, если оба операнда являются целыми числами. Раньше результатом было число с плавающей запятой, как при вызовеMath/f.pow
() - Приведение
NaN
кfalse
при преобразовании вbool
() - При сдвиге небольшого целочисленного значения типа
i8
/u8
илиi16
/u16
, на результат влияют только соответственно 3 или 4 младших разряда значения RHS, аналогично тому, как при сдвигеi32.shl
на результат влияют только 5 младших разрядов значения RHS. Пример:someI8 << 8
ранее выдавал значение0
, но теперь выдает значениеsomeI8
благодаря маскировке RHS как8 & 7 = 0
(3 бита) () - Исправлена ошибка сравнения реляционных строк при разных размерах ()