@jskit-ai/http-web 0.1.58 → 0.1.60
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.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/http-web",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.60",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -44,14 +44,14 @@
|
|
|
44
44
|
"./client/support/contractGuards": "./src/client/support/contractGuards.js"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@jskit-ai/resource-crud-core": "0.1.
|
|
47
|
+
"@jskit-ai/resource-crud-core": "0.1.157",
|
|
48
48
|
"@mdi/js": "^7.4.47"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
52
|
-
"@jskit-ai/kernel": "0.1.
|
|
53
|
-
"@jskit-ai/realtime": "0.1.
|
|
54
|
-
"@jskit-ai/shell-web": "0.1.
|
|
51
|
+
"@jskit-ai/http-runtime": "0.1.213",
|
|
52
|
+
"@jskit-ai/kernel": "0.1.215",
|
|
53
|
+
"@jskit-ai/realtime": "0.1.212",
|
|
54
|
+
"@jskit-ai/shell-web": "0.1.219",
|
|
55
55
|
"@tanstack/vue-query": "^5.90.5",
|
|
56
56
|
"vue": "^3.5.13",
|
|
57
57
|
"vue-router": "^5.0.4",
|
|
@@ -91,7 +91,7 @@ function useEndpointResource({
|
|
|
91
91
|
|
|
92
92
|
const query = useQuery({
|
|
93
93
|
queryKey,
|
|
94
|
-
queryFn: () => {
|
|
94
|
+
queryFn: ({ signal }) => {
|
|
95
95
|
const requestPath = normalizedPath.value;
|
|
96
96
|
if (!requestPath) {
|
|
97
97
|
throw new Error("Resource path is required.");
|
|
@@ -102,7 +102,8 @@ function useEndpointResource({
|
|
|
102
102
|
method: readMethod,
|
|
103
103
|
query: readQuery,
|
|
104
104
|
transport
|
|
105
|
-
})
|
|
105
|
+
}),
|
|
106
|
+
signal
|
|
106
107
|
});
|
|
107
108
|
},
|
|
108
109
|
enabled: queryEnabled,
|
|
@@ -79,20 +79,15 @@ function useListCore({
|
|
|
79
79
|
queryKey,
|
|
80
80
|
initialPageParam,
|
|
81
81
|
enabled: queryEnabled,
|
|
82
|
-
queryFn: async ({ pageParam }) => {
|
|
82
|
+
queryFn: async ({ pageParam, signal }) => {
|
|
83
83
|
const requestPath = normalizedPath.value;
|
|
84
84
|
if (!requestPath) {
|
|
85
85
|
throw new Error("List path is required.");
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
requestOptions,
|
|
92
|
-
transport,
|
|
93
|
-
pageParam
|
|
94
|
-
})
|
|
95
|
-
);
|
|
88
|
+
const options = buildListRequestOptions({ requestOptions, transport, pageParam });
|
|
89
|
+
options.signal = options.signal ? AbortSignal.any([signal, options.signal]) : signal;
|
|
90
|
+
return activeClient.request(requestPath, options);
|
|
96
91
|
},
|
|
97
92
|
getNextPageParam,
|
|
98
93
|
selectItems,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
configureHttpWebClient,
|
|
15
15
|
resetHttpWebClientForTests
|
|
16
16
|
} from "../src/client/lib/httpClient.js";
|
|
17
|
-
import { buildListRequestOptions } from "../src/client/composables/runtime/useListCore.js";
|
|
17
|
+
import { buildListRequestOptions, useListCore } from "../src/client/composables/runtime/useListCore.js";
|
|
18
18
|
import {
|
|
19
19
|
resolveOperationRealtimeOptions
|
|
20
20
|
} from "../src/client/composables/useRealtimeQueryInvalidation.js";
|
|
@@ -293,6 +293,75 @@ test("endpoint resource reads attach request recovery metadata to query options"
|
|
|
293
293
|
});
|
|
294
294
|
});
|
|
295
295
|
|
|
296
|
+
for (const kind of ["endpoint", "list", "list with caller signal"]) {
|
|
297
|
+
test(`repeated ${kind} refreshes abort superseded HTTP requests`, async (t) => {
|
|
298
|
+
const paginated = kind !== "endpoint";
|
|
299
|
+
const caller = kind === "list with caller signal" ? new AbortController() : null;
|
|
300
|
+
const queryClient = new QueryClient();
|
|
301
|
+
const queryKey = ["endpoint-cancellation"];
|
|
302
|
+
const requests = [];
|
|
303
|
+
const active = new Set();
|
|
304
|
+
let resource;
|
|
305
|
+
const payload = (revision) => paginated
|
|
306
|
+
? { pages: [{ items: [revision] }], pageParams: [null] }
|
|
307
|
+
: { revision };
|
|
308
|
+
queryClient.setQueryData(queryKey, payload(0));
|
|
309
|
+
const app = createSSRApp({
|
|
310
|
+
setup() {
|
|
311
|
+
resource = (paginated ? useListCore : useEndpointResource)({
|
|
312
|
+
queryKey,
|
|
313
|
+
path: "/api/endpoint-cancellation",
|
|
314
|
+
requestOptions: caller ? { signal: caller.signal } : null,
|
|
315
|
+
queryOptions: { staleTime: Infinity, retry: false },
|
|
316
|
+
client: {
|
|
317
|
+
request(_path, { signal }) {
|
|
318
|
+
return new Promise((resolve, reject) => {
|
|
319
|
+
const request = { signal, resolve };
|
|
320
|
+
requests.push(request);
|
|
321
|
+
active.add(request);
|
|
322
|
+
signal?.addEventListener("abort", () => {
|
|
323
|
+
active.delete(request);
|
|
324
|
+
reject(new DOMException("Query cancelled", "AbortError"));
|
|
325
|
+
}, { once: true });
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
return () => h("div");
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
app.use(VueQueryPlugin, { queryClient });
|
|
334
|
+
t.after(() => {
|
|
335
|
+
queryClient.clear();
|
|
336
|
+
for (const request of requests) request.resolve({ revision: -1 });
|
|
337
|
+
});
|
|
338
|
+
await renderToString(app);
|
|
339
|
+
|
|
340
|
+
const refreshes = [];
|
|
341
|
+
for (let revision = 1; revision <= 10; revision += 1) {
|
|
342
|
+
// SSR has no mounted observer; include its cached query in invalidation.
|
|
343
|
+
refreshes.push(revision % 2 === 0
|
|
344
|
+
? queryClient.invalidateQueries({ queryKey, refetchType: "all" })
|
|
345
|
+
: resource.reload());
|
|
346
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
347
|
+
assert.equal(requests.length, revision);
|
|
348
|
+
assert.equal(active.size, 1, "only the latest refresh should keep an HTTP request alive");
|
|
349
|
+
}
|
|
350
|
+
assert.equal(requests[0].signal.aborted, true);
|
|
351
|
+
assert.equal(requests.at(-1).signal.aborted, false);
|
|
352
|
+
requests.at(-1).resolve(paginated ? { items: [10] } : { revision: 10 });
|
|
353
|
+
await Promise.all(refreshes);
|
|
354
|
+
assert.deepEqual(queryClient.getQueryData(queryKey), payload(10));
|
|
355
|
+
if (caller) {
|
|
356
|
+
const pending = resource.reload();
|
|
357
|
+
caller.abort();
|
|
358
|
+
assert.equal(requests.at(-1).signal.aborted, true);
|
|
359
|
+
await pending;
|
|
360
|
+
assert.equal(resource.query.error.value.name, "AbortError");
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
296
365
|
test("endpoint resources use the configured http-web HTTP client by default", async () => {
|
|
297
366
|
const queryClient = new QueryClient();
|
|
298
367
|
const calls = [];
|
|
@@ -256,10 +256,14 @@ test("route-synchronized lists make one initial request with the hydrated route
|
|
|
256
256
|
});
|
|
257
257
|
|
|
258
258
|
assert.equal(runtime.calls.length, 1);
|
|
259
|
+
const { signal } = runtime.calls[0].options;
|
|
260
|
+
assert.ok(signal instanceof AbortSignal);
|
|
261
|
+
assert.equal(signal.aborted, false);
|
|
259
262
|
assert.deepEqual(runtime.calls[0], {
|
|
260
263
|
path: "/contacts",
|
|
261
264
|
options: {
|
|
262
265
|
method: "GET",
|
|
266
|
+
signal,
|
|
263
267
|
query: {
|
|
264
268
|
currentness: "archived",
|
|
265
269
|
limit: 20
|