@grest-ts/ipc 0.0.14 → 0.0.17

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 (2) hide show
  1. package/README.md +185 -0
  2. package/package.json +7 -8
package/README.md CHANGED
@@ -3,3 +3,188 @@
3
3
  > [Documentation](https://github.com/grest-ts/grest-ts#readme) | [All packages](https://github.com/grest-ts/grest-ts#package-reference)
4
4
  <!-- GREST-TS-BANNER-END -->
5
5
 
6
+ # IPC Package (@grest-ts/ipc)
7
+
8
+ > **Internal package.** This is used by the framework internals (discovery, testkit). You should not need to use it directly in application code.
9
+
10
+ Type-safe inter-process communication over WebSocket with built-in HTTP routing and proxying. Provides the transport layer for local service discovery and the test framework's runtime communication.
11
+
12
+ ## What it provides
13
+
14
+ - **IPCServer** - HTTP server with WebSocket support, route interception, and proxy routing
15
+ - **IPCClient** - WebSocket client that connects to an IPCServer
16
+ - **IPCSocket** - Low-level WebSocket wrapper with request-response messaging and fire-and-forget messages
17
+ - **Type-safe request definitions** - Branded string types (`IPCServerRequest`, `IPCClientRequest`) that enforce payload/response types at compile time
18
+
19
+ ## How it is used
20
+
21
+ ### 1. Service Discovery
22
+
23
+ The discovery package uses IPC for service registration and lookup between runtimes running locally.
24
+
25
+ **Defining request types:**
26
+
27
+ ```typescript
28
+ import {IPCServer} from "@grest-ts/ipc"
29
+
30
+ export const GGDiscoveryIPC = {
31
+ discoveryServer: {
32
+ register: IPCServer.defineRequest<GGServiceDiscoveryEntry[], void>("discovery/register"),
33
+ discoverApi: IPCServer.defineRequest<string, DiscoverApiResult>("discovery/discoverApi"),
34
+ }
35
+ }
36
+ ```
37
+
38
+ **Server side** - the discovery server registers handlers on the IPCServer:
39
+
40
+ ```typescript
41
+ constructor(server: IPCServer) {
42
+ server.onFrameworkMessage(GGDiscoveryIPC.discoveryServer.register, async (routes) => {
43
+ routes.forEach(route => this.addRoute(route))
44
+ })
45
+
46
+ server.onFrameworkMessage(GGDiscoveryIPC.discoveryServer.discoverApi, async (apiName) => {
47
+ const route = this.getRoute(apiName)
48
+ if (route) return {success: true, url: this.server.getUrl()}
49
+ return {success: false, error: "Service not registered"}
50
+ })
51
+
52
+ // Route unhandled HTTP/WebSocket traffic to actual services
53
+ server.setRouteProxyResolver((path) => {
54
+ return this.matchRoute(path)?.baseUrl || undefined
55
+ })
56
+ }
57
+ ```
58
+
59
+ **Client side** - services connect and register themselves:
60
+
61
+ ```typescript
62
+ const client = new IPCClient(port)
63
+ await client.connect()
64
+
65
+ // Register routes
66
+ await client.sendFrameworkRequest(GGDiscoveryIPC.discoveryServer.register, entries)
67
+
68
+ // Discover another service
69
+ const result = await client.sendFrameworkRequest(GGDiscoveryIPC.discoveryServer.discoverApi, "my-api")
70
+ ```
71
+
72
+ ### 2. Test Framework (Runner <-> Worker communication)
73
+
74
+ The testkit uses IPC for bidirectional communication between the test runner process and runtime worker processes.
75
+
76
+ **Test runner (server side)** - sends commands to workers and receives registrations:
77
+
78
+ ```typescript
79
+ // Runner creates the server
80
+ const ipcServer = new IPCServer(port)
81
+ await ipcServer.start()
82
+
83
+ // Handle worker registrations
84
+ ipcServer.onFrameworkMessage(TestableIPC.server.registerKeys, async (payload) => {
85
+ runtime.registerLocatorKeys(payload.keys)
86
+ })
87
+
88
+ // Send command to a specific worker by runtimeId
89
+ await ipcServer.sendFrameworkMessage(runtimeId, GGConfigIPC.worker.update, {
90
+ storeName: "myStore",
91
+ keyName: "myKey",
92
+ value: newValue
93
+ })
94
+ ```
95
+
96
+ **Runtime worker (client side)** - connects with a `runtimeId` and handles commands from the runner:
97
+
98
+ ```typescript
99
+ const client = new IPCClient(config.testRouterPort)
100
+
101
+ // runtimeId allows the server to target this specific worker
102
+ await client.connect(config.runtimeId)
103
+
104
+ // Handle commands from test runner
105
+ client.onFrameworkRequest(GGConfigIPC.worker.update, async (payload) => {
106
+ await getStore(payload.storeName).updateValueOverride(
107
+ GGConfigKey.getKey(payload.keyName),
108
+ payload.value
109
+ )
110
+ })
111
+
112
+ // Send data back to the runner
113
+ await client.sendFrameworkRequest(TestableIPC.server.registerKeys, {
114
+ runtimeId: config.runtimeId,
115
+ keys: runtime.scope.getKeys()
116
+ })
117
+ ```
118
+
119
+ ### 3. HTTP Interception (Test Mocking/Spying)
120
+
121
+ The HTTP testkit uses the IPCServer's HTTP routing to intercept requests during tests - either mocking responses or spying on traffic while proxying to real services.
122
+
123
+ **Mock mode** - intercept and return custom responses:
124
+
125
+ ```typescript
126
+ // Route discovery traffic for this API to the test server
127
+ discoveryServer.addRoute({api: "user-api", baseUrl: server.getUrl(), pathPrefix: "/users"})
128
+
129
+ // Register a mock handler
130
+ server.interceptHttp("GET", "/users/:id", async (body, pathParams) => {
131
+ return {success: true, statusCode: 200, data: {id: pathParams.id, name: "Mock User"}}
132
+ })
133
+ ```
134
+
135
+ **Spy mode** - observe traffic while forwarding to the real service:
136
+
137
+ ```typescript
138
+ server.interceptHttp("POST", "/orders", async (body, _pathParams, headers) => {
139
+ await interceptor.onRequest(body) // observe the request
140
+ const response = await fetch(targetUrl, {...}) // forward to real service
141
+ await interceptor.onResponse(response) // observe the response
142
+ return response
143
+ })
144
+ ```
145
+
146
+ ### 4. Leader Election
147
+
148
+ The resilient discovery client uses IPC's port-binding behavior for leader election. The first instance to successfully start the IPCServer on a known port becomes the leader; others become followers that connect as clients.
149
+
150
+ ```typescript
151
+ const server = new IPCServer(knownPort)
152
+ if (await server.start()) {
153
+ // Port was available - this instance is the leader
154
+ this.isLeader = true
155
+ } else {
156
+ // Port already taken - connect as follower
157
+ await client.connect()
158
+ client.onClose(async () => {
159
+ // Leader died - try to become the new leader
160
+ await this.becomeLeaderOrFollower()
161
+ })
162
+ }
163
+ ```
164
+
165
+ ## Defining Request Types
166
+
167
+ Requests are defined as branded strings with phantom type parameters for compile-time safety.
168
+
169
+ ```typescript
170
+ // For requests sent TO the server (client -> server)
171
+ const myRequest = IPCServer.defineRequest<RequestPayload, ResponsePayload>("my/request")
172
+
173
+ // For requests sent TO the client (server -> client)
174
+ const myCommand = IPCClient.defineRequest<CommandPayload, CommandResult>("my/command")
175
+ ```
176
+
177
+ ## Architecture
178
+
179
+ ```
180
+ IPCServer
181
+ ├── HttpHandler - Route matching (find-my-way), request handling, HTTP proxying (http-proxy)
182
+ ├── SocketHandler - WebSocket upgrade handling, client tracking by clientId/runtimeId, WS proxying
183
+ │ └── IPCSocket - Per-connection message framing, request-response correlation, timeouts
184
+ └── http.Server - Underlying Node.js HTTP server (handles both HTTP and WS upgrade)
185
+
186
+ IPCClient
187
+ └── IPCSocket - WebSocket connection to server, message/request handlers
188
+ ```
189
+
190
+ **Message protocol:** Messages are framed as `type:id:path:data` over WebSocket text frames, where type is `m` (fire-and-forget), `r` (request), or `s` (response). Data is JSON-serialized.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grest-ts/ipc",
3
- "version": "0.0.14",
3
+ "version": "0.0.17",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Service internal process communications library. For local testing only.",
@@ -25,7 +25,7 @@
25
25
  "url": "https://github.com/grest-ts/grest-ts.git",
26
26
  "directory": "packages/ipc"
27
27
  },
28
- "homepage": "https://github.com/grest-ts/grest-ts/tree/master/packages/ipc",
28
+ "homepage": "https://grest-ts.com/packages/ipc",
29
29
  "bugs": {
30
30
  "url": "https://github.com/grest-ts/grest-ts/issues"
31
31
  },
@@ -44,17 +44,16 @@
44
44
  "node": ">=25"
45
45
  },
46
46
  "dependencies": {
47
- "@grest-ts/common": "0.0.14",
48
- "@grest-ts/context": "0.0.14",
49
- "@grest-ts/locator": "0.0.14",
50
- "@grest-ts/logger": "0.0.14",
51
- "@grest-ts/trace": "0.0.14",
47
+ "@grest-ts/common": "0.0.17",
48
+ "@grest-ts/context": "0.0.17",
49
+ "@grest-ts/locator": "0.0.17",
50
+ "@grest-ts/logger": "0.0.17",
51
+ "@grest-ts/trace": "0.0.17",
52
52
  "find-my-way": "^9.4.0",
53
53
  "http-proxy": "^1.18.1",
54
54
  "ws": "^8.19.0"
55
55
  },
56
56
  "devDependencies": {
57
- "@grest-ts/x-packager": "0.0.14",
58
57
  "@types/http-proxy": "^1.17.17"
59
58
  }
60
59
  }