}
- */
-export function createServer(handlers = {}) {
- const server = Server.create({
- id: service,
- codec: CAR.inbound,
- service: {
- space: {
- info: Server.provide(Space.info, async ({ capability }) => {
- return {
- ok: {
- did: 'did:key:sss',
- agent: 'did:key:agent',
- email: 'mail@mail.com',
- product: 'product:free',
- updated_at: 'sss',
- inserted_at: 'date',
- },
- }
- }),
- },
- ...handlers,
- },
- validateAuthorization,
- })
-
- // @ts-ignore
- return server
-}
-
-export const validateAuthorization = () => ({ ok: {} })
diff --git a/packages/access-client/tsconfig.json b/packages/access-client/tsconfig.json
index 186028130..0e14aa5aa 100644
--- a/packages/access-client/tsconfig.json
+++ b/packages/access-client/tsconfig.json
@@ -6,5 +6,5 @@
},
"include": ["src", "scripts", "test", "package.json"],
"exclude": ["**/node_modules/**"],
- "references": [{ "path": "../capabilities" }, { "path": "../did-mailto" }]
+ "references": [{ "path": "../w3up-client" }]
}
diff --git a/packages/filecoin-api/test/context/queue-implementations.js b/packages/filecoin-api/test/context/queue-implementations.js
index 2785a2210..a75936644 100644
--- a/packages/filecoin-api/test/context/queue-implementations.js
+++ b/packages/filecoin-api/test/context/queue-implementations.js
@@ -2,6 +2,7 @@ import { Queue } from './queue.js'
/**
* @param {Map} queuedMessages
+ * @param QueueImplementation
*/
export const getQueueImplementations = (
queuedMessages,
diff --git a/packages/filecoin-api/test/context/store-implementations.js b/packages/filecoin-api/test/context/store-implementations.js
index 5c47f336e..a576ba198 100644
--- a/packages/filecoin-api/test/context/store-implementations.js
+++ b/packages/filecoin-api/test/context/store-implementations.js
@@ -1,6 +1,7 @@
import { UpdatableStore } from './store.js'
/**
+ * @param StoreImplementation
* @typedef {import('@ucanto/interface').Link} Link
* @typedef {import('../../src/storefront/api.js').PieceRecord} PieceRecord
* @typedef {import('../../src/storefront/api.js').PieceRecordKey} PieceRecordKey
diff --git a/packages/filecoin-client/package.json b/packages/filecoin-client/package.json
index 711ee9d8e..7c8796516 100644
--- a/packages/filecoin-client/package.json
+++ b/packages/filecoin-client/package.json
@@ -24,11 +24,31 @@
"rc": "npm version prerelease --preid rc"
},
"exports": {
- ".": "./dist/src/index.js",
- "./aggregator": "./dist/src/aggregator.js",
- "./dealer": "./dist/src/dealer.js",
- "./storefront": "./dist/src/storefront.js",
- "./types": "./dist/src/types.js"
+ ".": {
+ "types": "./dist/src/index.d.ts",
+ "import": "./src/index.js",
+ "default": "./dist/src/index.js"
+ },
+ "./aggregator": {
+ "types": "./dist/src/aggregator.d.ts",
+ "import": "./src/aggregator.js",
+ "default": "./dist/src/aggregator.js"
+ },
+ "./dealer": {
+ "types": "./dist/src/dealer.d.ts",
+ "import": "./src/dealer.js",
+ "default": "./dist/src/dealer.js"
+ },
+ "./storefront": {
+ "types": "./dist/src/storefront.d.ts",
+ "import": "./src/storefront.js",
+ "default": "./dist/src/storefront.js"
+ },
+ "./types": {
+ "types": "./dist/src/types.d.ts",
+ "import": "./src/types.js",
+ "default": "./dist/src/types.js"
+ }
},
"typesVersions": {
"*": {
diff --git a/packages/upload-api/README.md b/packages/upload-api/README.md
index d0484a415..66582a476 100644
--- a/packages/upload-api/README.md
+++ b/packages/upload-api/README.md
@@ -1,29 +1,3 @@
-⁂
web3.storage
-The upload API for https://web3.storage
-
-## About
-
-The `@web3-storage/upload-api` package provides an implementation of the w3up
-UCAN invocation service. It provides a set of storage interfaces that can
-be implemented to run w3up on top of arbitrary data stores.
-
-## Install
-
-Install the package using npm:
-
-```bash
-npm install @web3-storage/upload-api
-```
-
-## Usage
-
-Coming soon!
-
-## Contributing
-
-Feel free to join in. All welcome. Please [open an issue](https://github.com/web3-storage/w3up/issues)!
-
-## License
-
-Dual-licensed under [MIT + Apache 2.0](https://github.com/web3-storage/w3up/blob/main/license.md)
+# ⚠️ Deprecated
+Use `@web3-storage/w3up-client` instead.
diff --git a/packages/upload-api/package.json b/packages/upload-api/package.json
index ec262a45e..04e950892 100644
--- a/packages/upload-api/package.json
+++ b/packages/upload-api/package.json
@@ -114,7 +114,7 @@
"@ucanto/server": "^9.0.1",
"@ucanto/transport": "^9.0.0",
"@ucanto/validator": "^9.0.0",
- "@web3-storage/access": "workspace:^",
+ "@web3-storage/w3up-client": "workspace:^",
"@web3-storage/capabilities": "workspace:^",
"@web3-storage/did-mailto": "workspace:^",
"@web3-storage/filecoin-api": "workspace:^",
diff --git a/packages/upload-api/src/access/authorize.js b/packages/upload-api/src/access/authorize.js
index a0417d413..8bd1f2484 100644
--- a/packages/upload-api/src/access/authorize.js
+++ b/packages/upload-api/src/access/authorize.js
@@ -2,7 +2,7 @@ import * as Server from '@ucanto/server'
import * as API from '../types.js'
import * as Access from '@web3-storage/capabilities/access'
import * as DidMailto from '@web3-storage/did-mailto'
-import { delegationToString } from '@web3-storage/access/encoding'
+import { delegationToString } from '@web3-storage/w3up-client/agent/encoding'
import { mailtoDidToDomain, mailtoDidToEmail } from '../utils/did-mailto.js'
import { ensureRateLimitAbove } from '../utils/rate-limits.js'
diff --git a/packages/upload-api/src/space.js b/packages/upload-api/src/space.js
index 3faf937f6..e24cac501 100644
--- a/packages/upload-api/src/space.js
+++ b/packages/upload-api/src/space.js
@@ -34,7 +34,7 @@ export const info = async ({ capability }, ctx) => {
}
}
- /** @type {import('@web3-storage/access/types').SpaceUnknown} */
+ /** @type {import('@web3-storage/w3up-client').SpaceUnknown} */
const spaceUnknownFailure = {
name: 'SpaceUnknown',
message: `Space not found.`,
diff --git a/packages/upload-api/src/utils/delegations-response.js b/packages/upload-api/src/utils/delegations-response.js
index 6a0a46109..e54dd9a8e 100644
--- a/packages/upload-api/src/utils/delegations-response.js
+++ b/packages/upload-api/src/utils/delegations-response.js
@@ -7,7 +7,7 @@ import * as Ucanto from '@ucanto/interface'
import {
bytesToDelegations,
delegationsToBytes,
-} from '@web3-storage/access/encoding'
+} from '@web3-storage/w3up-client/agent/encoding'
/**
* @template D
@@ -37,7 +37,7 @@ export function encode(delegations) {
export function* decode(encoded) {
for (const carBytes of Object.values(encoded)) {
const delegations = bytesToDelegations(
- /** @type {import('@web3-storage/access/types').BytesDelegation} */ (
+ /** @type {import('@web3-storage/w3up-client').BytesDelegation} */ (
carBytes
)
)
diff --git a/packages/upload-api/src/validate.js b/packages/upload-api/src/validate.js
index 5d21755ae..9331dfbb7 100644
--- a/packages/upload-api/src/validate.js
+++ b/packages/upload-api/src/validate.js
@@ -1,7 +1,7 @@
import {
delegationsToString,
stringToDelegation,
-} from '@web3-storage/access/encoding'
+} from '@web3-storage/w3up-client/agent/encoding'
import * as DidMailto from '@web3-storage/did-mailto'
import { Verifier } from '@ucanto/principal'
import * as delegationsResponse from './utils/delegations-response.js'
diff --git a/packages/upload-api/test/access-client-agent.js b/packages/upload-api/test/access-client-agent.js
index 3f805cca9..f78f357e8 100644
--- a/packages/upload-api/test/access-client-agent.js
+++ b/packages/upload-api/test/access-client-agent.js
@@ -3,16 +3,15 @@ import { Absentee } from '@ucanto/principal'
import * as delegationsResponse from '../src/utils/delegations-response.js'
import * as DidMailto from '@web3-storage/did-mailto'
import { Access, Space } from '@web3-storage/capabilities'
-import { AgentData } from '@web3-storage/access'
+import { AgentData } from '@web3-storage/w3up-client/agent'
import { alice } from './helpers/utils.js'
-import { stringToDelegations } from '@web3-storage/access/encoding'
+import { stringToDelegations } from '@web3-storage/w3up-client/agent/encoding'
import {
confirmConfirmationUrl,
extractConfirmInvocation,
} from './helpers/utils.js'
import {
Agent,
- Access as AgentAccess,
claimAccess,
addProvider,
authorizeAndWait,
@@ -21,8 +20,9 @@ import {
delegationsIncludeSessionProof,
addSpacesFromDelegations,
requestAccess,
-} from '@web3-storage/access/agent'
-import * as Provider from '@web3-storage/access/provider'
+} from '@web3-storage/w3up-client/agent'
+import * as AgentAccess from '@web3-storage/w3up-client/capability/access'
+import * as Provider from '@web3-storage/w3up-client/capability/provider'
/**
* @type {API.Tests}
@@ -313,7 +313,7 @@ export const test = {
assert.ok(spaceInfoResult.out.ok)
const result =
- /** @type {import('@web3-storage/access/types').SpaceInfoResult} */ (
+ /** @type {import('@web3-storage/w3up-client').SpaceInfoResult} */ (
spaceInfoResult.out.ok
)
assert.deepEqual(result.did, spaceCreation.did())
diff --git a/packages/upload-api/test/handlers/access/authorize.js b/packages/upload-api/test/handlers/access/authorize.js
index 0293c8df3..46689e1bb 100644
--- a/packages/upload-api/test/handlers/access/authorize.js
+++ b/packages/upload-api/test/handlers/access/authorize.js
@@ -7,7 +7,7 @@ import * as DidMailto from '@web3-storage/did-mailto'
import {
stringToDelegation,
bytesToDelegations,
-} from '@web3-storage/access/encoding'
+} from '@web3-storage/w3up-client/agent/encoding'
import { authorizeFromUrl } from '../../../src/validate.js'
/**
@@ -36,7 +36,7 @@ export const test = {
const url = new URL(email.url)
const encoded =
- /** @type {import('@web3-storage/access/types').EncodedDelegation<[import('@web3-storage/capabilities/types').AccessConfirm]>} */ (
+ /** @type {import('@web3-storage/w3up-client').EncodedDelegation<[import('@web3-storage/capabilities/types').AccessConfirm]>} */ (
url.searchParams.get('ucan')
)
const delegation = stringToDelegation(encoded)
diff --git a/packages/upload-api/test/helpers/utils.js b/packages/upload-api/test/helpers/utils.js
index 3581f1565..6374e71c9 100644
--- a/packages/upload-api/test/helpers/utils.js
+++ b/packages/upload-api/test/helpers/utils.js
@@ -8,7 +8,7 @@ import * as Context from './context.js'
import { Provider, UCAN, Space } from '@web3-storage/capabilities'
import * as DidMailto from '@web3-storage/did-mailto'
import * as API from '../types.js'
-import { stringToDelegation } from '@web3-storage/access/encoding'
+import { stringToDelegation } from '@web3-storage/w3up-client/agent/encoding'
export { Context }
@@ -202,7 +202,7 @@ export const queue = (buffer = []) => {
/**
* @param {Types.Signer} issuer
* @param {Types.Signer} service
- * @param {Types.ConnectionView} conn
+ * @param {Types.ConnectionView} conn
* @param {`${string}@${string}`} email
*/
export async function createSpace(issuer, service, conn, email) {
@@ -254,7 +254,7 @@ export async function extractConfirmInvocation(confirmationUrl) {
}
/**
- * @param {API.ConnectionView} connection
+ * @param {API.ConnectionView} connection
* @param {{ url: string|URL }} confirmation
*/
export async function confirmConfirmationUrl(connection, confirmation) {
diff --git a/packages/upload-api/tsconfig.json b/packages/upload-api/tsconfig.json
index ec01de0e1..89380dc41 100644
--- a/packages/upload-api/tsconfig.json
+++ b/packages/upload-api/tsconfig.json
@@ -6,5 +6,9 @@
},
"include": ["src", "test"],
"exclude": ["**/node_modules/**", "dist"],
- "references": [{ "path": "../capabilities" }, { "path": "../access-client"}, { "path": "../filecoin-api" }]
+ "references": [
+ { "path": "../capabilities" },
+ { "path": "../filecoin-api" },
+ { "path": "../w3up-client" }
+ ]
}
diff --git a/packages/upload-client/package.json b/packages/upload-client/package.json
index 95ced970f..181c3c2a9 100644
--- a/packages/upload-client/package.json
+++ b/packages/upload-client/package.json
@@ -18,14 +18,6 @@
"lint": "eslint '**/*.{js,ts}' && prettier --check '**/*.{js,ts,yml,json}' --ignore-path ../../.gitignore",
"build": "tsc --build",
"check": "tsc --build",
- "test": "npm-run-all -p -r mock test:all",
- "test:all": "run-s test:node test:browser",
- "test:node": "hundreds -r html -r text mocha 'test/**/!(*.browser).test.js' -n experimental-vm-modules -n no-warnings",
- "test:browser": "playwright-test 'test/**/!(*.node).test.js'",
- "mock": "run-p mock:*",
- "mock:bucket-200": "PORT=9200 STATUS=200 node test/helpers/bucket-server.js",
- "mock:bucket-401": "PORT=9400 STATUS=400 node test/helpers/bucket-server.js",
- "mock:bucket-500": "PORT=9500 STATUS=500 node test/helpers/bucket-server.js",
"rc": "npm version prerelease --preid rc"
},
"exports": {
@@ -66,36 +58,10 @@
"dist/src/**/*.d.ts.map"
],
"dependencies": {
- "@ipld/car": "^5.2.2",
- "@ipld/dag-cbor": "^9.0.6",
- "@ipld/dag-ucan": "^3.4.0",
- "@ipld/unixfs": "^2.1.1",
- "@ucanto/client": "^9.0.0",
- "@ucanto/interface": "^9.0.0",
- "@ucanto/transport": "^9.0.0",
- "@web3-storage/capabilities": "workspace:^",
- "fr32-sha2-256-trunc254-padded-binary-tree-multihash": "^3.1.0",
- "ipfs-utils": "^9.0.14",
- "multiformats": "^12.1.2",
- "p-retry": "^5.1.2",
- "parallel-transform-web": "^1.0.0",
- "varint": "^6.0.0"
+ "@web3-storage/w3up-client": "workspace:^"
},
"devDependencies": {
- "@types/assert": "^1.5.6",
- "@types/mocha": "^10.0.1",
- "@types/varint": "^6.0.1",
"@web3-storage/eslint-config-w3up": "workspace:^",
- "@ucanto/principal": "^9.0.0",
- "@ucanto/server": "^9.0.1",
- "assert": "^2.0.0",
- "blockstore-core": "^3.0.0",
- "c8": "^7.13.0",
- "hundreds": "^0.0.9",
- "ipfs-unixfs-exporter": "^10.0.0",
- "mocha": "^10.2.0",
- "npm-run-all": "^4.1.5",
- "playwright-test": "^12.3.4",
"typescript": "5.2.2"
},
"eslintConfig": {
diff --git a/packages/upload-client/src/car.js b/packages/upload-client/src/car.js
index 773170a40..4fb4fcf36 100644
--- a/packages/upload-client/src/car.js
+++ b/packages/upload-client/src/car.js
@@ -1,115 +1 @@
-import { CarBlockIterator, CarWriter } from '@ipld/car'
-import * as dagCBOR from '@ipld/dag-cbor'
-import varint from 'varint'
-
-/**
- * @typedef {import('@ipld/unixfs').Block} Block
- */
-
-/** Byte length of a CBOR encoded CAR header with zero roots. */
-const NO_ROOTS_HEADER_LENGTH = 17
-
-/** @param {import('./types.js').AnyLink} [root] */
-export function headerEncodingLength(root) {
- if (!root) return NO_ROOTS_HEADER_LENGTH
- const headerLength = dagCBOR.encode({ version: 1, roots: [root] }).length
- const varintLength = varint.encodingLength(headerLength)
- return varintLength + headerLength
-}
-
-/** @param {Block} block */
-export function blockEncodingLength(block) {
- const payloadLength = block.cid.bytes.length + block.bytes.length
- const varintLength = varint.encodingLength(payloadLength)
- return varintLength + payloadLength
-}
-
-/**
- * @param {Iterable | AsyncIterable} blocks
- * @param {import('./types.js').AnyLink} [root]
- * @returns {Promise}
- */
-export async function encode(blocks, root) {
- // @ts-expect-error
- const { writer, out } = CarWriter.create(root)
- /** @type {Error?} */
- let error
- void (async () => {
- try {
- for await (const block of blocks) {
- await writer.put(block)
- }
- } catch (/** @type {any} */ err) {
- error = err
- } finally {
- await writer.close()
- }
- })()
- const chunks = []
- for await (const chunk of out) chunks.push(chunk)
- // @ts-expect-error
- if (error != null) throw error
- const roots = root != null ? [root] : []
- return Object.assign(new Blob(chunks), { version: 1, roots })
-}
-
-/** @extends {ReadableStream} */
-export class BlockStream extends ReadableStream {
- /** @param {import('./types.js').BlobLike} car */
- constructor(car) {
- /** @type {Promise?} */
- let blocksPromise = null
- const getBlocksIterable = () => {
- if (blocksPromise) return blocksPromise
- blocksPromise = CarBlockIterator.fromIterable(toIterable(car.stream()))
- return blocksPromise
- }
-
- /** @type {AsyncIterator?} */
- let iterator = null
- super({
- async start() {
- const blocks = await getBlocksIterable()
- iterator = /** @type {AsyncIterator} */ (
- blocks[Symbol.asyncIterator]()
- )
- },
- async pull(controller) {
- /* c8 ignore next */
- if (!iterator) throw new Error('missing blocks iterator')
- const { value, done } = await iterator.next()
- if (done) return controller.close()
- controller.enqueue(value)
- },
- })
-
- /** @returns {Promise} */
- this.getRoots = async () => {
- const blocks = await getBlocksIterable()
- return await blocks.getRoots()
- }
- }
-}
-
-/* c8 ignore next 20 */
-/**
- * @template T
- * @param {{ getReader: () => ReadableStreamDefaultReader } | AsyncIterable} stream
- * @returns {AsyncIterable}
- */
-function toIterable(stream) {
- return Symbol.asyncIterator in stream
- ? stream
- : (async function* () {
- const reader = stream.getReader()
- try {
- while (true) {
- const { done, value } = await reader.read()
- if (done) return
- yield value
- }
- } finally {
- reader.releaseLock()
- }
- })()
-}
+export * from '@web3-storage/w3up-client/capability/upload/sharding'
diff --git a/packages/upload-client/src/index.js b/packages/upload-client/src/index.js
index 3336ee0fd..7f6e94e11 100644
--- a/packages/upload-client/src/index.js
+++ b/packages/upload-client/src/index.js
@@ -1,155 +1 @@
-import { Parallel } from 'parallel-transform-web'
-import * as PieceHasher from 'fr32-sha2-256-trunc254-padded-binary-tree-multihash/async'
-import * as Link from 'multiformats/link'
-import * as raw from 'multiformats/codecs/raw'
-import * as Store from './store.js'
-import * as Upload from './upload.js'
-import * as UnixFS from './unixfs.js'
-import * as CAR from './car.js'
-import { ShardingStream } from './sharding.js'
-
-export { Store, Upload, UnixFS, CAR }
-export * from './sharding.js'
-
-const CONCURRENT_REQUESTS = 3
-
-/**
- * Uploads a file to the service and returns the root data CID for the
- * generated DAG.
- *
- * Required delegated capability proofs: `store/add`, `upload/add`
- *
- * @param {import('./types.js').InvocationConfig} conf Configuration
- * for the UCAN invocation. An object with `issuer`, `with` and `proofs`.
- *
- * The `issuer` is the signing authority that is issuing the UCAN
- * invocation(s). It is typically the user _agent_.
- *
- * The `with` is the resource the invocation applies to. It is typically the
- * DID of a space.
- *
- * The `proofs` are a set of capability delegations that prove the issuer
- * has the capability to perform the action.
- *
- * The issuer needs the `store/add` and `upload/add` delegated capability.
- * @param {import('./types.js').BlobLike} file File data.
- * @param {import('./types.js').UploadOptions} [options]
- */
-export async function uploadFile(conf, file, options = {}) {
- return await uploadBlockStream(
- conf,
- UnixFS.createFileEncoderStream(file),
- options
- )
-}
-
-/**
- * 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.
- *
- * Required delegated capability proofs: `store/add`, `upload/add`
- *
- * @param {import('./types.js').InvocationConfig} conf Configuration
- * for the UCAN invocation. An object with `issuer`, `with` and `proofs`.
- *
- * The `issuer` is the signing authority that is issuing the UCAN
- * invocation(s). It is typically the user _agent_.
- *
- * The `with` is the resource the invocation applies to. It is typically the
- * DID of a space.
- *
- * The `proofs` are a set of capability delegations that prove the issuer
- * has the capability to perform the action.
- *
- * The issuer needs the `store/add` and `upload/add` delegated capability.
- * @param {import('./types.js').FileLike[]} files File data.
- * @param {import('./types.js').UploadDirectoryOptions} [options]
- */
-export async function uploadDirectory(conf, files, options = {}) {
- return await uploadBlockStream(
- conf,
- UnixFS.createDirectoryEncoderStream(files, options),
- options
- )
-}
-
-/**
- * Uploads a CAR file to the service.
- *
- * The difference between this function and `Store.add` is that the CAR file is
- * automatically sharded and an "upload" is registered, linking the individual
- * shards (see `Upload.add`).
- *
- * Use the `onShardStored` callback to obtain the CIDs of the CAR file shards.
- *
- * Required delegated capability proofs: `store/add`, `upload/add`
- *
- * @param {import('./types.js').InvocationConfig} conf Configuration
- * for the UCAN invocation. An object with `issuer`, `with` and `proofs`.
- *
- * The `issuer` is the signing authority that is issuing the UCAN
- * invocation(s). It is typically the user _agent_.
- *
- * The `with` is the resource the invocation applies to. It is typically the
- * DID of a space.
- *
- * The `proofs` are a set of capability delegations that prove the issuer
- * has the capability to perform the action.
- *
- * The issuer needs the `store/add` and `upload/add` delegated capability.
- * @param {import('./types.js').BlobLike} car CAR file.
- * @param {import('./types.js').UploadOptions} [options]
- */
-export async function uploadCAR(conf, car, options = {}) {
- const blocks = new CAR.BlockStream(car)
- options.rootCID = options.rootCID ?? (await blocks.getRoots())[0]
- return await uploadBlockStream(conf, blocks, options)
-}
-
-/**
- * @param {import('./types.js').InvocationConfig} conf
- * @param {ReadableStream} blocks
- * @param {import('./types.js').UploadOptions} [options]
- * @returns {Promise}
- */
-async function uploadBlockStream(conf, blocks, options = {}) {
- /** @type {import('./types.js').CARLink[]} */
- const shards = []
- /** @type {import('./types.js').AnyLink?} */
- let root = null
- const concurrency = options.concurrentRequests ?? CONCURRENT_REQUESTS
- await blocks
- .pipeThrough(new ShardingStream(options))
- .pipeThrough(
- new Parallel(concurrency, async (car) => {
- const bytes = new Uint8Array(await car.arrayBuffer())
- const [cid, piece] = await Promise.all([
- Store.add(conf, bytes, options),
- (async () => {
- const multihashDigest = await PieceHasher.digest(bytes)
- return /** @type {import('@web3-storage/capabilities/types').PieceLink} */ (
- Link.create(raw.code, multihashDigest)
- )
- })(),
- ])
- const { version, roots, size } = car
- return { version, roots, size, cid, piece }
- })
- )
- .pipeTo(
- new WritableStream({
- write(meta) {
- root = root || meta.roots[0]
- shards.push(meta.cid)
- if (options.onShardStored) options.onShardStored(meta)
- },
- })
- )
-
- /* c8 ignore next */
- if (!root) throw new Error('missing root CID')
-
- await Upload.add(conf, root, shards, options)
- return root
-}
+export * from '@web3-storage/w3up-client/capability/upload'
diff --git a/packages/upload-client/src/sharding.js b/packages/upload-client/src/sharding.js
index b69bdc4e0..4fb4fcf36 100644
--- a/packages/upload-client/src/sharding.js
+++ b/packages/upload-client/src/sharding.js
@@ -1,86 +1 @@
-import { blockEncodingLength, encode, headerEncodingLength } from './car.js'
-
-// https://observablehq.com/@gozala/w3up-shard-size
-const SHARD_SIZE = 133_169_152
-
-/**
- * Shard a set of blocks into a set of CAR files. By default the last block
- * received is assumed to be the DAG root and becomes the CAR root CID for the
- * last CAR output. Set the `rootCID` option to override.
- *
- * @extends {TransformStream}
- */
-export class ShardingStream extends TransformStream {
- /**
- * @param {import('./types.js').ShardingOptions} [options]
- */
- constructor(options = {}) {
- const shardSize = options.shardSize ?? SHARD_SIZE
- const maxBlockLength = shardSize - headerEncodingLength()
- /** @type {import('@ipld/unixfs').Block[]} */
- let blocks = []
- /** @type {import('@ipld/unixfs').Block[] | null} */
- let readyBlocks = null
- let currentLength = 0
-
- super({
- async transform(block, controller) {
- if (readyBlocks != null) {
- controller.enqueue(await encode(readyBlocks))
- readyBlocks = null
- }
-
- const blockLength = blockEncodingLength(block)
- if (blockLength > maxBlockLength) {
- throw new Error(
- `block will cause CAR to exceed shard size: ${block.cid}`
- )
- }
-
- if (blocks.length && currentLength + blockLength > maxBlockLength) {
- readyBlocks = blocks
- blocks = []
- currentLength = 0
- }
- blocks.push(block)
- currentLength += blockLength
- },
-
- async flush(controller) {
- if (readyBlocks != null) {
- controller.enqueue(await encode(readyBlocks))
- }
-
- const rootBlock = blocks.at(-1)
- if (rootBlock == null) return
-
- const rootCID = options.rootCID ?? rootBlock.cid
- const headerLength = headerEncodingLength(rootCID)
-
- // if adding CAR root overflows the shard limit we move overflowing
- // blocks into a another CAR.
- if (headerLength + currentLength > shardSize) {
- const overage = headerLength + currentLength - shardSize
- const overflowBlocks = []
- let overflowCurrentLength = 0
- while (overflowCurrentLength < overage) {
- const block = blocks[blocks.length - 1]
- blocks.pop()
- overflowBlocks.unshift(block)
- overflowCurrentLength += blockEncodingLength(block)
-
- // need at least 1 block in original shard
- if (blocks.length < 1)
- throw new Error(
- `block will cause CAR to exceed shard size: ${block.cid}`
- )
- }
- controller.enqueue(await encode(blocks))
- controller.enqueue(await encode(overflowBlocks, rootCID))
- } else {
- controller.enqueue(await encode(blocks, rootCID))
- }
- },
- })
- }
-}
+export * from '@web3-storage/w3up-client/capability/upload/sharding'
diff --git a/packages/upload-client/src/store.js b/packages/upload-client/src/store.js
index 6dab2a436..f1e6092c0 100644
--- a/packages/upload-client/src/store.js
+++ b/packages/upload-client/src/store.js
@@ -1,231 +1 @@
-import { CAR } from '@ucanto/transport'
-import * as StoreCapabilities from '@web3-storage/capabilities/store'
-import { SpaceDID } from '@web3-storage/capabilities/utils'
-import retry, { AbortError } from 'p-retry'
-import { servicePrincipal, connection } from './service.js'
-import { REQUEST_RETRIES } from './constants.js'
-import fetchPkg from 'ipfs-utils/src/http/fetch.js'
-const { fetch } = fetchPkg
-
-/**
- *
- * @param {string} url
- * @param {import('./types.js').ProgressFn} handler
- */
-function createUploadProgressHandler(url, handler) {
- /**
- *
- * @param {import('./types.js').ProgressStatus} status
- */
- function onUploadProgress({ total, loaded, lengthComputable }) {
- return handler({ total, loaded, lengthComputable, url })
- }
- return onUploadProgress
-}
-
-/**
- * Store a DAG encoded as a CAR file. The issuer needs the `store/add`
- * delegated capability.
- *
- * Required delegated capability proofs: `store/add`
- *
- * @param {import('./types.js').InvocationConfig} conf Configuration
- * for the UCAN invocation. An object with `issuer`, `with` and `proofs`.
- *
- * The `issuer` is the signing authority that is issuing the UCAN
- * invocation(s). It is typically the user _agent_.
- *
- * The `with` is the resource the invocation applies to. It is typically the
- * DID of a space.
- *
- * The `proofs` are a set of capability delegations that prove the issuer
- * has the capability to perform the action.
- *
- * The issuer needs the `store/add` delegated capability.
- * @param {Blob|Uint8Array} car CAR file data.
- * @param {import('./types.js').RequestOptions} [options]
- * @returns {Promise}
- */
-export async function add(
- { issuer, with: resource, proofs, audience },
- car,
- options = {}
-) {
- // TODO: validate blob contains CAR data
- const bytes =
- car instanceof Uint8Array ? car : new Uint8Array(await car.arrayBuffer())
- const link = await CAR.codec.link(bytes)
- /* c8 ignore next */
- const conn = options.connection ?? connection
- const result = await retry(
- async () => {
- return await StoreCapabilities.add
- .invoke({
- issuer,
- /* c8 ignore next */
- audience: audience ?? servicePrincipal,
- with: SpaceDID.from(resource),
- nb: { link, size: bytes.length },
- proofs,
- })
- .execute(conn)
- },
- {
- onFailedAttempt: console.warn,
- retries: options.retries ?? REQUEST_RETRIES,
- }
- )
-
- if (!result.out.ok) {
- throw new Error(`failed ${StoreCapabilities.add.can} invocation`, {
- cause: result.out.error,
- })
- }
-
- // Return early if it was already uploaded.
- if (result.out.ok.status === 'done') {
- return link
- }
-
- const responseAddUpload = result.out.ok
-
- const fetchWithUploadProgress =
- /** @type {(url: string, init?: import('./types.js').FetchOptions) => Promise} */ (
- fetch
- )
-
- const res = await retry(
- async () => {
- try {
- const res = await fetchWithUploadProgress(responseAddUpload.url, {
- method: 'PUT',
- mode: 'cors',
- body: car,
- headers: responseAddUpload.headers,
- signal: options.signal,
- onUploadProgress: options.onUploadProgress
- ? createUploadProgressHandler(
- responseAddUpload.url,
- options.onUploadProgress
- )
- : undefined,
- // @ts-expect-error - this is needed by recent versions of node - see https://github.com/bluesky-social/atproto/pull/470 for more info
- duplex: 'half',
- })
- if (res.status >= 400 && res.status < 500) {
- throw new AbortError(`upload failed: ${res.status}`)
- }
- return res
- } catch (err) {
- if (options.signal?.aborted === true) {
- throw new AbortError('upload aborted')
- }
- throw err
- }
- },
- {
- retries: options.retries ?? REQUEST_RETRIES,
- }
- )
-
- if (!res.ok) {
- throw new Error(`upload failed: ${res.status}`)
- }
-
- return link
-}
-
-/**
- * List CAR files stored by the issuer.
- *
- * @param {import('./types.js').InvocationConfig} conf Configuration
- * for the UCAN invocation. An object with `issuer`, `with` and `proofs`.
- *
- * The `issuer` is the signing authority that is issuing the UCAN
- * invocation(s). It is typically the user _agent_.
- *
- * The `with` is the resource the invocation applies to. It is typically the
- * DID of a space.
- *
- * The `proofs` are a set of capability delegations that prove the issuer
- * has the capability to perform the action.
- *
- * The issuer needs the `store/list` delegated capability.
- * @param {import('./types.js').ListRequestOptions} [options]
- * @returns {Promise}
- */
-export async function list(
- { issuer, with: resource, proofs, audience },
- options = {}
-) {
- /* c8 ignore next */
- const conn = options.connection ?? connection
- const result = await StoreCapabilities.list
- .invoke({
- issuer,
- /* c8 ignore next */
- audience: audience ?? servicePrincipal,
- with: SpaceDID.from(resource),
- proofs,
- nb: {
- cursor: options.cursor,
- size: options.size,
- pre: options.pre,
- },
- })
- .execute(conn)
-
- if (!result.out.ok) {
- throw new Error(`failed ${StoreCapabilities.list.can} invocation`, {
- cause: result.out.error,
- })
- }
-
- return result.out.ok
-}
-
-/**
- * Remove a stored CAR file by CAR CID.
- *
- * @param {import('./types.js').InvocationConfig} conf Configuration
- * for the UCAN invocation. An object with `issuer`, `with` and `proofs`.
- *
- * The `issuer` is the signing authority that is issuing the UCAN
- * invocation(s). It is typically the user _agent_.
- *
- * The `with` is the resource the invocation applies to. It is typically the
- * DID of a space.
- *
- * The `proofs` are a set of capability delegations that prove the issuer
- * has the capability to perform the action.
- *
- * The issuer needs the `store/remove` delegated capability.
- * @param {import('./types.js').CARLink} link CID of CAR file to remove.
- * @param {import('./types.js').RequestOptions} [options]
- */
-export async function remove(
- { issuer, with: resource, proofs, audience },
- link,
- options = {}
-) {
- /* c8 ignore next */
- const conn = options.connection ?? connection
- const result = await StoreCapabilities.remove
- .invoke({
- issuer,
- /* c8 ignore next */
- audience: audience ?? servicePrincipal,
- with: SpaceDID.from(resource),
- nb: { link },
- proofs,
- })
- .execute(conn)
-
- if (!result.out.ok) {
- throw new Error(`failed ${StoreCapabilities.remove.can} invocation`, {
- cause: result.out.error,
- })
- }
-
- return result.out
-}
+export * from '@web3-storage/w3up-client/capability/store'
diff --git a/packages/upload-client/src/types.js b/packages/upload-client/src/types.js
new file mode 100644
index 000000000..7f6e94e11
--- /dev/null
+++ b/packages/upload-client/src/types.js
@@ -0,0 +1 @@
+export * from '@web3-storage/w3up-client/capability/upload'
diff --git a/packages/upload-client/src/unixfs.js b/packages/upload-client/src/unixfs.js
index 3547ba71f..5e48bc917 100644
--- a/packages/upload-client/src/unixfs.js
+++ b/packages/upload-client/src/unixfs.js
@@ -1,176 +1 @@
-import * as UnixFS from '@ipld/unixfs'
-import * as raw from 'multiformats/codecs/raw'
-import { withMaxChunkSize } from '@ipld/unixfs/file/chunker/fixed'
-import { withWidth } from '@ipld/unixfs/file/layout/balanced'
-
-const SHARD_THRESHOLD = 1000 // shard directory after > 1,000 items
-const queuingStrategy = UnixFS.withCapacity()
-
-const settings = UnixFS.configure({
- fileChunkEncoder: raw,
- smallFileEncoder: raw,
- chunker: withMaxChunkSize(1024 * 1024),
- fileLayout: withWidth(1024),
-})
-
-/**
- * @param {import('./types.js').BlobLike} blob
- * @returns {Promise}
- */
-export async function encodeFile(blob) {
- const readable = createFileEncoderStream(blob)
- const blocks = await collect(readable)
- // @ts-expect-error There is always a root block
- return { cid: blocks.at(-1).cid, blocks }
-}
-
-/**
- * @param {import('./types.js').BlobLike} blob
- * @returns {ReadableStream}
- */
-export function createFileEncoderStream(blob) {
- /** @type {TransformStream} */
- const { readable, writable } = new TransformStream({}, queuingStrategy)
- const unixfsWriter = UnixFS.createWriter({ writable, settings })
- const fileBuilder = new UnixFSFileBuilder('', blob)
- void (async () => {
- await fileBuilder.finalize(unixfsWriter)
- await unixfsWriter.close()
- })()
- return readable
-}
-
-class UnixFSFileBuilder {
- #file
-
- /**
- * @param {string} name
- * @param {import('./types.js').BlobLike} file
- */
- constructor(name, file) {
- this.name = name
- this.#file = file
- }
-
- /** @param {import('@ipld/unixfs').View} writer */
- async finalize(writer) {
- const unixfsFileWriter = UnixFS.createFileWriter(writer)
- await this.#file.stream().pipeTo(
- new WritableStream({
- async write(chunk) {
- await unixfsFileWriter.write(chunk)
- },
- })
- )
- return await unixfsFileWriter.close()
- }
-}
-
-class UnixFSDirectoryBuilder {
- #options
-
- /** @type {Map} */
- entries = new Map()
-
- /**
- * @param {string} name
- * @param {import('./types.js').UnixFSDirectoryEncoderOptions} [options]
- */
- constructor(name, options) {
- this.name = name
- this.#options = options
- }
-
- /** @param {import('@ipld/unixfs').View} writer */
- async finalize(writer) {
- const dirWriter =
- this.entries.size <= SHARD_THRESHOLD
- ? UnixFS.createDirectoryWriter(writer)
- : UnixFS.createShardedDirectoryWriter(writer)
- for (const [name, entry] of this.entries) {
- const link = await entry.finalize(writer)
- if (this.#options?.onDirectoryEntryLink) {
- // @ts-expect-error
- this.#options.onDirectoryEntryLink({ name: entry.name, ...link })
- }
- dirWriter.set(name, link)
- }
- return await dirWriter.close()
- }
-}
-
-/**
- * @param {Iterable} files
- * @param {import('./types.js').UnixFSDirectoryEncoderOptions} [options]
- * @returns {Promise}
- */
-export async function encodeDirectory(files, options) {
- const readable = createDirectoryEncoderStream(files, options)
- const blocks = await collect(readable)
- // @ts-expect-error There is always a root block
- return { cid: blocks.at(-1).cid, blocks }
-}
-
-/**
- * @param {Iterable} files
- * @param {import('./types.js').UnixFSDirectoryEncoderOptions} [options]
- * @returns {ReadableStream}
- */
-export function createDirectoryEncoderStream(files, options) {
- const rootDir = new UnixFSDirectoryBuilder('', options)
-
- for (const file of files) {
- const path = file.name.split('/')
- if (path[0] === '' || path[0] === '.') {
- path.shift()
- }
- let dir = rootDir
- for (const [i, name] of path.entries()) {
- if (i === path.length - 1) {
- dir.entries.set(name, new UnixFSFileBuilder(path.join('/'), file))
- break
- }
- let dirBuilder = dir.entries.get(name)
- if (dirBuilder == null) {
- const dirName = dir === rootDir ? name : `${dir.name}/${name}`
- dirBuilder = new UnixFSDirectoryBuilder(dirName, options)
- dir.entries.set(name, dirBuilder)
- }
- if (!(dirBuilder instanceof UnixFSDirectoryBuilder)) {
- throw new Error(`"${file.name}" cannot be a file and a directory`)
- }
- dir = dirBuilder
- }
- }
-
- /** @type {TransformStream} */
- const { readable, writable } = new TransformStream({}, queuingStrategy)
- const unixfsWriter = UnixFS.createWriter({ writable, settings })
- void (async () => {
- const link = await rootDir.finalize(unixfsWriter)
- if (options?.onDirectoryEntryLink) {
- options.onDirectoryEntryLink({ name: '', ...link })
- }
- await unixfsWriter.close()
- })()
-
- return readable
-}
-
-/**
- * @template T
- * @param {ReadableStream} collectable
- * @returns {Promise}
- */
-async function collect(collectable) {
- /** @type {T[]} */
- const chunks = []
- await collectable.pipeTo(
- new WritableStream({
- write(chunk) {
- chunks.push(chunk)
- },
- })
- )
- return chunks
-}
+export * from '@web3-storage/w3up-client/capability/upload/unixfs'
diff --git a/packages/upload-client/src/upload.js b/packages/upload-client/src/upload.js
index 28dd89e6c..7f6e94e11 100644
--- a/packages/upload-client/src/upload.js
+++ b/packages/upload-client/src/upload.js
@@ -1,161 +1 @@
-import * as UploadCapabilities from '@web3-storage/capabilities/upload'
-import { SpaceDID } from '@web3-storage/capabilities/utils'
-import retry from 'p-retry'
-import { servicePrincipal, connection } from './service.js'
-import { REQUEST_RETRIES } from './constants.js'
-
-/**
- * Register an "upload" with the service. The issuer needs the `upload/add`
- * delegated capability.
- *
- * Required delegated capability proofs: `upload/add`
- *
- * @param {import('./types.js').InvocationConfig} conf Configuration
- * for the UCAN invocation. An object with `issuer`, `with` and `proofs`.
- *
- * The `issuer` is the signing authority that is issuing the UCAN
- * invocation(s). It is typically the user _agent_.
- *
- * The `with` is the resource the invocation applies to. It is typically the
- * DID of a space.
- *
- * The `proofs` are a set of capability delegations that prove the issuer
- * has the capability to perform the action.
- *
- * The issuer needs the `upload/add` delegated capability.
- * @param {import('multiformats/link').UnknownLink} root Root data CID for the DAG that was stored.
- * @param {import('./types.js').CARLink[]} shards CIDs of CAR files that contain the DAG.
- * @param {import('./types.js').RequestOptions} [options]
- * @returns {Promise}
- */
-export async function add(
- { issuer, with: resource, proofs, audience },
- root,
- shards,
- options = {}
-) {
- /* c8 ignore next */
- const conn = options.connection ?? connection
- const result = await retry(
- async () => {
- return await UploadCapabilities.add
- .invoke({
- issuer,
- /* c8 ignore next */
- audience: audience ?? servicePrincipal,
- with: SpaceDID.from(resource),
- nb: { root, shards },
- proofs,
- })
- .execute(conn)
- },
- {
- onFailedAttempt: console.warn,
- retries: options.retries ?? REQUEST_RETRIES,
- }
- )
-
- if (!result.out.ok) {
- throw new Error(`failed ${UploadCapabilities.add.can} invocation`, {
- cause: result.out.error,
- })
- }
-
- return result.out.ok
-}
-
-/**
- * List uploads created by the issuer.
- *
- * @param {import('./types.js').InvocationConfig} conf Configuration
- * for the UCAN invocation. An object with `issuer`, `with` and `proofs`.
- *
- * The `issuer` is the signing authority that is issuing the UCAN
- * invocation(s). It is typically the user _agent_.
- *
- * The `with` is the resource the invocation applies to. It is typically the
- * DID of a space.
- *
- * The `proofs` are a set of capability delegations that prove the issuer
- * has the capability to perform the action.
- *
- * The issuer needs the `upload/list` delegated capability.
- * @param {import('./types.js').ListRequestOptions} [options]
- * @returns {Promise}
- */
-export async function list(
- { issuer, with: resource, proofs, audience },
- options = {}
-) {
- /* c8 ignore next */
- const conn = options.connection ?? connection
-
- const result = await UploadCapabilities.list
- .invoke({
- issuer,
- /* c8 ignore next */
- audience: audience ?? servicePrincipal,
- with: SpaceDID.from(resource),
- proofs,
- nb: {
- cursor: options.cursor,
- size: options.size,
- pre: options.pre,
- },
- })
- .execute(conn)
-
- if (!result.out.ok) {
- throw new Error(`failed ${UploadCapabilities.list.can} invocation`, {
- cause: result.out.error,
- })
- }
-
- return result.out.ok
-}
-
-/**
- * Remove an upload by root data CID.
- *
- * @param {import('./types.js').InvocationConfig} conf Configuration
- * for the UCAN invocation. An object with `issuer`, `with` and `proofs`.
- *
- * The `issuer` is the signing authority that is issuing the UCAN
- * invocation(s). It is typically the user _agent_.
- *
- * The `with` is the resource the invocation applies to. It is typically the
- * DID of a space.
- *
- * The `proofs` are a set of capability delegations that prove the issuer
- * has the capability to perform the action.
- *
- * The issuer needs the `upload/remove` delegated capability.
- * @param {import('multiformats').UnknownLink} root Root data CID to remove.
- * @param {import('./types.js').RequestOptions} [options]
- */
-export async function remove(
- { issuer, with: resource, proofs, audience },
- root,
- options = {}
-) {
- /* c8 ignore next */
- const conn = options.connection ?? connection
- const result = await UploadCapabilities.remove
- .invoke({
- issuer,
- /* c8 ignore next */
- audience: audience ?? servicePrincipal,
- with: SpaceDID.from(resource),
- nb: { root },
- proofs,
- })
- .execute(conn)
-
- if (!result.out.ok) {
- throw new Error(`failed ${UploadCapabilities.remove.can} invocation`, {
- cause: result.out.error,
- })
- }
-
- return result.out.ok
-}
+export * from '@web3-storage/w3up-client/capability/upload'
diff --git a/packages/upload-client/test/fixtures.js b/packages/upload-client/test/fixtures.js
deleted file mode 100644
index 50a464a0a..000000000
--- a/packages/upload-client/test/fixtures.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import * as ed25519 from '@ucanto/principal/ed25519'
-
-/** did:key:z6MkrZ1r5XBFZjBU34qyD8fueMbMRkKw17BZaq2ivKFjnz2z */
-export const serviceSigner = ed25519.parse(
- 'MgCYKXoHVy7Vk4/QjcEGi+MCqjntUiasxXJ8uJKY0qh11e+0Bs8WsdqGK7xothgrDzzWD0ME7ynPjz2okXDh8537lId8='
-)
diff --git a/packages/upload-client/test/helpers/bucket-server.js b/packages/upload-client/test/helpers/bucket-server.js
deleted file mode 100644
index 9db6842fd..000000000
--- a/packages/upload-client/test/helpers/bucket-server.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import { createServer } from 'http'
-
-const port = process.env.PORT ?? 9000
-const status = process.env.STATUS ? parseInt(process.env.STATUS) : 200
-
-const server = createServer((req, res) => {
- res.setHeader('Access-Control-Allow-Origin', '*')
- res.setHeader('Access-Control-Allow-Methods', '*')
- res.setHeader('Access-Control-Allow-Headers', '*')
- if (req.method === 'OPTIONS') return res.end()
- res.statusCode = status
- res.end()
-})
-
-server.listen(port, () => console.log(`Listening on :${port}`))
diff --git a/packages/upload-client/test/helpers/car.js b/packages/upload-client/test/helpers/car.js
deleted file mode 100644
index 56d2b3a9b..000000000
--- a/packages/upload-client/test/helpers/car.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { CarWriter } from '@ipld/car'
-import * as CAR from '@ucanto/transport/car'
-import { toBlock } from './block.js'
-
-/**
- * @param {Uint8Array} bytes
- */
-export async function toCAR(bytes) {
- const block = await toBlock(bytes)
- const { writer, out } = CarWriter.create(block.cid)
- writer.put(block)
- writer.close()
-
- const chunks = []
- for await (const chunk of out) {
- chunks.push(chunk)
- }
- const blob = new Blob(chunks)
- const cid = await CAR.codec.link(new Uint8Array(await blob.arrayBuffer()))
-
- return Object.assign(blob, { cid, roots: [block.cid] })
-}
diff --git a/packages/upload-client/test/helpers/mocks.js b/packages/upload-client/test/helpers/mocks.js
deleted file mode 100644
index 555287f04..000000000
--- a/packages/upload-client/test/helpers/mocks.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import * as Server from '@ucanto/server'
-
-const notImplemented = () => {
- throw new Server.Failure('not implemented')
-}
-
-/**
- * @param {Partial<{
- * store: Partial
- * upload: Partial
- * }>} impl
- */
-export function mockService(impl) {
- return {
- store: {
- add: withCallCount(impl.store?.add ?? notImplemented),
- get: withCallCount(impl.store?.get ?? notImplemented),
- list: withCallCount(impl.store?.list ?? notImplemented),
- remove: withCallCount(impl.store?.remove ?? notImplemented),
- },
- upload: {
- add: withCallCount(impl.upload?.add ?? notImplemented),
- get: withCallCount(impl.upload?.get ?? notImplemented),
- list: withCallCount(impl.upload?.list ?? notImplemented),
- remove: withCallCount(impl.upload?.remove ?? notImplemented),
- },
- }
-}
-
-/**
- * @template {Function} T
- * @param {T} fn
- */
-function withCallCount(fn) {
- /** @param {T extends (...args: infer A) => any ? A : never} args */
- const countedFn = (...args) => {
- countedFn.called = true
- countedFn.callCount++
- return fn(...args)
- }
- countedFn.called = false
- countedFn.callCount = 0
- return countedFn
-}
diff --git a/packages/upload-client/test/helpers/random.js b/packages/upload-client/test/helpers/random.js
deleted file mode 100644
index 5cce080e8..000000000
--- a/packages/upload-client/test/helpers/random.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import { toBlock } from './block.js'
-import { toCAR } from './car.js'
-
-/** @param {number} size */
-export async function randomBytes(size) {
- const bytes = new Uint8Array(size)
- while (size) {
- const chunk = new Uint8Array(Math.min(size, 65_536))
- if (!globalThis.crypto) {
- try {
- const { webcrypto } = await import('node:crypto')
- webcrypto.getRandomValues(chunk)
- } catch (err) {
- throw new Error(
- 'unknown environment - no global crypto and not Node.js',
- { cause: err }
- )
- }
- } else {
- crypto.getRandomValues(chunk)
- }
- size -= bytes.length
- bytes.set(chunk, size)
- }
- return bytes
-}
-
-/** @param {number} size */
-export async function randomCAR(size) {
- const bytes = await randomBytes(size)
- return toCAR(bytes)
-}
-
-/** @param {number} size */
-export async function randomBlock(size) {
- const bytes = await randomBytes(size)
- return await toBlock(bytes)
-}
diff --git a/packages/upload-client/test/helpers/shims.js b/packages/upload-client/test/helpers/shims.js
deleted file mode 100644
index 26b0c5c1f..000000000
--- a/packages/upload-client/test/helpers/shims.js
+++ /dev/null
@@ -1,10 +0,0 @@
-export class File extends Blob {
- /**
- * @param {BlobPart[]} blobParts
- * @param {string} name
- */
- constructor(blobParts, name) {
- super(blobParts)
- this.name = name
- }
-}
diff --git a/packages/upload-client/test/helpers/utils.js b/packages/upload-client/test/helpers/utils.js
deleted file mode 100644
index c173e7e70..000000000
--- a/packages/upload-client/test/helpers/utils.js
+++ /dev/null
@@ -1 +0,0 @@
-export const validateAuthorization = () => ({ ok: {} })
diff --git a/packages/upload-client/tsconfig.json b/packages/upload-client/tsconfig.json
index 3cbd72f5a..0e14aa5aa 100644
--- a/packages/upload-client/tsconfig.json
+++ b/packages/upload-client/tsconfig.json
@@ -6,5 +6,5 @@
},
"include": ["src", "scripts", "test", "package.json"],
"exclude": ["**/node_modules/**"],
- "references": [{ "path": "../access-client" }, { "path": "../capabilities" }]
+ "references": [{ "path": "../w3up-client" }]
}
diff --git a/packages/w3up-client/package.json b/packages/w3up-client/package.json
index 0bcb8795f..a3744e755 100644
--- a/packages/w3up-client/package.json
+++ b/packages/w3up-client/package.json
@@ -32,6 +32,14 @@
"types": "./dist/src/client.d.ts",
"import": "./src/client.js"
},
+ "./agent": {
+ "types": "./dist/src/agent.d.ts",
+ "import": "./src/agent.js"
+ },
+ "./account": {
+ "types": "./dist/src/account.d.ts",
+ "import": "./src/account.js"
+ },
"./capability/access": {
"types": "./dist/src/capability/access.d.ts",
"import": "./src/capability/access.js"
@@ -48,7 +56,54 @@
"types": "./dist/src/capability/upload.d.ts",
"import": "./src/capability/upload.js"
},
- "./types": "./src/types.js"
+ "./capability/upload/sharding": {
+ "types": "./dist/src/capability/upload/sharding.d.ts",
+ "import": "./src/capability/upload/sharding.js"
+ },
+ "./capability/upload/car": {
+ "types": "./dist/src/capability/upload/car.d.ts",
+ "import": "./src/capability/upload/car.js"
+ },
+ "./capability/upload/unixfs": {
+ "types": "./dist/src/capability/upload/unixfs.d.ts",
+ "import": "./src/capability/upload/unixfs.js"
+ },
+ "./capability/provider": {
+ "types": "./dist/src/capability/provider.d.ts",
+ "import": "./src/capability/provider.js"
+ },
+ "./types": {
+ "types": "./dist/src/types.d.ts",
+ "import": "./src/types.js"
+ },
+ "./store/conf": {
+ "types": "./dist/src/store/conf.d.ts",
+ "import": "./src/store/conf.js"
+ },
+ "./store/indexed-db": {
+ "types": "./dist/src/store/indexed-db.d.ts",
+ "import": "./src/store/indexed-db.js"
+ },
+ "./store/memory": {
+ "types": "./dist/src/store/memory.d.ts",
+ "import": "./src/store/memory.js"
+ },
+ "./driver/conf": {
+ "types": "./dist/src/driver/conf.d.ts",
+ "import": "./src/driver/conf.js"
+ },
+ "./driver/indexed-db": {
+ "types": "./dist/src/driver/indexed-db.d.ts",
+ "import": "./src/driver/indexed-db.js"
+ },
+ "./driver/memory": {
+ "types": "./dist/src/driver/memory.d.ts",
+ "import": "./src/driver/memory.js"
+ },
+ "./agent/encoding": {
+ "types": "./dist/src/agent/encoding.d.ts",
+ "import": "./src/agent/encoding.js"
+ }
},
"publishConfig": {
"access": "public"
@@ -69,21 +124,35 @@
"test:browser": "playwright-test --runner mocha 'test/**/!(*.node).test.js'",
"mock": "run-p mock:*",
"mock:bucket-200": "PORT=9200 STATUS=200 node test/helpers/bucket-server.js",
+ "mock:bucket-401": "PORT=9400 STATUS=400 node test/helpers/bucket-server.js",
+ "mock:bucket-500": "PORT=9500 STATUS=500 node test/helpers/bucket-server.js",
"rc": "npm version prerelease --preid rc",
"docs": "npm run build && typedoc --out docs-generated",
"docs:markdown": "npm run build && docusaurus generate-typedoc"
},
"dependencies": {
"@ipld/dag-ucan": "^3.4.0",
+ "@ipld/car": "^5.2.2",
+ "@ipld/dag-cbor": "^9.0.6",
+ "@ipld/unixfs": "^2.1.1",
"@ucanto/client": "^9.0.0",
"@ucanto/core": "^9.0.0",
"@ucanto/interface": "^9.0.0",
"@ucanto/principal": "^9.0.0",
"@ucanto/transport": "^9.0.0",
+ "@scure/bip39": "^1.2.1",
+ "p-defer": "^4.0.0",
+ "conf": "11.0.2",
+ "uint8arrays": "^4.0.6",
"@web3-storage/did-mailto": "workspace:^",
- "@web3-storage/access": "workspace:^",
"@web3-storage/capabilities": "workspace:^",
- "@web3-storage/upload-client": "workspace:^"
+ "fr32-sha2-256-trunc254-padded-binary-tree-multihash": "^3.1.0",
+ "ipfs-utils": "^9.0.14",
+ "multiformats": "^12.1.2",
+ "p-retry": "^5.1.2",
+ "parallel-transform-web": "^1.0.0",
+ "bigint-mod-arith": "^3.1.2",
+ "one-webcrypto": "git://github.com/web3-storage/one-webcrypto"
},
"devDependencies": {
"@web3-storage/upload-api": "workspace:^",
@@ -105,7 +174,11 @@
"typedoc": "^0.23.24",
"typedoc-plugin-markdown": "^3.14.0",
"typedoc-plugin-missing-exports": "^1.0.0",
- "typescript": "^5.2.2"
+ "typescript": "^5.2.2",
+ "@types/sinon": "^10.0.19",
+ "sinon": "^15.0.3",
+ "ipfs-unixfs-exporter": "^10.0.0",
+ "blockstore-core": "^3.0.0"
},
"eslintConfig": {
"extends": [
@@ -123,7 +196,8 @@
"ignorePatterns": [
"dist",
"coverage",
- "src/types.js"
+ "src/types.js",
+ "src/agent/types.js"
]
},
"directories": {
diff --git a/packages/w3up-client/src/account.js b/packages/w3up-client/src/account.js
index 97b581cfb..5156862cd 100644
--- a/packages/w3up-client/src/account.js
+++ b/packages/w3up-client/src/account.js
@@ -1,7 +1,7 @@
import * as API from './types.js'
import * as Access from './capability/access.js'
-import { Delegation, importAuthorization } from '@web3-storage/access/agent'
-import { add as provision, AccountDID } from '@web3-storage/access/provider'
+import { Delegation, importAuthorization } from './agent.js'
+import { add as provision, AccountDID } from './capability/provider.js'
import { fromEmail, toEmail } from '@web3-storage/did-mailto'
export { fromEmail }
@@ -75,13 +75,10 @@ export const list = ({ agent }, { account } = {}) => {
*/
export const login = async ({ agent }, email) => {
const account = fromEmail(email)
- const result = await Access.request(
- { agent },
- {
- account,
- access: Access.accountAccess,
- }
- )
+ const result = await Access.request(agent, {
+ account,
+ access: Access.accountAccess,
+ })
const { ok: access, error } = result
/* c8 ignore next 2 - don't know how to test this */
diff --git a/packages/w3up-client/src/agent.js b/packages/w3up-client/src/agent.js
new file mode 100644
index 000000000..7b93d4d3e
--- /dev/null
+++ b/packages/w3up-client/src/agent.js
@@ -0,0 +1,676 @@
+import * as Client from '@ucanto/client'
+import * as CAR from '@ucanto/transport/car'
+import * as HTTP from '@ucanto/transport/http'
+import * as ucanto from '@ucanto/core'
+import * as Capabilities from '@web3-storage/capabilities/space'
+import { attest } from '@web3-storage/capabilities/ucan'
+import * as Space from './capability/space.js'
+
+import { invoke, delegate, DID, Delegation, Schema } from '@ucanto/core'
+import {
+ isExpired,
+ isTooEarly,
+ canDelegateCapability,
+} from './agent/delegation.js'
+import { AgentData, getSessionProofs } from './agent/data.js'
+import { UCAN } from '@web3-storage/capabilities'
+
+import * as API from './agent/types.js'
+
+export * from './types.js'
+export * from './agent/delegation.js'
+export { AgentData, Space, Delegation, Schema, getSessionProofs }
+export * from './agent/use-cases.js'
+
+const HOST = 'https://up.web3.storage'
+const PRINCIPAL = DID.parse('did:web:web3.storage')
+
+/**
+ * Keeps track of AgentData for all Agents constructed.
+ * Used by addSpacesFromDelegations - so it can only accept Agent as param, but
+ * still mutate corresponding AgentData
+ *
+ * @deprecated - remove this when deprecated addSpacesFromDelegations is removed
+ */
+/** @type {WeakMap>, AgentData>} */
+const agentToData = new WeakMap()
+
+/**
+ * @typedef {API.Service} Service
+ * @typedef {API.Receipt} Receipt
+ */
+
+/**
+ * Creates a Ucanto connection for the w3access API
+ *
+ * Usage:
+ *
+ * ```js
+ * import { connection } from '@web3-storage/access/agent'
+ * ```
+ *
+ * @template {API.DID} T - DID method
+ * @template {Record} [S=Service]
+ * @param {object} [options]
+ * @param {API.Principal} [options.principal] - w3access API Principal
+ * @param {URL} [options.url] - w3access API URL
+ * @param {API.Transport.Channel} [options.channel] - Ucanto channel to use
+ * @param {typeof fetch} [options.fetch] - Fetch implementation to use
+ * @returns {API.ConnectionView}
+ */
+export function connection(options = {}) {
+ return Client.connect({
+ id: options.principal ?? PRINCIPAL,
+ codec: CAR.outbound,
+ channel:
+ options.channel ??
+ HTTP.open({
+ /* c8 ignore next */
+ url: options.url ?? new URL(HOST),
+ method: 'POST',
+ fetch: options.fetch ?? globalThis.fetch.bind(globalThis),
+ }),
+ })
+}
+
+/**
+ * Agent
+ *
+ * Usage:
+ *
+ * ```js
+ * import { Agent } from '@web3-storage/access/agent'
+ * ```
+ *
+ * @template {Record} [S=Service]
+ */
+export class Agent {
+ /** @type {import('./agent/data.js').AgentData} */
+ #data
+
+ /**
+ * @param {import('./agent/data.js').AgentData} data - Agent data
+ * @param {import('./agent/types.js').AgentOptions} [options]
+ */
+ constructor(data, options = {}) {
+ /** @type { Client.Channel & { url?: URL } | undefined } */
+ const channel = options.connection?.channel
+ this.url = options.url ?? channel?.url ?? new URL(HOST)
+ this.connection =
+ options.connection ??
+ connection({
+ principal: options.servicePrincipal,
+ url: this.url,
+ })
+ this.#data = data
+ agentToData.set(this, this.#data)
+ }
+
+ /**
+ * Create a new Agent instance, optionally with the passed initialization data.
+ *
+ * @template {Record} [R=Service]
+ * @param {Partial} [init]
+ * @param {API.AgentOptions & API.AgentDataOptions} [options]
+ */
+ static async create(init, options = {}) {
+ const data = await AgentData.create(init, options)
+ return new Agent(data, options)
+ }
+
+ /**
+ * Instantiate an Agent from pre-exported agent data.
+ *
+ * @template {Record} [R=Service]
+ * @param {import('./types.js').AgentDataExport} raw
+ * @param {import('./agent/types.js').AgentOptions & import('./agent/types.js').AgentDataOptions} [options]
+ */
+ /* c8 ignore next 4 */
+ static from(raw, options = {}) {
+ const data = AgentData.fromExport(raw, options)
+ return new Agent(data, options)
+ }
+
+ get issuer() {
+ return this.#data.principal
+ }
+
+ get meta() {
+ return this.#data.meta
+ }
+
+ get spaces() {
+ return this.#data.spaces
+ }
+
+ did() {
+ return this.#data.principal.did()
+ }
+
+ /**
+ * Add a proof to the agent store.
+ *
+ * @param {API.Delegation} delegation
+ */
+ async addProof(delegation) {
+ return await this.addProofs([delegation])
+ }
+
+ /**
+ * Adds set of proofs to the agent store.
+ *
+ * @param {Iterable} delegations
+ */
+ async addProofs(delegations) {
+ for (const proof of delegations) {
+ await this.#data.addDelegation(proof, { audience: this.meta })
+ }
+ await this.removeExpiredDelegations()
+
+ return {}
+ }
+
+ /**
+ * Query the delegations store for all the delegations matching the capabilities provided.
+ *
+ * @param {API.CapabilityQuery[]} [caps]
+ */
+ #delegations(caps) {
+ const _caps = new Set(caps)
+ /** @type {Array<{ delegation: API.Delegation, meta: API.DelegationMeta }>} */
+ const values = []
+ for (const [, value] of this.#data.delegations) {
+ // check expiration
+ if (
+ !isExpired(value.delegation) && // check if delegation can be used
+ !isTooEarly(value.delegation)
+ ) {
+ // check if we need to filter for caps
+ if (Array.isArray(caps) && caps.length > 0) {
+ for (const cap of _caps) {
+ if (canDelegateCapability(value.delegation, cap)) {
+ values.push(value)
+ }
+ }
+ } else {
+ values.push(value)
+ }
+ }
+ }
+ return values
+ }
+
+ /**
+ * Clean up any expired delegations.
+ */
+ async removeExpiredDelegations() {
+ for (const [, value] of this.#data.delegations) {
+ /* c8 ignore next 3 */
+ if (isExpired(value.delegation)) {
+ await this.#data.removeDelegation(value.delegation.cid)
+ }
+ }
+ }
+
+ /**
+ * Revoke a delegation by CID.
+ *
+ * If the delegation was issued by this agent (and therefore is stored in the
+ * delegation store) you can just pass the CID. If not, or if the current agent's
+ * delegation store no longer contains the delegation, you MUST pass a chain of
+ * proofs that proves your authority to revoke this delegation as `options.proofs`.
+ *
+ * @param {API.UCANLink} delegationCID
+ * @param {object} [options]
+ * @param {API.Delegation[]} [options.proofs]
+ */
+ async revoke(delegationCID, options = {}) {
+ const additionalProofs = options.proofs ?? []
+ // look for the identified delegation in the delegation store and the passed proofs
+ const delegation = [...this.delegations(), ...additionalProofs].find(
+ (delegation) => delegation.cid.equals(delegationCID)
+ )
+ if (!delegation) {
+ return {
+ error: new Error(
+ `could not find delegation ${delegationCID.toString()} - please include the delegation in options.proofs`
+ ),
+ }
+ }
+ const receipt = await this.invokeAndExecute(UCAN.revoke, {
+ // per https://github.com/web3-storage/w3up/blob/main/packages/capabilities/src/ucan.js#L38C6-L38C6 the resource here should be
+ // the current issuer - using the space DID here works for simple cases but falls apart when a delegee tries to revoke a delegation
+ // they have re-delegated, since they don't have "ucan/revoke" capabilities on the space
+ with: this.issuer.did(),
+ nb: {
+ ucan: delegation.cid,
+ },
+ proofs: [delegation, ...additionalProofs],
+ })
+ return receipt.out
+ }
+
+ /**
+ * Get all the proofs matching the capabilities.
+ *
+ * Proofs are delegations with an audience matching agent DID, or with an
+ * audience matching the session DID.
+ *
+ * Proof of session will also be included in the returned proofs if any
+ * proofs matching the passed capabilities require it.
+ *
+ * @param {API.CapabilityQuery[]} [caps] - Capabilities to filter by. Empty or undefined caps with return all the proofs.
+ * @param {object} [options]
+ * @param {API.DID} [options.sessionProofIssuer] - only include session proofs for this issuer
+ */
+ proofs(caps, options) {
+ const authorizations = []
+ for (const { delegation } of this.#delegations(caps)) {
+ if (delegation.audience.did() === this.issuer.did()) {
+ authorizations.push(delegation)
+ }
+ }
+
+ // now let's add any session proofs that refer to those authorizations
+ const sessions = getSessionProofs(this.#data)
+ for (const proof of authorizations) {
+ const proofsByIssuer = sessions[proof.asCID.toString()] ?? {}
+ const sessionProofs = options?.sessionProofIssuer
+ ? proofsByIssuer[options.sessionProofIssuer] ?? []
+ : Object.values(proofsByIssuer).flat()
+ if (sessionProofs.length) {
+ authorizations.push(...sessionProofs)
+ }
+ }
+ return authorizations
+ }
+
+ /**
+ * Get delegations created by the agent for others.
+ *
+ * @param {API.CapabilityQuery[]} [caps] - Capabilities to filter by. Empty or undefined caps with return all the delegations.
+ */
+ delegations(caps) {
+ const arr = []
+
+ for (const { delegation } of this.delegationsWithMeta(caps)) {
+ arr.push(delegation)
+ }
+
+ return arr
+ }
+
+ /**
+ * Get delegations created by the agent for others and their metadata.
+ *
+ * @param {API.CapabilityQuery[]} [caps] - Capabilities to filter by. Empty or undefined caps with return all the delegations.
+ */
+ delegationsWithMeta(caps) {
+ const arr = []
+
+ for (const value of this.#delegations(caps)) {
+ const { delegation } = value
+ const isSession = delegation.capabilities.some(
+ (c) => c.can === attest.can
+ )
+ if (!isSession && delegation.audience.did() !== this.issuer.did()) {
+ arr.push(value)
+ }
+ }
+
+ return arr
+ }
+
+ /**
+ * Creates a space signer and a delegation to the agent
+ *
+ * @param {string} name
+ */
+ async createSpace(name) {
+ return await Space.generate({ name })
+ }
+
+ /**
+ * Import a space from a delegation.
+ *
+ * @param {API.Delegation} delegation
+ * @param {object} options
+ * @param {string} [options.name]
+ */
+ async importSpaceFromDelegation(delegation, { name = '' } = {}) {
+ const space =
+ name === ''
+ ? Space.fromDelegation(delegation)
+ : /* c8 ignore next */
+ Space.fromDelegation(delegation).withName(name)
+
+ /* c8 ignore next 5 */
+ if (space.name === '') {
+ throw new Error(
+ 'Space has no name, please pass a `name` option to specify it'
+ )
+ }
+
+ this.#data.spaces.set(space.did(), { ...space.meta, name: space.name })
+
+ await this.addProof(space.delegation)
+
+ // if we do not have a current space, make this one current
+ if (!this.currentSpace()) {
+ await this.setCurrentSpace(space.did())
+ }
+
+ return space
+ }
+
+ /**
+ * Sets the current selected space
+ *
+ * Other methods will default to use the current space if no resource is defined
+ *
+ * @param {API.SpaceDID} space
+ */
+ async setCurrentSpace(space) {
+ if (!this.#data.spaces.has(space)) {
+ throw new Error(`Agent has no proofs for ${space}.`)
+ }
+
+ await this.#data.setCurrentSpace(space)
+
+ return space
+ }
+
+ /**
+ * Get current space DID
+ */
+ currentSpace() {
+ return this.#data.currentSpace
+ }
+
+ /**
+ * Get current space DID, proofs and abilities
+ */
+ currentSpaceWithMeta() {
+ /* c8 ignore next 3 */
+ if (!this.#data.currentSpace) {
+ return
+ }
+
+ const proofs = this.proofs([
+ {
+ can: 'space/info',
+ with: this.#data.currentSpace,
+ },
+ ])
+
+ const caps = new Set()
+ for (const p of proofs) {
+ for (const cap of p.capabilities) {
+ caps.add(cap.can)
+ }
+ }
+
+ return {
+ did: this.#data.currentSpace,
+ proofs,
+ capabilities: [...caps],
+ meta: this.#data.spaces.get(this.#data.currentSpace),
+ }
+ }
+
+ /**
+ *
+ * @param {API.DelegationOptions} options
+ */
+ async delegate(options) {
+ const space = this.currentSpaceWithMeta()
+ /* c8 ignore next 3 */
+ if (!space) {
+ throw new Error('no space selected.')
+ }
+
+ const caps = /** @type {API.Capabilities} */ (
+ options.abilities.map((a) => {
+ return {
+ with: space.did,
+ can: a,
+ }
+ })
+ )
+
+ // Verify agent can provide proofs for each requested capability
+ for (const cap of caps) {
+ if (!this.proofs([cap]).length) {
+ throw new Error(
+ `cannot delegate capability ${cap.can} with ${cap.with}`
+ )
+ }
+ }
+
+ const delegation = await delegate({
+ issuer: this.issuer,
+ capabilities: caps,
+ proofs: this.proofs(caps),
+ /* c8 ignore next */
+ facts: [{ space: space.meta ?? {} }],
+ ...options,
+ })
+
+ await this.#data.addDelegation(delegation, {
+ audience: options.audienceMeta,
+ })
+ await this.removeExpiredDelegations()
+
+ return delegation
+ }
+
+ /**
+ * Invoke and execute the given capability on the Access service connection
+ *
+ * ```js
+ *
+ * await agent.invokeAndExecute(Space.recover, {
+ * nb: {
+ * identity: 'mailto: email@gmail.com',
+ * },
+ * })
+ *
+ * // sugar for
+ * const recoverInvocation = await agent.invoke(Space.recover, {
+ * nb: {
+ * identity: 'mailto: email@gmail.com',
+ * },
+ * })
+ *
+ * await recoverInvocation.execute(agent.connection)
+ * ```
+ *
+ * @template {API.Ability} A
+ * @template {API.URI} R
+ * @template {API.Caveats} C
+ * @param {API.TheCapabilityParser>} cap
+ * @param {API.InvokeOptions>>} options
+ * @returns {Promise, S>>}
+ */
+ async invokeAndExecute(cap, options) {
+ const inv = await this.invoke(cap, options)
+ const out = inv.execute(/** @type {*} */ (this.connection))
+ return /** @type {*} */ (out)
+ }
+
+ /**
+ * Execute invocations on the agent's connection
+ *
+ * @example
+ * ```js
+ * const i1 = await agent.invoke(Space.info, {})
+ * const i2 = await agent.invoke(Space.recover, {
+ * nb: {
+ * identity: 'mailto:hello@web3.storage',
+ * },
+ * })
+ *
+ * const results = await agent.execute2(i1, i2)
+ *
+ * ```
+ * @template {API.Capability} C
+ * @template {API.Tuple>} I
+ * @param {I} invocations
+ */
+ execute(...invocations) {
+ return this.connection.execute(...invocations)
+ }
+
+ /**
+ * Creates an invocation for the given capability with Agent's proofs, service, issuer and space.
+ *
+ * @example
+ * ```js
+ * const recoverInvocation = await agent.invoke(Space.recover, {
+ * nb: {
+ * identity: 'mailto: email@gmail.com',
+ * },
+ * })
+ *
+ * await recoverInvocation.execute(agent.connection)
+ * // or
+ * await agent.execute(recoverInvocation)
+ * ```
+ *
+ * @template {API.Ability} A
+ * @template {API.URI} R
+ * @template {API.TheCapabilityParser>} CAP
+ * @template {API.Caveats} [C={}]
+ * @param {CAP} cap
+ * @param {import('./agent/types.js').InvokeOptions} options
+ */
+ async invoke(cap, options) {
+ const audience = options.audience || this.connection.id
+
+ const space = options.with || this.currentSpace()
+ /* c8 ignore next 5 */
+ if (!space) {
+ throw new Error(
+ 'No space or resource selected, you need pass a resource.'
+ )
+ }
+
+ const proofs = [
+ ...(options.proofs || []),
+ ...this.proofs(
+ [
+ {
+ with: space,
+ can: cap.can,
+ },
+ ],
+ { sessionProofIssuer: audience.did() }
+ ),
+ ]
+
+ if (proofs.length === 0 && options.with !== this.did()) {
+ throw new Error(
+ `no proofs available for resource ${space} and ability ${cap.can}`
+ )
+ }
+ const inv = invoke({
+ ...options,
+ audience,
+ // @ts-ignore
+ capability: cap.create({
+ with: space,
+ nb: options.nb,
+ }),
+ issuer: this.issuer,
+ proofs: [...proofs],
+ })
+
+ return /** @type {API.IssuedInvocationView>} */ (
+ inv
+ )
+ }
+
+ /**
+ * Get Space information from Access service
+ *
+ * @param {API.URI<"did:">} [space]
+ */
+ async getSpaceInfo(space) {
+ const _space = space || this.currentSpace()
+ /* c8 ignore next 3 */
+ if (!_space) {
+ throw new Error('No space selected, you need pass a resource.')
+ }
+ const inv = await this.invokeAndExecute(Capabilities.info, {
+ with: _space,
+ })
+
+ /* c8 ignore next 3 */
+ if (inv.out.error) {
+ throw inv.out.error
+ }
+
+ return /** @type {import('./agent/types.js').SpaceInfoResult} */ (
+ inv.out.ok
+ )
+ }
+}
+
+/**
+ * Given a list of delegations, add to agent data spaces list.
+ *
+ * @deprecated - trying to remove explicit space tracking from Agent/AgentData
+ * in favor of functions that derive the space set from access.delegations
+ *
+ * @template {Record} [S=Service]
+ * @param {Agent} agent
+ * @param {API.Delegation[]} delegations
+ */
+export async function addSpacesFromDelegations(agent, delegations) {
+ const data = agentToData.get(agent)
+ /* c8 ignore next 5 */
+ if (!data) {
+ throw Object.assign(new Error(`cannot determine AgentData for Agent`), {
+ agent: agent,
+ })
+ }
+
+ for (const delegation of delegations) {
+ // We only consider delegations to this agent as those are only spaces that
+ // this agent will be able to interact with.
+ if (delegation.audience.did() === agent.did()) {
+ // TODO: we need a more robust way to determine which spaces a user has access to
+ // it may or may not involve look at delegations
+ const allows = ucanto.Delegation.allows(delegation)
+
+ for (const [did, value] of Object.entries(allows)) {
+ // If we discovered a delegation to any DID, we add it to the spaces list.
+ if (did.startsWith('did:key') && Object.keys(value).length > 0) {
+ data.addSpace(/** @type {API.DID} */ (did), {
+ name: '',
+ })
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Stores given delegations in the agent's data store and adds discovered spaces
+ * to the agent's space list.
+ *
+ * @param {Agent} agent
+ * @param {object} authorization
+ * @param {API.Delegation[]} authorization.proofs
+ * @returns {Promise>}
+ */
+export const importAuthorization = async (agent, { proofs }) => {
+ try {
+ await agent.addProofs(proofs)
+ await addSpacesFromDelegations(agent, proofs)
+ return { ok: {} }
+ /* c8 ignore next 3 */
+ } catch (error) {
+ return /** @type {{error:Error}} */ ({ error })
+ }
+}
diff --git a/packages/access-client/src/agent-data.js b/packages/w3up-client/src/agent/data.js
similarity index 97%
rename from packages/access-client/src/agent-data.js
rename to packages/w3up-client/src/agent/data.js
index a03bb680d..c0e220c26 100644
--- a/packages/access-client/src/agent-data.js
+++ b/packages/w3up-client/src/agent/data.js
@@ -4,7 +4,7 @@ import { importDAG } from '@ucanto/core/delegation'
import * as Ucanto from '@ucanto/interface'
import { CID } from 'multiformats'
import { UCAN } from '@web3-storage/capabilities'
-import { isExpired } from './delegations.js'
+import { isExpired } from './delegation.js'
/** @typedef {import('./types.js').AgentDataModel} AgentDataModel */
@@ -32,6 +32,7 @@ export class AgentData {
*
* @param {Partial} [init]
* @param {import('./types.js').AgentDataOptions} [options]
+ * @returns {Promise}
*/
static async create(init = {}, options = {}) {
const agentData = new AgentData(
@@ -117,6 +118,7 @@ export class AgentData {
*/
async addSpace(did, meta, proof) {
this.spaces.set(did, meta)
+ /* c8 ignore next 1 */
await (proof ? this.addDelegation(proof) : this.#save(this.export()))
}
@@ -144,6 +146,7 @@ export class AgentData {
/**
* @param {import('@ucanto/interface').UCANLink} cid
*/
+ /* c8 ignore next 4 */
async removeDelegation(cid) {
this.delegations.delete(cid.toString())
await this.#save(this.export())
diff --git a/packages/access-client/src/delegations.js b/packages/w3up-client/src/agent/delegation.js
similarity index 96%
rename from packages/access-client/src/delegations.js
rename to packages/w3up-client/src/agent/delegation.js
index 7cda291f6..070349626 100644
--- a/packages/access-client/src/delegations.js
+++ b/packages/w3up-client/src/agent/delegation.js
@@ -7,6 +7,7 @@ import { canDelegateAbility } from '@web3-storage/capabilities/utils'
* @param {API.Delegation} delegation
*/
export function isExpired(delegation) {
+ /* c8 ignore next 6 */
if (
delegation.expiration === undefined ||
delegation.expiration <= Math.floor(Date.now() / 1000)
@@ -24,6 +25,7 @@ export function isTooEarly(delegation) {
if (!delegation.notBefore) {
return false
}
+ /* c8 ignore next */
return delegation.notBefore > Math.floor(Date.now() / 1000)
}
@@ -35,6 +37,7 @@ export function isTooEarly(delegation) {
* @param {boolean} [opts.checkIsExpired]
* @param {boolean} [opts.checkIsTooEarly]
*/
+/* c8 ignore next 21 */
export function validate(delegation, opts) {
const {
checkAudience,
@@ -87,6 +90,7 @@ export function canDelegateCapability(delegation, capability) {
* @param {API.ResourceQuery} query
*/
export const matchResource = (resource, query) => {
+ /* c8 ignore next 2 */
if (query === 'ucan:*') {
return true
} else if (typeof query === 'string') {
diff --git a/packages/w3up-client/src/agent/encoding.js b/packages/w3up-client/src/agent/encoding.js
new file mode 100644
index 000000000..ce6dde1fa
--- /dev/null
+++ b/packages/w3up-client/src/agent/encoding.js
@@ -0,0 +1,158 @@
+/**
+ * Encoding utilities
+ *
+ * It is recommended that you import directly with:
+ * ```js
+ * import * as Encoding from '@web3-storage/access/encoding'
+ *
+ * // or
+ *
+ * import { encodeDelegations } from '@web3-storage/access/encoding'
+ * ```
+ *
+ * @module
+ */
+import { CarBufferReader } from '@ipld/car/buffer-reader'
+import * as CarBufferWriter from '@ipld/car/buffer-writer'
+import { Delegation } from '@ucanto/core/delegation'
+import * as u8 from 'uint8arrays'
+// eslint-disable-next-line no-unused-vars
+import * as Types from '@ucanto/interface'
+
+/**
+ * Encode delegations as bytes
+ *
+ * @param {Types.Delegation[]} delegations
+ */
+export function delegationsToBytes(delegations) {
+ if (!Array.isArray(delegations) || delegations.length === 0) {
+ throw new Error('Delegations required to be an non empty array.')
+ }
+
+ const roots = delegations.map(
+ (d) => /** @type {CarBufferWriter.CID} */ (d.root.cid)
+ )
+ const cids = new Set()
+ /** @type {CarBufferWriter.Block[]} */
+ const blocks = []
+ let byteLength = 0
+
+ for (const delegation of delegations) {
+ for (const block of delegation.export()) {
+ const cid = block.cid.toV1().toString()
+ if (!cids.has(cid)) {
+ byteLength += CarBufferWriter.blockLength(
+ /** @type {CarBufferWriter.Block} */ (block)
+ )
+ blocks.push(/** @type {CarBufferWriter.Block} */ (block))
+ cids.add(cid)
+ }
+ }
+ }
+ const headerLength = CarBufferWriter.estimateHeaderLength(roots.length)
+ const writer = CarBufferWriter.createWriter(
+ new ArrayBuffer(headerLength + byteLength),
+ { roots }
+ )
+ for (const block of blocks) {
+ writer.write(block)
+ }
+
+ return writer.close()
+}
+
+/**
+ * Decode bytes into Delegations
+ *
+ * @template {Types.Capabilities} [T=Types.Capabilities]
+ * @param {import('./types.js').BytesDelegation} bytes
+ */
+export function bytesToDelegations(bytes) {
+ if (!(bytes instanceof Uint8Array) || bytes.length === 0) {
+ throw new TypeError('Input should be a non-empty Uint8Array.')
+ }
+ const reader = CarBufferReader.fromBytes(bytes)
+ const roots = reader.getRoots()
+
+ /** @type {Types.Delegation[]} */
+ const delegations = []
+
+ for (const root of roots) {
+ const rootBlock = reader.get(root)
+
+ if (rootBlock) {
+ const blocks = new Map()
+ for (const block of reader.blocks()) {
+ if (block.cid.toString() !== root.toString())
+ blocks.set(block.cid.toString(), block)
+ }
+
+ // @ts-ignore
+ delegations.push(new Delegation(rootBlock, blocks))
+ /* c8 ignore next 3 */
+ } else {
+ throw new Error('Failed to find root from raw delegation.')
+ }
+ }
+
+ return delegations
+}
+
+/**
+ * @param {Types.Delegation[]} delegations
+ * @param {import('uint8arrays/to-string').SupportedEncodings} encoding
+ */
+export function delegationsToString(delegations, encoding = 'base64url') {
+ const bytes = delegationsToBytes(delegations)
+
+ return u8.toString(bytes, encoding)
+}
+
+/**
+ * Encode one {@link Types.Delegation Delegation} into a string
+ *
+ * @param {Types.Delegation} delegation
+ * @param {import('uint8arrays/to-string').SupportedEncodings} [encoding]
+ */
+export function delegationToString(delegation, encoding) {
+ return delegationsToString([delegation], encoding)
+}
+
+/**
+ * Decode string into {@link Types.Delegation Delegation}
+ *
+ * @template {Types.Capabilities} [T=Types.Capabilities]
+ * @param {import('./types.js').EncodedDelegation} raw
+ * @param {import('uint8arrays/to-string').SupportedEncodings} [encoding]
+ */
+export function stringToDelegations(raw, encoding = 'base64url') {
+ const bytes = u8.fromString(raw, encoding)
+
+ return bytesToDelegations(bytes)
+}
+
+/**
+ * Decode string into a {@link Types.Delegation Delegation}
+ *
+ * @template {Types.Capabilities} [T=Types.Capabilities]
+ * @param {import('./types.js').EncodedDelegation} raw
+ * @param {import('uint8arrays/to-string').SupportedEncodings} [encoding]
+ */
+export function stringToDelegation(raw, encoding) {
+ const delegations = stringToDelegations(raw, encoding)
+
+ return /** @type {Types.Delegation} */ (delegations[0])
+}
+
+/**
+ * @param {number} [expiration]
+ */
+/* c8 ignore next 8 */
+export function expirationToDate(expiration) {
+ const expires =
+ expiration === Infinity || !expiration
+ ? undefined
+ : new Date(expiration * 1000)
+
+ return expires
+}
diff --git a/packages/access-client/src/errors.ts b/packages/w3up-client/src/agent/errors.ts
similarity index 100%
rename from packages/access-client/src/errors.ts
rename to packages/w3up-client/src/agent/errors.ts
diff --git a/packages/w3up-client/src/agent/types.js b/packages/w3up-client/src/agent/types.js
new file mode 100644
index 000000000..336ce12bb
--- /dev/null
+++ b/packages/w3up-client/src/agent/types.js
@@ -0,0 +1 @@
+export {}
diff --git a/packages/access-client/src/types.ts b/packages/w3up-client/src/agent/types.ts
similarity index 96%
rename from packages/access-client/src/types.ts
rename to packages/w3up-client/src/agent/types.ts
index 044735731..d4406ec91 100644
--- a/packages/access-client/src/types.ts
+++ b/packages/w3up-client/src/agent/types.ts
@@ -53,8 +53,7 @@ import type {
PlanGetSuccess,
PlanGetFailure,
} from '@web3-storage/capabilities/types'
-import type { SetRequired } from 'type-fest'
-import { Driver } from './drivers/types.js'
+import { Driver } from '../driver/types.js'
import { SpaceUnknown } from './errors.js'
// export other types
@@ -62,7 +61,13 @@ export * from '@ucanto/interface'
export * from '@web3-storage/capabilities/types'
export * from './errors.js'
export * from '@web3-storage/did-mailto'
-export type { Agent } from './agent.js'
+export type { Agent } from '../agent.js'
+
+export type {
+ UCANRevoke,
+ UCANRevokeSuccess,
+ UCANRevokeFailure,
+} from '@web3-storage/capabilities/types'
export interface SpaceInfoResult {
// space did
@@ -231,7 +236,9 @@ export type InvokeOptions<
proofs?: Delegation[]
}
-export type DelegationOptions = SetRequired & {
+export type DelegationOptions = UCANBasicOptions & {
+ audience: Principal
+
/**
* Abilities to delegate
*/
diff --git a/packages/access-client/src/agent-use-cases.js b/packages/w3up-client/src/agent/use-cases.js
similarity index 94%
rename from packages/access-client/src/agent-use-cases.js
rename to packages/w3up-client/src/agent/use-cases.js
index c32f2f2c5..445a18007 100644
--- a/packages/access-client/src/agent-use-cases.js
+++ b/packages/w3up-client/src/agent/use-cases.js
@@ -1,12 +1,13 @@
-import { addSpacesFromDelegations, Agent as AccessAgent } from './agent.js'
+import { addSpacesFromDelegations, Agent as AccessAgent } from '../agent.js'
import * as Access from '@web3-storage/capabilities/access'
import { bytesToDelegations } from './encoding.js'
import { Provider, Plan } from '@web3-storage/capabilities'
import * as w3caps from '@web3-storage/capabilities'
import { Schema, delegate } from '@ucanto/core'
-import { AgentData, isSessionProof } from './agent-data.js'
+import { AgentData, isSessionProof } from './data.js'
import * as DidMailto from '@web3-storage/did-mailto'
import * as API from './types.js'
+import * as Result from '../result.js'
const DIDWeb = Schema.DID.match({ method: 'web' })
@@ -27,9 +28,8 @@ export async function requestAccess(access, account, capabilities) {
att: [...capabilities],
},
})
- if (res?.out.error) {
- throw res.out.error
- }
+
+ return Result.try(res.out)
}
/**
@@ -50,11 +50,9 @@ export async function claimAccess(
audience: access.connection.id,
with: audienceOfClaimedDelegations,
})
- if (res.out.error) {
- throw res.out.error
- }
- const delegations = Object.values(res.out.ok.delegations).flatMap((bytes) =>
- bytesToDelegations(bytes)
+
+ const delegations = Object.values(Result.try(res.out).delegations).flatMap(
+ (bytes) => bytesToDelegations(bytes)
)
if (addProofs) {
for (const d of delegations) {
@@ -74,6 +72,7 @@ export async function claimAccess(
* @param {API.Principal} opts.account
* @param {API.ProviderDID} opts.provider - e.g. 'did:web:staging.web3.storage'
*/
+/* c8 ignore next 12 */
export async function addProvider({ access, space, account, provider }) {
const result = await access.invokeAndExecute(Provider.add, {
audience: access.connection.id,
@@ -83,9 +82,8 @@ export async function addProvider({ access, space, account, provider }) {
consumer: space,
},
})
- if (result.out.error) {
- throw result.out.error
- }
+
+ return Result.try(result.out)
}
/**
@@ -118,12 +116,12 @@ export async function pollAccessClaimUntil(
// eslint-disable-next-line no-constant-condition
while (true) {
if (opts?.signal?.aborted)
+ /* c8 ignore next */
throw opts.signal.reason ?? new Error('operation aborted')
const res = await access.invokeAndExecute(w3caps.Access.claim, {
with: delegee,
})
- if (res.out.error) throw res.out.error
- const claims = Object.values(res.out.ok.delegations).flatMap((d) =>
+ const claims = Object.values(Result.try(res.out).delegations).flatMap((d) =>
bytesToDelegations(d)
)
if (delegationsMatch(claims)) return claims
@@ -210,6 +208,7 @@ export async function authorizeAndWait(access, email, opts = {}) {
export async function authorizeWaitAndClaim(accessAgent, email, opts) {
await authorizeAndWait(accessAgent, email, opts)
await claimAccess(accessAgent, accessAgent.issuer.did(), {
+ /* c8 ignore next */
addProofs: opts?.addProofs ?? true,
})
}
@@ -227,6 +226,7 @@ export async function authorizeWaitAndClaim(accessAgent, email, opts) {
* @param {API.DID<'key'>} [opts.space]
* @param {API.ProviderDID} [opts.provider] - provider to register - defaults to this.connection.id
*/
+/* c8 ignore next 39 */
export async function addProviderAndDelegateToAccount(
access,
agentData,
@@ -266,6 +266,7 @@ export async function addProviderAndDelegateToAccount(
throw delegateSpaceAccessResult.out.error
}
+ /* c8 ignore next 2 */
await agentData.addSpace(space, spaceMeta)
}
@@ -274,6 +275,7 @@ export async function addProviderAndDelegateToAccount(
* @param {API.SpaceDID} space
* @param {API.Principal} account
*/
+/* c8 ignore next 26 */
async function delegateSpaceAccessToAccount(access, space, account) {
const issuerSaysAccountCanAdminSpace =
await createIssuerSaysAccountCanAdminSpace(
@@ -300,6 +302,7 @@ async function delegateSpaceAccessToAccount(access, space, account) {
issuerSaysAccountCanAdminSpace,
],
})
+ /* c8 ignore next */
}
/**
@@ -311,6 +314,7 @@ async function delegateSpaceAccessToAccount(access, space, account) {
* @param {number} expiration
* @returns
*/
+/* c8 ignore next 20 */
async function createIssuerSaysAccountCanAdminSpace(
issuer,
space,
@@ -331,6 +335,7 @@ async function createIssuerSaysAccountCanAdminSpace(
proofs,
expiration,
})
+ /* c8 ignore next */
}
/**
diff --git a/packages/access-client/src/utils/json.js b/packages/w3up-client/src/agent/utils/json.js
similarity index 95%
rename from packages/access-client/src/utils/json.js
rename to packages/w3up-client/src/agent/utils/json.js
index 588c49645..e90e3c8e5 100644
--- a/packages/access-client/src/utils/json.js
+++ b/packages/w3up-client/src/agent/utils/json.js
@@ -5,6 +5,7 @@
* @param {any} v
*/
export const replacer = (k, v) => {
+ /* c8 ignore next 2 */
if (v instanceof URL) {
return { $url: v.toString() }
} else if (v instanceof Map) {
@@ -23,6 +24,7 @@ export const replacer = (k, v) => {
*/
export const reviver = (k, v) => {
if (!v) return v
+ /* c8 ignore next */
if (v.$url) return new URL(v.$url)
if (v.$map) return new Map(v.$map)
if (v.$bytes) return new Uint8Array(v.$bytes)
diff --git a/packages/w3up-client/src/base.js b/packages/w3up-client/src/base.js
index e49b0ffa4..fd12cdf2c 100644
--- a/packages/w3up-client/src/base.js
+++ b/packages/w3up-client/src/base.js
@@ -1,4 +1,4 @@
-import { Agent } from '@web3-storage/access/agent'
+import { Agent } from './agent.js'
import { serviceConf } from './service.js'
export class Base {
@@ -15,7 +15,7 @@ export class Base {
_serviceConf
/**
- * @param {import('@web3-storage/access').AgentData} agentData
+ * @param {import('./agent.js').AgentData} agentData
* @param {object} [options]
* @param {import('./types.js').ServiceConf} [options.serviceConf]
*/
diff --git a/packages/w3up-client/src/capability/access.js b/packages/w3up-client/src/capability/access.js
index ad97c99be..4d2cb537d 100644
--- a/packages/w3up-client/src/capability/access.js
+++ b/packages/w3up-client/src/capability/access.js
@@ -1,9 +1,11 @@
import { Base } from '../base.js'
-import * as Agent from '@web3-storage/access/agent'
+import * as Access from '@web3-storage/capabilities/access'
import * as DIDMailto from '@web3-storage/did-mailto'
import * as Result from '../result.js'
-
-import * as API from '../types.js'
+import { Failure, DID } from '@ucanto/core'
+import { bytesToDelegations } from '../agent/encoding.js'
+import { importAuthorization } from '../agent.js'
+import * as API from '../agent/types.js'
export { DIDMailto }
@@ -25,7 +27,7 @@ export class AccessClient extends Base {
*/
async authorize(email, options) {
const account = DIDMailto.fromEmail(email)
- const authorization = Result.unwrap(await request(this, { account }))
+ const authorization = Result.unwrap(await request(this.agent, { account }))
const access = Result.unwrap(await authorization.claim(options))
await Result.unwrap(await access.save())
@@ -40,7 +42,7 @@ export class AccessClient extends Base {
* @param {API.DID} [input.audience]
*/
async claim(input) {
- const access = Result.unwrap(await claim(this, input))
+ const access = Result.unwrap(await claim(this.agent, input))
await Result.unwrap(await access.save())
return access.proofs
}
@@ -54,7 +56,7 @@ export class AccessClient extends Base {
* @param {AbortSignal} [input.signal]
*/
async request(input) {
- return await request(this, input)
+ return await request(this.agent, input)
}
/**
@@ -66,41 +68,358 @@ export class AccessClient extends Base {
* @param {API.Delegation[]} [input.proofs]
*/
async delegate(input) {
- return await delegate(this, input)
+ return await delegate(this.agent, input)
}
}
/**
- * @param {{agent: API.Agent}} client
- * @param {object} [input]
- * @param {API.DID} [input.audience]
+ * Takes array of delegations and propagates them to their respective audiences
+ * through a given space (or the current space if none is provided).
+ *
+ * Returns error result if agent has no current space and no space was provided.
+ * Also returns error result if invocation fails.
+ *
+ * @param {API.Agent} agent - Agent connected to the w3up service.
+ * @param {object} input
+ * @param {API.Delegation[]} input.delegations - Delegations to propagate.
+ * @param {API.SpaceDID} [input.space] - Space to propagate through.
+ * @param {API.Delegation[]} [input.proofs] - Optional set of proofs to be
+ * included in the invocation.
*/
-export const claim = async ({ agent }, input) =>
- Agent.Access.claim(agent, input)
+export const delegate = async (
+ agent,
+ { delegations, proofs = [], space = agent.currentSpace() }
+) => {
+ /* c8 ignore next 3 */
+ if (!space) {
+ return Result.fail('Space must be specified')
+ }
+
+ const entries = Object.values(delegations).map((proof) => [
+ proof.cid.toString(),
+ proof.cid,
+ ])
+
+ const { out } = await agent.invokeAndExecute(Access.delegate, {
+ with: space,
+ nb: {
+ delegations: Object.fromEntries(entries),
+ },
+ // must be embedded here because it's referenced by cid in .nb.delegations
+ proofs: [...delegations, ...proofs],
+ })
+
+ return out
+}
/**
- * Requests specified `access` level from specified `account`. It will invoke
- * `access/authorize` capability and keep polling `access/claim` capability
- * until access is granted or request is aborted.
+ * Requests specified `access` level from specified `account`. It invokes
+ * `access/authorize` capability, if invocation succeeds it will return a
+ * `PendingAccessRequest` object that can be used to poll for the requested
+ * delegation through `access/claim` capability.
*
- * @param {{agent: API.Agent}} agent
+ * @param {API.Agent} agent
* @param {object} input
- * @param {API.AccountDID} input.account
- * @param {API.Access} [input.access]
- * @param {API.DID} [input.audience]
+ * @param {API.AccountDID} input.account - Account from which access is requested.
+ * @param {API.ProviderDID} [input.provider] - Provider that will receive the invocation.
+ * @param {API.DID} [input.audience] - Principal requesting an access.
+ * @param {API.Access} [input.access] - Access been requested.
+ * @returns {Promise>}
*/
-export const request = async ({ agent }, input) =>
- Agent.Access.request(agent, input)
+export const request = async (
+ agent,
+ {
+ account,
+ provider = /** @type {API.ProviderDID} */ (agent.connection.id.did()),
+ audience: audience = agent.did(),
+ access = spaceAccess,
+ }
+) => {
+ // Request access from the account.
+ const { out: result } = await agent.invokeAndExecute(Access.authorize, {
+ audience: DID.parse(provider),
+ with: audience,
+ nb: {
+ iss: account,
+ // New ucan spec moved to recap style layout for capabilities and new
+ // `access/request` will use similar format as opposed to legacy one,
+ // in the meantime we translate new format to legacy format here.
+ att: [...toCapabilities(access)],
+ },
+ })
+
+ return result.error
+ ? /* c8 ignore next */
+ result
+ : {
+ ok: new PendingAccessRequest({
+ ...result.ok,
+ agent,
+ audience,
+ provider,
+ }),
+ }
+}
/**
+ * Claims access that has been delegated to the given audience, which by
+ * default is the agent's DID.
*
- * @param {{agent: API.Agent}} agent
+ * @param {API.Agent} agent
* @param {object} input
- * @param {API.Delegation[]} input.delegations
- * @param {API.SpaceDID} [input.space]
- * @param {API.Delegation[]} [input.proofs]
+ * @param {API.DID} [input.audience] - Principal requesting an access.
+ * @param {API.ProviderDID} [input.provider] - Provider handling the invocation.
+ * @returns {Promise>}
+ */
+export const claim = async (
+ agent,
+ {
+ provider = /** @type {API.ProviderDID} */ (agent.connection.id.did()),
+ audience = agent.did(),
+ } = {}
+) => {
+ const { out: result } = await agent.invokeAndExecute(Access.claim, {
+ audience: DID.parse(provider),
+ with: audience,
+ })
+
+ /* c8 ignore next 2 */
+ if (result.error) {
+ return result
+ } else {
+ const delegations = Object.values(result.ok.delegations)
+ const proofs = delegations.flatMap((proof) => bytesToDelegations(proof))
+ return { ok: new GrantedAccess({ agent, provider, audience, proofs }) }
+ }
+}
+
+/**
+ * Represents a pending access request. It can be used to poll for the requested
+ * delegation.
+ */
+class PendingAccessRequest {
+ /**
+ * @typedef {object} PendingAccessRequestModel
+ * @property {API.Agent} agent - Agent handling interaction.
+ * @property {API.DID} audience - Principal requesting an access.
+ * @property {API.ProviderDID} provider - Provider handling request.
+ * @property {API.UTCUnixTimestamp} expiration - Seconds in UTC.
+ * @property {API.Link} request - Link to the `access/authorize` invocation.
+ *
+ * @param {PendingAccessRequestModel} model
+ */
+ constructor(model) {
+ this.model = model
+ }
+
+ get agent() {
+ return this.model.agent
+ }
+ get audience() {
+ return this.model.audience
+ }
+ get expiration() {
+ return new Date(this.model.expiration * 1000)
+ }
+
+ get request() {
+ return this.model.request
+ }
+
+ get provider() {
+ return this.model.provider
+ }
+
+ /**
+ * Low level method and most likely you want to use `.claim` instead. This method will poll
+ * fetch delegations **just once** and will return proofs matching to this request. Please note
+ * that there may not be any matches in which case result will be `{ ok: [] }`.
+ *
+ * If you do want to continuously poll until request is approved or expired, you should use
+ * `.claim` method instead.
+ *
+ * @returns {Promise>}
+ */
+ async poll() {
+ const { agent, audience, provider, expiration } = this.model
+ const timeout = expiration * 1000 - Date.now()
+ /* c8 ignore next 2 */
+ if (timeout <= 0) {
+ return { error: new RequestExpired(this.model) }
+ } else {
+ const result = await claim(agent, { audience, provider })
+ return result.error
+ ? /* c8 ignore next */
+ result
+ : {
+ ok: result.ok.proofs.filter((proof) =>
+ isRequestedAccess(proof, this.model)
+ ),
+ }
+ }
+ }
+
+ /**
+ * Continuously polls delegations until this request is approved or expired. Returns
+ * a `GrantedAccess` object (view over the delegations) that can be used in the
+ * invocations or can be saved in the agent (store) using `.save()` method.
+ *
+ * @param {object} options
+ * @param {number} [options.interval]
+ * @param {AbortSignal} [options.signal]
+ * @returns {Promise>}
+ */
+ async claim({ signal, interval = 250 } = {}) {
+ /* c8 ignore next */
+ while (signal?.aborted !== true) {
+ const result = await this.poll()
+ // If polling failed, return the error.
+ /* c8 ignore next 3 */
+ if (result.error) {
+ return result
+ }
+ // If we got some matching proofs, return them.
+ else if (result.ok.length > 0) {
+ return {
+ ok: new GrantedAccess({
+ agent: this.agent,
+ provider: this.provider,
+ audience: this.audience,
+ proofs: result.ok,
+ }),
+ }
+ }
+
+ await new Promise((resolve) => setTimeout(resolve, interval))
+ }
+ /* c8 ignore next 4 */
+ return {
+ error: Object.assign(new Error('Aborted'), { reason: signal.reason }),
+ }
+ }
+}
+
+/**
+ * Error returned when pending access request expires.
*/
-export const delegate = async ({ agent }, input) =>
- Agent.Access.delegate(agent, input)
+class RequestExpired extends Failure {
+ /**
+ * @param {PendingAccessRequestModel} model
+ */
+ /* c8 ignore next 4 */
+ constructor(model) {
+ super()
+ this.model = model
+ }
+
+ /* c8 ignore next 4 */
+ get name() {
+ return 'RequestExpired'
+ }
+
+ /* c8 ignore next 3 */
+ get request() {
+ return this.model.request
+ }
+
+ /* c8 ignore next 3 */
+ get expiredAt() {
+ return new Date(this.model.expiration * 1000)
+ }
+
+ /* c8 ignore next 3 */
+ describe() {
+ return `Access request expired at ${this.expiredAt} for ${this.request} request.`
+ }
+}
-export const { spaceAccess, accountAccess } = Agent.Access
+/**
+ * View over the UCAN Delegations that grant access to a specific principal.
+ */
+class GrantedAccess {
+ /**
+ * @typedef {object} GrantedAccessModel
+ * @property {API.Agent} agent - Agent that processed the request.
+ * @property {API.DID} audience - Principal access was granted to.
+ * @property {API.Delegation[]} proofs - Delegations that grant access.
+ * @property {API.ProviderDID} provider - Provider that handled the request.
+ *
+ * @param {GrantedAccessModel} model
+ */
+ constructor(model) {
+ this.model = model
+ }
+ get proofs() {
+ return this.model.proofs
+ }
+ get provider() {
+ return this.model.provider
+ }
+ get authority() {
+ return this.model.audience
+ }
+
+ /**
+ * Saves access into the agents proofs store so that it can be retained
+ * between sessions.
+ *
+ * @param {object} input
+ * @param {API.Agent} [input.agent]
+ */
+ save({ agent = this.model.agent } = {}) {
+ return importAuthorization(agent, this)
+ }
+}
+
+/**
+ * Checks if the given delegation is caused by the passed `request` for access.
+ *
+ * @param {API.Delegation} delegation
+ * @param {object} selector
+ * @param {API.Link} selector.request
+ * @returns
+ */
+const isRequestedAccess = (delegation, { request }) =>
+ // `access/confirm` handler adds facts to the delegation issued by the account
+ // so that principal requesting access can identify correct delegation when
+ // access is granted.
+ delegation.facts.some((fact) => `${fact['access/request']}` === `${request}`)
+
+/**
+ * Maps access object that uses UCAN 0.10 capabilities format as opposed
+ * to legacy UCAN 0.9 format used by w3up which predates new format.
+ *
+ * @param {API.Access} access
+ * @returns {{ can: API.Ability }[]}
+ */
+export const toCapabilities = (access) => {
+ const abilities = []
+ const entries = /** @type {[API.Ability, API.Unit][]} */ (
+ Object.entries(access)
+ )
+
+ for (const [can, details] of entries) {
+ if (details) {
+ abilities.push({ can })
+ }
+ }
+ return abilities
+}
+
+/**
+ * Set of capabilities required by the agent to manage a space.
+ */
+export const spaceAccess = {
+ 'space/*': {},
+ 'store/*': {},
+ 'upload/*': {},
+ 'access/*': {},
+ 'filecoin/*': {},
+}
+
+/**
+ * Set of capabilities required for by the agent to manage an account.
+ */
+export const accountAccess = {
+ '*': {},
+}
diff --git a/packages/w3up-client/src/capability/provider.js b/packages/w3up-client/src/capability/provider.js
new file mode 100644
index 000000000..e9c5220f3
--- /dev/null
+++ b/packages/w3up-client/src/capability/provider.js
@@ -0,0 +1,46 @@
+import * as API from '../agent/types.js'
+import * as Provider from '@web3-storage/capabilities/provider'
+
+export const { Provider: ProviderDID, AccountDID } = Provider
+
+/**
+ * Provisions specified `space` with the specified `account`. It is expected
+ * that delegation from the account authorizing agent is either stored in the
+ * agent proofs or provided explicitly.
+ *
+ * @template {Record} [S=API.Service]
+ * @param {API.Agent} agent
+ * @param {object} input
+ * @param {API.AccountDID} input.account - Account provisioning the space.
+ * @param {API.SpaceDID} input.consumer - Space been provisioned.
+ * @param {API.ProviderDID} [input.provider] - Provider been provisioned.
+ * @param {API.Delegation[]} [input.proofs] - Delegation from the account
+ * authorizing agent to call `provider/add` capability.
+ */
+export const add = async (
+ agent,
+ {
+ account,
+ consumer,
+ provider = /** @type {API.ProviderDID} */ (agent.connection.id.did()),
+ proofs,
+ }
+) => {
+ /* c8 ignore next 5 */
+ if (!ProviderDID.is(provider)) {
+ throw new Error(
+ `Unable to determine provider from agent.connection.id did ${provider}. expected a did:web:`
+ )
+ }
+
+ const { out } = await agent.invokeAndExecute(Provider.add, {
+ with: account,
+ nb: {
+ provider,
+ consumer,
+ },
+ proofs,
+ })
+
+ return out
+}
diff --git a/packages/w3up-client/src/capability/space.js b/packages/w3up-client/src/capability/space.js
index f2c077d28..26b91cf3d 100644
--- a/packages/w3up-client/src/capability/space.js
+++ b/packages/w3up-client/src/capability/space.js
@@ -1,4 +1,273 @@
import { Base } from '../base.js'
+import * as ED25519 from '@ucanto/principal/ed25519'
+import { delegate, Schema, UCAN } from '@ucanto/core'
+import * as BIP39 from '@scure/bip39'
+import { wordlist } from '@scure/bip39/wordlists/english'
+import * as API from '../types.js'
+import * as Access from './access.js'
+
+/**
+ * Data model for the (owned) space.
+ *
+ * @typedef {object} Model
+ * @property {ED25519.EdSigner} signer
+ * @property {string} name
+ */
+
+/**
+ * Generates a new space.
+ *
+ * @param {object} options
+ * @param {string} options.name
+ */
+export const generate = async ({ name }) => {
+ const { signer } = await ED25519.generate()
+
+ return new OwnedSpace({ signer, name })
+}
+
+/**
+ * Recovers space from the saved mnemonic.
+ *
+ * @param {string} mnemonic
+ * @param {object} options
+ * @param {string} options.name - Name to give to the recovered space.
+ */
+export const fromMnemonic = async (mnemonic, { name }) => {
+ const secret = BIP39.mnemonicToEntropy(mnemonic, wordlist)
+ const signer = await ED25519.derive(secret)
+ return new OwnedSpace({ signer, name })
+}
+
+/**
+ * Turns (owned) space into a BIP39 mnemonic that later can be used to recover
+ * the space using `fromMnemonic` function.
+ *
+ * @param {object} space
+ * @param {ED25519.EdSigner} space.signer
+ */
+export const toMnemonic = ({ signer }) => {
+ /** @type {Uint8Array} */
+ // @ts-expect-error - Field is defined but not in the interface
+ const secret = signer.secret
+
+ return BIP39.entropyToMnemonic(secret, wordlist)
+}
+
+/**
+ * Creates a (UCAN) delegation that gives full access to the space to the
+ * specified `account`. At the moment we only allow `did:mailto` principal
+ * to be used as an `account`.
+ *
+ * @param {Model} space
+ * @param {API.AccountDID} account
+ */
+export const createRecovery = (space, account) =>
+ createAuthorization(space, {
+ agent: space.signer.withDID(account),
+ access: Access.accountAccess,
+ expiration: Infinity,
+ })
+
+// Default authorization session is valid for 1 year
+export const SESSION_LIFETIME = 60 * 60 * 24 * 365
+
+/**
+ * Creates (UCAN) delegation that gives specified `agent` an access to
+ * specified ability (passed as `access.can` field) on this space.
+ * Optionally, you can specify `access.expiration` field to set the
+ * expiration time for the authorization. By default the authorization
+ * is valid for 1 year and gives access to all capabilities on the space
+ * that are needed to use the space.
+ *
+ * @param {Model} space
+ * @param {object} options
+ * @param {API.Principal} options.agent
+ * @param {API.Access} [options.access]
+ * @param {API.UTCUnixTimestamp} [options.expiration]
+ */
+export const createAuthorization = async (
+ { signer, name },
+ {
+ agent,
+ access = Access.spaceAccess,
+ expiration = UCAN.now() + SESSION_LIFETIME,
+ }
+) => {
+ return await delegate({
+ issuer: signer,
+ audience: agent,
+ capabilities: toCapabilities({
+ [signer.did()]: access,
+ }),
+ /* c8 ignore next */
+ ...(expiration ? { expiration } : {}),
+ facts: [{ space: { name } }],
+ })
+}
+
+/**
+ * @param {Record} allow
+ * @returns {API.Capabilities}
+ */
+const toCapabilities = (allow) => {
+ const capabilities = []
+ for (const [subject, access] of Object.entries(allow)) {
+ const entries = /** @type {[API.Ability, API.Unit][]} */ (
+ Object.entries(access)
+ )
+
+ for (const [can, details] of entries) {
+ if (details) {
+ capabilities.push({ can, with: subject })
+ }
+ }
+ }
+
+ return /** @type {API.Capabilities} */ (capabilities)
+}
+
+/**
+ * Represents an owned space, meaning a space for which we have a private key
+ * and consequently have full authority over.
+ */
+class OwnedSpace {
+ /**
+ * @param {Model} model
+ */
+ constructor(model) {
+ this.model = model
+ }
+
+ get signer() {
+ return this.model.signer
+ }
+
+ get name() {
+ return this.model.name
+ }
+
+ did() {
+ return this.signer.did()
+ }
+
+ /**
+ * Creates a renamed version of this space.
+ *
+ * @param {string} name
+ */
+ withName(name) {
+ return new OwnedSpace({ signer: this.signer, name })
+ }
+
+ /**
+ * Creates a (UCAN) delegation that gives full access to the space to the
+ * specified `account`. At the moment we only allow `did:mailto` principal
+ * to be used as an `account`.
+ *
+ * @param {API.AccountDID} account
+ */
+ async createRecovery(account) {
+ return createRecovery(this, account)
+ }
+
+ /**
+ * Creates (UCAN) delegation that gives specified `agent` an access to
+ * specified ability (passed as `access.can` field) on the this space.
+ * Optionally, you can specify `access.expiration` field to set the
+ *
+ * @param {API.Principal} agent
+ * @param {object} [input]
+ * @param {API.Access} [input.access]
+ * @param {API.UCAN.UTCUnixTimestamp} [input.expiration]
+ */
+ createAuthorization(agent, input) {
+ return createAuthorization(this, { ...input, agent })
+ }
+
+ /**
+ * Derives BIP39 mnemonic that can be used to recover the space.
+ *
+ * @returns {string}
+ */
+ toMnemonic() {
+ return toMnemonic(this)
+ }
+}
+
+const SpaceDID = Schema.did({ method: 'key' })
+
+/**
+ * Creates a (shared) space from given delegation.
+ *
+ * @param {API.Delegation} delegation
+ */
+export const fromDelegation = (delegation) => {
+ const result = SpaceDID.read(delegation.capabilities[0].with)
+ /* c8 ignore next 10 */
+ if (result.error) {
+ throw Object.assign(
+ new Error(
+ `Invalid delegation, expected capabilities[0].with to be DID, ${result.error}`
+ ),
+ {
+ cause: result.error,
+ }
+ )
+ }
+
+ /** @type {{name?:string}} */
+ /* c8 ignore next */
+ const meta = delegation.facts[0]?.space ?? {}
+
+ return new SharedSpace({ id: result.ok, delegation, meta })
+}
+
+/**
+ * Represents a shared space, meaning a space for which we have a delegation
+ * and consequently have limited authority over.
+ */
+class SharedSpace {
+ /**
+ * @typedef {object} SharedSpaceModel
+ * @property {API.SpaceDID} id
+ * @property {API.Delegation} delegation
+ * @property {{name?:string}} meta
+ *
+ * @param {SharedSpaceModel} model
+ */
+ constructor(model) {
+ this.model = model
+ }
+
+ get delegation() {
+ return this.model.delegation
+ }
+
+ get meta() {
+ return this.model.meta
+ }
+
+ get name() {
+ /* c8 ignore next */
+ return this.meta.name ?? ''
+ }
+
+ did() {
+ return this.model.id
+ }
+
+ /**
+ * @param {string} name
+ */
+ /* c8 ignore next 6 */
+ withName(name) {
+ return new SharedSpace({
+ ...this.model,
+ meta: { ...this.meta, name },
+ })
+ }
+}
/**
* Client for interacting with the `space/*` capabilities.
diff --git a/packages/w3up-client/src/capability/store.js b/packages/w3up-client/src/capability/store.js
index 1ced89bfe..ac530578b 100644
--- a/packages/w3up-client/src/capability/store.js
+++ b/packages/w3up-client/src/capability/store.js
@@ -1,6 +1,12 @@
-import { Store } from '@web3-storage/upload-client'
import { Store as StoreCapabilities } from '@web3-storage/capabilities'
import { Base } from '../base.js'
+import { CAR } from '@ucanto/transport'
+import { SpaceDID } from '@web3-storage/capabilities/utils'
+import retry, { AbortError } from 'p-retry'
+import { servicePrincipal, connection } from './upload/service.js'
+import { REQUEST_RETRIES } from './upload/constants.js'
+import fetchPkg from 'ipfs-utils/src/http/fetch.js'
+const { fetch } = fetchPkg
/**
* Client for interacting with the `store/*` capabilities.
@@ -15,7 +21,7 @@ export class StoreClient extends Base {
async add(car, options = {}) {
const conf = await this._invocationConfig([StoreCapabilities.add.can])
options.connection = this._serviceConf.upload
- return Store.add(conf, car, options)
+ return add(conf, car, options)
}
/**
@@ -26,7 +32,7 @@ export class StoreClient extends Base {
async list(options = {}) {
const conf = await this._invocationConfig([StoreCapabilities.add.can])
options.connection = this._serviceConf.upload
- return Store.list(conf, options)
+ return list(conf, options)
}
/**
@@ -38,6 +44,231 @@ export class StoreClient extends Base {
async remove(link, options = {}) {
const conf = await this._invocationConfig([StoreCapabilities.remove.can])
options.connection = this._serviceConf.upload
- return Store.remove(conf, link, options)
+ return remove(conf, link, options)
}
}
+
+/**
+ *
+ * @param {string} url
+ * @param {import('./upload/types.js').ProgressFn} handler
+ */
+function createUploadProgressHandler(url, handler) {
+ /**
+ *
+ * @param {import('./upload/types.js').ProgressStatus} status
+ */
+ function onUploadProgress({ total, loaded, lengthComputable }) {
+ return handler({ total, loaded, lengthComputable, url })
+ }
+ return onUploadProgress
+}
+
+/**
+ * Store a DAG encoded as a CAR file. The issuer needs the `store/add`
+ * delegated capability.
+ *
+ * Required delegated capability proofs: `store/add`
+ *
+ * @param {import('./upload/types.js').InvocationConfig} conf Configuration
+ * for the UCAN invocation. An object with `issuer`, `with` and `proofs`.
+ *
+ * The `issuer` is the signing authority that is issuing the UCAN
+ * invocation(s). It is typically the user _agent_.
+ *
+ * The `with` is the resource the invocation applies to. It is typically the
+ * DID of a space.
+ *
+ * The `proofs` are a set of capability delegations that prove the issuer
+ * has the capability to perform the action.
+ *
+ * The issuer needs the `store/add` delegated capability.
+ * @param {Blob|Uint8Array} car CAR file data.
+ * @param {import('./upload/types.js').RequestOptions} [options]
+ * @returns {Promise}
+ */
+export async function add(
+ { issuer, with: resource, proofs, audience },
+ car,
+ options = {}
+) {
+ // TODO: validate blob contains CAR data
+ const bytes =
+ car instanceof Uint8Array ? car : new Uint8Array(await car.arrayBuffer())
+ const link = await CAR.codec.link(bytes)
+ /* c8 ignore next */
+ const conn = options.connection ?? connection
+ const result = await retry(
+ async () => {
+ return await StoreCapabilities.add
+ .invoke({
+ issuer,
+ /* c8 ignore next */
+ audience: audience ?? servicePrincipal,
+ with: SpaceDID.from(resource),
+ nb: { link, size: bytes.length },
+ proofs,
+ })
+ .execute(conn)
+ },
+ {
+ onFailedAttempt: console.warn,
+ retries: options.retries ?? REQUEST_RETRIES,
+ }
+ )
+
+ if (!result.out.ok) {
+ throw new Error(`failed ${StoreCapabilities.add.can} invocation`, {
+ cause: result.out.error,
+ })
+ }
+
+ // Return early if it was already uploaded.
+ if (result.out.ok.status === 'done') {
+ return link
+ }
+
+ const responseAddUpload = result.out.ok
+
+ const fetchWithUploadProgress =
+ /** @type {(url: string, init?: import('./upload/types.js').FetchOptions) => Promise} */ (
+ fetch
+ )
+
+ const res = await retry(
+ async () => {
+ try {
+ const res = await fetchWithUploadProgress(responseAddUpload.url, {
+ method: 'PUT',
+ mode: 'cors',
+ body: car,
+ headers: responseAddUpload.headers,
+ signal: options.signal,
+ onUploadProgress: options.onUploadProgress
+ ? createUploadProgressHandler(
+ responseAddUpload.url,
+ options.onUploadProgress
+ )
+ : undefined,
+ // @ts-expect-error - this is needed by recent versions of node - see https://github.com/bluesky-social/atproto/pull/470 for more info
+ duplex: 'half',
+ })
+ if (res.status >= 400 && res.status < 500) {
+ throw new AbortError(`upload failed: ${res.status}`)
+ }
+ return res
+ } catch (err) {
+ if (options.signal?.aborted === true) {
+ throw new AbortError('upload aborted')
+ }
+ throw err
+ }
+ },
+ {
+ retries: options.retries ?? REQUEST_RETRIES,
+ }
+ )
+
+ if (!res.ok) {
+ throw new Error(`upload failed: ${res.status}`)
+ }
+
+ return link
+}
+
+/**
+ * List CAR files stored by the issuer.
+ *
+ * @param {import('./upload/types.js').InvocationConfig} conf Configuration
+ * for the UCAN invocation. An object with `issuer`, `with` and `proofs`.
+ *
+ * The `issuer` is the signing authority that is issuing the UCAN
+ * invocation(s). It is typically the user _agent_.
+ *
+ * The `with` is the resource the invocation applies to. It is typically the
+ * DID of a space.
+ *
+ * The `proofs` are a set of capability delegations that prove the issuer
+ * has the capability to perform the action.
+ *
+ * The issuer needs the `store/list` delegated capability.
+ *
+ * @param {import('./upload/types.js').ListRequestOptions} [options]
+ * @returns {Promise}
+ */
+export async function list(
+ { issuer, with: resource, proofs, audience },
+ options = {}
+) {
+ /* c8 ignore next */
+ const conn = options.connection ?? connection
+ const result = await StoreCapabilities.list
+ .invoke({
+ issuer,
+ /* c8 ignore next */
+ audience: audience ?? servicePrincipal,
+ with: SpaceDID.from(resource),
+ proofs,
+ nb: {
+ cursor: options.cursor,
+ size: options.size,
+ pre: options.pre,
+ },
+ })
+ .execute(conn)
+
+ if (!result.out.ok) {
+ throw new Error(`failed ${StoreCapabilities.list.can} invocation`, {
+ cause: result.out.error,
+ })
+ }
+
+ return result.out.ok
+}
+
+/**
+ * Remove a stored CAR file by CAR CID.
+ *
+ * @param {import('./upload/types.js').InvocationConfig} conf Configuration
+ * for the UCAN invocation. An object with `issuer`, `with` and `proofs`.
+ *
+ * The `issuer` is the signing authority that is issuing the UCAN
+ * invocation(s). It is typically the user _agent_.
+ *
+ * The `with` is the resource the invocation applies to. It is typically the
+ * DID of a space.
+ *
+ * The `proofs` are a set of capability delegations that prove the issuer
+ * has the capability to perform the action.
+ *
+ * The issuer needs the `store/remove` delegated capability.
+ *
+ * @param {import('./upload/types.js').CARLink} link CID of CAR file to remove.
+ * @param {import('./upload/types.js').RequestOptions} [options]
+ */
+export async function remove(
+ { issuer, with: resource, proofs, audience },
+ link,
+ options = {}
+) {
+ /* c8 ignore next */
+ const conn = options.connection ?? connection
+ const result = await StoreCapabilities.remove
+ .invoke({
+ issuer,
+ /* c8 ignore next */
+ audience: audience ?? servicePrincipal,
+ with: SpaceDID.from(resource),
+ nb: { link },
+ proofs,
+ })
+ .execute(conn)
+
+ if (!result.out.ok) {
+ throw new Error(`failed ${StoreCapabilities.remove.can} invocation`, {
+ cause: result.out.error,
+ })
+ }
+
+ return result.out
+}
diff --git a/packages/w3up-client/src/capability/upload.js b/packages/w3up-client/src/capability/upload.js
index f27e41965..8af1c3d20 100644
--- a/packages/w3up-client/src/capability/upload.js
+++ b/packages/w3up-client/src/capability/upload.js
@@ -1,7 +1,12 @@
-import { Upload } from '@web3-storage/upload-client'
-import { Upload as UploadCapabilities } from '@web3-storage/capabilities'
+import * as UploadCapabilities from '@web3-storage/capabilities/upload'
+import { SpaceDID } from '@web3-storage/capabilities/utils'
+import retry from 'p-retry'
+import { servicePrincipal, connection } from './upload/service.js'
+import { REQUEST_RETRIES } from './upload/constants.js'
import { Base } from '../base.js'
+import * as API from '../types.js'
+export * from './upload/index.js'
/**
* Client for interacting with the `upload/*` capabilities.
*/
@@ -16,7 +21,7 @@ export class UploadClient extends Base {
async add(root, shards, options = {}) {
const conf = await this._invocationConfig([UploadCapabilities.add.can])
options.connection = this._serviceConf.upload
- return Upload.add(conf, root, shards, options)
+ return add(conf, root, shards, options)
}
/**
@@ -27,7 +32,7 @@ export class UploadClient extends Base {
async list(options = {}) {
const conf = await this._invocationConfig([UploadCapabilities.list.can])
options.connection = this._serviceConf.upload
- return Upload.list(conf, options)
+ return list(conf, options)
}
/**
@@ -39,6 +44,163 @@ export class UploadClient extends Base {
async remove(root, options = {}) {
const conf = await this._invocationConfig([UploadCapabilities.remove.can])
options.connection = this._serviceConf.upload
- return Upload.remove(conf, root, options)
+ return remove(conf, root, options)
}
}
+
+/**
+ * Register an "upload" with the service. The issuer needs the `upload/add`
+ * delegated capability.
+ *
+ * Required delegated capability proofs: `upload/add`
+ *
+ * @param {API.InvocationConfig} conf Configuration
+ * for the UCAN invocation. An object with `issuer`, `with` and `proofs`.
+ *
+ * The `issuer` is the signing authority that is issuing the UCAN
+ * invocation(s). It is typically the user _agent_.
+ *
+ * The `with` is the resource the invocation applies to. It is typically the
+ * DID of a space.
+ *
+ * The `proofs` are a set of capability delegations that prove the issuer
+ * has the capability to perform the action.
+ *
+ * The issuer needs the `upload/add` delegated capability.
+ * @param {import('multiformats/link').UnknownLink} root Root data CID for the DAG that was stored.
+ * @param {API.CARLink[]} shards CIDs of CAR files that contain the DAG.
+ * @param {API.RequestOptions} [options]
+ * @returns {Promise}
+ */
+export async function add(
+ { issuer, with: resource, proofs, audience },
+ root,
+ shards,
+ options = {}
+) {
+ /* c8 ignore next */
+ const conn = options.connection ?? connection
+ const result = await retry(
+ async () => {
+ return await UploadCapabilities.add
+ .invoke({
+ issuer,
+ /* c8 ignore next */
+ audience: audience ?? servicePrincipal,
+ with: SpaceDID.from(resource),
+ nb: { root, shards },
+ proofs,
+ })
+ .execute(conn)
+ },
+ {
+ onFailedAttempt: console.warn,
+ retries: options.retries ?? REQUEST_RETRIES,
+ }
+ )
+
+ if (!result.out.ok) {
+ throw new Error(`failed ${UploadCapabilities.add.can} invocation`, {
+ cause: result.out.error,
+ })
+ }
+
+ return result.out.ok
+}
+
+/**
+ * List uploads created by the issuer.
+ *
+ * @param {API.InvocationConfig} conf Configuration
+ * for the UCAN invocation. An object with `issuer`, `with` and `proofs`.
+ *
+ * The `issuer` is the signing authority that is issuing the UCAN
+ * invocation(s). It is typically the user _agent_.
+ *
+ * The `with` is the resource the invocation applies to. It is typically the
+ * DID of a space.
+ *
+ * The `proofs` are a set of capability delegations that prove the issuer
+ * has the capability to perform the action.
+ *
+ * The issuer needs the `upload/list` delegated capability.
+ *
+ * @param {API.ListRequestOptions} [options]
+ * @returns {Promise}
+ */
+export async function list(
+ { issuer, with: resource, proofs, audience },
+ options = {}
+) {
+ /* c8 ignore next */
+ const conn = options.connection ?? connection
+
+ const result = await UploadCapabilities.list
+ .invoke({
+ issuer,
+ /* c8 ignore next */
+ audience: audience ?? servicePrincipal,
+ with: SpaceDID.from(resource),
+ proofs,
+ nb: {
+ cursor: options.cursor,
+ size: options.size,
+ pre: options.pre,
+ },
+ })
+ .execute(conn)
+
+ if (!result.out.ok) {
+ throw new Error(`failed ${UploadCapabilities.list.can} invocation`, {
+ cause: result.out.error,
+ })
+ }
+
+ return result.out.ok
+}
+
+/**
+ * Remove an upload by root data CID.
+ *
+ * @param {API.InvocationConfig} conf Configuration
+ * for the UCAN invocation. An object with `issuer`, `with` and `proofs`.
+ *
+ * The `issuer` is the signing authority that is issuing the UCAN
+ * invocation(s). It is typically the user _agent_.
+ *
+ * The `with` is the resource the invocation applies to. It is typically the
+ * DID of a space.
+ *
+ * The `proofs` are a set of capability delegations that prove the issuer
+ * has the capability to perform the action.
+ *
+ * The issuer needs the `upload/remove` delegated capability.
+ * @param {API.UnknownLink} root Root data CID to remove.
+ * @param {API.RequestOptions} [options]
+ */
+export async function remove(
+ { issuer, with: resource, proofs, audience },
+ root,
+ options = {}
+) {
+ /* c8 ignore next */
+ const conn = options.connection ?? connection
+ const result = await UploadCapabilities.remove
+ .invoke({
+ issuer,
+ /* c8 ignore next */
+ audience: audience ?? servicePrincipal,
+ with: SpaceDID.from(resource),
+ nb: { root },
+ proofs,
+ })
+ .execute(conn)
+
+ if (!result.out.ok) {
+ throw new Error(`failed ${UploadCapabilities.remove.can} invocation`, {
+ cause: result.out.error,
+ })
+ }
+
+ return result.out.ok
+}
diff --git a/packages/w3up-client/src/capability/upload/car.js b/packages/w3up-client/src/capability/upload/car.js
new file mode 100644
index 000000000..66253aff6
--- /dev/null
+++ b/packages/w3up-client/src/capability/upload/car.js
@@ -0,0 +1,115 @@
+import { CarBlockIterator, CarWriter } from '@ipld/car'
+import * as dagCBOR from '@ipld/dag-cbor'
+import { varint } from 'multiformats'
+
+/**
+ * @typedef {import('@ipld/unixfs').Block} Block
+ */
+
+/** Byte length of a CBOR encoded CAR header with zero roots. */
+const NO_ROOTS_HEADER_LENGTH = 17
+
+/** @param {import('./types.js').AnyLink} [root] */
+export function headerEncodingLength(root) {
+ if (!root) return NO_ROOTS_HEADER_LENGTH
+ const headerLength = dagCBOR.encode({ version: 1, roots: [root] }).length
+ const varintLength = varint.encodingLength(headerLength)
+ return varintLength + headerLength
+}
+
+/** @param {Block} block */
+export function blockEncodingLength(block) {
+ const payloadLength = block.cid.bytes.length + block.bytes.length
+ const varintLength = varint.encodingLength(payloadLength)
+ return varintLength + payloadLength
+}
+
+/**
+ * @param {Iterable | AsyncIterable} blocks
+ * @param {import('./types.js').AnyLink} [root]
+ * @returns {Promise}
+ */
+export async function encode(blocks, root) {
+ // @ts-expect-error
+ const { writer, out } = CarWriter.create(root)
+ /** @type {Error?} */
+ let error
+ void (async () => {
+ try {
+ for await (const block of blocks) {
+ await writer.put(block)
+ }
+ } catch (/** @type {any} */ err) {
+ error = err
+ } finally {
+ await writer.close()
+ }
+ })()
+ const chunks = []
+ for await (const chunk of out) chunks.push(chunk)
+ // @ts-expect-error
+ if (error != null) throw error
+ const roots = root != null ? [root] : []
+ return Object.assign(new Blob(chunks), { version: 1, roots })
+}
+
+/** @extends {ReadableStream} */
+export class BlockStream extends ReadableStream {
+ /** @param {import('./types.js').BlobLike} car */
+ constructor(car) {
+ /** @type {Promise?} */
+ let blocksPromise = null
+ const getBlocksIterable = () => {
+ if (blocksPromise) return blocksPromise
+ blocksPromise = CarBlockIterator.fromIterable(toIterable(car.stream()))
+ return blocksPromise
+ }
+
+ /** @type {AsyncIterator?} */
+ let iterator = null
+ super({
+ async start() {
+ const blocks = await getBlocksIterable()
+ iterator = /** @type {AsyncIterator} */ (
+ blocks[Symbol.asyncIterator]()
+ )
+ },
+ async pull(controller) {
+ /* c8 ignore next */
+ if (!iterator) throw new Error('missing blocks iterator')
+ const { value, done } = await iterator.next()
+ if (done) return controller.close()
+ controller.enqueue(value)
+ },
+ })
+
+ /** @returns {Promise} */
+ this.getRoots = async () => {
+ const blocks = await getBlocksIterable()
+ return await blocks.getRoots()
+ }
+ }
+}
+
+/* c8 ignore next 20 */
+/**
+ * @template T
+ * @param {{ getReader: () => ReadableStreamDefaultReader } | AsyncIterable} stream
+ * @returns {AsyncIterable}
+ */
+function toIterable(stream) {
+ return Symbol.asyncIterator in stream
+ ? stream
+ : (async function* () {
+ const reader = stream.getReader()
+ try {
+ while (true) {
+ const { done, value } = await reader.read()
+ if (done) return
+ yield value
+ }
+ } finally {
+ reader.releaseLock()
+ }
+ })()
+}
diff --git a/packages/upload-client/src/constants.js b/packages/w3up-client/src/capability/upload/constants.js
similarity index 100%
rename from packages/upload-client/src/constants.js
rename to packages/w3up-client/src/capability/upload/constants.js
diff --git a/packages/w3up-client/src/capability/upload/index.js b/packages/w3up-client/src/capability/upload/index.js
new file mode 100644
index 000000000..ba222c7bb
--- /dev/null
+++ b/packages/w3up-client/src/capability/upload/index.js
@@ -0,0 +1,155 @@
+import { Parallel } from 'parallel-transform-web'
+import * as PieceHasher from 'fr32-sha2-256-trunc254-padded-binary-tree-multihash/async'
+import * as Link from 'multiformats/link'
+import * as raw from 'multiformats/codecs/raw'
+import * as Store from '../store.js'
+import * as UnixFS from './unixfs.js'
+import * as CAR from './car.js'
+import { ShardingStream } from './sharding.js'
+import { add } from '../upload.js'
+
+export { Store, UnixFS, CAR }
+export * from './sharding.js'
+
+const CONCURRENT_REQUESTS = 3
+
+/**
+ * Uploads a file to the service and returns the root data CID for the
+ * generated DAG.
+ *
+ * Required delegated capability proofs: `store/add`, `upload/add`
+ *
+ * @param {import('./types.js').InvocationConfig} conf Configuration
+ * for the UCAN invocation. An object with `issuer`, `with` and `proofs`.
+ *
+ * The `issuer` is the signing authority that is issuing the UCAN
+ * invocation(s). It is typically the user _agent_.
+ *
+ * The `with` is the resource the invocation applies to. It is typically the
+ * DID of a space.
+ *
+ * The `proofs` are a set of capability delegations that prove the issuer
+ * has the capability to perform the action.
+ *
+ * The issuer needs the `store/add` and `upload/add` delegated capability.
+ * @param {import('./types.js').BlobLike} file File data.
+ * @param {import('./types.js').UploadOptions} [options]
+ */
+export async function uploadFile(conf, file, options = {}) {
+ return await uploadBlockStream(
+ conf,
+ UnixFS.createFileEncoderStream(file),
+ options
+ )
+}
+
+/**
+ * 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.
+ *
+ * Required delegated capability proofs: `store/add`, `upload/add`
+ *
+ * @param {import('./types.js').InvocationConfig} conf Configuration
+ * for the UCAN invocation. An object with `issuer`, `with` and `proofs`.
+ *
+ * The `issuer` is the signing authority that is issuing the UCAN
+ * invocation(s). It is typically the user _agent_.
+ *
+ * The `with` is the resource the invocation applies to. It is typically the
+ * DID of a space.
+ *
+ * The `proofs` are a set of capability delegations that prove the issuer
+ * has the capability to perform the action.
+ *
+ * The issuer needs the `store/add` and `upload/add` delegated capability.
+ * @param {import('./types.js').FileLike[]} files File data.
+ * @param {import('./types.js').UploadDirectoryOptions} [options]
+ */
+export async function uploadDirectory(conf, files, options = {}) {
+ return await uploadBlockStream(
+ conf,
+ UnixFS.createDirectoryEncoderStream(files, options),
+ options
+ )
+}
+
+/**
+ * Uploads a CAR file to the service.
+ *
+ * The difference between this function and `Store.add` is that the CAR file is
+ * automatically sharded and an "upload" is registered, linking the individual
+ * shards (see `Upload.add`).
+ *
+ * Use the `onShardStored` callback to obtain the CIDs of the CAR file shards.
+ *
+ * Required delegated capability proofs: `store/add`, `upload/add`
+ *
+ * @param {import('./types.js').InvocationConfig} conf Configuration
+ * for the UCAN invocation. An object with `issuer`, `with` and `proofs`.
+ *
+ * The `issuer` is the signing authority that is issuing the UCAN
+ * invocation(s). It is typically the user _agent_.
+ *
+ * The `with` is the resource the invocation applies to. It is typically the
+ * DID of a space.
+ *
+ * The `proofs` are a set of capability delegations that prove the issuer
+ * has the capability to perform the action.
+ *
+ * The issuer needs the `store/add` and `upload/add` delegated capability.
+ * @param {import('./types.js').BlobLike} car CAR file.
+ * @param {import('./types.js').UploadOptions} [options]
+ */
+export async function uploadCAR(conf, car, options = {}) {
+ const blocks = new CAR.BlockStream(car)
+ options.rootCID = options.rootCID ?? (await blocks.getRoots())[0]
+ return await uploadBlockStream(conf, blocks, options)
+}
+
+/**
+ * @param {import('./types.js').InvocationConfig} conf
+ * @param {ReadableStream} blocks
+ * @param {import('./types.js').UploadOptions} [options]
+ * @returns {Promise}
+ */
+async function uploadBlockStream(conf, blocks, options = {}) {
+ /** @type {import('./types.js').CARLink[]} */
+ const shards = []
+ /** @type {import('./types.js').AnyLink?} */
+ let root = null
+ const concurrency = options.concurrentRequests ?? CONCURRENT_REQUESTS
+ await blocks
+ .pipeThrough(new ShardingStream(options))
+ .pipeThrough(
+ new Parallel(concurrency, async (car) => {
+ const bytes = new Uint8Array(await car.arrayBuffer())
+ const [cid, piece] = await Promise.all([
+ Store.add(conf, bytes, options),
+ (async () => {
+ const multihashDigest = await PieceHasher.digest(bytes)
+ return /** @type {import('@web3-storage/capabilities/types').PieceLink} */ (
+ Link.create(raw.code, multihashDigest)
+ )
+ })(),
+ ])
+ const { version, roots, size } = car
+ return { version, roots, size, cid, piece }
+ })
+ )
+ .pipeTo(
+ new WritableStream({
+ write(meta) {
+ root = root || meta.roots[0]
+ shards.push(meta.cid)
+ if (options.onShardStored) options.onShardStored(meta)
+ },
+ })
+ )
+
+ /* c8 ignore next */
+ if (!root) throw new Error('missing root CID')
+
+ await add(conf, root, shards, options)
+ return root
+}
diff --git a/packages/upload-client/src/service.js b/packages/w3up-client/src/capability/upload/service.js
similarity index 100%
rename from packages/upload-client/src/service.js
rename to packages/w3up-client/src/capability/upload/service.js
diff --git a/packages/w3up-client/src/capability/upload/sharding.js b/packages/w3up-client/src/capability/upload/sharding.js
new file mode 100644
index 000000000..b69bdc4e0
--- /dev/null
+++ b/packages/w3up-client/src/capability/upload/sharding.js
@@ -0,0 +1,86 @@
+import { blockEncodingLength, encode, headerEncodingLength } from './car.js'
+
+// https://observablehq.com/@gozala/w3up-shard-size
+const SHARD_SIZE = 133_169_152
+
+/**
+ * Shard a set of blocks into a set of CAR files. By default the last block
+ * received is assumed to be the DAG root and becomes the CAR root CID for the
+ * last CAR output. Set the `rootCID` option to override.
+ *
+ * @extends {TransformStream}
+ */
+export class ShardingStream extends TransformStream {
+ /**
+ * @param {import('./types.js').ShardingOptions} [options]
+ */
+ constructor(options = {}) {
+ const shardSize = options.shardSize ?? SHARD_SIZE
+ const maxBlockLength = shardSize - headerEncodingLength()
+ /** @type {import('@ipld/unixfs').Block[]} */
+ let blocks = []
+ /** @type {import('@ipld/unixfs').Block[] | null} */
+ let readyBlocks = null
+ let currentLength = 0
+
+ super({
+ async transform(block, controller) {
+ if (readyBlocks != null) {
+ controller.enqueue(await encode(readyBlocks))
+ readyBlocks = null
+ }
+
+ const blockLength = blockEncodingLength(block)
+ if (blockLength > maxBlockLength) {
+ throw new Error(
+ `block will cause CAR to exceed shard size: ${block.cid}`
+ )
+ }
+
+ if (blocks.length && currentLength + blockLength > maxBlockLength) {
+ readyBlocks = blocks
+ blocks = []
+ currentLength = 0
+ }
+ blocks.push(block)
+ currentLength += blockLength
+ },
+
+ async flush(controller) {
+ if (readyBlocks != null) {
+ controller.enqueue(await encode(readyBlocks))
+ }
+
+ const rootBlock = blocks.at(-1)
+ if (rootBlock == null) return
+
+ const rootCID = options.rootCID ?? rootBlock.cid
+ const headerLength = headerEncodingLength(rootCID)
+
+ // if adding CAR root overflows the shard limit we move overflowing
+ // blocks into a another CAR.
+ if (headerLength + currentLength > shardSize) {
+ const overage = headerLength + currentLength - shardSize
+ const overflowBlocks = []
+ let overflowCurrentLength = 0
+ while (overflowCurrentLength < overage) {
+ const block = blocks[blocks.length - 1]
+ blocks.pop()
+ overflowBlocks.unshift(block)
+ overflowCurrentLength += blockEncodingLength(block)
+
+ // need at least 1 block in original shard
+ if (blocks.length < 1)
+ throw new Error(
+ `block will cause CAR to exceed shard size: ${block.cid}`
+ )
+ }
+ controller.enqueue(await encode(blocks))
+ controller.enqueue(await encode(overflowBlocks, rootCID))
+ } else {
+ controller.enqueue(await encode(blocks, rootCID))
+ }
+ },
+ })
+ }
+}
diff --git a/packages/upload-client/src/types.ts b/packages/w3up-client/src/capability/upload/types.ts
similarity index 100%
rename from packages/upload-client/src/types.ts
rename to packages/w3up-client/src/capability/upload/types.ts
diff --git a/packages/w3up-client/src/capability/upload/unixfs.js b/packages/w3up-client/src/capability/upload/unixfs.js
new file mode 100644
index 000000000..3547ba71f
--- /dev/null
+++ b/packages/w3up-client/src/capability/upload/unixfs.js
@@ -0,0 +1,176 @@
+import * as UnixFS from '@ipld/unixfs'
+import * as raw from 'multiformats/codecs/raw'
+import { withMaxChunkSize } from '@ipld/unixfs/file/chunker/fixed'
+import { withWidth } from '@ipld/unixfs/file/layout/balanced'
+
+const SHARD_THRESHOLD = 1000 // shard directory after > 1,000 items
+const queuingStrategy = UnixFS.withCapacity()
+
+const settings = UnixFS.configure({
+ fileChunkEncoder: raw,
+ smallFileEncoder: raw,
+ chunker: withMaxChunkSize(1024 * 1024),
+ fileLayout: withWidth(1024),
+})
+
+/**
+ * @param {import('./types.js').BlobLike} blob
+ * @returns {Promise}
+ */
+export async function encodeFile(blob) {
+ const readable = createFileEncoderStream(blob)
+ const blocks = await collect(readable)
+ // @ts-expect-error There is always a root block
+ return { cid: blocks.at(-1).cid, blocks }
+}
+
+/**
+ * @param {import('./types.js').BlobLike} blob
+ * @returns {ReadableStream}
+ */
+export function createFileEncoderStream(blob) {
+ /** @type {TransformStream} */
+ const { readable, writable } = new TransformStream({}, queuingStrategy)
+ const unixfsWriter = UnixFS.createWriter({ writable, settings })
+ const fileBuilder = new UnixFSFileBuilder('', blob)
+ void (async () => {
+ await fileBuilder.finalize(unixfsWriter)
+ await unixfsWriter.close()
+ })()
+ return readable
+}
+
+class UnixFSFileBuilder {
+ #file
+
+ /**
+ * @param {string} name
+ * @param {import('./types.js').BlobLike} file
+ */
+ constructor(name, file) {
+ this.name = name
+ this.#file = file
+ }
+
+ /** @param {import('@ipld/unixfs').View} writer */
+ async finalize(writer) {
+ const unixfsFileWriter = UnixFS.createFileWriter(writer)
+ await this.#file.stream().pipeTo(
+ new WritableStream({
+ async write(chunk) {
+ await unixfsFileWriter.write(chunk)
+ },
+ })
+ )
+ return await unixfsFileWriter.close()
+ }
+}
+
+class UnixFSDirectoryBuilder {
+ #options
+
+ /** @type {Map} */
+ entries = new Map()
+
+ /**
+ * @param {string} name
+ * @param {import('./types.js').UnixFSDirectoryEncoderOptions} [options]
+ */
+ constructor(name, options) {
+ this.name = name
+ this.#options = options
+ }
+
+ /** @param {import('@ipld/unixfs').View} writer */
+ async finalize(writer) {
+ const dirWriter =
+ this.entries.size <= SHARD_THRESHOLD
+ ? UnixFS.createDirectoryWriter(writer)
+ : UnixFS.createShardedDirectoryWriter(writer)
+ for (const [name, entry] of this.entries) {
+ const link = await entry.finalize(writer)
+ if (this.#options?.onDirectoryEntryLink) {
+ // @ts-expect-error
+ this.#options.onDirectoryEntryLink({ name: entry.name, ...link })
+ }
+ dirWriter.set(name, link)
+ }
+ return await dirWriter.close()
+ }
+}
+
+/**
+ * @param {Iterable} files
+ * @param {import('./types.js').UnixFSDirectoryEncoderOptions} [options]
+ * @returns {Promise}
+ */
+export async function encodeDirectory(files, options) {
+ const readable = createDirectoryEncoderStream(files, options)
+ const blocks = await collect(readable)
+ // @ts-expect-error There is always a root block
+ return { cid: blocks.at(-1).cid, blocks }
+}
+
+/**
+ * @param {Iterable} files
+ * @param {import('./types.js').UnixFSDirectoryEncoderOptions} [options]
+ * @returns {ReadableStream}
+ */
+export function createDirectoryEncoderStream(files, options) {
+ const rootDir = new UnixFSDirectoryBuilder('', options)
+
+ for (const file of files) {
+ const path = file.name.split('/')
+ if (path[0] === '' || path[0] === '.') {
+ path.shift()
+ }
+ let dir = rootDir
+ for (const [i, name] of path.entries()) {
+ if (i === path.length - 1) {
+ dir.entries.set(name, new UnixFSFileBuilder(path.join('/'), file))
+ break
+ }
+ let dirBuilder = dir.entries.get(name)
+ if (dirBuilder == null) {
+ const dirName = dir === rootDir ? name : `${dir.name}/${name}`
+ dirBuilder = new UnixFSDirectoryBuilder(dirName, options)
+ dir.entries.set(name, dirBuilder)
+ }
+ if (!(dirBuilder instanceof UnixFSDirectoryBuilder)) {
+ throw new Error(`"${file.name}" cannot be a file and a directory`)
+ }
+ dir = dirBuilder
+ }
+ }
+
+ /** @type {TransformStream} */
+ const { readable, writable } = new TransformStream({}, queuingStrategy)
+ const unixfsWriter = UnixFS.createWriter({ writable, settings })
+ void (async () => {
+ const link = await rootDir.finalize(unixfsWriter)
+ if (options?.onDirectoryEntryLink) {
+ options.onDirectoryEntryLink({ name: '', ...link })
+ }
+ await unixfsWriter.close()
+ })()
+
+ return readable
+}
+
+/**
+ * @template T
+ * @param {ReadableStream} collectable
+ * @returns {Promise}
+ */
+async function collect(collectable) {
+ /** @type {T[]} */
+ const chunks = []
+ await collectable.pipeTo(
+ new WritableStream({
+ write(chunk) {
+ chunks.push(chunk)
+ },
+ })
+ )
+ return chunks
+}
diff --git a/packages/w3up-client/src/client.js b/packages/w3up-client/src/client.js
index 016719d76..a39715b34 100644
--- a/packages/w3up-client/src/client.js
+++ b/packages/w3up-client/src/client.js
@@ -2,7 +2,7 @@ import {
uploadFile,
uploadDirectory,
uploadCAR,
-} from '@web3-storage/upload-client'
+} from './capability/upload/index.js'
import {
Store as StoreCapabilities,
Upload as UploadCapabilities,
@@ -14,13 +14,14 @@ import { StoreClient } from './capability/store.js'
import { UploadClient } from './capability/upload.js'
import { SpaceClient } from './capability/space.js'
import { AccessClient } from './capability/access.js'
+import { AgentData } from './agent.js'
export * as Access from './capability/access.js'
-export { StoreClient, UploadClient, SpaceClient, AccessClient }
+export { StoreClient, UploadClient, SpaceClient, AccessClient, AgentData }
export class Client extends Base {
/**
- * @param {import('@web3-storage/access').AgentData} agentData
+ * @param {AgentData} agentData
* @param {object} [options]
* @param {import('./types.js').ServiceConf} [options.serviceConf]
*/
diff --git a/packages/access-client/src/crypto/aes-key.js b/packages/w3up-client/src/crypto/aes-key.js
similarity index 100%
rename from packages/access-client/src/crypto/aes-key.js
rename to packages/w3up-client/src/crypto/aes-key.js
diff --git a/packages/access-client/src/crypto/encoding.js b/packages/w3up-client/src/crypto/encoding.js
similarity index 100%
rename from packages/access-client/src/crypto/encoding.js
rename to packages/w3up-client/src/crypto/encoding.js
diff --git a/packages/access-client/src/crypto/p256-ecdh.js b/packages/w3up-client/src/crypto/p256-ecdh.js
similarity index 100%
rename from packages/access-client/src/crypto/p256-ecdh.js
rename to packages/w3up-client/src/crypto/p256-ecdh.js
diff --git a/packages/access-client/src/crypto/types.ts b/packages/w3up-client/src/crypto/types.ts
similarity index 100%
rename from packages/access-client/src/crypto/types.ts
rename to packages/w3up-client/src/crypto/types.ts
diff --git a/packages/w3up-client/src/driver/conf.js b/packages/w3up-client/src/driver/conf.js
new file mode 100644
index 000000000..f65fd2195
--- /dev/null
+++ b/packages/w3up-client/src/driver/conf.js
@@ -0,0 +1,73 @@
+import Conf from 'conf'
+import * as JSON from '../agent/utils/json.js'
+
+/**
+ * @template T
+ * @typedef {import('./types.js').Driver} Driver
+ */
+
+/**
+ * Driver implementation with "[conf](https://github.com/sindresorhus/conf)"
+ *
+ * Usage:
+ *
+ * ```js
+ * import { ConfDriver } from '@web3-storage/access/drivers/conf'
+ * ```
+ *
+ * @template {Record} T
+ * @implements {Driver}
+ */
+export class ConfDriver {
+ /**
+ * @type {Conf}
+ */
+ #config
+
+ /**
+ * @param {{ profile: string }} opts
+ */
+ constructor(opts) {
+ this.#config = new Conf({
+ projectName: 'w3access',
+ projectSuffix: '',
+ configName: opts.profile,
+ serialize: (v) => JSON.stringify(v),
+ deserialize: (v) => JSON.parse(v),
+ })
+ this.path = this.#config.path
+ }
+
+ /* c8 ignore next */
+ async open() {}
+
+ /* c8 ignore next */
+ async close() {}
+
+ /* c8 ignore next 3 */
+ async reset() {
+ this.#config.clear()
+ }
+
+ /** @param {T} data */
+ async save(data) {
+ if (typeof data === 'object') {
+ data = { ...data }
+ for (const [k, v] of Object.entries(data)) {
+ if (v === undefined) {
+ delete data[k]
+ }
+ }
+ }
+ this.#config.set(data)
+ }
+
+ /** @returns {Promise} */
+ async load() {
+ const data =
+ /* c8 ignore next */
+ this.#config.store ?? {}
+ if (Object.keys(data).length === 0) return
+ return data
+ }
+}
diff --git a/packages/w3up-client/src/driver/indexed-db.js b/packages/w3up-client/src/driver/indexed-db.js
new file mode 100644
index 000000000..4ee22a20a
--- /dev/null
+++ b/packages/w3up-client/src/driver/indexed-db.js
@@ -0,0 +1,194 @@
+import defer from 'p-defer'
+
+/**
+ * @template T
+ * @typedef {import('./types.js').Driver} Driver
+ */
+
+const STORE_NAME = 'AccessStore'
+const DATA_ID = 1
+
+/* c8 ignore next 150 */
+/**
+ * Driver implementation for the browser.
+ *
+ * Usage:
+ *
+ * ```js
+ * import { IndexedDBDriver } from '@web3-storage/w3up-client/driver/indexed-db'
+ * ```
+ *
+ * @template T
+ * @implements {Driver}
+ */
+export class IndexedDBDriver {
+ /** @type {string} */
+ #dbName
+
+ /** @type {number|undefined} */
+ #dbVersion
+
+ /** @type {string} */
+ #dbStoreName
+
+ /** @type {IDBDatabase|undefined} */
+ #db
+
+ /** @type {boolean} */
+ #autoOpen
+
+ /**
+ * @param {string} dbName
+ * @param {object} [options]
+ * @param {number} [options.dbVersion]
+ * @param {string} [options.dbStoreName]
+ * @param {boolean} [options.autoOpen]
+ */
+ constructor(dbName, options = {}) {
+ this.#dbName = dbName
+ this.#dbVersion = options.dbVersion
+ this.#dbStoreName = options.dbStoreName ?? STORE_NAME
+ this.#autoOpen = options.autoOpen ?? true
+ }
+
+ /** @returns {Promise} */
+ async #getOpenDB() {
+ if (!this.#db) {
+ if (!this.#autoOpen) throw new Error('Store is not open')
+ await this.open()
+ }
+ // @ts-expect-error open sets this.#db
+ return this.#db
+ }
+
+ async open() {
+ const db = this.#db
+ if (db) return
+
+ /** @type {import('p-defer').DeferredPromise} */
+ const { resolve, reject, promise } = defer()
+ const openReq = indexedDB.open(this.#dbName, this.#dbVersion)
+
+ openReq.addEventListener('upgradeneeded', () => {
+ const db = openReq.result
+ db.createObjectStore(this.#dbStoreName, { keyPath: 'id' })
+ })
+
+ openReq.addEventListener('success', () => {
+ this.#db = openReq.result
+ resolve()
+ })
+
+ openReq.addEventListener('error', () => reject(openReq.error))
+
+ return promise
+ }
+
+ async close() {
+ const db = this.#db
+ if (!db) throw new Error('Store is not open')
+
+ db.close()
+ this.#db = undefined
+ }
+
+ /** @param {T} data */
+ async save(data) {
+ const db = await this.#getOpenDB()
+
+ const putData = withObjectStore(
+ db,
+ 'readwrite',
+ this.#dbStoreName,
+ async (store) => {
+ /** @type {import('p-defer').DeferredPromise} */
+ const { resolve, reject, promise } = defer()
+ const putReq = store.put({ id: DATA_ID, ...data })
+ putReq.addEventListener('success', () => resolve())
+ putReq.addEventListener('error', () =>
+ reject(new Error('failed to query DB', { cause: putReq.error }))
+ )
+
+ return promise
+ }
+ )
+
+ return await putData()
+ }
+
+ async load() {
+ const db = await this.#getOpenDB()
+
+ const getData = withObjectStore(
+ db,
+ 'readonly',
+ this.#dbStoreName,
+ async (store) => {
+ /** @type {import('p-defer').DeferredPromise} */
+ const { resolve, reject, promise } = defer()
+
+ const getReq = store.get(DATA_ID)
+ getReq.addEventListener('success', () => resolve(getReq.result))
+ getReq.addEventListener('error', () =>
+ reject(new Error('failed to query DB', { cause: getReq.error }))
+ )
+
+ return promise
+ }
+ )
+
+ return await getData()
+ }
+
+ async reset() {
+ const db = await this.#getOpenDB()
+
+ withObjectStore(db, 'readwrite', this.#dbStoreName, (s) => {
+ /** @type {import('p-defer').DeferredPromise} */
+ const { resolve, reject, promise } = defer()
+ const req = s.clear()
+ req.addEventListener('success', () => {
+ resolve()
+ })
+
+ req.addEventListener('error', () =>
+ reject(new Error('failed to query DB', { cause: req.error }))
+ )
+
+ return promise
+ })
+ }
+}
+
+/* c8 ignore next 35 */
+/**
+ * @template T
+ * @param {IDBDatabase} db
+ * @param {IDBTransactionMode} txnMode
+ * @param {string} storeName
+ * @param {(s: IDBObjectStore) => Promise} fn
+ */
+function withObjectStore(db, txnMode, storeName, fn) {
+ return async () => {
+ const tx = db.transaction(storeName, txnMode)
+ /** @type {import('p-defer').DeferredPromise} */
+ const { resolve, reject, promise } = defer()
+ /** @type {T} */
+ let result
+ tx.addEventListener('complete', () => resolve(result))
+ tx.addEventListener('abort', () =>
+ reject(tx.error || new Error('transaction aborted'))
+ )
+ tx.addEventListener('error', () =>
+ reject(new Error('transaction error', { cause: tx.error }))
+ )
+ try {
+ result = await fn(tx.objectStore(storeName))
+ tx.commit()
+ } catch (error) {
+ reject(error)
+ tx.abort()
+ }
+ return promise
+ }
+}
diff --git a/packages/w3up-client/src/driver/memory.js b/packages/w3up-client/src/driver/memory.js
new file mode 100644
index 000000000..dc4c785fb
--- /dev/null
+++ b/packages/w3up-client/src/driver/memory.js
@@ -0,0 +1,51 @@
+/**
+ * @template T
+ * @typedef {import('./types.js').Driver} Driver
+ */
+
+/**
+ * Driver implementation that stores data in memory."
+ *
+ * Usage:
+ *
+ * ```js
+ * import { MemoryDriver } from '@web3-storage/access/drivers/memory'
+ * ```
+ *
+ * @template {Record} T
+ * @implements {Driver}
+ */
+export class MemoryDriver {
+ /**
+ * @type {T|undefined}
+ */
+ #data
+
+ constructor() {
+ this.#data = undefined
+ }
+
+ /* c8 ignore next */
+ async open() {}
+
+ /* c8 ignore next */
+ async close() {}
+
+ /* c8 ignore next 3 */
+ async reset() {
+ this.#data = undefined
+ }
+
+ /** @param {T} data */
+ async save(data) {
+ this.#data = { ...data }
+ }
+
+ /** @returns {Promise} */
+ async load() {
+ /* c8 ignore next 3 */
+ if (this.#data === undefined) return
+ if (Object.keys(this.#data).length === 0) return
+ return this.#data
+ }
+}
diff --git a/packages/access-client/src/drivers/types.ts b/packages/w3up-client/src/driver/types.ts
similarity index 100%
rename from packages/access-client/src/drivers/types.ts
rename to packages/w3up-client/src/driver/types.ts
diff --git a/packages/w3up-client/src/index.js b/packages/w3up-client/src/index.js
index 9b5d4f85b..10b889d69 100644
--- a/packages/w3up-client/src/index.js
+++ b/packages/w3up-client/src/index.js
@@ -5,10 +5,11 @@
*
* @module
*/
-import { AgentData } from '@web3-storage/access/agent'
-import { StoreIndexedDB } from '@web3-storage/access/stores/store-indexeddb'
+import { AgentData } from './agent.js'
+import { StoreIndexedDB } from './store/indexed-db.js'
import { generate } from '@ucanto/principal/rsa'
import { Client } from './client.js'
+export * from './types.js'
/**
* Create a new w3up client.
@@ -25,6 +26,7 @@ import { Client } from './client.js'
* @type {import('./types.js').ClientFactory}
*/
export async function create(options = {}) {
+ /* c8 ignore next 11 */
const store = options.store ?? new StoreIndexedDB('w3up-client')
const raw = await store.load()
if (raw) {
diff --git a/packages/w3up-client/src/index.node.js b/packages/w3up-client/src/index.node.js
index 2778d4b16..caedbd8ea 100644
--- a/packages/w3up-client/src/index.node.js
+++ b/packages/w3up-client/src/index.node.js
@@ -2,10 +2,9 @@
* @hidden
* @module
*/
-import { AgentData } from '@web3-storage/access/agent'
-import { StoreConf } from '@web3-storage/access/stores/store-conf'
import { generate } from '@ucanto/principal/ed25519'
-import { Client } from './client.js'
+import { Client, AgentData } from './client.js'
+import { StoreConf } from './store/conf.js'
/**
* Create a new w3up client.
diff --git a/packages/w3up-client/src/space.js b/packages/w3up-client/src/space.js
index 7593ccfaa..769810f01 100644
--- a/packages/w3up-client/src/space.js
+++ b/packages/w3up-client/src/space.js
@@ -1,4 +1,4 @@
-export * from '@web3-storage/access/space'
+export * from './capability/space.js'
export class Space {
/** @type {import('./types.js').DID} */
diff --git a/packages/access-client/src/stores/store-conf.js b/packages/w3up-client/src/store/conf.js
similarity index 86%
rename from packages/access-client/src/stores/store-conf.js
rename to packages/w3up-client/src/store/conf.js
index b0d91b218..7b89a7d9d 100644
--- a/packages/access-client/src/stores/store-conf.js
+++ b/packages/w3up-client/src/store/conf.js
@@ -1,4 +1,4 @@
-import { ConfDriver } from '../drivers/conf.js'
+import { ConfDriver } from '../driver/conf.js'
/**
* Store implementation with "[conf](https://github.com/sindresorhus/conf)"
diff --git a/packages/w3up-client/src/store/indexed-db.js b/packages/w3up-client/src/store/indexed-db.js
new file mode 100644
index 000000000..eb316b816
--- /dev/null
+++ b/packages/w3up-client/src/store/indexed-db.js
@@ -0,0 +1,14 @@
+import { IndexedDBDriver } from '../driver/indexed-db.js'
+
+/**
+ * Store implementation for the browser.
+ *
+ * Usage:
+ *
+ * ```js
+ * import { StoreIndexedDB } from '@web3-storage/access/stores/store-indexeddb'
+ * ```
+ *
+ * @extends {IndexedDBDriver}
+ */
+export class StoreIndexedDB extends IndexedDBDriver {}
diff --git a/packages/access-client/src/stores/store-memory.js b/packages/w3up-client/src/store/memory.js
similarity index 84%
rename from packages/access-client/src/stores/store-memory.js
rename to packages/w3up-client/src/store/memory.js
index e9b40df55..29137fa9b 100644
--- a/packages/access-client/src/stores/store-memory.js
+++ b/packages/w3up-client/src/store/memory.js
@@ -1,4 +1,4 @@
-import { MemoryDriver } from '../drivers/memory.js'
+import { MemoryDriver } from '../driver/memory.js'
/**
* Store implementation with in-memory storage
diff --git a/packages/w3up-client/src/types.ts b/packages/w3up-client/src/types.ts
index 5201c7109..f8f552f4c 100644
--- a/packages/w3up-client/src/types.ts
+++ b/packages/w3up-client/src/types.ts
@@ -1,9 +1,9 @@
-import { type Driver } from '@web3-storage/access/drivers/types'
+import { type Driver } from './driver/types.js'
import {
type Service as AccessService,
type AgentDataExport,
-} from '@web3-storage/access/types'
-import { type Service as UploadService } from '@web3-storage/upload-client/types'
+} from './agent/types.js'
+import { type Service as UploadService } from './capability/upload/types.js'
import type {
ConnectionView,
Signer,
@@ -12,16 +12,36 @@ import type {
Resource,
Unit,
} from '@ucanto/interface'
+export type { UTCUnixTimestamp } from '@ipld/dag-ucan'
import { type Client } from './client.js'
export * from '@ucanto/interface'
export * from '@web3-storage/did-mailto'
-export type { Agent, CapabilityQuery } from '@web3-storage/access/agent'
+export type {
+ Agent,
+ CapabilityQuery,
+ BytesDelegation,
+ SpaceInfoResult,
+ EncodedDelegation,
+} from './agent/types.js'
+
+export * from './capability/upload/types.js'
+export * from './agent/types.js'
+export type { DelegationOptions } from './agent/types.js'
+
export type {
Access,
AccountDID,
ProviderDID,
SpaceDID,
-} from '@web3-storage/access/types'
+ UCANRevoke,
+ UCANRevokeSuccess,
+ UCANRevokeFailure,
+} from './agent/types.js'
+
+export type { Service as UploadService } from './capability/upload/types.js'
+export type { Service as AccessService } from './agent/types.js'
+
+export type Service = AccessService & UploadService
export type ProofQuery = Record>
@@ -81,7 +101,7 @@ export type {
AgentDataExport,
AgentMeta,
DelegationMeta,
-} from '@web3-storage/access/types'
+} from './agent/types.js'
export type {
StoreAddSuccess,
@@ -108,4 +128,4 @@ export type {
UploadDirectoryOptions,
FileLike,
BlobLike,
-} from '@web3-storage/upload-client/types'
+} from './capability/upload/types.js'
diff --git a/packages/w3up-client/test/access.test.js b/packages/w3up-client/test/access.test.js
index d4f72b01e..2562f3c87 100644
--- a/packages/w3up-client/test/access.test.js
+++ b/packages/w3up-client/test/access.test.js
@@ -22,10 +22,12 @@ export const testAccess = {
assert.deepEqual(request.audience, client.did())
assert.ok(request.expiration.getTime() >= Date.now())
+ assert.ok(request.request.toString().startsWith('bafy'))
const access = Result.try(await request.claim())
assert.deepEqual(access.authority, client.did())
assert.ok(access.proofs.length > 0)
+ assert.ok(access.provider, client.did())
const proofs = client.proofs()
assert.deepEqual(proofs.length, 0)
diff --git a/packages/w3up-client/test/account.test.js b/packages/w3up-client/test/account.test.js
index 8170dad20..9829341ad 100644
--- a/packages/w3up-client/test/account.test.js
+++ b/packages/w3up-client/test/account.test.js
@@ -1,6 +1,6 @@
import * as Test from './test.js'
+import * as Space from '../src/capability/space.js'
import * as Account from '../src/account.js'
-import * as Space from '../src/space.js'
import * as Result from '../src/result.js'
/**
@@ -72,6 +72,8 @@ export const testAccount = {
{ client, mail, grantAccess }
) => {
const space = await client.createSpace('test')
+ assert.deepEqual(space.name, 'test')
+ assert.deepEqual(space.withName('another').name, 'another')
const mnemonic = space.toMnemonic()
const { signer } = await Space.fromMnemonic(mnemonic, { name: 'import' })
assert.deepEqual(
@@ -96,6 +98,9 @@ export const testAccount = {
expiration: Infinity,
})
+ const importedSpace = Space.fromDelegation(proof)
+ assert.deepEqual(importedSpace.name, 'test')
+
await client.addSpace(proof)
const info = await client.capability.space.info(space.did())
diff --git a/packages/access-client/test/agent.test.js b/packages/w3up-client/test/agent.test.js
similarity index 97%
rename from packages/access-client/test/agent.test.js
rename to packages/w3up-client/test/agent.test.js
index df10989d3..ea349abe6 100644
--- a/packages/access-client/test/agent.test.js
+++ b/packages/w3up-client/test/agent.test.js
@@ -1,8 +1,8 @@
import assert from 'assert'
-import * as ucanto from '@ucanto/core'
-import { URI } from '@ucanto/validator'
+import { Schema, delegate } from '@ucanto/core'
import { Delegation, provide } from '@ucanto/server'
-import { Agent, Access, AgentData, connection } from '../src/agent.js'
+import { Agent, AgentData, connection } from '../src/agent.js'
+import * as Access from '../src/capability/access.js'
import * as Space from '@web3-storage/capabilities/space'
import { createServer } from './helpers/utils.js'
import * as fixtures from './helpers/fixtures.js'
@@ -199,12 +199,12 @@ describe('Agent', function () {
async () => {
await agent.invokeAndExecute(Space.info, {
audience: fixtures.service,
- with: URI.from(fixtures.alice.did()),
+ with: Schema.URI.from(fixtures.alice.did()),
})
},
{
name: 'Error',
- message: `no proofs available for resource ${URI.from(
+ message: `no proofs available for resource ${Schema.URI.from(
fixtures.alice.did()
)} and ability space/info`,
}
@@ -429,7 +429,7 @@ describe('Agent', function () {
const services = [serviceAWeb, serviceBWeb]
for (const service of services) {
// note: these delegations will have the same CID regardless of `service`
- const delegation = await ucanto.delegate({
+ const delegation = await delegate({
issuer: Absentee.from({ id: account }),
audience: agent,
capabilities: [
@@ -483,7 +483,7 @@ describe('Agent', function () {
const services = [serviceAWeb, serviceBWeb]
for (const service of services) {
const nonce = (await ed25519.Signer.generate()).did()
- const delegation = await ucanto.delegate({
+ const delegation = await delegate({
issuer: Absentee.from({ id: account }),
audience: agent,
capabilities: [
diff --git a/packages/access-client/test/agent-data.test.js b/packages/w3up-client/test/agent/data.test.js
similarity index 97%
rename from packages/access-client/test/agent-data.test.js
rename to packages/w3up-client/test/agent/data.test.js
index 5931451c7..026c48c67 100644
--- a/packages/access-client/test/agent-data.test.js
+++ b/packages/w3up-client/test/agent/data.test.js
@@ -1,5 +1,5 @@
import assert from 'assert'
-import { AgentData, getSessionProofs } from '../src/agent-data.js'
+import { AgentData, getSessionProofs } from '../../src/agent.js'
import * as ed25519 from '@ucanto/principal/ed25519'
import { UCAN } from '@web3-storage/capabilities'
import { Absentee } from '@ucanto/principal'
diff --git a/packages/access-client/test/agent-use-cases.test.js b/packages/w3up-client/test/agent/use-cases.test.js
similarity index 97%
rename from packages/access-client/test/agent-use-cases.test.js
rename to packages/w3up-client/test/agent/use-cases.test.js
index 9783c9b51..a455ed52f 100644
--- a/packages/access-client/test/agent-use-cases.test.js
+++ b/packages/w3up-client/test/agent/use-cases.test.js
@@ -7,16 +7,16 @@ import * as Ucan from '@web3-storage/capabilities/ucan'
import * as Space from '@web3-storage/capabilities/space'
import * as Plan from '@web3-storage/capabilities/plan'
import { createAuthorization } from '@web3-storage/capabilities/test/helpers/utils'
-import { Agent, connection } from '../src/agent.js'
+import { Agent, connection } from '../../src/agent.js'
import {
delegationsIncludeSessionProof,
authorizeWaitAndClaim,
waitForAuthorizationByPolling,
getAccountPlan,
-} from '../src/agent-use-cases.js'
-import { createServer } from './helpers/utils.js'
-import * as fixtures from './helpers/fixtures.js'
-import { delegationsToBytes } from '../src/encoding.js'
+} from '../../src/agent.js'
+import { createServer } from '../helpers/utils.js'
+import * as fixtures from '../helpers/fixtures.js'
+import { delegationsToBytes } from '../../src/agent/encoding.js'
describe('delegationsIncludeSessionProof', function () {
it('should return true if and only if one of the delegations is a session proof', async function () {
diff --git a/packages/w3up-client/test/capability/access.test.js b/packages/w3up-client/test/capability/access.test.js
index 29044500b..f60b42243 100644
--- a/packages/w3up-client/test/capability/access.test.js
+++ b/packages/w3up-client/test/capability/access.test.js
@@ -3,9 +3,8 @@ import { create as createServer, provide } from '@ucanto/server'
import * as CAR from '@ucanto/transport/car'
import * as Signer from '@ucanto/principal/ed25519'
import * as AccessCapabilities from '@web3-storage/capabilities/access'
-import { AgentData } from '@web3-storage/access/agent'
import { mockService, mockServiceConf } from '../helpers/mocks.js'
-import { Client } from '../../src/client.js'
+import { Client, AgentData } from '../../src/client.js'
import { validateAuthorization } from '../helpers/utils.js'
describe('AccessClient', () => {
diff --git a/packages/w3up-client/test/capability/space.test.js b/packages/w3up-client/test/capability/space.test.js
index d6519de2c..5ffeaf87a 100644
--- a/packages/w3up-client/test/capability/space.test.js
+++ b/packages/w3up-client/test/capability/space.test.js
@@ -3,9 +3,8 @@ import { create as createServer, provide } from '@ucanto/server'
import * as CAR from '@ucanto/transport/car'
import * as Signer from '@ucanto/principal/ed25519'
import * as SpaceCapabilities from '@web3-storage/capabilities/space'
-import { AgentData } from '@web3-storage/access/agent'
import { mockService, mockServiceConf } from '../helpers/mocks.js'
-import { Client } from '../../src/client.js'
+import { Client, AgentData } from '../../src/client.js'
import { validateAuthorization } from '../helpers/utils.js'
describe('SpaceClient', () => {
diff --git a/packages/w3up-client/test/capability/store.test.js b/packages/w3up-client/test/capability/store.test.js
index 12f7b367a..3f82eb214 100644
--- a/packages/w3up-client/test/capability/store.test.js
+++ b/packages/w3up-client/test/capability/store.test.js
@@ -3,10 +3,9 @@ import { create as createServer, provide } from '@ucanto/server'
import * as CAR from '@ucanto/transport/car'
import * as Signer from '@ucanto/principal/ed25519'
import { Store as StoreCapabilities } from '@web3-storage/capabilities'
-import { AgentData } from '@web3-storage/access/agent'
import { randomCAR } from '../helpers/random.js'
import { mockService, mockServiceConf } from '../helpers/mocks.js'
-import { Client } from '../../src/client.js'
+import { Client, AgentData } from '../../src/client.js'
import { validateAuthorization } from '../helpers/utils.js'
describe('StoreClient', () => {
diff --git a/packages/w3up-client/test/capability/upload.test.js b/packages/w3up-client/test/capability/upload.test.js
index 55ca559e0..03d0ab8d7 100644
--- a/packages/w3up-client/test/capability/upload.test.js
+++ b/packages/w3up-client/test/capability/upload.test.js
@@ -3,10 +3,9 @@ import { create as createServer, provide } from '@ucanto/server'
import * as CAR from '@ucanto/transport/car'
import * as Signer from '@ucanto/principal/ed25519'
import { Upload as UploadCapabilities } from '@web3-storage/capabilities'
-import { AgentData } from '@web3-storage/access/agent'
import { randomCAR } from '../helpers/random.js'
import { mockService, mockServiceConf } from '../helpers/mocks.js'
-import { Client } from '../../src/client.js'
+import { Client, AgentData } from '../../src/client.js'
import { validateAuthorization } from '../helpers/utils.js'
describe('StoreClient', () => {
diff --git a/packages/upload-client/test/car.test.js b/packages/w3up-client/test/car.test.js
similarity index 94%
rename from packages/upload-client/test/car.test.js
rename to packages/w3up-client/test/car.test.js
index c23462b02..3ed9882ae 100644
--- a/packages/upload-client/test/car.test.js
+++ b/packages/w3up-client/test/car.test.js
@@ -1,6 +1,6 @@
import assert from 'assert'
import { CID } from 'multiformats'
-import { BlockStream, encode } from '../src/car.js'
+import { BlockStream, encode } from '../src/capability/upload/car.js'
import { toCAR } from './helpers/car.js'
import { randomBytes } from './helpers/random.js'
diff --git a/packages/w3up-client/test/client.test.js b/packages/w3up-client/test/client.test.js
index c010a6550..b57f70f97 100644
--- a/packages/w3up-client/test/client.test.js
+++ b/packages/w3up-client/test/client.test.js
@@ -5,7 +5,7 @@ import * as Signer from '@ucanto/principal/ed25519'
import * as StoreCapabilities from '@web3-storage/capabilities/store'
import * as UploadCapabilities from '@web3-storage/capabilities/upload'
import * as UCANCapabilities from '@web3-storage/capabilities/ucan'
-import { AgentData } from '@web3-storage/access/agent'
+import { AgentData } from '../src/agent.js'
import { randomBytes, randomCAR } from './helpers/random.js'
import { toCAR } from './helpers/car.js'
import { mockService, mockServiceConf } from './helpers/mocks.js'
@@ -20,7 +20,7 @@ describe('Client', () => {
const file = new Blob([bytes])
const expectedCar = await toCAR(bytes)
- /** @type {import('@web3-storage/upload-client/types').CARLink|undefined} */
+ /** @type {import('../src/index.js').CARLink|undefined} */
let carCID
const service = mockService({
@@ -37,7 +37,7 @@ describe('Client', () => {
status: 'upload',
headers: { 'x-test': 'true' },
url: 'http://localhost:9200',
- link: /** @type {import('@web3-storage/upload-client/types').CARLink} */ (
+ link: /** @type {import('@web3-storage/w3up-client').CARLink} */ (
invocation.capabilities[0].nb?.link
),
with: space.did(),
@@ -116,7 +116,7 @@ describe('Client', () => {
new File([await randomBytes(32)], '2.txt'),
]
- /** @type {import('@web3-storage/upload-client/types').CARLink|undefined} */
+ /** @type {import('@web3-storage/w3up-client').CARLink|undefined} */
let carCID
const service = mockService({
@@ -132,7 +132,7 @@ describe('Client', () => {
status: 'upload',
headers: { 'x-test': 'true' },
url: 'http://localhost:9200',
- link: /** @type {import('@web3-storage/upload-client/types').CARLink} */ (
+ link: /** @type {import('@web3-storage/w3up-client').CARLink} */ (
invocation.capabilities[0].nb?.link
),
with: space.did(),
@@ -210,7 +210,7 @@ describe('Client', () => {
status: 'upload',
headers: { 'x-test': 'true' },
url: 'http://localhost:9200',
- link: /** @type {import('@web3-storage/upload-client/types').CARLink} */ (
+ link: /** @type {import('@web3-storage/w3up-client').CARLink} */ (
invocation.capabilities[0].nb?.link
),
with: space.did(),
diff --git a/packages/access-client/test/drivers/conf.node.test.js b/packages/w3up-client/test/drivers/conf.node.test.js
similarity index 93%
rename from packages/access-client/test/drivers/conf.node.test.js
rename to packages/w3up-client/test/drivers/conf.node.test.js
index fc4166a48..60c271ea6 100644
--- a/packages/access-client/test/drivers/conf.node.test.js
+++ b/packages/w3up-client/test/drivers/conf.node.test.js
@@ -1,6 +1,6 @@
import assert from 'assert'
import { Buffer } from 'node:buffer'
-import { ConfDriver } from '../../src/drivers/conf.js'
+import { ConfDriver } from '../../src/driver/conf.js'
describe('Conf driver', () => {
it('should not fail on to store undefined value', async () => {
diff --git a/packages/access-client/test/encoding.test.js b/packages/w3up-client/test/encoding.test.js
similarity index 99%
rename from packages/access-client/test/encoding.test.js
rename to packages/w3up-client/test/encoding.test.js
index 7d7d48291..b961da8d6 100644
--- a/packages/access-client/test/encoding.test.js
+++ b/packages/w3up-client/test/encoding.test.js
@@ -7,7 +7,7 @@ import {
delegationsToBytes,
delegationsToString,
stringToDelegations,
-} from '../src/encoding.js'
+} from '../src/agent/encoding.js'
describe('Encoding', function () {
it('delegationsToBytes should fail with empty array', async function () {
diff --git a/packages/upload-client/test/helpers/block.js b/packages/w3up-client/test/helpers/block.js
similarity index 100%
rename from packages/upload-client/test/helpers/block.js
rename to packages/w3up-client/test/helpers/block.js
diff --git a/packages/access-client/test/helpers/fixtures.js b/packages/w3up-client/test/helpers/fixtures.js
similarity index 92%
rename from packages/access-client/test/helpers/fixtures.js
rename to packages/w3up-client/test/helpers/fixtures.js
index 5f03c4314..de9b2d243 100644
--- a/packages/access-client/test/helpers/fixtures.js
+++ b/packages/w3up-client/test/helpers/fixtures.js
@@ -13,6 +13,7 @@ export const mallory = Signer.parse(
'MgCYtH0AvYxiQwBG6+ZXcwlXywq9tI50G2mCAUJbwrrahkO0B0elFYkl3Ulf3Q3A/EvcVY0utb4etiSE8e6pi4H0FEmU='
)
+/** did:key:z6MkrZ1r5XBFZjBU34qyD8fueMbMRkKw17BZaq2ivKFjnz2z */
export const service = Signer.parse(
'MgCYKXoHVy7Vk4/QjcEGi+MCqjntUiasxXJ8uJKY0qh11e+0Bs8WsdqGK7xothgrDzzWD0ME7ynPjz2okXDh8537lId8='
)
diff --git a/packages/w3up-client/test/helpers/mocks.js b/packages/w3up-client/test/helpers/mocks.js
index 7f98a3544..609dfbf24 100644
--- a/packages/w3up-client/test/helpers/mocks.js
+++ b/packages/w3up-client/test/helpers/mocks.js
@@ -8,23 +8,25 @@ const notImplemented = () => {
/**
* @param {Partial<{
- * access: Partial
- * provider: Partial
- * store: Partial
- * upload: Partial
- * space: Partial
- * ucan: Partial
+ * access: Partial
+ * provider: Partial
+ * store: Partial
+ * upload: Partial
+ * space: Partial
+ * ucan: Partial
* }>} impl
*/
export function mockService(impl) {
return {
store: {
add: withCallCount(impl.store?.add ?? notImplemented),
+ get: withCallCount(impl.store?.get ?? notImplemented),
list: withCallCount(impl.store?.list ?? notImplemented),
remove: withCallCount(impl.store?.remove ?? notImplemented),
},
upload: {
add: withCallCount(impl.upload?.add ?? notImplemented),
+ get: withCallCount(impl.upload?.get ?? notImplemented),
list: withCallCount(impl.upload?.list ?? notImplemented),
remove: withCallCount(impl.upload?.remove ?? notImplemented),
},
diff --git a/packages/w3up-client/test/helpers/random.js b/packages/w3up-client/test/helpers/random.js
index b7e8aedb4..b2aafd1ce 100644
--- a/packages/w3up-client/test/helpers/random.js
+++ b/packages/w3up-client/test/helpers/random.js
@@ -1,4 +1,5 @@
import { toCAR } from './car.js'
+import { toBlock } from './block.js'
/** @param {number} size */
export async function randomBytes(size) {
@@ -29,3 +30,9 @@ export async function randomCAR(size) {
const bytes = await randomBytes(size)
return toCAR(bytes)
}
+
+/** @param {number} size */
+export async function randomBlock(size) {
+ const bytes = await randomBytes(size)
+ return await toBlock(bytes)
+}
diff --git a/packages/w3up-client/test/helpers/utils.js b/packages/w3up-client/test/helpers/utils.js
index c173e7e70..972441c10 100644
--- a/packages/w3up-client/test/helpers/utils.js
+++ b/packages/w3up-client/test/helpers/utils.js
@@ -1 +1,66 @@
+// eslint-disable-next-line no-unused-vars
+import * as Ucanto from '@ucanto/interface'
+import { parseLink } from '@ucanto/core'
+import * as Server from '@ucanto/server'
+import * as Space from '@web3-storage/capabilities/space'
+import * as CAR from '@ucanto/transport/car'
+import * as CBOR from '@ucanto/core/cbor'
+import { service } from './fixtures.js'
+
+/**
+ * @param {string} source
+ */
+export function parseCarLink(source) {
+ return /** @type {Ucanto.Link} */ (parseLink(source))
+}
+
+/**
+ * @param {any} data
+ */
+export async function createCborCid(data) {
+ const cbor = await CBOR.write(data)
+ return cbor.cid
+}
+
+/**
+ * @param {string} source
+ */
+export async function createCarCid(source) {
+ const cbor = await CBOR.write({ hello: source })
+ const shard = await CAR.codec.write({ roots: [cbor] })
+ return shard.cid
+}
+
+/**
+ * @param {object} handlers - a map of keys to capability handler maps
+ * @returns {Ucanto.ServerView}
+ */
+export function createServer(handlers = {}) {
+ const server = Server.create({
+ id: service,
+ codec: CAR.inbound,
+ service: {
+ space: {
+ info: Server.provide(Space.info, async ({ capability }) => {
+ return {
+ ok: {
+ did: 'did:key:sss',
+ agent: 'did:key:agent',
+ email: 'mail@mail.com',
+ product: 'product:free',
+ updated_at: 'sss',
+ inserted_at: 'date',
+ },
+ }
+ }),
+ },
+ ...handlers,
+ },
+ validateAuthorization,
+ })
+
+ // @ts-ignore
+ return server
+}
+
export const validateAuthorization = () => ({ ok: {} })
diff --git a/packages/w3up-client/test/index.node.test.js b/packages/w3up-client/test/index.node.test.js
index f9f263c2b..6b801dfcf 100644
--- a/packages/w3up-client/test/index.node.test.js
+++ b/packages/w3up-client/test/index.node.test.js
@@ -1,7 +1,7 @@
import assert from 'assert'
import { Signer } from '@ucanto/principal/ed25519'
import { EdDSA } from '@ipld/dag-ucan/signature'
-import { StoreConf } from '@web3-storage/access/stores/store-conf'
+import { StoreConf } from '../src/store/conf.js'
import { create } from '../src/index.node.js'
describe('create', () => {
diff --git a/packages/upload-client/test/sharding.test.js b/packages/w3up-client/test/sharding.test.js
similarity index 96%
rename from packages/upload-client/test/sharding.test.js
rename to packages/w3up-client/test/sharding.test.js
index a40f22f9e..d8b7a31f7 100644
--- a/packages/upload-client/test/sharding.test.js
+++ b/packages/w3up-client/test/sharding.test.js
@@ -1,7 +1,7 @@
import assert from 'assert'
import { CID } from 'multiformats'
-import { createFileEncoderStream } from '../src/unixfs.js'
-import { ShardingStream } from '../src/sharding.js'
+import { createFileEncoderStream } from '../src/capability/upload/unixfs.js'
+import { ShardingStream } from '../src/capability/upload/sharding.js'
import { randomBlock, randomBytes } from './helpers/random.js'
describe('ShardingStream', () => {
diff --git a/packages/upload-client/test/store.test.js b/packages/w3up-client/test/store.test.js
similarity index 99%
rename from packages/upload-client/test/store.test.js
rename to packages/w3up-client/test/store.test.js
index c6e81f84b..65040bc13 100644
--- a/packages/upload-client/test/store.test.js
+++ b/packages/w3up-client/test/store.test.js
@@ -5,8 +5,8 @@ import { provide } from '@ucanto/server'
import * as CAR from '@ucanto/transport/car'
import * as Signer from '@ucanto/principal/ed25519'
import * as StoreCapabilities from '@web3-storage/capabilities/store'
-import * as Store from '../src/store.js'
-import { serviceSigner } from './fixtures.js'
+import * as Store from '../src/capability/store.js'
+import { service as serviceSigner } from './helpers/fixtures.js'
import { randomCAR } from './helpers/random.js'
import { mockService } from './helpers/mocks.js'
import { validateAuthorization } from './helpers/utils.js'
diff --git a/packages/access-client/test/stores/store-indexeddb.browser.test.js b/packages/w3up-client/test/stores/store-indexeddb.browser.test.js
similarity index 96%
rename from packages/access-client/test/stores/store-indexeddb.browser.test.js
rename to packages/w3up-client/test/stores/store-indexeddb.browser.test.js
index 81495e132..d7dd0111f 100644
--- a/packages/access-client/test/stores/store-indexeddb.browser.test.js
+++ b/packages/w3up-client/test/stores/store-indexeddb.browser.test.js
@@ -2,8 +2,8 @@ import assert from 'assert'
import { top } from '@web3-storage/capabilities/top'
import { Signer as EdSigner } from '@ucanto/principal/ed25519'
import * as RSASigner from '@ucanto/principal/rsa'
-import { AgentData } from '../../src/agent-data.js'
-import { StoreIndexedDB } from '../../src/stores/store-indexeddb.js'
+import { AgentData } from '../../src/agent.js'
+import { StoreIndexedDB } from '../../src/store/indexed-db.js'
describe('IndexedDB store', () => {
it('should create and load data', async () => {
diff --git a/packages/w3up-client/test/test.js b/packages/w3up-client/test/test.js
index 5ffe569ae..84d001b37 100644
--- a/packages/w3up-client/test/test.js
+++ b/packages/w3up-client/test/test.js
@@ -1,6 +1,7 @@
-import { StoreMemory } from '@web3-storage/access/stores/store-memory'
+import { StoreMemory } from '../src/store/memory.js'
+import * as Client from '../src/index.js'
+// @ts-ignore - break the circular dependency between @web3-storage/upload-api and @web3-storage/w3up-client
import * as Context from '@web3-storage/upload-api/test/context'
-import * as Client from '@web3-storage/w3up-client'
import * as assert from 'assert'
/**
diff --git a/packages/upload-client/test/unixfs.test.js b/packages/w3up-client/test/unixfs.test.js
similarity index 97%
rename from packages/upload-client/test/unixfs.test.js
rename to packages/w3up-client/test/unixfs.test.js
index 2a45be0ad..ba47b0b77 100644
--- a/packages/upload-client/test/unixfs.test.js
+++ b/packages/w3up-client/test/unixfs.test.js
@@ -5,7 +5,7 @@ import { exporter } from 'ipfs-unixfs-exporter'
import { MemoryBlockstore } from 'blockstore-core/memory'
import * as raw from 'multiformats/codecs/raw'
import path from 'path'
-import { encodeFile, encodeDirectory } from '../src/unixfs.js'
+import { encodeFile, encodeDirectory } from '../src/capability/upload/unixfs.js'
import { File } from './helpers/shims.js'
/** @param {import('ipfs-unixfs-exporter').UnixFSDirectory} dir */
@@ -110,7 +110,7 @@ describe('UnixFS', () => {
new File(['file'], 'file.txt'),
new File(['another'], '/dir/another.txt'),
]
- /** @type {import('../src/types.js').DirectoryEntryLink[]} */
+ /** @type {import('../src/index.js').DirectoryEntryLink[]} */
const links = []
await encodeDirectory(files, { onDirectoryEntryLink: (l) => links.push(l) })
assert.equal(links.length, 4)
diff --git a/packages/upload-client/test/upload.test.js b/packages/w3up-client/test/upload.test.js
similarity index 99%
rename from packages/upload-client/test/upload.test.js
rename to packages/w3up-client/test/upload.test.js
index a22b10848..ac49c188d 100644
--- a/packages/upload-client/test/upload.test.js
+++ b/packages/w3up-client/test/upload.test.js
@@ -5,8 +5,8 @@ import { provide } from '@ucanto/server'
import * as CAR from '@ucanto/transport/car'
import * as Signer from '@ucanto/principal/ed25519'
import * as UploadCapabilities from '@web3-storage/capabilities/upload'
-import * as Upload from '../src/upload.js'
-import { serviceSigner } from './fixtures.js'
+import * as Upload from '../src/capability/upload.js'
+import { service as serviceSigner } from './helpers/fixtures.js'
import { randomCAR } from './helpers/random.js'
import { mockService } from './helpers/mocks.js'
import { validateAuthorization } from './helpers/utils.js'
diff --git a/packages/upload-client/test/index.test.js b/packages/w3up-client/test/uploader.test.js
similarity index 98%
rename from packages/upload-client/test/index.test.js
rename to packages/w3up-client/test/uploader.test.js
index a5ffb6055..2c46755d7 100644
--- a/packages/upload-client/test/index.test.js
+++ b/packages/w3up-client/test/uploader.test.js
@@ -6,8 +6,12 @@ import * as CAR from '@ucanto/transport/car'
import * as Signer from '@ucanto/principal/ed25519'
import * as StoreCapabilities from '@web3-storage/capabilities/store'
import * as UploadCapabilities from '@web3-storage/capabilities/upload'
-import { uploadFile, uploadDirectory, uploadCAR } from '../src/index.js'
-import { serviceSigner } from './fixtures.js'
+import {
+ uploadFile,
+ uploadDirectory,
+ uploadCAR,
+} from '../src/capability/upload.js'
+import { service as serviceSigner } from './helpers/fixtures.js'
import { randomBlock, randomBytes } from './helpers/random.js'
import { toCAR } from './helpers/car.js'
import { File } from './helpers/shims.js'
@@ -17,7 +21,7 @@ import {
blockEncodingLength,
encode,
headerEncodingLength,
-} from '../src/car.js'
+} from '../src/capability/upload/car.js'
import { toBlock } from './helpers/block.js'
describe('uploadFile', () => {
diff --git a/packages/w3up-client/tsconfig.json b/packages/w3up-client/tsconfig.json
index 1c359f7c4..f5242ea4f 100644
--- a/packages/w3up-client/tsconfig.json
+++ b/packages/w3up-client/tsconfig.json
@@ -16,11 +16,5 @@
},
"customCss": "./static/docs.css"
},
- "references": [
- { "path": "../access-client" },
- { "path": "../capabilities" },
- { "path": "../upload-client" },
- { "path": "../did-mailto" },
- { "path": "../upload-api" }
- ]
+ "references": [{ "path": "../capabilities" }, { "path": "../did-mailto" }]
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 070946331..6ec4cf4d8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,7 +26,7 @@ importers:
version: 2.4.3(typescript@5.2.2)
docusaurus-plugin-typedoc:
specifier: ^0.18.0
- version: 0.18.0(typedoc-plugin-markdown@3.17.0)(typedoc@0.25.3)
+ version: 0.18.0(typedoc-plugin-markdown@3.17.1)(typedoc@0.25.3)
lint-staged:
specifier: ^13.2.0
version: 13.3.0
@@ -35,113 +35,23 @@ importers:
version: 2.8.3
typedoc-plugin-markdown:
specifier: ^3.14.0
- version: 3.17.0(typedoc@0.25.3)
+ version: 3.17.1(typedoc@0.25.3)
typescript:
specifier: 5.2.2
version: 5.2.2
packages/access-client:
dependencies:
- '@ipld/car':
- specifier: ^5.1.1
- version: 5.2.4
- '@ipld/dag-ucan':
- specifier: ^3.4.0
- version: 3.4.0
- '@scure/bip39':
- specifier: ^1.2.1
- version: 1.2.1
- '@ucanto/client':
- specifier: ^9.0.0
- version: 9.0.0
- '@ucanto/core':
- specifier: ^9.0.0
- version: 9.0.0
- '@ucanto/interface':
- specifier: ^9.0.0
- version: 9.0.0
- '@ucanto/principal':
- specifier: ^9.0.0
- version: 9.0.0
- '@ucanto/transport':
- specifier: ^9.0.0
- version: 9.0.0
- '@ucanto/validator':
- specifier: ^9.0.0
- version: 9.0.0
- '@web3-storage/capabilities':
- specifier: workspace:^
- version: link:../capabilities
- '@web3-storage/did-mailto':
+ '@web3-storage/w3up-client':
specifier: workspace:^
- version: link:../did-mailto
- bigint-mod-arith:
- specifier: ^3.1.2
- version: 3.3.1
- conf:
- specifier: 11.0.2
- version: 11.0.2
- multiformats:
- specifier: ^12.1.2
- version: 12.1.3
- one-webcrypto:
- specifier: git://github.com/web3-storage/one-webcrypto
- version: github.com/web3-storage/one-webcrypto/5148cd14d5489a8ac4cd38223870e02db15a2382
- p-defer:
- specifier: ^4.0.0
- version: 4.0.0
- type-fest:
- specifier: ^3.3.0
- version: 3.13.1
- uint8arrays:
- specifier: ^4.0.6
- version: 4.0.6
+ version: link:../w3up-client
devDependencies:
- '@types/assert':
- specifier: ^1.5.6
- version: 1.5.8
- '@types/inquirer':
- specifier: ^9.0.4
- version: 9.0.6
- '@types/mocha':
- specifier: ^10.0.1
- version: 10.0.3
- '@types/node':
- specifier: ^20.8.4
- version: 20.8.10
- '@types/sinon':
- specifier: ^10.0.19
- version: 10.0.20
- '@types/varint':
- specifier: ^6.0.1
- version: 6.0.2
- '@types/ws':
- specifier: ^8.5.4
- version: 8.5.8
- '@ucanto/server':
- specifier: ^9.0.1
- version: 9.0.1
'@web3-storage/eslint-config-w3up':
specifier: workspace:^
version: link:../eslint-config-w3up
- assert:
- specifier: ^2.0.0
- version: 2.1.0
- mocha:
- specifier: ^10.2.0
- version: 10.2.0
- playwright-test:
- specifier: ^12.3.4
- version: 12.4.3
- sinon:
- specifier: ^15.0.3
- version: 15.2.0
typescript:
specifier: 5.2.2
version: 5.2.2
- watch:
- specifier: ^1.0.2
- version: 1.0.2
packages/capabilities:
dependencies:
@@ -166,7 +76,7 @@ importers:
devDependencies:
'@types/assert':
specifier: ^1.5.6
- version: 1.5.8
+ version: 1.5.9
'@types/mocha':
specifier: ^10.0.0
version: 10.0.3
@@ -199,7 +109,7 @@ importers:
devDependencies:
'@types/assert':
specifier: ^1.5.6
- version: 1.5.8
+ version: 1.5.9
'@types/mocha':
specifier: ^10.0.1
version: 10.0.3
@@ -214,16 +124,16 @@ importers:
dependencies:
'@typescript-eslint/eslint-plugin':
specifier: ^6.9.1
- version: 6.9.1(@typescript-eslint/parser@6.9.1)(eslint@8.52.0)(typescript@5.2.2)
+ version: 6.10.0(@typescript-eslint/parser@6.10.0)(eslint@8.53.0)(typescript@5.2.2)
'@typescript-eslint/parser':
specifier: ^6.9.1
- version: 6.9.1(eslint@8.52.0)(typescript@5.2.2)
+ version: 6.10.0(eslint@8.53.0)(typescript@5.2.2)
eslint:
specifier: '>= 8'
- version: 8.52.0
+ version: 8.53.0
eslint-plugin-jsdoc:
specifier: ^46.8.2
- version: 46.8.2(eslint@8.52.0)
+ version: 46.8.2(eslint@8.53.0)
packages/filecoin-api:
dependencies:
@@ -312,7 +222,7 @@ importers:
version: 10.1.5
'@types/assert':
specifier: ^1.5.6
- version: 1.5.8
+ version: 1.5.9
'@types/mocha':
specifier: ^10.0.1
version: 10.0.3
@@ -373,9 +283,6 @@ importers:
'@ucanto/validator':
specifier: ^9.0.0
version: 9.0.0
- '@web3-storage/access':
- specifier: workspace:^
- version: link:../access-client
'@web3-storage/capabilities':
specifier: workspace:^
version: link:../capabilities
@@ -385,6 +292,9 @@ importers:
'@web3-storage/filecoin-api':
specifier: workspace:^
version: link:../filecoin-api
+ '@web3-storage/w3up-client':
+ specifier: workspace:^
+ version: link:../w3up-client
multiformats:
specifier: ^12.1.2
version: 12.1.3
@@ -424,6 +334,19 @@ importers:
version: github.com/web3-storage/one-webcrypto/5148cd14d5489a8ac4cd38223870e02db15a2382
packages/upload-client:
+ dependencies:
+ '@web3-storage/w3up-client':
+ specifier: workspace:^
+ version: link:../w3up-client
+ devDependencies:
+ '@web3-storage/eslint-config-w3up':
+ specifier: workspace:^
+ version: link:../eslint-config-w3up
+ typescript:
+ specifier: 5.2.2
+ version: 5.2.2
+
+ packages/w3up-client:
dependencies:
'@ipld/car':
specifier: ^5.2.2
@@ -437,18 +360,36 @@ importers:
'@ipld/unixfs':
specifier: ^2.1.1
version: 2.1.2
+ '@scure/bip39':
+ specifier: ^1.2.1
+ version: 1.2.1
'@ucanto/client':
specifier: ^9.0.0
version: 9.0.0
+ '@ucanto/core':
+ specifier: ^9.0.0
+ version: 9.0.0
'@ucanto/interface':
specifier: ^9.0.0
version: 9.0.0
+ '@ucanto/principal':
+ specifier: ^9.0.0
+ version: 9.0.0
'@ucanto/transport':
specifier: ^9.0.0
version: 9.0.0
'@web3-storage/capabilities':
specifier: workspace:^
version: link:../capabilities
+ '@web3-storage/did-mailto':
+ specifier: workspace:^
+ version: link:../did-mailto
+ bigint-mod-arith:
+ specifier: ^3.1.2
+ version: 3.3.1
+ conf:
+ specifier: 11.0.2
+ version: 11.0.2
fr32-sha2-256-trunc254-padded-binary-tree-multihash:
specifier: ^3.1.0
version: 3.1.0
@@ -458,110 +399,37 @@ importers:
multiformats:
specifier: ^12.1.2
version: 12.1.3
+ one-webcrypto:
+ specifier: git://github.com/web3-storage/one-webcrypto
+ version: github.com/web3-storage/one-webcrypto/5148cd14d5489a8ac4cd38223870e02db15a2382
+ p-defer:
+ specifier: ^4.0.0
+ version: 4.0.0
p-retry:
specifier: ^5.1.2
version: 5.1.2
parallel-transform-web:
specifier: ^1.0.0
version: 1.0.0
- varint:
- specifier: ^6.0.0
- version: 6.0.0
- devDependencies:
- '@types/assert':
- specifier: ^1.5.6
- version: 1.5.8
- '@types/mocha':
- specifier: ^10.0.1
- version: 10.0.3
- '@types/varint':
- specifier: ^6.0.1
- version: 6.0.2
- '@ucanto/principal':
- specifier: ^9.0.0
- version: 9.0.0
- '@ucanto/server':
- specifier: ^9.0.1
- version: 9.0.1
- '@web3-storage/eslint-config-w3up':
- specifier: workspace:^
- version: link:../eslint-config-w3up
- assert:
- specifier: ^2.0.0
- version: 2.1.0
- blockstore-core:
- specifier: ^3.0.0
- version: 3.0.0
- c8:
- specifier: ^7.13.0
- version: 7.14.0
- hundreds:
- specifier: ^0.0.9
- version: 0.0.9
- ipfs-unixfs-exporter:
- specifier: ^10.0.0
- version: 10.0.1
- mocha:
- specifier: ^10.2.0
- version: 10.2.0
- npm-run-all:
- specifier: ^4.1.5
- version: 4.1.5
- playwright-test:
- specifier: ^12.3.4
- version: 12.4.3
- typescript:
- specifier: 5.2.2
- version: 5.2.2
-
- packages/w3up-client:
- dependencies:
- '@ipld/dag-ucan':
- specifier: ^3.4.0
- version: 3.4.0
- '@ucanto/client':
- specifier: ^9.0.0
- version: 9.0.0
- '@ucanto/core':
- specifier: ^9.0.0
- version: 9.0.0
- '@ucanto/interface':
- specifier: ^9.0.0
- version: 9.0.0
- '@ucanto/principal':
- specifier: ^9.0.0
- version: 9.0.0
- '@ucanto/transport':
- specifier: ^9.0.0
- version: 9.0.0
- '@web3-storage/access':
- specifier: workspace:^
- version: link:../access-client
- '@web3-storage/capabilities':
- specifier: workspace:^
- version: link:../capabilities
- '@web3-storage/did-mailto':
- specifier: workspace:^
- version: link:../did-mailto
- '@web3-storage/upload-client':
- specifier: workspace:^
- version: link:../upload-client
+ uint8arrays:
+ specifier: ^4.0.6
+ version: 4.0.6
devDependencies:
'@docusaurus/core':
specifier: ^2.2.0
version: 2.4.3(typescript@5.2.2)
- '@ipld/car':
- specifier: ^5.1.1
- version: 5.2.4
'@types/assert':
specifier: ^1.5.6
- version: 1.5.8
+ version: 1.5.9
'@types/mocha':
specifier: ^10.0.1
version: 10.0.3
'@types/node':
specifier: ^20.8.4
version: 20.8.10
+ '@types/sinon':
+ specifier: ^10.0.19
+ version: 10.0.20
'@ucanto/server':
specifier: ^9.0.1
version: 9.0.1
@@ -574,33 +442,39 @@ importers:
assert:
specifier: ^2.0.0
version: 2.1.0
+ blockstore-core:
+ specifier: ^3.0.0
+ version: 3.0.0
c8:
specifier: ^7.13.0
version: 7.14.0
docusaurus-plugin-typedoc:
specifier: ^0.18.0
- version: 0.18.0(typedoc-plugin-markdown@3.17.0)(typedoc@0.23.28)
+ version: 0.18.0(typedoc-plugin-markdown@3.17.1)(typedoc@0.23.28)
hundreds:
specifier: ^0.0.9
version: 0.0.9
+ ipfs-unixfs-exporter:
+ specifier: ^10.0.0
+ version: 10.0.1
mocha:
specifier: ^10.1.0
version: 10.2.0
- multiformats:
- specifier: ^12.1.2
- version: 12.1.3
npm-run-all:
specifier: ^4.1.5
version: 4.1.5
playwright-test:
specifier: ^12.3.4
version: 12.4.3
+ sinon:
+ specifier: ^15.0.3
+ version: 15.2.0
typedoc:
specifier: ^0.23.24
version: 0.23.28(typescript@5.2.2)
typedoc-plugin-markdown:
specifier: ^3.14.0
- version: 3.17.0(typedoc@0.23.28)
+ version: 3.17.1(typedoc@0.23.28)
typedoc-plugin-missing-exports:
specifier: ^1.0.0
version: 1.0.0(typedoc@0.23.28)
@@ -2208,7 +2082,7 @@ packages:
react:
optional: true
dependencies:
- '@types/react': 18.2.34
+ '@types/react': 18.2.36
prop-types: 15.8.1
dev: true
@@ -2482,13 +2356,13 @@ packages:
dev: true
optional: true
- /@eslint-community/eslint-utils@4.4.0(eslint@8.52.0):
+ /@eslint-community/eslint-utils@4.4.0(eslint@8.53.0):
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
dependencies:
- eslint: 8.52.0
+ eslint: 8.53.0
eslint-visitor-keys: 3.4.3
dev: false
@@ -2497,8 +2371,8 @@ packages:
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
dev: false
- /@eslint/eslintrc@2.1.2:
- resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==}
+ /@eslint/eslintrc@2.1.3:
+ resolution: {integrity: sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
ajv: 6.12.6
@@ -2514,8 +2388,8 @@ packages:
- supports-color
dev: false
- /@eslint/js@8.52.0:
- resolution: {integrity: sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==}
+ /@eslint/js@8.53.0:
+ resolution: {integrity: sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: false
@@ -2615,10 +2489,10 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@jest/schemas': 29.6.3
- '@types/istanbul-lib-coverage': 2.0.5
- '@types/istanbul-reports': 3.0.3
+ '@types/istanbul-lib-coverage': 2.0.6
+ '@types/istanbul-reports': 3.0.4
'@types/node': 20.8.10
- '@types/yargs': 17.0.29
+ '@types/yargs': 17.0.30
chalk: 4.1.2
dev: true
@@ -3011,56 +2885,56 @@ packages:
engines: {node: '>=10.13.0'}
dev: true
- /@types/assert@1.5.8:
- resolution: {integrity: sha512-tL1NSDf4CF5hiVTnLd4KSth6bmRO3+tw8cJzEAUaN6fYQ26DIixX0lRFmtrA0jhxUS8SL6PDWtphMz3maxapjA==}
+ /@types/assert@1.5.9:
+ resolution: {integrity: sha512-uQ+/hp0DbfrhnJFgiqHlsrLTGgr8HZGssV7vYN8XunlvyJkzArcyBy/9e5Rey2T7lpL7jVuoNI/8CWCDCwISgg==}
dev: true
- /@types/body-parser@1.19.4:
- resolution: {integrity: sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==}
+ /@types/body-parser@1.19.5:
+ resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
dependencies:
- '@types/connect': 3.4.37
+ '@types/connect': 3.4.38
'@types/node': 20.8.10
dev: true
- /@types/bonjour@3.5.12:
- resolution: {integrity: sha512-ky0kWSqXVxSqgqJvPIkgFkcn4C8MnRog308Ou8xBBIVo39OmUFy+jqNe0nPwLCDFxUpmT9EvT91YzOJgkDRcFg==}
+ /@types/bonjour@3.5.13:
+ resolution: {integrity: sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==}
dependencies:
'@types/node': 20.8.10
dev: true
- /@types/connect-history-api-fallback@1.5.2:
- resolution: {integrity: sha512-gX2j9x+NzSh4zOhnRPSdPPmTepS4DfxES0AvIFv3jGv5QyeAJf6u6dY5/BAoAJU9Qq1uTvwOku8SSC2GnCRl6Q==}
+ /@types/connect-history-api-fallback@1.5.3:
+ resolution: {integrity: sha512-6mfQ6iNvhSKCZJoY6sIG3m0pKkdUcweVNOLuBBKvoWGzl2yRxOJcYOTRyLKt3nxXvBLJWa6QkW//tgbIwJehmA==}
dependencies:
- '@types/express-serve-static-core': 4.17.39
+ '@types/express-serve-static-core': 4.17.41
'@types/node': 20.8.10
dev: true
- /@types/connect@3.4.37:
- resolution: {integrity: sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==}
+ /@types/connect@3.4.38:
+ resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
dependencies:
'@types/node': 20.8.10
dev: true
- /@types/eslint-scope@3.7.6:
- resolution: {integrity: sha512-zfM4ipmxVKWdxtDaJ3MP3pBurDXOCoyjvlpE3u6Qzrmw4BPbfm4/ambIeTk/r/J0iq/+2/xp0Fmt+gFvXJY2PQ==}
+ /@types/eslint-scope@3.7.7:
+ resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
dependencies:
- '@types/eslint': 8.44.6
- '@types/estree': 1.0.4
+ '@types/eslint': 8.44.7
+ '@types/estree': 1.0.5
dev: true
- /@types/eslint@8.44.6:
- resolution: {integrity: sha512-P6bY56TVmX8y9J87jHNgQh43h6VVU+6H7oN7hgvivV81K2XY8qJZ5vqPy/HdUoVIelii2kChYVzQanlswPWVFw==}
+ /@types/eslint@8.44.7:
+ resolution: {integrity: sha512-f5ORu2hcBbKei97U73mf+l9t4zTGl74IqZ0GQk4oVea/VS8tQZYkUveSYojk+frraAVYId0V2WC9O4PTNru2FQ==}
dependencies:
- '@types/estree': 1.0.4
- '@types/json-schema': 7.0.14
+ '@types/estree': 1.0.5
+ '@types/json-schema': 7.0.15
dev: true
- /@types/estree@1.0.4:
- resolution: {integrity: sha512-2JwWnHK9H+wUZNorf2Zr6ves96WHoWDJIftkcxPKsS7Djta6Zu519LarhRNljPXkpsZR2ZMwNCPeW7omW07BJw==}
+ /@types/estree@1.0.5:
+ resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
dev: true
- /@types/express-serve-static-core@4.17.39:
- resolution: {integrity: sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==}
+ /@types/express-serve-static-core@4.17.41:
+ resolution: {integrity: sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==}
dependencies:
'@types/node': 20.8.10
'@types/qs': 6.9.9
@@ -3068,11 +2942,11 @@ packages:
'@types/send': 0.17.3
dev: true
- /@types/express@4.17.20:
- resolution: {integrity: sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==}
+ /@types/express@4.17.21:
+ resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==}
dependencies:
- '@types/body-parser': 1.19.4
- '@types/express-serve-static-core': 4.17.39
+ '@types/body-parser': 1.19.5
+ '@types/express-serve-static-core': 4.17.41
'@types/qs': 6.9.9
'@types/serve-static': 1.15.4
dev: true
@@ -3087,41 +2961,34 @@ packages:
resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==}
dev: true
- /@types/http-errors@2.0.3:
- resolution: {integrity: sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==}
+ /@types/http-errors@2.0.4:
+ resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
dev: true
- /@types/http-proxy@1.17.13:
- resolution: {integrity: sha512-GkhdWcMNiR5QSQRYnJ+/oXzu0+7JJEPC8vkWXK351BkhjraZF+1W13CUYARUvX9+NqIU2n6YHA4iwywsc/M6Sw==}
+ /@types/http-proxy@1.17.14:
+ resolution: {integrity: sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==}
dependencies:
'@types/node': 20.8.10
dev: true
- /@types/inquirer@9.0.6:
- resolution: {integrity: sha512-1Go1AAP/yOy3Pth5Xf1DC3nfZ03cJLCPx6E2YnSN/5I3w1jHBVH4170DkZ+JxfmA7c9kL9+bf9z3FRGa4kNAqg==}
- dependencies:
- '@types/through': 0.0.32
- rxjs: 7.8.1
- dev: true
-
- /@types/istanbul-lib-coverage@2.0.5:
- resolution: {integrity: sha512-zONci81DZYCZjiLe0r6equvZut0b+dBRPBN5kBDjsONnutYNtJMoWQ9uR2RkL1gLG9NMTzvf+29e5RFfPbeKhQ==}
+ /@types/istanbul-lib-coverage@2.0.6:
+ resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
dev: true
- /@types/istanbul-lib-report@3.0.2:
- resolution: {integrity: sha512-8toY6FgdltSdONav1XtUHl4LN1yTmLza+EuDazb/fEmRNCwjyqNVIQWs2IfC74IqjHkREs/nQ2FWq5kZU9IC0w==}
+ /@types/istanbul-lib-report@3.0.3:
+ resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==}
dependencies:
- '@types/istanbul-lib-coverage': 2.0.5
+ '@types/istanbul-lib-coverage': 2.0.6
dev: true
- /@types/istanbul-reports@3.0.3:
- resolution: {integrity: sha512-1nESsePMBlf0RPRffLZi5ujYh7IH1BWL4y9pr+Bn3cJBdxz+RTP8bUFljLz9HvzhhOSWKdyBZ4DIivdL6rvgZg==}
+ /@types/istanbul-reports@3.0.4:
+ resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==}
dependencies:
- '@types/istanbul-lib-report': 3.0.2
+ '@types/istanbul-lib-report': 3.0.3
dev: true
- /@types/json-schema@7.0.14:
- resolution: {integrity: sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==}
+ /@types/json-schema@7.0.15:
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
/@types/keyv@3.1.4:
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
@@ -3181,8 +3048,8 @@ packages:
resolution: {integrity: sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==}
dev: true
- /@types/react@18.2.34:
- resolution: {integrity: sha512-U6eW/alrRk37FU/MS2RYMjx0Va2JGIVXELTODaTIYgvWGCV4Y4TfTUzG8DdmpDNIT0Xpj/R7GfyHOJJrDttcvg==}
+ /@types/react@18.2.36:
+ resolution: {integrity: sha512-o9XFsHYLLZ4+sb9CWUYwHqFVoG61SesydF353vFMMsQziiyRu8np4n2OYMUSDZ8XuImxDr9c5tR7gidlH29Vnw==}
dependencies:
'@types/prop-types': 15.7.9
'@types/scheduler': 0.16.5
@@ -3221,13 +3088,13 @@ packages:
/@types/serve-index@1.9.3:
resolution: {integrity: sha512-4KG+yMEuvDPRrYq5fyVm/I2uqAJSAwZK9VSa+Zf+zUq9/oxSSvy3kkIqyL+jjStv6UCVi8/Aho0NHtB1Fwosrg==}
dependencies:
- '@types/express': 4.17.20
+ '@types/express': 4.17.21
dev: true
/@types/serve-static@1.15.4:
resolution: {integrity: sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==}
dependencies:
- '@types/http-errors': 2.0.3
+ '@types/http-errors': 2.0.4
'@types/mime': 3.0.3
'@types/node': 20.8.10
dev: true
@@ -3248,22 +3115,10 @@ packages:
'@types/node': 20.8.10
dev: true
- /@types/through@0.0.32:
- resolution: {integrity: sha512-7XsfXIsjdfJM2wFDRAtEWp3zb2aVPk5QeyZxGlVK57q4u26DczMHhJmlhr0Jqv0THwxam/L8REXkj8M2I/lcvw==}
- dependencies:
- '@types/node': 20.8.10
- dev: true
-
/@types/unist@2.0.9:
resolution: {integrity: sha512-zC0iXxAv1C1ERURduJueYzkzZ2zaGyc+P2c95hgkikHPr3z8EdUZOlgEQ5X0DRmwDZn+hekycQnoeiiRVrmilQ==}
dev: true
- /@types/varint@6.0.2:
- resolution: {integrity: sha512-gBTZgG13ulb81tQwk//LnPwpl8hue2SJ9peL3i8ZVA3ZXU6Y7gT9S7MHcunzDbG07yXiT1+m1LkQjPYb+PCNWQ==}
- dependencies:
- '@types/node': 20.8.10
- dev: true
-
/@types/ws@8.5.8:
resolution: {integrity: sha512-flUksGIQCnJd6sZ1l5dqCEG/ksaoAg/eUwiLAGTJQcfgvZJKF++Ta4bJA6A5aPSJmsr+xlseHn4KLgVlNnvPTg==}
dependencies:
@@ -3274,14 +3129,14 @@ packages:
resolution: {integrity: sha512-5qcvofLPbfjmBfKaLfj/+f+Sbd6pN4zl7w7VSVI5uz7m9QZTuB2aZAa2uo1wHFBNN2x6g/SoTkXmd8mQnQF2Cw==}
dev: true
- /@types/yargs@17.0.29:
- resolution: {integrity: sha512-nacjqA3ee9zRF/++a3FUY1suHTFKZeHba2n8WeDw9cCVdmzmHpIxyzOJBcpHvvEmS8E9KqWlSnWHUkOrkhWcvA==}
+ /@types/yargs@17.0.30:
+ resolution: {integrity: sha512-3SJLzYk3yz3EgI9I8OLoH06B3PdXIoU2imrBZzaGqUtUXf5iUNDtmAfCGuQrny1bnmyjh/GM/YNts6WK5jR5Rw==}
dependencies:
'@types/yargs-parser': 21.0.2
dev: true
- /@typescript-eslint/eslint-plugin@6.9.1(@typescript-eslint/parser@6.9.1)(eslint@8.52.0)(typescript@5.2.2):
- resolution: {integrity: sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==}
+ /@typescript-eslint/eslint-plugin@6.10.0(@typescript-eslint/parser@6.10.0)(eslint@8.53.0)(typescript@5.2.2):
+ resolution: {integrity: sha512-uoLj4g2OTL8rfUQVx2AFO1hp/zja1wABJq77P6IclQs6I/m9GLrm7jCdgzZkvWdDCQf1uEvoa8s8CupsgWQgVg==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
'@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
@@ -3292,13 +3147,13 @@ packages:
optional: true
dependencies:
'@eslint-community/regexpp': 4.10.0
- '@typescript-eslint/parser': 6.9.1(eslint@8.52.0)(typescript@5.2.2)
- '@typescript-eslint/scope-manager': 6.9.1
- '@typescript-eslint/type-utils': 6.9.1(eslint@8.52.0)(typescript@5.2.2)
- '@typescript-eslint/utils': 6.9.1(eslint@8.52.0)(typescript@5.2.2)
- '@typescript-eslint/visitor-keys': 6.9.1
+ '@typescript-eslint/parser': 6.10.0(eslint@8.53.0)(typescript@5.2.2)
+ '@typescript-eslint/scope-manager': 6.10.0
+ '@typescript-eslint/type-utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2)
+ '@typescript-eslint/utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2)
+ '@typescript-eslint/visitor-keys': 6.10.0
debug: 4.3.4(supports-color@8.1.1)
- eslint: 8.52.0
+ eslint: 8.53.0
graphemer: 1.4.0
ignore: 5.2.4
natural-compare: 1.4.0
@@ -3309,8 +3164,8 @@ packages:
- supports-color
dev: false
- /@typescript-eslint/parser@6.9.1(eslint@8.52.0)(typescript@5.2.2):
- resolution: {integrity: sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==}
+ /@typescript-eslint/parser@6.10.0(eslint@8.53.0)(typescript@5.2.2):
+ resolution: {integrity: sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
@@ -3319,27 +3174,27 @@ packages:
typescript:
optional: true
dependencies:
- '@typescript-eslint/scope-manager': 6.9.1
- '@typescript-eslint/types': 6.9.1
- '@typescript-eslint/typescript-estree': 6.9.1(typescript@5.2.2)
- '@typescript-eslint/visitor-keys': 6.9.1
+ '@typescript-eslint/scope-manager': 6.10.0
+ '@typescript-eslint/types': 6.10.0
+ '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2)
+ '@typescript-eslint/visitor-keys': 6.10.0
debug: 4.3.4(supports-color@8.1.1)
- eslint: 8.52.0
+ eslint: 8.53.0
typescript: 5.2.2
transitivePeerDependencies:
- supports-color
dev: false
- /@typescript-eslint/scope-manager@6.9.1:
- resolution: {integrity: sha512-38IxvKB6NAne3g/+MyXMs2Cda/Sz+CEpmm+KLGEM8hx/CvnSRuw51i8ukfwB/B/sESdeTGet1NH1Wj7I0YXswg==}
+ /@typescript-eslint/scope-manager@6.10.0:
+ resolution: {integrity: sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==}
engines: {node: ^16.0.0 || >=18.0.0}
dependencies:
- '@typescript-eslint/types': 6.9.1
- '@typescript-eslint/visitor-keys': 6.9.1
+ '@typescript-eslint/types': 6.10.0
+ '@typescript-eslint/visitor-keys': 6.10.0
dev: false
- /@typescript-eslint/type-utils@6.9.1(eslint@8.52.0)(typescript@5.2.2):
- resolution: {integrity: sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==}
+ /@typescript-eslint/type-utils@6.10.0(eslint@8.53.0)(typescript@5.2.2):
+ resolution: {integrity: sha512-wYpPs3hgTFblMYwbYWPT3eZtaDOjbLyIYuqpwuLBBqhLiuvJ+9sEp2gNRJEtR5N/c9G1uTtQQL5AhV0fEPJYcg==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
@@ -3348,23 +3203,23 @@ packages:
typescript:
optional: true
dependencies:
- '@typescript-eslint/typescript-estree': 6.9.1(typescript@5.2.2)
- '@typescript-eslint/utils': 6.9.1(eslint@8.52.0)(typescript@5.2.2)
+ '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2)
+ '@typescript-eslint/utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2)
debug: 4.3.4(supports-color@8.1.1)
- eslint: 8.52.0
+ eslint: 8.53.0
ts-api-utils: 1.0.3(typescript@5.2.2)
typescript: 5.2.2
transitivePeerDependencies:
- supports-color
dev: false
- /@typescript-eslint/types@6.9.1:
- resolution: {integrity: sha512-BUGslGOb14zUHOUmDB2FfT6SI1CcZEJYfF3qFwBeUrU6srJfzANonwRYHDpLBuzbq3HaoF2XL2hcr01c8f8OaQ==}
+ /@typescript-eslint/types@6.10.0:
+ resolution: {integrity: sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==}
engines: {node: ^16.0.0 || >=18.0.0}
dev: false
- /@typescript-eslint/typescript-estree@6.9.1(typescript@5.2.2):
- resolution: {integrity: sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw==}
+ /@typescript-eslint/typescript-estree@6.10.0(typescript@5.2.2):
+ resolution: {integrity: sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
typescript: '*'
@@ -3372,8 +3227,8 @@ packages:
typescript:
optional: true
dependencies:
- '@typescript-eslint/types': 6.9.1
- '@typescript-eslint/visitor-keys': 6.9.1
+ '@typescript-eslint/types': 6.10.0
+ '@typescript-eslint/visitor-keys': 6.10.0
debug: 4.3.4(supports-color@8.1.1)
globby: 11.1.0
is-glob: 4.0.3
@@ -3384,30 +3239,30 @@ packages:
- supports-color
dev: false
- /@typescript-eslint/utils@6.9.1(eslint@8.52.0)(typescript@5.2.2):
- resolution: {integrity: sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==}
+ /@typescript-eslint/utils@6.10.0(eslint@8.53.0)(typescript@5.2.2):
+ resolution: {integrity: sha512-v+pJ1/RcVyRc0o4wAGux9x42RHmAjIGzPRo538Z8M1tVx6HOnoQBCX/NoadHQlZeC+QO2yr4nNSFWOoraZCAyg==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
dependencies:
- '@eslint-community/eslint-utils': 4.4.0(eslint@8.52.0)
- '@types/json-schema': 7.0.14
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0)
+ '@types/json-schema': 7.0.15
'@types/semver': 7.5.4
- '@typescript-eslint/scope-manager': 6.9.1
- '@typescript-eslint/types': 6.9.1
- '@typescript-eslint/typescript-estree': 6.9.1(typescript@5.2.2)
- eslint: 8.52.0
+ '@typescript-eslint/scope-manager': 6.10.0
+ '@typescript-eslint/types': 6.10.0
+ '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2)
+ eslint: 8.53.0
semver: 7.5.4
transitivePeerDependencies:
- supports-color
- typescript
dev: false
- /@typescript-eslint/visitor-keys@6.9.1:
- resolution: {integrity: sha512-MUaPUe/QRLEffARsmNfmpghuQkW436DvESW+h+M52w0coICHRfD6Np9/K6PdACwnrq1HmuLl+cSPZaJmeVPkSw==}
+ /@typescript-eslint/visitor-keys@6.10.0:
+ resolution: {integrity: sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==}
engines: {node: ^16.0.0 || >=18.0.0}
dependencies:
- '@typescript-eslint/types': 6.9.1
+ '@typescript-eslint/types': 6.10.0
eslint-visitor-keys: 3.4.3
dev: false
@@ -3472,56 +3327,56 @@ packages:
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
dev: false
- /@vue/compiler-core@3.3.7:
- resolution: {integrity: sha512-pACdY6YnTNVLXsB86YD8OF9ihwpolzhhtdLVHhBL6do/ykr6kKXNYABRtNMGrsQXpEXXyAdwvWWkuTbs4MFtPQ==}
+ /@vue/compiler-core@3.3.8:
+ resolution: {integrity: sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==}
dependencies:
'@babel/parser': 7.23.0
- '@vue/shared': 3.3.7
+ '@vue/shared': 3.3.8
estree-walker: 2.0.2
source-map-js: 1.0.2
dev: false
- /@vue/compiler-dom@3.3.7:
- resolution: {integrity: sha512-0LwkyJjnUPssXv/d1vNJ0PKfBlDoQs7n81CbO6Q0zdL7H1EzqYRrTVXDqdBVqro0aJjo/FOa1qBAPVI4PGSHBw==}
+ /@vue/compiler-dom@3.3.8:
+ resolution: {integrity: sha512-+PPtv+p/nWDd0AvJu3w8HS0RIm/C6VGBIRe24b9hSyNWOAPEUosFZ5diwawwP8ip5sJ8n0Pe87TNNNHnvjs0FQ==}
dependencies:
- '@vue/compiler-core': 3.3.7
- '@vue/shared': 3.3.7
+ '@vue/compiler-core': 3.3.8
+ '@vue/shared': 3.3.8
dev: false
- /@vue/compiler-sfc@3.3.7:
- resolution: {integrity: sha512-7pfldWy/J75U/ZyYIXRVqvLRw3vmfxDo2YLMwVtWVNew8Sm8d6wodM+OYFq4ll/UxfqVr0XKiVwti32PCrruAw==}
+ /@vue/compiler-sfc@3.3.8:
+ resolution: {integrity: sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA==}
dependencies:
'@babel/parser': 7.23.0
- '@vue/compiler-core': 3.3.7
- '@vue/compiler-dom': 3.3.7
- '@vue/compiler-ssr': 3.3.7
- '@vue/reactivity-transform': 3.3.7
- '@vue/shared': 3.3.7
+ '@vue/compiler-core': 3.3.8
+ '@vue/compiler-dom': 3.3.8
+ '@vue/compiler-ssr': 3.3.8
+ '@vue/reactivity-transform': 3.3.8
+ '@vue/shared': 3.3.8
estree-walker: 2.0.2
magic-string: 0.30.5
postcss: 8.4.31
source-map-js: 1.0.2
dev: false
- /@vue/compiler-ssr@3.3.7:
- resolution: {integrity: sha512-TxOfNVVeH3zgBc82kcUv+emNHo+vKnlRrkv8YvQU5+Y5LJGJwSNzcmLUoxD/dNzv0bhQ/F0s+InlgV0NrApJZg==}
+ /@vue/compiler-ssr@3.3.8:
+ resolution: {integrity: sha512-hXCqQL/15kMVDBuoBYpUnSYT8doDNwsjvm3jTefnXr+ytn294ySnT8NlsFHmTgKNjwpuFy7XVV8yTeLtNl/P6w==}
dependencies:
- '@vue/compiler-dom': 3.3.7
- '@vue/shared': 3.3.7
+ '@vue/compiler-dom': 3.3.8
+ '@vue/shared': 3.3.8
dev: false
- /@vue/reactivity-transform@3.3.7:
- resolution: {integrity: sha512-APhRmLVbgE1VPGtoLQoWBJEaQk4V8JUsqrQihImVqKT+8U6Qi3t5ATcg4Y9wGAPb3kIhetpufyZ1RhwbZCIdDA==}
+ /@vue/reactivity-transform@3.3.8:
+ resolution: {integrity: sha512-49CvBzmZNtcHua0XJ7GdGifM8GOXoUMOX4dD40Y5DxI3R8OUhMlvf2nvgUAcPxaXiV5MQQ1Nwy09ADpnLQUqRw==}
dependencies:
'@babel/parser': 7.23.0
- '@vue/compiler-core': 3.3.7
- '@vue/shared': 3.3.7
+ '@vue/compiler-core': 3.3.8
+ '@vue/shared': 3.3.8
estree-walker: 2.0.2
magic-string: 0.30.5
dev: false
- /@vue/shared@3.3.7:
- resolution: {integrity: sha512-N/tbkINRUDExgcPTBvxNkvHGu504k8lzlNQRITVnm6YjOjwa4r0nnbd4Jb01sNpur5hAllyRJzSK5PvB9PPwRg==}
+ /@vue/shared@3.3.8:
+ resolution: {integrity: sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==}
dev: false
/@web-std/blob@3.0.5:
@@ -3959,7 +3814,7 @@ packages:
postcss: ^8.1.0
dependencies:
browserslist: 4.22.1
- caniuse-lite: 1.0.30001559
+ caniuse-lite: 1.0.30001561
fraction.js: 4.3.7
normalize-range: 0.1.2
picocolors: 1.0.0
@@ -4194,8 +4049,8 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
dependencies:
- caniuse-lite: 1.0.30001559
- electron-to-chromium: 1.4.575
+ caniuse-lite: 1.0.30001561
+ electron-to-chromium: 1.4.577
node-releases: 2.0.13
update-browserslist-db: 1.0.13(browserslist@4.22.1)
dev: true
@@ -4240,7 +4095,7 @@ packages:
'@istanbuljs/schema': 0.1.3
find-up: 5.0.0
foreground-child: 2.0.0
- istanbul-lib-coverage: 3.2.0
+ istanbul-lib-coverage: 3.2.1
istanbul-lib-report: 3.0.1
istanbul-reports: 3.1.6
rimraf: 3.0.2
@@ -4259,7 +4114,7 @@ packages:
'@istanbuljs/schema': 0.1.3
find-up: 5.0.0
foreground-child: 2.0.0
- istanbul-lib-coverage: 3.2.0
+ istanbul-lib-coverage: 3.2.1
istanbul-lib-report: 3.0.1
istanbul-reports: 3.1.6
rimraf: 3.0.2
@@ -4323,13 +4178,13 @@ packages:
resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==}
dependencies:
browserslist: 4.22.1
- caniuse-lite: 1.0.30001559
+ caniuse-lite: 1.0.30001561
lodash.memoize: 4.1.2
lodash.uniq: 4.5.0
dev: true
- /caniuse-lite@1.0.30001559:
- resolution: {integrity: sha512-cPiMKZgqgkg5LY3/ntGeLFUpi6tzddBNS58A4tnTgQw1zON7u2sZMU7SzOeVH4tj20++9ggL+V6FDOFMTaFFYA==}
+ /caniuse-lite@1.0.30001561:
+ resolution: {integrity: sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw==}
dev: true
/cardinal@2.1.1:
@@ -4674,7 +4529,7 @@ packages:
peerDependencies:
webpack: ^5.1.0
dependencies:
- fast-glob: 3.3.1
+ fast-glob: 3.3.2
glob-parent: 6.0.2
globby: 13.2.2
normalize-path: 3.0.0
@@ -5080,7 +4935,7 @@ packages:
dependencies:
'@babel/parser': 7.23.0
'@babel/traverse': 7.23.2
- '@vue/compiler-sfc': 3.3.7
+ '@vue/compiler-sfc': 3.3.8
callsite: 1.0.0
camelcase: 6.3.0
cosmiconfig: 7.1.0
@@ -5194,24 +5049,24 @@ packages:
esutils: 2.0.3
dev: false
- /docusaurus-plugin-typedoc@0.18.0(typedoc-plugin-markdown@3.17.0)(typedoc@0.23.28):
+ /docusaurus-plugin-typedoc@0.18.0(typedoc-plugin-markdown@3.17.1)(typedoc@0.23.28):
resolution: {integrity: sha512-kurIUu8LhVIOPT88HoeBcu0/D2GMDdg0pUYaFlqeuXT9an6Wlgvuy0C22ZMYcJUcp/gA/Mw2XdUHubsLK2M4uA==}
peerDependencies:
typedoc: '>=0.23.0'
typedoc-plugin-markdown: '>=3.13.0'
dependencies:
typedoc: 0.23.28(typescript@5.2.2)
- typedoc-plugin-markdown: 3.17.0(typedoc@0.23.28)
+ typedoc-plugin-markdown: 3.17.1(typedoc@0.23.28)
dev: true
- /docusaurus-plugin-typedoc@0.18.0(typedoc-plugin-markdown@3.17.0)(typedoc@0.25.3):
+ /docusaurus-plugin-typedoc@0.18.0(typedoc-plugin-markdown@3.17.1)(typedoc@0.25.3):
resolution: {integrity: sha512-kurIUu8LhVIOPT88HoeBcu0/D2GMDdg0pUYaFlqeuXT9an6Wlgvuy0C22ZMYcJUcp/gA/Mw2XdUHubsLK2M4uA==}
peerDependencies:
typedoc: '>=0.23.0'
typedoc-plugin-markdown: '>=3.13.0'
dependencies:
typedoc: 0.25.3(typescript@5.2.2)
- typedoc-plugin-markdown: 3.17.0(typedoc@0.25.3)
+ typedoc-plugin-markdown: 3.17.1(typedoc@0.25.3)
dev: true
/dom-converter@0.2.0:
@@ -5291,8 +5146,8 @@ packages:
encoding: 0.1.13
dev: false
- /electron-to-chromium@1.4.575:
- resolution: {integrity: sha512-kY2BGyvgAHiX899oF6xLXSIf99bAvvdPhDoJwG77nxCSyWYuRH6e9a9a3gpXBvCs6lj4dQZJkfnW2hdKWHEISg==}
+ /electron-to-chromium@1.4.577:
+ resolution: {integrity: sha512-/5xHPH6f00SxhHw6052r+5S1xO7gHNc89hV7tqlvnStvKbSrDqc/u6AlwPvVWWNj+s4/KL6T6y8ih+nOY0qYNA==}
dev: true
/emoji-regex@10.3.0:
@@ -5490,7 +5345,7 @@ packages:
engines: {node: '>=12'}
dev: true
- /eslint-plugin-jsdoc@46.8.2(eslint@8.52.0):
+ /eslint-plugin-jsdoc@46.8.2(eslint@8.53.0):
resolution: {integrity: sha512-5TSnD018f3tUJNne4s4gDWQflbsgOycIKEUBoCLn6XtBMgNHxQFmV8vVxUtiPxAQq8lrX85OaSG/2gnctxw9uQ==}
engines: {node: '>=16'}
peerDependencies:
@@ -5501,7 +5356,7 @@ packages:
comment-parser: 1.4.0
debug: 4.3.4(supports-color@8.1.1)
escape-string-regexp: 4.0.0
- eslint: 8.52.0
+ eslint: 8.53.0
esquery: 1.5.0
is-builtin-module: 3.2.1
semver: 7.5.4
@@ -5531,15 +5386,15 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: false
- /eslint@8.52.0:
- resolution: {integrity: sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==}
+ /eslint@8.53.0:
+ resolution: {integrity: sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true
dependencies:
- '@eslint-community/eslint-utils': 4.4.0(eslint@8.52.0)
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0)
'@eslint-community/regexpp': 4.10.0
- '@eslint/eslintrc': 2.1.2
- '@eslint/js': 8.52.0
+ '@eslint/eslintrc': 2.1.3
+ '@eslint/js': 8.53.0
'@humanwhocodes/config-array': 0.11.13
'@humanwhocodes/module-importer': 1.0.1
'@nodelib/fs.walk': 1.2.8
@@ -5773,8 +5628,8 @@ packages:
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
dev: false
- /fast-glob@3.3.1:
- resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
+ /fast-glob@3.3.2:
+ resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
engines: {node: '>=8.6.0'}
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -5962,7 +5817,7 @@ packages:
optional: true
dependencies:
'@babel/code-frame': 7.22.13
- '@types/json-schema': 7.0.14
+ '@types/json-schema': 7.0.15
chalk: 4.1.2
chokidar: 3.5.3
cosmiconfig: 6.0.0
@@ -6213,7 +6068,7 @@ packages:
dependencies:
array-union: 2.1.0
dir-glob: 3.0.1
- fast-glob: 3.3.1
+ fast-glob: 3.3.2
ignore: 5.2.4
merge2: 1.4.1
slash: 3.0.0
@@ -6223,7 +6078,7 @@ packages:
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
dir-glob: 3.0.1
- fast-glob: 3.3.1
+ fast-glob: 3.3.2
ignore: 5.2.4
merge2: 1.4.1
slash: 4.0.0
@@ -6541,7 +6396,7 @@ packages:
resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==}
dev: true
- /http-proxy-middleware@2.0.6(@types/express@4.17.20):
+ /http-proxy-middleware@2.0.6(@types/express@4.17.21):
resolution: {integrity: sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==}
engines: {node: '>=12.0.0'}
peerDependencies:
@@ -6550,8 +6405,8 @@ packages:
'@types/express':
optional: true
dependencies:
- '@types/express': 4.17.20
- '@types/http-proxy': 1.17.13
+ '@types/express': 4.17.21
+ '@types/http-proxy': 1.17.14
http-proxy: 1.18.1
is-glob: 4.0.3
is-plain-obj: 3.0.0
@@ -6773,7 +6628,7 @@ packages:
it-glob: 1.0.2
it-to-stream: 1.0.0
merge-options: 3.0.4
- nanoid: 3.3.6
+ nanoid: 3.3.7
native-fetch: 3.0.0(node-fetch@2.7.0)
node-fetch: 2.7.0
react-native-fetch-api: 3.0.0
@@ -7128,8 +6983,8 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
- /istanbul-lib-coverage@3.2.0:
- resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==}
+ /istanbul-lib-coverage@3.2.1:
+ resolution: {integrity: sha512-opCrKqbthmq3SKZ10mFMQG9dk3fTa3quaOLD35kJa5ejwZHd9xAr+kLuziiZz2cG32s4lMZxNdmdcEQnTDP4+g==}
engines: {node: '>=8'}
dev: true
@@ -7137,7 +6992,7 @@ packages:
resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
engines: {node: '>=10'}
dependencies:
- istanbul-lib-coverage: 3.2.0
+ istanbul-lib-coverage: 3.2.1
make-dir: 4.0.0
supports-color: 7.2.0
dev: true
@@ -7945,13 +7800,13 @@ packages:
hasBin: true
dev: true
- /nanoid@3.3.6:
- resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
+ /nanoid@3.3.7:
+ resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
- /nanoid@5.0.2:
- resolution: {integrity: sha512-2ustYUX1R2rL/Br5B/FMhi8d5/QzvkJ912rBYxskcpu0myTHzSZfTr1LAS2Sm7jxRUObRrSBFoyzwAhL49aVSg==}
+ /nanoid@5.0.3:
+ resolution: {integrity: sha512-I7X2b22cxA4LIHXPSqbBCEQSL+1wv8TuoefejsX4HFWyC6jc5JG7CEaxOltiKjc1M+YCS2YkrZZcj4+dytw9GA==}
engines: {node: ^18 || >=20}
hasBin: true
dev: true
@@ -8554,7 +8409,7 @@ packages:
lilconfig: 2.1.0
lodash: 4.17.21
merge-options: 3.0.4
- nanoid: 5.0.2
+ nanoid: 5.0.3
ora: 7.0.1
p-timeout: 6.1.2
path-browserify: 1.0.1
@@ -8991,7 +8846,7 @@ packages:
resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
- nanoid: 3.3.6
+ nanoid: 3.3.7
picocolors: 1.0.0
source-map-js: 1.0.2
@@ -9635,7 +9490,7 @@ packages:
resolution: {integrity: sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==}
engines: {node: '>= 8.9.0'}
dependencies:
- '@types/json-schema': 7.0.14
+ '@types/json-schema': 7.0.15
ajv: 6.12.6
ajv-keywords: 3.5.2(ajv@6.12.6)
dev: true
@@ -9644,7 +9499,7 @@ packages:
resolution: {integrity: sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==}
engines: {node: '>= 8.9.0'}
dependencies:
- '@types/json-schema': 7.0.14
+ '@types/json-schema': 7.0.15
ajv: 6.12.6
ajv-keywords: 3.5.2(ajv@6.12.6)
dev: true
@@ -9653,7 +9508,7 @@ packages:
resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==}
engines: {node: '>= 10.13.0'}
dependencies:
- '@types/json-schema': 7.0.14
+ '@types/json-schema': 7.0.15
ajv: 6.12.6
ajv-keywords: 3.5.2(ajv@6.12.6)
dev: true
@@ -9662,7 +9517,7 @@ packages:
resolution: {integrity: sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==}
engines: {node: '>= 12.13.0'}
dependencies:
- '@types/json-schema': 7.0.14
+ '@types/json-schema': 7.0.15
ajv: 8.12.0
ajv-formats: 2.1.1(ajv@8.12.0)
ajv-keywords: 5.1.0(ajv@8.12.0)
@@ -10437,6 +10292,7 @@ packages:
/type-fest@3.13.1:
resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==}
engines: {node: '>=14.16'}
+ dev: true
/type-is@1.6.18:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
@@ -10490,8 +10346,8 @@ packages:
is-typedarray: 1.0.0
dev: true
- /typedoc-plugin-markdown@3.17.0(typedoc@0.23.28):
- resolution: {integrity: sha512-+uh5fHNfNSGdUxae0FWOuJ8Xu9Sl08jkdshOg6dilAqN/ZXmYsUFFDKw70fYfiGxdCLvpUuyr9FYO+WAa2lHeA==}
+ /typedoc-plugin-markdown@3.17.1(typedoc@0.23.28):
+ resolution: {integrity: sha512-QzdU3fj0Kzw2XSdoL15ExLASt2WPqD7FbLeaqwT70+XjKyTshBnUlQA5nNREO1C2P8Uen0CDjsBLMsCQ+zd0lw==}
peerDependencies:
typedoc: '>=0.24.0'
dependencies:
@@ -10499,8 +10355,8 @@ packages:
typedoc: 0.23.28(typescript@5.2.2)
dev: true
- /typedoc-plugin-markdown@3.17.0(typedoc@0.25.3):
- resolution: {integrity: sha512-+uh5fHNfNSGdUxae0FWOuJ8Xu9Sl08jkdshOg6dilAqN/ZXmYsUFFDKw70fYfiGxdCLvpUuyr9FYO+WAa2lHeA==}
+ /typedoc-plugin-markdown@3.17.1(typedoc@0.25.3):
+ resolution: {integrity: sha512-QzdU3fj0Kzw2XSdoL15ExLASt2WPqD7FbLeaqwT70+XjKyTshBnUlQA5nNREO1C2P8Uen0CDjsBLMsCQ+zd0lw==}
peerDependencies:
typedoc: '>=0.24.0'
dependencies:
@@ -10800,7 +10656,7 @@ packages:
engines: {node: '>=10.12.0'}
dependencies:
'@jridgewell/trace-mapping': 0.3.20
- '@types/istanbul-lib-coverage': 2.0.5
+ '@types/istanbul-lib-coverage': 2.0.6
convert-source-map: 2.0.0
dev: true
@@ -10966,9 +10822,9 @@ packages:
webpack-cli:
optional: true
dependencies:
- '@types/bonjour': 3.5.12
- '@types/connect-history-api-fallback': 1.5.2
- '@types/express': 4.17.20
+ '@types/bonjour': 3.5.13
+ '@types/connect-history-api-fallback': 1.5.3
+ '@types/express': 4.17.21
'@types/serve-index': 1.9.3
'@types/serve-static': 1.15.4
'@types/sockjs': 0.3.35
@@ -10983,7 +10839,7 @@ packages:
express: 4.18.2
graceful-fs: 4.2.11
html-entities: 2.4.0
- http-proxy-middleware: 2.0.6(@types/express@4.17.20)
+ http-proxy-middleware: 2.0.6(@types/express@4.17.21)
ipaddr.js: 2.1.0
launch-editor: 2.6.1
open: 8.4.2
@@ -11028,8 +10884,8 @@ packages:
webpack-cli:
optional: true
dependencies:
- '@types/eslint-scope': 3.7.6
- '@types/estree': 1.0.4
+ '@types/eslint-scope': 3.7.7
+ '@types/estree': 1.0.5
'@webassemblyjs/ast': 1.11.6
'@webassemblyjs/wasm-edit': 1.11.6
'@webassemblyjs/wasm-parser': 1.11.6
@@ -11243,11 +11099,11 @@ packages:
/yargs-parser@20.2.4:
resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==}
engines: {node: '>=10'}
+ dev: true
/yargs-parser@20.2.9:
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
engines: {node: '>=10'}
- dev: true
/yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
@@ -11274,7 +11130,7 @@ packages:
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
- yargs-parser: 20.2.4
+ yargs-parser: 20.2.9
/yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}