Backblaze B2 ​

The @sobj/b2 adapter connects Sobj to Backblaze B2 using the S3-compatible API.

It signs requests with AWS Signature Version 4 using the Web Crypto API — no AWS SDK and no external dependencies.

📥 Installation ​

bash
pnpm add sobj @sobj/b2

⚙️ Configuration ​

Create a B2 client by passing your credentials to b2():

ts
import { sobj } from 'sobj'
import { b2 } from '@sobj/b2'

const storage = sobj(
  b2({
    region: 'us-east-005',
    bucket: 'YOUR_BUCKET',
    applicationKeyId: 'YOUR_APPLICATION_KEY_ID',
    applicationKey: 'YOUR_APPLICATION_KEY',
  })
)

🔧 Config options ​

OptionTypeRequiredDescription
regionstring✓Backblaze B2 S3-compatible region, for example us-east-005
bucketstring✓B2 bucket name
applicationKeyIdstring✓Backblaze application key ID
applicationKeystring✓Backblaze application key
endpointstring–Override the S3 endpoint (e.g. for custom routing or dedicated endpoints)
fetchtypeof fetch–Custom fetch implementation

📊 Capabilities ​

The B2 adapter supports all Sobj operations:

OperationSupported
put✓
get✓
head✓
delete✓
list✓
copy✓
presign✓
multipart✓
versioning✓

⚡ Operations ​

📤 put ​

Upload an object:

ts
await storage.put('images/avatar.png', body, {
  contentType: 'image/png',
  metadata: { userId: '123' },
})

The body can be a string, Uint8Array, ArrayBuffer, Blob, or ReadableStream<Uint8Array>.

📥 get ​

Retrieve an object. Returns null if not found.

ts
const object = await storage.get('images/avatar.png')

if (object) {
  const text = await new Response(object.body).text()
}

With a byte range:

ts
const object = await storage.get('video.mp4', {
  range: { start: 0, end: 1_048_575 }, // first 1 MB
})

🔍 head ​

Retrieve metadata without downloading the object body:

ts
const metadata = await storage.head('images/avatar.png')

if (metadata) {
  console.log(metadata.size, metadata.contentType, metadata.etag)
}

🗑️ delete ​

Delete an object:

ts
await storage.delete('images/avatar.png')

📋 list ​

List objects in the bucket:

ts
const result = await storage.list()

for (const object of result.objects) {
  console.log(object.key)
}

With options:

ts
const result = await storage.list({
  prefix: 'images/',
  delimiter: '/',
  limit: 100,
  cursor: result.cursor, // for pagination
})

The prefixes array contains common prefixes (virtual directories) when a delimiter is used.

📂 copy ​

Copy an object within the same bucket:

ts
await storage.copy('source/file.txt', 'destination/file.txt')

Prevent overwriting an existing destination:

ts
await storage.copy('source.txt', 'dest.txt', { overwrite: false })

🔑 presign ​

Generate a pre-signed URL for GET or PUT:

ts
const url = await storage.presign.get('images/avatar.png', {
  expiresIn: 3600, // seconds (default: 3600)
})

Pre-signed upload URL:

ts
const url = await storage.presign.put('uploads/file.bin', {
  expiresIn: 900,
  contentType: 'application/octet-stream',
})

📦 multipart ​

Upload large objects using the S3 multipart upload API:

ts
const upload = await storage.multipart.create('large-file.bin')

const part1 = await storage.multipart.uploadPart(upload, 1, chunk1)
const part2 = await storage.multipart.uploadPart(upload, 2, chunk2)

await storage.multipart.complete(upload, [part1, part2])

Abort an incomplete upload:

ts
await storage.multipart.abort(upload)

🏷️ versioning ​

Manage object versions when bucket versioning is enabled:

ts
// List versions of a key
const result = await storage.versioning.list('images/avatar.png')

for (const version of result.versions) {
  console.log(version.version, version.isLatest, version.isDeleteMarker)
}

// Retrieve a specific version
const object = await storage.versioning.get('images/avatar.png', '4_z8e9..._f0')

// Delete a specific version
await storage.versioning.delete('images/avatar.png', '4_z8e9..._f0')

⚠️ Error handling ​

The adapter throws SobjError subclasses:

ts
import { isSobjError, SobjNotFoundError } from 'sobj/errors'

try {
  const object = await storage.get('missing.txt')
} catch (error) {
  if (error instanceof SobjNotFoundError) {
    console.log('Object not found')
  } else if (isSobjError(error)) {
    console.log(error.code, error.status)
  }
}

See the Errors reference for all available error classes.