UNPKG

@codex-storage/sdk-js

Version:

Codex SDK to interact with the Codex decentralized storage network.

684 lines (673 loc) 18.6 kB
'use strict'; var v = require('valibot'); function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var v__namespace = /*#__PURE__*/_interopNamespace(v); // src/api/config.ts var Api = { config: { prefix: "/api/codex/v1" } }; var CodexError = class extends Error { code; errors; sourceStack; constructor(message, { code, errors, sourceStack } = {}) { super(message); this.code = code || null; this.errors = errors || null; this.sourceStack = sourceStack || null; } }; var CodexValibotIssuesMap = (issues) => issues.map((i) => { var _a; return { expected: i.expected, received: i.received, message: i.message, path: (_a = i.path) == null ? void 0 : _a.map((item) => item.key).join(".") }; }); // src/promise-safe/promise-safe.ts var Promises = { async safe(promise) { try { const result = await promise(); return { error: false, data: result }; } catch (e) { return { error: true, data: new CodexError(e instanceof Error ? e.message : "" + e, { sourceStack: e instanceof Error ? e.stack || null : null }) }; } } }; // src/fetch-safe/fetch-safe.ts var FetchAuthBuilder = { build(auth) { if (auth == null ? void 0 : auth.basic) { return { Authorization: "Basic " + auth.basic }; } return {}; } }; var Fetch = { async safe(url, init) { const res = await Promises.safe(() => fetch(url, init)); if (res.error) { return { error: true, data: new CodexError(res.data.message, { code: 502 }) }; } if (!res.data.ok) { const message = await Promises.safe(() => res.data.text()); if (message.error) { return message; } return { error: true, data: new CodexError(message.data, { code: res.data.status }) }; } return { error: false, data: res.data }; }, async safeJson(url, init) { const res = await this.safe(url, init); if (res.error) { return res; } return Promises.safe(() => res.data.json()); }, async safeText(url, init) { const res = await this.safe(url, init); if (res.error) { return res; } return Promises.safe(() => res.data.text()); } }; // src/data/data.ts var CodexData = class { url; auth = {}; constructor(url, options) { this.url = url; if (options == null ? void 0 : options.auth) { this.auth = options.auth; } } /** * Lists manifest CIDs stored locally in node. * TODO: remove the faker data part when the api is ready */ cids() { const url = this.url + Api.config.prefix + "/data"; return Fetch.safeJson(url, { method: "GET", headers: FetchAuthBuilder.build(this.auth) }).then((data) => { if (data.error) { return data; } return { error: false, data: { content: data.data.content } }; }); } /** * Gets a summary of the storage space allocation of the node. */ space() { const url = this.url + Api.config.prefix + "/space"; return Fetch.safeJson(url, { method: "GET", headers: FetchAuthBuilder.build(this.auth) }); } /** * Upload a file in a streaming manner. * Once completed, the file is stored in the node and can be retrieved by any node in the network using the returned CID. * XMLHttpRequest is used instead of fetch for this case, to obtain progress information. * A callback onProgress can be passed to receive upload progress data information. */ upload(stategy) { const url = this.url + Api.config.prefix + "/data"; return { result: stategy.upload(url, { auth: this.auth }), abort: () => { stategy.abort(); } }; } /** * Download a file from the local node in a streaming manner. * If the file is not available locally, a 404 is returned. */ async localDownload(cid) { const url = this.url + Api.config.prefix + "/data/" + cid; return Fetch.safe(url, { method: "GET", headers: FetchAuthBuilder.build(this.auth) }); } /** * Download a file from the network to the local node if it's not available locally. * Note: Download is performed async. Call can return before download is completed. */ async networkDownload(cid) { const url = this.url + Api.config.prefix + `/data/${cid}/network`; return Fetch.safeJson(url, { method: "POST", headers: FetchAuthBuilder.build(this.auth) }); } /** * Download a file from the network in a streaming manner. * If the file is not available locally, it will be retrieved from other nodes in the network if able. */ async networkDownloadStream(cid) { const url = this.url + Api.config.prefix + `/data/${cid}/network/stream`; return Fetch.safe(url, { method: "GET", headers: FetchAuthBuilder.build(this.auth) }); } /** * Download only the dataset manifest from the network to the local node * if it's not available locally. */ async fetchManifest(cid) { const url = this.url + Api.config.prefix + `/data/${cid}/network/manifest`; return Fetch.safeJson(url, { method: "GET", headers: FetchAuthBuilder.build(this.auth) }); } }; // src/node/node.ts var CodexNode = class { url; auth = {}; constructor(url, options) { this.url = url; if (options == null ? void 0 : options.auth) { this.auth = options.auth; } } /** * Connect to a peer */ connect(peerId, addrs = []) { const params = new URLSearchParams(); for (const addr of addrs) { params.append("addrs", addr); } const url = this.url + Api.config.prefix + `/connect/${peerId}?` + params.toString(); return Fetch.safeText(url, { method: "GET", headers: FetchAuthBuilder.build(this.auth) }); } /** * Get Node's SPR */ async spr(type = "json") { const url = this.url + Api.config.prefix + "/spr"; if (type === "json") { return Fetch.safeJson(url, { method: "GET", headers: { ...FetchAuthBuilder.build(this.auth), "Content-Type": "application/json" } }); } return Fetch.safeText(url, { method: "GET", headers: { ...FetchAuthBuilder.build(this.auth), "Content-Type": "text/plain" } }); } /** * Get Node's PeerID */ peerId(type = "json") { const url = this.url + Api.config.prefix + "/node/peerid"; if (type === "json") { return Fetch.safeJson(url, { method: "GET", headers: { ...FetchAuthBuilder.build(this.auth), "Content-Type": "application/json" } }); } return Fetch.safeText(url, { method: "GET", headers: { ...FetchAuthBuilder.build(this.auth), "Content-Type": "text/plain" } }); } }; var CodexCreateAvailabilityInput = v__namespace.strictObject({ totalSize: v__namespace.pipe(v__namespace.number(), v__namespace.minValue(1)), duration: v__namespace.pipe(v__namespace.number(), v__namespace.minValue(1)), minPricePerBytePerSecond: v__namespace.number(), totalCollateral: v__namespace.number(), enabled: v__namespace.optional(v__namespace.boolean()), until: v__namespace.optional(v__namespace.number()) }); var CodexAvailabilityPatchInput = v__namespace.strictObject({ id: v__namespace.string(), totalSize: v__namespace.pipe(v__namespace.number(), v__namespace.minValue(1)), duration: v__namespace.pipe(v__namespace.number(), v__namespace.minValue(1)), minPricePerBytePerSecond: v__namespace.number(), totalCollateral: v__namespace.number(), enabled: v__namespace.optional(v__namespace.boolean()), until: v__namespace.optional(v__namespace.number()) }); var CodexCreateStorageRequestInput = v__namespace.strictObject({ cid: v__namespace.string(), duration: v__namespace.pipe(v__namespace.number(), v__namespace.minValue(1)), pricePerBytePerSecond: v__namespace.number(), proofProbability: v__namespace.number(), nodes: v__namespace.optional(v__namespace.number(), 1), tolerance: v__namespace.optional(v__namespace.number(), 0), expiry: v__namespace.pipe(v__namespace.number(), v__namespace.minValue(1)), collateralPerByte: v__namespace.number() }); // src/marketplace/marketplace.ts var CodexMarketplace = class { url; auth = {}; constructor(url, options) { this.url = url; if (options == null ? void 0 : options.auth) { this.auth = options.auth; } } /** * Returns active slots */ async activeSlots() { const url = this.url + Api.config.prefix + "/sales/slots"; return Fetch.safeJson(url, { method: "GET", headers: FetchAuthBuilder.build(this.auth) }); } /** * Returns active slot with id {slotId} for the host */ async activeSlot(slotId) { const url = this.url + Api.config.prefix + "/sales/slots/" + slotId; return Fetch.safeJson(url, { method: "GET", headers: FetchAuthBuilder.build(this.auth) }); } transformAvailability({ freeSize, ...a }) { const availability = { ...a, minPricePerBytePerSecond: parseInt(a.minPricePerBytePerSecond, 10), totalCollateral: parseInt(a.totalCollateral, 10), totalRemainingCollateral: parseInt(a.totalRemainingCollateral, 10) }; if (freeSize) { availability.freeSize = freeSize; } return availability; } /** * Returns storage that is for sale */ async availabilities() { const url = this.url + Api.config.prefix + "/sales/availability"; const res = await Fetch.safeJson(url, { method: "GET", headers: FetchAuthBuilder.build(this.auth) }); if (res.error) { return res; } return { error: false, data: res.data.map(this.transformAvailability) }; } /** * Offers storage for sale */ async createAvailability(input) { const result = v__namespace.safeParse(CodexCreateAvailabilityInput, input); if (!result.success) { return { error: true, data: new CodexError("Cannot validate the input", { errors: CodexValibotIssuesMap(result.issues) }) }; } const url = this.url + Api.config.prefix + "/sales/availability"; const body = { totalSize: result.output.totalSize, duration: result.output.duration, minPricePerBytePerSecond: result.output.minPricePerBytePerSecond.toString(), totalCollateral: result.output.totalCollateral.toString() }; if (result.output.enabled) { body.enabled = result.output.enabled; } if (result.output.until) { body.until = result.output.until; } return Fetch.safeJson(url, { method: "POST", headers: FetchAuthBuilder.build(this.auth), body: JSON.stringify(body) }).then((result2) => { if (result2.error) { return result2; } return { error: false, data: this.transformAvailability(result2.data) }; }); } /** * The new parameters will be only considered for new requests. * Existing Requests linked to this Availability will continue as is. */ async updateAvailability(input) { const result = v__namespace.safeParse(CodexAvailabilityPatchInput, input); if (!result.success) { return { error: true, data: new CodexError("Cannot validate the input", { errors: CodexValibotIssuesMap(result.issues) }) }; } const url = this.url + Api.config.prefix + "/sales/availability/" + result.output.id; const body = { totalSize: result.output.totalSize, duration: result.output.duration, minPricePerBytePerSecond: result.output.minPricePerBytePerSecond.toString(), totalCollateral: result.output.totalCollateral.toString() }; if (result.output.enabled) { body.enabled = result.output.enabled; } if (result.output.until) { body.until = result.output.until; } const res = await Fetch.safe(url, { method: "PATCH", headers: FetchAuthBuilder.build(this.auth), body: JSON.stringify(body) }); if (res.error) { return res; } return { error: false, data: "" }; } /** * Return's list of Reservations for ongoing Storage Requests that the node hosts. */ async reservations(availabilityId) { const url = this.url + Api.config.prefix + `/sales/availability/${availabilityId}/reservations`; return Fetch.safeJson(url, { method: "GET", headers: FetchAuthBuilder.build(this.auth) }); } /** * Returns list of purchase IDs */ async purchaseIds() { const url = this.url + Api.config.prefix + `/storage/purchases`; return Fetch.safeJson(url, { method: "GET", headers: FetchAuthBuilder.build(this.auth) }); } transformPurchase(p) { const purchase = { requestId: p.requestId, state: p.state }; if (p.error) { purchase.error = p.error; } if (!p.request) { return purchase; } return { ...purchase, request: { ...p.request, ask: { ...p.request.ask, proofProbability: parseInt(p.request.ask.proofProbability, 10), pricePerBytePerSecond: parseInt( p.request.ask.pricePerBytePerSecond, 10 ) } } }; } async purchases() { const res = await this.purchaseIds(); if (res.error) { return res; } const promises = []; for (const id of res.data) { promises.push(this.purchaseDetail(id)); } const purchases = await Promise.all(promises); return { error: false, data: purchases.map( (p) => p.error ? { state: "error", error: p.data.message, requestId: "" } : p.data ) }; } /** * Returns purchase details */ async purchaseDetail(purchaseId) { const url = this.url + Api.config.prefix + `/storage/purchases/` + purchaseId; return Fetch.safeJson(url, { headers: FetchAuthBuilder.build(this.auth), method: "GET" }).then((res) => { if (res.error) { return res; } return { error: false, data: this.transformPurchase(res.data) }; }); } /** * Creates a new request for storage. */ async createStorageRequest(input) { const result = v__namespace.safeParse(CodexCreateStorageRequestInput, input); if (!result.success) { return { error: true, data: new CodexError("Cannot validate the input", { errors: CodexValibotIssuesMap(result.issues) }) }; } const { cid, duration, pricePerBytePerSecond, proofProbability, nodes, collateralPerByte, expiry, tolerance } = result.output; const url = this.url + Api.config.prefix + "/storage/request/" + cid; return Fetch.safeText(url, { method: "POST", headers: FetchAuthBuilder.build(this.auth), body: JSON.stringify({ duration, pricePerBytePerSecond: pricePerBytePerSecond.toString(), proofProbability: proofProbability.toString(), nodes, collateralPerByte: collateralPerByte.toString(), expiry, tolerance }) }); } }; var CodexLogLevelInput = v__namespace.picklist([ "TRACE", "DEBUG", "INFO", "NOTICE", "WARN", "ERROR", "FATAL" ]); var CodexDebug = class { url; auth = {}; constructor(url, options) { this.url = url; if (options == null ? void 0 : options.auth) { this.auth = options.auth; } } /** * Set log level at run time */ async setLogLevel(level) { const result = v__namespace.safeParse(CodexLogLevelInput, level); if (!result.success) { return Promise.resolve({ error: true, data: new CodexError("Cannot validate the input", { errors: CodexValibotIssuesMap(result.issues) }) }); } const url = this.url + Api.config.prefix + "/debug/chronicles/loglevel?level=" + level; return Fetch.safeText(url, { method: "POST", headers: FetchAuthBuilder.build(this.auth), body: "" }); } /** * Gets node information */ info() { const url = this.url + Api.config.prefix + `/debug/info`; return Fetch.safeJson(url, { method: "GET", headers: FetchAuthBuilder.build(this.auth) }); } }; // src/index.ts var Codex = class { url; _marketplace; _data; _node; _debug; auth = {}; constructor(url, options) { this.url = url; this._marketplace = null; this._data = null; this._node = null; this._debug = null; if (options == null ? void 0 : options.auth) { this.auth = options == null ? void 0 : options.auth; } } get marketplace() { if (this._marketplace) { return this._marketplace; } this._marketplace = new CodexMarketplace(this.url, { auth: this.auth }); return this._marketplace; } get data() { if (this._data) { return this._data; } this._data = new CodexData(this.url, { auth: this.auth }); return this._data; } get node() { if (this._node) { return this._node; } this._node = new CodexNode(this.url, { auth: this.auth }); return this._node; } get debug() { if (this._debug) { return this._debug; } this._debug = new CodexDebug(this.url, { auth: this.auth }); return this._debug; } }; exports.Codex = Codex; exports.CodexAvailabilityPatchInput = CodexAvailabilityPatchInput; exports.CodexCreateAvailabilityInput = CodexCreateAvailabilityInput; exports.CodexCreateStorageRequestInput = CodexCreateStorageRequestInput; exports.CodexData = CodexData; exports.CodexDebug = CodexDebug; exports.CodexError = CodexError; exports.CodexLogLevelInput = CodexLogLevelInput; exports.CodexMarketplace = CodexMarketplace; exports.CodexNode = CodexNode; exports.CodexValibotIssuesMap = CodexValibotIssuesMap; exports.Fetch = Fetch; exports.FetchAuthBuilder = FetchAuthBuilder; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map