@mora-light/core
Version:
Mora Light Core
1,594 lines (1,540 loc) • 46.1 kB
JavaScript
import { Principal } from '@dfinity/principal';
import { d as deepClone, i as isSame, a as isSameNumber } from './clones-e92190f4.esm.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.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: deepClone(type),
child: 0
};
if (type.subitems.length !== 1) return {
type: 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 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 (!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 (!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 (!isSameNumber(type1.id, type2.id)) return false;
} else {
// recent id
if (!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 (!isSame(type1.annotations, type2.annotations)) return false;
if (!isSame(type1.argTypes, type2.argTypes, (t1, t2) => isSameCandidTypeInner(t1, t2, rec1, rec2))) return false;
if (!isSame(type1.retTypes, type2.retTypes, (t1, t2) => isSameCandidTypeInner(t1, t2, rec1, rec2))) return false;
// service
if (!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(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.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.fromText(v['value']['service']), v['value']['method']];
break;
case 'service':
// actually, it is Principal
v = 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.fromUint8Array(parseUint8Array(value[0]['_arr'])).toText(),
method: value[1]
}
};
break;
case 'service':
// actually, it is Principal
v = {
type: 'service',
value: 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;
};
export { isOpt as $, findRecType as A, findRecSubtype as B, isPrincipal as C, isCanisterId as D, getEmptyTupleCandidType as E, FLOAT32_REGEX as F, isArray as G, isBool as H, INT8_MAX as I, isNat as J, isInt as K, isNat8 as L, isNat16 as M, NAT_MIN as N, isNat32 as O, isNat64 as P, isInt8 as Q, isInt16 as R, isInt32 as S, isInt64 as T, isFloat32 as U, isFloat64 as V, isNull as W, isText as X, isCandidPrincipal as Y, isBlob as Z, isVec as _, findChildType as a, isRecord as a0, isVariant as a1, isTuple as a2, isCandidFunc as a3, isCandidService as a4, checkCandidTypeAndCandidValue as a5, findChildTypeAndValue as b, checkCandidValue as c, unwrapCandidType as d, NAT8_MAX as e, findAloneType as f, NAT16_MAX as g, NAT32_MAX as h, isSameCandidType as i, NAT64_MAX as j, INT8_MIN as k, INT16_MAX as l, INT16_MIN as m, INT32_MAX as n, INT32_MIN as o, parseTuple as p, INT64_MAX as q, INT64_MIN as r, NAT_REGEX as s, INT_REGEX as t, unwrapCandidValue as u, FLOAT64_REGEX as v, wrapCandidValue as w, wrapCandidType as x, stringifyCandidType as y, getInitialCandidTypeValue as z };