From 620683f19d43d48dedb6dce5e7a8b80251568519 Mon Sep 17 00:00:00 2001 From: Seweryn Kras Date: Fri, 7 Jun 2024 12:03:34 +0200 Subject: [PATCH 1/3] docs: refactor local gvmi example to use oneOf --- .../advanced/local-image/serveLocalGvmi.ts | 66 +++++-------------- 1 file changed, 18 insertions(+), 48 deletions(-) diff --git a/examples/advanced/local-image/serveLocalGvmi.ts b/examples/advanced/local-image/serveLocalGvmi.ts index c0701bba7..29cce32c1 100644 --- a/examples/advanced/local-image/serveLocalGvmi.ts +++ b/examples/advanced/local-image/serveLocalGvmi.ts @@ -1,14 +1,16 @@ -import { DraftOfferProposalPool, GolemNetwork, MarketOrderSpec } from "@golem-sdk/golem-js"; - +/** + * This example demonstrates how to upload a local GVMI file to the provider. + * Take a look at the `Dockerfile` in the same directory to see what's inside the image. + */ +import { GolemNetwork, MarketOrderSpec } from "@golem-sdk/golem-js"; import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; -import { fileURLToPath } from "url"; // get the absolute path to the local image in case this file is run from a different directory -const getImagePath = (path: string) => fileURLToPath(new URL(path, import.meta.url).toString()); +const getImagePath = (path: string) => new URL(path, import.meta.url).toString(); (async () => { const logger = pinoPrettyLogger({ - level: "debug", + level: "info", }); const glm = new GolemNetwork({ @@ -21,13 +23,13 @@ const getImagePath = (path: string) => fileURLToPath(new URL(path, import.meta.u const order: MarketOrderSpec = { demand: { workload: { - // Here you supply the path to the GVMI file that you want to deploy and use - // using the file:// protocol will make the SDK switch to "GVMI" serving mode - imageUrl: `file://${getImagePath("./alpine.gvmi")}`, + // if the image url starts with "file://" it will be treated as a local file + // and the sdk will automatically serve it to the provider + imageUrl: getImagePath("./alpine.gvmi"), }, }, market: { - rentHours: 12, + rentHours: 5 / 60, pricing: { model: "linear", maxStartPrice: 1, @@ -37,48 +39,16 @@ const getImagePath = (path: string) => fileURLToPath(new URL(path, import.meta.u }, }; - const proposalPool = new DraftOfferProposalPool({ - logger, - }); - - const allocation = await glm.payment.createAllocation({ - budget: 1, - expirationSec: 30 * 60, // 30 minutes - }); - const demandSpecification = await glm.market.buildDemandDetails(order.demand, allocation); - const draftProposal$ = glm.market.collectDraftOfferProposals({ - demandSpecification, - pricing: order.market.pricing, - }); - const proposalSubscription = proposalPool.readFrom(draftProposal$); - const draftProposal = await proposalPool.acquire(); - - const agreement = await glm.market.proposeAgreement(draftProposal); - - const lease = glm.lease.createLease(agreement, allocation); - const activity = await glm.activity.createActivity(agreement); - - // We managed to create the activity, no need to look for more agreement candidates - proposalSubscription.unsubscribe(); - - // Access your work context to perform operations - const ctx = await glm.activity.createWorkContext(activity); - - // Perform your work - const result = await ctx.run("cat hello.txt"); - console.log("Contents of 'hello.txt': ", result.stdout?.toString().trim()); - - // Start clean shutdown procedure for business components - await glm.activity.destroyActivity(activity); - await glm.market.terminateAgreement(agreement); - await proposalPool.remove(draftProposal); - - // This will keep the script waiting for payments etc - await lease.finalize(); + const lease = await glm.oneOf(order); + // in our Dockerfile we have created a file called hello.txt, let's read it + const result = await lease + .getExeUnit() + .then((exe) => exe.run("cat hello.txt")) + .then((res) => res.stdout); + console.log(result); } catch (err) { console.error("Failed to run example on Golem", err); } finally { - // Clean shutdown of infrastructure components await glm.disconnect(); } })().catch(console.error); From 06130328d3780708577c54e218e7a4f8a44963ab Mon Sep 17 00:00:00 2001 From: Seweryn Kras Date: Fri, 7 Jun 2024 15:03:14 +0200 Subject: [PATCH 2/3] feat: allow passing factory functions to override modules --- examples/advanced/override-module.ts | 72 ++++++++++++++++++++++++++ src/golem-network/golem-network.ts | 77 ++++++++++++++++++++-------- tests/examples/examples.json | 1 + 3 files changed, 130 insertions(+), 20 deletions(-) create mode 100644 examples/advanced/override-module.ts diff --git a/examples/advanced/override-module.ts b/examples/advanced/override-module.ts new file mode 100644 index 000000000..df49d26ba --- /dev/null +++ b/examples/advanced/override-module.ts @@ -0,0 +1,72 @@ +/** + * In this advanced example, we will provide our own implementation of one of the core modules + * of the SDK. This example is catered towards library authors who want to extend the SDK's + * functionality or to advanced users who know what they're doing. + * It's **very** easy to break things if you don't have a good understanding of the SDK's internals, + * therefore this feature is not recommended for most users. + */ + +import { Concurrency, MarketOrderSpec, GolemNetwork } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; + +// let's override the `estimateBudget` method from the `MarketModule` interface +// to provide our own implementation +// we need to import the default implementation of the module we want to override +import { MarketModuleImpl } from "@golem-sdk/golem-js"; + +class MyMarketModule extends MarketModuleImpl { + estimateBudget({ concurrency, order }: { concurrency: Concurrency; order: MarketOrderSpec }): number { + // let's take the original estimate and add 20% to it as a buffer + const originalEstimate = super.estimateBudget({ concurrency, order }); + return originalEstimate * 1.2; + } +} + +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + // based on this order, the "normal" estimateBudget would return 1.5 + // (0.5 start price + 0.5 / hour for CPU + 0.5 / hour for env). + // Our override should return 1.8 (1.5 * 1.2) + market: { + rentHours: 1, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 0.5, + maxEnvPerHourPrice: 0.5, + }, + }, +}; + +(async () => { + const glm = new GolemNetwork({ + logger: pinoPrettyLogger({ + level: "info", + }), + // here's where we provide our own implementation + override: { + market: MyMarketModule, + }, + }); + + // look at the console output to see the budget estimate + glm.payment.events.on("allocationCreated", (allocation) => { + console.log("Allocation created with budget:", Number(allocation.remainingAmount).toFixed(2)); + }); + + try { + await glm.connect(); + const lease = await glm.oneOf(order); + await lease + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem! 👋")) + .then((res) => console.log(res.stdout)); + await lease.finalize(); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); diff --git a/src/golem-network/golem-network.ts b/src/golem-network/golem-network.ts index 459a6858e..dafcce6cd 100644 --- a/src/golem-network/golem-network.ts +++ b/src/golem-network/golem-network.ts @@ -39,6 +39,36 @@ import { DataTransferProtocol } from "../shared/types"; import { NetworkApiAdapter } from "../shared/yagna/adapters/network-api-adapter"; import { IProposalRepository } from "../market/proposal"; +/** + * Instance of an object or a factory function that you can call `new` on. + * Optionally you can provide constructor arguments. + */ +type InstanceOrFactory = + | TargetInterface + | { new (...args: ConstructorArgs): TargetInterface }; + +/** + * If no override is provided, return a function that creates a new instance of the default factory. + * If override is a factory, return a function that creates a new instance of that factory. + * If override is an instance, return a function that returns that instance (ignoring the arguments). + */ +function getFactory< + DefaultFactoryConstructorArgs extends unknown[], + InstanceType extends object, + FactoryType extends { new (...args: DefaultFactoryConstructorArgs): InstanceType }, +>( + defaultFactory: FactoryType, + override: InstanceOrFactory | undefined, +): (...args: ConstructorParameters) => InstanceType { + if (override) { + if (typeof override === "function") { + return (...args) => new override(...args); + } + return () => override; + } + return (...args) => new defaultFactory(...args); +} + export interface GolemNetworkOptions { /** * Logger instance to use for logging. @@ -68,14 +98,15 @@ export interface GolemNetworkOptions { * Override some of the services used by the GolemNetwork instance. * This is useful for testing or when you want to provide your own implementation of some services. * Only set this if you know what you are doing. + * To override a module you can pass either an instance of an object or a factory function (that we can call `new` on). */ override?: Partial< GolemServices & { - market: MarketModule; - payment: PaymentModule; - activity: ActivityModule; - network: NetworkModule; - lease: LeaseModule; + market: InstanceOrFactory; + payment: InstanceOrFactory; + activity: InstanceOrFactory; + network: InstanceOrFactory; + lease: InstanceOrFactory; } >; } @@ -221,21 +252,27 @@ export class GolemNetwork { networkApi: this.options.override?.networkApi || new NetworkApiAdapter(this.yagna, this.logger), fileServer: this.options.override?.fileServer || new GftpServerAdapter(this.storageProvider), }; - this.network = this.options.override?.network || new NetworkModuleImpl(this.services); - this.market = - this.options.override?.market || new MarketModuleImpl({ ...this.services, networkModule: this.network }); - this.payment = this.options.override?.payment || new PaymentModuleImpl(this.services, this.options.payment); - this.activity = this.options.override?.activity || new ActivityModuleImpl(this.services); - this.lease = - this.options.override?.lease || - new LeaseModuleImpl({ - activityModule: this.activity, - paymentModule: this.payment, - marketModule: this.market, - networkModule: this.network, - logger: this.logger, - storageProvider: this.storageProvider, - }); + this.network = getFactory(NetworkModuleImpl, this.options.override?.network)(this.services); + this.market = getFactory( + MarketModuleImpl, + this.options.override?.market, + )({ + ...this.services, + networkModule: this.network, + }); + this.payment = getFactory(PaymentModuleImpl, this.options.override?.payment)(this.services, this.options.payment); + this.activity = getFactory(ActivityModuleImpl, this.options.override?.activity)(this.services); + this.lease = getFactory( + LeaseModuleImpl, + this.options.override?.lease, + )({ + activityModule: this.activity, + paymentModule: this.payment, + marketModule: this.market, + networkModule: this.network, + logger: this.logger, + storageProvider: this.storageProvider, + }); } catch (err) { this.events.emit("error", err); throw err; diff --git a/tests/examples/examples.json b/tests/examples/examples.json index 70e1cd979..85036fefd 100644 --- a/tests/examples/examples.json +++ b/tests/examples/examples.json @@ -9,6 +9,7 @@ { "cmd": "tsx", "path": "examples/advanced/payment-filters.ts" }, { "cmd": "tsx", "path": "examples/advanced/proposal-filter.ts" }, { "cmd": "tsx", "path": "examples/advanced/proposal-predefined-filter.ts" }, + { "cmd": "tsx", "path": "examples/advanced/override-module.ts" }, { "cmd": "tsx", "path": "examples/experimental/deployment/new-api.ts" }, { "cmd": "tsx", "path": "examples/experimental/job/getJobById.ts" }, { "cmd": "tsx", "path": "examples/experimental/job/waitForResults.ts" }, From f0e952a23cb270e4e176ab16904a90e0e0072ad5 Mon Sep 17 00:00:00 2001 From: Seweryn Kras Date: Fri, 7 Jun 2024 15:09:17 +0200 Subject: [PATCH 3/3] docs: list all golem-js 3.0 features in the docs --- README.md | 417 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 278 insertions(+), 139 deletions(-) diff --git a/README.md b/README.md index 77ead52bb..e177d282f 100644 --- a/README.md +++ b/README.md @@ -15,22 +15,28 @@ - [Golem JavaScript API](#golem-javascript-api) - [Table of contents](#table-of-contents) - [What's Golem and `golem-js`?](#whats-golem-and-golem-js) - - [System requirements](#system-requirements) + - [Documentation](#documentation) - [Installation](#installation) - - [Building](#building) - - [Usage](#usage) - - [Hello World example](#hello-world-example) - - [More examples](#more-examples) - [Supported environments](#supported-environments) - - [Golem Network Market Basics](#golem-network-market-basics) - - [Mid-agreement payments to the Providers for used resources](#mid-agreement-payments-to-the-providers-for-used-resources) - - [Limit price limits to filter out offers that are too expensive](#limit-price-limits-to-filter-out-offers-that-are-too-expensive) - - [Work with reliable providers](#work-with-reliable-providers) + - [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) + - [Going further](#going-further) + - [More examples](#more-examples) - [Debugging](#debugging) - [Testing](#testing) - [Contributing](#contributing) - [See also](#see-also) - ## What's Golem and `golem-js`? @@ -42,19 +48,42 @@ 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. -## System requirements +## Documentation + +Visit our [official documentation](https://docs.golem.network/docs/creators/javascript) to learn more about the +JavaScript SDK and how to use it. + +## Installation + +To quickly get started with a new project using `golem-js`, you can use the following template: + +```bash +npx @golem-sdk/cli@latest new my-awesome-golem-project +``` + +`@golem-sdk/golem-js` is available as a [NPM package](https://www.npmjs.com/package/@golem-sdk/golem-js). + +You can install it through `npm`: + +```bash +npm install @golem-sdk/golem-js +``` -To use `golem-js`, it is necessary to have yagna installed, with a **recommended minimum version of v0.14.0**. Yagna is a -service that communicates and performs operations on the Golem Network, upon your requests via the SDK. You -can [follow these instructions](https://docs.golem.network/docs/creators/javascript/quickstarts/quickstart#install-yagna-2) -to set it up. +or by `yarn`: -### Simplified installation steps +```bash +yarn add @golem-sdk/golem-js +``` + +## Supported environments + +The SDK is designed to work with LTS versions of Node (starting from 18) +and with browsers. -In order to get started and on Golem Network and obtain test GLM tokens (`tGLM`) that will allow you to build on the -test network, follow these steps: +## Getting started with Golem Network -#### Join the network as a requestor and obtain test tokens +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. ```bash # Join the network as a requestor @@ -63,7 +92,11 @@ curl -sSf https://join.golem.network/as-requestor | bash - # Start the golem node on your machine, # you can use `daemonize` to run this in background yagna service run +``` + +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) # Initialize your requestor yagna payment init --sender --network holesky @@ -75,188 +108,294 @@ yagna payment fund --network holesky yagna payment status --network holesky ``` -#### Obtain your `app-key` to use with SDK +#### Obtain an `app-key` to use with SDK If you don't have any app-keys available from `yagna app-key list`, go ahead and create one with the command below. -You will need this key in order to communicate with `yagna` from your application via `golem-js`.You can set it +You will need this key in order to communicate with `yagna` from your application. You can set it as `YAGNA_APPKEY` environment variable. ```bash yagna app-key create my-golem-app ``` -## Installation - -`@golem-sdk/golem-js` is available as a [NPM package](https://www.npmjs.com/package/@golem-sdk/golem-js). - -You can install it through `npm`: - -```bash -npm install @golem-sdk/golem-js -``` +## Usage -or by `yarn`: +### Renting a single machine and running a simple task on it -```bash -yarn add @golem-sdk/golem-js -``` +```ts +import { MarketOrderSpec, GolemNetwork } from "@golem-sdk/golem-js"; -## Building +// 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, + }, + }, +}; -To build a library available to the NodeJS environment: +(async () => { + const glm = new GolemNetwork(); -```bash -npm run build -# or -yarn build + try { + await glm.connect(); + // Lease a machine + const lease = await glm.oneOf(order); + await lease + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem! 👋")) + .then((res) => console.log(res.stdout)); + await lease.finalize(); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); ``` -This will generate production code in the `dist/` directory ready to be used in your nodejs or browser applications. +### Renting many machines and running tasks in parallel -## Usage +```ts +import { GolemNetwork, MarketOrderSpec } from "@golem-sdk/golem-js"; -### Hello World example +// 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, + }, + }, +}; -```ts -import { TaskExecutor } from "@golem-sdk/golem-js"; +(async () => { + const glm = new GolemNetwork(); -(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("Computation failed:", error); + await glm.connect(); + // create a pool that can grow up to 3 leases 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.withLease(async (lease) => + lease + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem from the first machine! 👋")) + .then((res) => console.log(res.stdout)), + ), + pool.withLease(async (lease) => + lease + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem from the second machine! 👋")) + .then((res) => console.log(res.stdout)), + ), + pool.withLease(async (lease) => + lease + .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 executor.shutdown(); + await glm.disconnect(); } -})(); +})().catch(console.error); ``` -### More examples +## Features -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. +### Streaming command results -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. +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. -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). +```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(); +``` -## Supported environments +[Check the full example](./examples/basic/run-and-stream.ts) -The SDK is designed to work with LTS versions of Node (starting from 18) -and with browsers. +### File transfer -## Golem Network Market Basics +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. -The Golem Network provides an open marketplace where anyone can join as a Provider and supply the network with their -computing power. In return for their service, they are billing Requestors (users of this SDK) according to the pricing -that they define. +```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" })); +``` -As a Requestor, you might want to: +[Check the full example](./examples/basic/transfer.ts) -- control the limit price so that you're not going to over-spend your funds -- control the interactions with the providers if you have a list of the ones which you like or the ones which you would - like to avoid +### VPN -To make this easy, we provided you with a set of predefined market proposal filters, which you can combine to implement -your own market strategy (described below). +You can connect yourself and multiple providers to a VPN network. This is useful when you want to communicate +securely between the nodes. -### Mid-agreement payments to the Providers for used resources +```ts +const network = await glm.createNetwork({ ip: "192.168.7.0/24" }); +// ... +const exe1 = await lease1.getExeUnit(); +const exe2 = await lease2.getExeUnit(); +await exe1 + .run(`ping ${exe2.getIp()} -c 4`) + .then((res) => console.log(`Response from provider: ${exe1.provider.name} (ip: ${exe1.getIp()})`, res.stdout)); +``` -When you obtain resources from the Provider and start using them, the billing cycle will start immediately. -Since reliable service and payments are important for all actors in the Golem Network, -the SDK makes use of the mid-agreement payments model and implements best practices for the market, which include: +[Check the full example](./examples/basic/vpn.ts) -- responding and accepting debit notes for activities that last longer than 30 minutes -- issuing mid-agreement payments (pay-as-you-go) +### Events -By default, the SDK will: +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. -- accept debit notes sent by the Providers within two minutes of receipt (so that the Provider knows that we're alive, - and it will continue serving the resources) -- issue a mid-agreement payment every 12 hours (so that the provider will be paid on a regular interval for serving the - resources for more than 10 hours) +```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) -You can learn more about -the [mid-agreement and other payment models from the official docs](https://docs.golem.network/docs/golem/payments). +### Custom filters -These values are defaults and can be influenced by the following settings: +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. -- `DemandOptions.expirationSec` -- `DemandOptions.debitNotesAcceptanceTimeoutSec` -- `DemandOptions.midAgreementPaymentTimeoutSec` +```ts +const myFilter: ProposalFilter = (proposal) => proposal.provider.name !== "bad-provider"; -If you're using `TaskExecutor` to run tasks on Golem, you can pass them as part of the configuration object accepted -by `TaskExecutor.create`. Consult [JS API reference](https://docs.golem.network/docs/golem-js/reference/overview) for -details. +const order: MarketOrderSpec = { + market: { + proposalFilter: myFilter, + // other options + }, +}; +``` -### Limit price limits to filter out offers that are too expensive +[Check the full example](./examples/advanced/proposal-filter.ts) -```typescript -import { TaskExecutor, ProposalFilterFactory } from "@golem-sdk/golem-js"; +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) -const executor = await TaskExecutor.create({ - // What do you want to run - package: "golem/alpine:3.18.2", +### Custom ranking of proposals - // How much you wish to spend - budget: 0.5, - proposalFilter: ProposalFilterFactory.limitPriceFilter({ - start: 1, - cpuPerSec: 1 / 3600, - envPerSec: 1 / 3600, - }), +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. - // Where you want to spend - payment: { - network: "polygon", +```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 }, -}); +}; ``` -To learn more about other filters, please check -the [API reference of the market/strategy module](https://docs.golem.network/docs/golem-js/reference/modules/market_strategy) +[Check the full example](./examples/advanced/proposal-selector.ts) -### Work with reliable providers +### Uploading local images to the provider -The `getHealthyProvidersWhiteList` helper will provide you with a list of Provider ID's that were checked with basic -health-checks. Using this whitelist will increase the chance of working with a reliable provider. Please note, that you -can also build up your own list of favourite providers and use it in a similar fashion. +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. -```typescript -import { MarketHelpers, ProposalFilterFactory, TaskExecutor } from "@golem-sdk/golem-js"; - -// Collect the whitelist -const verifiedProviders = await MarketHelpers.getHealthyProvidersWhiteList(); +```ts +const order: MarketOrderSpec = { + demand: { + workload: { + imageUrl: "file:///path/to/your/image.gvmi", + }, + // other options + }, +}; +``` -// Prepare the whitelist filter -const whiteList = ProposalFilterFactory.allowProvidersById(verifiedProviders); +[Check the full example](./examples/advanced//local-image/) -// Prepare the price filter -const acceptablePrice = ProposalFilterFactory.limitPriceFilter({ - start: 1, - cpuPerSec: 1 / 3600, - envPerSec: 1 / 3600, -}); + + +## 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 + +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). + ## 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).