Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Node apis #1575

Merged
merged 3 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
402 changes: 392 additions & 10 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
"dependencies": {
"@dfinity/candid": "github:demergent-labs/candid#minimum_viable",
"@dfinity/principal": "^0.19.3",
"@swc/core": "^1.3.86",
"@types/uuid": "^9.0.4",
"buffer": "^6.0.3",
"crypto-browserify": "^3.12.0",
"esbuild": "^0.19.3",
"fs-extra": "10.0.1",
"js-sha256": "0.9.0",
Expand Down
94 changes: 15 additions & 79 deletions src/compiler/compile_typescript_code.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,12 @@
import { buildSync } from 'esbuild';
import { JSCanisterConfig, JavaScript, TypeScript } from './utils/types';
import { JavaScript, TypeScript } from './utils/types';
import { Result } from './utils/result';
import { GLOBAL_AZLE_WASMEDGE_QUICKJS_DIR } from './utils/global_paths';

export function compileTypeScriptToJavaScript(
main: string,
canisterConfig: JSCanisterConfig
main: string
): Result<JavaScript, unknown> {
try {
const globalThisProcess = `
globalThis.process = {
env: {
${(canisterConfig.env ?? [])
.map((envVarName) => {
return `'${envVarName}': '${process.env[envVarName]}'`;
})
.join(',')}
}
};
`;

const imports = `
// Trying to make sure that all globalThis dependencies are defined
// Before the developer imports azle on their own
Expand Down Expand Up @@ -54,8 +41,7 @@ export function compileTypeScriptToJavaScript(
globalThis.exports.canisterMethods = canisterMethods;
`;

const bundledJavaScript = bundleAndTranspileJs(`
${globalThisProcess}
const bundledJavaScript = bundleFromString(`
${imports}
`);

Expand All @@ -67,23 +53,6 @@ export function compileTypeScriptToJavaScript(
}
}

export function bundleAndTranspileJs(ts: TypeScript): JavaScript {
const jsBundled: JavaScript = bundleFromString(ts);
// const jsTranspiled: JavaScript = transpile(jsBundled);

// TODO enabling strict mode is causing lots of issues
// TODO it would be nice if I could remove strict mode code in esbuild or swc
// TODO look into the implications of this, but since we are trying to transpile to es3 to cope with missing features in boa, I do not think we need strict mode
// const jsStrictModeRemoved: JavaScript = jsTranspiled.replace(
// /"use strict";/g,
// ''
// );

return jsBundled;
}

// TODO there is a lot of minification/transpiling etc we could do with esbuild or with swc
// TODO we need to decide which to use for what
export function bundleFromString(ts: TypeScript): JavaScript {
// TODO tree-shaking does not seem to work with stdin. I have learned this from sad experience
const buildResult = buildSync({
Expand All @@ -102,6 +71,7 @@ export function bundleFromString(ts: TypeScript): JavaScript {
util: `${GLOBAL_AZLE_WASMEDGE_QUICKJS_DIR}/modules/util`,
fs: `${GLOBAL_AZLE_WASMEDGE_QUICKJS_DIR}/modules/fs`,
fmt: `${GLOBAL_AZLE_WASMEDGE_QUICKJS_DIR}/modules/fmt`,
assert: `${GLOBAL_AZLE_WASMEDGE_QUICKJS_DIR}/modules/assert.js`,
buffer: `${GLOBAL_AZLE_WASMEDGE_QUICKJS_DIR}/modules/buffer.js`,
path: `${GLOBAL_AZLE_WASMEDGE_QUICKJS_DIR}/modules/path.js`,
stream: `${GLOBAL_AZLE_WASMEDGE_QUICKJS_DIR}/modules/stream.js`,
Expand All @@ -112,7 +82,10 @@ export function bundleFromString(ts: TypeScript): JavaScript {
punycode: `${GLOBAL_AZLE_WASMEDGE_QUICKJS_DIR}/modules/punycode.js`,
querystring: `${GLOBAL_AZLE_WASMEDGE_QUICKJS_DIR}/modules/querystring.js`,
whatwg_url: `${GLOBAL_AZLE_WASMEDGE_QUICKJS_DIR}/modules/whatwg_url.js`,
encoding: `${GLOBAL_AZLE_WASMEDGE_QUICKJS_DIR}/modules/encoding.js`
encoding: `${GLOBAL_AZLE_WASMEDGE_QUICKJS_DIR}/modules/encoding.js`,
crypto: 'crypto-browserify',
'internal/deps/acorn/acorn/dist/acorn': `crypto-browserify`, // TODO this is a bug, wasmedge-quickjs should probably add these files
'internal/deps/acorn/acorn-walk/dist/walk': `crypto-browserify` // TODO this is a bug, wasmedge-quickjs should probably add these files
},
external: ['_node:fs', '_encoding']
// TODO tsconfig was here to attempt to set importsNotUsedAsValues to true to force Principal to always be bundled
Expand All @@ -123,48 +96,11 @@ export function bundleFromString(ts: TypeScript): JavaScript {
const bundleArray = buildResult.outputFiles[0].contents;
const bundleString = Buffer.from(bundleArray).toString('utf-8');

return bundleString;
// TODO consuming code tries to require assert.js which is now an ES module
// TODO in wasmedge-quickjs, so the expected output is now on the .default property
// TODO this has only come up with assert for now
return bundleString.replace(
/__toCommonJS\(assert_exports\)\);/g,
`__toCommonJS(assert_exports)).default;`
);
}

// TODO I have left the code for bundleFromPath
// TODO We might run into the situation again where we need to use bundleFromPath
// TODO there are some issues with tree-shaking and possibly some others in bundleFromString, so I will just leave the code here for now until the project is more mature
// function bundleFromPath(tsPath: string): JavaScript {
// const buildResult = buildSync({
// entryPoints: [tsPath],
// format: 'esm',
// bundle: true,
// treeShaking: true,
// write: false,
// logLevel: 'silent',
// // TODO tsconfig was here to attempt to set importsNotUsedAsValues to true to force Principal to always be bundled
// // TODO now we always bundle Principal for all code, but I am keeping this here in case we run into the problem elsewhere
// // tsconfig: path.join( __dirname, './esbuild-tsconfig.json') // TODO this path resolution may cause problems on non-Linux systems, beware...might not be necessary now that we are using stdin
// });

// const bundleArray = buildResult.outputFiles[0].contents;
// const bundleString = Buffer.from(bundleArray).toString('utf-8');

// return bundleString;
// }

// TODO there is a lot of minification/transpiling etc we could do with esbuild or with swc
// TODO we need to decide which to use for what
// function transpile(js: JavaScript): JavaScript {
// return swc.transformSync(js, {
// module: {
// type: 'commonjs'
// },
// jsc: {
// parser: {
// syntax: 'ecmascript'
// },
// target: 'es2017', // TODO had to change this to get generator objects natively...not sure what else will break now
// experimental: {
// cacheRoot: '/dev/null'
// },
// loose: true
// },
// minify: false // TODO keeping this off for now, enable once the project is more stable
// }).code;
// }
19 changes: 14 additions & 5 deletions src/compiler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { Err, ok } from './utils/result';
import {
AzleError,
CompilerInfo,
JSCanisterConfig,
Toml,
TsCompilationError,
TsSyntaxErrorLocation
Expand Down Expand Up @@ -64,8 +65,7 @@ async function azle() {
installRustDependencies(azleVersion, rustVersion);

const compilationResult = compileTypeScriptToJavaScript(
canisterConfig.main,
canisterConfig
canisterConfig.main
);

if (!ok(compilationResult)) {
Expand Down Expand Up @@ -113,14 +113,16 @@ async function azle() {
// TODO stuff is repeated which is messy and bad of course
writeFileSync(`${canisterPath}/canister/src/candid.did`, ''); // This is for the Rust canister to have access to the candid file

const envVars = getEnvVars(canisterConfig);

const compilerInfo0: CompilerInfo = {
// TODO The spread is because canisterMethods is a function with properties
canister_methods: {
candid: '',
queries: [],
updates: [],
callbacks: {}
} // TODO we should probably just grab the props out that we need
},
env_vars: envVars
};

const compilerInfoPath0 = join(
Expand All @@ -147,7 +149,8 @@ async function azle() {
// 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
}, // TODO we should probably just grab the props out that we need
env_vars: envVars
};

const compilerInfoPath = join(
Expand Down Expand Up @@ -216,3 +219,9 @@ function generateVisualDisplayOfErrorLocation(
)}${marker}`;
return `${preciseLocation}\n${previousLine}\n${offendingLine}\n${subsequentLine}`;
}

function getEnvVars(canisterConfig: JSCanisterConfig): [string, string][] {
return (canisterConfig.env ?? []).map((envVarName) => {
return [envVarName, process.env[envVarName] ?? ''];
});
}
2 changes: 0 additions & 2 deletions src/compiler/rust/canister/src/ic/accept_message.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::convert::TryFrom;

use wasmedge_quickjs::{Context, JsFn, JsValue};

pub struct NativeFunction;
Expand Down
2 changes: 0 additions & 2 deletions src/compiler/rust/canister/src/ic/arg_data_raw_size.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::convert::TryFrom;

use wasmedge_quickjs::{Context, JsFn, JsValue};

pub struct NativeFunction;
Expand Down
2 changes: 0 additions & 2 deletions src/compiler/rust/canister/src/ic/candid_decode.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::convert::TryInto;

use wasmedge_quickjs::{Context, JsFn, JsValue};

pub struct NativeFunction;
Expand Down
2 changes: 0 additions & 2 deletions src/compiler/rust/canister/src/ic/candid_encode.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::convert::TryInto;

use wasmedge_quickjs::{Context, JsArrayBuffer, JsFn, JsValue};

pub struct NativeFunction;
Expand Down
2 changes: 0 additions & 2 deletions src/compiler/rust/canister/src/ic/reply_raw.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::convert::TryInto;

use wasmedge_quickjs::{Context, JsArrayBuffer, JsFn, JsValue};

pub struct NativeFunction;
Expand Down
2 changes: 0 additions & 2 deletions src/compiler/rust/canister/src/ic/set_timer.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::convert::TryInto;

use slotmap::Key;
use wasmedge_quickjs::{AsObject, Context, JsFn, JsValue};

Expand Down
2 changes: 0 additions & 2 deletions src/compiler/rust/canister/src/ic/set_timer_interval.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::convert::TryInto;

use slotmap::Key;
use wasmedge_quickjs::{AsObject, Context, JsFn, JsValue};

Expand Down
2 changes: 0 additions & 2 deletions src/compiler/rust/canister/src/ic/stable64_write.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::convert::TryInto;

use wasmedge_quickjs::{Context, JsFn, JsValue};

pub struct NativeFunction;
Expand Down
2 changes: 0 additions & 2 deletions src/compiler/rust/canister/src/ic/stable_write.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::convert::TryInto;

use wasmedge_quickjs::{Context, JsFn, JsValue};

pub struct NativeFunction;
Expand Down
15 changes: 9 additions & 6 deletions src/compiler/rust/canister_methods/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ impl ToIdent for String {
#[derive(Debug, Serialize, Deserialize)]
struct CompilerInfo {
canister_methods: CanisterMethods,
env_vars: Vec<(String, String)>,
}

#[derive(Debug, Serialize, Deserialize)]
Expand All @@ -45,6 +46,12 @@ struct CanisterMethod {
pub fn canister_methods(_: TokenStream) -> TokenStream {
let compiler_info = get_compiler_info("canister/src/compiler_info.json").unwrap();

let env_vars: Vec<_> = compiler_info
.env_vars
.iter()
.map(|(key, value)| quote!((#key, #value)))
.collect();

let init_method_call = compiler_info.canister_methods.init.map(|init_method| {
let js_function_name = &init_method.name;

Expand All @@ -54,7 +61,7 @@ pub fn canister_methods(_: TokenStream) -> TokenStream {
let init_method = quote! {
#[ic_cdk_macros::init]
fn init() {
ic_wasi_polyfill::init(&[], &[]);
ic_wasi_polyfill::init(&[], &[#(#env_vars),*]);

let mut rt = wasmedge_quickjs::Runtime::new();

Expand Down Expand Up @@ -82,8 +89,6 @@ pub fn canister_methods(_: TokenStream) -> TokenStream {
// ic_cdk::println!("temp: {:#?}", temp);
});

ic_cdk::println!("init result: {:#?}", r);

RUNTIME.with(|runtime| {
let mut runtime = runtime.borrow_mut();
*runtime = Some(rt);
Expand All @@ -106,7 +111,7 @@ pub fn canister_methods(_: TokenStream) -> TokenStream {
let post_update_method = quote! {
#[ic_cdk_macros::post_upgrade]
fn post_upgrade() {
ic_wasi_polyfill::init(&[], &[]);
ic_wasi_polyfill::init(&[], &[#(#env_vars),*]);

let mut rt = wasmedge_quickjs::Runtime::new();

Expand Down Expand Up @@ -134,8 +139,6 @@ pub fn canister_methods(_: TokenStream) -> TokenStream {
// ic_cdk::println!("temp: {:#?}", temp);
});

ic_cdk::println!("post_upgrade result: {:#?}", r);

RUNTIME.with(|runtime| {
let mut runtime = runtime.borrow_mut();
*runtime = Some(rt);
Expand Down
1 change: 1 addition & 0 deletions src/compiler/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type OptLevel = '0' | '1' | '2' | '3' | '4';

export type CompilerInfo = {
canister_methods: CanisterMethods;
env_vars: [string, string][];
};

export type CanisterMethods = {
Expand Down
29 changes: 20 additions & 9 deletions src/lib/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ic } from './ic';
import { AzleIc } from './ic/types/azle_ic';
import { Buffer } from 'buffer';
import { replacer } from './stable_structures/stable_json';
import * as process from 'process';

declare global {
var _azleInsideCanister: boolean;
Expand All @@ -18,15 +19,18 @@ globalThis._azleInsideCanister =
globalThis._azleIc === undefined ? false : true;

if (globalThis._azleInsideCanister) {
const log = (...args: any[]) => {
const jsonStringifiedArgs = args
.map((arg) => JSON.stringify(arg, replacer, 4))
.join(' ');

ic.print(jsonStringifiedArgs);
};

globalThis.console = {
...globalThis.console,
log: (...args: any[]) => {
const jsonStringifiedArgs = args
.map((arg) => JSON.stringify(arg, replacer, 4))
.join(' ');

ic.print(jsonStringifiedArgs);
}
log,
error: log
};

const originalSetTimeout = setTimeout;
Expand All @@ -40,6 +44,7 @@ if (globalThis._azleInsideCanister) {
}

// TODO change this to throw once errors throw and show up properly
// TODO should this throw an error or just not do anything? At least a warning would be good right?
ic.trap(`setTimeout cannot be called with milliseconds above 0`);
};
}
Expand All @@ -56,8 +61,9 @@ globalThis._azleGuardFunctions = {};
// TODO the randomness is predictable
globalThis.crypto = {
...globalThis.crypto,
getRandomValues: (() => {
let array = new Uint8Array(32);
getRandomValues: ((array: Uint8Array) => {
// TODO the type is wrong of array
// TODO this could possibly be any kind of TypedArray

for (let i = 0; i < array.length; i++) {
array[i] = Math.floor(Math.random() * 256);
Expand All @@ -68,3 +74,8 @@ globalThis.crypto = {
};

globalThis.Buffer = Buffer;

globalThis.process = process;
globalThis.clearInterval = () => {}; // TODO should this throw an error or just not do anything? At least a warning would be good right?

globalThis.global = globalThis;
Loading