Example

JS Task API Examples: transferring data

Introduction

In this article, we will present methods that let you send files to and from a provider as well as send JSON to a remote computer.

warning

Look at the table below to check what particular methods are available in each environment.

The following commands are currently available:

CommandAvailable in node.jsAvailable in web browser
run()yesyes
uploadFile()yesno
uploadJson()yesyes
downloadFile()yesno
uploadData()yesyes
downloadData()noyes
downloadJson()noyes

Prerequisites

Yagna service is installed and running with the try_golem app-key configured.

How to run examples

Create a project folder, initialize a Node.js project, and install libraries.

mkdir golem-example
cd golem-example
npm init
npm i @golem-sdk/task-executor
npm i @golem-sdk/pino-logger

To run the examples provided below, copy the code supplied there into the index.mjs file in the project folder and run:

node index.mjs

Uploading a file to the provider

In this example, we calculate the md5 hash of the example script worker.mjs, send it to a remote computer, and calculate the md5 hash of the file in the remote location. Finally, we print both values for comparison.

info

This example requires a simple worker.mjs script that can be created with the following command:

echo console.log("Hello Golem World!"); > worker.mjs
import { TaskExecutor } from "@golem-sdk/task-executor";
import { pinoPrettyLogger } from "@golem-sdk/pino-logger";
import { createHash } from "node:crypto";
import * as fs from "fs";

(async () => {
  const executor = await TaskExecutor.create({
    logger: pinoPrettyLogger({ level: "info" }),
    api: { key: "try_golem" },
    demand: {
      workload: {
        imageTag: "golem/node:20-alpine",
      },
    },
    market: {
      rentHours: 0.5,
      pricing: {
        model: "linear",
        maxStartPrice: 0.5,
        maxCpuPerHourPrice: 1.0,
        maxEnvPerHourPrice: 0.5,
      },
    },
  });

  const buff = fs.readFileSync("worker.mjs");
  const hash = createHash("md5").update(buff).digest("hex");

  try {
    const result = await executor.run(async (exe) => {
      await exe.uploadFile("./worker.mjs", "/golem/input/worker.mjs");

      const res = await exe.run(
        `node -e "const crypto = require('node:crypto'); const fs = require('fs'); const buff = fs.readFileSync('/golem/input/worker.mjs'); const hash = crypto.createHash('md5').update(buff).digest('hex'); console.log(hash);"`,
      );

      return res.stdout;
    });

    console.log("md5 of the file sent to provider: ", result);
    console.log("Locally computed md5: ", hash);
  } catch (error) {
    console.error("Computation failed:", error);
  } finally {
    await executor.shutdown();
  }
})();

Uploadfile output log

Downloading a file from the provider

In this example, we create a file on a remote computer, list its content to a result object, and finally download it to compare its content with the result obtained remotely.

import { TaskExecutor } from "@golem-sdk/task-executor";
import { pinoPrettyLogger } from "@golem-sdk/pino-logger";
import { readFileSync } from "fs";

(async () => {
  const executor = await TaskExecutor.create({
    logger: pinoPrettyLogger(),
    api: { key: "try_golem" },
    demand: {
      workload: {
        imageTag: "golem/node:20-alpine",
      },
    },
    market: {
      rentHours: 0.5,
      pricing: {
        model: "linear",
        maxStartPrice: 0.5,
        maxCpuPerHourPrice: 1.0,
        maxEnvPerHourPrice: 0.5,
      },
    },
  });

  try {
    await executor.run(async (exe) => {
      await exe
        .beginBatch()
        .run("ls -l /golem > /golem/work/output.txt")
        .run("cat /golem/work/output.txt")
        .downloadFile("/golem/work/output.txt", "./output.txt")
        .end();
    });
    console.log(readFileSync("./output.txt", "utf8"));
  } catch (error) {
    console.error(error);
  } finally {
    await executor.shutdown();
  }
})();

Downloadfile output log

Uploading JSON to provider

import { TaskExecutor } from "@golem-sdk/task-executor";
import { pinoPrettyLogger } from "@golem-sdk/pino-logger";

(async () => {
  const executor = await TaskExecutor.create({
    logger: pinoPrettyLogger(),
    api: { key: "try_golem" },
    demand: {
      workload: {
        imageTag: "golem/node:20-alpine",
      },
    },
    market: {
      rentHours: 0.5,
      pricing: {
        model: "linear",
        maxStartPrice: 0.5,
        maxCpuPerHourPrice: 1.0,
        maxEnvPerHourPrice: 0.5,
      },
    },
  });

  try {
    const output = await executor.run(async (exe) => {
      // Upload test JSON object
      await exe.uploadJson({ input: "Hello World" }, "/golem/work/input.json");

      // Read the content of the JSON object.
      return await exe.run("cat /golem/work/input.json");
    });
    console.log(output.stdout);
  } catch (error) {
    console.error("Computation failed:", error);
  } finally {
    await executor.shutdown();
  }
})();

DownloadJSON output logs

Uploading data to and from the provider (in a browser)

In this example, we demonstrate how to use the uploadData() and downloadData() methods that allow you to send and receive data when you run your requestor script from a browser. The code below realizes the following job: let the user select an image file and a text, then send it to the provider where it utilizes ImageMagick to combine the image and text to create a meme. The result is displayed in the browser window.

The example utilizes a basic HTML boilerplate that defines UI components:

  • a form to select the background image for the meme and user text.
  • the Generate meme button to trigger the task
  • the result frame where the output meme will be displayed
  • log section - where the logs are displayed.
warning

To run this example you must use Yagna version 0.13 or higher and run it using the --api-allow-origin parameter. The example code should be saved as the index.html file and served by i.e. http-server. See Web Quickstart for instructions.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>WebRequestor Task API</title>
  </head>
  <body>
    <h1>WebRequestor - Meme Example</h1>
    <div class="container">
      <div class="col-6">
        <h3>Options</h3>
        <div class="column">
          <div>
            <label for="YAGNA_API_BASEPATH">Yagna Api BaseUrl: </label>
            <input id="YAGNA_API_BASEPATH" type="text" value="http://127.0.0.1:7465" />
          </div>
          <div>
            <label for="SUBNET_TAG">Subnet Tag: </label>
            <input id="SUBNET_TAG" type="text" value="public" />
          </div>
          <div>
            <label for="PAYMENT_NETWORK">Payment network: </label>
            <input id="PAYMENT_NETWORK" type="text" value="holesky" />
          </div>
        </div>
        <h3>Input data</h3>
        <div class="column">
          <div>
            <label for="MEME_TEXT">Meme Text: </label>
            <input id="MEME_TEXT" type="text" value="some meme text" />
          </div>
          <div>
            <label for="MEME_IMG">Meme Image: </label>
            <input type="file" id="MEME_IMG" accept="image/*" />
          </div>
        </div>
        <h3>Actions</h3>
        <div class="row vertical">
          <div>
            <button id="RUN">Generate meme</button>
          </div>
        </div>
        <div class="results console">
          <h3>Result Meme</h3>
          <img style="width: 100%" alt="" id="RESULT_MEME" />
        </div>
      </div>
      <div class="col-6 border-left">
        <div class="logs console">
          <h3>Logs</h3>
          <ul id="logs"></ul>
        </div>
      </div>
    </div>

    <script type="module">
      import { TaskExecutor } from "https://unpkg.com/@golem-sdk/task-executor";

      let activity;
      const imgInput = document.getElementById("MEME_IMG");
      const textInput = document.getElementById("MEME_TEXT");
      const imgResult = document.getElementById("RESULT_MEME");
      let urlObject;

      function readFile(file) {
        return new Promise((resolve, reject) => {
          const fileReader = new FileReader();
          fileReader.readAsArrayBuffer(file);

          fileReader.onload = () => {
            resolve(new Uint8Array(fileReader.result));
          };

          fileReader.onerror = (error) => {
            reject(error);
          };
        });
      }

      function appendLog(msg) {
        const logs_el = document.getElementById("logs");
        const li = document.createElement("li");
        li.appendChild(document.createTextNode(msg));
        logs_el.appendChild(li);
      }
      function setResponse(result) {
        if (urlObject) {
          URL.revokeObjectURL(urlObject);
        }
        urlObject = URL.createObjectURL(new Blob([result], { type: "image/jpeg" }));
        imgResult.src = urlObject;
      }

      const logger = {
        warn: (msg) => appendLog(`[${new Date().toISOString()}] [warn] ${msg}`),
        debug: (msg) => appendLog(`[${new Date().toISOString()}] [debug] ${msg}`),
        error: (msg) => appendLog(`[${new Date().toISOString()}] [error] ${msg}`),
        info: (msg) => appendLog(`[${new Date().toISOString()}] [info] ${msg}`),
        child: () => logger,
      };

      async function run() {
        if (!imgInput.files.length) {
          alert("Please select an image file");
          return;
        }

        const fileData = await readFile(imgInput.files[0]);
        const extension = imgInput.files[0].name.split(".").pop();
        const inputImage = `/golem/input/input.${extension}`;
        const outputImage = `/golem/output/output.jpeg`;
        const text = textInput.value.replace(/'/g, `'\\''`);

        const executor = await TaskExecutor.create({
          logger,
          api: { key: "try_golem", url: document.getElementById("YAGNA_API_BASEPATH").value },
          demand: {
            workload: {
              imageTag: "golem/imagemagick:latest",
            },
            subnetTag: document.getElementById("SUBNET_TAG").value,
          },
          market: {
            rentHours: 0.5,
            pricing: {
              model: "linear",
              maxStartPrice: 0.5,
              maxCpuPerHourPrice: 1.0,
              maxEnvPerHourPrice: 0.5,
            },
          },
          payment: { network: document.getElementById("PAYMENT_NETWORK").value },
        });

        const result = await executor
          .run(async (exe) => {
            await exe.uploadData(fileData, inputImage);
            await exe.run(
              `convert ${inputImage} -background Khaki -pointsize 50 label:'${text}' -gravity Center -append ${outputImage}`,
            );
            return await exe.downloadData(outputImage);
          })
          .catch((e) => logger.error(e));

        await executor.shutdown();
        if (result?.data) setResponse(result.data);
      }
      document.getElementById("RUN").onclick = run;
    </script>
  </body>
</html>

The .uploadData(fileData, inputImage) method is used to copy the user-provided data to the location defined by inputImage. The file is read from a disk by the readFile() function.

The .downloadData(outputImage) method downloads the data which is accessible in the data attribute of the respective result object. The setResponse() function is used to update the src attribute of the dom element devised to display the output.

Other functions are explained in the Web Quickstart article.

Uploading JSON to and from the provider (in a browser)

In this example, we demonstrate how to use the uploadJson() and downloadJson() methods that allow you to send and receive JSON data when you run your requestor script from a browser. The code below realizes the following job: let the user define the JSON object { "input": "Hello World" }, then send it to the provider. On the remote computer, JSON is saved as /golem/work/input.json. Next the attribute input is replaced into output with sed command and saved as /golem/work/output.json. Finally, the file is downloaded with the downloadJson() method. The output of the command contains the JSON as data attribute.

The example utilizes a basic HTML boilerplate that defines UI components:

  • the Run button to trigger the task
  • the result frame where the content of output attribute of the JSON will be displayed
  • log section - where the logs are displayed.
warning

To run this example you must use Yagna version 0.13 or higher and run it using the --api-allow-origin parameter. See Web Quickstart for instructions.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Golem JSON App</title>
    <script type="module">
      import { TaskExecutor } from "https://unpkg.com/@golem-sdk/task-executor";

      function appendResults(result) {
        const results = document.getElementById("results");
        const div = document.createElement("div");
        div.appendChild(document.createTextNode(result));
        results.appendChild(div);
      }
      function appendLog(msg, level = "info") {
        const logs = document.getElementById("logs");
        const div = document.createElement("div");
        div.appendChild(document.createTextNode(`[${new Date().toISOString()}] [${level}] ${msg}`));
        logs.appendChild(div);
      }
      const logger = {
        warn: (msg) => appendLog(msg, "warn"),
        debug: (msg) => 0, //appendLog(msg, 'debug'),
        error: (msg) => appendLog(msg, "error"),
        info: (msg) => appendLog(msg, "info"),
        child: () => logger,
      };
      async function run() {
        const executor = await TaskExecutor.create({
          logger,
          api: { key: "try_golem", url: document.getElementById("YAGNA_API_BASEPATH").value },
          demand: {
            workload: {
              imageTag: "golem/node:20-alpine",
            },
            subnetTag: document.getElementById("SUBNET_TAG").value,
          },
          market: {
            rentHours: 0.5,
            pricing: {
              model: "linear",
              maxStartPrice: 0.5,
              maxCpuPerHourPrice: 1.0,
              maxEnvPerHourPrice: 0.5,
            },
          },
          payment: { network: document.getElementById("PAYMENT_NETWORK").value },
        }).catch((e) => logger.error(e));

        await executor
          .run(async (exe) => {
            // Upload test JSON object
            await exe.uploadJson({ input: "Hello World" }, "/golem/work/input.json");
            // Modify sent JSON to replace the input key with output
            await exe.run("cat /golem/work/input.json | sed s/input/output/ > /golem/work/output.json");
            // Download the JSON object.
            const output = await exe.downloadJson("/golem/work/output.json");
            appendResults(JSON.stringify(output.data.output, null, "\t"));
          })
          .catch((e) => logger.error(e));
        await executor.shutdown();
      }
      document.getElementById("echo").onclick = run;
    </script>
  </head>
  <body>
    <h1>JSON upload and download</h1>
    <div class="container">
      <div class="col-6">
        <h3>Options</h3>
        <div class="column">
          <div>
            <label for="YAGNA_API_BASEPATH">Yagna Api BaseUrl: </label>
            <input id="YAGNA_API_BASEPATH" type="text" value="http://127.0.0.1:7465" />
          </div>
          <div>
            <label for="SUBNET_TAG">Subnet Tag: </label>
            <input id="SUBNET_TAG" type="text" value="public" />
          </div>
          <div>
            <label for="PAYMENT_NETWORK">Payment network: </label>
            <input id="PAYMENT_NETWORK" type="text" value="holesky" />
          </div>
        </div>
        <h3>Actions</h3>
        <div class="row vertical">
          <div>
            <button id="echo">Run</button>
          </div>
        </div>
        <div class="results console">
          <h3>Results</h3>
          <ul id="results"></ul>
        </div>
      </div>
      <div class="col-6 border-left">
        <div class="logs console">
          <h3>Logs</h3>
          <ul id="logs"></ul>
        </div>
      </div>
    </div>
  </body>
</html>

All other functions are explained in the Web Quickstart article.