Update JS-SDK docs to reflect the latest version of JS SDK and the Aqua compiler

This commit is contained in:
Pavel Murygin 2021-09-11 17:34:57 +03:00
parent f2d9dc5d04
commit 52a935e855
3 changed files with 93 additions and 80 deletions

View File

@ -14,9 +14,20 @@ service HelloWorld("hello-world"):
func sayHello():
HelloWorld.hello("Hello, world!")
func getRelayTime() -> u64:
on HOST_PEER:
ts <- Peer.timestamp_ms()
<- ts
```
This file has two definitions. The first one 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. The second definition is the function `sayHello`. The only thing the function is doing is calling the `hello` method of `HelloWorld` service located on the current peer. We will shouw you how to call this function from the typescript application.
This file has three definitions.
The first one 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.
The second definition is the function `sayHello`. The only thing the function is doing is calling the `hello` method of `HelloWorld` service located on the current peer. We will show you how to call this function from the typescript application.
Finally we have a functions wich demomnstrate how to work with the network. It asks the current time from the relay peer and return back the our peer.
## Installing dependencies
@ -40,6 +51,12 @@ Aqua compiler cli has to be installed, but is not needed at runtime.
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
@ -74,7 +91,7 @@ The overall project structure looks like this:
The Aqua compiler can be run with `npm`:
```bash
npx aqua -i ./aqua/ -o ./src/_aqua
npx aqua -i ./aqua/ -o ./src/_aqua
```
We recommend to store this logic inside a script in `packages.json` file:
@ -100,14 +117,19 @@ Using the code generated by the compiler is as easy as calling a function. The c
Let's see how use generated code in our application. `index.ts`:
```typescript
import { FluencePeer } from "@fluencelabs/fluence";
import { registerHelloWorld, sayHello } from "./_aqua/hello-world"; // (1)
import { Fluence } from "@fluencelabs/fluence";
import { krasnodar } from "@fluencelabs/fluence-network-environment"; // (1)
import {
registerHelloWorld,
sayHello,
getRelayTime,
} from "./_aqua/hello-world"; // (2)
async function main() {
await FluencePeer.default.init(); // (2)
await Fluence.start({ connectTo: krasnodar[0] }); // (3)
// (3)
registerHelloWorld({
// (3)
hello: async (str) => {
console.log(str);
},
@ -115,21 +137,27 @@ async function main() {
await sayHello(); // (4)
await FluencePeer.default.uninit(); // (5)
const relayTime = await getRelayTime(); // (5)
console.log("The relay time is: ", new Date(relayTime).toLocaleString());
await Fluence.stop(); // (6)
}
main();
```
\(1\) Aqua compiler provides functions which can be directly imported like any normal typescript function.
\(1\) Import list of possible relay nodes (network enironment)
\(2\) `FluencePeer` has to be initialized before running any application in Fluence Network. A peer represents the identity in the network, so most of the time you will only need a single peer per application. JS SDK provides a default instance which is accesible via `default` propery of the class. `init` method accepts a parameters object which will be covered in the next section. By default the peer is not get connected to the network and will only be able to execute air on the local machine only. Please keep in mind that the init function is asyncrhounous
\(2\) Aqua compiler provides functions which can be directly imported like any normal typescript function.
For every exported `service XXX` definition in aqua code, the compiler provides a `registerXXX` counterpart. These funtions 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 \(3\) we are registering handler for `hello` function which outputs it's parameter to the console
\(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 asyncrhounous
For every exported `func XXX` definition in aqua code, the compiler provides an async function which can be directly called from typescripyt. In \(4\) we are calling the `sayHello` function with no arguments. Note that every function is asyncrhonous.
For every exported `service XXX` definition in aqua code, the compiler provides a `registerXXX` counterpart. These funtions 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 \(3\) we are registering handler for `HelloWorld` service which outputs it's parameter to the console
\(5\) You should call `uninit` method of `FluencePeer` when it is no longer needed. As a rule of thumb all the peers should be uninitilized before destroying the application.
For every exported `func XXX` definition in aqua code, the compiler provides an async function which can be directly called from typescripyt. In \(4, 5\) we are calling exported aqua function with no arguments. Note that every function is asyncrhonous.
\(6\) You should call `stop` when the peer is no longer needed. As a rule of thumb all the peers should be uninitilized before destroying the application.
Let's try running the example:
@ -140,3 +168,5 @@ node -r ts-node/register src/index.ts
If everything has been done correctly yuo should see `Hello, world!` in the console.
The next secion will cover in-depth and advanced usage JS SDK
The code from this section is available in on (github)[https://github.com/fluencelabs/examples/tree/main/js-sdk-examples/hello-world]

View File

@ -6,14 +6,28 @@
In this section we will cover the JS SDK in-depth.
## Fluence
`@fluencelabs/fluence` exports a facade `Fluence` which provides all the needed functionality for the most uses cases. It defined 4 functions:
- `start`: Start the default peer.
- `stop`: Stops the default peer
- `getStatus`: Gets the status of the default peer. This includes connection
- `getPeer`: Gets the default Fluence Peer instance (see below)
Under the hood `Fluence` facade calls the corresponding method on the default instance of FluencePeer. This instance is passed to the Aqua-compiler generated functions by default.
## FluencePeer class
The overall workflow with the `FluencePeer` is the following:
The second export `@fluencelabs/fluence` package is `FluencePeer` class. It is useful in scenarios when the application need to run everal different peer at once. The overall workflow with the `FluencePeer` is the following:
1. Create an instance of the peer
2. Initializing the peer
2. Startign the peer
3. Using the peer in the application
4. Uninitializing the peer
4. Stopping the peer
To create a new peer simple instantiate the `FluencePeer` class:
@ -21,61 +35,23 @@ To create a new peer simple instantiate the `FluencePeer` class:
const peer = new FluencePeer();
```
The constructor simply creates a new object and does not initialize any workflow. The `init` function starts the Aqua VM, initializes the default call service handlers and \(optionally\) connect to the Fluence network. The function takes an optional object specifying additonal peer configuration. On option you will be using a lot is `connectTo`. It tells the peer to connect to a relay. For example:
The constructor simply creates a new object and does not initialize any workflow. The `start` function starts the Aqua VM, initializes the default call service handlers and \(optionally\) connect to the Fluence network. The function takes an optional object specifying additonal peer configuration. On option you will be using a lot is `connectTo`. It tells the peer to connect to a relay. For example:
```typescript
await peer.init({
await peer.star({
connectTo: krasnodar[0],
});
```
connects the first node of the Kranodar network. You can find the officially maintained list networks in the `@fluencelabs/fluence-network-environment` package. The full list of supported options is described in the [API reference](https://github.com/fluencelabs/gitbook-docs/tree/77344eb147c2ce17fe1c0f37013082fc85c1ffa3/js-sdk/js-sdk/6_reference/modules.md)
Most of the time a single peer is enough for the whole application. For these use cases`FluncePeer` class contains the default instance which can be accessed with the corresponding property:
```typescript
await FluencePeer.default.init();
```
The peer by itself does not do any useful work. You should take advantage of functions generated by the Aqua compiler. You can use them both with a single peer or in muliple peers scenario. If you are using the default peer for your application you don't need to explicitly pass it: the compiled functions will use the `default` instance in that case \(see "Using multiple peers in one applicaton"\)
To uninitialize the peer simply call `uninit` method. It will disconnect from the network and stop the Aqua vm,
```typescript
await peer.unint();
await peer.stop();
```
## Using multiple peers in one applicaton
In most cases using a single peer is enough. However sometimes you might need to run multiple peers inside the same JS environment. When using a single peer you should initialize the `FluencePeer.default` and call Aqua compiler-generated functions without passing any peer. For example:
```typescript
import { FluencePeer } from "@fluencelabs/fluence";
import {
registerSomeService,
someCallableFunction,
} from "./_aqua/someFunction";
async function main() {
await FluencePeer.default.init({
connectTo: relay,
});
// ... more application logic
registerSomeService({
handler: async (str) => {
console.log(str);
},
});
await someCallableFunction(arg1, arg2, arg3);
await FluencePeer.default.uninit();
}
// ... more application logic
```
The peer by itself does not do any useful work. You should take advantage of functions generated by the Aqua compiler.
If your application needs several peers, you should create a separate `FluncePeer` instance for each of them. The generated functions accept the peer as the first argument. For example:
@ -91,10 +67,10 @@ async function main() {
const peer2 = new FluencePeer();
// Don't forget to initialize peers
await peer1.init({
await peer1.start({
connectTo: relay,
});
await peer2.init({
await peer2.start({
connectTo: relay,
});
@ -124,8 +100,8 @@ async function main() {
await someCallableFunction(peer1, arg1, arg2, arg3);
await peer1.uninit();
await peer2.uninit();
await peer1.stop();
await peer2.stop();
}
// ... more application logic
@ -163,7 +139,7 @@ Aqua compiler emits TypeScript or JavaScript which in turn can be called from a
For every exported function definition in aqua the compiler generated two overloads. One accepting the `FluencePeer` instance as the first argument, and one without it. Otherwise arguments are the same and correspond to the arguments of aqua functions. The last argument is always an optional config object with the following properties:
* `ttl`: Optional parameter which specify TTL \(time to live\) of particle with execution logic for the function
- `ttl`: Optional parameter which specify TTL \(time to live\) of particle with execution logic for the function
The return type is always a promise of the aqua function return type. If the function does not return anything, the return type will be `Promise<void>`.
@ -193,6 +169,11 @@ export async function callMeBack(
### Service definitions
```
service ServiceName:
-- service interface
```
For every exported `service` declaration the compiler will generate two entities: service interface under the name `{serviceName}Def` and a function named `register{serviceName}` with several overloads. First let's describe the most complete one using the following example:
```typescript
@ -200,48 +181,48 @@ export interface ServiceNameDef {
//... service function definitions
}
export function registerStringExtra(
export function registerServiceName(
peer: FluencePeer,
serviceId: string,
service: ServiceNameDef
): void;
```
* `peer` - the Fluence Peer instance where the handler should be registered. The peer can be ommited. In that case the `FluencePeer.default` will be used instead
* `serviceId` - the name of the service id. If the service was defined with the default service id in aqua code, this argument can be ommited.
* `service` - the handler for the service.
- `peer` - the Fluence Peer instance where the handler should be registered. The peer can be ommited. In that case the default Fluence Peer will be used instead
- `serviceId` - the name of the service id. If the service was defined with the default service id in aqua code, this argument can be ommited.
- `service` - the handler for the service.
Depending on whether or not the services was defined with the default id the number of overloads will be different. In the case it **is defined**, there would be four overloads:
```typescript
// (1)
export function registerStringExtra(
export function registerServiceName(
//
service: ServiceNameDef
): void;
// (2)
export function registerStringExtra(
export function registerServiceName(
serviceId: string,
service: ServiceNameDef
): void;
// (3)
export function registerStringExtra(
export function registerServiceName(
peer: FluencePeer,
service: ServiceNameDef
): void;
// (4)
export function registerStringExtra(
export function registerServiceName(
peer: FluencePeer,
serviceId: string,
service: ServiceNameDef
): void;
```
1. Uses `FluencePeer.default` and the default id taken from aqua definition
2. Uses `FluencePeer.default` and specifies the service id explicitly
1. Uses default Fluence Peer and the default id taken from aqua definition
2. Uses default Fluence Peer and specifies the service id explicitly
3. The default id is taken from aqua definition. The peer is specified explicitly
4. Specifying both peer and the service id.
@ -249,20 +230,20 @@ If the default id **is not defined** in aqua code the overloads will exclude one
```typescript
// (1)
export function registerStringExtra(
export function registerServiceName(
serviceId: string,
service: ServiceNameDef
): void;
// (2)
export function registerStringExtra(
export function registerServiceName(
peer: FluencePeer,
serviceId: string,
service: ServiceNameDef
): void;
```
1. Uses `FluencePeer.default` and specifies the service id explicitly
1. Uses default Fluence Peer and specifies the service id explicitly
2. Specifying both peer and the service id.
### Service interface
@ -298,9 +279,9 @@ export interface CalcDef {
Basic types convertion is pretty much straightforward:
* `string` is converted to `string` in typescript
* `bool` is converted to `boolean` in typescript
* All number types \(`u8`, `u16`, `u32`, `u64`, `s8`, `s16`, `s32`, `s64`, `f32`, `f64`\) are converted to `number` in typescript
- `string` is converted to `string` in typescript
- `bool` is converted to `boolean` in typescript
- All number types \(`u8`, `u16`, `u32`, `u64`, `s8`, `s16`, `s32`, `s64`, `f32`, `f64`\) are converted to `number` in typescript
Arrow types translate to functions in typescript which have their arguments translated to typescript types. In addition to arguments defined in aqua, typescript counterparts have an additional argument for call params. For the majority of use cases this parameter is not needed and can be ommited.
@ -336,4 +317,3 @@ Tetraplets have the form of:
To learn more about tetraplets and application security see [Security](https://github.com/fluencelabs/gitbook-docs/tree/77344eb147c2ce17fe1c0f37013082fc85c1ffa3/js-sdk/knowledge_security.md)
To see full specification of `CallParms` type see [Api reference](https://github.com/fluencelabs/gitbook-docs/tree/77344eb147c2ce17fe1c0f37013082fc85c1ffa3/js-sdk/js-sdk/6_reference/modules.md)

View File

@ -1,4 +1,7 @@
# Running app in browser
You can use the JS SDK with any framework \(or even without it\). Just follow the steps from the previous sections. FluentPad is an example application written in React: [https://github.com/fluencelabs/fluent-pad](https://github.com/fluencelabs/fluent-pad)
You can use the JS SDK with any framework \(or even without it\). The "flunce" part of the application is a collection of pure typesctipt\javascript functions which can be called withing any framework of your choosing.
See the browser-example which demonstrate integrating Fluence with React: (github)[https://github.com/fluencelabs/examples/tree/main/js-sdk-examples/browser-example]
Also take a look at FluentPad. It is an example application written in React: [https://github.com/fluencelabs/fluent-pad](https://github.com/fluencelabs/fluent-pad)