Skip to main content
Ask your AI

S3

Server Source code Package

S3-compatible object storage store using s3mini (~20 KB, zero dependencies). Works with AWS S3, Cloudflare R2, Scaleway, DigitalOcean Spaces, Backblaze B2, MinIO, and any S3-compatible provider.

Installation​

npm install @walkeros/server-store-s3
import { startFlow } from '@walkeros/collector';
import { storeS3Init } from '@walkeros/server-store-s3';

await startFlow({
  stores: {
    assets: {
      code: storeS3Init,
      config: {
        settings: {
          bucket: 'my-assets',
          endpoint: 'https://s3.eu-west-1.amazonaws.com',
          accessKeyId: process.env.S3_ACCESS_KEY,
          secretAccessKey: process.env.S3_SECRET_KEY,
          region: 'eu-west-1',
          prefix: 'public',
        },
      },
    },
  },
});

Configuration​

This store uses the standard store config wrapper (consent, data, env, id, ...). For the shared fields see store configuration. Package-specific fields live under config.settings and are listed below.

Settings​

PropertyTypeDescriptionMore
bucket*stringS3 bucket name
endpoint*stringS3-compatible endpoint URL
accessKeyId*stringS3 access key ID
secretAccessKey*stringS3 secret access key
regionstringAWS region for SigV4 signing
prefixstringKey prefix prepended to all store keys for scoping
* Required fields

Mapping​

This package does not define custom rule-level settings. For the standard rule fields (consent, condition, data, batch, name, policy) see mapping.

Examples

Prefix scoping

Key "walker.js" with prefix "public/" resolves to S3 path "public/walker.js"

Event
{
  "operation": "get",
  "key": "walker.js",
  "settings": {
    "bucket": "my-assets",
    "prefix": "public"
  }
}
Out
get("public/walker.js", "Bytes<...>")

Read from S3

Read object from S3 and receive its raw bytes byte-exact

Event
{
  "operation": "get",
  "key": "walker.js"
}
Out
get("walker.js", "Bytes<(function(){...})()>")

Provider examples​

ProviderEndpointNotes
AWS S3https://s3.<region>.amazonaws.comSet region to your actual region
Cloudflare R2https://<account>.r2.cloudflarestorage.comNo egress fees
Scalewayhttps://s3.<region>.scw.cloudEU hosting
DigitalOceanhttps://<region>.digitaloceanspaces.comSimple pricing
Backblaze B2https://s3.<region>.backblazeb2.comCheapest storage
MinIOhttp://localhost:9000Self-hosted

Credentials​

Use $env. references in your flow config to avoid hardcoding secrets:

"accessKeyId": "$env.S3_ACCESS_KEY",
"secretAccessKey": "$env.S3_SECRET_KEY"

Unlike the AWS SDK, s3mini has no implicit credential chain: accessKeyId and secretAccessKey are always required.

File serving pattern​

The primary use case is serving static files via the file transformer. This is the recommended pattern for managed deployments where files live in a bucket rather than needing to be baked into a Docker image. Set file: true so the store persists and returns bytes byte-exact:

{
  "stores": {
    "assets": {
      "package": "@walkeros/server-store-s3",
      "config": {
        "settings": {
          "bucket": "my-assets",
          "endpoint": "https://s3.eu-west-1.amazonaws.com",
          "accessKeyId": "$env.S3_ACCESS_KEY",
          "secretAccessKey": "$env.S3_SECRET_KEY",
          "prefix": "public"
        },
        "file": true
      }
    }
  },
  "transformers": {
    "file": {
      "package": "@walkeros/server-transformer-file",
      "config": { "settings": { "prefix": "/static" } },
      "env": { "store": "$store.assets" }
    }
  }
}

A request to /static/walker.js looks up public/walker.js in the my-assets bucket. Omit file for a structured key-value store instead.

Provisioning​

The package ships an idempotent setup() lifecycle, invoked only by the explicit operator command:

walkeros setup store.<id>

It never runs automatically. It checks whether the bucket exists and creates it if not. Re-running setup is a no-op when the bucket already exists in your account.

Setup options​

OptionTypeDefaultNotes
regionstringeu-central-1Region the bucket is created in (LocationConstraint). Falls back to settings.region when concrete (not auto).

bucket is taken from settings.bucket and is NOT duplicated under setup.

Enable provisioning​

Set setup: true in the component config to enable provisioning with defaults, or pass an object to override:

{
  "stores": {
    "assets": {
      "package": "@walkeros/server-store-s3",
      "config": {
        "settings": {
          "bucket": "my-assets",
          "endpoint": "https://s3.eu-central-1.amazonaws.com",
          "accessKeyId": "$env.S3_ACCESS_KEY",
          "secretAccessKey": "$env.S3_SECRET_KEY",
          "region": "eu-central-1"
        },
        "setup": true
      }
    }
  }
}

What setup does NOT apply​

s3mini is a minimal S3 client that exposes createBucket and bucketExists only. It does NOT expose PutBucketEncryption, PutPublicAccessBlock, PutBucketVersioning, PutBucketLifecycleConfiguration, or PutBucketTagging. Configure those once via the AWS Console or aws s3api.

Behavior​

  • Idempotent create: BucketAlreadyOwnedByYou (concurrent caller, your account) is treated as success and returns { bucketCreated: false }. BucketAlreadyExists (different AWS account owns the global name) fails loud with an actionable message so you pick a different name.
  • Region resolution: explicit setup.region wins; otherwise settings.region is used when concrete (not auto); otherwise the EU default eu-central-1.

Runtime hard-fail​

storeS3Init probes bucketExists() once when the collector wires the store. On a missing bucket it throws with an actionable message:

S3 bucket not found: my-assets at https://s3.eu-central-1.amazonaws.com. Run "walkeros setup store.assets" to create it.

Run walkeros setup store.<id> once to provision the bucket, then redeploy.

Security​

  • Key validation: Path traversal attempts (.., absolute paths) are rejected
  • Prefix scoping: The prefix setting restricts all operations to a subdirectory
  • No credential chain: Credentials must be explicitly provided (no ambient AWS credentials)

Structured vs file mode​

The S3 store has two modes, decided once at init by config.file:

  • Structured (default): values are structured StoreValue data, serialized by the shared core codec and stored with Content-Type: application/json. Use for session state, lookups, and cached responses.
  • File (file: true): values persist as raw bytes byte-exact, stored with the real mime derived from the key (or application/octet-stream when unknown). set() accepts a Uint8Array or string; get() hands the exact bytes back. Use for serving assets such as walker.js.

One store instance is exactly one mode.

API​

const file = await store.get('walker.js');           // StoreValue | undefined
await store.set('data.json', { ok: true });          // structured value
await store.set('walker.js', new Uint8Array([/*…*/])); // file mode: raw bytes
await store.delete('old-file.txt');                   // void

In file mode, get() returns the bytes as a Uint8Array leaf and set() rejects non-Uint8Array, non-string values with a clear error.

💡 Need implementation support?
elbwalker offers hands-on support: setup review, measurement planning, destination mapping, and live troubleshooting. Book a 2-hour session (€399)