@joycostudio/susano 0.1.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +337 -112
- package/dist/index.d.mts +164 -83
- package/dist/index.d.ts +164 -83
- package/dist/index.js +216 -124
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +216 -125
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,68 +1,82 @@
|
|
|
1
1
|
import { TinyEmitter } from 'tiny-emitter';
|
|
2
2
|
|
|
3
|
-
type
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
type LoaderStatus = 'idle' | 'loading' | 'loaded' | 'error';
|
|
4
|
+
/**
|
|
5
|
+
* Base loader. Owns and caches a single piece of raw **content** per URL (the expensive fetch),
|
|
6
|
+
* deduped and idempotent. It does NOT run `postprocess` — that is a per-call projection applied
|
|
7
|
+
* by `Susano` (`_runLoad`). If you want to cache a transformed result, encode the transform in a
|
|
8
|
+
* loader subclass so its output becomes the cached content.
|
|
9
|
+
*
|
|
10
|
+
* Subclasses add their own construction args as a second constructor parameter (see `LoaderRegistry`).
|
|
11
|
+
*/
|
|
12
|
+
declare class SusanoLoader<T> extends TinyEmitter {
|
|
8
13
|
url: string;
|
|
9
14
|
loaded: boolean;
|
|
15
|
+
status: LoaderStatus;
|
|
10
16
|
content: T;
|
|
11
|
-
config: SusanoLoaderConfig<T>;
|
|
12
|
-
weight: number;
|
|
13
17
|
progress: number;
|
|
14
18
|
promise: Promise<T>;
|
|
15
19
|
private _resolve;
|
|
16
20
|
private _reject;
|
|
17
|
-
constructor(url: string
|
|
18
|
-
load()
|
|
21
|
+
constructor(url: string);
|
|
22
|
+
/** `true` while a content load is in flight (`status === 'loading'`). */
|
|
23
|
+
get loading(): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Load (or join) the content fetch. Idempotent: repeat calls return the same promise without
|
|
26
|
+
* re-fetching.
|
|
27
|
+
*
|
|
28
|
+
* An in-flight fetch can never be preempted: while `status === 'loading'`, every call joins the
|
|
29
|
+
* same promise regardless of `cache`. This keeps the invariant that at most one fetch runs per
|
|
30
|
+
* URL, so resetting the promise can never orphan a consumer already awaiting it. `cache: false`
|
|
31
|
+
* therefore acts on a *completed* load — it busts the cached content and forces a fresh fetch —
|
|
32
|
+
* not on concurrent work. Resolves with the raw content `T`; postprocessing happens per call in
|
|
33
|
+
* `Susano`.
|
|
34
|
+
*/
|
|
35
|
+
load(cache?: boolean): Promise<T>;
|
|
36
|
+
/** Subclasses implement the raw fetch here, calling `_onLoaded` / `_onError` / `_onProgress`. */
|
|
37
|
+
protected _load(): void;
|
|
19
38
|
_onLoaded(): void;
|
|
20
39
|
_onError(error: unknown): void;
|
|
21
40
|
private _resetPromise;
|
|
22
41
|
_onProgress(value: number): void;
|
|
23
|
-
/**
|
|
24
|
-
* This is ment to be used as a way to attach to master loader events
|
|
25
|
-
*/
|
|
26
|
-
_appendConfig(cnfg: SusanoLoaderConfig<T>): void;
|
|
27
|
-
}
|
|
28
|
-
interface ISusanoLoader<T> {
|
|
29
|
-
load: () => Promise<T>;
|
|
30
42
|
}
|
|
31
43
|
|
|
32
|
-
type SusanoImageLoaderConfig =
|
|
44
|
+
type SusanoImageLoaderConfig = {
|
|
33
45
|
srcSet?: string;
|
|
34
46
|
sizes?: string;
|
|
35
47
|
};
|
|
36
|
-
declare class ImageLoader extends SusanoLoader<HTMLImageElement>
|
|
48
|
+
declare class ImageLoader extends SusanoLoader<HTMLImageElement> {
|
|
37
49
|
static type: "image";
|
|
38
50
|
srcSet?: string;
|
|
39
51
|
sizes?: string;
|
|
40
52
|
constructor(url: string, cnfg?: SusanoImageLoaderConfig);
|
|
41
|
-
|
|
53
|
+
protected _load(): void;
|
|
42
54
|
}
|
|
43
55
|
|
|
44
56
|
type LoadEvent$1 = 'canplay' | 'canplaythrough';
|
|
45
|
-
type SusanoVideoLoaderConfig =
|
|
57
|
+
type SusanoVideoLoaderConfig = {
|
|
46
58
|
video?: HTMLVideoElement;
|
|
47
59
|
loadEvent?: LoadEvent$1;
|
|
48
60
|
};
|
|
49
|
-
declare class VideoLoader extends SusanoLoader<HTMLVideoElement>
|
|
61
|
+
declare class VideoLoader extends SusanoLoader<HTMLVideoElement> {
|
|
50
62
|
static type: "video";
|
|
51
63
|
loadEvent: LoadEvent$1;
|
|
64
|
+
private _attempt;
|
|
52
65
|
constructor(url: string, cnfg?: SusanoVideoLoaderConfig);
|
|
53
|
-
|
|
66
|
+
protected _load(): void;
|
|
54
67
|
}
|
|
55
68
|
|
|
56
69
|
type LoadEvent = 'canplay' | 'canplaythrough';
|
|
57
|
-
type SusanoAudioLoaderConfig =
|
|
70
|
+
type SusanoAudioLoaderConfig = {
|
|
58
71
|
audio?: HTMLAudioElement;
|
|
59
72
|
loadEvent?: LoadEvent;
|
|
60
73
|
};
|
|
61
|
-
declare class AudioLoader extends SusanoLoader<HTMLAudioElement>
|
|
74
|
+
declare class AudioLoader extends SusanoLoader<HTMLAudioElement> {
|
|
62
75
|
static type: "audio";
|
|
63
76
|
loadEvent: LoadEvent;
|
|
77
|
+
private _attempt;
|
|
64
78
|
constructor(url: string, cnfg?: SusanoAudioLoaderConfig);
|
|
65
|
-
|
|
79
|
+
protected _load(): void;
|
|
66
80
|
}
|
|
67
81
|
|
|
68
82
|
type GenericLoadFn = (config: {
|
|
@@ -71,81 +85,148 @@ type GenericLoadFn = (config: {
|
|
|
71
85
|
error: (error: Error) => void;
|
|
72
86
|
progress: (progress: number) => void;
|
|
73
87
|
}) => void;
|
|
74
|
-
type SusanoGenericLoaderConfig =
|
|
88
|
+
type SusanoGenericLoaderConfig = {
|
|
75
89
|
loadFn: GenericLoadFn;
|
|
76
90
|
};
|
|
77
91
|
declare class GenericLoader extends SusanoLoader<any> {
|
|
78
92
|
static type: "generic";
|
|
79
93
|
loadFn: GenericLoadFn;
|
|
80
94
|
constructor(url: string, cnfg: SusanoGenericLoaderConfig);
|
|
81
|
-
|
|
95
|
+
protected _load(): void;
|
|
82
96
|
}
|
|
83
97
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
};
|
|
89
|
-
};
|
|
90
|
-
type LoaderMap<T extends LoaderTypes> = {
|
|
91
|
-
[K in keyof T]: T[K]['loader'];
|
|
98
|
+
/** One tracked load within a batch: a content loader and its per-call result promise. */
|
|
99
|
+
type BatchItem = {
|
|
100
|
+
loader: SusanoLoader<unknown>;
|
|
101
|
+
promise: Promise<unknown>;
|
|
92
102
|
};
|
|
93
|
-
type
|
|
103
|
+
type BatchProgressEventArgs = {
|
|
94
104
|
value: number;
|
|
95
|
-
|
|
96
|
-
|
|
105
|
+
/** The loader that just settled, or `null` for a batch-level tick (e.g. an empty batch). */
|
|
106
|
+
loader: SusanoLoader<unknown> | null;
|
|
107
|
+
batch: Batch;
|
|
108
|
+
};
|
|
109
|
+
type BatchErrorEventArgs = {
|
|
110
|
+
loader: SusanoLoader<unknown>;
|
|
111
|
+
error: unknown;
|
|
112
|
+
batch: Batch;
|
|
113
|
+
};
|
|
114
|
+
type BatchHandlers = {
|
|
115
|
+
onProgress?: (progress: BatchProgressEventArgs) => void;
|
|
116
|
+
onCompleted?: (batch: Batch) => void;
|
|
117
|
+
onError?: (error: BatchErrorEventArgs) => void;
|
|
97
118
|
};
|
|
98
119
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
120
|
+
* Tracks progress and completion for a fixed set of loads, independent of other loading activity.
|
|
121
|
+
* Each batch owns its own counter and completion promise, so concurrent batches (and lazy
|
|
122
|
+
* `susano.load()` calls) never interfere.
|
|
123
|
+
*
|
|
124
|
+
* Completion is the resolution of `Promise.all` over the set — one-shot by construction. A failed
|
|
125
|
+
* item is fail-soft: it surfaces via `onError` / the `'error'` event and still advances the batch.
|
|
102
126
|
*/
|
|
103
|
-
declare class
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
active: InstanceType<T[keyof T]['loader']>[];
|
|
107
|
-
items: Map<string, InstanceType<T[keyof T]['loader']>>;
|
|
127
|
+
declare class Batch extends TinyEmitter {
|
|
128
|
+
readonly items: BatchItem[];
|
|
129
|
+
readonly loadLength: number;
|
|
108
130
|
loadCount: number;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
progress: number;
|
|
132
|
+
completed: boolean;
|
|
133
|
+
readonly promise: Promise<Batch>;
|
|
134
|
+
private _counted;
|
|
135
|
+
private _resolve;
|
|
136
|
+
constructor(items: BatchItem[], handlers?: BatchHandlers);
|
|
137
|
+
private _start;
|
|
138
|
+
/** Count an item at most once so the progress fraction can't be inflated by repeat settles. */
|
|
139
|
+
private _tick;
|
|
140
|
+
private _finish;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Per-call options. `postprocess` is the projection from cached content `T` to this call's
|
|
145
|
+
* result `R` — it runs **every call**, never cached. `onProgress` observes the shared content
|
|
146
|
+
* fetch; `onLoaded` / `onError` fire for this call's result.
|
|
147
|
+
*/
|
|
148
|
+
type LoadOptions<T, R> = {
|
|
149
|
+
postprocess?: (content: T, loader: SusanoLoader<T>) => R | Promise<R>;
|
|
150
|
+
onLoaded?: (result: R, loader: SusanoLoader<T>) => void;
|
|
151
|
+
onProgress?: (value: number, loader: SusanoLoader<T>) => void;
|
|
152
|
+
onError?: (error: unknown, loader: SusanoLoader<T>) => void;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* The common shape of any loader class: constructs a `SusanoLoader` from a `url` plus that
|
|
156
|
+
* loader's own construction args. The two `any`s are deliberate and contained to this bound:
|
|
157
|
+
* - `args` — each loader narrows it to its own config; the precise type is recovered per call via
|
|
158
|
+
* `ConstructorParameters<R[K]>` in `LoadConfig` / `BatchEntry`.
|
|
159
|
+
* - `SusanoLoader<any>` — `SusanoLoader<T>` is *invariant* in `T` (it holds `(value: T) => void`
|
|
160
|
+
* resolve/reject callbacks), so no single concrete content type bounds every loader. `any` admits
|
|
161
|
+
* them all here; per-call typing stays precise via `ContentOf<R, K>`.
|
|
162
|
+
*/
|
|
163
|
+
type AnyLoaderConstructor = new (url: string, args?: any) => SusanoLoader<any>;
|
|
164
|
+
/** The loader registry: a map from `type` key to loader class. The single source of truth. */
|
|
165
|
+
type LoaderRegistry = Record<string, AnyLoaderConstructor>;
|
|
166
|
+
/** The content type produced by the loader registered under `K`. */
|
|
167
|
+
type ContentOf<R extends LoaderRegistry, K extends keyof R> = InstanceType<R[K]> extends SusanoLoader<infer C> ? C : never;
|
|
168
|
+
/** Construction args + cache flag for a load. */
|
|
169
|
+
type LoadConfig<R extends LoaderRegistry, K extends keyof R> = {
|
|
170
|
+
type: K;
|
|
171
|
+
loaderArgs?: ConstructorParameters<R[K]>[1];
|
|
172
|
+
/** Opt out of dedup; force a fresh content fetch that overwrites the cached content. Defaults to `true`. */
|
|
173
|
+
cache?: boolean;
|
|
174
|
+
};
|
|
175
|
+
/** A single entry in a {@link Susano.batch} call — construction + per-call options, typed per `type`. */
|
|
176
|
+
type BatchEntry<R extends LoaderRegistry> = {
|
|
177
|
+
[K in keyof R]: LoadConfig<R, K> & {
|
|
178
|
+
url: string;
|
|
179
|
+
} & LoadOptions<ContentOf<R, K>, unknown>;
|
|
180
|
+
}[keyof R];
|
|
181
|
+
/**
|
|
182
|
+
* Registry + per-URL **content** cache. The loader map passed to the constructor is the single
|
|
183
|
+
* source of truth: its keys are the valid `type`s and its values are the loader classes, so
|
|
184
|
+
* `load()` / `batch()` are fully inferred — no separate type map, no drift.
|
|
185
|
+
*
|
|
186
|
+
* Content (the expensive fetch) is deduped per URL; a `postprocess` is a per-call projection
|
|
187
|
+
* (see `_runLoad`), so two call sites can derive different results from one shared fetch.
|
|
188
|
+
*
|
|
189
|
+
* @template R The loader registry — `{ [type]: LoaderClass }`.
|
|
190
|
+
*/
|
|
191
|
+
declare class Susano<R extends LoaderRegistry> {
|
|
192
|
+
private loaders;
|
|
193
|
+
items: Map<string, SusanoLoader<unknown>>;
|
|
194
|
+
constructor(loaders: R);
|
|
195
|
+
/** The shared content loader cached for `url`, if one exists. Shorthand for `items.get(url)`. */
|
|
196
|
+
get(url: string): SusanoLoader<unknown> | undefined;
|
|
197
|
+
/**
|
|
198
|
+
* Get-or-create the content loader for `url`, typed to the loader registered under `type`.
|
|
199
|
+
* The one unavoidable cast: `items` is keyed by URL (a string), which can't carry the content
|
|
200
|
+
* type, so a cache hit is asserted back to the loader's known type.
|
|
201
|
+
*/
|
|
202
|
+
private _resolve;
|
|
203
|
+
/**
|
|
204
|
+
* Load one asset immediately and return a promise of *this call's result* (content after this
|
|
205
|
+
* call's `postprocess`). The content fetch is deduped per URL. Reach the shared content loader —
|
|
206
|
+
* for raw content or events — via `susano.items.get(url)`.
|
|
207
|
+
*/
|
|
208
|
+
load<K extends keyof R, Result = ContentOf<R, K>>(url: string, cnfg: LoadConfig<R, K> & LoadOptions<ContentOf<R, K>, Result>): Promise<Result>;
|
|
209
|
+
/**
|
|
210
|
+
* Run a single load against a shared content loader: ensure content (deduped), then apply this
|
|
211
|
+
* call's `postprocess` to produce the result. Two calls for the same URL share one content fetch
|
|
212
|
+
* but each get their own result promise. Stays a function of (loader, options) — it never touches
|
|
213
|
+
* the registry.
|
|
214
|
+
*/
|
|
215
|
+
private _runLoad;
|
|
216
|
+
/**
|
|
217
|
+
* Load a fixed set and track *only* that set's progress/completion, independent of other
|
|
218
|
+
* loading activity. Content fetches are deduped across entries; each entry's `postprocess` runs
|
|
219
|
+
* per call. Auto-starts; returns a {@link Batch} exposing `.promise`, `.progress`, `.completed`.
|
|
220
|
+
*/
|
|
221
|
+
batch(entries: BatchEntry<R>[], handlers?: BatchHandlers): Batch;
|
|
129
222
|
}
|
|
130
223
|
declare const susano: Susano<{
|
|
131
|
-
image:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
video: {
|
|
136
|
-
type: "video";
|
|
137
|
-
loader: typeof VideoLoader;
|
|
138
|
-
};
|
|
139
|
-
audio: {
|
|
140
|
-
type: "audio";
|
|
141
|
-
loader: typeof AudioLoader;
|
|
142
|
-
};
|
|
143
|
-
generic: {
|
|
144
|
-
type: "generic";
|
|
145
|
-
loader: typeof GenericLoader;
|
|
146
|
-
};
|
|
224
|
+
image: typeof ImageLoader;
|
|
225
|
+
video: typeof VideoLoader;
|
|
226
|
+
audio: typeof AudioLoader;
|
|
227
|
+
generic: typeof GenericLoader;
|
|
147
228
|
}>;
|
|
148
229
|
|
|
149
230
|
declare const VERSION: string;
|
|
150
231
|
|
|
151
|
-
export { AudioLoader, type
|
|
232
|
+
export { type AnyLoaderConstructor, AudioLoader, Batch, type BatchEntry, type BatchErrorEventArgs, type BatchHandlers, type BatchItem, type BatchProgressEventArgs, type ContentOf, type GenericLoadFn, GenericLoader, ImageLoader, type LoadConfig, type LoadOptions, type LoaderRegistry, type LoaderStatus, Susano, type SusanoAudioLoaderConfig, type SusanoGenericLoaderConfig, type SusanoImageLoaderConfig, SusanoLoader, type SusanoVideoLoaderConfig, VERSION, VideoLoader, susano };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,68 +1,82 @@
|
|
|
1
1
|
import { TinyEmitter } from 'tiny-emitter';
|
|
2
2
|
|
|
3
|
-
type
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
type LoaderStatus = 'idle' | 'loading' | 'loaded' | 'error';
|
|
4
|
+
/**
|
|
5
|
+
* Base loader. Owns and caches a single piece of raw **content** per URL (the expensive fetch),
|
|
6
|
+
* deduped and idempotent. It does NOT run `postprocess` — that is a per-call projection applied
|
|
7
|
+
* by `Susano` (`_runLoad`). If you want to cache a transformed result, encode the transform in a
|
|
8
|
+
* loader subclass so its output becomes the cached content.
|
|
9
|
+
*
|
|
10
|
+
* Subclasses add their own construction args as a second constructor parameter (see `LoaderRegistry`).
|
|
11
|
+
*/
|
|
12
|
+
declare class SusanoLoader<T> extends TinyEmitter {
|
|
8
13
|
url: string;
|
|
9
14
|
loaded: boolean;
|
|
15
|
+
status: LoaderStatus;
|
|
10
16
|
content: T;
|
|
11
|
-
config: SusanoLoaderConfig<T>;
|
|
12
|
-
weight: number;
|
|
13
17
|
progress: number;
|
|
14
18
|
promise: Promise<T>;
|
|
15
19
|
private _resolve;
|
|
16
20
|
private _reject;
|
|
17
|
-
constructor(url: string
|
|
18
|
-
load()
|
|
21
|
+
constructor(url: string);
|
|
22
|
+
/** `true` while a content load is in flight (`status === 'loading'`). */
|
|
23
|
+
get loading(): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Load (or join) the content fetch. Idempotent: repeat calls return the same promise without
|
|
26
|
+
* re-fetching.
|
|
27
|
+
*
|
|
28
|
+
* An in-flight fetch can never be preempted: while `status === 'loading'`, every call joins the
|
|
29
|
+
* same promise regardless of `cache`. This keeps the invariant that at most one fetch runs per
|
|
30
|
+
* URL, so resetting the promise can never orphan a consumer already awaiting it. `cache: false`
|
|
31
|
+
* therefore acts on a *completed* load — it busts the cached content and forces a fresh fetch —
|
|
32
|
+
* not on concurrent work. Resolves with the raw content `T`; postprocessing happens per call in
|
|
33
|
+
* `Susano`.
|
|
34
|
+
*/
|
|
35
|
+
load(cache?: boolean): Promise<T>;
|
|
36
|
+
/** Subclasses implement the raw fetch here, calling `_onLoaded` / `_onError` / `_onProgress`. */
|
|
37
|
+
protected _load(): void;
|
|
19
38
|
_onLoaded(): void;
|
|
20
39
|
_onError(error: unknown): void;
|
|
21
40
|
private _resetPromise;
|
|
22
41
|
_onProgress(value: number): void;
|
|
23
|
-
/**
|
|
24
|
-
* This is ment to be used as a way to attach to master loader events
|
|
25
|
-
*/
|
|
26
|
-
_appendConfig(cnfg: SusanoLoaderConfig<T>): void;
|
|
27
|
-
}
|
|
28
|
-
interface ISusanoLoader<T> {
|
|
29
|
-
load: () => Promise<T>;
|
|
30
42
|
}
|
|
31
43
|
|
|
32
|
-
type SusanoImageLoaderConfig =
|
|
44
|
+
type SusanoImageLoaderConfig = {
|
|
33
45
|
srcSet?: string;
|
|
34
46
|
sizes?: string;
|
|
35
47
|
};
|
|
36
|
-
declare class ImageLoader extends SusanoLoader<HTMLImageElement>
|
|
48
|
+
declare class ImageLoader extends SusanoLoader<HTMLImageElement> {
|
|
37
49
|
static type: "image";
|
|
38
50
|
srcSet?: string;
|
|
39
51
|
sizes?: string;
|
|
40
52
|
constructor(url: string, cnfg?: SusanoImageLoaderConfig);
|
|
41
|
-
|
|
53
|
+
protected _load(): void;
|
|
42
54
|
}
|
|
43
55
|
|
|
44
56
|
type LoadEvent$1 = 'canplay' | 'canplaythrough';
|
|
45
|
-
type SusanoVideoLoaderConfig =
|
|
57
|
+
type SusanoVideoLoaderConfig = {
|
|
46
58
|
video?: HTMLVideoElement;
|
|
47
59
|
loadEvent?: LoadEvent$1;
|
|
48
60
|
};
|
|
49
|
-
declare class VideoLoader extends SusanoLoader<HTMLVideoElement>
|
|
61
|
+
declare class VideoLoader extends SusanoLoader<HTMLVideoElement> {
|
|
50
62
|
static type: "video";
|
|
51
63
|
loadEvent: LoadEvent$1;
|
|
64
|
+
private _attempt;
|
|
52
65
|
constructor(url: string, cnfg?: SusanoVideoLoaderConfig);
|
|
53
|
-
|
|
66
|
+
protected _load(): void;
|
|
54
67
|
}
|
|
55
68
|
|
|
56
69
|
type LoadEvent = 'canplay' | 'canplaythrough';
|
|
57
|
-
type SusanoAudioLoaderConfig =
|
|
70
|
+
type SusanoAudioLoaderConfig = {
|
|
58
71
|
audio?: HTMLAudioElement;
|
|
59
72
|
loadEvent?: LoadEvent;
|
|
60
73
|
};
|
|
61
|
-
declare class AudioLoader extends SusanoLoader<HTMLAudioElement>
|
|
74
|
+
declare class AudioLoader extends SusanoLoader<HTMLAudioElement> {
|
|
62
75
|
static type: "audio";
|
|
63
76
|
loadEvent: LoadEvent;
|
|
77
|
+
private _attempt;
|
|
64
78
|
constructor(url: string, cnfg?: SusanoAudioLoaderConfig);
|
|
65
|
-
|
|
79
|
+
protected _load(): void;
|
|
66
80
|
}
|
|
67
81
|
|
|
68
82
|
type GenericLoadFn = (config: {
|
|
@@ -71,81 +85,148 @@ type GenericLoadFn = (config: {
|
|
|
71
85
|
error: (error: Error) => void;
|
|
72
86
|
progress: (progress: number) => void;
|
|
73
87
|
}) => void;
|
|
74
|
-
type SusanoGenericLoaderConfig =
|
|
88
|
+
type SusanoGenericLoaderConfig = {
|
|
75
89
|
loadFn: GenericLoadFn;
|
|
76
90
|
};
|
|
77
91
|
declare class GenericLoader extends SusanoLoader<any> {
|
|
78
92
|
static type: "generic";
|
|
79
93
|
loadFn: GenericLoadFn;
|
|
80
94
|
constructor(url: string, cnfg: SusanoGenericLoaderConfig);
|
|
81
|
-
|
|
95
|
+
protected _load(): void;
|
|
82
96
|
}
|
|
83
97
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
};
|
|
89
|
-
};
|
|
90
|
-
type LoaderMap<T extends LoaderTypes> = {
|
|
91
|
-
[K in keyof T]: T[K]['loader'];
|
|
98
|
+
/** One tracked load within a batch: a content loader and its per-call result promise. */
|
|
99
|
+
type BatchItem = {
|
|
100
|
+
loader: SusanoLoader<unknown>;
|
|
101
|
+
promise: Promise<unknown>;
|
|
92
102
|
};
|
|
93
|
-
type
|
|
103
|
+
type BatchProgressEventArgs = {
|
|
94
104
|
value: number;
|
|
95
|
-
|
|
96
|
-
|
|
105
|
+
/** The loader that just settled, or `null` for a batch-level tick (e.g. an empty batch). */
|
|
106
|
+
loader: SusanoLoader<unknown> | null;
|
|
107
|
+
batch: Batch;
|
|
108
|
+
};
|
|
109
|
+
type BatchErrorEventArgs = {
|
|
110
|
+
loader: SusanoLoader<unknown>;
|
|
111
|
+
error: unknown;
|
|
112
|
+
batch: Batch;
|
|
113
|
+
};
|
|
114
|
+
type BatchHandlers = {
|
|
115
|
+
onProgress?: (progress: BatchProgressEventArgs) => void;
|
|
116
|
+
onCompleted?: (batch: Batch) => void;
|
|
117
|
+
onError?: (error: BatchErrorEventArgs) => void;
|
|
97
118
|
};
|
|
98
119
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
120
|
+
* Tracks progress and completion for a fixed set of loads, independent of other loading activity.
|
|
121
|
+
* Each batch owns its own counter and completion promise, so concurrent batches (and lazy
|
|
122
|
+
* `susano.load()` calls) never interfere.
|
|
123
|
+
*
|
|
124
|
+
* Completion is the resolution of `Promise.all` over the set — one-shot by construction. A failed
|
|
125
|
+
* item is fail-soft: it surfaces via `onError` / the `'error'` event and still advances the batch.
|
|
102
126
|
*/
|
|
103
|
-
declare class
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
active: InstanceType<T[keyof T]['loader']>[];
|
|
107
|
-
items: Map<string, InstanceType<T[keyof T]['loader']>>;
|
|
127
|
+
declare class Batch extends TinyEmitter {
|
|
128
|
+
readonly items: BatchItem[];
|
|
129
|
+
readonly loadLength: number;
|
|
108
130
|
loadCount: number;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
progress: number;
|
|
132
|
+
completed: boolean;
|
|
133
|
+
readonly promise: Promise<Batch>;
|
|
134
|
+
private _counted;
|
|
135
|
+
private _resolve;
|
|
136
|
+
constructor(items: BatchItem[], handlers?: BatchHandlers);
|
|
137
|
+
private _start;
|
|
138
|
+
/** Count an item at most once so the progress fraction can't be inflated by repeat settles. */
|
|
139
|
+
private _tick;
|
|
140
|
+
private _finish;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Per-call options. `postprocess` is the projection from cached content `T` to this call's
|
|
145
|
+
* result `R` — it runs **every call**, never cached. `onProgress` observes the shared content
|
|
146
|
+
* fetch; `onLoaded` / `onError` fire for this call's result.
|
|
147
|
+
*/
|
|
148
|
+
type LoadOptions<T, R> = {
|
|
149
|
+
postprocess?: (content: T, loader: SusanoLoader<T>) => R | Promise<R>;
|
|
150
|
+
onLoaded?: (result: R, loader: SusanoLoader<T>) => void;
|
|
151
|
+
onProgress?: (value: number, loader: SusanoLoader<T>) => void;
|
|
152
|
+
onError?: (error: unknown, loader: SusanoLoader<T>) => void;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* The common shape of any loader class: constructs a `SusanoLoader` from a `url` plus that
|
|
156
|
+
* loader's own construction args. The two `any`s are deliberate and contained to this bound:
|
|
157
|
+
* - `args` — each loader narrows it to its own config; the precise type is recovered per call via
|
|
158
|
+
* `ConstructorParameters<R[K]>` in `LoadConfig` / `BatchEntry`.
|
|
159
|
+
* - `SusanoLoader<any>` — `SusanoLoader<T>` is *invariant* in `T` (it holds `(value: T) => void`
|
|
160
|
+
* resolve/reject callbacks), so no single concrete content type bounds every loader. `any` admits
|
|
161
|
+
* them all here; per-call typing stays precise via `ContentOf<R, K>`.
|
|
162
|
+
*/
|
|
163
|
+
type AnyLoaderConstructor = new (url: string, args?: any) => SusanoLoader<any>;
|
|
164
|
+
/** The loader registry: a map from `type` key to loader class. The single source of truth. */
|
|
165
|
+
type LoaderRegistry = Record<string, AnyLoaderConstructor>;
|
|
166
|
+
/** The content type produced by the loader registered under `K`. */
|
|
167
|
+
type ContentOf<R extends LoaderRegistry, K extends keyof R> = InstanceType<R[K]> extends SusanoLoader<infer C> ? C : never;
|
|
168
|
+
/** Construction args + cache flag for a load. */
|
|
169
|
+
type LoadConfig<R extends LoaderRegistry, K extends keyof R> = {
|
|
170
|
+
type: K;
|
|
171
|
+
loaderArgs?: ConstructorParameters<R[K]>[1];
|
|
172
|
+
/** Opt out of dedup; force a fresh content fetch that overwrites the cached content. Defaults to `true`. */
|
|
173
|
+
cache?: boolean;
|
|
174
|
+
};
|
|
175
|
+
/** A single entry in a {@link Susano.batch} call — construction + per-call options, typed per `type`. */
|
|
176
|
+
type BatchEntry<R extends LoaderRegistry> = {
|
|
177
|
+
[K in keyof R]: LoadConfig<R, K> & {
|
|
178
|
+
url: string;
|
|
179
|
+
} & LoadOptions<ContentOf<R, K>, unknown>;
|
|
180
|
+
}[keyof R];
|
|
181
|
+
/**
|
|
182
|
+
* Registry + per-URL **content** cache. The loader map passed to the constructor is the single
|
|
183
|
+
* source of truth: its keys are the valid `type`s and its values are the loader classes, so
|
|
184
|
+
* `load()` / `batch()` are fully inferred — no separate type map, no drift.
|
|
185
|
+
*
|
|
186
|
+
* Content (the expensive fetch) is deduped per URL; a `postprocess` is a per-call projection
|
|
187
|
+
* (see `_runLoad`), so two call sites can derive different results from one shared fetch.
|
|
188
|
+
*
|
|
189
|
+
* @template R The loader registry — `{ [type]: LoaderClass }`.
|
|
190
|
+
*/
|
|
191
|
+
declare class Susano<R extends LoaderRegistry> {
|
|
192
|
+
private loaders;
|
|
193
|
+
items: Map<string, SusanoLoader<unknown>>;
|
|
194
|
+
constructor(loaders: R);
|
|
195
|
+
/** The shared content loader cached for `url`, if one exists. Shorthand for `items.get(url)`. */
|
|
196
|
+
get(url: string): SusanoLoader<unknown> | undefined;
|
|
197
|
+
/**
|
|
198
|
+
* Get-or-create the content loader for `url`, typed to the loader registered under `type`.
|
|
199
|
+
* The one unavoidable cast: `items` is keyed by URL (a string), which can't carry the content
|
|
200
|
+
* type, so a cache hit is asserted back to the loader's known type.
|
|
201
|
+
*/
|
|
202
|
+
private _resolve;
|
|
203
|
+
/**
|
|
204
|
+
* Load one asset immediately and return a promise of *this call's result* (content after this
|
|
205
|
+
* call's `postprocess`). The content fetch is deduped per URL. Reach the shared content loader —
|
|
206
|
+
* for raw content or events — via `susano.items.get(url)`.
|
|
207
|
+
*/
|
|
208
|
+
load<K extends keyof R, Result = ContentOf<R, K>>(url: string, cnfg: LoadConfig<R, K> & LoadOptions<ContentOf<R, K>, Result>): Promise<Result>;
|
|
209
|
+
/**
|
|
210
|
+
* Run a single load against a shared content loader: ensure content (deduped), then apply this
|
|
211
|
+
* call's `postprocess` to produce the result. Two calls for the same URL share one content fetch
|
|
212
|
+
* but each get their own result promise. Stays a function of (loader, options) — it never touches
|
|
213
|
+
* the registry.
|
|
214
|
+
*/
|
|
215
|
+
private _runLoad;
|
|
216
|
+
/**
|
|
217
|
+
* Load a fixed set and track *only* that set's progress/completion, independent of other
|
|
218
|
+
* loading activity. Content fetches are deduped across entries; each entry's `postprocess` runs
|
|
219
|
+
* per call. Auto-starts; returns a {@link Batch} exposing `.promise`, `.progress`, `.completed`.
|
|
220
|
+
*/
|
|
221
|
+
batch(entries: BatchEntry<R>[], handlers?: BatchHandlers): Batch;
|
|
129
222
|
}
|
|
130
223
|
declare const susano: Susano<{
|
|
131
|
-
image:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
video: {
|
|
136
|
-
type: "video";
|
|
137
|
-
loader: typeof VideoLoader;
|
|
138
|
-
};
|
|
139
|
-
audio: {
|
|
140
|
-
type: "audio";
|
|
141
|
-
loader: typeof AudioLoader;
|
|
142
|
-
};
|
|
143
|
-
generic: {
|
|
144
|
-
type: "generic";
|
|
145
|
-
loader: typeof GenericLoader;
|
|
146
|
-
};
|
|
224
|
+
image: typeof ImageLoader;
|
|
225
|
+
video: typeof VideoLoader;
|
|
226
|
+
audio: typeof AudioLoader;
|
|
227
|
+
generic: typeof GenericLoader;
|
|
147
228
|
}>;
|
|
148
229
|
|
|
149
230
|
declare const VERSION: string;
|
|
150
231
|
|
|
151
|
-
export { AudioLoader, type
|
|
232
|
+
export { type AnyLoaderConstructor, AudioLoader, Batch, type BatchEntry, type BatchErrorEventArgs, type BatchHandlers, type BatchItem, type BatchProgressEventArgs, type ContentOf, type GenericLoadFn, GenericLoader, ImageLoader, type LoadConfig, type LoadOptions, type LoaderRegistry, type LoaderStatus, Susano, type SusanoAudioLoaderConfig, type SusanoGenericLoaderConfig, type SusanoImageLoaderConfig, SusanoLoader, type SusanoVideoLoaderConfig, VERSION, VideoLoader, susano };
|