exisjs 0.7.6 → 0.7.8
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/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +12 -0
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/clean.d.ts +5 -0
- package/dist/cli/commands/clean.d.ts.map +1 -0
- package/dist/cli/commands/clean.js +30 -0
- package/dist/cli/commands/clean.js.map +1 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +15 -1
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/generate.d.ts +6 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +57 -2
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/index.js +16 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/manifest.d.ts +1 -0
- package/dist/cli/manifest.d.ts.map +1 -1
- package/dist/cli/manifest.js +161 -23
- package/dist/cli/manifest.js.map +1 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +9 -0
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +4 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/cron/expressions.d.ts +45 -0
- package/dist/cron/expressions.d.ts.map +1 -0
- package/dist/cron/expressions.js +87 -0
- package/dist/cron/expressions.js.map +1 -0
- package/dist/cron/index.d.ts +6 -0
- package/dist/cron/index.d.ts.map +1 -0
- package/dist/cron/index.js +22 -0
- package/dist/cron/index.js.map +1 -0
- package/dist/cron/job.d.ts +50 -0
- package/dist/cron/job.d.ts.map +1 -0
- package/dist/cron/job.js +247 -0
- package/dist/cron/job.js.map +1 -0
- package/dist/cron/manager.d.ts +105 -0
- package/dist/cron/manager.d.ts.map +1 -0
- package/dist/cron/manager.js +245 -0
- package/dist/cron/manager.js.map +1 -0
- package/dist/cron/parser.d.ts +22 -0
- package/dist/cron/parser.d.ts.map +1 -0
- package/dist/cron/parser.js +269 -0
- package/dist/cron/parser.js.map +1 -0
- package/dist/cron/types.d.ts +76 -0
- package/dist/cron/types.d.ts.map +1 -0
- package/dist/cron/types.js +3 -0
- package/dist/cron/types.js.map +1 -0
- package/dist/decorators/constants.d.ts +2 -0
- package/dist/decorators/constants.d.ts.map +1 -1
- package/dist/decorators/constants.js +3 -1
- package/dist/decorators/constants.js.map +1 -1
- package/dist/decorators/core.d.ts +29 -11
- package/dist/decorators/core.d.ts.map +1 -1
- package/dist/decorators/core.js +49 -11
- package/dist/decorators/core.js.map +1 -1
- package/dist/decorators/cron.d.ts +57 -0
- package/dist/decorators/cron.d.ts.map +1 -0
- package/dist/decorators/cron.js +143 -0
- package/dist/decorators/cron.js.map +1 -0
- package/dist/decorators/index.d.ts +2 -0
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +2 -0
- package/dist/decorators/index.js.map +1 -1
- package/dist/decorators/params.d.ts +97 -2
- package/dist/decorators/params.d.ts.map +1 -1
- package/dist/decorators/params.js +94 -2
- package/dist/decorators/params.js.map +1 -1
- package/dist/decorators/routing.d.ts +81 -16
- package/dist/decorators/routing.d.ts.map +1 -1
- package/dist/decorators/routing.js +81 -16
- package/dist/decorators/routing.js.map +1 -1
- package/dist/docs/content/cron.mdx +196 -0
- package/dist/docs/content/errors.mdx +58 -9
- package/dist/docs/content/structure.mdx +1 -1
- package/dist/docs/content/swagger.mdx +289 -0
- package/dist/docs/reference/cron.mdx +300 -0
- package/dist/docs/reference/decorators.mdx +8 -0
- package/dist/docs/reference/error.mdx +44 -0
- package/dist/docs/reference/swagger.mdx +226 -0
- package/dist/docs/reference/validator.mdx +1 -1
- package/dist/router/boundary.d.ts +24 -2
- package/dist/router/boundary.d.ts.map +1 -1
- package/dist/router/boundary.js +24 -2
- package/dist/router/boundary.js.map +1 -1
- package/dist/router/controller-registrar.d.ts.map +1 -1
- package/dist/router/controller-registrar.js +52 -3
- package/dist/router/controller-registrar.js.map +1 -1
- package/dist/router/route-builder.d.ts +196 -33
- package/dist/router/route-builder.d.ts.map +1 -1
- package/dist/router/route-builder.js +171 -11
- package/dist/router/route-builder.js.map +1 -1
- package/dist/router/route-scanner.d.ts +6 -0
- package/dist/router/route-scanner.d.ts.map +1 -1
- package/dist/router/route-scanner.js +69 -812
- package/dist/router/route-scanner.js.map +1 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +27 -0
- package/dist/router/router.js.map +1 -1
- package/dist/router/scanner/boundary-loader.d.ts +18 -0
- package/dist/router/scanner/boundary-loader.d.ts.map +1 -0
- package/dist/router/scanner/boundary-loader.js +539 -0
- package/dist/router/scanner/boundary-loader.js.map +1 -0
- package/dist/router/scanner/cron-mounter.d.ts +15 -0
- package/dist/router/scanner/cron-mounter.d.ts.map +1 -0
- package/dist/router/scanner/cron-mounter.js +216 -0
- package/dist/router/scanner/cron-mounter.js.map +1 -0
- package/dist/router/scanner/error-mounter.d.ts +14 -0
- package/dist/router/scanner/error-mounter.d.ts.map +1 -0
- package/dist/router/scanner/error-mounter.js +200 -0
- package/dist/router/scanner/error-mounter.js.map +1 -0
- package/dist/router/scanner/fs-scanner.d.ts +25 -0
- package/dist/router/scanner/fs-scanner.d.ts.map +1 -0
- package/dist/router/scanner/fs-scanner.js +115 -0
- package/dist/router/scanner/fs-scanner.js.map +1 -0
- package/dist/router/scanner/functional-compiler.d.ts +9 -0
- package/dist/router/scanner/functional-compiler.d.ts.map +1 -0
- package/dist/router/scanner/functional-compiler.js +233 -0
- package/dist/router/scanner/functional-compiler.js.map +1 -0
- package/dist/router/scanner/index.d.ts +9 -0
- package/dist/router/scanner/index.d.ts.map +1 -0
- package/dist/router/scanner/index.js +19 -0
- package/dist/router/scanner/index.js.map +1 -0
- package/dist/router/types.d.ts +15 -0
- package/dist/router/types.d.ts.map +1 -1
- package/dist/server/app.d.ts +2 -0
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/app.js +3 -0
- package/dist/server/app.js.map +1 -1
- package/dist/server/helpers/body-parser.d.ts +18 -0
- package/dist/server/helpers/body-parser.d.ts.map +1 -0
- package/dist/server/helpers/body-parser.js +246 -0
- package/dist/server/helpers/body-parser.js.map +1 -0
- package/dist/server/helpers/cookie.d.ts +4 -0
- package/dist/server/helpers/cookie.d.ts.map +1 -0
- package/dist/server/helpers/cookie.js +61 -0
- package/dist/server/helpers/cookie.js.map +1 -0
- package/dist/server/helpers/etag.d.ts +3 -0
- package/dist/server/helpers/etag.d.ts.map +1 -0
- package/dist/server/helpers/etag.js +25 -0
- package/dist/server/helpers/etag.js.map +1 -0
- package/dist/server/helpers/index.d.ts +6 -0
- package/dist/server/helpers/index.d.ts.map +1 -0
- package/dist/server/helpers/index.js +21 -0
- package/dist/server/helpers/index.js.map +1 -0
- package/dist/server/helpers/ip.d.ts +9 -0
- package/dist/server/helpers/ip.d.ts.map +1 -0
- package/dist/server/helpers/ip.js +62 -0
- package/dist/server/helpers/ip.js.map +1 -0
- package/dist/server/helpers/stream.d.ts +4 -0
- package/dist/server/helpers/stream.d.ts.map +1 -0
- package/dist/server/helpers/stream.js +193 -0
- package/dist/server/helpers/stream.js.map +1 -0
- package/dist/server/request.d.ts +1 -139
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +19 -433
- package/dist/server/request.js.map +1 -1
- package/dist/server/response.d.ts +105 -158
- package/dist/server/response.d.ts.map +1 -1
- package/dist/server/response.js +121 -423
- package/dist/server/response.js.map +1 -1
- package/dist/swagger/decorators.d.ts +81 -0
- package/dist/swagger/decorators.d.ts.map +1 -0
- package/dist/swagger/decorators.js +172 -0
- package/dist/swagger/decorators.js.map +1 -0
- package/dist/swagger/generator.d.ts +19 -0
- package/dist/swagger/generator.d.ts.map +1 -0
- package/dist/swagger/generator.js +344 -0
- package/dist/swagger/generator.js.map +1 -0
- package/dist/swagger/index.d.ts +31 -0
- package/dist/swagger/index.d.ts.map +1 -0
- package/dist/swagger/index.js +53 -0
- package/dist/swagger/index.js.map +1 -0
- package/dist/swagger/mounter.d.ts +7 -0
- package/dist/swagger/mounter.d.ts.map +1 -0
- package/dist/swagger/mounter.js +56 -0
- package/dist/swagger/mounter.js.map +1 -0
- package/dist/swagger/types.d.ts +190 -0
- package/dist/swagger/types.d.ts.map +1 -0
- package/dist/swagger/types.js +3 -0
- package/dist/swagger/types.js.map +1 -0
- package/dist/swagger/ui.d.ts +6 -0
- package/dist/swagger/ui.d.ts.map +1 -0
- package/dist/swagger/ui.js +118 -0
- package/dist/swagger/ui.js.map +1 -0
- package/dist/validator/builder.d.ts +223 -0
- package/dist/validator/builder.d.ts.map +1 -0
- package/dist/validator/builder.js +517 -0
- package/dist/validator/builder.js.map +1 -0
- package/dist/validator/engine.d.ts +19 -0
- package/dist/validator/engine.d.ts.map +1 -0
- package/dist/validator/engine.js +568 -0
- package/dist/validator/engine.js.map +1 -0
- package/dist/validator/error.d.ts +12 -0
- package/dist/validator/error.d.ts.map +1 -0
- package/dist/validator/error.js +63 -0
- package/dist/validator/error.js.map +1 -0
- package/dist/validator/pagination.d.ts +40 -0
- package/dist/validator/pagination.d.ts.map +1 -0
- package/dist/validator/pagination.js +60 -0
- package/dist/validator/pagination.js.map +1 -0
- package/dist/validator/tex.d.ts +6 -158
- package/dist/validator/tex.d.ts.map +1 -1
- package/dist/validator/tex.js +14 -971
- package/dist/validator/tex.js.map +1 -1
- package/dist/validator/types.d.ts +83 -0
- package/dist/validator/types.d.ts.map +1 -0
- package/dist/validator/types.js +3 -0
- package/dist/validator/types.js.map +1 -0
- package/package.json +9 -1
package/dist/server/response.js
CHANGED
|
@@ -8,30 +8,11 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const index_1 = require("../logger/index");
|
|
10
10
|
const sse_1 = require("./sse");
|
|
11
|
+
const helpers_1 = require("./helpers");
|
|
11
12
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
12
13
|
const contentDisposition = require('content-disposition');
|
|
13
14
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
14
15
|
const mime = require('mime-types');
|
|
15
|
-
const rs_1 = require("@exisjs/rs");
|
|
16
|
-
function generateETag(content) {
|
|
17
|
-
return (0, rs_1.generateEtag)(content);
|
|
18
|
-
}
|
|
19
|
-
function nativeStringify(data) {
|
|
20
|
-
if (data === null || data === undefined) {
|
|
21
|
-
return 'null';
|
|
22
|
-
}
|
|
23
|
-
// If native rust serialization is available, use it
|
|
24
|
-
if (typeof rs_1.fastJsonStringifyBuffer === 'function') {
|
|
25
|
-
// Validate object can be serialized without throwing V8 check failure on circularity
|
|
26
|
-
const str = JSON.stringify(data);
|
|
27
|
-
if (str.length > 512) {
|
|
28
|
-
// For larger payloads, Rust serde serialization into Buffer gives zero V8 GC overhead
|
|
29
|
-
return (0, rs_1.fastJsonStringifyBuffer)(data);
|
|
30
|
-
}
|
|
31
|
-
return str;
|
|
32
|
-
}
|
|
33
|
-
return JSON.stringify(data);
|
|
34
|
-
}
|
|
35
16
|
class ExisResponse {
|
|
36
17
|
constructor(raw) {
|
|
37
18
|
this.raw = raw;
|
|
@@ -96,11 +77,15 @@ class ExisResponse {
|
|
|
96
77
|
}
|
|
97
78
|
}
|
|
98
79
|
/**
|
|
99
|
-
*
|
|
80
|
+
* Sets the HTTP response status code.
|
|
81
|
+
*
|
|
82
|
+
* @param code HTTP status code (e.g. `200`, `201`, `400`, `404`, `500`)
|
|
83
|
+
* @returns Response instance for chaining
|
|
100
84
|
*
|
|
101
|
-
* @
|
|
102
|
-
*
|
|
103
|
-
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* res.status(201).json({ created: true })
|
|
88
|
+
* ```
|
|
104
89
|
*/
|
|
105
90
|
status(code) {
|
|
106
91
|
if (this.raw.destroyed || this.raw.headersSent)
|
|
@@ -109,51 +94,24 @@ class ExisResponse {
|
|
|
109
94
|
return this;
|
|
110
95
|
}
|
|
111
96
|
/**
|
|
112
|
-
*
|
|
113
|
-
* an object of header fields.
|
|
114
|
-
*
|
|
115
|
-
* Examples:
|
|
116
|
-
*
|
|
117
|
-
* res.set('Foo', ['bar', 'baz']);
|
|
118
|
-
* res.set('Accept', 'application/json');
|
|
97
|
+
* Sets a response header field to a specific value.
|
|
119
98
|
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* @
|
|
123
|
-
* @param {string | string[]} value
|
|
124
|
-
* @return {this}
|
|
125
|
-
* @public
|
|
99
|
+
* @param header Header name
|
|
100
|
+
* @param value Header value or array of values
|
|
101
|
+
* @returns Response instance for chaining
|
|
126
102
|
*/
|
|
127
103
|
set(header, value) {
|
|
128
104
|
this.setHeader(header, value);
|
|
129
105
|
return this;
|
|
130
106
|
}
|
|
131
107
|
/**
|
|
132
|
-
*
|
|
133
|
-
* an object of header fields.
|
|
134
|
-
*
|
|
135
|
-
* Alias for `res.set()`.
|
|
136
|
-
*
|
|
137
|
-
* @param {string} name
|
|
138
|
-
* @param {string | string[]} value
|
|
139
|
-
* @return {this}
|
|
140
|
-
* @public
|
|
108
|
+
* Sets a response header field (alias for `res.set()`).
|
|
141
109
|
*/
|
|
142
110
|
header(name, value) {
|
|
143
111
|
return this.set(name, value);
|
|
144
112
|
}
|
|
145
113
|
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
* Sets the response status to `code` and the body
|
|
149
|
-
* to the string representation of the `code`.
|
|
150
|
-
*
|
|
151
|
-
* Examples:
|
|
152
|
-
*
|
|
153
|
-
* res.sendStatus(200);
|
|
154
|
-
*
|
|
155
|
-
* @param {number} code
|
|
156
|
-
* @public
|
|
114
|
+
* Sets status code and sends its numeric string representation.
|
|
157
115
|
*/
|
|
158
116
|
sendStatus(code) {
|
|
159
117
|
this.statusCode = code;
|
|
@@ -170,18 +128,7 @@ class ExisResponse {
|
|
|
170
128
|
return this;
|
|
171
129
|
}
|
|
172
130
|
/**
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
* Example:
|
|
176
|
-
*
|
|
177
|
-
* res.append('Link', ['<http://localhost/>', '<http://localhost:3000/>']);
|
|
178
|
-
* res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
|
|
179
|
-
* res.append('Warning', '199 Miscellaneous warning');
|
|
180
|
-
*
|
|
181
|
-
* @param {string} field
|
|
182
|
-
* @param {string | string[]} value
|
|
183
|
-
* @return {this}
|
|
184
|
-
* @public
|
|
131
|
+
* Appends an additional value to an existing header field (e.g. `Set-Cookie`, `Link`, `Warning`).
|
|
185
132
|
*/
|
|
186
133
|
append(field, value) {
|
|
187
134
|
const prev = this.getHeader(field);
|
|
@@ -195,16 +142,14 @@ class ExisResponse {
|
|
|
195
142
|
return this;
|
|
196
143
|
}
|
|
197
144
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
* Examples:
|
|
145
|
+
* Sends a string, Buffer, or object response to the client.
|
|
201
146
|
*
|
|
202
|
-
*
|
|
203
|
-
* res.send({ some: 'json' });
|
|
204
|
-
* res.send('<p>some html</p>');
|
|
147
|
+
* @param body Response payload (string, Buffer, or object)
|
|
205
148
|
*
|
|
206
|
-
* @
|
|
207
|
-
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* res.send('Hello World')
|
|
152
|
+
* ```
|
|
208
153
|
*/
|
|
209
154
|
send(body) {
|
|
210
155
|
if (!this.isWritable)
|
|
@@ -221,7 +166,7 @@ class ExisResponse {
|
|
|
221
166
|
const buf = isBuffer
|
|
222
167
|
? body
|
|
223
168
|
: Buffer.from(body, 'utf8');
|
|
224
|
-
this.raw.setHeader('ETag', generateETag(buf));
|
|
169
|
+
this.raw.setHeader('ETag', (0, helpers_1.generateETag)(buf));
|
|
225
170
|
}
|
|
226
171
|
if (this.req &&
|
|
227
172
|
(this.raw.hasHeader('ETag') || this.raw.hasHeader('Last-Modified')) &&
|
|
@@ -230,23 +175,20 @@ class ExisResponse {
|
|
|
230
175
|
this.end();
|
|
231
176
|
return;
|
|
232
177
|
}
|
|
233
|
-
// Rely on Node.js core to automatically calculate Content-Length
|
|
234
|
-
// for strings in res.end() rather than creating a Buffer here.
|
|
235
178
|
if (isBuffer && !this.raw.hasHeader('Content-Length')) {
|
|
236
179
|
this.raw.setHeader('Content-Length', body.length);
|
|
237
180
|
}
|
|
238
181
|
this.end(body);
|
|
239
182
|
}
|
|
240
183
|
/**
|
|
241
|
-
*
|
|
184
|
+
* Sends a JSON response with automatic native serialization and fast ETag support.
|
|
242
185
|
*
|
|
243
|
-
*
|
|
186
|
+
* @param data JSON-serializable data payload
|
|
244
187
|
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
* @public
|
|
188
|
+
* @example
|
|
189
|
+
* ```ts
|
|
190
|
+
* res.json({ success: true, users: ['Alice', 'Bob'] })
|
|
191
|
+
* ```
|
|
250
192
|
*/
|
|
251
193
|
json(data) {
|
|
252
194
|
if (!this.isWritable)
|
|
@@ -254,7 +196,7 @@ class ExisResponse {
|
|
|
254
196
|
let payload;
|
|
255
197
|
try {
|
|
256
198
|
const useSerializer = this._serializer && this.statusCode < 400;
|
|
257
|
-
payload = useSerializer ? this._serializer(data) : nativeStringify(data);
|
|
199
|
+
payload = useSerializer ? this._serializer(data) : (0, helpers_1.nativeStringify)(data);
|
|
258
200
|
}
|
|
259
201
|
catch (err) {
|
|
260
202
|
if (this.req && this.req.log) {
|
|
@@ -275,7 +217,7 @@ class ExisResponse {
|
|
|
275
217
|
const buf = Buffer.isBuffer(payload)
|
|
276
218
|
? payload
|
|
277
219
|
: Buffer.from(payload, 'utf8');
|
|
278
|
-
this.raw.setHeader('ETag', generateETag(buf));
|
|
220
|
+
this.raw.setHeader('ETag', (0, helpers_1.generateETag)(buf));
|
|
279
221
|
}
|
|
280
222
|
if (this.req &&
|
|
281
223
|
(this.raw.hasHeader('ETag') || this.raw.hasHeader('Last-Modified')) &&
|
|
@@ -287,14 +229,14 @@ class ExisResponse {
|
|
|
287
229
|
this.end(payload);
|
|
288
230
|
}
|
|
289
231
|
/**
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
* Examples:
|
|
232
|
+
* Sends an HTML response with `Content-Type: text/html; charset=utf-8`.
|
|
293
233
|
*
|
|
294
|
-
*
|
|
234
|
+
* @param content HTML string
|
|
295
235
|
*
|
|
296
|
-
* @
|
|
297
|
-
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```ts
|
|
238
|
+
* res.html('<h1>Welcome to ExisJS</h1>')
|
|
239
|
+
* ```
|
|
298
240
|
*/
|
|
299
241
|
html(content) {
|
|
300
242
|
if (!this.isWritable)
|
|
@@ -305,18 +247,15 @@ class ExisResponse {
|
|
|
305
247
|
this.end(content);
|
|
306
248
|
}
|
|
307
249
|
/**
|
|
308
|
-
*
|
|
309
|
-
* defaulting to 302.
|
|
310
|
-
*
|
|
311
|
-
* Examples:
|
|
250
|
+
* Performs an HTTP redirect to a given URL with an optional status code (defaults to 302).
|
|
312
251
|
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
* res.redirect('http://example.com', 301);
|
|
252
|
+
* @param url Target destination URL
|
|
253
|
+
* @param code HTTP status code (`301` Moved Permanently, `302` Found, `307` Temporary Redirect, `308` Permanent Redirect)
|
|
316
254
|
*
|
|
317
|
-
* @
|
|
318
|
-
*
|
|
319
|
-
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```ts
|
|
257
|
+
* res.redirect('/login')
|
|
258
|
+
* ```
|
|
320
259
|
*/
|
|
321
260
|
redirect(url, code = 302) {
|
|
322
261
|
if (!this.isWritable)
|
|
@@ -326,197 +265,21 @@ class ExisResponse {
|
|
|
326
265
|
this.end();
|
|
327
266
|
}
|
|
328
267
|
/**
|
|
329
|
-
*
|
|
330
|
-
* to the client with native
|
|
331
|
-
*
|
|
268
|
+
* Streams a Node.js Readable stream, Web standard `ReadableStream`, or `AsyncIterable` generator
|
|
269
|
+
* to the client with native backpressure handling and client disconnect auto-cancellation.
|
|
270
|
+
*
|
|
271
|
+
* @param readable Stream or async generator instance
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* ```ts
|
|
275
|
+
* // Streaming from an AI SDK or Web stream:
|
|
276
|
+
* res.sendStream(aiResponse.toReadableStream())
|
|
277
|
+
* ```
|
|
332
278
|
*/
|
|
333
279
|
sendStream(readable) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
readable.destroy();
|
|
338
|
-
}
|
|
339
|
-
else if (typeof readable.cancel === 'function') {
|
|
340
|
-
;
|
|
341
|
-
readable.cancel().catch(() => {
|
|
342
|
-
/* ignore */
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
if (!this.hasHeader('Content-Type')) {
|
|
348
|
-
this.setHeader('Content-Type', 'application/octet-stream');
|
|
349
|
-
}
|
|
350
|
-
// 1. Web Standard ReadableStream (e.g. from fetch, OpenAI, Anthropic, AI SDKs)
|
|
351
|
-
if (typeof readable.getReader === 'function') {
|
|
352
|
-
const reader = readable.getReader();
|
|
353
|
-
let closed = false;
|
|
354
|
-
const cleanup = () => {
|
|
355
|
-
if (closed)
|
|
356
|
-
return;
|
|
357
|
-
closed = true;
|
|
358
|
-
try {
|
|
359
|
-
reader.cancel().catch(() => {
|
|
360
|
-
/* ignore */
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
catch {
|
|
364
|
-
// ignore
|
|
365
|
-
}
|
|
366
|
-
};
|
|
367
|
-
this.raw.once('close', cleanup);
|
|
368
|
-
if (this.req && this.req.raw) {
|
|
369
|
-
;
|
|
370
|
-
this.req.raw.once('close', cleanup);
|
|
371
|
-
this.req.raw.once('aborted', cleanup);
|
|
372
|
-
}
|
|
373
|
-
;
|
|
374
|
-
(async () => {
|
|
375
|
-
try {
|
|
376
|
-
while (true) {
|
|
377
|
-
if (closed || this.raw.destroyed) {
|
|
378
|
-
cleanup();
|
|
379
|
-
break;
|
|
380
|
-
}
|
|
381
|
-
const { done, value } = await reader.read();
|
|
382
|
-
if (done) {
|
|
383
|
-
this.raw.removeListener('close', cleanup);
|
|
384
|
-
if (!this.raw.writableEnded && !this.raw.destroyed) {
|
|
385
|
-
this.end();
|
|
386
|
-
}
|
|
387
|
-
break;
|
|
388
|
-
}
|
|
389
|
-
if (value !== undefined && value !== null) {
|
|
390
|
-
const ok = this.raw.write(value);
|
|
391
|
-
if (!ok && !this.raw.destroyed && !this.raw.writableEnded) {
|
|
392
|
-
await new Promise((resolve) => this.raw.once('drain', resolve));
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
catch (err) {
|
|
398
|
-
cleanup();
|
|
399
|
-
if (this.req && this.req.log) {
|
|
400
|
-
this.req.log.error({ err }, '[ExisJS] Error in Web ReadableStream');
|
|
401
|
-
}
|
|
402
|
-
else {
|
|
403
|
-
index_1.logger.error({ err }, '[ExisJS] Error in Web ReadableStream');
|
|
404
|
-
}
|
|
405
|
-
if (this.isWritable && !this.headersSent) {
|
|
406
|
-
this.statusCode = 500;
|
|
407
|
-
this.end('{"error":"Stream transmission failed"}');
|
|
408
|
-
}
|
|
409
|
-
else if (!this.raw.destroyed) {
|
|
410
|
-
this.raw.destroy(err);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
})();
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
// 2. AsyncIterable / Generator stream
|
|
417
|
-
if (typeof readable[Symbol.asyncIterator] === 'function' &&
|
|
418
|
-
typeof readable.pipe !== 'function') {
|
|
419
|
-
let closed = false;
|
|
420
|
-
const cleanup = () => {
|
|
421
|
-
closed = true;
|
|
422
|
-
if (typeof readable.return === 'function') {
|
|
423
|
-
;
|
|
424
|
-
readable.return().catch(() => {
|
|
425
|
-
/* ignore */
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
};
|
|
429
|
-
this.raw.once('close', cleanup);
|
|
430
|
-
if (this.req && this.req.raw) {
|
|
431
|
-
;
|
|
432
|
-
this.req.raw.once('close', cleanup);
|
|
433
|
-
}
|
|
434
|
-
;
|
|
435
|
-
(async () => {
|
|
436
|
-
try {
|
|
437
|
-
for await (const chunk of readable) {
|
|
438
|
-
if (closed || this.raw.destroyed)
|
|
439
|
-
break;
|
|
440
|
-
if (chunk !== undefined && chunk !== null) {
|
|
441
|
-
const payload = typeof chunk === 'string' || Buffer.isBuffer(chunk)
|
|
442
|
-
? chunk
|
|
443
|
-
: JSON.stringify(chunk);
|
|
444
|
-
const ok = this.raw.write(payload);
|
|
445
|
-
if (!ok && !this.raw.destroyed && !this.raw.writableEnded) {
|
|
446
|
-
await new Promise((resolve) => this.raw.once('drain', resolve));
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
this.raw.removeListener('close', cleanup);
|
|
451
|
-
if (!this.raw.writableEnded && !this.raw.destroyed) {
|
|
452
|
-
this.end();
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
catch (err) {
|
|
456
|
-
cleanup();
|
|
457
|
-
if (this.req && this.req.log) {
|
|
458
|
-
this.req.log.error({ err }, '[ExisJS] Error in AsyncIterable stream');
|
|
459
|
-
}
|
|
460
|
-
else {
|
|
461
|
-
index_1.logger.error({ err }, '[ExisJS] Error in AsyncIterable stream');
|
|
462
|
-
}
|
|
463
|
-
if (this.isWritable && !this.headersSent) {
|
|
464
|
-
this.statusCode = 500;
|
|
465
|
-
this.end('{"error":"Stream transmission failed"}');
|
|
466
|
-
}
|
|
467
|
-
else if (!this.raw.destroyed) {
|
|
468
|
-
this.raw.destroy(err);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
})();
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
// 3. Standard Node.js Readable Stream
|
|
475
|
-
const cleanup = () => {
|
|
476
|
-
if (typeof readable.destroy === 'function' &&
|
|
477
|
-
!readable.destroyed) {
|
|
478
|
-
;
|
|
479
|
-
readable.destroy();
|
|
480
|
-
}
|
|
481
|
-
};
|
|
482
|
-
// Auto-destroy stream if client aborts or response closes early
|
|
483
|
-
this.raw.once('close', cleanup);
|
|
484
|
-
if (this.req && this.req.raw) {
|
|
485
|
-
;
|
|
486
|
-
this.req.raw.once('close', cleanup);
|
|
487
|
-
this.req.raw.once('aborted', cleanup);
|
|
488
|
-
}
|
|
489
|
-
// Handle stream error to prevent process crash
|
|
490
|
-
if (typeof readable.on === 'function') {
|
|
491
|
-
;
|
|
492
|
-
readable.once('error', (err) => {
|
|
493
|
-
this.raw.removeListener('close', cleanup);
|
|
494
|
-
if (this.req && this.req.log) {
|
|
495
|
-
this.req.log.error({ err }, '[ExisJS] Error in sendStream');
|
|
496
|
-
}
|
|
497
|
-
else {
|
|
498
|
-
index_1.logger.error({ err }, '[ExisJS] Error in sendStream');
|
|
499
|
-
}
|
|
500
|
-
if (this.isWritable && !this.headersSent) {
|
|
501
|
-
this.statusCode = 500;
|
|
502
|
-
this.end('{"error":"Stream transmission failed"}');
|
|
503
|
-
}
|
|
504
|
-
else {
|
|
505
|
-
cleanup();
|
|
506
|
-
if (!this.raw.destroyed) {
|
|
507
|
-
this.raw.destroy(err);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
if (typeof readable.once === 'function') {
|
|
513
|
-
;
|
|
514
|
-
readable.once('end', () => {
|
|
515
|
-
this.raw.removeListener('close', cleanup);
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
;
|
|
519
|
-
readable.pipe(this.raw);
|
|
280
|
+
(0, helpers_1.streamToResponse)(this.raw, readable, this.isWritable, this.headersSent, (name) => this.hasHeader(name), (name, val) => this.setHeader(name, val), (code) => {
|
|
281
|
+
this.statusCode = code;
|
|
282
|
+
}, (data) => this.end(data), this.req);
|
|
520
283
|
}
|
|
521
284
|
/**
|
|
522
285
|
* Alias for `sendStream()`.
|
|
@@ -525,29 +288,33 @@ class ExisResponse {
|
|
|
525
288
|
this.sendStream(readable);
|
|
526
289
|
}
|
|
527
290
|
/**
|
|
528
|
-
*
|
|
291
|
+
* Initializes a Server-Sent Events (SSE) stream for real-time data and AI token streaming.
|
|
529
292
|
*
|
|
530
|
-
*
|
|
293
|
+
* Sets standard SSE headers automatically:
|
|
531
294
|
* - `Content-Type: text/event-stream; charset=utf-8`
|
|
532
295
|
* - `Cache-Control: no-cache, no-transform`
|
|
533
296
|
* - `Connection: keep-alive`
|
|
534
|
-
* - `X-Accel-Buffering: no`
|
|
297
|
+
* - `X-Accel-Buffering: no`
|
|
535
298
|
*
|
|
536
|
-
* @param
|
|
537
|
-
* @param
|
|
299
|
+
* @param optionsOrHandler Optional SSE configuration or callback handler
|
|
300
|
+
* @param maybeHandler Callback handler when options object is provided
|
|
538
301
|
* @returns Active `SSEStream` instance
|
|
539
302
|
*
|
|
540
303
|
* @example
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
304
|
+
* ```ts
|
|
305
|
+
* // In route.ts
|
|
306
|
+
* export default controller({
|
|
307
|
+
* events: route.get('/events', {
|
|
308
|
+
* async handle({ res }) {
|
|
309
|
+
* res.sse(async (sse) => {
|
|
310
|
+
* sse.send({ event: 'ping', data: { time: Date.now() } })
|
|
311
|
+
* await sse.pipeFrom(openAiStream)
|
|
312
|
+
* sse.close()
|
|
313
|
+
* })
|
|
314
|
+
* }
|
|
315
|
+
* })
|
|
546
316
|
* })
|
|
547
|
-
*
|
|
548
|
-
* // Or assigning to variable:
|
|
549
|
-
* const sse = res.sse()
|
|
550
|
-
* sse.send({ event: 'connected', data: { time: Date.now() } })
|
|
317
|
+
* ```
|
|
551
318
|
*/
|
|
552
319
|
sse(optionsOrHandler, maybeHandler) {
|
|
553
320
|
let options = {};
|
|
@@ -573,7 +340,6 @@ class ExisResponse {
|
|
|
573
340
|
this.setHeader(key, value);
|
|
574
341
|
}
|
|
575
342
|
}
|
|
576
|
-
// Flush headers immediately
|
|
577
343
|
if (typeof this.raw.flushHeaders === 'function') {
|
|
578
344
|
this.raw.flushHeaders();
|
|
579
345
|
}
|
|
@@ -611,15 +377,16 @@ class ExisResponse {
|
|
|
611
377
|
return stream;
|
|
612
378
|
}
|
|
613
379
|
/**
|
|
614
|
-
*
|
|
380
|
+
* Transfers a file on disk as an attachment with automatic Content-Disposition and MIME lookup.
|
|
615
381
|
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
382
|
+
* @param filePath Absolute path to file on disk
|
|
383
|
+
* @param filename Optional override for downloaded filename
|
|
384
|
+
* @param options File stream options
|
|
618
385
|
*
|
|
619
|
-
* @
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
386
|
+
* @example
|
|
387
|
+
* ```ts
|
|
388
|
+
* res.download('/data/reports/report.pdf', 'monthly-report.pdf')
|
|
389
|
+
* ```
|
|
623
390
|
*/
|
|
624
391
|
download(filePath, filename, options) {
|
|
625
392
|
if (this.headersSent)
|
|
@@ -644,20 +411,13 @@ class ExisResponse {
|
|
|
644
411
|
stream.pipe(this.raw);
|
|
645
412
|
}
|
|
646
413
|
/**
|
|
647
|
-
*
|
|
648
|
-
* when it does not contain "/", or set the Content-Type to `type` otherwise.
|
|
414
|
+
* Sets Content-Type header using file extension or MIME type string.
|
|
649
415
|
*
|
|
650
|
-
*
|
|
651
|
-
*
|
|
652
|
-
*
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
* res.type('application/json');
|
|
656
|
-
* res.type('png');
|
|
657
|
-
*
|
|
658
|
-
* @param {string} type
|
|
659
|
-
* @return {this}
|
|
660
|
-
* @public
|
|
416
|
+
* @example
|
|
417
|
+
* ```ts
|
|
418
|
+
* res.type('json') // sets 'application/json; charset=utf-8'
|
|
419
|
+
* res.type('png') // sets 'image/png'
|
|
420
|
+
* ```
|
|
661
421
|
*/
|
|
662
422
|
type(type) {
|
|
663
423
|
const mimeType = mime.contentType(type) || type;
|
|
@@ -665,18 +425,7 @@ class ExisResponse {
|
|
|
665
425
|
return this;
|
|
666
426
|
}
|
|
667
427
|
/**
|
|
668
|
-
*
|
|
669
|
-
*
|
|
670
|
-
* Examples:
|
|
671
|
-
*
|
|
672
|
-
* res.links({
|
|
673
|
-
* next: 'http://api.example.com/users?page=2',
|
|
674
|
-
* last: 'http://api.example.com/users?page=5'
|
|
675
|
-
* });
|
|
676
|
-
*
|
|
677
|
-
* @param {Record<string, string>} links
|
|
678
|
-
* @return {this}
|
|
679
|
-
* @public
|
|
428
|
+
* Sets Link header field with pagination or resource relations.
|
|
680
429
|
*/
|
|
681
430
|
links(links) {
|
|
682
431
|
let linkHeader = this.getHeader('Link') || '';
|
|
@@ -688,12 +437,7 @@ class ExisResponse {
|
|
|
688
437
|
return this;
|
|
689
438
|
}
|
|
690
439
|
/**
|
|
691
|
-
*
|
|
692
|
-
* this call is simply ignored.
|
|
693
|
-
*
|
|
694
|
-
* @param {string} field
|
|
695
|
-
* @return {this}
|
|
696
|
-
* @public
|
|
440
|
+
* Adds a field to the Vary header if not already present.
|
|
697
441
|
*/
|
|
698
442
|
vary(field) {
|
|
699
443
|
if (!field)
|
|
@@ -714,94 +458,48 @@ class ExisResponse {
|
|
|
714
458
|
return this;
|
|
715
459
|
}
|
|
716
460
|
/**
|
|
717
|
-
*
|
|
718
|
-
*
|
|
719
|
-
* Options:
|
|
720
|
-
* - `maxAge` max-age in milliseconds, converted to `expires`
|
|
721
|
-
* - `path` cookie path, defaults to '/'
|
|
722
|
-
* - `domain` cookie domain
|
|
723
|
-
* - `secure` secure cookie
|
|
724
|
-
* - `httpOnly` httponly cookie
|
|
725
|
-
* - `sameSite` samesite cookie
|
|
726
|
-
*
|
|
727
|
-
* Examples:
|
|
461
|
+
* Sets a cookie with specified security options.
|
|
728
462
|
*
|
|
729
|
-
*
|
|
730
|
-
*
|
|
463
|
+
* @param name Cookie name
|
|
464
|
+
* @param value Cookie value
|
|
465
|
+
* @param options Cookie options (httpOnly, secure, sameSite, maxAge, path, domain)
|
|
731
466
|
*
|
|
732
|
-
* @
|
|
733
|
-
*
|
|
734
|
-
*
|
|
735
|
-
*
|
|
736
|
-
*
|
|
467
|
+
* @example
|
|
468
|
+
* ```ts
|
|
469
|
+
* res.cookie('session_token', token, {
|
|
470
|
+
* httpOnly: true,
|
|
471
|
+
* secure: true,
|
|
472
|
+
* sameSite: 'lax',
|
|
473
|
+
* maxAge: 86400000 // 1 day
|
|
474
|
+
* })
|
|
475
|
+
* ```
|
|
737
476
|
*/
|
|
738
477
|
cookie(name, value, options = {}) {
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
];
|
|
742
|
-
if (options.maxAge !== undefined)
|
|
743
|
-
parts.push(`Max-Age=${options.maxAge}`);
|
|
744
|
-
if (options.expires)
|
|
745
|
-
parts.push(`Expires=${options.expires.toUTCString()}`);
|
|
746
|
-
if (options.path ?? true)
|
|
747
|
-
parts.push(`Path=${options.path ?? '/'}`);
|
|
748
|
-
if (options.domain)
|
|
749
|
-
parts.push(`Domain=${options.domain}`);
|
|
750
|
-
if (options.httpOnly)
|
|
751
|
-
parts.push('HttpOnly');
|
|
752
|
-
if (options.secure)
|
|
753
|
-
parts.push('Secure');
|
|
754
|
-
if (options.sameSite !== undefined && options.sameSite !== false) {
|
|
755
|
-
if (options.sameSite === true) {
|
|
756
|
-
parts.push('SameSite=Strict');
|
|
757
|
-
}
|
|
758
|
-
else {
|
|
759
|
-
const str = String(options.sameSite).toLowerCase();
|
|
760
|
-
if (str === 'strict')
|
|
761
|
-
parts.push('SameSite=Strict');
|
|
762
|
-
else if (str === 'lax')
|
|
763
|
-
parts.push('SameSite=Lax');
|
|
764
|
-
else if (str === 'none') {
|
|
765
|
-
parts.push('SameSite=None');
|
|
766
|
-
if (!options.secure && !parts.includes('Secure')) {
|
|
767
|
-
parts.push('Secure');
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
if (options.partitioned)
|
|
773
|
-
parts.push('Partitioned');
|
|
774
|
-
if (options.priority) {
|
|
775
|
-
const p = options.priority.toLowerCase();
|
|
776
|
-
if (p === 'low')
|
|
777
|
-
parts.push('Priority=Low');
|
|
778
|
-
else if (p === 'medium')
|
|
779
|
-
parts.push('Priority=Medium');
|
|
780
|
-
else if (p === 'high')
|
|
781
|
-
parts.push('Priority=High');
|
|
782
|
-
}
|
|
783
|
-
this.append('Set-Cookie', parts.join('; '));
|
|
478
|
+
const serialized = (0, helpers_1.serializeCookie)(name, value, options);
|
|
479
|
+
this.append('Set-Cookie', serialized);
|
|
784
480
|
if (process.env.NODE_ENV === 'development') {
|
|
785
|
-
this.append('X-Set-Cookie',
|
|
481
|
+
this.append('X-Set-Cookie', serialized);
|
|
786
482
|
}
|
|
787
483
|
return this;
|
|
788
484
|
}
|
|
789
485
|
/**
|
|
790
|
-
*
|
|
486
|
+
* Clears a cookie by setting its expiration to the past.
|
|
487
|
+
*
|
|
488
|
+
* @param name Cookie name
|
|
489
|
+
* @param options Cookie options (path, domain)
|
|
791
490
|
*
|
|
792
|
-
* @
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
*
|
|
491
|
+
* @example
|
|
492
|
+
* ```ts
|
|
493
|
+
* res.clearCookie('session_token')
|
|
494
|
+
* ```
|
|
796
495
|
*/
|
|
797
496
|
clearCookie(name, options = {}) {
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
});
|
|
497
|
+
const serialized = (0, helpers_1.serializeClearCookie)(name, options);
|
|
498
|
+
this.append('Set-Cookie', serialized);
|
|
499
|
+
if (process.env.NODE_ENV === 'development') {
|
|
500
|
+
this.append('X-Set-Cookie', serialized);
|
|
501
|
+
}
|
|
502
|
+
return this;
|
|
805
503
|
}
|
|
806
504
|
}
|
|
807
505
|
exports.ExisResponse = ExisResponse;
|