webspresso 0.0.109 → 0.0.111
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/core/client/adapter/datastar.js +248 -51
- package/core/client/api.js +328 -22
- package/core/client/controller.js +921 -38
- package/core/client/debug.js +12 -12
- package/core/client/devtool.js +1239 -0
- package/core/client/forms.js +184 -21
- package/core/client/fragments.js +566 -72
- package/core/client/host.js +365 -0
- package/core/client/index.d.ts +37 -0
- package/core/client/index.js +190 -15
- package/core/client/lite.js +112 -4
- package/core/client/server-fragments.js +111 -24
- package/core/client/services.js +588 -0
- package/core/client/storage.js +339 -0
- package/core/client/utils.js +324 -1
- package/core/index.js +4 -0
- package/index.d.ts +523 -39
- package/index.js +7 -0
- package/package.json +7 -8
- package/src/bundle/index.js +175 -14
- package/src/client-runtime/bootstrap-swup.js +9 -0
- package/src/client-runtime/mount.js +6 -18
- package/src/client-runtime/resolve.js +3 -2
- package/src/dev/banner.js +4 -1
- package/src/dev-reload/index.js +29 -1
- package/src/file-router.js +12 -8
- package/src/routing/mount-discovered-routes.js +9 -2
- package/src/server.js +42 -24
- package/views/partials/webspresso-client-runtime.njk +1 -9
- package/src/client-runtime/bootstrap-alpine-swup.js +0 -34
package/core/client/fragments.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { defaultStorage } = require('./storage');
|
|
4
|
+
const { isArray, isNumber, parseDurationMs, parseRefetchMode, uid } = require('./utils');
|
|
5
|
+
|
|
3
6
|
/**
|
|
4
7
|
* Webspresso Client-Side SSR Fragment Coordinator & Invalidation Engine
|
|
5
8
|
*
|
|
@@ -31,7 +34,7 @@ function parseDependsOnAttr(raw) {
|
|
|
31
34
|
if (trimmed.startsWith('[')) {
|
|
32
35
|
try {
|
|
33
36
|
const parsed = JSON.parse(trimmed);
|
|
34
|
-
if (
|
|
37
|
+
if (isArray(parsed)) {
|
|
35
38
|
return parsed.map((s) => String(s).trim()).filter(Boolean);
|
|
36
39
|
}
|
|
37
40
|
} catch (_e) {
|
|
@@ -56,7 +59,7 @@ function parseFragmentInputAttr(raw) {
|
|
|
56
59
|
if (!trimmed) return {};
|
|
57
60
|
try {
|
|
58
61
|
const parsed = JSON.parse(trimmed);
|
|
59
|
-
return parsed && typeof parsed === 'object' && !
|
|
62
|
+
return parsed && typeof parsed === 'object' && !isArray(parsed) ? parsed : {};
|
|
60
63
|
} catch (_e) {
|
|
61
64
|
return {};
|
|
62
65
|
}
|
|
@@ -106,8 +109,24 @@ function createFragmentCoordinator(options = {}) {
|
|
|
106
109
|
const endpoint = options.endpoint || DEFAULT_FRAGMENT_ENDPOINT;
|
|
107
110
|
const datastar = options.datastar || options.adapter;
|
|
108
111
|
const debug = options.debug || null;
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
const defaultTimeout = isNumber(options.timeout) ? options.timeout : 15000;
|
|
113
|
+
|
|
114
|
+
// React Query-style automatic revalidation defaults
|
|
115
|
+
const defaultStaleTime = parseDurationMs(options.staleTime, 0);
|
|
116
|
+
const defaultRefetchOnWindowFocus = parseRefetchMode(
|
|
117
|
+
options.refetchOnWindowFocus !== undefined ? options.refetchOnWindowFocus : options.refetchOnFocus,
|
|
118
|
+
false
|
|
119
|
+
);
|
|
120
|
+
const defaultRefetchOnReconnect = parseRefetchMode(options.refetchOnReconnect, false);
|
|
121
|
+
const defaultRefetchOnMount = parseRefetchMode(
|
|
122
|
+
options.refetchOnMount !== undefined ? options.refetchOnMount : options.refetchOnVisit,
|
|
123
|
+
false
|
|
124
|
+
);
|
|
125
|
+
const defaultRefetchInterval = parseDurationMs(options.refetchInterval, 0);
|
|
126
|
+
const defaultRefetchIntervalInBackground =
|
|
127
|
+
parseRefetchMode(options.refetchIntervalInBackground, false) === true;
|
|
128
|
+
|
|
129
|
+
// Map<instanceId, { instanceId, name, pagePath, input, dependsOn: Set<string>, version: number, el: Element, abortController: AbortController|null, ... }>
|
|
111
130
|
const instances = new Map();
|
|
112
131
|
|
|
113
132
|
// Coalescing & in-flight state for tag invalidations
|
|
@@ -119,67 +138,378 @@ function createFragmentCoordinator(options = {}) {
|
|
|
119
138
|
let lastFailedBatch = null;
|
|
120
139
|
const inflightBatchWaiters = [];
|
|
121
140
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
141
|
+
// Automatic refetch (focus / reconnect / mount / interval) state
|
|
142
|
+
const pendingAutoInstanceIds = new Set();
|
|
143
|
+
let autoBatchScheduled = false;
|
|
144
|
+
let focusDebounceTimer = null;
|
|
145
|
+
let isDisposed = false;
|
|
146
|
+
let lifecycleListenersAttached = false;
|
|
147
|
+
|
|
148
|
+
function isDocumentHidden() {
|
|
149
|
+
const root = getRoot();
|
|
150
|
+
const doc = root && root.nodeType === 9 ? root : root && root.ownerDocument ? root.ownerDocument : typeof document !== 'undefined' ? document : null;
|
|
151
|
+
return Boolean(doc && doc.visibilityState === 'hidden');
|
|
152
|
+
}
|
|
128
153
|
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
|
|
154
|
+
function isInstanceStale(inst, mode) {
|
|
155
|
+
if (!inst || !mode) return false;
|
|
156
|
+
if (mode === 'always') return true;
|
|
157
|
+
const staleMs = isNumber(inst.staleTime) ? inst.staleTime : 0;
|
|
158
|
+
if (staleMs <= 0) return true;
|
|
159
|
+
const elapsed = Date.now() - (inst.lastUpdatedAt || 0);
|
|
160
|
+
return elapsed >= staleMs;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function clearInstanceInterval(inst) {
|
|
164
|
+
if (inst && inst.intervalTimer) {
|
|
165
|
+
clearInterval(inst.intervalTimer);
|
|
166
|
+
inst.intervalTimer = null;
|
|
167
|
+
inst.activeIntervalMs = 0;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function syncInstanceInterval(inst) {
|
|
172
|
+
if (!inst || isDisposed) return;
|
|
173
|
+
const targetMs = isNumber(inst.refetchInterval) && inst.refetchInterval > 0 ? inst.refetchInterval : 0;
|
|
174
|
+
if (targetMs <= 0) {
|
|
175
|
+
clearInstanceInterval(inst);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (inst.intervalTimer && inst.activeIntervalMs === targetMs) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
clearInstanceInterval(inst);
|
|
182
|
+
inst.activeIntervalMs = targetMs;
|
|
183
|
+
inst.intervalTimer = setInterval(() => {
|
|
184
|
+
if (isDisposed || !inst.el || inst.el.isConnected === false) {
|
|
185
|
+
clearInstanceInterval(inst);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (!inst.refetchIntervalInBackground && isDocumentHidden()) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
scheduleAutoRefresh([inst.instanceId], 'interval');
|
|
192
|
+
}, targetMs);
|
|
193
|
+
if (inst.intervalTimer && typeof inst.intervalTimer.unref === 'function') {
|
|
194
|
+
inst.intervalTimer.unref();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function scheduleAutoRefresh(instanceIds, reason = 'auto') {
|
|
199
|
+
if (isDisposed || !isArray(instanceIds) || instanceIds.length === 0) return;
|
|
200
|
+
for (const id of instanceIds) {
|
|
201
|
+
if (id) pendingAutoInstanceIds.add(id);
|
|
132
202
|
}
|
|
203
|
+
if (autoBatchScheduled || pendingAutoInstanceIds.size === 0) return;
|
|
204
|
+
autoBatchScheduled = true;
|
|
133
205
|
|
|
134
|
-
|
|
206
|
+
Promise.resolve().then(async () => {
|
|
207
|
+
autoBatchScheduled = false;
|
|
208
|
+
if (isDisposed || pendingAutoInstanceIds.size === 0) return;
|
|
209
|
+
const idsToRefresh = Array.from(pendingAutoInstanceIds);
|
|
210
|
+
pendingAutoInstanceIds.clear();
|
|
211
|
+
|
|
212
|
+
// Skip background auto-refresh if a mutation invalidation batch is currently in flight
|
|
213
|
+
if (inFlightBatchCount > 0) return;
|
|
214
|
+
|
|
215
|
+
const candidates = [];
|
|
216
|
+
for (const id of idsToRefresh) {
|
|
217
|
+
const inst = instances.get(id);
|
|
218
|
+
if (inst && inst.el && inst.el.isConnected !== false && !inst.abortController) {
|
|
219
|
+
candidates.push(inst);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (candidates.length === 0) return;
|
|
135
223
|
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
224
|
+
const deduped = deduplicateParentChildFragments(candidates);
|
|
225
|
+
const targets = deduped.map((inst) => {
|
|
226
|
+
inst.version += 1;
|
|
227
|
+
return {
|
|
228
|
+
instanceId: inst.instanceId,
|
|
229
|
+
name: inst.name,
|
|
230
|
+
pagePath: inst.pagePath,
|
|
231
|
+
input: { ...inst.input },
|
|
232
|
+
reqVersion: inst.version,
|
|
233
|
+
};
|
|
234
|
+
});
|
|
140
235
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
236
|
+
if (debug && typeof debug.recordEvent === 'function') {
|
|
237
|
+
debug.recordEvent('fragment:auto-refetch', {
|
|
238
|
+
reason,
|
|
239
|
+
fragments: targets.map((t) => t.instanceId),
|
|
240
|
+
});
|
|
145
241
|
}
|
|
146
242
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
243
|
+
try {
|
|
244
|
+
await executeBatchRefresh(targets);
|
|
245
|
+
} catch (_err) {
|
|
246
|
+
// Background auto-refetch errors are non-fatal
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
153
250
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
251
|
+
function triggerFocusRefetch() {
|
|
252
|
+
if (isDisposed || isDocumentHidden()) return;
|
|
253
|
+
if (focusDebounceTimer) clearTimeout(focusDebounceTimer);
|
|
254
|
+
focusDebounceTimer = setTimeout(() => {
|
|
255
|
+
focusDebounceTimer = null;
|
|
256
|
+
if (isDisposed || isDocumentHidden()) return;
|
|
257
|
+
scanDomInstances();
|
|
258
|
+
const matchedIds = [];
|
|
259
|
+
for (const inst of instances.values()) {
|
|
260
|
+
if (isInstanceStale(inst, inst.refetchOnWindowFocus)) {
|
|
261
|
+
matchedIds.push(inst.instanceId);
|
|
162
262
|
}
|
|
163
|
-
} else {
|
|
164
|
-
instances.set(instanceId, {
|
|
165
|
-
instanceId,
|
|
166
|
-
name,
|
|
167
|
-
pagePath,
|
|
168
|
-
input,
|
|
169
|
-
dependsOn,
|
|
170
|
-
version: 0,
|
|
171
|
-
el,
|
|
172
|
-
abortController: null,
|
|
173
|
-
});
|
|
174
263
|
}
|
|
264
|
+
if (matchedIds.length > 0) {
|
|
265
|
+
scheduleAutoRefresh(matchedIds, 'window-focus');
|
|
266
|
+
}
|
|
267
|
+
}, 20);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function triggerReconnectRefetch() {
|
|
271
|
+
if (isDisposed) return;
|
|
272
|
+
scanDomInstances();
|
|
273
|
+
const matchedIds = [];
|
|
274
|
+
for (const inst of instances.values()) {
|
|
275
|
+
if (isInstanceStale(inst, inst.refetchOnReconnect)) {
|
|
276
|
+
matchedIds.push(inst.instanceId);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (matchedIds.length > 0) {
|
|
280
|
+
scheduleAutoRefresh(matchedIds, 'reconnect');
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function onVisibilityChange() {
|
|
285
|
+
if (!isDocumentHidden()) {
|
|
286
|
+
triggerFocusRefetch();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function onPageShow(event) {
|
|
291
|
+
if (isDisposed) return;
|
|
292
|
+
if (event && event.persisted) {
|
|
293
|
+
scanDomInstances();
|
|
294
|
+
const matchedIds = [];
|
|
295
|
+
for (const inst of instances.values()) {
|
|
296
|
+
const mode = inst.refetchOnMount || inst.refetchOnWindowFocus;
|
|
297
|
+
if (isInstanceStale(inst, mode)) {
|
|
298
|
+
matchedIds.push(inst.instanceId);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (matchedIds.length > 0) {
|
|
302
|
+
scheduleAutoRefresh(matchedIds, 'pageshow');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function ensureLifecycleListeners() {
|
|
308
|
+
if (lifecycleListenersAttached || isDisposed) return;
|
|
309
|
+
const root = getRoot();
|
|
310
|
+
const doc = root && root.nodeType === 9 ? root : root && root.ownerDocument ? root.ownerDocument : typeof document !== 'undefined' ? document : null;
|
|
311
|
+
const win = (doc && doc.defaultView) || (typeof window !== 'undefined' ? window : null);
|
|
312
|
+
|
|
313
|
+
if (doc && typeof doc.addEventListener === 'function') {
|
|
314
|
+
doc.addEventListener('visibilitychange', onVisibilityChange);
|
|
315
|
+
}
|
|
316
|
+
if (win && typeof win.addEventListener === 'function') {
|
|
317
|
+
win.addEventListener('focus', triggerFocusRefetch);
|
|
318
|
+
win.addEventListener('online', triggerReconnectRefetch);
|
|
319
|
+
win.addEventListener('pageshow', onPageShow);
|
|
320
|
+
}
|
|
321
|
+
lifecycleListenersAttached = true;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function removeLifecycleListeners() {
|
|
325
|
+
if (!lifecycleListenersAttached) return;
|
|
326
|
+
const root = getRoot();
|
|
327
|
+
const doc = root && root.nodeType === 9 ? root : root && root.ownerDocument ? root.ownerDocument : typeof document !== 'undefined' ? document : null;
|
|
328
|
+
const win = (doc && doc.defaultView) || (typeof window !== 'undefined' ? window : null);
|
|
329
|
+
|
|
330
|
+
if (doc && typeof doc.removeEventListener === 'function') {
|
|
331
|
+
doc.removeEventListener('visibilitychange', onVisibilityChange);
|
|
332
|
+
}
|
|
333
|
+
if (win && typeof win.removeEventListener === 'function') {
|
|
334
|
+
win.removeEventListener('focus', triggerFocusRefetch);
|
|
335
|
+
win.removeEventListener('online', triggerReconnectRefetch);
|
|
336
|
+
win.removeEventListener('pageshow', onPageShow);
|
|
337
|
+
}
|
|
338
|
+
lifecycleListenersAttached = false;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function syncSingleElement(el, index, seenIds) {
|
|
342
|
+
const name = (el.getAttribute('data-w-fragment') || '').trim();
|
|
343
|
+
if (!name) return;
|
|
344
|
+
|
|
345
|
+
let instanceId = (el.getAttribute('data-w-fragment-id') || '').trim();
|
|
346
|
+
if (!instanceId) {
|
|
347
|
+
instanceId = index === 0 ? `${name}:default` : `${name}:${index}`;
|
|
348
|
+
el.setAttribute('data-w-fragment-id', instanceId);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (seenIds) seenIds.add(instanceId);
|
|
352
|
+
|
|
353
|
+
const existing = instances.get(instanceId);
|
|
354
|
+
const rawInput = el.getAttribute('data-w-fragment-input');
|
|
355
|
+
const rawDepends = el.getAttribute('data-w-depends-on');
|
|
356
|
+
const rawStale = el.getAttribute('data-w-stale-time');
|
|
357
|
+
const rawInterval = el.getAttribute('data-w-refetch-interval');
|
|
358
|
+
|
|
359
|
+
// Fast-path: if the same DOM element is already registered and its reactive attributes are unchanged,
|
|
360
|
+
// skip JSON.parse, Set allocation, storage lookup, and timer reconfiguration.
|
|
361
|
+
if (
|
|
362
|
+
existing &&
|
|
363
|
+
existing.el === el &&
|
|
364
|
+
existing.name === name &&
|
|
365
|
+
existing._rawInput === rawInput &&
|
|
366
|
+
existing._rawDepends === rawDepends &&
|
|
367
|
+
existing._rawStale === rawStale &&
|
|
368
|
+
existing._rawInterval === rawInterval
|
|
369
|
+
) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const pagePath =
|
|
374
|
+
(el.getAttribute('data-w-fragment-page') || '').trim() ||
|
|
375
|
+
(typeof window !== 'undefined' && window.location ? window.location.pathname : '/');
|
|
376
|
+
const input =
|
|
377
|
+
existing && existing.el === el && existing._rawInput === rawInput
|
|
378
|
+
? existing.input
|
|
379
|
+
: parseFragmentInputAttr(rawInput);
|
|
380
|
+
const dependsOn =
|
|
381
|
+
existing && existing.el === el && existing._rawDepends === rawDepends
|
|
382
|
+
? existing.dependsOn
|
|
383
|
+
: new Set(parseDependsOnAttr(rawDepends));
|
|
384
|
+
|
|
385
|
+
// Fragment input persistence setup
|
|
386
|
+
const rawPersistInput =
|
|
387
|
+
el.getAttribute('data-w-persist-input') ||
|
|
388
|
+
(el.getAttribute('data-w-persist') === 'input' ? 'session' : null);
|
|
389
|
+
const isInputPersistEnabled = Boolean(rawPersistInput && rawPersistInput !== 'false');
|
|
390
|
+
const inputStorageType = rawPersistInput && rawPersistInput.toLowerCase() === 'local' ? 'local' : 'session';
|
|
391
|
+
const inputStorageKey =
|
|
392
|
+
el.getAttribute('data-w-persist-key') ||
|
|
393
|
+
el.getAttribute('data-w-input-key') ||
|
|
394
|
+
`frag_input:${name}`;
|
|
395
|
+
|
|
396
|
+
let effectiveInput = input;
|
|
397
|
+
if (isInputPersistEnabled) {
|
|
398
|
+
const store = inputStorageType === 'local' ? defaultStorage.local : defaultStorage.session;
|
|
399
|
+
if (rawInput === null || Object.keys(effectiveInput).length === 0) {
|
|
400
|
+
const stored = store.get(inputStorageKey, null);
|
|
401
|
+
if (stored && typeof stored === 'object' && !isArray(stored)) {
|
|
402
|
+
effectiveInput = stored;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Parse React Query-style automatic revalidation attributes
|
|
408
|
+
const staleTime = parseDurationMs(rawStale, defaultStaleTime);
|
|
409
|
+
const rawFocusAttr =
|
|
410
|
+
el.getAttribute('data-w-refetch-on-focus') ??
|
|
411
|
+
el.getAttribute('data-w-refetch-on-window-focus');
|
|
412
|
+
const refetchOnWindowFocus = parseRefetchMode(rawFocusAttr, defaultRefetchOnWindowFocus);
|
|
413
|
+
const refetchOnReconnect = parseRefetchMode(
|
|
414
|
+
el.getAttribute('data-w-refetch-on-reconnect'),
|
|
415
|
+
defaultRefetchOnReconnect
|
|
416
|
+
);
|
|
417
|
+
const rawMountAttr =
|
|
418
|
+
el.getAttribute('data-w-refetch-on-mount') ??
|
|
419
|
+
el.getAttribute('data-w-refetch-on-visit');
|
|
420
|
+
const refetchOnMount = parseRefetchMode(rawMountAttr, defaultRefetchOnMount);
|
|
421
|
+
const refetchInterval = parseDurationMs(rawInterval, defaultRefetchInterval);
|
|
422
|
+
const refetchIntervalInBackground =
|
|
423
|
+
parseRefetchMode(
|
|
424
|
+
el.getAttribute('data-w-refetch-in-background'),
|
|
425
|
+
defaultRefetchIntervalInBackground
|
|
426
|
+
) === true;
|
|
427
|
+
|
|
428
|
+
if (refetchOnWindowFocus || refetchOnReconnect || refetchOnMount) {
|
|
429
|
+
ensureLifecycleListeners();
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (existing) {
|
|
433
|
+
existing.name = name;
|
|
434
|
+
existing.pagePath = pagePath;
|
|
435
|
+
existing.el = el;
|
|
436
|
+
existing.dependsOn = dependsOn;
|
|
437
|
+
if (rawInput !== null || isInputPersistEnabled) {
|
|
438
|
+
existing.input = effectiveInput;
|
|
439
|
+
}
|
|
440
|
+
existing.persistInput = isInputPersistEnabled ? { type: inputStorageType, key: inputStorageKey } : null;
|
|
441
|
+
existing.staleTime = staleTime;
|
|
442
|
+
existing.refetchOnWindowFocus = refetchOnWindowFocus;
|
|
443
|
+
existing.refetchOnReconnect = refetchOnReconnect;
|
|
444
|
+
existing.refetchOnMount = refetchOnMount;
|
|
445
|
+
existing.refetchInterval = refetchInterval;
|
|
446
|
+
existing.refetchIntervalInBackground = refetchIntervalInBackground;
|
|
447
|
+
existing._rawInput = rawInput;
|
|
448
|
+
existing._rawDepends = rawDepends;
|
|
449
|
+
existing._rawStale = rawStale;
|
|
450
|
+
existing._rawInterval = rawInterval;
|
|
451
|
+
syncInstanceInterval(existing);
|
|
452
|
+
} else {
|
|
453
|
+
const created = {
|
|
454
|
+
instanceId,
|
|
455
|
+
name,
|
|
456
|
+
pagePath,
|
|
457
|
+
input: effectiveInput,
|
|
458
|
+
dependsOn,
|
|
459
|
+
version: 0,
|
|
460
|
+
el,
|
|
461
|
+
abortController: null,
|
|
462
|
+
persistInput: isInputPersistEnabled ? { type: inputStorageType, key: inputStorageKey } : null,
|
|
463
|
+
lastUpdatedAt: Date.now(),
|
|
464
|
+
staleTime,
|
|
465
|
+
refetchOnWindowFocus,
|
|
466
|
+
refetchOnReconnect,
|
|
467
|
+
refetchOnMount,
|
|
468
|
+
refetchInterval,
|
|
469
|
+
refetchIntervalInBackground,
|
|
470
|
+
intervalTimer: null,
|
|
471
|
+
activeIntervalMs: 0,
|
|
472
|
+
_rawInput: rawInput,
|
|
473
|
+
_rawDepends: rawDepends,
|
|
474
|
+
_rawStale: rawStale,
|
|
475
|
+
_rawInterval: rawInterval,
|
|
476
|
+
};
|
|
477
|
+
instances.set(instanceId, created);
|
|
478
|
+
syncInstanceInterval(created);
|
|
479
|
+
if (refetchOnMount === 'always' || (refetchOnMount === true && staleTime <= 0)) {
|
|
480
|
+
scheduleAutoRefresh([instanceId], 'mount');
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Internal DOM scan without allocating a return array.
|
|
487
|
+
*/
|
|
488
|
+
function scanDomInstances(containerEl) {
|
|
489
|
+
const defaultRoot = getRoot();
|
|
490
|
+
const root = containerEl || defaultRoot;
|
|
491
|
+
if (!root || typeof root.querySelectorAll !== 'function') return;
|
|
492
|
+
|
|
493
|
+
const isFullScan = !containerEl || containerEl === defaultRoot;
|
|
494
|
+
const seenIds = isFullScan ? new Set() : null;
|
|
495
|
+
const nodeList = root.querySelectorAll('[data-w-fragment]');
|
|
496
|
+
let idx = 0;
|
|
497
|
+
|
|
498
|
+
if (root.nodeType === 1 && root.hasAttribute && root.hasAttribute('data-w-fragment')) {
|
|
499
|
+
syncSingleElement(root, idx++, seenIds);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
for (let i = 0, len = nodeList.length; i < len; i++) {
|
|
503
|
+
syncSingleElement(nodeList[i], idx++, seenIds);
|
|
175
504
|
}
|
|
176
505
|
|
|
177
506
|
// Prune disconnected instances if scanning full root
|
|
178
|
-
if (
|
|
507
|
+
if (isFullScan) {
|
|
179
508
|
for (const [id, inst] of instances.entries()) {
|
|
180
509
|
const disconnected =
|
|
181
510
|
!seenIds.has(id) || (inst.el && inst.el.isConnected === false);
|
|
182
511
|
if (disconnected) {
|
|
512
|
+
clearInstanceInterval(inst);
|
|
183
513
|
if (inst.abortController) {
|
|
184
514
|
try {
|
|
185
515
|
inst.abortController.abort();
|
|
@@ -191,7 +521,13 @@ function createFragmentCoordinator(options = {}) {
|
|
|
191
521
|
}
|
|
192
522
|
}
|
|
193
523
|
}
|
|
524
|
+
}
|
|
194
525
|
|
|
526
|
+
/**
|
|
527
|
+
* Scan DOM and synchronize tracked `[data-w-fragment]` instances.
|
|
528
|
+
*/
|
|
529
|
+
function syncDomInstances(containerEl) {
|
|
530
|
+
scanDomInstances(containerEl);
|
|
195
531
|
return Array.from(instances.values());
|
|
196
532
|
}
|
|
197
533
|
|
|
@@ -228,7 +564,13 @@ function createFragmentCoordinator(options = {}) {
|
|
|
228
564
|
pendingTags.size === 0
|
|
229
565
|
) {
|
|
230
566
|
inst.input = fragmentPayload.input;
|
|
231
|
-
|
|
567
|
+
const serializedInput = JSON.stringify(fragmentPayload.input);
|
|
568
|
+
inst.el.setAttribute('data-w-fragment-input', serializedInput);
|
|
569
|
+
inst._rawInput = serializedInput;
|
|
570
|
+
if (inst.persistInput) {
|
|
571
|
+
const store = inst.persistInput.type === 'local' ? defaultStorage.local : defaultStorage.session;
|
|
572
|
+
store.set(inst.persistInput.key, fragmentPayload.input);
|
|
573
|
+
}
|
|
232
574
|
}
|
|
233
575
|
|
|
234
576
|
if (datastar && typeof datastar.patchFragmentElement === 'function') {
|
|
@@ -241,9 +583,10 @@ function createFragmentCoordinator(options = {}) {
|
|
|
241
583
|
}
|
|
242
584
|
|
|
243
585
|
const patchMs = Math.max(0, Date.now() - patchStart);
|
|
586
|
+
inst.lastUpdatedAt = Date.now();
|
|
244
587
|
|
|
245
588
|
// Re-sync nested fragment instances that may have been updated inside this parent fragment
|
|
246
|
-
|
|
589
|
+
scanDomInstances(inst.el);
|
|
247
590
|
|
|
248
591
|
if (typeof options.onAfterPatch === 'function') {
|
|
249
592
|
options.onAfterPatch(inst.el, inst);
|
|
@@ -266,7 +609,7 @@ function createFragmentCoordinator(options = {}) {
|
|
|
266
609
|
* Execute a network refresh for a list of target fragment descriptors:
|
|
267
610
|
* `Array<{ instanceId, name, pagePath, input, reqVersion }>`
|
|
268
611
|
*/
|
|
269
|
-
async function executeBatchRefresh(targets, chainIds = [], abortSignal = null) {
|
|
612
|
+
async function executeBatchRefresh(targets, chainIds = [], abortSignal = null, batchTimeout = defaultTimeout) {
|
|
270
613
|
if (!targets || targets.length === 0) {
|
|
271
614
|
return { refreshed: [], skipped: true };
|
|
272
615
|
}
|
|
@@ -300,7 +643,7 @@ function createFragmentCoordinator(options = {}) {
|
|
|
300
643
|
})),
|
|
301
644
|
};
|
|
302
645
|
|
|
303
|
-
const reqId =
|
|
646
|
+
const reqId = uid('frag_req', 4);
|
|
304
647
|
if (debug) {
|
|
305
648
|
debug.trackRequestStart({
|
|
306
649
|
requestId: reqId,
|
|
@@ -312,6 +655,27 @@ function createFragmentCoordinator(options = {}) {
|
|
|
312
655
|
});
|
|
313
656
|
}
|
|
314
657
|
|
|
658
|
+
const batchAbortController = new AbortController();
|
|
659
|
+
let timeoutTimer = null;
|
|
660
|
+
let timedOut = false;
|
|
661
|
+
const timeoutMs = isNumber(batchTimeout) ? batchTimeout : defaultTimeout;
|
|
662
|
+
|
|
663
|
+
if (timeoutMs > 0) {
|
|
664
|
+
timeoutTimer = setTimeout(() => {
|
|
665
|
+
timedOut = true;
|
|
666
|
+
batchAbortController.abort();
|
|
667
|
+
}, timeoutMs);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const onCallerAbort = () => batchAbortController.abort();
|
|
671
|
+
if (abortSignal) {
|
|
672
|
+
if (abortSignal.aborted) {
|
|
673
|
+
batchAbortController.abort();
|
|
674
|
+
} else {
|
|
675
|
+
abortSignal.addEventListener('abort', onCallerAbort, { once: true });
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
315
679
|
let response;
|
|
316
680
|
try {
|
|
317
681
|
response = await fetchImpl(endpoint, {
|
|
@@ -319,11 +683,45 @@ function createFragmentCoordinator(options = {}) {
|
|
|
319
683
|
headers,
|
|
320
684
|
body: JSON.stringify(requestPayload),
|
|
321
685
|
credentials: 'same-origin',
|
|
322
|
-
signal:
|
|
686
|
+
signal: batchAbortController.signal,
|
|
323
687
|
});
|
|
324
688
|
} catch (err) {
|
|
689
|
+
if (timeoutTimer) {
|
|
690
|
+
clearTimeout(timeoutTimer);
|
|
691
|
+
timeoutTimer = null;
|
|
692
|
+
}
|
|
693
|
+
if (abortSignal) {
|
|
694
|
+
try {
|
|
695
|
+
abortSignal.removeEventListener('abort', onCallerAbort);
|
|
696
|
+
} catch (_e) {}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if (timedOut) {
|
|
700
|
+
if (debug) {
|
|
701
|
+
debug.trackRequestEnd(reqId, {
|
|
702
|
+
status: 'failed',
|
|
703
|
+
errorKind: 'timeout',
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
const timeoutError = new Error(`Fragment refresh timed out after ${timeoutMs}ms.`);
|
|
707
|
+
timeoutError.code = 'FRAGMENT_REFRESH_TIMEOUT';
|
|
708
|
+
timeoutError.isTimedOut = true;
|
|
709
|
+
lastFailedBatch = { targets, chainIds };
|
|
710
|
+
for (const cid of chainIds) {
|
|
711
|
+
if (debug) {
|
|
712
|
+
debug.recordChainRefresh(cid, {
|
|
713
|
+
fragmentIds: targets.map((t) => t.instanceId),
|
|
714
|
+
status: 'failed',
|
|
715
|
+
error: timeoutError,
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
throw timeoutError;
|
|
720
|
+
}
|
|
721
|
+
|
|
325
722
|
const isAbort =
|
|
326
723
|
(abortSignal && abortSignal.aborted) ||
|
|
724
|
+
batchAbortController.signal.aborted ||
|
|
327
725
|
(err && (err.name === 'AbortError' || err.code === 'ABORT_ERR'));
|
|
328
726
|
|
|
329
727
|
if (debug) {
|
|
@@ -333,7 +731,7 @@ function createFragmentCoordinator(options = {}) {
|
|
|
333
731
|
});
|
|
334
732
|
}
|
|
335
733
|
if (isAbort) {
|
|
336
|
-
return { refreshed: [], canceled: true };
|
|
734
|
+
return { ok: false, refreshed: [], canceled: true };
|
|
337
735
|
}
|
|
338
736
|
lastFailedBatch = { targets, chainIds };
|
|
339
737
|
for (const cid of chainIds) {
|
|
@@ -346,6 +744,16 @@ function createFragmentCoordinator(options = {}) {
|
|
|
346
744
|
}
|
|
347
745
|
}
|
|
348
746
|
throw err;
|
|
747
|
+
} finally {
|
|
748
|
+
if (timeoutTimer) {
|
|
749
|
+
clearTimeout(timeoutTimer);
|
|
750
|
+
timeoutTimer = null;
|
|
751
|
+
}
|
|
752
|
+
if (abortSignal) {
|
|
753
|
+
try {
|
|
754
|
+
abortSignal.removeEventListener('abort', onCallerAbort);
|
|
755
|
+
} catch (_e) {}
|
|
756
|
+
}
|
|
349
757
|
}
|
|
350
758
|
|
|
351
759
|
let body = null;
|
|
@@ -402,8 +810,8 @@ function createFragmentCoordinator(options = {}) {
|
|
|
402
810
|
const inst = instances.get(target.instanceId);
|
|
403
811
|
const fragResult = resultsMap[target.instanceId] || resultsMap[target.name];
|
|
404
812
|
if (!inst || !fragResult) continue;
|
|
405
|
-
if (
|
|
406
|
-
if (
|
|
813
|
+
if (isNumber(fragResult.loadMs)) totalLoadMs += fragResult.loadMs;
|
|
814
|
+
if (isNumber(fragResult.renderMs)) totalRenderMs += fragResult.renderMs;
|
|
407
815
|
|
|
408
816
|
const applied = applyFragmentPatch(
|
|
409
817
|
inst,
|
|
@@ -423,12 +831,13 @@ function createFragmentCoordinator(options = {}) {
|
|
|
423
831
|
status: 'ok',
|
|
424
832
|
loadMs: totalLoadMs,
|
|
425
833
|
renderMs: totalRenderMs,
|
|
426
|
-
totalServerMs: body &&
|
|
834
|
+
totalServerMs: body && isNumber(body.totalMs) ? body.totalMs : null,
|
|
427
835
|
});
|
|
428
836
|
}
|
|
429
837
|
}
|
|
430
838
|
|
|
431
839
|
return {
|
|
840
|
+
ok: true,
|
|
432
841
|
refreshed: refreshedIds,
|
|
433
842
|
errors: [],
|
|
434
843
|
timings: {
|
|
@@ -452,7 +861,7 @@ function createFragmentCoordinator(options = {}) {
|
|
|
452
861
|
}
|
|
453
862
|
|
|
454
863
|
if (pendingTags.size === 0) {
|
|
455
|
-
return { refreshed: [], skipped: true };
|
|
864
|
+
return { ok: true, refreshed: [], skipped: true };
|
|
456
865
|
}
|
|
457
866
|
|
|
458
867
|
const tagsToFlush = new Set(pendingTags);
|
|
@@ -460,7 +869,7 @@ function createFragmentCoordinator(options = {}) {
|
|
|
460
869
|
pendingTags.clear();
|
|
461
870
|
pendingChains.length = 0;
|
|
462
871
|
|
|
463
|
-
|
|
872
|
+
scanDomInstances();
|
|
464
873
|
|
|
465
874
|
// Find all mounted fragment instances whose `dependsOn` intersects `tagsToFlush`
|
|
466
875
|
const matchingInstances = [];
|
|
@@ -487,7 +896,7 @@ function createFragmentCoordinator(options = {}) {
|
|
|
487
896
|
});
|
|
488
897
|
}
|
|
489
898
|
}
|
|
490
|
-
return { refreshed: [], skipped: true };
|
|
899
|
+
return { ok: true, refreshed: [], skipped: true };
|
|
491
900
|
}
|
|
492
901
|
|
|
493
902
|
// Parent/Child deduplication: if parent & child are both invalidated, parent refresh covers child
|
|
@@ -539,7 +948,7 @@ function createFragmentCoordinator(options = {}) {
|
|
|
539
948
|
* @returns {Promise<{ refreshed: string[], skipped?: boolean }>}
|
|
540
949
|
*/
|
|
541
950
|
function invalidate(tags, meta = {}) {
|
|
542
|
-
const tagList =
|
|
951
|
+
const tagList = isArray(tags) ? tags : [tags];
|
|
543
952
|
for (const raw of tagList) {
|
|
544
953
|
if (typeof raw === 'string' && raw.trim()) {
|
|
545
954
|
pendingTags.add(raw.trim());
|
|
@@ -588,7 +997,7 @@ function createFragmentCoordinator(options = {}) {
|
|
|
588
997
|
* @param {object} [refreshOpts]
|
|
589
998
|
*/
|
|
590
999
|
async function refresh(idOrName, nextInput, refreshOpts = {}) {
|
|
591
|
-
|
|
1000
|
+
scanDomInstances();
|
|
592
1001
|
|
|
593
1002
|
const matched = [];
|
|
594
1003
|
if (instances.has(idOrName)) {
|
|
@@ -602,7 +1011,16 @@ function createFragmentCoordinator(options = {}) {
|
|
|
602
1011
|
}
|
|
603
1012
|
|
|
604
1013
|
if (matched.length === 0) {
|
|
605
|
-
|
|
1014
|
+
const skippedRes = { ok: true, refreshed: [], skipped: true };
|
|
1015
|
+
if (typeof refreshOpts.onSuccess === 'function') {
|
|
1016
|
+
await refreshOpts.onSuccess(skippedRes);
|
|
1017
|
+
}
|
|
1018
|
+
if (typeof refreshOpts.onSettled === 'function') {
|
|
1019
|
+
await refreshOpts.onSettled(skippedRes, null);
|
|
1020
|
+
} else if (typeof refreshOpts.onComplete === 'function') {
|
|
1021
|
+
await refreshOpts.onComplete(skippedRes, null);
|
|
1022
|
+
}
|
|
1023
|
+
return skippedRes;
|
|
606
1024
|
}
|
|
607
1025
|
|
|
608
1026
|
const deduped = deduplicateParentChildFragments(matched);
|
|
@@ -625,7 +1043,13 @@ function createFragmentCoordinator(options = {}) {
|
|
|
625
1043
|
? { ...nextInput }
|
|
626
1044
|
: { ...inst.input, ...nextInput };
|
|
627
1045
|
if (inst.el && typeof inst.el.setAttribute === 'function') {
|
|
628
|
-
|
|
1046
|
+
const serializedInput = JSON.stringify(inst.input);
|
|
1047
|
+
inst.el.setAttribute('data-w-fragment-input', serializedInput);
|
|
1048
|
+
inst._rawInput = serializedInput;
|
|
1049
|
+
}
|
|
1050
|
+
if (inst.persistInput) {
|
|
1051
|
+
const store = inst.persistInput.type === 'local' ? defaultStorage.local : defaultStorage.session;
|
|
1052
|
+
store.set(inst.persistInput.key, inst.input);
|
|
629
1053
|
}
|
|
630
1054
|
}
|
|
631
1055
|
|
|
@@ -638,7 +1062,45 @@ function createFragmentCoordinator(options = {}) {
|
|
|
638
1062
|
};
|
|
639
1063
|
});
|
|
640
1064
|
|
|
641
|
-
|
|
1065
|
+
try {
|
|
1066
|
+
const result = await executeBatchRefresh(targets, [], abortController.signal, refreshOpts.timeout);
|
|
1067
|
+
const enrichedResult = { ok: true, ...result };
|
|
1068
|
+
if (!enrichedResult.canceled && enrichedResult.ok !== false) {
|
|
1069
|
+
if (typeof refreshOpts.onSuccess === 'function') {
|
|
1070
|
+
await refreshOpts.onSuccess(enrichedResult);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
if (typeof refreshOpts.onSettled === 'function') {
|
|
1074
|
+
await refreshOpts.onSettled(enrichedResult, null);
|
|
1075
|
+
} else if (typeof refreshOpts.onComplete === 'function') {
|
|
1076
|
+
await refreshOpts.onComplete(enrichedResult, null);
|
|
1077
|
+
}
|
|
1078
|
+
return enrichedResult;
|
|
1079
|
+
} catch (err) {
|
|
1080
|
+
const failHandler = refreshOpts.onFailure || refreshOpts.onError;
|
|
1081
|
+
if (typeof failHandler === 'function') {
|
|
1082
|
+
try {
|
|
1083
|
+
await failHandler(err);
|
|
1084
|
+
} catch (callbackErr) {
|
|
1085
|
+
throw callbackErr;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
if (typeof refreshOpts.onSettled === 'function') {
|
|
1089
|
+
await refreshOpts.onSettled(null, err);
|
|
1090
|
+
} else if (typeof refreshOpts.onComplete === 'function') {
|
|
1091
|
+
await refreshOpts.onComplete(null, err);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
if (refreshOpts.throwOnError !== true && (typeof failHandler === 'function' || refreshOpts.throwOnError === false)) {
|
|
1095
|
+
return {
|
|
1096
|
+
ok: false,
|
|
1097
|
+
error: err,
|
|
1098
|
+
refreshed: [],
|
|
1099
|
+
timings: { loadMs: 0, renderMs: 0 },
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
throw err;
|
|
1103
|
+
}
|
|
642
1104
|
}
|
|
643
1105
|
|
|
644
1106
|
/**
|
|
@@ -677,10 +1139,10 @@ function createFragmentCoordinator(options = {}) {
|
|
|
677
1139
|
invalidate,
|
|
678
1140
|
refresh,
|
|
679
1141
|
refreshFragments(items = []) {
|
|
680
|
-
if (!
|
|
1142
|
+
if (!isArray(items) || items.length === 0) {
|
|
681
1143
|
return Promise.resolve({ refreshed: [], skipped: true });
|
|
682
1144
|
}
|
|
683
|
-
|
|
1145
|
+
scanDomInstances();
|
|
684
1146
|
for (const item of items) {
|
|
685
1147
|
const key = item.instanceId || item.name;
|
|
686
1148
|
const inst = instances.get(key) || Array.from(instances.values()).find((i) => i.name === key);
|
|
@@ -688,7 +1150,9 @@ function createFragmentCoordinator(options = {}) {
|
|
|
688
1150
|
if (item.input && typeof item.input === 'object') {
|
|
689
1151
|
inst.input = { ...item.input };
|
|
690
1152
|
if (inst.el && typeof inst.el.setAttribute === 'function') {
|
|
691
|
-
|
|
1153
|
+
const serializedInput = JSON.stringify(inst.input);
|
|
1154
|
+
inst.el.setAttribute('data-w-fragment-input', serializedInput);
|
|
1155
|
+
inst._rawInput = serializedInput;
|
|
692
1156
|
}
|
|
693
1157
|
}
|
|
694
1158
|
pendingTags.add(inst.name);
|
|
@@ -699,8 +1163,8 @@ function createFragmentCoordinator(options = {}) {
|
|
|
699
1163
|
return flushInvalidations();
|
|
700
1164
|
},
|
|
701
1165
|
findFragmentsByTags(tags = []) {
|
|
702
|
-
|
|
703
|
-
const tagSet = new Set((
|
|
1166
|
+
scanDomInstances();
|
|
1167
|
+
const tagSet = new Set((isArray(tags) ? tags : [tags]).map((t) => String(t || '').trim()).filter(Boolean));
|
|
704
1168
|
const matching = [];
|
|
705
1169
|
for (const inst of instances.values()) {
|
|
706
1170
|
for (const t of tagSet) {
|
|
@@ -715,16 +1179,46 @@ function createFragmentCoordinator(options = {}) {
|
|
|
715
1179
|
retryFailed,
|
|
716
1180
|
retryLastFailed: retryFailed,
|
|
717
1181
|
getInstance(instanceId) {
|
|
718
|
-
|
|
1182
|
+
const hit = instances.get(instanceId);
|
|
1183
|
+
if (hit && (!hit.el || hit.el.isConnected !== false)) {
|
|
1184
|
+
return hit;
|
|
1185
|
+
}
|
|
1186
|
+
scanDomInstances();
|
|
719
1187
|
return instances.get(instanceId) || null;
|
|
720
1188
|
},
|
|
721
1189
|
getInstances() {
|
|
722
|
-
|
|
1190
|
+
scanDomInstances();
|
|
723
1191
|
return Array.from(instances.values());
|
|
724
1192
|
},
|
|
1193
|
+
getStoredInput(instanceIdOrName, storageType = 'session') {
|
|
1194
|
+
const store = storageType === 'local' ? defaultStorage.local : defaultStorage.session;
|
|
1195
|
+
return store.get(`frag_input:${instanceIdOrName}`, null);
|
|
1196
|
+
},
|
|
1197
|
+
clearStoredInput(instanceIdOrName, storageType = 'session') {
|
|
1198
|
+
const store = storageType === 'local' ? defaultStorage.local : defaultStorage.session;
|
|
1199
|
+
return store.remove(`frag_input:${instanceIdOrName}`);
|
|
1200
|
+
},
|
|
725
1201
|
get hasFailedRefresh() {
|
|
726
1202
|
return Boolean(lastFailedBatch);
|
|
727
1203
|
},
|
|
1204
|
+
dispose() {
|
|
1205
|
+
isDisposed = true;
|
|
1206
|
+
if (focusDebounceTimer) {
|
|
1207
|
+
clearTimeout(focusDebounceTimer);
|
|
1208
|
+
focusDebounceTimer = null;
|
|
1209
|
+
}
|
|
1210
|
+
pendingAutoInstanceIds.clear();
|
|
1211
|
+
removeLifecycleListeners();
|
|
1212
|
+
for (const inst of instances.values()) {
|
|
1213
|
+
clearInstanceInterval(inst);
|
|
1214
|
+
if (inst.abortController) {
|
|
1215
|
+
try {
|
|
1216
|
+
inst.abortController.abort();
|
|
1217
|
+
} catch (_e) {}
|
|
1218
|
+
inst.abortController = null;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
},
|
|
728
1222
|
};
|
|
729
1223
|
}
|
|
730
1224
|
|