@spider-mesh/core 2.0.41 → 2.0.43
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/README.md +193 -372
- package/build/src/Registry.d.ts +17 -0
- package/build/src/Registry.js +76 -0
- package/build/src/Registry.js.map +1 -0
- package/build/src/RemoteService.d.ts +3 -9
- package/build/src/RemoteService.js +4 -5
- package/build/src/RemoteService.js.map +1 -1
- package/build/src/SpiderMesh.d.ts +8 -19
- package/build/src/SpiderMesh.js +225 -237
- package/build/src/SpiderMesh.js.map +1 -1
- package/build/src/decorators/NestJSLinkMicroservice.d.ts +3 -2
- package/build/src/decorators/NestJSLinkMicroservice.js +2 -5
- package/build/src/decorators/NestJSLinkMicroservice.js.map +1 -1
- package/build/src/index.d.ts +2 -2
- package/build/src/index.js +1 -1
- package/build/src/index.js.map +1 -1
- package/build/src/types.d.ts +17 -14
- package/build/tests/fixtures/process-mesh.d.ts +1 -0
- package/build/tests/fixtures/process-mesh.js +91 -0
- package/build/tests/fixtures/process-mesh.js.map +1 -0
- package/build/tests/mock-e2e.test.d.ts +1 -0
- package/build/tests/mock-e2e.test.js +173 -0
- package/build/tests/mock-e2e.test.js.map +1 -0
- package/build/tests/process-e2e.test.d.ts +1 -0
- package/build/tests/process-e2e.test.js +93 -0
- package/build/tests/process-e2e.test.js.map +1 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -2
- package/tsconfig.json +1 -3
- package/build/src/helpers/randomUUID.d.ts +0 -1
- package/build/src/helpers/randomUUID.js +0 -13
- package/build/src/helpers/randomUUID.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,47 +1,17 @@
|
|
|
1
1
|
# Spider Mesh Core
|
|
2
2
|
|
|
3
|
-
`@spider-mesh/core` is
|
|
3
|
+
`@spider-mesh/core` is the runtime-agnostic Spider Mesh package.
|
|
4
4
|
|
|
5
5
|
It provides:
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- NestJS adapters
|
|
7
|
+
- local microservice registration with decorators
|
|
8
|
+
- typed remote service linking through proxies
|
|
9
|
+
- RPC, discovery, and pubsub transporter contracts
|
|
10
|
+
- a `Registry` for remote peer state and RPC routing
|
|
11
|
+
- a `SpiderMesh` runtime for local services, transporters, and node metadata
|
|
12
|
+
- NestJS helper adapters
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Spider Mesh is now split into two main package layers:
|
|
17
|
-
|
|
18
|
-
- `@spider-mesh/core`: runtime-agnostic service runtime, decorators, linking, and shared contracts
|
|
19
|
-
- companion transport packages: concrete transport implementations such as `@spider-mesh/tcp` and `@spider-mesh/ws`
|
|
20
|
-
|
|
21
|
-
Keep companion packages on the same published version as `@spider-mesh/core` to avoid contract drift between the runtime and concrete transports.
|
|
22
|
-
|
|
23
|
-
## Metadata Contract
|
|
24
|
-
|
|
25
|
-
`SpiderMeshNode` is intentionally minimal.
|
|
26
|
-
|
|
27
|
-
- Node metadata includes `host`, `namespace`, `node_id`, `version`, `services`, `nodes`, and `transporters`.
|
|
28
|
-
- Node metadata no longer includes `ips`.
|
|
29
|
-
- Node metadata no longer includes `online`.
|
|
30
|
-
|
|
31
|
-
Discovery transporters should treat discovery as a node announcement stream, not as an interface-IP inventory.
|
|
32
|
-
|
|
33
|
-
The discovery transporter contract is:
|
|
34
|
-
|
|
35
|
-
```ts
|
|
36
|
-
broadcast(data: MdnsMessage<NodeMetadata>): Promise<void>
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
`@spider-mesh/core` no longer passes a separate IP list into discovery transporters.
|
|
40
|
-
|
|
41
|
-
If you are reading this package first, the practical rule is simple:
|
|
42
|
-
|
|
43
|
-
- stay in `@spider-mesh/core` for service code and runtime code
|
|
44
|
-
- add a companion transport package only when you explicitly need a concrete transport implementation
|
|
14
|
+
Concrete transport implementations live in companion packages such as `@spider-mesh/tcp` and `@spider-mesh/ws`, or in your own custom transporters.
|
|
45
15
|
|
|
46
16
|
## Install
|
|
47
17
|
|
|
@@ -49,124 +19,38 @@ If you are reading this package first, the practical rule is simple:
|
|
|
49
19
|
bun add @spider-mesh/core rxjs reflect-metadata
|
|
50
20
|
```
|
|
51
21
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
This package is ESM-only.
|
|
55
|
-
|
|
56
|
-
Practical implications:
|
|
57
|
-
|
|
58
|
-
- published output is loaded as ESM through the root export map
|
|
59
|
-
- repository TypeScript uses `module: NodeNext` and `moduleResolution: NodeNext`
|
|
60
|
-
- internal relative imports use emitted `.js` specifiers, for example `./SpiderMesh.js`
|
|
61
|
-
- avoid CommonJS patterns such as `require()` when adding new runtime code
|
|
62
|
-
|
|
63
|
-
## AI Usage Guide
|
|
64
|
-
|
|
65
|
-
Use this section as the canonical decision guide when generating code with AI tools.
|
|
66
|
-
|
|
67
|
-
### Environment support matrix
|
|
68
|
-
|
|
69
|
-
| Runtime | `@spider-mesh/core` | `@spider-mesh/tcp` | `@spider-mesh/ws` |
|
|
70
|
-
| --- | --- | --- | --- |
|
|
71
|
-
| Node.js | Supported | Supported | Supported |
|
|
72
|
-
| Bun | Supported | Supported | Supported |
|
|
73
|
-
| Browser | Partially supported, depends on your custom transporter | Not recommended | Not recommended |
|
|
74
|
-
| React Native | Supported for the core entry only | Not recommended | Not recommended |
|
|
75
|
-
|
|
76
|
-
### Recommended imports
|
|
77
|
-
|
|
78
|
-
Use these imports exactly.
|
|
79
|
-
|
|
80
|
-
- Core runtime, decorators, RPC linking, NestJS helpers: `import { ... } from '@spider-mesh/core'`
|
|
81
|
-
- Shared transporter contracts and runtime APIs: `import { ... } from '@spider-mesh/core'`
|
|
82
|
-
- Companion TCP transport implementation when needed: `import { Http2Rpc, Http2Pubsub, UdpDiscovery } from '@spider-mesh/tcp'`
|
|
83
|
-
- Companion WebSocket transport implementation when needed: `import { WebsocketTransporter } from '@spider-mesh/ws'`
|
|
84
|
-
|
|
85
|
-
### When to use what
|
|
22
|
+
For TypeScript projects using decorators, enable decorator metadata in your compiler settings.
|
|
86
23
|
|
|
87
|
-
|
|
88
|
-
- Use `@Microservice()` on local classes that should be callable remotely.
|
|
89
|
-
- Use `RemoteServiceLinker.link()` when you need a typed remote proxy for another service.
|
|
90
|
-
- Use transporter contracts from `@spider-mesh/core` when you are implementing or typing your own transport.
|
|
91
|
-
- Use `@spider-mesh/tcp` or `@spider-mesh/ws` when you want a companion transport package.
|
|
92
|
-
- Use a custom transporter when the runtime is not Node.js or Bun, or when you need a different protocol.
|
|
24
|
+
## Runtime Setup
|
|
93
25
|
|
|
94
|
-
|
|
26
|
+
Create a registry when you need remote peer discovery and RPC routing.
|
|
95
27
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
- Import runtime-agnostic APIs from `@spider-mesh/core`.
|
|
99
|
-
- Import transporter contracts from `@spider-mesh/core` when you need transport typing.
|
|
100
|
-
- Use a companion transport package such as `@spider-mesh/tcp` or `@spider-mesh/ws` when you need a ready-made transport implementation.
|
|
101
|
-
- Wait for remote service availability with `wait()` before making calls in startup flows.
|
|
102
|
-
- Treat companion package examples and e2e tests as canonical usage references for concrete transport behavior.
|
|
103
|
-
|
|
104
|
-
Don't:
|
|
105
|
-
|
|
106
|
-
- Do not assume `@spider-mesh/core` exports every concrete transporter.
|
|
107
|
-
- Do not assume `@spider-mesh/tcp` is React Native-ready.
|
|
108
|
-
- Do not assume `@spider-mesh/ws` is React Native-ready.
|
|
109
|
-
- Do not couple service code to a single transport package unless that is intentional.
|
|
110
|
-
- Do not call remote services before the target service has been discovered unless you intentionally rely on retry behavior.
|
|
111
|
-
|
|
112
|
-
### Canonical recipes
|
|
113
|
-
|
|
114
|
-
Provider recipe:
|
|
115
|
-
|
|
116
|
-
1. Import `Microservice` and `SpiderMesh` from `@spider-mesh/core`.
|
|
117
|
-
2. Choose a concrete transporter implementation, for example from `@spider-mesh/tcp` or `@spider-mesh/ws`.
|
|
118
|
-
3. Define a class and decorate it with `@Microservice()`.
|
|
119
|
-
4. Instantiate the service class.
|
|
120
|
-
5. Create `new SpiderMesh({ transporters: [transporter] })`.
|
|
121
|
-
|
|
122
|
-
Client recipe:
|
|
123
|
-
|
|
124
|
-
1. Import `SpiderMesh` and `RemoteServiceLinker` from `@spider-mesh/core`.
|
|
125
|
-
2. Choose a concrete transporter implementation, for example from `@spider-mesh/tcp` or `@spider-mesh/ws`.
|
|
126
|
-
3. Create `new SpiderMesh({ transporters: [transporter] })`.
|
|
127
|
-
4. Create a typed remote proxy with `RemoteServiceLinker.link()`.
|
|
128
|
-
5. Call `await proxy.wait()` before the first RPC call.
|
|
129
|
-
|
|
130
|
-
Companion transport package recipes:
|
|
131
|
-
|
|
132
|
-
TCP:
|
|
133
|
-
|
|
134
|
-
1. Import `UdpDiscovery`, `Http2Rpc`, and `Http2Pubsub` from `@spider-mesh/tcp`.
|
|
135
|
-
2. Add those transporters to `new SpiderMesh({ transporters: [...] })`.
|
|
136
|
-
3. Let the TCP package handle discovery, RPC, and pubsub transport.
|
|
137
|
-
|
|
138
|
-
WebSocket:
|
|
139
|
-
|
|
140
|
-
1. Import `WebsocketTransporter` from `@spider-mesh/ws`.
|
|
141
|
-
2. Add that transporter to `new SpiderMesh({ transporters: [...] })`.
|
|
142
|
-
3. Run `WebsocketRelayServer` from `@spider-mesh/ws/relay-server` when using the WebSocket companion package.
|
|
143
|
-
|
|
144
|
-
### AI-safe assumptions
|
|
28
|
+
```ts
|
|
29
|
+
import { Registry, SpiderMesh } from '@spider-mesh/core'
|
|
145
30
|
|
|
146
|
-
|
|
31
|
+
const registry = new Registry()
|
|
32
|
+
const mesh = new SpiderMesh(registry)
|
|
33
|
+
```
|
|
147
34
|
|
|
148
|
-
|
|
149
|
-
- Concrete transports are opt-in through companion packages or custom implementations.
|
|
150
|
-
- Transporter contracts come from the core package.
|
|
151
|
-
- The companion TCP and WebSocket packages are sibling transport options for Node.js and Bun runtimes.
|
|
152
|
-
- If the target runtime is React Native, prefer the core APIs and a runtime-appropriate custom transporter.
|
|
35
|
+
Register transporter instances on the runtime:
|
|
153
36
|
|
|
154
|
-
|
|
37
|
+
```ts
|
|
38
|
+
import { Http2Pubsub, Http2Rpc, UdpDiscovery } from '@spider-mesh/tcp'
|
|
155
39
|
|
|
156
|
-
|
|
40
|
+
mesh.registerTransporter(new UdpDiscovery())
|
|
41
|
+
mesh.registerTransporter(new Http2Rpc())
|
|
42
|
+
mesh.registerTransporter(new Http2Pubsub())
|
|
43
|
+
```
|
|
157
44
|
|
|
158
|
-
|
|
45
|
+
Transporter capability is inferred by instance shape:
|
|
159
46
|
|
|
160
|
-
-
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
- RPC-capable nodes for each service
|
|
164
|
-
- RPC pending and running stream state
|
|
165
|
-
- Pubsub, RPC, and discovery transporters
|
|
47
|
+
- `send()` => RPC transporter
|
|
48
|
+
- `publish()` => pubsub transporter
|
|
49
|
+
- `broadcast()` => discovery transporter
|
|
166
50
|
|
|
167
|
-
##
|
|
51
|
+
## Local Services
|
|
168
52
|
|
|
169
|
-
|
|
53
|
+
Use `@Microservice()` on local service classes and instantiate them normally.
|
|
170
54
|
|
|
171
55
|
```ts
|
|
172
56
|
import { BeforeMicroserviceOnline, Microservice } from '@spider-mesh/core'
|
|
@@ -185,209 +69,217 @@ export class UserService {
|
|
|
185
69
|
return { id, name: 'Ada' }
|
|
186
70
|
}
|
|
187
71
|
}
|
|
72
|
+
|
|
73
|
+
new UserService()
|
|
188
74
|
```
|
|
189
75
|
|
|
190
|
-
`@Microservice()`
|
|
76
|
+
`@Microservice()` emits the constructed instance into the shared `LOCAL_SERVICES$` stream. `SpiderMesh` subscribes to that stream and adds the service to local node metadata.
|
|
191
77
|
|
|
192
|
-
|
|
78
|
+
## Remote Services
|
|
193
79
|
|
|
194
|
-
|
|
80
|
+
Create a typed remote client with `RemoteServiceLinker.link()`.
|
|
195
81
|
|
|
196
82
|
```ts
|
|
197
|
-
import {
|
|
198
|
-
import { AppDiscoveryTransporter } from './AppDiscoveryTransporter.js'
|
|
199
|
-
import { AppRpcTransporter } from './AppRpcTransporter.js'
|
|
200
|
-
import { AppPubsubTransporter } from './AppPubsubTransporter.js'
|
|
201
|
-
|
|
202
|
-
const mesh = new SpiderMesh({
|
|
203
|
-
transporters: [
|
|
204
|
-
AppDiscoveryTransporter,
|
|
205
|
-
AppRpcTransporter,
|
|
206
|
-
AppPubsubTransporter,
|
|
207
|
-
],
|
|
208
|
-
})
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
These transporter class names are application-local examples, not exports from `@spider-mesh/core`.
|
|
212
|
-
|
|
213
|
-
Transporter capability is inferred by method shape:
|
|
214
|
-
|
|
215
|
-
- `send()` means RPC transporter
|
|
216
|
-
- `publish()` means pubsub transporter
|
|
217
|
-
- `broadcast()` means discovery transporter
|
|
83
|
+
import { RemoteServiceLinker } from '@spider-mesh/core'
|
|
218
84
|
|
|
219
|
-
|
|
85
|
+
type UserServiceContract = {
|
|
86
|
+
getUser(id: string): Promise<{ id: string; name: string }>
|
|
87
|
+
}
|
|
220
88
|
|
|
221
|
-
|
|
89
|
+
const users = RemoteServiceLinker.link<UserServiceContract>(mesh, {
|
|
90
|
+
service: 'UserService',
|
|
91
|
+
})
|
|
222
92
|
|
|
223
|
-
|
|
93
|
+
await users.wait()
|
|
224
94
|
|
|
225
|
-
|
|
95
|
+
const user = await users.getUser('42')
|
|
96
|
+
```
|
|
226
97
|
|
|
227
|
-
|
|
98
|
+
Remote methods return RxJS observables and can also be awaited.
|
|
228
99
|
|
|
229
100
|
```ts
|
|
230
|
-
|
|
231
|
-
import { Http2Pubsub, Http2Rpc, UdpDiscovery } from '@spider-mesh/tcp'
|
|
101
|
+
const user = await users.getUser('42')
|
|
232
102
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
new UdpDiscovery(),
|
|
236
|
-
new Http2Rpc(),
|
|
237
|
-
new Http2Pubsub(),
|
|
238
|
-
],
|
|
103
|
+
users.getUser('42').subscribe(value => {
|
|
104
|
+
console.log(value)
|
|
239
105
|
})
|
|
240
106
|
```
|
|
241
107
|
|
|
242
|
-
|
|
108
|
+
### Waiting and watching
|
|
243
109
|
|
|
244
110
|
```ts
|
|
245
|
-
|
|
246
|
-
import { WebsocketTransporter } from '@spider-mesh/ws'
|
|
111
|
+
await users.wait(nodes => nodes.length > 0)
|
|
247
112
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
reconnectIntervalMs: 1000,
|
|
113
|
+
users.watch().subscribe(nodes => {
|
|
114
|
+
console.log(nodes.map(node => node.node_id))
|
|
251
115
|
})
|
|
252
116
|
|
|
253
|
-
|
|
117
|
+
console.log(users.nodes)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Fan-out calls
|
|
254
121
|
|
|
255
|
-
|
|
256
|
-
|
|
122
|
+
```ts
|
|
123
|
+
users.__batch__getUser('42').subscribe(result => {
|
|
124
|
+
console.log(result)
|
|
257
125
|
})
|
|
258
126
|
```
|
|
259
127
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
### 2b. Minimal working startup order
|
|
128
|
+
Each emission is either `{ node, data }` or `{ node, error }`.
|
|
263
129
|
|
|
264
|
-
|
|
130
|
+
## RPC Options
|
|
265
131
|
|
|
266
|
-
|
|
267
|
-
2. Start client processes.
|
|
268
|
-
3. Wait for discovery to converge.
|
|
269
|
-
4. In clients, call `await remote.wait()` before the first remote method call.
|
|
132
|
+
The current `RpcOptions` shape is:
|
|
270
133
|
|
|
271
|
-
|
|
134
|
+
```ts
|
|
135
|
+
type RpcOptions<T = any> = {
|
|
136
|
+
service: string
|
|
137
|
+
method: string
|
|
138
|
+
args: any[]
|
|
139
|
+
fallback?: T
|
|
140
|
+
timeout?: number
|
|
141
|
+
retry?: number
|
|
142
|
+
node_id?: string
|
|
143
|
+
transporter?: string | { name?: string }
|
|
144
|
+
}
|
|
145
|
+
```
|
|
272
146
|
|
|
273
|
-
|
|
147
|
+
Examples:
|
|
274
148
|
|
|
275
149
|
```ts
|
|
276
|
-
import {
|
|
150
|
+
import { firstValueFrom } from 'rxjs'
|
|
277
151
|
|
|
278
|
-
|
|
279
|
-
getUser(id: string): Promise<{ id: string; name: string }>
|
|
280
|
-
}
|
|
152
|
+
await users.set({ timeout: 3000, retry: 2 }).getUser('42')
|
|
281
153
|
|
|
282
|
-
|
|
154
|
+
await firstValueFrom(mesh.callRemoteService({
|
|
283
155
|
service: 'UserService',
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const user = await users.getUser('42')
|
|
289
|
-
console.log(user.name)
|
|
156
|
+
method: 'getUser',
|
|
157
|
+
args: ['42'],
|
|
158
|
+
transporter: 'Http2Rpc',
|
|
159
|
+
}))
|
|
290
160
|
```
|
|
291
161
|
|
|
292
|
-
|
|
162
|
+
`transporter` may be:
|
|
293
163
|
|
|
294
|
-
|
|
164
|
+
- a registered transporter name string
|
|
165
|
+
- a class or object with a `name`
|
|
295
166
|
|
|
296
|
-
|
|
167
|
+
## Events
|
|
297
168
|
|
|
298
|
-
|
|
169
|
+
Use `SpiderMesh.linkEvent()` to bind a topic by event class name.
|
|
299
170
|
|
|
300
171
|
```ts
|
|
301
|
-
|
|
302
|
-
|
|
172
|
+
class UserCreatedEvent {
|
|
173
|
+
constructor(
|
|
174
|
+
public readonly id: string,
|
|
175
|
+
public readonly email: string,
|
|
176
|
+
) {}
|
|
177
|
+
}
|
|
303
178
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
179
|
+
const userCreated = mesh.linkEvent(UserCreatedEvent)
|
|
180
|
+
|
|
181
|
+
await userCreated.publish(new UserCreatedEvent('42', 'ada@example.com'))
|
|
182
|
+
|
|
183
|
+
const sub = userCreated.listen().subscribe(event => {
|
|
184
|
+
console.log(event.id)
|
|
307
185
|
})
|
|
186
|
+
|
|
187
|
+
sub.unsubscribe()
|
|
308
188
|
```
|
|
309
189
|
|
|
310
|
-
|
|
190
|
+
Current event behavior:
|
|
311
191
|
|
|
312
|
-
|
|
192
|
+
- `publish()` fans out through all registered pubsub transporters
|
|
193
|
+
- `listen()` merges all transporter listeners into one shared stream
|
|
194
|
+
- first subscribe adds the topic to local node metadata
|
|
195
|
+
- last unsubscribe removes the topic from local node metadata
|
|
196
|
+
- discovery transporters rebroadcast node metadata after topic changes
|
|
313
197
|
|
|
314
|
-
|
|
315
|
-
- If a local method returns a `Promise` or plain value, it is sent as one `data` event with `completed: true`.
|
|
316
|
-
- If the caller unsubscribes early, Spider Mesh sends a `cancel` packet so the remote node can stop the running stream.
|
|
198
|
+
## Registry
|
|
317
199
|
|
|
318
|
-
|
|
200
|
+
`Registry` stores remote peer and RPC routing state.
|
|
319
201
|
|
|
320
|
-
|
|
321
|
-
const resilientUsers = users.set({
|
|
322
|
-
timeout: 3000,
|
|
323
|
-
retry: 2,
|
|
324
|
-
fallback: { id: 'fallback', name: 'Unknown' },
|
|
325
|
-
})
|
|
202
|
+
Current public methods:
|
|
326
203
|
|
|
327
|
-
|
|
328
|
-
|
|
204
|
+
- `getPeer(nodeId)`
|
|
205
|
+
- `upsertPeer(node)`
|
|
206
|
+
- `removePeer(nodeId)`
|
|
207
|
+
- `listPeers(service?)`
|
|
208
|
+
- `watch(service?)`
|
|
209
|
+
- `pickRpcNode(service, { node_id? })`
|
|
210
|
+
- `getRpcTransporterName(service)`
|
|
211
|
+
- `listTopicNodes(topic)`
|
|
329
212
|
|
|
330
|
-
|
|
213
|
+
## Transporter Contracts
|
|
331
214
|
|
|
332
|
-
|
|
333
|
-
- `method`: remote method name
|
|
334
|
-
- `args`: positional arguments
|
|
335
|
-
- `timeout`: timeout per inactivity window in milliseconds
|
|
336
|
-
- `retry`: retry count for offline errors
|
|
337
|
-
- `fallback`: fallback value when the call fails
|
|
338
|
-
- `node_id`: force routing to a specific node
|
|
339
|
-
- `ip`: force routing to a specific node IP
|
|
215
|
+
The contract source of truth is `src/types.ts`.
|
|
340
216
|
|
|
341
|
-
###
|
|
217
|
+
### RPC transporter
|
|
342
218
|
|
|
343
219
|
```ts
|
|
344
|
-
|
|
220
|
+
type RpcTransporter = Observable<RpcEvent> & {
|
|
221
|
+
linkRegistry?(registry: Registry): void
|
|
222
|
+
send(data: RpcPacket, node_id?: string): Promise<void>
|
|
223
|
+
}
|
|
224
|
+
```
|
|
345
225
|
|
|
346
|
-
|
|
347
|
-
console.log(nodes.map(node => node.node_id))
|
|
348
|
-
})
|
|
226
|
+
### Pubsub transporter
|
|
349
227
|
|
|
350
|
-
|
|
228
|
+
```ts
|
|
229
|
+
type PubsubTransporter = Observable<PubsubEvent> & {
|
|
230
|
+
publish<T>(topic: string, data: T): Promise<void>
|
|
231
|
+
listen<T>(topic: string): Observable<T>
|
|
232
|
+
linkRegistry?(registry: Registry): void
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Discovery transporter
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
type DiscoveryTransporter = Observable<DiscoveryEvent> & {
|
|
240
|
+
linkRegistry?(registry: Registry): void
|
|
241
|
+
broadcast(data: MdnsMessage<NodeMetadata>): Promise<void>
|
|
242
|
+
}
|
|
351
243
|
```
|
|
352
244
|
|
|
353
|
-
|
|
245
|
+
## NestJS Helpers
|
|
354
246
|
|
|
355
|
-
|
|
247
|
+
The package exports:
|
|
356
248
|
|
|
357
|
-
|
|
249
|
+
- `NestJSExposeMicroservice(factory, metadata?)`
|
|
250
|
+
- `NestJSLinkMicroservice(factory, transporter?)`
|
|
251
|
+
- `NestJSLinkEvent(factory)`
|
|
358
252
|
|
|
359
|
-
|
|
253
|
+
`NestJSLinkMicroservice(factory, transporter?)` forwards the optional transporter selector into `RemoteServiceLinker.link()`.
|
|
360
254
|
|
|
361
|
-
|
|
362
|
-
users.__batch__getUser('42').subscribe(result => {
|
|
363
|
-
console.log(result)
|
|
364
|
-
})
|
|
365
|
-
```
|
|
255
|
+
## Companion Packages
|
|
366
256
|
|
|
367
|
-
|
|
257
|
+
Use companion packages when you need concrete transport implementations.
|
|
368
258
|
|
|
369
|
-
- `
|
|
370
|
-
- `
|
|
259
|
+
- `@spider-mesh/tcp`
|
|
260
|
+
- `@spider-mesh/ws`
|
|
371
261
|
|
|
372
|
-
|
|
262
|
+
Keep runtime logic in `@spider-mesh/core` and import concrete transporters explicitly from the companion package.
|
|
373
263
|
|
|
374
|
-
|
|
264
|
+
## Tests And Validation
|
|
375
265
|
|
|
376
|
-
|
|
377
|
-
class UserCreatedEvent {
|
|
378
|
-
constructor(
|
|
379
|
-
public readonly id: string,
|
|
380
|
-
public readonly email: string,
|
|
381
|
-
) {}
|
|
382
|
-
}
|
|
266
|
+
The repository includes mock e2e coverage for:
|
|
383
267
|
|
|
384
|
-
|
|
268
|
+
- RPC routing by transporter selector
|
|
269
|
+
- async `RemoteServiceLinker.wait()` behavior
|
|
270
|
+
- topic metadata lifecycle for `linkEvent()`
|
|
271
|
+
- RPC routing across isolated child processes
|
|
385
272
|
|
|
386
|
-
|
|
273
|
+
Run tests with:
|
|
387
274
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
275
|
+
```bash
|
|
276
|
+
bun run test:e2e
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Build with:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
bun run build
|
|
391
283
|
```
|
|
392
284
|
|
|
393
285
|
## NestJS Integration
|
|
@@ -396,18 +288,24 @@ userCreated.listen().subscribe(event => {
|
|
|
396
288
|
|
|
397
289
|
```ts
|
|
398
290
|
import { Module } from '@nestjs/common'
|
|
399
|
-
import { SpiderMesh } from '@spider-mesh/core'
|
|
291
|
+
import { Registry, SpiderMesh } from '@spider-mesh/core'
|
|
400
292
|
import { Http2Pubsub, Http2Rpc, UdpDiscovery } from '@spider-mesh/tcp'
|
|
401
293
|
|
|
402
294
|
@Module({
|
|
403
295
|
providers: [
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
296
|
+
{
|
|
297
|
+
provide: SpiderMesh,
|
|
298
|
+
useFactory: () => {
|
|
299
|
+
const registry = new Registry()
|
|
300
|
+
const mesh = new SpiderMesh(registry)
|
|
301
|
+
|
|
302
|
+
mesh.registerTransporter(new UdpDiscovery())
|
|
303
|
+
mesh.registerTransporter(new Http2Rpc())
|
|
304
|
+
mesh.registerTransporter(new Http2Pubsub())
|
|
305
|
+
|
|
306
|
+
return mesh
|
|
307
|
+
},
|
|
308
|
+
},
|
|
411
309
|
],
|
|
412
310
|
exports: [SpiderMesh],
|
|
413
311
|
})
|
|
@@ -469,6 +367,12 @@ export class CheckoutService {
|
|
|
469
367
|
export class CheckoutModule {}
|
|
470
368
|
```
|
|
471
369
|
|
|
370
|
+
Pass a transporter only when you need to force a specific RPC transporter:
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
NestJSLinkMicroservice(BillingService, 'Http2Rpc')
|
|
374
|
+
```
|
|
375
|
+
|
|
472
376
|
### Inject an event binding in NestJS
|
|
473
377
|
|
|
474
378
|
```ts
|
|
@@ -499,85 +403,6 @@ export class AuditService {
|
|
|
499
403
|
export class AuditModule {}
|
|
500
404
|
```
|
|
501
405
|
|
|
502
|
-
## Transporter Contract
|
|
503
|
-
|
|
504
|
-
Concrete transport implementations can live in companion packages such as `@spider-mesh/tcp` and `@spider-mesh/ws`, or in your own application code.
|
|
505
|
-
|
|
506
|
-
You can also provide your own classes that implement one or more transporter contracts exported by `@spider-mesh/core`.
|
|
507
|
-
|
|
508
|
-
### Public API map
|
|
509
|
-
|
|
510
|
-
- `SpiderMesh`: runtime coordinator for services, events, discovery, and transporters
|
|
511
|
-
- `RemoteServiceLinker.link()`: creates a typed remote proxy
|
|
512
|
-
- `@Microservice()`: exposes a local class instance as a remote service
|
|
513
|
-
- `SpiderMesh.linkEvent()`: creates a topic binding for publish and subscribe
|
|
514
|
-
- `RpcTransporter`: RPC transporter contract
|
|
515
|
-
- `DiscoveryTransporter`: discovery transporter contract
|
|
516
|
-
- `PubsubTransporter`: pubsub transporter contract
|
|
517
|
-
|
|
518
|
-
### RPC transporter
|
|
519
|
-
|
|
520
|
-
```ts
|
|
521
|
-
type RpcMessage = {
|
|
522
|
-
node_id: string
|
|
523
|
-
packet: RpcPacket
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
type RpcEvent = Partial<{
|
|
527
|
-
rpc: RpcMessage
|
|
528
|
-
offline: string
|
|
529
|
-
endpoints: Record<string, string | boolean | number>
|
|
530
|
-
}>
|
|
531
|
-
|
|
532
|
-
type RpcTransporter = Observable<RpcEvent> & {
|
|
533
|
-
send(data: RpcPacket, node: SpiderMeshNode): Promise<void>
|
|
534
|
-
}
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
The RPC observable can emit:
|
|
538
|
-
|
|
539
|
-
- `rpc`: inbound RPC message shaped as `{ node_id, packet }`
|
|
540
|
-
- `offline`: node offline event
|
|
541
|
-
- `endpoints`: transporter metadata to attach to the current node
|
|
542
|
-
|
|
543
|
-
The internal RPC wire protocol supports:
|
|
544
|
-
|
|
545
|
-
- `request`
|
|
546
|
-
- `response`
|
|
547
|
-
- `cancel`
|
|
548
|
-
|
|
549
|
-
`response` packets can carry:
|
|
550
|
-
|
|
551
|
-
- `data`
|
|
552
|
-
- `error`
|
|
553
|
-
- `completed`
|
|
554
|
-
|
|
555
|
-
### Discovery transporter
|
|
556
|
-
|
|
557
|
-
```ts
|
|
558
|
-
type DiscoveryEvent = {
|
|
559
|
-
discovered: SpiderMeshNode
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
type DiscoveryTransporter = Observable<DiscoveryEvent> & {
|
|
563
|
-
broadcast(
|
|
564
|
-
data: MdnsMessage<NodeMetadata>,
|
|
565
|
-
ips: string[],
|
|
566
|
-
): Promise<void>
|
|
567
|
-
}
|
|
568
|
-
```
|
|
569
|
-
|
|
570
|
-
Discovery transporters stream remote node snapshots into the mesh, and `SpiderMesh` itself calls `broadcast()` whenever local node metadata changes.
|
|
571
|
-
|
|
572
|
-
### Pubsub transporter
|
|
573
|
-
|
|
574
|
-
```ts
|
|
575
|
-
type PubsubTransporter = {
|
|
576
|
-
publish<T>(topic: string, data: T): Promise<void>
|
|
577
|
-
listen<T>(topic: string): Observable<T>
|
|
578
|
-
}
|
|
579
|
-
```
|
|
580
|
-
|
|
581
406
|
## Error Model
|
|
582
407
|
|
|
583
408
|
The core defines these error codes for RPC flows:
|
|
@@ -596,21 +421,17 @@ The core defines these error codes for RPC flows:
|
|
|
596
421
|
The package also exports:
|
|
597
422
|
|
|
598
423
|
- `LimitConcurrency(limit)` and `LimitConcurrentRunning(limit)` for throttling async method execution
|
|
599
|
-
- `
|
|
600
|
-
- `MicroserviceException` types for common RPC error codes
|
|
601
|
-
|
|
602
|
-
## Build
|
|
603
|
-
|
|
604
|
-
```bash
|
|
605
|
-
bun run build
|
|
606
|
-
```
|
|
424
|
+
- `MicroserviceError` for common RPC error codes
|
|
607
425
|
|
|
608
426
|
## Notes
|
|
609
427
|
|
|
428
|
+
- This package is ESM-only.
|
|
429
|
+
- Repository source uses emitted `.js` relative specifiers.
|
|
430
|
+
- `LOCAL_SERVICES$` is process-global inside one process.
|
|
610
431
|
- Local services are registered when their class instances are constructed.
|
|
611
432
|
- Service identity is based on the class name.
|
|
612
433
|
- Event topic identity is based on the event class name.
|
|
613
|
-
- RPC target selection is round-robin unless you force `node_id
|
|
434
|
+
- RPC target selection is round-robin unless you force `node_id`.
|
|
614
435
|
- `SpiderMesh` owns RPC stream lifecycle, timeout, retry, and cancel behavior.
|
|
615
436
|
- Transporters focus on byte transport, pubsub topic IO, and discovery broadcasts.
|
|
616
437
|
- The root package entry intentionally focuses on runtime-agnostic APIs and shared contracts.
|