Static Asset Component
StaticAssetComponent reads a map of storage backend configurations and generates a full bucket/object REST controller for each one - disk or S3-compatible - both through the same IStorageHelper contract.
IMPORTANT
StaticAssetComponent and its related exports are not on the @venizia/ignis root barrel. Import from the @venizia/ignis/static-asset subpath.
In one example
import { BaseApplication } from '@venizia/ignis';
import {
StaticAssetComponent,
StaticAssetComponentBindingKeys,
StaticAssetStorageTypes,
} from '@venizia/ignis/static-asset';
import type { TStaticAssetsComponentOptions } from '@venizia/ignis/static-asset';
import { DiskHelper } from '@venizia/ignis-helpers';
export class Application extends BaseApplication {
preConfigure() {
this.bind<TStaticAssetsComponentOptions>({
key: StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS,
}).toValue({
staticAsset: {
controller: { name: 'AssetController', basePath: '/assets', isStrict: true },
storage: StaticAssetStorageTypes.DISK,
helper: new DiskHelper({ basePath: './app_data/assets' }),
extra: { parseMultipartBody: { storage: 'memory' } },
},
});
this.component(StaticAssetComponent);
}
}This registers GET/POST/DELETE on /assets/buckets/:bucketName, POST /assets/buckets/:bucketName/objects, GET /assets/buckets/:bucketName/objects, plus stream/download/delete-object routes - no controller class to write by hand.
How it works
One options key, one generated controller.
TStaticAssetsComponentOptionsis a record. Each key (staticAssetabove) becomes an independently configured storage backend with its ownbasePath.AssetControllerFactory.defineAssetController()builds the controller insideStaticAssetComponent.binding().storageandhelperare a discriminated pair. Mismatching them fails at compile time, not at runtime:storageRequired helper'disk'DiskHelper'bun-s3'BunS3Helper'minio'(deprecated)MinioHelperStaticAssetStorageTypes.MINIOis still a live arm of the union, marked@deprecated. UseStaticAssetStorageTypes.BUN_S3with aBunS3Helperinstead - it reaches MinIO over the same S3 API.Every backend implements the same
IStorageHelpercontract.DiskHelper,BunS3HelperandMinioHelperall extendBaseStorageHelper. Bucket/object operations, name validation (isValidBucketName/isValidObjectKey), and upload normalization behave identically regardless of backend.Object names can embed folder paths, encoded as one segment.
objects/{objectName}percent-encodes the wholefolder/file.extstring viaencodeURIComponent(), which also escapes/. Hono decodes it before your handler runs - encode the name client-side, and never decode it again.MetaLink is opt-in. Set
useMetaLink: trueand providemetaLink.repository. IGNIS then persists a database row (bucket/object/mimetype/size/etag/principal/variant) alongside every upload. That row lets you query "which files does user X own" without listing a whole bucket.The default binding is empty.
StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONSdefaults to{}. Bind it with at least one storage backend beforethis.component(StaticAssetComponent)- an empty binding produces zero routes.
Common tasks
Add a second storage backend
Each key in the options object is independent - mix disk and S3 under different base paths in one binding.
import { BunS3Helper } from '@venizia/ignis-helpers/bun-s3';
this.bind<TStaticAssetsComponentOptions>({
key: StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS,
}).toValue({
uploads: {
controller: { name: 'UploadsController', basePath: '/uploads' },
storage: StaticAssetStorageTypes.BUN_S3,
helper: new BunS3Helper({
endpoint: { default: 'http://localhost:9000' },
accessKey: process.env.S3_ACCESS_KEY,
secretKey: process.env.S3_SECRET_KEY,
}),
},
tempFiles: {
controller: { name: 'TempController', basePath: '/temp' },
storage: StaticAssetStorageTypes.DISK,
helper: new DiskHelper({ basePath: './temp' }),
},
});Lock down individual routes
Each key under controller.routes accepts a partial authenticate/authorize/middleware/path override, plus enabled to drop the route entirely; unset routes stay public and registered.
{
controller: {
name: 'AssetController',
basePath: '/assets',
routes: {
upload: { authenticate: { strategies: ['jwt'], mode: 'required' } },
deleteObject: { authenticate: { strategies: ['jwt'], mode: 'required' } },
deleteBucket: { enabled: false },
},
},
}Split a shared bucket between controllers
controller.keyPrefix scopes every key a controller touches: a key outside the prefix answers 404 before any storage call, listObjects never lists outside it, and an upload key outside it is refused. See Share one bucket between controllers for the full example.
Switch multipart parsing to disk for large uploads
parseMultipartBody: { storage: 'memory' } (the default) buffers the whole file in RAM before writing it to the backend. Switch to 'disk' to spool it to a temp file instead.
extra: {
parseMultipartBody: { storage: 'disk', uploadDir: './tmp/uploads' },
}Enable MetaLink database tracking
Bind a repository over BaseMetaLinkModel (used directly, no subclass needed) and pass it as metaLink.repository.
useMetaLink: true,
metaLink: {
model: BaseMetaLinkModel,
repository: this.get<MetaLinkRepository>({ key: 'repositories.MetaLinkRepository' }),
},See Enable MetaLink tracking for the full repository/table/binding setup.
Customize object names and links
normalizeNameFn runs before the file is written to storage; normalizeLinkFn builds the link value returned to the client. Both are optional - omit either to keep the component's defaults.
Both take one options object. normalizeNameFn receives { file }, and normalizeLinkFn receives an IObjectLocation - { bucket: { name }, object: { key } }.
extra: {
normalizeNameFn: ({ file }) =>
`${Date.now()}_${file.originalName.toLowerCase().replace(/\s/g, '_')}`,
normalizeLinkFn: ({ bucket, object }) =>
`/api/files/${bucket.name}/${encodeURIComponent(object.key)}`,
},See also
- Usage & Examples - task-oriented walkthroughs for every endpoint and MetaLink setup
- Full Reference - controller factory,
IStorageHelperinterface, MetaLink schema, internals - Error Reference - name validation rules and troubleshooting
- Storage Helpers -
DiskHelper,BunS3Helper,BaseStorageHelperreference - Components Overview - component system basics
Files:
packages/core-server/src/components/static-asset/component.ts-StaticAssetComponentpackages/core-server/src/components/static-asset/common/types.ts-TStaticAssetsComponentOptions,TStaticAssetExtraOptions,TMetaLinkConfigpackages/core-server/src/components/static-asset/common/constants.ts-StaticAssetStorageTypespackages/core-server/src/components/static-asset/controller/factory.ts-AssetControllerFactorypackages/core-server/src/components/static-asset/models/base.model.ts-BaseMetaLinkModelpackages/helpers/src/modules/storage/base.ts-BaseStorageHelper,IStorageHelper