Skip to content

Node SDK ​

:::caution LNW/1 migration The package-root NativeClient now uses the server-only binary LNW/1 transport on Node 22+. Existing HTTP/JSON callers must migrate their constructor and method names, or temporarily import LegacyNativeClient from @kelphect/sdk/legacy-native. The S3 Client and JSON AdminClient remain separate compatibility and control-plane surfaces. Browser and Edge bundles cannot import the primary LNW/1 entry. :::

The first-party Lockwell SDK for Node.js is a native, encrypted alternative to the AWS S3 SDK for Node and Next.js apps. It is ESM and CommonJS and Promise-based. The package-root data plane bundles no second transport implementation: it uses the peer @kelphect/sdk-native core. The S3 entry uses node:crypto plus global fetch; the dedicated /edge entry uses only web globals such as crypto.subtle and ReadableStream.

Historical package line The private 0.3.0 package line is a pre-release artifact and predates the selected

PolyForm distribution payload. It is not an approved TangibleShift or commercial release; wait for B-010/B-013 clearance and the required written grant before commercial deployment. :::

It shares the language-neutral SigV4 signing fixtures with the Go and Java SDKs, so all three sign byte-for-byte identically.

sh
npm install @kelphect/sdk @kelphect/sdk-native

Requires Node.js >= 22 for LNW/1. Both import { Client } from '@kelphect/sdk' (ESM) and const { Client } = require('@kelphect/sdk') (CJS) work.

The package is published privately as @kelphect/sdk on GitHub Packages. The npm scope matches the GitHub owner namespace and is the supported package name for private app development.

Exports ​

ExportWhat it is
Clientthe S3 (SigV4 + XML) data-plane client. Node-only.
NativeClientthe canonical LNW/1 binary data-plane client. Node-only.
LegacyNativeClientexplicit HTTP/JSON migration client (/legacy-native).
AdminClientthe JSON admin client (admin listener).
LockwellKitthe high-level app kit.
verifyWebhookstandalone, edge-safe webhook verification.
ErpScopes / StorageProfiles / ErpErrorspure ERP scoping, storage-recipe, and safe error-classification helpers.

Error helpers ship alongside the clients:

  • S3: APIError and isNotFound.
  • LNW/1: LockwellError, ProtocolError, TransportError, ServiceError, CancelledError, and DeadlineExceededError.
  • Legacy HTTP/JSON: NativeError and isNative* helpers from @kelphect/sdk/legacy-native.
  • Admin: AdminError and isAdminNotFound.

Plus RetryPolicy, TimeoutError, sha256ChecksumBase64, computeChecksumBase64, checksumHeaderName, CHECKSUM_ALGORITHMS, buildPresignedGetUrl, buildPresignedObjectUrl, urlForKey, createNodeFetch, and WEBHOOK_SIGNATURE_HEADER_NAME.

On edge runtimes, import from @kelphect/sdk/edge instead.

Client (the S3 client) ​

ts
import { 
Client
} from "@kelphect/sdk";
import {
createReadStream
} from "node:fs";
const
client
= new
Client
({
endpoint
: "https://objects.example.com",
accessKeyId
:
process
.
env
.
LOCKWELL_ACCESS_KEY_ID
!,
secretKey
:
process
.
env
.
LOCKWELL_SECRET_KEY
!,
}); // SSE-S3 at rest, a server-verified CRC64NVME checksum, and an idempotency key // for safe retries, in one call. const
put
= await
client
.
putObject
("reports", "q1.txt",
Buffer
.
from
("hello"), {
contentType
: "text/plain",
serverSideEncryption
: true,
checksumAlgorithm
: "CRC64NVME",
idempotencyKey
: "q1-2026",
}); const
got
= await
client
.
getObject
("reports", "q1.txt");
console
.
log
(
got
.
body
.
toString
());
// Stream a large file without buffering (checksum sent in an aws-chunked trailer). await
client
.
putObjectStream
("reports", "big.bin",
createReadStream
("big.bin"), "CRC64NVME");
// Presigned GET; PUT, HEAD, and DELETE helpers are also available. const
url
=
client
.
presignGetObject
("reports", "q1.txt", 900);

Path-style addressing is the default. A clean reverse-proxy prefix such as https://objects.example.com/lockwell is preserved in normal and presigned request paths and participates in SigV4 signing. Already-mounted /api/v1 and /admin/api/v1 suffixes are normalized once, and traversal, encoded separators, malformed escapes, and unsafe interior path segments are rejected. Set virtualHostedStyle: true only when the endpoint has wildcard bucket DNS; normal requests and presigned URLs then put the bucket, including dotted bucket names, in the signed host.

Hover any identifier above: the tooltips are the SDK's real type signatures, checked at build time against the published type definitions.

Construction ​

Constructor options include endpoint, accessKeyId, secretKey, optional fetch, userAgent, now, retry, timeoutMs, signal, and virtualHostedStyle.

Retry is opt-in: pass retry: RetryPolicy.default() or retry: { maxAttempts: 3 }; the default is a single attempt. fetch lets you inject a custom fetch implementation, and now overrides the signing clock (useful in tests).

Buckets ​

MethodSignature
createBucketcreateBucket(bucket, opts?) (opts { objectLock: { mode, days } })
headBucketheadBucket(bucket)
deleteBucketdeleteBucket(bucket)
putBucketVersioningputBucketVersioning(bucket, status) ('Enabled' / 'Suspended')
getBucketVersioninggetBucketVersioning(bucket) (returns '' when never enabled)

Objects ​

MethodSignature
putObjectputObject(bucket, key, body, opts?)
putObjectStreamputObjectStream(bucket, key, source, checksumAlgorithm, opts?)
getObjectgetObject(bucket, key, opts?) (fully buffered body)
getObjectStreamgetObjectStream(bucket, key, opts?) (body is the ReadableStream; readAll() buffers it)
headObjectheadObject(bucket, key, opts?)
deleteObjectdeleteObject(bucket, key, opts?) (opts.versionId)
deleteObjectsdeleteObjects(bucket, objects, opts?) (batch <= 1000)
copyObjectcopyObject(srcBucket, srcKey, srcVersionId, dstBucket, dstKey, opts?)

putObject options:

OptionEffect
contentTypeSets Content-Type.
metadataObject of user metadata, stored as x-amz-meta-*.
idempotencyKeyThe signed x-lockwell-idempotency-key; makes the write replay-safe.
serverSideEncryption: trueRequests SSE-S3 at rest.
checksumAlgorithmOne of CHECKSUM_ALGORITHMS; the SDK computes and sends the verified digest.
objectLockMode'GOVERNANCE' or 'COMPLIANCE', set on PUT.
objectLockRetainUntilRFC3339 retain-until date.
objectLockLegalHoldtrue/false (mapped to ON/OFF).

getObject / getObjectStream / headObject options: range, partNumber, versionId, responseContentType. getObjectStream is the right call for large objects: iterate body yourself or call readAll() to buffer into a Buffer.

deleteObjects takes entries of { key, versionId? } (or bare key strings) and returns { deleted, errors }. The batch may partially succeed; opts.quiet suppresses the per-key deleted entries (errors are always returned).

Listing and pagination ​

MethodSignature
listObjectsV2listObjectsV2(bucket, opts?) (prefix, delimiter, startAfter, continuationToken, maxKeys)
listObjectslistObjects(bucket, opts?) (v1: marker pagination)
listObjectVersionslistObjectVersions(bucket, opts?) (keyMarker, versionIdMarker, ...)
listMultipartUploadslistMultipartUploads(bucket, opts?)
listPartslistParts(bucket, key, uploadId, opts?)

Each list* has a paginate* async iterator that threads continuation/marker tokens for you:

js
for await (const page of client.paginateObjectsV2("reports", { prefix: "logs/" })) {
  for (const obj of page.objects) console.log(obj.key, obj.size);
}

The five iterators are paginateObjectsV2, paginateObjects, paginateObjectVersions, paginateMultipartUploads, and paginateParts.

Multipart ​

js
const mpu = await client.createMultipartUpload("reports", "big.bin", { checksumAlgorithm: "CRC32C" });
const p1 = await client.uploadPart("reports", "big.bin", mpu.uploadId, 1, part1, {
  checksumAlgorithm: mpu.checksumAlgorithm,
});
const p2 = await client.uploadPart("reports", "big.bin", mpu.uploadId, 2, part2, {
  checksumAlgorithm: mpu.checksumAlgorithm,
});
const done = await client.completeMultipartUpload("reports", "big.bin", mpu.uploadId, [
  { partNumber: 1, etag: p1.etag },
  { partNumber: 2, etag: p2.etag },
]);

Also uploadPartStream(bucket, key, uploadId, partNumber, source, checksumAlgorithm, opts?), uploadPartCopy(srcBucket, srcKey, srcVersionId, dstBucket, dstKey, uploadId, partNumber, byteRange, opts?), and abortMultipartUpload(bucket, key, uploadId). When you declare a checksumAlgorithm on create, pass it back on every part, and the composite checksum returns on complete. SSE-C uses the same raw 32-byte sseCustomerKey at create, part, and completion; copy parts separately accept copySourceSseCustomerKey.

Tagging and Object Lock reads ​

putObjectTagging(bucket, key, tags), getObjectTagging(bucket, key), deleteObjectTagging(bucket, key), getObjectRetention(bucket, key, opts?), and getObjectLegalHold(bucket, key, opts?). Retention and legal hold are set on the write through the putObject object-lock options above, then read back here.

Presigned GET ​

presignGetObject(bucket, key, expiresSeconds, opts?) returns a time-limited GET URL (opts.versionId, opts.responseContentType are folded into the signature). presignPutObject, presignHeadObject, and presignDeleteObject cover the other supported object methods; for a native signed write URL use the native signUrl.

Streaming uploads and checksums ​

putObjectStream(bucket, key, source, checksumAlgorithm, opts?) streams source (a ReadableStream or async-iterable) and sends the checksum in an aws-chunked trailer, so checksumAlgorithm is required.

Streaming PUT/GET and multipart part options accept an awaited onProgress callback. TransferProgress reports direction, cumulative bytes, chunkBytes, totalBytes (null when unknown), and partNumber for a part. The callback is awaited before the next chunk is read/enqueued, preserving backpressure; a rejection aborts the request and cancels its source. onResponseMetadata is an additive client option for successful request-id/trace headers. One-shot streams are not transparently resumed; use range/ETag or multipart list/abort and application-owned atomic file handling for recovery.

The exported computeChecksumBase64(alg, data) and checksumHeaderName(alg) let you precompute a digest yourself. CHECKSUM_ALGORITHMS is the supported list (CRC32, CRC32C, CRC64NVME, SHA1, SHA256).

Errors ​

Client throws APIError with code, message, statusCode, requestId. Use isNotFound(err) for a missing bucket/key/upload.

NativeError and AdminError preserve RFC 9457 type, title, detail, instance, and unknown extension members alongside code, statusCode, and requestId. Treat the HTTP status and exact machine code as authoritative; never branch on the human message.

js
import { isNotFound } from "@kelphect/sdk";
try {
  await client.getObject("reports", "missing.txt");
} catch (err) {
  if (isNotFound(err)) {
    /* 404 */
  } else throw err;
}

NativeClient (canonical LNW/1 data plane) ​

The package-root NativeClient is the server-only binary LNW/1 client backed by the shared @kelphect/sdk-native TypeScript core. It requires Node 22+ (or Bun 1.4+ when using the shared runtime package) and preserves bounded frames, TLS/mTLS verification, multiplexing, flow-control backpressure, deadlines, cancellation, replay-safe retries, redacted diagnostics, and typed response metadata.

js
import { NativeClient } from "@kelphect/sdk";

const native = new NativeClient({
  host: "objects.example.com",
  port: 9444,
  tls: { ca: process.env.LOCKWELL_CA_PEM },
  credentials: { accessKeyId, secretKey },
});

await native.putObject({
  bucket: "reports",
  key: "daily.json",
  body: new TextEncoder().encode("{}"),
  contentLength: 2,
  options: { idempotencyKey: "daily-2026-08-31" },
});
const response = await native.getObject({ bucket: "reports", key: "daily.json" });
for await (const chunk of response.body) {
  // consume the bounded stream; cancellation propagates to LNW/1
  void chunk;
}
await native.close();

The typed client includes buckets, objects, ranges, metadata, checksums, pagination, copy, multipart, versioning/delete markers, Object Lock retention, legal hold, CORS, notifications, and signed capabilities. Use createLockwellClient(config) when selecting the Node or Bun connector explicitly. The JSON AdminClient remains a separate control plane; no admin wire opcodes are added to LNW/1.

LegacyNativeClient (HTTP/JSON migration client) ​

The legacy JSON data plane at /api/v1/ remains available only for bounded migrations. New server code must use the package-root LNW/1 NativeClient.

Configured with an access-key id plus secret, it auto-manages the bearer token: mints a lwtk_… token on first use, caches it until shortly before expiry, refreshes transparently, and re-mints once on a 401. Token acquisition is concurrency-safe, with a single in-flight mint shared across a burst of requests (single-flight), never one mint per request.

js
import { LegacyNativeClient, sha256ChecksumBase64 } from "@kelphect/sdk/legacy-native";

const native = new LegacyNativeClient({
  endpoint: "https://objects.example.com", // public listener (same as S3)
  accessKeyId: process.env.LOCKWELL_ACCESS_KEY_ID,
  secretKey: process.env.LOCKWELL_SECRET_KEY,
});

await native.createBucket("reports", { versioning: true });

// Body may be a Buffer/Uint8Array/string OR a ReadableStream/async-iterable.
// An idempotent PUT needs a body-integrity signal: pass an expected checksum.
const put = await native.putObject("reports", "q1.txt", Buffer.from("hello"), {
  contentType: "text/plain",
  idempotencyKey: "q1-2026",
  checksums: { sha256: await sha256ChecksumBase64("hello") },
});
console.log(put.versionId, put.etag);

// Streaming GET (no whole-object buffering) with a Range.
const got = await native.getObjectStream("reports", "big.bin", { range: "bytes=0-1023" });
for await (const chunk of got.body) {
  /* Uint8Array */
}

Buckets and objects ​

MethodSignature
listBucketslistBuckets()
createBucketcreateBucket(bucket, opts?) ({ versioning?, objectLock? })
getBucketgetBucket(bucket)
deleteBucketdeleteBucket(bucket)
getBucketVersioning / setBucketVersioningversioning state ('Enabled'/'Suspended')
setBucketCors / getBucketCors / deleteBucketCorsbrowser CORS rules
putObjectputObject(bucket, key, body, opts?) (streaming)
getObject / getObjectStreambuffered / streaming download ({ range?, versionId? })
headObjectheadObject(bucket, key, opts?)
deleteObjectdeleteObject(bucket, key, opts?)
listObjectslistObjects(bucket, opts?)
batchDeleteObjectsbatchDeleteObjects(bucket, objects, opts?) (<= 1000)
copyObjectcopyObject(dstBucket, dstKey, source)

putObject options: { contentType?, metadata?, idempotencyKey?, ifMatch?, ifNoneMatch?, checksums?: { <alg>: <base64> }, contentLength? }. ifNoneMatch: '*' creates only when absent; ifMatch: '<etag>' overwrites only on a matching ETag. A bad checksums digest is rejected before any bytes are committed.

copyObject(dstBucket, dstKey, source) takes the source plus directives in source:

ts
{ sourceBucket, sourceKey, sourceVersionId?, metadataDirective?, contentType?, metadata?,
  ifMatch?, ifNoneMatch?, ifModifiedSince?, ifUnmodifiedSince?, requireAbsent?, requireMatchEtag? }

getObjectTags / setObjectTags / deleteObjectTags, getObjectRetention / setObjectRetention, getObjectLegalHold / setObjectLegalHold, listObjectVersions, and multipart (createMultipartUpload, uploadPart, listParts, completeMultipartUpload, abortMultipartUpload, listMultipartUploads).

The async iterators paginateObjects, paginateObjectVersions, and paginateMultipartUploads page automatically.

Bucket CORS ​

setBucketCors accepts the native shape ({ rules: [...] }), a single rule, or an array of rules:

js
await native.setBucketCors("reports", {
  rules: [
    {
      allowedOrigins: ["https://app.example.com"],
      allowedMethods: ["GET", "HEAD", "PUT"],
      allowedHeaders: ["content-type"],
      exposeHeaders: ["ETag"],
      maxAgeSeconds: 600,
    },
  ],
});

const cors = await native.getBucketCors("reports");
await native.deleteBucketCors("reports");

CORS is browser policy, not authorization. Changing it is an admin-scoped bucket operation; app code should usually use kit.configureBucketCors(...) or provisionTenant(..., { bucketCors }) so the admin-capable key is transient.

Signed URLs (GET and PUT) ​

The native API supports signed write URLs (unlike the S3 presigner):

js
const upload = await native.signUrl({ method: "PUT", bucket: "reports", key: "incoming.bin", ttlSeconds: 300 });
const download = await native.signUrl({ method: "GET", bucket: "reports", key: "q1.txt" });

The minted URL is usable without a bearer token and can never exceed the minting key's scope (a read-only key minting a PUT URL is a 403). See signed URLs.

Bucket notifications ​

Webhook-only delivery; a new config ID returns its signing secret exactly once (GET/update carry only hasSecret). Events accept the canonical s3:Object* names or the shorthand 'object-created' / 'object-removed'; filters take { prefix?, suffix? }.

js
const created = await native.setBucketNotification("reports", {
  id: "reports-events",
  webhookUrl: "https://my-app.example.com/hooks/lockwell",
  events: ["object-created", "object-removed"],
  filters: { prefix: "uploads/", suffix: ".pdf" },
});
console.log(created.configs[0].signingSecret); // shown once; store securely
const cfg = await native.getBucketNotification("reports");
console.log(cfg.configs[0].hasSecret); // signingSecret is omitted on GET
await native.deleteBucketNotification("reports"); // clear

Errors ​

LegacyNativeClient throws NativeError mapped from the API's problem+json: isNativeNotFound (404), isNativeConflict (409), isNativePreconditionFailed (412), isNativeForbidden (403), isNativeUnauthorized (401), isNativeQuotaExceeded (507).

AdminClient (the admin client) ​

Targets /admin/api/v1/ on the admin listener (never the public S3 port). It authenticates by an admin API bearer token (lockwell admin-token create).

A created or rotated key returns its secret exactly once. Store it on the spot; listKeys never returns

secrets. :::

js
import { AdminClient } from "@kelphect/sdk";

const admin = new AdminClient({
  endpoint: "https://admin.example.com", // admin listener, not the S3 port
  token: process.env.LOCKWELL_ADMIN_TOKEN, // Authorization: Bearer <token>
});

const tenant = await admin.createTenant({ id: "acme", name: "Acme Inc" });

// Mutations accept { dryRun } to preview the plan (sends ?dryRun=true).
const plan = await admin.deleteTenant("acme", { reason: "offboarding", confirm: "acme", dryRun: true });

// The secret is returned exactly once on create/rotate. Store it immediately.
const key = await admin.createKey("acme", { scopes: "read,write,delete" });
console.log(key.secretKey);
MethodSignature
listTenants / getTenantlistTenants() / getTenant(id)
createTenantcreateTenant({ id, name?, dryRun? })
disableTenantdisableTenant(id, { reason, dryRun? })
deleteTenantdeleteTenant(id, { reason, confirm, dryRun? })
getQuota / setQuota / clearQuotasetQuota(id, bytes, { dryRun? })
getUsagegetUsage(id)
listAccounts / createAccountcreateAccount(id, { name, dryRun? })
listKeyslistKeys(id) (never returns secrets)
createKeycreateKey(id, { accountId?, scopes?, expiresAt?, dryRun? })
rotateKeyrotateKey(id, keyId, { scopes?, expiresAt?, dryRun? })
revokeKeyrevokeKey(id, keyId, { reason, dryRun? })
queryAuditqueryAudit({ tenant?, since?, limit? })

scopes follows the verb list (read,write,delete,admin) or resource grammar (op=read:bucket=reports:prefix=in/,op=write:bucket=reports:prefix=in/); expiresAt is RFC3339 or YYYY-MM-DD. Errors throw AdminError; use isAdminNotFound(err). See the Admin API reference.

LockwellKit (the app kit) ​

Composes the Admin and Native clients so an app does not hand-roll tenant-to-key provisioning, per-tenant clients, browser direct upload and download, or webhook verification.

js
import { LockwellKit } from "@kelphect/sdk";

const kit = new LockwellKit({
  admin: { endpoint: "https://admin.example.com", token: process.env.LOCKWELL_ADMIN_TOKEN },
  native: { endpoint: "https://objects.example.com" }, // public listener; creds are per-tenant
});

// 1) Provision: ensure the tenant exists (a pre-existing tenant is NOT an error),
//    mint a FRESH scoped key, optionally create a default bucket. Secret returned ONCE.
const { key } = await kit.provisionTenant("acme", { defaultBucket: "inbox" });
// persist { key.accessKeyId, key.secretKey } in your tenant store

// 2) A per-tenant native client (cached per tenant+accessKeyId).
const client = kit.clientForTenant("acme", { accessKeyId: key.accessKeyId, secretKey: key.secretKey });
await client.putObject("inbox", "hello.txt", "hi");

// 3) Browser direct upload/download. Hand the URL straight to the browser.
const up = await kit.signedUploadUrl(client, "inbox", "photo.jpg", { ttl: 300, contentType: "image/jpeg" });
const down = await kit.signedDownloadUrl(client, "inbox", "photo.jpg", { ttl: 300 });

// Or update CORS later with another transient admin-scoped key.
await kit.configureBucketCors("acme", "inbox", {
  rules: [{ allowedOrigins: ["https://app.example.com"], allowedMethods: ["GET", "HEAD", "PUT"] }],
});

// 4) Verify an incoming Lockwell webhook (constant-time HMAC-SHA256).
const ok = await kit.verifyWebhook(rawRequestBodyBytes, req.headers["x-lockwell-signature"], secret);

provisionTenant(tenantId, opts?) options: { name?, scopes?, accountId?, expiresAt?, defaultBucket?, bucketVersioning?, bucketScope?, bucketCors? }. It mints a data key only (default read,write,delete, or narrowed via bucketScope). A defaultBucket is created with a transient admin-on-that-bucket key that is revoked immediately; bucketCors uses that same transient key before revoke.

signedUploadUrl and signedDownloadUrl accept either a LegacyNativeClient (from clientForTenant) or { accessKeyId, secretKey, tenantId? } creds. configureBucketCors mints and revokes its own transient admin-scoped bucket key. clientForTenant is also aliased as forTenant. See the app kit guide.

The retry-safe adoption helpers are ensureTenant, ensureKey, and ensureTenantProvisioning. They reuse only a matching active key anchored by externalRef; a reused key never pretends to have a recoverable secret. The ERP helper families derive opaque tenant/company/purpose paths and least-privilege scopes, produce explicit storage recipes, and classify native/admin failures without copying unsafe server messages. They do not choose legal policy or persist ERP mappings for you.

NativeClient and AdminClient also expose healthz() and readyz() with per-call signal/timeoutMs controls and typed readiness components. Probe calls do not send data-plane or admin credentials.

Edge safety ​

The default barrel re-exports the S3 Client, whose SigV4 code statically imports node:crypto. On Cloudflare Workers, Vercel Edge, Bun, and Deno, import from the dedicated edge entry instead:

js
import { LockwellKit, LegacyNativeClient, AdminClient, verifyWebhook } from "@kelphect/sdk/edge";

@kelphect/sdk/edge re-exports only the node:*-free compatibility surface (the LegacyNativeClient, AdminClient, LockwellKit, verifyWebhook, sha256ChecksumBase64, RetryPolicy). It deliberately omits the S3 Client, so a bundler produces a bundle with zero node:crypto and no nodejs_compat flag.

The LegacyNativeClient itself uses only web globals (fetch, ReadableStream, TextEncoder, btoa, crypto.subtle). For a SHA-256 checksum without node:crypto, sha256ChecksumBase64 computes one with WebCrypto. See edge runtimes.

The default @kelphect/sdk import pulls in node:crypto through the S3 Client. On an edge runtime,

import from @kelphect/sdk/edge, which omits the S3 Client. :::

Retry policy ​

RetryPolicy uses the shared replay-safety rules, while language defaults and callback shapes differ where the capability index says so. RetryPolicy.default() is up to 3 attempts, 100ms base backoff doubling to a 2s cap, with full jitter; RetryPolicy.disabled() attempts every request once. The constructor accepts { maxAttempts?, baseBackoffMs?, maxBackoffMs?, jitter?, rand?, sleep? }.

It retries GET/HEAD/DELETE and any PUT/POST carrying an idempotency key, on transport errors and 5xx/429; a 4xx other than 429 is never retried, and streaming bodies are never retried.

TypeScript ​

The package ships type declarations (@kelphect/sdk resolves to types/index.d.ts), so endpoint, accessKeyId, secretKey, the PutOptions and GetOptions shapes, the ChecksumAlgorithm union ('CRC32' | 'CRC32C' | 'CRC64NVME' | 'SHA1' | 'SHA256'), and the result types are all typed. The edge entry has its own types/edge.d.ts.

Not supported (by design) ​

The S3 Client presigns GET/PUT/HEAD/DELETE. Everywhere: no public-bucket or anonymous access without a token or signed URL, no SSE-KMS, no IAM/STS/bucket-policy, no website/tiering/Select/Object-Lambda, and webhook is the only notification target (SNS/SQS/Lambda are a 501 non-goal).

The Lockwell server and this first-party client support SSE-C and copy-source SSE-C through sseCustomerKey and copySourceSseCustomerKey raw 32-byte options on object, copy, and multipart operations.

Source-available under PolyForm Noncommercial 1.0.0; commercial use requires a written grant. License