json-rest-api 1.0.21 → 1.0.23

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 (88) hide show
  1. package/CLAUDE.md +1 -2
  2. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
  3. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
  4. package/docs/API.md +51 -14
  5. package/docs/COMPARISON.md +2 -2
  6. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +28 -1
  7. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
  8. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
  9. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
  10. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
  11. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
  12. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
  13. package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
  14. package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
  15. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +250 -0
  16. package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
  17. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
  18. package/docs/GUIDE/index.md +18 -9
  19. package/docs/QUICKSTART.md +5 -1
  20. package/docs/index.md +1 -1
  21. package/index.js +3 -2
  22. package/package.json +4 -4
  23. package/plugins/core/connectors/express-plugin.js +97 -184
  24. package/plugins/core/connectors/fastify-plugin.js +206 -0
  25. package/plugins/core/connectors/lib/connector-core.js +195 -0
  26. package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
  27. package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
  28. package/plugins/core/lib/dbIntrospection.js +1078 -0
  29. package/plugins/core/lib/dbTablesOperations.js +1104 -160
  30. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -2
  31. package/plugins/core/lib/querying/knex-process-includes.js +3 -4
  32. package/plugins/core/lib/querying/knex-query-helpers.js +17 -71
  33. package/plugins/core/lib/querying/knex-relationship-includes.js +466 -206
  34. package/plugins/core/lib/querying/query-builder-utils.js +19 -0
  35. package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
  36. package/plugins/core/lib/querying/sort-helpers.js +33 -0
  37. package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
  38. package/plugins/core/lib/querying/url-helpers.js +8 -2
  39. package/plugins/core/lib/querying-writing/compile-schemas.js +12 -1
  40. package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -13
  41. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
  42. package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
  43. package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
  44. package/plugins/core/lib/storage/canonical-storage-mapping.js +113 -0
  45. package/plugins/core/lib/storage/storage-adapter.js +79 -2
  46. package/plugins/core/lib/storage/storage-mapping.js +204 -0
  47. package/plugins/core/rest-api-anyapi-knex-plugin.js +355 -228
  48. package/plugins/core/rest-api-autofilter-plugin.js +414 -0
  49. package/plugins/core/rest-api-knex-plugin.js +249 -211
  50. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
  51. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
  52. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
  53. package/plugins/core/rest-api-plugin-methods/common.js +121 -83
  54. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
  55. package/plugins/core/rest-api-plugin-methods/get.js +23 -14
  56. package/plugins/core/rest-api-plugin-methods/patch.js +28 -12
  57. package/plugins/core/rest-api-plugin-methods/post.js +13 -8
  58. package/plugins/core/rest-api-plugin-methods/put.js +45 -21
  59. package/plugins/core/rest-api-plugin-methods/query.js +15 -53
  60. package/plugins/core/rest-api-plugin.js +2 -0
  61. package/plugins/core/rest-api-positioning-plugin.js +1 -1
  62. package/plugins/core/rest-api-query-projections-plugin.js +130 -0
  63. package/plugins/core/socketio-plugin.js +1 -3
  64. package/tests/autofilter.test.js +463 -0
  65. package/tests/custom-id-simplified-post.test.js +75 -0
  66. package/tests/custom-idproperty-relationships.test.js +33 -0
  67. package/tests/db-introspection.test.js +518 -0
  68. package/tests/db-table-operations.test.js +781 -0
  69. package/tests/fastify-plugin.test.js +141 -0
  70. package/tests/field-getters.test.js +1 -2
  71. package/tests/field-setters.test.js +0 -4
  72. package/tests/fixtures/api-configs.js +188 -219
  73. package/tests/helpers/fake-fastify.js +99 -0
  74. package/tests/http-connectors-parity.test.js +235 -0
  75. package/tests/projected-fields.test.js +319 -0
  76. package/tests/relationships.test.js +3 -3
  77. package/tests/searchschema-knex-behavior.test.js +187 -0
  78. package/tests/searchschema-merge.test.js +21 -69
  79. package/tests/storage-mapping.test.js +210 -0
  80. package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
  81. package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
  82. package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
  83. package/plugins/core/rest-api-access.js +0 -459
  84. package/plugins/core/rest-api-multihome-plugin.js +0 -314
  85. package/tests/multihome.test.js +0 -553
  86. package/tests/multihome.test.js.bak +0 -550
  87. package/tests/ownership-optout.test.js +0 -76
  88. package/tests/rest-api-access.test.js +0 -206
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-rest-api",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -25,7 +25,7 @@
25
25
  "description": "REST API plugin for hooked-api with JSON:API compliance",
26
26
  "dependencies": {
27
27
  "hooked-api": "^1.0.24",
28
- "json-rest-schema": "^1.0.12",
28
+ "json-rest-schema": "^1.0.13",
29
29
  "semver": "^7.7.2"
30
30
  },
31
31
  "peerDependencies": {
@@ -35,7 +35,7 @@
35
35
  "express": "^4.0.0 || ^5.0.0",
36
36
  "formidable": "^3.0.0",
37
37
  "fractional-indexing": "^3.2.0",
38
- "jose": "^5.10.0",
38
+ "jose": "^5.10.0 || ^6.0.0",
39
39
  "knex": "^3.1.0",
40
40
  "raw-body": "^2.0.0",
41
41
  "redis": "^5.6.0",
@@ -81,7 +81,7 @@
81
81
  "better-sqlite3": "^11.10.0",
82
82
  "express": "^5.1.0",
83
83
  "fractional-indexing": "^3.2.0",
84
- "jose": "^5.10.0",
84
+ "jose": "^6.2.2",
85
85
  "knex": "^3.1.0",
86
86
  "redis": "^5.6.0",
87
87
  "socket.io": "^4.8.1",
@@ -18,6 +18,20 @@ import { requirePackage } from 'hooked-api'
18
18
  import { parseJsonApiQuery } from '../lib/querying-writing/connectors-query-parser.js'
19
19
  import { createContext } from './lib/request-helpers.js'
20
20
  import { createEnhancedLogger } from '../../../lib/enhanced-logger.js'
21
+ import {
22
+ isWriteMethod,
23
+ isAllowedWriteContentType,
24
+ getUnsupportedMediaTypeErrorBody
25
+ } from './lib/transport-http-helpers.js'
26
+ import {
27
+ extractQueryString,
28
+ buildTransportRequestData,
29
+ createConnectorContext,
30
+ runTransportRequestLifecycle,
31
+ buildTransportRejectionBody,
32
+ executeConnectorRoute,
33
+ handleConnectorError
34
+ } from './lib/connector-core.js'
21
35
 
22
36
  export const ExpressPlugin = {
23
37
  name: 'express',
@@ -115,58 +129,39 @@ export const ExpressPlugin = {
115
129
 
116
130
  // Add transport hook middleware
117
131
  router.use(async (req, res, next) => {
118
- const context = createContext(req, res, 'express')
119
-
120
- // Check for Express middleware override first
121
- if (req.urlPrefixOverride) {
122
- context.urlPrefixOverride = req.urlPrefixOverride
123
- }
124
-
125
- // Calculate and cache URL prefix (getUrlPrefix handles all logic)
126
- const { getUrlPrefix } = await import('../lib/querying/url-helpers.js')
127
- context.urlPrefix = getUrlPrefix(context, { vars: { transport: { mountPath: basePath } } }, req)
128
-
129
- // Transport-specific data for hooks
130
- const transportData = {
131
- request: {
132
- method: req.method,
133
- url: req.url,
134
- path: req.path,
135
- headers: req.headers,
136
- body: req.body,
137
- params: req.params,
138
- query: req.query
139
- },
140
- response: {
141
- headers: {},
142
- status: null
143
- }
144
- }
145
-
146
- // Add transport data to context
147
- context.transport = transportData
148
-
149
- // Run transport hooks - don't check return value for consistency with other hooks
150
- // Hooks communicate via context flags (rejection, handled) not return values
151
- await runHooks('transport:request', context)
132
+ const requestData = buildTransportRequestData({
133
+ method: req.method,
134
+ url: req.url,
135
+ path: req.path,
136
+ headers: req.headers,
137
+ body: req.body,
138
+ params: req.params,
139
+ query: req.query
140
+ })
141
+ const { context, transportData } = createConnectorContext({
142
+ request: req,
143
+ reply: res,
144
+ source: 'express',
145
+ mountPath: basePath,
146
+ requestData,
147
+ createContext,
148
+ urlPrefixOverride: req.urlPrefixOverride
149
+ })
150
+ const { rejected, handled } = await runTransportRequestLifecycle({
151
+ context,
152
+ runHooks
153
+ })
152
154
 
153
- // Check if request was rejected (e.g., authentication failure)
154
- if (context.rejection) {
155
- // Apply response headers from hooks
155
+ if (rejected) {
156
156
  if (transportData.response.headers) {
157
157
  res.set(transportData.response.headers)
158
158
  }
159
- return res.status(context.rejection.status || 500).json({
160
- errors: [{
161
- status: String(context.rejection.status || 500),
162
- title: context.rejection.title || 'Request Rejected',
163
- detail: context.rejection.message
164
- }]
165
- })
159
+ return res.status(context.rejection.status || 500).json(
160
+ buildTransportRejectionBody(context)
161
+ )
166
162
  }
167
163
 
168
- // Check if request was already handled by a hook
169
- if (context.handled) {
164
+ if (handled) {
170
165
  return
171
166
  }
172
167
 
@@ -179,18 +174,13 @@ export const ExpressPlugin = {
179
174
  // Content type validation middleware
180
175
  if (strictContentType) {
181
176
  router.use((req, res, next) => {
182
- if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
177
+ if (isWriteMethod(req.method)) {
183
178
  const contentType = req.get('Content-Type')
184
179
 
185
- if (contentType && !contentType.includes('application/vnd.api+json') &&
186
- !contentType.includes('application/json') && !contentType.includes('multipart/form-data')) {
187
- return res.status(415).json({
188
- errors: [{
189
- status: '415',
190
- title: 'Unsupported Media Type',
191
- detail: 'Content-Type must be application/vnd.api+json or application/json'
192
- }]
193
- })
180
+ if (!isAllowedWriteContentType(contentType, { allowMultipart: true })) {
181
+ return res.status(415).json(
182
+ getUnsupportedMediaTypeErrorBody({ allowMultipart: true })
183
+ )
194
184
  }
195
185
  }
196
186
  next()
@@ -207,77 +197,16 @@ export const ExpressPlugin = {
207
197
  url: req.url
208
198
  })
209
199
 
210
- let status = 500
211
- const errorResponse = {
212
- errors: [{
213
- status: '500',
214
- title: 'Internal Server Error',
215
- detail: error.message
216
- }]
217
- }
218
-
219
- // Map error codes to HTTP status
220
- if (error.code === 'REST_API_VALIDATION') {
221
- status = 422
222
- errorResponse.errors = [{
223
- status: '422',
224
- title: 'Validation Error',
225
- detail: error.message,
226
- source: error.details
227
- }]
228
- if (error.details?.violations) {
229
- errorResponse.errors = error.details.violations.map(v => ({
230
- status: '422',
231
- title: 'Validation Error',
232
- detail: v.message,
233
- source: { pointer: v.field }
234
- }))
235
- }
236
- } else if (error.code === 'REST_API_RESOURCE') {
237
- switch (error.subtype) {
238
- case 'not_found':
239
- status = 404
240
- errorResponse.errors[0].status = '404'
241
- errorResponse.errors[0].title = 'Not Found'
242
- break
243
- case 'conflict':
244
- status = 409
245
- errorResponse.errors[0].status = '409'
246
- errorResponse.errors[0].title = 'Conflict'
247
- break
248
- case 'forbidden':
249
- status = 403
250
- errorResponse.errors[0].status = '403'
251
- errorResponse.errors[0].title = 'Forbidden'
252
- break
253
- default:
254
- status = 400
255
- errorResponse.errors[0].status = '400'
256
- errorResponse.errors[0].title = 'Bad Request'
257
- }
258
- } else if (error.code === 'REST_API_PAYLOAD') {
259
- status = 400
260
- errorResponse.errors = [{
261
- status: '400',
262
- title: 'Bad Request',
263
- detail: error.message,
264
- source: { pointer: error.path }
265
- }]
266
- }
267
-
268
- // Run transport:response hook for errors
269
- if (req.transportData && req.context) {
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)
200
+ const { status, body: errorResponse, headers } = await handleConnectorError({
201
+ error,
202
+ context: req.context,
203
+ transportData: req.transportData,
204
+ runHooks
205
+ })
274
206
 
275
- // Apply response headers from hooks
276
- if (req.transportData.response.headers) {
277
- res.set(req.transportData.response.headers)
278
- }
207
+ if (headers) {
208
+ res.set(headers)
279
209
  }
280
-
281
210
  res.status(status).json(errorResponse)
282
211
  }
283
212
 
@@ -285,7 +214,7 @@ export const ExpressPlugin = {
285
214
  * Listen to addRoute hook to create Express routes
286
215
  */
287
216
  addHook('addRoute', 'expressRouteCreator', {}, async ({ context }) => {
288
- const { method, path, handler } = context
217
+ const { method, path, handler, routeMeta } = context
289
218
 
290
219
  // Apply any global before middleware
291
220
  const beforeMiddleware = expressOptions.middleware?.beforeAll || []
@@ -294,78 +223,62 @@ export const ExpressPlugin = {
294
223
  // Extract the handler logic into a shared function to keep it DRY (Don't Repeat Yourself).
295
224
  const expressHandler = async (req, res) => {
296
225
  try {
297
- // Extract request data
298
- const queryString = req.url.split('?')[1] || ''
299
- const context = req.context || createContext(req, res, 'express')
226
+ let context = req.context
227
+ let transportData = req.transportData
228
+
229
+ if (!context || !transportData) {
230
+ const requestData = buildTransportRequestData({
231
+ method: req.method,
232
+ url: req.url,
233
+ path: req.path,
234
+ headers: req.headers,
235
+ body: req.body,
236
+ params: req.params,
237
+ query: req.query
238
+ })
239
+ const setup = createConnectorContext({
240
+ request: req,
241
+ reply: res,
242
+ source: 'express',
243
+ mountPath: basePath,
244
+ requestData,
245
+ createContext,
246
+ urlPrefixOverride: req.urlPrefixOverride
247
+ })
248
+ context = setup.context
249
+ transportData = setup.transportData
250
+ req.context = context
251
+ req.transportData = transportData
252
+ }
300
253
 
301
- // Call the generic handler
302
- const result = await handler({
303
- queryString,
254
+ const outcome = await executeConnectorRoute({
255
+ method: req.method,
256
+ handler,
257
+ queryString: extractQueryString(req.url),
304
258
  headers: req.headers,
305
259
  params: req.params,
306
260
  body: req.body,
307
- context
261
+ context,
262
+ transportData,
263
+ routeMeta,
264
+ helpers,
265
+ mountPath: basePath,
266
+ runHooks
308
267
  })
309
-
310
- // Prepare response status
311
- let responseStatus = 200
312
- if (result && typeof result.statusCode === 'number') {
313
- responseStatus = result.statusCode
314
- } else if (req.method === 'POST' && result) {
315
- // POST with content returns 201 Created
316
- responseStatus = 201
317
- } else if (req.method === 'POST' && !result) {
318
- // POST without content returns 204 No Content
319
- responseStatus = 204
320
- } else if (req.method === 'DELETE') {
321
- // DELETE always returns 204 No Content
322
- responseStatus = 204
323
- } else if ((req.method === 'PUT' || req.method === 'PATCH') && !result) {
324
- // PUT/PATCH return 204 when no content is returned
325
- responseStatus = 204
326
- }
327
-
328
- // Apply headers from the handler result
329
- if (result && result.headers) {
330
- res.set(result.headers)
331
- }
332
-
333
- // Update transport data for response (if transport data exists)
334
- if (req.transportData) {
335
- req.transportData.response.status = responseStatus
336
- req.transportData.response.body = result
337
- context.transport = req.transportData
338
- await runHooks('transport:response', context)
339
- if (req.transportData.response.headers) {
340
- res.set(req.transportData.response.headers)
341
- }
342
- }
268
+ res.set(outcome.headers)
343
269
 
344
270
  // Set content type
345
271
  res.set('Content-Type', 'application/vnd.api+json')
346
272
 
347
- // Set Location header for successful POST requests
348
- if (req.method === 'POST' && context.id) {
349
- // Extract scopeName from the path (e.g., /api/countries -> countries)
350
- const pathParts = path.split('/')
351
- const scopeName = pathParts[pathParts.length - 1]
352
-
353
- // Set Location header for any successful POST (201 or 204)
354
- if (helpers.getLocation) {
355
- const location = helpers.getLocation({ scopeName, id: context.id })
356
- const baseUrl = context.urlPrefix || basePath
357
- res.set('Location', `${baseUrl}${location}`)
358
- }
273
+ if (outcome.location) {
274
+ res.set('Location', outcome.location)
359
275
  }
360
276
 
361
277
  // Handle response based on status
362
- if (responseStatus === 204) {
278
+ if (outcome.status === 204) {
363
279
  res.sendStatus(204)
364
280
  } else {
365
- // If result has a body property, that's what we should send
366
- // This happens when handler returns { statusCode, body, headers }
367
- const responseBody = result && result.body !== undefined ? result.body : result
368
- res.status(responseStatus).json(responseBody)
281
+ res.status(outcome.status).json(outcome.body)
369
282
  }
370
283
  } catch (error) {
371
284
  handleError(error, req, res)
@@ -0,0 +1,206 @@
1
+ import { createContext } from './lib/request-helpers.js'
2
+ import { createEnhancedLogger } from '../../../lib/enhanced-logger.js'
3
+ import { buildTransportRouteSchema } from './lib/transport-route-schemas.js'
4
+ import {
5
+ isWriteMethod,
6
+ isAllowedWriteContentType,
7
+ getUnsupportedMediaTypeErrorBody
8
+ } from './lib/transport-http-helpers.js'
9
+ import {
10
+ extractQueryString,
11
+ buildTransportRequestData,
12
+ createConnectorContext,
13
+ runTransportRequestLifecycle,
14
+ buildTransportRejectionBody,
15
+ executeConnectorRoute,
16
+ handleConnectorError
17
+ } from './lib/connector-core.js'
18
+
19
+ function applyHeaders (reply, headers = {}) {
20
+ for (const [headerName, headerValue] of Object.entries(headers)) {
21
+ reply.header(headerName, headerValue)
22
+ }
23
+ }
24
+
25
+ function registerVendorJsonParser (app) {
26
+ if (typeof app?.addContentTypeParser !== 'function') return
27
+ if (typeof app?.hasContentTypeParser === 'function' && app.hasContentTypeParser('application/vnd.api+json')) {
28
+ return
29
+ }
30
+
31
+ app.addContentTypeParser(
32
+ 'application/vnd.api+json',
33
+ { parseAs: 'string' },
34
+ (request, body, done) => {
35
+ if (body === '' || body === undefined || body === null) {
36
+ done(null, {})
37
+ return
38
+ }
39
+
40
+ try {
41
+ done(null, JSON.parse(body))
42
+ } catch (error) {
43
+ error.statusCode = 400
44
+ done(error)
45
+ }
46
+ }
47
+ )
48
+ }
49
+
50
+ export const FastifyPlugin = {
51
+ name: 'fastify',
52
+ dependencies: ['rest-api'],
53
+
54
+ async install ({ vars, helpers, pluginOptions, log, api, runHooks, addHook }) {
55
+ const fastifyOptions = pluginOptions || {}
56
+ const app = fastifyOptions.app
57
+
58
+ if (!app || typeof app.route !== 'function') {
59
+ throw new Error('FastifyPlugin requires a Fastify instance in pluginOptions.app.')
60
+ }
61
+
62
+ const enhancedLog = createEnhancedLogger(log, {
63
+ logFullErrors: true,
64
+ includeStack: true
65
+ })
66
+
67
+ if (!api.http) {
68
+ api.http = {}
69
+ }
70
+ api.http.fastify = { app }
71
+
72
+ const mountPath = fastifyOptions.mountPath || ''
73
+ const strictContentType = fastifyOptions.strictContentType !== false
74
+
75
+ vars.transport = {
76
+ type: 'fastify',
77
+ matchAll: '*',
78
+ mountPath
79
+ }
80
+
81
+ registerVendorJsonParser(app)
82
+
83
+ const buildFastifyHandler = ({ method, path, handler, routeMeta }) => {
84
+ const routeSchema = buildTransportRouteSchema({ routeMeta, api })
85
+
86
+ const fastifyErrorHandler = async (error, request, reply) => {
87
+ enhancedLog.logError('Fastify request error', error, {
88
+ method: request.method,
89
+ path: request.url
90
+ })
91
+
92
+ const context = request.jsonRestContext || null
93
+ const transportData = context?.transport || null
94
+ const { status, body, headers } = await handleConnectorError({
95
+ error,
96
+ context,
97
+ transportData,
98
+ runHooks
99
+ })
100
+
101
+ applyHeaders(reply, headers)
102
+ reply.code(status)
103
+ reply.type('application/vnd.api+json')
104
+ return reply.send(body)
105
+ }
106
+
107
+ const preValidation = async (request, reply) => {
108
+ if (!strictContentType || !isWriteMethod(method)) {
109
+ return
110
+ }
111
+
112
+ const contentType = request.headers?.['content-type'] || ''
113
+ if (isAllowedWriteContentType(contentType)) {
114
+ return
115
+ }
116
+
117
+ reply.code(415)
118
+ reply.type('application/vnd.api+json')
119
+ return reply.send(getUnsupportedMediaTypeErrorBody())
120
+ }
121
+
122
+ return {
123
+ method,
124
+ url: path,
125
+ ...(routeSchema ? { schema: routeSchema } : {}),
126
+ errorHandler: fastifyErrorHandler,
127
+ preValidation,
128
+ handler: async (request, reply) => {
129
+ const requestData = buildTransportRequestData({
130
+ method: request.method,
131
+ url: request.url,
132
+ path: request.url,
133
+ headers: request.headers,
134
+ body: request.body,
135
+ params: request.params,
136
+ query: request.query
137
+ })
138
+ const { context, transportData } = createConnectorContext({
139
+ request,
140
+ reply,
141
+ source: 'fastify',
142
+ mountPath,
143
+ requestData,
144
+ createContext
145
+ })
146
+ request.jsonRestContext = context
147
+
148
+ const { rejected, handled } = await runTransportRequestLifecycle({
149
+ context,
150
+ runHooks
151
+ })
152
+
153
+ if (rejected) {
154
+ const rejectionBody = buildTransportRejectionBody(context)
155
+ applyHeaders(reply, transportData.response.headers)
156
+ reply.code(context.rejection.status || 500)
157
+ reply.type('application/vnd.api+json')
158
+ return reply.send(rejectionBody)
159
+ }
160
+
161
+ if (handled) {
162
+ return
163
+ }
164
+
165
+ const outcome = await executeConnectorRoute({
166
+ method,
167
+ handler,
168
+ queryString: extractQueryString(request?.raw?.url || request?.url || ''),
169
+ headers: request.headers,
170
+ params: request.params,
171
+ body: request.body,
172
+ context,
173
+ transportData,
174
+ routeMeta,
175
+ helpers,
176
+ mountPath,
177
+ runHooks
178
+ })
179
+
180
+ applyHeaders(reply, outcome.headers)
181
+ reply.type('application/vnd.api+json')
182
+
183
+ if (outcome.location) {
184
+ reply.header('Location', outcome.location)
185
+ }
186
+
187
+ if (outcome.status === 204) {
188
+ reply.code(204)
189
+ return reply.send()
190
+ }
191
+
192
+ reply.code(outcome.status)
193
+ return reply.send(outcome.body)
194
+ }
195
+ }
196
+ }
197
+
198
+ addHook('addRoute', 'fastifyRouteCreator', {}, async ({ context }) => {
199
+ const route = buildFastifyHandler(context)
200
+ app.route(route)
201
+ log.trace(`Fastify route created: ${context.method} ${context.path}`)
202
+ })
203
+
204
+ log.info('Fastify plugin initialized successfully')
205
+ }
206
+ }