@taquito/michelson-encoder
Version:
converts michelson data and types into convenient JS/TS objects
205 lines (204 loc) • 7.4 kB
JavaScript
"use strict";
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MichelsonMap = exports.MapTypecheckError = exports.InvalidMapTypeError = void 0;
const storage_1 = require("./schema/storage");
const fast_json_stable_stringify_1 = require("fast-json-stable-stringify");
const core_1 = require("@taquito/core");
/**
* @category Error
* @description Error that indicates an invalid map type being passed or used
*/
class InvalidMapTypeError extends core_1.TaquitoError {
constructor(mapType, reason) {
super();
this.mapType = mapType;
this.reason = reason;
this.message = `The map type '${JSON.stringify(mapType)}' is invalid. Reason: ${reason}.`;
this.name = 'InvalidMapTypeError';
}
}
exports.InvalidMapTypeError = InvalidMapTypeError;
// Retrieve a unique symbol associated with the key from the environment
// Used in order to identify all object that are of type MichelsonMap even if they come from different module
const michelsonMapTypeSymbol = Symbol.for('taquito-michelson-map-type-symbol');
/**
*
* @throws {@link InvalidMapTypeError} when the argument passed to mapType is not a valid map type
*/
function validateMapType(value) {
if (!('prim' in value)) {
throw new InvalidMapTypeError(value, `Missing 'prim' field`);
}
if (!['map', 'big_map'].includes(value.prim)) {
throw new InvalidMapTypeError(value, `The prim field should be 'map' or 'big_map'`);
}
if (!('args' in value)) {
throw new InvalidMapTypeError(value, `Missing 'args' field`);
}
if (!Array.isArray(value.args)) {
throw new InvalidMapTypeError(value, `The 'args' field should be an array`);
}
if (value.args.length !== 2) {
throw new InvalidMapTypeError(value, `The 'args' field should have 2 elements`);
}
}
/**
* @category Error
* @description Error that indicates a map type mismatch, where an attempt to set a key or value in a Map doesn't match the defined type of the Map
*/
class MapTypecheckError extends core_1.TaquitoError {
constructor(value, type, objectType, reason) {
super();
this.value = value;
this.type = type;
this.reason = reason;
this.name = 'MapTypecheckError';
this.message = `The ${objectType} provided: ${JSON.stringify(value)} is not compatible with the expected michelson type: ${JSON.stringify(type)}. Reason: ${JSON.stringify(reason)}.`;
this.name = 'MapTypecheckError';
}
}
exports.MapTypecheckError = MapTypecheckError;
/**
* @description Michelson Map is an abstraction over the michelson native map. It supports complex Pair as key
*/
class MichelsonMap {
// Used to check if an object is a michelson map.
// Using instanceof was not working for project that had multiple instance of taquito dependencies
// as the class constructor is different
static isMichelsonMap(obj) {
return obj && obj[michelsonMapTypeSymbol] === true;
}
/**
* @param mapType If specified key and value will be type-checked before being added to the map
*
* @example new MichelsonMap({ prim: "map", args: [{prim: "string"}, {prim: "int"}]})
*/
constructor(mapType) {
this.valueMap = new Map();
this.keyMap = new Map();
this[_a] = true;
if (mapType) {
this.setType(mapType);
}
}
setType(mapType) {
validateMapType(mapType);
this.keySchema = new storage_1.Schema(mapType.args[0]);
this.valueSchema = new storage_1.Schema(mapType.args[1]);
}
removeType() {
this.keySchema = undefined;
this.valueSchema = undefined;
}
static fromLiteral(obj, mapType) {
const map = new MichelsonMap(mapType);
Object.keys(obj).forEach((key) => {
map.set(key, obj[key]);
});
return map;
}
typecheckKey(key) {
if (!this.keySchema) {
return;
}
this.keySchema.Typecheck(key);
}
typecheckValue(value) {
if (!this.valueSchema) {
return;
}
this.valueSchema.Typecheck(value);
}
/**
* @throws {@link MapTypecheckError} when the argument passed does not match the expected schema for value
*/
assertTypecheckValue(value) {
try {
this.typecheckValue(value);
}
catch (e) {
throw new MapTypecheckError(value, this.valueSchema, 'value', e);
}
}
/**
* @throws {@link MapTypecheckError} when the argument passed does not match the expected schema for key
*/
assertTypecheckKey(key) {
try {
this.typecheckKey(key);
}
catch (e) {
throw new MapTypecheckError(key, this.keySchema, 'key', e);
}
}
serializeDeterministically(key) {
return (0, fast_json_stable_stringify_1.default)(key);
}
*keys() {
for (const [key] of this.entries()) {
yield key;
}
}
*values() {
for (const [, value] of this.entries()) {
yield value;
}
}
*entries() {
for (const key of this.valueMap.keys()) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
yield [this.keyMap.get(key), this.valueMap.get(key)];
}
}
get(key) {
this.assertTypecheckKey(key);
const strKey = this.serializeDeterministically(key);
return this.valueMap.get(strKey);
}
/**
*
* @description Set a key and a value in the MichelsonMap. If the key already exists, override the current value.
*
* @example map.set("myKey", "myValue") // Using a string as key
*
* @example map.set({0: "test", 1: "test1"}, "myValue") // Using a pair as key
*
* @warn The same key can be represented in multiple ways, depending on the type of the key. This duplicate key situation will cause a runtime error (duplicate key) when sending the map data to the Tezos RPC node.
*
* For example, consider a contract with a map whose key is of type boolean. If you set the following values in MichelsonMap: map.set(false, "myValue") and map.set(null, "myValue").
*
* You will get two unique entries in the MichelsonMap. These values will both be evaluated as falsy by the MichelsonEncoder and ultimately rejected by the Tezos RPC.
*/
set(key, value) {
this.assertTypecheckKey(key);
this.assertTypecheckValue(value);
const strKey = this.serializeDeterministically(key);
this.keyMap.set(strKey, key);
this.valueMap.set(strKey, value);
}
delete(key) {
this.assertTypecheckKey(key);
this.keyMap.delete(this.serializeDeterministically(key));
this.valueMap.delete(this.serializeDeterministically(key));
}
has(key) {
this.assertTypecheckKey(key);
const strKey = this.serializeDeterministically(key);
return this.keyMap.has(strKey) && this.valueMap.has(strKey);
}
clear() {
this.keyMap.clear();
this.valueMap.clear();
}
get size() {
return this.keyMap.size;
}
forEach(cb) {
for (const [key, value] of this.entries()) {
cb(value, key, this);
}
}
}
exports.MichelsonMap = MichelsonMap;
_a = michelsonMapTypeSymbol;