diff --git a/.gitignore b/.gitignore index c9ff9d5a1..cdd4e7899 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ coverage/ /examples/yacat/*.txt /examples/yacat/*.hash /examples/yacat/*.potfile -/docs +api-reference/ logs/ .cypress diff --git a/.docs/summary-generator.cjs b/.typedoc/summary-generator.cjs similarity index 96% rename from .docs/summary-generator.cjs rename to .typedoc/summary-generator.cjs index 012acc63b..1eee19679 100644 --- a/.docs/summary-generator.cjs +++ b/.typedoc/summary-generator.cjs @@ -21,7 +21,7 @@ async function prepareDocAnchor(docsDir, type, file) { } (async () => { - const docsDir = process.argv[2] || "docs"; + const docsDir = process.argv[2] || "api-reference"; const directoryPath = path.join(__dirname, "..", docsDir); const logFilePath = path.join(process.argv[3] || docsDir, "overview.md"); const logStream = fs.createWriteStream(logFilePath, { flags: "w" }); diff --git a/.docs/typedoc-frontmatter-theme.cjs b/.typedoc/typedoc-frontmatter-theme.cjs similarity index 100% rename from .docs/typedoc-frontmatter-theme.cjs rename to .typedoc/typedoc-frontmatter-theme.cjs diff --git a/README.md b/README.md index 80142a41b..53b103a98 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +
+ +

+ golem-js SDK logo +

+ # Golem JavaScript API ![GitHub](https://img.shields.io/github/license/golemfactory/golem-js) @@ -14,32 +20,36 @@ - [Golem JavaScript API](#golem-javascript-api) - [Table of contents](#table-of-contents) - - [What's Golem and `golem-js`?](#whats-golem-and-golem-js) - - [Documentation](#documentation) + - [Features](#features) + - [Getting Started](#getting-started) + - [What's Golem and `golem-js`?](#whats-golem-and-golem-js) + - [SDK Learning resources](#sdk-learning-resources) - [Installation](#installation) - [Supported environments](#supported-environments) - [Getting started with Golem Network](#getting-started-with-golem-network) - [Obtain an `app-key` to use with SDK](#obtain-an-app-key-to-use-with-sdk) - [Usage](#usage) - - [Renting a single machine and running a simple task on it](#renting-a-single-machine-and-running-a-simple-task-on-it) - - [Renting many machines and running tasks in parallel](#renting-many-machines-and-running-tasks-in-parallel) - - [Features](#features) - - [Streaming command results](#streaming-command-results) - - [File transfer](#file-transfer) - - [VPN](#vpn) - - [Events](#events) - - [Custom filters](#custom-filters) - - [Custom ranking of proposals](#custom-ranking-of-proposals) - - [Uploading local images to the provider](#uploading-local-images-to-the-provider) - - [Setup and teardown methods](#setup-and-teardown-methods) - - [Going further](#going-further) - - [More examples](#more-examples) + - [Examples](#examples) + - [Documentation](#documentation) - [Debugging](#debugging) - [Testing](#testing) - [Contributing](#contributing) + - [Discord](#discord) - [See also](#see-also) + + +## Features + +Become a **Requestor** in the **Golem Network** and use this SDK to: -## What's Golem and `golem-js`? +- 🌐 Acquire compute resources from Providers using a convenient API +- 🚒 Run your workloads with these resources and get the results back to your machine +- πŸ” Build N-tier application deployments and run them within a VPN +- πŸ’° Settle payments with Providers for the resources you've utilized + +## Getting Started + +### What's Golem and `golem-js`? **[The Golem Network](https://golem.network)** fosters a global group of creators building ambitious software solutions that will shape the technological landscape of future generations by accessing computing resources across the platform. @@ -49,10 +59,12 @@ resources and connecting users through a flexible, open-source platform. **golem-js** is the JavaScript API that allows developers to connect to their Golem nodes and manage their distributed, computational loads through Golem Network. -## Documentation +### SDK Learning resources -Visit our [official documentation](https://docs.golem.network/docs/creators/javascript) to learn more about the -JavaScript SDK and how to use it. +- [Basic concepts documentation](./docs/CONCEPTS.md) - Learn about the basic and advanced building blocks at your + disposal. +- [Usage documentation](./docs/USAGE.md) - Explore supported usage and implementation patterns. +- [Feature documentation](./docs/FEATURES.md) - Description of interesting features that we've prepared. ## Installation @@ -84,7 +96,10 @@ and with browsers. ## Getting started with Golem Network Before you start using the SDK, you need to have `yagna` installed and running on your machine. Yagna is a service that -communicates and performs operations on the Golem Network, upon your requests via the SDK. You can follow the instructions below or visit the [official documentation](https://docs.golem.network/docs/creators/javascript/quickstarts/quickstart#install-yagna-2) to set it up. +communicates and performs operations on the Golem Network, upon your requests via the SDK. You can follow the +instructions below or visit +the [official documentation](https://docs.golem.network/docs/creators/javascript/quickstarts/quickstart#install-yagna-2) +to set it up. ```bash # Join the network as a requestor @@ -95,7 +110,8 @@ curl -sSf https://join.golem.network/as-requestor | bash - yagna service run ``` -Now that you have `yagna` running, you can initialize your requestor and request funds (`tGLM` tokens) on the test network. +Now that you have `yagna` running, you can initialize your requestor and request funds (`tGLM` tokens) on the test +network. ```bash # IN SEPARATE TERMINAL (if not daemonized) @@ -121,7 +137,7 @@ yagna app-key create my-golem-app ## Usage -### Renting a single machine and running a simple task on it +You can rent a single machine and run a simple task on it: ```ts import { MarketOrderSpec, GolemNetwork } from "@golem-sdk/golem-js"; @@ -149,7 +165,7 @@ const order: MarketOrderSpec = { try { await glm.connect(); // Rent a machine - const rental = await glm.oneOf(order); + const rental = await glm.oneOf({ order }); await rental .getExeUnit() .then((exe) => exe.run("echo Hello, Golem! πŸ‘‹")) @@ -163,280 +179,41 @@ const order: MarketOrderSpec = { })().catch(console.error); ``` -### Renting many machines and running tasks in parallel - -```ts -import { GolemNetwork, MarketOrderSpec } from "@golem-sdk/golem-js"; - -// Define the order that we're going to place on the market -const order: MarketOrderSpec = { - demand: { - workload: { imageTag: "golem/alpine:latest" }, - }, - market: { - rentHours: 0.5, - pricing: { - model: "linear", - maxStartPrice: 0.5, - maxCpuPerHourPrice: 1.0, - maxEnvPerHourPrice: 0.5, - }, - }, -}; - -(async () => { - const glm = new GolemNetwork(); - - try { - await glm.connect(); - // create a pool that can grow up to 3 rentals at the same time - const pool = await glm.manyOf({ - concurrency: 3, - order, - }); - // run 3 tasks in parallel on 3 different machines - await Promise.allSettled([ - pool.withRental(async (rental) => - rental - .getExeUnit() - .then((exe) => exe.run("echo Hello, Golem from the first machine! πŸ‘‹")) - .then((res) => console.log(res.stdout)), - ), - pool.withRental(async (rental) => - rental - .getExeUnit() - .then((exe) => exe.run("echo Hello, Golem from the second machine! πŸ‘‹")) - .then((res) => console.log(res.stdout)), - ), - pool.withRental(async (rental) => - rental - .getExeUnit() - .then((exe) => exe.run("echo Hello, Golem from the third machine! πŸ‘‹")) - .then((res) => console.log(res.stdout)), - ), - ]); - } catch (err) { - console.error("Failed to run the example", err); - } finally { - await glm.disconnect(); - } -})().catch(console.error); -``` - -## Features - -### Streaming command results - -Instead of waiting for the command to finish, you can stream the results as they come in. This is useful for long-running -commands, where you want to see the output as it's being produced. - -```ts -const remoteProcess = await exe.runAndStream( - ` -sleep 1 -echo -n 'Hello from stdout' >&1 -sleep 1 -echo -n 'Hello from stdout again' >&1 -sleep 1 -echo -n 'Hello from stdout yet again' >&1 -`, -); -remoteProcess.stdout.on("data", (data) => console.log("stdout>", data)); -await remoteProcess.waitForExit(); -``` - -[Check the full example](./examples/basic/run-and-stream.ts) - -### File transfer - -You can transfer files to and from the remote machine. This is useful when you need to provide input files or retrieve -the results of the computation. - -```ts -await exe - .beginBatch() - .run(`echo "Message from provider ${exe.provider.name}. Hello 😻" >> /golem/work/message.txt`) - .downloadFile("/golem/work/message.txt", "./message.txt") - .end(); -console.log(await readFile("./results.txt", { encoding: "utf-8" })); -``` - -[Check the full example](./examples/basic/transfer.ts) - -### VPN - -You can connect yourself and multiple providers to a VPN network. This is useful when you want to communicate -securely between the nodes. +Read about [other available usage patterns](./docs/USAGE.md) to learn more on how you can leverage the SDK. -```ts -const network = await glm.createNetwork({ ip: "192.168.7.0/24" }); -// ... -const exe1 = await rental1.getExeUnit(); -const exe2 = await rental2.getExeUnit(); -await exe1 - .run(`ping ${exe2.getIp()} -c 4`) - .then((res) => console.log(`Response from provider: ${exe1.provider.name} (ip: ${exe1.getIp()})`, res.stdout)); -``` - -[Check the full example](./examples/basic/vpn.ts) - -### Events - -You can listen to various events that are emitted by the SDK. This is useful when you want to react to certain -conditions, like calculating the total cost of all invoices received. - -```ts -glm.payment.events.on("invoiceAccepted", (invoice) => { - console.log("Invoice '%s' accepted for %s GLM", invoice.id, invoice.amount); -}); -``` - -[Check the full example](./examples/basic/events.ts) - -### Custom filters - -You can define custom filters to select the providers that you want to work with. This is useful when you want to -blacklist or whitelist certain providers. - -```ts -const myFilter: ProposalFilter = (proposal) => proposal.provider.name !== "bad-provider"; - -const order: MarketOrderSpec = { - market: { - proposalFilter: myFilter, - // other options - }, -}; -``` - -[Check the full example](./examples/advanced/proposal-filter.ts) - -We have also prepared a set of predefined filters for common use-cases. [Check out the example with predefined filters here](./examples/advanced/proposal-predefined-filter.ts) - -### Custom ranking of proposals - -You can define a method that will select which proposal should be chosen first. This is useful when you want to -prioritize certain providers over others. - -```ts -const scores = { - "very-good-provider": 10, - "good-provider": 5, - "bad-provider": -10, -}; - -const bestProviderSelector = (proposals: OfferProposal[]) => { - return proposals.sort((a, b) => (scores[b.provider.name] || 0) - (scores[a.provider.name] || 0))[0]; -}; - -const order: MarketOrderSpec = { - market: { - proposalSelector: bestProviderSelector, - // other options - }, -}; -``` - -[Check the full example](./examples/advanced/proposal-selector.ts) - -### Uploading local images to the provider - -You can avoid using the registry and upload a GVMI image directly to the provider. This is useful when you want to -quickly prototype your image without having to update the registry with every change. - -```ts -const order: MarketOrderSpec = { - demand: { - workload: { - imageUrl: "file:///path/to/your/image.gvmi", - }, - // other options - }, -}; -``` - -[Check the full example](./examples/advanced//local-image/) - -### Setup and teardown methods - -You can define a setup method that will be executed the first time a provider is rented and a teardown method -that will be executed before the rental is done. This is useful when you want to avoid doing the same work -multiple times when running multiple tasks on the same provider. - -```ts -// I want to upload a big file to each provider only once -const setup: LifecycleFunction = async (exe) => exe.uploadFile("./big-file.txt", "/golem/work/big-file.txt"); - -// I want to remove the file after I'm done -const teardown: LifecycleFunction = async (exe) => exe.run("rm /golem/work/big-file.txt"); - -const pool = await glm.manyOf({ - order, - concurrency, - setup, - teardown, -}); -``` - -[Check the full example](./examples/advanced/setup-and-teardown.ts) - - - -## Going further - -If you wish to learn more about how the SDK functions under the hood, please check out our more advanced examples: - -- [Creating pools manually](./examples/advanced/manual-pools.ts) -- [Performing all market operations manually](./examples/advanced/step-by-step.ts) -- [(for library authors) Override internal module](./examples/advanced/override-module.ts) - -## More examples +## Examples The [examples directory](./examples) in the repository contains various usage patterns for the SDK. You can browse through them and learn about the recommended practices. All examples are automatically tested during our release process. -In case you find an issue with the examples, feel free to submit -an [issue report](https://github.com/golemfactory/golem-js/issues) to the repository. - You can find even more examples and tutorials in the [JavaScript API section of the Golem Network Docs](https://docs.golem.network/docs/creators/javascript). +## Documentation + +Visit our [official documentation](https://docs.golem.network/docs/creators/javascript) to learn more about the +JavaScript SDK and how to use it. + ## Debugging -The SDK uses the [debug](https://www.npmjs.com/package/debug) package to provide debug logs. To enable them, set the `DEBUG` environment variable to `golem-js:*` or `golem-js:market:*` to see all logs or only the market-related ones, respectively. For more information, please refer to the [debug package documentation](https://www.npmjs.com/package/debug). +The SDK uses the [debug](https://www.npmjs.com/package/debug) package to provide debug logs. To enable them, set +the `DEBUG` environment variable to `golem-js:*` or `golem-js:market:*` to see all logs or only the market-related ones, +respectively. For more information, please refer to +the [debug package documentation](https://www.npmjs.com/package/debug). ## Testing -Read the dedicated [testing documentation](./TESTING.md) to learn more about how to run tests of the SDK. +Read the dedicated [testing documentation](./docs/TESTING.md) to learn more about how to run tests of the SDK. ## Contributing -It is recommended to run unit tests and static code analysis before committing changes. +Read the [Contributing Guide](docs/CONTRIBUTING.md) for details on how you can get involved. In case you find an issue with the examples or the SDK itself, feel free to submit +an [issue report](https://github.com/golemfactory/golem-js/issues) to the repository. -```bash -yarn lint -# and -yarn format -``` +## Discord + +Feel invited to join our [Discord](http://discord.gg/golem). You can meet other SDK users and developers in the `#sdk-discussion` and `#js-discussion` channels. ## See also diff --git a/docs/CONCEPTS.md b/docs/CONCEPTS.md new file mode 100644 index 000000000..9143f7deb --- /dev/null +++ b/docs/CONCEPTS.md @@ -0,0 +1,113 @@ +# Concepts + +This document explains the concepts modelled by the SDK which foster your interactions with the Golem Network. + +## Table of Contents + + + +- [Concepts](#concepts) + - [Table of Contents](#table-of-contents) + - [GolemNetwork module](#golemnetwork-module) + - [Why is it needed](#why-is-it-needed) + - [What should it do](#what-should-it-do) + - [How it was done](#how-it-was-done) + - [Resource Rental Model](#resource-rental-model) + - [Why it is needed](#why-it-is-needed) + - [What it should do](#what-it-should-do) + - [How it was done](#how-it-was-done-1) + - [How is it different from TaskExecutor available in versions `1.x` and `2.x`](#how-is-it-different-from-taskexecutor-available-in-versions-1x-and-2x) + - [Exe Unit](#exe-unit) + - [Why is it needed](#why-is-it-needed-1) + - [What it should do](#what-it-should-do-1) + - [How it was done](#how-it-was-done-2) + + +## GolemNetwork module + +### Why is it needed + +When picturing the problem domain which is tackled by Golem Network, one see that the activities performed in this domain can fall into the following categories: + +- **Market** - where the computational needs of the Requestors need to meet the offers of the Providers, so that they can negotiate and establish an agreement. +- **Activity** - where Activities are started on ExeUnits running on the Providers under the terms of the negotiated Agreement. +- **Work** - where the Requestors preform their operations on the acquired resources (within the started activities). +- **Payment** - where payment related entities (DebitNotes, Invoices) are exchanged and final payments are made by the Requestor to the Provider. + +With `golem-js` we help you (the Requestor) by taking care of the _Market_, _Activity_ and _Payments_ faucets of the domain. + +### What should it do + +The `GolemNetwork` should serve as the main entry-point for `golem-js`. Users are expected to create new instances of this object, use `connect` and `disconnect` methods properly to feel the notion of "connecting" to the Golem Network. + +```ts +import { GolemNetwork } from "@golem-sdk/golem-js"; + +const glm = new GolemNetwork(); + +try { + await glm.connect(); + + // Do your work here +} catch (err) { + // Handle any errors +} finally { + await glm.disconnect(); +} +``` + +Once the user _connects_ to the network (in reality, connecting to the locally installed `yagna`), they have two was of leveraging the API's of that object. + +1. Use high-level generic purpose APIs such as `oneOf` or `manyOf` to acquire computational resources, without the need of diving deep into the various subdomains within Golem Network's problem space. +2. Use low-level modules representing these subdomains by accessing `glm.market`, `glm.activity`, `glm.payment` properties of the `GolemNetwork` object. + +### How it was done + +We do this by shaping modules reflecting these subdomains and exposing them from `golem-js` in form of properties of the `GolemNetwork` object. This way, you can rely on `golem-js` in these three areas and focus on your field of expertise, which is _Work_. + +## Resource Rental Model + +### Why it is needed + +The [Golem Network's whitepaper](https://assets.website-files.com/62446d07873fde065cbcb8d5/62446d07873fdeb626bcb927_Golemwhitepaper.pdf) coined the following definition: + +> Golem connects computers in a peer-to-peer network, enabling both application +> owners and individual users ("requestors") to rent resources of other users’ +> ("providers") machines. These resources can be used to complete tasks requiring any +> amount of computation time and capacity. + +Since version `3.0`, `golem-js` leverages the notion of _renting compute resources_ and models the exposed domain APIs around it. + +### What it should do + +The primary tasks for the model to deliver are: + +- provide an abstraction over the complexity of _Golem Network Protocol_ and leverage the "rent compute resources, access agreement and cost information" +- shorten the path of the user required to access a deployed instance of their workload +- provide convenience APIs that allow easier exploration of the Golem Network domain (example: `rental.agreement.provider` allows _easier_ access to Provider information) + +### How it was done + +To deliver this vision, `golem-js` exposes the `ResourceRental` **aggregate** which wraps around the details of the _Golem Network Protocol_: the loosely coupled entities which it defines, and the processes/communication schemes which it requires. These include things _Agreements_, _Allocations_, _Activities_, _Invoices_, _DebitNotes_ and related conversations required by the protocol such as _debit note or invoice acceptance_ between Provider and Requestor (you). + +When using the `ResourceRental` model, you can still access these lower level domain objects via the APIs exposed by the `ResourceRental` object instance. + +### How is it different from TaskExecutor available in versions `1.x` and `2.x` + +`TaskExecutor` implemented the so-called _Task Model_, which is oriented around the notion of a _Task_ defined as a series of commands to be executed that have to succeed altogether for the task to be considered as successful. While this model is suitable for use-cases involving batch-map-reduce type of operations that can be distributed across many rented resources, it falls short in cases where users want to rent out resources and execute long-running activities such as hosting web services for many users (multi-tenancy). + +## Exe Unit + +### Why is it needed + +When you obtain resources via the Golem Network, your _Golem Virtual Machine Image (GVMI for short)_ is going to be deployed by `golem-js` into the _Activity_ running on the Provider. Technically, on the Provider instantiates an _ExeUnit_ which is a physical implementation of the _Activity_ of the Golem Network Protocol. Without digging in too much details, it's this _ExeUnit_ which at the end performs the operations that you issue via the Golem Network. + +As a Requestor you're interested in quickly executing your commands within the container that runs your image. The `ExeUnit` abstraction delivered by the SDK is meant to do enable you to do so. The `ExeUnit` type documents the available features of particular ExeUnit type so that you can build up your solution faster, and in a type-safe manner. + +### What it should do + +The delivered `ExeUnit` implementation is a **use case** object which exposes APIs that simplify the interaction with the exe-unit running on the Provider. Technically, it models the _commands_ supported by the exe-unit depending on its runtime, so that the user can focus on issuing `exe.upload` or `exe.run` commands instead of understanding the implementation details of the VM exe-unit runtime. + +### How it was done + +Given a Requestor obtains `ResourceRental`, they can obtain the handle to the exe-unit thanks to `getExeUnit` method. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 000000000..ccd1d3c39 --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,81 @@ +# Contributing + +You want to contribute to `golem-js`? That's great! This guide will help you get started. + +## Setup local environment + +1. Clone this repository +2. In the root of this project run `npm install` to install all necessary dependencies +3. To build the SDK run `npm run build` +4. Install yagna as described in the [README](../README.md) file - you will need it to test your changes against testnet (no real funds will be required to execute workloads on Golem Network) + +### Unit Testing + +For unit testing `golem-js` uses `jest` with [ts-mockito](https://www.npmjs.com/package/@johanblumenberg/ts-mockito) to mock code. + +You can run tests using: + +```bash +npm run test:unit +``` + +The test files are usually co-located with the files that they test in the `src/` folder. + +### Pre-commit hooks + +We use `husky` to enforce few rules using `prettier`, `eslint` or even commit message format which allows us to use [semantic-release](https://github.com/semantic-release/semantic-release). + +## Pull Request Guidelines + +Our development revolves around few branches: + +- `master` - contains the latest stable production code (production track) +- `beta` - where the SDK team developers next major releases (slow track) +- `alpha` - when a different major release has to take precedence before `beta` (fast track) + +The process is as follows: + +- Depending on the contribution you're planning to make, create a `feature/`, `bugfix/` branch from the base branch (typically `master`), and merge back against that branch. +- In case of any contribution: + - Make sure you provide proper description for the PR (see template below) + - Add test cases if possible within the same PR + +### PR description templates + +#### Feature + +```markdown +## Why is it needed? + +_Explain why the feature is valuable and what problem does it solve._ + +## What should be changed? + +_Explain the general idea behind the code changes - high level description of your solution to the problem stated above._ +``` + +#### Bugfix + +```markdown +## Steps to reproduce + +1. _Do this_ +2. _Then that_ +3. _Finally this_ + +## Expected result + +_Describe the desired outcome (how does fixed look like)_ + +## Actual result + +_Describe the actual outcome (what happens now)_ +``` + +## Discord + +Feel invited to join our [Discord](http://discord.gg/golem). You can meet other SDK users and developers in the `#sdk-discussion` and `#js-discussion` channels. + +## Thanks πŸ’™ + +Thanks for all your contributions and efforts towards improving `golem-js`! diff --git a/docs/FEATURES.md b/docs/FEATURES.md new file mode 100644 index 000000000..50313be25 --- /dev/null +++ b/docs/FEATURES.md @@ -0,0 +1,204 @@ +# Features + +## Table of Contents + + + +- [Features](#features) + - [Table of Contents](#table-of-contents) + - [Streaming command results](#streaming-command-results) + - [File transfer](#file-transfer) + - [VPN](#vpn) + - [Events](#events) + - [Custom filters](#custom-filters) + - [Custom ranking of proposals](#custom-ranking-of-proposals) + - [Uploading local images to the provider](#uploading-local-images-to-the-provider) + - [Setup and teardown methods](#setup-and-teardown-methods) + - [Read more](#read-more) + + +## Streaming command results + +Instead of waiting for the command to finish, you can stream the results as they come in. This is useful for +long-running +commands, where you want to see the output as it's being produced. + +```ts +const remoteProcess = await exe.runAndStream( + ` +sleep 1 +echo -n 'Hello from stdout' >&1 +sleep 1 +echo -n 'Hello from stdout again' >&1 +sleep 1 +echo -n 'Hello from stdout yet again' >&1 +`, +); +remoteProcess.stdout.on("data", (data) => console.log("stdout>", data)); +await remoteProcess.waitForExit(); +``` + +[Check the full example](../examples/basic/run-and-stream.ts) + +## File transfer + +You can transfer files to and from the remote machine. This is useful when you need to provide input files or retrieve +the results of the computation. + +```ts +await exe + .beginBatch() + .run(`echo "Message from provider ${exe.provider.name}. Hello 😻" >> /golem/work/message.txt`) + .downloadFile("/golem/work/message.txt", "./message.txt") + .end(); +console.log(await readFile("./results.txt", { encoding: "utf-8" })); +``` + +[Check the full example](../examples/basic/transfer.ts) + +## VPN + +You can connect yourself and multiple providers to a VPN network. This is useful when you want to communicate +securely between the nodes. + +```ts +const network = await glm.createNetwork({ ip: "192.168.7.0/24" }); +// ... +const exe1 = await rental1.getExeUnit(); +const exe2 = await rental2.getExeUnit(); +await exe1 + .run(`ping ${exe2.getIp()} -c 4`) + .then((res) => console.log(`Response from provider: ${exe1.provider.name} (ip: ${exe1.getIp()})`, res.stdout)); +``` + +[Check the full example](../examples/basic/vpn.ts) + +## Events + +You can listen to various events that are emitted by the SDK. This is useful when you want to react to certain +conditions, like calculating the total cost of all invoices received. + +```ts +glm.payment.events.on("invoiceAccepted", (invoice) => { + console.log("Invoice '%s' accepted for %s GLM", invoice.id, invoice.amount); +}); +``` + +[Check the full example](../examples/basic/events.ts) + +## Custom filters + +You can define custom filters to select the providers that you want to work with. This is useful when you want to +blacklist or whitelist certain providers. + +```ts +const myFilter: ProposalFilter = (proposal) => proposal.provider.name !== "bad-provider"; + +const order: MarketOrderSpec = { + market: { + offerProposalFilter: myFilter, + // other options + }, +}; +``` + +[Check the full example](../examples/advanced/proposal-filter.ts) + +We have also prepared a set of predefined filters for common +use-cases. [Check out the example with predefined filters here](../examples/advanced/proposal-predefined-filter.ts) + +## Custom ranking of proposals + +You can define a method that will select which proposal should be chosen first. This is useful when you want to +prioritize certain providers over others. + +```ts +const scores = { + "very-good-provider": 10, + "good-provider": 5, + "bad-provider": -10, +}; + +const bestProviderSelector = (proposals: OfferProposal[]) => { + return proposals.sort((a, b) => (scores[b.provider.name] || 0) - (scores[a.provider.name] || 0))[0]; +}; + +const order: MarketOrderSpec = { + market: { + proposalSelector: bestProviderSelector, + // other options + }, +}; +``` + +[Check the full example](../examples/advanced/proposal-selector.ts) + +## Uploading local images to the provider + +You can avoid using the registry and upload a GVMI image directly to the provider. This is useful when you want to +quickly prototype your image without having to update the registry with every change. + +```ts +const order: MarketOrderSpec = { + demand: { + workload: { + imageUrl: "file:///path/to/your/image.gvmi", + }, + // other options + }, +}; +``` + +[Check the full example](../examples/advanced//local-image/) + +### Setup and teardown methods + +You can define a setup method that will be executed the first time a provider is rented and a teardown method +that will be executed before the rental is done. This is useful when you want to avoid doing the same work +multiple times when running multiple tasks on the same provider. + +```ts +// I want to upload a big file to each provider only once +const setup: LifecycleFunction = async (exe) => exe.uploadFile("./big-file.txt", "/golem/work/big-file.txt"); + +// I want to remove the file after I'm done +const teardown: LifecycleFunction = async (exe) => exe.run("rm /golem/work/big-file.txt"); + +const pool = await glm.manyOf({ + order, + poolSize, + setup, + teardown, +}); +``` + +[Check the full example](../examples/advanced/setup-and-teardown.ts) + + + +## Read more + +If you wish to learn more about how the SDK functions under the hood, please check out our more advanced examples: + +- [Creating pools manually](./.../examples/advanced/manual-pools.ts) +- [Performing all market operations manually](./.../examples/advanced/step-by-step.ts) +- [(for library authors) Override internal module](./.../examples/advanced/override-module.ts) diff --git a/TESTING.md b/docs/TESTING.md similarity index 100% rename from TESTING.md rename to docs/TESTING.md diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md new file mode 100644 index 000000000..6e1ec44be --- /dev/null +++ b/docs/UPGRADING.md @@ -0,0 +1,174 @@ +# Upgrade Guide + +This document describes the breaking changes introduced in each major version of `golem-js` and the necessary steps you need to take to ensure your application continues to work correctly. + +## Upgrading from 2.x to 3.x + +### Migrating from `TaskExecutor` to `GolemNetwork` + +Since the `TaskExecutor` has been removed in this release, you can migrate to the [@golem-sdk/task-executor](https://www.npmjs.com/package/@golem-sdk/task-executor) package as `1.x` of that package is compatible with `golem-js@2.x`. + +If you wish to stick to `golem-js`, here are the examples of changes which you need to make: + +#### Simple, single-command use cases + +Areas where the changes are needed: + +- You stop using `TaskExecutor` and switch to `GolemNetwork` instead. +- Be explicit about the expected computation time and pricing strategy so that `golem-js` can estimate the budget. +- You reach for an exe-unit representation with the `ResourceRental.getExeUnit` method and call your commands via the provided `ExeUnit` instance. + +**Before:** + +```ts +// before +import { TaskExecutor } from "@golem-sdk/golem-js"; + +(async function main() { + const executor = await TaskExecutor.create("golem/alpine:latest"); + try { + await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); + } catch (error) { + console.error("Failed to execute work:", error); + } finally { + await executor.shutdown(); + } +})(); +``` + +**After:** + +```ts +// after +import { GolemNetwork } from "@golem-sdk/golem-js"; + +(async function main() { + const glm = new GolemNetwork(); + try { + await glm.connect(); + + const retnal = await glm.oneOf({ + order: { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + // You have to be now explicit about about your terms and expectatios from the market + market: { + rentHours: 5 / 60, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }, + }, + }); + + // You will work with exe-unit objects instead of "executor" + await rental + .getExeUnit() + .then((exe) => exe.run("echo 'Hello World'")) + .then((res) => console.log(res.stdout)); + } catch (error) { + console.error("Failed to execute work:", error); + } finally { + await glm.disconnect(); + } +})(); +``` + +#### Engaging with many providers at the same time + +Areas where the changes are needed: + +- instead of using `maxParallelTasks` from `TaskExecutor`, use `poolSize` option on `GolemNetwork.manyOf` market order spec argument. + +**Before:** + +```ts +// before +import { GolemNetwork } from "@golem-sdk/golem-js"; + +(async function main() { + const executor = await GolemNetwork.create({ + imageTag: "golem/alpine:latest", + // πŸ”’ Number of max providers which you want to engage with + maxParallelTasks: 3, + }); + + try { + const inputs = [1, 2, 3, 4, 5]; + + const results = await Promise.allSettled( + inputs.map((input) => executor.run((ctx) => ctx.run(`echo 'Hello ${input}`))), + ); + + const responses = results.map((p) => (p.status === "fulfilled" ? p.value.stdout : null)).filter((v) => v !== null); + + console.log(responses); + } catch (error) { + console.error("Failed to execute work:", error); + } finally { + await executor.shutdown(); + } +})(); +``` + +```ts +// after +import { GolemNetwork } from "@golem-sdk/golem-js"; + +(async function main() { + const glm = new GolemNetwork(); + try { + await glm.connect(); + + // 🌟 You acquire a pool of ResourceRentals + const pool = await glm.manyOf({ + // πŸ”’ Number of max providers which you want to engage with + poolSize: 3, + order: { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + // You have to be now explicit about about your terms and expectatios from the market + market: { + rentHours: 5 / 60, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }, + }, + }); + + const inputs = [1, 2, 3, 4, 5]; + + // You still take the necessary precaucions, pipeline your work and processing + const results = await Promise.allSettled( + inputs.map((input) => + // 🌟🌟 You access rentals from the pool + pool.withRental((rental) => + rental + // 🌟🌟🌟 You issue the comands as in case of a single-provider scenario + .getExeUnit() + .run(`echo 'Hello ${input}`) + .then((res) => res.stdout), + ), + ), + ); + + // You still filter for the values which succeeded + const responses = results.map((p) => (p.status === "fulfilled" ? p.value : null)).filter((v) => v !== null); + + console.log(responses); + } catch (error) { + console.error("Failed to execute work:", error); + } finally { + await glm.disconnect(); + } +})(); +``` diff --git a/docs/USAGE.md b/docs/USAGE.md new file mode 100644 index 000000000..5289ac82b --- /dev/null +++ b/docs/USAGE.md @@ -0,0 +1,113 @@ +# Usage + +## Table of Contents + + + +- [Usage](#usage) + - [Table of Contents](#table-of-contents) + - [Renting a single machine and running a simple task on it](#renting-a-single-machine-and-running-a-simple-task-on-it) + - [Renting many machines and running tasks in parallel](#renting-many-machines-and-running-tasks-in-parallel) + + +## Renting a single machine and running a simple task on it + +```ts +import { MarketOrderSpec, GolemNetwork } from "@golem-sdk/golem-js"; + +// Define the order that we're going to place on the market +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + // We're only going to rent the provider for 5 minutes max + rentHours: 5 / 60, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }, +}; + +(async () => { + const glm = new GolemNetwork(); + + try { + await glm.connect(); + // Rent a machine + const rental = await glm.oneOf({ order }); + await rental + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem! πŸ‘‹")) + .then((res) => console.log(res.stdout)); + await rental.stopAndFinalize(); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); +``` + +## Renting many machines and running tasks in parallel + +```ts +import { GolemNetwork, MarketOrderSpec } from "@golem-sdk/golem-js"; + +// Define the order that we're going to place on the market +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }, +}; + +(async () => { + const glm = new GolemNetwork(); + + try { + await glm.connect(); + // create a pool that can grow up to 3 rentals at the same time + const pool = await glm.manyOf({ + poolSize: 3, + order, + }); + // run 3 tasks in parallel on 3 different machines + await Promise.allSettled([ + pool.withRental(async (rental) => + rental + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem from the first machine! πŸ‘‹")) + .then((res) => console.log(res.stdout)), + ), + pool.withRental(async (rental) => + rental + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem from the second machine! πŸ‘‹")) + .then((res) => console.log(res.stdout)), + ), + pool.withRental(async (rental) => + rental + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem from the third machine! πŸ‘‹")) + .then((res) => console.log(res.stdout)), + ), + ]); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); +``` diff --git a/package.json b/package.json index f0168ec25..944d91524 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,8 @@ "scripts": { "build": "rollup -c --forceExit", "dev": "rollup -c -w", - "docs": "typedoc src/ --plugin typedoc-plugin-merge-modules --plugin typedoc-theme-hierarchy", - "docs:md": "typedoc src/ --plugin typedoc-plugin-markdown --plugin .docs/typedoc-frontmatter-theme.cjs --hideBreadcrumbs true && node .docs/summary-generator.cjs", + "docs": "typedoc src/ --plugin typedoc-plugin-merge-modules --plugin typedoc-theme-hierarchy --out api-reference", + "docs:md": "typedoc src/ --plugin typedoc-plugin-markdown --plugin .typedoc/typedoc-frontmatter-theme.cjs --hideBreadcrumbs true --out api-reference && node .typedoc/summary-generator.cjs", "test": "npm run test:unit && npm run test:e2e", "test:unit": "jest --config tests/jest.config.json", "test:e2e": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --config tests/e2e/jest.config.json tests/e2e/**.spec.ts --runInBand --forceExit", diff --git a/src/golem-network/golem-network.ts b/src/golem-network/golem-network.ts index e2c78be3a..c6462e242 100644 --- a/src/golem-network/golem-network.ts +++ b/src/golem-network/golem-network.ts @@ -357,7 +357,7 @@ export class GolemNetwork { * * @example * ```ts - * const rental = await glm.oneOf(demand); + * const rental = await glm.oneOf({ order }); * await rental * .getExeUnit() * .then((exe) => exe.run("echo Hello, Golem! πŸ‘‹"))