@nodefony/http 10.0.0-beta.6 → 10.0.0-beta.7
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/dist/_virtual/{_@oxc-project_runtime@0.157.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.157.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorateParam.js +1 -1
- package/dist/index.js +3 -2
- package/dist/nodefony/config/config.js +25 -2
- package/dist/nodefony/grpc/GrpcCall.js +298 -0
- package/dist/nodefony/grpc/GrpcEndpoint.js +528 -0
- package/dist/nodefony/grpc/index.js +4 -0
- package/dist/nodefony/interfaces/IRpcCall.js +1 -0
- package/dist/nodefony/service/audit-logger.js +52 -6
- package/dist/nodefony/service/error-renderer.js +41 -9
- package/dist/nodefony/service/http-kernel.js +198 -19
- package/dist/nodefony/service/pretty-request-logger.js +5 -3
- package/dist/nodefony/service/request-logger.js +21 -6
- package/dist/nodefony/service/reverse-proxy.js +1 -1
- package/dist/nodefony/service/servers/clientError.js +26 -1
- package/dist/nodefony/service/servers/server-h2c.js +3 -3
- package/dist/nodefony/service/servers/server-http.js +3 -3
- package/dist/nodefony/service/servers/server-https.js +4 -4
- package/dist/nodefony/service/servers/server-websocket-secure.js +3 -3
- package/dist/nodefony/service/servers/server-websocket.js +3 -3
- package/dist/nodefony/service/servers/serverShutdown.js +14 -36
- package/dist/nodefony/service/sessions/sessions-service.js +6 -6
- package/dist/nodefony/service/upload/upload-service.js +2 -2
- package/dist/nodefony/src/context/Context.js +12 -1
- package/dist/nodefony/src/context/http/ByteStream.js +9 -4
- package/dist/nodefony/src/context/http/HttpContext.js +86 -8
- package/dist/nodefony/src/context/http/Request.js +96 -16
- package/dist/nodefony/src/context/http/Response.js +120 -17
- package/dist/nodefony/src/context/http/SseStream.js +4 -4
- package/dist/nodefony/src/context/http/StreamLifecycle.js +328 -19
- package/dist/nodefony/src/context/http/parser.js +29 -11
- package/dist/nodefony/src/context/http/webResponse.js +56 -0
- package/dist/nodefony/src/context/http2/Response.js +47 -8
- package/dist/nodefony/src/context/requestTrace.js +6 -2
- package/dist/nodefony/src/context/rpcOutcome.js +20 -0
- package/dist/nodefony/src/context/websocket/Response.js +11 -1
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +15 -4
- package/dist/nodefony/src/cookies/cookie.js +3 -1
- package/dist/nodefony/src/errors/httpError.js +58 -25
- package/dist/nodefony/src/grpc/cors.js +59 -0
- package/dist/nodefony/src/grpc/limits.js +16 -0
- package/dist/nodefony/src/grpc/refusal.js +271 -0
- package/dist/nodefony/src/profiler/aggregate.js +2 -2
- package/dist/nodefony/src/proxy/forward.js +1 -1
- package/dist/nodefony/src/proxy/generateProxyConfig.js +91 -15
- package/dist/nodefony/src/servers/http2Goaway.js +76 -0
- package/dist/nodefony/src/servers/http2Limits.js +41 -15
- package/dist/nodefony/src/servers/http2StreamGrace.js +100 -0
- package/dist/nodefony/src/servers/receiveDeadline.js +177 -0
- package/dist/types/index.d.ts +5 -2
- package/dist/types/nodefony/config/config.d.ts +10 -0
- package/dist/types/nodefony/grpc/GrpcCall.d.ts +142 -0
- package/dist/types/nodefony/grpc/GrpcEndpoint.d.ts +98 -0
- package/dist/types/nodefony/grpc/index.d.ts +15 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +9 -0
- package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +14 -0
- package/dist/types/nodefony/interfaces/IRequest.d.ts +2 -1
- package/dist/types/nodefony/interfaces/IRequestTrace.d.ts +10 -0
- package/dist/types/nodefony/interfaces/IRouting.d.ts +19 -0
- package/dist/types/nodefony/interfaces/IRpcCall.d.ts +83 -0
- package/dist/types/nodefony/interfaces/IUpload.d.ts +51 -4
- package/dist/types/nodefony/interfaces/index.d.ts +2 -1
- package/dist/types/nodefony/service/audit-logger.d.ts +33 -3
- package/dist/types/nodefony/service/error-renderer.d.ts +22 -1
- package/dist/types/nodefony/service/http-kernel.d.ts +21 -1
- package/dist/types/nodefony/service/request-logger.d.ts +10 -0
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +2 -2
- package/dist/types/nodefony/src/context/Context.d.ts +9 -1
- package/dist/types/nodefony/src/context/http/ByteStream.d.ts +1 -1
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +56 -2
- package/dist/types/nodefony/src/context/http/Request.d.ts +38 -8
- package/dist/types/nodefony/src/context/http/Response.d.ts +61 -7
- package/dist/types/nodefony/src/context/http/SseStream.d.ts +1 -1
- package/dist/types/nodefony/src/context/http/StreamLifecycle.d.ts +53 -0
- package/dist/types/nodefony/src/context/http/parser.d.ts +17 -3
- package/dist/types/nodefony/src/context/http/webResponse.d.ts +31 -0
- package/dist/types/nodefony/src/context/http2/Response.d.ts +35 -2
- package/dist/types/nodefony/src/context/requestTrace.d.ts +3 -1
- package/dist/types/nodefony/src/context/rpcOutcome.d.ts +11 -0
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +6 -1
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +8 -0
- package/dist/types/nodefony/src/errors/httpError.d.ts +29 -4
- package/dist/types/nodefony/src/grpc/cors.d.ts +32 -0
- package/dist/types/nodefony/src/grpc/limits.d.ts +14 -0
- package/dist/types/nodefony/src/grpc/refusal.d.ts +136 -0
- package/dist/types/nodefony/src/proxy/forward.d.ts +7 -0
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +15 -2
- package/dist/types/nodefony/src/servers/http2Goaway.d.ts +41 -0
- package/dist/types/nodefony/src/servers/http2Limits.d.ts +27 -14
- package/dist/types/nodefony/src/servers/http2StreamGrace.d.ts +34 -0
- package/dist/types/nodefony/src/servers/receiveDeadline.d.ts +77 -0
- package/docs/byte-stream.md +52 -17
- package/docs/diagnostics-channel.md +45 -1
- package/docs/servers.md +57 -44
- package/docs/upload.md +43 -17
- package/package.json +26 -6
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import HttpError from "../src/errors/httpError.js";
|
|
2
|
+
import { STREAM_STALL_TIMEOUT_MS, disarmResponseTimeout, openStreamLifecycle, resolveStreamTarget } from "../src/context/http/StreamLifecycle.js";
|
|
3
|
+
import { GRPC_DEFAULT_MAX_MESSAGE_BYTES } from "../src/grpc/limits.js";
|
|
4
|
+
import { applicationErrorCode } from "../src/grpc/refusal.js";
|
|
5
|
+
import { RPC_EXPOSED_HEADERS } from "../src/grpc/cors.js";
|
|
6
|
+
import DefaultErrorRenderer from "../service/error-renderer.js";
|
|
7
|
+
import { GrpcCall } from "./GrpcCall.js";
|
|
8
|
+
import { Code, ConnectError } from "@connectrpc/connect";
|
|
9
|
+
import { connectNodeAdapter } from "@connectrpc/connect-node";
|
|
10
|
+
import { createRegistry } from "@bufbuild/protobuf";
|
|
11
|
+
// nodefony/grpc/GrpcEndpoint.ts
|
|
12
|
+
/**
|
|
13
|
+
* Levé par le repli de l'adaptateur (`fallback`), qui l'appelle de façon
|
|
14
|
+
* SYNCHRONE quand aucune méthode ne correspond au chemin exact — casse
|
|
15
|
+
* modifiée, le Router étant insensible à la casse. Une seule instance : le
|
|
16
|
+
* repli ne paie aucune allocation, et `serve` le reconnaît par identité.
|
|
17
|
+
*/
|
|
18
|
+
const UNROUTED = /* @__PURE__ */ new Error("gRPC : aucune méthode à ce chemin");
|
|
19
|
+
/**
|
|
20
|
+
* Repli de l'adaptateur : rien n'est écrit ici. Le refus remonte au pipeline,
|
|
21
|
+
* qui l'écrit au format du protocole comme tout chemin inconnu (ADR-0026 D13
|
|
22
|
+
* point 2) — un seul écrivain des refus. Le repli par défaut de Connect, un
|
|
23
|
+
* 404 nu terminé par une trame DATA, est lu `INTERNAL` par grpc-go
|
|
24
|
+
* (connectrpc/connect-es#1882).
|
|
25
|
+
*/
|
|
26
|
+
function unrouted() {
|
|
27
|
+
throw UNROUTED;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Défauts de la section `grpc`, quand le contexte n'a pas de noyau http (banc
|
|
31
|
+
* hors serveur) : les trois protocoles (défaut de l'adaptateur,
|
|
32
|
+
* `router.d.ts`) et les bornes de Nodefony — jamais celles de l'adaptateur,
|
|
33
|
+
* qui n'en pose aucune.
|
|
34
|
+
*/
|
|
35
|
+
const DEFAULT_SETTINGS = Object.freeze({
|
|
36
|
+
grpc: true,
|
|
37
|
+
grpcWeb: true,
|
|
38
|
+
connect: true,
|
|
39
|
+
readMaxBytes: GRPC_DEFAULT_MAX_MESSAGE_BYTES,
|
|
40
|
+
writeMaxBytes: GRPC_DEFAULT_MAX_MESSAGE_BYTES,
|
|
41
|
+
requireConnectProtocolHeader: false
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* Type retenu pour la politique d'en-têtes d'un appel sans `Content-Type` (GET
|
|
45
|
+
* Connect, dont le codage passe en paramètre d'URL).
|
|
46
|
+
*/
|
|
47
|
+
const RPC_RESPONSE_TYPE = "application/proto";
|
|
48
|
+
/**
|
|
49
|
+
* Point d'entrée gRPC d'un service : possède l'adaptateur Connect
|
|
50
|
+
* (`connectNodeAdapter`) et lui confie la requête et la réponse BRUTES de Node
|
|
51
|
+
* d'un contexte déjà passé par le pipeline (pare-feu, gardes).
|
|
52
|
+
*
|
|
53
|
+
* L'adaptateur est construit au premier appel, depuis la section `grpc` de la
|
|
54
|
+
* configuration http. Il reçoit la fiche d'appel ({@link GrpcCall}) par son
|
|
55
|
+
* option `contextValues`, qu'il appelle de façon SYNCHRONE avant toute
|
|
56
|
+
* attente (`connect-node-adapter.js#nodeRequestHandler`) : la cible passe par
|
|
57
|
+
* une case lue à cet instant — ni ALS, ni promesse, ni écouteur de plus.
|
|
58
|
+
*
|
|
59
|
+
* Les méthodes s'inscrivent une à une par `router.rpc`, SANS options : des
|
|
60
|
+
* options passées à l'inscription rallumeraient gRPC et gRPC-Web coupés par la
|
|
61
|
+
* configuration (connectrpc/connect-es#1835). La restriction de protocoles
|
|
62
|
+
* vit sur l'adaptateur seul.
|
|
63
|
+
*
|
|
64
|
+
* @experimental Surface gRPC, ADR-0026 D11.
|
|
65
|
+
*/
|
|
66
|
+
var GrpcEndpoint = class {
|
|
67
|
+
/** Le contrat servi (descripteur généré par `buf`). */
|
|
68
|
+
service;
|
|
69
|
+
/** Cible relue à chaque appel (`@Scope("request")`). */
|
|
70
|
+
perCall;
|
|
71
|
+
#handler = null;
|
|
72
|
+
/** Case synchrone : la cible de l'appel en cours de remise à l'adaptateur. */
|
|
73
|
+
#pendingTarget = null;
|
|
74
|
+
/** Case synchrone : le contexte de l'appel en cours de remise. */
|
|
75
|
+
#pendingContext = null;
|
|
76
|
+
/**
|
|
77
|
+
* Option `contextValues` : une fiche par appel, rien sur le chemin du 404.
|
|
78
|
+
* Accrochée au contexte (`HttpContext.rpc`, ADR-0026 D10) : à l'échéance du
|
|
79
|
+
* délai de réponse, le noyau la termine au lieu d'écrire un 503.
|
|
80
|
+
*/
|
|
81
|
+
#contextValues = () => {
|
|
82
|
+
const context = this.#pendingContext;
|
|
83
|
+
const call = new GrpcCall(this.#pendingTarget, context);
|
|
84
|
+
if (context !== null) context.rpc = call;
|
|
85
|
+
return call;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Chemins des méthodes en flux (serveur, client, bidirectionnel) : elles
|
|
89
|
+
* s'ouvrent par le cycle de vie d'un flux. Tout autre chemin — unaire, ou
|
|
90
|
+
* inconnu, que l'adaptateur refusera sans rien écrire — garde son délai de
|
|
91
|
+
* réponse.
|
|
92
|
+
*/
|
|
93
|
+
#streamingPaths;
|
|
94
|
+
constructor(service, options = {}) {
|
|
95
|
+
this.service = service;
|
|
96
|
+
this.perCall = options.perCall ?? false;
|
|
97
|
+
this.#streamingPaths = streamingPathsOf(service);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Remet la requête du contexte à l'adaptateur, qui écrit lui-même la
|
|
101
|
+
* réponse et ses trailers. Synchrone : rien n'est attendu ici.
|
|
102
|
+
*
|
|
103
|
+
* Avant de la remettre : le contexte est marqué répondu (`sended`), si bien
|
|
104
|
+
* que le pipeline n'attend aucune réponse de l'action.
|
|
105
|
+
*
|
|
106
|
+
* - Appel unaire : le délai de réponse de Nodefony reste armé, et termine
|
|
107
|
+
* l'appel en 4 `DEADLINE_EXCEEDED` par sa fiche (`HttpContext.rpc`,
|
|
108
|
+
* ADR-0026 D4 point 3).
|
|
109
|
+
* - Flux : aucune échéance. Il s'ouvre par le cycle de vie d'un flux
|
|
110
|
+
* (`openStreamLifecycle`, D4 point 4) — délai de réponse désarmé (à
|
|
111
|
+
* l'expiration il écrirait un 503 par-dessus l'adaptateur), place dans
|
|
112
|
+
* le budget de connexions longues par IP, client qui ne lit plus coupé
|
|
113
|
+
* par le balayage de blocage. Synchrone, sauf quand une session est à
|
|
114
|
+
* sauver : l'appel n'est remis qu'après.
|
|
115
|
+
*
|
|
116
|
+
* @param context - le contexte HTTP de la requête, pas encore répondu.
|
|
117
|
+
* @param target - l'objet qui implémente le service pour cet appel.
|
|
118
|
+
* @returns rien, ou la promesse de la remise quand une session est à sauver.
|
|
119
|
+
* @throws HttpError 500 quand la réponse est déjà partie, ou que son
|
|
120
|
+
* transport n'est pas reconnu.
|
|
121
|
+
* @throws HttpError 429 quand l'IP du client a déjà tous ses flux et sockets
|
|
122
|
+
* (`wsMaxConnectionsPerIp`) — rendu par le pipeline en 14 `UNAVAILABLE`.
|
|
123
|
+
* @throws HttpError 400 quand l'adaptateur refuse la requête avant d'avoir
|
|
124
|
+
* rien écrit (requête sans autorité `Host`) — la réponse revient alors au
|
|
125
|
+
* pipeline.
|
|
126
|
+
* @throws HttpError 404 quand aucune méthode ne correspond au chemin exact
|
|
127
|
+
* (casse modifiée) — rendu par le pipeline en 12 `UNIMPLEMENTED`.
|
|
128
|
+
*/
|
|
129
|
+
serve(context, target) {
|
|
130
|
+
if (context.sended || context.finished || context.response.isHeaderSent()) throw new HttpError("gRPC : la réponse est déjà partie", 500);
|
|
131
|
+
const nodeResponse = context.response.response;
|
|
132
|
+
const streamTarget = resolveStreamTarget(context);
|
|
133
|
+
if (nodeResponse === null || streamTarget === null) throw new HttpError("gRPC : transport de réponse inconnu", 500, context);
|
|
134
|
+
const handler = this.#handler ?? this.#build(context, target);
|
|
135
|
+
if (!this.#streamingPaths.has(context.request.url.pathname)) {
|
|
136
|
+
context.sended = true;
|
|
137
|
+
this.#hand(context, target, handler, nodeResponse);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
disarmResponseTimeout(streamTarget);
|
|
141
|
+
const opened = openStreamLifecycle(context, {
|
|
142
|
+
label: "gRPC",
|
|
143
|
+
stallTimeout: STREAM_STALL_TIMEOUT_MS,
|
|
144
|
+
maxDuration: false,
|
|
145
|
+
foreignWriter: true
|
|
146
|
+
});
|
|
147
|
+
if (opened instanceof Promise) return opened.then((lifecycle) => this.#handStream(lifecycle, target, handler, nodeResponse));
|
|
148
|
+
this.#handStream(opened, target, handler, nodeResponse);
|
|
149
|
+
}
|
|
150
|
+
/** Remet un flux à l'adaptateur — sauf si le client est déjà parti. */
|
|
151
|
+
#handStream(lifecycle, target, handler, nodeResponse) {
|
|
152
|
+
if (lifecycle.closed) return;
|
|
153
|
+
this.#hand(lifecycle.context, target, handler, nodeResponse);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Remet la requête à l'adaptateur. La fiche d'appel est créée par
|
|
157
|
+
* `contextValues`, appelée de façon SYNCHRONE dans l'appel : les cases
|
|
158
|
+
* `#pending*` ne vivent que le temps de celui-ci.
|
|
159
|
+
*
|
|
160
|
+
* L'adaptateur écrit sur la réponse Node : ce que le pipeline a posé dans la
|
|
161
|
+
* réserve — transport, CORS et `Vary`, corrélation (`X-Request-Id`,
|
|
162
|
+
* `traceparent`), politique de sécurité, cookie de session — y passe d'abord
|
|
163
|
+
* (ADR-0026 D6). Node fusionne au `writeHead` de l'adaptateur, qui garde la
|
|
164
|
+
* main sur ce qu'il écrit lui-même (`content-type`).
|
|
165
|
+
*/
|
|
166
|
+
#hand(context, target, handler, nodeResponse) {
|
|
167
|
+
if (context.response.hasHeader("access-control-allow-origin")) exposeRpcHeaders(context.response);
|
|
168
|
+
context.response.commitHeaders(context.request.request.headers["content-type"] ?? RPC_RESPONSE_TYPE);
|
|
169
|
+
this.#pendingTarget = target;
|
|
170
|
+
this.#pendingContext = context;
|
|
171
|
+
try {
|
|
172
|
+
handler(context.request.request, nodeResponse);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (nodeResponse.headersSent) throw error;
|
|
175
|
+
context.sended = false;
|
|
176
|
+
context.rpc = null;
|
|
177
|
+
if (error === UNROUTED) throw HttpError.withoutStack("Not Found", 404, context);
|
|
178
|
+
context.log(error, "WARNING", "GRPC");
|
|
179
|
+
throw HttpError.withoutStack("gRPC : requête mal formée", 400, context);
|
|
180
|
+
} finally {
|
|
181
|
+
this.#pendingTarget = null;
|
|
182
|
+
this.#pendingContext = null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/** Construit l'adaptateur, une fois, depuis la configuration http. */
|
|
186
|
+
#build(context, target) {
|
|
187
|
+
const settings = context.httpKernel?.options.grpc ?? DEFAULT_SETTINGS;
|
|
188
|
+
const shared = this.perCall ? null : target;
|
|
189
|
+
const handler = connectNodeAdapter({
|
|
190
|
+
grpc: settings.grpc,
|
|
191
|
+
grpcWeb: settings.grpcWeb,
|
|
192
|
+
connect: settings.connect,
|
|
193
|
+
readMaxBytes: settings.readMaxBytes,
|
|
194
|
+
writeMaxBytes: settings.writeMaxBytes,
|
|
195
|
+
requireConnectProtocolHeader: settings.requireConnectProtocolHeader,
|
|
196
|
+
contextValues: this.#contextValues,
|
|
197
|
+
requestGate: observeCall,
|
|
198
|
+
fallback: unrouted,
|
|
199
|
+
jsonOptions: { registry: contractRegistry(this.service) },
|
|
200
|
+
routes: (router) => registerMethods(router, this.service, shared)
|
|
201
|
+
});
|
|
202
|
+
this.#handler = handler;
|
|
203
|
+
return handler;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
/** Les chemins `/<paquet.Service>/<Méthode>` des méthodes en flux du contrat. */
|
|
207
|
+
function streamingPathsOf(service) {
|
|
208
|
+
const paths = /* @__PURE__ */ new Set();
|
|
209
|
+
for (const method of service.methods) if (!isUnary(method)) paths.add(`/${service.typeName}/${method.name}`);
|
|
210
|
+
return paths;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Fin d'un appel à réponse unique — unaire (ADR-0026 D4, point 3), flux
|
|
214
|
+
* client : l'appel se termine dès que le serveur l'interrompt — délai de
|
|
215
|
+
* réponse échu (unaire), identité révoquée (D6) —, même si le gestionnaire ne
|
|
216
|
+
* lit pas son signal.
|
|
217
|
+
*
|
|
218
|
+
* - L'adaptateur tient l'échéance DU CLIENT et abaisse `ctx.signal`, mais
|
|
219
|
+
* attend la promesse du gestionnaire
|
|
220
|
+
* (`invoke-implementation.js#invokeUnaryImplementation`) : un gestionnaire
|
|
221
|
+
* sourd tiendrait l'appel ouvert sans fin.
|
|
222
|
+
* - Le délai du serveur est celui que Nodefony arme déjà sur toute requête,
|
|
223
|
+
* gardé sur une route unaire : à l'expiration, `HttpContext._onTimeout`
|
|
224
|
+
* appelle `GrpcCall.expire`, qui abaisse le signal et rejette la promesse
|
|
225
|
+
* rendue ici avec 4 `DEADLINE_EXCEEDED`. Aucun minuteur de plus.
|
|
226
|
+
* - Ce délai n'est PAS écrit dans l'en-tête d'échéance : le gestionnaire
|
|
227
|
+
* verrait par `ctx.timeoutMs()` une échéance que le client n'a pas envoyée
|
|
228
|
+
* (suite de conformité de Connect : « server echoed back a timeout but none
|
|
229
|
+
* was expected »).
|
|
230
|
+
* - Un résultat synchrone passe tel quel : ni promesse, ni suivi.
|
|
231
|
+
*/
|
|
232
|
+
function settleUnary(result, ctx) {
|
|
233
|
+
if (!(result instanceof Promise)) return result;
|
|
234
|
+
return settlePending(result, ctx);
|
|
235
|
+
}
|
|
236
|
+
/** {@link settleUnary} d'une réponse attendue : suivie par la fiche d'appel. */
|
|
237
|
+
function settlePending(pending, ctx) {
|
|
238
|
+
const call = ctx.values;
|
|
239
|
+
if (!(call instanceof GrpcCall)) return pending;
|
|
240
|
+
return new Promise((resolve, reject) => {
|
|
241
|
+
call.attach(ctx, reject);
|
|
242
|
+
pending.then((value) => {
|
|
243
|
+
call.detach();
|
|
244
|
+
resolve(value);
|
|
245
|
+
}, (error) => {
|
|
246
|
+
call.detach();
|
|
247
|
+
reject(translateError(error, ctx));
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Registre des types du contrat : son fichier et tout ce qu'il importe, types
|
|
253
|
+
* bien connus compris. Sans lui, un `google.protobuf.Any` ne se lit ni ne
|
|
254
|
+
* s'écrit en JSON (Connect et gRPC-Web JSON répondent `INVALID_ARGUMENT`) :
|
|
255
|
+
* le mappage JSON de proto3 exige de résoudre le type emballé. Construit une
|
|
256
|
+
* fois ; Connect ne le consulte que pour `Any` en JSON. Un type emballé hors
|
|
257
|
+
* du graphe d'imports du contrat reste non résolu en JSON — le binaire, lui,
|
|
258
|
+
* le transporte toujours.
|
|
259
|
+
*/
|
|
260
|
+
function contractRegistry(service) {
|
|
261
|
+
const files = [];
|
|
262
|
+
const seen = /* @__PURE__ */ new Set();
|
|
263
|
+
const visit = (file) => {
|
|
264
|
+
if (seen.has(file)) return;
|
|
265
|
+
seen.add(file);
|
|
266
|
+
files.push(file);
|
|
267
|
+
for (const dependency of file.dependencies) visit(dependency);
|
|
268
|
+
};
|
|
269
|
+
visit(service.file);
|
|
270
|
+
return createRegistry(...files);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Inscrit chaque méthode du contrat. `shared` non nul : ses méthodes, liées
|
|
274
|
+
* une fois. `null` : une enveloppe par méthode, qui relit la cible dans la
|
|
275
|
+
* fiche d'appel. Un garde par genre de méthode donne à chaque enveloppe le
|
|
276
|
+
* type exact de sa méthode — aucune conversion.
|
|
277
|
+
*/
|
|
278
|
+
function registerMethods(router, service, shared) {
|
|
279
|
+
for (const method of service.methods) if (isUnary(method)) {
|
|
280
|
+
if (shared !== null) {
|
|
281
|
+
const impl = pick(method, shared);
|
|
282
|
+
router.rpc(method, (request, ctx) => settleUnary(invoke(impl, shared, request, ctx), ctx));
|
|
283
|
+
} else router.rpc(method, (request, ctx) => {
|
|
284
|
+
const target = callTarget(ctx);
|
|
285
|
+
return settleUnary(invoke(pick(method, target), target, request, ctx), ctx);
|
|
286
|
+
});
|
|
287
|
+
} else if (isServerStreaming(method)) {
|
|
288
|
+
if (shared !== null) {
|
|
289
|
+
const impl = pick(method, shared);
|
|
290
|
+
router.rpc(method, (request, ctx) => translateFirst(invoke(impl, shared, request, ctx), ctx));
|
|
291
|
+
} else router.rpc(method, (request, ctx) => {
|
|
292
|
+
const target = callTarget(ctx);
|
|
293
|
+
return translateFirst(invoke(pick(method, target), target, request, ctx), ctx);
|
|
294
|
+
});
|
|
295
|
+
} else if (isClientStreaming(method)) {
|
|
296
|
+
if (shared !== null) {
|
|
297
|
+
const impl = pick(method, shared);
|
|
298
|
+
router.rpc(method, (requests, ctx) => translateSettled(invoke(impl, shared, requests, ctx), ctx));
|
|
299
|
+
} else router.rpc(method, (requests, ctx) => {
|
|
300
|
+
const target = callTarget(ctx);
|
|
301
|
+
return translateSettled(invoke(pick(method, target), target, requests, ctx), ctx);
|
|
302
|
+
});
|
|
303
|
+
} else if (isBiDiStreaming(method)) {
|
|
304
|
+
if (shared !== null) {
|
|
305
|
+
const impl = pick(method, shared);
|
|
306
|
+
router.rpc(method, (requests, ctx) => translateFirst(invoke(impl, shared, requests, ctx), ctx));
|
|
307
|
+
} else router.rpc(method, (requests, ctx) => {
|
|
308
|
+
const target = callTarget(ctx);
|
|
309
|
+
return translateFirst(invoke(pick(method, target), target, requests, ctx), ctx);
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Lit les erreurs comme une route HTTP les lirait : statut et message client
|
|
315
|
+
* (`DefaultErrorRenderer.describe`). Sans état, partagé par tous les appels.
|
|
316
|
+
*/
|
|
317
|
+
const ERRORS = new DefaultErrorRenderer();
|
|
318
|
+
/**
|
|
319
|
+
* Traduit une erreur levée par l'application en `ConnectError` (ADR-0026 D7) :
|
|
320
|
+
* une `HttpError` d'un service partagé avec des routes HTTP, une validation, un
|
|
321
|
+
* conflit, un code numérique, gardent leur sens — 404 → 5 `NOT_FOUND`, et non
|
|
322
|
+
* 13. Le message suit la règle des routes HTTP (opaque pour un 5xx en
|
|
323
|
+
* production) ; l'erreur d'origine reste la cause, côté serveur.
|
|
324
|
+
*
|
|
325
|
+
* Une exception que rien ne classe devient 13 avec la MÊME règle de message
|
|
326
|
+
* qu'une route HTTP : son message en développement (« sinon on débogue à
|
|
327
|
+
* l'aveugle »), opaque en production, où sa cause ne quitte jamais le serveur.
|
|
328
|
+
*
|
|
329
|
+
* Passent TELLES QUELLES : une `ConnectError` (son code est voulu) et une
|
|
330
|
+
* valeur qui n'est pas une `Error` (l'adaptateur en fait 13 « internal error »).
|
|
331
|
+
*
|
|
332
|
+
* Chemin d'erreur seulement : rien n'est payé sur un appel qui réussit.
|
|
333
|
+
*/
|
|
334
|
+
function translateError(error, ctx) {
|
|
335
|
+
if (!(error instanceof Error) || error instanceof ConnectError) return error;
|
|
336
|
+
const { status, message } = ERRORS.describe(error);
|
|
337
|
+
const call = ctx.values;
|
|
338
|
+
const identified = call instanceof GrpcCall && call.context !== null && call.context.authenticatedUser() !== null;
|
|
339
|
+
return new ConnectError(message, CONNECT_CODES[applicationErrorCode(status, identified)] ?? Code.Unknown, void 0, void 0, error);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Le `Code` de Connect de chaque code gRPC (index = numéro, 1 à 16). La table
|
|
343
|
+
* D7 vit hors du sous-chemin gRPC, sans Connect (ADR-0026 D12) : elle rend
|
|
344
|
+
* un nombre, que cette correspondance ramène au type attendu.
|
|
345
|
+
*/
|
|
346
|
+
const CONNECT_CODES = [
|
|
347
|
+
void 0,
|
|
348
|
+
Code.Canceled,
|
|
349
|
+
Code.Unknown,
|
|
350
|
+
Code.InvalidArgument,
|
|
351
|
+
Code.DeadlineExceeded,
|
|
352
|
+
Code.NotFound,
|
|
353
|
+
Code.AlreadyExists,
|
|
354
|
+
Code.PermissionDenied,
|
|
355
|
+
Code.ResourceExhausted,
|
|
356
|
+
Code.FailedPrecondition,
|
|
357
|
+
Code.Aborted,
|
|
358
|
+
Code.OutOfRange,
|
|
359
|
+
Code.Unimplemented,
|
|
360
|
+
Code.Internal,
|
|
361
|
+
Code.Unavailable,
|
|
362
|
+
Code.DataLoss,
|
|
363
|
+
Code.Unauthenticated
|
|
364
|
+
];
|
|
365
|
+
/**
|
|
366
|
+
* Ajoute à `Access-Control-Expose-Headers` les en-têtes qu'un client RPC doit
|
|
367
|
+
* lire (statut, message, détails, encodage) — à ce que la politique de
|
|
368
|
+
* l'application expose déjà. Une chaîne composée au chargement ; un appel
|
|
369
|
+
* d'une origine autorisée seulement.
|
|
370
|
+
*/
|
|
371
|
+
function exposeRpcHeaders(response) {
|
|
372
|
+
const current = response.getHeader("access-control-expose-headers");
|
|
373
|
+
const listed = Array.isArray(current) ? current.join(", ") : current;
|
|
374
|
+
response.setHeader("access-control-expose-headers", listed === void 0 || listed === "" ? RPC_EXPOSED_HEADERS : `${String(listed)}, ${RPC_EXPOSED_HEADERS}`);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Option `requestGate` de l'adaptateur, en simple OBSERVATEUR : Connect
|
|
378
|
+
* l'appelle avec le contexte d'appel AVANT de lire le corps, dans les trois
|
|
379
|
+
* protocoles. La fiche le garde jusqu'au `close`, où son signal porte la cause
|
|
380
|
+
* de la fin (ADR-0026 D10 point 2) — y compris d'un refus de Connect avant
|
|
381
|
+
* le gestionnaire (bombe gzip → 8, encodage inconnu → 12), que le
|
|
382
|
+
* gestionnaire ne voit jamais. Ne lève jamais.
|
|
383
|
+
*/
|
|
384
|
+
function observeCall(ctx) {
|
|
385
|
+
const call = ctx.values;
|
|
386
|
+
if (call instanceof GrpcCall) call.open(ctx);
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Appelle l'implémentation sur sa cible ; une erreur levée de façon SYNCHRONE
|
|
390
|
+
* (validation avant toute attente) est traduite ({@link translateError}). Un
|
|
391
|
+
* `try` ne coûte rien à l'appel qui réussit.
|
|
392
|
+
*/
|
|
393
|
+
function invoke(impl, target, arg, ctx) {
|
|
394
|
+
try {
|
|
395
|
+
return impl.call(target, arg, ctx);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
throw translateError(error, ctx);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Flux client (une seule réponse) : toute erreur est traduite (ADR-0026 D7,
|
|
402
|
+
* point 3), et l'appel se termine dès que le serveur l'interrompt (identité
|
|
403
|
+
* révoquée, D6) — le même règlement qu'un appel unaire. Une promesse par
|
|
404
|
+
* appel, aucune par message reçu.
|
|
405
|
+
*/
|
|
406
|
+
function translateSettled(result, ctx) {
|
|
407
|
+
return settlePending(result, ctx);
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Flux serveur et bidirectionnel : traduit les erreurs levées AVANT le premier
|
|
411
|
+
* message (validation, entité introuvable, droit refusé) — là où un service
|
|
412
|
+
* partagé avec des routes HTTP lève (ADR-0026 D7, point 3). Après le premier
|
|
413
|
+
* message, l'erreur passe telle quelle : 13, sauf une `ConnectError`, dont le
|
|
414
|
+
* code est voulu.
|
|
415
|
+
*/
|
|
416
|
+
function translateFirst(source, ctx) {
|
|
417
|
+
return new FirstMessageTranslator(source, ctx);
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Itérateur d'un flux : l'erreur levée AVANT le premier message est traduite
|
|
421
|
+
* (D7), et une source qui finit après une interruption du serveur (identité
|
|
422
|
+
* révoquée, arrêt) part avec le statut de l'interruption — jamais `OK`.
|
|
423
|
+
* `return` et `throw` vont à la source : un gestionnaire `async function*`
|
|
424
|
+
* voit son `finally` s'exécuter quand le client part.
|
|
425
|
+
*
|
|
426
|
+
* Coût : un `.then` par message, sur des fonctions créées une fois par appel
|
|
427
|
+
* (mesuré : +34 ns par message, médiane de 9) — le prix d'un flux tronqué qui
|
|
428
|
+
* ne se fait pas passer pour complet.
|
|
429
|
+
*/
|
|
430
|
+
var FirstMessageTranslator = class {
|
|
431
|
+
#source;
|
|
432
|
+
#ctx;
|
|
433
|
+
/** La fiche de l'appel : son interruption arrête le flux (ADR-0026 D6). */
|
|
434
|
+
#call;
|
|
435
|
+
#started = false;
|
|
436
|
+
/**
|
|
437
|
+
* Une source qui ÉCOUTE son signal finit normalement après l'interruption :
|
|
438
|
+
* sa fin porte alors le statut de l'interruption, sinon Connect écrirait
|
|
439
|
+
* `OK` et le client prendrait un flux tronqué pour complet.
|
|
440
|
+
*/
|
|
441
|
+
#onResult = (result) => {
|
|
442
|
+
if (result.done === true) {
|
|
443
|
+
const interruption = this.#call?.interruption ?? null;
|
|
444
|
+
if (interruption !== null) throw interruption;
|
|
445
|
+
}
|
|
446
|
+
return result;
|
|
447
|
+
};
|
|
448
|
+
/** Erreur avant le premier message : traduite au format du protocole (D7). */
|
|
449
|
+
#onFirstError = (error) => {
|
|
450
|
+
throw translateError(error, this.#ctx);
|
|
451
|
+
};
|
|
452
|
+
constructor(source, ctx) {
|
|
453
|
+
this.#source = source[Symbol.asyncIterator]();
|
|
454
|
+
this.#ctx = ctx;
|
|
455
|
+
const call = ctx.values;
|
|
456
|
+
this.#call = call instanceof GrpcCall ? call : null;
|
|
457
|
+
this.#call?.follow(ctx);
|
|
458
|
+
}
|
|
459
|
+
[Symbol.asyncIterator]() {
|
|
460
|
+
return this;
|
|
461
|
+
}
|
|
462
|
+
next() {
|
|
463
|
+
const interruption = this.#call?.interruption ?? null;
|
|
464
|
+
if (interruption !== null) return this.#stop(interruption);
|
|
465
|
+
if (this.#started) return this.#source.next().then(this.#onResult);
|
|
466
|
+
this.#started = true;
|
|
467
|
+
return this.#source.next().then(this.#onResult, this.#onFirstError);
|
|
468
|
+
}
|
|
469
|
+
return(value) {
|
|
470
|
+
const source = this.#source;
|
|
471
|
+
return source.return === void 0 ? Promise.resolve({
|
|
472
|
+
done: true,
|
|
473
|
+
value: void 0
|
|
474
|
+
}) : source.return(value);
|
|
475
|
+
}
|
|
476
|
+
throw(error) {
|
|
477
|
+
const source = this.#source;
|
|
478
|
+
if (source.throw !== void 0) return source.throw(error);
|
|
479
|
+
return this.return().then(() => {
|
|
480
|
+
throw error;
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Arrête un flux interrompu : la source est refermée (le `finally` d'un
|
|
485
|
+
* gestionnaire `async function*` s'exécute), sans attendre qu'elle finisse
|
|
486
|
+
* — le statut part tout de suite.
|
|
487
|
+
*/
|
|
488
|
+
#stop(reason) {
|
|
489
|
+
this.#source.return?.().catch(ignoreClosingError);
|
|
490
|
+
return Promise.reject(reason);
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
/** Une source qui lève en se refermant : l'appel est déjà terminé. */
|
|
494
|
+
function ignoreClosingError() {}
|
|
495
|
+
function isUnary(method) {
|
|
496
|
+
return method.methodKind === "unary";
|
|
497
|
+
}
|
|
498
|
+
function isServerStreaming(method) {
|
|
499
|
+
return method.methodKind === "server_streaming";
|
|
500
|
+
}
|
|
501
|
+
function isClientStreaming(method) {
|
|
502
|
+
return method.methodKind === "client_streaming";
|
|
503
|
+
}
|
|
504
|
+
function isBiDiStreaming(method) {
|
|
505
|
+
return method.methodKind === "bidi_streaming";
|
|
506
|
+
}
|
|
507
|
+
/** La cible de l'appel, posée par {@link GrpcEndpoint.serve} dans la fiche. */
|
|
508
|
+
function callTarget(ctx) {
|
|
509
|
+
const call = ctx.values;
|
|
510
|
+
if (call instanceof GrpcCall && call.target !== null) return call.target;
|
|
511
|
+
throw new ConnectError("appel gRPC reçu hors de GrpcEndpoint.serve : aucune cible", Code.Internal);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* L'implémentation d'une méthode du contrat sur sa cible. La signature est
|
|
515
|
+
* garantie à la compilation par `@GrpcService` et la présence vérifiée au
|
|
516
|
+
* démarrage ; ici, le constat qu'il s'agit bien d'une fonction.
|
|
517
|
+
*
|
|
518
|
+
* @throws ConnectError `UNIMPLEMENTED` quand la cible ne la porte pas.
|
|
519
|
+
*/
|
|
520
|
+
function pick(method, target) {
|
|
521
|
+
const impl = Reflect.get(target, method.localName);
|
|
522
|
+
if (isImplOf(method, impl)) return impl;
|
|
523
|
+
throw new ConnectError(`${method.parent.typeName}.${method.name} n'est pas implémentée`, Code.Unimplemented);
|
|
524
|
+
}
|
|
525
|
+
function isImplOf(_method, value) {
|
|
526
|
+
return typeof value === "function";
|
|
527
|
+
}
|
|
528
|
+
export { GrpcEndpoint };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { handshakeRefusalStatus } from "../src/context/websocket/WebsocketContext.js";
|
|
2
|
+
import { rpcOutcomeOf } from "../src/context/rpcOutcome.js";
|
|
2
3
|
// nodefony/service/audit-logger.ts
|
|
3
4
|
const statusOf = (context) => {
|
|
4
5
|
return context.response?.statusCode ?? null;
|
|
@@ -18,6 +19,46 @@ function severityFromStatus(status) {
|
|
|
18
19
|
return "INFO";
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
22
|
+
* Codes gRPC qu'un SERVEUR compte comme des erreurs, selon la convention
|
|
23
|
+
* OpenTelemetry (`docs/rpc/grpc.md`, « SHOULD be considered errors ») :
|
|
24
|
+
* 2 UNKNOWN, 4 DEADLINE_EXCEEDED, 12 UNIMPLEMENTED, 13 INTERNAL,
|
|
25
|
+
* 14 UNAVAILABLE, 15 DATA_LOSS. Un masque : un décalage, aucune table.
|
|
26
|
+
*/
|
|
27
|
+
const RPC_SERVER_ERRORS = 61460;
|
|
28
|
+
/**
|
|
29
|
+
* Gravité d'un appel RPC selon son code (ADR-0026 D10 point 4) : 0 → INFO ;
|
|
30
|
+
* une erreur du serveur ({@link RPC_SERVER_ERRORS}) → ERROR ; les autres —
|
|
31
|
+
* refus, échecs métier ou client — → WARNING.
|
|
32
|
+
*
|
|
33
|
+
* @param code - le code de l'appel (gRPC : 0 à 16).
|
|
34
|
+
* @returns la gravité de la ligne.
|
|
35
|
+
*/
|
|
36
|
+
function severityFromRpcCode(code) {
|
|
37
|
+
if (code === 0) return "INFO";
|
|
38
|
+
return code < 32 && (RPC_SERVER_ERRORS >>> code & 1) === 1 ? "ERROR" : "WARNING";
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Gravité de la ligne d'une requête HTTP : celle du code de son appel RPC
|
|
42
|
+
* quand il est fixé — le statut HTTP d'un appel gRPC reste 200 quand l'appel
|
|
43
|
+
* échoue —, sinon celle de son statut.
|
|
44
|
+
*
|
|
45
|
+
* @param context - le contexte de la requête.
|
|
46
|
+
* @param status - son statut HTTP.
|
|
47
|
+
* @returns la gravité de la ligne.
|
|
48
|
+
*/
|
|
49
|
+
function severityOfHttp(context, status) {
|
|
50
|
+
const code = context.rpc?.code ?? null;
|
|
51
|
+
return code === null ? severityFromStatus(status) : severityFromRpcCode(code);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Un appel RPC en échec (code fixé, non nul) — sous un statut HTTP qui peut
|
|
55
|
+
* rester 200 : jamais écarté par l'échantillonnage.
|
|
56
|
+
*/
|
|
57
|
+
function rpcFailed(context) {
|
|
58
|
+
const code = context.rpc?.code ?? null;
|
|
59
|
+
return code !== null && code !== 0;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
21
62
|
* JSON audit logger — implements IRequestLogger so it slots into
|
|
22
63
|
* `httpKernel.setRequestLogger(new JsonAuditLogger())`.
|
|
23
64
|
*
|
|
@@ -43,8 +84,9 @@ var JsonAuditLogger = class {
|
|
|
43
84
|
/**
|
|
44
85
|
* Decide whether the current HTTP request must be logged (audit sampling).
|
|
45
86
|
*
|
|
46
|
-
* Always `true` when `sampleRate <= 1`, on errors,
|
|
47
|
-
*
|
|
87
|
+
* Always `true` when `sampleRate <= 1`, on errors, for `status >= 400` and
|
|
88
|
+
* for a failed RPC call — whose HTTP status may stay 200 (failures are
|
|
89
|
+
* never sampled out). Otherwise selects 1 in `sampleRate` of
|
|
48
90
|
* the 2xx/3xx requests with a deterministic counter.
|
|
49
91
|
*
|
|
50
92
|
* Called by `Context.logRequest()` **before** `renderHttp`, so a sampled-out
|
|
@@ -58,11 +100,13 @@ var JsonAuditLogger = class {
|
|
|
58
100
|
const ctx = context;
|
|
59
101
|
if (!this.nominalEnabled) {
|
|
60
102
|
if (error ?? ctx.error) return true;
|
|
103
|
+
if (rpcFailed(ctx)) return true;
|
|
61
104
|
const status = statusOf(ctx);
|
|
62
105
|
return status !== null && status >= 400;
|
|
63
106
|
}
|
|
64
107
|
if (this.sampleRate <= 1) return true;
|
|
65
108
|
if (error ?? ctx.error) return true;
|
|
109
|
+
if (rpcFailed(ctx)) return true;
|
|
66
110
|
const status = statusOf(ctx);
|
|
67
111
|
if (status !== null && status >= 400) return true;
|
|
68
112
|
this.sampleCounter = (this.sampleCounter + 1) % this.sampleRate;
|
|
@@ -92,11 +136,12 @@ var JsonAuditLogger = class {
|
|
|
92
136
|
name: p.name,
|
|
93
137
|
durationMs: p.durationMs ?? null
|
|
94
138
|
})) : void 0,
|
|
95
|
-
error: err ? this.serializeError(err, 0) : void 0
|
|
139
|
+
error: err ? this.serializeError(err, 0) : void 0,
|
|
140
|
+
rpc: rpcOutcomeOf(ctx.rpc ?? null)
|
|
96
141
|
};
|
|
97
142
|
return {
|
|
98
143
|
text: JSON.stringify(entry),
|
|
99
|
-
severity:
|
|
144
|
+
severity: severityOfHttp(ctx, status ?? (err ? 500 : null)),
|
|
100
145
|
msgid: "audit"
|
|
101
146
|
};
|
|
102
147
|
}
|
|
@@ -125,7 +170,8 @@ var JsonAuditLogger = class {
|
|
|
125
170
|
durationMs: p.durationMs ?? null
|
|
126
171
|
})) : void 0,
|
|
127
172
|
protocol: acceptedProtocol ?? null,
|
|
128
|
-
error: error ? this.serializeError(error, 0) : void 0
|
|
173
|
+
error: error ? this.serializeError(error, 0) : void 0,
|
|
174
|
+
rpc: null
|
|
129
175
|
};
|
|
130
176
|
return {
|
|
131
177
|
text: JSON.stringify(entry),
|
|
@@ -151,4 +197,4 @@ var JsonAuditLogger = class {
|
|
|
151
197
|
return entry;
|
|
152
198
|
}
|
|
153
199
|
};
|
|
154
|
-
export { JsonAuditLogger as default, severityFromStatus, statusOf };
|
|
200
|
+
export { JsonAuditLogger as default, severityFromRpcCode, severityFromStatus, severityOfHttp, statusOf };
|