json-rest-api 1.0.13 → 1.0.15

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/connectors_ideas/fastify.js +66 -68
  2. package/connectors_ideas/koa-plugin.js +50 -51
  3. package/eslint.config.js +13 -0
  4. package/examples/url-override-example.js +63 -63
  5. package/index.js +13 -13
  6. package/lib/enhanced-logger.js +51 -51
  7. package/lib/error-formatter.js +61 -61
  8. package/lib/rest-api-errors.js +28 -28
  9. package/package.json +9 -5
  10. package/plugins/core/bulk-operations-plugin.js +142 -145
  11. package/plugins/core/connectors/express-plugin.js +210 -217
  12. package/plugins/core/connectors/lib/busboy-detector.js +84 -84
  13. package/plugins/core/connectors/lib/formidable-detector.js +50 -50
  14. package/plugins/core/connectors/lib/request-helpers.js +74 -74
  15. package/plugins/core/file-handling-plugin.js +125 -125
  16. package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
  17. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
  18. package/plugins/core/lib/anyapi/schema-utils.js +101 -101
  19. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
  20. package/plugins/core/lib/dbTablesOperations.js +134 -135
  21. package/plugins/core/lib/fractional-positioning.js +74 -76
  22. package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
  23. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
  24. package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
  25. package/plugins/core/lib/querying/knex-process-includes.js +50 -50
  26. package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
  27. package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
  28. package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
  29. package/plugins/core/lib/querying/knex-window-queries.js +89 -89
  30. package/plugins/core/lib/querying/url-helpers.js +35 -35
  31. package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
  32. package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
  33. package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
  34. package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
  35. package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
  36. package/plugins/core/lib/querying-writing/field-utils.js +44 -42
  37. package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
  38. package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
  39. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
  40. package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
  41. package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
  42. package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
  43. package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
  44. package/plugins/core/lib/storage/storage-adapter.js +68 -68
  45. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
  46. package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
  47. package/plugins/core/lib/writing/relationship-processor.js +58 -58
  48. package/plugins/core/rest-api-access.js +8 -8
  49. package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
  50. package/plugins/core/rest-api-cors-plugin.js +170 -171
  51. package/plugins/core/rest-api-knex-plugin.js +482 -493
  52. package/plugins/core/rest-api-multihome-plugin.js +247 -251
  53. package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
  54. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
  55. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
  56. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
  57. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
  58. package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
  59. package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
  60. package/plugins/core/rest-api-plugin-methods/common.js +295 -297
  61. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
  62. package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
  63. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
  64. package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
  65. package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
  66. package/plugins/core/rest-api-plugin-methods/get.js +86 -86
  67. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
  68. package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
  69. package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
  70. package/plugins/core/rest-api-plugin-methods/post.js +61 -62
  71. package/plugins/core/rest-api-plugin-methods/put.js +86 -87
  72. package/plugins/core/rest-api-plugin-methods/query.js +88 -88
  73. package/plugins/core/rest-api-plugin-methods/release.js +5 -6
  74. package/plugins/core/rest-api-plugin.js +76 -78
  75. package/plugins/core/rest-api-positioning-plugin.js +251 -252
  76. package/plugins/core/socketio-plugin.js +240 -236
  77. package/plugins/storage/local-storage.js +103 -103
  78. package/plugins/storage/s3-storage.js +41 -42
  79. package/quickTest.js +37 -37
  80. package/scripts/debug-anyapi-review-filter.mjs +18 -18
  81. package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
  82. package/scripts/inspect-anyapi-descriptor.mjs +10 -10
  83. package/scripts/run-docs.js +112 -113
  84. package/test-debug.js +28 -28
  85. package/tests/TEST_TEMPLATE.test.js +51 -51
  86. package/tests/anyapi-basic.test.js +42 -42
  87. package/tests/anyapi-cursor-pagination.test.js +48 -48
  88. package/tests/bulk-operations.test.js +208 -208
  89. package/tests/computed-fields.test.js +204 -204
  90. package/tests/cors.test.js +412 -413
  91. package/tests/curl.test.js +275 -276
  92. package/tests/custom-idproperty-relationships.test.js +266 -266
  93. package/tests/field-getters.test.js +132 -132
  94. package/tests/field-setters.test.js +110 -110
  95. package/tests/fixtures/api-configs.js +699 -699
  96. package/tests/helpers/storage-mode.js +32 -32
  97. package/tests/helpers/test-utils.js +76 -76
  98. package/tests/include-depth-validation.test.js +76 -76
  99. package/tests/includes.test.js +145 -145
  100. package/tests/multihome.test.js +175 -175
  101. package/tests/nested-includes.test.js +255 -255
  102. package/tests/pagination-cursor-multifield.test.js +164 -164
  103. package/tests/pagination-enhanced.test.js +225 -225
  104. package/tests/polymorphic-relationships.test.js +96 -96
  105. package/tests/polymorphic-simple.test.js +44 -44
  106. package/tests/positioning-advanced.test.js +226 -226
  107. package/tests/positioning-extended.test.js +399 -399
  108. package/tests/positioning-multifilter.test.js +204 -204
  109. package/tests/positioning.test.js +266 -266
  110. package/tests/queries.test.js +184 -184
  111. package/tests/query-limits.test.js +154 -155
  112. package/tests/relationship-endpoints.test.js +127 -127
  113. package/tests/relationships.test.js +178 -179
  114. package/tests/rest-api-access.test.js +86 -86
  115. package/tests/rest-api.test.js +103 -103
  116. package/tests/searchschema-merge.test.js +93 -93
  117. package/tests/socketio.test.js +326 -326
  118. package/tests/virtual-fields.test.js +164 -164
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Express Plugin for Hooked API
3
- *
3
+ *
4
4
  * This plugin creates HTTP endpoints for your REST API by listening to
5
5
  * route registrations from the REST API plugin and creating Express routes.
6
- *
6
+ *
7
7
  * Features:
8
8
  * - Automatic route creation via addRoute hook
9
9
  * - JSON:API compliant request/response handling
@@ -14,121 +14,118 @@
14
14
  * - File upload support with busboy or formidable
15
15
  */
16
16
 
17
- import { requirePackage } from 'hooked-api';
18
- import { parseJsonApiQuery } from '../lib/querying-writing/connectors-query-parser.js';
19
- import { createContext } from './lib/request-helpers.js';
20
- import { createEnhancedLogger } from '../../../lib/enhanced-logger.js';
17
+ import { requirePackage } from 'hooked-api'
18
+ import { parseJsonApiQuery } from '../lib/querying-writing/connectors-query-parser.js'
19
+ import { createContext } from './lib/request-helpers.js'
20
+ import { createEnhancedLogger } from '../../../lib/enhanced-logger.js'
21
21
 
22
22
  export const ExpressPlugin = {
23
23
  name: 'express',
24
24
  dependencies: ['rest-api'],
25
-
26
- async install({ on, vars, helpers, pluginOptions, log, scopes, api, runHooks, addHook }) {
27
-
28
-
29
- addHook('release', 'releaseHook', {},
30
- async ({ api }) => {} // TODO: Anything to do here?
31
- );
32
25
 
26
+ async install ({ on, vars, helpers, pluginOptions, log, scopes, api, runHooks, addHook }) {
27
+ addHook('release', 'releaseHook', {},
28
+ async ({ api }) => {} // TODO: Anything to do here?
29
+ )
33
30
 
34
31
  // Dynamic import for Express
35
- let express;
32
+ let express
36
33
  try {
37
- express = (await import('express')).default;
34
+ express = (await import('express')).default
38
35
  } catch (e) {
39
- requirePackage('express', 'express',
40
- 'Express.js is required for HTTP server functionality. This is a peer dependency.');
36
+ requirePackage('express', 'express',
37
+ 'Express.js is required for HTTP server functionality. This is a peer dependency.')
41
38
  }
42
39
  // Enhance the logger
43
- const enhancedLog = createEnhancedLogger(log, {
44
- logFullErrors: true,
45
- includeStack: true
46
- });
47
-
40
+ const enhancedLog = createEnhancedLogger(log, {
41
+ logFullErrors: true,
42
+ includeStack: true
43
+ })
44
+
48
45
  // Initialize express namespace
49
46
  if (!api.http) {
50
- api.http = {};
47
+ api.http = {}
51
48
  }
52
- api.http.express = {};
53
-
54
- const expressOptions = pluginOptions || {};
55
-
49
+ api.http.express = {}
50
+
51
+ const expressOptions = pluginOptions || {}
52
+
56
53
  // Get mountPath from options (this is now a transport concern)
57
- const mountPath = expressOptions.mountPath || '';
58
- const basePath = mountPath; // Keep basePath for internal use
59
- const strictContentType = expressOptions.strictContentType !== false;
60
- const requestSizeLimit = expressOptions.requestSizeLimit || '1mb';
61
-
54
+ const mountPath = expressOptions.mountPath || ''
55
+ const basePath = mountPath // Keep basePath for internal use
56
+ const strictContentType = expressOptions.strictContentType !== false
57
+ const requestSizeLimit = expressOptions.requestSizeLimit || '1mb'
58
+
62
59
  // Set transport information for other plugins
63
60
  vars.transport = {
64
61
  type: 'express',
65
62
  matchAll: '*', // Express wildcard pattern for matching all routes
66
- mountPath: mountPath // Transport-specific mount path
67
- };
68
-
63
+ mountPath // Transport-specific mount path
64
+ }
65
+
69
66
  // Register file detector if enabled
70
67
  if (expressOptions.enableFileUploads !== false && api.rest?.registerFileDetector) {
71
- const parserLib = expressOptions.fileParser || 'busboy';
72
- const parserOptions = expressOptions.fileParserOptions || {};
73
-
74
- let detector;
75
-
68
+ const parserLib = expressOptions.fileParser || 'busboy'
69
+ const parserOptions = expressOptions.fileParserOptions || {}
70
+
71
+ let detector
72
+
76
73
  if (parserLib === 'busboy') {
77
74
  try {
78
- const { createBusboyDetector } = await import('../lib/busboy-detector.js');
79
- detector = createBusboyDetector(parserOptions);
75
+ const { createBusboyDetector } = await import('../lib/busboy-detector.js')
76
+ detector = createBusboyDetector(parserOptions)
80
77
  } catch (e) {
81
- log.warn('Busboy not installed. Install with: npm install busboy');
78
+ log.warn('Busboy not installed. Install with: npm install busboy')
82
79
  }
83
80
  } else if (parserLib === 'formidable') {
84
81
  try {
85
- const { createFormidableDetector } = await import('../lib/formidable-detector.js');
86
- detector = createFormidableDetector(parserOptions);
82
+ const { createFormidableDetector } = await import('../lib/formidable-detector.js')
83
+ detector = createFormidableDetector(parserOptions)
87
84
  } catch (e) {
88
- log.warn('Formidable not installed. Install with: npm install formidable');
85
+ log.warn('Formidable not installed. Install with: npm install formidable')
89
86
  }
90
87
  }
91
-
88
+
92
89
  if (detector) {
93
90
  api.rest.registerFileDetector({
94
91
  name: `express-${detector.name}`,
95
92
  detect: (params, context) => {
96
- if (!context || !context.raw || !context.raw.req) return false;
97
- const detectParams = { ...params, _expressReq: context.raw.req };
98
- return detector.detect(detectParams);
93
+ if (!context || !context.raw || !context.raw.req) return false
94
+ const detectParams = { ...params, _expressReq: context.raw.req }
95
+ return detector.detect(detectParams)
99
96
  },
100
97
  parse: (params, context) => {
101
- const parseParams = { ...params, _expressReq: context.raw.req, _expressRes: context.raw.res };
102
- return detector.parse(parseParams);
98
+ const parseParams = { ...params, _expressReq: context.raw.req, _expressRes: context.raw.res }
99
+ return detector.parse(parseParams)
103
100
  }
104
- });
105
- log.info(`Express plugin registered file detector: ${detector.name}`);
101
+ })
102
+ log.info(`Express plugin registered file detector: ${detector.name}`)
106
103
  }
107
104
  }
108
-
105
+
109
106
  // Create Express routers
110
- const router = expressOptions.router || express.Router();
111
- const notFoundRouter = express.Router();
112
-
107
+ const router = expressOptions.router || express.Router()
108
+ const notFoundRouter = express.Router()
109
+
113
110
  // Add body parsing middleware
114
- router.use(express.json({
111
+ router.use(express.json({
115
112
  limit: requestSizeLimit,
116
113
  type: ['application/json', 'application/vnd.api+json']
117
- }));
118
-
114
+ }))
115
+
119
116
  // Add transport hook middleware
120
117
  router.use(async (req, res, next) => {
121
- const context = createContext(req, res, 'express');
122
-
118
+ const context = createContext(req, res, 'express')
119
+
123
120
  // Check for Express middleware override first
124
121
  if (req.urlPrefixOverride) {
125
- context.urlPrefixOverride = req.urlPrefixOverride;
122
+ context.urlPrefixOverride = req.urlPrefixOverride
126
123
  }
127
-
124
+
128
125
  // Calculate and cache URL prefix (getUrlPrefix handles all logic)
129
- const { getUrlPrefix } = await import('../lib/querying/url-helpers.js');
130
- context.urlPrefix = getUrlPrefix(context, { vars: { transport: { mountPath: basePath } } }, req);
131
-
126
+ const { getUrlPrefix } = await import('../lib/querying/url-helpers.js')
127
+ context.urlPrefix = getUrlPrefix(context, { vars: { transport: { mountPath: basePath } } }, req)
128
+
132
129
  // Transport-specific data for hooks
133
130
  const transportData = {
134
131
  request: {
@@ -144,20 +141,20 @@ export const ExpressPlugin = {
144
141
  headers: {},
145
142
  status: null
146
143
  }
147
- };
148
-
144
+ }
145
+
149
146
  // Add transport data to context
150
- context.transport = transportData;
147
+ context.transport = transportData
151
148
 
152
149
  // Run transport hooks - don't check return value for consistency with other hooks
153
150
  // Hooks communicate via context flags (rejection, handled) not return values
154
- await runHooks('transport:request', context);
151
+ await runHooks('transport:request', context)
155
152
 
156
153
  // Check if request was rejected (e.g., authentication failure)
157
154
  if (context.rejection) {
158
155
  // Apply response headers from hooks
159
156
  if (transportData.response.headers) {
160
- res.set(transportData.response.headers);
157
+ res.set(transportData.response.headers)
161
158
  }
162
159
  return res.status(context.rejection.status || 500).json({
163
160
  errors: [{
@@ -165,27 +162,27 @@ export const ExpressPlugin = {
165
162
  title: context.rejection.title || 'Request Rejected',
166
163
  detail: context.rejection.message
167
164
  }]
168
- });
165
+ })
169
166
  }
170
167
 
171
168
  // Check if request was already handled by a hook
172
169
  if (context.handled) {
173
- return;
170
+ return
174
171
  }
175
-
172
+
176
173
  // Store transport data and context for later use
177
- req.transportData = transportData;
178
- req.context = context;
179
- next();
180
- });
181
-
174
+ req.transportData = transportData
175
+ req.context = context
176
+ next()
177
+ })
178
+
182
179
  // Content type validation middleware
183
180
  if (strictContentType) {
184
181
  router.use((req, res, next) => {
185
182
  if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
186
- const contentType = req.get('Content-Type');
187
-
188
- if (contentType && !contentType.includes('application/vnd.api+json') &&
183
+ const contentType = req.get('Content-Type')
184
+
185
+ if (contentType && !contentType.includes('application/vnd.api+json') &&
189
186
  !contentType.includes('application/json') && !contentType.includes('multipart/form-data')) {
190
187
  return res.status(415).json({
191
188
  errors: [{
@@ -193,13 +190,13 @@ export const ExpressPlugin = {
193
190
  title: 'Unsupported Media Type',
194
191
  detail: 'Content-Type must be application/vnd.api+json or application/json'
195
192
  }]
196
- });
193
+ })
197
194
  }
198
195
  }
199
- next();
200
- });
196
+ next()
197
+ })
201
198
  }
202
-
199
+
203
200
  /**
204
201
  * Error handler - maps REST API errors to HTTP responses
205
202
  */
@@ -208,99 +205,98 @@ export const ExpressPlugin = {
208
205
  method: req.method,
209
206
  path: req.path,
210
207
  url: req.url
211
- });
212
-
213
- let status = 500;
214
- let errorResponse = {
208
+ })
209
+
210
+ let status = 500
211
+ const errorResponse = {
215
212
  errors: [{
216
213
  status: '500',
217
214
  title: 'Internal Server Error',
218
215
  detail: error.message
219
216
  }]
220
- };
221
-
217
+ }
218
+
222
219
  // Map error codes to HTTP status
223
220
  if (error.code === 'REST_API_VALIDATION') {
224
- status = 422;
221
+ status = 422
225
222
  errorResponse.errors = [{
226
223
  status: '422',
227
224
  title: 'Validation Error',
228
225
  detail: error.message,
229
226
  source: error.details
230
- }];
227
+ }]
231
228
  if (error.details?.violations) {
232
229
  errorResponse.errors = error.details.violations.map(v => ({
233
230
  status: '422',
234
231
  title: 'Validation Error',
235
232
  detail: v.message,
236
233
  source: { pointer: v.field }
237
- }));
234
+ }))
238
235
  }
239
236
  } else if (error.code === 'REST_API_RESOURCE') {
240
237
  switch (error.subtype) {
241
238
  case 'not_found':
242
- status = 404;
243
- errorResponse.errors[0].status = '404';
244
- errorResponse.errors[0].title = 'Not Found';
245
- break;
239
+ status = 404
240
+ errorResponse.errors[0].status = '404'
241
+ errorResponse.errors[0].title = 'Not Found'
242
+ break
246
243
  case 'conflict':
247
- status = 409;
248
- errorResponse.errors[0].status = '409';
249
- errorResponse.errors[0].title = 'Conflict';
250
- break;
244
+ status = 409
245
+ errorResponse.errors[0].status = '409'
246
+ errorResponse.errors[0].title = 'Conflict'
247
+ break
251
248
  case 'forbidden':
252
- status = 403;
253
- errorResponse.errors[0].status = '403';
254
- errorResponse.errors[0].title = 'Forbidden';
255
- break;
249
+ status = 403
250
+ errorResponse.errors[0].status = '403'
251
+ errorResponse.errors[0].title = 'Forbidden'
252
+ break
256
253
  default:
257
- status = 400;
258
- errorResponse.errors[0].status = '400';
259
- errorResponse.errors[0].title = 'Bad Request';
254
+ status = 400
255
+ errorResponse.errors[0].status = '400'
256
+ errorResponse.errors[0].title = 'Bad Request'
260
257
  }
261
258
  } else if (error.code === 'REST_API_PAYLOAD') {
262
- status = 400;
259
+ status = 400
263
260
  errorResponse.errors = [{
264
261
  status: '400',
265
262
  title: 'Bad Request',
266
263
  detail: error.message,
267
264
  source: { pointer: error.path }
268
- }];
265
+ }]
269
266
  }
270
-
267
+
271
268
  // Run transport:response hook for errors
272
269
  if (req.transportData && req.context) {
273
- req.transportData.response.status = status;
274
- req.transportData.response.body = errorResponse;
275
- req.context.transport = req.transportData;
276
- await runHooks('transport:response', req.context);
277
-
270
+ req.transportData.response.status = status
271
+ req.transportData.response.body = errorResponse
272
+ req.context.transport = req.transportData
273
+ await runHooks('transport:response', req.context)
274
+
278
275
  // Apply response headers from hooks
279
276
  if (req.transportData.response.headers) {
280
- res.set(req.transportData.response.headers);
277
+ res.set(req.transportData.response.headers)
281
278
  }
282
279
  }
283
-
284
- res.status(status).json(errorResponse);
285
- };
286
-
280
+
281
+ res.status(status).json(errorResponse)
282
+ }
283
+
287
284
  /**
288
285
  * Listen to addRoute hook to create Express routes
289
286
  */
290
287
  addHook('addRoute', 'expressRouteCreator', {}, async ({ context }) => {
291
- const { method, path, handler } = context;
292
-
288
+ const { method, path, handler } = context
289
+
293
290
  // Apply any global before middleware
294
- const beforeMiddleware = expressOptions.middleware?.beforeAll || [];
295
-
291
+ const beforeMiddleware = expressOptions.middleware?.beforeAll || []
292
+
296
293
  try {
297
-
298
294
  // Extract the handler logic into a shared function to keep it DRY (Don't Repeat Yourself).
299
295
  const expressHandler = async (req, res) => {
300
296
  try {
301
297
  // Extract request data
302
- const queryString = req.url.split('?')[1] || '';
303
- const context = req.context || createContext(req, res, 'express');
298
+ const queryString = req.url.split('?')[1] || ''
299
+ const context = req.context || createContext(req, res, 'express')
304
300
 
305
301
  // Call the generic handler
306
302
  const result = await handler({
@@ -309,85 +305,85 @@ export const ExpressPlugin = {
309
305
  params: req.params,
310
306
  body: req.body,
311
307
  context
312
- });
308
+ })
313
309
 
314
310
  // Prepare response status
315
- let responseStatus = 200;
311
+ let responseStatus = 200
316
312
  if (result && typeof result.statusCode === 'number') {
317
- responseStatus = result.statusCode;
313
+ responseStatus = result.statusCode
318
314
  } else if (req.method === 'POST' && result) {
319
315
  // POST with content returns 201 Created
320
- responseStatus = 201;
316
+ responseStatus = 201
321
317
  } else if (req.method === 'POST' && !result) {
322
318
  // POST without content returns 204 No Content
323
- responseStatus = 204;
319
+ responseStatus = 204
324
320
  } else if (req.method === 'DELETE') {
325
321
  // DELETE always returns 204 No Content
326
- responseStatus = 204;
322
+ responseStatus = 204
327
323
  } else if ((req.method === 'PUT' || req.method === 'PATCH') && !result) {
328
324
  // PUT/PATCH return 204 when no content is returned
329
- responseStatus = 204;
325
+ responseStatus = 204
330
326
  }
331
327
 
332
328
  // Apply headers from the handler result
333
329
  if (result && result.headers) {
334
- res.set(result.headers);
330
+ res.set(result.headers)
335
331
  }
336
332
 
337
333
  // Update transport data for response (if transport data exists)
338
334
  if (req.transportData) {
339
- req.transportData.response.status = responseStatus;
340
- req.transportData.response.body = result;
341
- context.transport = req.transportData;
342
- await runHooks('transport:response', context);
335
+ req.transportData.response.status = responseStatus
336
+ req.transportData.response.body = result
337
+ context.transport = req.transportData
338
+ await runHooks('transport:response', context)
343
339
  if (req.transportData.response.headers) {
344
- res.set(req.transportData.response.headers);
340
+ res.set(req.transportData.response.headers)
345
341
  }
346
342
  }
347
343
 
348
344
  // Set content type
349
- res.set('Content-Type', 'application/vnd.api+json');
345
+ res.set('Content-Type', 'application/vnd.api+json')
350
346
 
351
347
  // Set Location header for successful POST requests
352
348
  if (req.method === 'POST' && context.id) {
353
349
  // Extract scopeName from the path (e.g., /api/countries -> countries)
354
- const pathParts = path.split('/');
355
- const scopeName = pathParts[pathParts.length - 1];
356
-
350
+ const pathParts = path.split('/')
351
+ const scopeName = pathParts[pathParts.length - 1]
352
+
357
353
  // Set Location header for any successful POST (201 or 204)
358
354
  if (helpers.getLocation) {
359
- const location = helpers.getLocation({ scopeName, id: context.id });
360
- const baseUrl = context.urlPrefix || basePath;
361
- res.set('Location', `${baseUrl}${location}`);
355
+ const location = helpers.getLocation({ scopeName, id: context.id })
356
+ const baseUrl = context.urlPrefix || basePath
357
+ res.set('Location', `${baseUrl}${location}`)
362
358
  }
363
359
  }
364
360
 
365
361
  // Handle response based on status
366
362
  if (responseStatus === 204) {
367
- res.sendStatus(204);
363
+ res.sendStatus(204)
368
364
  } else {
369
365
  // If result has a body property, that's what we should send
370
366
  // This happens when handler returns { statusCode, body, headers }
371
- const responseBody = result && result.body !== undefined ? result.body : result;
372
- res.status(responseStatus).json(responseBody);
367
+ const responseBody = result && result.body !== undefined ? result.body : result
368
+ res.status(responseStatus).json(responseBody)
373
369
  }
374
370
  } catch (error) {
375
- handleError(error, req, res);
371
+ handleError(error, req, res)
376
372
  }
377
- };
373
+ }
378
374
 
379
375
  // CRITICAL: Express routing method selection - wildcard vs specific routes
380
- //
376
+ //
381
377
  // Express provides two different ways to register routes:
382
378
  // 1. router.METHOD(path, handler) - e.g., router.get('/users', handler)
383
379
  // - Only responds to the SPECIFIC HTTP method
384
380
  // - Perfect for normal REST endpoints
385
- //
386
- // 2. router.use(path, handler)
381
+ //
382
+ // 2. router.use(path, handler)
387
383
  // - Responds to ALL HTTP methods
388
384
  // - Needed for wildcard paths that must handle any method
389
385
  //
390
- // The CORS plugin needs wildcard routes because it must handle OPTIONS
386
+ // The CORS plugin needs wildcard routes because it must handle OPTIONS
391
387
  // requests for ANY path under the API prefix, even paths that don't exist
392
388
  // as defined routes. For example:
393
389
  // - Defined route: GET /api/users
@@ -400,22 +396,21 @@ export const ExpressPlugin = {
400
396
  // - We need to catch ALL paths (using '*' or '/api/*')
401
397
  // - We need to handle a SPECIFIC method (e.g., OPTIONS)
402
398
  // - router.options('*') would NOT work for paths like '/api/some/nested/path'
403
-
404
399
 
405
400
  // Since router.use() responds to ALL methods, we need a wrapper
406
401
  // that only handles our specific method (e.g., OPTIONS)
407
402
  const methodSpecificMiddleware = (req, res, next) => {
408
403
  if (req.method.toLowerCase() === method.toLowerCase()) {
409
- expressHandler(req, res);
404
+ expressHandler(req, res)
410
405
  } else {
411
406
  // Not our method, pass to next middleware
412
- next();
407
+ next()
413
408
  }
414
- };
409
+ }
415
410
  // IMPORTANT: We do NOT need to pass a path to router.use() here!
416
411
  // When no path is provided, router.use() matches ALL requests
417
412
  // This is exactly what we want for wildcard routes
418
- router.use(...beforeMiddleware, methodSpecificMiddleware);
413
+ router.use(...beforeMiddleware, methodSpecificMiddleware)
419
414
  } else {
420
415
  // This is a normal route with a specific path (e.g., '/api/users')
421
416
  // We use router.METHOD() because:
@@ -426,48 +421,46 @@ export const ExpressPlugin = {
426
421
  // Note: Routes from RestApiPlugin already include the full path with mountPath
427
422
  // So we use them as-is without adding basePath to avoid double-prefixing
428
423
 
429
- router[method.toLowerCase()](path, ...beforeMiddleware, expressHandler);
424
+ router[method.toLowerCase()](path, ...beforeMiddleware, expressHandler)
430
425
 
431
426
  // Debug logging for route registration
432
427
 
433
428
  // Check if the route was actually added
434
429
  if (router.stack) {
435
- const lastRoute = router.stack[router.stack.length - 1];
430
+ const lastRoute = router.stack[router.stack.length - 1]
436
431
  }
437
432
  }
438
-
439
-
440
433
  } catch (routeError) {
441
434
  log.error('[EXPRESS DEBUG] Error creating route:', {
442
435
  error: routeError.message,
443
436
  stack: routeError.stack,
444
- path: path,
437
+ path,
445
438
  method: method.toLowerCase()
446
- });
447
- throw routeError;
439
+ })
440
+ throw routeError
448
441
  }
449
-
450
- const logPath = path === vars.transport.matchAll
442
+
443
+ const logPath = path === vars.transport.matchAll
451
444
  ? '(all paths)' // router.use() with no path matches everything
452
- : path;
453
- log.trace(`Express route created: ${method} ${logPath}`);
454
- });
455
-
445
+ : path
446
+ log.trace(`Express route created: ${method} ${logPath}`)
447
+ })
448
+
456
449
  // Apply global middleware if configured
457
- let finalRouter = router;
450
+ let finalRouter = router
458
451
  if (expressOptions.middleware?.beforeAll) {
459
- const globalRouter = express.Router();
460
- globalRouter.use(...expressOptions.middleware.beforeAll);
461
- globalRouter.use(router);
462
- finalRouter = globalRouter;
452
+ const globalRouter = express.Router()
453
+ globalRouter.use(...expressOptions.middleware.beforeAll)
454
+ globalRouter.use(router)
455
+ finalRouter = globalRouter
463
456
  }
464
-
457
+
465
458
  // Set up 404 handler in separate router (unless disabled)
466
459
  if (expressOptions.handle404 !== false) {
467
460
  notFoundRouter.use(async (req, res, next) => {
468
461
  if (basePath && req.path.startsWith(basePath)) {
469
462
  // Create minimal context for 404
470
- const context = createContext(req, res, 'express');
463
+ const context = createContext(req, res, 'express')
471
464
  const transportData = {
472
465
  request: {
473
466
  method: req.method,
@@ -486,54 +479,54 @@ export const ExpressPlugin = {
486
479
  }]
487
480
  }
488
481
  }
489
- };
490
-
482
+ }
483
+
491
484
  // Add transport data to context
492
- context.transport = transportData;
493
-
485
+ context.transport = transportData
486
+
494
487
  // Run transport:response hook for 404
495
- await runHooks('transport:response', context);
496
-
488
+ await runHooks('transport:response', context)
489
+
497
490
  // Apply response headers from hooks
498
491
  if (transportData.response.headers) {
499
- res.set(transportData.response.headers);
492
+ res.set(transportData.response.headers)
500
493
  }
501
-
502
- res.status(404).json(transportData.response.body);
494
+
495
+ res.status(404).json(transportData.response.body)
503
496
  } else {
504
- next();
497
+ next()
505
498
  }
506
- });
499
+ })
507
500
  }
508
-
501
+
509
502
  // Store routers in api.http.express namespace
510
- api.http.express.router = finalRouter;
511
- api.http.express.notFoundRouter = notFoundRouter;
512
-
503
+ api.http.express.router = finalRouter
504
+ api.http.express.notFoundRouter = notFoundRouter
505
+
513
506
  // Allow other plugins to add middleware before 404 handler
514
- const beforeNotFoundMiddleware = [];
507
+ const beforeNotFoundMiddleware = []
515
508
  api.http.express.beforeNotFound = (middleware) => {
516
- beforeNotFoundMiddleware.push(middleware);
517
- };
518
-
509
+ beforeNotFoundMiddleware.push(middleware)
510
+ }
511
+
519
512
  // Add convenient mounting method
520
513
  api.http.express.mount = (app, path = '') => {
521
514
  // Mount main router with all routes
522
- app.use(path, finalRouter);
523
-
515
+ app.use(path, finalRouter)
516
+
524
517
  // Mount any middleware that should come before 404
525
518
  beforeNotFoundMiddleware.forEach(middleware => {
526
- app.use(path, middleware);
527
- });
528
-
519
+ app.use(path, middleware)
520
+ })
521
+
529
522
  // Mount 404 handler router after all other routes (if enabled)
530
523
  if (expressOptions.handle404 !== false) {
531
- app.use(path, notFoundRouter);
524
+ app.use(path, notFoundRouter)
532
525
  }
533
-
534
- log.info(`Express routes mounted at ${path || '/'}`);
535
- };
536
-
537
- log.info('Express plugin initialized successfully');
526
+
527
+ log.info(`Express routes mounted at ${path || '/'}`)
528
+ }
529
+
530
+ log.info('Express plugin initialized successfully')
538
531
  }
539
- };
532
+ }