Package Exports
- @vsaas/loopback-component-storage
- @vsaas/loopback-component-storage/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@vsaas/loopback-component-storage) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
LoopBack Component Storage
Modernized fork of loopback-component-storage for LoopBack 3 projects that
only need:
filesystems3/aws/amazon
This fork removes pkgcloud, formidable, strong-globalize, and the legacy
provider matrix. The package is now implemented in TypeScript and keeps the
LoopBack 3 connector surface.
Stack
- TypeScript
6.0.2 @fastify/busboyfor streaming multipart parsing- AWS SDK for JavaScript v3 for S3
tsdownfor buildsvitestfor testsoxlintfor linting
Behavior
- Uploads are streamed end to end.
- When
maxFileSizeis exceeded, the request is aborted immediately. - Filesystem uploads do not buffer the full body in memory.
- S3 uploads use AWS SDK v3 multipart upload support.
- Signed URLs validate object existence before signing by default. Set
signedUrl.validateBeforeSigntofalseto skip the extraHeadObjectrequest. - Filesystem signed URLs are local application URLs signed with HMAC. They
require
signedUrl.secret.
Supported upload options
getFilenamenameConflict: "makeUnique"allowedContentTypesmaxFileSizemaxFieldsSizeaclStorageClassCacheControlServerSideEncryptionSSEKMSKeyIdSSECustomerAlgorithmSSECustomerKeySSECustomerKeyMD5
S3 client tuning
For S3 datasources you can also tune the AWS SDK v3 client:
maxAttemptsretryModeconnectionTimeoutrequestTimeoutsocketTimeoutthrowOnRequestTimeoutmaxSockets
Example:
{
"name": "storage",
"connector": "@vsaas/loopback-component-storage",
"provider": "s3",
"region": "us-east-1",
"maxAttempts": 3,
"retryMode": "standard",
"connectionTimeout": 2000,
"requestTimeout": 30000,
"socketTimeout": 30000,
"throwOnRequestTimeout": true,
"maxSockets": 50
}Datasource examples
Filesystem:
{
"name": "storage",
"connector": "@vsaas/loopback-component-storage",
"provider": "filesystem",
"root": "./storage",
"maxFileSize": 10485760,
"maxFieldsSize": 1048576,
"signedUrl": {
"enabled": true,
"expiresIn": 900,
"secret": "replace-this-with-a-long-random-secret",
"baseUrl": "https://api.example.com"
}
}AWS S3:
{
"name": "storage",
"connector": "@vsaas/loopback-component-storage",
"provider": "s3",
"region": "us-east-1",
"accessKeyId": "YOUR_ACCESS_KEY",
"secretAccessKey": "YOUR_SECRET_KEY",
"CacheControl": "max-age=300",
"signedUrl": {
"enabled": true,
"expiresIn": 900,
"strategy": "provider",
"validateBeforeSign": true
},
"maxAttempts": 3,
"retryMode": "standard",
"connectionTimeout": 2000,
"requestTimeout": 30000,
"socketTimeout": 30000,
"throwOnRequestTimeout": true,
"maxSockets": 50
}MinIO or local S3-compatible storage:
{
"name": "storage",
"connector": "@vsaas/loopback-component-storage",
"provider": "amazon",
"region": "us-east-1",
"endpoint": "http://localhost:9000",
"forcePathStyle": true,
"keyId": "MINIO_ACCESS_KEY",
"key": "MINIO_SECRET_KEY",
"CacheControl": "max-age=300",
"signedUrl": {
"enabled": true,
"expiresIn": 1800,
"strategy": "provider",
"validateBeforeSign": false
},
"requestTimeout": 20000
}Dedicated datasource with signed URLs enabled:
{
"AssetStorage": {
"name": "AssetStorage",
"connector": "@vsaas/loopback-component-storage",
"provider": "amazon",
"region": "us-west-1",
"endpoint": "http://localhost:9000",
"forcePathStyle": true,
"keyId": "${S3_ACCESS_KEY_ID_WEST_1}",
"key": "${S3_SECRET_ACCESS_KEY_WEST_1}",
"CacheControl": "max-age=300",
"signedUrl": {
"enabled": true,
"expiresIn": 1800,
"strategy": "provider",
"validateBeforeSign": true
},
"maxAttempts": 2,
"requestTimeout": 20000
}
}signedUrl is only used when the download request includes ?signedUrl=true
or ?signed-url=true.
For S3-compatible providers:
signedUrl.strategy: "provider"keeps the current behavior and returns a presigned URL from S3 or the compatible backendsignedUrl.strategy: "local"returns a signed URL from your own app instead of exposing the backend URL; that URL hits the component and streams the file through your server- when
signedUrl.strategyis"local",signedUrl.secretis required because the component signs and validates the local URL itself - you can override the datasource strategy per request via
signedUrlStrategy=local|providerorsigned-url-strategy=local|provider - when calling
getSignedUrl()ordownload()from code, you can pass an options object like{ strategy: "local" }; that explicit option has higher priority than both the query string and the datasource configuration
For filesystem providers:
signedUrl.secretis required to generate and validate local signed URLssignedUrl.baseUrlis optional and lets you force the public origin used in generated URLs when the app is behind a proxy- signed filesystem URLs now prefer
/:container/signed-download?file=...withexpiresandsignaturequery params /:container/download/:file(*)and/:container/signed-download/:file(*)are still supported for compatibility- the current request query string is preserved except for signing-related and
sensitive params such as
signedUrl,signed-url,signature,expires, and anything that looks like tokens, secrets, passwords, authorization, API keys, or AWS presign parameters
This allows you to keep /:container/download protected and expose only
/:container/signed-download as a public route that validates the signature
before streaming the file. The target file can be provided as a query string
like ?file=projects/project-1/event-summaries/2026-03-29.json.gz.
Example S3 datasource that always returns app-local signed URLs:
{
"name": "storage",
"connector": "@vsaas/loopback-component-storage",
"provider": "amazon",
"region": "us-east-1",
"endpoint": "http://localhost:9000",
"forcePathStyle": true,
"keyId": "MINIO_ACCESS_KEY",
"key": "MINIO_SECRET_KEY",
"signedUrl": {
"enabled": true,
"expiresIn": 1800,
"strategy": "local",
"secret": "replace-this-with-a-long-random-secret",
"baseUrl": "https://api.example.com",
"validateBeforeSign": true
}
}Migration notes from legacy pkgcloud-style configs
forcePathBucket->forcePathStylemaxRetries->maxAttemptsdefaultUploadParams.CacheControl->CacheControlsignatureVersionis no longer usedprotocolis no longer used; include the protocol directly inendpointhttpOptions.timeout->requestTimeouthttpOptions.agent.keepAliveis not needed because AWS SDK v3 already reuses connections by default
When using custom S3-compatible endpoints, endpoint should usually point to
the service root, for example http://localhost:9000, not to a bucket URL such
as http://localhost:9000/my-bucket.
Development
npm run lint
npm run typecheck
npm run build
npm test