webspresso 0.0.106 → 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 (118) 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/bin/webspresso.js +17 -7
  12. package/core/app.js +45 -11
  13. package/core/applySchema.js +3 -72
  14. package/core/auth/index.js +4 -1
  15. package/core/auth/jwt.js +85 -13
  16. package/core/auth/magic-link.js +23 -5
  17. package/core/auth/manager.js +7 -1
  18. package/core/auth/redis-session.js +62 -25
  19. package/core/auth/session-manager.js +28 -2
  20. package/core/auth/sessions.js +182 -52
  21. package/core/cache/lru.js +10 -0
  22. package/core/cache/tagged-lru.js +4 -0
  23. package/core/compileSchema.js +3 -68
  24. package/core/compression/compressible.js +53 -6
  25. package/core/compression/negotiate.js +31 -12
  26. package/core/console/eval.js +4 -1
  27. package/core/content/service.js +1 -0
  28. package/core/context/app-context.js +201 -0
  29. package/core/context/index.js +55 -0
  30. package/core/context/request.js +241 -0
  31. package/core/errors/domain.js +20 -2
  32. package/core/errors/http.js +1 -0
  33. package/core/events/dispatcher.js +144 -15
  34. package/core/events/signal.js +9 -0
  35. package/core/health/probes.js +12 -2
  36. package/core/http/hono-adapter.js +1 -1
  37. package/core/http/index.js +10 -1
  38. package/core/http/pipeline.js +519 -334
  39. package/core/index.js +36 -2
  40. package/core/mail/mailer.js +1 -1
  41. package/core/orm/eager-loader.js +170 -55
  42. package/core/orm/index.js +25 -47
  43. package/core/orm/json-fields.js +44 -13
  44. package/core/orm/model.js +18 -1
  45. package/core/orm/query-builder.js +33 -15
  46. package/core/orm/repository.js +59 -12
  47. package/core/orm/schema-helpers.js +59 -0
  48. package/core/orm/scopes.js +54 -4
  49. package/core/orm/transaction.js +109 -14
  50. package/core/plugins/index.js +8 -0
  51. package/core/plugins/plugin-manager.js +1098 -0
  52. package/core/queue/manager.js +55 -27
  53. package/core/rate-limit/stores/memory.js +23 -10
  54. package/core/resilience/circuit-breaker.js +34 -14
  55. package/core/schema-cache.js +2 -64
  56. package/core/services/builtins/auth.js +273 -0
  57. package/core/services/builtins/exchange.js +229 -0
  58. package/core/services/builtins/file-manager.js +178 -0
  59. package/core/services/builtins/mail.js +134 -0
  60. package/core/services/builtins/media.js +196 -0
  61. package/core/services/builtins/system.js +219 -0
  62. package/core/services/discovery.js +133 -0
  63. package/core/services/executor.js +629 -0
  64. package/core/services/index.js +114 -0
  65. package/core/services/loader.js +161 -0
  66. package/core/services/memoize.js +408 -0
  67. package/core/services/registry.js +731 -0
  68. package/core/services/validator.js +424 -0
  69. package/core/shutdown/shutdown-manager.js +194 -114
  70. package/core/ssr/stream.js +109 -16
  71. package/core/stream/text-stream.js +45 -4
  72. package/core/studio/inspector.js +9 -1
  73. package/core/studio/server.js +24 -8
  74. package/core/utils/index.js +110 -0
  75. package/core/utils/string.js +77 -16
  76. package/core/utils/timeout.js +4 -0
  77. package/core/validation/apply.js +74 -0
  78. package/core/validation/cache.js +66 -0
  79. package/core/validation/compiler.js +77 -0
  80. package/core/validation/index.js +40 -1
  81. package/index.d.ts +249 -12
  82. package/index.js +68 -20
  83. package/package.json +18 -8
  84. package/plugins/admin-panel/client/parts/07-model-list.js +5 -2
  85. package/plugins/admin-panel/index.js +24 -11
  86. package/plugins/admin-panel/modules/user-management.js +3 -2
  87. package/plugins/csrf/index.js +2 -1
  88. package/plugins/dashboard/index.js +27 -7
  89. package/src/app-context.js +4 -188
  90. package/src/dev/typegen.js +1 -1
  91. package/src/file-router.js +12 -3
  92. package/src/plugin-manager.js +5 -861
  93. package/src/server.js +30 -34
  94. package/src/services/builtins/auth.js +2 -273
  95. package/src/services/builtins/exchange.js +2 -229
  96. package/src/services/builtins/file-manager.js +2 -178
  97. package/src/services/builtins/mail.js +2 -134
  98. package/src/services/builtins/media.js +2 -196
  99. package/src/services/builtins/system.js +2 -219
  100. package/src/services/discovery.js +2 -125
  101. package/src/services/executor.js +2 -362
  102. package/src/services/index.js +4 -60
  103. package/src/services/loader.js +2 -0
  104. package/src/services/memoize.js +2 -234
  105. package/src/services/registry.js +2 -314
  106. package/src/services/validator.js +2 -273
  107. package/core/utils/array.js +0 -12
  108. package/core/utils/base64url.js +0 -8
  109. package/core/utils/ensure-leading-slash.js +0 -6
  110. package/core/utils/escape-html.js +0 -7
  111. package/core/utils/escape-regexp.js +0 -7
  112. package/core/utils/get-client-ip.js +0 -6
  113. package/core/utils/hmac.js +0 -6
  114. package/core/utils/random.js +0 -9
  115. package/core/utils/safe-compare.js +0 -3
  116. package/core/utils/sha256.js +0 -6
  117. package/core/utils/strip-trailing-slash.js +0 -6
  118. package/core/utils/walk.js +0 -7
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Process-wide app context set by createApp() — use from API handlers, jobs, etc.
3
+ * @module src/app-context
4
+ */
5
+
6
+ const { circuitBreakerRegistry, retry } = require('../resilience');
7
+
8
+ /** @type {{ db: object|null, shutdownManager: object|null, serviceRegistry: object|null, events: object|null, mailer: object|null, cache: object|null, breakers: object|null }} */
9
+ let context = {
10
+ db: null,
11
+ shutdownManager: null,
12
+ serviceRegistry: null,
13
+ events: null,
14
+ mailer: null,
15
+ cache: null,
16
+ breakers: circuitBreakerRegistry,
17
+ };
18
+
19
+ /**
20
+ * Merge into the current context (typically called once from createApp).
21
+ * @param {{ db?: object|null, shutdownManager?: object|null, serviceRegistry?: object|null, events?: object|null, mailer?: object|null, cache?: object|null }} partial
22
+ */
23
+ function setAppContext(partial) {
24
+ context = { ...context, ...partial };
25
+ }
26
+
27
+ /**
28
+ * @returns {{ db: object|null, shutdownManager: object|null, serviceRegistry: object|null, events: object|null, mailer: object|null }}
29
+ */
30
+ function getAppContext() {
31
+ return context;
32
+ }
33
+
34
+ /**
35
+ * Database instance passed to createApp({ db }) (Knex + ORM helpers).
36
+ * @returns {object}
37
+ * @throws {Error} If createApp was not given a db instance
38
+ */
39
+ function getDb() {
40
+ if (!context.db) {
41
+ throw new Error(
42
+ 'No database registered. Create a DB with createDatabase(), then pass it to createApp({ db }). ' +
43
+ 'Or use hasDb() before calling getDb().'
44
+ );
45
+ }
46
+ return context.db;
47
+ }
48
+
49
+ /**
50
+ * @returns {boolean}
51
+ */
52
+ function hasDb() {
53
+ return context.db != null;
54
+ }
55
+
56
+ /**
57
+ * @returns {object|null}
58
+ */
59
+ function getShutdownManager() {
60
+ return context.shutdownManager;
61
+ }
62
+
63
+ /**
64
+ * @returns {boolean}
65
+ */
66
+ function hasShutdownManager() {
67
+ return context.shutdownManager != null;
68
+ }
69
+
70
+ /**
71
+ * Get the registered ServiceRegistry instance
72
+ * @returns {object|null}
73
+ */
74
+ function getServiceRegistry() {
75
+ return context.serviceRegistry;
76
+ }
77
+
78
+ /**
79
+ * Check if a ServiceRegistry is registered
80
+ * @returns {boolean}
81
+ */
82
+ function hasServiceRegistry() {
83
+ return context.serviceRegistry != null;
84
+ }
85
+
86
+ /**
87
+ * Get the registered EventDispatcher instance
88
+ * @returns {object|null}
89
+ */
90
+ function getEvents() {
91
+ return context.events;
92
+ }
93
+
94
+ /**
95
+ * Check if an EventDispatcher is registered
96
+ * @returns {boolean}
97
+ */
98
+ function hasEvents() {
99
+ return context.events != null;
100
+ }
101
+
102
+ /**
103
+ * Clear context (e.g. between tests).
104
+ */
105
+ function resetAppContext() {
106
+ context = {
107
+ db: null,
108
+ shutdownManager: null,
109
+ serviceRegistry: null,
110
+ events: null,
111
+ mailer: null,
112
+ cache: null,
113
+ breakers: circuitBreakerRegistry,
114
+ };
115
+ }
116
+
117
+ /**
118
+ * @returns {object|null}
119
+ */
120
+ function getMailer() {
121
+ return context.mailer;
122
+ }
123
+
124
+ /**
125
+ * @returns {boolean}
126
+ */
127
+ function hasMailer() {
128
+ return context.mailer != null;
129
+ }
130
+
131
+ /**
132
+ * @returns {object|null}
133
+ */
134
+ function getCache() {
135
+ return context.cache;
136
+ }
137
+
138
+ /**
139
+ * @returns {boolean}
140
+ */
141
+ function hasCache() {
142
+ return context.cache != null;
143
+ }
144
+
145
+ /**
146
+ * Express middleware: sets `req.db`, `req.service`, `req.events`, `req.mailer`, and `req.cache` from registered app context.
147
+ * File-based `pages/api/*` routes attach this automatically; use in `setupRoutes`
148
+ * for manually registered handlers that need `req.db` and `req.service`.
149
+ * @type {import('express').RequestHandler}
150
+ */
151
+ function attachDbMiddleware(req, res, next) {
152
+ if (context.db != null) {
153
+ req.db = context.db;
154
+ }
155
+ if (context.events != null) {
156
+ req.events = context.events;
157
+ }
158
+ if (context.mailer != null) {
159
+ req.mailer = context.mailer;
160
+ }
161
+ if (context.cache != null) {
162
+ req.cache = context.cache;
163
+ }
164
+ req.breakers = context.breakers || circuitBreakerRegistry;
165
+ req.breaker = (name, opts) => (context.breakers || circuitBreakerRegistry).getBreaker(name, opts);
166
+ req.retry = retry;
167
+ if (context.serviceRegistry != null) {
168
+ req.service = (name, input, opts) =>
169
+ context.serviceRegistry.call(name, input, req.context || {
170
+ req,
171
+ res,
172
+ db: context.db,
173
+ events: context.events,
174
+ mailer: context.mailer,
175
+ cache: context.cache,
176
+ breakers: req.breakers,
177
+ breaker: req.breaker,
178
+ retry,
179
+ }, opts);
180
+ }
181
+ next();
182
+ }
183
+
184
+ module.exports = {
185
+ setAppContext,
186
+ getAppContext,
187
+ getDb,
188
+ hasDb,
189
+ getShutdownManager,
190
+ hasShutdownManager,
191
+ getServiceRegistry,
192
+ hasServiceRegistry,
193
+ getEvents,
194
+ hasEvents,
195
+ getMailer,
196
+ hasMailer,
197
+ getCache,
198
+ hasCache,
199
+ resetAppContext,
200
+ attachDbMiddleware,
201
+ };
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Webspresso Context Management Layer
5
+ * Process-wide application context and request-scoped AsyncLocalStorage ambient context.
6
+ *
7
+ * @module core/context
8
+ */
9
+
10
+ const appContext = require('./app-context');
11
+ const requestContext = require('./request');
12
+
13
+ module.exports = {
14
+ // App context (process-wide)
15
+ setAppContext: appContext.setAppContext,
16
+ getAppContext: appContext.getAppContext,
17
+ getDb: appContext.getDb,
18
+ hasDb: appContext.hasDb,
19
+ getShutdownManager: appContext.getShutdownManager,
20
+ hasShutdownManager: appContext.hasShutdownManager,
21
+ getServiceRegistry: appContext.getServiceRegistry,
22
+ hasServiceRegistry: appContext.hasServiceRegistry,
23
+ getEvents: appContext.getEvents,
24
+ hasEvents: appContext.hasEvents,
25
+ getMailer: appContext.getMailer,
26
+ hasMailer: appContext.hasMailer,
27
+ getCache: appContext.getCache,
28
+ hasCache: appContext.hasCache,
29
+ resetAppContext: appContext.resetAppContext,
30
+ attachDbMiddleware: appContext.attachDbMiddleware,
31
+
32
+ // Request & ambient execution context (AsyncLocalStorage)
33
+ requestContextStorage: requestContext.requestContextStorage,
34
+ runWithContext: requestContext.runWithContext,
35
+ withContext: requestContext.withContext,
36
+ getContext: requestContext.getContext,
37
+ useContext: requestContext.useContext,
38
+ useDb: requestContext.useDb,
39
+ useAuth: requestContext.useAuth,
40
+ useUser: requestContext.useUser,
41
+ useLogger: requestContext.useLogger,
42
+ useReq: requestContext.useReq,
43
+ useRes: requestContext.useRes,
44
+ useParams: requestContext.useParams,
45
+ useQuery: requestContext.useQuery,
46
+ useService: requestContext.useService,
47
+ useQueue: requestContext.useQueue,
48
+ useCache: requestContext.useCache,
49
+ useBreaker: requestContext.useBreaker,
50
+ useTransaction: requestContext.useTransaction,
51
+
52
+ // Namespace accessors
53
+ appContext,
54
+ requestContext,
55
+ };
@@ -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
+ };
@@ -17,20 +17,38 @@ class ValidationError extends HttpError {
17
17
  * @param {string} [message='Validation failed']
18
18
  * @param {Object} [options]
19
19
  * @param {Record<string, string[]|string>} [options.fields={}] - Field-specific validation error messages
20
+ * @param {Record<string, string[]>} [options.fieldErrors={}] - Detailed array of errors per field
20
21
  * @param {number} [options.status=422]
22
+ * @param {number} [options.statusCode]
21
23
  * @param {string} [options.code='VALIDATION_ERROR']
24
+ * @param {string} [options.service] - Service identifier if originating from services layer
22
25
  * @param {unknown} [options.details]
26
+ * @param {unknown} [options.issues]
23
27
  * @param {unknown} [options.cause]
24
28
  */
25
29
  constructor(message = 'Validation failed', options = {}) {
26
- super(options.status || 422, message, {
30
+ const status = typeof options.status === 'number'
31
+ ? options.status
32
+ : (typeof options.statusCode === 'number' ? options.statusCode : 422);
33
+
34
+ super(status, message, {
27
35
  code: options.code || 'VALIDATION_ERROR',
28
36
  expose: true,
29
37
  ...options,
30
38
  });
31
39
 
40
+ this.statusCode = this.status;
32
41
  this.fields = options.fields || {};
33
- this.issues = options.issues || options.details || [];
42
+ if (options.fieldErrors) {
43
+ this.fieldErrors = options.fieldErrors;
44
+ }
45
+ if (options.issues !== undefined || options.details !== undefined) {
46
+ this.issues = options.issues !== undefined ? options.issues : (options.details || []);
47
+ this.details = options.details !== undefined ? options.details : this.issues;
48
+ }
49
+ if (options.service) {
50
+ this.service = options.service;
51
+ }
34
52
  }
35
53
  }
36
54
 
@@ -23,6 +23,7 @@ class HttpError extends WebspressoError {
23
23
  super(message, options);
24
24
 
25
25
  this.status = typeof status === 'number' ? status : 500;
26
+ this.statusCode = this.status;
26
27
  this.headers = options.headers || {};
27
28
  this.expose = options.expose !== undefined ? Boolean(options.expose) : this.status < 500;
28
29
  }