diff --git a/README.md b/README.md index 3488d3d..6629660 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,20 @@ To use a module, you need to use the await syntax. If the module is not loaded y const marketplace = await codex.marketplace(); ``` +### Authentication + +You can use basic authentication when creating a new Codex object: + +```js +const codex = new Codex("http://localhost:3000", { + auth: { + basic: "MY BASIC AUTH SECRET" + } +}); + +You can obtain your secret using the `btoa` method in the browser or `Buffer.from(string).toString('base64')` in Node.js. The secret is stored in memory only. +``` + ### Error handling The SDK provides a type called `SafeValue` for error handling instead of throwing errors. It is inspired by Go's "error as value" concept. diff --git a/examples/basic-auth/.gitignore b/examples/basic-auth/.gitignore new file mode 100644 index 0000000..91097d8 --- /dev/null +++ b/examples/basic-auth/.gitignore @@ -0,0 +1 @@ +index.bundle.js \ No newline at end of file diff --git a/examples/basic-auth/README.md b/examples/basic-auth/README.md new file mode 100644 index 0000000..c999b21 --- /dev/null +++ b/examples/basic-auth/README.md @@ -0,0 +1,23 @@ +# Download example + +Small example to show how to download a file in the browser with Codex. + +## Install dependencies + +```bash +npm install +``` + +## Build the javascript asset + +```bash +CODEX_CID=REPLACE_BY_YOUR_CID npm run build +``` + +The response will be displayed as text, so it is better to test with .txt files. + +Note: You can define `CODEX_NODE_URL`, default value is "http://localhost:8080". + +## Check the results + +Open the index.html and open the web console. diff --git a/examples/basic-auth/esbuild.js b/examples/basic-auth/esbuild.js new file mode 100644 index 0000000..37b09c6 --- /dev/null +++ b/examples/basic-auth/esbuild.js @@ -0,0 +1,22 @@ +const { build } = require("esbuild"); +const define = {}; + +for (const k in process.env) { + define[`process.env.${k}`] = JSON.stringify(process.env[k]); +} + +if (!process.env["CODEX_NODE_URL"]) { + define[`process.env.CODEX_NODE_URL`] = '"http://localhost:8080"'; +} + +const options = { + entryPoints: ["./index.js"], + outfile: "./index.bundle.js", + bundle: true, + define, + logOverride: { + "ignored-bare-import": "silent", + }, +}; + +build(options).catch(() => process.exit(1)); diff --git a/examples/basic-auth/index.html b/examples/basic-auth/index.html new file mode 100644 index 0000000..0a494ed --- /dev/null +++ b/examples/basic-auth/index.html @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/examples/basic-auth/index.js b/examples/basic-auth/index.js new file mode 100644 index 0000000..b3d1117 --- /dev/null +++ b/examples/basic-auth/index.js @@ -0,0 +1,19 @@ +import { Codex } from "@codex-storage/sdk-js"; + +async function main() { + const codex = new Codex(process.env.CODEX_NODE_URL, { + auth: { + basic: btoa("admin:SuperSecret123"), + }, + }); + + const data = codex.data; + + const cid = process.env.CODEX_CID; + + const result = await data.networkDownloadStream(cid); + + console.info(await result.data.text()); +} + +main(); diff --git a/examples/download/package-lock.json b/examples/basic-auth/package-lock.json similarity index 94% rename from examples/download/package-lock.json rename to examples/basic-auth/package-lock.json index a7d1c79..7701ddd 100644 --- a/examples/download/package-lock.json +++ b/examples/basic-auth/package-lock.json @@ -1,24 +1,48 @@ { - "name": "@codex-storage/sdk-js-download-example", + "name": "@codex-storage/sdk-js-basic-auth-example", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@codex-storage/sdk-js-download-example", + "name": "@codex-storage/sdk-js-basic-auth-example", "version": "1.0.0", "license": "ISC", "dependencies": { - "@codex-storage/sdk-js": ".." + "@codex-storage/sdk-js": "../.." }, "devDependencies": { "esbuild": "^0.25.1", "prettier": "^3.5.3" } }, - "..": {}, + "..": { + "extraneous": true + }, + "../..": { + "name": "@codex-storage/sdk-js", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "undici": "^7.5.0", + "valibot": "^0.32.0" + }, + "devDependencies": { + "@tsconfig/strictest": "^2.0.5", + "@types/node": "^22.13.13", + "oas-normalize": "^13.1.2", + "openapi-typescript": "^7.6.1", + "prettier": "^3.5.3", + "tsup": "^8.3.6", + "typescript": "^5.8.2", + "vitest": "^3.0.9" + }, + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/@codex-storage/sdk-js": { - "resolved": "..", + "resolved": "../..", "link": true }, "node_modules/@esbuild/aix-ppc64": { diff --git a/examples/basic-auth/package.json b/examples/basic-auth/package.json new file mode 100644 index 0000000..c5b2110 --- /dev/null +++ b/examples/basic-auth/package.json @@ -0,0 +1,18 @@ +{ + "name": "@codex-storage/sdk-js-basic-auth-example", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "build": "node esbuild.js" + }, + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "@codex-storage/sdk-js": "../.." + }, + "devDependencies": { + "esbuild": "^0.25.1", + "prettier": "^3.5.3" + } +} diff --git a/examples/download/README.md b/examples/download/README.md index 1c13969..c999b21 100644 --- a/examples/download/README.md +++ b/examples/download/README.md @@ -11,7 +11,7 @@ npm install ## Build the javascript asset ```bash -CODEX_CID=REPLACE_BY_YOUR_CIDE npm run build +CODEX_CID=REPLACE_BY_YOUR_CID npm run build ``` The response will be displayed as text, so it is better to test with .txt files. diff --git a/examples/download/esbuild.js b/examples/download/esbuild.js index 93c9831..37b09c6 100644 --- a/examples/download/esbuild.js +++ b/examples/download/esbuild.js @@ -14,6 +14,9 @@ const options = { outfile: "./index.bundle.js", bundle: true, define, + logOverride: { + "ignored-bare-import": "silent", + }, }; build(options).catch(() => process.exit(1)); diff --git a/examples/download/package.json b/examples/download/package.json index 6dcbd6b..1d32f3b 100644 --- a/examples/download/package.json +++ b/examples/download/package.json @@ -9,7 +9,7 @@ "license": "ISC", "description": "", "dependencies": { - "@codex-storage/sdk-js": ".." + "@codex-storage/sdk-js": "../.." }, "devDependencies": { "esbuild": "^0.25.1", diff --git a/examples/upload-browser/README.md b/examples/upload-browser/README.md index 1c13969..c999b21 100644 --- a/examples/upload-browser/README.md +++ b/examples/upload-browser/README.md @@ -11,7 +11,7 @@ npm install ## Build the javascript asset ```bash -CODEX_CID=REPLACE_BY_YOUR_CIDE npm run build +CODEX_CID=REPLACE_BY_YOUR_CID npm run build ``` The response will be displayed as text, so it is better to test with .txt files. diff --git a/examples/upload-node/package-lock.json b/examples/upload-node/package-lock.json index ebc2c0a..e3433cb 100644 --- a/examples/upload-node/package-lock.json +++ b/examples/upload-node/package-lock.json @@ -39,6 +39,9 @@ }, "engines": { "node": ">=20.18.1" + }, + "peerDependencies": { + "undici": "^7.6.0" } }, "../dist": { @@ -74,4 +77,4 @@ } } } -} +} \ No newline at end of file diff --git a/examples/upload-node/package.json b/examples/upload-node/package.json index 3b05f70..2741a4d 100644 --- a/examples/upload-node/package.json +++ b/examples/upload-node/package.json @@ -13,4 +13,4 @@ "devDependencies": { "prettier": "^3.5.3" } -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 97e116f..b89356a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,9 @@ }, "engines": { "node": ">=20.18.1" + }, + "peerDependencies": { + "undici": "^7.0.0" } }, "node_modules/@apidevtools/json-schema-ref-parser": { @@ -3030,6 +3033,7 @@ "resolved": "https://registry.npmjs.org/undici/-/undici-7.7.0.tgz", "integrity": "sha512-tZ6+5NBq4KH35rr46XJ2JPFKxfcBlYNaqLF/wyWIO9RMHqqU/gx/CLB1Y2qMcgB8lWw/bKHa7qzspqCN7mUHvA==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.18.1" } @@ -3473,4 +3477,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/data/browser-upload.ts b/src/data/browser-upload.ts index 8ddf0c5..9470441 100644 --- a/src/data/browser-upload.ts +++ b/src/data/browser-upload.ts @@ -1,6 +1,6 @@ import { CodexError } from "../errors/errors"; import type { SafeValue } from "../values/values"; -import type { UploadStategy } from "./types"; +import type { UploadStategy, UploadStategyOptions } from "./types"; export class BrowserUploadStategy implements UploadStategy { private readonly file: Document | XMLHttpRequestBodyInit; @@ -22,7 +22,10 @@ export class BrowserUploadStategy implements UploadStategy { this.metadata = metadata; } - download(url: string): Promise> { + upload( + url: string, + { auth }: UploadStategyOptions + ): Promise> { const xhr = new XMLHttpRequest(); this.xhr = xhr; @@ -42,6 +45,10 @@ export class BrowserUploadStategy implements UploadStategy { ); } + if (auth?.basic) { + xhr.setRequestHeader("Authorization", "Basic " + auth.basic); + } + if (this.metadata?.mimetype) { xhr.setRequestHeader("Content-Type", this.metadata.mimetype); } diff --git a/src/data/data.ts b/src/data/data.ts index 13c7d7c..d709432 100644 --- a/src/data/data.ts +++ b/src/data/data.ts @@ -1,5 +1,9 @@ import { Api } from "../api/config"; -import { Fetch } from "../fetch-safe/fetch-safe"; +import { + Fetch, + FetchAuthBuilder, + type FetchAuth, +} from "../fetch-safe/fetch-safe"; import type { SafeValue } from "../values/values"; import type { CodexDataResponse, @@ -13,11 +17,20 @@ import type { CodexDataItems, } from "./types"; +type CodexDataOptions = { + auth?: FetchAuth; +}; + export class CodexData { readonly url: string; + readonly auth: FetchAuth = {}; - constructor(url: string) { + constructor(url: string, options?: CodexDataOptions) { this.url = url; + + if (options?.auth) { + this.auth = options.auth; + } } /** @@ -27,7 +40,16 @@ export class CodexData { cids(): Promise> { const url = this.url + Api.config.prefix + "/data"; - return Fetch.safeJson(url, { method: "GET" }); + 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 } }; + }); } /** @@ -36,7 +58,10 @@ export class CodexData { space(): Promise> { const url = this.url + Api.config.prefix + "/space"; - return Fetch.safeJson(url, { method: "GET" }); + return Fetch.safeJson(url, { + method: "GET", + headers: FetchAuthBuilder.build(this.auth), + }); } /** @@ -49,7 +74,7 @@ export class CodexData { const url = this.url + Api.config.prefix + "/data"; return { - result: stategy.download(url), + result: stategy.upload(url, { auth: this.auth }), abort: () => { stategy.abort(); }, @@ -63,7 +88,10 @@ export class CodexData { async localDownload(cid: string): Promise> { const url = this.url + Api.config.prefix + "/data/" + cid; - return Fetch.safe(url, { method: "GET" }); + return Fetch.safe(url, { + method: "GET", + headers: FetchAuthBuilder.build(this.auth), + }); } /** @@ -73,7 +101,10 @@ export class CodexData { async networkDownload(cid: string): Promise> { const url = this.url + Api.config.prefix + `/data/${cid}/network`; - return Fetch.safeJson(url, { method: "POST" }); + return Fetch.safeJson(url, { + method: "POST", + headers: FetchAuthBuilder.build(this.auth), + }); } /** @@ -83,7 +114,10 @@ export class CodexData { async networkDownloadStream(cid: string): Promise> { const url = this.url + Api.config.prefix + `/data/${cid}/network/stream`; - return Fetch.safe(url, { method: "GET" }); + return Fetch.safe(url, { + method: "GET", + headers: FetchAuthBuilder.build(this.auth), + }); } /** @@ -93,6 +127,9 @@ export class CodexData { async fetchManifest(cid: string): Promise> { const url = this.url + Api.config.prefix + `/data/${cid}/network/manifest`; - return Fetch.safeJson(url, { method: "GET" }); + return Fetch.safeJson(url, { + method: "GET", + headers: FetchAuthBuilder.build(this.auth), + }); } } diff --git a/src/data/node-upload.ts b/src/data/node-upload.ts index 507b5e8..7c262e3 100644 --- a/src/data/node-upload.ts +++ b/src/data/node-upload.ts @@ -3,7 +3,8 @@ import { CodexError } from "../errors/errors"; import type { SafeValue } from "../values/values"; import Undici from "undici"; import { type FormData } from "undici"; -import type { UploadStategy } from "./types"; +import type { UploadStategy, UploadStategyOptions } from "./types"; +import { FetchAuthBuilder } from "../fetch-safe/fetch-safe"; export class NodeUploadStategy implements UploadStategy { private readonly body: @@ -26,8 +27,11 @@ export class NodeUploadStategy implements UploadStategy { this.metadata = metadata; } - async download(url: string): Promise> { - const headers: Record = {}; + async upload( + url: string, + { auth }: UploadStategyOptions + ): Promise> { + const headers: Record = FetchAuthBuilder.build(auth); if (this.metadata?.filename) { headers["Content-Disposition"] = diff --git a/src/data/types.ts b/src/data/types.ts index 973979b..8e105a0 100644 --- a/src/data/types.ts +++ b/src/data/types.ts @@ -1,4 +1,5 @@ import type { components, paths } from "../openapi"; +import type { FetchAuth } from "../fetch-safe/fetch-safe"; import type { SafeValue } from "../values/values"; export type CodexDataResponse = @@ -28,7 +29,14 @@ export type CodexFetchManifestResponse = export type CodexManifest = CodexFetchManifestResponse; +export type UploadStategyOptions = { + auth?: FetchAuth; +}; + export interface UploadStategy { - download(url: string): Promise>; + upload( + url: string, + options?: UploadStategyOptions + ): Promise>; abort(): void; } diff --git a/src/debug/debug.ts b/src/debug/debug.ts index 66e0c7e..f476ba3 100644 --- a/src/debug/debug.ts +++ b/src/debug/debug.ts @@ -1,6 +1,10 @@ import { Api } from "../api/config"; import { CodexError, CodexValibotIssuesMap } from "../errors/errors"; -import { Fetch } from "../fetch-safe/fetch-safe"; +import { + Fetch, + FetchAuthBuilder, + type FetchAuth, +} from "../fetch-safe/fetch-safe"; import type { SafeValue } from "../values/values"; import { CodexLogLevelInput, @@ -10,11 +14,20 @@ import { } from "./types"; import * as v from "valibot"; +type CodexDebugOptions = { + auth?: FetchAuth; +}; + export class CodexDebug { readonly url: string; + readonly auth: FetchAuth = {}; - constructor(url: string) { + constructor(url: string, options?: CodexDebugOptions) { this.url = url; + + if (options?.auth) { + this.auth = options.auth; + } } /** @@ -40,6 +53,7 @@ export class CodexDebug { return Fetch.safeText(url, { method: "POST", + headers: FetchAuthBuilder.build(this.auth), body: "", }); } @@ -52,6 +66,7 @@ export class CodexDebug { return Fetch.safeJson(url, { method: "GET", + headers: FetchAuthBuilder.build(this.auth), }); } } diff --git a/src/fetch-safe/fetch-safe.test.ts b/src/fetch-safe/fetch-safe.test.ts index 0464cba..5a6484a 100644 --- a/src/fetch-safe/fetch-safe.test.ts +++ b/src/fetch-safe/fetch-safe.test.ts @@ -1,6 +1,6 @@ import { afterEach, assert, describe, it, vi } from "vitest"; import { Fetch } from "../fetch-safe/fetch-safe"; -import { CodexError } from "../async"; +import { CodexError } from "../errors/errors"; describe.only("fetch", () => { afterEach(() => { diff --git a/src/fetch-safe/fetch-safe.ts b/src/fetch-safe/fetch-safe.ts index 6cec0c9..0f98871 100644 --- a/src/fetch-safe/fetch-safe.ts +++ b/src/fetch-safe/fetch-safe.ts @@ -2,6 +2,21 @@ import { CodexError } from "../errors/errors"; import { Promises } from "../promise-safe/promise-safe"; import { type SafeValue } from "../values/values"; +export type FetchAuth = { + basic?: string; +}; + +export const FetchAuthBuilder = { + build(auth: FetchAuth | undefined) { + if (auth?.basic) { + return { + Authorization: "Basic " + auth.basic, + }; + } + return {}; + }, +}; + export const Fetch = { async safe(url: string, init: RequestInit): Promise> { const res = await Promises.safe(() => fetch(url, init)); diff --git a/src/index.ts b/src/index.ts index 56cc17a..79b3a8f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import { CodexData } from "./data/data"; import { CodexNode } from "./node/node"; import { CodexMarketplace } from "./marketplace/marketplace"; import { CodexDebug } from "./debug/debug"; +import type { FetchAuth } from "./fetch-safe/fetch-safe"; export * from "./fetch-safe/fetch-safe"; export * from "./marketplace/types"; @@ -15,19 +16,28 @@ export { CodexData } from "./data/data"; export { CodexNode } from "./node/node"; export { CodexMarketplace } from "./marketplace/marketplace"; +type CodexProps = { + auth?: FetchAuth; +}; + export class Codex { readonly url: string; private _marketplace: CodexMarketplace | null; private _data: CodexData | null; private _node: CodexNode | null; private _debug: CodexDebug | null; + private readonly auth: FetchAuth = {}; - constructor(url: string) { + 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() { @@ -35,7 +45,7 @@ export class Codex { return this._marketplace; } - this._marketplace = new CodexMarketplace(this.url); + this._marketplace = new CodexMarketplace(this.url, { auth: this.auth }); return this._marketplace; } @@ -45,7 +55,7 @@ export class Codex { return this._data; } - this._data = new CodexData(this.url); + this._data = new CodexData(this.url, { auth: this.auth }); return this._data; } @@ -55,7 +65,7 @@ export class Codex { return this._node; } - this._node = new CodexNode(this.url); + this._node = new CodexNode(this.url, { auth: this.auth }); return this._node; } @@ -65,7 +75,7 @@ export class Codex { return this._debug; } - this._debug = new CodexDebug(this.url); + this._debug = new CodexDebug(this.url, { auth: this.auth }); return this._debug; } diff --git a/src/marketplace/marketplace.ts b/src/marketplace/marketplace.ts index c5a4838..917d2d8 100644 --- a/src/marketplace/marketplace.ts +++ b/src/marketplace/marketplace.ts @@ -1,7 +1,11 @@ import * as v from "valibot"; import { Api } from "../api/config"; import { CodexError, CodexValibotIssuesMap } from "../errors/errors"; -import { Fetch } from "../fetch-safe/fetch-safe"; +import { + Fetch, + FetchAuthBuilder, + type FetchAuth, +} from "../fetch-safe/fetch-safe"; import type { SafeValue } from "../values/values"; import { type CodexAvailabilityResponse, @@ -27,11 +31,20 @@ import { CodexCreateStorageRequestInput, } from "./types"; +type CodexMarketplaceOptions = { + auth?: FetchAuth; +}; + export class CodexMarketplace { readonly url: string; + readonly auth: FetchAuth = {}; - constructor(url: string) { + constructor(url: string, options?: CodexMarketplaceOptions) { this.url = url; + + if (options?.auth) { + this.auth = options.auth; + } } /** @@ -42,6 +55,7 @@ export class CodexMarketplace { return Fetch.safeJson(url, { method: "GET", + headers: FetchAuthBuilder.build(this.auth), }); } @@ -53,6 +67,7 @@ export class CodexMarketplace { return Fetch.safeJson(url, { method: "GET", + headers: FetchAuthBuilder.build(this.auth), }); } @@ -84,6 +99,7 @@ export class CodexMarketplace { const res = await Fetch.safeJson(url, { method: "GET", + headers: FetchAuthBuilder.build(this.auth), }); if (res.error) { @@ -133,6 +149,7 @@ export class CodexMarketplace { return Fetch.safeJson(url, { method: "POST", + headers: FetchAuthBuilder.build(this.auth), body: JSON.stringify(body), }).then((result) => { if (result.error) { @@ -182,6 +199,7 @@ export class CodexMarketplace { const res = await Fetch.safe(url, { method: "PATCH", + headers: FetchAuthBuilder.build(this.auth), body: JSON.stringify(body), }); @@ -205,6 +223,7 @@ export class CodexMarketplace { return Fetch.safeJson(url, { method: "GET", + headers: FetchAuthBuilder.build(this.auth), }); } @@ -216,6 +235,7 @@ export class CodexMarketplace { return Fetch.safeJson(url, { method: "GET", + headers: FetchAuthBuilder.build(this.auth), }); } @@ -288,6 +308,7 @@ export class CodexMarketplace { this.url + Api.config.prefix + `/storage/purchases/` + purchaseId; return Fetch.safeJson(url, { + headers: FetchAuthBuilder.build(this.auth), method: "GET", }).then((res) => { if (res.error) { @@ -329,6 +350,7 @@ export class CodexMarketplace { return Fetch.safeText(url, { method: "POST", + headers: FetchAuthBuilder.build(this.auth), body: JSON.stringify({ duration: duration, pricePerBytePerSecond: pricePerBytePerSecond.toString(), diff --git a/src/node/node.ts b/src/node/node.ts index ff7f9b9..532152f 100644 --- a/src/node/node.ts +++ b/src/node/node.ts @@ -1,5 +1,9 @@ import { Api } from "../api/config"; -import { Fetch } from "../fetch-safe/fetch-safe"; +import { + Fetch, + FetchAuthBuilder, + type FetchAuth, +} from "../fetch-safe/fetch-safe"; import type { SafeValue } from "../values/values"; import type { CodexPeerId, @@ -10,11 +14,20 @@ import type { CodexSprJsonResponse, } from "./types"; +type CodexNodeOptions = { + auth?: FetchAuth; +}; + export class CodexNode { readonly url: string; + readonly auth: FetchAuth = {}; - constructor(url: string) { + constructor(url: string, options?: CodexNodeOptions) { this.url = url; + + if (options?.auth) { + this.auth = options.auth; + } } /** @@ -32,6 +45,7 @@ export class CodexNode { return Fetch.safeText(url, { method: "GET", + headers: FetchAuthBuilder.build(this.auth), }); } @@ -56,6 +70,7 @@ export class CodexNode { method: "GET", headers: { "Content-Type": "text/plain", + ...FetchAuthBuilder.build(this.auth), }, }); } @@ -81,6 +96,7 @@ export class CodexNode { method: "GET", headers: { "Content-Type": "text/plain", + ...FetchAuthBuilder.build(this.auth), }, }); } diff --git a/src/promise-safe/promise-safe.test.ts b/src/promise-safe/promise-safe.test.ts index bccbdc5..4ce2f14 100644 --- a/src/promise-safe/promise-safe.test.ts +++ b/src/promise-safe/promise-safe.test.ts @@ -1,6 +1,6 @@ import { assert, describe, it } from "vitest"; import { Promises } from "./promise-safe"; -import { CodexError } from "../async"; +import { CodexError } from "../errors/errors"; describe("promise safe", () => { it("returns an error when the promise failed", async () => { diff --git a/src/promise-safe/promise-safe.ts b/src/promise-safe/promise-safe.ts index 0a9cb91..4944f18 100644 --- a/src/promise-safe/promise-safe.ts +++ b/src/promise-safe/promise-safe.ts @@ -1,4 +1,4 @@ -import { CodexError } from "../async"; +import { CodexError } from "../errors/errors"; import type { SafeValue } from "../values/values"; export const Promises = {