@storacha/client
Version:
Client for the storacha.network w3up api
1,062 lines (788 loc) • 46 kB
Markdown
<h1 align="center"><img src="https://bafybeich5z6anhfhf23tlnb67y32nl37nttkjfyqv6maeehur3unuuhd2e.ipfs.w3s.link/transparent-red.png" width="100" /></h1>
<p align="center">The main JavaScript client for the <a href="https://storacha.network">Storacha Network</a></p>
<p align="center">
<a href="https://github.com/storacha/upload-service/actions/workflows/ci.yml"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/storacha/upload-service/ci.yml?branch=main&style=for-the-badge" /></a>
<a href="https://discord.gg/KzySUzcxd5"><img src="https://img.shields.io/badge/chat-discord?style=for-the-badge&logo=discord&label=discord&logoColor=ffffff&color=7389D8" /></a>
<a href="https://x.com/storachanetwork"><img alt="X Follow" src="https://img.shields.io/badge/follow-storacha-black?style=for-the-badge&logo=x" /></a>
<a href="https://github.com/storacha/upload-service/blob/main/packages/@storacha/client/LICENSE.md"><img alt="License: Apache-2.0 OR MIT" src="https://img.shields.io/badge/LICENSE-Apache--2.0%20OR%20MIT-yellow?style=for-the-badge" /></a>
</p>
## About
`/client` is a JavaScript library that provides a convenient interface to the Storacha platform, a simple "on-ramp" to the content-addressed decentralized IPFS network.
This library is the user-facing "porcelain" client for interacting with Storacha services from JavaScript. It wraps the lower-level [`/access`][access-client-github] and [`@storacha/upload-client`][upload-client-github] client packages, which target individual Storacha services. We recommend using `@storacha/client` instead of using those "plumbing" packages directly, but you may find them useful if you need more context on Storacha's architecture and internals.
**`/client` requires modern browser or Node 18+**.
> ⚠️❗ **Public Data** 🌎: All data uploaded to Storacha is available to anyone who requests it using the correct CID. Do not store any private or sensitive information in an unencrypted form using Storacha.
> ⚠️❗ **Permanent Data** ♾️: Removing files from Storacha will remove them from the file listing for your account, but that doesn’t prevent nodes on the decentralized storage network from retaining copies of the data indefinitely. Do not use Storacha for data that may need to be permanently deleted in the future.
- [Install](#install)
- [Usage](#usage)
- [How Storacha and /client use UCANs](#how-storacha-and-storachaclient-use-ucans)
- [Space](#space)
- [Agent](#agent)
- [Basic usage with storacha.network](#basic-usage-with-storachanetwork)
- [Creating a client object](#creating-a-client-object)
- [Creating and registering Spaces](#creating-and-registering-spaces)
- [Delegating from Space to Agent](#delegating-from-space-to-agent)
- [Bringing your own Agent and delegation](#bringing-your-own-agent-and-delegation)
- [Uploading data](#uploading-data)
- [Integration options](#integration-options)
- [Client-server](#client-server)
- [Delegated](#delegated)
- [User owned](#user-owned)
- [Environments requiring wasm import](#environments-requiring-wasm-import)
- [API](#api)
- [Contributing](#contributing)
- [License](#license)
## Install
You can add the `/client` package to your JavaScript or TypeScript project with `npm`:
```sh
npm install /client
```
## Usage
[API Reference](#api)
Most users' usage of `/client` will be for interacting with storacha.network, a hosted storage product that developed Storacha for their upload APIs. However, any user that has an implementation of Storacha ([specs](https://github.com/storacha/specs), [protocol](https://github.com/storacha/upload-service)) can configure `@storacha/client` for their usage.
For authorization, Storacha services use [ucanto][ucanto], a Remote Procedure Call (RPC) framework built around [UCAN](https://ucan.xzy), or User Controlled Authorization Networks. UCANs are a powerful capability-based authorization system that allows fine-grained sharing of permissions through a process called _delegation_ on top of [public key cryptography](https://en.wikipedia.org/wiki/Public-key_cryptography). See our [intro to UCAN blog post](https://blog.storacha.network/posts/intro-to-ucan) for an overview of UCAN.
You can think about UCAN replacing bearer tokens in traditional APIs for authorization with Storacha. Since any actor can be represented by a cryptographic keypair and permissions can be delegated to them, users can interact with Storacha directly in cases where a developer might have needed to previously run additional back-end infrastructure to keep API keys secure. This can be extended even to have end users using applications integrated with Storacha using their own keypair-based identity.
### How Storacha and /client use UCANs
`/client` and `ucanto` take care of the details of UCANs for you, but a few of the underlying terms and concepts may "bubble up" to the surface of the API, so we'll cover the basics here. We'll also go over some terms that are specific to Storacha that you might not have encountered elsewhere.
UCAN-based APIs are centered around _capabilities_, which are comprised of an _ability_ and a _resource_. Together, the ability and resource determine what action a client can perform and what objects in the system can be acted upon. When invoking a service method, a client will present a UCAN token that includes an ability and resource, along with _proofs_ that verify that they should be allowed to exercise the capability. The proof might be signed directly by the capability owner, or have a chain of signatures (_delegations_) where the actor invoking the capability has been verifiably delegated permission to do so.
#### Space
When you upload data to Storacha, your uploads are linked to a unique _Space_ that acts as a "namespace" for the data you upload. Each Space corresponds to a _DID_, or [Decentralized Identity Document](https://www.w3.org/TR/did-core/). In storacha.network's implementation of Storacha, these Space DIDs generally use the key DID method, of the form `did:key:publicKey` with a corresponding private signing key.
When creating a Space using `/client`, it generates this private key and `did:key` for you locally. To use storacha.network, you then register a Space by associating it with your email address. From there, when invoking storage capabilities with storacha.network, the Space `did:key` is the "resource" portion of the capability, while the ability is an action like `blob/add` or `blob/remove`. (A Space registered with storacha.network is imperfectly analogous to an "account" with storacha.network.)
Under the hood in the email registration process, your Space delegates the capabilities needed to use Storacha to your email address, and this delegation is stored by storacha.network. If you need access to your Space in the future from any device, storacha.network allows you to reclaim those capabilities the same way you would reset a password in other services - using an email verification process. This means you don't need to store or manage Space private keys to use Storacha - just create a new space, register it with Storacha and use it from as many devices as you like. More on this "sign in" process is detailed in the next section on Agents.
#### Agent
To invoke a capability like `blob/add` on a Space using `@storacha/client`, the client must have an _Agent_. Like a Space, an Agent corresponds to a `did:key` whose private key is generated locally. An Agent is useful once `/client` has a UCAN delegation where a registered Space(s) delegates the Agent its capabilities. (An imperfect analogy is Agent with login session.)
The first time `/client` is instantiated on a device, it creates an Agent automatically. Alternatively, if you have your own Agent corresponding to a specific private key locally available, you can pass it to the client.
The delegation from a Space to your Agent that `/client` needs can be passed either by verifying the email address the Space is registered to and claiming the UCAN delegation (`login(email)` then `capability.access.claim`) or directly if you have the UCAN delegation available locally (`addSpace(delegation)`).
### Basic usage with storacha.network
```mermaid
flowchart TD
A[/client instance] -->|Automatic if specific Agent is not passed when client object created|B(Create local Agent DID and key)
B --> |If Space has not yet been created|S(Create local Space, login client with your email address, and register Space + email address with storacha.network)
S --> C(Get UCAN delegation from Space to Agent)
C --> D(Upload to Space using Agent)
```
All uses of `/client` to upload with storacha.network follow the flow above. This section shows the most basic way to use the client to start storing data. For more complex integration options, check out the [integration options][https://github.com/storacha/upload-service/blob/main/packages/@storacha/client/README.md#integration-options] docs. For reference, check out the [API reference docs][docs] or the source code of the [`w3cli` package][w3cli-github], which uses `@storacha/client` throughout.
> By you or your users registering a Storacha Space via email confirmation with [storacha.network](http://storacha.network), you agree to the [Terms of Service](https://docs.storacha.network/terms/).
#### Creating a client object
The package provides a [static `create` function][docs-create] that returns a [`Client` object][docs-Client].
```js
import { create } from '/client'
const client = await create()
```
By default, clients will create a new [`Agent`][access-docs-Agent] and put it in a persistent local [`Store`](https://github.com/storacha/upload-service/tree/main/packages/access-client) if it can't find an existing one to load (so the next time the client is initialized on the same device, it will use the same `Agent`).
`create` accepts an optional [`ClientFactoryOptions` object][docs-ClientFactoryOptions] that can be used configured to use a non-default persistent `Store`. See the [`/access` docs](https://github.com/storacha/upload-service/tree/main/packages/access-client) for more about `Store` configuration. If you'd like to bring your own Agent, you can initialize the client with your own storage [Driver](https://github.com/storacha/upload-service/blob/main/packages/@storacha/client/README.md#driver). An example would be using `Signer` from the [ucanto][ucanto] package.
```js
import { create } from '/client'
import * as Signer from '/principal/ed25519' // Agents on Node should use Ed25519 keys
const principal = Signer.parse(agentPrivateKey) // created by `npx ucan-key ed --json` in command line
const client = await create({ principal })
```
Once initialized, you can access the client's `Agent` with the [`agent` getter][docs-Client#agent].
##### Pre-built bundle
You can also import a pre-built bundle, which adds the exports from the client to a _global_ variable `StorachaClient`:
```html
<!DOCTYPE html>
<script src="https://cdn.jsdelivr.net/npm/@storacha/client/browser.min.js"></script>
<script>
async function main() {
const client = await StorachaClient.create()
console.log(client.did())
}
main()
</script>
```
#### Creating and registering Spaces
A [`Space`][docs-Space] acts as a namespace for your uploads, and what your Agent will need a delegation from to store data with Storacha. The first thing to do is login your Agent with your email address. Calling `login` will cause an email to be sent to the given address. Once a user clicks the confirmation link in the email, the `login` method will resolve. Make sure to check for errors, as `login` will fail if the email is not confirmed within the expiration timeout. Authorization needs to happen only once per agent.
```js
const account = await client.login('zaphod.galaxy')
```
If your account doesn't have a payment plan yet, you'll be prompted to select one after verifying your email. A payment plan is required to provision a space. You can use the following loop to wait until a payment plan is selected:
```js
// Wait for a payment plan with a 1-second polling interval and 15-minute timeout
await account.plan.wait()
```
Spaces can be created using the [`createSpace` client method][docs-client#createSpace]:
```js
const space = await client.createSpace('my-awesome-space', { account })
```
Alternatively, you can use the CLI command [`storacha space create`](https://github.com/storacha/upload-service/tree/main/packages/cli#storacha-space-create-name).
The `name` parameter is optional. If provided, it will be stored in your client's local state store and can be used to provide a friendly name for user interfaces.
If an `account` is provided in the options, a delegated recovery account is automatically created and provisioned, allowing you to store data and delegate access to the recovery account. This means you can access your space from other devices, as long as you have access to your account.
If this is your Agent's first space, it will automatically be set as the "current space." If you already have spaces and want to set the new one as current, you can do so manually:
```js
await client.setCurrentSpace(space.did())
```
ℹ️ Note: If you do not create the space passing the account parameter you run the risk of losing access to your space!
#### Delegating from Space to Agent
In order to store data with Storacha, your Agent will need a delegation from a Space. This automatically happens if you called `createSpace`. However, if you are initializing the client with a previously created Space, you can `login(email)` then claim a delegation granted to the account associated with your email:
```js
await client.login('zaphod.galaxy')
await client.setCurrentSpace(space.did()) # select the relevant Space DID that is associated with your account
```
```mermaid
sequenceDiagram
Client->>storacha.network Storacha service: Here is my email address and Agent DID
storacha.network Storacha service-->>Client: Please click the link to validate
Client-->>storacha.network Storacha service: Email address validated
storacha.network Storacha service->>Client: Here is a UCAN delegating permission from Space DID to Agent DID
```
##### Bringing your own Agent and delegation
For uses of `/client` in environments where the Agent is not persisted and/or the email verification step would be prohibitive (e.g., serverless backend environment where local Store with the Agent is dropped in between runs, and going through the email verification flow isn't practical), you can manually add a delegation for access to a Space created by a different authorized agent (see the [`addSpace` client method](docs-client#addSpace)). An example (where [`storacha`](https://github.com/storacha/upload-service/tree/main/packages/cli) is set up with the Space that we want to delegate permissions from in our client instance):
```js
import * as Signer from '/principal/ed25519' // Agents on Node should use Ed25519 keys
import { importDAG } from '/core/delegation'
import { CarReader } from '/car'
import * as Client from '/client'
import { StoreMemory } from '/client/stores/memory'
async function main() {
// from "bring your own Agent" example in `Creating a client object" section`
// used command line to generate KEY and PROOF (stored in env variables)
// KEY: `npx ucan-key ed --json` in command line, which returns private key and DID for Agent (the private key is stored in KEY)
// PROOF: CLI used to run `storacha delegation create <did_from_ucan-key_command_above> --can 'blob/add' --can 'index/add' --can 'filecoin/offer' --can 'upload/add' | base64`, which returns the delegation from Space to the Agent we're using (stored in PROOF)
const principal = Signer.parse(process.env.KEY)
const store = new StoreMemory()
const client = await Client.create({ principal, store })
// now give Agent the delegation from the Space
const proof = await parseProof(process.env.PROOF)
const space = await client.addSpace(proof)
await client.setCurrentSpace(space.did())
// READY to go!
}
/** @param {string} data Base64 encoded CAR file */
async function parseProof(data) {
const blocks = []
const reader = await CarReader.fromBytes(Buffer.from(data, 'base64'))
for await (const block of reader.blocks()) {
blocks.push(block)
}
return importDAG(blocks)
}
```
#### Uploading data
Once you've [created and registered a Space](#creating-and-registering-spaces) and [authorized your Agent](#authorize), you can upload files to the Storacha platform.
Call [`uploadFile`][docs-Client#uploadFile] to upload a single file, or [`uploadDirectory`][docs-Client#uploadDirectory] to upload multiple files.
`uploadFile` expects a "Blob like" input, which can be a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) or [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) when running in a browser. On node.js, see the [`filesFromPath` library](https://github.com/storacha/files-from-path), which can load compatible objects from the local filesystem.
`uploadDirectory` requires `File`-like objects instead of `Blob`s, as the file's `name` property is used to build the directory hierarchy.
You can control the directory layout and create nested directory structures by using `/` delimited paths in your filenames:
```js
const files = [
new File(['some-file-content'], 'readme.md'),
new File(['import foo'], 'src/main.py'),
new File([someBinaryData], 'images/example.png'),
]
const directoryCid = await client.uploadDirectory(files)
```
In the example above, `directoryCid` resolves to an IPFS directory with the following layout:
```text
.
├── images
│ └── example.png
├── readme.md
└── src
└── main.py
```
### Integration options
As mentioned, UCAN opens up a number of options in how to integrate with Storacha: Should you, the developer, own the Space? Should you delegate permissions to your users? Or should your user own their own Space? Broadly, there are three ways to integrate:
- (Simplest) Client-server: You (the developer) own the Space, and your user uploads to your backend infra before you upload it to the service
- (More complex) Delegated: You own the Space, but you give a delegated UCAN token to your user's Agent to upload directly to the service (rather than needing to touch the upload in your backend)
- (Most complex) User-owned: Your user owns the Space and registers it and they use it to upload directly with the service; if you want to instrument visibility into what they’re uploading, you’ll have to write separate code in your app for it
You can implement each of these in a number of ways, but we talk through some considerations when implementing a given option.
#### Client-server
```mermaid
sequenceDiagram
participant User
/client in backend->>@storacha/client in backend: Client set with Agent with delegation from Space
User->>/client in backend: Upload data
/client in backend->>storacha.network Storacha service: Upload data
```
- For your backend to be scalable, you might consider using serverless workers or a queue in front of a server
- In either case, you'll need a registered Space, and your client instance in your backend to have an Agent with a delegation from this Space
- (Recommended) It's likely easiest to create and register your Space using the [CLI](https://github.com/storacha/upload-service/tree/main/packages/cli) rather than using `@storacha/client` to do so (especially if your backend isn't persistent); you can then generate your own Agent and delegate the ability to upload to your Space using something like [this example](#bringing-your-own-agent-and-delegation)
- If your backend is persistent, you can do this or do everything in the client directly ([create Space](#creating-and-registering-spaces) and [get delegation](#delegating-from-space-to-agent))
- After this, once your user uploads data to your backend, you can run any of the `upload` methods
#### Delegated
```mermaid
sequenceDiagram
participant /client in user
participant /client in backend
participant storacha.network Storacha service
/client in backend->>@storacha/client in backend: Client created with Agent and delegation from Space
/client in user->>@storacha/client in user: Client instantiated with default Agent
/client in user->>@storacha/client in backend: Request delegation with user's Agent DID
/client in backend->>@storacha/client in user: Send delegation from Space to user's Agent DID
/client in user->>storacha.network Storacha service: Upload data
```
- You will likely have `/client` running in your end-user's client code, as well as backend code that's able to generate UCANs that delegate the ability to upload and pass them to your users (e.g., `@storacha/client` running in a serverless worker)
- For your backend to be scalable, you might consider using serverless workers or a queue in front of a server
- As the developer, you'll need a registered Space, and your client instance in your backend to have an Agent with a delegation from this Space
- (Recommended) It's likely easiest to create and register your Space using the [CLI](https://github.com/storacha/upload-service/tree/main/packages/cli) rather than using `@storacha/client` to do so (especially if your backend isn't persistent); you can then generate your own Agent and delegate the ability to upload to your Space using something like [this example](#bringing-your-own-agent-and-delegation)
- If your backend is persistent, you can do this or do everything in the client directly ([create Space](#creating-and-registering-spaces) and [get delegation](#delegating-from-space-to-agent))
- Your user does not need a registered Space - just an Agent with a delegation from your Space
- `/client` in the end user environment should have a unique Agent for each user, which should happen by default (since when `@storacha/client` is instantiated it creates a new Agent anyway, or uses the one in local Store)
- From there, when your end user is ready to upload, they should request from your backend a delegation from your developer-owned Space to their Agent (which can be derived via [`client.agent`](docs-Client#agent))
- In your backend, you can call [`client.createDelegation()`](docs-Client#createDelegation) passing in the Agent object from `client.agent()` in your end user's instance, and passing through `options?` params to limit the scope of the delegation (e.g., `blob/add`, `upload/add`, expiration time)
- You can serialize this using `delegation.archive()` and send it to your user
- The end user instance of the client should not need to call `client.login(email)`, as it is not claiming any delegations via email address (but rather getting the delegation directly from your backend)
- Once your user receives the delegation, they can deserialize it using [`ucanto.Delegation.extract()`](https://github.com/storacha/ucanto/blob/c8999a59852b61549d163532a83bac62290b629d/packages/core/src/delegation.js#L399) and pass it in using `client.addSpace()`, and from there they can run any of the `upload` methods
- Note that this alone does not give visibility into which of your end users are uploading what; to track this, you'll probably need them to send you that information separately (e.g., once they've run `upload` and get back a content CID, you can have them send that CID to you for tracking)
- A code example that does this can be found below
```js
import { CarReader } from '/car'
import * as DID from '/dag-ucan/did'
import * as Delegation from '/core/delegation'
import { importDAG } from '/core/delegation'
import * as Signer from '/principal/ed25519'
import * as Client from '/client'
async function backend(did: string) {
// Load client with specific private key
const principal = Signer.parse(process.env.KEY)
const client = await Client.create({ principal })
// Add proof that this agent has been delegated capabilities on the space
const proof = await parseProof(process.env.PROOF)
const space = await client.addSpace(proof)
await client.setCurrentSpace(space.did())
// Create a delegation for a specific DID
const audience = DID.parse(did)
const abilities = ['blob/add', 'index/add', 'filecoin/offer', 'upload/add']
const expiration = Math.floor(Date.now() / 1000) + 60 * 60 * 24 // 24 hours from now
const delegation = await client.createDelegation(audience, abilities, {
expiration,
})
// Serialize the delegation and send it to the client
const archive = await delegation.archive()
return archive.ok
}
/** @param {string} data Base64 encoded CAR file */
async function parseProof(data) {
const blocks = []
const reader = await CarReader.fromBytes(Buffer.from(data, 'base64'))
for await (const block of reader.blocks()) {
blocks.push(block)
}
return importDAG(blocks)
}
async function frontend() {
// Create a new client
const client = await Client.create()
// Fetch the delegation from the backend
const apiUrl = `/api/Storacha-delegation/${client.agent().did()}`
const response = await fetch(apiUrl)
const data = await response.arrayBuffer()
// Deserialize the delegation
const delegation = await Delegation.extract(new Uint8Array(data))
if (!delegation.ok) {
throw new Error('Failed to extract delegation')
}
// Add proof that this agent has been delegated capabilities on the space
const space = await client.addSpace(delegation.ok)
client.setCurrentSpace(space.did())
// READY to go!
}
```
#### User-owned
```mermaid
sequenceDiagram
participant User
participant Application backend
participant storacha.network Storacha service
Application backend->>User: Front end code that includes /client
User->>storacha.network Storacha service: (If needed) Create Space and register it
User->>storacha.network Storacha service: (If needed) Use Agent email verification to "log in" to Space
User->>storacha.network Storacha service: Upload data using /client
```
- If you want your user to own their own Space, you'll likely be relying on the `/client` methods to create a Space, authorize the Space, and authorize the Agent on the end user-side; from there they can run any of the `upload` methods
- Doing this does take some of the UX out of your control; for instance, when storacha.network fully launches with Storacha, your users will have to set up their payment methods with storacha.network
- Note that this alone does not give visibility into which of your end users are uploading what; to track this, you'll probably need them to send you that information separately (e.g., once they've run `upload` and get back a content CID, you can have them send that CID to you for tracking)
- There is a world of possibilities with your users "bringing their own identity" for their Space; you could explore how crypto wallet private keys, Apple Passkey, and more might map to Space DIDs and have the client use those
- If you have code snippet(s) that works for you, please share them in a PR or [Github issue](https://github.com/storacha/upload-service/issues) and we'll link them here!
### Environments requiring wasm import
Some environments (for instance Cloudflare Workers) require wasm bytecode to be imported. All other paths to load wasmm are disallowed by embedder. For these use cases, the default export of `/client` (most compatible) won't work out of the box. A custom build will need to be created to get the client working.
We created a `esbuild-plugin` [esbuild-plugin-w3up-client-wasm-import](https://github.com/vasco-santos/esbuild-plugin-w3up-client-wasm-import) that you can easily use. There is also an [example repository](https://github.com/vasco-santos/worker-w3up-client-example).
## API
- [`create`](#create)
- `Client`
- [`uploadDirectory`](#uploaddirectory)
- [`uploadFile`](#uploadfile)
- [`uploadCAR`](#uploadcar)
- [`agent`](#agent)
- [`login`](#login)
- [`accounts`](#accounts)
- [`currentSpace`](#currentspace)
- [`setCurrentSpace`](#setcurrentspace)
- [`spaces`](#spaces)
- [`createSpace`](#createspace)
- [`addSpace`](#addSpace)
- [`proofs`](#proofs)
- [`addProof`](#addproof)
- [`delegations`](#delegations)
- [`createDelegation`](#createdelegation)
- [`remove`](#remove)
- [`capability.access.authorize`](#capabilityaccessauthorize)
- [`capability.access.claim`](#capabilityaccessclaim)
- [`capability.blob.add`](#capabilityblobadd)
- [`capability.blob.list`](#capabilitybloblist)
- [`capability.blob.remove`](#capabilityblobremove)
- [`capability.index.add`](#capabilityindexadd)
- [`capability.plan.get`](#capabilityplanget)
- [`capability.plan.set`](#capabilityplanset)
- [`capability.plan.createAdminSession`](#capabilityplancreateadminsession)
- [`capability.space.info`](#capabilityspaceinfo)
- [`capability.upload.add`](#capabilityuploadadd)
- [`capability.upload.list`](#capabilityuploadlist)
- [`capability.upload.remove`](#capabilityuploadremove)
- [`capability.filecoin.offer`](#capabilityfilecoinoffer)
- [`capability.filecoin.info`](#capabilityfilecoininfo)
- [`capability.usage.report`](#capabilityusagereport)
- [Types](#types)
- [`BlobListResult`](#bloblistresult)
- [`Capability`](#capability)
- [`CARMetadata`](#carmetadata)
- [`ClientFactoryOptions`](#clientfactoryoptions)
- [`Delegation`](#delegation)
- [`Driver`](#driver)
- [`ListResponse`](#listresponse)
- [`ServiceConf`](#serviceconf)
- [`ShardStoredCallback`](#shardstoredcallback)
- [`Space`](#space)
- [`UploadListResult`](#uploadlistresult)
---
### `create`
```ts
function create(options?: ClientFactoryOptions): Promise<Client>
```
Create a new Storacha client.
If no backing store is passed one will be created that is appropriate for the environment.
If the backing store is empty, a new signing key will be generated and persisted to the store. In the browser an unextractable RSA key will be generated by default. In other environments an Ed25519 key is generated.
If the backing store already has data stored, it will be loaded and used.
More information: [`ClientFactoryOptions`](#clientfactoryoptions)
### `uploadDirectory`
```ts
function uploadDirectory(
files: File[],
options: {
retries?: number
signal?: AbortSignal
onShardStored?: ShardStoredCallback
shardSize?: number
} = {}
): Promise<CID>
```
Uploads a directory of files to the service and returns the root data CID for the generated DAG. All files are added to a container directory, with paths in file names preserved.
More information: [`ShardStoredCallback`](#shardstoredcallback)
### `uploadFile`
```ts
function uploadFile(
file: Blob,
options: {
retries?: number
signal?: AbortSignal
onShardStored?: ShardStoredCallback
shardSize?: number
} = {}
): Promise<CID>
```
Uploads a file to the service and returns the root data CID for the generated DAG.
More information: [`ShardStoredCallback`](#shardstoredcallback)
### `uploadCAR`
```ts
function uploadCAR(
car: Blob,
options: {
retries?: number
signal?: AbortSignal
onShardStored?: ShardStoredCallback
shardSize?: number
rootCID?: CID
} = {}
): Promise<CID>
```
Uploads a CAR file to the service. The difference between this function and [capability.store.add](#capabilitystoreadd) is that the CAR file is automatically sharded and an "upload" is registered (see [`capability.upload.add`](#capabilityuploadadd)), linking the individual shards. Use the `onShardStored` callback to obtain the CIDs of the CAR file shards.
More information: [`ShardStoredCallback`](#shardstoredcallback)
### `agent`
```ts
function agent(): Signer
```
The user agent. The agent is a signer - an entity that can sign UCANs with keys from a `Principal` using a signing algorithm.
### `authorize`
```ts
function authorize(
email: string,
options?: { signal?: AbortSignal }
): Promise<void>
```
Authorize the current agent to use capabilities granted to the passed email account.
### `accounts`
```ts
function accounts(): Record<DID, Account>
```
List all accounts the agent has stored access to.
### `currentSpace`
```ts
function currentSpace(): Space | undefined
```
The current space in use by the agent.
### `setCurrentSpace`
```ts
function setCurrentSpace(did: DID): Promise<void>
```
Use a specific space.
### `spaces`
```ts
function spaces(): Space[]
```
Spaces available to this agent.
### `createSpace`
```ts
async function createSpace(
name?: string,
options?: { account: Account }
): Promise<Space>
```
Create a new space with an optional name.
### `addSpace`
```ts
async function addSpace(proof: Delegation): Promise<Space>
```
Add a space from a received proof. Proofs are delegations with an _audience_ matching the agent DID.
### `proofs`
```ts
function proofs(capabilities?: Capability[]): Delegation[]
```
Get all the proofs matching the capabilities. Proofs are delegations with an _audience_ matching the agent DID.
### `addProof`
```ts
function addProof(proof: Delegation): Promise<void>
```
Add a proof to the agent. Proofs are delegations with an _audience_ matching the agent DID. Note: `addSpace` should be used for delegating from `Space` to `Agent` (i.e., you want the Agent to fully be able to act on behalf of the Space), as it calls `addProof` with some additional client logic. `addProof` is for more generically adding delegations to the Agent (e.g., delegation targets a resource _other_ than a Space).
### `delegations`
```ts
function delegations(capabilities?: Capability[]): Delegation[]
```
Get delegations created by the agent for others. Filtered optionally by capability.
### `createDelegation`
```ts
function createDelegation(
audience: Principal,
abilities: string[],
options?: UCANOptions
): Promise<Delegation>
```
Create a delegation to the passed audience for the given abilities with the _current_ space as the resource.
### `remove`
```ts
function remove (
contentCID?: CID
options: {
shards?: boolean
} = {}
): Promise<void>
```
Removes association of a content CID with the space. Optionally, also removes association of CAR shards with space.
⚠️ If `shards` option is `true` all shards will be deleted even if there is another upload(s) that reference same shards, which in turn could corrupt those uploads.
### `getReceipt`
```ts
function getReceipt(taskCid: CID): Promise<Receipt>
```
Get a receipt for an executed task by its CID.
### `capability.access.authorize`
```ts
function authorize(
email: string,
options: { signal?: AbortSignal } = {}
): Promise<void>
```
Authorize the current agent to use capabilities granted to the passed email account.
### `capability.access.claim`
```ts
function claim(): Promise<Delegation<Capabilities>[]>
```
Claim delegations granted to the account associated with this agent. Note: the received delegations are added to the agent's persistent store.
### `capability.blob.add`
```ts
function add(
blob: Blob,
options: { retries?: number; signal?: AbortSignal } = {}
): Promise<MultihashDigest>
```
Store a blob to the service.
### `capability.blob.list`
```ts
function list(
options: { retries?: number; signal?: AbortSignal } = {}
): Promise<ListResponse<BlobListResult>>
```
List blobs stored in the current space.
More information: [`BlobListResult`](#bloblistresult), [`ListResponse`](#listresponse)
### `capability.blob.remove`
```ts
function remove(
digest: MultihashDigest,
options: { retries?: number; signal?: AbortSignal } = {}
): Promise<void>
```
Remove a stored blob by multihash digest.
### `capability.index.add`
```ts
function add(
index: CID,
options: { retries?: number; signal?: AbortSignal } = {}
): Promise<IndexAddResponse>
```
Register an "index" with the service. The `index` CID should be the CID of a CAR file, containing an index ad defined by [w3-index](https://github.com/storacha/specs/blob/main/w3-index.md).
Required delegated capability proofs: `index/add`
### `capability.plan.get`
```ts
function get(account: AccountDID): Promise<PlanGetSuccess>
```
Get information about an account's billing plan.
### `capability.plan.set`
```ts
function set(account: AccountDID, product: DID): Promise<{}>
```
Switch an account's "plan" to the given product. **This may result in
changes to your billing or unexpected billing cycles depending on the
type of change.**
### `capability.plan.createAdminSession`
```ts
function createAdminSession(
account: AccountDID,
returnURL: string
): Promise<{ url: string }>
```
Create a billing customer portal admin session. Returns a URL that
the customer can visit to administer `account`. Design and implementation driven
by our Stripe integration and may not be supported by all billing providers.
### `capability.upload.add`
```ts
function add(
root: CID,
shards: CID[],
options: { retries?: number; signal?: AbortSignal } = {}
): Promise<UploadAddResponse>
```
Register a set of stored CAR files as an "upload" in the system. A DAG can be split between multiple CAR files. Calling this function allows multiple stored CAR files to be considered as a single upload.
### `capability.upload.list`
```ts
function list(
options: { cursor?: string, size?: number, retries?: number; signal?: AbortSignal } = {}
): Promise<ListResponse<UploadListResult>>
```
List uploads created in the current space.
More information: [`UploadListResult`](#uploadlistresult), [`ListResponse`](#listresponse)
### `capability.upload.remove`
```ts
function remove(
link: CID,
options: { retries?: number; signal?: AbortSignal } = {}
): Promise<void>
```
Remove a upload by root data CID.
### `capability.filecoin.offer`
```ts
function offer(content: CID, piece: PieceLink): Promise<FilecoinOfferResponse>
```
Offer a Filecoin "piece" to be added to an aggregate that will be offered for Filecoin deal(s).
### `capability.filecoin.info`
```ts
function info(piece: PieceLink): Promise<FilecoinInfoResponse>
```
Get know deals and aggregate info of a Filecoin "piece" previously offered.
### `capability.usage.report`
```ts
function report(
space: DID,
period: { from: Date, to: Date },
options?: { nonce?: string }
): Promise<UsageReportSuccess>
```
Get a usage report for the passed space in the given time period.
More information: [`UsageReportSuccess`](#usagereportsuccess)
## Types
### `BlobListResult`
```ts
interface BlobListResult {
blob: {
digest: Uint8Array
size: number
}
}
```
### `Capability`
An object describing a UCAN capability, which specifies what action the UCAN holder `can` perform `with` some resource.
Defined by the [`/dag-ucan` package](https://github.com/ipld/js-dag-ucan).
```ts
export interface Capability<
Can extends Ability = Ability,
With extends Resource = Resource,
Caveats extends unknown = unknown
> {
with: With
can: Can
nb?: Caveats
}
export type Ability = `${string}/${string}` | '*'
export type Resource = `${string}:${string}`
```
The `can` field contains a string ability identifier, e.g. `blob/add` or `space/info`.
The `with` field contains a resource URI, often a `did:key` URI that identifies a Space.
The optional `nb` (_nota bene_) field contains "caveats" that add supplemental information to a UCAN invocation or delegation.
See [the `/capabilities` package](https://github.com/storacha/upload-service/tree/main/packages/capabilities) for more information about capabilities and how they are defined in Storacha services.
### `CARMetadata`
Metadata pertaining to a CAR file.
```ts
export interface CARMetadata {
/**
* CAR version number.
*/
version: number
/**
* Root CIDs present in the CAR header.
*/
roots: CID[]
/**
* CID of the CAR file (not the data it contains).
*/
cid: CID
/**
* Size of the CAR file in bytes.
*/
size: number
}
```
### `Delegation`
An in-memory view of a UCAN delegation, including proofs that can be used to invoke capabilities or delegate to other agents.
```ts
import { Delegation as CoreDelegation } from '/core/delegation'
export interface Delegation extends CoreDelegation {
/**
* User defined delegation metadata.
*/
meta(): Record<string, any>
}
```
The `Delegation` type in `/client` extends the `Delegation` type defined by [`ucanto`][ucanto]:
```ts
export interface Delegation<C extends Capabilities = Capabilities> {
readonly root: UCANBlock<C>
readonly blocks: Map<string, Block>
readonly cid: UCANLink<C>
readonly bytes: ByteView<UCAN.UCAN<C>>
readonly data: UCAN.View<C>
asCID: UCANLink<C>
export(): IterableIterator<Block>
issuer: UCAN.Principal
audience: UCAN.Principal
capabilities: C
expiration?: UCAN.UTCUnixTimestamp
notBefore?: UCAN.UTCUnixTimestamp
nonce?: UCAN.Nonce
facts: Fact[]
proofs: Proof[]
iterate(): IterableIterator<Delegation>
}
```
Delegations can be serialized by calling `export()` and piping the returned `Block` iterator into a `CarWriter` from the [`/car` package](https://www.npmjs.com/package/@ipld/car).
### `Driver`
Storage drivers can be obtained from [`/access/stores`](https://github.com/storacha/upload-service/tree/main/packages/access-client). They persist data created and managed by an agent.
### `ListResponse`
A paginated list of items.
```ts
interface ListResponse<R> {
cursor?: string
size: number
results: R[]
}
```
### `ServiceConf`
Service DID and URL configuration.
### `ShardStoredCallback`
A function called after a DAG shard has been successfully stored by the service:
```ts
type ShardStoredCallback = (meta: CARMetadata) => void
```
More information: [`CARMetadata`](#carmetadata)
### `Space`
An object representing a storage location. Spaces must be [registered](#registerspace) with the service before they can be used for storage.
```ts
interface Space {
/**
* The given space name.
*/
name(): string
/**
* The DID of the space.
*/
did(): string
/**
* Whether the space has been registered with the service.
*/
registered(): boolean
/**
* User defined space metadata.
*/
meta(): Record<string, any>
}
```
### `UploadListResult`
```ts
interface UploadListResult {
root: CID
shards?: CID[]
}
```
### `UsageReportSuccess`
```ts
type UsageReportSuccess = Record<DID, UsageData>
interface UsageData {
/** Provider the report concerns, e.g. `did:web:storacha.network` */
provider: DID
/** Space the report concerns. */
space: DID
/** Period the report applies to. */
period: {
/** ISO datetime the report begins from (inclusive). */
from: ISO8601Date
/** ISO datetime the report ends at (inclusive). */
to: ISO8601Date
}
/** Observed space size for the period. */
size: {
/** Size at the beginning of the report period. */
initial: number
/** Size at the end of the report period. */
final: number
}
/** Events that caused the size to change during the period. */
events: Array<{
/** CID of the invoked task that caused the size to change. */
cause: Link
/** Number of bytes that were added or removed. */
delta: number
/** ISO datetime that the receipt was issued for the change. */
receiptAt: ISO8601Date
}>
}
```
## Contributing
Feel free to join in. All welcome. Please [open an issue](https://github.com/storacha/upload-service/issues)!
## License
Dual-licensed under [MIT + Apache 2.0](https://github.com/storacha/upload-service/blob/main/packages/@storacha/client/LICENSE.md)
[w3cli-github]: https://github.com/storacha/w3cli
[access-client-github]: https://github.com/storacha/upload-service/tree/main/packages/access-client
[upload-client-github]: https://github.com/storacha/upload-service/tree/main/packages/upload-client
[elastic-ipfs]: https://github.com/elastic-ipfs/elastic-ipfs
[ucanto]: https://github.com/storacha/ucanto
[car-spec]: https://ipld.io/specs/transport/car/
[web3storage-docs-cars]: https://docs.storacha.network/concepts/car/
[docs]: https://web3-storage.github.io/Storacha/modules/_web3_storage_Storacha_client.html
[docs-Client]: https://web3-storage.github.io/Storacha/classes/_web3_storage_Storacha_client.Client.html
[docs-Client#agent]: https://web3-storage.github.io/Storacha/classes/_web3_storage_Storacha_client.Client.html#agent
[docs-Client#createDelegation]: https://web3-storage.github.io/Storacha/classes/_web3_storage_Storacha_client.Client.html#createDelegation
[docs-Client#createSpace]: https://web3-storage.github.io/Storacha/classes/_web3_storage_Storacha_client.Client.html#createSpace
[docs-Client#setCurrentSpace]: https://web3-storage.github.io/Storacha/classes/_web3_storage_Storacha_client.Client.html#setCurrentSpace
[docs-Client#uploadFile]: https://web3-storage.github.io/Storacha/classes/_web3_storage_Storacha_client.Client.html#uploadFile
[docs-Client#uploadDirectory]: https://web3-storage.github.io/Storacha/classes/_web3_storage_Storacha_client.Client.html#uploadDirectory
[docs-Space]: https://web3-storage.github.io/Storacha/modules/_web3_storage_access.Space.html
[docs-create]: #create
[docs-ClientFactoryOptions]: https://web3-storage.github.io/Storacha/interfaces/_web3_storage_Storacha_client.unknown.ClientFactoryOptions.html
[access-docs-Agent]: https://web3-storage.github.io/Storacha/classes/_web3_storage_access.Agent.html
---