Focus mode
There are many features on AssemblyScript. In order to specialize in the NEAR ecosystem, you need to dive deep into AssemblyScript.
Several collections are provided including PersistentMap
, PersistentVector
and PersistentDeque
.
There are currently four types of collections. These all write and read from storage abstracting away a lot of what you might want to add to the storage object.
These collections wrap the Storage
class with convenience methods so you must always use a unique storage prefix for different collections to avoid data collision.
import {
PersistentMap, // implementation of a map you would find in most languages
PersistentVector, // implementation of an array you would find in most languages
PersistentDeque, // implementation of a deque (bidirectional queue)
} from "near-sdk-as";
// contract code below this line can now make use of these collections
PersistentMap:
Acts like a map you would find in most languages. A map class that implements a persistent unordered map. Note that PersistentMap
doesn't store keys, so if you need to retrieve them, include keys in the values.
To create a map;
let map = new PersistentMap<string, string>("m");
To use the map;
map.set(key, value);
map.get(key);
The complete interface for the PersistentMap
class is provided by the snippet below with inline comments.
// REFERENCE ONLY
// this is the interface provided by the PersistentMap object
class PersistentMap<K, V> {
constructor(prefix: string); // unique prefix to avoid data collision
set(key: K, value: V): void; // wraps Storage#set<V>
get(key: K, defaultValue: V | null = null): V | null; // wraps Storage#get<V>
getSome(key: K): V; // wraps Storage#getSome<V>, use if key is known to exist
contains(key: K): bool; // wraps Storage#contains
delete(key: K): void; // wraps Storage#delete
}
PersistentVector:
Acts like an array. A vector class that implements a persistent array.
To create a vector;
let vec = new PersistentVector<string>("v");
To use the vector;
vec.push(value);
vec.pop(value);
vec.length;
The complete interface for the PersistentVector
class is provided by the snippet below with inline comments.
// REFERENCE ONLY
// this is the interface provided by the PersistentVector object
class PersistentVector<T> {
// referred to as "pv" below
constructor(prefix: string); // unique prefix to avoid data collision
containsIndex(index: i32): bool; // confirms that index is within range
get length(): i32; // get length of the pv
get isEmpty(): bool; // check whether pv is empty
@operator("[]") // (index: i32): T wraps Storage#getSome<T> with checks
@operator("[]=") // (index: i32, value: T): void wraps Storage#set<T> with checks
@operator("{}") // (index: i32): T wraps Storage#getSome<T>
@operator("{}=") // (index: i32, value: T): void wraps Storage#set<T>
push(element: T): i32; // wraps Storage#set<T>
pushBack(element: T): i32; // alias for pv.push
pop(): T; // wraps Storage#get<T> and Storage#delete
popBack(): T; // alias for pv.pop
get front(): T; // get first/front of pv (wraps Storage#getSome with checks)
get first(): T; // alias for pv.front
get back(): T; // get last/back of pv (wraps Storage#getSome with checks)
get last(): T; // alias for pv.back
}
PersistentDeque:
Implementation of a deque (bidirectional queue). A deque class that implements a persistent bidirectional queue.
To create a deque;
let dq = new PersistentDeque<string>("d");
To use a deque;
dq.pushFront(value);
dq.popBack();
The complete interface for the PersistentDeque
class is provided by the snippet below with inline comments.
// REFERENCE ONLY
// this is the interface provided by the PersistentDeque object
class PersistentDeque<T> {
// referred to as "pdq" below
constructor(prefix: string); // unique prefix to avoid data collision
containsIndex(index: i32): bool; // checks whether pdq contains the given indexget length(): i32; // get length of the pdq
get isEmpty(): bool; // check whether pdq is empty
@operator("[]") // (index: i32): T wraps Storage#getSome<T> with checks to get T
@operator("{}") // (index: i32): T wraps Storage#getSome<T> to get T
@operator("{}=") // (index: i32, value: T): void wraps Storage#set<T> to set value<T> at index
pushFront(element: T): i32; // add element to front of pdq (wraps Storage#set<T>)
popFront(): T; // get and remove first/front element (wraps Storage#getSome<T> and Storage#delete)
pushBack(element: T): i32; // add element to back of pdq (wraps Storage#set<T>)
popBack(): T; // get and remove last/back element (wraps Storage#getSome<T> and Storage#delete)
get front(): T; // get first/front of pdq (wraps Storage#getSome with checks)
get first(): T; // alias for pdq.front
get back(): T; // get last/back of pdq (wraps Storage#getSome with checks)
get last(): T; // alias for pdq.back
}
Note: If you're coming from JavaScript, you might not be familiar with the type declaration in the two brackets <>
. In AssemblyScript, you need to declare the types that any collection is going to take. This enforces that any data added to the collection must have the same type. If not, an error will be raised by the AssemblyScript compiler insisting that the types must all match. This adds a little up-front effort when programming but means far fewer run time errors happen because of type mismatches.
Note: The letter passed in as an argument (ie. "m"
in the case of the PersistentMap
) is the key that gets assigned as a prefix to distinguish the collections from one another (precisely because they're persisted using the same underlying key-value storage that is controlled by the contract account).
It's important when storing data to carefully consider the key in the key-value pair. With collection types, be sure to add a prefix that is unique to the account which will own the data when the contract is deployed.
To understand why, consider that a single representation of "storage" (set of key-value pairs) is used by each account and so each key in the key-value pair must uniquely identify its data.
This should come as no surprise until we consider that a collection type like PersistentVector
(which behaves like an array) is using exactly the same underlying account storage. This is why we expose a prefix in the constructor of these collection types -- to avoid data collision with other collections.
This means that storage used by a contract must always use a unique storage prefix for each collection to avoid data collision.
Programs to Accelerate Your Progress in a Software Career
Join our 4-8 month intensive Patika+ bootcamps, start with the fundamentals and gain comprehensive knowledge to kickstart your software career!
You need to enroll in the course to be able to comment!