UNPKG

@mora-light/core

Version:
1,594 lines (1,540 loc) 46.1 kB
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 };