@mora-light/core
Version:
Mora Light Core
1,655 lines (1,600 loc) • 47.2 kB
JavaScript
var principal = require('@dfinity/principal');
var clones = require('./clones-e2de357d.cjs.dev.js');
const NAT_MIN = '0';
const NAT8_MAX = '255';
const NAT16_MAX = '65535';
const NAT32_MAX = '4294967295';
const NAT64_MAX = '18446744073709551615';
const INT8_MAX = '127';
const INT8_MIN = '-128';
const INT16_MAX = '32767';
const INT16_MIN = '-32768';
const INT32_MAX = '2147483647';
const INT32_MIN = '-2147483648';
const INT64_MAX = '9223372036854775807';
const INT64_MIN = '-9223372036854775808';
const NAT_REGEX = '^(0|[1-9][0-9]*)$';
const INT_REGEX = '^(0|[1-9][0-9]*|-[1-9][0-9]*)$';
// ! maybe not exactly correct
const FLOAT32_REGEX = '^(0|[+-]?(0|([1-9][0-9]*))(.[0-9]{0,5}[1-9])?|[+-]?[1-9](.[0-9]{0,5}[1-9])?[eE][+-]?([1-9]|[1-2][0-9]|[3][0-8]))$';
const FLOAT64_REGEX = '^(0|[+-]?(0|([1-9][0-9]*))(.[0-9]{0,14}[1-9])?|[+-]?[1-9](.[0-9]{0,14}[1-9])?[eE][+-]?([1-9]|[1-9][0-9]|[12][0-9][0-9]|30[0-8]))$';
// parse candid type array to tuple candid type
const parseTuple = types => {
const subitems = [];
for (let i = 0; i < types.length; i++) subitems[i] = {
key: `_${i}_`,
type: types[i]
};
return {
type: 'tuple',
subitems
};
};
// check text of principal
const isPrincipal = text => {
try {
principal.Principal.fromText(text);
} catch (e) {
// console.error("principal from text", e);
return false;
}
return true;
};
// check text of canister id
const isCanisterId = text => {
if (!isPrincipal(text)) return false;
return text.length === 27;
};
// find alone type
const findAloneType = type => {
if (type.type !== 'tuple') return {
type: clones.deepClone(type),
child: 0
};
if (type.subitems.length !== 1) return {
type: clones.deepClone(type),
child: 0
};
const r = findAloneType(type.subitems[0].type);
return {
type: r.type,
child: r.child + 1
};
};
// find alone type
const findChildType = (type, child) => {
if (child === 0) return clones.deepClone(type);
if (type.type !== 'tuple') throw new Error(`can not abstract child type: not tuple candid`);
if (type.subitems.length !== 1) throw new Error(`can not abstract child type: not alone`);
return findChildType(type.subitems[0].type, child - 1);
};
// get empty tuple candid type
const getEmptyTupleCandidType = () => {
return {
type: 'tuple',
subitems: []
};
};
// compare two candid type
const isSameCandidType = (type1, type2) => isSameCandidTypeInner(type1, type2, [], []);
const isSameCandidTypeInner = (type1, type2, rec1, rec2) => {
if (!type1) return true; // ! if left type is undefined
// ! some types have different type
// * situation 1: blob and vec nat8 are same type
if (type1.subtype && type2.subtype && isSameCandidType({
type: 'nat8'
}, type1.subtype) && isSameCandidType({
type: 'nat8'
}, type2.subtype) && ['blob', 'vec'].includes(type1.type) && ['blob', 'vec'].includes(type2.type)) {
return true;
}
// * situation 2: record and variant type actually does not care order of fields
if (type1.subitems && type2.subitems && type1.subitems.length === type2.subitems.length && (type1.type === 'record' && type2.type === 'record' || type1.type === 'variant' && type2.type === 'variant')) {
// check fields
if ((() => {
const keys1 = type1.subitems.map(item => item.key);
const keys2 = type2.subitems.map(item => item.key);
for (const key of keys1) if (!keys2.includes(key)) return false;
for (const key of keys2) if (!keys1.includes(key)) return false;
for (const key of keys1) {
const index1 = type1.subitems.findIndex(subitem => subitem.key === key);
const index2 = type2.subitems.findIndex(subitem => subitem.key === key);
const subtype1 = type1.subitems[index1].type;
const subtype2 = type2.subitems[index2].type;
if (!isSameCandidType(subtype1, subtype2)) return false;
}
return true;
})()) return true;
}
if (type1.type !== type2.type) return false; // main type
// subtype
if (!clones.isSame(type1.subtype, type2.subtype, (t1, t2) => isSameCandidTypeInner(t1, t2, type1.type !== 'rec' ? rec1 : [...rec1, type1.id], type1.type !== 'rec' ? rec2 : [...rec2, type2.id]))) {
return false;
}
// subitems
if (!clones.isSame(type1.subitems, type2.subitems, (items1, items2) => {
if (items1.length !== items2.length) return false; // length
for (let i = 0; i < items1.length; i++) {
if (items1[i].key !== items2[i].key ||
// key
!isSameCandidTypeInner(items1[i].type, items2[i].type, rec1, rec2) // subitem subtype
) {
return false;
}
}
return true;
})) {
return false;
}
// id
if (type1.type !== 'rec') {
if (!clones.isSameNumber(type1.id, type2.id)) return false;
} else {
// recent id
if (!clones.isSame(type1.id, type2.id, () => true)) return false;
const index1 = rec1.lastIndexOf(type1.id);
const index2 = rec2.lastIndexOf(type2.id);
if (index1 !== index2) return false;
}
// func
if (!clones.isSame(type1.annotations, type2.annotations)) return false;
if (!clones.isSame(type1.argTypes, type2.argTypes, (t1, t2) => isSameCandidTypeInner(t1, t2, rec1, rec2))) return false;
if (!clones.isSame(type1.retTypes, type2.retTypes, (t1, t2) => isSameCandidTypeInner(t1, t2, rec1, rec2))) return false;
// service
if (!clones.isSame(type1.apis, type2.apis, (apis1, apis2) => {
if (apis1.length !== apis2.length) return false; // length
for (let i = 0; i < apis1.length; i++) {
if (apis1[i].method !== apis2[i].method ||
// method name
!isSameCandidTypeInner(apis1[i].func, apis2[i].func, rec1, rec2)) {
return false;
}
}
return true;
})) {
return false;
}
return true;
};
// candid type to string
const unwrapCandidType = type => {
const isAlphaBeta = key => /^[a-zA-Z_][a-zA-Z_0-9]*$/.test(key);
switch (type.type) {
case 'bool':
case 'nat':
case 'int':
case 'nat8':
case 'nat16':
case 'nat32':
case 'nat64':
case 'int8':
case 'int16':
case 'int32':
case 'int64':
case 'float32':
case 'float64':
case 'null':
case 'text':
case 'principal':
case 'blob':
return type.type;
case 'vec':
case 'opt':
return `${type.type} ${unwrapCandidType(type.subtype)}`;
case 'record':
case 'variant':
if (type.subitems.length === 0) return `${type.type} {}`;
return `${type.type} { ${type.subitems.map(subitem => {
let key = isAlphaBeta(subitem.key) ? `${subitem.key}` : `"${subitem.key.replaceAll('"', '\\"')}`;
return isSameCandidType(subitem.type, {
type: 'null'
}) ? key : `${key}: ${unwrapCandidType(subitem.type)}`;
}).join('; ')} }`;
case 'tuple':
if (type.subitems.length === 0) return `record {}`;
return `record { ${type.subitems.map(subitem => `${unwrapCandidType(subitem.type)}`).join('; ')} }`;
case 'rec':
if (type.subtype === undefined) return `rec_${type.id}`;
return `μrec_${type.id}.${unwrapCandidType(type.subtype)}`;
case 'unknown':
case 'empty':
case 'reserved':
return type.type;
case 'func':
return `(${type.argTypes.subitems.map(subitem => unwrapCandidType(subitem.type)).join(', ')}) -> (${type.retTypes.subitems.map(subitem => unwrapCandidType(subitem.type)).join(', ')})${type.annotations.length ? ' query' : ''}`;
case 'service':
if (type.apis.length === 0) return `service: {}`;
return `service: { ${type.apis.map(api => {
if (isAlphaBeta(api.method)) {
return `${api.method}: ${unwrapCandidType(api.func)}`;
} else {
return `"${api.method.replaceAll('"', '\\"')}": ${unwrapCandidType(api.func)}`;
}
})} }`;
}
};
// string to candid type
// ! maybe there are bugs here
const wrapCandidType = text => {
try {
// if (text === 'vec nat8') debugger;
const {
type,
remained
} = wrapCandidType0({
text,
index: -1
});
if (remained.text.length === 0) return {
ok: type
};
throw new Error(`wrong type from position: ${remained.index} | ${remained.text}`);
} catch (e) {
console.error('wrapCandidType failed', text, e);
return {
err: e.message
};
}
};
const wrapCandidType0 = data => {
const EMPTY_CHAR = [' ', '\n', '\r'];
const trimStart = data => {
let text = data.text;
let index = data.index;
while (text.length > 0 && EMPTY_CHAR.includes(text.charAt(0))) {
index += 1;
text = text.substring(1);
}
return {
text,
index
};
};
// try to read a candid type
// console.error('wrapCandidType0', data.text, data.index);
// 1. trim first blanks
data = trimStart(data);
// 2. empty is wrong
if (data.text.length === 0) {
throw new Error(`wrong type from position: ${data.index} | ${data.text}`);
}
// 3. simple type
const simpleType = type => {
if (data.text.startsWith(type.type)) {
const text = data.text.substring(type.type.length);
const index = data.index + type.type.length;
const next = text.length === 0 ? '' : text.substring(0, 1);
if (next === '' || EMPTY_CHAR.includes(next) || ['{', '(', ';', '}'].includes(next)) {
let data = {
text,
index
};
data = trimStart(data);
return {
type,
remained: data
};
}
}
return undefined;
};
let r = undefined;
if ((r = simpleType({
type: 'bool'
})) !== undefined) return r;
if ((r = simpleType({
type: 'nat'
})) !== undefined) return r;
if ((r = simpleType({
type: 'int'
})) !== undefined) return r;
if ((r = simpleType({
type: 'nat8'
})) !== undefined) return r;
if ((r = simpleType({
type: 'nat16'
})) !== undefined) return r;
if ((r = simpleType({
type: 'nat32'
})) !== undefined) return r;
if ((r = simpleType({
type: 'nat64'
})) !== undefined) return r;
if ((r = simpleType({
type: 'int8'
})) !== undefined) return r;
if ((r = simpleType({
type: 'int16'
})) !== undefined) return r;
if ((r = simpleType({
type: 'int32'
})) !== undefined) return r;
if ((r = simpleType({
type: 'int64'
})) !== undefined) return r;
if ((r = simpleType({
type: 'float32'
})) !== undefined) return r;
if ((r = simpleType({
type: 'float64'
})) !== undefined) return r;
if ((r = simpleType({
type: 'null'
})) !== undefined) return r;
if ((r = simpleType({
type: 'text'
})) !== undefined) return r;
if ((r = simpleType({
type: 'principal'
})) !== undefined) return r;
if ((r = simpleType({
type: 'blob',
subtype: {
type: 'nat8'
}
})) !== undefined) return r;
if ((r = simpleType({
type: 'unknown'
})) !== undefined) return r;
if ((r = simpleType({
type: 'empty'
})) !== undefined) return r;
if ((r = simpleType({
type: 'reserved'
})) !== undefined) return r;
// vec and opt have subtype
if (data.text.startsWith('vec') || data.text.startsWith('opt')) {
const main = data.text.substring(0, 3);
const text = data.text.substring(main.length);
const index = data.index + main.length;
const next = text.length === 0 ? '' : text.substring(0, 1);
if (next === ' ') {
// next char must be blank
let data = {
text,
index
};
data = trimStart(data);
// try to read subtype
let {
type: subtype,
remained
} = wrapCandidType0(data);
remained = trimStart(remained);
const type = {
type: main,
subtype: subtype
};
return {
type,
remained
};
}
}
const readKey = text => {
if (text.length === 0) return ['', 0];
const next = text.substring(0, 1);
if (/^[a-zA-Z_]$/.test(next)) {
let key = next;
text = text.substring(1);
while (true) {
if (text.length === 0) return ['', 0];
const next = text.substring(0, 1);
if ([' ', ':', ';', '}'].includes(next)) break;
key = key + next;
text = text.substring(1);
}
return [key, key.length];
} else if (next === '"') {
let key = '';
let count = 0;
text = text.substring(1);
while (true) {
if (text.length === 0) return ['', 0];
const next = text.substring(0, 1);
if (next === '\\') {
const next2 = text.length >= 2 ? text.substring(1, 2) : '';
const CHARS = [['b', '\b'], ['t', '\t'], ['n', '\n'], ['v', '\v'], ['f', '\f'], ['r', '\r'], ['"', '"'], ["'", "'"], ['\\', '\\']];
const item = CHARS.find(cs => cs[0] === next2);
if (item !== undefined) {
key = key + item[1];
text = text.substring(2);
count += 2;
continue;
}
return ['', 0];
}
if (next === '"') {
text = text.substring(1);
return [key, count + 2];
}
key = key + next;
count += 1;
text = text.substring(1);
}
}
return ['', 0];
};
// variant
if (data.text.startsWith('variant')) {
const main = data.text.substring(0, 7);
const text = data.text.substring(main.length);
const index = data.index + main.length;
if (text.length) {
// must has something
let data = {
text,
index
};
data = trimStart(data);
// next char must be {
if (data.text.startsWith('{')) {
data = {
text: data.text.substring(1),
index: data.index + 1
};
// try to read subitem
const subitems = [];
while (true) {
data = trimStart(data);
if (data.text.length === 0) break;
if (data.text.startsWith('}')) break;
const [key, length] = readKey(data.text);
if (key.length === 0) break;
let temp = {
text: data.text.substring(length),
index: data.index + length
};
temp = trimStart(temp);
if (temp.text.startsWith('}')) {
subitems.push({
key,
type: {
type: 'null'
}
});
data = temp;
break;
}
if (temp.text.startsWith(';')) {
subitems.push({
key,
type: {
type: 'null'
}
});
} else {
if (!temp.text.startsWith(':')) break;
temp = {
text: temp.text.substring(1),
index: temp.index + 1
};
temp = trimStart(temp);
let {
type,
remained
} = wrapCandidType0(temp);
subitems.push({
key,
type
});
temp = remained;
}
let remained = trimStart(temp);
if (remained.text.startsWith(';')) remained = {
text: remained.text.substring(1),
index: remained.index + 1
};
data = remained;
}
if (data.text.startsWith('}')) {
const type = {
type: main,
subitems
};
data = {
text: data.text.substring(1),
index: data.index + 1
};
data = trimStart(data);
return {
type,
remained: data
};
}
}
}
}
// record
if (data.text.startsWith('record')) {
const main = data.text.substring(0, 6);
const text = data.text.substring(main.length);
const index = data.index + main.length;
if (text.length) {
// must has something
let data = {
text,
index
};
data = trimStart(data);
// next char must be {
if (data.text.startsWith('{')) {
data = {
text: data.text.substring(1),
index: data.index + 1
};
// try to read subitem
const subitems = [];
while (true) {
data = trimStart(data);
if (data.text.length === 0) break;
if (data.text.startsWith('}')) break;
const [key, length] = readKey(data.text);
if (key.length === 0) break;
let temp = {
text: data.text.substring(length),
index: data.index + length
};
temp = trimStart(temp);
if (!temp.text.startsWith(':')) break;
temp = {
text: temp.text.substring(1),
index: temp.index + 1
};
temp = trimStart(temp);
let {
type,
remained
} = wrapCandidType0(temp);
subitems.push({
key,
type
});
remained = trimStart(remained);
if (remained.text.startsWith(';')) remained = {
text: remained.text.substring(1),
index: remained.index + 1
};
data = remained;
}
if (data.text.startsWith('}')) {
const type = {
type: main,
subitems
};
data = {
text: data.text.substring(1),
index: data.index + 1
};
data = trimStart(data);
return {
type,
remained: data
};
}
}
}
}
// tuple
if (data.text.startsWith('record') || data.text.startsWith('tuple')) {
const main = data.text.substring(0, data.text.startsWith('record') ? 6 : 5);
const text = data.text.substring(main.length);
const index = data.index + main.length;
if (text.length) {
// must has something
let data = {
text,
index
};
data = trimStart(data);
// next char must be {
if (data.text.startsWith('{')) {
data = {
text: data.text.substring(1),
index: data.index + 1
};
// try to read subitem
const subitems = [];
while (true) {
data = trimStart(data);
if (data.text.length === 0) break;
if (data.text.startsWith('}')) break;
let {
type,
remained
} = wrapCandidType0(data);
subitems.push({
key: `_${subitems.length}_`,
type
});
remained = trimStart(remained);
if (remained.text.startsWith(';')) remained = {
text: remained.text.substring(1),
index: remained.index + 1
};
data = remained;
}
if (data.text.startsWith('}')) {
const type = {
type: 'tuple',
subitems
};
data = {
text: data.text.substring(1),
index: data.index + 1
};
data = trimStart(data);
return {
type,
remained: data
};
}
}
}
}
// rec
if (/^μrec_\d+\./.test(data.text)) {
// rec start
let text = data.text.substring(5);
let index = data.index + 5;
const id = parseInt(text.substring(0, text.indexOf('.')));
index = index + text.indexOf('.') + 1;
text = text.substring(text.indexOf('.') + 1);
let {
type,
remained
} = wrapCandidType0({
text,
index
});
remained = trimStart(remained);
return {
type: {
type: 'rec',
subtype: type,
id
},
remained
};
}
if (/^rec_\d+/.test(data.text)) {
// rec end
let text = data.text.substring(4);
let index = data.index + 4;
let m = text.match(/^\d+/);
if (m && m[0].length) {
const id = parseInt(m[0]);
text = text.substring(m[0].length);
index = index + m[0].length;
let remained = {
text,
index
};
remained = trimStart(remained);
const type = {
type: 'rec',
id
};
return {
type,
remained
};
}
}
// func
const findFunc = data => {
if (data.text.startsWith('(')) {
const readTuple = (text, index) => {
if (!text.startsWith('(')) return undefined;
text = text.substring(1);
index = index + 1;
let data = {
text,
index
};
const subitems = [];
while (true) {
data = trimStart(data);
if (data.text.length === 0) break;
if (data.text.startsWith(')')) break;
let {
type,
remained
} = wrapCandidType0(data);
subitems.push({
key: `_${subitems.length}_`,
type
});
remained = trimStart(remained);
if (remained.text.startsWith(',')) remained = {
text: remained.text.substring(1),
index: remained.index + 1
};
data = remained;
}
if (data.text.startsWith(')')) {
const type = {
type: 'tuple',
subitems
};
data = {
text: data.text.substring(1),
index: data.index + 1
};
data = trimStart(data);
return [type, data];
}
};
const arg = readTuple(data.text, data.index);
if (arg !== undefined) {
const argTypes = arg[0];
let data = arg[1];
// must be ->
if (data.text.startsWith('->')) {
data = {
text: data.text.substring(2),
index: data.index + 2
};
data = trimStart(data);
const ret = readTuple(data.text, data.index);
if (ret !== undefined) {
const retTypes = ret[0];
let data = ret[1];
data = trimStart(data);
let annotations = [];
if (data.text.startsWith('query')) {
annotations = ['query'];
data = {
text: data.text.substring(5),
index: data.index + 5
};
data = trimStart(data);
}
const type = {
type: 'func',
annotations,
argTypes,
retTypes
};
return {
type,
remained: data
};
}
}
}
}
return undefined;
};
const func = findFunc({
...data
});
if (func !== undefined) return func;
// service
if (data.text.startsWith('service')) {
const text = data.text.substring(7);
const index = data.index + 7;
if (text.length) {
let data = {
text,
index
};
data = trimStart(data);
if (data.text.startsWith(':')) {
const text = data.text.substring(1);
const index = data.index + 1;
data = {
text,
index
};
data = trimStart(data);
if (data.text.startsWith('{')) {
data = {
text: data.text.substring(1),
index: data.index + 1
};
const apis = [];
const findKey = text => {
if (text.length === 0) return ['', 0];
const next = text.substring(0, 1);
if (/^[a-zA-Z_]$/.test(next)) {
let key = next;
text = text.substring(0, 1);
while (true) {
if (text.length === 0) return ['', 0];
const next = text.substring(0, 1);
if ([' ', ':'].includes(next)) break;
key = key + next;
text = text.substring(1);
}
return [key, key.length];
}
return ['', 0];
};
while (true) {
data = trimStart(data);
if (data.text.length === 0) break;
if (data.text.startsWith('}')) break;
const [key, length] = findKey(data.text);
if (key.length === 0) break;
let temp = {
text: data.text.substring(length),
index: data.index + length
};
temp = trimStart(temp);
if (!temp.text.startsWith(':')) break;
temp = {
text: temp.text.substring(1),
index: temp.index + 1
};
temp = trimStart(temp);
const func = findFunc(temp);
if (func === undefined) break;
apis.push({
method: key,
func: func.type
});
let remained = trimStart(func.remained);
if (remained.text.startsWith(';')) remained = {
text: remained.text.substring(1),
index: remained.index + 1
};
data = remained;
}
if (data.text.startsWith('}')) {
const type = {
type: 'service',
apis
};
data = {
text: data.text.substring(1),
index: data.index + 1
};
data = trimStart(data);
return {
type,
remained: data
};
}
}
}
}
}
// can not read type
throw new Error(`wrong type from position: ${data.index} | ${data.text}`);
};
// stringify candid type
const stringifyCandidType = type => {
const clean = t => {
switch (t.type) {
case 'bool':
case 'nat':
case 'int':
case 'nat8':
case 'nat16':
case 'nat32':
case 'nat64':
case 'int8':
case 'int16':
case 'int32':
case 'int64':
case 'float32':
case 'float64':
case 'null':
case 'text':
case 'principal':
return {
type: t.type
};
case 'blob':
return {
type: t.type,
subtype: {
type: 'nat8'
}
};
case 'vec':
return {
type: t.type,
subtype: clean(t.subtype)
};
case 'opt':
return {
type: t.type,
subtype: clean(t.subtype)
};
case 'record':
return {
type: t.type,
subitems: t.subitems.map(subitem => {
return {
key: subitem.key,
type: clean(subitem.type)
};
})
};
case 'variant':
return {
type: t.type,
subitems: t.subitems.map(subitem => {
return {
key: subitem.key,
type: clean(subitem.type)
};
})
};
case 'tuple':
return {
type: t.type,
subitems: t.subitems.map(subitem => {
return {
key: subitem.key,
type: clean(subitem.type)
};
})
};
case 'rec':
return t.subtype !== undefined ? {
type: t.type,
subtype: clean(t.subtype),
id: t.id
} : {
type: t.type,
subtype: undefined,
id: t.id
};
case 'unknown':
case 'empty':
case 'reserved':
return {
type: t.type
};
case 'func':
return {
type: t.type,
annotations: [...t.annotations],
argTypes: clean(t.argTypes),
retTypes: clean(t.retTypes)
};
case 'service':
return {
type: t.type,
apis: t.apis.map(api => {
return {
method: api.method,
func: clean(api.func)
};
})
};
}
};
const t = clean(clones.deepClone(type));
return JSON.stringify(t);
};
// initial value for candid type
const getInitialCandidTypeValue = (type, recItems, requested) => {
const subitems = type.subitems ?? [];
switch (type.type) {
case 'bool':
return false;
case 'nat':
case 'int':
return {
type: 'bigint',
value: '0'
};
case 'nat8':
case 'nat16':
case 'nat32':
return 0;
case 'nat64':
return {
type: 'bigint',
value: '0'
};
case 'int8':
case 'int16':
case 'int32':
return 0;
case 'int64':
return {
type: 'bigint',
value: '0'
};
case 'float32':
case 'float64':
return 0.0;
case 'null':
return null;
case 'text':
return '';
case 'principal':
return {
type: 'principal',
value: ''
};
// ! it is not a correct value
case 'blob':
return [];
case 'vec':
return [];
case 'opt':
return [];
// none
case 'record':
const record = {};
subitems.forEach(subitem => record[subitem.key] = getInitialCandidTypeValue(subitem.type, recItems, requested)); // every key has value
return record;
case 'variant':
const variant = {};
for (let i = 0; i < subitems.length; i++) {
variant[subitems[i].key] = getInitialCandidTypeValue(subitems[i].type, recItems, requested);
break; // one is enough
}
return variant;
case 'tuple':
return subitems.map(subitem => getInitialCandidTypeValue(subitem.type, recItems, requested));
// every key has value
case 'rec':
const request = stringifyCandidType(type);
if (requested.includes(request)) {
console.error('recursive object can not be instanced', requested, request, type);
throw new Error(`recursive object can not be instanced: ${stringifyCandidType(type)}`);
}
requested = [...requested, request];
const {
subtype: recSubtype,
recItems: recItems2
} = findRecSubtype(type, recItems);
return getInitialCandidTypeValue(recSubtype, recItems2, requested);
case 'unknown':
throw new Error(`can not instanced for type 'unknown'`);
case 'empty':
throw new Error(`can not instanced for type 'empty'`);
case 'reserved':
return null;
case 'func':
return {
type: 'func',
value: {
service: '',
// ! it is not a correct value
method: '' // ! it is not a correct value
}
};
case 'service':
return {
type: 'service',
value: '' // ! it is not a correct value
};
}
};
const findRecType = (recItems, id) => {
for (let i = recItems.length - 1; 0 <= i; i--) {
if (recItems[i].id === id) return recItems[i].type;
}
return undefined;
};
const findRecSubtype = (type, recItems) => {
const found = findRecType(recItems, type.id);
let recSubType;
if (found !== undefined) {
if (type.subtype !== undefined) {
recSubType = type.subtype;
recItems = [...recItems, {
id: type.id,
type
}];
} else {
recSubType = found.subtype;
}
} else {
if (type.subtype !== undefined) {
recSubType = type.subtype;
recItems = [...recItems, {
id: type.id,
type
}];
} else {
throw new Error(`wrong recursive type: ${stringifyCandidType(type)}`);
}
}
return {
subtype: recSubType,
recItems
};
};
const findChildTypeAndValue = (value, from, child) => {
// 1. check type and values
if (!checkCandidValue(from, value, [])) {
throw new Error(`value: ${JSON.stringify(value)} is not match type: ${stringifyCandidType(from)}`);
}
if (child === undefined || child === 0) return {
type: from,
value
}; // does not need fetch child
// 2. check child level
if (findAloneType(from).child !== child) {
throw new Error(`type: ${stringifyCandidType(from)} and child: ${child} is not matched`);
}
// 3. fetch child
while (child > 0) {
from = from.subitems[0].type;
value = value[0];
child--;
}
// 4. return value
return {
type: from,
value
};
};
// ================== check value and type ==================
const isArray = value => {
if (value === undefined) return false;
if (value === null) return false;
const flag = Object.prototype.toString.call(value) === '[object Array]';
if (flag) return flag;
if (typeof value !== 'object') return false;
// some array like {length: 1, 0: 1}, obviously it is object
let length = value['length'];
if (!['number', 'undefined'].includes(typeof length)) return false;
const keys = Object.keys(value).filter(key => key !== 'length').map(key => parseInt(key));
if (typeof length === 'number' && length !== keys.length) return false;
length = keys.length;
for (let i = 0; i < length; i++) {
const index = keys.indexOf(i);
if (index >= 0) keys.splice(index, 1);
}
if (keys.length) return false;
value['length'] = length; // add length filed
return true;
};
const isBool = value => typeof value === 'boolean';
const isNat = value => typeof value === 'object' && value['type'] === 'bigint' && value['value'].match(NAT_REGEX);
const isInt = value => typeof value === 'object' && value['type'] === 'bigint' && value['value'].match(INT_REGEX);
const isNat8 = value => typeof value === 'number' && Number(NAT_MIN) <= value && value <= Number(NAT8_MAX);
const isNat16 = value => typeof value === 'number' && Number(NAT_MIN) <= value && value <= Number(NAT16_MAX);
const isNat32 = value => typeof value === 'number' && Number(NAT_MIN) <= value && value <= Number(NAT32_MAX);
const isNat64 = value => {
if (!isNat(value)) return false;
const v = BigInt(value['value']);
return BigInt(`${NAT_MIN}`) <= v && v <= BigInt(NAT64_MAX);
};
const isInt8 = value => typeof value === 'number' && Number(INT8_MIN) <= value && value <= Number(INT8_MAX);
const isInt16 = value => typeof value === 'number' && Number(INT16_MIN) <= value && value <= Number(INT16_MAX);
const isInt32 = value => typeof value === 'number' && Number(INT32_MIN) <= value && value <= Number(INT32_MAX);
const isInt64 = value => {
if (!isInt(value)) return false;
const v = BigInt(value['value']);
return BigInt(INT64_MIN) <= v && v <= BigInt(INT64_MAX);
};
const isFloat32 = value =>
// typeof value === "number" && !!`${value}`.match(FLOAT32_REGEX);
typeof value === 'number'; // ? decimal ???
const isFloat64 = value =>
// typeof value === "number" && !!`${value}`.match(FLOAT64_REGEX);
typeof value === 'number'; // ? decimal ???
const isNull = value => value === null;
const isText = value => typeof value === 'string';
const isCandidPrincipal = value => typeof value === 'object' && value['type'] === 'principal' && isPrincipal(value['value']);
const isBlob = value => {
if (!isArray(value)) return false;
const length = value['length'];
if (typeof length !== 'number') return false;
for (let i = 0; i < length; i++) if (!isNat8(value[i])) return false;
return true;
};
const isVec = (value, subtype, recItems) => {
if (!isArray(value)) return false;
const length = value['length'];
if (typeof length !== 'number') return false;
for (let i = 0; i < length; i++) if (!checkCandidValue(subtype, value[i], recItems)) return false;
return true;
};
const isOpt = (value, subtype, recItems) => {
if (!isArray(value)) return false;
const length = value['length'];
if (typeof length !== 'number') return false;
switch (length) {
case 0:
return true;
case 1:
return checkCandidValue(subtype, value[0], recItems);
}
return false;
};
const isRecord = (value, subitems, recItems) => {
if (typeof value !== 'object') return false;
const valueKeys = Object.keys(value);
if (valueKeys.length !== subitems.length) return false;
for (let i = 0; i < subitems.length; i++) {
const subitem = subitems[i];
if (!checkCandidValue(subitem.type, value[subitem.key], recItems)) return false;
valueKeys.splice(valueKeys.findIndex(key => key === subitem.key), 1);
}
return valueKeys.length === 0;
};
const isVariant = (value, subitems, recItems) => {
if (typeof value !== 'object') return false;
const valueKeys = Object.keys(value);
if (valueKeys.length > 1) return false;
if (subitems.length > 0) {
if (valueKeys.length > 0) {
const key = valueKeys[0]; // the only key
const findSubitems = subitems.filter(subitem => subitem.key === key); // find the chosen key
if (findSubitems.length !== 1) return false;
const subitem = findSubitems[0];
return checkCandidValue(subitem.type, value[key], recItems);
} else {
return false;
}
} else {
if (valueKeys.length > 0) {
return false;
} else {
return true;
}
}
};
const isTuple = (value, subitems, recItems) => {
if (!isArray(value)) return false;
const length = value['length'];
if (typeof length !== 'number') return false;
if (length !== subitems.length) return false;
for (let i = 0; i < length; i++) {
if (!checkCandidValue(subitems[i].type, value[i], recItems)) return false;
}
return true;
};
const isCandidFunc = value => typeof value === 'object' && value['type'] === 'func' && typeof value['value'] === 'object' && isCanisterId(value['value']['service']) && value['value']['method']; // ? method ???
const isCandidService = value => typeof value === 'object' && value['type'] === 'service' && isCanisterId(value['value']);
const checkCandidValue = (type, value, recItems) => {
if (value === undefined) return false;
// console.error("checkCandidValue", type, value);
switch (type.type) {
case 'bool':
return isBool(value);
case 'nat':
return isNat(value);
case 'int':
return isInt(value);
case 'nat8':
return isNat8(value);
case 'nat16':
return isNat16(value);
case 'nat32':
return isNat32(value);
case 'nat64':
return isNat64(value);
case 'int8':
return isInt8(value);
case 'int16':
return isInt16(value);
case 'int32':
return isInt32(value);
case 'int64':
return isInt64(value);
case 'float32':
return isFloat32(value);
case 'float64':
return isFloat64(value);
case 'null':
return isNull(value);
case 'text':
return isText(value);
case 'principal':
return isCandidPrincipal(value);
case 'blob':
return isBlob(value);
case 'vec':
return isVec(value, type.subtype, recItems);
case 'opt':
return isOpt(value, type.subtype, recItems);
case 'record':
return isRecord(value, type.subitems, recItems);
case 'variant':
return isVariant(value, type.subitems, recItems);
case 'tuple':
return isTuple(value, type.subitems, recItems);
case 'rec':
const {
subtype: recSubtype,
recItems: recItems2
} = findRecSubtype(type, recItems);
return checkCandidValue(recSubtype, value, recItems2);
case 'unknown':
return false;
case 'empty':
return false;
case 'reserved':
return true;
// any type
case 'func':
return isCandidFunc(value);
case 'service':
return isCandidService(value);
}
};
const checkCandidTypeAndCandidValue = (type, value, recItems, el) => {
if (!checkCandidValue(type, value, recItems)) {
return {
err: {
message: `value: ${JSON.stringify(value)} is not match type: ${stringifyCandidType(type)}`,
el
}
};
}
return {
ok: value
};
};
// ================== wrap value ==================
// unwrap candid value
// * CandidBigInt -> BigInt
// * CandidPrincipal -> Principal
const unwrapCandidValue = (type, value, recItems) => {
if (value === undefined) throw new Error('candid value can not be undefined');
if (!checkCandidValue(type, value, [])) {
throw new Error(`value: ${JSON.stringify(value)} is not match type: ${stringifyCandidType(type)}`);
}
let v = value;
switch (type.type) {
case 'bool':
break;
case 'nat':
case 'int':
v = BigInt(v['value']);
break;
case 'nat8':
case 'nat16':
case 'nat32':
break;
case 'nat64':
v = BigInt(v['value']);
break;
case 'int8':
case 'int16':
case 'int32':
break;
case 'int64':
v = BigInt(v['value']);
break;
case 'float32':
case 'float64':
break;
case 'null':
break;
case 'text':
break;
case 'principal':
v = principal.Principal.fromText(v['value']); // ! real type
break;
case 'blob':
break;
case 'vec':
for (let i = 0; i < v['length']; i++) v[i] = unwrapCandidValue(type.subtype, v[i], recItems);
break;
case 'opt':
for (let i = 0; i < v['length']; i++) v[i] = unwrapCandidValue(type.subtype, v[i], recItems);
break;
case 'record':
for (let i = 0; i < type.subitems.length; i++) {
const item = type.subitems[i];
v[item.key] = unwrapCandidValue(item.type, v[item.key], recItems);
}
break;
case 'variant':
const variantValueKeys = Object.keys(v);
if (variantValueKeys.length) {
const key = variantValueKeys[0];
for (let i = 0; i < type.subitems.length; i++) {
if (key === type.subitems[i].key) {
v[key] = unwrapCandidValue(type.subitems[i].type, v[key], recItems);
break;
}
}
}
break;
case 'tuple':
for (let i = 0; i < type.subitems.length; i++) v[i] = unwrapCandidValue(type.subitems[i].type, v[i], recItems);
break;
case 'rec':
const {
subtype: recSubtype,
recItems: recItems2
} = findRecSubtype(type, recItems);
return unwrapCandidValue(recSubtype, value, recItems2);
case 'unknown':
case 'empty':
case 'reserved':
break;
case 'func':
// actually, it is [ Principal, string ]
v = [principal.Principal.fromText(v['value']['service']), v['value']['method']];
break;
case 'service':
// actually, it is Principal
v = principal.Principal.fromText(v['value']); // ! real type
break;
}
return v;
};
// wrap value
// * BigInt -> CandidBigInt
// * Principal -> CandidPrincipal
const wrapCandidValue = (type, value, recItems) => {
if (value === undefined) throw new Error('candid value can not be undefined');
let v = value;
switch (type.type) {
case 'bool':
break;
case 'nat':
case 'int':
v = {
type: 'bigint',
value: `${v}`
};
break;
case 'nat8':
case 'nat16':
case 'nat32':
break;
case 'nat64':
v = {
type: 'bigint',
value: `${v}`
};
break;
case 'int8':
case 'int16':
case 'int32':
break;
case 'int64':
v = {
type: 'bigint',
value: `${v}`
};
break;
case 'float32':
case 'float64':
break;
case 'null':
break;
case 'text':
break;
case 'principal':
v = {
type: 'principal',
value: value.toText()
}; // ! to text
break;
case 'blob':
break;
case 'vec':
for (let i = 0; i < v['length']; i++) v[i] = wrapCandidValue(type.subtype, v[i], recItems);
break;
case 'opt':
for (let i = 0; i < v['length']; i++) v[i] = wrapCandidValue(type.subtype, v[i], recItems);
break;
case 'record':
for (let i = 0; i < type.subitems.length; i++) {
const item = type.subitems[i];
v[item.key] = wrapCandidValue(item.type, v[item.key], recItems);
}
break;
case 'variant':
const variantValueKeys = Object.keys(v);
if (variantValueKeys.length) {
const key = variantValueKeys[0];
for (let i = 0; i < type.subitems.length; i++) {
if (key === type.subitems[i].key) {
v[key] = wrapCandidValue(type.subitems[i].type, v[key], recItems);
break;
}
}
}
break;
case 'tuple':
for (let i = 0; i < type.subitems.length; i++) v[i] = wrapCandidValue(type.subitems[i].type, v[i], recItems);
break;
case 'rec':
const {
subtype: recSubtype,
recItems: recItems2
} = findRecSubtype(type, recItems);
return wrapCandidValue(recSubtype, value, recItems2);
case 'unknown':
case 'empty':
case 'reserved':
break;
case 'func':
// actually, it is [ Principal, string ]
v = {
type: 'func',
value: {
service: principal.Principal.fromUint8Array(parseUint8Array(value[0]['_arr'])).toText(),
method: value[1]
}
};
break;
case 'service':
// actually, it is Principal
v = {
type: 'service',
value: principal.Principal.fromUint8Array(parseUint8Array(value['_arr'])).toText() // ! to text
};
break;
}
return v;
};
// for principal
const parseUint8Array = arr => {
if (!isArray(arr)) throw new Error('arr is not array');
const value = new Uint8Array(arr.length);
for (let i = 0; i < arr.length; i++) {
value[i] = arr[i];
}
return value;
};
exports.FLOAT32_REGEX = FLOAT32_REGEX;
exports.FLOAT64_REGEX = FLOAT64_REGEX;
exports.INT16_MAX = INT16_MAX;
exports.INT16_MIN = INT16_MIN;
exports.INT32_MAX = INT32_MAX;
exports.INT32_MIN = INT32_MIN;
exports.INT64_MAX = INT64_MAX;
exports.INT64_MIN = INT64_MIN;
exports.INT8_MAX = INT8_MAX;
exports.INT8_MIN = INT8_MIN;
exports.INT_REGEX = INT_REGEX;
exports.NAT16_MAX = NAT16_MAX;
exports.NAT32_MAX = NAT32_MAX;
exports.NAT64_MAX = NAT64_MAX;
exports.NAT8_MAX = NAT8_MAX;
exports.NAT_MIN = NAT_MIN;
exports.NAT_REGEX = NAT_REGEX;
exports.checkCandidTypeAndCandidValue = checkCandidTypeAndCandidValue;
exports.checkCandidValue = checkCandidValue;
exports.findAloneType = findAloneType;
exports.findChildType = findChildType;
exports.findChildTypeAndValue = findChildTypeAndValue;
exports.findRecSubtype = findRecSubtype;
exports.findRecType = findRecType;
exports.getEmptyTupleCandidType = getEmptyTupleCandidType;
exports.getInitialCandidTypeValue = getInitialCandidTypeValue;
exports.isArray = isArray;
exports.isBlob = isBlob;
exports.isBool = isBool;
exports.isCandidFunc = isCandidFunc;
exports.isCandidPrincipal = isCandidPrincipal;
exports.isCandidService = isCandidService;
exports.isCanisterId = isCanisterId;
exports.isFloat32 = isFloat32;
exports.isFloat64 = isFloat64;
exports.isInt = isInt;
exports.isInt16 = isInt16;
exports.isInt32 = isInt32;
exports.isInt64 = isInt64;
exports.isInt8 = isInt8;
exports.isNat = isNat;
exports.isNat16 = isNat16;
exports.isNat32 = isNat32;
exports.isNat64 = isNat64;
exports.isNat8 = isNat8;
exports.isNull = isNull;
exports.isOpt = isOpt;
exports.isPrincipal = isPrincipal;
exports.isRecord = isRecord;
exports.isSameCandidType = isSameCandidType;
exports.isText = isText;
exports.isTuple = isTuple;
exports.isVariant = isVariant;
exports.isVec = isVec;
exports.parseTuple = parseTuple;
exports.stringifyCandidType = stringifyCandidType;
exports.unwrapCandidType = unwrapCandidType;
exports.unwrapCandidValue = unwrapCandidValue;
exports.wrapCandidType = wrapCandidType;
exports.wrapCandidValue = wrapCandidValue;
;