@joycostudio/susano 0.2.0 → 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 +170 -91
- package/dist/index.d.ts +170 -91
- package/dist/index.js +217 -139
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +217 -140
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -3,39 +3,57 @@
|
|
|
3
3
|
var tinyEmitter = require('tiny-emitter');
|
|
4
4
|
|
|
5
5
|
// package.json
|
|
6
|
-
var version = "0.
|
|
6
|
+
var version = "1.0.0";
|
|
7
7
|
var SusanoLoader = class extends tinyEmitter.TinyEmitter {
|
|
8
|
-
constructor(url
|
|
8
|
+
constructor(url) {
|
|
9
9
|
super();
|
|
10
10
|
this.url = url;
|
|
11
11
|
this.loaded = false;
|
|
12
|
+
this.status = "idle";
|
|
12
13
|
this.content = null;
|
|
13
|
-
this.config = _cnfg;
|
|
14
|
-
this.weight = 1;
|
|
15
14
|
this.progress = 0;
|
|
16
15
|
this._resetPromise();
|
|
17
16
|
}
|
|
18
|
-
load()
|
|
17
|
+
/** `true` while a content load is in flight (`status === 'loading'`). */
|
|
18
|
+
get loading() {
|
|
19
|
+
return this.status === "loading";
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Load (or join) the content fetch. Idempotent: repeat calls return the same promise without
|
|
23
|
+
* re-fetching.
|
|
24
|
+
*
|
|
25
|
+
* An in-flight fetch can never be preempted: while `status === 'loading'`, every call joins the
|
|
26
|
+
* same promise regardless of `cache`. This keeps the invariant that at most one fetch runs per
|
|
27
|
+
* URL, so resetting the promise can never orphan a consumer already awaiting it. `cache: false`
|
|
28
|
+
* therefore acts on a *completed* load — it busts the cached content and forces a fresh fetch —
|
|
29
|
+
* not on concurrent work. Resolves with the raw content `T`; postprocessing happens per call in
|
|
30
|
+
* `Susano`.
|
|
31
|
+
*/
|
|
32
|
+
load(cache = true) {
|
|
33
|
+
if (this.status === "loading") return this.promise;
|
|
34
|
+
if (cache && this.status === "loaded") return this.promise;
|
|
35
|
+
if (this.status !== "idle") this._resetPromise();
|
|
36
|
+
this.status = "loading";
|
|
37
|
+
this.loaded = false;
|
|
38
|
+
this.progress = 0;
|
|
39
|
+
this._load();
|
|
40
|
+
return this.promise;
|
|
41
|
+
}
|
|
42
|
+
/** Subclasses implement the raw fetch here, calling `_onLoaded` / `_onError` / `_onProgress`. */
|
|
43
|
+
_load() {
|
|
19
44
|
throw new Error("Method not implemented");
|
|
20
45
|
}
|
|
21
46
|
_onLoaded() {
|
|
22
|
-
if (this.config.postprocess) {
|
|
23
|
-
Promise.resolve(this.config.postprocess(this.content, this)).then((result) => this._finalize(result)).catch((error) => this._onError(error));
|
|
24
|
-
} else {
|
|
25
|
-
this._finalize(this.content);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
_finalize(result) {
|
|
29
47
|
this.loaded = true;
|
|
48
|
+
this.status = "loaded";
|
|
30
49
|
this.progress = 1;
|
|
31
|
-
this._resolve(
|
|
50
|
+
this._resolve(this.content);
|
|
32
51
|
this.emit("loaded", this);
|
|
33
|
-
this.config.onLoaded?.(this);
|
|
34
52
|
}
|
|
35
53
|
_onError(error) {
|
|
54
|
+
this.status = "error";
|
|
36
55
|
this._reject(error);
|
|
37
56
|
this.progress = 0;
|
|
38
|
-
this._resetPromise();
|
|
39
57
|
this.emit("error", error);
|
|
40
58
|
}
|
|
41
59
|
_resetPromise() {
|
|
@@ -47,51 +65,23 @@ var SusanoLoader = class extends tinyEmitter.TinyEmitter {
|
|
|
47
65
|
_onProgress(value) {
|
|
48
66
|
this.progress = value;
|
|
49
67
|
this.emit("progress", this);
|
|
50
|
-
this.config.onProgress?.(this);
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* This is ment to be used as a way to attach to master loader events
|
|
54
|
-
*/
|
|
55
|
-
_appendConfig(cnfg) {
|
|
56
|
-
const _onLoaded = this.config.onLoaded;
|
|
57
|
-
this.config.onLoaded = (loader) => {
|
|
58
|
-
_onLoaded?.(loader);
|
|
59
|
-
cnfg.onLoaded?.(loader);
|
|
60
|
-
};
|
|
61
|
-
const _onProgress = this.config.onProgress;
|
|
62
|
-
this.config.onProgress = (loader) => {
|
|
63
|
-
_onProgress?.(loader);
|
|
64
|
-
cnfg.onProgress?.(loader);
|
|
65
|
-
};
|
|
66
|
-
const _postprocess = this.config.postprocess;
|
|
67
|
-
if (cnfg.postprocess) {
|
|
68
|
-
this.config.postprocess = async (content, loader) => {
|
|
69
|
-
const result = _postprocess ? await _postprocess(content, loader) : content;
|
|
70
|
-
return cnfg.postprocess(result, loader);
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
68
|
}
|
|
74
69
|
};
|
|
75
70
|
|
|
76
71
|
// packages/core/loaders/image.ts
|
|
77
72
|
var ImageLoader = class extends SusanoLoader {
|
|
78
73
|
constructor(url, cnfg = {}) {
|
|
79
|
-
super(url
|
|
74
|
+
super(url);
|
|
80
75
|
this.content = new Image();
|
|
81
76
|
this.srcSet = cnfg.srcSet;
|
|
82
77
|
this.sizes = cnfg.sizes;
|
|
83
78
|
}
|
|
84
|
-
|
|
85
|
-
if (this.loaded) {
|
|
86
|
-
this._onLoaded();
|
|
87
|
-
return this.promise;
|
|
88
|
-
}
|
|
79
|
+
_load() {
|
|
89
80
|
if (this.srcSet) this.content.srcset = this.srcSet;
|
|
90
81
|
if (this.sizes) this.content.sizes = this.sizes;
|
|
91
82
|
this.content.src = this.url;
|
|
92
83
|
this.content.onload = () => this._onLoaded();
|
|
93
84
|
this.content.onerror = (e) => this._onError(e);
|
|
94
|
-
return this.promise;
|
|
95
85
|
}
|
|
96
86
|
};
|
|
97
87
|
ImageLoader.type = "image";
|
|
@@ -99,20 +89,34 @@ ImageLoader.type = "image";
|
|
|
99
89
|
// packages/core/loaders/video.ts
|
|
100
90
|
var VideoLoader = class extends SusanoLoader {
|
|
101
91
|
constructor(url, cnfg = {}) {
|
|
102
|
-
super(url
|
|
92
|
+
super(url);
|
|
93
|
+
this._attempt = null;
|
|
103
94
|
this.content = cnfg.video || document.createElement("video");
|
|
104
95
|
this.loadEvent = cnfg.loadEvent || "canplay";
|
|
105
96
|
}
|
|
106
|
-
|
|
107
|
-
if (this.loaded) {
|
|
108
|
-
this._onLoaded();
|
|
109
|
-
return this.promise;
|
|
110
|
-
}
|
|
97
|
+
_load() {
|
|
111
98
|
const el = this.content;
|
|
99
|
+
this._attempt?.abort();
|
|
100
|
+
const attempt = new AbortController();
|
|
101
|
+
this._attempt = attempt;
|
|
102
|
+
const { signal } = attempt;
|
|
112
103
|
el.src = this.url;
|
|
113
|
-
el.addEventListener(
|
|
114
|
-
|
|
115
|
-
|
|
104
|
+
el.addEventListener(
|
|
105
|
+
this.loadEvent,
|
|
106
|
+
() => {
|
|
107
|
+
attempt.abort();
|
|
108
|
+
this._onLoaded();
|
|
109
|
+
},
|
|
110
|
+
{ signal }
|
|
111
|
+
);
|
|
112
|
+
el.addEventListener(
|
|
113
|
+
"error",
|
|
114
|
+
(e) => {
|
|
115
|
+
attempt.abort();
|
|
116
|
+
this._onError(e);
|
|
117
|
+
},
|
|
118
|
+
{ signal }
|
|
119
|
+
);
|
|
116
120
|
}
|
|
117
121
|
};
|
|
118
122
|
VideoLoader.type = "video";
|
|
@@ -120,20 +124,34 @@ VideoLoader.type = "video";
|
|
|
120
124
|
// packages/core/loaders/audio.ts
|
|
121
125
|
var AudioLoader = class extends SusanoLoader {
|
|
122
126
|
constructor(url, cnfg = {}) {
|
|
123
|
-
super(url
|
|
127
|
+
super(url);
|
|
128
|
+
this._attempt = null;
|
|
124
129
|
this.content = cnfg.audio || document.createElement("audio");
|
|
125
130
|
this.loadEvent = cnfg.loadEvent || "canplay";
|
|
126
131
|
}
|
|
127
|
-
|
|
128
|
-
if (this.loaded) {
|
|
129
|
-
this._onLoaded();
|
|
130
|
-
return this.promise;
|
|
131
|
-
}
|
|
132
|
+
_load() {
|
|
132
133
|
const el = this.content;
|
|
134
|
+
this._attempt?.abort();
|
|
135
|
+
const attempt = new AbortController();
|
|
136
|
+
this._attempt = attempt;
|
|
137
|
+
const { signal } = attempt;
|
|
133
138
|
el.src = this.url;
|
|
134
|
-
el.addEventListener(
|
|
135
|
-
|
|
136
|
-
|
|
139
|
+
el.addEventListener(
|
|
140
|
+
this.loadEvent,
|
|
141
|
+
() => {
|
|
142
|
+
attempt.abort();
|
|
143
|
+
this._onLoaded();
|
|
144
|
+
},
|
|
145
|
+
{ signal }
|
|
146
|
+
);
|
|
147
|
+
el.addEventListener(
|
|
148
|
+
"error",
|
|
149
|
+
(e) => {
|
|
150
|
+
attempt.abort();
|
|
151
|
+
this._onError(e);
|
|
152
|
+
},
|
|
153
|
+
{ signal }
|
|
154
|
+
);
|
|
137
155
|
}
|
|
138
156
|
};
|
|
139
157
|
AudioLoader.type = "audio";
|
|
@@ -141,17 +159,13 @@ AudioLoader.type = "audio";
|
|
|
141
159
|
// packages/core/loaders/generic.ts
|
|
142
160
|
var GenericLoader = class extends SusanoLoader {
|
|
143
161
|
constructor(url, cnfg) {
|
|
144
|
-
super(url
|
|
162
|
+
super(url);
|
|
145
163
|
this.loadFn = cnfg.loadFn;
|
|
146
164
|
}
|
|
147
|
-
|
|
165
|
+
_load() {
|
|
148
166
|
if (!this.loadFn) {
|
|
149
167
|
this._onError(new Error("No load function provided"));
|
|
150
|
-
return
|
|
151
|
-
}
|
|
152
|
-
if (this.loaded) {
|
|
153
|
-
this._onLoaded();
|
|
154
|
-
return this.promise;
|
|
168
|
+
return;
|
|
155
169
|
}
|
|
156
170
|
this.loadFn({
|
|
157
171
|
url: this.url,
|
|
@@ -163,95 +177,159 @@ var GenericLoader = class extends SusanoLoader {
|
|
|
163
177
|
/* 0 to 1 */
|
|
164
178
|
progress: (value) => this._onProgress(value)
|
|
165
179
|
});
|
|
166
|
-
return this.promise;
|
|
167
180
|
}
|
|
168
181
|
};
|
|
169
182
|
GenericLoader.type = "generic";
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
var Susano = class extends tinyEmitter.TinyEmitter {
|
|
173
|
-
constructor() {
|
|
183
|
+
var Batch = class extends tinyEmitter.TinyEmitter {
|
|
184
|
+
constructor(items, handlers = {}) {
|
|
174
185
|
super();
|
|
175
|
-
this.loaders = {};
|
|
176
|
-
this.queue = [];
|
|
177
|
-
this.active = [];
|
|
178
|
-
this.items = /* @__PURE__ */ new Map();
|
|
179
186
|
this.loadCount = 0;
|
|
180
|
-
this.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
this.queue.push(item);
|
|
193
|
-
return item;
|
|
187
|
+
this.progress = 0;
|
|
188
|
+
this.completed = false;
|
|
189
|
+
this._counted = /* @__PURE__ */ new Set();
|
|
190
|
+
this.items = items;
|
|
191
|
+
this.loadLength = items.length;
|
|
192
|
+
this.promise = new Promise((resolve) => {
|
|
193
|
+
this._resolve = resolve;
|
|
194
|
+
});
|
|
195
|
+
if (handlers.onProgress) this.on("progress", handlers.onProgress);
|
|
196
|
+
if (handlers.onCompleted) this.once("completed", handlers.onCompleted);
|
|
197
|
+
if (handlers.onError) this.on("error", handlers.onError);
|
|
198
|
+
Promise.resolve().then(() => this._start());
|
|
194
199
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
this.items.set(url, item);
|
|
202
|
-
} else if (cnfg.loaderArgs) {
|
|
203
|
-
item._appendConfig(cnfg.loaderArgs);
|
|
200
|
+
_start() {
|
|
201
|
+
if (this.loadLength === 0) {
|
|
202
|
+
this.progress = 1;
|
|
203
|
+
this.emit("progress", { value: 1, loader: null, batch: this });
|
|
204
|
+
this._finish();
|
|
205
|
+
return;
|
|
204
206
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
+
const settled = this.items.map(
|
|
208
|
+
(item) => item.promise.then(() => this._tick(item)).catch((error) => {
|
|
209
|
+
this.emit("error", { loader: item.loader, error, batch: this });
|
|
210
|
+
this._tick(item);
|
|
211
|
+
})
|
|
212
|
+
);
|
|
213
|
+
Promise.all(settled).then(() => this._finish());
|
|
207
214
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
this.loadCount
|
|
213
|
-
|
|
215
|
+
/** Count an item at most once so the progress fraction can't be inflated by repeat settles. */
|
|
216
|
+
_tick(item) {
|
|
217
|
+
if (this._counted.has(item)) return;
|
|
218
|
+
this._counted.add(item);
|
|
219
|
+
this.loadCount = this._counted.size;
|
|
220
|
+
this.progress = this.loadCount / this.loadLength;
|
|
214
221
|
this.emit("progress", {
|
|
215
|
-
value: progress,
|
|
216
|
-
item,
|
|
217
|
-
|
|
222
|
+
value: this.progress,
|
|
223
|
+
loader: item.loader,
|
|
224
|
+
batch: this
|
|
218
225
|
});
|
|
219
|
-
if (progress === 1) {
|
|
220
|
-
this.emit("completed", this);
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
226
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
this.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
227
|
+
_finish() {
|
|
228
|
+
this.completed = true;
|
|
229
|
+
this.progress = 1;
|
|
230
|
+
this._resolve(this);
|
|
231
|
+
this.emit("completed", this);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// packages/core/core.tsx
|
|
236
|
+
var Susano = class {
|
|
237
|
+
constructor(loaders) {
|
|
238
|
+
this.items = /* @__PURE__ */ new Map();
|
|
239
|
+
this.loaders = loaders;
|
|
240
|
+
}
|
|
241
|
+
/** The shared content loader cached for `url`, if one exists. Shorthand for `items.get(url)`. */
|
|
242
|
+
get(url) {
|
|
243
|
+
return this.items.get(url);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Get-or-create the content loader for `url`, typed to the loader registered under `type`.
|
|
247
|
+
* The one unavoidable cast: `items` is keyed by URL (a string), which can't carry the content
|
|
248
|
+
* type, so a cache hit is asserted back to the loader's known type.
|
|
249
|
+
*/
|
|
250
|
+
_resolve(url, type, loaderArgs) {
|
|
251
|
+
const cached = this.items.get(url);
|
|
252
|
+
if (cached) return cached;
|
|
253
|
+
const Loader = this.loaders[type];
|
|
254
|
+
if (!Loader) throw new Error(`No loader registered for type "${String(type)}"`);
|
|
255
|
+
const loader = new Loader(url, loaderArgs);
|
|
256
|
+
this.items.set(url, loader);
|
|
257
|
+
return loader;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Load one asset immediately and return a promise of *this call's result* (content after this
|
|
261
|
+
* call's `postprocess`). The content fetch is deduped per URL. Reach the shared content loader —
|
|
262
|
+
* for raw content or events — via `susano.items.get(url)`.
|
|
263
|
+
*/
|
|
264
|
+
load(url, cnfg) {
|
|
265
|
+
const loader = this._resolve(url, cnfg.type, cnfg.loaderArgs);
|
|
266
|
+
return this._runLoad(
|
|
267
|
+
loader,
|
|
268
|
+
{ postprocess: cnfg.postprocess, onLoaded: cnfg.onLoaded, onProgress: cnfg.onProgress, onError: cnfg.onError },
|
|
269
|
+
cnfg.cache ?? true
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Run a single load against a shared content loader: ensure content (deduped), then apply this
|
|
274
|
+
* call's `postprocess` to produce the result. Two calls for the same URL share one content fetch
|
|
275
|
+
* but each get their own result promise. Stays a function of (loader, options) — it never touches
|
|
276
|
+
* the registry.
|
|
277
|
+
*/
|
|
278
|
+
_runLoad(loader, options, cache) {
|
|
279
|
+
const { postprocess, onLoaded, onProgress, onError } = options;
|
|
280
|
+
let progressHandler;
|
|
281
|
+
if (onProgress) {
|
|
282
|
+
progressHandler = (l) => onProgress(l.progress, l);
|
|
283
|
+
loader.on("progress", progressHandler);
|
|
284
|
+
}
|
|
285
|
+
const detach = () => {
|
|
286
|
+
if (progressHandler) {
|
|
287
|
+
loader.off("progress", progressHandler);
|
|
288
|
+
progressHandler = void 0;
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
return loader.load(cache).then(async (content) => {
|
|
292
|
+
const result = postprocess ? await postprocess(content, loader) : content;
|
|
293
|
+
detach();
|
|
294
|
+
onLoaded?.(result, loader);
|
|
295
|
+
return result;
|
|
296
|
+
}).catch((error) => {
|
|
297
|
+
detach();
|
|
298
|
+
onError?.(error, loader);
|
|
299
|
+
throw error;
|
|
236
300
|
});
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Load a fixed set and track *only* that set's progress/completion, independent of other
|
|
304
|
+
* loading activity. Content fetches are deduped across entries; each entry's `postprocess` runs
|
|
305
|
+
* per call. Auto-starts; returns a {@link Batch} exposing `.promise`, `.progress`, `.completed`.
|
|
306
|
+
*/
|
|
307
|
+
batch(entries, handlers = {}) {
|
|
308
|
+
const items = entries.map((entry) => {
|
|
309
|
+
const loader = this._resolve(entry.url, entry.type, entry.loaderArgs);
|
|
310
|
+
const options = {
|
|
311
|
+
postprocess: entry.postprocess,
|
|
312
|
+
onLoaded: entry.onLoaded,
|
|
313
|
+
onProgress: entry.onProgress,
|
|
314
|
+
onError: entry.onError
|
|
315
|
+
};
|
|
316
|
+
return { loader, promise: this._runLoad(loader, options, entry.cache ?? true) };
|
|
242
317
|
});
|
|
318
|
+
return new Batch(items, handlers);
|
|
243
319
|
}
|
|
244
320
|
};
|
|
245
|
-
var susano = new Susano(
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
321
|
+
var susano = new Susano({
|
|
322
|
+
image: ImageLoader,
|
|
323
|
+
video: VideoLoader,
|
|
324
|
+
audio: AudioLoader,
|
|
325
|
+
generic: GenericLoader
|
|
326
|
+
});
|
|
250
327
|
|
|
251
328
|
// packages/core/index.ts
|
|
252
329
|
var VERSION = version;
|
|
253
330
|
|
|
254
331
|
exports.AudioLoader = AudioLoader;
|
|
332
|
+
exports.Batch = Batch;
|
|
255
333
|
exports.GenericLoader = GenericLoader;
|
|
256
334
|
exports.ImageLoader = ImageLoader;
|
|
257
335
|
exports.Susano = Susano;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../package.json","../packages/core/loaders/loader.ts","../packages/core/loaders/image.ts","../packages/core/loaders/video.ts","../packages/core/loaders/audio.ts","../packages/core/loaders/generic.ts","../packages/core/core.tsx","../packages/core/index.ts"],"names":["TinyEmitter"],"mappings":"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","file":"index.js","sourcesContent":["{\n \"name\": \"@joycostudio/susano\",\n \"publishConfig\": {\n \"registry\": \"https://registry.npmjs.org\",\n \"access\": \"public\"\n },\n \"version\": \"0.2.0\",\n \"description\": \"Asset load orchestration made easy\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.mjs\",\n \"types\": \"dist/index.d.ts\",\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"concurrently \\\"tsup --watch\\\" \\\"cd templates/basic && pnpm dev\\\"\",\n \"typecheck\": \"tsc --noEmit\",\n \"version:package\": \"pnpm changeset version\",\n \"release\": \"pnpm build && pnpm changeset publish\",\n \"lint\": \"eslint -c ./eslint.config.mjs . --fix --no-cache\"\n },\n \"packageManager\": \"pnpm@10.29.2\",\n \"author\": \"@joycostudio\",\n \"license\": \"ISC\",\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.27.11\",\n \"@eslint/js\": \"^9.18.0\",\n \"@types/node\": \"^20.11.24\",\n \"@types/react\": \"^18.2.61\",\n \"@typescript-eslint/eslint-plugin\": \"^8.21.0\",\n \"@typescript-eslint/parser\": \"^8.21.0\",\n \"concurrently\": \"^9.1.2\",\n \"eslint\": \"^9.18.0\",\n \"eslint-config-prettier\": \"^10.0.1\",\n \"eslint-plugin-prettier\": \"^5.2.3\",\n \"eslint-plugin-react\": \"^7.37.4\",\n \"eslint-plugin-react-compiler\": \"19.0.0-beta-decd7b8-20250118\",\n \"globals\": \"^15.14.0\",\n \"prettier\": \"^3.4.2\",\n \"react\": \"^18.2.0\",\n \"tsup\": \"^8.0.2\",\n \"typescript\": \"^5.7.3\",\n \"typescript-eslint\": \"^8.21.0\"\n },\n \"peerDependencies\": {\n \"react\": \">=16.8.0\"\n },\n \"dependencies\": {\n \"tiny-emitter\": \"^2.1.0\"\n }\n}","import { TinyEmitter } from 'tiny-emitter'\n\nexport type SusanoLoaderConfig<T, R = T> = {\n onLoaded?: (loader: SusanoLoader<T, R>) => void\n onProgress?: (loader: SusanoLoader<T, R>) => void\n postprocess?: (content: T, loader: SusanoLoader<T, R>) => R | Promise<R>\n}\n\nexport class SusanoLoader<T, R = T> extends TinyEmitter implements ISusanoLoader<T, R> {\n public url: string\n public loaded: boolean\n public content: T\n public config: SusanoLoaderConfig<T, R>\n public weight: number\n public progress: number\n public promise!: Promise<R>\n private _resolve!: (value: R) => void\n private _reject!: (error: unknown) => void\n\n constructor(url: string, _cnfg: SusanoLoaderConfig<T, R> = {}) {\n super()\n this.url = url\n this.loaded = false\n this.content = null as unknown as T\n this.config = _cnfg\n this.weight = 1\n this.progress = 0\n\n this._resetPromise()\n }\n\n load(): Promise<R> {\n throw new Error('Method not implemented')\n }\n\n _onLoaded() {\n if (this.config.postprocess) {\n Promise.resolve(this.config.postprocess(this.content, this))\n .then((result) => this._finalize(result))\n .catch((error) => this._onError(error))\n } else {\n this._finalize(this.content as unknown as R)\n }\n }\n\n private _finalize(result: R) {\n this.loaded = true\n this.progress = 1\n this._resolve(result)\n this.emit('loaded', this)\n this.config.onLoaded?.(this)\n }\n\n _onError(error: unknown) {\n this._reject(error)\n this.progress = 0\n this._resetPromise()\n this.emit('error', error)\n }\n\n private _resetPromise() {\n this.promise = new Promise<R>((resolve, reject) => {\n this._resolve = resolve\n this._reject = reject\n })\n }\n\n _onProgress(value: number) {\n this.progress = value\n this.emit('progress', this)\n this.config.onProgress?.(this)\n }\n\n /**\n * This is ment to be used as a way to attach to master loader events\n */\n _appendConfig(cnfg: SusanoLoaderConfig<T, R>) {\n const _onLoaded = this.config.onLoaded\n this.config.onLoaded = (loader) => {\n _onLoaded?.(loader)\n cnfg.onLoaded?.(loader)\n }\n\n const _onProgress = this.config.onProgress\n\n this.config.onProgress = (loader) => {\n _onProgress?.(loader)\n cnfg.onProgress?.(loader)\n }\n\n const _postprocess = this.config.postprocess\n if (cnfg.postprocess) {\n this.config.postprocess = async (content, loader) => {\n const result = _postprocess ? await _postprocess(content, loader) : content\n return cnfg.postprocess!(result as unknown as T, loader)\n }\n }\n }\n}\n\nexport interface ISusanoLoader<T, R = T> {\n load: () => Promise<R>\n}\n","import { ISusanoLoader, SusanoLoaderConfig, SusanoLoader } from './loader'\n\nexport type SusanoImageLoaderConfig<R = HTMLImageElement> = SusanoLoaderConfig<HTMLImageElement, R> & {\n srcSet?: string\n sizes?: string\n}\n\nexport class ImageLoader<R = HTMLImageElement> extends SusanoLoader<HTMLImageElement, R> implements ISusanoLoader<HTMLImageElement, R> {\n public static type = 'image' as const\n public srcSet?: string\n public sizes?: string\n\n constructor(url: string, cnfg: SusanoImageLoaderConfig<R> = {}) {\n super(url, cnfg)\n this.content = new Image()\n this.srcSet = cnfg.srcSet\n this.sizes = cnfg.sizes\n }\n\n load(): Promise<R> {\n if (this.loaded) { this._onLoaded(); return this.promise }\n\n if (this.srcSet) this.content.srcset = this.srcSet\n if (this.sizes) this.content.sizes = this.sizes\n this.content.src = this.url\n\n this.content.onload = () => this._onLoaded()\n this.content.onerror = (e) => this._onError(e)\n\n return this.promise\n }\n}\n","import { ISusanoLoader, SusanoLoaderConfig, SusanoLoader } from './loader'\n\ntype LoadEvent = 'canplay' | 'canplaythrough'\n\nexport type SusanoVideoLoaderConfig<R = HTMLVideoElement> = SusanoLoaderConfig<HTMLVideoElement, R> & {\n video?: HTMLVideoElement\n loadEvent?: LoadEvent\n}\n\nexport class VideoLoader<R = HTMLVideoElement> extends SusanoLoader<HTMLVideoElement, R> implements ISusanoLoader<HTMLVideoElement, R> {\n public static type = 'video' as const\n public loadEvent: LoadEvent\n\n constructor(url: string, cnfg: SusanoVideoLoaderConfig<R> = {}) {\n super(url, cnfg)\n this.content = cnfg.video || document.createElement('video')\n this.loadEvent = cnfg.loadEvent || 'canplay'\n }\n\n load(): Promise<R> {\n if (this.loaded) { this._onLoaded(); return this.promise }\n\n const el = this.content\n el.src = this.url\n el.addEventListener(this.loadEvent, () => this._onLoaded(), { once: true })\n el.addEventListener('error', (e) => this._onError(e), { once: true })\n\n return this.promise\n }\n}\n","import { ISusanoLoader, SusanoLoaderConfig, SusanoLoader } from './loader'\n\ntype LoadEvent = 'canplay' | 'canplaythrough'\n\nexport type SusanoAudioLoaderConfig<R = HTMLAudioElement> = SusanoLoaderConfig<HTMLAudioElement, R> & {\n audio?: HTMLAudioElement\n loadEvent?: LoadEvent\n}\n\nexport class AudioLoader<R = HTMLAudioElement> extends SusanoLoader<HTMLAudioElement, R> implements ISusanoLoader<HTMLAudioElement, R> {\n public static type = 'audio' as const\n public loadEvent: LoadEvent\n\n constructor(url: string, cnfg: SusanoAudioLoaderConfig<R> = {}) {\n super(url, cnfg)\n this.content = cnfg.audio || document.createElement('audio')\n this.loadEvent = cnfg.loadEvent || 'canplay'\n }\n\n load(): Promise<R> {\n if (this.loaded) { this._onLoaded(); return this.promise }\n\n const el = this.content\n el.src = this.url\n el.addEventListener(this.loadEvent, () => this._onLoaded(), { once: true })\n el.addEventListener('error', (e) => this._onError(e), { once: true })\n\n return this.promise\n }\n}\n","import { SusanoLoader, SusanoLoaderConfig } from './loader'\n\nexport type GenericLoadFn = (config: {\n url: string\n done: (generic: any) => void\n error: (error: Error) => void\n progress: (progress: number) => void\n}) => void\n\nexport type SusanoGenericLoaderConfig<R = any> = SusanoLoaderConfig<any, R> & {\n loadFn: GenericLoadFn\n}\n\nexport class GenericLoader<R = any> extends SusanoLoader<any, R> {\n public static type = 'generic' as const\n public loadFn: GenericLoadFn\n\n constructor(url: string, cnfg: SusanoGenericLoaderConfig<R>) {\n super(url, cnfg)\n this.loadFn = cnfg.loadFn\n }\n\n load(): Promise<R> {\n if (!this.loadFn) { this._onError(new Error('No load function provided')); return this.promise }\n if (this.loaded) { this._onLoaded(); return this.promise }\n\n this.loadFn({\n url: this.url,\n done: (generic: any) => { this.content = generic; this._onLoaded() },\n error: (err) => this._onError(err),\n /* 0 to 1 */\n progress: (value: number) => this._onProgress(value),\n })\n\n return this.promise\n }\n}\n","import { TinyEmitter } from 'tiny-emitter'\nimport { SusanoLoader } from './loaders/loader'\nimport { ImageLoader } from './loaders/image'\nimport { VideoLoader } from './loaders/video'\nimport { AudioLoader } from './loaders/audio'\nimport { GenericLoader } from './loaders/generic'\n\nexport type LoaderTypes = {\n [K: string]: {\n type: typeof K\n loader: typeof SusanoLoader<any>\n }\n}\n\nexport type LoaderMap<T extends LoaderTypes> = {\n [K in keyof T]: T[K]['loader']\n}\n\nexport type ProgressEventArgs = {\n value: number\n item: SusanoLoader<unknown, unknown>\n susano: Susano<any>\n}\n\n/**\n * Main loader class that manages loading of different asset types\n * @template T Type definition for the loaders to be used, extending LoaderTypes\n * @extends TinyEmitter\n */\nexport class Susano<T extends LoaderTypes> extends TinyEmitter {\n private loaders: LoaderMap<T> = {} as LoaderMap<T>\n public queue: InstanceType<T[keyof T]['loader']>[] = []\n public active: InstanceType<T[keyof T]['loader']>[] = []\n public items: Map<string, InstanceType<T[keyof T]['loader']>> = new Map()\n public loadCount: number = 0\n public loadLength: number = 0\n\n constructor() {\n super()\n }\n\n add<K extends keyof T>(\n url: string,\n cnfg: {\n type: K\n loaderArgs?: ConstructorParameters<T[K]['loader']>[1]\n }\n ): InstanceType<T[K]['loader']> {\n const Loader = this.loaders[cnfg.type]\n\n if (!Loader) throw new Error(`Loader for type ${String(cnfg.type)} not found`)\n\n let item = this.items.get(url)\n\n if (!item) {\n item = new Loader(url, cnfg.loaderArgs) as InstanceType<T[K]['loader']>\n this.items.set(url, item)\n } else if (cnfg.loaderArgs) {\n item._appendConfig(cnfg.loaderArgs)\n }\n\n this.queue.push(item)\n\n return item\n }\n\n load<K extends keyof T>(\n url: string,\n cnfg: {\n type: K\n loaderArgs?: ConstructorParameters<T[K]['loader']>[1]\n }\n ) {\n const Loader = this.loaders[cnfg.type]\n if (!Loader) throw new Error(`Loader for type ${String(cnfg.type)} not found`)\n\n let item = this.items.get(url)\n\n if (!item) {\n item = new Loader(url, cnfg.loaderArgs) as InstanceType<T[K]['loader']>\n this.items.set(url, item)\n } else if (cnfg.loaderArgs) {\n item._appendConfig(cnfg.loaderArgs)\n }\n\n item.load()\n\n return item\n }\n\n registerLoader<K extends keyof T>(type: K, loader: T[K]['loader']) {\n this.loaders[type] = loader\n }\n\n _onProgress(item: SusanoLoader<unknown, unknown>) {\n this.loadCount++\n\n // const idx = this.active.indexOf(item);\n // this.active.splice(idx, 1);\n\n const progress = this.loadCount / this.loadLength\n\n this.emit('progress', {\n value: progress,\n item: item,\n susano: this,\n } satisfies ProgressEventArgs)\n\n if (progress === 1) {\n this.emit('completed', this)\n return\n }\n }\n\n start({\n onCompleted,\n onProgress,\n }: {\n onCompleted?: (susano: Susano<T>) => void\n onProgress?: (progress: { value: number; item: SusanoLoader<unknown, unknown>; susano: Susano<T> }) => void\n } = {}) {\n this.active = this.queue.splice(0, this.queue.length)\n this.loadLength = this.active.length\n this.loadCount = 0\n\n this.emit('start', this)\n\n if (onProgress) this.on('progress', onProgress)\n\n this.once('completed', () => {\n onCompleted?.(this)\n if (onProgress) this.off('progress', onProgress)\n })\n\n this.active.map((item) => {\n item.once('loaded', () => {\n this._onProgress(item)\n })\n\n item.load()\n })\n }\n}\n\nexport const susano = new Susano<{\n image: {\n type: 'image'\n loader: typeof ImageLoader\n }\n video: {\n type: 'video'\n loader: typeof VideoLoader\n }\n audio: {\n type: 'audio'\n loader: typeof AudioLoader\n }\n generic: {\n type: 'generic'\n loader: typeof GenericLoader\n }\n}>()\n\nsusano.registerLoader(ImageLoader.type, ImageLoader)\nsusano.registerLoader(VideoLoader.type, VideoLoader)\nsusano.registerLoader(AudioLoader.type, AudioLoader)\nsusano.registerLoader(GenericLoader.type, GenericLoader)\n\nexport * from './loaders/loader'\nexport * from './loaders/image'\nexport * from './loaders/video'\nexport * from './loaders/audio'\nexport * from './loaders/generic'\n","import { version } from '../../package.json'\n\nexport const VERSION = version\nexport * from './core'\n"]}
|
|
1
|
+
{"version":3,"sources":["../package.json","../packages/core/loaders/loader.ts","../packages/core/loaders/image.ts","../packages/core/loaders/video.ts","../packages/core/loaders/audio.ts","../packages/core/loaders/generic.ts","../packages/core/batch.ts","../packages/core/core.tsx","../packages/core/index.ts"],"names":["TinyEmitter"],"mappings":"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","file":"index.js","sourcesContent":["{\n \"name\": \"@joycostudio/susano\",\n \"publishConfig\": {\n \"registry\": \"https://registry.npmjs.org\",\n \"access\": \"public\"\n },\n \"version\": \"1.0.0\",\n \"description\": \"Asset load orchestration made easy\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.mjs\",\n \"types\": \"dist/index.d.ts\",\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"concurrently \\\"tsup --watch\\\" \\\"cd templates/basic && pnpm dev\\\"\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"typecheck\": \"tsc --noEmit\",\n \"version:package\": \"pnpm changeset version\",\n \"release\": \"pnpm build && pnpm changeset publish\",\n \"lint\": \"eslint -c ./eslint.config.mjs . --fix --no-cache\"\n },\n \"packageManager\": \"pnpm@10.29.2\",\n \"author\": \"@joycostudio\",\n \"license\": \"ISC\",\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.27.11\",\n \"@eslint/js\": \"^9.18.0\",\n \"@types/node\": \"^20.11.24\",\n \"@types/react\": \"^18.2.61\",\n \"@typescript-eslint/eslint-plugin\": \"^8.21.0\",\n \"@typescript-eslint/parser\": \"^8.21.0\",\n \"concurrently\": \"^9.1.2\",\n \"eslint\": \"^9.18.0\",\n \"eslint-config-prettier\": \"^10.0.1\",\n \"eslint-plugin-prettier\": \"^5.2.3\",\n \"eslint-plugin-react\": \"^7.37.4\",\n \"eslint-plugin-react-compiler\": \"19.0.0-beta-decd7b8-20250118\",\n \"globals\": \"^15.14.0\",\n \"prettier\": \"^3.4.2\",\n \"react\": \"^18.2.0\",\n \"tsup\": \"^8.0.2\",\n \"typescript\": \"^5.7.3\",\n \"typescript-eslint\": \"^8.21.0\",\n \"vitest\": \"^3.0.5\",\n \"jsdom\": \"^26.0.0\"\n },\n \"peerDependencies\": {\n \"react\": \">=16.8.0\"\n },\n \"dependencies\": {\n \"tiny-emitter\": \"^2.1.0\"\n }\n}","import { TinyEmitter } from 'tiny-emitter'\n\nexport type LoaderStatus = 'idle' | 'loading' | 'loaded' | 'error'\n\n/**\n * Base loader. Owns and caches a single piece of raw **content** per URL (the expensive fetch),\n * deduped and idempotent. It does NOT run `postprocess` — that is a per-call projection applied\n * by `Susano` (`_runLoad`). If you want to cache a transformed result, encode the transform in a\n * loader subclass so its output becomes the cached content.\n *\n * Subclasses add their own construction args as a second constructor parameter (see `LoaderRegistry`).\n */\nexport class SusanoLoader<T> extends TinyEmitter {\n public url: string\n public loaded: boolean\n public status: LoaderStatus\n public content: T\n public progress: number\n public promise!: Promise<T>\n private _resolve!: (value: T) => void\n private _reject!: (error: unknown) => void\n\n constructor(url: string) {\n super()\n this.url = url\n this.loaded = false\n this.status = 'idle'\n this.content = null as unknown as T\n this.progress = 0\n\n this._resetPromise()\n }\n\n /** `true` while a content load is in flight (`status === 'loading'`). */\n get loading(): boolean {\n return this.status === 'loading'\n }\n\n /**\n * Load (or join) the content fetch. Idempotent: repeat calls return the same promise without\n * re-fetching.\n *\n * An in-flight fetch can never be preempted: while `status === 'loading'`, every call joins the\n * same promise regardless of `cache`. This keeps the invariant that at most one fetch runs per\n * URL, so resetting the promise can never orphan a consumer already awaiting it. `cache: false`\n * therefore acts on a *completed* load — it busts the cached content and forces a fresh fetch —\n * not on concurrent work. Resolves with the raw content `T`; postprocessing happens per call in\n * `Susano`.\n */\n load(cache = true): Promise<T> {\n // In-flight: always join. A re-fetch cannot preempt a fetch with consumers attached.\n if (this.status === 'loading') return this.promise\n // Completed: reuse the cached content unless the caller opts out with `cache: false`.\n if (cache && this.status === 'loaded') return this.promise\n\n // Retry/refetch from a settled state: swap in a fresh promise for the new attempt. The old\n // one has already settled, so no consumer is left behind.\n if (this.status !== 'idle') this._resetPromise()\n this.status = 'loading'\n this.loaded = false\n this.progress = 0\n\n this._load()\n\n return this.promise\n }\n\n /** Subclasses implement the raw fetch here, calling `_onLoaded` / `_onError` / `_onProgress`. */\n protected _load(): void {\n throw new Error('Method not implemented')\n }\n\n _onLoaded() {\n this.loaded = true\n this.status = 'loaded'\n this.progress = 1\n this._resolve(this.content)\n this.emit('loaded', this)\n }\n\n _onError(error: unknown) {\n this.status = 'error'\n this._reject(error)\n this.progress = 0\n // Keep the rejected promise as `this.promise`: it is the loader's truthful settled state, so\n // a consumer reading `.promise` after an error observes the rejection instead of a fresh\n // pending promise that nothing will ever settle. The next `load()` swaps in a new one.\n this.emit('error', error)\n }\n\n private _resetPromise() {\n this.promise = new Promise<T>((resolve, reject) => {\n this._resolve = resolve\n this._reject = reject\n })\n }\n\n _onProgress(value: number) {\n this.progress = value\n this.emit('progress', this)\n }\n}\n","import { SusanoLoader } from './loader'\n\nexport type SusanoImageLoaderConfig = {\n srcSet?: string\n sizes?: string\n}\n\nexport class ImageLoader extends SusanoLoader<HTMLImageElement> {\n public static type = 'image' as const\n public srcSet?: string\n public sizes?: string\n\n constructor(url: string, cnfg: SusanoImageLoaderConfig = {}) {\n super(url)\n this.content = new Image()\n this.srcSet = cnfg.srcSet\n this.sizes = cnfg.sizes\n }\n\n protected _load(): void {\n if (this.srcSet) this.content.srcset = this.srcSet\n if (this.sizes) this.content.sizes = this.sizes\n this.content.src = this.url\n\n this.content.onload = () => this._onLoaded()\n this.content.onerror = (e) => this._onError(e)\n }\n}\n","import { SusanoLoader } from './loader'\n\ntype LoadEvent = 'canplay' | 'canplaythrough'\n\nexport type SusanoVideoLoaderConfig = {\n video?: HTMLVideoElement\n loadEvent?: LoadEvent\n}\n\nexport class VideoLoader extends SusanoLoader<HTMLVideoElement> {\n public static type = 'video' as const\n public loadEvent: LoadEvent\n private _attempt: AbortController | null = null\n\n constructor(url: string, cnfg: SusanoVideoLoaderConfig = {}) {\n super(url)\n this.content = cnfg.video || document.createElement('video')\n this.loadEvent = cnfg.loadEvent || 'canplay'\n }\n\n protected _load(): void {\n const el = this.content\n\n // One controller per attempt: starting a new attempt drops any listeners left over from a\n // previous one (an error would otherwise leave the loadEvent listener attached, and a retry\n // would stack a second — firing _onLoaded twice). Settling also aborts, so an attempt\n // resolves exactly once and leaves no listener behind in either direction.\n this._attempt?.abort()\n const attempt = new AbortController()\n this._attempt = attempt\n const { signal } = attempt\n\n el.src = this.url\n el.addEventListener(\n this.loadEvent,\n () => {\n attempt.abort()\n this._onLoaded()\n },\n { signal }\n )\n el.addEventListener(\n 'error',\n (e) => {\n attempt.abort()\n this._onError(e)\n },\n { signal }\n )\n }\n}\n","import { SusanoLoader } from './loader'\n\ntype LoadEvent = 'canplay' | 'canplaythrough'\n\nexport type SusanoAudioLoaderConfig = {\n audio?: HTMLAudioElement\n loadEvent?: LoadEvent\n}\n\nexport class AudioLoader extends SusanoLoader<HTMLAudioElement> {\n public static type = 'audio' as const\n public loadEvent: LoadEvent\n private _attempt: AbortController | null = null\n\n constructor(url: string, cnfg: SusanoAudioLoaderConfig = {}) {\n super(url)\n this.content = cnfg.audio || document.createElement('audio')\n this.loadEvent = cnfg.loadEvent || 'canplay'\n }\n\n protected _load(): void {\n const el = this.content\n\n // One controller per attempt: starting a new attempt drops any listeners left over from a\n // previous one (an error would otherwise leave the loadEvent listener attached, and a retry\n // would stack a second — firing _onLoaded twice). Settling also aborts, so an attempt\n // resolves exactly once and leaves no listener behind in either direction.\n this._attempt?.abort()\n const attempt = new AbortController()\n this._attempt = attempt\n const { signal } = attempt\n\n el.src = this.url\n el.addEventListener(\n this.loadEvent,\n () => {\n attempt.abort()\n this._onLoaded()\n },\n { signal }\n )\n el.addEventListener(\n 'error',\n (e) => {\n attempt.abort()\n this._onError(e)\n },\n { signal }\n )\n }\n}\n","import { SusanoLoader } from './loader'\n\nexport type GenericLoadFn = (config: {\n url: string\n done: (generic: any) => void\n error: (error: Error) => void\n progress: (progress: number) => void\n}) => void\n\nexport type SusanoGenericLoaderConfig = {\n loadFn: GenericLoadFn\n}\n\nexport class GenericLoader extends SusanoLoader<any> {\n public static type = 'generic' as const\n public loadFn: GenericLoadFn\n\n constructor(url: string, cnfg: SusanoGenericLoaderConfig) {\n super(url)\n this.loadFn = cnfg.loadFn\n }\n\n protected _load(): void {\n if (!this.loadFn) {\n this._onError(new Error('No load function provided'))\n return\n }\n\n this.loadFn({\n url: this.url,\n done: (generic: any) => {\n this.content = generic\n this._onLoaded()\n },\n error: (err) => this._onError(err),\n /* 0 to 1 */\n progress: (value: number) => this._onProgress(value),\n })\n }\n}\n","import { TinyEmitter } from 'tiny-emitter'\nimport { SusanoLoader } from './loaders/loader'\n\n/** One tracked load within a batch: a content loader and its per-call result promise. */\nexport type BatchItem = {\n loader: SusanoLoader<unknown>\n promise: Promise<unknown>\n}\n\nexport type BatchProgressEventArgs = {\n value: number\n /** The loader that just settled, or `null` for a batch-level tick (e.g. an empty batch). */\n loader: SusanoLoader<unknown> | null\n batch: Batch\n}\n\nexport type BatchErrorEventArgs = {\n loader: SusanoLoader<unknown>\n error: unknown\n batch: Batch\n}\n\nexport type BatchHandlers = {\n onProgress?: (progress: BatchProgressEventArgs) => void\n onCompleted?: (batch: Batch) => void\n onError?: (error: BatchErrorEventArgs) => void\n}\n\n/**\n * Tracks progress and completion for a fixed set of loads, independent of other loading activity.\n * Each batch owns its own counter and completion promise, so concurrent batches (and lazy\n * `susano.load()` calls) never interfere.\n *\n * Completion is the resolution of `Promise.all` over the set — one-shot by construction. A failed\n * item is fail-soft: it surfaces via `onError` / the `'error'` event and still advances the batch.\n */\nexport class Batch extends TinyEmitter {\n public readonly items: BatchItem[]\n public readonly loadLength: number\n public loadCount = 0\n public progress = 0\n public completed = false\n public readonly promise: Promise<Batch>\n private _counted = new Set<BatchItem>()\n private _resolve!: (batch: Batch) => void\n\n constructor(items: BatchItem[], handlers: BatchHandlers = {}) {\n super()\n this.items = items\n this.loadLength = items.length\n this.promise = new Promise<Batch>((resolve) => {\n this._resolve = resolve\n })\n\n if (handlers.onProgress) this.on('progress', handlers.onProgress)\n if (handlers.onCompleted) this.once('completed', handlers.onCompleted)\n if (handlers.onError) this.on('error', handlers.onError)\n\n // Defer tracking one microtask so listeners attached right after construction (via `.on()`)\n // are registered before the first tick. Handlers passed above are already wired.\n Promise.resolve().then(() => this._start())\n }\n\n private _start() {\n if (this.loadLength === 0) {\n this.progress = 1\n this.emit('progress', { value: 1, loader: null, batch: this } satisfies BatchProgressEventArgs)\n this._finish()\n return\n }\n\n const settled = this.items.map((item) =>\n item.promise\n .then(() => this._tick(item))\n .catch((error) => {\n this.emit('error', { loader: item.loader, error, batch: this } satisfies BatchErrorEventArgs)\n this._tick(item)\n })\n )\n\n Promise.all(settled).then(() => this._finish())\n }\n\n /** Count an item at most once so the progress fraction can't be inflated by repeat settles. */\n private _tick(item: BatchItem) {\n if (this._counted.has(item)) return\n this._counted.add(item)\n this.loadCount = this._counted.size\n this.progress = this.loadCount / this.loadLength\n this.emit('progress', {\n value: this.progress,\n loader: item.loader,\n batch: this,\n } satisfies BatchProgressEventArgs)\n }\n\n private _finish() {\n this.completed = true\n this.progress = 1\n this._resolve(this)\n this.emit('completed', this)\n }\n}\n","import { SusanoLoader } from './loaders/loader'\nimport { ImageLoader } from './loaders/image'\nimport { VideoLoader } from './loaders/video'\nimport { AudioLoader } from './loaders/audio'\nimport { GenericLoader } from './loaders/generic'\nimport { Batch, BatchItem, BatchHandlers } from './batch'\n\n/**\n * Per-call options. `postprocess` is the projection from cached content `T` to this call's\n * result `R` — it runs **every call**, never cached. `onProgress` observes the shared content\n * fetch; `onLoaded` / `onError` fire for this call's result.\n */\nexport type LoadOptions<T, R> = {\n postprocess?: (content: T, loader: SusanoLoader<T>) => R | Promise<R>\n onLoaded?: (result: R, loader: SusanoLoader<T>) => void\n onProgress?: (value: number, loader: SusanoLoader<T>) => void\n onError?: (error: unknown, loader: SusanoLoader<T>) => void\n}\n\n/**\n * The common shape of any loader class: constructs a `SusanoLoader` from a `url` plus that\n * loader's own construction args. The two `any`s are deliberate and contained to this bound:\n * - `args` — each loader narrows it to its own config; the precise type is recovered per call via\n * `ConstructorParameters<R[K]>` in `LoadConfig` / `BatchEntry`.\n * - `SusanoLoader<any>` — `SusanoLoader<T>` is *invariant* in `T` (it holds `(value: T) => void`\n * resolve/reject callbacks), so no single concrete content type bounds every loader. `any` admits\n * them all here; per-call typing stays precise via `ContentOf<R, K>`.\n */\nexport type AnyLoaderConstructor = new (url: string, args?: any) => SusanoLoader<any>\n\n/** The loader registry: a map from `type` key to loader class. The single source of truth. */\nexport type LoaderRegistry = Record<string, AnyLoaderConstructor>\n\n/** The content type produced by the loader registered under `K`. */\nexport type ContentOf<R extends LoaderRegistry, K extends keyof R> =\n InstanceType<R[K]> extends SusanoLoader<infer C> ? C : never\n\n/** Construction args + cache flag for a load. */\nexport type LoadConfig<R extends LoaderRegistry, K extends keyof R> = {\n type: K\n loaderArgs?: ConstructorParameters<R[K]>[1]\n /** Opt out of dedup; force a fresh content fetch that overwrites the cached content. Defaults to `true`. */\n cache?: boolean\n}\n\n/** A single entry in a {@link Susano.batch} call — construction + per-call options, typed per `type`. */\nexport type BatchEntry<R extends LoaderRegistry> = {\n [K in keyof R]: LoadConfig<R, K> & { url: string } & LoadOptions<ContentOf<R, K>, unknown>\n}[keyof R]\n\n/**\n * Registry + per-URL **content** cache. The loader map passed to the constructor is the single\n * source of truth: its keys are the valid `type`s and its values are the loader classes, so\n * `load()` / `batch()` are fully inferred — no separate type map, no drift.\n *\n * Content (the expensive fetch) is deduped per URL; a `postprocess` is a per-call projection\n * (see `_runLoad`), so two call sites can derive different results from one shared fetch.\n *\n * @template R The loader registry — `{ [type]: LoaderClass }`.\n */\nexport class Susano<R extends LoaderRegistry> {\n private loaders: R\n public items: Map<string, SusanoLoader<unknown>> = new Map()\n\n constructor(loaders: R) {\n this.loaders = loaders\n }\n\n /** The shared content loader cached for `url`, if one exists. Shorthand for `items.get(url)`. */\n get(url: string): SusanoLoader<unknown> | undefined {\n return this.items.get(url)\n }\n\n /**\n * Get-or-create the content loader for `url`, typed to the loader registered under `type`.\n * The one unavoidable cast: `items` is keyed by URL (a string), which can't carry the content\n * type, so a cache hit is asserted back to the loader's known type.\n */\n private _resolve<K extends keyof R>(\n url: string,\n type: K,\n loaderArgs: ConstructorParameters<R[K]>[1]\n ): InstanceType<R[K]> {\n const cached = this.items.get(url)\n if (cached) return cached as InstanceType<R[K]>\n\n const Loader = this.loaders[type]\n if (!Loader) throw new Error(`No loader registered for type \"${String(type)}\"`)\n\n const loader = new Loader(url, loaderArgs) as InstanceType<R[K]>\n this.items.set(url, loader)\n return loader\n }\n\n /**\n * Load one asset immediately and return a promise of *this call's result* (content after this\n * call's `postprocess`). The content fetch is deduped per URL. Reach the shared content loader —\n * for raw content or events — via `susano.items.get(url)`.\n */\n load<K extends keyof R, Result = ContentOf<R, K>>(\n url: string,\n cnfg: LoadConfig<R, K> & LoadOptions<ContentOf<R, K>, Result>\n ): Promise<Result> {\n const loader = this._resolve(url, cnfg.type, cnfg.loaderArgs)\n return this._runLoad<ContentOf<R, K>, Result>(\n loader,\n { postprocess: cnfg.postprocess, onLoaded: cnfg.onLoaded, onProgress: cnfg.onProgress, onError: cnfg.onError },\n cnfg.cache ?? true\n )\n }\n\n /**\n * Run a single load against a shared content loader: ensure content (deduped), then apply this\n * call's `postprocess` to produce the result. Two calls for the same URL share one content fetch\n * but each get their own result promise. Stays a function of (loader, options) — it never touches\n * the registry.\n */\n private _runLoad<T, Result>(\n loader: SusanoLoader<T>,\n options: LoadOptions<T, Result>,\n cache: boolean\n ): Promise<Result> {\n const { postprocess, onLoaded, onProgress, onError } = options\n\n let progressHandler: ((loader: SusanoLoader<T>) => void) | undefined\n if (onProgress) {\n progressHandler = (l) => onProgress(l.progress, l)\n loader.on('progress', progressHandler)\n }\n const detach = () => {\n if (progressHandler) {\n loader.off('progress', progressHandler)\n progressHandler = undefined\n }\n }\n\n return loader\n .load(cache)\n .then(async (content) => {\n const result = postprocess ? await postprocess(content, loader) : (content as unknown as Result)\n detach()\n onLoaded?.(result, loader)\n return result\n })\n .catch((error) => {\n detach()\n onError?.(error, loader)\n throw error\n })\n }\n\n /**\n * Load a fixed set and track *only* that set's progress/completion, independent of other\n * loading activity. Content fetches are deduped across entries; each entry's `postprocess` runs\n * per call. Auto-starts; returns a {@link Batch} exposing `.promise`, `.progress`, `.completed`.\n */\n batch(entries: BatchEntry<R>[], handlers: BatchHandlers = {}): Batch {\n const items: BatchItem[] = entries.map((entry) => {\n const loader = this._resolve(entry.url, entry.type, entry.loaderArgs)\n const options = {\n postprocess: entry.postprocess,\n onLoaded: entry.onLoaded,\n onProgress: entry.onProgress,\n onError: entry.onError,\n } as unknown as LoadOptions<unknown, unknown>\n return { loader, promise: this._runLoad(loader, options, entry.cache ?? true) }\n })\n return new Batch(items, handlers)\n }\n}\n\nexport const susano = new Susano({\n image: ImageLoader,\n video: VideoLoader,\n audio: AudioLoader,\n generic: GenericLoader,\n})\n\nexport * from './batch'\nexport * from './loaders/loader'\nexport * from './loaders/image'\nexport * from './loaders/video'\nexport * from './loaders/audio'\nexport * from './loaders/generic'\n","import { version } from '../../package.json'\n\nexport const VERSION = version\nexport * from './core'\n"]}
|