9 минуты
Руководство по миграции AssemblyScript
Up until now, Subgraphs have been using one of the first versions of AssemblyScript (v0.6). Finally we’ve added support for the newest one available (v0.19.10)! 🎉
That will enable Subgraph developers to use newer features of the AS language and standard library.
Это руководство применимо для всех, кто использует graph-cli
/graph-ts
версии ниже 0.22.0. Если у Вас уже есть версия выше (или равная) этой, значит, Вы уже использовали версию 0.19.10 AssemblyScript 🙂
Note: As of 0.24.0
, graph-node
can support both versions, depending on the apiVersion
specified in the Subgraph manifest.
Особенности
Новый функционал
- Теперь
TypedArray
можно создавать изArrayBuffer
, используя новый статический методwrap
(v0.8.1) - Новые функции стандартной библиотеки:
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) - Реализован аргумент
radix
вNumber#toString
(v0.10.1) - Добавлена поддержка разделителей в литералах с плавающей точкой (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) - Добавлен
toUTCString
дляDate
(v0.18.30) - Добавлен встроенный тип
nonnull/NonNullable
(v0.19.2)
Оптимизации
- Функции
Math
, такие какexp
,exp2
,log
,log2
иpow
, были заменены на более быстрые варианты (v0.9.0) - Немного оптимизирована функция
Math.mod
(v0.17.1) - Кэшировано больше обращений к полям в стандартных Map и Set (v0.17.8)
- Проведена оптимизация для степеней двойки в
ipow32/64
(v0.18.2)
Прочее
- Тип литерала массива теперь может быть выведен из его содержимого (v0.9.0)
- Обновлена стандартная библиотека до Unicode 13.0.0 (v0.10.0)
Как выполнить обновление?
- Change your mappings
apiVersion
insubgraph.yaml
to0.0.9
:
1...2dataSources:3 ...4 mapping:5 ...6 apiVersion: 0.0.97 ...
- Обновите используемую Вами версию
graph-cli
доlatest
, выполнив команду:
1# если он у Вас установлен глобально2npm install --global @graphprotocol/graph-cli@latest34# или в Вашем субграфе, если он у Вас как зависимость dev5npm 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()! // прерывается во время выполнения, если значение равно null23maybeValue.aMethod()
If you are unsure which to choose, we recommend always using the safe version. If the value doesn’t exist you might want to just do an early if statement with a return in you Subgraph handler.
Затенение переменных
Раньше можно было использовать затенение переменных, и такой код работал:
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)
Вам нужно будет переименовать дублированные переменные, если Вы используете затенение переменных.
Нулевые сравнения
By doing the upgrade on your Subgraph, sometimes you might get errors like these:
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 // или45 if (decimals === null) {
Подобное относится к случаям, когда вместо == используется !=.
Кастинг
Раньше преобразование типов обычно выполнялось с использованием ключевого слова as
, например:
1let byteArray = new ByteArray(10)2let uint8Array = byteArray as Uint8Array // эквивалентно: <Uint8Array>byteArray
Однако это работает только в двух случаях:
- Примитивное преобразование (между такими типами, как
u8
,i32
,bool
; например:let b: isize = 10; b as usize
); - Укрупнение по наследованию классов (subclass → superclass)
Примеры:
1// примитивный кастинг2let a: usize = 103let b: isize = 54let c: usize = a + (b as usize)
1// приведение к базовому типу при наследовании классов2class Bytes extends Uint8Array {}34let bytes = new Bytes(2)5// <Uint8Array>bytes // то же самое, что: bytes as Uint8Array
Есть два сценария, где Вам может понадобиться преобразование типов, но использование as
/<T>var
небезопасно:
- Понижение уровня наследования классов (superclass → subclass)
- Между двумя типами, имеющими общий супер класс
1// понижение уровня наследования классов2class Bytes extends Uint8Array {}34let uint8Array = new Uint8Array(2)5// <Bytes>uint8Array // перерывы в работе :(
1// между двумя типами, имеющими общий суперкласс2class Bytes extends Uint8Array {}3class ByteArray extends Uint8Array {}45let bytes = new Bytes(2)6// <ByteArray>bytes // перерывы в работе :(
В таких случаях Вы можете использовать функцию changetype<T>
:
1// понижение уровня наследования классов2class Bytes extends Uint8Array {}34let uint8Array = new Uint8Array(2)5changetype<Bytes>(uint8Array) // работает :)
1// между двумя типами, имеющими общий суперкласс2class Bytes extends Uint8Array {}3class ByteArray extends Uint8Array {}45let bytes = new Bytes(2)6changetype<ByteArray>(bytes) // работает :)
Если Вы просто хотите убрать возможность обнуления, Вы можете продолжить использовать оператор as
(или <T>variable
), но помните, что это значение не может быть нулевым, иначе оно приведет к ошибке.
1// удалить значение NULL2let previousBalance = AccountBalance.load(balanceId) // AccountBalance | null34if (previousBalance != null) {5 return previousBalance as AccountBalance // безопасно удалить значение 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// или67let 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' // не компилируется
В результате чего выдается ошибка:
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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Чтобы решить эту проблему, Вы можете создать переменную для доступа к этому свойству, чтобы компилятор мог выполнять проверку допустимости значений NULL:
1class Container {2 data: string | null3}45let container = new Container()6container.data = 'data'78let data = container.data910let somethingOrElse: string = data ? data : 'else' // компилируется просто отлично :)
Перегрузка оператора при доступе к свойствам
Если Вы попытаетесь суммировать (например) тип, допускающий значение Null (из доступа к свойству), с типом, не допускающим значение Null, компилятор AssemblyScript вместо того, чтобы выдать предупреждение об ошибке компиляции, предупреждающую, что одно из значений допускает значение Null, просто компилируется молча, давая возможность сломать код во время выполнения.
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 // выдает ошибку времени компиляции о возможности обнуления1617let wrapper = new Wrapper(y)1819wrapper.n = wrapper.n + x // не выдает ошибок времени компиляции, как это должно быть
We’ve opened a issue on the AssemblyScript compiler for this, but for now if you do these kind of operations in your Subgraph mappings, you should change them to do a null check before it.
1let wrapper = new Wrapper(y)23if (!wrapper.n) {4 wrapper.n = BigInt.fromI32(0)5}67wrapper.n = wrapper.n + x // теперь `n` гарантированно будет BigInt
Инициализация значения
Если у Вас есть такой код:
1var value: Type // null2value.x = 103value.y = 'content'
It will compile but break at runtime, that happens because the value hasn’t been initialized, so make sure your Subgraph has initialized their values, like this:
1var value = new Type() // initialized2value.x = 103value.y = 'content'
Также, если у Вас есть свойства, допускающие значение NULL, в объекте GraphQL, например:
1type Total @entity {2 id: Bytes!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') // уже инициализирует свойства, не допускающие значения NULL5}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// или67export class Something {8 value: Thing910 constructor(value: Thing) {11 this.value = value12 }13}1415// или1617export class Something {18 value!: Thing19}
Инициализация массива
Класс Array
по-прежнему принимает число для инициализации длины списка, однако следует учитывать, что операции, такие как .push
, будут увеличивать размер массива, а не добавлять элемент в начало. Например:
1let arr = new Array<string>(5) // ["", "", "", "", ""]23arr.push('something') // ["", "", "", "", "", "something"] // размер 6 :(
В зависимости от используемых типов, например, допускающих значение NULL, и способа доступа к ним, можно столкнуться с ошибкой времени выполнения, подобной этой:
1ERRO 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
с нулевым размером, например, так:
1let arr = new Array<string>(0) // []23arr.push('something') // ["something"]
Или Вы должны изменить его через индекс:
1let arr = new Array<string>(5) // ["", "", "", "", ""]23arr[0] = 'something' // ["something", "", "", "", ""]
Схема GraphQL
Это не прямое изменение AssemblyScript, но Вам, возможно, придется обновить файл schema.graphql
.
Теперь Вы больше не можете определять поля в своих типах, которые являются списками, не допускающими значение NULL. Если у Вас такая схема:
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)- Массивы больше не наследуются от ArrayBufferView, а теперь являются отдельными (v0.10.0)
- Классы, инициализируемые из объектных литералов, больше не могут определять конструктор (v0.10.0)
- Результат бинарной операции
**
теперь является общим целочисленным знаменателем, если оба операнда - целые числа. Ранее результат был числом с плавающей точкой, как при вызовеMath/f.pow
(v0.11.0) - При приведении к
bool
значениеNaN
теперь принудительно преобразуется вfalse
(v0.14.9) - При сдвиге небольшого целочисленного значения типа
i8
/u8
илиi16
/u16
на результат влияют только 3 или 4 младших бита значения RHS, аналогично результатуi32.shl
, на который влияют только 5 младших битов значения RHS. Пример:someI8 << 8
ранее выдавало значение 0, а теперь выдаетsomeI8
благодаря маскировке RHS как8 & 7 = 0
(3 бита) (v0.17.0) - Исправлена ошибка в сравнении строк разной длины (v0.17.8)