Apply prettier format

This commit is contained in:
Arnaud 2024-08-15 12:08:45 +02:00
parent ddb98b794a
commit 5e36ffb443
No known key found for this signature in database
GPG Key ID: 69D6CE281FCAE663
14 changed files with 786 additions and 720 deletions

View File

@ -1,10 +0,0 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

18
package-lock.json generated
View File

@ -14,11 +14,12 @@
"devDependencies": {
"@faker-js/faker": "^8.4.1",
"@tsconfig/strictest": "^2.0.5",
"prettier": "^3.3.3",
"tsup": "^8.2.3",
"typescript": "^5.5.4"
},
"engines": {
"node": ">=20"
"node": ">=18"
}
},
"node_modules/@esbuild/aix-ppc64": {
@ -1568,6 +1569,21 @@
}
}
},
"node_modules/prettier": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",

View File

@ -9,11 +9,12 @@
"scripts": {
"prepack": "npm run build",
"prebuild": "rm -Rf dist/*",
"build": "tsup tsup src/index.ts --format esm,cjs --dts --minify",
"build": "tsup tsup src/index.ts --format esm,cjs --dts",
"compile": "tsc --noEmit",
"pretest": "npm run build",
"test": "node --test",
"watch": "tsc --watch"
"watch": "tsc --watch",
"format": "prettier --write ./src"
},
"keywords": [
"Codex",
@ -48,10 +49,11 @@
"devDependencies": {
"@faker-js/faker": "^8.4.1",
"@tsconfig/strictest": "^2.0.5",
"prettier": "^3.3.3",
"tsup": "^8.2.3",
"typescript": "^5.5.4"
},
"dependencies": {
"valibot": "^0.36.0"
}
}
}

4
prettier.config.cjs Normal file
View File

@ -0,0 +1,4 @@
module.exports = {
trailingComma: 'es5',
bracketSameLine: true,
};

View File

@ -1,5 +1,5 @@
export const Api = {
config: {
prefix: "/api/codex/v1"
}
}
config: {
prefix: "/api/codex/v1",
},
};

View File

@ -1,19 +1,19 @@
import type { SafeValue } from "../values/values"
import type { SafeValue } from "../values/values";
export class Disk {
readonly url: string
readonly url: string;
constructor(url: string) {
this.url = url
}
constructor(url: string) {
this.url = url;
}
async available(): Promise<SafeValue<{ full: number, used: number }>> {
return {
error: false,
data: {
full: 500,
used: 200
}
}
}
async available(): Promise<SafeValue<{ full: number; used: number }>> {
return {
error: false,
data: {
full: 500,
used: 200,
},
};
}
}

View File

@ -1,37 +1,39 @@
import { type InferIssue } from "valibot"
import { type InferIssue } from "valibot";
type ValidationError = {
expected: string
received: string
message: string
path: string
}
expected: string;
received: string;
message: string;
path: string;
};
/**
* The CodexError which can be error object of 3 types:
* `error`: Object containing the error message
* `api`: Object containing the api error message and the status code
* `validation`: Object containing the error message and a field `errors` of type ValidationError
* containing the error message for each fields.
* The CodexError which can be error object of 3 types:
* `error`: Object containing the error message
* `api`: Object containing the api error message and the status code
* `validation`: Object containing the error message and a field `errors` of type ValidationError
* containing the error message for each fields.
*/
export type CodexError = {
type: "error"
message: string
} | {
type: "api"
message: string
status: number
} | {
type: "validation"
message: string
errors: ValidationError[]
}
export const CodexValibotIssuesMap = (issues: InferIssue<any>[]) => issues.map(i => ({
expected: i.expected,
received: i.received,
message: i.message,
path: i.path.map((item: { key: string }) => item.key).join('.')
}))
export type CodexError =
| {
type: "error";
message: string;
}
| {
type: "api";
message: string;
status: number;
}
| {
type: "validation";
message: string;
errors: ValidationError[];
};
export const CodexValibotIssuesMap = (issues: InferIssue<any>[]) =>
issues.map((i) => ({
expected: i.expected,
received: i.received,
message: i.message,
path: i.path.map((item: { key: string }) => item.key).join("."),
}));

View File

@ -1,88 +1,95 @@
import assert from "assert";
import { describe, it } from "node:test";
import { Fetch } from '../fetch-safe/fetch-safe';
import { Fetch } from "../fetch-safe/fetch-safe";
class MockResponse implements Response {
headers: Headers = new Headers()
ok: boolean;
redirected = false
status: number;
statusText = "";
type = "basic" as "basic"
url = ""
body = null;
bodyUsed = false;
_text: string
headers: Headers = new Headers();
ok: boolean;
redirected = false;
status: number;
statusText = "";
type = "basic" as "basic";
url = "";
body = null;
bodyUsed = false;
_text: string;
constructor(ok: boolean, status: number, text: string) {
this.ok = ok
this.status = status
this._text = text
}
constructor(ok: boolean, status: number, text: string) {
this.ok = ok;
this.status = status;
this._text = text;
}
clone(): Response {
throw new Error("Method not implemented.");
}
clone(): Response {
throw new Error("Method not implemented.");
}
arrayBuffer(): Promise<ArrayBuffer> {
throw new Error("Method not implemented.");
}
arrayBuffer(): Promise<ArrayBuffer> {
throw new Error("Method not implemented.");
}
blob(): Promise<Blob> {
throw new Error("Method not implemented.");
}
blob(): Promise<Blob> {
throw new Error("Method not implemented.");
}
formData(): Promise<FormData> {
throw new Error("Method not implemented.");
}
formData(): Promise<FormData> {
throw new Error("Method not implemented.");
}
json(): Promise<any> {
return Promise.resolve(JSON.parse(this._text))
}
json(): Promise<any> {
return Promise.resolve(JSON.parse(this._text));
}
text(): Promise<string> {
return Promise.resolve(this._text)
}
text(): Promise<string> {
return Promise.resolve(this._text);
}
}
describe("fetch", () => {
it("returns an error when the http call failed", async (t) => {
global.fetch = t.mock.fn(() =>
Promise.resolve(new MockResponse(false, 500, "error")),
);
it("returns an error when the http call failed", async (t) => {
global.fetch = t.mock.fn(() =>
Promise.resolve(new MockResponse(false, 500, "error"))
);
const result = await Fetch.safe("http://localhost:3000/some-url", { method: "GET" })
const error = {
type: "api",
message: "error",
status: 500
}
const result = await Fetch.safe("http://localhost:3000/some-url", {
method: "GET",
});
const error = {
type: "api",
message: "error",
status: 500,
};
assert.deepStrictEqual(result, { error: true, data: error });
assert.deepStrictEqual(result, { error: true, data: error });
});
it("returns an error when the json parsing failed", async (t) => {
global.fetch = t.mock.fn(() =>
Promise.resolve(new MockResponse(true, 200, ""))
);
const result = await Fetch.safe("http://localhost:3000/some-url", {
method: "GET",
});
const error = {
type: "error",
message: "Unexpected end of JSON input",
};
assert.deepStrictEqual(result, { error: true, data: error });
});
it("returns the data when the fetch succeed", async (t) => {
global.fetch = t.mock.fn(() =>
Promise.resolve(
new MockResponse(true, 200, JSON.stringify({ hello: "world" }))
)
);
const result = await Fetch.safe("http://localhost:3000/some-url", {
method: "GET",
});
it("returns an error when the json parsing failed", async (t) => {
global.fetch = t.mock.fn(() =>
Promise.resolve(new MockResponse(true, 200, "")),
);
const result = await Fetch.safe("http://localhost:3000/some-url", { method: "GET" })
const error = {
type: "error",
message: "Unexpected end of JSON input"
}
assert.deepStrictEqual(result, { error: true, data: error });
});
it("returns the data when the fetch succeed", async (t) => {
global.fetch = t.mock.fn(() =>
Promise.resolve(new MockResponse(true, 200, JSON.stringify({ hello: "world" }))),
);
const result = await Fetch.safe("http://localhost:3000/some-url", { method: "GET" })
assert.deepStrictEqual(result, { error: false, data: { hello: "world" } });
});
assert.deepStrictEqual(result, { error: false, data: { hello: "world" } });
});
});

View File

@ -1,34 +1,37 @@
import { type SafeValue } from "../values/values"
import { type SafeValue } from "../values/values";
export const Fetch = {
async safe<T extends Object>(url: string, init: RequestInit): Promise<SafeValue<T>> {
const res = await fetch(url, init)
async safe<T extends Object>(
url: string,
init: RequestInit
): Promise<SafeValue<T>> {
const res = await fetch(url, init);
if (!res.ok) {
const message = await res.text()
if (!res.ok) {
const message = await res.text();
return {
error: true,
data: {
type: "api",
message,
status: res.status
}
}
}
return {
error: true,
data: {
type: "api",
message,
status: res.status,
},
};
}
try {
const json = await res.json()
try {
const json = await res.json();
return { error: false, data: json }
} catch (e) {
return {
error: true,
data: {
type: "error",
message: e instanceof Error ? e.message : "JSON parsing error :" + e
}
}
}
}
}
return { error: false, data: json };
} catch (e) {
return {
error: true,
data: {
type: "error",
message: e instanceof Error ? e.message : "JSON parsing error :" + e,
},
};
}
},
};

View File

@ -5,26 +5,25 @@ export * from "./fetch-safe/fetch-safe";
export * from "./marketplace/types";
export class Codex {
readonly url: string
private _marketplace: Marketplace | null
readonly disk: Disk
readonly url: string;
private _marketplace: Marketplace | null;
readonly disk: Disk;
constructor(url: string) {
this.url = url
this._marketplace = null
this.disk = new Disk(url)
}
constructor(url: string) {
this.url = url;
this._marketplace = null;
this.disk = new Disk(url);
}
async marketplace() {
if (this._marketplace) {
return this._marketplace
}
async marketplace() {
if (this._marketplace) {
return this._marketplace;
}
const module = await import("./marketplace/marketplace")
const module = await import("./marketplace/marketplace");
this._marketplace = new module.Marketplace(this.url)
this._marketplace = new module.Marketplace(this.url);
return module.Marketplace
}
return module.Marketplace;
}
}

View File

@ -36,343 +36,354 @@ import { Marketplace } from "./marketplace";
// }
function createStorageRequest() {
return {
cid: faker.string.alphanumeric(64),
duration: faker.number.int({ min: 1 }),
reward: faker.number.int(),
proofProbability: faker.number.int(),
nodes: faker.number.int(),
tolerance: faker.number.int(),
expiry: faker.number.int({ min: 1 }),
collateral: faker.number.int()
}
return {
cid: faker.string.alphanumeric(64),
duration: faker.number.int({ min: 1 }),
reward: faker.number.int(),
proofProbability: faker.number.int(),
nodes: faker.number.int(),
tolerance: faker.number.int(),
expiry: faker.number.int({ min: 1 }),
collateral: faker.number.int(),
};
}
function missingNumberValidationError(field: string) {
return {
error: true,
data: {
type: "validation",
message: "Cannot validate the input",
errors: [
{
path: field,
expected: 'number',
message: 'Invalid type: Expected number but received undefined',
received: 'undefined'
},
]
}
}
return {
error: true,
data: {
type: "validation",
message: "Cannot validate the input",
errors: [
{
path: field,
expected: "number",
message: "Invalid type: Expected number but received undefined",
received: "undefined",
},
],
},
};
}
function extraValidationError(field: string, value: unknown) {
return {
error: true,
data: {
type: "validation",
message: "Cannot validate the input",
errors: [
{
path: field,
expected: 'never',
message: `Invalid type: Expected never but received "${value}"`,
received: `"${value}"`
},
]
}
}
return {
error: true,
data: {
type: "validation",
message: "Cannot validate the input",
errors: [
{
path: field,
expected: "never",
message: `Invalid type: Expected never but received "${value}"`,
received: `"${value}"`,
},
],
},
};
}
function missingStringValidationError(field: string) {
return {
error: true,
data: {
type: "validation",
message: "Cannot validate the input",
errors: [
{
path: field,
expected: 'string',
message: 'Invalid type: Expected string but received undefined',
received: 'undefined'
}
]
}
}
return {
error: true,
data: {
type: "validation",
message: "Cannot validate the input",
errors: [
{
path: field,
expected: "string",
message: "Invalid type: Expected string but received undefined",
received: "undefined",
},
],
},
};
}
function mistypeNumberValidationError(field: string, value: string) {
return {
error: true,
data: {
type: "validation",
message: "Cannot validate the input",
errors: [
{
path: field,
expected: 'number',
message: `Invalid type: Expected number but received "${value}"`,
received: `"${value}"`
},
]
}
}
return {
error: true,
data: {
type: "validation",
message: "Cannot validate the input",
errors: [
{
path: field,
expected: "number",
message: `Invalid type: Expected number but received "${value}"`,
received: `"${value}"`,
},
],
},
};
}
function minNumberValidationError(field: string, min: number) {
return {
error: true,
data: {
type: "validation",
message: "Cannot validate the input",
errors: [
{
path: field,
expected: '>=' + min,
message: 'Invalid value: Expected >=1 but received 0',
received: '0'
}
]
}
}
return {
error: true,
data: {
type: "validation",
message: "Cannot validate the input",
errors: [
{
path: field,
expected: ">=" + min,
message: "Invalid value: Expected >=1 but received 0",
received: "0",
},
],
},
};
}
function createAvailability() {
return {
"id": faker.finance.ethereumAddress(),
"availabilityId": faker.finance.ethereumAddress(),
"size": faker.number.int({ min: 3000, max: 300000 }),
"requestId": faker.finance.ethereumAddress(),
"slotIndex": faker.number.int({ min: 0, max: 9 })
}
return {
id: faker.finance.ethereumAddress(),
availabilityId: faker.finance.ethereumAddress(),
size: faker.number.int({ min: 3000, max: 300000 }),
requestId: faker.finance.ethereumAddress(),
slotIndex: faker.number.int({ min: 0, max: 9 }),
};
}
describe("marketplace", () => {
const marketplace = new Marketplace("http://localhost:3000")
const marketplace = new Marketplace("http://localhost:3000");
it("returns an error when trying to create an availability without total size", async () => {
const response = await marketplace.createAvailability({
duration: 3000,
maxCollateral: 1,
minPrice: 100,
} as any)
it("returns an error when trying to create an availability without total size", async () => {
const response = await marketplace.createAvailability({
duration: 3000,
maxCollateral: 1,
minPrice: 100,
} as any);
assert.deepStrictEqual(response, missingNumberValidationError("totalSize"));
});
assert.deepStrictEqual(response, missingNumberValidationError("totalSize"));
});
it.only("returns an error when trying to create an availability with an invalid number valid", async () => {
const response = await marketplace.createAvailability({
duration: 3000,
maxCollateral: 1,
minPrice: 100,
totalSize: "abc"
} as any)
it.only("returns an error when trying to create an availability with an invalid number valid", async () => {
const response = await marketplace.createAvailability({
duration: 3000,
maxCollateral: 1,
minPrice: 100,
totalSize: "abc",
} as any);
assert.deepStrictEqual(response, mistypeNumberValidationError("totalSize", "abc"));
});
assert.deepStrictEqual(
response,
mistypeNumberValidationError("totalSize", "abc")
);
});
it("returns an error when trying to create an availability with zero total size", async () => {
const response = await marketplace.createAvailability({
duration: 3000,
maxCollateral: 1,
minPrice: 100,
totalSize: 0
})
it("returns an error when trying to create an availability with zero total size", async () => {
const response = await marketplace.createAvailability({
duration: 3000,
maxCollateral: 1,
minPrice: 100,
totalSize: 0,
});
assert.deepStrictEqual(response, minNumberValidationError("totalSize", 1))
});
assert.deepStrictEqual(response, minNumberValidationError("totalSize", 1));
});
it("returns an error when trying to create an availability without duration", async () => {
const response = await marketplace.createAvailability({
totalSize: 3000,
maxCollateral: 1,
minPrice: 100,
} as any)
it("returns an error when trying to create an availability without duration", async () => {
const response = await marketplace.createAvailability({
totalSize: 3000,
maxCollateral: 1,
minPrice: 100,
} as any);
console.info(response.error)
console.info(response.error);
assert.deepStrictEqual(response, missingNumberValidationError("duration"));
});
assert.deepStrictEqual(response, missingNumberValidationError("duration"));
});
it("returns an error when trying to create an availability with zero duration", async () => {
const response = await marketplace.createAvailability({
duration: 0,
maxCollateral: 1,
minPrice: 100,
totalSize: 3000
})
it("returns an error when trying to create an availability with zero duration", async () => {
const response = await marketplace.createAvailability({
duration: 0,
maxCollateral: 1,
minPrice: 100,
totalSize: 3000,
});
assert.deepStrictEqual(response, minNumberValidationError("duration", 1))
});
assert.deepStrictEqual(response, minNumberValidationError("duration", 1));
});
it("returns an error when trying to create an availability without min price", async () => {
const response = await marketplace.createAvailability({
totalSize: 3000,
maxCollateral: 1,
duration: 100,
} as any)
it("returns an error when trying to create an availability without min price", async () => {
const response = await marketplace.createAvailability({
totalSize: 3000,
maxCollateral: 1,
duration: 100,
} as any);
assert.deepStrictEqual(response, missingNumberValidationError("minPrice"));
});
assert.deepStrictEqual(response, missingNumberValidationError("minPrice"));
});
it("returns an error when trying to create an availability without max collateral", async () => {
const response = await marketplace.createAvailability({
totalSize: 3000,
minPrice: 100,
duration: 100,
} as any)
it("returns an error when trying to create an availability without max collateral", async () => {
const response = await marketplace.createAvailability({
totalSize: 3000,
minPrice: 100,
duration: 100,
} as any);
assert.deepStrictEqual(response, missingNumberValidationError("maxCollateral"));
});
assert.deepStrictEqual(
response,
missingNumberValidationError("maxCollateral")
);
});
it("returns an error when trying to create an availability with an extra field", async () => {
const response = await marketplace.createAvailability({
maxCollateral: 1,
totalSize: 3000,
minPrice: 100,
duration: 100,
hello: "world"
} as any)
it("returns an error when trying to create an availability with an extra field", async () => {
const response = await marketplace.createAvailability({
maxCollateral: 1,
totalSize: 3000,
minPrice: 100,
duration: 100,
hello: "world",
} as any);
assert.deepStrictEqual(response, extraValidationError("hello", "world"));
});
assert.deepStrictEqual(response, extraValidationError("hello", "world"));
});
it("returns a response when the request succeed", async (t) => {
const data = { ...createAvailability(), freeSize: 1000 }
it("returns a response when the request succeed", async (t) => {
const data = { ...createAvailability(), freeSize: 1000 };
t.mock.method(Fetch, "safe", () =>
Promise.resolve({ error: false, data }),
);
t.mock.method(Fetch, "safe", () => Promise.resolve({ error: false, data }));
const response = await marketplace.createAvailability({
maxCollateral: 1,
totalSize: 3000,
minPrice: 100,
duration: 100,
})
const response = await marketplace.createAvailability({
maxCollateral: 1,
totalSize: 3000,
minPrice: 100,
duration: 100,
});
assert.deepStrictEqual(response, { error: false, data });
});
assert.deepStrictEqual(response, { error: false, data });
});
it("returns a response when the create availability succeed", async (t) => {
const data = { ...createAvailability(), freeSize: 1000 }
it("returns a response when the create availability succeed", async (t) => {
const data = { ...createAvailability(), freeSize: 1000 };
t.mock.method(Fetch, "safe", () =>
Promise.resolve({ error: false, data }),
);
t.mock.method(Fetch, "safe", () => Promise.resolve({ error: false, data }));
const response = await marketplace.createAvailability({
maxCollateral: 1,
totalSize: 3000,
minPrice: 100,
duration: 100,
})
const response = await marketplace.createAvailability({
maxCollateral: 1,
totalSize: 3000,
minPrice: 100,
duration: 100,
});
assert.deepStrictEqual(response, { error: false, data });
});
assert.deepStrictEqual(response, { error: false, data });
});
it("returns an error when trying to update an availability without id", async () => {
const response = await marketplace.updateAvailability({
} as any)
it("returns an error when trying to update an availability without id", async () => {
const response = await marketplace.updateAvailability({} as any);
assert.deepStrictEqual(response, missingStringValidationError("id"));
});
assert.deepStrictEqual(response, missingStringValidationError("id"));
});
it("returns an error when trying to update an availability with zero total size", async () => {
const response = await marketplace.updateAvailability({
id: faker.string.alphanumeric(64),
totalSize: 0
})
it("returns an error when trying to update an availability with zero total size", async () => {
const response = await marketplace.updateAvailability({
id: faker.string.alphanumeric(64),
totalSize: 0,
});
assert.deepStrictEqual(response, minNumberValidationError("totalSize", 1))
});
assert.deepStrictEqual(response, minNumberValidationError("totalSize", 1));
});
it("returns an error when trying to update an availability with zero duration", async () => {
const response = await marketplace.updateAvailability({
id: faker.string.alphanumeric(64),
duration: 0
})
it("returns an error when trying to update an availability with zero duration", async () => {
const response = await marketplace.updateAvailability({
id: faker.string.alphanumeric(64),
duration: 0,
});
assert.deepStrictEqual(response, minNumberValidationError("duration", 1))
});
assert.deepStrictEqual(response, minNumberValidationError("duration", 1));
});
it("returns a response when the update availability succeed", async (t) => {
const data = createAvailability()
it("returns a response when the update availability succeed", async (t) => {
const data = createAvailability();
t.mock.method(Fetch, "safe", () =>
Promise.resolve({ error: false, data }),
);
t.mock.method(Fetch, "safe", () => Promise.resolve({ error: false, data }));
const response = await marketplace.updateAvailability({
id: faker.string.alphanumeric(64),
totalSize: 3000,
})
const response = await marketplace.updateAvailability({
id: faker.string.alphanumeric(64),
totalSize: 3000,
});
assert.deepStrictEqual(response, { error: false, data });
});
assert.deepStrictEqual(response, { error: false, data });
});
it("returns an error when trying to create a storage request without cid", async () => {
const { cid, ...rest } = createStorageRequest()
it("returns an error when trying to create a storage request without cid", async () => {
const { cid, ...rest } = createStorageRequest();
const response = await marketplace.createStorageRequest(rest as any)
const response = await marketplace.createStorageRequest(rest as any);
assert.deepStrictEqual(response, missingStringValidationError("cid"));
});
assert.deepStrictEqual(response, missingStringValidationError("cid"));
});
it("returns an error when trying to create a storage request without duration", async () => {
const { duration, ...rest } = createStorageRequest()
it("returns an error when trying to create a storage request without duration", async () => {
const { duration, ...rest } = createStorageRequest();
const response = await marketplace.createStorageRequest(rest as any)
const response = await marketplace.createStorageRequest(rest as any);
assert.deepStrictEqual(response, missingNumberValidationError("duration"));
});
assert.deepStrictEqual(response, missingNumberValidationError("duration"));
});
it("returns an error when trying to create a storage request with zero duration", async () => {
const { duration, ...rest } = createStorageRequest()
it("returns an error when trying to create a storage request with zero duration", async () => {
const { duration, ...rest } = createStorageRequest();
const response = await marketplace.createStorageRequest({ ...rest, duration: 0 })
const response = await marketplace.createStorageRequest({
...rest,
duration: 0,
});
assert.deepStrictEqual(response, minNumberValidationError("duration", 1));
});
assert.deepStrictEqual(response, minNumberValidationError("duration", 1));
});
it("returns an error when trying to create a storage request without reward", async () => {
const { reward, ...rest } = createStorageRequest()
it("returns an error when trying to create a storage request without reward", async () => {
const { reward, ...rest } = createStorageRequest();
const response = await marketplace.createStorageRequest(rest as any)
const response = await marketplace.createStorageRequest(rest as any);
assert.deepStrictEqual(response, missingNumberValidationError("reward"));
});
assert.deepStrictEqual(response, missingNumberValidationError("reward"));
});
it("returns an error when trying to create a storage request without proof probability", async () => {
const { proofProbability, ...rest } = createStorageRequest()
it("returns an error when trying to create a storage request without proof probability", async () => {
const { proofProbability, ...rest } = createStorageRequest();
const response = await marketplace.createStorageRequest(rest as any)
const response = await marketplace.createStorageRequest(rest as any);
assert.deepStrictEqual(response, missingNumberValidationError("proofProbability"));
});
assert.deepStrictEqual(
response,
missingNumberValidationError("proofProbability")
);
});
it("returns an error when trying to create a storage request without expiry", async () => {
const { expiry, ...rest } = createStorageRequest()
it("returns an error when trying to create a storage request without expiry", async () => {
const { expiry, ...rest } = createStorageRequest();
const response = await marketplace.createStorageRequest(rest as any)
const response = await marketplace.createStorageRequest(rest as any);
assert.deepStrictEqual(response, missingNumberValidationError("expiry"));
});
assert.deepStrictEqual(response, missingNumberValidationError("expiry"));
});
it("returns an error when trying to create a storage request with zero expiry", async () => {
const { expiry, ...rest } = createStorageRequest()
it("returns an error when trying to create a storage request with zero expiry", async () => {
const { expiry, ...rest } = createStorageRequest();
const response = await marketplace.createStorageRequest({ ...rest, expiry: 0 })
const response = await marketplace.createStorageRequest({
...rest,
expiry: 0,
});
assert.deepStrictEqual(response, minNumberValidationError("expiry", 1));
});
assert.deepStrictEqual(response, minNumberValidationError("expiry", 1));
});
it("returns an error when trying to create a storage request without collateral", async () => {
const { collateral, ...rest } = createStorageRequest()
it("returns an error when trying to create a storage request without collateral", async () => {
const { collateral, ...rest } = createStorageRequest();
const response = await marketplace.createStorageRequest(rest as any)
const response = await marketplace.createStorageRequest(rest as any);
assert.deepStrictEqual(response, missingNumberValidationError("collateral"));
});
assert.deepStrictEqual(
response,
missingNumberValidationError("collateral")
);
});
});

View File

@ -1,167 +1,189 @@
import * as v from 'valibot'
import { Api } from "../api/config"
import { CodexValibotIssuesMap } from '../errors/errors'
import { Fetch } from "../fetch-safe/fetch-safe"
import type { SafeValue } from "../values/values"
import { type CodexAvailability, type CodexAvailabilityCreateResponse, CodexCreateAvailabilityInput, CodexCreateStorageRequestInput, type CodexCreateStorageRequestResponse, type CodexPurchase, type CodexReservation, type CodexSlot, CodexUpdateAvailabilityInput } from "./types"
import * as v from "valibot";
import { Api } from "../api/config";
import { CodexValibotIssuesMap } from "../errors/errors";
import { Fetch } from "../fetch-safe/fetch-safe";
import type { SafeValue } from "../values/values";
import {
type CodexAvailability,
type CodexAvailabilityCreateResponse,
CodexCreateAvailabilityInput,
CodexCreateStorageRequestInput,
type CodexCreateStorageRequestResponse,
type CodexPurchase,
type CodexReservation,
type CodexSlot,
CodexUpdateAvailabilityInput,
} from "./types";
export class Marketplace {
readonly url: string
readonly url: string;
constructor(url: string) {
this.url = url
}
constructor(url: string) {
this.url = url;
}
/**
* Returns active slots
*/
async activeSlots(): Promise<SafeValue<CodexSlot[]>> {
const url = this.url + Api.config.prefix + "/sales/slots"
/**
* Returns active slots
*/
async activeSlots(): Promise<SafeValue<CodexSlot[]>> {
const url = this.url + Api.config.prefix + "/sales/slots";
return Fetch.safe<CodexSlot[]>(url, {
method: "GET"
})
}
return Fetch.safe<CodexSlot[]>(url, {
method: "GET",
});
}
/**
* Returns active slot with id {slotId} for the host
*/
async activeSlot(slotId: string): Promise<SafeValue<CodexSlot>> {
const url = this.url + Api.config.prefix + "/sales/slots/" + slotId
/**
* Returns active slot with id {slotId} for the host
*/
async activeSlot(slotId: string): Promise<SafeValue<CodexSlot>> {
const url = this.url + Api.config.prefix + "/sales/slots/" + slotId;
return Fetch.safe<CodexSlot>(url, {
method: "GET"
})
}
return Fetch.safe<CodexSlot>(url, {
method: "GET",
});
}
/**
* Returns storage that is for sale
*/
async availabilities(): Promise<SafeValue<CodexAvailability[]>> {
const url = this.url + Api.config.prefix + "/sales/availability"
/**
* Returns storage that is for sale
*/
async availabilities(): Promise<SafeValue<CodexAvailability[]>> {
const url = this.url + Api.config.prefix + "/sales/availability";
return Fetch.safe<CodexAvailability[]>(url, {
method: "GET"
})
}
return Fetch.safe<CodexAvailability[]>(url, {
method: "GET",
});
}
/**
* Offers storage for sale
*/
async createAvailability(input: CodexCreateAvailabilityInput)
: Promise<SafeValue<CodexAvailabilityCreateResponse>> {
const result = v.safeParse(CodexCreateAvailabilityInput, input)
/**
* Offers storage for sale
*/
async createAvailability(
input: CodexCreateAvailabilityInput,
): Promise<SafeValue<CodexAvailabilityCreateResponse>> {
const result = v.safeParse(CodexCreateAvailabilityInput, input);
if (!result.success) {
return {
error: true,
data: {
type: "validation",
message: "Cannot validate the input",
errors: CodexValibotIssuesMap(result.issues)
}
}
}
if (!result.success) {
return {
error: true,
data: {
type: "validation",
message: "Cannot validate the input",
errors: CodexValibotIssuesMap(result.issues),
},
};
}
const url = this.url + Api.config.prefix + "/sales/availability"
const url = this.url + Api.config.prefix + "/sales/availability";
return Fetch.safe<CodexAvailabilityCreateResponse>(url, {
method: "POST",
headers: {
"content-type": "application/json"
},
body: JSON.stringify(result.output)
})
}
return Fetch.safe<CodexAvailabilityCreateResponse>(url, {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify(result.output),
});
}
/**
* The new parameters will be only considered for new requests.
* Existing Requests linked to this Availability will continue as is.
*/
async updateAvailability(input: CodexUpdateAvailabilityInput): Promise<SafeValue<CodexAvailability>> {
const result = v.safeParse(CodexUpdateAvailabilityInput, input)
/**
* The new parameters will be only considered for new requests.
* Existing Requests linked to this Availability will continue as is.
*/
async updateAvailability(
input: CodexUpdateAvailabilityInput,
): Promise<SafeValue<CodexAvailability>> {
const result = v.safeParse(CodexUpdateAvailabilityInput, input);
if (!result.success) {
return {
error: true,
data: {
type: "validation",
message: "Cannot validate the input",
errors: CodexValibotIssuesMap(result.issues)
}
}
}
if (!result.success) {
return {
error: true,
data: {
type: "validation",
message: "Cannot validate the input",
errors: CodexValibotIssuesMap(result.issues),
},
};
}
const url = this.url + Api.config.prefix + "/sales/availability/" + result.output.id
const url =
this.url + Api.config.prefix + "/sales/availability/" + result.output.id;
return Fetch.safe<CodexAvailability>(url, {
method: "POST",
headers: {
"content-type": "application/json"
},
body: JSON.stringify(result.output)
})
}
return Fetch.safe<CodexAvailability>(url, {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify(result.output),
});
}
/**
* Return's list of Reservations for ongoing Storage Requests that the node hosts.
*/
async reservations(availabilityId: string): Promise<SafeValue<CodexReservation[]>> {
const url = this.url + Api.config.prefix + `/sales/availability/${availabilityId}/reservations`
/**
* Return's list of Reservations for ongoing Storage Requests that the node hosts.
*/
async reservations(
availabilityId: string,
): Promise<SafeValue<CodexReservation[]>> {
const url =
this.url +
Api.config.prefix +
`/sales/availability/${availabilityId}/reservations`;
return Fetch.safe<CodexReservation[]>(url, {
method: "GET"
})
}
return Fetch.safe<CodexReservation[]>(url, {
method: "GET",
});
}
/**
* Returns list of purchase IDs
*/
async purchaseIds(): Promise<SafeValue<string[]>> {
const url = this.url + Api.config.prefix + `/storage/purchases`
/**
* Returns list of purchase IDs
*/
async purchaseIds(): Promise<SafeValue<string[]>> {
const url = this.url + Api.config.prefix + `/storage/purchases`;
return Fetch.safe<string[]>(url, {
method: "GET"
})
}
return Fetch.safe<string[]>(url, {
method: "GET",
});
}
/**
* Returns purchase details
*/
async purchaseDetail(purchaseId: string): Promise<SafeValue<CodexPurchase>> {
const url = this.url + Api.config.prefix + `/storage/purchases/` + purchaseId
/**
* Returns purchase details
*/
async purchaseDetail(purchaseId: string): Promise<SafeValue<CodexPurchase>> {
const url =
this.url + Api.config.prefix + `/storage/purchases/` + purchaseId;
return Fetch.safe<CodexPurchase>(url, {
method: "GET"
})
}
return Fetch.safe<CodexPurchase>(url, {
method: "GET",
});
}
/**
* Creates a new request for storage.
*/
async createStorageRequest(input: CodexCreateStorageRequestInput): Promise<SafeValue<CodexCreateStorageRequestResponse>> {
const result = v.safeParse(CodexCreateStorageRequestInput, input)
/**
* Creates a new request for storage.
*/
async createStorageRequest(
input: CodexCreateStorageRequestInput,
): Promise<SafeValue<CodexCreateStorageRequestResponse>> {
const result = v.safeParse(CodexCreateStorageRequestInput, input);
if (!result.success) {
return {
error: true,
data: {
type: "validation",
message: "Cannot validate the input",
errors: CodexValibotIssuesMap(result.issues)
}
}
}
if (!result.success) {
return {
error: true,
data: {
type: "validation",
message: "Cannot validate the input",
errors: CodexValibotIssuesMap(result.issues),
},
};
}
const { cid, ...body } = result.output
const url = this.url + Api.config.prefix + "/storage/request/" + cid
const { cid, ...body } = result.output;
const url = this.url + Api.config.prefix + "/storage/request/" + cid;
return Fetch.safe<CodexCreateStorageRequestResponse>(url, {
method: "POST",
headers: {
"content-type": "application/json"
},
body: JSON.stringify(body)
})
}
return Fetch.safe<CodexCreateStorageRequestResponse>(url, {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify(body),
});
}
}

View File

@ -1,195 +1,204 @@
import * as v from 'valibot';
import * as v from "valibot";
export type CodexStorageRequest = {
id: string
id: string;
/**
* Address of Ethereum address
*/
client: string
/**
* Address of Ethereum address
*/
client: string;
ask: {
/**
* Number of slots that the tequest want to have the content spread over.
*/
slots: number
ask: {
/**
* Number of slots that the tequest want to have the content spread over.
*/
slots: number;
/**
* Amount of storage per slot (in bytes) as decimal string.
*/
slotSize: string
/**
* Amount of storage per slot (in bytes) as decimal string.
*/
slotSize: string;
/**
* The duration of the storage request in seconds.
*/
duration: string
/**
* The duration of the storage request in seconds.
*/
duration: string;
/**
* How often storage proofs are required as decimal string (in periods).
*/
proofProbability: string
/**
* How often storage proofs are required as decimal string (in periods).
*/
proofProbability: string;
/**
* The maximum amount of tokens paid per second per slot to hosts
* the client is willing to pay.
*/
reward: string
/**
* The maximum amount of tokens paid per second per slot to hosts
* the client is willing to pay.
*/
reward: string;
/**
* Max slots that can be lost without data considered to be lost.
*/
maxSlotLoss: number
},
/**
* Max slots that can be lost without data considered to be lost.
*/
maxSlotLoss: number;
};
content: {
/**
* Unique Content ID
*/
cid: string
content: {
/**
* Unique Content ID
*/
cid: string;
/**
* Erasure code parameters
*/
erasure: {
/**
* Total number of chunks generated by the erasure code process.
*/
totalChunks: number
},
/**
* Erasure code parameters
*/
erasure: {
/**
* Total number of chunks generated by the erasure code process.
*/
totalChunks: number;
};
/**
* Parameters for Proof of Retrievability
*/
por: {
u: string
publicKey: string
name: string
}
},
/**
* Parameters for Proof of Retrievability
*/
por: {
u: string;
publicKey: string;
name: string;
};
};
/* Number as decimal string that represents expiry threshold in seconds from when the Request is submitted.
* When the threshold is reached and the Request does not find requested amount of nodes to host the data, the Request is voided.
* The number of seconds can not be higher then the Request's duration itself.
*/
expiry: string,
/* Number as decimal string that represents expiry threshold in seconds from when the Request is submitted.
* When the threshold is reached and the Request does not find requested amount of nodes to host the data, the Request is voided.
* The number of seconds can not be higher then the Request's duration itself.
*/
expiry: string;
/**
* Random data
*/
nonce: string
}
/**
* Random data
*/
nonce: string;
};
/**
* A storage slot is a portion of a storage contract that needs to be fulfilled
* by a host. To initiate a contract, all the slots must be filled.
*/
export type CodexSlot = {
id: string
id: string;
request: CodexStorageRequest,
request: CodexStorageRequest;
/**
* The slot index as hexadecimal string
*/
slotIndex: "string"
}
/**
* The slot index as hexadecimal string
*/
slotIndex: "string";
};
/**
* Storage availability for sell.
*/
export type CodexAvailability = {
id: string
id: string;
/**
* Size of available storage in bytes
*/
totalSize: string
/**
* Size of available storage in bytes
*/
totalSize: string;
/**
* Maximum time the storage should be sold for (in seconds)
*/
duration: string
/**
* Maximum time the storage should be sold for (in seconds)
*/
duration: string;
/**
* Minimum price to be paid (in amount of tokens)
*/
minPrice: string
/**
* Minimum price to be paid (in amount of tokens)
*/
minPrice: string;
/**
* Maximum collateral user is willing to pay per filled Slot (in amount of tokens)
*/
maxCollateral: string
}
/**
* Maximum collateral user is willing to pay per filled Slot (in amount of tokens)
*/
maxCollateral: string;
};
export type CodexAvailabilityCreateResponse = CodexAvailability & {
id: string
id: string;
/**
* Unused size of availability's storage in bytes as decimal string
*/
freeSize: string
}
/**
* Unused size of availability's storage in bytes as decimal string
*/
freeSize: string;
};
export const CodexCreateAvailabilityInput = v.strictObject({
totalSize: v.pipe(v.number(), v.minValue(1)),
duration: v.pipe(v.number(), v.minValue(1)),
minPrice: v.number(),
maxCollateral: v.number(),
})
totalSize: v.pipe(v.number(), v.minValue(1)),
duration: v.pipe(v.number(), v.minValue(1)),
minPrice: v.number(),
maxCollateral: v.number(),
});
export type CodexCreateAvailabilityInput = v.InferOutput<typeof CodexCreateAvailabilityInput>;
export type CodexCreateAvailabilityInput = v.InferOutput<
typeof CodexCreateAvailabilityInput
>;
export const CodexUpdateAvailabilityInput = v.strictObject({
id: v.string(),
totalSize: v.optional(v.pipe(v.number(), v.minValue(1))),
duration: v.optional(v.pipe(v.number(), v.minValue(1))),
minPrice: v.optional(v.number()),
maxCollateral: v.optional(v.number()),
})
id: v.string(),
totalSize: v.optional(v.pipe(v.number(), v.minValue(1))),
duration: v.optional(v.pipe(v.number(), v.minValue(1))),
minPrice: v.optional(v.number()),
maxCollateral: v.optional(v.number()),
});
export type CodexUpdateAvailabilityInput = v.InferOutput<typeof CodexUpdateAvailabilityInput>;
export type CodexUpdateAvailabilityInput = v.InferOutput<
typeof CodexUpdateAvailabilityInput
>;
export type CodexReservation = {
id: string
availabilityId: string
requestId: string
id: string;
availabilityId: string;
requestId: string;
/**
* Size in bytes
*/
size: string
/**
* Size in bytes
*/
size: string;
/**
* Slot Index as hexadecimal string
*/
slotIndex: string
}
/**
* Slot Index as hexadecimal string
*/
slotIndex: string;
};
export type CodexPurchase = {
/**
* Description of the request's state
*/
state: string
/**
* Description of the request's state
*/
state: string;
/**
* If request failed, then here is presented the error message
*/
error: string
/**
* If request failed, then here is presented the error message
*/
error: string;
request: CodexStorageRequest
}
request: CodexStorageRequest;
};
export const CodexCreateStorageRequestInput = v.strictObject({
cid: v.string(),
duration: v.pipe(v.number(), v.minValue(1)),
reward: v.number(),
proofProbability: v.number(),
nodes: v.optional(v.number(), 1),
tolerance: v.optional(v.number(), 0),
expiry: v.pipe(v.number(), v.minValue(1)),
collateral: v.number(),
})
cid: v.string(),
duration: v.pipe(v.number(), v.minValue(1)),
reward: v.number(),
proofProbability: v.number(),
nodes: v.optional(v.number(), 1),
tolerance: v.optional(v.number(), 0),
expiry: v.pipe(v.number(), v.minValue(1)),
collateral: v.number(),
});
export type CodexCreateStorageRequestInput = v.InferOutput<typeof CodexCreateStorageRequestInput>;
export type CodexCreateStorageRequestInput = v.InferOutput<
typeof CodexCreateStorageRequestInput
>;
export type CodexCreateStorageRequestResponse = Omit<CodexCreateStorageRequestInput, "cid">
export type CodexCreateStorageRequestResponse = Omit<
CodexCreateStorageRequestInput,
"cid"
>;

View File

@ -6,5 +6,6 @@ import { type CodexError } from "../errors/errors";
* If the value represents an error, `error` is true and `data` will contain the error.
* If the value is not an error, `error` is false and `data` will contain the requested data.
*/
export type SafeValue<T> = { error: false, data: T } | { error: true, data: CodexError }
export type SafeValue<T> =
| { error: false; data: T }
| { error: true; data: CodexError };