@spider-mesh/core 2.0.41 → 2.0.42
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 +175 -272
- package/build/src/Registry.d.ts +19 -0
- package/build/src/Registry.js +77 -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 +242 -226
- package/build/src/SpiderMesh.js.map +1 -1
- package/build/src/decorators/NestJSLinkMicroservice.d.ts +3 -1
- package/build/src/decorators/NestJSLinkMicroservice.js +2 -2
- 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 +14 -2
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -2
- 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
|
|
22
|
+
For TypeScript projects using decorators, enable decorator metadata in your compiler settings.
|
|
68
23
|
|
|
69
|
-
|
|
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 |
|
|
24
|
+
## Runtime Setup
|
|
75
25
|
|
|
76
|
-
|
|
26
|
+
Create a registry when you need remote peer discovery and RPC routing.
|
|
77
27
|
|
|
78
|
-
|
|
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
|
|
86
|
-
|
|
87
|
-
- Use `SpiderMesh` when you need a local runtime that can expose services, discover nodes, publish events, or call remote services.
|
|
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.
|
|
93
|
-
|
|
94
|
-
### Do and don't
|
|
95
|
-
|
|
96
|
-
Do:
|
|
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,203 +69,222 @@ 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
|
-
|
|
277
|
-
|
|
278
|
-
type UserServiceContract = {
|
|
279
|
-
getUser(id: string): Promise<{ id: string; name: string }>
|
|
280
|
-
}
|
|
150
|
+
await users.set({ timeout: 3000, retry: 2 }).getUser('42')
|
|
281
151
|
|
|
282
|
-
|
|
152
|
+
await mesh.callRemoteService({
|
|
283
153
|
service: 'UserService',
|
|
154
|
+
method: 'getUser',
|
|
155
|
+
args: ['42'],
|
|
156
|
+
transporter: 'Http2Rpc',
|
|
284
157
|
})
|
|
285
|
-
|
|
286
|
-
await users.wait()
|
|
287
|
-
|
|
288
|
-
const user = await users.getUser('42')
|
|
289
|
-
console.log(user.name)
|
|
290
158
|
```
|
|
291
159
|
|
|
292
|
-
|
|
160
|
+
`transporter` may be:
|
|
293
161
|
|
|
294
|
-
|
|
162
|
+
- a registered transporter name string
|
|
163
|
+
- a class or object with a `name`
|
|
295
164
|
|
|
296
|
-
|
|
165
|
+
## Events
|
|
297
166
|
|
|
298
|
-
|
|
167
|
+
Use `SpiderMesh.linkEvent()` to bind a topic by event class name.
|
|
299
168
|
|
|
300
169
|
```ts
|
|
301
|
-
|
|
302
|
-
|
|
170
|
+
class UserCreatedEvent {
|
|
171
|
+
constructor(
|
|
172
|
+
public readonly id: string,
|
|
173
|
+
public readonly email: string,
|
|
174
|
+
) {}
|
|
175
|
+
}
|
|
303
176
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
177
|
+
const userCreated = mesh.linkEvent(UserCreatedEvent)
|
|
178
|
+
|
|
179
|
+
await userCreated.publish(new UserCreatedEvent('42', 'ada@example.com'))
|
|
180
|
+
|
|
181
|
+
const sub = userCreated.listen().subscribe(event => {
|
|
182
|
+
console.log(event.id)
|
|
307
183
|
})
|
|
184
|
+
|
|
185
|
+
sub.unsubscribe()
|
|
308
186
|
```
|
|
309
187
|
|
|
310
|
-
|
|
188
|
+
Current event behavior:
|
|
311
189
|
|
|
312
|
-
|
|
190
|
+
- `publish()` fans out through all registered pubsub transporters
|
|
191
|
+
- `listen()` merges all transporter listeners into one shared stream
|
|
192
|
+
- first subscribe adds the topic to local node metadata
|
|
193
|
+
- last unsubscribe removes the topic from local node metadata
|
|
194
|
+
- discovery transporters rebroadcast node metadata after topic changes
|
|
313
195
|
|
|
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.
|
|
196
|
+
## Registry
|
|
317
197
|
|
|
318
|
-
|
|
198
|
+
`Registry` stores remote peer and RPC routing state.
|
|
319
199
|
|
|
320
|
-
|
|
321
|
-
const resilientUsers = users.set({
|
|
322
|
-
timeout: 3000,
|
|
323
|
-
retry: 2,
|
|
324
|
-
fallback: { id: 'fallback', name: 'Unknown' },
|
|
325
|
-
})
|
|
200
|
+
Current public methods:
|
|
326
201
|
|
|
327
|
-
|
|
328
|
-
|
|
202
|
+
- `getPeer(nodeId)`
|
|
203
|
+
- `upsertPeer(node)`
|
|
204
|
+
- `removePeer(nodeId)`
|
|
205
|
+
- `listPeers({ service? })`
|
|
206
|
+
- `watch(service?)`
|
|
207
|
+
- `pickRpcNode(service, { node_id? })`
|
|
208
|
+
- `getRpcTransporterName(service, { node_id? })`
|
|
209
|
+
- `listTopicNodes(topic)`
|
|
329
210
|
|
|
330
|
-
|
|
211
|
+
## Transporter Contracts
|
|
331
212
|
|
|
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
|
|
213
|
+
The contract source of truth is `src/types.ts`.
|
|
340
214
|
|
|
341
|
-
###
|
|
215
|
+
### RPC transporter
|
|
342
216
|
|
|
343
217
|
```ts
|
|
344
|
-
|
|
218
|
+
type RpcTransporter = Observable<RpcEvent> & {
|
|
219
|
+
linkRegistry?(registry: Registry): void
|
|
220
|
+
send(data: RpcPacket, node_id?: string): Promise<void>
|
|
221
|
+
}
|
|
222
|
+
```
|
|
345
223
|
|
|
346
|
-
|
|
347
|
-
console.log(nodes.map(node => node.node_id))
|
|
348
|
-
})
|
|
224
|
+
### Pubsub transporter
|
|
349
225
|
|
|
350
|
-
|
|
226
|
+
```ts
|
|
227
|
+
type PubsubTransporter = {
|
|
228
|
+
publish<T>(topic: string, data: T): Promise<void>
|
|
229
|
+
listen<T>(topic: string): Observable<T>
|
|
230
|
+
linkRegistry?(registry: Registry): void
|
|
231
|
+
}
|
|
351
232
|
```
|
|
352
233
|
|
|
353
|
-
|
|
234
|
+
### Discovery transporter
|
|
354
235
|
|
|
355
|
-
|
|
236
|
+
```ts
|
|
237
|
+
type DiscoveryTransporter = Observable<DiscoveryEvent> & {
|
|
238
|
+
linkRegistry?(registry: Registry): void
|
|
239
|
+
broadcast(data: MdnsMessage<NodeMetadata>): Promise<void>
|
|
240
|
+
}
|
|
241
|
+
```
|
|
356
242
|
|
|
357
|
-
|
|
243
|
+
## NestJS Helpers
|
|
358
244
|
|
|
359
|
-
|
|
245
|
+
The package exports:
|
|
360
246
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
})
|
|
365
|
-
```
|
|
247
|
+
- `NestJSExposeMicroservice(factory, metadata?)`
|
|
248
|
+
- `NestJSLinkMicroservice(factory, transporter)`
|
|
249
|
+
- `NestJSLinkEvent(factory)`
|
|
366
250
|
|
|
367
|
-
|
|
251
|
+
`NestJSLinkMicroservice(factory, transporter)` forwards the transporter selector into `RemoteServiceLinker.link()`.
|
|
368
252
|
|
|
369
|
-
|
|
370
|
-
- `{ node, error }`
|
|
253
|
+
## Companion Packages
|
|
371
254
|
|
|
372
|
-
|
|
255
|
+
Use companion packages when you need concrete transport implementations.
|
|
373
256
|
|
|
374
|
-
`
|
|
257
|
+
- `@spider-mesh/tcp`
|
|
258
|
+
- `@spider-mesh/ws`
|
|
375
259
|
|
|
376
|
-
|
|
377
|
-
class UserCreatedEvent {
|
|
378
|
-
constructor(
|
|
379
|
-
public readonly id: string,
|
|
380
|
-
public readonly email: string,
|
|
381
|
-
) {}
|
|
382
|
-
}
|
|
260
|
+
Keep runtime logic in `@spider-mesh/core` and import concrete transporters explicitly from the companion package.
|
|
383
261
|
|
|
384
|
-
|
|
262
|
+
## Tests And Validation
|
|
263
|
+
|
|
264
|
+
The repository includes mock e2e coverage for:
|
|
265
|
+
|
|
266
|
+
- RPC routing by transporter selector
|
|
267
|
+
- async `RemoteServiceLinker.wait()` behavior
|
|
268
|
+
- topic metadata lifecycle for `linkEvent()`
|
|
269
|
+
- RPC routing across isolated child processes
|
|
270
|
+
|
|
271
|
+
Run tests with:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
bun run test:e2e
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Build with:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
bun run build
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Notes
|
|
284
|
+
|
|
285
|
+
- This package is ESM-only.
|
|
286
|
+
- Repository source uses emitted `.js` relative specifiers.
|
|
287
|
+
- `LOCAL_SERVICES$` is process-global inside one process.
|
|
385
288
|
|
|
386
289
|
await userCreated.publish(new UserCreatedEvent('42', 'ada@example.com'))
|
|
387
290
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BehaviorSubject } from 'rxjs';
|
|
2
|
+
import type { SpiderMeshNode } from './types.js';
|
|
3
|
+
export type RegistryPickRpcTargetOptions = {
|
|
4
|
+
node_id?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare class Registry {
|
|
7
|
+
#private;
|
|
8
|
+
readonly nodes$: BehaviorSubject<Map<string, SpiderMeshNode>>;
|
|
9
|
+
getPeer(nodeId: string): SpiderMeshNode | undefined;
|
|
10
|
+
upsertPeer(node: SpiderMeshNode): SpiderMeshNode;
|
|
11
|
+
removePeer(nodeId: string): boolean;
|
|
12
|
+
listPeers(options?: {
|
|
13
|
+
service?: string;
|
|
14
|
+
}): SpiderMeshNode[];
|
|
15
|
+
watch(service?: string): import("rxjs").Observable<SpiderMeshNode[]>;
|
|
16
|
+
pickRpcNode(service: string, options?: RegistryPickRpcTargetOptions): string | null;
|
|
17
|
+
getRpcTransporterName(service: string, options?: RegistryPickRpcTargetOptions): string | null;
|
|
18
|
+
listTopicNodes(topic: string): SpiderMeshNode[];
|
|
19
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { BehaviorSubject, map } from 'rxjs';
|
|
2
|
+
export class Registry {
|
|
3
|
+
nodes$ = new BehaviorSubject(new Map());
|
|
4
|
+
#rrIndexes = new Map();
|
|
5
|
+
getPeer(nodeId) {
|
|
6
|
+
return this.nodes$.value.get(nodeId);
|
|
7
|
+
}
|
|
8
|
+
upsertPeer(node) {
|
|
9
|
+
const nodes = new Map(this.nodes$.value);
|
|
10
|
+
const current = nodes.get(node.node_id);
|
|
11
|
+
nodes.set(node.node_id, {
|
|
12
|
+
...current,
|
|
13
|
+
...node,
|
|
14
|
+
topics: node.topics || current?.topics || [],
|
|
15
|
+
services: {
|
|
16
|
+
...(current?.services || {}),
|
|
17
|
+
...(node.services || {})
|
|
18
|
+
},
|
|
19
|
+
nodes: {
|
|
20
|
+
...(current?.nodes || {}),
|
|
21
|
+
...(node.nodes || {})
|
|
22
|
+
},
|
|
23
|
+
transporters: {
|
|
24
|
+
...(current?.transporters || {}),
|
|
25
|
+
...(node.transporters || {})
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
this.nodes$.next(nodes);
|
|
29
|
+
return nodes.get(node.node_id);
|
|
30
|
+
}
|
|
31
|
+
removePeer(nodeId) {
|
|
32
|
+
const nodes = new Map(this.nodes$.value);
|
|
33
|
+
const deleted = nodes.delete(nodeId);
|
|
34
|
+
if (deleted) {
|
|
35
|
+
this.nodes$.next(nodes);
|
|
36
|
+
}
|
|
37
|
+
return deleted;
|
|
38
|
+
}
|
|
39
|
+
listPeers(options = {}) {
|
|
40
|
+
return [...this.nodes$.value.values()].filter(node => {
|
|
41
|
+
if (options.service && node.services[options.service] == undefined)
|
|
42
|
+
return false;
|
|
43
|
+
return true;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
watch(service) {
|
|
47
|
+
return this.nodes$.pipe(map(() => this.listPeers({ service })));
|
|
48
|
+
}
|
|
49
|
+
pickRpcNode(service, options = {}) {
|
|
50
|
+
if (options.node_id) {
|
|
51
|
+
const node = this.getPeer(options.node_id);
|
|
52
|
+
if (!node)
|
|
53
|
+
return null;
|
|
54
|
+
if (node.services[service] == undefined)
|
|
55
|
+
return null;
|
|
56
|
+
return node.node_id;
|
|
57
|
+
}
|
|
58
|
+
const targets = this.listPeers({ service });
|
|
59
|
+
if (targets.length === 0)
|
|
60
|
+
return null;
|
|
61
|
+
const index = this.#rrIndexes.get(service) || 0;
|
|
62
|
+
this.#rrIndexes.set(service, (index + 1) % targets.length);
|
|
63
|
+
return targets[index % targets.length]?.node_id || null;
|
|
64
|
+
}
|
|
65
|
+
getRpcTransporterName(service, options = {}) {
|
|
66
|
+
const nodeId = options.node_id || this.pickRpcNode(service, options);
|
|
67
|
+
if (!nodeId)
|
|
68
|
+
return null;
|
|
69
|
+
const node = this.getPeer(nodeId);
|
|
70
|
+
const transporter = node?.transporters?.rpc;
|
|
71
|
+
return typeof transporter === 'string' ? transporter : null;
|
|
72
|
+
}
|
|
73
|
+
listTopicNodes(topic) {
|
|
74
|
+
return [...this.nodes$.value.values()].filter(node => node.topics.includes(topic));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=Registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Registry.js","sourceRoot":"","sources":["../../src/Registry.ts"],"names":[],"mappings":"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"}
|