@procedures/peer 0.10.0 → 0.12.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/CHANGELOG.md +78 -0
- package/README.md +46 -7
- package/dist/.tsbuildinfo +1 -1
- package/dist/host/faces.d.ts +78 -0
- package/dist/host/faces.d.ts.map +1 -0
- package/dist/host/faces.js +77 -0
- package/dist/host/faces.js.map +1 -0
- package/dist/host/index.d.ts +186 -0
- package/dist/host/index.d.ts.map +1 -0
- package/dist/host/index.js +257 -0
- package/dist/host/index.js.map +1 -0
- package/dist/host/tracking.d.ts +60 -0
- package/dist/host/tracking.d.ts.map +1 -0
- package/dist/host/tracking.js +162 -0
- package/dist/host/tracking.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/lockfile.d.ts +21 -4
- package/dist/lockfile.d.ts.map +1 -1
- package/dist/lockfile.js +31 -7
- package/dist/lockfile.js.map +1 -1
- package/dist/peer/index.d.ts +43 -9
- package/dist/peer/index.d.ts.map +1 -1
- package/dist/peer/index.js +156 -241
- package/dist/peer/index.js.map +1 -1
- package/package.json +6 -9
- package/src/host/faces.ts +130 -0
- package/src/host/index.ts +434 -0
- package/src/host/tracking.ts +217 -0
- package/src/index.ts +38 -2
- package/src/lockfile.ts +39 -7
- package/src/peer/index.ts +172 -292
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,83 @@
|
|
|
1
1
|
# @procedures/peer
|
|
2
2
|
|
|
3
|
+
## 0.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 9ea2030: `runProcedureHost()` of `@procedures/server` is the one runner entry on `run()` of `@mark1russell7/service`, for `procedure-server`, `mark serve` and `mark --server` (server stack CLI-8, decisions D6, D7 and D17). This change is a break of the server stack, thus it is its own MINOR (decision D13).
|
|
8
|
+
|
|
9
|
+
- New `runProcedureHost({ name, host, mode, profile, lockfile, log, run })`. It declares the parts of `createProcedureHost()` in one service, and starts `run()`. It gives `{ host, service, life }` when the startup ends.
|
|
10
|
+
- The mode `foreground` has the profile `localDev`: a second Ctrl-C exits at once. The mode `background` has the profile `daemon`: a repeat signal has no effect. Each run declares `onRepeatSignal`. `HOST_PROFILES` gives the profiles as data.
|
|
11
|
+
- The caps of the stages sum to 10 s. They replace the hard-exit timer of 10 s of the entries.
|
|
12
|
+
- The lockfile is a dependency of the service: its connect writes it before the listeners start, and its close removes it after the listeners close. New `lockfileHandle(data)` of `@procedures/peer/lockfile`.
|
|
13
|
+
- `runServer()` (the bin `procedure-server` and `mark serve`) and `startServerMode()` (`mark --server`) install no signal handler and no hard-exit timer. Their promise ends with the lifecycle.
|
|
14
|
+
- `mark --server` is the background host (decision D17). `procedure-server` and `mark serve` run in the foreground.
|
|
15
|
+
- The entries map their transports to a host with `peerHostOptions()` of `@procedures/peer` (CLI-7).
|
|
16
|
+
- `ServerRunOptions.run` and the second argument of `startServerMode()` take the options of `run()`, for a test: a process port and a clock.
|
|
17
|
+
- A random token of `procedure-server` has 32 bytes, as the token of `mark --server`.
|
|
18
|
+
- The one-shot commands of `mark` keep their interrupt. CLI-26 moves them under `run()`.
|
|
19
|
+
|
|
20
|
+
The behavior that changes:
|
|
21
|
+
|
|
22
|
+
- A second Ctrl-C of `mark --server` has no effect: the host ends its drain, in 10 s at most. A second Ctrl-C of `procedure-server` and `mark serve` exits at once with the code 1, as before.
|
|
23
|
+
- The process exits with the code of the lifecycle: 0 after a clean shutdown, and 1 after an error or a startup that failed.
|
|
24
|
+
- A random token of `procedure-server` has 64 hexadecimal characters, not 48.
|
|
25
|
+
|
|
26
|
+
Migration steps:
|
|
27
|
+
|
|
28
|
+
- The site, MiniMongo, Graph and jarvis: no change is necessary. They do not start these entries.
|
|
29
|
+
- A script that starts `procedure-server` or `mark serve` and stops it with a signal: no change is necessary. Wait for the exit, in 10 s at most.
|
|
30
|
+
- A program that embeds the server outside a service: use `startServer()`, which starts it as a peer, and `peer.stop()`.
|
|
31
|
+
|
|
32
|
+
### Patch Changes
|
|
33
|
+
|
|
34
|
+
- @procedures/client@0.12.0
|
|
35
|
+
|
|
36
|
+
## 0.11.0
|
|
37
|
+
|
|
38
|
+
### Minor Changes
|
|
39
|
+
|
|
40
|
+
- 37c96a3: `createProcedureHost()` of `@procedures/peer` is one factory for each procedures server in Node. It gives the parts of a service of `@mark1russell7/service`, and `createPeer()` is a thin wrapper over it (server stack CLI-7, decisions D2, D3 and D4). This change is a break of the server stack, thus it is its own MINOR (decision D13).
|
|
41
|
+
|
|
42
|
+
- New `createProcedureHost({ registry, policy, faces: { http, ws, mcp }, listen, collections })`. It gives `{ handler, upgrade, parts, dependencies, checks, controllers, info }` and `declare(svc)`. It starts nothing and installs no signal handler.
|
|
43
|
+
- The parts: the procedure server (its stop parts of CLI-5), the stop parts of each face, and an `httpTransport` of `@mark1russell7/service/http` for each listener that the host owns. The faces of a listener mount on its mount list, so they share one port.
|
|
44
|
+
- Without a listener, the app mounts `host.handler` and `host.upgrade` on its own listener.
|
|
45
|
+
- The request tracking: each call goes through `life.request`, with the route `<face> <procedure>`. One route pattern for each face (`http *`, `ws *`, `mcp *`) matches each call. The function of the pattern gives the idempotency of each call: `repeatable`, `keyed` (an idempotency key) or `once` (decision D3).
|
|
46
|
+
- Each call is a stream (decision D4). A stream stays in flight until its last item. A cut after the first item ends the stream with an error line, and the line tells the count of the items.
|
|
47
|
+
- The faces are factories: `httpFace()` and `wsFace()` of `@procedures/peer`, and the new `mcpFace()` of `@procedures/mcp-server`. A face of another package has the same shape.
|
|
48
|
+
- The collections come from the option `collections`. The host serves `collections.<name>.*`, and each storage is a dependency handle of the service.
|
|
49
|
+
- `createPeer()` is for a process that has no service of its own. It declares the parts of its host in a service of its own, and starts `run()` of `@mark1russell7/service` on `embeddedProcess()`, a process port that installs no signal handler and never ends the process. `stop()` asks that lifecycle for its shutdown. The new option `profile` gives the profile of that service, and the default is `PEER_PROFILE`.
|
|
50
|
+
- The WebSocket face refuses an upgrade that the server does not admit, with the path of each call: 401 without the token or for a refusal of the request hook, and 403 for a failed browser check. A request hook that admits a request without credentials lets the connection open.
|
|
51
|
+
- New exports of `@procedures/peer`: `createProcedureHost`, `httpFace`, `wsFace`, `LOCAL_LISTENER_KNOBS`, `peerHostOptions`, `PEER_PROFILE`, `faceHandler`, `callOperations`, `operationOfCall`, and their types. `Peer.getHost()` gives the host of a peer.
|
|
52
|
+
- `@procedures/mcp-server`: new `mcpFace()`. New option `httpOptions.mount` and method `McpServerTransport.handleHttp()`: a Streamable HTTP endpoint on a listener of another part. `McpServerTransport` takes each `RequestHandler`, not only a `Server`.
|
|
53
|
+
- `@procedures/client`: new `ProcedureServer.procedureOf(method)`, and `ProcedureServer.admits(credentials)`: the request hook, the token check and the authenticate function of a call, for a connection before its first call.
|
|
54
|
+
- `@procedures/peer` depends on `@mark1russell7/service` `^0.5.2`, for `embeddedProcess()` (SYS-27), and no longer on Express.
|
|
55
|
+
|
|
56
|
+
The behavior that changes:
|
|
57
|
+
|
|
58
|
+
- A peer has no `/health` and no `/api/health` route. The probes come from the runner. `checkServer()` of `@procedures/peer/lockfile` calls `host.info` with the token of the lockfile (a part of CLI-9).
|
|
59
|
+
- A peer has one token: the token of its transports, or else the token of its policy. Two different tokens throw at `createPeer()`. The token applies to each face.
|
|
60
|
+
- The token of a peer applies to the upgrade of each WebSocket connection, also for a transport with no token of its own.
|
|
61
|
+
- `peer.stop({ grace })`: the grace has no effect, and the option is deprecated. The profile of the peer gives the drain, and `PEER_PROFILE` aborts the calls in flight at the start of the drain, as `stop()` without a grace did.
|
|
62
|
+
- A refused browser request gets the error of wire v1: `FORBIDDEN` with a `reason`. Before, the peer sent its own JSON.
|
|
63
|
+
- A peer starts one time. A `start()` after `stop()` throws: create a new peer.
|
|
64
|
+
- A listener that the peer cannot use gives `The HTTP server cannot listen on <host>:<port>` or `The WebSocket server cannot listen on <host>:<port>`.
|
|
65
|
+
- `HttpTransportConfig.cors: true` gives the CORS of `procedureHandler`: each origin, without credentials.
|
|
66
|
+
|
|
67
|
+
Migration steps:
|
|
68
|
+
|
|
69
|
+
- The site and MiniMongo: no change is necessary, unless they read `/health` of a peer. Then call `host.info` with the token.
|
|
70
|
+
- The MCP host of Graph: no change is necessary. To share the port of the Graph host, mount `mcpFace()` on its listener.
|
|
71
|
+
- jarvis and Graph under `run()`: build the procedures with `createProcedureHost()`, and call `host.declare(svc)` in the service of the process. Give `listen` for a port of the host, or mount `host.handler` and `host.upgrade` on the listener of the app.
|
|
72
|
+
- A caller of `createPeer()` with a different token for each transport: give one token.
|
|
73
|
+
- A Docker `HEALTHCHECK` or a probe of Kubernetes that read `/health` of a peer: use the probes of the service. Give the service `probeServer({ port, server_retry_after })` on a port of its own, or mount `probeHandler(health, { prefix: "/health" })` on the port of the app. Both come from `@mark1russell7/service/http`, and they answer `/health/startup`, `/health/readiness` and `/health/liveness`. The lockfile check of a client calls `host.info` with the token of the lockfile.
|
|
74
|
+
- A test that read `/health` of a peer: call `host.info` with the token, or check the status 404.
|
|
75
|
+
|
|
76
|
+
### Patch Changes
|
|
77
|
+
|
|
78
|
+
- Updated dependencies [37c96a3]
|
|
79
|
+
- @procedures/client@0.11.0
|
|
80
|
+
|
|
3
81
|
## 0.10.0
|
|
4
82
|
|
|
5
83
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
- **Automatic Discovery**: Peers exchange procedure manifests on connection
|
|
18
18
|
- **Manifest Generation**: Generate TypeScript and JSON manifests from registered procedures
|
|
19
19
|
- **Connection Management**: Track and manage multiple peer connections
|
|
20
|
-
- **
|
|
20
|
+
- **One procedures host**: `createProcedureHost()` gives the parts of a service of `@mark1russell7/service` (CLI-7). It has no framework: the listener is the `httpTransport` of `@mark1russell7/service/http`.
|
|
21
21
|
- **Zero Configuration**: Auto-register all procedures from the global registry
|
|
22
22
|
|
|
23
23
|
## Architecture
|
|
@@ -107,12 +107,49 @@ This package depends on:
|
|
|
107
107
|
- `@procedures/client` - Core client/procedure system
|
|
108
108
|
- `zod` - Schema validation
|
|
109
109
|
- `zod-to-json-schema` - JSON schema generation
|
|
110
|
-
- `
|
|
110
|
+
- `@mark1russell7/service` - The runner: the contracts of the parts, and `httpTransport` of `./http`
|
|
111
111
|
|
|
112
112
|
## Usage
|
|
113
113
|
|
|
114
|
+
### The procedures host
|
|
115
|
+
|
|
116
|
+
`createProcedureHost()` builds one procedures server with its faces, and gives the parts of a service. It starts nothing and installs no signal handler. The service of the process declares the parts, and only the entry of the process starts `run()` (decision D2).
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { localDev, service } from "@mark1russell7/service";
|
|
120
|
+
import { hostPolicy, seconds } from "@procedures/client";
|
|
121
|
+
import { mcpFace } from "@procedures/mcp-server";
|
|
122
|
+
import { createProcedureHost, LOCAL_LISTENER_KNOBS } from "@procedures/peer";
|
|
123
|
+
|
|
124
|
+
const host = createProcedureHost({
|
|
125
|
+
registry,
|
|
126
|
+
policy: hostPolicy({ token }),
|
|
127
|
+
faces: { http: true, ws: true, mcp: mcpFace() },
|
|
128
|
+
listen: [{ port: 3000, knobs: LOCAL_LISTENER_KNOBS }],
|
|
129
|
+
collections: { notes: storage },
|
|
130
|
+
});
|
|
131
|
+
const svc = host
|
|
132
|
+
.declare(service("app").profile(localDev))
|
|
133
|
+
.readiness({ type: "debounce", interval: seconds(5), failAfter: 3, recoverAfter: 1 })
|
|
134
|
+
.liveness({ type: "window", failAfter: seconds(45) })
|
|
135
|
+
.startup(seconds(30));
|
|
136
|
+
await svc.run();
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
- **The parts.** `host.parts` are the transports: the procedure server, the stop parts of each face, and an `httpTransport` for each listener. `host.dependencies` has one dependency handle for each storage. `host.controllers` has one route pattern for each face.
|
|
140
|
+
- **The faces.** `http` and `ws` are the faces of this package (`httpFace()`, `wsFace()`). `mcp` takes `mcpFace()` of `@procedures/mcp-server`. A face of another package is a function with the same shape.
|
|
141
|
+
- **The listeners.** The faces of a listener mount on its mount list, so they share one port. Without `listen`, the app mounts `host.handler` and `host.upgrade` on its own listener.
|
|
142
|
+
- **The request tracking.** Each call goes through `life.request`, with the route `<face> <procedure>`. The function of the pattern gives the idempotency of each call: a repeatable procedure, a keyed call, or a call that runs one time (decision D3).
|
|
143
|
+
- **The streams.** Each call is a stream, and a unary call is a stream of one (decision D4). A stream stays in flight until its end. A cut after the first item ends the stream with an error line.
|
|
144
|
+
- **The collections.** The option `collections` gives the storages. The host serves `collections.<name>.*` for each one.
|
|
145
|
+
- **No `/health` route.** The probes come from the runner (`probeHandler` or `probeServer`). A client checks a lockfile with `host.info` and the token of the lockfile.
|
|
146
|
+
|
|
114
147
|
### Creating a Server Peer
|
|
115
148
|
|
|
149
|
+
`createPeer()` is for a process that has no service of its own: a test, or a host that a procedure starts (`server.create`). The peer builds a host with `createProcedureHost()`, declares its parts in a service of its own, and starts `run()` of `@mark1russell7/service` on a process port that installs no signal handler and never ends the process. `stop()` asks that lifecycle for its shutdown. A process that has a service of its own uses `createProcedureHost()` and `host.declare(svc)` in place of a peer.
|
|
150
|
+
|
|
151
|
+
A peer has one token, and it starts one time. The WebSocket face refuses an upgrade that the server does not admit: 401 without the token or for a refusal of the request hook, and 403 for a failed browser check.
|
|
152
|
+
|
|
116
153
|
```typescript
|
|
117
154
|
import { createPeer } from "@procedures/peer";
|
|
118
155
|
|
|
@@ -326,10 +363,12 @@ Create a new peer instance.
|
|
|
326
363
|
- `registry?: ProcedureRegistry` - Custom procedure registry (default: global)
|
|
327
364
|
|
|
328
365
|
**Returns:** Peer instance with methods:
|
|
329
|
-
- `start(): Promise<void>` - Start
|
|
330
|
-
- `stop(): Promise<void>` - Stop
|
|
366
|
+
- `start(): Promise<void>` - Start the parts of the host. A peer starts one time.
|
|
367
|
+
- `stop(options?: { grace }): Promise<void>` - Stop the parts in the sequence of the stages
|
|
331
368
|
- `getEndpoints(): PeerEndpoint[]` - Get active endpoints
|
|
332
369
|
- `getServer(): ProcedureServer` - Get underlying procedure server
|
|
370
|
+
- `getHost(): ProcedureHost` - The procedures host: its parts, its faces and its info
|
|
371
|
+
- `connections(): string[]` and `clientFor(id): Client` - The WebSocket connections, and a client of one
|
|
333
372
|
|
|
334
373
|
#### Transport Configurations
|
|
335
374
|
|
|
@@ -339,9 +378,9 @@ Create a new peer instance.
|
|
|
339
378
|
{
|
|
340
379
|
type: "http",
|
|
341
380
|
port?: number, // Default: 3000
|
|
342
|
-
host?: string, // Default: "
|
|
381
|
+
host?: string, // Default: "127.0.0.1"
|
|
343
382
|
basePath?: string, // Default: "/api"
|
|
344
|
-
cors?: boolean, // Default:
|
|
383
|
+
cors?: boolean, // Default: false
|
|
345
384
|
corsOrigins?: string[] // Allowed origins
|
|
346
385
|
}
|
|
347
386
|
```
|
|
@@ -352,7 +391,7 @@ Create a new peer instance.
|
|
|
352
391
|
{
|
|
353
392
|
type: "websocket",
|
|
354
393
|
port?: number, // Default: 3001
|
|
355
|
-
host?: string, // Default: "
|
|
394
|
+
host?: string, // Default: "127.0.0.1"
|
|
356
395
|
path?: string // Default: "/ws"
|
|
357
396
|
}
|
|
358
397
|
```
|