@procedures/client 0.43.0 → 0.45.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 +37 -0
- package/README.md +2 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/http/server/transport.d.ts +2 -0
- package/dist/adapters/http/server/transport.d.ts.map +1 -1
- package/dist/adapters/http/server/transport.js +8 -3
- package/dist/adapters/http/server/transport.js.map +1 -1
- package/dist/adapters/http/server/types.d.ts +6 -0
- package/dist/adapters/http/server/types.d.ts.map +1 -1
- package/dist/adapters/sequence.d.ts +4 -0
- package/dist/adapters/sequence.d.ts.map +1 -1
- package/dist/adapters/sequence.js +7 -1
- package/dist/adapters/sequence.js.map +1 -1
- package/dist/adapters/websocket/server/transport.d.ts +2 -0
- package/dist/adapters/websocket/server/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/server/transport.js +13 -8
- package/dist/adapters/websocket/server/transport.js.map +1 -1
- package/dist/adapters/websocket/server/types.d.ts +6 -0
- package/dist/adapters/websocket/server/types.d.ts.map +1 -1
- package/dist/adapters/websocket/shared/rpc.d.ts +3 -0
- package/dist/adapters/websocket/shared/rpc.d.ts.map +1 -1
- package/dist/adapters/websocket/shared/rpc.js +1 -1
- package/dist/adapters/websocket/shared/rpc.js.map +1 -1
- package/dist/browser.d.ts +7 -3
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +5 -1
- package/dist/browser.js.map +1 -1
- package/dist/logger.d.ts +36 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +40 -0
- package/dist/logger.js.map +1 -0
- package/dist/procedures/core/index.d.ts +50 -2
- package/dist/procedures/core/index.d.ts.map +1 -1
- package/dist/procedures/core/index.js +148 -3
- package/dist/procedures/core/index.js.map +1 -1
- package/dist/procedures/core/operands.d.ts +4 -2
- package/dist/procedures/core/operands.d.ts.map +1 -1
- package/dist/procedures/core/operands.js +4 -3
- package/dist/procedures/core/operands.js.map +1 -1
- package/dist/procedures/index.d.ts +2 -2
- package/dist/procedures/index.d.ts.map +1 -1
- package/dist/procedures/index.js +1 -1
- package/dist/procedures/index.js.map +1 -1
- package/dist/procedures/program.d.ts +14 -1
- package/dist/procedures/program.d.ts.map +1 -1
- package/dist/procedures/program.js +8 -1
- package/dist/procedures/program.js.map +1 -1
- package/dist/server/error-policy.d.ts +3 -3
- package/dist/server/error-policy.d.ts.map +1 -1
- package/dist/server/face-rules.d.ts +62 -0
- package/dist/server/face-rules.d.ts.map +1 -0
- package/dist/server/face-rules.js +79 -0
- package/dist/server/face-rules.js.map +1 -0
- package/dist/server/procedure-server.d.ts +9 -4
- package/dist/server/procedure-server.d.ts.map +1 -1
- package/dist/server/procedure-server.js +18 -4
- package/dist/server/procedure-server.js.map +1 -1
- package/dist/server/server.d.ts +27 -6
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +43 -12
- package/dist/server/server.js.map +1 -1
- package/dist/server/types.d.ts +14 -1
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js.map +1 -1
- package/package.json +2 -2
- package/src/adapters/http/server/transport.ts +8 -5
- package/src/adapters/http/server/types.ts +7 -0
- package/src/adapters/sequence.ts +6 -1
- package/src/adapters/websocket/server/transport.ts +14 -9
- package/src/adapters/websocket/server/types.ts +6 -0
- package/src/adapters/websocket/shared/rpc.ts +4 -1
- package/src/browser.ts +13 -1
- package/src/logger.ts +52 -0
- package/src/procedures/core/index.ts +211 -2
- package/src/procedures/core/operands.ts +5 -4
- package/src/procedures/index.ts +5 -0
- package/src/procedures/program.ts +24 -2
- package/src/server/error-policy.ts +3 -3
- package/src/server/face-rules.ts +115 -0
- package/src/server/procedure-server.ts +27 -7
- package/src/server/server.ts +58 -15
- package/src/server/types.ts +15 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"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"}
|
package/dist/server/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA2EzC;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAkB,MAAM,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;AAsR9F;;GAEG;AACH,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C,YAAY,MAAc;QACxB,KAAK,CACH,wBAAwB,UAAU,CAAC,MAAM,CAAC,GACxC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,EAC7C,EAAE,CACH,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,WAAY,SAAQ,KAAK;IAGlB,IAAI;IACJ,SAAS;IAH3B,YACE,OAAe,EACC,IAAI,GAAW,gBAAgB,EAC/B,SAAS,GAAY,KAAK;QAE1C,KAAK,CAAC,OAAO,CAAC,CAAC;oBAHC,IAAI;yBACJ,SAAS;QAGzB,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@procedures/client",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.45.0",
|
|
5
5
|
"description": "Procedures as data for TypeScript: define typed procedures once and call them in process, over HTTP, WebSocket, the CLI or MCP. Streaming, middleware, retries, caching and a JSON program format.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"rpc",
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
},
|
|
113
113
|
"dependencies": {
|
|
114
114
|
"@mark1russell7/algebra": "^0.8.0",
|
|
115
|
-
"@mark1russell7/coeval": "^0.
|
|
115
|
+
"@mark1russell7/coeval": "^0.10.0",
|
|
116
116
|
"@mark1russell7/quantity": "^0.2.0",
|
|
117
117
|
"@mark1russell7/service": "^0.6.0",
|
|
118
118
|
"@types/ws": "^8.18.1",
|
|
@@ -18,6 +18,7 @@ import { createPatternServerUrlStrategy } from "./strategies.js";
|
|
|
18
18
|
import { applyCors, corsOrigins, corsPolicy, type CorsPolicy } from "./cors.js";
|
|
19
19
|
import { createProcedureHandler, type NodeProcedureHandler } from "./handler.js";
|
|
20
20
|
import { isBatchRequest } from "./batch.js";
|
|
21
|
+
import { writeLog, type Logger } from "../../../logger.js";
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* This function gives the route of the procedure handler: each path under the base path. With
|
|
@@ -61,9 +62,12 @@ export class HttpServerTransport implements ServerTransport {
|
|
|
61
62
|
/** The CORS of the app, or undefined when it is off */
|
|
62
63
|
private readonly cors: CorsPolicy | undefined;
|
|
63
64
|
private stopped = false;
|
|
65
|
+
/** The logger of the start and the stop: the option, or the logger of the server (CLI-87) */
|
|
66
|
+
private readonly logger: Logger | undefined;
|
|
64
67
|
|
|
65
68
|
constructor(server: RequestHandler, options: HttpServerTransportOptions = {}) {
|
|
66
69
|
const basePath = options.basePath ?? "/api";
|
|
70
|
+
this.logger = options.logger ?? server.logger;
|
|
67
71
|
this.options = {
|
|
68
72
|
port: options.port ?? 3000,
|
|
69
73
|
// Loopback by default (see BUGS-2026-07 H19)
|
|
@@ -159,7 +163,7 @@ export class HttpServerTransport implements ServerTransport {
|
|
|
159
163
|
// If an HTTP server was provided, use it (don't create or listen)
|
|
160
164
|
if (this.options.httpServer) {
|
|
161
165
|
this.httpServer = this.options.httpServer;
|
|
162
|
-
|
|
166
|
+
writeLog(this.logger, "info", "Using existing HTTP server", { transport: this.name }, `[${this.name}] Using existing HTTP server`);
|
|
163
167
|
return Promise.resolve();
|
|
164
168
|
}
|
|
165
169
|
|
|
@@ -170,9 +174,8 @@ export class HttpServerTransport implements ServerTransport {
|
|
|
170
174
|
this.httpServer = server;
|
|
171
175
|
|
|
172
176
|
server.listen(this.options.port, this.options.host, () => {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
);
|
|
177
|
+
const url = `http://${this.options.host}:${this.port() ?? this.options.port}`;
|
|
178
|
+
writeLog(this.logger, "info", "Server listening", { transport: this.name, url }, `[${this.name}] Server listening on ${url}`);
|
|
176
179
|
resolve();
|
|
177
180
|
});
|
|
178
181
|
|
|
@@ -203,7 +206,7 @@ export class HttpServerTransport implements ServerTransport {
|
|
|
203
206
|
if (err) {
|
|
204
207
|
reject(err);
|
|
205
208
|
} else {
|
|
206
|
-
|
|
209
|
+
writeLog(this.logger, "info", "Server stopped", { transport: this.name }, `[${this.name}] Server stopped`);
|
|
207
210
|
resolve();
|
|
208
211
|
}
|
|
209
212
|
});
|
|
@@ -10,6 +10,7 @@ import type { Method } from "../../../client/types.js";
|
|
|
10
10
|
import type { BrowserGuardOptions } from "../../../server/browser-guard.js";
|
|
11
11
|
import type { Clock } from "../../../clock.js";
|
|
12
12
|
import type { Duration } from "../../../duration.js";
|
|
13
|
+
import type { Logger } from "../../../logger.js";
|
|
13
14
|
import type { Addressing } from "./addressing.js";
|
|
14
15
|
import type { HandlerBatchOptions } from "./batch.js";
|
|
15
16
|
import type { UrlStrategyRequest } from "./strategies.js";
|
|
@@ -131,4 +132,10 @@ export interface HttpServerTransportOptions extends BrowserGuardOptions {
|
|
|
131
132
|
allowedHeaders?: string[];
|
|
132
133
|
credentials?: boolean;
|
|
133
134
|
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The logger of the transport (CLI-87): the lines of its start and its stop go to it. The default
|
|
138
|
+
* is the logger of the server. Without both, each line goes to `console.log` as before.
|
|
139
|
+
*/
|
|
140
|
+
logger?: Logger | undefined;
|
|
134
141
|
}
|
package/src/adapters/sequence.ts
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* so the reader skips it before the check.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { writeLog, type Logger } from "../logger.js";
|
|
18
|
+
|
|
17
19
|
/**
|
|
18
20
|
* The metadata key of a stream item after a gap: the number of frames that did not arrive
|
|
19
21
|
* immediately before this item. The receiver sets it. It does not cross the wire.
|
|
@@ -32,6 +34,9 @@ export const STREAM_ITEM_METADATA_KEY = "stream";
|
|
|
32
34
|
export class StreamSequence {
|
|
33
35
|
private last = 0;
|
|
34
36
|
|
|
37
|
+
/** The logger of the warning of a gap (CLI-87). Without it, the warning goes to `console.warn`. */
|
|
38
|
+
constructor(private readonly logger?: Logger | undefined) {}
|
|
39
|
+
|
|
35
40
|
/**
|
|
36
41
|
* This function examines the `seq` of a frame. It gives -1 for a frame to drop: its `seq` is
|
|
37
42
|
* not above the last one. Else it gives the number of frames that did not arrive before this
|
|
@@ -42,7 +47,7 @@ export class StreamSequence {
|
|
|
42
47
|
if (seq <= this.last) return -1;
|
|
43
48
|
const gap = seq - this.last - 1;
|
|
44
49
|
this.last = seq;
|
|
45
|
-
if (gap > 0)
|
|
50
|
+
if (gap > 0) writeLog(this.logger, "warn", "Frames lost", { requestId: id, seq, lost: gap }, `[stream] Frames lost before seq ${seq} of the call ${id}: ${gap}`);
|
|
46
51
|
return gap;
|
|
47
52
|
}
|
|
48
53
|
}
|
|
@@ -33,6 +33,7 @@ import { attachUpgradeTarget, refuseUpgrade } from "./upgrade-router.js";
|
|
|
33
33
|
import { realClock, type Cancel } from "../../../clock.js";
|
|
34
34
|
import { PROTOCOL_VERSION, protocolVersionOf } from "../../../protocol.js";
|
|
35
35
|
import { TIMER_DEFAULTS } from "../../../timers.js";
|
|
36
|
+
import { writeLog, type Logger } from "../../../logger.js";
|
|
36
37
|
import {
|
|
37
38
|
remoteEvents,
|
|
38
39
|
subscribeRemote,
|
|
@@ -75,7 +76,7 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
75
76
|
private options: Required<
|
|
76
77
|
Omit<
|
|
77
78
|
WebSocketServerTransportOptions,
|
|
78
|
-
"server" | "authenticate" | "onConnection" | "allowedOrigins" | "allowedHosts" | "heartbeat" | "reverseRequestTimeout" | "secretHeaders"
|
|
79
|
+
"server" | "authenticate" | "onConnection" | "allowedOrigins" | "allowedHosts" | "heartbeat" | "reverseRequestTimeout" | "secretHeaders" | "logger"
|
|
79
80
|
>
|
|
80
81
|
> & {
|
|
81
82
|
server: WebSocketServerTransportOptions["server"];
|
|
@@ -93,6 +94,8 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
93
94
|
/** True: a cookie of an upgrade is trusted only from a named origin, with or without an auth path */
|
|
94
95
|
private cookiesNamed = false;
|
|
95
96
|
private server: RequestHandler;
|
|
97
|
+
/** The logger of the lines of the transport: the option, or the logger of the server (CLI-87) */
|
|
98
|
+
private readonly logger: Logger | undefined;
|
|
96
99
|
private connections: Set<WebSocket> = new Set();
|
|
97
100
|
|
|
98
101
|
/** The time until the first frame of a call to a client, in milliseconds (R24) */
|
|
@@ -128,6 +131,7 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
128
131
|
|
|
129
132
|
constructor(server: RequestHandler, options: WebSocketServerTransportOptions = {}) {
|
|
130
133
|
this.server = server;
|
|
134
|
+
this.logger = options.logger ?? server.logger;
|
|
131
135
|
this.options = {
|
|
132
136
|
server: options.server,
|
|
133
137
|
path: options.path ?? "/ws",
|
|
@@ -161,7 +165,7 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
161
165
|
for (const ws of this.connections) {
|
|
162
166
|
const missed = this.missedPongs.get(ws) ?? 0;
|
|
163
167
|
if (missed >= this.heartbeat.misses) {
|
|
164
|
-
|
|
168
|
+
writeLog(this.logger, "error", "A connection missed its pongs: closing it", { transport: this.name, missed }, `[${this.name}] A connection missed ${missed} pongs: closing it`);
|
|
165
169
|
ws.terminate();
|
|
166
170
|
continue;
|
|
167
171
|
}
|
|
@@ -217,7 +221,7 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
217
221
|
try {
|
|
218
222
|
callback(await this.options.authenticate(req));
|
|
219
223
|
} catch (error) {
|
|
220
|
-
|
|
224
|
+
writeLog(this.logger, "error", "Authentication error", { transport: this.name, path: this.options.path, error }, "[WebSocket] Authentication error:", error);
|
|
221
225
|
callback(false);
|
|
222
226
|
}
|
|
223
227
|
},
|
|
@@ -235,7 +239,7 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
235
239
|
this.heartbeatTimer.unref?.();
|
|
236
240
|
}
|
|
237
241
|
|
|
238
|
-
|
|
242
|
+
writeLog(this.logger, "info", "WebSocket server listening", { transport: this.name, path: this.options.path }, `[${this.name}] WebSocket server listening on ${this.options.path}`);
|
|
239
243
|
}
|
|
240
244
|
|
|
241
245
|
async stop(): Promise<void> {
|
|
@@ -267,7 +271,7 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
267
271
|
if (err) {
|
|
268
272
|
reject(err);
|
|
269
273
|
} else {
|
|
270
|
-
|
|
274
|
+
writeLog(this.logger, "info", "WebSocket server stopped", { transport: this.name, path: this.options.path }, `[${this.name}] WebSocket server stopped`);
|
|
271
275
|
this.wss = null;
|
|
272
276
|
resolve();
|
|
273
277
|
}
|
|
@@ -409,6 +413,7 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
409
413
|
requestTimeoutMs: this.reverseRequestTimeoutMs,
|
|
410
414
|
streamWindow: 64,
|
|
411
415
|
idPrefix: "r:",
|
|
416
|
+
logger: this.logger,
|
|
412
417
|
}),
|
|
413
418
|
});
|
|
414
419
|
|
|
@@ -422,7 +427,7 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
422
427
|
try {
|
|
423
428
|
handler(trackedConnection);
|
|
424
429
|
} catch (error) {
|
|
425
|
-
|
|
430
|
+
writeLog(this.logger, "error", "Connect handler error", { transport: this.name, connectionId, error }, "[WebSocket] Connect handler error:", error);
|
|
426
431
|
}
|
|
427
432
|
}
|
|
428
433
|
|
|
@@ -434,7 +439,7 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
434
439
|
try {
|
|
435
440
|
await this.handleMessage(ws, data, req, connectionId);
|
|
436
441
|
} catch (error) {
|
|
437
|
-
|
|
442
|
+
writeLog(this.logger, "error", "Message handling error", { transport: this.name, connectionId, error }, "[WebSocket] Message handling error:", error);
|
|
438
443
|
this.sendError(ws, "unknown", "Internal server error");
|
|
439
444
|
}
|
|
440
445
|
});
|
|
@@ -457,7 +462,7 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
457
462
|
try {
|
|
458
463
|
handler(conn);
|
|
459
464
|
} catch (error) {
|
|
460
|
-
|
|
465
|
+
writeLog(this.logger, "error", "Disconnect handler error", { transport: this.name, connectionId, error }, "[WebSocket] Disconnect handler error:", error);
|
|
461
466
|
}
|
|
462
467
|
}
|
|
463
468
|
}
|
|
@@ -467,7 +472,7 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
467
472
|
// cleanup and the disconnect event (deep dive TRN-16: before, this handler removed the
|
|
468
473
|
// connection first, so the close handler found nothing and sent no disconnect event).
|
|
469
474
|
ws.on("error", (error: Error) => {
|
|
470
|
-
|
|
475
|
+
writeLog(this.logger, "error", "Connection error", { transport: this.name, connectionId, error }, "[WebSocket] Connection error:", error);
|
|
471
476
|
});
|
|
472
477
|
}
|
|
473
478
|
|
|
@@ -9,6 +9,7 @@ import type { Server as HttpServer } from "http";
|
|
|
9
9
|
import type { WebSocket } from "ws";
|
|
10
10
|
import type { BrowserGuardOptions } from "../../../server/browser-guard.js";
|
|
11
11
|
import type { Duration } from "../../../duration.js";
|
|
12
|
+
import type { Logger } from "../../../logger.js";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* WebSocket authentication handler.
|
|
@@ -99,6 +100,11 @@ export interface WebSocketServerTransportOptions extends Omit<BrowserGuardOption
|
|
|
99
100
|
* `Proxy-Authorization`, `Cookie` and `X-Api-Key` are always secret.
|
|
100
101
|
*/
|
|
101
102
|
secretHeaders?: readonly string[] | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* The logger of the transport (CLI-87): the lines of its start, its stop, its heartbeat and its
|
|
105
|
+
* errors go to it. The default is the logger of the server. Without both, each line goes to the console as before.
|
|
106
|
+
*/
|
|
107
|
+
logger?: Logger | undefined;
|
|
102
108
|
}
|
|
103
109
|
|
|
104
110
|
/**
|
|
@@ -19,6 +19,7 @@ import { StreamSequence, gapOf, itemMetadata } from "../../sequence.js";
|
|
|
19
19
|
import { DELIVERY_METADATA_KEY } from "../../../client/middleware/items.js";
|
|
20
20
|
import { errorCodeOf, foldedErrorCode, retryableOf } from "../../../protocol.js";
|
|
21
21
|
import { lastResortError } from "../../../server/last-resort.js";
|
|
22
|
+
import type { Logger } from "../../../logger.js";
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* A queue of response items that one reader takes in order: push, then end or fail. The reader
|
|
@@ -168,6 +169,8 @@ export interface OutgoingCallsOptions {
|
|
|
168
169
|
streamWindow: number;
|
|
169
170
|
/** The start of the wire id of each call: the two sides use different ones. */
|
|
170
171
|
idPrefix: string;
|
|
172
|
+
/** The logger of the warning of a stream gap (CLI-87). Without it, the warning goes to the console. */
|
|
173
|
+
logger?: Logger | undefined;
|
|
171
174
|
}
|
|
172
175
|
|
|
173
176
|
/** The calls that this side sends on one socket. */
|
|
@@ -201,7 +204,7 @@ export class OutgoingCalls {
|
|
|
201
204
|
const wireId = `${this.options.idPrefix}${message.id}~${++this.seq}`;
|
|
202
205
|
const window = this.options.streamWindow;
|
|
203
206
|
const queue = new ItemQueue<ResponseItem<unknown>>();
|
|
204
|
-
const pending: PendingCall = { messageId: message.id, queue, order: new StreamSequence() };
|
|
207
|
+
const pending: PendingCall = { messageId: message.id, queue, order: new StreamSequence(this.options.logger) };
|
|
205
208
|
const limited = Number.isFinite(this.options.requestTimeoutMs) && this.options.requestTimeoutMs > 0;
|
|
206
209
|
if (limited) pending.timeout = setTimeout(() => {
|
|
207
210
|
if (this.pending.get(wireId) !== pending) return;
|
package/src/browser.ts
CHANGED
|
@@ -77,11 +77,14 @@ export {
|
|
|
77
77
|
// The server transports are Node only: the root entry point exports them.
|
|
78
78
|
// ============================================================================
|
|
79
79
|
export { Server } from "./server/server.js";
|
|
80
|
-
export type { ServerOptions } from "./server/server.js";
|
|
80
|
+
export type { ServerOptions, ServerErrorContext } from "./server/server.js";
|
|
81
81
|
// The error policy of a server: what an error tells the caller (roadmap CLI-80)
|
|
82
82
|
export type { ServerErrorPolicy, ExposedError, ErrorCall } from "./server/error-policy.js";
|
|
83
83
|
export { exposeError, lastResortError, HIDDEN_ERROR_MESSAGE, DEFAULT_KEPT_METADATA } from "./server/error-policy.js";
|
|
84
84
|
export { ProcedureServer, createProcedureServer, tokenMatches } from "./server/procedure-server.js";
|
|
85
|
+
// The logger port of the server side, in the shape of the Logger of the runner (CLI-87)
|
|
86
|
+
export { CONSOLE_LOGGER, writeLog } from "./logger.js";
|
|
87
|
+
export type { Logger, LogLevel, LogFields } from "./logger.js";
|
|
85
88
|
export { STREAM_METADATA_KEY, isUnboundedStream } from "./server/stream-metadata.js";
|
|
86
89
|
// The credentials of a network request (roadmap R18), and the parts that a request hook reads (CLI-2a)
|
|
87
90
|
export { credentialsOf, bearerOf, withoutSecretHeaders, cookiesOf, requestCredentials, LOCAL_CREDENTIALS, isLocalCredentials } from "./server/request-auth.js";
|
|
@@ -259,6 +262,7 @@ export {
|
|
|
259
262
|
allCoreProcedures,
|
|
260
263
|
allCoreModules,
|
|
261
264
|
chainProcedure,
|
|
265
|
+
traceProcedure,
|
|
262
266
|
parallelProcedure,
|
|
263
267
|
conditionalProcedure,
|
|
264
268
|
andProcedure,
|
|
@@ -385,6 +389,11 @@ export type {
|
|
|
385
389
|
CallPosition,
|
|
386
390
|
RunDocumentOptions,
|
|
387
391
|
RunProgramOptions,
|
|
392
|
+
RunControlOptions,
|
|
393
|
+
StepObserver,
|
|
394
|
+
// The steps of client.trace
|
|
395
|
+
TraceRow,
|
|
396
|
+
TraceReason,
|
|
388
397
|
// The envelope form of a program (coeval M7)
|
|
389
398
|
EnvelopeProgram,
|
|
390
399
|
EnvelopeRef,
|
|
@@ -535,6 +544,9 @@ export type {
|
|
|
535
544
|
// The policy of a host (roadmap 2.3): one builder for the MCP server, the server CLI and mark
|
|
536
545
|
export { hostPolicy, procedureServerOptions, exposePatterns, surfaceRule } from "./server/host-policy.js";
|
|
537
546
|
export type { HostPolicy, HostPolicyOptions, ToolSurface } from "./server/host-policy.js";
|
|
547
|
+
// The rules of one face (CLI-43, AG1-B): a narrowing and an expose rule for each call of the face
|
|
548
|
+
export { faceRules } from "./server/face-rules.js";
|
|
549
|
+
export type { FaceRules, FaceRulesOptions } from "./server/face-rules.js";
|
|
538
550
|
|
|
539
551
|
// ============================================================================
|
|
540
552
|
// Nested Route API - Batch calls with per-call middleware overrides
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The logger port of the server side (CLI-87): the server, the host and each server transport
|
|
3
|
+
* write their log lines to it.
|
|
4
|
+
*
|
|
5
|
+
* The port has the shape of the `Logger` of `@mark1russell7/service`: one method `log(level,
|
|
6
|
+
* message, fields)`. The type is structural, so the engine imports no code of the runner (CLI-5).
|
|
7
|
+
* Thus one object goes to `run({ logger })` of the runner and to `createProcedureHost({ logger })`.
|
|
8
|
+
*
|
|
9
|
+
* - The message tells what occurred. The fields hold the context: the transport, the path, the
|
|
10
|
+
* request id and the error.
|
|
11
|
+
* - Without a logger, each line goes to the console, with the same method and the same arguments
|
|
12
|
+
* as before. `CONSOLE_LOGGER` is the console adapter of the port, for the hooks that get a logger.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** The levels of a log line, from the least to the most severe. They are the levels of the runner. */
|
|
16
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
17
|
+
|
|
18
|
+
/** The context of a log line: the transport, the path, the request id, the error. */
|
|
19
|
+
export type LogFields = Readonly<Record<string, unknown>>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The logger of a server, a host or a server transport. An adapter to another logger is a few
|
|
23
|
+
* lines, for example `{ log: (level, message, fields) => app[level](fields, message) }`.
|
|
24
|
+
*/
|
|
25
|
+
export interface Logger {
|
|
26
|
+
log(level: LogLevel, message: string, fields: LogFields): void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The method of the console for each level: `info` writes with `console.log`. */
|
|
30
|
+
const CONSOLE_METHODS = Object.freeze({ debug: "debug", info: "log", warn: "warn", error: "error" } as const);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The console adapter of the port. It writes the message and then the fields with the console method
|
|
34
|
+
* of the level: `console.debug`, `console.log`, `console.warn` or `console.error`. It leaves out empty fields.
|
|
35
|
+
*/
|
|
36
|
+
export const CONSOLE_LOGGER: Logger = Object.freeze({
|
|
37
|
+
log(level: LogLevel, message: string, fields: LogFields): void {
|
|
38
|
+
const method = console[CONSOLE_METHODS[level]];
|
|
39
|
+
if (Object.keys(fields).length > 0) method(message, fields);
|
|
40
|
+
else method(message);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* This function writes one log line. With a logger, the logger gets the level, the message and the
|
|
46
|
+
* fields. Without a logger, the console method of the level gets `consoleArgs`: the line as before.
|
|
47
|
+
* A face of another package uses it too, so its default output stays the same.
|
|
48
|
+
*/
|
|
49
|
+
export function writeLog(logger: Logger | undefined, level: LogLevel, message: string, fields: LogFields, ...consoleArgs: unknown[]): void {
|
|
50
|
+
if (logger) logger.log(level, message, fields);
|
|
51
|
+
else console[CONSOLE_METHODS[level]](...consoleArgs);
|
|
52
|
+
}
|
|
@@ -45,11 +45,22 @@
|
|
|
45
45
|
* ```
|
|
46
46
|
*/
|
|
47
47
|
|
|
48
|
-
import { PURE_EFFECTS, withEffects } from "../effects.js";
|
|
48
|
+
import { PURE_EFFECTS, REGISTRY_READ_EFFECTS, withEffects } from "../effects.js";
|
|
49
49
|
import { z } from "zod";
|
|
50
50
|
import { anyOutput, booleanOutput } from "./outputs.js";
|
|
51
51
|
import { defineProcedure, namespace } from "../define.js";
|
|
52
|
-
import type {
|
|
52
|
+
import type {
|
|
53
|
+
AnyProcedure,
|
|
54
|
+
CapabilityRequest,
|
|
55
|
+
Effects,
|
|
56
|
+
Procedure,
|
|
57
|
+
ProcedureClient,
|
|
58
|
+
ProcedureContext,
|
|
59
|
+
ProcedurePath,
|
|
60
|
+
ProcedureRegistryLike,
|
|
61
|
+
} from "../types.js";
|
|
62
|
+
import { effectsOf, isReadOnly } from "../capability.js";
|
|
63
|
+
import { PROCEDURE_REGISTRY } from "../registry.js";
|
|
53
64
|
|
|
54
65
|
// Re-export for convenience
|
|
55
66
|
export { anySchema } from "./schemas.js";
|
|
@@ -130,6 +141,8 @@ import {
|
|
|
130
141
|
getRefWhen,
|
|
131
142
|
WHEN_NEVER,
|
|
132
143
|
RUNS_REFS_TAG,
|
|
144
|
+
isPlainObject,
|
|
145
|
+
withScope,
|
|
133
146
|
type AnyProcedureRef,
|
|
134
147
|
} from "../ref.js";
|
|
135
148
|
import { substituteRefs, runControlFlow, controlFlowTags } from "./operands.js";
|
|
@@ -198,6 +211,200 @@ const chainProcedure: ChainProcedure = defineProcedure({
|
|
|
198
211
|
},
|
|
199
212
|
});
|
|
200
213
|
|
|
214
|
+
// =============================================================================
|
|
215
|
+
// Trace Procedure
|
|
216
|
+
// =============================================================================
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* The reason of a step of `client.trace` that did not run:
|
|
220
|
+
* - `writes`: the step can change data. Its procedure declares writes or no effects, or the
|
|
221
|
+
* capability refused a write in it.
|
|
222
|
+
* - `upTo`: the index of the step is above `upTo`.
|
|
223
|
+
* - `when`: the step has `$when: "$never"`. Its output is the call as data, as `client.chain` gives it.
|
|
224
|
+
* - `earlier`: an earlier step stopped the run with `writes` or an error.
|
|
225
|
+
*/
|
|
226
|
+
export type TraceReason = "writes" | "upTo" | "when" | "earlier";
|
|
227
|
+
|
|
228
|
+
/** One step of `client.trace`. */
|
|
229
|
+
export interface TraceRow {
|
|
230
|
+
/** The index of the step. */
|
|
231
|
+
index: number;
|
|
232
|
+
/** The `$name` of a call step. */
|
|
233
|
+
name?: string;
|
|
234
|
+
/** The procedure path of a call step. */
|
|
235
|
+
proc?: string[];
|
|
236
|
+
/** The declared effects of the procedure of a call step. */
|
|
237
|
+
effects?: Effects;
|
|
238
|
+
status: "ok" | "not-run" | "error";
|
|
239
|
+
reason?: TraceReason;
|
|
240
|
+
/** The result of the step, with `sampleLimit` applied. It equals the result of the step in `client.chain`. */
|
|
241
|
+
output?: unknown;
|
|
242
|
+
/** The error of the step: its code and its message. */
|
|
243
|
+
error?: { code?: string; message: string };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
interface TraceInput extends ChainInput {
|
|
247
|
+
/** The index of the last step to run. The rows of the later steps are `not-run` with reason `upTo`. */
|
|
248
|
+
upTo?: number;
|
|
249
|
+
/**
|
|
250
|
+
* The most items of each array in the output of a row: a top-level array, or each array in the
|
|
251
|
+
* top-level fields of an object. The next step gets the full value.
|
|
252
|
+
*/
|
|
253
|
+
sampleLimit?: number;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
interface TraceOutput {
|
|
257
|
+
/** One row for each step, in order */
|
|
258
|
+
steps: TraceRow[];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const TraceInputSchema = z.looseObject({
|
|
262
|
+
steps: z.array(z.unknown()).describe("The steps of a client.chain program, in order: calls or values"),
|
|
263
|
+
passThrough: z.boolean().optional().describe("Give each step that has no input the result of the step before"),
|
|
264
|
+
initialInput: operand("The input of the first step when passThrough is true"),
|
|
265
|
+
upTo: z.number().int().min(0).optional().describe("The index of the last step to run (default: all steps)"),
|
|
266
|
+
sampleLimit: z.number().int().min(0).optional().describe("The most items of each array in the output of a row. The next step gets the full value."),
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const TraceOutputSchema = z.looseObject({
|
|
270
|
+
steps: z
|
|
271
|
+
.array(
|
|
272
|
+
z.looseObject({
|
|
273
|
+
index: z.number().describe("The index of the step"),
|
|
274
|
+
name: z.string().optional().describe("The $name of the step"),
|
|
275
|
+
proc: z.array(z.string()).optional().describe("The procedure path of a call step"),
|
|
276
|
+
effects: z.unknown().optional().describe("The declared effects of the procedure of a call step"),
|
|
277
|
+
status: z.enum(["ok", "not-run", "error"]).describe("ok: the step ran. not-run: the step did not start. error: the step failed."),
|
|
278
|
+
reason: z.enum(["writes", "upTo", "when", "earlier"]).optional().describe("Why a step did not run"),
|
|
279
|
+
output: z.unknown().optional().describe("The result of the step, with sampleLimit applied"),
|
|
280
|
+
error: z.looseObject({ code: z.string().optional(), message: z.string() }).optional().describe("The error of the step"),
|
|
281
|
+
})
|
|
282
|
+
)
|
|
283
|
+
.describe("One row for each step, in order"),
|
|
284
|
+
}) as z.ZodType<TraceOutput>;
|
|
285
|
+
|
|
286
|
+
/** The narrowing of each call of `client.trace`: the call changes nothing. */
|
|
287
|
+
const TRACE_NARROWING: CapabilityRequest = Object.freeze({ effects: Object.freeze({ writes: Object.freeze([]) }) });
|
|
288
|
+
|
|
289
|
+
/** This function gives a client whose calls ask for a capability that changes nothing (`TRACE_NARROWING`). */
|
|
290
|
+
function readOnlyClient(client: ProcedureClient): ProcedureClient {
|
|
291
|
+
const narrowed: ProcedureClient = {
|
|
292
|
+
call: <TInput, TOutput>(path: ProcedurePath, input: TInput) => client.call<TInput, TOutput>(path, input, { narrow: TRACE_NARROWING }),
|
|
293
|
+
};
|
|
294
|
+
if (client.stream) {
|
|
295
|
+
const stream = client.stream.bind(client);
|
|
296
|
+
narrowed.stream = <TInput, TOutput>(path: ProcedurePath, input: TInput) => stream<TInput, TOutput>(path, input, { narrow: TRACE_NARROWING });
|
|
297
|
+
}
|
|
298
|
+
return narrowed;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** True when the procedure can change data: it declares writes, or it does not declare them. */
|
|
302
|
+
function canWrite(effects: Effects | undefined): boolean {
|
|
303
|
+
return !isReadOnly(effects);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* True for a refusal of a write by the capability: `FORBIDDEN` with the reason `effects-exceeded`
|
|
308
|
+
* or `effects-undeclared`, for a procedure that can change data. A refused read is an error.
|
|
309
|
+
*/
|
|
310
|
+
function isWriteRefusal(error: unknown, registry: ProcedureRegistryLike): boolean {
|
|
311
|
+
const { code, reason, path } = (error ?? {}) as { code?: unknown; reason?: unknown; path?: unknown };
|
|
312
|
+
if (code !== "FORBIDDEN" || (reason !== "effects-exceeded" && reason !== "effects-undeclared")) return false;
|
|
313
|
+
const procedure = Array.isArray(path) ? registry.get(path as ProcedurePath) : undefined;
|
|
314
|
+
return procedure === undefined || canWrite(effectsOf(procedure));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** The error of a row: the code and the message of the error. */
|
|
318
|
+
function traceError(error: unknown): { code?: string; message: string } {
|
|
319
|
+
const code = (error as { code?: unknown } | null | undefined)?.code;
|
|
320
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
321
|
+
return typeof code === "string" ? { code, message } : { message };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** The output of a row: `sampleLimit` cuts a top-level array, or each array in the top-level fields of an object. */
|
|
325
|
+
function sampleOf(value: unknown, limit: number | undefined): unknown {
|
|
326
|
+
if (limit === undefined) return value;
|
|
327
|
+
if (Array.isArray(value)) return value.slice(0, limit);
|
|
328
|
+
if (!isPlainObject(value)) return value;
|
|
329
|
+
return Object.fromEntries(Object.entries(value).map(([key, field]) => [key, Array.isArray(field) ? field.slice(0, limit) : field]));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
type TraceProcedure = Procedure<TraceInput, TraceOutput, { description: string; tags: string[]; effects: Effects }>;
|
|
333
|
+
|
|
334
|
+
const traceProcedure: TraceProcedure = defineProcedure({
|
|
335
|
+
path: ["trace"],
|
|
336
|
+
input: TraceInputSchema as any,
|
|
337
|
+
output: TraceOutputSchema,
|
|
338
|
+
metadata: {
|
|
339
|
+
description: "Run the steps of a chain one at a time, and give one row for each step. A step that can write does not run.",
|
|
340
|
+
tags: controlFlowTags("control-flow"),
|
|
341
|
+
// The procedure reads the declared effects of the steps in the registry
|
|
342
|
+
effects: REGISTRY_READ_EFFECTS,
|
|
343
|
+
},
|
|
344
|
+
handler: async (input: TraceInput, ctx?: ProcedureContext): Promise<TraceOutput> => {
|
|
345
|
+
const { steps, upTo, sampleLimit, ...chainFields } = input;
|
|
346
|
+
if (!Array.isArray(steps)) {
|
|
347
|
+
throw new Error(`client.trace: steps must be an array, got ${steps === null ? "null" : typeof steps}`);
|
|
348
|
+
}
|
|
349
|
+
if (!ctx?.client) throw new Error("client.trace: a run needs a client context");
|
|
350
|
+
const registry = ctx.registry ?? PROCEDURE_REGISTRY;
|
|
351
|
+
const limit = upTo ?? Number.POSITIVE_INFINITY;
|
|
352
|
+
|
|
353
|
+
// The facts of each step before the run: its name, its path and the declared effects of its procedure
|
|
354
|
+
const rows: TraceRow[] = steps.map((step, index) => {
|
|
355
|
+
if (!isAnyProcedureRef(step)) return { index, status: "ok" };
|
|
356
|
+
const call = normalizeRef(step);
|
|
357
|
+
const effects = effectsOf(registry.get(call.path) ?? {});
|
|
358
|
+
return {
|
|
359
|
+
index,
|
|
360
|
+
...(typeof call.$name === "string" ? { name: call.$name } : {}),
|
|
361
|
+
proc: [...call.path],
|
|
362
|
+
...(effects ? { effects } : {}),
|
|
363
|
+
status: "ok",
|
|
364
|
+
};
|
|
365
|
+
});
|
|
366
|
+
const never = (index: number): boolean => isAnyProcedureRef(steps[index]) && getRefWhen(steps[index] as AnyProcedureRef) === WHEN_NEVER;
|
|
367
|
+
// The run stops before the first step above upTo, and before the first call that can write. A $never step does not start.
|
|
368
|
+
let stop = 0;
|
|
369
|
+
while (stop < steps.length && stop <= limit && (rows[stop]!.proc === undefined || never(stop) || !canWrite(rows[stop]!.effects))) stop += 1;
|
|
370
|
+
|
|
371
|
+
// The chain runner runs the steps before the stop. Each call asks for a capability that
|
|
372
|
+
// changes nothing, so a nested write is refused too.
|
|
373
|
+
const outputs: unknown[] = [];
|
|
374
|
+
let failure: { error: unknown } | undefined;
|
|
375
|
+
if (stop > 0) {
|
|
376
|
+
const chainInput = withScope({ ...chainFields, steps: steps.slice(0, stop) }, scopeOf(input));
|
|
377
|
+
try {
|
|
378
|
+
await runControlFlow("chain", chainInput, { ...ctx, client: readOnlyClient(ctx.client) }, { onStep: (index, value) => { outputs[index] = value; } });
|
|
379
|
+
} catch (error) {
|
|
380
|
+
failure = { error };
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// A failure after the last step has no row: the caller gets it
|
|
384
|
+
if (failure && outputs.length >= stop) throw failure.error;
|
|
385
|
+
|
|
386
|
+
let halted = false;
|
|
387
|
+
const notRun = (row: TraceRow, reason: TraceReason): TraceRow => ({ ...row, status: "not-run", reason });
|
|
388
|
+
return {
|
|
389
|
+
steps: rows.map((row, index) => {
|
|
390
|
+
if (halted) return notRun(row, "earlier");
|
|
391
|
+
if (index < outputs.length) {
|
|
392
|
+
const output = sampleOf(outputs[index], sampleLimit);
|
|
393
|
+
return never(index) ? { ...notRun(row, "when"), output } : { ...row, output };
|
|
394
|
+
}
|
|
395
|
+
if (failure) {
|
|
396
|
+
// The step that failed: a refused write marks it as writes, another error as error
|
|
397
|
+
halted = true;
|
|
398
|
+
return isWriteRefusal(failure.error, registry) ? notRun(row, "writes") : { ...row, status: "error", error: traceError(failure.error) };
|
|
399
|
+
}
|
|
400
|
+
if (index > limit) return notRun(row, "upTo");
|
|
401
|
+
halted = true;
|
|
402
|
+
return notRun(row, "writes");
|
|
403
|
+
}),
|
|
404
|
+
};
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
|
|
201
408
|
// =============================================================================
|
|
202
409
|
// Parallel Procedure
|
|
203
410
|
// =============================================================================
|
|
@@ -662,6 +869,7 @@ const tryCatchProcedure: TryCatchProcedure = defineProcedure({
|
|
|
662
869
|
// call of a data-driven procedure (CAPABILITIES-2026-10).
|
|
663
870
|
export const coreProcedures: AnyProcedure[] = namespace(["client"], withEffects(PURE_EFFECTS, [
|
|
664
871
|
chainProcedure as AnyProcedure,
|
|
872
|
+
traceProcedure as AnyProcedure,
|
|
665
873
|
parallelProcedure as AnyProcedure,
|
|
666
874
|
conditionalProcedure as AnyProcedure,
|
|
667
875
|
// Logic operators (unified with group theory)
|
|
@@ -692,6 +900,7 @@ export const coreModule: { name: string; procedures: AnyProcedure[] } = {
|
|
|
692
900
|
// Re-export individual procedures for direct access
|
|
693
901
|
export {
|
|
694
902
|
chainProcedure,
|
|
903
|
+
traceProcedure,
|
|
695
904
|
parallelProcedure,
|
|
696
905
|
conditionalProcedure,
|
|
697
906
|
// Logic operators
|