webspresso 0.0.107 → 0.0.108

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.
Files changed (91) hide show
  1. package/.agents/AGENTS.md +4 -1
  2. package/.agents/CACHE.md +1 -0
  3. package/.agents/CONTEXT.md +152 -0
  4. package/.agents/ORM.md +74 -0
  5. package/.agents/SHUTDOWN.md +8 -1
  6. package/.agents/skills/webspresso-framework/SKILL.md +2 -1
  7. package/.agents/skills/webspresso-orm-and-database/SKILL.md +9 -0
  8. package/.agents/skills/webspresso-routing-and-api/SKILL.md +18 -2
  9. package/.agents/skills/webspresso-services/SKILL.md +36 -3
  10. package/AGENTS.md +6 -3
  11. package/core/app.js +7 -5
  12. package/core/applySchema.js +3 -72
  13. package/core/cache/lru.js +10 -0
  14. package/core/cache/tagged-lru.js +4 -0
  15. package/core/compileSchema.js +3 -68
  16. package/core/compression/compressible.js +53 -6
  17. package/core/compression/negotiate.js +31 -12
  18. package/core/context/app-context.js +201 -0
  19. package/core/context/index.js +55 -0
  20. package/core/context/request.js +241 -0
  21. package/core/events/dispatcher.js +144 -15
  22. package/core/events/signal.js +9 -0
  23. package/core/health/probes.js +12 -2
  24. package/core/http/index.js +10 -1
  25. package/core/http/pipeline.js +519 -368
  26. package/core/index.js +24 -1
  27. package/core/orm/eager-loader.js +90 -55
  28. package/core/orm/index.js +3 -39
  29. package/core/orm/model.js +6 -1
  30. package/core/orm/schema-helpers.js +59 -0
  31. package/core/orm/transaction.js +109 -14
  32. package/core/plugins/index.js +8 -0
  33. package/core/plugins/plugin-manager.js +1098 -0
  34. package/core/queue/manager.js +55 -27
  35. package/core/rate-limit/stores/memory.js +23 -10
  36. package/core/resilience/circuit-breaker.js +34 -14
  37. package/core/schema-cache.js +2 -64
  38. package/core/services/builtins/auth.js +273 -0
  39. package/core/services/builtins/exchange.js +229 -0
  40. package/core/services/builtins/file-manager.js +178 -0
  41. package/core/services/builtins/mail.js +134 -0
  42. package/core/services/builtins/media.js +196 -0
  43. package/core/services/builtins/system.js +219 -0
  44. package/core/services/discovery.js +133 -0
  45. package/core/services/executor.js +629 -0
  46. package/core/services/index.js +110 -5
  47. package/core/services/loader.js +161 -0
  48. package/core/services/memoize.js +408 -0
  49. package/core/services/registry.js +731 -0
  50. package/core/services/validator.js +424 -0
  51. package/core/shutdown/shutdown-manager.js +146 -85
  52. package/core/ssr/stream.js +109 -16
  53. package/core/stream/text-stream.js +45 -4
  54. package/core/utils/index.js +110 -0
  55. package/core/utils/timeout.js +4 -0
  56. package/core/validation/apply.js +74 -0
  57. package/core/validation/cache.js +66 -0
  58. package/core/validation/compiler.js +77 -0
  59. package/core/validation/index.js +22 -1
  60. package/index.d.ts +77 -4
  61. package/index.js +64 -20
  62. package/package.json +1 -1
  63. package/src/app-context.js +4 -196
  64. package/src/dev/typegen.js +1 -1
  65. package/src/plugin-manager.js +5 -1086
  66. package/src/server.js +4 -1
  67. package/src/services/builtins/auth.js +2 -273
  68. package/src/services/builtins/exchange.js +2 -229
  69. package/src/services/builtins/file-manager.js +2 -178
  70. package/src/services/builtins/mail.js +2 -134
  71. package/src/services/builtins/media.js +2 -196
  72. package/src/services/builtins/system.js +2 -219
  73. package/src/services/discovery.js +2 -133
  74. package/src/services/executor.js +2 -575
  75. package/src/services/index.js +4 -109
  76. package/src/services/loader.js +1 -119
  77. package/src/services/memoize.js +2 -408
  78. package/src/services/registry.js +1 -711
  79. package/src/services/validator.js +2 -424
  80. package/core/utils/array.js +0 -12
  81. package/core/utils/base64url.js +0 -8
  82. package/core/utils/ensure-leading-slash.js +0 -6
  83. package/core/utils/escape-html.js +0 -7
  84. package/core/utils/escape-regexp.js +0 -7
  85. package/core/utils/get-client-ip.js +0 -6
  86. package/core/utils/hmac.js +0 -6
  87. package/core/utils/random.js +0 -9
  88. package/core/utils/safe-compare.js +0 -3
  89. package/core/utils/sha256.js +0 -6
  90. package/core/utils/strip-trailing-slash.js +0 -6
  91. package/core/utils/walk.js +0 -7
@@ -0,0 +1,241 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Webspresso Ambient Request & Execution Context
5
+ * Zero-dependency execution context powered by Node.js AsyncLocalStorage.
6
+ * Provides composable hooks (useContext, useDb, useAuth, useUser, useLogger, useService,
7
+ * useReq, useRes, useParams, useQuery, useQueue, useCache, useBreaker, useTransaction, withContext)
8
+ * across loaders, services, models, and background workers without prop drilling.
9
+ *
10
+ * @module core/context/request
11
+ */
12
+
13
+ const { AsyncLocalStorage } = require('async_hooks');
14
+
15
+ const requestContextStorage = new AsyncLocalStorage();
16
+
17
+ /**
18
+ * Runs a callback within an ambient request or execution context
19
+ * @param {Object} context - Execution context payload
20
+ * @param {Function} fn - Function to execute within context
21
+ * @returns {*} Return value of fn
22
+ */
23
+ function runWithContext(context, fn) {
24
+ return requestContextStorage.run(context || {}, fn);
25
+ }
26
+
27
+ /**
28
+ * Run a callback with shallow-merged overrides on top of the current ambient context
29
+ * @param {Object} overrides - Object containing fields to override in current context
30
+ * @param {Function} fn - Function to execute
31
+ * @returns {*} Return value of fn
32
+ */
33
+ function withContext(overrides, fn) {
34
+ const current = requestContextStorage.getStore() || {};
35
+ const merged = { ...current, ...(overrides || {}) };
36
+ return requestContextStorage.run(merged, fn);
37
+ }
38
+
39
+ /**
40
+ * Get the current active ambient context (returns null if outside context)
41
+ * @returns {Object|null}
42
+ */
43
+ function getContext() {
44
+ return requestContextStorage.getStore() || null;
45
+ }
46
+
47
+ /**
48
+ * Hook to access the ambient execution context
49
+ * @returns {Object} Active context object
50
+ * @throws {Error} If called outside of an active execution context
51
+ */
52
+ function useContext() {
53
+ const store = requestContextStorage.getStore();
54
+ if (!store) {
55
+ throw new Error('useContext() must be called within an active request, job, or execution context.');
56
+ }
57
+ return store;
58
+ }
59
+
60
+ /**
61
+ * Hook to access the ambient database instance
62
+ * @returns {Object|null} Database / ORM instance or null
63
+ */
64
+ function useDb() {
65
+ const store = requestContextStorage.getStore();
66
+ return store?.db || store?.req?.db || null;
67
+ }
68
+
69
+ /**
70
+ * Hook to access the ambient authenticated user / principal
71
+ * @returns {Object|null} User / Auth object or null
72
+ */
73
+ function useAuth() {
74
+ const store = requestContextStorage.getStore();
75
+ return (
76
+ store?.user ||
77
+ store?.auth?.user ||
78
+ store?.auth ||
79
+ store?.req?.user ||
80
+ store?.req?.auth?.user ||
81
+ store?.req?.auth ||
82
+ null
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Hook to access the ambient authenticated user
88
+ * Alias / dedicated helper for useAuth()
89
+ * @returns {Object|null} User object or null
90
+ */
91
+ function useUser() {
92
+ return useAuth();
93
+ }
94
+
95
+ /**
96
+ * Hook to access the ambient logger
97
+ * @returns {Object} Logger instance
98
+ */
99
+ function useLogger() {
100
+ const store = requestContextStorage.getStore();
101
+ return store?.logger || store?.req?.logger || console;
102
+ }
103
+
104
+ /**
105
+ * Hook to access the ambient HTTP request object
106
+ * @returns {Object|null} Express/Hono request object or null if outside HTTP request
107
+ */
108
+ function useReq() {
109
+ const store = requestContextStorage.getStore();
110
+ return store?.req || null;
111
+ }
112
+
113
+ /**
114
+ * Hook to access the ambient HTTP response object
115
+ * @returns {Object|null} Express/Hono response object or null if outside HTTP request
116
+ */
117
+ function useRes() {
118
+ const store = requestContextStorage.getStore();
119
+ return store?.res || null;
120
+ }
121
+
122
+ /**
123
+ * Hook to access the ambient route parameters
124
+ * @returns {Object} Route parameters map (e.g. { id: '123' })
125
+ */
126
+ function useParams() {
127
+ const store = requestContextStorage.getStore();
128
+ return store?.params || store?.req?.params || {};
129
+ }
130
+
131
+ /**
132
+ * Hook to access the ambient URL query parameters
133
+ * @returns {Object} URL query parameters map
134
+ */
135
+ function useQuery() {
136
+ const store = requestContextStorage.getStore();
137
+ return store?.query || store?.req?.query || {};
138
+ }
139
+
140
+ /**
141
+ * Hook to invoke a registered service using the ambient context
142
+ * @param {string} name - Service name (e.g. 'user.get')
143
+ * @param {*} [input={}] - Input payload
144
+ * @param {Object} [opts] - Optional execution options
145
+ * @returns {Promise<*>}
146
+ */
147
+ async function useService(name, input = {}, opts) {
148
+ const store = requestContextStorage.getStore();
149
+ if (store?.service && typeof store.service === 'function') {
150
+ return store.service(name, input, opts);
151
+ }
152
+ if (store?.req?.service && typeof store.req.service === 'function') {
153
+ return store.req.service(name, input, opts);
154
+ }
155
+ throw new Error(`useService() failed: no service registry available in active context.`);
156
+ }
157
+
158
+ /**
159
+ * Hook to access the ambient background queue
160
+ * @returns {Object|null}
161
+ */
162
+ function useQueue() {
163
+ const store = requestContextStorage.getStore();
164
+ return store?.queue || store?.req?.queue || null;
165
+ }
166
+
167
+ /**
168
+ * Hook to access the ambient cache
169
+ * @returns {Object|null}
170
+ */
171
+ function useCache() {
172
+ const store = requestContextStorage.getStore();
173
+ return store?.cache || store?.req?.cache || null;
174
+ }
175
+
176
+ /**
177
+ * Hook to access or create a circuit breaker by name
178
+ * @param {string} name - Circuit breaker name
179
+ * @param {Object} [options] - Circuit breaker options
180
+ * @returns {Object|null} CircuitBreaker instance or null
181
+ */
182
+ function useBreaker(name, options) {
183
+ const store = requestContextStorage.getStore();
184
+ if (store?.breaker && typeof store.breaker === 'function') {
185
+ return store.breaker(name, options);
186
+ }
187
+ if (store?.req?.breaker && typeof store.req.breaker === 'function') {
188
+ return store.req.breaker(name, options);
189
+ }
190
+ try {
191
+ const { circuitBreakerRegistry } = require('../resilience');
192
+ return circuitBreakerRegistry.getBreaker(name, options);
193
+ } catch (_) {
194
+ return null;
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Hook to execute a callback within a managed transaction on the ambient database
200
+ * Automatically binds transactional context to ambient hooks inside the callback.
201
+ * @param {function(trx: any): Promise<*>} callback - Transaction callback
202
+ * @returns {Promise<*>} Result of callback
203
+ */
204
+ async function useTransaction(callback) {
205
+ const db = useDb();
206
+ if (!db) {
207
+ throw new Error('useTransaction() failed: no database instance available in active context.');
208
+ }
209
+
210
+ if (typeof db.transaction === 'function') {
211
+ return db.transaction(async (trx) => {
212
+ const current = requestContextStorage.getStore() || {};
213
+ const txDb = trx.db || trx;
214
+ const txCtx = { ...current, db: txDb, trx: trx.trx || trx };
215
+ return requestContextStorage.run(txCtx, () => callback(trx));
216
+ });
217
+ }
218
+
219
+ throw new Error('useTransaction() failed: db does not support transaction().');
220
+ }
221
+
222
+ module.exports = {
223
+ requestContextStorage,
224
+ runWithContext,
225
+ withContext,
226
+ getContext,
227
+ useContext,
228
+ useDb,
229
+ useAuth,
230
+ useUser,
231
+ useLogger,
232
+ useReq,
233
+ useRes,
234
+ useParams,
235
+ useQuery,
236
+ useService,
237
+ useQueue,
238
+ useCache,
239
+ useBreaker,
240
+ useTransaction,
241
+ };
@@ -324,7 +324,7 @@ class EventDispatcher extends EventEmitter {
324
324
  } catch (err) {
325
325
  // Emit error on dispatcher or log
326
326
  if (this.listenerCount('error') > 0) {
327
- super.emit('error', err);
327
+ this.emit('error', err);
328
328
  } else if (this.logger && typeof this.logger.error === 'function') {
329
329
  this.logger.error(`[EventDispatcher] Error in listener for event "${event}":`, err);
330
330
  }
@@ -355,10 +355,19 @@ class EventDispatcher extends EventEmitter {
355
355
  const meta = { event, sender, timestamp: Date.now() };
356
356
 
357
357
  const promises = listeners.map(async (listener) => {
358
- if (listener.isAny) {
359
- return listener.handler(event, payload, meta);
358
+ try {
359
+ if (listener.isAny) {
360
+ return await listener.handler(event, payload, meta);
361
+ }
362
+ return await listener.handler(payload, meta);
363
+ } catch (err) {
364
+ if (this.listenerCount('error') > 0) {
365
+ this.emit('error', err);
366
+ } else if (this.logger && typeof this.logger.error === 'function') {
367
+ this.logger.error(`[EventDispatcher] Error in async listener for event "${event}":`, err);
368
+ }
369
+ throw err;
360
370
  }
361
- return listener.handler(payload, meta);
362
371
  });
363
372
 
364
373
  return Promise.allSettled(promises);
@@ -399,10 +408,19 @@ class EventDispatcher extends EventEmitter {
399
408
  const results = [];
400
409
 
401
410
  for (const listener of listeners) {
402
- const res = listener.isAny
403
- ? await listener.handler(event, payload, meta)
404
- : await listener.handler(payload, meta);
405
- results.push(res);
411
+ try {
412
+ const res = listener.isAny
413
+ ? await listener.handler(event, payload, meta)
414
+ : await listener.handler(payload, meta);
415
+ results.push(res);
416
+ } catch (err) {
417
+ if (this.listenerCount('error') > 0) {
418
+ this.emit('error', err);
419
+ } else if (this.logger && typeof this.logger.error === 'function') {
420
+ this.logger.error(`[EventDispatcher] Error in serial listener for event "${event}":`, err);
421
+ }
422
+ throw err;
423
+ }
406
424
  }
407
425
 
408
426
  return results;
@@ -429,18 +447,113 @@ class EventDispatcher extends EventEmitter {
429
447
  let currentData = payload;
430
448
 
431
449
  for (const listener of listeners) {
432
- const res = listener.isAny
433
- ? await listener.handler(event, currentData, meta)
434
- : await listener.handler(currentData, meta);
450
+ try {
451
+ const res = listener.isAny
452
+ ? await listener.handler(event, currentData, meta)
453
+ : await listener.handler(currentData, meta);
435
454
 
436
- if (res !== undefined) {
437
- currentData = res;
455
+ if (res !== undefined) {
456
+ currentData = res;
457
+ }
458
+ } catch (err) {
459
+ if (this.listenerCount('error') > 0) {
460
+ this.emit('error', err);
461
+ } else if (this.logger && typeof this.logger.error === 'function') {
462
+ this.logger.error(`[EventDispatcher] Error in waterfall listener for event "${event}":`, err);
463
+ }
464
+ throw err;
438
465
  }
439
466
  }
440
467
 
441
468
  return currentData;
442
469
  }
443
470
 
471
+ /**
472
+ * Asynchronously wait for a matching event to be emitted
473
+ * @param {string} event - Event name or pattern
474
+ * @param {Object|number|Function} [optionsOrTimeout=5000] - Timeout in ms, predicate, or options
475
+ * @param {number} [optionsOrTimeout.timeout=5000] - Timeout in ms (0 for indefinite)
476
+ * @param {Function} [optionsOrTimeout.filter] - Predicate (data, meta) => boolean
477
+ * @param {AbortSignal} [optionsOrTimeout.signal] - AbortSignal for cancellation
478
+ * @param {any} [optionsOrTimeout.sender] - Filter by sender
479
+ * @returns {Promise<{ data: any, meta: Object }>}
480
+ */
481
+ waitFor(event, optionsOrTimeout = {}) {
482
+ let timeoutMs = 5000;
483
+ let filter = null;
484
+ let signal = null;
485
+ let sender = undefined;
486
+
487
+ if (typeof optionsOrTimeout === 'number') {
488
+ timeoutMs = optionsOrTimeout;
489
+ } else if (typeof optionsOrTimeout === 'function') {
490
+ filter = optionsOrTimeout;
491
+ } else if (typeof optionsOrTimeout === 'object' && optionsOrTimeout !== null) {
492
+ if (optionsOrTimeout.timeout !== undefined) timeoutMs = optionsOrTimeout.timeout;
493
+ if (typeof optionsOrTimeout.filter === 'function') filter = optionsOrTimeout.filter;
494
+ if (optionsOrTimeout.signal) signal = optionsOrTimeout.signal;
495
+ if (optionsOrTimeout.sender !== undefined) sender = optionsOrTimeout.sender;
496
+ }
497
+
498
+ return new Promise((resolve, reject) => {
499
+ if (signal && signal.aborted) {
500
+ return reject(new Error(`waitFor('${event}') aborted before execution`));
501
+ }
502
+
503
+ let timeoutTimer = null;
504
+ let unsubscribe = null;
505
+ let abortHandler = null;
506
+
507
+ const cleanup = () => {
508
+ if (timeoutTimer) {
509
+ clearTimeout(timeoutTimer);
510
+ timeoutTimer = null;
511
+ }
512
+ if (typeof unsubscribe === 'function') {
513
+ unsubscribe();
514
+ unsubscribe = null;
515
+ }
516
+ if (signal && abortHandler) {
517
+ signal.removeEventListener('abort', abortHandler);
518
+ abortHandler = null;
519
+ }
520
+ };
521
+
522
+ if (timeoutMs > 0) {
523
+ timeoutTimer = setTimeout(() => {
524
+ cleanup();
525
+ reject(new Error(`Timeout of ${timeoutMs}ms exceeded while waiting for event '${event}'`));
526
+ }, timeoutMs);
527
+ if (timeoutTimer && typeof timeoutTimer.unref === 'function') {
528
+ timeoutTimer.unref();
529
+ }
530
+ }
531
+
532
+ if (signal) {
533
+ abortHandler = () => {
534
+ cleanup();
535
+ reject(new Error(`waitFor('${event}') was aborted`));
536
+ };
537
+ signal.addEventListener('abort', abortHandler, { once: true });
538
+ }
539
+
540
+ const handler = (data, meta) => {
541
+ try {
542
+ if (filter && !filter(data, meta)) {
543
+ return;
544
+ }
545
+ cleanup();
546
+ resolve({ data, meta });
547
+ } catch (err) {
548
+ cleanup();
549
+ reject(err);
550
+ }
551
+ };
552
+
553
+ unsubscribe = this.on(event, handler, { sender });
554
+ });
555
+ }
556
+
444
557
  /**
445
558
  * Set or update background queue manager for job dispatching
446
559
  * @param {any} queueManager
@@ -462,11 +575,27 @@ class EventDispatcher extends EventEmitter {
462
575
  listenAndQueue(event, jobName, queueOptions = {}) {
463
576
  return this.on(event, (data) => {
464
577
  if (!this.queue || typeof this.queue.dispatch !== 'function') {
465
- throw new Error(
578
+ const err = new Error(
466
579
  `[EventDispatcher] Cannot queue job '${jobName}' for event '${event}'. No QueueManager is attached.`
467
580
  );
581
+ if (this.listenerCount('error') > 0) {
582
+ this.emit('error', err);
583
+ } else if (this.logger && typeof this.logger.error === 'function') {
584
+ this.logger.error(err.message);
585
+ }
586
+ return Promise.reject(err);
587
+ }
588
+ const dispatchPromise = this.queue.dispatch(jobName, data, queueOptions);
589
+ if (dispatchPromise && typeof dispatchPromise.catch === 'function') {
590
+ dispatchPromise.catch((dispatchErr) => {
591
+ if (this.listenerCount('error') > 0) {
592
+ this.emit('error', dispatchErr);
593
+ } else if (this.logger && typeof this.logger.error === 'function') {
594
+ this.logger.error(`[EventDispatcher] Failed to dispatch queued job '${jobName}':`, dispatchErr);
595
+ }
596
+ });
468
597
  }
469
- return this.queue.dispatch(jobName, data, queueOptions);
598
+ return dispatchPromise;
470
599
  });
471
600
  }
472
601
 
@@ -79,6 +79,15 @@ class Signal {
79
79
  async dispatch(data, options = {}) {
80
80
  return this.sendAsync(data, options);
81
81
  }
82
+
83
+ /**
84
+ * Asynchronously wait for this signal to be emitted
85
+ * @param {Object|number|Function} [optionsOrTimeout]
86
+ * @returns {Promise<{ data: any, meta: Object }>}
87
+ */
88
+ waitFor(optionsOrTimeout) {
89
+ return this.dispatcher.waitFor(this.name, optionsOrTimeout);
90
+ }
82
91
  }
83
92
 
84
93
  /**
@@ -112,9 +112,15 @@ async function probeDatabase(db, timeoutMs = 3000) {
112
112
  timer = setTimeout(() => {
113
113
  reject(new Error(`Database ping timed out after ${timeoutMs}ms`));
114
114
  }, timeoutMs);
115
+ if (typeof timer?.unref === 'function') timer.unref();
115
116
  });
116
117
 
117
- await Promise.race([knex.raw('SELECT 1'), timeoutPromise]);
118
+ const pingPromise = Promise.resolve(knex.raw('SELECT 1'));
119
+ if (typeof pingPromise.catch === 'function') {
120
+ pingPromise.catch(() => {});
121
+ }
122
+
123
+ await Promise.race([pingPromise, timeoutPromise]);
118
124
  const latencyMs = Date.now() - startTime;
119
125
  return { status: 'up', latencyMs };
120
126
  } catch (err) {
@@ -170,9 +176,13 @@ async function probeCustomChecks(checks = {}, context = {}, timeoutMs = 3000) {
170
176
  try {
171
177
  const timeoutPromise = new Promise((_, reject) => {
172
178
  timer = setTimeout(() => reject(new Error(`Health check "${name}" timed out after ${timeoutMs}ms`)), timeoutMs);
179
+ if (typeof timer?.unref === 'function') timer.unref();
173
180
  });
174
181
 
175
- const res = await Promise.race([Promise.resolve(checkFn(context)), timeoutPromise]);
182
+ const checkPromise = Promise.resolve().then(() => checkFn(context));
183
+ checkPromise.catch(() => {});
184
+
185
+ const res = await Promise.race([checkPromise, timeoutPromise]);
176
186
  const latencyMs = Date.now() - startTime;
177
187
 
178
188
  if (res === false || (res && typeof res === 'object' && res.status === 'down')) {
@@ -7,7 +7,14 @@
7
7
 
8
8
  const { HttpAdapter } = require('./adapter');
9
9
  const { ExpressHttpAdapter, createExpressHttpAdapter } = require('./express-adapter');
10
- const { runMiddlewares, enrichRouteError, executeApiPipeline, executePagePipeline } = require('./pipeline');
10
+ const {
11
+ createPipelineRunner,
12
+ runMiddlewares,
13
+ enrichRouteError,
14
+ validateRequestInput,
15
+ executeApiPipeline,
16
+ executePagePipeline,
17
+ } = require('./pipeline');
11
18
 
12
19
  let _honoModule = null;
13
20
  function getHonoModule() {
@@ -21,8 +28,10 @@ module.exports = {
21
28
  HttpAdapter,
22
29
  ExpressHttpAdapter,
23
30
  createExpressHttpAdapter,
31
+ createPipelineRunner,
24
32
  runMiddlewares,
25
33
  enrichRouteError,
34
+ validateRequestInput,
26
35
  executeApiPipeline,
27
36
  executePagePipeline,
28
37
  };