json-rest-api 1.0.14 → 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 +11 -2
  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 +1 -1
  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,17 +1,17 @@
1
- import { RestApiResourceError } from '../../../lib/rest-api-errors.js';
2
- import { validatePutPayload } from '../lib/querying-writing/payload-validators.js';
3
- import { processRelationships } from '../lib/writing/relationship-processor.js';
4
- import { updateManyToManyRelationship, createPivotRecords } from '../lib/writing/many-to-many-manipulations.js';
5
- import { ERROR_SUBTYPES } from '../lib/querying-writing/knex-constants.js';
6
- import {
7
- setupCommonRequest,
1
+ import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
2
+ import { validatePutPayload } from '../lib/querying-writing/payload-validators.js'
3
+ import { processRelationships } from '../lib/writing/relationship-processor.js'
4
+ import { updateManyToManyRelationship, createPivotRecords } from '../lib/writing/many-to-many-manipulations.js'
5
+ import { ERROR_SUBTYPES } from '../lib/querying-writing/knex-constants.js'
6
+ import {
7
+ setupCommonRequest,
8
8
  validateResourceAttributesBeforeWrite,
9
9
  validateRelationshipAccess,
10
10
  applyFieldSetters,
11
11
  validatePivotResource,
12
12
  handleRecordReturnAfterWrite,
13
13
  handleWriteMethodError
14
- } from './common.js';
14
+ } from './common.js'
15
15
 
16
16
  /**
17
17
  * PUT
@@ -20,39 +20,39 @@ import {
20
20
  * PUT is a complete replacement - any relationships not provided will be removed/nulled.
21
21
  * This method does NOT support creating new related resources via an `included` array.
22
22
  */
23
- export default async function putMethod({
24
- params,
25
- context,
26
- vars,
27
- helpers,
28
- scope,
29
- scopes,
30
- runHooks,
31
- apiOptions,
32
- pluginOptions,
33
- scopeOptions,
23
+ export default async function putMethod ({
24
+ params,
25
+ context,
26
+ vars,
27
+ helpers,
28
+ scope,
29
+ scopes,
30
+ runHooks,
31
+ apiOptions,
32
+ pluginOptions,
33
+ scopeOptions,
34
34
  scopeName,
35
35
  api,
36
36
  log
37
37
  }) {
38
- context.method = 'put';
39
-
38
+ context.method = 'put'
39
+
40
40
  try {
41
41
  const { schema, schemaStructure, schemaRelationships } = await setupCommonRequest({
42
- params,
43
- context,
44
- vars,
45
- scopes,
46
- scopeOptions,
42
+ params,
43
+ context,
44
+ vars,
45
+ scopes,
46
+ scopeOptions,
47
47
  scopeName,
48
- api,
48
+ api,
49
49
  helpers
50
- });
51
- context.id = context.inputRecord.data.id;
52
-
50
+ })
51
+ context.id = context.inputRecord.data.id
52
+
53
53
  // Run early hooks for pre-processing (e.g., file handling)
54
- await runHooks('beforeProcessing');
55
- await runHooks('beforeProcessingPut');
54
+ await runHooks('beforeProcessing')
55
+ await runHooks('beforeProcessingPut')
56
56
 
57
57
  // Validate PUT payload to ensure it's a complete resource replacement operation.
58
58
  // PUT requires the full resource representation including ID (unlike POST which generates ID).
@@ -60,35 +60,35 @@ export default async function putMethod({
60
60
  // read-only), and ensures the payload represents a complete replacement. Any fields not
61
61
  // provided will be removed or reset to defaults - this is the key difference from PATCH.
62
62
  // Example: PUT to /articles/123 must have data.id: '123' and all required fields.
63
- validatePutPayload(context.inputRecord, scopes);
64
-
63
+ validatePutPayload(context.inputRecord, scopes)
64
+
65
65
  // Validate that user has read access to all related resources
66
66
  // This ensures users can only create relationships to resources they can access
67
- await validateRelationshipAccess(context, context.inputRecord, helpers, runHooks, api);
68
-
67
+ await validateRelationshipAccess(context, context.inputRecord, helpers, runHooks, api)
68
+
69
69
  // Extract foreign keys from JSON:API relationships and prepare many-to-many operations
70
70
  // Example: relationships.author -> author_id: '123' for storage
71
71
  // Example: relationships.tags -> array of pivot records to create later
72
72
  const { belongsToUpdates, manyToManyRelationships } = processRelationships(
73
73
  scope,
74
74
  { context }
75
- );
75
+ )
76
76
 
77
- await validateResourceAttributesBeforeWrite({
78
- context,
79
- schema,
80
- belongsToUpdates,
77
+ await validateResourceAttributesBeforeWrite({
78
+ context,
79
+ schema,
80
+ belongsToUpdates,
81
81
  runHooks
82
- });
82
+ })
83
83
 
84
84
  // Check existence first
85
85
  context.exists = await helpers.dataExists({
86
86
  scopeName,
87
87
  context
88
- });
89
-
90
- context.isCreate = !context.exists;
91
- context.isUpdate = context.exists;
88
+ })
89
+
90
+ context.isCreate = !context.exists
91
+ context.isUpdate = context.exists
92
92
 
93
93
  // Fetch minimal record for authorization checks (only for updates)
94
94
  if (context.isUpdate) {
@@ -96,64 +96,64 @@ export default async function putMethod({
96
96
  scopeName,
97
97
  context,
98
98
  runHooks
99
- });
99
+ })
100
100
 
101
101
  if (!minimalRecord) {
102
102
  throw new RestApiResourceError(
103
103
  `Resource not found: ${scopeName}/${context.id}`,
104
104
  ERROR_SUBTYPES.NOT_FOUND
105
- );
105
+ )
106
106
  }
107
107
 
108
- context.minimalRecord = minimalRecord;
108
+ context.minimalRecord = minimalRecord
109
109
  }
110
110
 
111
111
  // For PUT, we also need to handle relationships that are NOT provided
112
112
  // (they should be set to null/empty as PUT is a complete replacement)
113
- const allRelationships = {};
114
-
113
+ const allRelationships = {}
114
+
115
115
  // Collect all defined relationships for this resource
116
116
  for (const [relName, relDef] of Object.entries(schemaRelationships || {})) {
117
117
  if (relDef.type === 'manyToMany') {
118
- allRelationships[relName] = {
119
- type: 'manyToMany',
118
+ allRelationships[relName] = {
119
+ type: 'manyToMany',
120
120
  relDef: {
121
121
  through: relDef.through,
122
122
  foreignKey: relDef.foreignKey,
123
123
  otherKey: relDef.otherKey
124
124
  }
125
- };
125
+ }
126
126
  }
127
127
  }
128
-
128
+
129
129
  // Also check schema fields for belongsTo relationships
130
130
  for (const [fieldName, fieldDef] of Object.entries(schemaStructure)) {
131
131
  if (fieldDef.as && fieldDef.belongsTo) {
132
- allRelationships[fieldDef.as] = {
132
+ allRelationships[fieldDef.as] = {
133
133
  type: 'belongsTo',
134
134
  fieldName,
135
- fieldDef
136
- };
135
+ fieldDef
136
+ }
137
137
  }
138
138
  }
139
-
139
+
140
140
  // Process missing relationships (PUT should null them out only if relationships object exists)
141
- const hasRelationshipsObject = context.inputRecord.data.relationships !== undefined;
142
- const providedRelationships = new Set(Object.keys(context.inputRecord.data.relationships || {}));
143
-
141
+ const hasRelationshipsObject = context.inputRecord.data.relationships !== undefined
142
+ const providedRelationships = new Set(Object.keys(context.inputRecord.data.relationships || {}))
143
+
144
144
  // Only null out missing relationships if a relationships object was provided
145
145
  if (hasRelationshipsObject) {
146
146
  for (const [relName, relInfo] of Object.entries(allRelationships)) {
147
147
  if (!providedRelationships.has(relName)) {
148
148
  if (relInfo.type === 'belongsTo') {
149
- belongsToUpdates[relInfo.fieldName] = null;
149
+ belongsToUpdates[relInfo.fieldName] = null
150
150
  } else if (relInfo.type === 'manyToMany') {
151
151
  // Add to manyToManyRelationships with empty array
152
152
  manyToManyRelationships.push({
153
153
  relName,
154
154
  relDef: relInfo.relDef,
155
155
  relData: [] // Empty array means delete all
156
- });
156
+ })
157
157
  }
158
158
  }
159
159
  }
@@ -164,18 +164,18 @@ export default async function putMethod({
164
164
  context.inputRecord.data.attributes = {
165
165
  ...context.inputRecord.data.attributes,
166
166
  ...belongsToUpdates
167
- };
167
+ }
168
168
  }
169
169
 
170
170
  // Centralised checkPermissions function
171
171
  await scope.checkPermissions({
172
172
  method: 'put',
173
173
  originalContext: context,
174
- });
175
-
176
- await runHooks('beforeDataCall');
177
- await runHooks('beforeDataCallPut');
178
-
174
+ })
175
+
176
+ await runHooks('beforeDataCall')
177
+ await runHooks('beforeDataCallPut')
178
+
179
179
  // Apply field setters after validation and before storage
180
180
  if (context.inputRecord?.data?.attributes) {
181
181
  context.inputRecord.data.attributes = await applyFieldSetters(
@@ -184,16 +184,16 @@ export default async function putMethod({
184
184
  context,
185
185
  api,
186
186
  helpers
187
- );
187
+ )
188
188
  }
189
-
189
+
190
190
  // Pass the operation type to the helper
191
191
  await helpers.dataPut({
192
192
  scopeName,
193
193
  context
194
- });
195
- await runHooks('afterDataCallPut');
196
- await runHooks('afterDataCall');
194
+ })
195
+ await runHooks('afterDataCallPut')
196
+ await runHooks('afterDataCall')
197
197
 
198
198
  // Process many-to-many relationships after main record update/creation
199
199
  for (const { relName, relDef, relData } of manyToManyRelationships) {
@@ -205,11 +205,11 @@ export default async function putMethod({
205
205
  relDef,
206
206
  relData,
207
207
  isUpdate: context.isUpdate,
208
- });
209
- continue;
208
+ })
209
+ continue
210
210
  }
211
211
 
212
- await validatePivotResource(scopes, relDef, relName);
212
+ await validatePivotResource(scopes, relDef, relName)
213
213
 
214
214
  if (context.isUpdate) {
215
215
  await updateManyToManyRelationship(null, {
@@ -220,12 +220,12 @@ export default async function putMethod({
220
220
  relData,
221
221
  transaction: context.transaction
222
222
  }
223
- });
223
+ })
224
224
  } else if (relData.length > 0) {
225
- await createPivotRecords(api, context.id, relDef, relData, context.transaction);
225
+ await createPivotRecords(api, context.id, relDef, relData, context.transaction)
226
226
  }
227
227
  }
228
-
228
+
229
229
  const ret = await handleRecordReturnAfterWrite({
230
230
  context,
231
231
  scopeName,
@@ -238,17 +238,16 @@ export default async function putMethod({
238
238
  runHooks,
239
239
  helpers,
240
240
  log
241
- });
241
+ })
242
242
 
243
243
  // Commit transaction if we created it
244
244
  if (context.shouldCommit) {
245
- await context.transaction.commit();
246
- await runHooks('afterCommit');
245
+ await context.transaction.commit()
246
+ await runHooks('afterCommit')
247
247
  }
248
248
 
249
- return ret;
250
-
249
+ return ret
251
250
  } catch (error) {
252
- await handleWriteMethodError(error, context, 'PUT', scopeName, log, runHooks);
251
+ await handleWriteMethodError(error, context, 'PUT', scopeName, log, runHooks)
253
252
  }
254
253
  }
@@ -1,9 +1,9 @@
1
- import { RestApiValidationError } from '../../../lib/rest-api-errors.js';
2
- import { validateQueryPayload } from '../lib/querying-writing/payload-validators.js';
3
- import { normalizeRecordAttributes } from '../lib/querying-writing/database-value-normalizers.js';
4
- import { getRequestedComputedFields } from '../lib/querying-writing/knex-field-helpers.js';
5
- import { transformJsonApiToSimplified } from '../lib/querying-writing/simplified-helpers.js';
6
- import { cascadeConfig } from './common.js';
1
+ import { RestApiValidationError } from '../../../lib/rest-api-errors.js'
2
+ import { validateQueryPayload } from '../lib/querying-writing/payload-validators.js'
3
+ import { normalizeRecordAttributes } from '../lib/querying-writing/database-value-normalizers.js'
4
+ import { getRequestedComputedFields } from '../lib/querying-writing/knex-field-helpers.js'
5
+ import { transformJsonApiToSimplified } from '../lib/querying-writing/simplified-helpers.js'
6
+ import { cascadeConfig } from './common.js'
7
7
 
8
8
  /**
9
9
  * QUERY
@@ -19,57 +19,57 @@ import { cascadeConfig } from './common.js';
19
19
  * @param {object} [queryParams.page] - An object for pagination. Typically includes `number` (page number) and `size` (items per page). E.g., `{ number: 1, size: 10 }`.
20
20
  * @returns {Promise<object>} A Promise that resolves to the JSON:API response document containing the resource collection.
21
21
  */
22
- export default async function queryMethod({
23
- params,
24
- context,
25
- vars,
26
- helpers,
27
- scope,
28
- scopes,
29
- runHooks,
30
- apiOptions,
31
- pluginOptions,
32
- scopeOptions,
33
- scopeName,
22
+ export default async function queryMethod ({
23
+ params,
24
+ context,
25
+ vars,
26
+ helpers,
27
+ scope,
28
+ scopes,
29
+ runHooks,
30
+ apiOptions,
31
+ pluginOptions,
32
+ scopeOptions,
33
+ scopeName,
34
34
  log,
35
- api
35
+ api
36
36
  }) {
37
- context.method = 'query';
38
-
37
+ context.method = 'query'
38
+
39
39
  // Determine which simplified setting to use based on transport
40
- const isTransport = params.isTransport === true;
41
-
40
+ const isTransport = params.isTransport === true
41
+
42
42
  // Use vars which automatically cascade from scope to global
43
- const defaultSimplified = isTransport ? vars.simplifiedTransport : vars.simplifiedApi;
44
-
43
+ const defaultSimplified = isTransport ? vars.simplifiedTransport : vars.simplifiedApi
44
+
45
45
  // Get simplified setting - from params only (per-call override) or use default
46
- context.simplified = params.simplified !== undefined ? params.simplified : defaultSimplified;
46
+ context.simplified = params.simplified !== undefined ? params.simplified : defaultSimplified
47
47
 
48
48
  // Assign common context properties
49
- context.schemaInfo = scopes[scopeName].vars.schemaInfo; // This is the object variable created by compileSchemas
50
- context.queryParams = params.queryParams || {};
49
+ context.schemaInfo = scopes[scopeName].vars.schemaInfo // This is the object variable created by compileSchemas
50
+ context.queryParams = params.queryParams || {}
51
51
 
52
52
  // These only make sense as parameter per query
53
- context.queryParams.fields = cascadeConfig('fields', [params.queryParams], {});
54
- context.queryParams.include = cascadeConfig('include', [params.queryParams], []);
55
- context.queryParams.sort = cascadeConfig('sort', [params.queryParams], []);
56
- context.queryParams.page = cascadeConfig('page', [params.queryParams], {});
53
+ context.queryParams.fields = cascadeConfig('fields', [params.queryParams], {})
54
+ context.queryParams.include = cascadeConfig('include', [params.queryParams], [])
55
+ context.queryParams.sort = cascadeConfig('sort', [params.queryParams], [])
56
+ context.queryParams.page = cascadeConfig('page', [params.queryParams], {})
57
57
 
58
- context.transaction = params.transaction;
59
- context.db = context.transaction || api.knex.instance;
60
-
61
- context.scopeName = scopeName;
58
+ context.transaction = params.transaction
59
+ context.db = context.transaction || api.knex.instance
60
+
61
+ context.scopeName = scopeName
62
62
 
63
63
  // These are just shortcuts used in this function and will be returned
64
- const searchSchema = context.schemaInfo.searchSchemaInstance;
65
- const schemaStructure = context.schemaInfo.schemaInstance.structure;
66
- const schemaRelationships = context.schemaInfo.schemaRelationships;
64
+ const searchSchema = context.schemaInfo.searchSchemaInstance
65
+ const schemaStructure = context.schemaInfo.schemaInstance.structure
66
+ const schemaRelationships = context.schemaInfo.schemaRelationships
67
67
 
68
68
  // Sortable fields and sort (mab)
69
- context.sortableFields = vars.sortableFields;
69
+ context.sortableFields = vars.sortableFields
70
70
  // Apply default sort if no sort specified
71
71
  if (context.queryParams.sort.length === 0 && vars.defaultSort) {
72
- context.queryParams.sort = Array.isArray(vars.defaultSort) ? vars.defaultSort : [vars.defaultSort];
72
+ context.queryParams.sort = Array.isArray(vars.defaultSort) ? vars.defaultSort : [vars.defaultSort]
73
73
  }
74
74
 
75
75
  // Validate query parameters to ensure they follow JSON:API specification and security rules.
@@ -77,7 +77,7 @@ export default async function queryMethod({
77
77
  // (preventing SQL injection), pagination uses valid page[size]/page[number] format, and include
78
78
  // paths reference real relationships. Example: sort: ['-createdAt', 'title'] is checked against
79
79
  // sortableFields to ensure users can't sort by sensitive fields like 'password_hash'.
80
- validateQueryPayload({ queryParams: context.queryParams }, context.sortableFields, vars.includeDepthLimit);
80
+ validateQueryPayload({ queryParams: context.queryParams }, context.sortableFields, vars.includeDepthLimit)
81
81
 
82
82
  // Validate search/filter parameters against searchSchema
83
83
  if (context.queryParams.filters && Object.keys(context.queryParams.filters).length > 0) {
@@ -85,7 +85,7 @@ export default async function queryMethod({
85
85
  if (!searchSchema) {
86
86
  throw new RestApiValidationError(
87
87
  `Filtering is not enabled for resource '${scopeName}'. To enable filtering, add 'search: true' to schema fields or define a searchSchema.`,
88
- {
88
+ {
89
89
  fields: Object.keys(context.queryParams.filters).map(field => `filters.${field}`),
90
90
  violations: [{
91
91
  field: 'filters',
@@ -93,13 +93,13 @@ export default async function queryMethod({
93
93
  message: 'Resource does not have searchable fields defined'
94
94
  }]
95
95
  }
96
- );
96
+ )
97
97
  }
98
-
98
+
99
99
  // Validate the filter parameters searchSchema
100
- const { validatedObject, errors } = await searchSchema.validate(context.queryParams.filters, {
100
+ const { validatedObject, errors } = await searchSchema.validate(context.queryParams.filters, {
101
101
  onlyObjectValues: true // Partial validation for filters
102
- });
102
+ })
103
103
 
104
104
  // If there are validation errors, throw an error
105
105
  if (Object.keys(errors).length > 0) {
@@ -107,101 +107,101 @@ export default async function queryMethod({
107
107
  field: `filters.${field}`,
108
108
  rule: error.code || 'invalid_value',
109
109
  message: error.message
110
- }));
111
-
110
+ }))
111
+
112
112
  throw new RestApiValidationError(
113
113
  'Invalid filter parameters',
114
- {
114
+ {
115
115
  fields: Object.keys(errors).map(field => `filters.${field}`),
116
- violations
116
+ violations
117
117
  }
118
- );
118
+ )
119
119
  }
120
-
120
+
121
121
  // Replace filter with validated/transformed values
122
- context.queryParams.filters = validatedObject;
122
+ context.queryParams.filters = validatedObject
123
123
  }
124
124
 
125
125
  // Centralised checkPermissions function
126
126
  await scope.checkPermissions({
127
127
  method: 'query',
128
128
  originalContext: context,
129
- });
129
+ })
130
130
 
131
- await runHooks('beforeData');
132
- await runHooks('beforeDataQuery');
131
+ await runHooks('beforeData')
132
+ await runHooks('beforeDataQuery')
133
133
  context.record = await helpers.dataQuery({
134
134
  scopeName,
135
135
  context,
136
136
  runHooks
137
- });
137
+ })
138
138
 
139
139
  // Normalize database values (e.g., convert 1/0 to true/false for booleans)
140
- context.record = normalizeRecordAttributes(context.record, scopes);
140
+ context.record = normalizeRecordAttributes(context.record, scopes)
141
141
 
142
142
  // Make a backup
143
143
  try {
144
- context.originalRecord = structuredClone(context.record);
144
+ context.originalRecord = structuredClone(context.record)
145
145
  } catch (e) {
146
146
  log.error('Failed to clone record:', {
147
147
  error: e.message,
148
148
  recordKeys: Object.keys(context.record || {}),
149
149
  hasHttpRequest: !!context.raw?.req
150
- });
151
- throw e;
150
+ })
151
+ throw e
152
152
  }
153
153
 
154
154
  // This will enhance record, which is the WHOLE JSON:API record
155
- await runHooks('enrichRecord');
155
+ await runHooks('enrichRecord')
156
156
 
157
157
  // Get computed field information for main resource
158
- const computedFields = scope.vars.schemaInfo?.computed || {};
159
- const requestedFields = context.queryParams.fields?.[scopeName];
160
- const requestedComputedFields = getRequestedComputedFields(scopeName, requestedFields, computedFields);
158
+ const computedFields = scope.vars.schemaInfo?.computed || {}
159
+ const requestedFields = context.queryParams.fields?.[scopeName]
160
+ const requestedComputedFields = getRequestedComputedFields(scopeName, requestedFields, computedFields)
161
161
 
162
162
  // Run enrichAttributes for every single set of attribute, calling it from the right scope
163
163
  for (const entry of context.record.data) {
164
- entry.attributes = await scope.enrichAttributes({
165
- attributes: entry.attributes,
164
+ entry.attributes = await scope.enrichAttributes({
165
+ attributes: entry.attributes,
166
166
  parentContext: context,
167
- requestedComputedFields: requestedComputedFields,
167
+ requestedComputedFields,
168
168
  isMainResource: true,
169
169
  computedDependencies: context.computedDependencies
170
- });
170
+ })
171
171
  }
172
172
  for (const entry of (context.record.included || [])) {
173
- const entryScope = scopes[entry.type];
174
- const entryComputed = entryScope.vars.schemaInfo?.computed || {};
175
- const entryRequestedFields = context.queryParams.fields?.[entry.type];
173
+ const entryScope = scopes[entry.type]
174
+ const entryComputed = entryScope.vars.schemaInfo?.computed || {}
175
+ const entryRequestedFields = context.queryParams.fields?.[entry.type]
176
176
  const entryRequestedComputed = getRequestedComputedFields(
177
- entry.type,
178
- entryRequestedFields,
177
+ entry.type,
178
+ entryRequestedFields,
179
179
  entryComputed
180
- );
181
-
182
- entry.attributes = await entryScope.enrichAttributes({
183
- attributes: entry.attributes,
180
+ )
181
+
182
+ entry.attributes = await entryScope.enrichAttributes({
183
+ attributes: entry.attributes,
184
184
  parentContext: context,
185
185
  requestedComputedFields: entryRequestedComputed,
186
186
  isMainResource: false,
187
187
  computedDependencies: entry.__$jsonrestapi_computed_deps$__
188
- });
188
+ })
189
189
  }
190
190
 
191
191
  // The called hooks should NOT change context.record
192
- await runHooks('finish');
193
- await runHooks('finishQuery');
194
-
192
+ await runHooks('finish')
193
+ await runHooks('finishQuery')
194
+
195
195
  // Transform output if in simplified mode
196
196
  if (context.simplified) {
197
197
  // Convert JSON:API response back to simplified format
198
- // Example: {data: {type: 'posts', id: '1', attributes: {title: 'My Post'}, relationships: {author: {data: {type: 'users', id: '123'}}}}}
198
+ // Example: {data: {type: 'posts', id: '1', attributes: {title: 'My Post'}, relationships: {author: {data: {type: 'users', id: '123'}}}}}
199
199
  // becomes: {id: '1', title: 'My Post', author_id: '123'} - flattens structure and restores foreign keys
200
200
  return transformJsonApiToSimplified(
201
201
  { record: context.record },
202
202
  { context: { schemaStructure, schemaRelationships, scopes } }
203
- );
203
+ )
204
204
  }
205
-
206
- return context.record;
207
- }
205
+
206
+ return context.record
207
+ }
@@ -1,13 +1,12 @@
1
- import { ValidationError } from 'hooked-api';
1
+ import { ValidationError } from 'hooked-api'
2
2
 
3
3
  /**
4
4
  * release
5
5
  * Runs the hook that tells plugins to release resources
6
6
  */
7
7
  export default async ({ runHooks }) => {
8
-
9
8
  // Run the addRoute hook to notify transport plugins
10
- await runHooks('release');
11
-
12
- return { };
13
- }
9
+ await runHooks('release')
10
+
11
+ return { }
12
+ }