@kubun/http-client 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.ts CHANGED
@@ -1,7 +1,21 @@
1
+ import type { RequestCall } from '@enkaku/client';
1
2
  import { type ClientParams as BaseClientParams, KubunClient } from '@kubun/client';
2
- export type ClientParams = Omit<BaseClientParams, 'transport'> & {
3
+ import type { ExecuteGraphParams, ExecuteGraphResult, ListGraphResult, LoadGraphParams, LoadGraphResult } from '@kubun/protocol';
4
+ export type ClientParams = Omit<BaseClientParams, 'transport' | 'handleTransportError'> & {
3
5
  url: string;
4
6
  };
7
+ export { GraphSubscriptionFailedError, GraphSubscriptionInterruptedError } from '@kubun/client';
8
+ /**
9
+ * A Kubun client over HTTP. A network failure errors an Enkaku HTTP transport for good,
10
+ * so the client replaces it with a fresh one to the same URL; an HTTP error status or an
11
+ * error reply fails only its own request and keeps the transport. Reads (`graph/query`,
12
+ * `graph/load`, `graph/list`) whose transport died are sent once more on the new one;
13
+ * nothing else is, since a failed send does not tell whether the server ran it.
14
+ */
5
15
  export declare class HTTPClient extends KubunClient {
16
+ #private;
6
17
  constructor(params: ClientParams);
18
+ listGraphs(signal?: AbortSignal): RequestCall<ListGraphResult>;
19
+ loadGraph(param: LoadGraphParams, signal?: AbortSignal): RequestCall<LoadGraphResult>;
20
+ queryGraph<Data extends Record<string, unknown> = Record<string, unknown>>(param: ExecuteGraphParams, signal?: AbortSignal): RequestCall<ExecuteGraphResult<Data>>;
7
21
  }
package/lib/index.js CHANGED
@@ -1,14 +1,68 @@
1
1
  import { ClientTransport } from '@enkaku/http-fetch';
2
2
  import { KubunClient } from '@kubun/client';
3
- export class HTTPClient extends KubunClient {
3
+ export { GraphSubscriptionFailedError, GraphSubscriptionInterruptedError } from '@kubun/client';
4
+ // The client swaps transports from its read loop, some turns after the failed send
5
+ // rejects: one macrotask lets that settle before the swap is checked.
6
+ const afterSwap = ()=>new Promise((resolve)=>setTimeout(resolve, 0));
7
+ /**
8
+ * A Kubun client over HTTP. A network failure errors an Enkaku HTTP transport for good,
9
+ * so the client replaces it with a fresh one to the same URL; an HTTP error status or an
10
+ * error reply fails only its own request and keeps the transport. Reads (`graph/query`,
11
+ * `graph/load`, `graph/list`) whose transport died are sent once more on the new one;
12
+ * nothing else is, since a failed send does not tell whether the server ran it.
13
+ */ export class HTTPClient extends KubunClient {
14
+ #transport;
4
15
  constructor(params){
5
16
  const { url, ...rest } = params;
6
- const transport = new ClientTransport({
7
- url
8
- });
17
+ const slot = {
18
+ current: new ClientTransport({
19
+ url
20
+ })
21
+ };
9
22
  super({
10
23
  ...rest,
11
- transport
24
+ transport: slot.current,
25
+ // Runs only when the transport's readable failed: a send that never reached the
26
+ // server, or a dropped event-stream session. Nothing is sent from here, so a server
27
+ // that stays down costs one transport per failed call.
28
+ handleTransportError: ()=>{
29
+ const dead = slot.current;
30
+ slot.current = new ClientTransport({
31
+ url
32
+ });
33
+ void dead.dispose().catch(()=>{});
34
+ return slot.current;
35
+ }
36
+ });
37
+ this.#transport = slot;
38
+ }
39
+ listGraphs(signal) {
40
+ return this.#repeatable(()=>super.listGraphs(signal));
41
+ }
42
+ loadGraph(param, signal) {
43
+ return this.#repeatable(()=>super.loadGraph(param, signal));
44
+ }
45
+ queryGraph(param, signal) {
46
+ // `graph/query` refuses mutation operations server-side, so it is a read.
47
+ return this.#repeatable(()=>super.queryGraph(param, signal));
48
+ }
49
+ #repeatable(send) {
50
+ const transport = this.#transport.current;
51
+ const first = send();
52
+ let current = first;
53
+ const result = first.catch(async (error)=>{
54
+ await afterSwap();
55
+ // Still the same transport: an HTTP status, an error reply, a timeout or an abort.
56
+ if (this.#transport.current === transport) throw error;
57
+ current = send();
58
+ return await current;
59
+ });
60
+ return Object.assign(result, {
61
+ id: first.id,
62
+ type: first.type,
63
+ procedure: first.procedure,
64
+ signal: first.signal,
65
+ abort: (reason)=>current.abort(reason)
12
66
  });
13
67
  }
14
68
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/http-client",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "keywords": [],
5
5
  "license": "see LICENSE.md",
6
6
  "sideEffects": false,
@@ -15,13 +15,19 @@
15
15
  "LICENSE.md"
16
16
  ],
17
17
  "dependencies": {
18
- "@enkaku/http-fetch": "^0.21.0",
19
- "@kubun/client": "^0.17.0"
18
+ "@enkaku/http-fetch": "^0.21.5",
19
+ "@kubun/client": "^0.18.0"
20
20
  },
21
21
  "devDependencies": {
22
- "@kokuin/token": "^0.5.0",
23
- "@kubun/logger": "^0.17.0",
24
- "@kubun/protocol": "^0.17.0"
22
+ "@enkaku/client": "^0.21.5",
23
+ "@enkaku/http-serve": "^0.21.5",
24
+ "@kokuin/token": "^0.5.1",
25
+ "@kubun/engine": "^0.18.0",
26
+ "@kubun/logger": "^0.18.0",
27
+ "@kubun/plugin-service-server": "^0.18.0",
28
+ "@kubun/protocol": "^0.18.0",
29
+ "@kubun/service-graph-api": "^0.18.0",
30
+ "@kubun/test-utils": "^0.0.0"
25
31
  },
26
32
  "publishConfig": {
27
33
  "access": "public"