@nextrush/core 3.0.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/LICENSE +21 -0
- package/README.md +626 -0
- package/dist/index.d.ts +388 -0
- package/dist/index.js +534 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
export { BadRequestError, ForbiddenError, HttpError, InternalServerError, NextRushError, NotFoundError, UnauthorizedError, createError as createHttpError } from '@nextrush/errors';
|
|
2
|
+
export { ContentType, HttpStatus } from '@nextrush/types';
|
|
3
|
+
|
|
4
|
+
// src/middleware.ts
|
|
5
|
+
function compose(middleware, options) {
|
|
6
|
+
if (!Array.isArray(middleware)) {
|
|
7
|
+
throw new TypeError("Middleware stack must be an array");
|
|
8
|
+
}
|
|
9
|
+
for (const fn of middleware) {
|
|
10
|
+
if (typeof fn !== "function") {
|
|
11
|
+
throw new TypeError("Middleware must be a function");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const warnDoubleResponse = options?.warnDoubleResponse ?? process.env.NODE_ENV !== "production";
|
|
15
|
+
const stack = [...middleware];
|
|
16
|
+
const len = stack.length;
|
|
17
|
+
if (len === 0) {
|
|
18
|
+
return function composedMiddleware(_ctx, next) {
|
|
19
|
+
return next ? next() : Promise.resolve();
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return function composedMiddleware(ctx, next) {
|
|
23
|
+
let index = -1;
|
|
24
|
+
function dispatch(i) {
|
|
25
|
+
if (i <= index) {
|
|
26
|
+
return Promise.reject(new Error("next() called multiple times"));
|
|
27
|
+
}
|
|
28
|
+
index = i;
|
|
29
|
+
let fn;
|
|
30
|
+
if (i < len) {
|
|
31
|
+
fn = stack[i];
|
|
32
|
+
} else if (i === len) {
|
|
33
|
+
fn = next;
|
|
34
|
+
}
|
|
35
|
+
if (!fn) {
|
|
36
|
+
return Promise.resolve();
|
|
37
|
+
}
|
|
38
|
+
const respondedBefore = warnDoubleResponse ? ctx.responded : false;
|
|
39
|
+
let nextCalled = false;
|
|
40
|
+
const nextFn = () => {
|
|
41
|
+
nextCalled = true;
|
|
42
|
+
return dispatch(i + 1);
|
|
43
|
+
};
|
|
44
|
+
if (ctx.setNext) {
|
|
45
|
+
ctx.setNext(nextFn);
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const result = Promise.resolve(fn(ctx, nextFn));
|
|
49
|
+
if (warnDoubleResponse) {
|
|
50
|
+
return result.then(() => {
|
|
51
|
+
if (!respondedBefore && ctx.responded && nextCalled) {
|
|
52
|
+
console.warn(
|
|
53
|
+
`[nextrush] Middleware at index ${String(i)} sent a response and called next(). Downstream middleware may attempt to write to an already-finished response. Either send a response OR call next(), not both.`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
} catch (err) {
|
|
60
|
+
return Promise.reject(err instanceof Error ? err : new Error(String(err)));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return dispatch(0);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function isMiddleware(fn) {
|
|
67
|
+
return typeof fn === "function";
|
|
68
|
+
}
|
|
69
|
+
function flattenMiddleware(arr) {
|
|
70
|
+
const flattened = arr.flat(10);
|
|
71
|
+
for (const fn of flattened) {
|
|
72
|
+
if (typeof fn !== "function") {
|
|
73
|
+
throw new TypeError(`Invalid middleware: expected function, got ${typeof fn}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return flattened;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/application.ts
|
|
80
|
+
var ORIGINAL_PATH = /* @__PURE__ */ Symbol.for("nextrush.originalPath");
|
|
81
|
+
var ROUTE_PREFIX = /* @__PURE__ */ Symbol.for("nextrush.routePrefix");
|
|
82
|
+
var NOOP_LOGGER = {
|
|
83
|
+
error(..._args) {
|
|
84
|
+
},
|
|
85
|
+
warn(..._args) {
|
|
86
|
+
},
|
|
87
|
+
info(..._args) {
|
|
88
|
+
},
|
|
89
|
+
debug(..._args) {
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var Application = class {
|
|
93
|
+
/**
|
|
94
|
+
* Middleware stack
|
|
95
|
+
*/
|
|
96
|
+
middlewareStack = [];
|
|
97
|
+
/**
|
|
98
|
+
* Installed plugins
|
|
99
|
+
*/
|
|
100
|
+
plugins = /* @__PURE__ */ new Map();
|
|
101
|
+
/**
|
|
102
|
+
* Custom error handler
|
|
103
|
+
*/
|
|
104
|
+
_errorHandler = null;
|
|
105
|
+
/**
|
|
106
|
+
* Pluggable logger
|
|
107
|
+
*/
|
|
108
|
+
logger;
|
|
109
|
+
/**
|
|
110
|
+
* Application options
|
|
111
|
+
*/
|
|
112
|
+
options;
|
|
113
|
+
/**
|
|
114
|
+
* Whether the app is running
|
|
115
|
+
*/
|
|
116
|
+
_isRunning = false;
|
|
117
|
+
constructor(options = {}) {
|
|
118
|
+
this.options = {
|
|
119
|
+
env: options.env ?? "development",
|
|
120
|
+
proxy: options.proxy ?? false
|
|
121
|
+
};
|
|
122
|
+
this.logger = options.logger ?? NOOP_LOGGER;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Check if running in production
|
|
126
|
+
*/
|
|
127
|
+
get isProduction() {
|
|
128
|
+
return this.options.env === "production";
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Check if app is running
|
|
132
|
+
*/
|
|
133
|
+
get isRunning() {
|
|
134
|
+
return this._isRunning;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get middleware count
|
|
138
|
+
*/
|
|
139
|
+
get middlewareCount() {
|
|
140
|
+
return this.middlewareStack.length;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Throws if the app is already running.
|
|
144
|
+
* Prevents configuration mutations after start().
|
|
145
|
+
*/
|
|
146
|
+
assertNotRunning(method) {
|
|
147
|
+
if (this._isRunning) {
|
|
148
|
+
throw new Error(`Cannot call ${method}() after the application has started`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// ===========================================================================
|
|
152
|
+
// Middleware Registration
|
|
153
|
+
// ===========================================================================
|
|
154
|
+
/**
|
|
155
|
+
* Register middleware function(s)
|
|
156
|
+
*
|
|
157
|
+
* @param middleware - Middleware function or array of middleware
|
|
158
|
+
* @returns this for chaining
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```typescript
|
|
162
|
+
* // Single middleware
|
|
163
|
+
* app.use(async (ctx) => {
|
|
164
|
+
* console.log(ctx.method, ctx.path);
|
|
165
|
+
* await ctx.next();
|
|
166
|
+
* });
|
|
167
|
+
*
|
|
168
|
+
* // Multiple middleware
|
|
169
|
+
* app.use(cors(), helmet(), json());
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
use(...middleware) {
|
|
173
|
+
this.assertNotRunning("use");
|
|
174
|
+
for (const mw of middleware) {
|
|
175
|
+
if (typeof mw !== "function") {
|
|
176
|
+
throw new TypeError("Middleware must be a function");
|
|
177
|
+
}
|
|
178
|
+
this.middlewareStack.push(mw);
|
|
179
|
+
}
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
// ===========================================================================
|
|
183
|
+
// Router Mounting (Hono-style)
|
|
184
|
+
// ===========================================================================
|
|
185
|
+
/**
|
|
186
|
+
* Mount a router at a path prefix
|
|
187
|
+
*
|
|
188
|
+
* This is the Hono-style API for mounting routers directly on the app.
|
|
189
|
+
* The router's routes will only match requests that start with the given prefix.
|
|
190
|
+
*
|
|
191
|
+
* @param path - Path prefix for the router (e.g., '/api/users')
|
|
192
|
+
* @param router - Router instance to mount
|
|
193
|
+
* @returns this for chaining
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```typescript
|
|
197
|
+
* // Create modular routers
|
|
198
|
+
* const users = createRouter();
|
|
199
|
+
* users.get('/', listUsers);
|
|
200
|
+
* users.get('/:id', getUser);
|
|
201
|
+
* users.post('/', createUser);
|
|
202
|
+
*
|
|
203
|
+
* const posts = createRouter();
|
|
204
|
+
* posts.get('/', listPosts);
|
|
205
|
+
* posts.get('/:id', getPost);
|
|
206
|
+
*
|
|
207
|
+
* // Mount directly on app - clean like Hono!
|
|
208
|
+
* const app = createApp();
|
|
209
|
+
* app.route('/api/users', users);
|
|
210
|
+
* app.route('/api/posts', posts);
|
|
211
|
+
*
|
|
212
|
+
* listen(app, 3000);
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
route(path, router) {
|
|
216
|
+
this.assertNotRunning("route");
|
|
217
|
+
if (path === "/" || path === "") {
|
|
218
|
+
this.middlewareStack.push(router.routes());
|
|
219
|
+
return this;
|
|
220
|
+
}
|
|
221
|
+
const routerMiddleware = router.routes();
|
|
222
|
+
let normalizedPrefix = path.endsWith("/") ? path.slice(0, -1) : path;
|
|
223
|
+
if (!normalizedPrefix.startsWith("/")) {
|
|
224
|
+
normalizedPrefix = "/" + normalizedPrefix;
|
|
225
|
+
}
|
|
226
|
+
const prefixLen = normalizedPrefix.length;
|
|
227
|
+
const mountedMiddleware = async (ctx, next) => {
|
|
228
|
+
const currentPath = ctx.path;
|
|
229
|
+
if (!currentPath.startsWith(normalizedPrefix)) {
|
|
230
|
+
return next();
|
|
231
|
+
}
|
|
232
|
+
const hasCharAfterPrefix = prefixLen < currentPath.length;
|
|
233
|
+
if (hasCharAfterPrefix && currentPath.charCodeAt(prefixLen) !== 47) {
|
|
234
|
+
return next();
|
|
235
|
+
}
|
|
236
|
+
const adjustedPath = currentPath.slice(prefixLen) || "/";
|
|
237
|
+
ctx.path = adjustedPath;
|
|
238
|
+
ctx.state[ORIGINAL_PATH] = currentPath;
|
|
239
|
+
ctx.state[ROUTE_PREFIX] = normalizedPrefix;
|
|
240
|
+
try {
|
|
241
|
+
await routerMiddleware(ctx, async () => {
|
|
242
|
+
ctx.path = currentPath;
|
|
243
|
+
await next();
|
|
244
|
+
ctx.path = adjustedPath;
|
|
245
|
+
});
|
|
246
|
+
} finally {
|
|
247
|
+
ctx.path = currentPath;
|
|
248
|
+
ctx.state[ORIGINAL_PATH] = void 0;
|
|
249
|
+
ctx.state[ROUTE_PREFIX] = void 0;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
this.middlewareStack.push(mountedMiddleware);
|
|
253
|
+
return this;
|
|
254
|
+
}
|
|
255
|
+
// ===========================================================================
|
|
256
|
+
// Error Handling
|
|
257
|
+
// ===========================================================================
|
|
258
|
+
/**
|
|
259
|
+
* Set the application error handler.
|
|
260
|
+
*
|
|
261
|
+
* Replaces any previously set handler. Only one error handler is active
|
|
262
|
+
* at a time. For additive error handling, compose logic within a single
|
|
263
|
+
* handler or use the `errorHandler()` middleware from `@nextrush/errors`.
|
|
264
|
+
*
|
|
265
|
+
* @param handler - Error handler function
|
|
266
|
+
* @returns this for chaining
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```typescript
|
|
270
|
+
* app.setErrorHandler((error, ctx) => {
|
|
271
|
+
* console.error('Request failed:', error);
|
|
272
|
+
*
|
|
273
|
+
* if (error instanceof ValidationError) {
|
|
274
|
+
* ctx.status = 400;
|
|
275
|
+
* ctx.json({ error: error.message, details: error.details });
|
|
276
|
+
* return;
|
|
277
|
+
* }
|
|
278
|
+
*
|
|
279
|
+
* ctx.status = 500;
|
|
280
|
+
* ctx.json({ error: 'Internal Server Error' });
|
|
281
|
+
* });
|
|
282
|
+
* ```
|
|
283
|
+
*/
|
|
284
|
+
setErrorHandler(handler) {
|
|
285
|
+
this._errorHandler = handler;
|
|
286
|
+
return this;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Set custom error handler.
|
|
290
|
+
*
|
|
291
|
+
* @param handler - Error handler function
|
|
292
|
+
* @returns this for chaining
|
|
293
|
+
*
|
|
294
|
+
* @deprecated Use `setErrorHandler()` instead — `onError()` implies
|
|
295
|
+
* event subscription (additive), but this is a setter (replaces).
|
|
296
|
+
*/
|
|
297
|
+
onError(handler) {
|
|
298
|
+
return this.setErrorHandler(handler);
|
|
299
|
+
}
|
|
300
|
+
// ===========================================================================
|
|
301
|
+
// Plugin System
|
|
302
|
+
// ===========================================================================
|
|
303
|
+
/**
|
|
304
|
+
* Install a plugin.
|
|
305
|
+
*
|
|
306
|
+
* Handles both sync and async `install()` methods automatically.
|
|
307
|
+
* Returns `this` for sync plugins and `Promise<this>` for async ones.
|
|
308
|
+
*
|
|
309
|
+
* @param plugin - Plugin to install
|
|
310
|
+
* @returns this (sync) or Promise<this> (async)
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* ```typescript
|
|
314
|
+
* // Sync plugin
|
|
315
|
+
* app.plugin(loggerPlugin({ level: 'info' }));
|
|
316
|
+
*
|
|
317
|
+
* // Async plugin — just await it
|
|
318
|
+
* await app.plugin(databasePlugin({ uri: '...' }));
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
plugin(plugin) {
|
|
322
|
+
this.assertNotRunning("plugin");
|
|
323
|
+
if (this.plugins.has(plugin.name)) {
|
|
324
|
+
throw new Error(`Plugin "${plugin.name}" is already installed`);
|
|
325
|
+
}
|
|
326
|
+
const result = plugin.install(this);
|
|
327
|
+
if (result instanceof Promise) {
|
|
328
|
+
return result.then(() => {
|
|
329
|
+
this.plugins.set(plugin.name, plugin);
|
|
330
|
+
return this;
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
this.plugins.set(plugin.name, plugin);
|
|
334
|
+
return this;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Install a plugin asynchronously.
|
|
338
|
+
*
|
|
339
|
+
* @param plugin - Plugin to install
|
|
340
|
+
* @returns Promise that resolves when plugin is installed
|
|
341
|
+
*
|
|
342
|
+
* @deprecated Use `plugin()` instead — it handles both sync and async
|
|
343
|
+
* plugins automatically.
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```typescript
|
|
347
|
+
* await app.plugin(new DatabasePlugin({ connectionString: '...' }));
|
|
348
|
+
* ```
|
|
349
|
+
*/
|
|
350
|
+
async pluginAsync(plugin) {
|
|
351
|
+
const result = this.plugin(plugin);
|
|
352
|
+
return result instanceof Promise ? result : Promise.resolve(result);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Get an installed plugin by name
|
|
356
|
+
*
|
|
357
|
+
* @param name - Plugin name
|
|
358
|
+
* @returns Plugin instance or undefined
|
|
359
|
+
*/
|
|
360
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
|
|
361
|
+
getPlugin(name) {
|
|
362
|
+
return this.plugins.get(name);
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Check if a plugin is installed
|
|
366
|
+
*/
|
|
367
|
+
hasPlugin(name) {
|
|
368
|
+
return this.plugins.has(name);
|
|
369
|
+
}
|
|
370
|
+
// ===========================================================================
|
|
371
|
+
// Request Handler
|
|
372
|
+
// ===========================================================================
|
|
373
|
+
/**
|
|
374
|
+
* Create the request handler callback
|
|
375
|
+
*
|
|
376
|
+
* This is the function that adapters call to handle each request.
|
|
377
|
+
* It composes all middleware and returns a function that processes context.
|
|
378
|
+
*
|
|
379
|
+
* **Important:** The middleware stack is snapshot at call time.
|
|
380
|
+
* Middleware or plugins added after `callback()` will NOT take effect
|
|
381
|
+
* on the returned handler. Call `callback()` again to get a
|
|
382
|
+
* handler that includes subsequent registrations.
|
|
383
|
+
*
|
|
384
|
+
* @returns Request handler function
|
|
385
|
+
*/
|
|
386
|
+
callback() {
|
|
387
|
+
const fn = compose(this.middlewareStack);
|
|
388
|
+
const hookPlugins = Array.from(this.plugins.values()).filter((p) => {
|
|
389
|
+
const hasHook = "onRequest" in p || "onResponse" in p || "onError" in p || "extendContext" in p;
|
|
390
|
+
if (!hasHook) return false;
|
|
391
|
+
if ("onRequest" in p && typeof p.onRequest !== "function") {
|
|
392
|
+
throw new TypeError(
|
|
393
|
+
`Plugin "${p.name}": onRequest must be a function, got ${typeof p.onRequest}`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
if ("onResponse" in p && typeof p.onResponse !== "function") {
|
|
397
|
+
throw new TypeError(
|
|
398
|
+
`Plugin "${p.name}": onResponse must be a function, got ${typeof p.onResponse}`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
if ("onError" in p && typeof p.onError !== "function") {
|
|
402
|
+
throw new TypeError(
|
|
403
|
+
`Plugin "${p.name}": onError must be a function, got ${typeof p.onError}`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
if ("extendContext" in p && typeof p.extendContext !== "function") {
|
|
407
|
+
throw new TypeError(
|
|
408
|
+
`Plugin "${p.name}": extendContext must be a function, got ${typeof p.extendContext}`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
return true;
|
|
412
|
+
});
|
|
413
|
+
return async (ctx) => {
|
|
414
|
+
try {
|
|
415
|
+
for (const p of hookPlugins) {
|
|
416
|
+
if (p.extendContext) p.extendContext(ctx);
|
|
417
|
+
if (p.onRequest) await p.onRequest(ctx);
|
|
418
|
+
}
|
|
419
|
+
await fn(ctx);
|
|
420
|
+
for (const p of hookPlugins) {
|
|
421
|
+
if (p.onResponse) {
|
|
422
|
+
try {
|
|
423
|
+
await p.onResponse(ctx);
|
|
424
|
+
} catch (hookError) {
|
|
425
|
+
this.logger.warn("Plugin onResponse hook threw:", {
|
|
426
|
+
plugin: p.name,
|
|
427
|
+
error: hookError
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
} catch (error) {
|
|
433
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
434
|
+
for (const p of hookPlugins) {
|
|
435
|
+
if (p.onError) {
|
|
436
|
+
try {
|
|
437
|
+
await p.onError(err, ctx);
|
|
438
|
+
} catch (hookError) {
|
|
439
|
+
this.logger.warn("Plugin onError hook threw:", {
|
|
440
|
+
plugin: p.name,
|
|
441
|
+
error: hookError
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
await this.handleError(error, ctx);
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Handle errors - uses custom handler if set, otherwise default
|
|
452
|
+
*/
|
|
453
|
+
async handleError(error, ctx) {
|
|
454
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
455
|
+
if (this._errorHandler) {
|
|
456
|
+
try {
|
|
457
|
+
await this._errorHandler(err, ctx);
|
|
458
|
+
return;
|
|
459
|
+
} catch (handlerError) {
|
|
460
|
+
this.logger.error("Error handler threw:", handlerError);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
this.defaultErrorHandler(err, ctx);
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Default error handler
|
|
467
|
+
*
|
|
468
|
+
* Uses the `expose` flag on errors (set by HttpError/NextRushError)
|
|
469
|
+
* to decide whether to include the error message in the response.
|
|
470
|
+
* 4xx errors expose messages by default; 5xx errors never do.
|
|
471
|
+
* This prevents leaking internal details (paths, SQL, stack info)
|
|
472
|
+
* regardless of environment.
|
|
473
|
+
*/
|
|
474
|
+
defaultErrorHandler(error, ctx) {
|
|
475
|
+
if (!this.isProduction) {
|
|
476
|
+
this.logger.error("Request error:", error);
|
|
477
|
+
}
|
|
478
|
+
const errorMeta = error;
|
|
479
|
+
let status = 500;
|
|
480
|
+
if (typeof errorMeta.status === "number") {
|
|
481
|
+
const raw = errorMeta.status;
|
|
482
|
+
status = raw >= 400 && raw < 600 ? raw : 500;
|
|
483
|
+
}
|
|
484
|
+
ctx.status = status;
|
|
485
|
+
const expose = errorMeta.expose === true;
|
|
486
|
+
const message = expose ? error.message : "Internal Server Error";
|
|
487
|
+
ctx.json({ error: message });
|
|
488
|
+
}
|
|
489
|
+
// ===========================================================================
|
|
490
|
+
// Lifecycle
|
|
491
|
+
// ===========================================================================
|
|
492
|
+
/**
|
|
493
|
+
* Mark app as running and freeze configuration.
|
|
494
|
+
*
|
|
495
|
+
* Called by adapters when the server starts listening. After this call,
|
|
496
|
+
* `use()`, `route()`, `plugin()`, and `pluginAsync()` will throw.
|
|
497
|
+
* This prevents unsafe middleware mutations while requests are in flight.
|
|
498
|
+
*
|
|
499
|
+
* To re-enable registration (e.g. for testing), call `close()` first.
|
|
500
|
+
*/
|
|
501
|
+
start() {
|
|
502
|
+
this._isRunning = true;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Graceful shutdown
|
|
506
|
+
* Destroys all plugins that have a destroy method.
|
|
507
|
+
* Uses Promise.allSettled to ensure all plugins are destroyed
|
|
508
|
+
* even if some throw errors.
|
|
509
|
+
*
|
|
510
|
+
* @returns Array of errors from plugins that failed to destroy (empty on success)
|
|
511
|
+
*/
|
|
512
|
+
async close() {
|
|
513
|
+
this._isRunning = false;
|
|
514
|
+
const pluginArray = Array.from(this.plugins.values()).reverse();
|
|
515
|
+
const destroyablePlugins = pluginArray.filter(
|
|
516
|
+
(p) => typeof p.destroy === "function"
|
|
517
|
+
);
|
|
518
|
+
const destroyPromises = destroyablePlugins.map(
|
|
519
|
+
(p) => Promise.resolve().then(() => p.destroy()).catch((err) => {
|
|
520
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
521
|
+
})
|
|
522
|
+
);
|
|
523
|
+
const results = await Promise.allSettled(destroyPromises);
|
|
524
|
+
this.plugins.clear();
|
|
525
|
+
return results.filter((r) => r.status === "rejected").map((r) => r.reason instanceof Error ? r.reason : new Error(String(r.reason)));
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
function createApp(options) {
|
|
529
|
+
return new Application(options);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
export { Application, compose, createApp, flattenMiddleware, isMiddleware };
|
|
533
|
+
//# sourceMappingURL=index.js.map
|
|
534
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/middleware.ts","../src/application.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @nextrush/core - Middleware Composition\n *\n * Koa-style middleware composition with async/await support.\n * This is the heart of the middleware pipeline.\n *\n * @packageDocumentation\n */\n\nimport type { Context, Middleware, Next } from '@nextrush/types';\n\n/**\n * Composed middleware handler - can be called with just context\n */\nexport type ComposedMiddleware = (ctx: Context, next?: Next) => Promise<void>;\n\n/**\n * Options for middleware composition\n */\nexport interface ComposeOptions {\n /**\n * Warn when a middleware sends a response AND calls next().\n * Enabled by default in non-production environments.\n * @default process.env.NODE_ENV !== 'production'\n */\n warnDoubleResponse?: boolean;\n}\n\n/**\n * Compose multiple middleware functions into a single middleware.\n *\n * Executes middleware in order, each middleware can call `await next()`\n * to pass control to the next middleware and wait for it to complete.\n *\n * @param middleware - Array of middleware functions to compose\n * @param options - Composition options\n * @returns Single composed middleware function\n *\n * @example\n * ```typescript\n * const composed = compose([\n * async (ctx, next) => {\n * console.log('1 - before');\n * await next();\n * console.log('1 - after');\n * },\n * async (ctx, next) => {\n * console.log('2 - before');\n * await next();\n * console.log('2 - after');\n * },\n * ]);\n *\n * // Output:\n * // 1 - before\n * // 2 - before\n * // 2 - after\n * // 1 - after\n * ```\n */\nexport function compose(middleware: Middleware[], options?: ComposeOptions): ComposedMiddleware {\n // Validate middleware array\n if (!Array.isArray(middleware)) {\n throw new TypeError('Middleware stack must be an array');\n }\n\n for (const fn of middleware) {\n if (typeof fn !== 'function') {\n throw new TypeError('Middleware must be a function');\n }\n }\n\n const warnDoubleResponse = options?.warnDoubleResponse ?? process.env.NODE_ENV !== 'production';\n\n // Snapshot middleware array at compose time\n const stack = [...middleware];\n const len = stack.length;\n\n // FAST PATH: No middleware — just call next()\n if (len === 0) {\n return function composedMiddleware(_ctx: Context, next?: Next): Promise<void> {\n return next ? next() : Promise.resolve();\n };\n }\n\n /**\n * Composed middleware function\n * Uses index-based dispatch to avoid per-request closure chains\n * while preserving double-next detection per call.\n */\n return function composedMiddleware(ctx: Context, next?: Next): Promise<void> {\n // Per-request index tracker — only state needed\n let index = -1;\n\n function dispatch(i: number): Promise<void> {\n if (i <= index) {\n return Promise.reject(new Error('next() called multiple times'));\n }\n\n index = i;\n\n let fn: Middleware | Next | undefined;\n if (i < len) {\n fn = stack[i];\n } else if (i === len) {\n fn = next;\n }\n\n if (!fn) {\n return Promise.resolve();\n }\n\n // Capture responded state before middleware runs (dev-mode warning)\n const respondedBefore = warnDoubleResponse ? ctx.responded : false;\n let nextCalled = false;\n\n const nextFn = () => {\n nextCalled = true;\n return dispatch(i + 1);\n };\n\n // Wire up ctx.next() if the context supports it\n if (ctx.setNext) {\n ctx.setNext(nextFn);\n }\n\n try {\n const result = Promise.resolve(fn(ctx, nextFn));\n\n if (warnDoubleResponse) {\n return result.then(() => {\n if (!respondedBefore && ctx.responded && nextCalled) {\n console.warn(\n `[nextrush] Middleware at index ${String(i)} sent a response and called next(). ` +\n 'Downstream middleware may attempt to write to an already-finished response. ' +\n 'Either send a response OR call next(), not both.'\n );\n }\n });\n }\n\n return result;\n } catch (err: unknown) {\n return Promise.reject(err instanceof Error ? err : new Error(String(err)));\n }\n }\n\n return dispatch(0);\n };\n}\n\n/**\n * Check if a function is a valid middleware\n */\nexport function isMiddleware(fn: unknown): fn is Middleware {\n return typeof fn === 'function';\n}\n\n/**\n * Flatten nested middleware arrays with type validation.\n * Uses bounded depth (10 levels) to prevent V8 deoptimization on deeply nested arrays.\n */\nexport function flattenMiddleware(arr: (Middleware | Middleware[])[]): Middleware[] {\n const flattened = arr.flat(10);\n for (const fn of flattened) {\n if (typeof fn !== 'function') {\n throw new TypeError(`Invalid middleware: expected function, got ${typeof fn}`);\n }\n }\n return flattened;\n}\n","/**\n * @nextrush/core - Application Class\n *\n * The Application class is the main entry point for NextRush.\n * It manages middleware registration and plugin installation.\n *\n * @packageDocumentation\n */\n\nimport type { Context, Middleware, Plugin, PluginWithHooks } from '@nextrush/types';\nimport { compose } from './middleware';\n\n/** @internal Symbol keys for route() state — avoids polluting user's ctx.state namespace */\nconst ORIGINAL_PATH = Symbol.for('nextrush.originalPath');\nconst ROUTE_PREFIX = Symbol.for('nextrush.routePrefix');\n\n/**\n * Error handler function type\n */\nexport type ErrorHandler = (error: Error, ctx: Context) => void | Promise<void>;\n\n/**\n * Logger interface for pluggable logging\n */\nexport interface Logger {\n error(...args: unknown[]): void;\n warn(...args: unknown[]): void;\n info(...args: unknown[]): void;\n debug(...args: unknown[]): void;\n}\n\n/** No-op logger — default when no logger is configured */\nconst NOOP_LOGGER: Logger = {\n error(..._args) {\n void _args;\n },\n warn(..._args) {\n void _args;\n },\n info(..._args) {\n void _args;\n },\n debug(..._args) {\n void _args;\n },\n};\n\n/**\n * Application options\n */\nexport interface ApplicationOptions {\n /**\n * Environment mode\n * @default 'development'\n */\n env?: 'development' | 'production' | 'test';\n\n /**\n * Whether to use proxy headers (X-Forwarded-For, etc.)\n * @default false\n */\n proxy?: boolean;\n\n /**\n * Custom logger. Defaults to no-op (silent).\n *\n * @remarks\n * Pass `console` for quick development logging:\n * ```ts\n * const app = createApp({ logger: console });\n * ```\n *\n * For production, provide a structured logger (e.g. pino, winston)\n * that implements the {@link Logger} interface.\n *\n * Adapters and internal hooks use `app.logger` — configuring it here\n * ensures consistent logging across the framework.\n */\n logger?: Logger;\n}\n\n/**\n * Listener callback type\n */\nexport type ListenCallback = () => void;\n\n/**\n * Routable interface - any object with routes() method\n * This allows mounting Router instances without circular dependency\n */\nexport interface Routable {\n routes(): Middleware;\n}\n\n/**\n * The Application class\n *\n * @example\n * ```typescript\n * const app = createApp();\n *\n * app.use(async (ctx) => {\n * ctx.json({ message: 'Hello World' });\n * });\n *\n * // Mount with an adapter\n * listen(app, { port: 3000 });\n * ```\n */\nexport class Application {\n /**\n * Middleware stack\n */\n private readonly middlewareStack: Middleware[] = [];\n\n /**\n * Installed plugins\n */\n private readonly plugins = new Map<string, Plugin>();\n\n /**\n * Custom error handler\n */\n private _errorHandler: ErrorHandler | null = null;\n\n /**\n * Pluggable logger\n */\n readonly logger: Logger;\n\n /**\n * Application options\n */\n readonly options: ApplicationOptions;\n\n /**\n * Whether the app is running\n */\n private _isRunning = false;\n\n constructor(options: ApplicationOptions = {}) {\n this.options = {\n env: options.env ?? 'development',\n proxy: options.proxy ?? false,\n };\n this.logger = options.logger ?? NOOP_LOGGER;\n }\n\n /**\n * Check if running in production\n */\n get isProduction(): boolean {\n return this.options.env === 'production';\n }\n\n /**\n * Check if app is running\n */\n get isRunning(): boolean {\n return this._isRunning;\n }\n\n /**\n * Get middleware count\n */\n get middlewareCount(): number {\n return this.middlewareStack.length;\n }\n\n /**\n * Throws if the app is already running.\n * Prevents configuration mutations after start().\n */\n private assertNotRunning(method: string): void {\n if (this._isRunning) {\n throw new Error(`Cannot call ${method}() after the application has started`);\n }\n }\n\n // ===========================================================================\n // Middleware Registration\n // ===========================================================================\n\n /**\n * Register middleware function(s)\n *\n * @param middleware - Middleware function or array of middleware\n * @returns this for chaining\n *\n * @example\n * ```typescript\n * // Single middleware\n * app.use(async (ctx) => {\n * console.log(ctx.method, ctx.path);\n * await ctx.next();\n * });\n *\n * // Multiple middleware\n * app.use(cors(), helmet(), json());\n * ```\n */\n use(...middleware: Middleware[]): this {\n this.assertNotRunning('use');\n for (const mw of middleware) {\n if (typeof mw !== 'function') {\n throw new TypeError('Middleware must be a function');\n }\n this.middlewareStack.push(mw);\n }\n return this;\n }\n\n // ===========================================================================\n // Router Mounting (Hono-style)\n // ===========================================================================\n\n /**\n * Mount a router at a path prefix\n *\n * This is the Hono-style API for mounting routers directly on the app.\n * The router's routes will only match requests that start with the given prefix.\n *\n * @param path - Path prefix for the router (e.g., '/api/users')\n * @param router - Router instance to mount\n * @returns this for chaining\n *\n * @example\n * ```typescript\n * // Create modular routers\n * const users = createRouter();\n * users.get('/', listUsers);\n * users.get('/:id', getUser);\n * users.post('/', createUser);\n *\n * const posts = createRouter();\n * posts.get('/', listPosts);\n * posts.get('/:id', getPost);\n *\n * // Mount directly on app - clean like Hono!\n * const app = createApp();\n * app.route('/api/users', users);\n * app.route('/api/posts', posts);\n *\n * listen(app, 3000);\n * ```\n */\n route(path: string, router: Routable): this {\n this.assertNotRunning('route');\n // Root mount optimization: skip all prefix processing\n if (path === '/' || path === '') {\n this.middlewareStack.push(router.routes());\n return this;\n }\n\n const routerMiddleware = router.routes();\n // Normalize prefix: ensure leading '/', strip trailing '/'\n let normalizedPrefix = path.endsWith('/') ? path.slice(0, -1) : path;\n if (!normalizedPrefix.startsWith('/')) {\n normalizedPrefix = '/' + normalizedPrefix;\n }\n const prefixLen = normalizedPrefix.length;\n\n // NOTE (DX-3): We cast `ctx` to `{ path: string }` inside this middleware\n // to temporarily strip the `readonly` modifier on `path`. This is intentional:\n // route mounting must adjust ctx.path for the sub-router and restore it\n // afterwards. The Context interface keeps `path` readonly to prevent\n // accidental mutation by user code, but internal mounting is the exception.\n const mountedMiddleware: Middleware = async (ctx, next) => {\n const currentPath = ctx.path;\n\n // Fast prefix check\n if (!currentPath.startsWith(normalizedPrefix)) {\n return next();\n }\n\n // Check prefix boundary (avoid /api/usersxxx matching /api/users)\n const hasCharAfterPrefix = prefixLen < currentPath.length;\n if (hasCharAfterPrefix && currentPath.charCodeAt(prefixLen) !== 47) {\n // 47 = '/'\n return next();\n }\n\n // Direct path manipulation (no Proxy - fast!)\n const adjustedPath = currentPath.slice(prefixLen) || '/';\n (ctx as { path: string }).path = adjustedPath;\n\n // Store original for recovery (Symbol keys avoid ctx.state pollution)\n (ctx.state as Record<symbol, unknown>)[ORIGINAL_PATH] = currentPath;\n (ctx.state as Record<symbol, unknown>)[ROUTE_PREFIX] = normalizedPrefix;\n\n try {\n await routerMiddleware(ctx, async () => {\n // Restore original path before calling downstream middleware\n // so downstream sees the full unmounted path\n (ctx as { path: string }).path = currentPath;\n await next();\n // Re-apply stripped path for router's return path\n (ctx as { path: string }).path = adjustedPath;\n });\n } finally {\n // Restore original path and clean up Symbol state\n (ctx as { path: string }).path = currentPath;\n (ctx.state as Record<symbol, unknown>)[ORIGINAL_PATH] = undefined;\n (ctx.state as Record<symbol, unknown>)[ROUTE_PREFIX] = undefined;\n }\n };\n\n this.middlewareStack.push(mountedMiddleware);\n return this;\n }\n\n // ===========================================================================\n // Error Handling\n // ===========================================================================\n\n /**\n * Set the application error handler.\n *\n * Replaces any previously set handler. Only one error handler is active\n * at a time. For additive error handling, compose logic within a single\n * handler or use the `errorHandler()` middleware from `@nextrush/errors`.\n *\n * @param handler - Error handler function\n * @returns this for chaining\n *\n * @example\n * ```typescript\n * app.setErrorHandler((error, ctx) => {\n * console.error('Request failed:', error);\n *\n * if (error instanceof ValidationError) {\n * ctx.status = 400;\n * ctx.json({ error: error.message, details: error.details });\n * return;\n * }\n *\n * ctx.status = 500;\n * ctx.json({ error: 'Internal Server Error' });\n * });\n * ```\n */\n setErrorHandler(handler: ErrorHandler): this {\n this._errorHandler = handler;\n return this;\n }\n\n /**\n * Set custom error handler.\n *\n * @param handler - Error handler function\n * @returns this for chaining\n *\n * @deprecated Use `setErrorHandler()` instead — `onError()` implies\n * event subscription (additive), but this is a setter (replaces).\n */\n onError(handler: ErrorHandler): this {\n return this.setErrorHandler(handler);\n }\n\n // ===========================================================================\n // Plugin System\n // ===========================================================================\n\n /**\n * Install a plugin.\n *\n * Handles both sync and async `install()` methods automatically.\n * Returns `this` for sync plugins and `Promise<this>` for async ones.\n *\n * @param plugin - Plugin to install\n * @returns this (sync) or Promise<this> (async)\n *\n * @example\n * ```typescript\n * // Sync plugin\n * app.plugin(loggerPlugin({ level: 'info' }));\n *\n * // Async plugin — just await it\n * await app.plugin(databasePlugin({ uri: '...' }));\n * ```\n */\n plugin(plugin: Plugin): this | Promise<this> {\n this.assertNotRunning('plugin');\n if (this.plugins.has(plugin.name)) {\n throw new Error(`Plugin \"${plugin.name}\" is already installed`);\n }\n\n const result = plugin.install(this);\n\n // If install() returned a promise, handle it asynchronously\n if (result instanceof Promise) {\n return result.then(() => {\n this.plugins.set(plugin.name, plugin);\n return this;\n });\n }\n\n this.plugins.set(plugin.name, plugin);\n return this;\n }\n\n /**\n * Install a plugin asynchronously.\n *\n * @param plugin - Plugin to install\n * @returns Promise that resolves when plugin is installed\n *\n * @deprecated Use `plugin()` instead — it handles both sync and async\n * plugins automatically.\n *\n * @example\n * ```typescript\n * await app.plugin(new DatabasePlugin({ connectionString: '...' }));\n * ```\n */\n async pluginAsync(plugin: Plugin): Promise<this> {\n const result = this.plugin(plugin);\n return result instanceof Promise ? result : Promise.resolve(result);\n }\n\n /**\n * Get an installed plugin by name\n *\n * @param name - Plugin name\n * @returns Plugin instance or undefined\n */\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters\n getPlugin<T extends Plugin>(name: string): T | undefined {\n return this.plugins.get(name) as T | undefined;\n }\n\n /**\n * Check if a plugin is installed\n */\n hasPlugin(name: string): boolean {\n return this.plugins.has(name);\n }\n\n // ===========================================================================\n // Request Handler\n // ===========================================================================\n\n /**\n * Create the request handler callback\n *\n * This is the function that adapters call to handle each request.\n * It composes all middleware and returns a function that processes context.\n *\n * **Important:** The middleware stack is snapshot at call time.\n * Middleware or plugins added after `callback()` will NOT take effect\n * on the returned handler. Call `callback()` again to get a\n * handler that includes subsequent registrations.\n *\n * @returns Request handler function\n */\n callback(): (ctx: Context) => Promise<void> {\n const fn = compose(this.middlewareStack);\n\n // Collect plugins that implement lifecycle hooks (once at build time)\n // Validate that hook properties are actually callable functions\n const hookPlugins = Array.from(this.plugins.values()).filter((p): p is PluginWithHooks => {\n const hasHook =\n 'onRequest' in p || 'onResponse' in p || 'onError' in p || 'extendContext' in p;\n if (!hasHook) return false;\n\n // Runtime validation: ensure hook properties are functions\n if ('onRequest' in p && typeof p.onRequest !== 'function') {\n throw new TypeError(\n `Plugin \"${p.name}\": onRequest must be a function, got ${typeof p.onRequest}`\n );\n }\n if ('onResponse' in p && typeof p.onResponse !== 'function') {\n throw new TypeError(\n `Plugin \"${p.name}\": onResponse must be a function, got ${typeof p.onResponse}`\n );\n }\n if ('onError' in p && typeof p.onError !== 'function') {\n throw new TypeError(\n `Plugin \"${p.name}\": onError must be a function, got ${typeof p.onError}`\n );\n }\n if ('extendContext' in p && typeof p.extendContext !== 'function') {\n throw new TypeError(\n `Plugin \"${p.name}\": extendContext must be a function, got ${typeof p.extendContext}`\n );\n }\n return true;\n });\n\n return async (ctx: Context): Promise<void> => {\n try {\n // Extend context and run onRequest hooks\n for (const p of hookPlugins) {\n if (p.extendContext) p.extendContext(ctx);\n if (p.onRequest) await p.onRequest(ctx);\n }\n\n await fn(ctx);\n\n // Run onResponse hooks — isolate errors so all hooks run\n for (const p of hookPlugins) {\n if (p.onResponse) {\n try {\n await p.onResponse(ctx);\n } catch (hookError) {\n this.logger.warn('Plugin onResponse hook threw:', {\n plugin: p.name,\n error: hookError,\n });\n }\n }\n }\n } catch (error) {\n // Run onError hooks\n const err = error instanceof Error ? error : new Error(String(error));\n for (const p of hookPlugins) {\n if (p.onError) {\n try {\n await p.onError(err, ctx);\n } catch (hookError) {\n this.logger.warn('Plugin onError hook threw:', {\n plugin: p.name,\n error: hookError,\n });\n }\n }\n }\n await this.handleError(error, ctx);\n }\n };\n }\n\n /**\n * Handle errors - uses custom handler if set, otherwise default\n */\n private async handleError(error: unknown, ctx: Context): Promise<void> {\n const err = error instanceof Error ? error : new Error(String(error));\n\n // Use custom error handler if set\n if (this._errorHandler) {\n try {\n await this._errorHandler(err, ctx);\n return;\n } catch (handlerError) {\n // If custom handler throws, fall through to default handling\n this.logger.error('Error handler threw:', handlerError);\n }\n }\n\n // Default error handling\n this.defaultErrorHandler(err, ctx);\n }\n\n /**\n * Default error handler\n *\n * Uses the `expose` flag on errors (set by HttpError/NextRushError)\n * to decide whether to include the error message in the response.\n * 4xx errors expose messages by default; 5xx errors never do.\n * This prevents leaking internal details (paths, SQL, stack info)\n * regardless of environment.\n */\n private defaultErrorHandler(error: Error, ctx: Context): void {\n // Always log non-production errors for DX\n if (!this.isProduction) {\n this.logger.error('Request error:', error);\n }\n\n // Determine status — clamp to valid error range (400-599)\n const errorMeta = error as Error & { status?: number; expose?: boolean };\n let status = 500;\n if (typeof errorMeta.status === 'number') {\n const raw = errorMeta.status;\n status = raw >= 400 && raw < 600 ? raw : 500;\n }\n ctx.status = status;\n\n // Use the `expose` flag to decide what message the client sees.\n // HttpError/NextRushError set expose=true for 4xx, false for 5xx.\n // Plain Error never exposes — prevents leaking internal details.\n const expose = errorMeta.expose === true;\n const message = expose ? error.message : 'Internal Server Error';\n\n ctx.json({ error: message });\n }\n\n // ===========================================================================\n // Lifecycle\n // ===========================================================================\n\n /**\n * Mark app as running and freeze configuration.\n *\n * Called by adapters when the server starts listening. After this call,\n * `use()`, `route()`, `plugin()`, and `pluginAsync()` will throw.\n * This prevents unsafe middleware mutations while requests are in flight.\n *\n * To re-enable registration (e.g. for testing), call `close()` first.\n */\n start(): void {\n this._isRunning = true;\n }\n\n /**\n * Graceful shutdown\n * Destroys all plugins that have a destroy method.\n * Uses Promise.allSettled to ensure all plugins are destroyed\n * even if some throw errors.\n *\n * @returns Array of errors from plugins that failed to destroy (empty on success)\n */\n async close(): Promise<Error[]> {\n this._isRunning = false;\n\n // Destroy plugins in reverse order using allSettled for resilience\n const pluginArray = Array.from(this.plugins.values()).reverse();\n const destroyablePlugins = pluginArray.filter(\n (p): p is Plugin & { destroy: () => void | Promise<void> } => typeof p.destroy === 'function'\n );\n\n const destroyPromises = destroyablePlugins.map((p) =>\n Promise.resolve()\n .then(() => p.destroy())\n .catch((err: unknown) => {\n throw err instanceof Error ? err : new Error(String(err));\n })\n );\n\n const results = await Promise.allSettled(destroyPromises);\n this.plugins.clear();\n\n return results\n .filter((r): r is PromiseRejectedResult => r.status === 'rejected')\n .map((r) => (r.reason instanceof Error ? r.reason : new Error(String(r.reason))));\n }\n}\n\n/**\n * Create a new Application instance\n *\n * @param options - Application options\n * @returns New Application instance\n *\n * @example\n * ```typescript\n * const app = createApp();\n * const app = createApp({ env: 'production' });\n * ```\n */\nexport function createApp(options?: ApplicationOptions): Application {\n return new Application(options);\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nextrush/core",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Core functionality for NextRush framework - Application, Context, Middleware",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"nextrush",
|
|
20
|
+
"framework",
|
|
21
|
+
"http",
|
|
22
|
+
"server",
|
|
23
|
+
"middleware",
|
|
24
|
+
"context"
|
|
25
|
+
],
|
|
26
|
+
"author": {
|
|
27
|
+
"name": "Tanzim Hossain",
|
|
28
|
+
"email": "tanzimhossain2@gmail.com",
|
|
29
|
+
"url": "https://github.com/0xTanzim"
|
|
30
|
+
},
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/0xTanzim/nextrush.git",
|
|
35
|
+
"directory": "packages/core"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/0xTanzim/nextrush/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/0xTanzim/nextrush#readme",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=22.0.0"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@nextrush/errors": "3.0.0",
|
|
49
|
+
"@nextrush/types": "3.0.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/node": "^25.5.2",
|
|
53
|
+
"tsup": "^8.5.1",
|
|
54
|
+
"typescript": "^6.0.2",
|
|
55
|
+
"vitest": "^4.1.4"
|
|
56
|
+
},
|
|
57
|
+
"sideEffects": false,
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsup",
|
|
60
|
+
"dev": "tsup --watch",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"test:watch": "vitest --watch",
|
|
63
|
+
"test:coverage": "vitest --coverage",
|
|
64
|
+
"typecheck": "tsc --noEmit",
|
|
65
|
+
"lint": "eslint src --ignore-pattern '**/__tests__/**'",
|
|
66
|
+
"lint:fix": "eslint src --fix --ignore-pattern '**/__tests__/**'",
|
|
67
|
+
"clean": "rm -rf dist .turbo"
|
|
68
|
+
}
|
|
69
|
+
}
|