@push-rpc/next 2.0.10 → 2.0.12

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.
Files changed (85) hide show
  1. package/README.md +216 -22
  2. package/example/client.ts +1 -1
  3. package/example/server.ts +2 -2
  4. package/package-lock.json +1291 -0
  5. package/package.json +1 -1
  6. package/src/client/HttpClient.ts +1 -20
  7. package/src/client/RpcClientImpl.ts +18 -6
  8. package/src/client/WebSocketConnection.ts +8 -27
  9. package/src/client/index.ts +2 -0
  10. package/src/index.ts +1 -0
  11. package/src/rpc.ts +1 -0
  12. package/src/server/RpcServerImpl.ts +12 -8
  13. package/src/server/index.ts +2 -0
  14. package/src/utils/middleware.ts +7 -2
  15. package/tests/connection.ts +2 -87
  16. package/tests/middleware.ts +129 -1
  17. package/tests/testUtils.ts +25 -0
  18. package/dist/client/HttpClient.d.ts +0 -13
  19. package/dist/client/HttpClient.js +0 -98
  20. package/dist/client/HttpClient.js.map +0 -1
  21. package/dist/client/RemoteSubscriptions.d.ts +0 -18
  22. package/dist/client/RemoteSubscriptions.js +0 -120
  23. package/dist/client/RemoteSubscriptions.js.map +0 -1
  24. package/dist/client/RpcClientImpl.d.ts +0 -23
  25. package/dist/client/RpcClientImpl.js +0 -117
  26. package/dist/client/RpcClientImpl.js.map +0 -1
  27. package/dist/client/WebSocketConnection.d.ts +0 -38
  28. package/dist/client/WebSocketConnection.js +0 -180
  29. package/dist/client/WebSocketConnection.js.map +0 -1
  30. package/dist/client/index.d.ts +0 -28
  31. package/dist/client/index.js +0 -38
  32. package/dist/client/index.js.map +0 -1
  33. package/dist/client/remote.d.ts +0 -14
  34. package/dist/client/remote.js +0 -66
  35. package/dist/client/remote.js.map +0 -1
  36. package/dist/index.d.ts +0 -12
  37. package/dist/index.js.map +0 -1
  38. package/dist/logger.d.ts +0 -8
  39. package/dist/logger.js +0 -9
  40. package/dist/logger.js.map +0 -1
  41. package/dist/rpc.d.ts +0 -36
  42. package/dist/rpc.js +0 -32
  43. package/dist/rpc.js.map +0 -1
  44. package/dist/server/ConnectionsServer.d.ts +0 -13
  45. package/dist/server/ConnectionsServer.js +0 -72
  46. package/dist/server/ConnectionsServer.js.map +0 -1
  47. package/dist/server/LocalSubscriptions.d.ts +0 -12
  48. package/dist/server/LocalSubscriptions.js +0 -113
  49. package/dist/server/LocalSubscriptions.js.map +0 -1
  50. package/dist/server/RpcServerImpl.d.ts +0 -24
  51. package/dist/server/RpcServerImpl.js +0 -195
  52. package/dist/server/RpcServerImpl.js.map +0 -1
  53. package/dist/server/http.d.ts +0 -9
  54. package/dist/server/http.js +0 -89
  55. package/dist/server/http.js.map +0 -1
  56. package/dist/server/index.d.ts +0 -30
  57. package/dist/server/index.js +0 -33
  58. package/dist/server/index.js.map +0 -1
  59. package/dist/server/local.d.ts +0 -15
  60. package/dist/server/local.js +0 -46
  61. package/dist/server/local.js.map +0 -1
  62. package/dist/utils/cookies.d.ts +0 -7
  63. package/dist/utils/cookies.js +0 -31
  64. package/dist/utils/cookies.js.map +0 -1
  65. package/dist/utils/env.d.ts +0 -6
  66. package/dist/utils/env.js +0 -22
  67. package/dist/utils/env.js.map +0 -1
  68. package/dist/utils/json.d.ts +0 -2
  69. package/dist/utils/json.js +0 -34
  70. package/dist/utils/json.js.map +0 -1
  71. package/dist/utils/middleware.d.ts +0 -2
  72. package/dist/utils/middleware.js +0 -31
  73. package/dist/utils/middleware.js.map +0 -1
  74. package/dist/utils/promises.d.ts +0 -5
  75. package/dist/utils/promises.js +0 -29
  76. package/dist/utils/promises.js.map +0 -1
  77. package/dist/utils/server.d.ts +0 -5
  78. package/dist/utils/server.js +0 -48
  79. package/dist/utils/server.js.map +0 -1
  80. package/dist/utils/throttle.d.ts +0 -4
  81. package/dist/utils/throttle.js +0 -40
  82. package/dist/utils/throttle.js.map +0 -1
  83. package/dist/utils/types.d.ts +0 -1
  84. package/dist/utils/types.js +0 -3
  85. package/dist/utils/types.js.map +0 -1
package/README.md CHANGED
@@ -1,4 +1,211 @@
1
- Client/server framework
1
+ A TypeScript framework for organizing bidirectional typesafe client-server communication. Supports
2
+ server-initiated data push (subscriptions). Uses HTTP, JSON and, optionally, WebSockets.
3
+ Main focus is on simplicity and good developer experience.
4
+
5
+ Best used with monorepos using TypeScript. Can also be used with JavaScript and non-JS clients.
6
+
7
+ ## Features
8
+
9
+ - Developer friendly - remote call is a plain TS call for easy tracing between client and server and good
10
+ IDE integration.
11
+ - Based on HTTP, easy to integrate with existing infrastructure. Call visibility in browser DevTools.
12
+ - Gradually upgradeable - WS is only used when you need subscriptions.
13
+ - Server runs on Node.JS, client runs in the Node.JS/Browser/ReactNative.
14
+
15
+ Extra:
16
+
17
+ - Client & Server middlewares.
18
+ - Consume compressed HTTP requests.
19
+ - Send & receive plain-text data.
20
+ - Throttling for subscriptions.
21
+ - Broken WS connection detection & auto-reconnecting.
22
+
23
+ ## History note!
24
+
25
+ Initially this project supported WS-only communication akin to OCPP-J protocol for EV charging stations.
26
+ Since that, library for OCPP-J was extracted to a separate project and this project was reworked to focus on generic
27
+ client/server communication.
28
+
29
+ ## Getting Started
30
+
31
+ ```
32
+ npm install @push-rpc/next
33
+ ```
34
+
35
+ For implementing subscriptions at backend, you'll also need to install ws package:
36
+
37
+ ```
38
+ npm install ws
39
+ ```
40
+
41
+ Contract between server and client is defined in shared module.
42
+
43
+ api.ts:
44
+
45
+ ```
46
+ // Note that API definition is plain TypeScript and is independent of the library
47
+
48
+ export type Services = {
49
+ todo: TodoService
50
+ }
51
+
52
+ export type TodoService = {
53
+ addTodo(req: {text: string}, ctx?: any): Promise<void>
54
+ getTodos(ctx?: any): Promise<Todo[]>
55
+ }
56
+
57
+ export type Todo = {
58
+ id: string
59
+ text: string
60
+ status: "open" | "closed"
61
+ }
62
+
63
+ ```
64
+
65
+ Contact then used in client.ts:
66
+
67
+ ```
68
+ import {Services} from "./api"
69
+ import {consumeServices} from "@push-rpc/next"
70
+
71
+ async function startClient() {
72
+ const {remote} = await consumeServices<Services>("http://127.0.0.1:8080/rpc")
73
+
74
+ console.log("Client created")
75
+
76
+ await remote.todo.getTodos.subscribe((todos) => {
77
+ console.log("Got todo items", todos)
78
+ })
79
+
80
+ await remote.todo.addTodo({text: "Buy groceries"})
81
+ }
82
+
83
+ startClient()
84
+
85
+ ```
86
+
87
+ And implemented in server.ts:
88
+
89
+ ```
90
+ import {Todo, TodoService} from "./api"
91
+ import {publishServices} from "@push-rpc/next"
92
+
93
+ async function startServer() {
94
+ const storage: Todo[] = []
95
+
96
+ class TodoServiceImpl implements TodoService {
97
+ async addTodo({text}: {text: string}) {
98
+ storage.push({
99
+ id: "" + Math.random(),
100
+ text,
101
+ status: "open",
102
+ })
103
+
104
+ console.log("New todo item added")
105
+ services.todo.getTodos.trigger()
106
+ }
107
+
108
+ async getTodos() {
109
+ return storage
110
+ }
111
+ }
112
+
113
+ const {services} = await publishServices(
114
+ {
115
+ todo: new TodoServiceImpl(),
116
+ },
117
+ {
118
+ port: 8080,
119
+ path: "/rpc",
120
+ }
121
+ )
122
+
123
+ console.log("RPC Server started at http://localhost:8080/rpc")
124
+ }
125
+
126
+ startServer()
127
+ ```
128
+
129
+ Run server.ts and then client.ts.
130
+
131
+ Server will send empty todo list on client connecting and then will send updated list on adding new item.
132
+
133
+ ## Protocol Details
134
+
135
+ There are the types of messages that can be sent from client to server:
136
+
137
+ 1. **Call** - a request to synchronously execute a remote function. Implemented as HTTP POST request. URL contains the
138
+ remote function name. Body contains JSON-encoded list of arguments. Response is JSON-encoded result of the
139
+ function.
140
+
141
+ ```
142
+ POST /rpc/todo/addTodo HTTP/1.1
143
+ Content-Type: application/json
144
+ X-Rpc-Client-Id: GoQ_xVYcthSEqMxDGV212
145
+
146
+ [{"text": "Buy groceries"}]
147
+
148
+ ...
149
+ HTTP/1.1 200 OK
150
+ Content-Type: application/json
151
+ {"id": "123"}
152
+ ```
153
+
154
+ `X-Rpc-Client-Id` header is used to identify caller clients. In can be used for session tracking. Client ID is
155
+ available at server side in the context.
156
+
157
+ 2. **Subscribe** - a request to subscribe to a remote function updates. Implemented as HTTP PUT request. URL contains
158
+ the remote function name. Body contains JSON-encoded list of arguments. Response is JSON-encoded result of the
159
+ function.
160
+
161
+ ```
162
+ PUT /rpc/todo/getTodos HTTP/1.1
163
+ Content-Type: application/json
164
+
165
+ []
166
+
167
+ ...
168
+ HTTP/1.1 200 OK
169
+ Content-Type: application/json
170
+ [{"id": 1, text: "Buy groceries", status: "open"}]
171
+ ```
172
+
173
+ Before subscribing, client would establish a WebSocket connection to the server. Server would then use established
174
+ connection to send subscription updates. Client ID is used to link WebSocket connection and HTTP requests.
175
+
176
+ 3. **Unsubscribe** - a request to unsubscribe from a remote function updates. Implemented as HTTP PATCH request. URL
177
+ contains the remote function name. Body contains JSON-encoded list of arguments. Response is always empty.
178
+
179
+ ```
180
+ PATCH /rpc/todo/getTodos HTTP/1.1
181
+ Content-Type: application/json
182
+
183
+ []
184
+
185
+ ...
186
+ HTTP/1.1 204 No Content
187
+ ```
188
+
189
+ Server sends updates to subscribed clients using WebSocket connection. Clients establish WebSocket connection using
190
+ the base URL:
191
+
192
+ ```
193
+ GET /rpc HTTP/1.1
194
+ Connection: Upgrade
195
+ Upgrade: websocket
196
+ Sec-Websocket-Protocol: GoQ_xVYcthSEqMxDGV212
197
+ ```
198
+
199
+ `Sec-Websocket-Protocol` header is used to transfer client ID.
200
+
201
+ After successful subscription, server sends updates to the client. Each update is a JSON-encoded message containing
202
+ topic name, remote function result and subscription parameters if any:
203
+
204
+ ```
205
+ ["todo/getTodos", [{"id": 1, text: "Buy groceries", status: "open"}], ...]
206
+ ```
207
+
208
+ Both client & server will try to connect broken connections by sending WS ping/pongs.
2
209
 
3
210
  ## Glossary
4
211
 
@@ -19,30 +226,17 @@ subscribe' invocation and copied to each 'trigger' invocation. Context, created
19
226
  contain only JSON data, to allow copying. Context can be modified in middlewares; these modification doesn't have to be
20
227
  JSON-only.
21
228
 
22
- **Middlewares**. Middlewares are used to intercept client and server requests. Both calls and subscriptions can be
23
- intercepted?. Middlewares can be attached on both client and server side. Middlewares receive context as the last
24
- arguments in the invocation. Middleware can modify context.
229
+ **Middlewares**. Middlewares are used to intercept client requests, server requests implementations and client
230
+ notifications. Both calls and subscriptions can be intercepted. Middlewares can be attached on both client and server
231
+ side. Middleware can modify context and parameters and data.
232
+
233
+ Request middleware is called with parameters (ctx, next, ...parameters)
234
+ Client notifications middleware is called with parameters (ctx, next, data, parameters).
25
235
 
26
236
  **Throttling**. Used to limit number of notifications from the remote functions. With throttling enabled, not all
27
237
  triggers will result in new notifications. Throttling can be used with reducers to aggregate values supplied in
28
238
  triggers.
29
239
 
30
- ## Issues / TBDs
31
-
32
- - [important] Importing index.js from the root of the package will import node's http package. Not good for clients.
33
- - Browser sockets don't have 'ping' event. Need to find a different way to detect connection loss.
34
- - Перевірити, що throttling працює відразу для всіх підписників
35
-
36
- ## Features
240
+ # Limitations
37
241
 
38
- - Developer friendly - everything is plain TypeScript calls, easy call tracing between client and server, good
39
- integration with IDE & Browser DevTools
40
- - Based on HTTP, easy to integrate with existing infrastructure
41
- - Gradually upgradeable - WS is only used when you need subscriptions
42
- - Supports compressed HTTP requests.
43
- - Server runs on Node.JS, client runs in the Node.JS/Browser/ReactNative. For RN some extra setup is required (
44
- document). Bun/Deno should also work, but not officially supported.
45
- - Limited cookie support. Due to limited support in React Native, Cookies can be set and read during HTTP requests and
46
- set during WS connection establishment. So if you need to share cookies between HTTP and WS, you need to make call (
47
- HTTP POST) right after creating client, and establish WS connection (ie make subscribe, HTTP PUT) only after that.
48
- Cookies cannot be shared when `connectOnCreate` client option is true.
242
+ - Cookies are not been sent during HTTP & WS requests.
package/example/client.ts CHANGED
@@ -8,7 +8,7 @@ async function startClient() {
8
8
 
9
9
  await remote.todo.getTodos.subscribe((todos) => {
10
10
  console.log("Got todo items", todos)
11
- }, null)
11
+ })
12
12
 
13
13
  await remote.todo.addTodo({text: "Buy groceries"})
14
14
  }
package/example/server.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import {Todo, TodoService} from "./api"
2
2
  import {publishServices} from "../src/server/index"
3
3
 
4
- const storage: Todo[] = []
5
-
6
4
  async function startServer() {
5
+ const storage: Todo[] = []
6
+
7
7
  class TodoServiceImpl implements TodoService {
8
8
  async addTodo({text}: {text: string}) {
9
9
  storage.push({