@stim-cli/cache
v1.14.0
Published
Cache provider contract and tier coordination for Stim.
Readme
@stim-cli/cache
The cache provider contract Stim uses for Metro transforms and native build artifacts, plus the tier coordination, timeout, and warning policy that sit in front of it.
The local filesystem is always the first tier. A project can add one optional
second tier by pointing cache.provider at a module that implements this
contract. Stim ships no network provider; writing one is an addition, not a
change to Stim.
If Stim is not installed globally, replace stim with npx stim.
Selecting a provider
{
"cache": {
"provider": "./tools/cache-provider.cjs",
"options": { "bucket": "mobile-cache" }
}
}The reference is a package name or a path relative to the settings file that
declares it. Commit .stim.json beside the app's package.json; monorepo apps do
not inherit an ancestor's provider. Machine settings override committed settings. cache.options merges by key
from the layer that selects the provider, higher-precedence layers that name no
provider, and lower-precedence layers that name the same provider resolved from
the same directory; options written for another provider are ignored. Keep secrets out of
committed settings; read them from the environment or from machine settings.
Writing a provider
export const apiVersion = 1;
export async function createCacheProvider({ projectRoot, options }) {
return {
metro: {
async get({ key, cacheName, signal }) {
return null;
},
async set({ key, value, cacheName, signal }) {},
},
builds: {
async resolve({ platform, key, destinationDir, signal }) {
return null;
},
async store({ platform, key, sourcePath, overwrite, signal }) {},
},
};
}A provider implements one or both capabilities. It owns transport, serialization, archive format, authentication, and remote retention. Stim owns fingerprints, cache keys, and local artifact paths.
metro.get returns the stored value or null.
builds.resolve returns an existing path to the artifact, or null for a
miss. destinationDir is a scratch directory Stim creates and owns: a provider
that fetches the artifact must materialize it there and return a path inside
it, and must leave the directory empty on a miss. The built-in filesystem tier
already holds the artifact, so it returns its own cache path instead.
builds.store receives the .app directory or .apk file that Stim just
built. overwrite: false must keep an entry that already exists for the key,
and overwrite: true must replace it.
Every call receives an AbortSignal. A provider must honor it: Stim abandons
the call at the deadline and keeps building or bundling with the local tier.
stim gc never deletes provider data, and the contract has no delete
operation, so shared team or CI data is never removed by a local command.
Failure rules
Provider failures are cache misses. A timeout, module error, authentication error, or network error produces one warning per failure class per command or supervisor run and never fails a bundle, an install, a launch, or a successful build.
Contract tests
Run the shipped checks against your own module:
import { runCacheProviderContract } from '@stim-cli/cache';
const results = await runCacheProviderContract({
provider: await createCacheProvider({ projectRoot, options }),
projectRoot,
workDir,
});
for (const result of results) {
if (!result.passed) throw new Error(`${result.name}: ${result.error}`);
}Pass providerModule instead of provider to load the module the way Stim
does, which also checks apiVersion and the factory:
const results = await runCacheProviderContract({
providerModule: './tools/cache-provider.cjs',
projectRoot,
workDir,
});cacheProviderContractChecks() returns the same checks as individual cases for
a test runner that reports each one separately. Both helpers only check the
capabilities a provider advertises, bound every check with a deadline, and
verify that a call settles once its AbortSignal aborts.
Budgets
Every provider call is bounded. The defaults are 2s for a Metro read, 10s for a Metro write, 30s for a build lookup, 60s for a build upload, and 10s to load the module. Override any of them per run with an environment variable:
STIM_CACHE_METRO_READ_TIMEOUT_MS=5000 stim start
STIM_CACHE_BUILD_RESOLVE_TIMEOUT_MS=60000 stim iosThe variables are STIM_CACHE_METRO_READ_TIMEOUT_MS,
STIM_CACHE_METRO_WRITE_TIMEOUT_MS, STIM_CACHE_BUILD_RESOLVE_TIMEOUT_MS,
STIM_CACHE_BUILD_UPLOAD_TIMEOUT_MS, and STIM_CACHE_LOAD_TIMEOUT_MS. Each
takes whole milliseconds; any other value keeps the default.
The tiered Metro store also exposes flush(), which resolves once queued
provider writes have drained. Metro never calls it; every in-flight write holds
a referenced deadline, so a Metro process drains on its own within the write
budget. It exists for tests and embedders that own the process.
Metro reads are also capped: at most six can be in flight, and the tier turns itself off for the rest of the run after five consecutive failures, so a broken provider costs one round of warnings rather than a timeout per transform.
The npm scope remains @stim-cli until the @stim scope is available.
