@jskit-ai/shell-web 0.1.89 → 0.1.90
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/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/shell-web",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.90",
|
|
5
5
|
kind: "runtime",
|
|
6
6
|
description: "Web shell layout runtime with outlet-based placement contributions.",
|
|
7
7
|
dependsOn: [],
|
|
@@ -300,7 +300,7 @@ export default Object.freeze({
|
|
|
300
300
|
dependencies: {
|
|
301
301
|
runtime: {
|
|
302
302
|
"@mdi/js": "^7.4.47",
|
|
303
|
-
"@jskit-ai/kernel": "0.1.
|
|
303
|
+
"@jskit-ai/kernel": "0.1.91"
|
|
304
304
|
},
|
|
305
305
|
dev: {}
|
|
306
306
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/shell-web",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.90",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@mdi/js": "^7.4.47",
|
|
31
|
-
"@jskit-ai/kernel": "0.1.
|
|
31
|
+
"@jskit-ai/kernel": "0.1.91"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"pinia": "^3.0.4",
|
|
@@ -31,6 +31,11 @@ const CANCELED_REQUEST_ERROR_CODES = Object.freeze(new Set([
|
|
|
31
31
|
"ERR_CANCELED"
|
|
32
32
|
]));
|
|
33
33
|
|
|
34
|
+
const SAFE_REQUEST_RECOVERY_METHODS = Object.freeze(new Set([
|
|
35
|
+
"GET",
|
|
36
|
+
"HEAD"
|
|
37
|
+
]));
|
|
38
|
+
|
|
34
39
|
function normalizeText(value, fallback = "") {
|
|
35
40
|
const normalized = String(value || "").trim();
|
|
36
41
|
return normalized || String(fallback || "").trim();
|
|
@@ -142,6 +147,56 @@ function resolveQueryRecoveryLabel(query = null) {
|
|
|
142
147
|
);
|
|
143
148
|
}
|
|
144
149
|
|
|
150
|
+
function resolveQueryRecoverySource(query = null) {
|
|
151
|
+
const meta = resolveQueryMeta(query);
|
|
152
|
+
const jskitMeta = isRecord(meta.jskit) ? meta.jskit : {};
|
|
153
|
+
|
|
154
|
+
return normalizeText(
|
|
155
|
+
jskitMeta.requestRecoverySource ||
|
|
156
|
+
meta.jskitRequestRecoverySource ||
|
|
157
|
+
meta.requestRecoverySource,
|
|
158
|
+
"shell-web.request-recovery.query"
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function resolveQueryRecoveryDedupeKey(query = null, fallback = "") {
|
|
163
|
+
const meta = resolveQueryMeta(query);
|
|
164
|
+
const jskitMeta = isRecord(meta.jskit) ? meta.jskit : {};
|
|
165
|
+
|
|
166
|
+
return normalizeText(
|
|
167
|
+
jskitMeta.requestRecoveryDedupeKey ||
|
|
168
|
+
meta.jskitRequestRecoveryDedupeKey ||
|
|
169
|
+
meta.requestRecoveryDedupeKey,
|
|
170
|
+
fallback
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function resolveQueryRecoveryDedupeWindowMs(query = null) {
|
|
175
|
+
const meta = resolveQueryMeta(query);
|
|
176
|
+
const jskitMeta = isRecord(meta.jskit) ? meta.jskit : {};
|
|
177
|
+
const value =
|
|
178
|
+
jskitMeta.requestRecoveryDedupeWindowMs ??
|
|
179
|
+
meta.jskitRequestRecoveryDedupeWindowMs ??
|
|
180
|
+
meta.requestRecoveryDedupeWindowMs;
|
|
181
|
+
|
|
182
|
+
return Number.isFinite(Number(value)) ? Math.max(0, Number(value)) : null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function resolveQueryRecoveryMethod(query = null) {
|
|
186
|
+
const meta = resolveQueryMeta(query);
|
|
187
|
+
const jskitMeta = isRecord(meta.jskit) ? meta.jskit : {};
|
|
188
|
+
|
|
189
|
+
return normalizeText(
|
|
190
|
+
jskitMeta.requestRecoveryMethod ||
|
|
191
|
+
meta.jskitRequestRecoveryMethod ||
|
|
192
|
+
meta.requestRecoveryMethod
|
|
193
|
+
).toUpperCase();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isSafeQueryRecoveryMethod(query = null) {
|
|
197
|
+
return SAFE_REQUEST_RECOVERY_METHODS.has(resolveQueryRecoveryMethod(query));
|
|
198
|
+
}
|
|
199
|
+
|
|
145
200
|
function isActiveQuery(query = null) {
|
|
146
201
|
if (typeof query?.isActive === "function") {
|
|
147
202
|
return Boolean(query.isActive());
|
|
@@ -223,7 +278,12 @@ function installRecoverableQueryObserver({
|
|
|
223
278
|
const reportedErrorUpdateByQueryHash = new Map();
|
|
224
279
|
|
|
225
280
|
function inspectQuery(query = null) {
|
|
226
|
-
if (
|
|
281
|
+
if (
|
|
282
|
+
!query ||
|
|
283
|
+
isRequestRecoveryDisabled(query) ||
|
|
284
|
+
!isSafeQueryRecoveryMethod(query) ||
|
|
285
|
+
!isActiveQuery(query)
|
|
286
|
+
) {
|
|
227
287
|
return;
|
|
228
288
|
}
|
|
229
289
|
|
|
@@ -241,12 +301,19 @@ function installRecoverableQueryObserver({
|
|
|
241
301
|
}
|
|
242
302
|
reportedErrorUpdateByQueryHash.set(queryHash, reportKey);
|
|
243
303
|
|
|
304
|
+
const source = resolveQueryRecoverySource(query);
|
|
305
|
+
const dedupeKey = resolveQueryRecoveryDedupeKey(
|
|
306
|
+
query,
|
|
307
|
+
`shell-web.request-recovery.query.${queryHash}.${errorUpdateCount}`
|
|
308
|
+
);
|
|
309
|
+
const dedupeWindowMs = resolveQueryRecoveryDedupeWindowMs(query);
|
|
244
310
|
runtime.report(error, {
|
|
245
311
|
label: resolveQueryRecoveryLabel(query),
|
|
246
312
|
retry: createQueryRetry(queryClient, query),
|
|
247
|
-
source
|
|
313
|
+
source,
|
|
248
314
|
stale: query?.state?.data !== undefined,
|
|
249
|
-
dedupeKey
|
|
315
|
+
dedupeKey,
|
|
316
|
+
...(dedupeWindowMs !== null ? { dedupeWindowMs } : {})
|
|
250
317
|
});
|
|
251
318
|
}
|
|
252
319
|
|
package/test/provider.test.js
CHANGED
|
@@ -371,13 +371,21 @@ test("shell request recovery reports active query transport failures with retry
|
|
|
371
371
|
const provider = new ShellWebClientProvider();
|
|
372
372
|
provider.register(app);
|
|
373
373
|
await provider.boot(app);
|
|
374
|
+
const reportEvents = [];
|
|
375
|
+
app.make("runtime.web-error.client").subscribe((event = {}) => {
|
|
376
|
+
reportEvents.push(event);
|
|
377
|
+
});
|
|
374
378
|
|
|
375
379
|
const failedQuery = {
|
|
376
380
|
queryHash: "[\"project-access\"]",
|
|
377
381
|
queryKey: ["project-access"],
|
|
378
382
|
meta: {
|
|
379
383
|
jskit: {
|
|
380
|
-
requestRecoveryLabel: "Project access"
|
|
384
|
+
requestRecoveryLabel: "Project access",
|
|
385
|
+
requestRecoverySource: "project-access.panel",
|
|
386
|
+
requestRecoveryDedupeKey: "project-access",
|
|
387
|
+
requestRecoveryMethod: "GET",
|
|
388
|
+
requestRecoveryDedupeWindowMs: 100
|
|
381
389
|
}
|
|
382
390
|
},
|
|
383
391
|
state: {
|
|
@@ -403,6 +411,9 @@ test("shell request recovery reports active query transport failures with retry
|
|
|
403
411
|
"Project access could not reach the server or network. Check the connection and try again."
|
|
404
412
|
);
|
|
405
413
|
assert.equal(state.channels.banner[0].action.label, "Retry");
|
|
414
|
+
assert.equal(state.channels.banner[0].dedupeKey, "project-access");
|
|
415
|
+
assert.equal(reportEvents[0]?.result?.event?.source, "project-access.panel");
|
|
416
|
+
assert.equal(reportEvents[0]?.result?.decision?.dedupeWindowMs, 100);
|
|
406
417
|
|
|
407
418
|
await state.channels.banner[0].action.handler();
|
|
408
419
|
assert.equal(refetchCalls.length, 1);
|
|
@@ -417,6 +428,55 @@ test("shell request recovery reports active query transport failures with retry
|
|
|
417
428
|
});
|
|
418
429
|
});
|
|
419
430
|
|
|
431
|
+
test("shell request recovery ignores query transport failures without a safe read method", async () => {
|
|
432
|
+
await withFetchStub({ surfaceAccess: {} }, async () => {
|
|
433
|
+
const queryCache = createQueryCacheDouble();
|
|
434
|
+
const queryClient = {
|
|
435
|
+
getQueryCache() {
|
|
436
|
+
return queryCache;
|
|
437
|
+
},
|
|
438
|
+
async refetchQueries() {}
|
|
439
|
+
};
|
|
440
|
+
const app = createAppDouble({ queryClient });
|
|
441
|
+
const provider = new ShellWebClientProvider();
|
|
442
|
+
provider.register(app);
|
|
443
|
+
await provider.boot(app);
|
|
444
|
+
|
|
445
|
+
for (const [queryKey, meta] of [
|
|
446
|
+
[["unmarked"], {}],
|
|
447
|
+
[["unsafe"], {
|
|
448
|
+
jskit: {
|
|
449
|
+
requestRecoveryMethod: "POST"
|
|
450
|
+
}
|
|
451
|
+
}]
|
|
452
|
+
]) {
|
|
453
|
+
queryCache.emit({
|
|
454
|
+
type: "updated",
|
|
455
|
+
query: {
|
|
456
|
+
queryHash: JSON.stringify(queryKey),
|
|
457
|
+
queryKey,
|
|
458
|
+
meta,
|
|
459
|
+
state: {
|
|
460
|
+
status: "error",
|
|
461
|
+
fetchStatus: "idle",
|
|
462
|
+
error: {
|
|
463
|
+
status: 0,
|
|
464
|
+
message: "Network request failed."
|
|
465
|
+
},
|
|
466
|
+
errorUpdateCount: 1
|
|
467
|
+
},
|
|
468
|
+
isActive() {
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const errorStore = app.make("runtime.web-error.presentation-store.client");
|
|
476
|
+
assert.equal(errorStore.getState().channels.banner.length, 0);
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
|
|
420
480
|
test("shell request recovery ignores ordinary active query validation failures", async () => {
|
|
421
481
|
await withFetchStub({ surfaceAccess: {} }, async () => {
|
|
422
482
|
const queryCache = createQueryCacheDouble();
|
|
@@ -433,10 +493,15 @@ test("shell request recovery ignores ordinary active query validation failures",
|
|
|
433
493
|
|
|
434
494
|
queryCache.emit({
|
|
435
495
|
type: "updated",
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
496
|
+
query: {
|
|
497
|
+
queryHash: "[\"project-access\"]",
|
|
498
|
+
queryKey: ["project-access"],
|
|
499
|
+
meta: {
|
|
500
|
+
jskit: {
|
|
501
|
+
requestRecoveryMethod: "GET"
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
state: {
|
|
440
505
|
status: "error",
|
|
441
506
|
fetchStatus: "idle",
|
|
442
507
|
error: {
|