ObjectStackObjectStack

services.storage

File/object storage contract for upload/download and presigned URL workflows.

  • Stability: stable
  • Canonical source: packages/spec/src/contracts/storage-service.ts
  • Registry slot: storage — resolve with ctx.getService('storage'). The pre-rename spelling file-storage stays accepted as a deprecated alias within v17. See Registry slot and its deprecated alias.

Registry slot and its deprecated alias

storage is the canonical registry slot (maintainer ruling, 2026-08-18, issue #9683): it is a member of CoreServiceName (packages/spec/src/system/core-services.zod.ts), CORE_SERVICE_PROVIDER maps it to @objectstack/service-storage, and it is the key the /api/v1/discovery document reports this service's availability under. The accessor spelling and the slot are now the same word, like every other service in this chapter.

The slot was spelled file-storage before the rename, and that spelling is a published CoreServiceName member — so it stays accepted as a deprecated alias within v17. @objectstack/service-storage registers the same instance under both names:

ctx.getService('storage');       // ✓ canonical — the IStorageService documented below
ctx.getService('file-storage');  // ✓ deprecated v17 alias — the SAME instance

New code resolves storage. The alias is retired through the standard retirement flow at the next major. If you ship a custom provider for this slot in v17, register it under both names the way service-storage does, so callers of either spelling keep resolving it.

Not this: the client SDK storage accessor

A second storage accessor exists and is easy to reach for by mistake. ObjectStackClient.storage (packages/client/src/index.ts) is the browser/HTTP client surface — upload(file, scope), getDownloadUrl(fileId), getPresignedUrl(req), initChunkedUpload(req) — which calls /api/v1/storage over the wire. It is a different shape from the server-side IStorageService documented on this page, which takes storage keys and returns Buffers. ctx.getService('storage') resolves the server-side service documented here — never the client accessor.

Core Methods

services.storage.upload(key: string, data: Buffer | ReadableStream, options?: StorageUploadOptions): Promise<void>
services.storage.download(key: string): Promise<Buffer>
services.storage.delete(key: string): Promise<void>
services.storage.exists(key: string): Promise<boolean>
services.storage.getInfo(key: string): Promise<StorageFileInfo>
services.storage.getSignedUrl?(key: string, expiresIn: number, options?: PresignedDownloadOptions): Promise<string>

Presigned / Chunked (optional)

services.storage.getPresignedUpload?(key: string, expiresIn: number, options?: StorageUploadOptions): Promise<PresignedUploadDescriptor>
services.storage.getPresignedDownload?(key: string, expiresIn: number, options?: PresignedDownloadOptions): Promise<PresignedDownloadDescriptor>
services.storage.initiateChunkedUpload?(key: string, options?: StorageUploadOptions): Promise<string>
services.storage.uploadChunk?(uploadId: string, partNumber: number, data: Buffer): Promise<string>
services.storage.completeChunkedUpload?(uploadId: string, parts: Array<{ partNumber: number; eTag: string }>): Promise<string>
services.storage.abortChunkedUpload?(uploadId: string): Promise<void>

Removed

services.storage.list?(prefix) was removed in @objectstack/spec 5.x — the contract has no prefix-enumeration method. It had no consumer, and the two shipped adapters gave the same call two different, silently-incomplete answers (local: one level, directories reported as files; S3: recursive, truncated at 1000 objects). Query the file records you wrote rather than the bucket; a future enumeration returns cursor-shaped (list(prefix, { cursor, limit })). See the contract reference.

Typical Errors

Storage methods reject with a plain Error carrying a descriptive message — the built-in adapters do not attach a stable error code. Common cases include:

  • File not found on download / getInfo — the local adapter surfaces the underlying filesystem ENOENT error; reference/mock adapters reject with a File not found: <key> message.
  • Invalid or expired upload session on the chunked methods (e.g. Upload session "<id>" not found).
  • Path-traversal / invalid key rejection from the local adapter (e.g. path traversal not allowed).

A request that fails an upstream permission check surfaces the framework-wide PERMISSION_DENIED error from the security layer (code: 'PERMISSION_DENIED'), not the storage adapter.

On this page