Add throwable usage

This commit is contained in:
Arnaud 2025-05-30 16:55:33 +02:00
parent 26a3caacc8
commit 63a868e311
No known key found for this signature in database
GPG Key ID: B8FBC178F10CA7AE
13 changed files with 384 additions and 1 deletions

View File

@ -114,6 +114,20 @@ if (slots.error) {
// Access the slots within slots.data.
```
If you prefer to use the "classic" JavaScript mode and deal with exceptions, you can import the throwable component instead:
```js
import { Codex } from "@codex-storage/sdk-js/throwable";
const marketplace = codex.marketplace;
try {
const slots = marketplace.activeSlots();
} catch (e) {
// Do something
}
```
### Compatibility
| SDK version | Codex version | Codex app |

View File

@ -0,0 +1,17 @@
# Download example
Small example to show how to download a file in the browser with Codex.
## Install dependencies
```bash
npm install
```
## Run node
```bash
node index.js
```
Note: You can define `CODEX_NODE_URL`, default value is "http://localhost:8080".

View File

@ -0,0 +1,18 @@
const { Codex } = require("@codex-storage/sdk-js/throwable");
const { NodeUploadStrategy } = require("@codex-storage/sdk-js/node");
async function main() {
const codex = new Codex(
process.env.CODEX_NODE_URL || "http://localhost:8080"
);
const data = codex.data;
const strategy = new NodeUploadStrategy("Hello World !");
const uploadResponse = data.upload(strategy);
const cid = await uploadResponse.result;
console.info("CID is", cid);
}
main();

View File

@ -0,0 +1,79 @@
{
"name": "@codex-storage/sdk-js-update-node-throwable-example",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@codex-storage/sdk-js-update-node-throwable-example",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@codex-storage/sdk-js": "../..",
"undici": "^7.7.0"
},
"devDependencies": {
"prettier": "^3.5.3"
}
},
"..": {
"extraneous": true
},
"../..": {
"name": "@codex-storage/sdk-js",
"version": "0.1.2",
"license": "MIT",
"dependencies": {
"valibot": "^1.0.0"
},
"devDependencies": {
"@tsconfig/strictest": "^2.0.5",
"@types/node": "^22.13.17",
"oas-normalize": "^14.0.0",
"openapi-typescript": "^7.6.1",
"prettier": "^3.5.3",
"tsup": "^8.3.6",
"typescript": "^5.8.2",
"vitest": "^3.1.1"
},
"engines": {
"node": ">=20.18.1"
},
"peerDependencies": {
"undici": "^7.7.0"
}
},
"../dist": {
"extraneous": true
},
"node_modules/@codex-storage/sdk-js": {
"resolved": "../..",
"link": true
},
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/undici": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.7.0.tgz",
"integrity": "sha512-tZ6+5NBq4KH35rr46XJ2JPFKxfcBlYNaqLF/wyWIO9RMHqqU/gx/CLB1Y2qMcgB8lWw/bKHa7qzspqCN7mUHvA==",
"license": "MIT",
"engines": {
"node": ">=20.18.1"
}
}
}
}

View File

@ -0,0 +1,16 @@
{
"name": "@codex-storage/sdk-js-update-node-throwable-example",
"version": "1.0.0",
"main": "index.js",
"scripts": {},
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@codex-storage/sdk-js": "../..",
"undici": "^7.7.0"
},
"devDependencies": {
"prettier": "^3.5.3"
}
}

View File

@ -9,7 +9,7 @@
"scripts": {
"prepack": "npm run build",
"prebuild": "npm run compile && rm -Rf dist/*",
"build": "tsup src/index.ts src/async.ts src/browser.ts src/node.ts --format esm,cjs --dts --sourcemap --treeshake",
"build": "tsup src/index.ts src/async.ts src/browser.ts src/node.ts src/throwable.ts --format esm,cjs --dts --sourcemap --treeshake",
"compile": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
@ -35,6 +35,16 @@
"default": "./dist/index.js"
}
},
"./throwable": {
"import": {
"types": "./dist/throwable.d.ts",
"default": "./dist/throwable.mjs"
},
"require": {
"types": "./dist/throwable.d.cts",
"default": "./dist/throwable.js"
}
},
"./browser": {
"import": {
"types": "./dist/browser.d.ts",

View File

@ -0,0 +1,33 @@
import { CodexData } from "./data";
import type { UploadStrategy } from "./types";
import { type FetchAuth } from "../fetch-safe/fetch-safe";
import { Throwable } from "../throwable/throwable";
type CodexDataThrowableOptions = {
auth?: FetchAuth;
};
export class CodexDataThrowable {
readonly data: CodexData;
constructor(url: string, options?: CodexDataThrowableOptions) {
this.data = new CodexData(url, options);
}
cids = () => Throwable.from(this.data.cids());
space = () => Throwable.from(this.data.space());
upload = (strategy: UploadStrategy) => {
const { result, abort } = this.data.upload(strategy);
return {
result: Throwable.from(result),
abort,
};
};
localDownload = (cid: string) => Throwable.from(this.data.localDownload(cid));
networkDownload = (cid: string) =>
Throwable.from(this.data.networkDownload(cid));
networkDownloadStream = (cid: string) =>
Throwable.from(this.data.networkDownloadStream(cid));
fetchManifest = (cid: string) => Throwable.from(this.data.fetchManifest(cid));
delete = (cid: string) => Throwable.from(this.data.delete(cid));
}

View File

@ -0,0 +1,20 @@
import type { CodexLogLevel } from "./types";
import { type FetchAuth } from "../fetch-safe/fetch-safe";
import { Throwable } from "../throwable/throwable";
import { CodexDebug } from "./debug";
type CodexDebugThrowableOptions = {
auth?: FetchAuth;
};
export class CodexDebugThrowable {
readonly debug: CodexDebug;
constructor(url: string, options?: CodexDebugThrowableOptions) {
this.debug = new CodexDebug(url, options);
}
setLogLevel = (level: CodexLogLevel) =>
Throwable.from(this.debug.setLogLevel(level));
info = () => Throwable.from(this.debug.info());
}

View File

@ -0,0 +1,37 @@
import { type FetchAuth } from "../fetch-safe/fetch-safe";
import { Throwable } from "../throwable/throwable";
import { CodexMarketplace } from "./marketplace";
import type {
CodexAvailabilityPatchInput,
CodexCreateAvailabilityInput,
CodexCreateStorageRequestInput,
} from "./types";
type CodexMarketplaceThrowableOptions = {
auth?: FetchAuth;
};
export class CodexMarketplaceThrowable {
readonly marketplace: CodexMarketplace;
constructor(url: string, options?: CodexMarketplaceThrowableOptions) {
this.marketplace = new CodexMarketplace(url, options);
}
activeSlots = () => Throwable.from(this.marketplace.activeSlots());
activeSlot = (slotId: string) =>
Throwable.from(this.marketplace.activeSlot(slotId));
availabilities = () => Throwable.from(this.marketplace.availabilities());
createAvailability = (input: CodexCreateAvailabilityInput) =>
Throwable.from(this.marketplace.createAvailability(input));
updateAvailability = (input: CodexAvailabilityPatchInput) =>
Throwable.from(this.marketplace.updateAvailability(input));
reservations = (availabilityId: string) =>
Throwable.from(this.marketplace.reservations(availabilityId));
purchaseIds = () => Throwable.from(this.marketplace.purchaseIds());
purchases = () => Throwable.from(this.marketplace.purchases());
purchaseDetail = (purchaseId: string) =>
Throwable.from(this.marketplace.purchaseDetail(purchaseId));
createStorageRequest = (input: CodexCreateStorageRequestInput) =>
Throwable.from(this.marketplace.createStorageRequest(input));
}

View File

@ -0,0 +1,23 @@
import type { CodexPeerIdContentType, CodexSprContentType } from "./types";
import { type FetchAuth } from "../fetch-safe/fetch-safe";
import { Throwable } from "../throwable/throwable";
import { CodexNode } from "./node";
type CodexNodeThrowableOptions = {
auth?: FetchAuth;
};
export class CodexNodeThrowable {
readonly node: CodexNode;
constructor(url: string, options?: CodexNodeThrowableOptions) {
this.node = new CodexNode(url, options);
}
connect = (peerId: string, addrs: string[] = []) =>
Throwable.from(this.node.connect(peerId, addrs));
spr = (type: CodexSprContentType = "json") =>
Throwable.from(this.node.spr(type));
peerId = (type: CodexPeerIdContentType = "json") =>
Throwable.from(this.node.peerId(type));
}

84
src/throwable.ts Normal file
View File

@ -0,0 +1,84 @@
import { CodexDataThrowable } from "./data/data.throwable";
import { CodexNodeThrowable } from "./node/node.throwable";
import { CodexMarketplaceThrowable } from "./marketplace/marketplace.throwable";
import { CodexDebugThrowable } from "./debug/debug.throwable";
import type { FetchAuth } from "./fetch-safe/fetch-safe";
export * from "./fetch-safe/fetch-safe";
export * from "./marketplace/types";
export * from "./debug/types";
export * from "./data/types";
export * from "./values/values";
export * from "./errors/errors";
export { CodexDebugThrowable } from "./debug/debug.throwable";
export { CodexDataThrowable } from "./data/data.throwable";
export { CodexNodeThrowable } from "./node/node.throwable";
export { CodexMarketplaceThrowable } from "./marketplace/marketplace.throwable";
type CodexProps = {
auth?: FetchAuth;
};
export class Codex {
readonly url: string;
private _marketplace: CodexMarketplaceThrowable | null;
private _data: CodexDataThrowable | null;
private _node: CodexNodeThrowable | null;
private _debug: CodexDebugThrowable | null;
private readonly auth: FetchAuth = {};
constructor(url: string, options?: CodexProps) {
this.url = url;
this._marketplace = null;
this._data = null;
this._node = null;
this._debug = null;
if (options?.auth) {
this.auth = options?.auth;
}
}
get marketplace() {
if (this._marketplace) {
return this._marketplace;
}
this._marketplace = new CodexMarketplaceThrowable(this.url, {
auth: this.auth,
});
return this._marketplace;
}
get data() {
if (this._data) {
return this._data;
}
this._data = new CodexDataThrowable(this.url, { auth: this.auth });
return this._data;
}
get node() {
if (this._node) {
return this._node;
}
this._node = new CodexNodeThrowable(this.url, { auth: this.auth });
return this._node;
}
get debug() {
if (this._debug) {
return this._debug;
}
this._debug = new CodexDebugThrowable(this.url, { auth: this.auth });
return this._debug;
}
}

View File

@ -0,0 +1,19 @@
import { assert, describe, it } from "vitest";
import { CodexDataThrowable } from "../data/data.throwable";
import { CodexError } from "../errors/errors";
describe("data", () => {
const data = new CodexDataThrowable(
process.env["CLIENT_URL"] || "http://localhost:8080"
);
it("returns an error when providing an invalid cid", async () => {
try {
await data.delete("hello");
assert.fail();
} catch (e) {
assert.ok(e instanceof CodexError);
assert.ok(e.message.includes("Incorrect Cid"));
}
});
});

View File

@ -0,0 +1,13 @@
import type { SafeValue } from "../values/values";
export const Throwable = {
async from<T>(safePromise: Promise<SafeValue<T>>): Promise<T> {
const result = await safePromise;
if (result.error) {
throw result.data;
}
return result.data;
},
};