@asenajs/hono-adapter 2.0.0 → 3.1.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/README.md +16 -3
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/HonoAdapter.d.ts +30 -5
- package/dist/lib/HonoAdapter.js +87 -17
- package/dist/lib/HonoAdapter.js.map +1 -1
- package/dist/lib/HonoContextWrapper.d.ts +12 -0
- package/dist/lib/HonoContextWrapper.js +17 -0
- package/dist/lib/HonoContextWrapper.js.map +1 -1
- package/dist/lib/HonoWebsocketAdapter.d.ts +45 -3
- package/dist/lib/HonoWebsocketAdapter.js +81 -7
- package/dist/lib/HonoWebsocketAdapter.js.map +1 -1
- package/dist/lib/errors.d.ts +56 -8
- package/dist/lib/errors.js +70 -8
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/middlewares/CorsMiddleware.d.ts +15 -6
- package/dist/lib/middlewares/CorsMiddleware.js +33 -27
- package/dist/lib/middlewares/CorsMiddleware.js.map +1 -1
- package/dist/lib/middlewares/RateLimiterMiddleware.d.ts +13 -2
- package/dist/lib/middlewares/RateLimiterMiddleware.js +31 -2
- package/dist/lib/middlewares/RateLimiterMiddleware.js.map +1 -1
- package/package.json +11 -8
|
@@ -28,17 +28,72 @@ export class HonoWebsocketAdapter extends AsenaWebsocketAdapter {
|
|
|
28
28
|
return this.activeConnections.get(namespace)?.size || 0;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
31
|
+
* Releases everything this adapter owns: heartbeat timers, connection tracking, and the
|
|
32
|
+
* transport.
|
|
33
|
+
*
|
|
34
|
+
* The transport is the part that matters beyond a single process. `BunLocalTransport` has
|
|
35
|
+
* nothing to let go of, but a remote one holds broker state - `RedisTransport` keeps a
|
|
36
|
+
* subscriber connection with a live channel subscription plus a publisher - and `destroy()`
|
|
37
|
+
* had no call site anywhere in the framework, so every stop leaked both. A test suite doing
|
|
38
|
+
* twenty stop/start cycles, or a pod restarting under a rolling deploy, accumulates them until
|
|
39
|
+
* the broker refuses new clients.
|
|
40
|
+
*
|
|
41
|
+
* This method does **not** close sockets, despite the name it has carried since the first
|
|
42
|
+
* version. Closing them is `server.stop(closeActiveConnections)`'s job, and `HonoAdapter.stop()`
|
|
43
|
+
* has already done it by the time this runs; a second pass from here would only race with Bun.
|
|
44
|
+
* Called standalone, it leaves open connections alone - it just stops pinging them.
|
|
45
|
+
*
|
|
46
|
+
* @param timeoutMs - Ceiling for the transport teardown (default: 5000). A remote transport's
|
|
47
|
+
* `destroy()` is network I/O, and the pod being shut down is often precisely the one that
|
|
48
|
+
* cannot reach the broker - without a bound, one unreachable Redis holds the entire shutdown
|
|
49
|
+
* open indefinitely.
|
|
33
50
|
*/
|
|
34
|
-
async shutdown(
|
|
51
|
+
async shutdown(timeoutMs = 5000) {
|
|
35
52
|
this.logger.info('Starting WebSocket graceful shutdown...');
|
|
36
53
|
// Stop all heartbeats
|
|
37
54
|
this.clearAllHeartbeats();
|
|
38
55
|
// Clear connection tracking
|
|
39
56
|
this.activeConnections.clear();
|
|
57
|
+
await this.destroyTransport(timeoutMs);
|
|
40
58
|
this.logger.info('WebSocket shutdown complete');
|
|
41
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Runs the transport's `destroy()` under a ceiling, containing every failure.
|
|
62
|
+
*
|
|
63
|
+
* A transport that throws or hangs must not abort the rest of the shutdown - the same policy
|
|
64
|
+
* the core applies to `@OnStop` hooks, and for the same reason: a shutdown that gives up
|
|
65
|
+
* halfway leaves more behind than one that limps to the end. The timer is cleared on the happy
|
|
66
|
+
* path too, so a fast teardown is not held up by a pending timeout.
|
|
67
|
+
*
|
|
68
|
+
* The transport reference is kept rather than cleared. Dropping it would silently downgrade a
|
|
69
|
+
* restarted server to `BunLocalTransport`, and every remote `destroy()` in the ecosystem is
|
|
70
|
+
* already idempotent.
|
|
71
|
+
*
|
|
72
|
+
* @param timeoutMs - Ceiling in milliseconds
|
|
73
|
+
*/
|
|
74
|
+
async destroyTransport(timeoutMs) {
|
|
75
|
+
const transport = this._transport;
|
|
76
|
+
if (typeof transport?.destroy !== 'function') {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
let timer;
|
|
80
|
+
try {
|
|
81
|
+
await Promise.race([
|
|
82
|
+
transport.destroy(),
|
|
83
|
+
new Promise((_resolve, reject) => {
|
|
84
|
+
timer = setTimeout(() => reject(new Error(`WebSocket transport destroy() did not finish within ${timeoutMs}ms`)), timeoutMs);
|
|
85
|
+
}),
|
|
86
|
+
]);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
this.logger.error('WebSocket transport teardown failed, continuing shutdown:', error);
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
if (timer) {
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
42
97
|
registerWebSocket(webSocketService) {
|
|
43
98
|
if (!webSocketService) {
|
|
44
99
|
throw new Error('WebSocket service is required');
|
|
@@ -72,16 +127,35 @@ export class HonoWebsocketAdapter extends AsenaWebsocketAdapter {
|
|
|
72
127
|
if (!this.websockets || this.websockets.size < 1) {
|
|
73
128
|
return;
|
|
74
129
|
}
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
await
|
|
130
|
+
// Assigned to the field, not a local: sockets and destroyTransport() read `this._transport`.
|
|
131
|
+
this._transport ??= new BunLocalTransport();
|
|
132
|
+
await this._transport.init?.(server);
|
|
133
|
+
this.warnIfTransportLacksPublishRemote();
|
|
78
134
|
// Create a single shared wrapper for all WebSocket services
|
|
79
|
-
const sharedServer = new AsenaWebSocketServer(
|
|
135
|
+
const sharedServer = new AsenaWebSocketServer(this._transport);
|
|
80
136
|
// Assign the shared wrapper to all services
|
|
81
137
|
for (const websocket of this.websockets.values()) {
|
|
82
138
|
websocket.server = sharedServer;
|
|
83
139
|
}
|
|
84
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Warns once, at startup, when the configured transport predates `publishRemote()`.
|
|
143
|
+
*
|
|
144
|
+
* Such a transport still works: `socket.publish()` falls back to its `publish()`, which does
|
|
145
|
+
* local delivery as well - so the sender receives its own message, unlike every other
|
|
146
|
+
* configuration. Warning here rather than per message keeps a hot path clean, and warning at
|
|
147
|
+
* all is the point: the alternative reading, treating the missing method as "no cross-pod
|
|
148
|
+
* delivery wanted", would drop messages between pods in silence.
|
|
149
|
+
*/
|
|
150
|
+
warnIfTransportLacksPublishRemote() {
|
|
151
|
+
if (typeof this._transport?.publishRemote === 'function') {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
this.logger.warn('WebSocket transport does not implement publishRemote(). socket.publish() will fall back to ' +
|
|
155
|
+
'transport.publish(), which delivers to the publishing socket as well. Implement ' +
|
|
156
|
+
'publishRemote() (broker publish only, no local delivery) to keep the sender excluded. ' +
|
|
157
|
+
'The fallback is removed in the next major version.');
|
|
158
|
+
}
|
|
85
159
|
prepareWebSocket(options) {
|
|
86
160
|
if (this.websockets?.size < 1) {
|
|
87
161
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HonoWebsocketAdapter.js","sourceRoot":"","sources":["../../lib/HonoWebsocketAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAG/D,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,iBAAiB,GAIlB,MAAM,2BAA2B,CAAC;AAGnC,MAAM,OAAO,oBAAqB,SAAQ,qBAAqB;IACtD,IAAI,GAAG,sBAAsB,CAAC;IAE7B,iBAAiB,GAA6B,IAAI,GAAG,EAAE,CAAC,CAAC,qCAAqC;IAE9F,gBAAgB,GAAwB,IAAI,GAAG,EAAE,CAAC,CAAC,+BAA+B;IAE1F,YAAmB,MAAoB;QACrC,KAAK,CAAC,MAAM,CAAC,CAAC;IAChB,CAAC;IAED;;;;OAIG;IACI,kBAAkB,CAAC,SAAiB,EAAE,KAAa;QACxD,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACzD,CAAC;QAED,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,uCAAuC,SAAS,MAAM,KAAK,EAAE,CAAC,CAAC;IAClF,CAAC;IAED;;;;OAIG;IACI,kBAAkB,CAAC,SAAiB;QACzC,OAAO,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC;IAC1D,CAAC;IAED
|
|
1
|
+
{"version":3,"file":"HonoWebsocketAdapter.js","sourceRoot":"","sources":["../../lib/HonoWebsocketAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAG/D,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,iBAAiB,GAIlB,MAAM,2BAA2B,CAAC;AAGnC,MAAM,OAAO,oBAAqB,SAAQ,qBAAqB;IACtD,IAAI,GAAG,sBAAsB,CAAC;IAE7B,iBAAiB,GAA6B,IAAI,GAAG,EAAE,CAAC,CAAC,qCAAqC;IAE9F,gBAAgB,GAAwB,IAAI,GAAG,EAAE,CAAC,CAAC,+BAA+B;IAE1F,YAAmB,MAAoB;QACrC,KAAK,CAAC,MAAM,CAAC,CAAC;IAChB,CAAC;IAED;;;;OAIG;IACI,kBAAkB,CAAC,SAAiB,EAAE,KAAa;QACxD,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACzD,CAAC;QAED,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,uCAAuC,SAAS,MAAM,KAAK,EAAE,CAAC,CAAC;IAClF,CAAC;IAED;;;;OAIG;IACI,kBAAkB,CAAC,SAAiB;QACzC,OAAO,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC;IAC1D,CAAC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACI,KAAK,CAAC,QAAQ,CAAC,SAAS,GAAG,IAAI;QACpC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;QAE5D,sBAAsB;QACtB,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAE1B,4BAA4B;QAC5B,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC;QAE/B,MAAM,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;QAEvC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;IAClD,CAAC;IAED;;;;;;;;;;;;;OAaG;IACK,KAAK,CAAC,gBAAgB,CAAC,SAAiB;QAC9C,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC;QAElC,IAAI,OAAO,SAAS,EAAE,OAAO,KAAK,UAAU,EAAE,CAAC;YAC7C,OAAO;QACT,CAAC;QAED,IAAI,KAAwB,CAAC;QAE7B,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,IAAI,CAAC;gBACjB,SAAS,CAAC,OAAO,EAAE;gBACnB,IAAI,OAAO,CAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE;oBACtC,KAAK,GAAG,UAAU,CAChB,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,uDAAuD,SAAS,IAAI,CAAC,CAAC,EAC7F,SAAS,CACV,CAAC;gBACJ,CAAC,CAAC;aACH,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,2DAA2D,EAAE,KAAK,CAAC,CAAC;QACxF,CAAC;gBAAS,CAAC;YACT,IAAI,KAAK,EAAE,CAAC;gBACV,YAAY,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;IACH,CAAC;IAEM,iBAAiB,CAAC,gBAA4C;QACnE,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;QACnD,CAAC;QAED,MAAM,YAAY,GAAG,gBAAgB,CAAC,SAAS,CAAC;QAEhD,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACrD,CAAC;QAED,kEAAkE;QAClE,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,IAAI,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;QAEnH,0EAA0E;QAC1E,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CACb,wCAAwC,SAAS,iFAAiF,CACnI,CAAC;QACJ,CAAC;QAED,sCAAsC;QACtC,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YAClC,IAAI,CAAC,UAAU,GAAG,IAAI,GAAG,EAAsC,CAAC;QAClE,CAAC;QAED,mCAAmC;QACnC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YACnC,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,wBAAwB,SAAS,+DAA+D,CACjG,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;IACnD,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,cAAc,CAAC,MAA6B;QACvD,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YACjD,OAAO;QACT,CAAC;QAED,6FAA6F;QAC7F,IAAI,CAAC,UAAU,KAAK,IAAI,iBAAiB,EAAE,CAAC;QAE5C,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC;QAErC,IAAI,CAAC,iCAAiC,EAAE,CAAC;QAEzC,4DAA4D;QAC5D,MAAM,YAAY,GAAG,IAAI,oBAAoB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAE/D,4CAA4C;QAC5C,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;YACjD,SAAS,CAAC,MAAM,GAAG,YAAY,CAAC;QAClC,CAAC;IACH,CAAC;IAED;;;;;;;;OAQG;IACK,iCAAiC;QACvC,IAAI,OAAO,IAAI,CAAC,UAAU,EAAE,aAAa,KAAK,UAAU,EAAE,CAAC;YACzD,OAAO;QACT,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,6FAA6F;YAC3F,kFAAkF;YAClF,wFAAwF;YACxF,oDAAoD,CACvD,CAAC;IACJ,CAAC;IAEM,gBAAgB,CAAC,OAAmB;QACzC,IAAI,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC;YAC9B,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GAAG,OAAO,EAAE,gBAAgB,IAAI,SAAS,CAAC;QACxD,6CAA6C;QAC7C,MAAM,iBAAiB,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC;QAE1F,uEAAuE;QACvE,MAAM,EACJ,SAAS,EAAE,UAAU,EACrB,gBAAgB,EAAE,SAAS,EAC3B,iBAAiB,EAAE,WAAW,EAC9B,GAAG,WAAW,EACf,GAAG,OAAO,IAAK,EAAU,CAAC;QAE3B,IAAI,CAAC,SAAS,GAAG;YACf,+BAA+B;YAC/B,uEAAuE;YACvE,qDAAqD;YACrD,mDAAmD;YACnD,SAAS,EAAE,QAAQ,KAAK,QAAQ;YAEhC,IAAI,EAAE,CAAC,EAAkC,EAAE,EAAE;gBAC3C,MAAM,SAAS,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;gBAE/B,yBAAyB;gBACzB,MAAM,KAAK,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACnD,MAAM,YAAY,GAAG,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;gBAExD,IAAI,KAAK,IAAI,YAAY,IAAI,KAAK,EAAE,CAAC;oBACnC,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,2CAA2C,SAAS,MAAM,YAAY,IAAI,KAAK,6BAA6B,CAC7G,CAAC;oBAEF,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,0BAA0B,CAAC,CAAC;oBAE3C,OAAO;gBACT,CAAC;gBAED,mBAAmB;gBACnB,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;oBAC3C,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;gBACnD,CAAC;gBAED,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBAEtD,qDAAqD;gBACrD,IAAI,iBAAiB,EAAE,CAAC;oBACtB,IAAI,CAAC,cAAc,CAAC,EAAE,EAAE,iBAAiB,CAAC,CAAC;gBAC7C,CAAC;gBAED,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,qBAAqB,EAAE,CAAC,IAAI,CAAC,EAAE,kBAAkB,SAAS,MAAM,YAAY,GAAG,CAAC,sBAAsB,CACvG,CAAC;gBAEF,oBAAoB;gBACpB,IAAI,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC;YAC3C,CAAC;YAED,KAAK,EAAE,CAAC,EAAkC,EAAE,IAAY,EAAE,MAAc,EAAE,EAAE;gBAC1E,MAAM,SAAS,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;gBAE/B,iBAAiB;gBACjB,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBAE/B,uBAAuB;gBACvB,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBAE1D,MAAM,cAAc,GAAG,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;gBAE1D,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,qBAAqB,EAAE,CAAC,IAAI,CAAC,EAAE,kBAAkB,SAAS,MAAM,cAAc,yBAAyB,CACxG,CAAC;gBAEF,oBAAoB;gBACpB,IAAI,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;YAC1D,CAAC;YAED,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC;YACxC,KAAK,EAAE,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC;YACpC,IAAI,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC;YAClC,IAAI,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC;YAClC,GAAG,WAAW;SACf,CAAC;IACJ,CAAC;IAEO,aAAa,CAAC,IAAoB;QACxC,OAAO,KAAK,EAAE,EAAkC,EAAE,GAAG,IAAW,EAAE,EAAE;YAClE,MAAM,qBAAqB,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAEhE,IAAI,CAAC,qBAAqB,EAAE,CAAC;gBAC3B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,yCAAyC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;gBAC3E,wDAAwD;gBACxD,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,UAAU,EAAE,CAAC;oBAC/E,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,0CAA0C,CAAC,CAAC;gBAC7D,CAAC;gBAED,OAAO;YACT,CAAC;YAED,mFAAmF;YACnF,wFAAwF;YACxF,4BAA4B;YAC5B,MAAM,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,qBAAqB,CAAC,CAAC;YAEzE,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,yDAAyD;gBACzD,OAAO;YACT,CAAC;YAED,IAAI,CAAC;gBACH,MAAO,OAAwF,CAC7F,IAAI,WAAW,CAAC,EAAE,EAAE,qBAAqB,CAAC,SAAS,EAAE,IAAI,CAAC,UAAU,CAAC,EACrE,GAAG,IAAI,CACR,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,IAAI,2BAA2B,EAAE,CAAC,IAAI,CAAC,IAAI,GAAG,EAAE;oBAC7E,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;oBAC7D,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;oBACvD,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE;oBACpB,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI;iBACnB,CAAC,CAAC;gBAEH,0DAA0D;gBAC1D,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;oBACrC,IAAI,CAAC;wBACH,EAAE,CAAC,IAAI,CACL,IAAI,CAAC,SAAS,CAAC;4BACb,IAAI,EAAE,OAAO;4BACb,OAAO,EAAE,uBAAuB;4BAChC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;yBACpC,CAAC,CACH,CAAC;oBACJ,CAAC;oBAAC,OAAO,SAAS,EAAE,CAAC;wBACnB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,yCAAyC,EAAE,SAAS,CAAC,CAAC;oBAC1E,CAAC;gBACH,CAAC;gBAED,mDAAmD;gBACnD,IAAI,IAAI,KAAK,gBAAgB,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;oBACtD,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;wBACrC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;oBAClC,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC,CAAC;IACJ,CAAC;CACF"}
|
package/dist/lib/errors.d.ts
CHANGED
|
@@ -6,19 +6,25 @@ import { type ZodError } from 'zod';
|
|
|
6
6
|
* application's `ConfigService.onError` like every other error instead of being
|
|
7
7
|
* answered inside the validator middleware.
|
|
8
8
|
*
|
|
9
|
-
* Extends Hono's `HTTPException` with status 400 deliberately:
|
|
10
|
-
*
|
|
11
|
-
* keeps answering 400, so adopting this does not silently
|
|
12
|
-
* into 500s.
|
|
9
|
+
* Extends Hono's `HTTPException` with status 400 deliberately: a handler that matches
|
|
10
|
+
* with `isHttpException()` - or with the older `error instanceof HTTPException` - and
|
|
11
|
+
* replies with `error.status` keeps answering 400, so adopting this does not silently
|
|
12
|
+
* turn validation failures into 500s.
|
|
13
|
+
*
|
|
14
|
+
* Note it does *not* extend `HttpException` from `@asenajs/asena/adapter`, the class
|
|
15
|
+
* applications throw. Both are branded, so `isHttpException()` cannot tell them apart and
|
|
16
|
+
* does not need to; `instanceof` can, which is one more reason not to reach for it.
|
|
13
17
|
*
|
|
14
18
|
* @example
|
|
15
19
|
* ```typescript
|
|
16
20
|
* public onError(error: Error, context: Context) {
|
|
21
|
+
* // Before isHttpException: ValidationError is an HTTP exception too, so the
|
|
22
|
+
* // generic branch below would otherwise swallow it.
|
|
17
23
|
* if (isValidationError(error)) {
|
|
18
24
|
* return context.send({ success: false, errors: error.issues }, 400);
|
|
19
25
|
* }
|
|
20
26
|
*
|
|
21
|
-
* if (error
|
|
27
|
+
* if (isHttpException(error)) {
|
|
22
28
|
* return context.send({ error: error.message }, error.status);
|
|
23
29
|
* }
|
|
24
30
|
*
|
|
@@ -62,10 +68,52 @@ export declare class ValidationError extends HTTPException implements Validation
|
|
|
62
68
|
*
|
|
63
69
|
* Without this the brand is a trap rather than a feature: the JSDoc on `isHttpException` tells
|
|
64
70
|
* users to write `if (isHttpException(error)) ... else 500`, and on this adapter every
|
|
65
|
-
* deliberate 401/403/404/429
|
|
66
|
-
*
|
|
67
|
-
*
|
|
71
|
+
* deliberate 401/403/404/429 raised by `hono/basic-auth`, `hono/jwt` or hono's own validator
|
|
72
|
+
* would take the else branch and answer 500.
|
|
73
|
+
*
|
|
74
|
+
* The brand covers one copy of `hono` and cannot reach another: it is installed on the prototype
|
|
75
|
+
* of the class *this* package resolved. Since 3.0.0 `hono` is a **peer** dependency, so this
|
|
76
|
+
* package has no resolution slot of its own and an application normally resolves exactly one copy
|
|
77
|
+
* - which is what makes the brand sufficient rather than a trap. Before that it was a regular
|
|
78
|
+
* dependency, and a downstream application that bumped its own `hono` range ended up with a second
|
|
79
|
+
* nested copy whose `HTTPException` was unbranded: every deliberate 400/403 answered 500 while the
|
|
80
|
+
* API kept responding. `warnOnNestedHono` below reports the one shape that can still produce it.
|
|
81
|
+
*
|
|
82
|
+
* Import `HTTPException` from `@asenajs/hono-adapter` rather than from `hono/http-exception` to
|
|
83
|
+
* stay on this package's copy regardless. The adapter's default response also checks
|
|
84
|
+
* `instanceof HTTPException` before the brand, so a hono exception is answered correctly even if
|
|
85
|
+
* this function never ran.
|
|
68
86
|
*
|
|
69
87
|
* Idempotent, and called from the HonoAdapter constructor.
|
|
70
88
|
*/
|
|
71
89
|
export declare const brandHonoHttpException: () => void;
|
|
90
|
+
/**
|
|
91
|
+
* Warns when this package resolved a *nested* copy of `hono`.
|
|
92
|
+
*
|
|
93
|
+
* `hono` is a peer dependency, so it should always resolve from the application's own
|
|
94
|
+
* `node_modules`. A path under `@asenajs/hono-adapter/node_modules/hono` means a second copy was
|
|
95
|
+
* installed privately for this package - the exact topology that makes `brandHonoHttpException()`
|
|
96
|
+
* useless, because the brand lands on the prototype of *this* copy's class and an `HTTPException`
|
|
97
|
+
* the application threw from its own copy is neither `instanceof` it nor branded, so it is
|
|
98
|
+
* answered 500.
|
|
99
|
+
*
|
|
100
|
+
* This does not - and cannot - detect the *application's* copy. `import.meta.resolve` always
|
|
101
|
+
* answers relative to this module, Node resolution is per-importer so there is no single "the
|
|
102
|
+
* application's hono" to compare against, and after `asena build` or `bun build --compile` there
|
|
103
|
+
* is no `node_modules` to inspect at all. What it can prove is the asymmetric half: if *our* copy
|
|
104
|
+
* is nested, there are two. That is enough, because under a peer dependency a nested copy is the
|
|
105
|
+
* only way this package acquires one.
|
|
106
|
+
*
|
|
107
|
+
* Worth having even though the peer move makes the topology rare: `bun install` has been observed
|
|
108
|
+
* to drop the lockfile entry for a nested copy while leaving the directory on disk, so an
|
|
109
|
+
* application upgrading in place can still be broken by a leftover it cannot see.
|
|
110
|
+
*
|
|
111
|
+
* The path predicate is {@link isNestedHonoPath}, exported so it can be tested against both
|
|
112
|
+
* layouts directly - inside this repo `import.meta.resolve` always answers
|
|
113
|
+
* `hono-adapter/node_modules/hono`, which is the package's own root and deliberately does *not*
|
|
114
|
+
* match, so a test driving the real resolver could only ever assert the negative case.
|
|
115
|
+
*/
|
|
116
|
+
export declare const isNestedHonoPath: (resolved: string) => boolean;
|
|
117
|
+
export declare const warnOnNestedHono: (logger?: {
|
|
118
|
+
warn: (message: string) => void;
|
|
119
|
+
}) => void;
|
package/dist/lib/errors.js
CHANGED
|
@@ -6,19 +6,25 @@ import { flattenError } from 'zod';
|
|
|
6
6
|
* application's `ConfigService.onError` like every other error instead of being
|
|
7
7
|
* answered inside the validator middleware.
|
|
8
8
|
*
|
|
9
|
-
* Extends Hono's `HTTPException` with status 400 deliberately:
|
|
10
|
-
*
|
|
11
|
-
* keeps answering 400, so adopting this does not silently
|
|
12
|
-
* into 500s.
|
|
9
|
+
* Extends Hono's `HTTPException` with status 400 deliberately: a handler that matches
|
|
10
|
+
* with `isHttpException()` - or with the older `error instanceof HTTPException` - and
|
|
11
|
+
* replies with `error.status` keeps answering 400, so adopting this does not silently
|
|
12
|
+
* turn validation failures into 500s.
|
|
13
|
+
*
|
|
14
|
+
* Note it does *not* extend `HttpException` from `@asenajs/asena/adapter`, the class
|
|
15
|
+
* applications throw. Both are branded, so `isHttpException()` cannot tell them apart and
|
|
16
|
+
* does not need to; `instanceof` can, which is one more reason not to reach for it.
|
|
13
17
|
*
|
|
14
18
|
* @example
|
|
15
19
|
* ```typescript
|
|
16
20
|
* public onError(error: Error, context: Context) {
|
|
21
|
+
* // Before isHttpException: ValidationError is an HTTP exception too, so the
|
|
22
|
+
* // generic branch below would otherwise swallow it.
|
|
17
23
|
* if (isValidationError(error)) {
|
|
18
24
|
* return context.send({ success: false, errors: error.issues }, 400);
|
|
19
25
|
* }
|
|
20
26
|
*
|
|
21
|
-
* if (error
|
|
27
|
+
* if (isHttpException(error)) {
|
|
22
28
|
* return context.send({ error: error.message }, error.status);
|
|
23
29
|
* }
|
|
24
30
|
*
|
|
@@ -81,9 +87,21 @@ export class ValidationError extends HTTPException {
|
|
|
81
87
|
*
|
|
82
88
|
* Without this the brand is a trap rather than a feature: the JSDoc on `isHttpException` tells
|
|
83
89
|
* users to write `if (isHttpException(error)) ... else 500`, and on this adapter every
|
|
84
|
-
* deliberate 401/403/404/429
|
|
85
|
-
*
|
|
86
|
-
*
|
|
90
|
+
* deliberate 401/403/404/429 raised by `hono/basic-auth`, `hono/jwt` or hono's own validator
|
|
91
|
+
* would take the else branch and answer 500.
|
|
92
|
+
*
|
|
93
|
+
* The brand covers one copy of `hono` and cannot reach another: it is installed on the prototype
|
|
94
|
+
* of the class *this* package resolved. Since 3.0.0 `hono` is a **peer** dependency, so this
|
|
95
|
+
* package has no resolution slot of its own and an application normally resolves exactly one copy
|
|
96
|
+
* - which is what makes the brand sufficient rather than a trap. Before that it was a regular
|
|
97
|
+
* dependency, and a downstream application that bumped its own `hono` range ended up with a second
|
|
98
|
+
* nested copy whose `HTTPException` was unbranded: every deliberate 400/403 answered 500 while the
|
|
99
|
+
* API kept responding. `warnOnNestedHono` below reports the one shape that can still produce it.
|
|
100
|
+
*
|
|
101
|
+
* Import `HTTPException` from `@asenajs/hono-adapter` rather than from `hono/http-exception` to
|
|
102
|
+
* stay on this package's copy regardless. The adapter's default response also checks
|
|
103
|
+
* `instanceof HTTPException` before the brand, so a hono exception is answered correctly even if
|
|
104
|
+
* this function never ran.
|
|
87
105
|
*
|
|
88
106
|
* Idempotent, and called from the HonoAdapter constructor.
|
|
89
107
|
*/
|
|
@@ -97,4 +115,48 @@ export const brandHonoHttpException = () => {
|
|
|
97
115
|
configurable: true,
|
|
98
116
|
});
|
|
99
117
|
};
|
|
118
|
+
/**
|
|
119
|
+
* Warns when this package resolved a *nested* copy of `hono`.
|
|
120
|
+
*
|
|
121
|
+
* `hono` is a peer dependency, so it should always resolve from the application's own
|
|
122
|
+
* `node_modules`. A path under `@asenajs/hono-adapter/node_modules/hono` means a second copy was
|
|
123
|
+
* installed privately for this package - the exact topology that makes `brandHonoHttpException()`
|
|
124
|
+
* useless, because the brand lands on the prototype of *this* copy's class and an `HTTPException`
|
|
125
|
+
* the application threw from its own copy is neither `instanceof` it nor branded, so it is
|
|
126
|
+
* answered 500.
|
|
127
|
+
*
|
|
128
|
+
* This does not - and cannot - detect the *application's* copy. `import.meta.resolve` always
|
|
129
|
+
* answers relative to this module, Node resolution is per-importer so there is no single "the
|
|
130
|
+
* application's hono" to compare against, and after `asena build` or `bun build --compile` there
|
|
131
|
+
* is no `node_modules` to inspect at all. What it can prove is the asymmetric half: if *our* copy
|
|
132
|
+
* is nested, there are two. That is enough, because under a peer dependency a nested copy is the
|
|
133
|
+
* only way this package acquires one.
|
|
134
|
+
*
|
|
135
|
+
* Worth having even though the peer move makes the topology rare: `bun install` has been observed
|
|
136
|
+
* to drop the lockfile entry for a nested copy while leaving the directory on disk, so an
|
|
137
|
+
* application upgrading in place can still be broken by a leftover it cannot see.
|
|
138
|
+
*
|
|
139
|
+
* The path predicate is {@link isNestedHonoPath}, exported so it can be tested against both
|
|
140
|
+
* layouts directly - inside this repo `import.meta.resolve` always answers
|
|
141
|
+
* `hono-adapter/node_modules/hono`, which is the package's own root and deliberately does *not*
|
|
142
|
+
* match, so a test driving the real resolver could only ever assert the negative case.
|
|
143
|
+
*/
|
|
144
|
+
export const isNestedHonoPath = (resolved) => resolved.includes('@asenajs/hono-adapter/node_modules/hono');
|
|
145
|
+
export const warnOnNestedHono = (logger) => {
|
|
146
|
+
try {
|
|
147
|
+
const resolved = import.meta.resolve('hono/http-exception');
|
|
148
|
+
if (!isNestedHonoPath(resolved)) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
(logger ?? console).warn(`A nested copy of \`hono\` was found at ${resolved}.\n` +
|
|
152
|
+
` \`hono\` is a peer dependency of @asenajs/hono-adapter and must resolve from your\n` +
|
|
153
|
+
` application's node_modules. With two copies, an HTTPException thrown from your copy is\n` +
|
|
154
|
+
` not recognised by isHttpException() and will be answered as 500.\n` +
|
|
155
|
+
` Fix: rm -rf node_modules bun.lock && bun install`);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// `import.meta.resolve` is unavailable or throws under some bundlers and in compiled binaries.
|
|
159
|
+
// A diagnostic must never be the reason a server fails to boot.
|
|
160
|
+
}
|
|
161
|
+
};
|
|
100
162
|
//# sourceMappingURL=errors.js.map
|
package/dist/lib/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../lib/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EACL,cAAc,EAEd,gBAAgB,GAGjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,YAAY,EAAiB,MAAM,KAAK,CAAC;AAElD
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../lib/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -86,9 +86,10 @@ export interface CorsOptions {
|
|
|
86
86
|
*
|
|
87
87
|
* **CORS Flow:**
|
|
88
88
|
* 1. Check if request has Origin header (if not, skip CORS)
|
|
89
|
-
* 2.
|
|
90
|
-
* 3.
|
|
91
|
-
* 4.
|
|
89
|
+
* 2. Advertise that the response varies by Origin (unless the config is the literal '*')
|
|
90
|
+
* 3. Validate origin - when it is not allowed, emit no CORS headers and carry on
|
|
91
|
+
* 4. Handle preflight OPTIONS request → return 204 immediately
|
|
92
|
+
* 5. For other requests → set CORS headers and call next()
|
|
92
93
|
*/
|
|
93
94
|
export declare class CorsMiddleware extends MiddlewareService {
|
|
94
95
|
private readonly origin;
|
|
@@ -123,9 +124,17 @@ export declare class CorsMiddleware extends MiddlewareService {
|
|
|
123
124
|
*
|
|
124
125
|
* Execution flow:
|
|
125
126
|
* 1. Check if Origin header present (if not, skip CORS)
|
|
126
|
-
* 2.
|
|
127
|
-
* 3.
|
|
128
|
-
* 4.
|
|
127
|
+
* 2. Advertise that the response varies by Origin (unless the config is the literal '*')
|
|
128
|
+
* 3. Validate origin - when it is not allowed, emit no CORS headers and carry on
|
|
129
|
+
* 4. Handle preflight OPTIONS → return 204 No Content
|
|
130
|
+
* 5. Set CORS headers for actual request → call next()
|
|
131
|
+
*
|
|
132
|
+
* A disallowed origin is *not* answered with 403. CORS is a policy the browser enforces on
|
|
133
|
+
* behalf of the user, not an access-control decision the server makes: the correct denial is a
|
|
134
|
+
* normal response with no `Access-Control-Allow-Origin`, which the browser then refuses to
|
|
135
|
+
* expose. Rejecting outright also turned away every non-browser caller that happens to send an
|
|
136
|
+
* Origin header - server-to-server clients, proxies, webviews - and forced applications to
|
|
137
|
+
* register this middleware conditionally when CORS was already terminated at the ingress.
|
|
129
138
|
*
|
|
130
139
|
* @param context - Hono context wrapper
|
|
131
140
|
* @param next - Function to call next middleware or handler
|
|
@@ -41,9 +41,10 @@ import { MiddlewareService } from '../defaults';
|
|
|
41
41
|
*
|
|
42
42
|
* **CORS Flow:**
|
|
43
43
|
* 1. Check if request has Origin header (if not, skip CORS)
|
|
44
|
-
* 2.
|
|
45
|
-
* 3.
|
|
46
|
-
* 4.
|
|
44
|
+
* 2. Advertise that the response varies by Origin (unless the config is the literal '*')
|
|
45
|
+
* 3. Validate origin - when it is not allowed, emit no CORS headers and carry on
|
|
46
|
+
* 4. Handle preflight OPTIONS request → return 204 immediately
|
|
47
|
+
* 5. For other requests → set CORS headers and call next()
|
|
47
48
|
*/
|
|
48
49
|
export class CorsMiddleware extends MiddlewareService {
|
|
49
50
|
origin;
|
|
@@ -87,9 +88,17 @@ export class CorsMiddleware extends MiddlewareService {
|
|
|
87
88
|
*
|
|
88
89
|
* Execution flow:
|
|
89
90
|
* 1. Check if Origin header present (if not, skip CORS)
|
|
90
|
-
* 2.
|
|
91
|
-
* 3.
|
|
92
|
-
* 4.
|
|
91
|
+
* 2. Advertise that the response varies by Origin (unless the config is the literal '*')
|
|
92
|
+
* 3. Validate origin - when it is not allowed, emit no CORS headers and carry on
|
|
93
|
+
* 4. Handle preflight OPTIONS → return 204 No Content
|
|
94
|
+
* 5. Set CORS headers for actual request → call next()
|
|
95
|
+
*
|
|
96
|
+
* A disallowed origin is *not* answered with 403. CORS is a policy the browser enforces on
|
|
97
|
+
* behalf of the user, not an access-control decision the server makes: the correct denial is a
|
|
98
|
+
* normal response with no `Access-Control-Allow-Origin`, which the browser then refuses to
|
|
99
|
+
* expose. Rejecting outright also turned away every non-browser caller that happens to send an
|
|
100
|
+
* Origin header - server-to-server clients, proxies, webviews - and forced applications to
|
|
101
|
+
* register this middleware conditionally when CORS was already terminated at the ingress.
|
|
93
102
|
*
|
|
94
103
|
* @param context - Hono context wrapper
|
|
95
104
|
* @param next - Function to call next middleware or handler
|
|
@@ -103,32 +112,29 @@ export class CorsMiddleware extends MiddlewareService {
|
|
|
103
112
|
}
|
|
104
113
|
// Validate origin
|
|
105
114
|
const allowedOrigin = this.getAllowedOrigin(origin);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// Set CORS headers via AsenaContext abstraction
|
|
111
|
-
context.setResponseHeader('Access-Control-Allow-Origin', allowedOrigin);
|
|
112
|
-
if (this.credentials) {
|
|
113
|
-
context.setResponseHeader('Access-Control-Allow-Credentials', 'true');
|
|
115
|
+
// Non-'*' configs make the response depend on the Origin, refusals included; a shared cache
|
|
116
|
+
// that does not key on it hands one origin's response to another.
|
|
117
|
+
if (this.origin !== '*') {
|
|
118
|
+
context.setResponseHeader('Vary', 'Origin');
|
|
114
119
|
}
|
|
115
|
-
if (
|
|
116
|
-
context.setResponseHeader('Access-Control-
|
|
120
|
+
if (allowedOrigin) {
|
|
121
|
+
context.setResponseHeader('Access-Control-Allow-Origin', allowedOrigin);
|
|
122
|
+
if (this.credentials) {
|
|
123
|
+
context.setResponseHeader('Access-Control-Allow-Credentials', 'true');
|
|
124
|
+
}
|
|
125
|
+
if (this.exposedHeaders) {
|
|
126
|
+
context.setResponseHeader('Access-Control-Expose-Headers', this.exposedHeaders);
|
|
127
|
+
}
|
|
117
128
|
}
|
|
118
129
|
// Handle preflight OPTIONS request
|
|
119
130
|
if (context.req.method === 'OPTIONS') {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
'Access-Control-Allow-
|
|
123
|
-
'Access-Control-
|
|
124
|
-
'Access-Control-Allow-Headers': this.allowedHeaders,
|
|
125
|
-
'Access-Control-Max-Age': this.maxAge,
|
|
126
|
-
};
|
|
127
|
-
if (this.credentials) {
|
|
128
|
-
headers['Access-Control-Allow-Credentials'] = 'true';
|
|
131
|
+
if (allowedOrigin) {
|
|
132
|
+
context.setResponseHeader('Access-Control-Allow-Methods', this.methods);
|
|
133
|
+
context.setResponseHeader('Access-Control-Allow-Headers', this.allowedHeaders);
|
|
134
|
+
context.setResponseHeader('Access-Control-Max-Age', this.maxAge);
|
|
129
135
|
}
|
|
130
|
-
//
|
|
131
|
-
return new Response(null, { status: 204, headers });
|
|
136
|
+
// Accumulated headers, not a fresh object: earlier middlewares' headers survive the 204.
|
|
137
|
+
return new Response(null, { status: 204, headers: context.res.headers });
|
|
132
138
|
}
|
|
133
139
|
// For actual requests, continue to handler
|
|
134
140
|
return await next();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CorsMiddleware.js","sourceRoot":"","sources":["../../../lib/middlewares/CorsMiddleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAgB,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAqD9D
|
|
1
|
+
{"version":3,"file":"CorsMiddleware.js","sourceRoot":"","sources":["../../../lib/middlewares/CorsMiddleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -294,9 +294,20 @@ export declare class RateLimiterMiddleware extends MiddlewareService {
|
|
|
294
294
|
*/
|
|
295
295
|
handle(context: Context, next: () => Promise<void>): Promise<any>;
|
|
296
296
|
/**
|
|
297
|
-
* Cleanup resources (stop cleanup timer)
|
|
297
|
+
* Cleanup resources (stop cleanup timer, drop bucket state)
|
|
298
298
|
*
|
|
299
|
-
*
|
|
299
|
+
* `@OnStop` hands this to the component lifecycle, so `server.stop()` calls it instead of the
|
|
300
|
+
* application having to remember to. The hook is inherited, so the `@Middleware()` subclass
|
|
301
|
+
* users actually register gets it without repeating the decorator - and it only fires for
|
|
302
|
+
* singletons, which is what a rate limiter has to be to share one bucket map across requests.
|
|
303
|
+
*
|
|
304
|
+
* The timer is `unref()`'d, so it never held the process open. What it does do is survive a
|
|
305
|
+
* stop/start cycle *inside* one process - twenty of them is an ordinary test suite - leaving a
|
|
306
|
+
* timer per stopped server still sweeping a bucket map nobody reads. The buckets go with it:
|
|
307
|
+
* a restarted server must not inherit the rate-limit state of the one before it, or the first
|
|
308
|
+
* requests after a restart get counted against a window that has already closed.
|
|
309
|
+
*
|
|
310
|
+
* Safe to call twice; the second call has nothing left to do.
|
|
300
311
|
*/
|
|
301
312
|
destroy(): void;
|
|
302
313
|
/**
|
|
@@ -46,7 +46,17 @@
|
|
|
46
46
|
* public login() { ... }
|
|
47
47
|
* ```
|
|
48
48
|
*/
|
|
49
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
50
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
51
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
52
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
53
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
54
|
+
};
|
|
55
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
56
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
57
|
+
};
|
|
49
58
|
import { MiddlewareService } from '../defaults';
|
|
59
|
+
import { OnStop } from '@asenajs/asena/decorators/ioc';
|
|
50
60
|
/**
|
|
51
61
|
* Rate Limiter Middleware
|
|
52
62
|
*
|
|
@@ -238,14 +248,27 @@ export class RateLimiterMiddleware extends MiddlewareService {
|
|
|
238
248
|
return await next();
|
|
239
249
|
}
|
|
240
250
|
/**
|
|
241
|
-
* Cleanup resources (stop cleanup timer)
|
|
251
|
+
* Cleanup resources (stop cleanup timer, drop bucket state)
|
|
242
252
|
*
|
|
243
|
-
*
|
|
253
|
+
* `@OnStop` hands this to the component lifecycle, so `server.stop()` calls it instead of the
|
|
254
|
+
* application having to remember to. The hook is inherited, so the `@Middleware()` subclass
|
|
255
|
+
* users actually register gets it without repeating the decorator - and it only fires for
|
|
256
|
+
* singletons, which is what a rate limiter has to be to share one bucket map across requests.
|
|
257
|
+
*
|
|
258
|
+
* The timer is `unref()`'d, so it never held the process open. What it does do is survive a
|
|
259
|
+
* stop/start cycle *inside* one process - twenty of them is an ordinary test suite - leaving a
|
|
260
|
+
* timer per stopped server still sweeping a bucket map nobody reads. The buckets go with it:
|
|
261
|
+
* a restarted server must not inherit the rate-limit state of the one before it, or the first
|
|
262
|
+
* requests after a restart get counted against a window that has already closed.
|
|
263
|
+
*
|
|
264
|
+
* Safe to call twice; the second call has nothing left to do.
|
|
244
265
|
*/
|
|
245
266
|
destroy() {
|
|
246
267
|
if (this.cleanupTimer) {
|
|
247
268
|
clearInterval(this.cleanupTimer);
|
|
269
|
+
this.cleanupTimer = undefined;
|
|
248
270
|
}
|
|
271
|
+
this.buckets.clear();
|
|
249
272
|
}
|
|
250
273
|
/**
|
|
251
274
|
* Get current bucket state for a client (for testing/debugging)
|
|
@@ -300,4 +323,10 @@ export class RateLimiterMiddleware extends MiddlewareService {
|
|
|
300
323
|
this.cleanupTimer.unref();
|
|
301
324
|
}
|
|
302
325
|
}
|
|
326
|
+
__decorate([
|
|
327
|
+
OnStop(),
|
|
328
|
+
__metadata("design:type", Function),
|
|
329
|
+
__metadata("design:paramtypes", []),
|
|
330
|
+
__metadata("design:returntype", void 0)
|
|
331
|
+
], RateLimiterMiddleware.prototype, "destroy", null);
|
|
303
332
|
//# sourceMappingURL=RateLimiterMiddleware.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RateLimiterMiddleware.js","sourceRoot":"","sources":["../../../lib/middlewares/RateLimiterMiddleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG
|
|
1
|
+
{"version":3,"file":"RateLimiterMiddleware.js","sourceRoot":"","sources":["../../../lib/middlewares/RateLimiterMiddleware.ts"],"names":[],"mappings":"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"}
|