webspresso 0.0.111 → 0.0.113
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 +700 -50
- package/core/client/api.js +147 -7
- package/core/client/controller.js +254 -22
- package/core/client/devtool.js +59 -33
- package/core/client/forms.js +598 -38
- package/core/client/fragments.js +644 -112
- package/core/client/host.js +5 -1
- package/core/client/index.js +13 -1
- package/core/client/lite.js +1 -1
- package/core/client/server-fragments.js +145 -37
- package/core/client/utils.js +72 -17
- package/core/http/pipeline.js +82 -1
- package/core/openapi/build-from-api-routes.js +5 -5
- package/core/orm/json-fields.js +7 -6
- package/core/orm/schema-helpers.js +18 -15
- package/core/orm/utils/nanoid.js +7 -8
- package/core/services/validator.js +12 -1
- package/core/validation/apply.js +3 -0
- package/core/validation/index.js +25 -0
- package/core/validation/zod-introspect.js +187 -0
- package/index.d.ts +110 -2
- package/package.json +9 -8
- package/plugins/mcp/providers/services.js +2 -2
- package/src/api/define-api.js +2 -0
- package/src/api/validation-i18n.js +6 -2
- package/src/bundle/index.js +67 -3
- package/src/dev/api-typegen.js +79 -22
- package/src/dev/typegen.js +61 -35
- package/src/file-router.js +24 -6
- package/src/routing/mount-discovered-routes.js +24 -6
package/core/client/fragments.js
CHANGED
|
@@ -65,6 +65,16 @@ function parseFragmentInputAttr(raw) {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
function isStrictAncestor(parentEl, childEl) {
|
|
69
|
+
return Boolean(
|
|
70
|
+
parentEl &&
|
|
71
|
+
childEl &&
|
|
72
|
+
parentEl !== childEl &&
|
|
73
|
+
typeof parentEl.contains === 'function' &&
|
|
74
|
+
parentEl.contains(childEl)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
68
78
|
/**
|
|
69
79
|
* Filter a list of fragment records so that if fragment B's DOM element is inside
|
|
70
80
|
* fragment A's DOM subtree, B is omitted from the batch because refreshing A will
|
|
@@ -79,7 +89,7 @@ function deduplicateParentChildFragments(candidates) {
|
|
|
79
89
|
if (!candidate.el) return true;
|
|
80
90
|
for (const other of candidates) {
|
|
81
91
|
if (other === candidate || !other.el) continue;
|
|
82
|
-
if (other.el
|
|
92
|
+
if (isStrictAncestor(other.el, candidate.el)) {
|
|
83
93
|
return false;
|
|
84
94
|
}
|
|
85
95
|
}
|
|
@@ -140,11 +150,179 @@ function createFragmentCoordinator(options = {}) {
|
|
|
140
150
|
|
|
141
151
|
// Automatic refetch (focus / reconnect / mount / interval) state
|
|
142
152
|
const pendingAutoInstanceIds = new Set();
|
|
153
|
+
const pendingAutoReasons = new Set();
|
|
143
154
|
let autoBatchScheduled = false;
|
|
155
|
+
let autoBatchTimer = null;
|
|
144
156
|
let focusDebounceTimer = null;
|
|
145
157
|
let isDisposed = false;
|
|
146
158
|
let lifecycleListenersAttached = false;
|
|
147
159
|
|
|
160
|
+
// Per-version patch options (mode, limit, target, removeKeys, onBeforePatch, onAfterPatch)
|
|
161
|
+
const pendingPatchOptsByKey = new Map();
|
|
162
|
+
|
|
163
|
+
// Lazy fragment IntersectionObserver state
|
|
164
|
+
let lazyObserver = null;
|
|
165
|
+
const observedLazyEls = new WeakSet();
|
|
166
|
+
|
|
167
|
+
function getLazyObserver() {
|
|
168
|
+
if (lazyObserver) return lazyObserver;
|
|
169
|
+
const win =
|
|
170
|
+
typeof window !== 'undefined'
|
|
171
|
+
? window
|
|
172
|
+
: typeof globalThis !== 'undefined'
|
|
173
|
+
? globalThis
|
|
174
|
+
: null;
|
|
175
|
+
const IOCtor = win && typeof win.IntersectionObserver === 'function' ? win.IntersectionObserver : null;
|
|
176
|
+
if (!IOCtor) return null;
|
|
177
|
+
lazyObserver = new IOCtor((entries) => {
|
|
178
|
+
if (isDisposed) return;
|
|
179
|
+
const visibleIds = [];
|
|
180
|
+
for (let i = 0; i < entries.length; i++) {
|
|
181
|
+
const entry = entries[i];
|
|
182
|
+
if (!entry || !entry.isIntersecting) continue;
|
|
183
|
+
const targetEl = entry.target;
|
|
184
|
+
try {
|
|
185
|
+
lazyObserver.unobserve(targetEl);
|
|
186
|
+
} catch (_e) {}
|
|
187
|
+
const fragId = targetEl && targetEl.getAttribute ? targetEl.getAttribute('data-w-fragment-id') : null;
|
|
188
|
+
const inst = fragId ? instances.get(fragId) : null;
|
|
189
|
+
if (inst && inst.lazy && inst.lazyState === 'idle') {
|
|
190
|
+
inst.lazyState = 'loading';
|
|
191
|
+
visibleIds.push(inst.instanceId);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (visibleIds.length > 0) {
|
|
195
|
+
scheduleAutoRefresh(visibleIds, 'lazy');
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
return lazyObserver;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function observeLazyFragment(inst) {
|
|
202
|
+
if (!inst || !inst.el || !inst.lazy || inst.lazyState !== 'idle') return;
|
|
203
|
+
if (observedLazyEls.has(inst.el)) return;
|
|
204
|
+
const obs = getLazyObserver();
|
|
205
|
+
if (!obs) return;
|
|
206
|
+
observedLazyEls.add(inst.el);
|
|
207
|
+
try {
|
|
208
|
+
obs.observe(inst.el);
|
|
209
|
+
} catch (_e) {}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function unobserveLazyFragment(inst) {
|
|
213
|
+
if (!inst || !inst.el || !lazyObserver) return;
|
|
214
|
+
try {
|
|
215
|
+
lazyObserver.unobserve(inst.el);
|
|
216
|
+
} catch (_e) {}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function getListItemKey(node) {
|
|
220
|
+
if (!node || node.nodeType !== 1 || typeof node.getAttribute !== 'function') return null;
|
|
221
|
+
if (node.id) return `id:${node.id}`;
|
|
222
|
+
const wKey = node.getAttribute('data-w-key');
|
|
223
|
+
if (wKey) return `wkey:${wKey}`;
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function patchIncrementalList(fragEl, html, opts = {}) {
|
|
228
|
+
const doc = fragEl.ownerDocument || (typeof document !== 'undefined' ? document : null);
|
|
229
|
+
const parsed = datastar && typeof datastar.parseHtmlFragment === 'function'
|
|
230
|
+
? datastar.parseHtmlFragment(html, doc)
|
|
231
|
+
: null;
|
|
232
|
+
if (!parsed) return false;
|
|
233
|
+
|
|
234
|
+
const targetSel = opts.target || null;
|
|
235
|
+
const container =
|
|
236
|
+
(targetSel && fragEl.querySelector ? fragEl.querySelector(targetSel) : null) ||
|
|
237
|
+
(fragEl.querySelector ? fragEl.querySelector('[data-w-list]') : null) ||
|
|
238
|
+
fragEl;
|
|
239
|
+
const incomingContainer =
|
|
240
|
+
(targetSel && parsed.querySelector ? parsed.querySelector(targetSel) : null) ||
|
|
241
|
+
(parsed.querySelector ? parsed.querySelector('[data-w-list]') : null) ||
|
|
242
|
+
parsed;
|
|
243
|
+
|
|
244
|
+
// 1. Remove explicitly requested keys if any
|
|
245
|
+
if (isArray(opts.removeKeys) && opts.removeKeys.length > 0) {
|
|
246
|
+
const removeSet = new Set(opts.removeKeys.map(String));
|
|
247
|
+
const curChildren = Array.from(container.children || []);
|
|
248
|
+
for (let i = 0; i < curChildren.length; i++) {
|
|
249
|
+
const child = curChildren[i];
|
|
250
|
+
const rawKey = child.getAttribute('data-w-key') || child.id || '';
|
|
251
|
+
const fullKey = getListItemKey(child);
|
|
252
|
+
if (removeSet.has(rawKey) || (fullKey && removeSet.has(fullKey))) {
|
|
253
|
+
if (typeof opts.onBeforeNodeRemove === 'function') opts.onBeforeNodeRemove(child);
|
|
254
|
+
container.removeChild(child);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// 2. Index existing keyed items in container
|
|
260
|
+
const existingByKey = new Map();
|
|
261
|
+
const existingChildren = Array.from(container.children || []);
|
|
262
|
+
for (let i = 0; i < existingChildren.length; i++) {
|
|
263
|
+
const k = getListItemKey(existingChildren[i]);
|
|
264
|
+
if (k && !existingByKey.has(k)) {
|
|
265
|
+
existingByKey.set(k, existingChildren[i]);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const incomingItems = Array.from(incomingContainer.children || []);
|
|
270
|
+
const mode = opts.mode === 'prepend' ? 'prepend' : 'append';
|
|
271
|
+
const itemsToInsert = mode === 'prepend' ? incomingItems.slice().reverse() : incomingItems;
|
|
272
|
+
|
|
273
|
+
for (let i = 0; i < itemsToInsert.length; i++) {
|
|
274
|
+
const incoming = itemsToInsert[i];
|
|
275
|
+
if (incoming.nodeType !== 1) continue;
|
|
276
|
+
const key = getListItemKey(incoming);
|
|
277
|
+
const existingMatch = key ? existingByKey.get(key) : null;
|
|
278
|
+
|
|
279
|
+
if (existingMatch && existingMatch.tagName === incoming.tagName) {
|
|
280
|
+
// Update existing keyed item in place without duplicating
|
|
281
|
+
if (datastar && typeof datastar.morphDomTrees === 'function') {
|
|
282
|
+
datastar.morphDomTrees(existingMatch, incoming, {
|
|
283
|
+
onBeforeNodeRemove: opts.onBeforeNodeRemove,
|
|
284
|
+
onNodeAdded: opts.onNodeAdded,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const imported = container.ownerDocument
|
|
291
|
+
? container.ownerDocument.importNode(incoming, true)
|
|
292
|
+
: incoming.cloneNode(true);
|
|
293
|
+
|
|
294
|
+
if (mode === 'prepend') {
|
|
295
|
+
const firstRef = (container.children && container.children[0]) || container.firstChild || null;
|
|
296
|
+
container.insertBefore(imported, firstRef);
|
|
297
|
+
} else {
|
|
298
|
+
container.appendChild(imported);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (key) {
|
|
302
|
+
existingByKey.set(key, imported);
|
|
303
|
+
}
|
|
304
|
+
if (typeof opts.onNodeAdded === 'function') {
|
|
305
|
+
opts.onNodeAdded(imported);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 3. Enforce optional retained item limit
|
|
310
|
+
const limitAttr = container.getAttribute ? container.getAttribute('data-w-limit') : null;
|
|
311
|
+
const rawLimit = opts.limit !== undefined ? opts.limit : limitAttr;
|
|
312
|
+
const limit = rawLimit !== null && rawLimit !== undefined && rawLimit !== '' ? Number(rawLimit) : 0;
|
|
313
|
+
if (Number.isFinite(limit) && limit > 0) {
|
|
314
|
+
while (container.children && container.children.length > limit) {
|
|
315
|
+
const ch = container.children;
|
|
316
|
+
const victim = mode === 'prepend' ? ch[ch.length - 1] : ch[0];
|
|
317
|
+
if (!victim) break;
|
|
318
|
+
if (typeof opts.onBeforeNodeRemove === 'function') opts.onBeforeNodeRemove(victim);
|
|
319
|
+
container.removeChild(victim);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
|
|
148
326
|
function isDocumentHidden() {
|
|
149
327
|
const root = getRoot();
|
|
150
328
|
const doc = root && root.nodeType === 9 ? root : root && root.ownerDocument ? root.ownerDocument : typeof document !== 'undefined' ? document : null;
|
|
@@ -195,76 +373,100 @@ function createFragmentCoordinator(options = {}) {
|
|
|
195
373
|
}
|
|
196
374
|
}
|
|
197
375
|
|
|
198
|
-
function
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
376
|
+
async function flushAutoRefresh() {
|
|
377
|
+
autoBatchScheduled = false;
|
|
378
|
+
if (autoBatchTimer) {
|
|
379
|
+
clearTimeout(autoBatchTimer);
|
|
380
|
+
autoBatchTimer = null;
|
|
202
381
|
}
|
|
203
|
-
if (
|
|
204
|
-
|
|
382
|
+
if (isDisposed || pendingAutoInstanceIds.size === 0) return;
|
|
383
|
+
const idsToRefresh = Array.from(pendingAutoInstanceIds);
|
|
384
|
+
const reasons = Array.from(pendingAutoReasons);
|
|
385
|
+
pendingAutoInstanceIds.clear();
|
|
386
|
+
pendingAutoReasons.clear();
|
|
387
|
+
|
|
388
|
+
// Skip background auto-refresh if a mutation invalidation batch is currently in flight
|
|
389
|
+
if (inFlightBatchCount > 0) return;
|
|
390
|
+
|
|
391
|
+
const candidates = [];
|
|
392
|
+
for (const id of idsToRefresh) {
|
|
393
|
+
const inst = instances.get(id);
|
|
394
|
+
if (
|
|
395
|
+
inst &&
|
|
396
|
+
inst.el &&
|
|
397
|
+
inst.el.isConnected !== false &&
|
|
398
|
+
!inst.abortController &&
|
|
399
|
+
!(inst.inFlightCount > 0)
|
|
400
|
+
) {
|
|
401
|
+
candidates.push(inst);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (candidates.length === 0) return;
|
|
205
405
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
406
|
+
const deduped = deduplicateParentChildFragments(candidates);
|
|
407
|
+
const targets = deduped.map((inst) => {
|
|
408
|
+
inst.version += 1;
|
|
409
|
+
return {
|
|
410
|
+
instanceId: inst.instanceId,
|
|
411
|
+
name: inst.name,
|
|
412
|
+
pagePath: inst.pagePath,
|
|
413
|
+
input: { ...inst.input },
|
|
414
|
+
reqVersion: inst.version,
|
|
415
|
+
};
|
|
416
|
+
});
|
|
211
417
|
|
|
212
|
-
|
|
213
|
-
|
|
418
|
+
if (debug && typeof debug.recordEvent === 'function') {
|
|
419
|
+
debug.recordEvent('fragment:auto-refetch', {
|
|
420
|
+
reason: reasons.length === 1 ? reasons[0] : reasons.join('+'),
|
|
421
|
+
reasons,
|
|
422
|
+
fragments: targets.map((t) => t.instanceId),
|
|
423
|
+
});
|
|
424
|
+
}
|
|
214
425
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
426
|
+
try {
|
|
427
|
+
await executeBatchRefresh(targets);
|
|
428
|
+
} catch (_err) {
|
|
429
|
+
// Background auto-refetch errors are non-fatal; reset lazyState if still unloaded so retry/re-observe works
|
|
430
|
+
for (const inst of deduped) {
|
|
431
|
+
if (inst.lazy && inst.lazyState === 'loading') {
|
|
432
|
+
inst.lazyState = 'idle';
|
|
220
433
|
}
|
|
221
434
|
}
|
|
222
|
-
|
|
435
|
+
}
|
|
436
|
+
}
|
|
223
437
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
input: { ...inst.input },
|
|
232
|
-
reqVersion: inst.version,
|
|
233
|
-
};
|
|
234
|
-
});
|
|
438
|
+
function scheduleAutoRefresh(instanceIds, reason = 'auto') {
|
|
439
|
+
if (isDisposed || !isArray(instanceIds) || instanceIds.length === 0) return;
|
|
440
|
+
for (const id of instanceIds) {
|
|
441
|
+
if (id) pendingAutoInstanceIds.add(id);
|
|
442
|
+
}
|
|
443
|
+
if (reason) pendingAutoReasons.add(reason);
|
|
444
|
+
if (pendingAutoInstanceIds.size === 0) return;
|
|
235
445
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
});
|
|
446
|
+
if (reason === 'mount' || reason === 'lazy') {
|
|
447
|
+
if (!autoBatchScheduled) {
|
|
448
|
+
autoBatchScheduled = true;
|
|
449
|
+
Promise.resolve().then(flushAutoRefresh);
|
|
241
450
|
}
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
242
453
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
} catch (_err) {
|
|
246
|
-
// Background auto-refetch errors are non-fatal
|
|
247
|
-
}
|
|
248
|
-
});
|
|
454
|
+
if (autoBatchTimer || autoBatchScheduled) return;
|
|
455
|
+
autoBatchTimer = setTimeout(flushAutoRefresh, 5);
|
|
249
456
|
}
|
|
250
457
|
|
|
251
458
|
function triggerFocusRefetch() {
|
|
252
459
|
if (isDisposed || isDocumentHidden()) return;
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (
|
|
257
|
-
|
|
258
|
-
const matchedIds = [];
|
|
259
|
-
for (const inst of instances.values()) {
|
|
260
|
-
if (isInstanceStale(inst, inst.refetchOnWindowFocus)) {
|
|
261
|
-
matchedIds.push(inst.instanceId);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
if (matchedIds.length > 0) {
|
|
265
|
-
scheduleAutoRefresh(matchedIds, 'window-focus');
|
|
460
|
+
scanDomInstances();
|
|
461
|
+
const matchedIds = [];
|
|
462
|
+
for (const inst of instances.values()) {
|
|
463
|
+
if (isInstanceStale(inst, inst.refetchOnWindowFocus)) {
|
|
464
|
+
matchedIds.push(inst.instanceId);
|
|
266
465
|
}
|
|
267
|
-
}
|
|
466
|
+
}
|
|
467
|
+
if (matchedIds.length > 0) {
|
|
468
|
+
scheduleAutoRefresh(matchedIds, 'window-focus');
|
|
469
|
+
}
|
|
268
470
|
}
|
|
269
471
|
|
|
270
472
|
function triggerReconnectRefetch() {
|
|
@@ -394,12 +596,21 @@ function createFragmentCoordinator(options = {}) {
|
|
|
394
596
|
`frag_input:${name}`;
|
|
395
597
|
|
|
396
598
|
let effectiveInput = input;
|
|
397
|
-
|
|
599
|
+
let pendingRestoredInput = null;
|
|
600
|
+
if (!existing && isInputPersistEnabled) {
|
|
398
601
|
const store = inputStorageType === 'local' ? defaultStorage.local : defaultStorage.session;
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
602
|
+
let stored = store.get(inputStorageKey, null);
|
|
603
|
+
if (
|
|
604
|
+
(!stored || typeof stored !== 'object' || isArray(stored)) &&
|
|
605
|
+
inputStorageKey !== `frag_input:${instanceId}`
|
|
606
|
+
) {
|
|
607
|
+
stored = store.get(`frag_input:${instanceId}`, null);
|
|
608
|
+
}
|
|
609
|
+
if (stored && typeof stored === 'object' && !isArray(stored)) {
|
|
610
|
+
if (rawInput === null || Object.keys(effectiveInput).length === 0) {
|
|
611
|
+
effectiveInput = { ...stored };
|
|
612
|
+
} else if (JSON.stringify(stored) !== JSON.stringify(effectiveInput)) {
|
|
613
|
+
pendingRestoredInput = { ...stored };
|
|
403
614
|
}
|
|
404
615
|
}
|
|
405
616
|
}
|
|
@@ -429,13 +640,16 @@ function createFragmentCoordinator(options = {}) {
|
|
|
429
640
|
ensureLifecycleListeners();
|
|
430
641
|
}
|
|
431
642
|
|
|
643
|
+
const rawLazyAttr = el.getAttribute('data-w-lazy');
|
|
644
|
+
const isLazyUnloaded = Boolean(rawLazyAttr && rawLazyAttr !== 'false' && rawLazyAttr !== 'loaded');
|
|
645
|
+
|
|
432
646
|
if (existing) {
|
|
433
647
|
existing.name = name;
|
|
434
648
|
existing.pagePath = pagePath;
|
|
435
649
|
existing.el = el;
|
|
436
650
|
existing.dependsOn = dependsOn;
|
|
437
|
-
if (rawInput !== null
|
|
438
|
-
existing.input =
|
|
651
|
+
if (rawInput !== null && rawInput !== existing._rawInput) {
|
|
652
|
+
existing.input = input;
|
|
439
653
|
}
|
|
440
654
|
existing.persistInput = isInputPersistEnabled ? { type: inputStorageType, key: inputStorageKey } : null;
|
|
441
655
|
existing.staleTime = staleTime;
|
|
@@ -449,16 +663,21 @@ function createFragmentCoordinator(options = {}) {
|
|
|
449
663
|
existing._rawStale = rawStale;
|
|
450
664
|
existing._rawInterval = rawInterval;
|
|
451
665
|
syncInstanceInterval(existing);
|
|
666
|
+
if (existing.lazy && existing.lazyState === 'idle') {
|
|
667
|
+
observeLazyFragment(existing);
|
|
668
|
+
}
|
|
452
669
|
} else {
|
|
453
670
|
const created = {
|
|
454
671
|
instanceId,
|
|
455
672
|
name,
|
|
456
673
|
pagePath,
|
|
457
674
|
input: effectiveInput,
|
|
675
|
+
restoredInput: pendingRestoredInput,
|
|
458
676
|
dependsOn,
|
|
459
677
|
version: 0,
|
|
460
678
|
el,
|
|
461
679
|
abortController: null,
|
|
680
|
+
inFlightCount: 0,
|
|
462
681
|
persistInput: isInputPersistEnabled ? { type: inputStorageType, key: inputStorageKey } : null,
|
|
463
682
|
lastUpdatedAt: Date.now(),
|
|
464
683
|
staleTime,
|
|
@@ -469,6 +688,9 @@ function createFragmentCoordinator(options = {}) {
|
|
|
469
688
|
refetchIntervalInBackground,
|
|
470
689
|
intervalTimer: null,
|
|
471
690
|
activeIntervalMs: 0,
|
|
691
|
+
lazy: isLazyUnloaded,
|
|
692
|
+
lazyState: isLazyUnloaded ? 'idle' : 'loaded',
|
|
693
|
+
staleWhileUnloaded: false,
|
|
472
694
|
_rawInput: rawInput,
|
|
473
695
|
_rawDepends: rawDepends,
|
|
474
696
|
_rawStale: rawStale,
|
|
@@ -476,7 +698,15 @@ function createFragmentCoordinator(options = {}) {
|
|
|
476
698
|
};
|
|
477
699
|
instances.set(instanceId, created);
|
|
478
700
|
syncInstanceInterval(created);
|
|
479
|
-
if (
|
|
701
|
+
if (isLazyUnloaded) {
|
|
702
|
+
observeLazyFragment(created);
|
|
703
|
+
} else if (pendingRestoredInput) {
|
|
704
|
+
Promise.resolve().then(() => {
|
|
705
|
+
if (!isDisposed && created.version === 0 && instances.get(instanceId) === created) {
|
|
706
|
+
refresh(instanceId, pendingRestoredInput).catch(() => {});
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
} else if (refetchOnMount === 'always' || (refetchOnMount === true && staleTime <= 0)) {
|
|
480
710
|
scheduleAutoRefresh([instanceId], 'mount');
|
|
481
711
|
}
|
|
482
712
|
}
|
|
@@ -510,6 +740,7 @@ function createFragmentCoordinator(options = {}) {
|
|
|
510
740
|
!seenIds.has(id) || (inst.el && inst.el.isConnected === false);
|
|
511
741
|
if (disconnected) {
|
|
512
742
|
clearInstanceInterval(inst);
|
|
743
|
+
unobserveLazyFragment(inst);
|
|
513
744
|
if (inst.abortController) {
|
|
514
745
|
try {
|
|
515
746
|
inst.abortController.abort();
|
|
@@ -535,7 +766,7 @@ function createFragmentCoordinator(options = {}) {
|
|
|
535
766
|
* Apply a single server fragment result `{ instanceId, name, html, input, loadMs, renderMs }`
|
|
536
767
|
* to the DOM if and only if `reqVersion >= inst.version`.
|
|
537
768
|
*/
|
|
538
|
-
function applyFragmentPatch(inst, fragmentPayload, reqVersion, chainId = null) {
|
|
769
|
+
function applyFragmentPatch(inst, fragmentPayload, reqVersion, chainId = null, requestedInput = null) {
|
|
539
770
|
if (!inst || !inst.el) return false;
|
|
540
771
|
|
|
541
772
|
if (reqVersion < inst.version) {
|
|
@@ -555,25 +786,58 @@ function createFragmentCoordinator(options = {}) {
|
|
|
555
786
|
const html = fragmentPayload && typeof fragmentPayload.html === 'string' ? fragmentPayload.html : '';
|
|
556
787
|
if (!html) return false;
|
|
557
788
|
|
|
789
|
+
const patchKey = `${inst.instanceId}:${reqVersion}`;
|
|
790
|
+
const patchOpts = pendingPatchOptsByKey.get(patchKey) || null;
|
|
791
|
+
if (patchOpts) {
|
|
792
|
+
pendingPatchOptsByKey.delete(patchKey);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (typeof options.onBeforePatch === 'function') {
|
|
796
|
+
options.onBeforePatch(inst.el, inst);
|
|
797
|
+
}
|
|
798
|
+
if (patchOpts && typeof patchOpts.onBeforePatch === 'function') {
|
|
799
|
+
try {
|
|
800
|
+
patchOpts.onBeforePatch(inst.el, inst);
|
|
801
|
+
} catch (_e) {}
|
|
802
|
+
}
|
|
803
|
+
|
|
558
804
|
const patchStart = Date.now();
|
|
559
805
|
|
|
806
|
+
const committedInput =
|
|
807
|
+
fragmentPayload.input && typeof fragmentPayload.input === 'object' && !isArray(fragmentPayload.input)
|
|
808
|
+
? fragmentPayload.input
|
|
809
|
+
: requestedInput && typeof requestedInput === 'object' && !isArray(requestedInput)
|
|
810
|
+
? requestedInput
|
|
811
|
+
: null;
|
|
812
|
+
|
|
560
813
|
if (
|
|
561
|
-
|
|
562
|
-
typeof fragmentPayload.input === 'object' &&
|
|
814
|
+
committedInput &&
|
|
563
815
|
!followUpNeeded &&
|
|
564
816
|
pendingTags.size === 0
|
|
565
817
|
) {
|
|
566
|
-
inst.input =
|
|
567
|
-
const serializedInput = JSON.stringify(
|
|
568
|
-
inst.el.setAttribute
|
|
818
|
+
inst.input = { ...committedInput };
|
|
819
|
+
const serializedInput = JSON.stringify(inst.input);
|
|
820
|
+
if (inst.el && typeof inst.el.setAttribute === 'function') {
|
|
821
|
+
inst.el.setAttribute('data-w-fragment-input', serializedInput);
|
|
822
|
+
}
|
|
569
823
|
inst._rawInput = serializedInput;
|
|
570
824
|
if (inst.persistInput) {
|
|
571
825
|
const store = inst.persistInput.type === 'local' ? defaultStorage.local : defaultStorage.session;
|
|
572
|
-
store.set(inst.persistInput.key,
|
|
826
|
+
store.set(inst.persistInput.key, inst.input);
|
|
573
827
|
}
|
|
574
828
|
}
|
|
575
829
|
|
|
576
|
-
|
|
830
|
+
const patchMode = fragmentPayload.mode || (patchOpts && patchOpts.mode) || 'replace';
|
|
831
|
+
if (patchMode === 'append' || patchMode === 'prepend') {
|
|
832
|
+
patchIncrementalList(inst.el, html, {
|
|
833
|
+
mode: patchMode,
|
|
834
|
+
limit: fragmentPayload.limit !== undefined ? fragmentPayload.limit : patchOpts && patchOpts.limit,
|
|
835
|
+
target: fragmentPayload.target || (patchOpts && patchOpts.target),
|
|
836
|
+
removeKeys: fragmentPayload.removeKeys || (patchOpts && patchOpts.removeKeys),
|
|
837
|
+
onBeforeNodeRemove: options.onBeforeNodeRemove,
|
|
838
|
+
onNodeAdded: options.onNodeAdded,
|
|
839
|
+
});
|
|
840
|
+
} else if (datastar && typeof datastar.patchFragmentElement === 'function') {
|
|
577
841
|
datastar.patchFragmentElement(inst.el, html, {
|
|
578
842
|
onBeforeNodeRemove: options.onBeforeNodeRemove,
|
|
579
843
|
onNodeAdded: options.onNodeAdded,
|
|
@@ -582,15 +846,37 @@ function createFragmentCoordinator(options = {}) {
|
|
|
582
846
|
inst.el.innerHTML = html;
|
|
583
847
|
}
|
|
584
848
|
|
|
849
|
+
// Clear lazy unloaded markers once a real patch is applied
|
|
850
|
+
if (inst.lazy || inst.lazyState !== 'loaded') {
|
|
851
|
+
inst.lazy = false;
|
|
852
|
+
inst.lazyState = 'loaded';
|
|
853
|
+
inst.staleWhileUnloaded = false;
|
|
854
|
+
unobserveLazyFragment(inst);
|
|
855
|
+
if (inst.el && typeof inst.el.removeAttribute === 'function') {
|
|
856
|
+
inst.el.removeAttribute('data-w-lazy');
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
585
860
|
const patchMs = Math.max(0, Date.now() - patchStart);
|
|
586
|
-
|
|
861
|
+
const now = Date.now();
|
|
862
|
+
inst.lastUpdatedAt = now;
|
|
587
863
|
|
|
588
864
|
// Re-sync nested fragment instances that may have been updated inside this parent fragment
|
|
589
865
|
scanDomInstances(inst.el);
|
|
866
|
+
for (const otherInst of instances.values()) {
|
|
867
|
+
if (otherInst !== inst && otherInst.el && isStrictAncestor(inst.el, otherInst.el)) {
|
|
868
|
+
otherInst.lastUpdatedAt = now;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
590
871
|
|
|
591
872
|
if (typeof options.onAfterPatch === 'function') {
|
|
592
873
|
options.onAfterPatch(inst.el, inst);
|
|
593
874
|
}
|
|
875
|
+
if (patchOpts && typeof patchOpts.onAfterPatch === 'function') {
|
|
876
|
+
try {
|
|
877
|
+
patchOpts.onAfterPatch(inst.el, inst);
|
|
878
|
+
} catch (_e) {}
|
|
879
|
+
}
|
|
594
880
|
|
|
595
881
|
if (debug) {
|
|
596
882
|
debug.recordChainPatch(chainId, {
|
|
@@ -633,6 +919,29 @@ function createFragmentCoordinator(options = {}) {
|
|
|
633
919
|
targets[0].pagePath ||
|
|
634
920
|
(typeof window !== 'undefined' && window.location ? window.location.pathname : '/');
|
|
635
921
|
|
|
922
|
+
// Collect active inputs of any nested descendant fragments inside the requested parent targets
|
|
923
|
+
// so server-side parent re-rendering preserves nested child filters & pagination.
|
|
924
|
+
const childInputs = {};
|
|
925
|
+
let hasChildInputs = false;
|
|
926
|
+
for (const t of targets) {
|
|
927
|
+
const parentInst = instances.get(t.instanceId);
|
|
928
|
+
if (!parentInst || !parentInst.el) continue;
|
|
929
|
+
for (const otherInst of instances.values()) {
|
|
930
|
+
if (otherInst !== parentInst && otherInst.el && isStrictAncestor(parentInst.el, otherInst.el)) {
|
|
931
|
+
// Supersede any older in-flight refresh on the nested child so a late child response
|
|
932
|
+
// cannot overwrite the newer parent+child snapshot.
|
|
933
|
+
otherInst.version += 1;
|
|
934
|
+
if (otherInst.input && typeof otherInst.input === 'object') {
|
|
935
|
+
childInputs[otherInst.instanceId] = { ...otherInst.input };
|
|
936
|
+
if (!childInputs[otherInst.name]) {
|
|
937
|
+
childInputs[otherInst.name] = { ...otherInst.input };
|
|
938
|
+
}
|
|
939
|
+
hasChildInputs = true;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
636
945
|
const requestPayload = {
|
|
637
946
|
pagePath,
|
|
638
947
|
fragments: targets.map((t) => ({
|
|
@@ -641,6 +950,29 @@ function createFragmentCoordinator(options = {}) {
|
|
|
641
950
|
input: t.input || {},
|
|
642
951
|
version: t.reqVersion,
|
|
643
952
|
})),
|
|
953
|
+
...(hasChildInputs ? { childInputs } : {}),
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
for (const t of targets) {
|
|
957
|
+
const inst = instances.get(t.instanceId);
|
|
958
|
+
if (inst) {
|
|
959
|
+
inst.inFlightCount = (inst.inFlightCount || 0) + 1;
|
|
960
|
+
if (inst.el && typeof inst.el.setAttribute === 'function') {
|
|
961
|
+
inst.el.setAttribute('aria-busy', 'true');
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
const decrementTargetBusy = () => {
|
|
967
|
+
for (const t of targets) {
|
|
968
|
+
const inst = instances.get(t.instanceId);
|
|
969
|
+
if (inst) {
|
|
970
|
+
inst.inFlightCount = Math.max(0, (inst.inFlightCount || 1) - 1);
|
|
971
|
+
if (inst.inFlightCount === 0 && inst.el && typeof inst.el.removeAttribute === 'function') {
|
|
972
|
+
inst.el.removeAttribute('aria-busy');
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
644
976
|
};
|
|
645
977
|
|
|
646
978
|
const reqId = uid('frag_req', 4);
|
|
@@ -686,6 +1018,7 @@ function createFragmentCoordinator(options = {}) {
|
|
|
686
1018
|
signal: batchAbortController.signal,
|
|
687
1019
|
});
|
|
688
1020
|
} catch (err) {
|
|
1021
|
+
decrementTargetBusy();
|
|
689
1022
|
if (timeoutTimer) {
|
|
690
1023
|
clearTimeout(timeoutTimer);
|
|
691
1024
|
timeoutTimer = null;
|
|
@@ -766,6 +1099,8 @@ function createFragmentCoordinator(options = {}) {
|
|
|
766
1099
|
}
|
|
767
1100
|
} catch (_e) {
|
|
768
1101
|
body = null;
|
|
1102
|
+
} finally {
|
|
1103
|
+
decrementTargetBusy();
|
|
769
1104
|
}
|
|
770
1105
|
|
|
771
1106
|
if (!response.ok) {
|
|
@@ -800,16 +1135,61 @@ function createFragmentCoordinator(options = {}) {
|
|
|
800
1135
|
debug.trackRequestEnd(reqId, { status: 'completed', httpStatus: response.status });
|
|
801
1136
|
}
|
|
802
1137
|
|
|
803
|
-
|
|
804
|
-
const
|
|
1138
|
+
const rawFragments = (body && body.fragments) || {};
|
|
1139
|
+
const rawErrors = (body && body.errors && typeof body.errors === 'object' ? body.errors : {}) || {};
|
|
1140
|
+
const resultsMap = {};
|
|
1141
|
+
if (isArray(rawFragments)) {
|
|
1142
|
+
for (const item of rawFragments) {
|
|
1143
|
+
if (item && typeof item === 'object') {
|
|
1144
|
+
if (item.instanceId) resultsMap[item.instanceId] = item;
|
|
1145
|
+
if (item.name && !resultsMap[item.name]) resultsMap[item.name] = item;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
} else if (rawFragments && typeof rawFragments === 'object') {
|
|
1149
|
+
Object.assign(resultsMap, rawFragments);
|
|
1150
|
+
}
|
|
805
1151
|
const refreshedIds = [];
|
|
1152
|
+
const batchErrors = [];
|
|
1153
|
+
const failedTargets = [];
|
|
1154
|
+
let staleDiscardCount = 0;
|
|
806
1155
|
let totalLoadMs = 0;
|
|
807
1156
|
let totalRenderMs = 0;
|
|
808
1157
|
|
|
809
1158
|
for (const target of targets) {
|
|
810
1159
|
const inst = instances.get(target.instanceId);
|
|
1160
|
+
const fragError =
|
|
1161
|
+
rawErrors[target.instanceId] ||
|
|
1162
|
+
rawErrors[target.name] ||
|
|
1163
|
+
(resultsMap[target.instanceId] && resultsMap[target.instanceId].error) ||
|
|
1164
|
+
(resultsMap[target.name] && resultsMap[target.name].error) ||
|
|
1165
|
+
null;
|
|
1166
|
+
if (fragError) {
|
|
1167
|
+
const errObj =
|
|
1168
|
+
fragError instanceof Error
|
|
1169
|
+
? fragError
|
|
1170
|
+
: Object.assign(
|
|
1171
|
+
new Error(
|
|
1172
|
+
(fragError && typeof fragError === 'object' && fragError.message) ||
|
|
1173
|
+
String(fragError) ||
|
|
1174
|
+
`Fragment "${target.name}" failed to refresh`
|
|
1175
|
+
),
|
|
1176
|
+
{
|
|
1177
|
+
status: (fragError && fragError.status) || 500,
|
|
1178
|
+
code: (fragError && fragError.code) || 'FRAGMENT_REFRESH_ERROR',
|
|
1179
|
+
instanceId: target.instanceId,
|
|
1180
|
+
fragmentName: target.name,
|
|
1181
|
+
}
|
|
1182
|
+
);
|
|
1183
|
+
batchErrors.push(errObj);
|
|
1184
|
+
failedTargets.push(target);
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
811
1188
|
const fragResult = resultsMap[target.instanceId] || resultsMap[target.name];
|
|
812
1189
|
if (!inst || !fragResult) continue;
|
|
1190
|
+
if (target.reqVersion < inst.version) {
|
|
1191
|
+
staleDiscardCount += 1;
|
|
1192
|
+
}
|
|
813
1193
|
if (isNumber(fragResult.loadMs)) totalLoadMs += fragResult.loadMs;
|
|
814
1194
|
if (isNumber(fragResult.renderMs)) totalRenderMs += fragResult.renderMs;
|
|
815
1195
|
|
|
@@ -817,18 +1197,42 @@ function createFragmentCoordinator(options = {}) {
|
|
|
817
1197
|
inst,
|
|
818
1198
|
fragResult,
|
|
819
1199
|
target.reqVersion,
|
|
820
|
-
chainIds[0] || null
|
|
1200
|
+
chainIds[0] || null,
|
|
1201
|
+
target.input
|
|
821
1202
|
);
|
|
822
1203
|
if (applied) {
|
|
823
1204
|
refreshedIds.push(target.instanceId);
|
|
824
1205
|
}
|
|
825
1206
|
}
|
|
826
1207
|
|
|
1208
|
+
const allSuperseded =
|
|
1209
|
+
refreshedIds.length === 0 &&
|
|
1210
|
+
batchErrors.length === 0 &&
|
|
1211
|
+
staleDiscardCount > 0 &&
|
|
1212
|
+
staleDiscardCount === targets.length;
|
|
1213
|
+
|
|
1214
|
+
if (refreshedIds.length > 0 && lastFailedBatch && isArray(lastFailedBatch.targets)) {
|
|
1215
|
+
const refreshedSet = new Set(refreshedIds);
|
|
1216
|
+
const remainingFailed = lastFailedBatch.targets.filter((t) => !refreshedSet.has(t.instanceId));
|
|
1217
|
+
lastFailedBatch =
|
|
1218
|
+
remainingFailed.length > 0
|
|
1219
|
+
? { targets: remainingFailed, chainIds: lastFailedBatch.chainIds }
|
|
1220
|
+
: null;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
if (batchErrors.length > 0) {
|
|
1224
|
+
lastFailedBatch = {
|
|
1225
|
+
targets: failedTargets,
|
|
1226
|
+
chainIds,
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
|
|
827
1230
|
for (const cid of chainIds) {
|
|
828
1231
|
if (debug) {
|
|
829
1232
|
debug.recordChainRefresh(cid, {
|
|
830
1233
|
fragmentIds: refreshedIds,
|
|
831
|
-
status: 'ok',
|
|
1234
|
+
status: batchErrors.length > 0 ? (refreshedIds.length > 0 ? 'partial-error' : 'error') : allSuperseded ? 'superseded' : 'ok',
|
|
1235
|
+
error: batchErrors[0] || null,
|
|
832
1236
|
loadMs: totalLoadMs,
|
|
833
1237
|
renderMs: totalRenderMs,
|
|
834
1238
|
totalServerMs: body && isNumber(body.totalMs) ? body.totalMs : null,
|
|
@@ -836,6 +1240,34 @@ function createFragmentCoordinator(options = {}) {
|
|
|
836
1240
|
}
|
|
837
1241
|
}
|
|
838
1242
|
|
|
1243
|
+
if (allSuperseded) {
|
|
1244
|
+
return {
|
|
1245
|
+
ok: false,
|
|
1246
|
+
canceled: true,
|
|
1247
|
+
superseded: true,
|
|
1248
|
+
refreshed: [],
|
|
1249
|
+
errors: [],
|
|
1250
|
+
timings: {
|
|
1251
|
+
loadMs: totalLoadMs,
|
|
1252
|
+
renderMs: totalRenderMs,
|
|
1253
|
+
},
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
if (batchErrors.length > 0) {
|
|
1258
|
+
return {
|
|
1259
|
+
ok: false,
|
|
1260
|
+
partial: refreshedIds.length > 0,
|
|
1261
|
+
refreshed: refreshedIds,
|
|
1262
|
+
errors: batchErrors,
|
|
1263
|
+
error: batchErrors[0],
|
|
1264
|
+
timings: {
|
|
1265
|
+
loadMs: totalLoadMs,
|
|
1266
|
+
renderMs: totalRenderMs,
|
|
1267
|
+
},
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
|
|
839
1271
|
return {
|
|
840
1272
|
ok: true,
|
|
841
1273
|
refreshed: refreshedIds,
|
|
@@ -876,13 +1308,18 @@ function createFragmentCoordinator(options = {}) {
|
|
|
876
1308
|
for (const inst of instances.values()) {
|
|
877
1309
|
let matched = false;
|
|
878
1310
|
for (const tag of tagsToFlush) {
|
|
879
|
-
if (inst.dependsOn.has(tag) || inst.name === tag) {
|
|
1311
|
+
if (inst.dependsOn.has(tag) || inst.name === tag || inst.instanceId === tag) {
|
|
880
1312
|
matched = true;
|
|
881
1313
|
break;
|
|
882
1314
|
}
|
|
883
1315
|
}
|
|
884
1316
|
if (matched) {
|
|
885
|
-
|
|
1317
|
+
if (inst.lazy && inst.lazyState === 'idle') {
|
|
1318
|
+
// Do not fetch off-screen unloaded lazy fragments on invalidation; mark stale for when visible
|
|
1319
|
+
inst.staleWhileUnloaded = true;
|
|
1320
|
+
} else {
|
|
1321
|
+
matchingInstances.push(inst);
|
|
1322
|
+
}
|
|
886
1323
|
}
|
|
887
1324
|
}
|
|
888
1325
|
|
|
@@ -1038,34 +1475,73 @@ function createFragmentCoordinator(options = {}) {
|
|
|
1038
1475
|
inst.abortController = abortController;
|
|
1039
1476
|
inst.version += 1;
|
|
1040
1477
|
|
|
1041
|
-
if (
|
|
1042
|
-
inst.
|
|
1043
|
-
|
|
1044
|
-
: { ...inst.input, ...nextInput };
|
|
1045
|
-
if (inst.el && typeof inst.el.setAttribute === 'function') {
|
|
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);
|
|
1053
|
-
}
|
|
1478
|
+
if (inst.lazy && inst.lazyState === 'idle') {
|
|
1479
|
+
inst.lazyState = 'loading';
|
|
1480
|
+
unobserveLazyFragment(inst);
|
|
1054
1481
|
}
|
|
1055
1482
|
|
|
1483
|
+
if (
|
|
1484
|
+
refreshOpts.mode ||
|
|
1485
|
+
refreshOpts.limit !== undefined ||
|
|
1486
|
+
refreshOpts.target ||
|
|
1487
|
+
refreshOpts.removeKeys ||
|
|
1488
|
+
typeof refreshOpts.onBeforePatch === 'function' ||
|
|
1489
|
+
typeof refreshOpts.onAfterPatch === 'function'
|
|
1490
|
+
) {
|
|
1491
|
+
pendingPatchOptsByKey.set(`${inst.instanceId}:${inst.version}`, {
|
|
1492
|
+
mode: refreshOpts.mode || null,
|
|
1493
|
+
limit: refreshOpts.limit,
|
|
1494
|
+
target: refreshOpts.target,
|
|
1495
|
+
removeKeys: refreshOpts.removeKeys,
|
|
1496
|
+
onBeforePatch: refreshOpts.onBeforePatch,
|
|
1497
|
+
onAfterPatch: refreshOpts.onAfterPatch,
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
const candidateInput =
|
|
1502
|
+
nextInput && typeof nextInput === 'object'
|
|
1503
|
+
? refreshOpts.replaceInput
|
|
1504
|
+
? { ...nextInput }
|
|
1505
|
+
: { ...inst.input, ...nextInput }
|
|
1506
|
+
: { ...inst.input };
|
|
1507
|
+
|
|
1056
1508
|
return {
|
|
1057
1509
|
instanceId: inst.instanceId,
|
|
1058
1510
|
name: inst.name,
|
|
1059
1511
|
pagePath: inst.pagePath,
|
|
1060
|
-
input:
|
|
1512
|
+
input: candidateInput,
|
|
1061
1513
|
reqVersion: inst.version,
|
|
1062
1514
|
};
|
|
1063
1515
|
});
|
|
1064
1516
|
|
|
1517
|
+
const clearOwnedAbortControllers = () => {
|
|
1518
|
+
for (const inst of deduped) {
|
|
1519
|
+
if (inst.abortController === abortController) {
|
|
1520
|
+
inst.abortController = null;
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
};
|
|
1524
|
+
|
|
1065
1525
|
try {
|
|
1066
1526
|
const result = await executeBatchRefresh(targets, [], abortController.signal, refreshOpts.timeout);
|
|
1527
|
+
clearOwnedAbortControllers();
|
|
1067
1528
|
const enrichedResult = { ok: true, ...result };
|
|
1068
|
-
if (!enrichedResult.canceled && enrichedResult.
|
|
1529
|
+
if (enrichedResult.ok === false && !enrichedResult.canceled && !enrichedResult.superseded && enrichedResult.error) {
|
|
1530
|
+
const failHandler = refreshOpts.onFailure || refreshOpts.onError;
|
|
1531
|
+
if (typeof failHandler === 'function') {
|
|
1532
|
+
await failHandler(enrichedResult.error);
|
|
1533
|
+
}
|
|
1534
|
+
if (typeof refreshOpts.onSettled === 'function') {
|
|
1535
|
+
await refreshOpts.onSettled(enrichedResult, enrichedResult.error);
|
|
1536
|
+
} else if (typeof refreshOpts.onComplete === 'function') {
|
|
1537
|
+
await refreshOpts.onComplete(enrichedResult, enrichedResult.error);
|
|
1538
|
+
}
|
|
1539
|
+
if (refreshOpts.throwOnError === true) {
|
|
1540
|
+
throw enrichedResult.error;
|
|
1541
|
+
}
|
|
1542
|
+
return enrichedResult;
|
|
1543
|
+
}
|
|
1544
|
+
if (!enrichedResult.canceled && !enrichedResult.superseded && enrichedResult.ok !== false) {
|
|
1069
1545
|
if (typeof refreshOpts.onSuccess === 'function') {
|
|
1070
1546
|
await refreshOpts.onSuccess(enrichedResult);
|
|
1071
1547
|
}
|
|
@@ -1077,6 +1553,7 @@ function createFragmentCoordinator(options = {}) {
|
|
|
1077
1553
|
}
|
|
1078
1554
|
return enrichedResult;
|
|
1079
1555
|
} catch (err) {
|
|
1556
|
+
clearOwnedAbortControllers();
|
|
1080
1557
|
const failHandler = refreshOpts.onFailure || refreshOpts.onError;
|
|
1081
1558
|
if (typeof failHandler === 'function') {
|
|
1082
1559
|
try {
|
|
@@ -1121,7 +1598,7 @@ function createFragmentCoordinator(options = {}) {
|
|
|
1121
1598
|
inst.version += 1;
|
|
1122
1599
|
retryTargets.push({
|
|
1123
1600
|
...t,
|
|
1124
|
-
input: { ...inst.input },
|
|
1601
|
+
input: t.input ? { ...t.input } : { ...inst.input },
|
|
1125
1602
|
reqVersion: inst.version,
|
|
1126
1603
|
});
|
|
1127
1604
|
}
|
|
@@ -1134,6 +1611,24 @@ function createFragmentCoordinator(options = {}) {
|
|
|
1134
1611
|
return executeBatchRefresh(retryTargets, chainIds);
|
|
1135
1612
|
}
|
|
1136
1613
|
|
|
1614
|
+
function resolveInstanceByIdOrName(idOrName) {
|
|
1615
|
+
if (!idOrName) return null;
|
|
1616
|
+
const direct = instances.get(idOrName);
|
|
1617
|
+
if (direct && (!direct.el || direct.el.isConnected !== false)) {
|
|
1618
|
+
return direct;
|
|
1619
|
+
}
|
|
1620
|
+
const defaultInst = instances.get(`${idOrName}:default`);
|
|
1621
|
+
if (defaultInst && (!defaultInst.el || defaultInst.el.isConnected !== false)) {
|
|
1622
|
+
return defaultInst;
|
|
1623
|
+
}
|
|
1624
|
+
for (const inst of instances.values()) {
|
|
1625
|
+
if (inst.name === idOrName && (!inst.el || inst.el.isConnected !== false)) {
|
|
1626
|
+
return inst;
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
return null;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1137
1632
|
return {
|
|
1138
1633
|
syncDomInstances,
|
|
1139
1634
|
invalidate,
|
|
@@ -1145,17 +1640,28 @@ function createFragmentCoordinator(options = {}) {
|
|
|
1145
1640
|
scanDomInstances();
|
|
1146
1641
|
for (const item of items) {
|
|
1147
1642
|
const key = item.instanceId || item.name;
|
|
1148
|
-
const
|
|
1149
|
-
if (
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1643
|
+
const matchedList = [];
|
|
1644
|
+
if (item.instanceId && instances.has(item.instanceId)) {
|
|
1645
|
+
matchedList.push(instances.get(item.instanceId));
|
|
1646
|
+
} else {
|
|
1647
|
+
for (const inst of instances.values()) {
|
|
1648
|
+
if (inst.instanceId === key || inst.name === key) {
|
|
1649
|
+
matchedList.push(inst);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
if (matchedList.length > 0) {
|
|
1654
|
+
for (const inst of matchedList) {
|
|
1655
|
+
if (item.input && typeof item.input === 'object') {
|
|
1656
|
+
inst.input = { ...item.input };
|
|
1657
|
+
if (inst.el && typeof inst.el.setAttribute === 'function') {
|
|
1658
|
+
const serializedInput = JSON.stringify(inst.input);
|
|
1659
|
+
inst.el.setAttribute('data-w-fragment-input', serializedInput);
|
|
1660
|
+
inst._rawInput = serializedInput;
|
|
1661
|
+
}
|
|
1156
1662
|
}
|
|
1663
|
+
pendingTags.add(item.instanceId ? inst.instanceId : inst.name);
|
|
1157
1664
|
}
|
|
1158
|
-
pendingTags.add(inst.name);
|
|
1159
1665
|
} else if (key) {
|
|
1160
1666
|
pendingTags.add(String(key).trim());
|
|
1161
1667
|
}
|
|
@@ -1168,7 +1674,7 @@ function createFragmentCoordinator(options = {}) {
|
|
|
1168
1674
|
const matching = [];
|
|
1169
1675
|
for (const inst of instances.values()) {
|
|
1170
1676
|
for (const t of tagSet) {
|
|
1171
|
-
if (inst.dependsOn.has(t) || inst.name === t) {
|
|
1677
|
+
if (inst.dependsOn.has(t) || inst.name === t || inst.instanceId === t) {
|
|
1172
1678
|
matching.push(inst);
|
|
1173
1679
|
break;
|
|
1174
1680
|
}
|
|
@@ -1178,24 +1684,38 @@ function createFragmentCoordinator(options = {}) {
|
|
|
1178
1684
|
},
|
|
1179
1685
|
retryFailed,
|
|
1180
1686
|
retryLastFailed: retryFailed,
|
|
1181
|
-
getInstance(
|
|
1182
|
-
const hit =
|
|
1183
|
-
if (hit
|
|
1687
|
+
getInstance(instanceIdOrName) {
|
|
1688
|
+
const hit = resolveInstanceByIdOrName(instanceIdOrName);
|
|
1689
|
+
if (hit) {
|
|
1184
1690
|
return hit;
|
|
1185
1691
|
}
|
|
1186
1692
|
scanDomInstances();
|
|
1187
|
-
return
|
|
1693
|
+
return resolveInstanceByIdOrName(instanceIdOrName);
|
|
1188
1694
|
},
|
|
1189
|
-
getInstances() {
|
|
1695
|
+
getInstances(nameOrId) {
|
|
1190
1696
|
scanDomInstances();
|
|
1191
|
-
|
|
1697
|
+
const all = Array.from(instances.values());
|
|
1698
|
+
if (!nameOrId) return all;
|
|
1699
|
+
return all.filter((inst) => inst.instanceId === nameOrId || inst.name === nameOrId);
|
|
1192
1700
|
},
|
|
1193
1701
|
getStoredInput(instanceIdOrName, storageType = 'session') {
|
|
1194
1702
|
const store = storageType === 'local' ? defaultStorage.local : defaultStorage.session;
|
|
1195
|
-
|
|
1703
|
+
const direct = store.get(`frag_input:${instanceIdOrName}`, null);
|
|
1704
|
+
if (direct !== null) return direct;
|
|
1705
|
+
const inst = instances.get(instanceIdOrName) || Array.from(instances.values()).find((i) => i.name === instanceIdOrName);
|
|
1706
|
+
if (inst && inst.persistInput) {
|
|
1707
|
+
const instStore = inst.persistInput.type === 'local' ? defaultStorage.local : defaultStorage.session;
|
|
1708
|
+
return instStore.get(inst.persistInput.key, null);
|
|
1709
|
+
}
|
|
1710
|
+
return null;
|
|
1196
1711
|
},
|
|
1197
1712
|
clearStoredInput(instanceIdOrName, storageType = 'session') {
|
|
1198
1713
|
const store = storageType === 'local' ? defaultStorage.local : defaultStorage.session;
|
|
1714
|
+
const inst = instances.get(instanceIdOrName) || Array.from(instances.values()).find((i) => i.name === instanceIdOrName);
|
|
1715
|
+
if (inst && inst.persistInput) {
|
|
1716
|
+
const instStore = inst.persistInput.type === 'local' ? defaultStorage.local : defaultStorage.session;
|
|
1717
|
+
instStore.remove(inst.persistInput.key);
|
|
1718
|
+
}
|
|
1199
1719
|
return store.remove(`frag_input:${instanceIdOrName}`);
|
|
1200
1720
|
},
|
|
1201
1721
|
get hasFailedRefresh() {
|
|
@@ -1207,7 +1727,19 @@ function createFragmentCoordinator(options = {}) {
|
|
|
1207
1727
|
clearTimeout(focusDebounceTimer);
|
|
1208
1728
|
focusDebounceTimer = null;
|
|
1209
1729
|
}
|
|
1730
|
+
if (autoBatchTimer) {
|
|
1731
|
+
clearTimeout(autoBatchTimer);
|
|
1732
|
+
autoBatchTimer = null;
|
|
1733
|
+
}
|
|
1210
1734
|
pendingAutoInstanceIds.clear();
|
|
1735
|
+
pendingAutoReasons.clear();
|
|
1736
|
+
pendingPatchOptsByKey.clear();
|
|
1737
|
+
if (lazyObserver && typeof lazyObserver.disconnect === 'function') {
|
|
1738
|
+
try {
|
|
1739
|
+
lazyObserver.disconnect();
|
|
1740
|
+
} catch (_e) {}
|
|
1741
|
+
lazyObserver = null;
|
|
1742
|
+
}
|
|
1211
1743
|
removeLifecycleListeners();
|
|
1212
1744
|
for (const inst of instances.values()) {
|
|
1213
1745
|
clearInstanceInterval(inst);
|