diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 840e71fb4f..bd1bbfb236 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,6 @@ # The check-basic-integration-tests-success job is designed to ensure that all jobs spun up from the matrix in the basic-integration-tests have succeeded # All Examples TODO restore when https://github.com/demergent-labs/azle/issues/1192 is resolved -# "examples/audio_recorder", # "examples/bitcoin", # "examples/blob_array", # "examples/bytes", @@ -66,7 +65,6 @@ # "examples/robust_imports", # "examples/run_time_errors", # "examples/rust_type_conversions", -# "examples/service", # "examples/stable_structures", # "examples/tuple_types", @@ -115,10 +113,12 @@ jobs: EXAMPLE_DIRECTORIES=$(cat << END [ "examples/async_await", + "examples/audio_recorder", "examples/primitive_types", "examples/principal", "examples/query", "examples/randomness", + "examples/service", "examples/simple_erc20", "examples/simple_user_accounts", "examples/stable_memory", diff --git a/canisters/management/index.ts b/canisters/management/index.ts index b4d9f84b4c..fdbc36c211 100644 --- a/canisters/management/index.ts +++ b/canisters/management/index.ts @@ -1,8 +1,5 @@ import { blob, Principal, Service, update } from '../../src/lib_functional'; -export const managementCanister = Service( - { - raw_rand: update([], blob) - }, - Principal.fromText('aaaaa-aa') -); +export const managementCanister = Service({ + raw_rand: update([], blob) +})(Principal.fromText('aaaaa-aa')); diff --git a/examples/audio_recorder/src/index.did b/examples/audio_recorder/src/index.did index 2e43aa009e..00906c820d 100644 --- a/examples/audio_recorder/src/index.did +++ b/examples/audio_recorder/src/index.did @@ -1,21 +1,21 @@ -type rec_0 = record {id:principal; username:text; recordingIds:vec principal; createdAt:nat64}; -type rec_1 = record {id:principal; username:text; recordingIds:vec principal; createdAt:nat64}; type rec_2 = record {id:principal; username:text; recordingIds:vec principal; createdAt:nat64}; type rec_3 = record {id:principal; username:text; recordingIds:vec principal; createdAt:nat64}; -type rec_4 = variant {RecordingDoesNotExist:principal; UserDoesNotExist:principal}; -type rec_5 = record {id:principal; audio:vec nat8; userId:principal; name:text; createdAt:nat64}; +type rec_4 = record {id:principal; username:text; recordingIds:vec principal; createdAt:nat64}; +type rec_5 = record {id:principal; username:text; recordingIds:vec principal; createdAt:nat64}; type rec_6 = variant {RecordingDoesNotExist:principal; UserDoesNotExist:principal}; type rec_7 = record {id:principal; audio:vec nat8; userId:principal; name:text; createdAt:nat64}; -type rec_8 = record {id:principal; audio:vec nat8; userId:principal; name:text; createdAt:nat64}; +type rec_8 = variant {RecordingDoesNotExist:principal; UserDoesNotExist:principal}; type rec_9 = record {id:principal; audio:vec nat8; userId:principal; name:text; createdAt:nat64}; -type rec_10 = variant {RecordingDoesNotExist:principal; UserDoesNotExist:principal}; +type rec_10 = record {id:principal; audio:vec nat8; userId:principal; name:text; createdAt:nat64}; +type rec_11 = record {id:principal; audio:vec nat8; userId:principal; name:text; createdAt:nat64}; +type rec_12 = variant {RecordingDoesNotExist:principal; UserDoesNotExist:principal}; service: () -> { - createUser: (text) -> (rec_0); - readUsers: () -> (vec rec_1) query; - readUserById: (principal) -> (opt rec_2) query; - deleteUser: (principal) -> (variant {Ok:rec_3; Err:rec_4}); - createRecording: (vec nat8, text, principal) -> (variant {Ok:rec_5; Err:rec_6}); - readRecordings: () -> (vec rec_7) query; - readRecordingById: (principal) -> (opt rec_8) query; - deleteRecording: (principal) -> (variant {Ok:rec_9; Err:rec_10}); + createUser: (text) -> (rec_2); + readUsers: () -> (vec rec_3) query; + readUserById: (principal) -> (opt rec_4) query; + deleteUser: (principal) -> (variant {Ok:rec_5; Err:rec_6}); + createRecording: (vec nat8, text, principal) -> (variant {Ok:rec_7; Err:rec_8}); + readRecordings: () -> (vec rec_9) query; + readRecordingById: (principal) -> (opt rec_10) query; + deleteRecording: (principal) -> (variant {Ok:rec_11; Err:rec_12}); } diff --git a/examples/audio_recorder/src/index.ts b/examples/audio_recorder/src/index.ts index 021d8bd56e..397627fc68 100644 --- a/examples/audio_recorder/src/index.ts +++ b/examples/audio_recorder/src/index.ts @@ -1,6 +1,5 @@ import { blob, - candid, ic, nat64, Opt, @@ -17,194 +16,161 @@ import { Vec } from 'azle'; -class User extends Record { - @candid(principal) - id: Principal; - - @candid(nat64) - createdAt: nat64; - - @candid(Vec(principal)) - recordingIds: Vec; - - @candid(text) - username: text; -} - -class Recording extends Record { - @candid(principal) - id: Principal; - - @candid(blob) - audio: blob; - - @candid(nat64) - createdAt: nat64; - - @candid(text) - name: text; - - @candid(principal) - userId: Principal; -} - -class AudioRecorderError extends Variant { - @candid(principal) - RecordingDoesNotExist: Principal; - - @candid(principal) - UserDoesNotExist: Principal; -} - -export default class extends Service { - users = new StableBTreeMap(principal, User as any, 0); - recordings = new StableBTreeMap( - principal, - Recording as any, - 1 - ); - - @update([text], User) - createUser(username: text): User { +const User = Record({ + id: principal, + createdAt: nat64, + recordingIds: Vec(principal), + username: text +}); + +const Recording = Record({ + id: principal, + audio: blob, + createdAt: nat64, + name: text, + userId: principal +}); + +const AudioRecorderError = Variant({ + RecordingDoesNotExist: principal, + UserDoesNotExist: principal +}); + +let users = StableBTreeMap(principal, User, 0); +let recordings = StableBTreeMap(principal, Recording, 1); + +export default Service({ + createUser: update([text], User, (username) => { const id = generateId(); - const user: User = { + const user: typeof User = { id, createdAt: ic.time(), recordingIds: [], username }; - this.users.insert(user.id, user); + users.insert(user.id, user); return user; - } - - @query([], Vec(User)) - readUsers(): Vec { - return this.users.values(); - } - - @query([principal], Opt(User)) - readUserById(id: Principal): Opt { - return this.users.get(id); - } - - @update([principal], Result(User, AudioRecorderError)) - deleteUser(id: Principal): Result { - const userOpt = this.users.get(id); + }), + readUsers: query([], Vec(User), () => { + return users.values(); + }), + readUserById: query([principal], Opt(User), (id) => { + return users.get(id); + }), + deleteUser: update([principal], Result(User, AudioRecorderError), (id) => { + const userOpt = users.get(id); if (userOpt.length === 0) { return { - Err: AudioRecorderError.create({ + Err: { UserDoesNotExist: id - }) + } }; } const user = userOpt[0]; user.recordingIds.forEach((recordingId) => { - this.recordings.remove(recordingId); + recordings.remove(recordingId); }); - this.users.remove(user.id); + users.remove(user.id); return { Ok: user }; - } - - @update([blob, text, principal], Result(Recording, AudioRecorderError)) - createRecording( - audio: blob, - name: text, - userId: Principal - ): Result { - const userOpt = this.users.get(userId); - - if (userOpt.length === 0) { - return { - Err: AudioRecorderError.create({ - UserDoesNotExist: userId - }) + }), + createRecording: update( + [blob, text, principal], + Result(Recording, AudioRecorderError), + (audio: blob, name: text, userId: Principal) => { + const userOpt = users.get(userId); + + if (userOpt.length === 0) { + return { + Err: { + UserDoesNotExist: userId + } + }; + } + + const user = userOpt[0]; + + const id = generateId(); + const recording: typeof Recording = { + id, + audio, + createdAt: ic.time(), + name, + userId }; - } - - const user = userOpt[0]; - const id = generateId(); - const recording: Recording = { - id, - audio, - createdAt: ic.time(), - name, - userId - }; - - this.recordings.insert(recording.id, recording); - - const updatedUser: User = { - ...user, - recordingIds: [...user.recordingIds, recording.id] - }; + recordings.insert(recording.id, recording); - this.users.insert(updatedUser.id, updatedUser); - - return { - Ok: recording - }; - } - - @query([], Vec(Recording)) - readRecordings(): Vec { - return this.recordings.values(); - } - - @query([principal], Opt(Recording)) - readRecordingById(id: Principal): Opt { - return this.recordings.get(id); - } + const updatedUser: typeof User = { + ...user, + recordingIds: [...user.recordingIds, recording.id] + }; - @update([principal], Result(Recording, AudioRecorderError)) - deleteRecording(id: Principal): Result { - const recordingOpt = this.recordings.get(id); + users.insert(updatedUser.id, updatedUser); - if (recordingOpt.length === 0) { return { - Err: AudioRecorderError.create({ RecordingDoesNotExist: id }) + Ok: recording }; } + ), + readRecordings: query([], Vec(Recording), () => { + return recordings.values(); + }), + readRecordingById: query([principal], Opt(Recording), (id) => { + return recordings.get(id); + }), + deleteRecording: update( + [principal], + Result(Recording, AudioRecorderError), + (id) => { + const recordingOpt = recordings.get(id); + + if (recordingOpt.length === 0) { + return { + Err: { RecordingDoesNotExist: id } + }; + } + + const recording = recordingOpt[0]; + + const userOpt = users.get(recording.userId); + + if (userOpt.length === 0) { + return { + Err: { + UserDoesNotExist: recording.userId + } + }; + } + + const user = userOpt[0]; + + const updatedUser: typeof User = { + ...user, + recordingIds: user.recordingIds.filter( + (recordingId) => + recordingId.toText() !== recording.id.toText() + ) + }; - const recording = recordingOpt[0]; + users.insert(updatedUser.id, updatedUser); - const userOpt = this.users.get(recording.userId); + recordings.remove(id); - if (userOpt.length === 0) { return { - Err: AudioRecorderError.create({ - UserDoesNotExist: recording.userId - }) + Ok: recording }; } - - const user = userOpt[0]; - - const updatedUser: User = { - ...user, - recordingIds: user.recordingIds.filter( - (recordingId) => recordingId.toText() !== recording.id.toText() - ) - }; - - this.users.insert(updatedUser.id, updatedUser); - - this.recordings.remove(id); - - return { - Ok: recording - }; - } -} + ) +}); function generateId(): Principal { const randomBytes = new Array(29) diff --git a/examples/service/src/index.ts b/examples/service/src/index.ts index 6b931e575e..c62cbb1770 100644 --- a/examples/service/src/index.ts +++ b/examples/service/src/index.ts @@ -1,42 +1,25 @@ -import { - candid, - ic, - Principal, - query, - Record, - Service, - text, - update, - Vec -} from 'azle'; - +import { ic, Principal, query, Record, Service, text, update, Vec } from 'azle'; import SomeService from './some_service'; -class Wrapper extends Record { - @candid(SomeService) - someService: SomeService; -} +const Wrapper = Record({ + someService: SomeService +}); -export default class extends Service { - @query([SomeService], SomeService) - serviceParam(someService: SomeService): SomeService { +export default Service({ + serviceParam: query([SomeService], SomeService, (someService) => { return someService; - } - - @query([], SomeService) - serviceReturnType(): SomeService { - return new SomeService( + }), + serviceReturnType: query([], SomeService, () => { + return SomeService( Principal.fromText( process.env.SOME_SERVICE_PRINCIPAL ?? ic.trap('process.env.SOME_SERVICE_PRINCIPAL is undefined') ) ); - } - - @update([], Wrapper) - serviceNestedReturnType(): Wrapper { + }), + serviceNestedReturnType: update([], Wrapper, () => { return { - someService: new SomeService( + someService: SomeService( Principal.fromText( process.env.SOME_SERVICE_PRINCIPAL ?? ic.trap( @@ -45,15 +28,19 @@ export default class extends Service { ) ) }; - } - - @update([Vec(SomeService)], Vec(SomeService)) - serviceList(someServices: Vec): Vec { - return someServices; - } - - @update([SomeService], text) - async serviceCrossCanisterCall(someService: SomeService): Promise { - return await ic.call(someService.update1); - } -} + }), + serviceList: update( + [Vec(SomeService)], + Vec(SomeService), + (someServices) => { + return someServices; + } + ), + serviceCrossCanisterCall: update( + [SomeService], + text, + async (someService) => { + return await ic.call(someService.update1); + } + ) +}); diff --git a/examples/service/src/some_service.ts b/examples/service/src/some_service.ts index f815dbc8a4..39a9a7e74c 100644 --- a/examples/service/src/some_service.ts +++ b/examples/service/src/some_service.ts @@ -1,13 +1,10 @@ import { bool, query, Service, text, update } from 'azle'; -export default class extends Service { - @query([], bool) - query1(): bool { +export default Service({ + query1: query([], bool, () => { return true; - } - - @update([], text) - update1(): text { + }), + update1: update([], text, () => { return 'SomeService update1'; - } -} + }) +}); diff --git a/src/compiler/typescript_to_rust/azle_generate_rearchitecture/src/main.rs b/src/compiler/typescript_to_rust/azle_generate_rearchitecture/src/main.rs index 44e0e24370..4c0b8ef8c4 100644 --- a/src/compiler/typescript_to_rust/azle_generate_rearchitecture/src/main.rs +++ b/src/compiler/typescript_to_rust/azle_generate_rearchitecture/src/main.rs @@ -280,7 +280,8 @@ fn main() -> Result<(), String> { let exports = global.get_property("exports").unwrap(); let canister_methods = exports.get_property("canisterMethods").unwrap(); let callbacks = canister_methods.get_property("callbacks").unwrap(); - let method = callbacks.get_property(function_name).unwrap(); + let methodCallbacks = callbacks.get_property(function_name).unwrap(); + let canisterMethodCallback = methodCallbacks.get_property("canisterCallback").unwrap(); let candid_args = if pass_arg_data { ic_cdk::api::call::arg_data_raw() } else { vec![] }; @@ -288,7 +289,7 @@ fn main() -> Result<(), String> { let candid_args_js_value_ref = to_qjs_value(&context, &candid_args_js_value).unwrap(); // TODO I am not sure what the first parameter to call is supposed to be - method.call(&method, &[candid_args_js_value_ref]).unwrap(); + canisterMethodCallback.call(&canisterMethodCallback, &[candid_args_js_value_ref]).unwrap(); context.execute_pending().unwrap(); }); diff --git a/src/compiler/typescript_to_rust/generate_rust_canister.ts b/src/compiler/typescript_to_rust/generate_rust_canister.ts index 813a5c8e06..1e26babf60 100644 --- a/src/compiler/typescript_to_rust/generate_rust_canister.ts +++ b/src/compiler/typescript_to_rust/generate_rust_canister.ts @@ -37,7 +37,10 @@ export function generateRustCanister( ts_root: join(process.cwd(), canisterConfig.ts), alias_tables: aliasTables, alias_lists: aliasLists, - canister_methods: canisterMethods + // TODO The spread is because canisterMethods is a function with properties + canister_methods: { + ...canisterMethods + } // TODO we should probably just grab the props out that we need }; const compilerInfoPath = join( diff --git a/src/lib_functional/candid/index.ts b/src/lib_functional/candid/index.ts index e990146b82..38b672b806 100644 --- a/src/lib_functional/candid/index.ts +++ b/src/lib_functional/candid/index.ts @@ -38,7 +38,9 @@ import { empty, AzleBool, bool, - AzlePrincipal + AzlePrincipal, + AzleResult, + Result } from '../../lib_new'; export type TypeMapping = T extends IDL.TextClass @@ -75,6 +77,8 @@ export type TypeMapping = T extends IDL.TextClass ? TypeMapping[] : T extends AzleOpt ? [TypeMapping] | [] + : T extends AzleResult + ? Result : T extends AzleBlob ? blob : T extends AzlePrincipal diff --git a/src/lib_functional/candid/reference/service.ts b/src/lib_functional/candid/reference/service.ts index a5a1a72e67..186bf58a68 100644 --- a/src/lib_functional/candid/reference/service.ts +++ b/src/lib_functional/candid/reference/service.ts @@ -1,5 +1,10 @@ import { Principal, TypeMapping } from '../../'; -import { serviceCall } from '../../../lib_new'; +import { IDL, ServiceFunctionInfo, serviceCall } from '../../../lib_new'; +import { + Parent, + toParamIDLTypes, + toReturnIDLType +} from '../../../lib_new/utils'; import { CanisterMethodInfo } from '../../canister_methods'; type ServiceOptions = { @@ -17,32 +22,45 @@ type ServiceReturn = { : never; }; +type CallableObject = { + (principal: Principal): CallableObject; +} & ServiceReturn; + export function Service( - serviceOptions: T, - principal?: Principal -): ServiceReturn { + serviceOptions: T +): CallableObject { const callbacks = Object.entries(serviceOptions).reduce((acc, entry) => { const key = entry[0]; const value = entry[1]; - if (value.callback === undefined) { - return { - ...acc, - [key]: (...args: any[]) => { + // if (principal === undefined) { + // return { + // ...acc, + // [key]: (...args: any[]) => { + // return serviceCall( + // principal as any, + // key, + // value.paramsIdls, + // value.returnIdl + // )(...args); + // } + // }; + // } else { + return { + ...acc, + [key]: { + canisterCallback: value.callback, + crossCanisterCallback: (...args: any[]) => { return serviceCall( - principal as any, + this.principal as any, key, value.paramsIdls, value.returnIdl )(...args); } - }; - } else { - return { - ...acc, - [key]: value.callback - }; - } + } + }; + // } }, {}); const candidTypes = Object.values(serviceOptions).reduce( @@ -57,7 +75,7 @@ export function Service( const key = entry[0]; const value = entry[1]; - return value.type === 'query'; + return value.mode === 'query'; }) .map((entry) => { const key = entry[0]; @@ -73,7 +91,7 @@ export function Service( const key = entry[0]; const value = entry[1]; - return value.type === 'update'; + return value.mode === 'update'; }) .map((entry) => { const key = entry[0]; @@ -84,22 +102,134 @@ export function Service( }; }); - // TODO loop through each key and simply grab the candid off - // TODO grab the init/post_upgrade candid as well - return { - candid: `${ - candidTypes.length === 0 ? '' : candidTypes.join('\n') + '\n' - }service: () -> { + let returnFunction = (principal: Principal) => { + const callbacks = Object.entries(serviceOptions).reduce( + (acc, entry) => { + const key = entry[0]; + const value = entry[1]; + + // if (principal === undefined) { + // return { + // ...acc, + // [key]: (...args: any[]) => { + // return serviceCall( + // principal as any, + // key, + // value.paramsIdls, + // value.returnIdl + // )(...args); + // } + // }; + // } else { + return { + ...acc, + [key]: { + canisterCallback: value.callback, + crossCanisterCallback: (...args: any[]) => { + return serviceCall( + principal as any, + key, + value.paramsIdls, + value.returnIdl + )(...args); + } + } + }; + // } + }, + {} + ); + + return { + ...callbacks, + principal + }; + }; + + returnFunction.candid = `${ + candidTypes.length === 0 ? '' : candidTypes.join('\n') + '\n' + }service: () -> { ${Object.entries(serviceOptions) .map((entry) => { return `${entry[0]}: ${entry[1].candid}`; }) .join('\n ')} } -`, - queries, - updates, - callbacks, - ...callbacks // TODO then we can't use any names that could collide in this object - } as any; +`; + + returnFunction.queries = queries; + returnFunction.updates = updates; + returnFunction.callbacks = callbacks; + returnFunction.getIDL = (parents: Parent[]): IDL.ServiceClass => { + const serviceFunctionInfo: ServiceFunctionInfo = serviceOptions; + + const record = Object.entries(serviceFunctionInfo).reduce( + (accumulator, [methodName, functionInfo]) => { + const paramRealIdls = toParamIDLTypes(functionInfo.paramsIdls); + const returnRealIdl = toReturnIDLType(functionInfo.returnIdl); + + const annotations = + functionInfo.mode === 'update' ? [] : ['query']; + + return { + ...accumulator, + [methodName]: IDL.Func( + paramRealIdls, + returnRealIdl, + annotations + ) + }; + }, + {} as Record + ); + + return IDL.Service(record); + }; + + return returnFunction; + + // TODO loop through each key and simply grab the candid off + // TODO grab the init/post_upgrade candid as well + // return { + // candid: `${ + // candidTypes.length === 0 ? '' : candidTypes.join('\n') + '\n' + // }service: () -> { + // ${Object.entries(serviceOptions) + // .map((entry) => { + // return `${entry[0]}: ${entry[1].candid}`; + // }) + // .join('\n ')} + // } + // `, + // queries, + // updates, + // callbacks, + // principal, + // ...callbacks, // TODO then we can't use any names that could collide in this object + // getIDL(parents: Parent[]): IDL.ServiceClass { + // const serviceFunctionInfo: ServiceFunctionInfo = serviceOptions; + + // const record = Object.entries(serviceFunctionInfo).reduce( + // (accumulator, [methodName, functionInfo]) => { + // const paramRealIdls = toParamIDLTypes(functionInfo.paramsIdls); + // const returnRealIdl = toReturnIDLType(functionInfo.returnIdl); + + // const annotations = + // functionInfo.mode === 'update' ? [] : ['query']; + + // return { + // ...accumulator, + // [methodName]: IDL.Func( + // paramRealIdls, + // returnRealIdl, + // annotations + // ) + // }; + // }, + // {} as Record + // ); + + // return IDL.Service(record); + // } + // } as any; } diff --git a/src/lib_functional/canister_methods/index.ts b/src/lib_functional/canister_methods/index.ts index 1b62b957a9..fdfb8e0af3 100644 --- a/src/lib_functional/canister_methods/index.ts +++ b/src/lib_functional/canister_methods/index.ts @@ -1,12 +1,16 @@ import { IDL } from '../../lib_new/index'; import { ic } from '../../lib_new/ic'; import { TypeMapping } from '..'; +import { + DecodeVisitor, + EncodeVisitor +} from '../../lib_new/visitors/encode_decode'; export * from './query'; export * from './update'; export type CanisterMethodInfo, K> = { - type: 'query' | 'update'; + mode: 'query' | 'update'; callback?: (...args: any) => any; candid: string; candidTypes: string[]; @@ -22,11 +26,20 @@ export function executeMethod( paramCandid: any, returnCandid: any, args: any[], - callback: any + callback: any, + paramsIdls: any[], + returnIdl: any ) { const decoded = IDL.decode(paramCandid[0] as any, args[0]); - const result = callback(...decoded); + const myDecodedObject = paramCandid[0].map((idl: any, index: any) => { + return idl.accept(new DecodeVisitor(), { + js_class: paramsIdls[index], + js_data: decoded[index] + }); + }); + + const result = callback(...myDecodedObject); if ( result !== undefined && @@ -40,7 +53,13 @@ export function executeMethod( console.log(`final instructions: ${ic.instructionCounter()}`); // if (!manual) { - const encodeReadyResult = result === undefined ? [] : [result]; + // const encodeReadyResult = result === undefined ? [] : [result]; + const encodeReadyResult = returnCandid[0].map((idl: any) => { + return idl.accept(new EncodeVisitor(), { + js_class: returnIdl, + js_data: result + }); + }); const encoded = IDL.encode( returnCandid[0] as any, encodeReadyResult @@ -52,7 +71,13 @@ export function executeMethod( ic.trap(error.toString()); }); } else { - const encodeReadyResult = result === undefined ? [] : [result]; + // const encodeReadyResult = result === undefined ? [] : [result]; + const encodeReadyResult = returnCandid[0].map((idl: any) => { + return idl.accept(new EncodeVisitor(), { + js_class: returnIdl, + js_data: result + }); + }); // if (!manual) { const encoded = IDL.encode(returnCandid[0] as any, encodeReadyResult); diff --git a/src/lib_functional/canister_methods/query.ts b/src/lib_functional/canister_methods/query.ts index aa2bc11b52..25565892d7 100644 --- a/src/lib_functional/canister_methods/query.ts +++ b/src/lib_functional/canister_methods/query.ts @@ -23,15 +23,23 @@ export function query< paramCandid[2] ); + // TODO maybe the cross canister callback should be made here? const finalCallback = callback === undefined ? undefined : (...args: any[]) => { - executeMethod(paramCandid, returnCandid, args, callback); + executeMethod( + paramCandid, + returnCandid, + args, + callback, + paramsIdls as any, + returnIdl + ); }; return { - type: 'query', + mode: 'query', callback: finalCallback, candid: `(${paramCandid[1].join(', ')}) -> (${returnCandid[1]}) query;`, candidTypes: newTypesToStingArr(returnCandid[2]), diff --git a/src/lib_functional/canister_methods/update.ts b/src/lib_functional/canister_methods/update.ts index 9e109ec5c9..42f110ba0a 100644 --- a/src/lib_functional/canister_methods/update.ts +++ b/src/lib_functional/canister_methods/update.ts @@ -27,11 +27,18 @@ export function update< callback === undefined ? undefined : (...args: any[]) => { - executeMethod(paramCandid, returnCandid, args, callback); + executeMethod( + paramCandid, + returnCandid, + args, + callback, + paramsIdls as any, + returnIdl + ); }; return { - type: 'update', + mode: 'update', callback: finalCallback, candid: `(${paramCandid[1].join(', ')}) -> (${returnCandid[1]});`, candidTypes: newTypesToStingArr(returnCandid[2]), diff --git a/src/lib_functional/index.ts b/src/lib_functional/index.ts index 7cd2820650..e603c297a9 100644 --- a/src/lib_functional/index.ts +++ b/src/lib_functional/index.ts @@ -2,3 +2,5 @@ export * from './canister_methods'; export * from './candid'; export * from '../lib_new/primitives'; export * from '../lib_new/ic'; +export * from '../lib_new/stable_b_tree_map'; +export * from '../lib_new/result'; diff --git a/src/lib_new/ic.ts b/src/lib_new/ic.ts index 86966bc456..a585e50695 100644 --- a/src/lib_new/ic.ts +++ b/src/lib_new/ic.ts @@ -455,7 +455,7 @@ export const ic: Ic = globalThis._azleIc return new Uint8Array(globalThis._azleIc.argDataRaw()); }, call: (method, config) => { - return method( + return method.crossCanisterCallback( '_AZLE_CROSS_CANISTER_CALL', false, ic.callRaw, diff --git a/src/lib_new/result.ts b/src/lib_new/result.ts index ab4d195967..ecefbd1846 100644 --- a/src/lib_new/result.ts +++ b/src/lib_new/result.ts @@ -2,7 +2,7 @@ import { RequireExactlyOne } from '../lib/candid_types/variant'; import { IDL } from './index'; import { CandidClass, Parent, toIDLType } from './utils'; -export class AzleResult { +export class AzleResult { constructor(ok: any, err: any) { this._azleOk = ok; this._azleErr = err; diff --git a/src/lib_new/stable_b_tree_map.ts b/src/lib_new/stable_b_tree_map.ts index 3cb1bc82b8..5583eaf250 100644 --- a/src/lib_new/stable_b_tree_map.ts +++ b/src/lib_new/stable_b_tree_map.ts @@ -1,235 +1,245 @@ +import { TypeMapping } from '../lib_functional'; import { IDL, nat8, nat64, Opt } from './index'; -import { CandidClass, toIDLType } from './utils'; +import { toIDLType } from './utils'; -// TODO something like this is how we would do the inference -// TODO but there seems to be a problem with the fixed int and nat classes -// TODO there is no way that I can see to distinguish between int64/nat64 and lower types -// TODO which is necessary for inferring bigint or number correctly -// type IDLToCandid = T extends nat64 ? bigint : T extends nat32 ? number : T; +export function StableBTreeMap( + key: Key, + value: Value, + memoryId: nat8 +) { + const keyIdl = toIDLType(key, []); + const valueIdl = toIDLType(value, []); -export class StableBTreeMap { - keyIdl: IDL.Type; - valueIdl: IDL.Type; - memoryId: nat8; - - constructor(keyIdl: CandidClass, valueIdl: CandidClass, memoryId: nat8) { - this.keyIdl = toIDLType(keyIdl, []); - this.valueIdl = toIDLType(valueIdl, []); - this.memoryId = memoryId; - - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [this.memoryId]) - ).buffer; + const candidEncodedMemoryId = new Uint8Array( + IDL.encode([IDL.Nat8], [memoryId]) + ).buffer; + if ((globalThis as any)._azleIc !== undefined) { (globalThis as any)._azleIc.stableBTreeMapInit(candidEncodedMemoryId); } - /** - * Checks if the given key exists in the map. - * @param key the key to check. - * @returns `true` if the key exists in the map, `false` otherwise. - */ - containsKey(key: Key): boolean { - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [this.memoryId]) - ).buffer; - - const candidEncodedKey = new Uint8Array( - IDL.encode([this.keyIdl as any], [key]) - ).buffer; - - return (globalThis as any)._azleIc.stableBTreeMapContainsKey( - candidEncodedMemoryId, - candidEncodedKey - ); - } + return { + /** + * Checks if the given key exists in the map. + * @param key the key to check. + * @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; + + return (globalThis as any)._azleIc.stableBTreeMapContainsKey( + candidEncodedMemoryId, + candidEncodedKey + ); + }, + /** + * Retrieves the value stored at the provided key. + * @param key the location from which to retrieve. + * @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 candidEncodedValue = ( + globalThis as any + )._azleIc.stableBTreeMapGet( + candidEncodedMemoryId, + candidEncodedKey + ); - /** - * Retrieves the value stored at the provided key. - * @param key the location from which to retrieve. - * @returns the value associated with the given key, if it exists. - */ - get(key: Key): Opt { - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [this.memoryId]) - ).buffer; - - const candidEncodedKey = new Uint8Array( - IDL.encode([this.keyIdl as any], [key]) - ).buffer; - - const candidEncodedValue = ( - globalThis as any - )._azleIc.stableBTreeMapGet(candidEncodedMemoryId, candidEncodedKey); - - if (candidEncodedValue === undefined) { - return []; - } else { - const candidDecodedValue = IDL.decode( - [this.valueIdl as any], + if (candidEncodedValue === undefined) { + return []; + } else { + const candidDecodedValue = IDL.decode( + [valueIdl as any], + candidEncodedValue + )[0]; + + return [candidDecodedValue as any]; + } + }, + /** + * Inserts a value into the map at the provided key. + * @param key the location at which to insert. + * @param value the value to insert. + * @returns the previous value of the key, if present. + */ + insert( + key: TypeMapping, + value: TypeMapping + ): Opt> { + console.log('keyIdl'); + console.log(keyIdl); + console.log('valueIdl'); + console.log(valueIdl); + + const candidEncodedMemoryId = new Uint8Array( + IDL.encode([IDL.Nat8], [memoryId]) + ).buffer; + + console.log(0); + + const candidEncodedKey = new Uint8Array( + IDL.encode([keyIdl as any], [key]) + ).buffer; + + console.log(1); + + const candidEncodedValue = new Uint8Array( + IDL.encode([valueIdl as any], [value]) + ).buffer; + + console.log(2); + + const candidEncodedResultValue = ( + globalThis as any + )._azleIc.stableBTreeMapInsert( + candidEncodedMemoryId, + candidEncodedKey, candidEncodedValue - )[0]; - - return [candidDecodedValue as any]; - } - } - - /** - * Inserts a value into the map at the provided key. - * @param key the location at which to insert. - * @param value the value to insert. - * @returns the previous value of the key, if present. - */ - insert(key: Key, value: Value): Opt { - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [this.memoryId]) - ).buffer; - - const candidEncodedKey = new Uint8Array( - IDL.encode([this.keyIdl as any], [key]) - ).buffer; - - const candidEncodedValue = new Uint8Array( - IDL.encode([this.valueIdl as any], [value]) - ).buffer; - - const candidEncodedResultValue = ( - globalThis as any - )._azleIc.stableBTreeMapInsert( - candidEncodedMemoryId, - candidEncodedKey, - candidEncodedValue - ); - - if (candidEncodedResultValue === undefined) { - return []; - } else { - const candidDecodedValue = IDL.decode( - [this.valueIdl as any], - candidEncodedResultValue ); - return [candidDecodedValue as any]; - } - } - - /** - * Checks if the map is empty. - * @returns `true` if the map contains no elements, `false` otherwise. - */ - isEmpty(): boolean { - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [this.memoryId]) - ).buffer; - - return (globalThis as any)._azleIc.stableBTreeMapIsEmpty( - candidEncodedMemoryId - ); - } - - /** - * Retrieves the items in the map in sorted order. - * @returns tuples representing key/value pairs. - */ - items(): [Key, Value][] { - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [this.memoryId]) - ).buffer; - - const candidEncodedItems = ( - globalThis as any - )._azleIc.stableBTreeMapItems(candidEncodedMemoryId); - - // TODO too much copying - return candidEncodedItems.map((candidEncodedItem: any) => { - return [ - IDL.decode([this.keyIdl as any], candidEncodedItem[0])[0], - IDL.decode([this.valueIdl as any], candidEncodedItem[1])[0] - ]; - }); - } - - /** - * The keys for each element in the map in sorted order. - * @returns they keys in the map. - */ - keys(): Key[] { - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [this.memoryId]) - ).buffer; - - const candidEncodedKeys = ( - globalThis as any - )._azleIc.stableBTreeMapKeys(candidEncodedMemoryId); - - // TODO too much copying - return candidEncodedKeys.map((candidEncodedKey: any) => { - return IDL.decode([this.keyIdl as any], candidEncodedKey)[0]; - }); - } - - /** - * Checks to see how many elements are in the map. - * @returns the number of elements in the map. - */ - len(): nat64 { - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [this.memoryId]) - ).buffer; - - const candidEncodedLen = (globalThis as any)._azleIc.stableBTreeMapLen( - candidEncodedMemoryId - ); - - return IDL.decode([IDL.Nat64], candidEncodedLen)[0] as any; - } - - /** - * Removes a key from the map. - * @param key the location from which to remove. - * @returns the previous value at the key if it exists, `null` otherwise. - */ - remove(key: Key): Opt { - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [this.memoryId]) - ).buffer; - - const candidEncodedKey = new Uint8Array( - IDL.encode([this.keyIdl as any], [key]) - ).buffer; - - const candidEncodedValue = ( - globalThis as any - )._azleIc.stableBTreeMapRemove(candidEncodedMemoryId, candidEncodedKey); - - if (candidEncodedValue === undefined) { - return []; - } else { - const candidDecodedValue = IDL.decode( - [this.valueIdl as any], - candidEncodedValue - )[0]; + console.log(3); + + if (candidEncodedResultValue === undefined) { + return []; + } else { + const candidDecodedValue = IDL.decode( + [valueIdl as any], + candidEncodedResultValue + ); + + return [candidDecodedValue as any]; + } + }, + /** + * Checks if the map is empty. + * @returns `true` if the map contains no elements, `false` otherwise. + */ + isEmpty(): boolean { + const candidEncodedMemoryId = new Uint8Array( + IDL.encode([IDL.Nat8], [memoryId]) + ).buffer; + + return (globalThis as any)._azleIc.stableBTreeMapIsEmpty( + candidEncodedMemoryId + ); + }, + /** + * Retrieves the items in the map in sorted order. + * @returns tuples representing key/value pairs. + */ + items(): [TypeMapping, TypeMapping][] { + const candidEncodedMemoryId = new Uint8Array( + IDL.encode([IDL.Nat8], [memoryId]) + ).buffer; + + const candidEncodedItems = ( + globalThis as any + )._azleIc.stableBTreeMapItems(candidEncodedMemoryId); + + // 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] + ]; + }); + }, + /** + * The keys for each element in the map in sorted order. + * @returns they keys in the map. + */ + keys(): TypeMapping[] { + const candidEncodedMemoryId = new Uint8Array( + IDL.encode([IDL.Nat8], [memoryId]) + ).buffer; + + const candidEncodedKeys = ( + globalThis as any + )._azleIc.stableBTreeMapKeys(candidEncodedMemoryId); + + // TODO too much copying + return candidEncodedKeys.map((candidEncodedKey: any) => { + return IDL.decode([keyIdl as any], candidEncodedKey)[0]; + }); + }, + /** + * Checks to see how many elements are in the map. + * @returns the number of elements in the map. + */ + len(): nat64 { + const candidEncodedMemoryId = new Uint8Array( + IDL.encode([IDL.Nat8], [memoryId]) + ).buffer; + + const candidEncodedLen = ( + globalThis as any + )._azleIc.stableBTreeMapLen(candidEncodedMemoryId); + + return IDL.decode([IDL.Nat64], candidEncodedLen)[0] as any; + }, + /** + * Removes a key from the map. + * @param key the location from which to remove. + * @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 candidEncodedValue = ( + globalThis as any + )._azleIc.stableBTreeMapRemove( + candidEncodedMemoryId, + candidEncodedKey + ); - return [candidDecodedValue as any]; + if (candidEncodedValue === undefined) { + return []; + } else { + const candidDecodedValue = IDL.decode( + [valueIdl as any], + candidEncodedValue + )[0]; + + return [candidDecodedValue as any]; + } + }, + /** + * The values in the map in sorted order. + * @returns the values in the map. + */ + values(): TypeMapping[] { + const candidEncodedMemoryId = new Uint8Array( + IDL.encode([IDL.Nat8], [memoryId]) + ).buffer; + + const candidEncodedValues = ( + globalThis as any + )._azleIc.stableBTreeMapValues(candidEncodedMemoryId); + + // TODO too much copying + return candidEncodedValues.map((candidEncodedValue: any) => { + return IDL.decode([valueIdl as any], candidEncodedValue)[0]; + }); } - } - - /** - * The values in the map in sorted order. - * @returns the values in the map. - */ - values(): Value[] { - const candidEncodedMemoryId = new Uint8Array( - IDL.encode([IDL.Nat8], [this.memoryId]) - ).buffer; - - const candidEncodedValues = ( - globalThis as any - )._azleIc.stableBTreeMapValues(candidEncodedMemoryId); - - // TODO too much copying - return candidEncodedValues.map((candidEncodedValue: any) => { - return IDL.decode([this.valueIdl as any], candidEncodedValue)[0]; - }); - } + }; } diff --git a/src/lib_new/visitors/encode_decode/decode_visitor.ts b/src/lib_new/visitors/encode_decode/decode_visitor.ts index 852bab5662..2c91d8342f 100644 --- a/src/lib_new/visitors/encode_decode/decode_visitor.ts +++ b/src/lib_new/visitors/encode_decode/decode_visitor.ts @@ -18,7 +18,7 @@ import { export class DecodeVisitor extends IDL.Visitor { visitService(t: IDL.ServiceClass, data: VisitorData): VisitorResult { - return new data.js_class(data.js_data); + return data.js_class(data.js_data); } visitFunc(t: IDL.FuncClass, data: VisitorData): VisitorResult { return new data.js_class(data.js_data[0], data.js_data[1]); diff --git a/src/lib_new/visitors/encode_decode/encode_visitor.ts b/src/lib_new/visitors/encode_decode/encode_visitor.ts index ba12ab7dcd..752c1063b8 100644 --- a/src/lib_new/visitors/encode_decode/encode_visitor.ts +++ b/src/lib_new/visitors/encode_decode/encode_visitor.ts @@ -19,7 +19,7 @@ import { export class EncodeVisitor extends IDL.Visitor { visitService(t: IDL.ServiceClass, data: VisitorData): VisitorResult { - return data.js_data.canisterId; + return data.js_data.principal; } visitFunc(t: IDL.FuncClass, data: VisitorData): VisitorResult { return [data.js_data.principal, data.js_data.name]; diff --git a/src/lib_new/visitors/encode_decode/index.ts b/src/lib_new/visitors/encode_decode/index.ts index 78d84715fd..c189d78c67 100644 --- a/src/lib_new/visitors/encode_decode/index.ts +++ b/src/lib_new/visitors/encode_decode/index.ts @@ -59,13 +59,12 @@ export function visitVec( if (ty instanceof IDL.PrimitiveType) { return data.js_data; } - const vec_elems = data.js_data.map((array_elem: any) => { + return data.js_data.map((array_elem: any) => { return ty.accept(visitor, { js_data: array_elem, js_class: data.js_class._azleType }); }); - return [...vec_elems]; } export function visitRecord( @@ -75,7 +74,7 @@ export function visitRecord( ): VisitorResult { const candidFields = fields.reduce((acc, [memberName, memberIdl]) => { const fieldData = data.js_data[memberName]; - const fieldClass = data.js_class._azleCandidMap[memberName]; + const fieldClass = data.js_class[memberName]; return { ...acc, @@ -85,7 +84,8 @@ export function visitRecord( }) }; }, {}); - return data.js_class.create(candidFields); + + return candidFields; } export function visitVariant( @@ -119,7 +119,7 @@ export function visitVariant( } const candidFields = fields.reduce((acc, [memberName, memberIdl]) => { const fieldData = data.js_data[memberName]; - const fieldClass = data.js_class._azleCandidMap[memberName]; + const fieldClass = data.js_class[memberName]; if (fieldData === undefined) { // If the field data is undefined then it is not the variant that was used return acc; @@ -132,7 +132,8 @@ export function visitVariant( }) }; }, {}); - return data.js_class.create(candidFields); + + return candidFields; } export function visitRec(