API reference

Every public class, method, and event in @codexo/exojs. Generated from source.

C

classLoader

@codexo/exojs / assets / stable

3
props
28
methods
8
events
Import
import { Loader } from '@codexo/exojs'
Constructors1
Methods28
awaitBackground(): Promise<void>
Resolves when the low-priority background queue has fully drained - every leaf enqueued via load(target, { priority: LoadPriority.Background }) has finished loading (successfully or not). Kicks the queue first, so a concurrency change that left pending entries unstarted still makes progress. Individual asset errors are reported via onError but do not reject the returned promise.
cacheSource(asset: AssetA typed, loadable asset reference. Holds config only - no loaded resource.<unknown>, options?: { signal?: AbortSignal }): Promise<void>
Acquires an asset's SOURCE and lets the cache keep it, without building the asset. This is how an application fills a persistent cache ahead of time - before a level, before going offline, on a fast connection - for assets it does not want resident yet. Nothing is constructed, nothing is claimed, and nothing stays in memory: what remains afterwards is a cache record, which a later load() of the same asset finds because both derive the same source identity from the same descriptor. Whether a hit is served from the cache or the network comes down to the route's policy, exactly as it does for a load. A cached source is not re-fetched. This is the explicit acquisition, so it applies to streamed media too: an ordinary music or video descriptor is fetched in full and persisted as a blob here, while a plain load() of that same descriptor keeps streaming for as long as the network is available. A type built entirely from other assets has no source of its own to acquire, and rejects saying so.
Creates a new claim scope: an owner whose assets are freed when it is destroyed, rather than when the application ends. Every call returns a new, independent owner - never a lookup of an existing one. Two scopes created under the same name are still two owners, because a name is a label for diagnostics and never an identifier, so one consumer can never release another's claim. Assets acquired directly on the loader are claimed for the application's lifetime instead, and released only by destroy. Nest shorter lifetimes with LoaderScope.createScope.
destroy(): void
Tears down the loader and all resources it owns. Clears all in-memory assets and in-flight tracking, and disconnects all signals. Also calls destroy?.() on every installed type's factory. Cache stores are destroyed only when this loader was given the stores themselves, and therefore owns the cache it wrapped them in. An AssetCache passed in belongs to its builder and may be serving other loaders, so it is left alone - destroy it yourself when the application is done with it.
get(path: [KindByPathThe asset type inferred from a path literal, or `never` when unregistered.<S>] extends [never] ? never : S, options?: unknown): LeafForPath<S>
Seamless deferred access by path (asset-system v2). Returns SYNCHRONOUSLY: an already-loaded source returns the stored resource; an unknown source returns a placeholder handle immediately, starts the fetch, and fills the handle in place when the payload arrives (track it via loadState / loaded). **This is an acquiring call, not a plain lookup.** Besides possibly starting a fetch it also **claims** the resolved key for this loader's lifetime, so the asset stays resident until a matching release (or, for scene.loader, until the scene tears down). That claim is the reason get is the right default: you almost always want the thing you are about to use to stay loaded. When you only want to know whether something is already in memory - without claiming it and without starting a fetch - use peek. Failed loads switch the handle to its failed representation; calling get again for a 'failed' source retries and heals the same handle in place. Invalid inputs and missing bindings throw synchronously before a fetch starts. The path is normalized to an AssetDefinitions type by its suffix (basename-only, longest-suffix-first), consulting the app-local registerType override, then the type that claimed the suffix. A resource suffix yields its heal-in-place handle; a value suffix (json, txt, csv, ...) yields a stable AssetRef. Only suffixes a catalog can materialize are accepted at compile time (ExtensionKindMap); dynamic strings resolving to an unclaimed suffix or a leafless type throw with guidance. The same source always yields the same instance - also across load - and options are first-wins: conflicting options on a later call are ignored with a one-time dev warning.
Adopts an Assets catalog: every handle-hybrid leaf is registered, claimed, and driven to load, and the same leaf objects are returned keyed by their record key. The catalog's own properties heal in place as payloads arrive - the returned map holds those very leaves.
Seamless/value access from an Asset.type(...) descriptor - the replacement for the removed get(Type, dynamicSource) form. Builds and adopts the descriptor's handle-hybrid leaf: a resource type yields its heal-in-place handle, a value type a stable AssetRef. A type with neither a seamless adapter nor a value channel throws with guidance to use load(Asset.type(...)). The return type follows the ValueAsset brand (as InferCatalogLeaf does): a value-type descriptor (Asset.type<T>('json', ...)) returns AssetRef<T> - even for an object payload - while a resource-type descriptor returns the resource itself, so the type always matches the runtime value. Both come back BRANDED, mirroring the _assetMeta stamp createLeaf applies, so the returned leaf can be fed straight back into a single-leaf load(...). Unlike bare-path get('x.png'), this form is **not instance-deduped by source**: each call builds a fresh leaf, so repeated get(Asset.type(type, sameSrc)) accumulates distinct handles (all healing to the same deduped backend payload). It is the dynamic-source escape hatch - capture the handle once.
Seamless deferred access by path (asset-system v2). Returns SYNCHRONOUSLY: an already-loaded source returns the stored resource; an unknown source returns a placeholder handle immediately, starts the fetch, and fills the handle in place when the payload arrives (track it via loadState / loaded). **This is an acquiring call, not a plain lookup.** Besides possibly starting a fetch it also **claims** the resolved key for this loader's lifetime, so the asset stays resident until a matching release (or, for scene.loader, until the scene tears down). That claim is the reason get is the right default: you almost always want the thing you are about to use to stay loaded. When you only want to know whether something is already in memory - without claiming it and without starting a fetch - use peek. Failed loads switch the handle to its failed representation; calling get again for a 'failed' source retries and heals the same handle in place. Invalid inputs and missing bindings throw synchronously before a fetch starts. The path is normalized to an AssetDefinitions type by its suffix (basename-only, longest-suffix-first), consulting the app-local registerType override, then the type that claimed the suffix. A resource suffix yields its heal-in-place handle; a value suffix (json, txt, csv, ...) yields a stable AssetRef. Only suffixes a catalog can materialize are accepted at compile time (ExtensionKindMap); dynamic strings resolving to an unclaimed suffix or a leafless type throw with guidance. The same source always yields the same instance - also across load - and options are first-wins: conflicting options on a later call are ignored with a one-time dev warning.
Adopts a single handle-hybrid leaf (an Assets.from() property) and returns it - the same object, healing in place once its payload arrives. Matches on the catalog-leaf brand, so only a MATERIALIZED leaf is accepted; a raw resource instance has no _assetMeta stamp and is rejected here as it is at runtime. The brand rides along on the result: the returned leaf is the very object that was passed in, stamp included, so it stays re-loadable.
hasAssetType(typeName: string): boolean
Whether an asset type with this id is installed on this application.
hasExtension(ext: string): boolean
Whether an installed type claims this file suffix.
The two identities a descriptor resolves to on this loader. Both are derived exactly as a real load derives them, against the base path in force right now, so they can be compared to reason about what the loader will deduplicate: equal AssetIdentity.resourceKey means one resident resource, equal AssetIdentity.sourceKey means one acquisition.
inspect(): readonly AssetInspection[]
Read-only, detached snapshot of every key this loader currently has a claim on - one AssetInspection row per claimed (type, source) key, sorted by key. Intended for diagnostics, support bundles, and developer tooling: the returned array and every row are frozen, so mutating (or attempting to mutate) the snapshot never touches residency, and no internal Set, claim symbol, or live handle/ref object is exposed - every field is plain data.
Reverse lookup: given a loaded resource object, return the asset type and source key it was first loaded under, or null for runtime-created, unloaded, or non-object resources. When a resource is shared across several aliases, the **first** alias it was stored under is returned (the canonical key). Primitive results (parsed JSON, text, CSV rows) are not keyable. Used by scene serialization to turn a live asset reference back into a portable source key; the contract is that the same asset is pre-loaded under that key before a matching deserialize.
Fetches and processes one or more assets. Every accepted input normalizes to the same canonical descriptor shape ({ type, source }) before dispatch: - **Path string** - normalized by suffix through the app-local registerType override, then the type that claimed the suffix (basename-only, longest-suffix-first). Only suffixes of a type that hands out a catalog leaf are accepted at compile time; a type that hands out none (bmFont, font, svg, image, music, video) must be named explicitly with Asset.type(...). - **Asset<T>** - an explicit descriptor from Asset.type(...). - **Assets<M>** - a typed catalog from Assets.from(...); keys become aliases. - **A catalog leaf** - an Assets.from() property, adopted and resolved. (The inline record-catalog form { alias: { type, source } } is no longer a public overload - build catalogs with Assets.from(...); a runtime record fallback is retained only for internal multi-alias plumbing.) In-flight and already-loaded assets are de-duplicated: calling load for the same (type, alias) pair while a fetch is in progress attaches to the existing promise rather than issuing a second request. Handler/fetch failures reject the returned queue and are also dispatched through onError. Invalid inputs or missing bindings throw synchronously before a queue is created. Per-asset options ride on the Asset.type(type, source, options) descriptor (or the extra fields of a config object).
Fetches and processes one or more assets. Every accepted input normalizes to the same canonical descriptor shape ({ type, source }) before dispatch: - **Path string** - normalized by suffix through the app-local registerType override, then the type that claimed the suffix (basename-only, longest-suffix-first). Only suffixes of a type that hands out a catalog leaf are accepted at compile time; a type that hands out none (bmFont, font, svg, image, music, video) must be named explicitly with Asset.type(...). - **Asset<T>** - an explicit descriptor from Asset.type(...). - **Assets<M>** - a typed catalog from Assets.from(...); keys become aliases. - **A catalog leaf** - an Assets.from() property, adopted and resolved. (The inline record-catalog form { alias: { type, source } } is no longer a public overload - build catalogs with Assets.from(...); a runtime record fallback is retained only for internal multi-alias plumbing.) In-flight and already-loaded assets are de-duplicated: calling load for the same (type, alias) pair while a fetch is in progress attaches to the existing promise rather than issuing a second request. Handler/fetch failures reject the returned queue and are also dispatched through onError. Invalid inputs or missing bindings throw synchronously before a queue is created. Per-asset options ride on the Asset.type(type, source, options) descriptor (or the extra fields of a config object).
Fetches and processes one or more assets. Every accepted input normalizes to the same canonical descriptor shape ({ type, source }) before dispatch: - **Path string** - normalized by suffix through the app-local registerType override, then the type that claimed the suffix (basename-only, longest-suffix-first). Only suffixes of a type that hands out a catalog leaf are accepted at compile time; a type that hands out none (bmFont, font, svg, image, music, video) must be named explicitly with Asset.type(...). - **Asset<T>** - an explicit descriptor from Asset.type(...). - **Assets<M>** - a typed catalog from Assets.from(...); keys become aliases. - **A catalog leaf** - an Assets.from() property, adopted and resolved. (The inline record-catalog form { alias: { type, source } } is no longer a public overload - build catalogs with Assets.from(...); a runtime record fallback is retained only for internal multi-alias plumbing.) In-flight and already-loaded assets are de-duplicated: calling load for the same (type, alias) pair while a fetch is in progress attaches to the existing promise rather than issuing a second request. Handler/fetch failures reject the returned queue and are also dispatched through onError. Invalid inputs or missing bindings throw synchronously before a queue is created. Per-asset options ride on the Asset.type(type, source, options) descriptor (or the extra fields of a config object).
Fetches and processes one or more assets. Every accepted input normalizes to the same canonical descriptor shape ({ type, source }) before dispatch: - **Path string** - normalized by suffix through the app-local registerType override, then the type that claimed the suffix (basename-only, longest-suffix-first). Only suffixes of a type that hands out a catalog leaf are accepted at compile time; a type that hands out none (bmFont, font, svg, image, music, video) must be named explicitly with Asset.type(...). - **Asset<T>** - an explicit descriptor from Asset.type(...). - **Assets<M>** - a typed catalog from Assets.from(...); keys become aliases. - **A catalog leaf** - an Assets.from() property, adopted and resolved. (The inline record-catalog form { alias: { type, source } } is no longer a public overload - build catalogs with Assets.from(...); a runtime record fallback is retained only for internal multi-alias plumbing.) In-flight and already-loaded assets are de-duplicated: calling load for the same (type, alias) pair while a fetch is in progress attaches to the existing promise rather than issuing a second request. Handler/fetch failures reject the returned queue and are also dispatched through onError. Invalid inputs or missing bindings throw synchronously before a queue is created. Per-asset options ride on the Asset.type(type, source, options) descriptor (or the extra fields of a config object).
Fetches an asset by path, normalizing the suffix to an AssetDefinitions type before dispatch. Resolution is basename-only and longest-suffix-first (hero.aseprite.json tries aseprite.json before json), and consults the app-local registerType override, then the type that claimed the suffix. Only suffixes a catalog can also materialize are accepted at compile time (ExtensionKindMap, the map to augment by declaration merging). Types that hand out no leaf (bmFont, font, svg, image, music, video) and unclaimed suffixes are rejected here; name them with load(Asset.type(type, path)) instead. ts const texture = await loader.load('image/hero.png'); // Texture const level = await loader.load('data/level.json'); // unknown const font = await loader.load(Asset.type('bmFont', 'fonts/ui.fnt')); // BmFont
Load every asset packed into a binary container (.exoa) and return the scope that owns them. A container is one file with an embedded index: its slices are read through each type's own codec and factory and stored under the entry's own logical source. A container is therefore a transport, not a second naming system - an entry resolves to exactly the same asset identity as a network load of that source, so both can hold it and the payload is fetched and decoded once. By default the file is fetched in a **single request** and cached whole, cross-session, like any other asset. Passing a ContainerBlockStore switches to the block-wise path instead: the head is read first, and only the blocks the store does not already hold are fetched. That is what makes a pack whose assets mostly did not change cost close to nothing on a second visit - at the price of the whole-file cache entry, since the two cache the same bytes at different granularity and keeping both would store them twice. Servers that do not answer byte ranges degrade to one request automatically. The returned scope holds one ordinary claim per entry. Destroying it frees only the entries no other owner still holds, so unpacking a container can never pull an asset out from under a scene that also uses it. Use LoaderScope.loadContainer to claim the entries under an existing scope instead. Each entry's asset type must be readable from bytes alone - its AssetSourceCodec has to implement fromBytes, which every built-in type does. Throws on a malformed container, an unsupported format version, an unknown type, or a type that cannot be read from bytes. A ManifestPack may be passed instead of a path. The pack carries the container's URL, length and hash, and the bytes are checked against that record before anything is unpacked - a mismatch is an AssetDecodeError and nothing is cached. How much is checked depends on how much is read: the whole-file path holds the file and verifies the digest, while the block-wise path never holds it and verifies only the length, leaving the content-addressed URL as the statement about the bytes. The digest is also skipped where crypto.subtle is unavailable, which is an insecure context; the length check still runs.
Read the asset manifest at url: which packs this deployment holds, and which file currently carries each of them. A pack file is named after the hash of its own bytes, so it can be cached forever and the manifest is the one URL worth re-reading. Hand the pack to loadContainer, which then checks the bytes against what the manifest states. The manifest is read straight from the network with cache: 'no-cache', outside the loader's asset cache and outside its connectivity policy: it is the one document whose whole purpose is to say what the current deployment is, so a cached or policy-suppressed answer would be worse than none. The application's fetchOptions are carried except for cache, which LoadManifestOptions.cache is the only way to change. A manifest request therefore fails offline rather than resolving from a store. Throws an AssetNetworkError when the manifest cannot be fetched, and an AssetDecodeError when it is not JSON, states a version this build does not read, or describes a pack it cannot address.
Pure in-memory lookup: the resource already stored for path, or undefined if nothing is held for it. The counterpart to get - and the one to reach for when the answer "not loaded" is a legitimate one rather than something to fix by loading. Unlike get, this **never** starts a fetch, **never** mints a placeholder handle, and **never** claims anything, so it cannot keep an asset alive by accident. Calling it in a loop is free. Invalid usage still fails loudly, exactly as it does on get: a path whose extension resolves to no registered type, or an input that is neither a path string nor an Asset.type(...) descriptor, throws. Only "the key is fine, nothing is stored under it" comes back as undefined. A catalog leaf needs no peek: the leaf *is* the handle, so read its loadState / loaded directly.
Pure in-memory lookup from an Asset.type(...) descriptor - see the path overload.
Registers an extension→type override for bare-path resolution, scoped to **this Loader instance only**: it applies to bare paths passed to this loader's get(...) / load(...), and takes precedence over the type that claimed the suffix by declaring it. It does NOT affect loader-free catalog construction - Assets.from('level.json') has no loader to consult and resolves through the built-in table alone. Use Asset.type(type, source) or the type's own asset(...) there instead. type is the type's id, not a constructor. This call installs nothing; the named type must still be installed on the application. Repeating the same pair is idempotent, while changing an existing explicit override to another type throws. Use Asset.type(...) for a one-off exception.
The type a file suffix resolves to on this application, or undefined. The suffix is normalised (leading dots stripped, lower-cased).
setConcurrency(n: number): this
Sets the maximum number of simultaneous background-queue fetches. Takes effect on the next awaitBackground call or load(..., { background }).
Read-only, detached summary of what this loader holds: ready / pending / failed counts, estimated resident bytes, the same split per asset type, and the topCount heaviest resident assets. The counterpart to inspect for callers that want totals rather than one row per asset - a debug HUD polling every frame, a memory dashboard, a regression check on how much a scene keeps resident. Aggregating here rather than over an inspect() result keeps a per-frame reader from rebuilding the whole row snapshot itself.
Properties3
Per-device selection between several files standing for one logical source - a texture shipped once per compressed format family, once per display density, or both. Empty by default, so a loader nobody configures resolves every source to itself. The Application publishes the device profile here once its render backend is up.
basePath: string
Base path prepended to every relative asset URL at fetch time. Absolute URLs (starting with http://, https://, or //) are passed through unchanged.
fetchOptions: RequestInit
Default RequestInit options merged into every fetch call. Assign a new value to change the defaults for subsequent loads. A signal here is composed with the cancellation a load carries of its own, so an application-wide abort and a per-load cancel both reach the request and neither disables the other.
Events8
Fires for cache failures the configured CachePolicy degraded instead of propagating - most commonly a persistent store hitting its quota. Purely diagnostic: the affected load still succeeds from the network, so without a listener the only symptom is that caching quietly stopped working. Narrow on AssetCacheError.operation and read Error.cause for the originating DOMException (QuotaExceededError and friends). Reports only failures caused by *this* loader's own requests, even when its AssetCache or CachePolicy is shared with other loaders - the cache is handed a per-acquisition sink rather than subscribing to one.
Fires whenever an asynchronous asset fetch fails, in both development and production builds. get() returns its placeholder / AssetRef synchronously; a later fetch failure is reflected by that handle's state/loaded promise and by this signal, not by a delayed throw from the original call. load() remains awaitable and rejects with the same failure while also dispatching it here. A later retry may heal the existing handle in place. Invalid inputs and missing registrations are configuration errors and can still throw synchronously before either operation starts a fetch.
Fired after each asset settles (loaded or failed). loaded = resolved count, total = batch size.
Dispatched after each background-queue item completes, with the running loaded/total counts.
Source