diff --git a/packages/capabilities/src/index.js b/packages/capabilities/src/index.js index 92222702a..a18071e59 100644 --- a/packages/capabilities/src/index.js +++ b/packages/capabilities/src/index.js @@ -40,10 +40,12 @@ export const abilitiesAsStrings = [ Space.info.can, Upload.upload.can, Upload.add.can, + Upload.get.can, Upload.remove.can, Upload.list.can, Store.store.can, Store.add.can, + Store.get.can, Store.remove.can, Store.list.can, Access.access.can, diff --git a/packages/capabilities/src/store.js b/packages/capabilities/src/store.js index 8995467d0..6582b50f0 100644 --- a/packages/capabilities/src/store.js +++ b/packages/capabilities/src/store.js @@ -77,6 +77,28 @@ export const add = capability({ }, }) +/** + * Capability to get store metadata by shard CID. + * Use to check for inclusion, or get shard size and origin + * + * `nb.link` is optional to allow delegation of `store/get` + * capability for any shard CID. If link is specified, then the + * capability only allows a get for that specific CID. + * + * When used as as an invocation, `nb.link` must be specified. + */ +export const get = capability({ + can: 'store/get', + with: SpaceDID, + nb: Schema.struct({ + /** + * shard CID to fetch info about. + */ + link: Link.optional(), + }), + derives: equalLink, +}) + /** * Capability can be used to remove the stored CAR file from the (memory) * space identified by `with` field. diff --git a/packages/capabilities/src/types.ts b/packages/capabilities/src/types.ts index 779472c1f..cd997ea5f 100644 --- a/packages/capabilities/src/types.ts +++ b/packages/capabilities/src/types.ts @@ -15,7 +15,7 @@ import type { PieceLink } from '@web3-storage/data-segment' import { space, info } from './space.js' import * as provider from './provider.js' import { top } from './top.js' -import { add, list, remove, store } from './store.js' +import * as StoreCaps from './store.js' import * as UploadCaps from './upload.js' import * as AccessCaps from './access.js' import * as CustomerCaps from './customer.js' @@ -221,14 +221,22 @@ export interface ChainTrackerInfoFailure extends Ucanto.Failure { // Upload export type Upload = InferInvokedCapability export type UploadAdd = InferInvokedCapability +export type UploadGet = InferInvokedCapability export type UploadRemove = InferInvokedCapability export type UploadList = InferInvokedCapability +export interface UploadNotFound extends Ucanto.Failure { + name: 'UploadNotFound' +} + +export type UploadGetFailure = UploadNotFound | Ucanto.Failure + // Store -export type Store = InferInvokedCapability -export type StoreAdd = InferInvokedCapability -export type StoreRemove = InferInvokedCapability -export type StoreList = InferInvokedCapability +export type Store = InferInvokedCapability +export type StoreAdd = InferInvokedCapability +export type StoreGet = InferInvokedCapability +export type StoreRemove = InferInvokedCapability +export type StoreList = InferInvokedCapability export type StoreAddSuccess = StoreAddSuccessDone | StoreAddSuccessUpload export interface StoreAddSuccessDone { @@ -257,6 +265,10 @@ export interface StoreItemNotFound extends Ucanto.Failure { export type StoreRemoveFailure = StoreItemNotFound | Ucanto.Failure +export type StoreGetSuccess = StoreListItem + +export type StoreGetFailure = StoreItemNotFound | Ucanto.Failure + export interface StoreListSuccess extends ListResponse {} export interface ListResponse { @@ -271,13 +283,21 @@ export interface StoreListItem { link: UnknownLink size: number origin?: UnknownLink + insertedAt: string } -export interface UploadAddSuccess { +export interface UploadListItem { root: UnknownLink shards?: CARLink[] + insertedAt: string + updatedAt: string } +// TODO: (olizilla) make this an UploadListItem too? +export type UploadAddSuccess = Omit + +export type UploadGetSuccess = UploadListItem + export type UploadRemoveSuccess = UploadDidRemove | UploadDidNotRemove export interface UploadDidRemove extends UploadAddSuccess {} @@ -289,8 +309,6 @@ export interface UploadDidNotRemove { export interface UploadListSuccess extends ListResponse {} -export interface UploadListItem extends UploadAddSuccess {} - // UCAN core events export type UCANRevoke = InferInvokedCapability @@ -392,10 +410,12 @@ export type AbilitiesArray = [ SpaceInfo['can'], Upload['can'], UploadAdd['can'], + UploadGet['can'], UploadRemove['can'], UploadList['can'], Store['can'], StoreAdd['can'], + StoreGet['can'], StoreRemove['can'], StoreList['can'], Access['can'], diff --git a/packages/capabilities/src/upload.js b/packages/capabilities/src/upload.js index 794b04fdd..e35b5c631 100644 --- a/packages/capabilities/src/upload.js +++ b/packages/capabilities/src/upload.js @@ -77,6 +77,38 @@ export const add = capability({ }, }) +/** + * Capability to get upload metadata by root CID. + * Use to check for inclusion, or find the shards for a root. + * + * `nb.root` is optional to allow delegation of `upload/get` + * capability for any root. If root is specified, then the + * capability only allows a get for that single cid. + * + * When used as as an invocation, `nb.root` must be specified. + */ +export const get = capability({ + can: 'upload/get', + with: SpaceDID, + nb: Schema.struct({ + /** + * Root CID of the DAG to fetch upload info about. + */ + root: Link.optional(), + }), + derives: (self, from) => { + const res = equalWith(self, from) + if (res.error) { + return res + } + if (!from.nb.root) { + return res + } + // root must match if specified in the proof + return equal(self.nb.root, from.nb.root, 'root') + }, +}) + /** * Capability removes an upload (identified by it's root CID) from the upload * list. Please note that removing an upload does not delete corresponding shards diff --git a/packages/capabilities/src/utils.js b/packages/capabilities/src/utils.js index 3b12142fb..c149c6cfb 100644 --- a/packages/capabilities/src/utils.js +++ b/packages/capabilities/src/utils.js @@ -60,7 +60,7 @@ export function equal(child, parent, constraint) { } /** - * @template {Types.ParsedCapability<"store/add"|"store/remove", Types.URI<'did:'>, {link?: Types.Link}>} T + * @template {Types.ParsedCapability<"store/add"|"store/get"|"store/remove", Types.URI<'did:'>, {link?: Types.Link}>} T * @param {T} claimed * @param {T} delegated * @returns {Types.Result<{}, Types.Failure>} diff --git a/packages/upload-api/src/store.js b/packages/upload-api/src/store.js index 0461fde34..146bcf09c 100644 --- a/packages/upload-api/src/store.js +++ b/packages/upload-api/src/store.js @@ -1,4 +1,5 @@ import { storeAddProvider } from './store/add.js' +import { storeGetProvider } from './store/get.js' import { storeListProvider } from './store/list.js' import { storeRemoveProvider } from './store/remove.js' import * as API from './types.js' @@ -9,6 +10,7 @@ import * as API from './types.js' export function createService(context) { return { add: storeAddProvider(context), + get: storeGetProvider(context), list: storeListProvider(context), remove: storeRemoveProvider(context), } diff --git a/packages/upload-api/src/store/get.js b/packages/upload-api/src/store/get.js new file mode 100644 index 000000000..e8a3c1983 --- /dev/null +++ b/packages/upload-api/src/store/get.js @@ -0,0 +1,29 @@ +import * as Server from '@ucanto/server' +import * as Store from '@web3-storage/capabilities/store' +import * as API from '../types.js' + +/** + * @param {API.StoreServiceContext} context + * @returns {API.ServiceMethod} + */ +export function storeGetProvider(context) { + return Server.provide(Store.get, async ({ capability }) => { + const { link } = capability.nb + if (!link) { + return Server.fail('nb.link must be set') + } + const space = Server.DID.parse(capability.with).did() + const res = await context.storeTable.get(space, link) + if (!res) { + return { + error: { + name: 'StoreItemNotFound', + message: 'Store item not found', + }, + } + } + return { + ok: res, + } + }) +} diff --git a/packages/upload-api/src/types.ts b/packages/upload-api/src/types.ts index d25634f13..015ad2444 100644 --- a/packages/upload-api/src/types.ts +++ b/packages/upload-api/src/types.ts @@ -52,6 +52,7 @@ export interface DebugEmail extends Email { import { StoreAdd, + StoreGet, StoreAddSuccess, StoreRemove, StoreRemoveSuccess, @@ -60,6 +61,7 @@ import { StoreListSuccess, StoreListItem, UploadAdd, + UploadGet, UploadAddSuccess, UploadRemove, UploadRemoveSuccess, @@ -109,9 +111,13 @@ import { ProviderAddFailure, SpaceInfo, ProviderDID, + StoreGetFailure, + UploadGetFailure, UCANRevoke, ListResponse, CARLink, + StoreGetSuccess, + UploadGetSuccess, UCANRevokeSuccess, UCANRevokeFailure, } from '@web3-storage/capabilities/types' @@ -137,11 +143,13 @@ export type { RateLimitsStorage, RateLimit } from './types/rate-limits' export interface Service { store: { add: ServiceMethod + get: ServiceMethod remove: ServiceMethod list: ServiceMethod } upload: { add: ServiceMethod + get: ServiceMethod remove: ServiceMethod list: ServiceMethod } @@ -325,9 +333,7 @@ export interface UcantoServerTestContext fetch: typeof fetch } -export interface StoreTestContext { - testStoreTable: TestStoreTable -} +export interface StoreTestContext {} export interface UploadTestContext {} @@ -367,9 +373,9 @@ export interface DudewhereBucket { } export interface StoreTable { - inspect: (link: UnknownLink) => Promise + inspect: (link: UnknownLink) => Promise exists: (space: DID, link: UnknownLink) => Promise - get: (space: DID, link: UnknownLink) => Promise + get: (space: DID, link: UnknownLink) => Promise insert: (item: StoreAddInput) => Promise remove: (space: DID, link: UnknownLink) => Promise list: ( @@ -378,26 +384,16 @@ export interface StoreTable { ) => Promise> } -export interface TestStoreTable { - get( - space: DID, - link: UnknownLink - ): Promise< - (StoreAddInput & StoreListItem & { insertedAt: string }) | undefined - > -} - export interface UploadTable { - inspect: (link: UnknownLink) => Promise + inspect: (link: UnknownLink) => Promise exists: (space: DID, root: UnknownLink) => Promise + get: (space: DID, link: UnknownLink) => Promise insert: (item: UploadAddInput) => Promise remove: (space: DID, root: UnknownLink) => Promise list: ( space: DID, options?: ListOptions - ) => Promise< - ListResponse - > + ) => Promise> } export type SpaceInfoSuccess = { @@ -435,7 +431,7 @@ export interface StoreAddInput { export interface StoreAddOutput extends Omit {} -export interface StoreGetOk { +export interface StoreInspectSuccess { spaces: Array<{ did: DID; insertedAt: string }> } @@ -447,7 +443,7 @@ export interface UploadAddInput { invocation: UCANLink } -export interface UploadGetOk { +export interface UploadInspectSuccess { spaces: Array<{ did: DID; insertedAt: string }> } diff --git a/packages/upload-api/src/upload.js b/packages/upload-api/src/upload.js index cbb6be515..693fa0644 100644 --- a/packages/upload-api/src/upload.js +++ b/packages/upload-api/src/upload.js @@ -1,4 +1,5 @@ import { uploadAddProvider } from './upload/add.js' +import { uploadGetProvider } from './upload/get.js' import { uploadListProvider } from './upload/list.js' import { uploadRemoveProvider } from './upload/remove.js' import * as API from './types.js' @@ -9,6 +10,7 @@ import * as API from './types.js' export function createService(context) { return { add: uploadAddProvider(context), + get: uploadGetProvider(context), list: uploadListProvider(context), remove: uploadRemoveProvider(context), } diff --git a/packages/upload-api/src/upload/get.js b/packages/upload-api/src/upload/get.js new file mode 100644 index 000000000..d15547c51 --- /dev/null +++ b/packages/upload-api/src/upload/get.js @@ -0,0 +1,29 @@ +import * as Server from '@ucanto/server' +import * as Upload from '@web3-storage/capabilities/upload' +import * as API from '../types.js' + +/** + * @param {API.UploadServiceContext} context + * @returns {API.ServiceMethod} + */ +export function uploadGetProvider(context) { + return Server.provide(Upload.get, async ({ capability }) => { + const { root } = capability.nb + if (!root) { + return Server.fail('nb.root must be set') + } + const space = Server.DID.parse(capability.with).did() + const res = await context.uploadTable.get(space, root) + if (!res) { + return { + error: { + name: 'UploadNotFound', + message: 'Upload not found', + }, + } + } + return { + ok: res, + } + }) +} diff --git a/packages/upload-api/test/handlers/store.js b/packages/upload-api/test/handlers/store.js index 956dc7703..0d51ef246 100644 --- a/packages/upload-api/test/handlers/store.js +++ b/packages/upload-api/test/handlers/store.js @@ -1,6 +1,5 @@ import { createServer, connect } from '../../src/lib.js' import * as API from '../../src/types.js' -import { CID } from 'multiformats' import * as CAR from '@ucanto/transport/car' import { base64pad } from 'multiformats/bases/base64' import * as StoreCapabilities from '@web3-storage/capabilities/store' @@ -72,7 +71,7 @@ export const test = { assert.equal(goodPut.status, 200, await goodPut.text()) - const item = await context.testStoreTable.get(spaceDid, link) + const item = await context.storeTable.get(spaceDid, link) if (!item) { return assert.equal(item != null, true) @@ -80,20 +79,15 @@ export const test = { assert.deepEqual( { - space: item.space, link: item.link.toString(), size: item.size, - issuer: item.issuer, }, { - space: spaceDid, link: link.toString(), size: data.byteLength, - issuer: alice.did(), } ) - assert.equal(CID.parse(item.invocation.toString()) != null, true) assert.equal( Date.now() - new Date(item?.insertedAt).getTime() < 60_000, true @@ -297,23 +291,19 @@ export const test = { assert.deepEqual(storeAdd.out.ok.link.toString(), link.toString()) assert.equal(storeAdd.out.ok.url == null, true) - const item = await context.testStoreTable.get(spaceDid, link) + const item = await context.storeTable.get(spaceDid, link) if (!item) { throw assert.equal(item != null, true, 'should have stored item') } assert.deepEqual( { - space: item.space, link: item.link.toString(), size: item.size, - issuer: item.issuer, }, { - space: spaceDid, link: link.toString(), size: data.byteLength, - issuer: alice.did(), } ) @@ -686,4 +676,81 @@ export const test = { assert.deepEqual(prevListResponse.out.ok.before, listResponse.out.ok.before) assert.deepEqual(prevListResponse.out.ok.after, listResponse.out.ok.after) }, + + 'store/get returns shard info': async (assert, context) => { + const { proof, spaceDid } = await registerSpace(alice, context) + const connection = connect({ + id: context.id, + channel: createServer(context), + }) + + const data = [ + new Uint8Array([11, 22, 34, 44, 55]), + new Uint8Array([22, 34, 44, 55, 66]), + ] + const links = [] + for (const datum of data) { + const storeAdd = await StoreCapabilities.add + .invoke({ + issuer: alice, + audience: connection.id, + with: spaceDid, + nb: { link: await CAR.codec.link(datum), size: datum.byteLength }, + proofs: [proof], + }) + .execute(connection) + + if (storeAdd.out.error) { + throw new Error('invocation failed', { cause: storeAdd }) + } + + assert.equal(storeAdd.out.ok.status, 'upload') + links.push(storeAdd.out.ok.link) + } + + const storeGet = await StoreCapabilities.get + .invoke({ + issuer: alice, + audience: connection.id, + with: spaceDid, + proofs: [proof], + nb: { + link: links[0], + }, + }) + .execute(connection) + + if (storeGet.out.error) { + throw new Error('invocation failed', { cause: storeGet }) + } + + assert.deepEqual(storeGet.out.ok.link, links[0]) + assert.equal(storeGet.out.ok.size, data[0].byteLength) + assert.ok(storeGet.out.ok.insertedAt) + }, + + 'store/get returns StoreItemNotFound Failure': async (assert, context) => { + const { proof, spaceDid } = await registerSpace(alice, context) + const connection = connect({ + id: context.id, + channel: createServer(context), + }) + + const link = await CAR.codec.link(new Uint8Array([11, 22, 34, 44, 55])) + + const storeGet = await StoreCapabilities.get + .invoke({ + issuer: alice, + audience: connection.id, + with: spaceDid, + proofs: [proof], + nb: { + link, + }, + }) + .execute(connection) + + assert.ok(storeGet.out.error) + assert.equal(storeGet.out.error?.name, 'StoreItemNotFound') + }, } diff --git a/packages/upload-api/test/handlers/upload.js b/packages/upload-api/test/handlers/upload.js index f6d8ab067..8e957064d 100644 --- a/packages/upload-api/test/handlers/upload.js +++ b/packages/upload-api/test/handlers/upload.js @@ -858,4 +858,76 @@ export const test = { 'mentions passed audience' ) }, + + 'upload/get returns info for one upload': async (assert, context) => { + const { proof, spaceDid } = await registerSpace(alice, context) + const connection = connect({ + id: context.id, + channel: createServer(context), + }) + + // invoke multiple upload/add with proof + const cars = [await randomCAR(128), await randomCAR(128)] + + for (const car of cars) { + await Upload.add + .invoke({ + issuer: alice, + audience: connection.id, + with: spaceDid, + nb: { root: car.roots[0], shards: [car.cid] }, + proofs: [proof], + }) + .execute(connection) + } + + const uploadGet = await Upload.get + .invoke({ + issuer: alice, + audience: connection.id, + with: spaceDid, + proofs: [proof], + nb: { + root: cars[0].roots[0], + }, + }) + .execute(connection) + + if (!uploadGet.out.ok) { + throw new Error('invocation failed', { cause: uploadGet }) + } + + assert.equal( + uploadGet.out.ok.shards?.[0].toString(), + cars[0].cid.toString() + ) + assert.equal(uploadGet.out.ok.root.toString(), cars[0].roots[0].toString()) + }, + + 'upload/get returns UploadNotFound when root is not in uploads': async ( + assert, + context + ) => { + const { proof, spaceDid } = await registerSpace(alice, context) + const connection = connect({ + id: context.id, + channel: createServer(context), + }) + + const car = await randomCAR(128) + + const uploadGet = await Upload.get + .invoke({ + issuer: alice, + audience: connection.id, + with: spaceDid, + proofs: [proof], + nb: { + root: car.roots[0], + }, + }) + .execute(connection) + + assert.equal(uploadGet.out.error?.name, 'UploadNotFound') + }, } diff --git a/packages/upload-api/test/helpers/context.js b/packages/upload-api/test/helpers/context.js index e2d9790bc..209efe887 100644 --- a/packages/upload-api/test/helpers/context.js +++ b/packages/upload-api/test/helpers/context.js @@ -61,7 +61,6 @@ export const createContext = async (options = {}) => { mail: /** @type {TestTypes.DebugEmail} */ (serviceContext.email), service: /** @type {TestTypes.ServiceSigner} */ (serviceContext.id), connection, - testStoreTable: storeTable, fetch, } } diff --git a/packages/upload-api/test/storage/store-table.js b/packages/upload-api/test/storage/store-table.js index de4e68c97..31907e4c2 100644 --- a/packages/upload-api/test/storage/store-table.js +++ b/packages/upload-api/test/storage/store-table.js @@ -2,16 +2,16 @@ import * as API from '../../src/types.js' /** * @implements {API.StoreTable} - * @implements {API.TestStoreTable} */ export class StoreTable { constructor() { - /** @type {(API.StoreAddInput & API.StoreListItem & { insertedAt: string })[]} */ + /** @type {(API.StoreAddInput & API.StoreListItem)[]} */ this.items = [] } /** * @param {API.StoreAddInput} input + * @returns {Promise} */ async insert({ space, issuer, invocation, ...output }) { this.items.unshift({ @@ -27,7 +27,7 @@ export class StoreTable { /** * * @param {API.UnknownLink} link - * @returns {Promise} + * @returns {Promise} */ async inspect(link) { const items = @@ -42,9 +42,10 @@ export class StoreTable { } /** - * + * Get info for a single shard or undefined if it doesn't exist * @param {API.DID} space * @param {API.UnknownLink} link + * @returns {Promise<(API.StoreAddInput & API.StoreListItem) | undefined>} */ async get(space, link) { return this.items.find( diff --git a/packages/upload-api/test/storage/upload-table.js b/packages/upload-api/test/storage/upload-table.js index bf9d91333..d6dcf597c 100644 --- a/packages/upload-api/test/storage/upload-table.js +++ b/packages/upload-api/test/storage/upload-table.js @@ -13,7 +13,7 @@ export class UploadTable { /** * * @param {API.UnknownLink} link - * @returns {Promise} + * @returns {Promise} */ async inspect(link) { const items = diff --git a/packages/upload-client/src/types.ts b/packages/upload-client/src/types.ts index 648caeda2..b1390f265 100644 --- a/packages/upload-client/src/types.ts +++ b/packages/upload-client/src/types.ts @@ -18,6 +18,8 @@ import { StoreAddSuccess, StoreAddSuccessUpload, StoreAddSuccessDone, + StoreGet, + StoreGetFailure, StoreList, StoreListSuccess, StoreListItem, @@ -34,6 +36,10 @@ import { ListResponse, CARLink, PieceLink, + StoreGetSuccess, + UploadGet, + UploadGetSuccess, + UploadGetFailure, } from '@web3-storage/capabilities/types' import * as UnixFS from '@ipld/unixfs/src/unixfs' @@ -70,11 +76,13 @@ export type ProgressFn = (status: ProgressStatus) => void export interface Service { store: { add: ServiceMethod + get: ServiceMethod remove: ServiceMethod list: ServiceMethod } upload: { add: ServiceMethod + get: ServiceMethod remove: ServiceMethod list: ServiceMethod } diff --git a/packages/upload-client/test/helpers/mocks.js b/packages/upload-client/test/helpers/mocks.js index 4d1386aae..937914bf6 100644 --- a/packages/upload-client/test/helpers/mocks.js +++ b/packages/upload-client/test/helpers/mocks.js @@ -14,11 +14,13 @@ 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/upload-client/test/store.test.js b/packages/upload-client/test/store.test.js index deaccca34..c6e81f84b 100644 --- a/packages/upload-client/test/store.test.js +++ b/packages/upload-client/test/store.test.js @@ -354,6 +354,7 @@ describe('Store.list', () => { { link: car.cid, size: 123, + insertedAt: '1970-01-01T00:00:00.000Z', }, ], } @@ -422,6 +423,7 @@ describe('Store.list', () => { { link: (await randomCAR(128)).cid, size: 123, + insertedAt: '1970-01-01T00:00:00.000Z', }, ], } @@ -431,6 +433,7 @@ describe('Store.list', () => { { link: (await randomCAR(128)).cid, size: 123, + insertedAt: '1970-01-01T00:00:00.000Z', }, ], } diff --git a/packages/upload-client/test/upload.test.js b/packages/upload-client/test/upload.test.js index 878d157b6..a22b10848 100644 --- a/packages/upload-client/test/upload.test.js +++ b/packages/upload-client/test/upload.test.js @@ -137,6 +137,8 @@ describe('Upload.list', () => { { root: car.roots[0], shards: [car.cid], + insertedAt: '1970-01-01T00:00:00.000Z', + updatedAt: '1970-01-01T00:00:00.000Z', }, ], } @@ -208,6 +210,8 @@ describe('Upload.list', () => { { root: car0.roots[0], shards: [car0.cid], + insertedAt: '1970-01-01T00:00:00.000Z', + updatedAt: '1970-01-01T00:00:00.000Z', }, ], } @@ -218,6 +222,8 @@ describe('Upload.list', () => { { root: car1.roots[0], shards: [car1.cid], + insertedAt: '1970-01-01T00:00:00.000Z', + updatedAt: '1970-01-01T00:00:00.000Z', }, ], } diff --git a/packages/w3up-client/test/capability/store.test.js b/packages/w3up-client/test/capability/store.test.js index 348f33834..3c5d60754 100644 --- a/packages/w3up-client/test/capability/store.test.js +++ b/packages/w3up-client/test/capability/store.test.js @@ -68,6 +68,7 @@ describe('StoreClient', () => { { link: (await randomCAR(128)).cid, size: 123, + insertedAt: '1970-01-01T00:00:00.000Z', }, ], } diff --git a/packages/w3up-client/test/capability/upload.test.js b/packages/w3up-client/test/capability/upload.test.js index e20acec29..bc8ed0956 100644 --- a/packages/w3up-client/test/capability/upload.test.js +++ b/packages/w3up-client/test/capability/upload.test.js @@ -68,6 +68,8 @@ describe('StoreClient', () => { { root: car.roots[0], shards: [car.cid], + insertedAt: '1970-01-01T00:00:00.000Z', + updatedAt: '1970-01-01T00:00:00.000Z', }, ], }