Overview
@lastshotlabs/slingshot-assets is Slingshot’s asset storage package. It owns asset metadata, storage
adapter resolution, presigned-upload support, image-aware asset behavior, and the runtime wiring
that keeps stored bytes and persisted asset records aligned.
The asset entities themselves follow the shared package-first/entity authoring model;
createAssetsPackage() is the runtime shell that composes them into the live package.
When To Use It
Section titled “When To Use It”Use this package when your app needs:
- uploaded files with persisted metadata
- storage adapters that can swap between memory, local files, and S3-compatible backends
- presigned upload or download flows
- image-serving or transform-aware asset routes
- a standard asset domain that other packages can reference instead of inventing their own upload tables
What You Need Before Wiring It In
Section titled “What You Need Before Wiring It In”This package declares these dependencies:
slingshot-authslingshot-permissions
It also requires a storage adapter configuration. storage is the only required config field.
You can provide storage as either:
- a runtime adapter instance
- a built-in adapter helper such as
s3Storage(...),localStorage(...), ormemoryStorage(...)
Minimum Setup
Section titled “Minimum Setup”At minimum, provide:
- a storage adapter
Then layer on the optional controls as needed:
mountPathfor route placement. It must start with/; trailing slashes are trimmed.maxFileSizeandmaxFilesfor upload limitsallowedMimeTypesfor MIME policykeyPrefixandtenantScopedKeysfor storage-key shapepresignedUrlsfor direct upload/download flowsregistryTtlSecondsfor metadata cachingimagefor image-specific limits and cache behavior
If mountPath is omitted, the package mounts at /assets.
What You Get
Section titled “What You Get”The package gives you both data and byte-storage plumbing:
- an asset entity definition and runtime adapter wiring
- storage adapter resolution for built-in or runtime-provided backends
- asset routes mounted through the entity layer
- presign, image-serving, TTL, and delete-to-storage behavior through the entity runtime
- three capabilities published for cross-package consumers:
AssetsRuntimeCap— bundled asset adapter, storage adapter, and resolved configAssetsHealthCap— aggregated health snapshot getterAssetsOrphanedKeysCap— bounded orphan-key registry for recovery flows
import { AssetsHealthCap, AssetsOrphanedKeysCap } from '@lastshotlabs/slingshot-assets';import type { HookServices } from '@lastshotlabs/slingshot-core';
function readAssetsHealth(ctx: HookServices) { const health = ctx.capabilities.require(AssetsHealthCap)(); const orphans = ctx.capabilities.require(AssetsOrphanedKeysCap)(); return { health, orphans };}This makes it the right package to standardize uploads for the rest of the platform.
Common Customization
Section titled “Common Customization”The first files to read when changing behavior are:
src/plugin.tsfor package lifecycle and dependency behaviorsrc/config.schema.tsfor supported config and validation rulessrc/adapters/index.tsfor storage resolutionsrc/entities/runtime.tsfor asset-specific runtime hooks (presign / serveImage / TTL transforms)src/middleware/deleteStorageFile.tsfor delete-cascade behavior and the orphan registrysrc/image/for image-serving and caching behavior
The most important configuration choices are:
- storage backend selection
- MIME and file-count limits
- whether keys are tenant-scoped
- whether presigned URLs are enabled
- whether image transforms are enabled and cached
Credential rotation
Section titled “Credential rotation”The S3 adapter accepts either a static credentials object or an
AwsCredentialProvider — an async function the AWS SDK calls each time it
needs fresh credentials. Use the provider form for any long-running service:
the SDK caches the result until expiration passes, then calls back into your
function so STS, Vault, or instance-metadata rotation is honored without a
restart.
If you omit credentials entirely the SDK uses its default credential chain
(env, profile, EC2/ECS metadata, STS web identity), which already refreshes
itself. Pass a AwsCredentialProvider only when you load credentials from a
backend the default chain doesn’t cover.
import { createAssetsPackage, s3Storage } from '@lastshotlabs/slingshot-assets';import type { AwsCredentialProvider } from '@lastshotlabs/slingshot-assets';
// The SDK calls this whenever cached creds are within `expiration` of expiring,// so set `expiration` to the upstream credential expiry, not your local polling// interval. This example uses an internal credential broker; STS, Vault, and// AWS Secrets Manager work the same way.const rotatingCredentialProvider: AwsCredentialProvider = async () => { const response = await fetch(process.env.ASSETS_CREDENTIAL_ENDPOINT!, { headers: { authorization: `Bearer ${process.env.ASSETS_CREDENTIAL_TOKEN!}` }, }); if (!response.ok) { throw new Error('asset credential endpoint failed'); } const creds = (await response.json()) as { accessKeyId: string; secretAccessKey: string; sessionToken?: string; expiresAt: string; }; return { accessKeyId: creds.accessKeyId, secretAccessKey: creds.secretAccessKey, sessionToken: creds.sessionToken, expiration: new Date(creds.expiresAt), };};
createAssetsPackage({ storage: s3Storage({ bucket: 'my-app-assets', region: 'us-east-1', credentials: rotatingCredentialProvider, }),});Vault, AWS Secrets Manager, or any other store works the same way — fetch the
current secret inside the provider and return an object with an accurate
expiration. Do not roll your own setInterval rotation loop; the SDK
already invalidates the cached value when the returned expiration is near.
Gotchas
Section titled “Gotchas”- The package expects permissions state to exist and throws during startup if
slingshot-permissionsis missing. - Image behavior is opt-in. If
imageis omitted, the package does not apply image-specific overrides. - When
imageconfig is present butimage.cacheis not a runtime cache adapter, the package falls back to an in-memory image cache. - Asset-record deletion and underlying storage cleanup are coordinated through the delete-cascade
middleware in
src/middleware/deleteStorageFile.ts. If you change delete behavior, trace both the entity adapter and the storage path.
Key Files
Section titled “Key Files”src/index.tssrc/plugin.tssrc/config.schema.tssrc/types.tssrc/adapters/index.tssrc/entities/runtime.tssrc/middleware/deleteStorageFile.tssrc/image/serve.ts