@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,100 @@
|
|
|
1
|
+
import http2 from "node:http2";
|
|
2
|
+
// nodefony/src/servers/http2StreamGrace.ts
|
|
3
|
+
/**
|
|
4
|
+
* Délai avant de retenter un `PING` que Node a refusé (plafond de `PING` en vol
|
|
5
|
+
* atteint par d'autres émetteurs de la session) : ceux-là reviennent en un
|
|
6
|
+
* aller-retour, une boucle `setImmediate` brûlerait le processeur pendant ce
|
|
7
|
+
* temps sur un lien lent.
|
|
8
|
+
*/
|
|
9
|
+
const PING_RETRY_MS = 10;
|
|
10
|
+
/**
|
|
11
|
+
* Les flux d'une session qui attendent leur grâce. `inFlight` : couverts par le
|
|
12
|
+
* `PING` en vol ; `next` : arrivés après son envoi, donc couverts par le
|
|
13
|
+
* suivant — un `PING` envoyé AVANT leurs en-têtes ne prouve rien pour eux.
|
|
14
|
+
*/
|
|
15
|
+
var SessionGrace = class {
|
|
16
|
+
inFlight = null;
|
|
17
|
+
next = null;
|
|
18
|
+
};
|
|
19
|
+
/** Chemin d'erreur seulement : aucune session n'a d'entrée hors refus en cours. */
|
|
20
|
+
const graces = /* @__PURE__ */ new WeakMap();
|
|
21
|
+
/** Coupe le flux en `NO_ERROR` s'il est encore ouvert et que le client émet encore. */
|
|
22
|
+
function decide(stream) {
|
|
23
|
+
if (stream.closed || stream.destroyed || stream.state.remoteClose) return;
|
|
24
|
+
stream.close(http2.constants.NGHTTP2_NO_ERROR);
|
|
25
|
+
}
|
|
26
|
+
function sendPing(session, grace) {
|
|
27
|
+
if (session.destroyed) {
|
|
28
|
+
graces.delete(session);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (session.closed) {
|
|
32
|
+
const pending = grace.inFlight;
|
|
33
|
+
const later = grace.next;
|
|
34
|
+
grace.inFlight = null;
|
|
35
|
+
grace.next = null;
|
|
36
|
+
graces.delete(session);
|
|
37
|
+
setImmediate(() => {
|
|
38
|
+
if (pending !== null) for (const stream of pending) decide(stream);
|
|
39
|
+
if (later !== null) for (const stream of later) decide(stream);
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!session.ping(() => settle(session, grace))) setTimeout(() => sendPing(session, grace), PING_RETRY_MS).unref();
|
|
44
|
+
}
|
|
45
|
+
function settle(session, grace) {
|
|
46
|
+
const batch = grace.inFlight;
|
|
47
|
+
grace.inFlight = grace.next;
|
|
48
|
+
grace.next = null;
|
|
49
|
+
if (batch !== null) for (const stream of batch) decide(stream);
|
|
50
|
+
if (grace.inFlight !== null) sendPing(session, grace);
|
|
51
|
+
else graces.delete(session);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Ferme en `NO_ERROR` un flux HTTP/2 dont la réponse est partie mais dont le
|
|
55
|
+
* client émet encore, après la grâce d'un aller-retour (`PING`, RFC 9113 §6.7).
|
|
56
|
+
*
|
|
57
|
+
* RFC 9113 §8.1 : après une réponse complète, le serveur PEUT demander au
|
|
58
|
+
* client d'arrêter d'émettre par un `RST_STREAM` `NO_ERROR`. La coupe ne doit
|
|
59
|
+
* jamais précéder l'envoi de la réponse : un `RST_STREAM` soumis pendant que
|
|
60
|
+
* les en-têtes attendent encore dans la file de nghttp2 les fait JETER, et le
|
|
61
|
+
* client reçoit une fin de flux sans statut (un client gRPC la lit INTERNAL).
|
|
62
|
+
* Le `PING` part derrière la réponse ; son écho prouve qu'elle est arrivée.
|
|
63
|
+
*
|
|
64
|
+
* **Un seul `PING` par session** couvre tous les flux qui attendent : un flux
|
|
65
|
+
* arrivé pendant qu'un `PING` est en vol attend le suivant. Le plafond de
|
|
66
|
+
* `PING` en vol de Node (10 par session) n'est donc jamais atteint par ce
|
|
67
|
+
* chemin, et aucun flux n'est coupé sans grâce — ce que faisait la coupe
|
|
68
|
+
* immédiate quand `ping()` rendait `false`, au-delà de 10 refus simultanés.
|
|
69
|
+
*
|
|
70
|
+
* Session en fermeture GRACIEUSE (délai d'inactivité, arrêt) : Node n'envoie
|
|
71
|
+
* plus de `PING`, et la session attend la fin de ses flux — un client muet la
|
|
72
|
+
* retiendrait. La coupe part alors au tour de boucle suivant, le temps que les
|
|
73
|
+
* réponses déjà soumises quittent la file de nghttp2.
|
|
74
|
+
*
|
|
75
|
+
* Après la grâce : client qui a fini d'émettre → rien (le flux se ferme seul,
|
|
76
|
+
* sans `RST_STREAM`) ; sinon → coupé. L'écho d'un `PING` passe avant les autres
|
|
77
|
+
* trames (§6.7) : une trame DATA encore en route peut arriver après lui, la
|
|
78
|
+
* coupe reste conforme (§8.1).
|
|
79
|
+
*
|
|
80
|
+
* Coût : chemin d'erreur seulement — une entrée de `WeakMap` par session tant
|
|
81
|
+
* que des flux attendent, un `PING` par aller-retour et par session.
|
|
82
|
+
*
|
|
83
|
+
* @param stream - flux dont la réponse est ÉCRITE (`finish` passé).
|
|
84
|
+
*/
|
|
85
|
+
function closeStreamAfterGrace(stream) {
|
|
86
|
+
if (stream.closed || stream.destroyed || stream.state.remoteClose) return;
|
|
87
|
+
const session = stream.session;
|
|
88
|
+
if (session === void 0) return;
|
|
89
|
+
let grace = graces.get(session);
|
|
90
|
+
if (grace === void 0) {
|
|
91
|
+
grace = new SessionGrace();
|
|
92
|
+
graces.set(session, grace);
|
|
93
|
+
}
|
|
94
|
+
if (grace.inFlight === null) {
|
|
95
|
+
grace.inFlight = [stream];
|
|
96
|
+
sendPing(session, grace);
|
|
97
|
+
} else if (grace.next === null) grace.next = [stream];
|
|
98
|
+
else grace.next.push(stream);
|
|
99
|
+
}
|
|
100
|
+
export { closeStreamAfterGrace };
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// nodefony/src/servers/receiveDeadline.ts
|
|
2
|
+
/** Capacité de départ de l'anneau, et celle où il revient une fois vide. */
|
|
3
|
+
const INITIAL_CAPACITY = 64;
|
|
4
|
+
/**
|
|
5
|
+
* Une échéance ABSOLUE pour des réceptions en cours, sans un minuteur par
|
|
6
|
+
* réception : un seul balayage périodique pour toutes.
|
|
7
|
+
*
|
|
8
|
+
* ## Pourquoi
|
|
9
|
+
*
|
|
10
|
+
* `node:http2` ne borne pas la réception d'une requête : il n'a que des délais
|
|
11
|
+
* d'INACTIVITÉ (`http2stream.setTimeout`, `http2session.setTimeout`), qu'un
|
|
12
|
+
* client tient en vie avec un octet avant chaque échéance. `requestTimeout`,
|
|
13
|
+
* la borne absolue anti « slow-loris », n'existe que pour HTTP/1.1.
|
|
14
|
+
*
|
|
15
|
+
* ## Le coût, et pourquoi cette forme
|
|
16
|
+
*
|
|
17
|
+
* C'est la forme que Node donne lui-même à `requestTimeout` : aucun minuteur
|
|
18
|
+
* par requête, un contrôle périodique (`connectionsCheckingInterval`). Les
|
|
19
|
+
* réceptions suivies vivent dans un ANNEAU — un tableau des éléments et un
|
|
20
|
+
* `Int32Array` des tours d'entrée, indexés par un numéro de séquence :
|
|
21
|
+
* - suivre = deux écritures ; libérer = une comparaison et une écriture. Ni
|
|
22
|
+
* minuteur, ni fermeture, ni lecture d'horloge (le temps se compte en TOURS),
|
|
23
|
+
* ni champ ajouté à l'élément suivi ;
|
|
24
|
+
* - mesuré contre une `Map` (hachage d'identité de la clé) : ~26–32 ns par
|
|
25
|
+
* réception contre ~90–150 ns, de 1 à 10 000 réceptions simultanées.
|
|
26
|
+
*
|
|
27
|
+
* Toutes les entrées ont le même délai : l'ordre d'insertion EST l'ordre
|
|
28
|
+
* d'échéance. Un tour avance la tête et s'arrête à la première entrée vivante
|
|
29
|
+
* non échue — il ne coûte que ce qu'il expire ou saute.
|
|
30
|
+
*
|
|
31
|
+
* Contrepartie, bornée : une case libérée DERRIÈRE une entrée vivante attend
|
|
32
|
+
* que la tête la dépasse, soit au plus un délai de réception — 12 octets par
|
|
33
|
+
* réception commencée dans cette fenêtre. Sous charge, l'anneau garde sa
|
|
34
|
+
* capacité d'un tour à l'autre (jamais rétréci puis recrû) ; il la rend au
|
|
35
|
+
* premier tour sans réception nouvelle. Le minuteur unique est `unref`, et s'arrête au
|
|
36
|
+
* même moment.
|
|
37
|
+
*
|
|
38
|
+
* ## Précision
|
|
39
|
+
*
|
|
40
|
+
* Une entrée expire entre `timeoutMs` et `timeoutMs + periodMs` après son
|
|
41
|
+
* suivi — jamais AVANT le délai configuré.
|
|
42
|
+
*
|
|
43
|
+
* @typeParam T - l'élément suivi (un contexte de requête)
|
|
44
|
+
*/
|
|
45
|
+
var ReceiveDeadline = class {
|
|
46
|
+
#items = new Array(INITIAL_CAPACITY).fill(null);
|
|
47
|
+
#ticks = new Int32Array(INITIAL_CAPACITY);
|
|
48
|
+
#mask = 63;
|
|
49
|
+
/** Numéro de séquence de la plus ancienne case non balayée. */
|
|
50
|
+
#head = 0;
|
|
51
|
+
/** Numéro de séquence de la prochaine entrée. */
|
|
52
|
+
#tail = 0;
|
|
53
|
+
#tick = 0;
|
|
54
|
+
#timer = null;
|
|
55
|
+
#onExpire;
|
|
56
|
+
#periodMs;
|
|
57
|
+
/** Tours pleins qu'une entrée doit voir passer avant d'expirer. */
|
|
58
|
+
#ttl;
|
|
59
|
+
/**
|
|
60
|
+
* @param timeoutMs - le délai de réception (> 0)
|
|
61
|
+
* @param onExpire - appelé une fois par entrée échue, déjà retirée
|
|
62
|
+
* @param periodMs - la période du balayage, bornée par le délai (défaut 1 s)
|
|
63
|
+
* @throws RangeError si `timeoutMs` ou `periodMs` n'est pas un entier > 0
|
|
64
|
+
*/
|
|
65
|
+
constructor(timeoutMs, onExpire, periodMs = 1e3) {
|
|
66
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) throw new RangeError(`délai de réception invalide : ${timeoutMs}`);
|
|
67
|
+
if (!Number.isInteger(periodMs) || periodMs <= 0) throw new RangeError(`période de balayage invalide : ${periodMs}`);
|
|
68
|
+
this.#onExpire = onExpire;
|
|
69
|
+
this.#periodMs = Math.min(periodMs, timeoutMs);
|
|
70
|
+
this.#ttl = Math.ceil(timeoutMs / this.#periodMs);
|
|
71
|
+
}
|
|
72
|
+
/** Cases occupées, libérées ou non, entre la tête et la queue. */
|
|
73
|
+
get size() {
|
|
74
|
+
return this.#tail - this.#head;
|
|
75
|
+
}
|
|
76
|
+
/** Capacité courante de l'anneau. */
|
|
77
|
+
get capacity() {
|
|
78
|
+
return this.#mask + 1;
|
|
79
|
+
}
|
|
80
|
+
/** Vrai tant que le balayage tourne. */
|
|
81
|
+
get running() {
|
|
82
|
+
return this.#timer !== null;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Suit une réception qui commence ; démarre le balayage s'il dormait.
|
|
86
|
+
*
|
|
87
|
+
* @param item - la réception
|
|
88
|
+
* @returns le numéro à rendre à {@link ReceiveDeadline.release}
|
|
89
|
+
*/
|
|
90
|
+
track(item) {
|
|
91
|
+
if (this.#tail - this.#head > this.#mask) this.#grow();
|
|
92
|
+
const seq = this.#tail++;
|
|
93
|
+
const index = seq & this.#mask;
|
|
94
|
+
this.#items[index] = item;
|
|
95
|
+
this.#ticks[index] = this.#tick;
|
|
96
|
+
if (this.#timer === null) {
|
|
97
|
+
this.#timer = setInterval(() => this.#sweep(), this.#periodMs);
|
|
98
|
+
this.#timer.unref();
|
|
99
|
+
}
|
|
100
|
+
return seq;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Cesse de suivre une réception terminée (reçue, refusée ou échue). Sans
|
|
104
|
+
* effet si la case a déjà été balayée ou réattribuée : l'identité de
|
|
105
|
+
* l'élément est vérifiée. Le balayage ne s'arrête pas ici mais au tour
|
|
106
|
+
* suivant s'il ne reste rien — jamais un minuteur armé puis désarmé par
|
|
107
|
+
* requête.
|
|
108
|
+
*
|
|
109
|
+
* @param seq - le numéro rendu par {@link ReceiveDeadline.track}
|
|
110
|
+
* @param item - la réception suivie
|
|
111
|
+
*/
|
|
112
|
+
release(seq, item) {
|
|
113
|
+
const index = seq & this.#mask;
|
|
114
|
+
if (this.#items[index] === item) this.#items[index] = null;
|
|
115
|
+
}
|
|
116
|
+
/** Arrête le balayage et oublie toutes les réceptions (arrêt du serveur). */
|
|
117
|
+
close() {
|
|
118
|
+
if (this.#timer !== null) {
|
|
119
|
+
clearInterval(this.#timer);
|
|
120
|
+
this.#timer = null;
|
|
121
|
+
}
|
|
122
|
+
this.#reset();
|
|
123
|
+
}
|
|
124
|
+
#sweep() {
|
|
125
|
+
const tick = ++this.#tick;
|
|
126
|
+
const items = this.#items;
|
|
127
|
+
const carried = this.#tail - this.#head;
|
|
128
|
+
while (this.#head < this.#tail) {
|
|
129
|
+
const index = this.#head & this.#mask;
|
|
130
|
+
const item = items[index] ?? null;
|
|
131
|
+
if (item !== null && tick - (this.#ticks[index] ?? 0) <= this.#ttl) break;
|
|
132
|
+
items[index] = null;
|
|
133
|
+
this.#head++;
|
|
134
|
+
if (item !== null) this.#onExpire(item);
|
|
135
|
+
}
|
|
136
|
+
if (this.#head !== this.#tail) return;
|
|
137
|
+
if (carried > 0) {
|
|
138
|
+
this.#head = 0;
|
|
139
|
+
this.#tail = 0;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (this.#timer !== null) {
|
|
143
|
+
clearInterval(this.#timer);
|
|
144
|
+
this.#timer = null;
|
|
145
|
+
}
|
|
146
|
+
this.#reset();
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Anneau au repos ou fermé : numéros remis à zéro et capacité de départ
|
|
150
|
+
* rendue. Un numéro encore détenu par un appelant ne désigne plus son
|
|
151
|
+
* élément : `release` le reconnaît à l'identité.
|
|
152
|
+
*/
|
|
153
|
+
#reset() {
|
|
154
|
+
this.#head = 0;
|
|
155
|
+
this.#tail = 0;
|
|
156
|
+
if (this.#mask + 1 !== INITIAL_CAPACITY) {
|
|
157
|
+
this.#items = new Array(INITIAL_CAPACITY).fill(null);
|
|
158
|
+
this.#ticks = new Int32Array(INITIAL_CAPACITY);
|
|
159
|
+
this.#mask = 63;
|
|
160
|
+
} else this.#items.fill(null);
|
|
161
|
+
}
|
|
162
|
+
/** Double la capacité, les entrées gardant leur numéro de séquence. */
|
|
163
|
+
#grow() {
|
|
164
|
+
const capacity = (this.#mask + 1) * 2;
|
|
165
|
+
const mask = capacity - 1;
|
|
166
|
+
const items = new Array(capacity).fill(null);
|
|
167
|
+
const ticks = new Int32Array(capacity);
|
|
168
|
+
for (let seq = this.#head; seq < this.#tail; seq++) {
|
|
169
|
+
items[seq & mask] = this.#items[seq & this.#mask] ?? null;
|
|
170
|
+
ticks[seq & mask] = this.#ticks[seq & this.#mask] ?? 0;
|
|
171
|
+
}
|
|
172
|
+
this.#items = items;
|
|
173
|
+
this.#ticks = ticks;
|
|
174
|
+
this.#mask = mask;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
export { ReceiveDeadline };
|
package/dist/types/index.d.ts
CHANGED
|
@@ -77,7 +77,10 @@ export { decideSend, readBackpressureOptions, type WsBackpressurePolicy, type Ws
|
|
|
77
77
|
export { admitFrame, frameRateRejectedTotal, readFrameRateBound, readFrameRateCloseAfter, FRAME_RATE_WINDOW_MS, type IFrameRateWindow, type IWsFrameRateOptions, } from "./nodefony/src/context/websocket/wsFrameRate.js";
|
|
78
78
|
export { isWebsocketContext } from "./nodefony/src/context/websocket/isWebsocketContext.js";
|
|
79
79
|
export { REQUEST_TRACE_CHANNEL } from "./nodefony/src/context/requestTrace.js";
|
|
80
|
+
export { rpcCorsLists } from "./nodefony/src/grpc/cors.js";
|
|
81
|
+
export type { IRpcCorsLists } from "./nodefony/src/grpc/cors.js";
|
|
80
82
|
export type { IRequestTrace, RequestTraceProtocol, } from "./nodefony/interfaces/IRequestTrace.js";
|
|
83
|
+
export type { IRpcCall, IRpcOutcome, RpcSystem, } from "./nodefony/interfaces/IRpcCall.js";
|
|
81
84
|
export { compileDomainPattern, compileDomainPatterns, compileTrustedHosts, isDomainAllowed, } from "./nodefony/src/context/domainMatcher.js";
|
|
82
85
|
export type { DomainPattern, ITrustedHostsConfig, } from "./nodefony/src/context/domainMatcher.js";
|
|
83
86
|
export { MemoryRateLimitStore } from "./nodefony/src/rateLimit/MemoryRateLimitStore.js";
|
|
@@ -108,8 +111,8 @@ export type { IHttpResponse, IWebsocketResponse, } from "./nodefony/interfaces/I
|
|
|
108
111
|
export type { IHttpKernel } from "./nodefony/interfaces/IHttpKernel.js";
|
|
109
112
|
export type { IResolvedRoute, IRouteResolver, IRequestRouter, } from "./nodefony/interfaces/IRouting.js";
|
|
110
113
|
export type { ISecurityZone, IFirewallGate, } from "./nodefony/interfaces/ISecurity.js";
|
|
111
|
-
export type { IUploadedFile, IUploadService, IParsedUploadFile, IUploadOptions, } from "./nodefony/interfaces/IUpload.js";
|
|
112
|
-
export type { IErrorRenderer, IErrorHttpResult, IErrorWebsocketResult, } from "./nodefony/interfaces/IErrorRenderer.js";
|
|
114
|
+
export type { IUploadedFile, IUploadService, IParsedUploadFile, IUploadOptions, IRouteUploadOptions, } from "./nodefony/interfaces/IUpload.js";
|
|
115
|
+
export type { IErrorRenderer, IErrorHttpResult, IErrorWebsocketResult, IErrorDescription, } from "./nodefony/interfaces/IErrorRenderer.js";
|
|
113
116
|
export type { IRequestLogger, IRequestLogEntry, } from "./nodefony/interfaces/IRequestLogger.js";
|
|
114
117
|
export type { AuditLogEntry, AuditErrorEntry, JsonAuditLoggerOptions, } from "./nodefony/service/audit-logger.js";
|
|
115
118
|
export type { ContextType, httpRequest, httpResponse, ProtocolType, } from "./nodefony/service/http-kernel.js";
|
|
@@ -108,6 +108,7 @@ export declare const httpConfigSchema: z.ZodObject<{
|
|
|
108
108
|
}, z.core.$loose>]>>;
|
|
109
109
|
skipUTF8Validation: z.ZodDefault<z.ZodBoolean>;
|
|
110
110
|
autoPong: z.ZodDefault<z.ZodBoolean>;
|
|
111
|
+
maxPendingAutoPongs: z.ZodDefault<z.ZodNumber>;
|
|
111
112
|
allowSynchronousEvents: z.ZodDefault<z.ZodBoolean>;
|
|
112
113
|
maxBackpressure: z.ZodDefault<z.ZodNumber>;
|
|
113
114
|
backpressureCloseAfterDrops: z.ZodDefault<z.ZodNumber>;
|
|
@@ -134,6 +135,7 @@ export declare const httpConfigSchema: z.ZodObject<{
|
|
|
134
135
|
}, z.core.$loose>]>>;
|
|
135
136
|
skipUTF8Validation: z.ZodDefault<z.ZodBoolean>;
|
|
136
137
|
autoPong: z.ZodDefault<z.ZodBoolean>;
|
|
138
|
+
maxPendingAutoPongs: z.ZodDefault<z.ZodNumber>;
|
|
137
139
|
allowSynchronousEvents: z.ZodDefault<z.ZodBoolean>;
|
|
138
140
|
maxBackpressure: z.ZodDefault<z.ZodNumber>;
|
|
139
141
|
backpressureCloseAfterDrops: z.ZodDefault<z.ZodNumber>;
|
|
@@ -208,6 +210,14 @@ export declare const httpConfigSchema: z.ZodObject<{
|
|
|
208
210
|
}>>;
|
|
209
211
|
}, z.core.$strict>>>;
|
|
210
212
|
}, z.core.$strict>>;
|
|
213
|
+
grpc: z.ZodDefault<z.ZodObject<{
|
|
214
|
+
grpc: z.ZodDefault<z.ZodBoolean>;
|
|
215
|
+
grpcWeb: z.ZodDefault<z.ZodBoolean>;
|
|
216
|
+
connect: z.ZodDefault<z.ZodBoolean>;
|
|
217
|
+
readMaxBytes: z.ZodDefault<z.ZodNumber>;
|
|
218
|
+
writeMaxBytes: z.ZodDefault<z.ZodNumber>;
|
|
219
|
+
requireConnectProtocolHeader: z.ZodDefault<z.ZodBoolean>;
|
|
220
|
+
}, z.core.$strict>>;
|
|
211
221
|
wsMaxConnectionsPerIp: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
|
|
212
222
|
}, z.core.$strict>;
|
|
213
223
|
/** Type de sortie (config normalisée + défauts appliqués). */
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { ContextKey, ContextValues, HandlerContext } from "@connectrpc/connect";
|
|
2
|
+
import type { IRpcCall, RpcSystem } from "../interfaces/IRpcCall.js";
|
|
3
|
+
import type HttpContext from "../src/context/http/HttpContext.js";
|
|
4
|
+
/**
|
|
5
|
+
* Fiche d'un appel gRPC : ce que Nodefony attache à l'appel, et le
|
|
6
|
+
* `ContextValues` que Connect remet au gestionnaire (`ctx.values`).
|
|
7
|
+
*
|
|
8
|
+
* Connect alloue à chaque appel un `ContextValues` fait d'un objet littéral et
|
|
9
|
+
* de trois fermetures (`context-values.js#createContextValues`), dont la forme
|
|
10
|
+
* change à chaque `set`. La fiche le REMPLACE (option `contextValues` de
|
|
11
|
+
* l'adaptateur) : une classe à champs déclarés, méthodes au prototype, une
|
|
12
|
+
* allocation par appel au lieu de quatre.
|
|
13
|
+
*
|
|
14
|
+
* Les clés posées par un tiers (intercepteur, porte) sont confiées à un
|
|
15
|
+
* `createContextValues()` créé au premier `set` : aucune allocation tant que
|
|
16
|
+
* personne n'en pose, et aucune conversion de type — la valeur rendue est celle
|
|
17
|
+
* que le magasin de Connect a typée.
|
|
18
|
+
*
|
|
19
|
+
* C'est aussi l'appel RPC du contexte (`HttpContext.rpc`, ADR-0026 D10) : à
|
|
20
|
+
* l'échéance du délai de réponse, le noyau le termine par {@link expire} ; au
|
|
21
|
+
* `close` de la réponse, il en fixe le code par {@link settle}, que lisent la
|
|
22
|
+
* ligne d'accès, l'audit et le canal de traçage.
|
|
23
|
+
*
|
|
24
|
+
* @experimental Surface gRPC, ADR-0026 D11.
|
|
25
|
+
*/
|
|
26
|
+
export declare class GrpcCall implements ContextValues, IRpcCall {
|
|
27
|
+
#private;
|
|
28
|
+
/**
|
|
29
|
+
* L'objet qui implémente le service pour CET appel : le contrôleur, une
|
|
30
|
+
* instance par appel sous `@Scope("request")`. `null` quand l'adaptateur est
|
|
31
|
+
* appelé hors de {@link GrpcEndpoint.serve} (jamais en service normal).
|
|
32
|
+
*/
|
|
33
|
+
readonly target: object | null;
|
|
34
|
+
/**
|
|
35
|
+
* Le contexte de la requête qui porte l'appel : l'identité y est lue
|
|
36
|
+
* (`authenticatedUser()`) sur son magasin, jamais par l'ALS, pour choisir
|
|
37
|
+
* entre 16 et 7 (ADR-0026 D5, D7). `null` hors de {@link GrpcEndpoint.serve}.
|
|
38
|
+
*/
|
|
39
|
+
readonly context: HttpContext | null;
|
|
40
|
+
constructor(target: object | null, context?: HttpContext | null);
|
|
41
|
+
/**
|
|
42
|
+
* Le protocole parlé sur le fil : celui du contexte d'appel (`grpc`,
|
|
43
|
+
* `grpc-web`, `connect`) ; à défaut, celui des en-têtes de la requête.
|
|
44
|
+
*/
|
|
45
|
+
get system(): RpcSystem;
|
|
46
|
+
/**
|
|
47
|
+
* `paquet.Service/Méthode` : le chemin de la route, que l'adaptateur a
|
|
48
|
+
* reconnu à l'octet près. Lu à la première demande, puis retenu.
|
|
49
|
+
*/
|
|
50
|
+
get method(): string;
|
|
51
|
+
/** Le code de l'appel (0 à 16), `null` avant {@link settle}. */
|
|
52
|
+
get code(): number | null;
|
|
53
|
+
/** Le nom du code (`OK`, `UNAVAILABLE`…), `null` avec lui. */
|
|
54
|
+
get codeName(): string | null;
|
|
55
|
+
/**
|
|
56
|
+
* Fixe le code de l'appel au `close` de la réponse (ADR-0026 D10 point 2) :
|
|
57
|
+
*
|
|
58
|
+
* - motif `ConnectError` → son code ; sa cause, si c'est une `Error`, part
|
|
59
|
+
* à la ligne d'accès (exception traduite par D7). Réponse NON finie : 1,
|
|
60
|
+
* sauf une fin posée par le serveur (échéance 4, interruption de la fiche)
|
|
61
|
+
* — le client est parti, quel que soit le code de son `RST_STREAM` ;
|
|
62
|
+
* - `AbortError` ou aucun motif → 0 si la réponse est finie, sinon 1 (le
|
|
63
|
+
* client est parti — le 499 du noyau, dit en gRPC) ;
|
|
64
|
+
* - autre valeur → 13, et elle part à la ligne d'accès.
|
|
65
|
+
*
|
|
66
|
+
* Une interruption du serveur (délai échu, identité révoquée) abaisse le
|
|
67
|
+
* signal avec sa `ConnectError` : elle suit la première règle. Sans contexte
|
|
68
|
+
* d'appel, le code reste `null`.
|
|
69
|
+
*
|
|
70
|
+
* @param ended - la réponse a-t-elle été terminée par le serveur ?
|
|
71
|
+
* @returns la cause à porter par la ligne d'accès, `null` sinon.
|
|
72
|
+
*/
|
|
73
|
+
settle(ended: boolean): Error | null;
|
|
74
|
+
/**
|
|
75
|
+
* Lit une valeur posée par un tiers, ou la valeur par défaut de sa clé.
|
|
76
|
+
*
|
|
77
|
+
* @param key - la clé (`createContextKey`).
|
|
78
|
+
* @returns la valeur posée, sinon `key.defaultValue`.
|
|
79
|
+
*/
|
|
80
|
+
get<T>(key: ContextKey<T>): T;
|
|
81
|
+
/**
|
|
82
|
+
* Pose une valeur pour la durée de l'appel.
|
|
83
|
+
*
|
|
84
|
+
* @param key - la clé (`createContextKey`).
|
|
85
|
+
* @param value - la valeur.
|
|
86
|
+
* @returns la fiche, pour chaîner.
|
|
87
|
+
*/
|
|
88
|
+
set<T>(key: ContextKey<T>, value: T): this;
|
|
89
|
+
/**
|
|
90
|
+
* Retire une valeur ; sa clé rendra de nouveau sa valeur par défaut.
|
|
91
|
+
*
|
|
92
|
+
* @param key - la clé (`createContextKey`).
|
|
93
|
+
* @returns la fiche, pour chaîner.
|
|
94
|
+
*/
|
|
95
|
+
delete(key: ContextKey<unknown>): this;
|
|
96
|
+
/**
|
|
97
|
+
* Le délai de réponse du serveur est échu : le signal de l'appel s'abaisse
|
|
98
|
+
* (`ctx.abort`) et l'appel se termine en 4 `DEADLINE_EXCEEDED`, même si le
|
|
99
|
+
* gestionnaire ne lit pas son signal. Échu avant que le gestionnaire ne
|
|
100
|
+
* démarre : il est terminé dès son démarrage. Sans effet après la fin.
|
|
101
|
+
*/
|
|
102
|
+
expire(): void;
|
|
103
|
+
/**
|
|
104
|
+
* L'identité qui a ouvert l'appel n'est plus valable : le signal de l'appel
|
|
105
|
+
* s'abaisse et l'appel se termine en 16 `UNAUTHENTICATED`. Réponse unique :
|
|
106
|
+
* sa promesse est rejetée. Flux : il s'arrête au message suivant, même si le
|
|
107
|
+
* gestionnaire ne lit pas son signal — au plus le message déjà en vol part.
|
|
108
|
+
* Sans effet après la fin, ou après une autre interruption.
|
|
109
|
+
*/
|
|
110
|
+
revoke(): void;
|
|
111
|
+
/**
|
|
112
|
+
* Le serveur s'arrête : le signal de l'appel s'abaisse et l'appel se termine
|
|
113
|
+
* en 14 `UNAVAILABLE` — le client le rejoue ailleurs (ADR-0026 D8). Flux : il
|
|
114
|
+
* s'arrête au message suivant, même si le gestionnaire ne lit pas son
|
|
115
|
+
* signal. Un gestionnaire bloqué qui n'écoute rien tombe avec sa connexion, à
|
|
116
|
+
* `shutdownTimeout`. Sans effet après la fin, ou après une autre interruption.
|
|
117
|
+
*/
|
|
118
|
+
shutdown(): void;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Le contexte d'appel que Connect remet réellement au gestionnaire : son type
|
|
122
|
+
* public (`HandlerContext`) ne dit pas `abort`, que porte pourtant l'objet créé
|
|
123
|
+
* par `createHandlerContext` (`HandlerContextController`, non exporté).
|
|
124
|
+
*
|
|
125
|
+
* @experimental Surface gRPC, ADR-0026 D11.
|
|
126
|
+
*/
|
|
127
|
+
export interface IAbortableHandlerContext extends HandlerContext {
|
|
128
|
+
/** Interrompt l'appel : `ctx.signal` s'abaisse avec `reason`. */
|
|
129
|
+
abort(reason?: unknown): void;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Garde de type : le contexte d'appel porte-t-il `abort` ? Connect le pose sur
|
|
133
|
+
* tout contexte qu'il crée ; le garde le CONSTATE au lieu de le supposer, pour
|
|
134
|
+
* qu'une version de Connect qui le retirerait se voie à l'exécution (et au
|
|
135
|
+
* test qui le garde) plutôt que par un appel qui ne s'interrompt plus.
|
|
136
|
+
*
|
|
137
|
+
* @param ctx - le contexte remis au gestionnaire.
|
|
138
|
+
* @returns `true` si `ctx.abort` est une fonction.
|
|
139
|
+
*
|
|
140
|
+
* @experimental Surface gRPC, ADR-0026 D11.
|
|
141
|
+
*/
|
|
142
|
+
export declare function hasAbort(ctx: HandlerContext): ctx is IAbortableHandlerContext;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { DescService } from "@bufbuild/protobuf";
|
|
2
|
+
import type HttpContext from "../src/context/http/HttpContext.js";
|
|
3
|
+
/**
|
|
4
|
+
* Protocoles servis — la section `grpc` de la configuration http
|
|
5
|
+
* (ADR-0026 D3, point 5).
|
|
6
|
+
*
|
|
7
|
+
* @experimental Surface gRPC, ADR-0026 D11.
|
|
8
|
+
*/
|
|
9
|
+
export interface IGrpcProtocols {
|
|
10
|
+
readonly grpc: boolean;
|
|
11
|
+
readonly grpcWeb: boolean;
|
|
12
|
+
readonly connect: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* La section `grpc` de la configuration http : protocoles servis (D3, point
|
|
16
|
+
* 5) et bornes des messages (D4, points 1, 2 et 6).
|
|
17
|
+
*
|
|
18
|
+
* @experimental Surface gRPC, ADR-0026 D11.
|
|
19
|
+
*/
|
|
20
|
+
export interface IGrpcSettings extends IGrpcProtocols {
|
|
21
|
+
/** Taille maximale d'un message reçu, après décompression (octets). */
|
|
22
|
+
readonly readMaxBytes: number;
|
|
23
|
+
/** Taille maximale d'un message émis (octets). */
|
|
24
|
+
readonly writeMaxBytes: number;
|
|
25
|
+
/** Exige l'en-tête `Connect-Protocol-Version` sur les requêtes Connect. */
|
|
26
|
+
readonly requireConnectProtocolHeader: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Options d'un {@link GrpcEndpoint}.
|
|
30
|
+
*
|
|
31
|
+
* @experimental Surface gRPC, ADR-0026 D11.
|
|
32
|
+
*/
|
|
33
|
+
export interface IGrpcEndpointOptions {
|
|
34
|
+
/**
|
|
35
|
+
* `true` : la cible change à chaque appel (contrôleur `@Scope("request")`),
|
|
36
|
+
* chaque méthode la relit dans la fiche d'appel. `false` (défaut) : la cible
|
|
37
|
+
* du premier appel sert tous les suivants (contrôleur singleton) et ses
|
|
38
|
+
* méthodes sont liées une fois — aucune indirection par appel.
|
|
39
|
+
*/
|
|
40
|
+
readonly perCall?: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Point d'entrée gRPC d'un service : possède l'adaptateur Connect
|
|
44
|
+
* (`connectNodeAdapter`) et lui confie la requête et la réponse BRUTES de Node
|
|
45
|
+
* d'un contexte déjà passé par le pipeline (pare-feu, gardes).
|
|
46
|
+
*
|
|
47
|
+
* L'adaptateur est construit au premier appel, depuis la section `grpc` de la
|
|
48
|
+
* configuration http. Il reçoit la fiche d'appel ({@link GrpcCall}) par son
|
|
49
|
+
* option `contextValues`, qu'il appelle de façon SYNCHRONE avant toute
|
|
50
|
+
* attente (`connect-node-adapter.js#nodeRequestHandler`) : la cible passe par
|
|
51
|
+
* une case lue à cet instant — ni ALS, ni promesse, ni écouteur de plus.
|
|
52
|
+
*
|
|
53
|
+
* Les méthodes s'inscrivent une à une par `router.rpc`, SANS options : des
|
|
54
|
+
* options passées à l'inscription rallumeraient gRPC et gRPC-Web coupés par la
|
|
55
|
+
* configuration (connectrpc/connect-es#1835). La restriction de protocoles
|
|
56
|
+
* vit sur l'adaptateur seul.
|
|
57
|
+
*
|
|
58
|
+
* @experimental Surface gRPC, ADR-0026 D11.
|
|
59
|
+
*/
|
|
60
|
+
export declare class GrpcEndpoint<S extends DescService = DescService> {
|
|
61
|
+
#private;
|
|
62
|
+
/** Le contrat servi (descripteur généré par `buf`). */
|
|
63
|
+
readonly service: S;
|
|
64
|
+
/** Cible relue à chaque appel (`@Scope("request")`). */
|
|
65
|
+
readonly perCall: boolean;
|
|
66
|
+
constructor(service: S, options?: IGrpcEndpointOptions);
|
|
67
|
+
/**
|
|
68
|
+
* Remet la requête du contexte à l'adaptateur, qui écrit lui-même la
|
|
69
|
+
* réponse et ses trailers. Synchrone : rien n'est attendu ici.
|
|
70
|
+
*
|
|
71
|
+
* Avant de la remettre : le contexte est marqué répondu (`sended`), si bien
|
|
72
|
+
* que le pipeline n'attend aucune réponse de l'action.
|
|
73
|
+
*
|
|
74
|
+
* - Appel unaire : le délai de réponse de Nodefony reste armé, et termine
|
|
75
|
+
* l'appel en 4 `DEADLINE_EXCEEDED` par sa fiche (`HttpContext.rpc`,
|
|
76
|
+
* ADR-0026 D4 point 3).
|
|
77
|
+
* - Flux : aucune échéance. Il s'ouvre par le cycle de vie d'un flux
|
|
78
|
+
* (`openStreamLifecycle`, D4 point 4) — délai de réponse désarmé (à
|
|
79
|
+
* l'expiration il écrirait un 503 par-dessus l'adaptateur), place dans
|
|
80
|
+
* le budget de connexions longues par IP, client qui ne lit plus coupé
|
|
81
|
+
* par le balayage de blocage. Synchrone, sauf quand une session est à
|
|
82
|
+
* sauver : l'appel n'est remis qu'après.
|
|
83
|
+
*
|
|
84
|
+
* @param context - le contexte HTTP de la requête, pas encore répondu.
|
|
85
|
+
* @param target - l'objet qui implémente le service pour cet appel.
|
|
86
|
+
* @returns rien, ou la promesse de la remise quand une session est à sauver.
|
|
87
|
+
* @throws HttpError 500 quand la réponse est déjà partie, ou que son
|
|
88
|
+
* transport n'est pas reconnu.
|
|
89
|
+
* @throws HttpError 429 quand l'IP du client a déjà tous ses flux et sockets
|
|
90
|
+
* (`wsMaxConnectionsPerIp`) — rendu par le pipeline en 14 `UNAVAILABLE`.
|
|
91
|
+
* @throws HttpError 400 quand l'adaptateur refuse la requête avant d'avoir
|
|
92
|
+
* rien écrit (requête sans autorité `Host`) — la réponse revient alors au
|
|
93
|
+
* pipeline.
|
|
94
|
+
* @throws HttpError 404 quand aucune méthode ne correspond au chemin exact
|
|
95
|
+
* (casse modifiée) — rendu par le pipeline en 12 `UNIMPLEMENTED`.
|
|
96
|
+
*/
|
|
97
|
+
serve(context: HttpContext, target: object): void | Promise<void>;
|
|
98
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nodefony/http/grpc` — la primitive de transport gRPC (ADR-0026).
|
|
3
|
+
*
|
|
4
|
+
* Sous-chemin à part : le point d'entrée principal de `@nodefony/http` ne cite
|
|
5
|
+
* ni Connect ni protobuf, même en type (D12). Seule une application qui
|
|
6
|
+
* déclare un service l'importe — par `@nodefony/framework/grpc` — et charge
|
|
7
|
+
* alors Connect au démarrage.
|
|
8
|
+
*
|
|
9
|
+
* @experimental Surface gRPC, ADR-0026 D11.
|
|
10
|
+
*/
|
|
11
|
+
export { GrpcCall, hasAbort } from "./GrpcCall.js";
|
|
12
|
+
export type { IAbortableHandlerContext } from "./GrpcCall.js";
|
|
13
|
+
export { GrpcEndpoint } from "./GrpcEndpoint.js";
|
|
14
|
+
export type { IGrpcEndpointOptions, IGrpcProtocols, IGrpcSettings, } from "./GrpcEndpoint.js";
|
|
15
|
+
export { markGrpcRoute } from "../src/grpc/refusal.js";
|
|
@@ -3,6 +3,7 @@ import type { MaybePromise } from "nodefony";
|
|
|
3
3
|
import type { ICookie } from "./ICookie.js";
|
|
4
4
|
import type { ISession } from "./ISession.js";
|
|
5
5
|
import type { HTTPMethodType } from "./IRequest.js";
|
|
6
|
+
import type { IRpcCall } from "./IRpcCall.js";
|
|
6
7
|
/**
|
|
7
8
|
* Identité du serveur qui a reçu la requête, nommée par le schéma d'URI qu'il
|
|
8
9
|
* sert en LOCAL (RFC 9110 §4.2, RFC 6455 §3) : elle choisit la section de
|
|
@@ -156,6 +157,14 @@ export interface IContext {
|
|
|
156
157
|
export interface IHttpContext extends IContext {
|
|
157
158
|
proxy: object | null;
|
|
158
159
|
isRedirect: boolean;
|
|
160
|
+
/**
|
|
161
|
+
* L'appel RPC servi par cette requête (gRPC, gRPC-Web, Connect), `null` hors
|
|
162
|
+
* appel RPC — ADR-0026 D10. Son code est fixé au `close`, avant la ligne
|
|
163
|
+
* d'accès.
|
|
164
|
+
*
|
|
165
|
+
* @experimental Surface gRPC, ADR-0026 D11.
|
|
166
|
+
*/
|
|
167
|
+
rpc: IRpcCall | null;
|
|
159
168
|
/**
|
|
160
169
|
* Sert la requête — synchrone quand l'action et son rendu le sont.
|
|
161
170
|
*
|
|
@@ -11,6 +11,20 @@ export interface IErrorHttpResult {
|
|
|
11
11
|
body: unknown;
|
|
12
12
|
headers?: Record<string, string | number>;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Ce qu'une route HTTP rendrait pour une erreur — son statut et le message
|
|
16
|
+
* destiné au client —, sans rien écrire. Sert les transports qui n'ont pas de
|
|
17
|
+
* corps d'erreur à rendre : un appel gRPC traduit ce statut en code
|
|
18
|
+
* (ADR-0026 D7).
|
|
19
|
+
*
|
|
20
|
+
* @experimental Surface gRPC, ADR-0026 D11.
|
|
21
|
+
*/
|
|
22
|
+
export interface IErrorDescription {
|
|
23
|
+
/** Statut HTTP émettable (100 à 599). */
|
|
24
|
+
readonly status: number;
|
|
25
|
+
/** Message pour le client : opaque pour un 5xx en production. */
|
|
26
|
+
readonly message: string;
|
|
27
|
+
}
|
|
14
28
|
/**
|
|
15
29
|
* Aide jointe au corps d'erreur — ce que le développeur doit faire, en clair.
|
|
16
30
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { URL } from "node:url";
|
|
2
2
|
import type { ICookie } from "./ICookie.js";
|
|
3
|
+
import type { IUploadedFile } from "./IUpload.js";
|
|
3
4
|
export type HTTPMethodType = "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "CONNECT" | "OPTIONS" | "TRACE" | "PATCH" | "WEBSOCKET";
|
|
4
5
|
export interface IRequest {
|
|
5
6
|
url: URL | string;
|
|
@@ -19,7 +20,7 @@ export interface IHttpRequest extends IRequest {
|
|
|
19
20
|
url: URL;
|
|
20
21
|
query: Record<string, unknown>;
|
|
21
22
|
body: RequestBody;
|
|
22
|
-
files: readonly
|
|
23
|
+
files: readonly IUploadedFile[];
|
|
23
24
|
remoteAddress: string | null | undefined;
|
|
24
25
|
acceptHtml: boolean;
|
|
25
26
|
getRemoteAddress(): string | null | undefined;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SchemeType } from "./IContext.js";
|
|
2
2
|
import type { HTTPMethodType } from "./IRequest.js";
|
|
3
|
+
import type { IRpcOutcome } from "./IRpcCall.js";
|
|
3
4
|
/**
|
|
4
5
|
* Nature de l'opération tracée : une requête HTTP, ou le handshake d'une
|
|
5
6
|
* connexion WebSocket.
|
|
@@ -53,4 +54,13 @@ export interface IRequestTrace {
|
|
|
53
54
|
* sans exception n'en porte pas ; c'est à l'abonné de classer un statut.
|
|
54
55
|
*/
|
|
55
56
|
readonly error: unknown;
|
|
57
|
+
/**
|
|
58
|
+
* L'appel RPC (gRPC, gRPC-Web, Connect) servi par la requête, `null` hors
|
|
59
|
+
* RPC : système, méthode, code et son nom (ADR-0026 D10 point 5). Un flux
|
|
60
|
+
* est UN message, de l'ouverture au `close` ; rien par message.
|
|
61
|
+
* `statusCode` reste le statut HTTP, vrai sur le fil.
|
|
62
|
+
*
|
|
63
|
+
* @experimental Surface gRPC, ADR-0026 D11.
|
|
64
|
+
*/
|
|
65
|
+
readonly rpc: IRpcOutcome | null;
|
|
56
66
|
}
|