@signal-spec/react-client 0.1.0 → 0.2.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/dist/index.d.mts CHANGED
@@ -1,21 +1,23 @@
1
1
  import * as SignalR from "@microsoft/signalr";
2
2
  import { HubConnection } from "@microsoft/signalr";
3
- //#region src/helpers/useSignalRConnectionsStore.d.ts
4
- export interface SignalRConnectionData {
3
+ //#region src/helpers/useSignalRConnections.d.ts
4
+ interface SignalRConnectionData {
5
5
  connection: SignalR.HubConnection;
6
6
  componentsSubscribed: number;
7
7
  }
8
8
  export declare function isConnectionAlive(connectionState: SignalR.HubConnectionState | null | undefined): boolean | null | undefined;
9
- export interface SignalRConnectionsStore {
10
- backendUri: string;
11
- EnableSignalRDebug: boolean;
12
- connectionsStore: Map<string, SignalRConnectionData>;
9
+ interface SignalRConnections {
13
10
  createConnection: (hubName: string) => Promise<SignalRConnectionData>;
14
11
  setupEventHandlers: <T>(hubName: string, handlers: Partial<T>, onConnected?: (connection: SignalR.HubConnection) => void) => Promise<void>;
15
12
  deleteConnection: <T>(hubName: string, handlers: Partial<T>) => void;
16
13
  }
17
- export declare const useSignalRConnectionsStore: import("zustand").UseBoundStore<import("zustand").StoreApi<SignalRConnectionsStore>>;
18
- export declare const useSignalRConnections: import("zustand").UseBoundStore<import("zustand").StoreApi<SignalRConnectionsStore>>;
14
+ interface SignalRConfiguration {
15
+ backendUri: string;
16
+ enableDebug?: boolean;
17
+ }
18
+ /** Configure SignalR before mounting consumers. Backend switching is not supported. */
19
+ export declare function configureSignalR({ backendUri, enableDebug }: SignalRConfiguration): void;
20
+ export declare function useSignalRConnections(): SignalRConnections;
19
21
  //#endregion
20
22
  //#region src/helpers/useStableHandlers.d.ts
21
23
  /**
@@ -25,15 +27,12 @@ export declare const useSignalRConnections: import("zustand").UseBoundStore<impo
25
27
  */
26
28
  export declare function useStableHandlers<T extends Record<string, (...args: any[]) => any>>(handlers: T | undefined): T;
27
29
  //#endregion
28
- //#region src/generation/safeInvokeHubMethod.d.ts
29
- type Warning = {
30
- warning: string;
31
- };
30
+ //#region src/helpers/safeInvokeHubMethod.d.ts
32
31
  /**
33
32
  * Safely invokes the SignalR hub method, i.e. it will only throw in case of an actual error,
34
33
  * and will return `Warning` object in case the connection hasn't yet been established or
35
34
  * has been already closed.
36
35
  */
37
- export declare function safeInvokeHubMethod(connection: HubConnection | null, methodName: string, ...args: any[]): Promise<Warning | any>;
36
+ export declare function safeInvokeHubMethod(connection: HubConnection | null, methodName: string, ...args: any[]): Promise<any>;
38
37
  //#endregion
39
- export {};
38
+ export type { SignalRConfiguration, SignalRConnectionData, SignalRConnections };
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@ import { create } from "zustand";
2
2
  import * as SignalR from "@microsoft/signalr";
3
3
  import { HubConnectionState } from "@microsoft/signalr";
4
4
  import { useLayoutEffect, useMemo, useRef } from "react";
5
- //#region src/helpers/useSignalRConnectionsStore.ts
5
+ //#region src/helpers/useSignalRConnections.ts
6
6
  function isConnectionAlive(connectionState) {
7
7
  return connectionState && connectionState !== SignalR.HubConnectionState.Disconnected && connectionState !== SignalR.HubConnectionState.Disconnecting;
8
8
  }
@@ -29,10 +29,8 @@ const useSignalRConnectionsStore = create((set, get) => ({
29
29
  },
30
30
  async setupEventHandlers(hubName, handlers, onConnected) {
31
31
  let data = get().connectionsStore.get(hubName);
32
- if (!data) {
33
- data = await get().createConnection(hubName);
34
- onConnected?.(data.connection);
35
- }
32
+ if (!data) data = await get().createConnection(hubName);
33
+ onConnected?.(data.connection);
36
34
  for (const key of Object.keys(handlers)) {
37
35
  const handler = handlers[key];
38
36
  if (handler !== void 0) data.connection.on(key, handler);
@@ -58,7 +56,20 @@ const useSignalRConnectionsStore = create((set, get) => ({
58
56
  set({ connectionsStore });
59
57
  }
60
58
  }));
61
- const useSignalRConnections = useSignalRConnectionsStore;
59
+ /** Configure SignalR before mounting consumers. Backend switching is not supported. */
60
+ function configureSignalR({ backendUri, enableDebug = false }) {
61
+ useSignalRConnectionsStore.setState({
62
+ backendUri,
63
+ EnableSignalRDebug: enableDebug
64
+ });
65
+ }
66
+ function useSignalRConnections() {
67
+ return {
68
+ createConnection: useSignalRConnectionsStore((state) => state.createConnection),
69
+ setupEventHandlers: useSignalRConnectionsStore((state) => state.setupEventHandlers),
70
+ deleteConnection: useSignalRConnectionsStore((state) => state.deleteConnection)
71
+ };
72
+ }
62
73
  //#endregion
63
74
  //#region src/helpers/useStableHandlers.ts
64
75
  /**
@@ -81,20 +92,26 @@ function useStableHandlers(handlers) {
81
92
  }, []);
82
93
  }
83
94
  //#endregion
84
- //#region src/generation/safeInvokeHubMethod.ts
95
+ //#region src/helpers/safeInvokeHubMethod.ts
85
96
  /**
86
97
  * Safely invokes the SignalR hub method, i.e. it will only throw in case of an actual error,
87
98
  * and will return `Warning` object in case the connection hasn't yet been established or
88
99
  * has been already closed.
89
100
  */
90
101
  async function safeInvokeHubMethod(connection, methodName, ...args) {
91
- if (connection?.state !== HubConnectionState.Connected) return { warning: "Skipped invocation: not connected" };
102
+ if (connection?.state !== HubConnectionState.Connected) {
103
+ console.warn("[SignalSpec] Skipped invocation: not connected");
104
+ return;
105
+ }
92
106
  try {
93
107
  return await connection.invoke(methodName, ...args);
94
108
  } catch (err) {
95
- if (err instanceof Error && err.message.includes("underlying connection being closed")) return { warning: "Skipped invocation: connection is being closed" };
109
+ if (err instanceof Error && err.message.includes("underlying connection being closed")) {
110
+ console.warn("[SignalSpec] Skipped invocation: connection is being closed");
111
+ return;
112
+ }
96
113
  throw err;
97
114
  }
98
115
  }
99
116
  //#endregion
100
- export { isConnectionAlive, safeInvokeHubMethod, useSignalRConnections, useSignalRConnectionsStore, useStableHandlers };
117
+ export { configureSignalR, isConnectionAlive, safeInvokeHubMethod, useSignalRConnections, useStableHandlers };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signal-spec/react-client",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A SignalR client generator for React based on the SignalSpec.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -48,6 +48,6 @@
48
48
  "dev": "vp pack --watch",
49
49
  "test": "vp test",
50
50
  "check": "vp check",
51
- "generate": "node ./bin/signal-spec.mjs generate"
51
+ "generate": "node --use-system-ca ./bin/signal-spec.mjs generate --input=https://localhost:49363/api/signal-spec.json --output=./src/result --backend-uri=https://localhost:49363"
52
52
  }
53
53
  }
@@ -1,5 +1,14 @@
1
- import { useSignalRConnectionsStore } from "@signal-spec/react-client";
1
+ import { configureSignalR } from "@signal-spec/react-client";
2
+
3
+ //--------------------------------------------------------------------------------------------------------------
4
+ // <auto-generated>
5
+ // This file is auto-generated by "SignalSpec.ReactClient" (@signal-spec/react-client).
6
+ // Do not modify this file manually.
7
+ // If you want to adjust it, copy the template "config.hbs" and supply your edited version of it.
8
+ // </auto-generated>
9
+ //--------------------------------------------------------------------------------------------------------------
2
10
 
3
11
  export const backendUri = {{{backendUriLiteral}}};
4
12
  export const EnableSignalRDebug = false;
5
- useSignalRConnectionsStore.setState({ backendUri, EnableSignalRDebug });
13
+ configureSignalR({ backendUri, enableDebug: EnableSignalRDebug });
14
+
@@ -1,8 +1,8 @@
1
1
  //--------------------------------------------------------------------------------------------------------------
2
2
  // <auto-generated>
3
- // This file is auto-generated by "SignalSpec.ReactClient".
3
+ // This file is auto-generated by "SignalSpec.ReactClient" (@signal-spec/react-client).
4
4
  // Do not modify this file manually.
5
- // If you want to adjust it, copy the template and supply your edited version of it.
5
+ // If you want to adjust it, copy the template "dtos.hbs" and supply your edited version of it.
6
6
  // </auto-generated>
7
7
  //--------------------------------------------------------------------------------------------------------------
8
8
 
@@ -1,8 +1,8 @@
1
- import "../config.js";
1
+ import { backendUri } from "../config";
2
2
  import type { HubConnection } from "@microsoft/signalr";
3
3
  import { useState, useEffect } from "react";
4
4
  {{#if dtoImports}}
5
- import type { {{#each dtoImports}}{{this}}{{#unless @last}}, {{/unless}}{{/each}} } from "../DTOs.js";
5
+ import type { {{#each dtoImports}}{{this}}{{#unless @last}}, {{/unless}}{{/each}} } from "../DTOs";
6
6
  {{/if}}
7
7
  import { useStableHandlers, useSignalRConnections{{#if (hasAnyBackendMethods hubContract.methods)}}, safeInvokeHubMethod{{/if}} } from "@signal-spec/react-client";
8
8
 
@@ -11,7 +11,7 @@ import { useStableHandlers, useSignalRConnections{{#if (hasAnyBackendMethods hub
11
11
  // <auto-generated>
12
12
  // This file is auto-generated by "SignalSpec.ReactClient" (@signal-spec/react-client).
13
13
  // Do not modify this file manually.
14
- // If you want to adjust it, copy the template and supply your edited version of it.
14
+ // If you want to adjust it, copy the template "hubClient.hbs" and supply your edited version of it.
15
15
  // </auto-generated>
16
16
  //--------------------------------------------------------------------------------------------------------------
17
17
 
@@ -50,64 +50,61 @@ interface {{clientName}}Backend {
50
50
 
51
51
  export function use{{hubName}}(
52
52
  {{#if (hasAnyFrontendMethods hubContract.methods)}}handlers: Partial<{{clientName}}Frontend> = {},{{/if}}
53
- enabled: boolean = true,
54
- ): { {{#if (hasAnyBackendMethods hubContract.methods)}}invoker: {{clientName}}Backend;{{/if}} connection: HubConnection | null; } {
53
+ enabled: boolean = true,
54
+ ): { {{#if (hasAnyBackendMethods hubContract.methods)}}invoker: {{clientName}}Methods;{{/if}} connection: HubConnection | null; } {
55
55
 
56
- const { setupEventHandlers, deleteConnection } = useSignalRConnections();
57
- const [connection, setConnection] = useState<HubConnection | null>(
58
- null,
59
- );
56
+ const { setupEventHandlers, deleteConnection } = useSignalRConnections();
57
+ const [connection, setConnection] = useState<HubConnection | null>(
58
+ null,
59
+ );
60
60
 
61
- const stableHandlers = useStableHandlers({{#if (hasAnyFrontendMethods hubContract.methods)}}handlers{{else}}{}{{/if}});
62
- const onConnected = (connection: HubConnection) =>
63
- setConnection(connection);
61
+ const stableHandlers = useStableHandlers(handlers);
62
+ const onConnected = (connection: HubConnection) =>
63
+ setConnection(connection);
64
64
 
65
- useEffect(() => {
66
- if (!enabled) return;
65
+ useEffect(() => {
66
+ if (!enabled) return;
67
67
 
68
- const data = useSignalRConnections.getState().createConnection('{{hubName}}');
69
- onConnected(data.connection);
70
- setupEventHandlers('{{hubName}}', stableHandlers);
68
+ setupEventHandlers('{{hubName}}', stableHandlers, onConnected);
71
69
 
72
- return () => {
73
- deleteConnection('{{hubName}}', stableHandlers);
74
- };
75
- }, [enabled]);
76
-
77
- {{#if (hasAnyBackendMethods hubContract.methods)}}
78
- const invoker = {
79
- {{#each hubContract.methods}}
80
- {{#if (isBackendMethod this)}}
81
- {{name}}: async (
82
- {{#if parameters}}
83
- {{#each parameters}}
84
- {{name}}: {{safeType this}}{{#unless @last}}, {{/unless}}
85
- {{/each}}
86
- {{/if}}
87
- ) => {
88
- {{#if returns}}
89
- return await safeInvokeHubMethod(connection,
90
- "{{name}}"
91
- {{#if parameters}}, {{/if}}
92
- {{#each parameters}}
93
- {{name}}{{#unless @last}}, {{/unless}}
94
- {{/each}}
95
- ) as {{safeType returns}};
96
- {{else}}
97
- await safeInvokeHubMethod(connection,
98
- "{{name}}"
99
- {{#if parameters}}, {{/if}}
100
- {{#each parameters}}
101
- {{name}}{{#unless @last}}, {{/unless}}
102
- {{/each}}
103
- );
104
- {{/if}}
105
- }{{#unless @last}},{{/unless}}
106
- {{/if}}
107
- {{/each}}
70
+ return () => {
71
+ deleteConnection('{{hubName}}', stableHandlers);
108
72
  };
109
- {{/if}}
73
+ }, [backendUri, enabled]);
110
74
 
111
- return { {{#if (hasAnyBackendMethods hubContract.methods)}}invoker,{{/if}} connection };
112
- }
75
+ {{#if (hasAnyBackendMethods hubContract.methods)}}
76
+ const invoker = {
77
+ {{#each hubContract.methods}}
78
+ {{#if (isBackendMethod this)}}
79
+ {{name}}: async (
80
+ {{#if parameters}}
81
+ {{#each parameters}}
82
+ {{name}}: {{safeType this}}{{#unless @last}}, {{/unless}}
83
+ {{/each}}
84
+ {{/if}}
85
+ ) => {
86
+ {{#if returns}}
87
+ return await safeInvokeHubMethod(connection,
88
+ '{{name}}'
89
+ {{#if parameters}}, {{/if}}
90
+ {{#each parameters}}
91
+ {{name}}{{#unless @last}}, {{/unless}}
92
+ {{/each}}
93
+ ) as {{safeType returns}};
94
+ {{else}}
95
+ await safeInvokeHubMethod(connection,
96
+ '{{name}}'
97
+ {{#if parameters}}, {{/if}}
98
+ {{#each parameters}}
99
+ {{name}}{{#unless @last}}, {{/unless}}
100
+ {{/each}}
101
+ );
102
+ {{/if}}
103
+ }{{#unless @last}},{{/unless}}
104
+ {{/if}}
105
+ {{/each}}
106
+ };
107
+ {{/if}}
113
108
 
109
+ return { {{#if (hasAnyBackendMethods hubContract.methods)}}invoker,{{/if}} connection };
110
+ }
@@ -1,27 +0,0 @@
1
- import { HubConnectionState, type HubConnection } from "@microsoft/signalr";
2
-
3
- type Warning = { warning: string };
4
-
5
- /**
6
- * Safely invokes the SignalR hub method, i.e. it will only throw in case of an actual error,
7
- * and will return `Warning` object in case the connection hasn't yet been established or
8
- * has been already closed.
9
- */
10
- export async function safeInvokeHubMethod(
11
- connection: HubConnection | null,
12
- methodName: string,
13
- ...args: any[]
14
- ): Promise<Warning | any> {
15
- if (connection?.state !== HubConnectionState.Connected) {
16
- return { warning: "Skipped invocation: not connected" };
17
- }
18
-
19
- try {
20
- return await connection.invoke(methodName, ...args);
21
- } catch (err) {
22
- if (err instanceof Error && err.message.includes("underlying connection being closed")) {
23
- return { warning: "Skipped invocation: connection is being closed" };
24
- }
25
- throw err;
26
- }
27
- }