diff --git a/examples/audio_recorder/src/index.ts b/examples/audio_recorder/src/index.ts index 61b40e8e33..cc9747015e 100644 --- a/examples/audio_recorder/src/index.ts +++ b/examples/audio_recorder/src/index.ts @@ -63,13 +63,13 @@ export default Canister({ deleteUser: update([Principal], Result(User, AudioRecorderError), (id) => { const userOpt = users.get(id); - if (userOpt.length === 0) { + if ('None' in userOpt) { return Err({ UserDoesNotExist: id }); } - const user = userOpt[0]; + const user = userOpt.Some; user.recordingIds.forEach((recordingId) => { recordings.remove(recordingId); @@ -85,13 +85,13 @@ export default Canister({ (audio, name, userId) => { const userOpt = users.get(userId); - if (userOpt.length === 0) { + if ('None' in userOpt) { return Err({ UserDoesNotExist: userId }); } - const user = userOpt[0]; + const user = userOpt.Some; const id = generateId(); const recording: typeof Recording = { @@ -126,21 +126,21 @@ export default Canister({ (id) => { const recordingOpt = recordings.get(id); - if (recordingOpt.length === 0) { + if ('None' in recordingOpt) { return Err({ RecordingDoesNotExist: id }); } - const recording = recordingOpt[0]; + const recording = recordingOpt.Some; const userOpt = users.get(recording.userId); - if (userOpt.length === 0) { + if ('None' in userOpt) { return Err({ UserDoesNotExist: recording.userId }); } - const user = userOpt[0]; + const user = userOpt.Some; const updatedUser: typeof User = { ...user, diff --git a/examples/complex_init/src/complex_init/index.ts b/examples/complex_init/src/complex_init/index.ts index f1dc9f6126..73fd41e593 100644 --- a/examples/complex_init/src/complex_init/index.ts +++ b/examples/complex_init/src/complex_init/index.ts @@ -1,19 +1,29 @@ -import { Canister, init, Opt, query, Record, text, Tuple } from 'azle'; +import { + Canister, + init, + None, + Opt, + query, + Record, + Some, + text, + Tuple +} from 'azle'; const User = Record({ id: text }); let greeting: text = 'Hello User'; -let user: Opt = []; +let user: Opt = None; export default Canister({ init: init([Tuple(text, User)], (tuple) => { greeting = tuple[0]; - user = [tuple[1]]; + user = Some(tuple[1]); return undefined; }), greetUser: query([], text, () => { - return `${greeting} ${user[0]?.id ?? '??'}`; + return `${greeting} ${user.Some?.id ?? '??'}`; }) }); diff --git a/examples/ethereum_json_rpc/src/index.ts b/examples/ethereum_json_rpc/src/index.ts index be3f8801e8..6fb7f39983 100644 --- a/examples/ethereum_json_rpc/src/index.ts +++ b/examples/ethereum_json_rpc/src/index.ts @@ -3,7 +3,9 @@ import { ic, init, nat32, + Principal, query, + Some, StableBTreeMap, text, update @@ -23,22 +25,22 @@ export default Canister({ ethGetBalance: update([text], text, async (ethereumAddress) => { const urlOpt = stableStorage.get('ethereumUrl'); - if (urlOpt.length === 0) { + if ('None' in urlOpt) { throw new Error('ethereumUrl is not defined'); } - const url = urlOpt[0]; + const url = urlOpt.Some; const httpResponse = await ic.call(managementCanister.http_request, { args: [ { url, - max_response_bytes: [2_000n], + max_response_bytes: Some(2_000n), method: { post: null }, headers: [], - body: [ + body: Some( Buffer.from( JSON.stringify({ jsonrpc: '2.0', @@ -48,13 +50,14 @@ export default Canister({ }), 'utf-8' ) - ], - transform: [ - { - function: [ic.id(), 'ethTransform'], - context: Uint8Array.from([]) - } - ] + ), + transform: Some({ + function: [ic.id(), 'ethTransform'] as [ + Principal, + string + ], + context: Uint8Array.from([]) + }) } ], cycles: 50_000_000n @@ -65,22 +68,22 @@ export default Canister({ ethGetBlockByNumber: update([nat32], text, async (number) => { const urlOpt = stableStorage.get('ethereumUrl'); - if (urlOpt.length === 0) { + if ('None' in urlOpt) { throw new Error('ethereumUrl is not defined'); } - const url = urlOpt[0]; + const url = urlOpt.Some; const httpResponse = await ic.call(managementCanister.http_request, { args: [ { url, - max_response_bytes: [2_000n], + max_response_bytes: Some(2_000n), method: { post: null }, headers: [], - body: [ + body: Some( Buffer.from( JSON.stringify({ jsonrpc: '2.0', @@ -90,13 +93,14 @@ export default Canister({ }), 'utf-8' ) - ], - transform: [ - { - function: [ic.id(), 'ethTransform'], - context: Uint8Array.from([]) - } - ] + ), + transform: Some({ + function: [ic.id(), 'ethTransform'] as [ + Principal, + string + ], + context: Uint8Array.from([]) + }) } ], cycles: 50_000_000n diff --git a/examples/func_types/canisters/func_types/index.did b/examples/func_types/canisters/func_types/index.did index 289a4bd06a..c3796cd593 100644 --- a/examples/func_types/canisters/func_types/index.did +++ b/examples/func_types/canisters/func_types/index.did @@ -1,15 +1,14 @@ -type rec_26 = func (record {id:text; complexFunc:rec_26; basicFunc:func (text) -> (text) query}, variant {Bad; ComplexFunc:rec_26; Good; BasicFunc:func (text) -> (text) query}) -> (nat64) ; -type rec_29 = func (record {id:text; complexFunc:rec_29; basicFunc:func (text) -> (text) query}, variant {Bad; ComplexFunc:rec_29; Good; BasicFunc:func (text) -> (text) query}) -> (nat64) ; -type rec_34 = func (record {id:text; complexFunc:rec_34; basicFunc:func (text) -> (text) query}, variant {Bad; ComplexFunc:rec_34; Good; BasicFunc:func (text) -> (text) query}) -> (nat64) ; +type rec_26 = func (record {id:text; complexFunc:rec_26; basicFunc:func (text) -> (text) query}, variant {Bad; ComplexFunc:rec_26; Good; BasicFunc:func (text) -> (text) query}) -> (nat64); +type rec_29 = func (record {id:text; complexFunc:rec_29; basicFunc:func (text) -> (text) query}, variant {Bad; ComplexFunc:rec_29; Good; BasicFunc:func (text) -> (text) query}) -> (nat64); +type rec_34 = func (record {id:text; complexFunc:rec_34; basicFunc:func (text) -> (text) query}, variant {Bad; ComplexFunc:rec_34; Good; BasicFunc:func (text) -> (text) query}) -> (nat64); service: () -> { - basicFuncParam: (func (text) -> (text) query) -> (func (text) -> (text) query) query; - basicFuncParamArray: (vec func (text) -> (text) query) -> (vec func (text) -> (text) query) query; - basicFuncReturnType: () -> (func (text) -> (text) query) query; - basicFuncReturnTypeArray: () -> (vec func (text) -> (text) query) query; - complexFuncParam: (rec_26) -> (rec_29) query; - complexFuncReturnType: () -> (rec_34) query; - getNotifierFromNotifiersCanister: () -> (func (vec nat8) -> () oneway) ; - getStableFunc: () -> (func (nat64, text) -> () query) query; - init: () -> () query; - nullFuncParam: (func (opt null, vec null, null, vec vec null, vec opt null) -> (null) query) -> (func (opt null, vec null, null, vec vec null, vec opt null) -> (null) query) query; -} \ No newline at end of file + basicFuncParam: (func (text) -> (text) query) -> (func (text) -> (text) query) query; + basicFuncParamArray: (vec func (text) -> (text) query) -> (vec func (text) -> (text) query) query; + basicFuncReturnType: () -> (func (text) -> (text) query) query; + basicFuncReturnTypeArray: () -> (vec func (text) -> (text) query) query; + complexFuncParam: (rec_26) -> (rec_29) query; + complexFuncReturnType: () -> (rec_34) query; + getNotifierFromNotifiersCanister: () -> (func (vec nat8) -> () oneway); + getStableFunc: () -> (func (nat64, text) -> () query) query; + nullFuncParam: (func (opt null, vec null, null, vec vec null, vec opt null) -> (null) query) -> (func (opt null, vec null, null, vec vec null, vec opt null) -> (null) query) query; +} diff --git a/examples/func_types/canisters/func_types/index.ts b/examples/func_types/canisters/func_types/index.ts index 01165f4b0c..fe9cec1156 100644 --- a/examples/func_types/canisters/func_types/index.ts +++ b/examples/func_types/canisters/func_types/index.ts @@ -56,10 +56,10 @@ export default Canister({ getStableFunc: query([], StableFunc, () => { const stableFuncOpt = stableStorage.get('stableFunc'); - if (stableFuncOpt.length === 1) { - return stableFuncOpt[0]; + if ('None' in stableFuncOpt) { + return [Principal.from('aaaaa-aa'), 'raw_rand']; } - return [Principal.from('aaaaa-aa'), 'raw_rand']; + return stableFuncOpt.Some; }), basicFuncParam: query([BasicFunc], BasicFunc, (basicFunc) => { diff --git a/examples/func_types/canisters/notifiers/index.did b/examples/func_types/canisters/notifiers/index.did index 72c0545dc8..9ea58c8334 100644 --- a/examples/func_types/canisters/notifiers/index.did +++ b/examples/func_types/canisters/notifiers/index.did @@ -1,3 +1,3 @@ service: () -> { - getNotifier: () -> (func (vec nat8) -> () oneway) query; -} \ No newline at end of file + getNotifier: () -> (func (vec nat8) -> () oneway) query; +} diff --git a/examples/ledger_canister/src/ledger_canister/index.ts b/examples/ledger_canister/src/ledger_canister/index.ts index 607d0d4778..f7ea5e4740 100644 --- a/examples/ledger_canister/src/ledger_canister/index.ts +++ b/examples/ledger_canister/src/ledger_canister/index.ts @@ -53,9 +53,9 @@ export default Canister({ from_subaccount: None, to: binaryAddressFromAddress(to), created_at_time: - createdAtTime.length === 1 - ? Some({ timestamp_nanos: createdAtTime[0] }) - : None + 'None' in createdAtTime + ? None + : Some({ timestamp_nanos: createdAtTime.Some }) } ] }); diff --git a/examples/motoko_examples/http_counter/src/index.ts b/examples/motoko_examples/http_counter/src/index.ts index dfc5d2a69c..7ca87f777f 100644 --- a/examples/motoko_examples/http_counter/src/index.ts +++ b/examples/motoko_examples/http_counter/src/index.ts @@ -89,9 +89,9 @@ export default Canister({ const counterOpt = stableStorage.get('counter'); const counter = - counterOpt.length === 0 + 'None' in counterOpt ? ic.trap('counter does not exist') - : counterOpt[0]; + : counterOpt.Some; return { status_code: 200, @@ -144,18 +144,18 @@ export default Canister({ if (req.method === 'POST') { const counterOpt = stableStorage.get('counter'); const counter = - counterOpt.length === 0 + 'None' in counterOpt ? ic.trap('counter does not exist') - : counterOpt[0]; + : counterOpt.Some; stableStorage.insert('counter', counter + 1n); if (req.headers.find(isGzip) === undefined) { const counterOpt = stableStorage.get('counter'); const counter = - counterOpt.length === 0 + 'None' in counterOpt ? ic.trap('counter does not exist') - : counterOpt[0]; + : counterOpt.Some; return { status_code: 201, @@ -206,9 +206,9 @@ export default Canister({ case 'next': { const counterOpt = stableStorage.get('counter'); const counter = - counterOpt.length === 0 + 'None' in counterOpt ? ic.trap('counter does not exist') - : counterOpt[0]; + : counterOpt.Some; return { body: encode(`${counter}`), diff --git a/examples/motoko_examples/persistent-storage/src/index.ts b/examples/motoko_examples/persistent-storage/src/index.ts index 081bafc679..4046991b6e 100644 --- a/examples/motoko_examples/persistent-storage/src/index.ts +++ b/examples/motoko_examples/persistent-storage/src/index.ts @@ -27,9 +27,9 @@ export default Canister({ increment: update([], nat, () => { const counterOpt = stableStorage.get('counter'); const counter = - counterOpt.length === 0 + 'None' in counterOpt ? ic.trap('counter not defined') - : counterOpt[0] + 1n; + : counterOpt.Some + 1n; stableStorage.insert('counter', counter); @@ -38,9 +38,9 @@ export default Canister({ get: query([], nat, () => { const counterOpt = stableStorage.get('counter'); const counter = - counterOpt.length === 0 + 'None' in counterOpt ? ic.trap('counter not defined') - : counterOpt[0]; + : counterOpt.Some; return counter; }), @@ -49,9 +49,9 @@ export default Canister({ const counterOpt = stableStorage.get('counter'); const counter = - counterOpt.length === 0 + 'None' in counterOpt ? ic.trap('counter not defined') - : counterOpt[0]; + : counterOpt.Some; return counter; }) diff --git a/examples/optional_types/src/index.ts b/examples/optional_types/src/index.ts index aaaa6462ee..342b5c0062 100644 --- a/examples/optional_types/src/index.ts +++ b/examples/optional_types/src/index.ts @@ -1,6 +1,17 @@ // TODO let's add more examples here, really test it out -import { bool, Canister, Null, Opt, query, Record, text, Vec } from 'azle'; +import { + bool, + Canister, + None, + Null, + Opt, + query, + Record, + Some, + text, + Vec +} from 'azle'; const Element = Record({ id: text @@ -17,26 +28,22 @@ const Html = Record({ export default Canister({ getHtml: query([], Html, () => { return { - head: [] + head: None }; }), getHead: query([], Opt(Head), () => { - return [ - { - elements: [] - } - ]; + return Some({ + elements: [] + }); }), getHeadWithElements: query([], Opt(Head), () => { - return [ - { - elements: [ - { - id: '0' - } - ] - } - ]; + return Some({ + elements: [ + { + id: '0' + } + ] + }); }), getElement: query([Opt(Opt(Element))], Opt(Opt(Element)), (element) => { return element; @@ -45,12 +52,12 @@ export default Canister({ return null; }), getOptNull: query([], Opt(text), () => { - return []; + return None; }), stringToBoolean: query([Opt(text)], bool, (optString) => { - if (optString.length > 0) { - return true; + if ('None' in optString) { + return false; } - return false; + return true; }) }); diff --git a/examples/pre_and_post_upgrade/src/index.ts b/examples/pre_and_post_upgrade/src/index.ts index 0a6cfbc33e..726feb50d7 100644 --- a/examples/pre_and_post_upgrade/src/index.ts +++ b/examples/pre_and_post_upgrade/src/index.ts @@ -36,9 +36,7 @@ export default Canister({ const stableEntriesOpt = stableStorage.get('entries'); const stableEntries = - stableEntriesOpt.length === 0 - ? stableEntriesOpt - : stableEntriesOpt[0]; + 'None' in stableEntriesOpt ? [] : stableEntriesOpt.Some; entries = stableEntries.reduce((result, entry) => { return { diff --git a/examples/robust_imports/src/azle_coverage/index.ts b/examples/robust_imports/src/azle_coverage/index.ts index 1954098317..522cc2de19 100644 --- a/examples/robust_imports/src/azle_coverage/index.ts +++ b/examples/robust_imports/src/azle_coverage/index.ts @@ -38,7 +38,7 @@ import kiwi, { Vanilla } from './fruit'; -import { ic as lemon, int16 as coconut } from 'azle'; +import { ic as lemon, int16 as coconut, ic } from 'azle'; import * as starFruit from './fruit'; const FruitDeliveryCanister = Strawberry({ @@ -97,14 +97,16 @@ let soncoya = Soncoya(nectarine8, PreparedFruit, 0); function gatherGrapes() { const opt = soncoya.get(0); - if (opt.length !== 0) { - const preparedFruit = opt[0]; - soncoya.remove(0); - soncoya.insert(0, { - ...preparedFruit, - areGrapesGathered: true - }); + if ('None' in opt) { + return; } + + const preparedFruit = opt.Some; + soncoya.remove(0); + soncoya.insert(0, { + ...preparedFruit, + areGrapesGathered: true + }); } export const collectIcaco = icaco([], () => { @@ -125,40 +127,46 @@ export const collectIcaco = icaco([], () => { export const cutPineapple = pineapple(() => { const opt = soncoya.get(0); - if (opt.length !== 0) { - const preparedFruit = opt[0]; - soncoya.remove(0); - soncoya.insert(0, { - ...preparedFruit, - isPineappleCut: true - }); + if ('None' in opt) { + return; } + + const preparedFruit = opt.Some; + soncoya.remove(0); + soncoya.insert(0, { + ...preparedFruit, + isPineappleCut: true + }); }); export const separateArilsFromPith = pomegranate([], () => { const opt = soncoya.get(0); - if (opt.length !== 0) { - const preparedFruit = opt[0]; - soncoya.remove(0); - soncoya.insert(0, { - ...preparedFruit, - arePomegranateArilsSeparated: true - }); + if ('None' in opt) { + return; } + + const preparedFruit = opt.Some; + soncoya.remove(0); + soncoya.insert(0, { + ...preparedFruit, + arePomegranateArilsSeparated: true + }); }); export const buyHoneydew = honeydew(() => { const opt = soncoya.get(0); - if (opt.length !== 0) { - const preparedFruit = opt[0]; - soncoya.remove(0); - soncoya.insert(0, { - ...preparedFruit, - honeydewCount: preparedFruit.honeydewCount + 1 - }); + if ('None' in opt) { + return; } + + const preparedFruit = opt.Some; + soncoya.remove(0); + soncoya.insert(0, { + ...preparedFruit, + honeydewCount: preparedFruit.honeydewCount + 1 + }); }); export const keepIlamaClean = ilama(() => { @@ -228,11 +236,11 @@ export const getManagementPeach = kiwi([], peach, () => { }); export const pitOlives = kiwi([Olive(boysenberry)], boysenberry, (olive) => { - if (olive.length === 0) { + if ('None' in olive) { return false; } - const berry = olive[0]; + const berry = olive.Some; return berry; }); @@ -267,11 +275,11 @@ export const isFruitPrepared = quince( () => { const opt = soncoya.get(0); - if (opt.length === 0) { + if ('None' in opt) { return false; } - const pf = opt[0]; + const pf = opt.Some; return ( pf.honeydewCount > 0 && @@ -290,42 +298,48 @@ export const isFruitPrepared = quince( export const removeRambutanSkins = ugni([], rambutan, () => { const opt = soncoya.get(0); - if (opt.length !== 0) { - const preparedFruit = opt[0]; - soncoya.remove(0); - soncoya.insert(0, { - ...preparedFruit, - areRambutanSkinsRemoved: true - }); + if ('None' in opt) { + ic.trap('soncoya is None'); } + const preparedFruit = opt.Some; + soncoya.remove(0); + soncoya.insert(0, { + ...preparedFruit, + areRambutanSkinsRemoved: true + }); + return 'rambutan skins'; }); export const dirtyIlama = ugni([], Vanilla, () => { const opt = soncoya.get(0); - if (opt.length !== 0) { - const preparedFruit = opt[0]; - soncoya.remove(0); - soncoya.insert(0, { - ...preparedFruit, - isIlamaWashed: false - }); + if ('None' in opt) { + return; } + + const preparedFruit = opt.Some; + soncoya.remove(0); + soncoya.insert(0, { + ...preparedFruit, + isIlamaWashed: false + }); }); export const pickElderberry = ugni([], elderberry, () => { const opt = soncoya.get(0); - if (opt.length !== 0) { - const preparedFruit = opt[0]; - soncoya.remove(0); - soncoya.insert(0, { - ...preparedFruit, - haveElderberriesBeenPicked: true - }); + if ('None' in opt) { + ic.trap('soncoya is None'); } + const preparedFruit = opt.Some; + soncoya.remove(0); + soncoya.insert(0, { + ...preparedFruit, + haveElderberriesBeenPicked: true + }); + throw 'All out of elderberries'; }); diff --git a/examples/robust_imports/src/import_coverage/index.ts b/examples/robust_imports/src/import_coverage/index.ts index b29dad7f83..f73c0cc9b6 100644 --- a/examples/robust_imports/src/import_coverage/index.ts +++ b/examples/robust_imports/src/import_coverage/index.ts @@ -145,11 +145,11 @@ export const typeCheck = dollarSignQuery( coveredInt16, (vec) => { if (vec.length === 1) { - if (vec[0].length === 0) { + if ('None' in vec[0]) { return -1; } - return vec[0][0]; + return vec[0].Some; } return -2; } diff --git a/src/lib_functional/candid/index.ts b/src/lib_functional/candid/index.ts index 7a131edee7..7e33b3518d 100644 --- a/src/lib_functional/candid/index.ts +++ b/src/lib_functional/candid/index.ts @@ -41,7 +41,8 @@ import { Result, AzleTuple, AzleText, - AzleVoid + AzleVoid, + Opt } from '../../lib_new'; export type TypeMapping = T extends () => any @@ -80,8 +81,8 @@ export type TypeMapping = T extends () => any ? { [K in keyof U]: U[K] extends any ? any : TypeMapping } : T extends AzleVec ? TypeMapping[] - : T extends AzleOpt - ? [TypeMapping] | [] + : T extends AzleOpt + ? Opt> : T extends AzleResult ? Result, TypeMapping> : T extends AzleBlob diff --git a/src/lib_functional/candid/serde.ts b/src/lib_functional/candid/serde.ts new file mode 100644 index 0000000000..8af18a8257 --- /dev/null +++ b/src/lib_functional/candid/serde.ts @@ -0,0 +1,103 @@ +import { IDL } from '@dfinity/candid'; + +import { AzleVec, AzleOpt, AzleTuple } from '../../lib_new'; +import { toIDLType } from '../../lib_new/utils'; +import { + DecodeVisitor, + EncodeVisitor +} from '../../lib_new/visitors/encode_decode'; +import { CandidType } from '../'; + +/** + * Encodes the provided value as candid blob of the designated type. + * + * Intended to be a rich replacement for `IDL.encode` from `@dfinity/candid`, + * adding support for complex Azle IDL wrappers such as {@link AzleOpt}, + * {@link AzleVec}, and {@link AzleTuple}. It recursively visits all "inner" + * values, converting any Azle values to official IDL values. + * + * @param data the value to encode + * @param fakeIdl either a built-in IDL data type, or an Azle-defined super-type + * @returns candid bytes + */ +export function encode( + data: any, + fakeIdl: IDL.Type | CandidType +): Uint8Array { + // TODO: there is a discrepancy between CandidType and CandidClass that + // needs to be aligned so that this isn't an error. Both are representing + // candid IDLs, either from the @dfinity/candid library or the + // Azle-augmented ones + const realIDL = toIDLType(fakeIdl, []); + + const encodeReadyKey = realIDL.accept(new EncodeVisitor(), { + js_class: fakeIdl, + js_data: data + }); + + return new Uint8Array(IDL.encode([realIDL], [encodeReadyKey])); +} + +/** + * Decodes the provided buffer into the designated JS value. + * + * Intended to be a rich replacement for `IDL.decode` from `@dfinity/candid` + * adding support for complex Azle IDL wrappers such as {@link AzleOpt}, + * {@link AzleVec}, and {@link AzleTuple}. It recursively visits all "inner" + * values, converting them from their native shape to the shape that Azle expects. + * + * @param data the value to decode + * @param fakeIdl either a built-in IDL data type, or an Azle-defined super-type + * @returns the Azle representation of the data + */ +export function decode( + data: ArrayBuffer, + fakeIdl: IDL.Type | CandidType +): any { + // TODO: there is a discrepancy between CandidType and CandidClass that + // needs to be aligned so that this isn't an error. Both are representing + // candid IDLs, either from the @dfinity/candid library or the + // Azle-augmented ones + const realIDL = toIDLType(fakeIdl, []); + + const idlIsAzleVoid = Array.isArray(realIDL); + + if (idlIsAzleVoid) { + return undefined; + } + + const candidDecodedValue = IDL.decode([realIDL], data)[0] as any; + + return realIDL.accept(new DecodeVisitor(), { + js_class: fakeIdl, + js_data: candidDecodedValue + }); +} + +export function encodeMultiple( + data: any[], + fakeIdls: (IDL.Type | CandidType)[] +): Uint8Array { + const { values, idls } = data.reduce<{ + values: any[]; + idls: IDL.Type[]; + }>( + (acc, datum, index) => { + const fakeIdl = fakeIdls[index]; + const realIDL = toIDLType(fakeIdl, []); + + const encodeReadyValue = realIDL.accept(new EncodeVisitor(), { + js_class: fakeIdl, + js_data: datum + }); + + return { + values: [...acc.values, encodeReadyValue], + idls: [...acc.idls, realIDL] + }; + }, + { values: [], idls: [] } + ); + + return new Uint8Array(IDL.encode(idls, values)); +} diff --git a/src/lib_functional/canister_methods/heartbeat.ts b/src/lib_functional/canister_methods/heartbeat.ts index 2d791a4e07..0a475d3f22 100644 --- a/src/lib_functional/canister_methods/heartbeat.ts +++ b/src/lib_functional/canister_methods/heartbeat.ts @@ -4,7 +4,7 @@ import { Void } from '../../lib_new'; export function heartbeat( callback: () => void | Promise -): () => CanisterMethodInfo<[], Void> { +): CanisterMethodInfo<[], Void> { return () => { const finalCallback = (...args: any[]) => { executeMethod( diff --git a/src/lib_functional/canister_methods/init.ts b/src/lib_functional/canister_methods/init.ts index 122be5714c..e71bdf43fa 100644 --- a/src/lib_functional/canister_methods/init.ts +++ b/src/lib_functional/canister_methods/init.ts @@ -1,11 +1,11 @@ import { Callback, CanisterMethodInfo, createParents, executeMethod } from '.'; import { CandidType, TypeMapping } from '../candid'; -import { Void } from '../../lib_new'; +import { Void } from '../../lib_functional'; import { toParamIDLTypes, toReturnIDLType } from '../../lib_new/utils'; export function init< const Params extends ReadonlyArray, - GenericCallback extends Callback + GenericCallback extends Callback >( paramsIdls: Params, callback?: Awaited> extends TypeMapping diff --git a/src/lib_functional/canister_methods/post_upgrade.ts b/src/lib_functional/canister_methods/post_upgrade.ts index 35df523fac..5007c97905 100644 --- a/src/lib_functional/canister_methods/post_upgrade.ts +++ b/src/lib_functional/canister_methods/post_upgrade.ts @@ -5,7 +5,7 @@ import { toParamIDLTypes, toReturnIDLType } from '../../lib_new/utils'; export function postUpgrade< const Params extends ReadonlyArray, - GenericCallback extends Callback + GenericCallback extends Callback >( paramsIdls: Params, callback?: Awaited> extends TypeMapping diff --git a/src/lib_functional/canister_methods/pre_upgrade.ts b/src/lib_functional/canister_methods/pre_upgrade.ts index 8661c05b22..94940742a7 100644 --- a/src/lib_functional/canister_methods/pre_upgrade.ts +++ b/src/lib_functional/canister_methods/pre_upgrade.ts @@ -4,7 +4,7 @@ import { Void } from '../../lib_new'; export function preUpgrade( callback: () => void | Promise -): () => CanisterMethodInfo<[], Void> { +): CanisterMethodInfo<[], Void> { return () => { const finalCallback = (...args: any[]) => { executeMethod( diff --git a/src/lib_new/ic.ts b/src/lib_new/ic.ts index 2fd19746c3..385d46f185 100644 --- a/src/lib_new/ic.ts +++ b/src/lib_new/ic.ts @@ -1,7 +1,17 @@ import '@dfinity/candid/lib/esm/idl'; // This must remain or the build fails import { Principal } from '@dfinity/principal'; import { IDL } from './index'; -import { blob, nat, nat32, nat64, AzleNat64, Void, Opt } from './primitives'; +import { + AzleNat64, + blob, + nat, + nat32, + nat64, + None, + Opt, + Some, + Void +} from './primitives'; import { CandidType, RejectionCode } from '../lib_functional'; import { v4 } from 'uuid'; import { CandidClass, toIDLType } from './utils'; @@ -609,8 +619,8 @@ export const ic: Ic = globalThis._azleIc globalThis._azleIc.dataCertificate(); return rawRustValue === undefined - ? [] - : [new Uint8Array(rawRustValue)]; + ? None + : Some(new Uint8Array(rawRustValue)); }, id: () => { // TODO consider bytes instead of string, just like with caller diff --git a/src/lib_new/primitives.ts b/src/lib_new/primitives.ts index 9c9c1a240a..4be8fd007e 100644 --- a/src/lib_new/primitives.ts +++ b/src/lib_new/primitives.ts @@ -1,4 +1,4 @@ -import { IDL } from './index'; +import { IDL, RequireExactlyOne } from './index'; import { CandidClass, Parent, toIDLType } from './utils'; import { Principal as DfinityPrincipal } from '@dfinity/principal'; @@ -224,7 +224,7 @@ export class Principal extends DfinityPrincipal { * Represents an optional value: every {@link Opt} is either `Some` and contains * a value, or `None` and does not. */ -export type Opt = [Value] | []; +export type Opt = RequireExactlyOne<{ Some: T; None: null }>; export const Void: AzleVoid = AzleVoid as any; export type Void = void; @@ -233,12 +233,12 @@ export type Void = void; * @param value - the value to be wrapped * @returns a `Some` {@link Opt} containing the provided value */ -export function Some(value: T): [T] { - return [value]; +export function Some(value: T) { + return { Some: value }; } /** An {@link Opt} representing the absence of a value */ -export const None: [] = []; +export const None = { None: null }; // TODO what happens if we pass something to Opt() that can't be converted to CandidClass? export function Opt(t: T): AzleOpt { @@ -253,6 +253,7 @@ export class AzleOpt { _azleType: CandidClass; _azleCandidType?: '_azleCandidType'; + _kind: 'AzleOpt' = 'AzleOpt'; getIDL(parents: Parent[]) { return IDL.Opt(toIDLType(this._azleType, parents)); diff --git a/src/lib_new/service.ts b/src/lib_new/service.ts index 1e480a6b5d..ab515f7733 100644 --- a/src/lib_new/service.ts +++ b/src/lib_new/service.ts @@ -1,3 +1,4 @@ +import { decode, encodeMultiple } from '../lib_functional/candid/serde'; import { ic, IDL, Principal } from './index'; import { CandidClass, @@ -108,9 +109,7 @@ export function serviceCall( cycles: bigint, ...args: any[] ) { - const encodedArgs = new Uint8Array( - IDL.encode(toParamIDLTypes(paramsIdls), args) - ); + const encodedArgs = encodeMultiple(args, paramsIdls); if (notify) { try { @@ -131,10 +130,7 @@ export function serviceCall( cycles ); - const returnIdls = toReturnIDLType(returnIdl); - const decodedResult = IDL.decode(returnIdls, encodedResult)[0]; - - return decodedResult; + return decode(encodedResult, returnIdl); } }; } diff --git a/src/lib_new/stable_b_tree_map.ts b/src/lib_new/stable_b_tree_map.ts index 63489f2674..8a481c27e7 100644 --- a/src/lib_new/stable_b_tree_map.ts +++ b/src/lib_new/stable_b_tree_map.ts @@ -1,13 +1,14 @@ -import { CandidType, TypeMapping } from '../lib_functional'; +import { CandidType, None, Some, TypeMapping } from '../lib_functional'; import { IDL, nat8, nat64, Opt } from './index'; import { toIDLType } from './utils'; +import { encode, decode } from '../lib_functional/candid/serde'; export function StableBTreeMap< Key extends CandidType, Value extends CandidType ->(key: Key, value: Value, memoryId: nat8) { - const keyIdl = toIDLType(key, []); - const valueIdl = toIDLType(value, []); +>(keyType: Key, valueType: Value, memoryId: nat8) { + const keyIdl = toIDLType(keyType, []); + const valueIdl = toIDLType(valueType, []); const candidEncodedMemoryId = new Uint8Array( IDL.encode([IDL.Nat8], [memoryId]) @@ -24,13 +25,8 @@ export function StableBTreeMap< * @returns `true` if the key exists in the map, `false` otherwise. */ containsKey(key: TypeMapping): boolean { - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [memoryId]) - ).buffer; - - const candidEncodedKey = new Uint8Array( - IDL.encode([keyIdl as any], [key]) - ).buffer; + const candidEncodedMemoryId = encode(memoryId, IDL.Nat8).buffer; + const candidEncodedKey = encode(key, keyType).buffer; return (globalThis as any)._azleIc.stableBTreeMapContainsKey( candidEncodedMemoryId, @@ -43,13 +39,8 @@ export function StableBTreeMap< * @returns the value associated with the given key, if it exists. */ get(key: TypeMapping): Opt> { - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [memoryId]) - ).buffer; - - const candidEncodedKey = new Uint8Array( - IDL.encode([keyIdl as any], [key]) - ).buffer; + const candidEncodedMemoryId = encode(memoryId, IDL.Nat8).buffer; + const candidEncodedKey = encode(key, keyType).buffer; const candidEncodedValue = ( globalThis as any @@ -59,14 +50,9 @@ export function StableBTreeMap< ); if (candidEncodedValue === undefined) { - return []; + return None; } else { - const candidDecodedValue = IDL.decode( - [valueIdl as any], - candidEncodedValue - )[0]; - - return [candidDecodedValue as any]; + return Some(decode(candidEncodedValue, valueType)); } }, /** @@ -79,17 +65,9 @@ export function StableBTreeMap< key: TypeMapping, value: TypeMapping ): Opt> { - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [memoryId]) - ).buffer; - - const candidEncodedKey = new Uint8Array( - IDL.encode([keyIdl as any], [key]) - ).buffer; - - const candidEncodedValue = new Uint8Array( - IDL.encode([valueIdl as any], [value]) - ).buffer; + const candidEncodedMemoryId = encode(memoryId, IDL.Nat8).buffer; + const candidEncodedKey = encode(key, keyType).buffer; + const candidEncodedValue = encode(value, valueType).buffer; const candidEncodedResultValue = ( globalThis as any @@ -100,14 +78,9 @@ export function StableBTreeMap< ); if (candidEncodedResultValue === undefined) { - return []; + return None; } else { - const candidDecodedValue = IDL.decode( - [valueIdl as any], - candidEncodedResultValue - ); - - return [candidDecodedValue as any]; + return Some(decode(candidEncodedResultValue, valueType)); } }, /** @@ -115,9 +88,7 @@ export function StableBTreeMap< * @returns `true` if the map contains no elements, `false` otherwise. */ isEmpty(): boolean { - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [memoryId]) - ).buffer; + const candidEncodedMemoryId = encode(memoryId, IDL.Nat8).buffer; return (globalThis as any)._azleIc.stableBTreeMapIsEmpty( candidEncodedMemoryId @@ -128,9 +99,7 @@ export function StableBTreeMap< * @returns tuples representing key/value pairs. */ items(): [TypeMapping, TypeMapping][] { - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [memoryId]) - ).buffer; + const candidEncodedMemoryId = encode(memoryId, IDL.Nat8).buffer; const candidEncodedItems = ( globalThis as any @@ -139,8 +108,8 @@ export function StableBTreeMap< // TODO too much copying return candidEncodedItems.map((candidEncodedItem: any) => { return [ - IDL.decode([keyIdl as any], candidEncodedItem[0])[0], - IDL.decode([valueIdl as any], candidEncodedItem[1])[0] + decode(candidEncodedItem[0], keyType), + decode(candidEncodedItem[1], valueType) ]; }); }, @@ -149,9 +118,7 @@ export function StableBTreeMap< * @returns they keys in the map. */ keys(): TypeMapping[] { - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [memoryId]) - ).buffer; + const candidEncodedMemoryId = encode(memoryId, IDL.Nat8).buffer; const candidEncodedKeys = ( globalThis as any @@ -159,7 +126,7 @@ export function StableBTreeMap< // TODO too much copying return candidEncodedKeys.map((candidEncodedKey: any) => { - return IDL.decode([keyIdl as any], candidEncodedKey)[0]; + return decode(candidEncodedKey, keyType); }); }, /** @@ -167,15 +134,13 @@ export function StableBTreeMap< * @returns the number of elements in the map. */ len(): nat64 { - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [memoryId]) - ).buffer; + const candidEncodedMemoryId = encode(memoryId, IDL.Nat8).buffer; const candidEncodedLen = ( globalThis as any )._azleIc.stableBTreeMapLen(candidEncodedMemoryId); - return IDL.decode([IDL.Nat64], candidEncodedLen)[0] as any; + return decode(candidEncodedLen, IDL.Nat64); }, /** * Removes a key from the map. @@ -183,13 +148,8 @@ export function StableBTreeMap< * @returns the previous value at the key if it exists, `null` otherwise. */ remove(key: TypeMapping): Opt> { - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [memoryId]) - ).buffer; - - const candidEncodedKey = new Uint8Array( - IDL.encode([keyIdl as any], [key]) - ).buffer; + const candidEncodedMemoryId = encode(memoryId, IDL.Nat8).buffer; + const candidEncodedKey = encode(key, keyType).buffer; const candidEncodedValue = ( globalThis as any @@ -199,14 +159,9 @@ export function StableBTreeMap< ); if (candidEncodedValue === undefined) { - return []; + return None; } else { - const candidDecodedValue = IDL.decode( - [valueIdl as any], - candidEncodedValue - )[0]; - - return [candidDecodedValue as any]; + return Some(decode(candidEncodedValue, valueType)); } }, /** @@ -214,9 +169,7 @@ export function StableBTreeMap< * @returns the values in the map. */ values(): TypeMapping[] { - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [memoryId]) - ).buffer; + const candidEncodedMemoryId = encode(memoryId, IDL.Nat8).buffer; const candidEncodedValues = ( globalThis as any @@ -224,7 +177,7 @@ export function StableBTreeMap< // TODO too much copying return candidEncodedValues.map((candidEncodedValue: any) => { - return IDL.decode([valueIdl as any], candidEncodedValue)[0]; + return decode(candidEncodedValue, valueType); }); } }; diff --git a/src/lib_new/utils.ts b/src/lib_new/utils.ts index 2255869811..a0ae846fa6 100644 --- a/src/lib_new/utils.ts +++ b/src/lib_new/utils.ts @@ -111,11 +111,7 @@ export function toReturnIDLType( ): IDL.Type[] { const idlType = toIDLType(returnIdl, parents); - if (Array.isArray(idlType)) { - return [...idlType]; - } - - return [idlType]; + return Array.isArray(idlType) ? idlType : [idlType]; } export function isAsync(originalFunction: any) { diff --git a/src/lib_new/visitors/encode_decode/decode_visitor.ts b/src/lib_new/visitors/encode_decode/decode_visitor.ts index 65a3f67d87..5cb580dcac 100644 --- a/src/lib_new/visitors/encode_decode/decode_visitor.ts +++ b/src/lib_new/visitors/encode_decode/decode_visitor.ts @@ -1,14 +1,15 @@ import { IDL } from '@dfinity/candid'; import { + hch, VisitorData, VisitorResult, - visitOpt, visitRec, visitRecord, visitTuple, visitVariant, visitVec } from '.'; +import { Opt } from '../../primitives'; /** * When we decode a Service we are given a principal. We need to use that @@ -36,12 +37,34 @@ export class DecodeVisitor extends IDL.Visitor { ): VisitorResult { return visitTuple(this, components, data); } + /** + * Converts empty arrays to `{None: null}` and an array with one item into + * `{Some: value}`, transforming the value as needed as well. + * @param t the IDL of the Opt class. + * @param ty the IDL type of the `Some` value. + * @param data {VisitorData<[] | [CandidType], AzleOpt>} + * `data.js_data` is the raw array opt value. `data.js_class` is an + * `AzleOpt`. + * @returns an object representation of an opt with a transformed some value + * if necessary. + */ visitOpt( t: IDL.OptClass, ty: IDL.Type, data: VisitorData - ): VisitorResult { - return visitOpt(this, ty, data); + ): Opt { + if (data.js_data.length === 0) { + return { None: null }; + } + + const candid = hch(ty).accept(this, { + js_data: data.js_data[0], + js_class: data.js_class._azleType + }); + + return { + Some: candid + }; } visitVec( t: IDL.VecClass, diff --git a/src/lib_new/visitors/encode_decode/encode_visitor.ts b/src/lib_new/visitors/encode_decode/encode_visitor.ts index f33acb675d..7d01706654 100644 --- a/src/lib_new/visitors/encode_decode/encode_visitor.ts +++ b/src/lib_new/visitors/encode_decode/encode_visitor.ts @@ -1,8 +1,8 @@ import { IDL } from '@dfinity/candid'; import { + hch, VisitorData, VisitorResult, - visitOpt, visitRec, visitRecord, visitTuple, @@ -37,12 +37,34 @@ export class EncodeVisitor extends IDL.Visitor { ): VisitorResult { return visitTuple(this, components, data); } + /** + * Converts `Some` values (`{Some: value}`) to `[value]` and `None` values + * (`{None: null}`) to `[]` (the empty array), transforming any `Some` + * values. + * + * @param t the IDL of the Opt class. + * @param ty the IDL type of the `Some` value. + * @param data {VisitorData, + * AzleOpt>} `data.js_data` is the raw Some/None object. + * `data.js_class` is an `AzleOpt`. + * @returns an array representation of an opt with a transformed some value + * if necessary. + */ visitOpt( t: IDL.OptClass, ty: IDL.Type, data: VisitorData - ): VisitorResult { - return visitOpt(this, ty, data); + ): [] | [any] { + if ('Some' in data.js_data) { + const candid = hch(ty).accept(this, { + js_data: data.js_data.Some, + js_class: data.js_class._azleType + }); + + return [candid]; + } + + return []; } visitVec( t: IDL.VecClass, diff --git a/src/lib_new/visitors/encode_decode/index.ts b/src/lib_new/visitors/encode_decode/index.ts index bda98a20a3..c1378280fa 100644 --- a/src/lib_new/visitors/encode_decode/index.ts +++ b/src/lib_new/visitors/encode_decode/index.ts @@ -22,7 +22,7 @@ export type VisitorResult = any; * is extracted into these helper methods. */ -function hch(value: any) { +export function hch(value: any) { if (value._azleIsCanister) { return value().getIDL(); } @@ -43,21 +43,6 @@ export function visitTuple( return [...fields]; } -export function visitOpt( - visitor: DecodeVisitor | EncodeVisitor, - ty: IDL.Type, - data: VisitorData -): VisitorResult { - if (data.js_data.length === 0) { - return data.js_data; - } - const candid = hch(ty).accept(visitor, { - js_data: data.js_data[0], - js_class: data.js_class._azleType - }); - return [candid]; -} - export function visitVec( visitor: DecodeVisitor | EncodeVisitor, ty: IDL.Type,