@morojs/moro 1.8.8 → 1.8.9

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.
@@ -127,6 +127,7 @@ export declare class UWebSocketsHttpServer {
127
127
  private createMoroResponse;
128
128
  private readBody;
129
129
  private executeMiddleware;
130
+ private dispatchMiddleware;
130
131
  use(middleware: Middleware): void;
131
132
  setRequestTracking(enabled: boolean): void;
132
133
  setHookManager(hookManager: any): void;
@@ -673,10 +673,14 @@ export class UWebSocketsHttpServer {
673
673
  // rejection). Registration of the response hooks above still stands.
674
674
  if (httpRes.headersSent)
675
675
  return;
676
- // Execute global middleware chain
676
+ // Execute global middleware chain. executeMiddleware returns undefined
677
+ // when the whole chain completed synchronously, so a sync chain costs
678
+ // zero promise allocations.
677
679
  if (this.globalMiddleware.length > 0) {
678
680
  httpReq.materialize();
679
- await this.executeMiddleware(this.globalMiddleware, httpReq, httpRes);
681
+ const mwResult = this.executeMiddleware(this.globalMiddleware, httpReq, httpRes);
682
+ if (mwResult)
683
+ await mwResult;
680
684
  if (httpRes.headersSent)
681
685
  return;
682
686
  }
@@ -1586,27 +1590,105 @@ export class UWebSocketsHttpServer {
1586
1590
  }
1587
1591
  });
1588
1592
  }
1589
- async executeMiddleware(middleware, req, res) {
1590
- for (const mw of middleware) {
1593
+ // Middleware execution with Express-compatible error propagation - identical
1594
+ // semantics to MoroHttpServer/MoroEngineServer so a route behaves the same on
1595
+ // every backend. Supports 3-arg (req, res, next), 4-arg error middleware
1596
+ // (err, req, res, next), and next(err) to skip forward to the next error
1597
+ // middleware. Previously this adapter called EVERY middleware with three
1598
+ // arguments, so registering a 4-arg error handler passed `req` as `err` and
1599
+ // broke every ordinary request; with no 4-arg middleware registered the
1600
+ // behaviour here is unchanged (next(err) aborts the chain and propagates to
1601
+ // the request handler's catch).
1602
+ //
1603
+ // Sync-aware dispatch: middleware that calls next() synchronously (or throws
1604
+ // synchronously) advances the chain in a plain loop with ZERO promise
1605
+ // allocations; a promise is only created when a middleware actually completes
1606
+ // asynchronously. Returns undefined when the whole chain ran synchronously so
1607
+ // the caller can skip its await.
1608
+ executeMiddleware(middleware, req, res) {
1609
+ return this.dispatchMiddleware(middleware, req, res, 0, undefined);
1610
+ }
1611
+ dispatchMiddleware(middleware, req, res, startIndex, initialError) {
1612
+ const len = middleware.length;
1613
+ let activeError = initialError;
1614
+ let i = startIndex;
1615
+ while (i < len) {
1591
1616
  if (res.headersSent)
1592
- break;
1593
- await new Promise((resolve, reject) => {
1594
- try {
1595
- const result = mw(req, res, (err) => {
1596
- if (err)
1597
- reject(err);
1598
- else
1599
- resolve();
1600
- });
1601
- // Handle async middleware
1602
- if (result && typeof result.then === 'function') {
1603
- result.then(() => resolve()).catch(reject);
1604
- }
1617
+ return;
1618
+ const mw = middleware[i];
1619
+ i++;
1620
+ const isErrorHandler = mw.length >= 4;
1621
+ // Non-error middleware is skipped while an error is active; error middleware
1622
+ // is skipped while no error is active. Matches Express semantics.
1623
+ if (activeError !== undefined && !isErrorHandler)
1624
+ continue;
1625
+ if (activeError === undefined && isErrorHandler)
1626
+ continue;
1627
+ let settled = false;
1628
+ let settledError = undefined;
1629
+ let asyncResolve;
1630
+ const next = (err) => {
1631
+ if (settled)
1632
+ return;
1633
+ settled = true;
1634
+ settledError = err;
1635
+ if (asyncResolve)
1636
+ asyncResolve(err);
1637
+ };
1638
+ let result;
1639
+ try {
1640
+ result = isErrorHandler ? mw(activeError, req, res, next) : mw(req, res, next);
1641
+ }
1642
+ catch (err) {
1643
+ if (!settled) {
1644
+ settled = true;
1645
+ settledError = err;
1605
1646
  }
1606
- catch (error) {
1607
- reject(error);
1647
+ }
1648
+ const isThenable = result && typeof result.then === 'function';
1649
+ if (settled) {
1650
+ // Completed synchronously (next() called sync, or threw sync). A still
1651
+ // pending returned promise no longer gates the chain once next() has
1652
+ // been called; swallow late rejections so they never go unhandled.
1653
+ if (isThenable) {
1654
+ result.then(undefined, () => { });
1608
1655
  }
1609
- });
1656
+ activeError = settledError;
1657
+ continue;
1658
+ }
1659
+ if (!isThenable && res.headersSent) {
1660
+ // Terminal middleware (Express style): it answered the request and
1661
+ // deliberately never calls next(). The chain is finished - skip
1662
+ // allocating a promise that nothing would ever settle.
1663
+ return;
1664
+ }
1665
+ if (!isThenable) {
1666
+ // Sync-looking middleware that neither called next() nor returned a
1667
+ // promise: it will call next() later (e.g. from an event callback).
1668
+ // Fall back to a promise for the remainder of the chain.
1669
+ const resumeIndex = i;
1670
+ return new Promise(resolve => {
1671
+ asyncResolve = resolve;
1672
+ }).then(err => this.dispatchMiddleware(middleware, req, res, resumeIndex, err));
1673
+ }
1674
+ // Async middleware: completion is next() OR promise settle, whichever
1675
+ // happens first (same semantics as the previous implementation).
1676
+ const resumeIndex = i;
1677
+ return new Promise(resolve => {
1678
+ asyncResolve = resolve;
1679
+ result.then(() => {
1680
+ if (!settled)
1681
+ next();
1682
+ }, (err) => {
1683
+ if (!settled)
1684
+ next(err);
1685
+ });
1686
+ }).then(err => this.dispatchMiddleware(middleware, req, res, resumeIndex, err));
1687
+ }
1688
+ // If an error is still unhandled after the chain, re-throw so the request
1689
+ // handler's catch can invoke the registered errorHandler / default 500.
1690
+ if (activeError !== undefined) {
1691
+ throw activeError;
1610
1692
  }
1611
1693
  }
1612
1694
  // Public API - matches MoroHttpServer interface