@codex-storage/sdk-js
Version:
Codex SDK to interact with the Codex decentralized storage network.
684 lines (673 loc) • 18.6 kB
JavaScript
'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