diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 226bf6d995..ff9bfb0527 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -83,12 +83,10 @@ # These are the ones that don't work # "examples/tuple_types", # "examples/service", -# "examples/outgoing_http_requests", # "examples/robust_imports", # "examples/run_time_errors", # "examples/rust_type_conversions", # "examples/complex_types", -# "examples/composite_queries", # "examples/func_types", # "examples/generics", # "examples/guard_functions", @@ -149,6 +147,7 @@ jobs: "examples/candid_encoding", "examples/counter", "examples/complex_init", + "examples/composite_queries", "examples/cross_canister_calls", "examples/cycles", "examples/ethereum_json_rpc", @@ -180,6 +179,7 @@ jobs: "examples/null_example", "examples/optional_types", "examples/pre_and_post_upgrade", + "examples/outgoing_http_requests", "examples/primitive_types", "examples/principal", "examples/query", diff --git a/examples/composite_queries/canisters/canister1/canister1.did b/examples/composite_queries/canisters/canister1/canister1.did deleted file mode 100644 index ba4506572b..0000000000 --- a/examples/composite_queries/canisters/canister1/canister1.did +++ /dev/null @@ -1,14 +0,0 @@ -type ManualReply = variant { Ok : text; Err : text }; -type ManualReply_1 = variant { Ok : nat; Err : text }; -service : () -> { - deepQuery : () -> (ManualReply) query; - incCanister1 : () -> (ManualReply_1) query; - incCanister2 : () -> (ManualReply_1) query; - incCounter : () -> (nat) query; - manualQuery : () -> (ManualReply) query; - simpleCompositeQuery : () -> (ManualReply) query; - simpleQuery : () -> (ManualReply) query; - simpleUpdate : () -> (ManualReply); - totallyManualQuery : () -> (ManualReply) query; - updateQuery : () -> (ManualReply) query; -} \ No newline at end of file diff --git a/examples/composite_queries/canisters/canister1/canister1.ts b/examples/composite_queries/canisters/canister1/canister1.ts deleted file mode 100644 index f267ef91b2..0000000000 --- a/examples/composite_queries/canisters/canister1/canister1.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { - ic, - Manual, - match, - nat, - Principal, - $query, - Result, - $update -} from 'azle'; -import { Canister1 } from '../canister1/types'; -import { Canister2 } from '../canister2/types'; - -const canister1 = new Canister1( - Principal.fromText( - process.env.CANISTER1_PRINCIPAL ?? - ic.trap('process.env.CANISTER1_PRINCIPAL is undefined') - ) -); - -const canister2 = new Canister2( - Principal.fromText( - process.env.CANISTER2_PRINCIPAL ?? - ic.trap('process.env.CANISTER2_PRINCIPAL is undefined') - ) -); - -let counter: nat = 0n; - -// Composite query calling a query -$query; -export async function simpleCompositeQuery(): Promise> { - return await canister2.simpleQuery().call(); -} - -// Composite query calling a manual query -$query; -export async function manualQuery(): Promise> { - return await canister2.manualQuery().call(); -} - -// Manual composite query calling a manual query -$query; -export async function totallyManualQuery(): Promise< - Manual> -> { - ic.reply(await canister2.manualQuery().call()); -} - -// Composite query calling another composite query -$query; -export async function deepQuery(): Promise> { - const callResult = await canister2.deepQuery().call(); - - return match(callResult, { - Ok: (stringQueryResult) => - match(stringQueryResult, { - Ok: (stringQuery) => ({ Ok: stringQuery }), - Err: (err) => ({ Err: err }) - }), - Err: (err) => ({ Err: err }) - }); -} - -// Composite query calling an update method. SHOULDN'T WORK -$query; -export async function updateQuery(): Promise> { - return await canister2.updateQuery().call(); -} - -// Composite query being called by a query method. SHOULDN'T WORK -$query; -export async function simpleQuery(): Promise> { - return await canister2.simpleQuery().call(); -} - -// Composite query being called by an update method. SHOULDN'T WORK -$update; -export async function simpleUpdate(): Promise> { - const callResult = await canister2.deepQuery().call(); - - return match(callResult, { - Ok: (stringQueryResult) => - match(stringQueryResult, { - Ok: (stringQuery) => ({ Ok: stringQuery }), - Err: (err) => ({ Err: err }) - }), - Err: (err) => ({ Err: err }) - }); -} - -// Composite query that modifies the state. Should revert after the call is done -$query; -export async function incCounter(): Promise { - counter += 1n; - return counter; -} - -// Composite query calling queries on the same canister. SHOULDN'T WORK -$query; -export async function incCanister1(): Promise> { - counter += 1n; - - const canister1AResult = await canister1.incCounter().call(); - - return match(canister1AResult, { - Ok: async (canister1AOk) => { - const canister1BResult = await canister1.incCounter().call(); - - return match(canister1BResult, { - Ok: (canister1BOk) => ({ - Ok: counter + canister1AOk + canister1BOk - }), - Err: (err) => ({ Err: err }) - }); - }, - Err: (err) => ({ Err: err }) - }); -} - -// Composite query calling queries that modify the state -$query; -export async function incCanister2(): Promise> { - counter += 1n; - - const canister2AResult = await canister2.incCounter().call(); - - return match(canister2AResult, { - Ok: async (canister2AOk) => { - const canister2BResult = await canister2.incCounter().call(); - - return match(canister2BResult, { - Ok: (canister2BOk) => ({ - Ok: counter + canister2AOk + canister2BOk - }), - Err: (err) => ({ Err: err }) - }); - }, - Err: (err) => ({ Err: err }) - }); -} diff --git a/examples/composite_queries/canisters/canister1/index.did b/examples/composite_queries/canisters/canister1/index.did new file mode 100644 index 0000000000..c62ac41a3b --- /dev/null +++ b/examples/composite_queries/canisters/canister1/index.did @@ -0,0 +1,12 @@ +service: () -> { + simpleCompositeQuery: () -> (text) query; + manualQuery: () -> (text) query; + totallyManualQuery: () -> (text) query; + deepQuery: () -> (text) query; + updateQuery: () -> (text) query; + simpleQuery: () -> (text) query; + simpleUpdate: () -> (text); + incCounter: () -> (nat) query; + incCanister1: () -> (nat) query; + incCanister2: () -> (nat) query; +} diff --git a/examples/composite_queries/canisters/canister1/index.ts b/examples/composite_queries/canisters/canister1/index.ts new file mode 100644 index 0000000000..ff07b129a5 --- /dev/null +++ b/examples/composite_queries/canisters/canister1/index.ts @@ -0,0 +1,89 @@ +import { ic, Manual, nat, Principal, query, Service, text, update } from 'azle'; +import Canister2 from '../canister2'; + +class Canister1 extends Service { + canister2 = new Canister2( + Principal.fromText( + process.env.CANISTER2_PRINCIPAL ?? + ic.trap('process.env.CANISTER2_PRINCIPAL is undefined') + ) + ); + + counter: nat = 0n; + + // Composite query calling a query + @query([], text) + async simpleCompositeQuery(): Promise { + return await ic.call(this.canister2.simpleQuery); + } + + // Composite query calling a manual query + @query([], text) + async manualQuery(): Promise { + return await ic.call(this.canister2.manualQuery); + } + + // Manual composite query calling a manual query + @query([], text, { manual: true }) + async totallyManualQuery(): Promise> { + ic.reply(await ic.call(this.canister2.manualQuery), text); + } + + // Composite query calling another composite query + @query([], text) + async deepQuery(): Promise { + return await ic.call(this.canister2.deepQuery); + } + + // Composite query calling an update method. SHOULDN'T WORK + @query([], text) + async updateQuery(): Promise { + return await ic.call(this.canister2.updateQuery); + } + + // Composite query being called by a query method. SHOULDN'T WORK + @query([], text) + async simpleQuery(): Promise { + return await ic.call(this.canister2.simpleQuery); + } + + // Composite query being called by an update method. SHOULDN'T WORK + @update([], text) + async simpleUpdate(): Promise { + return await ic.call(this.canister2.deepQuery); + } + + // Composite query that modifies the state. Should revert after the call is done + @query([], nat) + async incCounter(): Promise { + this.counter += 1n; + + return this.counter; + } + + // Composite query calling queries on the same canister + @query([], nat) + async incCanister1(): Promise { + this.counter += 1n; + + const canister1AResult = await ic.call(this.incCounter); + + const canister1BResult = await ic.call(this.incCounter); + + return this.counter + canister1AResult + canister1BResult; + } + + // Composite query calling queries that modify the state + @query([], nat) + async incCanister2(): Promise { + this.counter += 1n; + + const canister2AResult = await ic.call(this.canister2.incCounter); + + const canister2BResult = await ic.call(this.canister2.incCounter); + + return this.counter + canister2AResult + canister2BResult; + } +} + +export default Canister1; diff --git a/examples/composite_queries/canisters/canister1/types.ts b/examples/composite_queries/canisters/canister1/types.ts deleted file mode 100644 index 55b2d5d5d7..0000000000 --- a/examples/composite_queries/canisters/canister1/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { CallResult, nat, Service, serviceQuery } from 'azle'; - -export class Canister1 extends Service { - @serviceQuery - incCounter: () => CallResult; -} diff --git a/examples/composite_queries/canisters/canister2/canister2.did b/examples/composite_queries/canisters/canister2/canister2.did deleted file mode 100644 index 7329fcc8ea..0000000000 --- a/examples/composite_queries/canisters/canister2/canister2.did +++ /dev/null @@ -1,8 +0,0 @@ -type ManualReply = variant { Ok : text; Err : text }; -service : () -> { - deepQuery : () -> (ManualReply) query; - incCounter : () -> (nat) query; - manualQuery : () -> (text) query; - simpleQuery : () -> (text) query; - updateQuery : () -> (text); -} \ No newline at end of file diff --git a/examples/composite_queries/canisters/canister2/canister2.ts b/examples/composite_queries/canisters/canister2/canister2.ts deleted file mode 100644 index 18b320a84e..0000000000 --- a/examples/composite_queries/canisters/canister2/canister2.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ic, Manual, nat, Principal, $query, Result, $update } from 'azle'; -import { Canister3 } from '../canister3/types'; - -const canister3 = new Canister3( - Principal.fromText( - process.env.CANISTER3_PRINCIPAL ?? - ic.trap('process.env.CANISTER3_PRINCIPAL is undefined') - ) -); - -let counter: nat = 0n; - -// TODO is this supposed to be a query? -$query; -export async function incCounter(): Promise { - counter += 1n; - return counter; -} - -$query; -export function simpleQuery(): string { - return 'Hello from Canister 2'; -} - -$update; -export function updateQuery(): string { - return 'Hello from a Canister 2 update'; -} - -$query; -export function manualQuery(): Manual { - ic.reply('Hello from Canister 2 manual query'); -} - -$query; -export async function deepQuery(): Promise> { - return await canister3.deepQuery().call(); -} diff --git a/examples/composite_queries/canisters/canister2/index.did b/examples/composite_queries/canisters/canister2/index.did new file mode 100644 index 0000000000..779ccf764d --- /dev/null +++ b/examples/composite_queries/canisters/canister2/index.did @@ -0,0 +1,7 @@ +service: () -> { + incCounter: () -> (nat) query; + simpleQuery: () -> (text) query; + updateQuery: () -> (text); + manualQuery: () -> (text) query; + deepQuery: () -> (text) query; +} diff --git a/examples/composite_queries/canisters/canister2/index.ts b/examples/composite_queries/canisters/canister2/index.ts new file mode 100644 index 0000000000..0c23bd210d --- /dev/null +++ b/examples/composite_queries/canisters/canister2/index.ts @@ -0,0 +1,40 @@ +import { ic, Manual, nat, Principal, query, Service, text, update } from 'azle'; +import Canister3 from '../canister3'; + +export default class extends Service { + canister3 = new Canister3( + Principal.fromText( + process.env.CANISTER3_PRINCIPAL ?? + ic.trap('process.env.CANISTER3_PRINCIPAL is undefined') + ) + ); + + counter: nat = 0n; + + // TODO is this supposed to be a query? + @query([], nat) + async incCounter(): Promise { + this.counter += 1n; + return this.counter; + } + + @query([], text) + simpleQuery(): text { + return 'Hello from Canister 2'; + } + + @update([], text) + updateQuery(): text { + return 'Hello from a Canister 2 update'; + } + + @query([], text, { manual: true }) + manualQuery(): Manual { + ic.reply('Hello from Canister 2 manual query', text); + } + + @query([], text) + async deepQuery(): Promise { + return await ic.call(this.canister3.deepQuery); + } +} diff --git a/examples/composite_queries/canisters/canister2/types.ts b/examples/composite_queries/canisters/canister2/types.ts deleted file mode 100644 index 38535e3296..0000000000 --- a/examples/composite_queries/canisters/canister2/types.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - CallResult, - nat, - Result, - Service, - serviceQuery, - serviceUpdate -} from 'azle'; - -export class Canister2 extends Service { - @serviceQuery - simpleQuery: () => CallResult; - - @serviceQuery - manualQuery: () => CallResult; - - @serviceUpdate - updateQuery: () => CallResult; - - @serviceQuery - deepQuery: () => CallResult>; - - @serviceQuery - incCounter: () => CallResult; -} diff --git a/examples/composite_queries/canisters/canister3/canister3.did b/examples/composite_queries/canisters/canister3/canister3.did deleted file mode 100644 index 58d65d4d45..0000000000 --- a/examples/composite_queries/canisters/canister3/canister3.did +++ /dev/null @@ -1 +0,0 @@ -service : () -> { deepQuery : () -> (text) query } \ No newline at end of file diff --git a/examples/composite_queries/canisters/canister3/canister3.ts b/examples/composite_queries/canisters/canister3/canister3.ts deleted file mode 100644 index dc3bf90cbe..0000000000 --- a/examples/composite_queries/canisters/canister3/canister3.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { $query } from 'azle'; - -$query; -export function deepQuery(): string { - return 'Hello from Canister 3'; -} diff --git a/examples/composite_queries/canisters/canister3/index.did b/examples/composite_queries/canisters/canister3/index.did new file mode 100644 index 0000000000..78eacc0a1e --- /dev/null +++ b/examples/composite_queries/canisters/canister3/index.did @@ -0,0 +1,3 @@ +service: () -> { + deepQuery: () -> (text) query; +} diff --git a/examples/composite_queries/canisters/canister3/index.ts b/examples/composite_queries/canisters/canister3/index.ts new file mode 100644 index 0000000000..922996147c --- /dev/null +++ b/examples/composite_queries/canisters/canister3/index.ts @@ -0,0 +1,8 @@ +import { Service, query, text } from 'azle'; + +export default class extends Service { + @query([], text) + deepQuery(): text { + return 'Hello from Canister 3'; + } +} diff --git a/examples/composite_queries/canisters/canister3/types.ts b/examples/composite_queries/canisters/canister3/types.ts deleted file mode 100644 index 33dc67858f..0000000000 --- a/examples/composite_queries/canisters/canister3/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { CallResult, Service, serviceQuery } from 'azle'; - -export class Canister3 extends Service { - @serviceQuery - deepQuery: () => CallResult; -} diff --git a/examples/composite_queries/dfx.json b/examples/composite_queries/dfx.json index 881010a286..7fb3b3400a 100644 --- a/examples/composite_queries/dfx.json +++ b/examples/composite_queries/dfx.json @@ -4,21 +4,25 @@ "type": "custom", "build": "npx azle canister1", "root": "canisters/canister1", - "ts": "canisters/canister1/canister1.ts", - "candid": "canisters/canister1/canister1.did", + "ts": "canisters/canister1/index.ts", + "candid": "canisters/canister1/index.did", "wasm": ".azle/canister1/canister1.wasm.gz", "declarations": { "output": "test/dfx_generated/canister1", "node_compatibility": true }, - "env": ["CANISTER1_PRINCIPAL", "CANISTER2_PRINCIPAL"] + "env": [ + "CANISTER1_PRINCIPAL", + "CANISTER2_PRINCIPAL", + "CANISTER3_PRINCIPAL" + ] }, "canister2": { "type": "custom", "build": "npx azle canister2", "root": "canisters/canister2", - "ts": "canisters/canister2/canister2.ts", - "candid": "canisters/canister2/canister2.did", + "ts": "canisters/canister2/index.ts", + "candid": "canisters/canister2/index.did", "wasm": ".azle/canister2/canister2.wasm.gz", "declarations": { "output": "test/dfx_generated/canister2", @@ -30,8 +34,8 @@ "type": "custom", "build": "npx azle canister3", "root": "canisters/canister3", - "ts": "canisters/canister3/canister3.ts", - "candid": "canisters/canister3/canister3.did", + "ts": "canisters/canister3/index.ts", + "candid": "canisters/canister3/index.did", "wasm": ".azle/canister3/canister3.wasm.gz", "declarations": { "output": "test/dfx_generated/canister3", diff --git a/examples/composite_queries/test/tests.ts b/examples/composite_queries/test/tests.ts index de43de06cd..c050655e41 100644 --- a/examples/composite_queries/test/tests.ts +++ b/examples/composite_queries/test/tests.ts @@ -9,7 +9,7 @@ export function get_tests(canister1: ActorSubclass<_SERVICE>): Test[] { test: async () => { const result = await canister1.simpleCompositeQuery(); return { - Ok: 'Ok' in result && result.Ok === 'Hello from Canister 2' + Ok: result === 'Hello from Canister 2' }; } }, @@ -18,9 +18,7 @@ export function get_tests(canister1: ActorSubclass<_SERVICE>): Test[] { test: async () => { const result = await canister1.manualQuery(); return { - Ok: - 'Ok' in result && - result.Ok === 'Hello from Canister 2 manual query' + Ok: result === 'Hello from Canister 2 manual query' }; } }, @@ -29,9 +27,7 @@ export function get_tests(canister1: ActorSubclass<_SERVICE>): Test[] { test: async () => { const result = await canister1.totallyManualQuery(); return { - Ok: - 'Ok' in result && - result.Ok === 'Hello from Canister 2 manual query' + Ok: result === 'Hello from Canister 2 manual query' }; } }, @@ -40,35 +36,48 @@ export function get_tests(canister1: ActorSubclass<_SERVICE>): Test[] { test: async () => { const result = await canister1.deepQuery(); return { - Ok: 'Ok' in result && result.Ok === 'Hello from Canister 3' + Ok: result === 'Hello from Canister 3' }; } }, { name: 'update_query test', test: async () => { - const result = await canister1.updateQuery(); - return { - Ok: - 'Err' in result && - result.Err.includes( - `Rejection code 3, Canister ${getCanisterId( - 'canister2' - )} has no query method` - ) - }; + try { + await canister1.updateQuery(); + return { + Ok: false + }; + } catch (error: any) { + return { + Ok: error + .toString() + .includes( + `Rejection code 3, Canister ${getCanisterId( + 'canister2' + )} has no query method` + ) + }; + } } }, { name: 'simple_update test', test: async () => { - const result = await canister1.simpleUpdate(); - return { - Ok: - 'Err' in result && - result.Err === - 'Rejection code 5, IC0527: Composite query cannot be called in replicated mode' - }; + try { + await canister1.simpleUpdate(); + return { + Ok: false + }; + } catch (error: any) { + return { + Ok: error + .toString() + .includes( + 'Rejection code 5, IC0527: Composite query cannot be called in replicated mode' + ) + }; + } } }, { @@ -77,7 +86,7 @@ export function get_tests(canister1: ActorSubclass<_SERVICE>): Test[] { const result = await canister1.incCanister1(); return { - Ok: 'Ok' in result && result.Ok === 3n + Ok: result === 3n }; } }, @@ -86,7 +95,7 @@ export function get_tests(canister1: ActorSubclass<_SERVICE>): Test[] { test: async () => { const result = await canister1.incCanister2(); return { - Ok: 'Ok' in result && result.Ok === 3n + Ok: result === 3n }; } } diff --git a/examples/optional_types/src/index.ts b/examples/optional_types/src/index.ts index 359788a27e..80415991dd 100644 --- a/examples/optional_types/src/index.ts +++ b/examples/optional_types/src/index.ts @@ -58,7 +58,7 @@ export default class { } @query([], Opt(text)) - getOptNull(): Opt { + getOptNull(): Opt { return []; } diff --git a/examples/outgoing_http_requests/src/index.did b/examples/outgoing_http_requests/src/index.did index 67e9034271..9447f44407 100644 --- a/examples/outgoing_http_requests/src/index.did +++ b/examples/outgoing_http_requests/src/index.did @@ -1,17 +1,14 @@ -type HttpHeader = record { value : text; name : text }; -type HttpResponse = record { - status : nat; - body : vec nat8; - headers : vec HttpHeader; -}; -type HttpTransformArgs = record { context : vec nat8; response : HttpResponse }; -type ManualReply = record { - status : nat; - body : vec nat8; - headers : vec HttpHeader; -}; -service : () -> { - xkcd : () -> (ManualReply); - xkcdRaw : () -> (ManualReply); - xkcdTransform : (HttpTransformArgs) -> (HttpResponse) query; -} \ No newline at end of file +type rec_1 = record {value:text; name:text}; +type rec_0 = record {status:nat; body:vec nat8; headers:vec rec_1}; +type rec_3 = record {value:text; name:text}; +type rec_2 = record {status:nat; body:vec nat8; headers:vec rec_3}; +type rec_6 = record {value:text; name:text}; +type rec_5 = record {status:nat; body:vec nat8; headers:vec rec_6}; +type rec_4 = record {context:vec nat8; response:rec_5}; +type rec_8 = record {value:text; name:text}; +type rec_7 = record {status:nat; body:vec nat8; headers:vec rec_8}; +service: () -> { + xkcd: () -> (rec_0); + xkcdRaw: () -> (rec_2); + xkcdTransform: (rec_4) -> (rec_7) query; +} diff --git a/examples/outgoing_http_requests/src/index.ts b/examples/outgoing_http_requests/src/index.ts index d249b2e979..7f65abad58 100644 --- a/examples/outgoing_http_requests/src/index.ts +++ b/examples/outgoing_http_requests/src/index.ts @@ -1,4 +1,13 @@ -import { ic, Principal, query, update, Service, Some, None } from 'azle'; +import { + ic, + Principal, + query, + update, + Service, + Some, + None, + Manual +} from 'azle'; import { HttpResponse, HttpTransformArgs, @@ -28,9 +37,8 @@ export default class extends Service { }); } - // TODO: Figure out how to do manuals in new syntax // TODO the replica logs give some concerning output: https://forum.dfinity.org/t/fix-me-in-http-outcalls-call-raw/19435 - @query([HttpResponse], HttpResponse, { manual: true }) + @update([], HttpResponse, { manual: true }) async xkcdRaw(): Promise> { const httpResponse = await ic.callRaw( Principal.fromText('aaaaa-aa'), 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 fc1f339166..093bdfd8d2 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 @@ -1,3 +1,4 @@ +use crate::traits::to_ident::ToIdent; use quote::quote; use serde::{Deserialize, Serialize}; use std::{ @@ -5,7 +6,6 @@ use std::{ fs::{self, File}, io::Write, }; -use crate::traits::to_ident::ToIdent; mod ic; mod traits; @@ -14,7 +14,7 @@ mod traits; struct CompilerInfo { file_names: Vec, ts_root: String, - canister_methods: CanisterMethods + canister_methods: CanisterMethods, } #[derive(Debug, Serialize, Deserialize)] @@ -30,7 +30,8 @@ struct CanisterMethods { #[derive(Debug, Serialize, Deserialize)] struct CanisterMethod { - name: String + name: String, + composite: Option, } fn main() -> Result<(), String> { @@ -46,68 +47,86 @@ fn main() -> Result<(), String> { let compiler_info = get_compiler_info(&args[1])?; - let ic = ic::generate(); - let pre_upgrade_method = compiler_info.canister_methods.pre_upgrade.map(|canister_method| { - let rust_function_name = canister_method.name.to_ident(); - let js_function_name = &canister_method.name; - - quote! { - #[ic_cdk_macros::pre_upgrade] - fn #rust_function_name() { - execute_js(#js_function_name, false); - } - } - }); - - let inspect_message_method = compiler_info.canister_methods.inspect_message.map(|canister_method| { - let rust_function_name = canister_method.name.to_ident(); - let js_function_name = &canister_method.name; - - quote! { - #[ic_cdk_macros::inspect_message] - fn #rust_function_name() { - execute_js(#js_function_name, true); + let pre_upgrade_method = compiler_info + .canister_methods + .pre_upgrade + .map(|canister_method| { + let rust_function_name = canister_method.name.to_ident(); + let js_function_name = &canister_method.name; + + quote! { + #[ic_cdk_macros::pre_upgrade] + fn #rust_function_name() { + execute_js(#js_function_name, false); + } } - } - }); - - let heartbeat_method = compiler_info.canister_methods.heartbeat.map(|canister_method| { - let rust_function_name = canister_method.name.to_ident(); - let js_function_name = &canister_method.name; + }); + + let inspect_message_method = + compiler_info + .canister_methods + .inspect_message + .map(|canister_method| { + let rust_function_name = canister_method.name.to_ident(); + let js_function_name = &canister_method.name; + + quote! { + #[ic_cdk_macros::inspect_message] + fn #rust_function_name() { + execute_js(#js_function_name, true); + } + } + }); - quote! { - #[ic_cdk_macros::heartbeat] - fn #rust_function_name() { - execute_js(#js_function_name, false); + let heartbeat_method = compiler_info + .canister_methods + .heartbeat + .map(|canister_method| { + let rust_function_name = canister_method.name.to_ident(); + let js_function_name = &canister_method.name; + + quote! { + #[ic_cdk_macros::heartbeat] + fn #rust_function_name() { + execute_js(#js_function_name, false); + } } - } - }); - - let query_methods = compiler_info.canister_methods.queries.iter().map(|canister_method| { - let rust_function_name = canister_method.name.to_ident(); - let js_function_name = &canister_method.name; - - quote! { - #[ic_cdk_macros::query(manual_reply = true)] - fn #rust_function_name() { - execute_js(#js_function_name, true); + }); + + let query_methods = compiler_info + .canister_methods + .queries + .iter() + .map(|canister_method| { + let rust_function_name = canister_method.name.to_ident(); + let js_function_name = &canister_method.name; + let is_composite = canister_method.composite.unwrap_or(false); + + quote! { + #[ic_cdk_macros::query(manual_reply = true, composite = #is_composite)] + fn #rust_function_name() { + execute_js(#js_function_name, true); + } } - } - }); - - let update_methods = compiler_info.canister_methods.updates.iter().map(|canister_method| { - let rust_function_name = canister_method.name.to_ident(); - let js_function_name = &canister_method.name; - - quote! { - #[ic_cdk_macros::update(manual_reply = true)] - fn #rust_function_name() { - execute_js(#js_function_name, true); + }); + + let update_methods = compiler_info + .canister_methods + .updates + .iter() + .map(|canister_method| { + let rust_function_name = canister_method.name.to_ident(); + let js_function_name = &canister_method.name; + + quote! { + #[ic_cdk_macros::update(manual_reply = true)] + fn #rust_function_name() { + execute_js(#js_function_name, true); + } } - } - }); + }); let lib_file = quote! { #![allow(non_snake_case)] @@ -165,7 +184,7 @@ fn main() -> Result<(), String> { static CONTEXT: RefCell> = RefCell::new(None); static MEMORY_MANAGER_REF_CELL: RefCell> = RefCell::new(MemoryManager::init(DefaultMemoryImpl::default())); - + static STABLE_B_TREE_MAPS: RefCell> = RefCell::new(BTreeMap::new()); } diff --git a/src/compiler/utils/types.ts b/src/compiler/utils/types.ts index 50f9f64f7b..caaacbc2ea 100644 --- a/src/compiler/utils/types.ts +++ b/src/compiler/utils/types.ts @@ -58,6 +58,7 @@ export type CanisterMethods = { // TODO things like guard might also go in here export type CanisterMethod = { name: string; + composite?: boolean; }; export type Plugin = { diff --git a/src/lib_new/ic.ts b/src/lib_new/ic.ts index e9d5048241..7fc84399b5 100644 --- a/src/lib_new/ic.ts +++ b/src/lib_new/ic.ts @@ -737,8 +737,8 @@ export const ic: Ic = globalThis._azleIc .buffer; return globalThis._azleIc.replyRaw(bytes); }, - replyRaw: (counterType: blob) => { - return globalThis._azleIc.replyRaw(counterType.buffer); + replyRaw: (replyBuffer: blob) => { + return globalThis._azleIc.replyRaw(replyBuffer.buffer); }, setCertifiedData: (data) => { const dataBytes = new Uint8Array( diff --git a/src/lib_new/method_decorators.ts b/src/lib_new/method_decorators.ts index 9e39331ce1..6c7075088a 100644 --- a/src/lib_new/method_decorators.ts +++ b/src/lib_new/method_decorators.ts @@ -231,9 +231,12 @@ function setupCanisterMethod( ); } + const originalMethod = descriptor.value; + if (mode === 'query') { target.constructor._azleCanisterMethods.queries.push({ - name: key + name: key, + composite: isAsync(originalMethod, key) }); } @@ -261,8 +264,6 @@ function setupCanisterMethod( }; } - const originalMethod = descriptor.value; - // This must remain a function and not an arrow function // in order to set the context (this) correctly descriptor.value = function (...args: any[]) { @@ -295,14 +296,6 @@ function setupCanisterMethod( ) { result .then((result) => { - const encodeReadyResult = - result === undefined ? [] : [result]; - - const encoded = IDL.encode( - returnCandid[0], - encodeReadyResult - ); - // TODO this won't be accurate because we have most likely had // TODO cross-canister calls console.log( @@ -310,6 +303,12 @@ function setupCanisterMethod( ); if (!manual) { + const encodeReadyResult = + result === undefined ? [] : [result]; + const encoded = IDL.encode( + returnCandid[0], + encodeReadyResult + ); ic.replyRaw(new Uint8Array(encoded)); } }) @@ -338,3 +337,15 @@ function setupCanisterMethod( return descriptor; } + +function isAsync(originalFunction: any, key: string) { + if (originalFunction[Symbol.toStringTag] === 'AsyncFunction') { + return true; + } else if (originalFunction.constructor.name === 'AsyncFunction') { + return true; + } else if (originalFunction.toString().includes('async ')) { + return true; + } else { + return false; + } +}