gitbook-docs/fluence-js/2_basics.md

195 lines
7.6 KiB
Markdown
Raw Permalink Normal View History

# Basics
## Intro
In this section we will show you how Fluence JS can be used to create a hello world application with Fluence stack.
## Aqua code
Let's start with the aqua code first:
```
import Peer from "@fluencelabs/aqua-lib/builtin.aqua" -- (1)
service HelloWorld("hello-world"): -- (2)
hello(str: string)
getFortune() -> string
func sayHello(): -- (3)
HelloWorld.hello("Hello, world!")
func tellFortune() -> string: -- (4)
res <- HelloWorld.getFortune()
<- res
func getRelayTime() -> u64: -- (5)
on HOST_PEER_ID:
ts <- Peer.timestamp_ms()
<- ts
```
We need to import definitions to call standard Peer operations (1)
This file has three definitions.
(2) is a service named `HelloWorld`. A Service interfaces functions executable on a peer. We will register a handler for this interface in our typescript application.
(3) and (4) are functions `sayHello` and `tellFortune` correspondingly. These functions very simple. The only thing the first one does is calling the `hello` method of `HelloWorld` service located on the current peer. Similarly `tellFortune` calls the `getFortune` method from the same service and returns the value to the caller. We will show you how to call these function from the typescript application.
Finally we have a function (5) which demonstrate how to work with the network. It asks the current time from the relay peer and return back the our peer.
## Installing dependencies
Initialize an empty npm package:
```bash
npm init
```
We will need these two packages for the application runtime
```bash
npm install @fluencelabs/fluence @fluencelabs/fluence-network-environment
```
The first one is the SDK itself and the second is a maintained list of Fluence networks and nodes to connect to.
Aqua compiler cli has to be installed, but is not needed at runtime.
```bash
npm install --save-dev @fluencelabs/aqua
```
Aqua comes with the standard library which can accessed from "@fluencelabs/aqua-lib" package. All the aqua packages are only needed at compiler time, so we install it as a development dependency
```bash
npm install --save-dev @fluencelabs/aqua-lib
```
Also we might want to have aqua source files automatically recompiled on every save. We will take advantage of chokidar for that:
```bash
npm install --save-dev chokidar-cli
```
And last, but no least we will need TypeScript
```
npm install --save-dev typescript
npx tsc --init
```
## Setting up aqua compiler
Let's put aqua described earlier into `aqua/hello-world.aqua` file. You probably want to keep the generated TypeScript in the same directory with other typescript files, usually `src`. Let's create the `src/_aqua` directory for that.
The overall project structure looks like this:
```
┣ aqua
┃ ┗ hello-world.aqua
┣ src
┃ ┣ _aqua
┃ ┃ ┗ hello-world.ts
┃ ┗ index.ts
┣ package-lock.json
┣ package.json
┗ tsconfig.json
```
The Aqua compiler can be run with `npm`:
```bash
npx aqua -i ./aqua/ -o ./src/_aqua
```
We recommend to store this logic inside a script in `packages.json` file:
```javascript
{
...
"scripts": {
...
"compile-aqua": "aqua -i ./aqua/ -o ./src/_aqua", // (1)
"watch-aqua": "chokidar \"**/*.aqua\" -c \"npm run compile-aqua\"" // (2)
},
...
}
```
`compile-aqua` (1) runs the compilation once, producing `src/_aqua/hello-world.ts` in our case `watch-aqua` (2) starts watching for any changes in .aqua files recompiling them on the fly
## Using the compiled code in typescript application
Using the code generated by the compiler is as easy as calling a function. The compiler generates all the boilerplate needed to send a particle into the network and wraps it into a single call. It also generate a function for service callback registration. Note that all the type information and therefore type checking and code completion facilities are there!
Let's see how use generated code in our application. `index.ts`:
```typescript
import { Fluence } from "@fluencelabs/fluence";
import { krasnodar } from "@fluencelabs/fluence-network-environment"; // (1)
import {
registerHelloWorld,
sayHello,
getRelayTime,
tellFortune,
} from "./_aqua/hello-world"; // (2)
async function main() {
await Fluence.start({ connectTo: krasnodar[0] }); // (3)
// (4)
registerHelloWorld({
hello: (str) => {
console.log(str);
},
getFortune: async () => {
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});
return "Wealth awaits you very soon.";
},
});
await sayHello(); // (4)
console.log(await tellFortune()); // (6)
const relayTime = await getRelayTime();
console.log("The relay time is: ", new Date(relayTime).toLocaleString());
await Fluence.stop(); // (7)
}
main();
```
(1) Import list of possible relay nodes (network environment)
(2) Aqua compiler provides functions which can be directly imported like any normal typescript function.
(3) A Fluence peer has to be started before running any application in Fluence Network. For the vast majority of use cases you should use `Fluence` facade to start and stop the peer. The `start` method accepts a parameters object which. The most common parameter is the address of the relay node the peer should connect to. In this example we are using the first node of the `krasnodar` network. If you do not specify the `connectTo` options will only be able to execute air on the local machine only. Please keep in mind that the init function is asynchronous.
For every exported `service XXX` definition in aqua code, the compiler provides a `registerXXX` counterpart. These functions provide a type-safe way of registering callback handlers for the services. The callbacks are executed when the appropriate service is called in aqua on the current peer. The handlers take form of the object where keys are the name of functions and the values are async functions used as the corresponding callbacks. For example in (4) we are registering handlers for `HelloWorld` service functions which outputs it's parameter to the console. Please note that the handlers can be implemented in both: synchronous and asynchronous way. The handler can be made asynchronous like any other function in javascript: either return a Promise or mark it with async keyword to take advantage of async-await pattern.
For every exported `func XXX` definition in aqua code, the compiler provides an async function which can be directly called from typescript. In (5, 6) we are calling exported aqua function with no arguments. Note that every function is asynchronous.
(7) You should call `stop` when the peer is no longer needed. As a rule of thumb all the peers should be uninitialized before destroying the application.
Let's try running the example:
```bash
node -r ts-node/register src/index.ts
```
If everything has been done correctly yuo should see `Hello, world!` in the console.
The next section will cover in-depth and advanced usage of Fluence JS
The code from this section is available in on [github](https://github.com/fluencelabs/examples/tree/main/fluence-js-examples/hello-world)
## Running Fluence application in different environments
Fluence JS instantiates Aqua Virtual Machine (AVM) from wasm file and runs it in the background thread. Different mechanism are used depending on the JS environment. In nodejs worker threads are used for background execution and wasm file is read from the filesystem. In browser-based environments web workers are used and the wasm file is being loaded from server hosting the application. Next two sections cover how to configure a fluence application depending on the environment.