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
- import { RestApiResourceError } from '../../../lib/rest-api-errors.js';
2
- import { validateGetPayload } 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 { RestApiResourceError } from '../../../lib/rest-api-errors.js'
2
+ import { validateGetPayload } 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
  * GET
@@ -14,171 +14,171 @@ import { cascadeConfig } from './common.js';
14
14
  * @param {object} [queryParams.fields] - An object to request only specific fields (sparse fieldsets). Keys are resource types, values are comma-separated field names.
15
15
  * @returns {Promise<object>} A Promise that resolves to the JSON:API response document.
16
16
  */
17
- export default async function getMethod({
18
- params,
19
- context,
20
- vars,
21
- helpers,
22
- scope,
23
- scopes,
24
- runHooks,
25
- apiOptions,
26
- pluginOptions,
27
- scopeOptions,
17
+ export default async function getMethod ({
18
+ params,
19
+ context,
20
+ vars,
21
+ helpers,
22
+ scope,
23
+ scopes,
24
+ runHooks,
25
+ apiOptions,
26
+ pluginOptions,
27
+ scopeOptions,
28
28
  scopeName,
29
29
  api
30
30
  }) {
31
- context.method = 'get';
31
+ context.method = 'get'
32
32
 
33
33
  // Determine which simplified setting to use based on transport
34
- const isTransport = params.isTransport === true;
35
-
34
+ const isTransport = params.isTransport === true
35
+
36
36
  // Use vars which automatically cascade from scope to global
37
- const defaultSimplified = isTransport ? vars.simplifiedTransport : vars.simplifiedApi;
38
-
37
+ const defaultSimplified = isTransport ? vars.simplifiedTransport : vars.simplifiedApi
38
+
39
39
  // Get simplified setting - from params only (per-call override) or use default
40
- context.simplified = params.simplified !== undefined ? params.simplified : defaultSimplified;
40
+ context.simplified = params.simplified !== undefined ? params.simplified : defaultSimplified
41
41
 
42
42
  // Assign common context properties
43
- context.schemaInfo = scopes[scopeName].vars.schemaInfo;
44
- context.queryParams = params.queryParams || {};
43
+ context.schemaInfo = scopes[scopeName].vars.schemaInfo
44
+ context.queryParams = params.queryParams || {}
45
45
 
46
46
  // These only make sense as parameter per query
47
- context.queryParams.fields = cascadeConfig('fields', [context.queryParams], {});
48
- context.queryParams.include = cascadeConfig('include', [context.queryParams], []);
49
-
50
- context.transaction = params.transaction;
51
- context.db = context.transaction || api.knex.instance;
47
+ context.queryParams.fields = cascadeConfig('fields', [context.queryParams], {})
48
+ context.queryParams.include = cascadeConfig('include', [context.queryParams], [])
52
49
 
53
- context.scopeName = scopeName;
50
+ context.transaction = params.transaction
51
+ context.db = context.transaction || api.knex.instance
52
+
53
+ context.scopeName = scopeName
54
54
 
55
55
  // These are just shortcuts used in this function and will be returned
56
- const schemaStructure = context.schemaInfo.schemaInstance.structure;
57
- const schemaRelationships = context.schemaInfo.schemaRelationships;
56
+ const schemaStructure = context.schemaInfo.schemaInstance.structure
57
+ const schemaRelationships = context.schemaInfo.schemaRelationships
58
58
 
59
- context.id = params.id;
59
+ context.id = params.id
60
60
 
61
61
  // Validate GET request to ensure required parameters are present and properly formatted.
62
62
  // This checks that 'id' parameter exists and is not empty (you can't GET without an ID),
63
63
  // validates 'include' contains valid relationship names (not arbitrary fields), and ensures
64
64
  // 'fields' for sparse fieldsets follow the format fields[type]=comma,separated,list.
65
65
  // Example: validates id: '123' exists, include: ['author', 'tags'] are real relationships.
66
- validateGetPayload({ id: context.id, queryParams: context.queryParams }, vars.includeDepthLimit);
66
+ validateGetPayload({ id: context.id, queryParams: context.queryParams }, vars.includeDepthLimit)
67
67
 
68
68
  // Fetch minimal record for authorization checks
69
69
  const minimalRecord = await helpers.dataGetMinimal({
70
70
  scopeName,
71
71
  context,
72
72
  runHooks
73
- });
73
+ })
74
74
 
75
75
  if (!minimalRecord) {
76
76
  throw new RestApiResourceError(
77
- `Resource not found`,
78
- {
77
+ 'Resource not found',
78
+ {
79
79
  subtype: 'not_found',
80
80
  resourceType: scopeName,
81
81
  resourceId: context.id
82
82
  }
83
- );
83
+ )
84
84
  }
85
85
 
86
- context.minimalRecord = minimalRecord;
86
+ context.minimalRecord = minimalRecord
87
87
 
88
88
  // Centralised checkPermissions function
89
89
  await scope.checkPermissions({
90
90
  method: 'get',
91
91
  originalContext: context,
92
- });
93
-
94
- await runHooks('beforeData');
95
- await runHooks('beforeDataGet');
92
+ })
93
+
94
+ await runHooks('beforeData')
95
+ await runHooks('beforeDataGet')
96
96
 
97
97
  context.record = await helpers.dataGet({
98
98
  scopeName,
99
99
  context,
100
100
  runHooks
101
- });
101
+ })
102
102
 
103
103
  // Check if record was found - storage layer returns null/undefined for non-existent records.
104
104
  // This generates a proper 404 error with JSON:API error format instead of returning empty data.
105
105
  if (!context.record || !context.record.data) {
106
106
  throw new RestApiResourceError(
107
- `Resource not found`,
108
- {
107
+ 'Resource not found',
108
+ {
109
109
  subtype: 'not_found',
110
110
  resourceType: scopeName,
111
111
  resourceId: context.id
112
112
  }
113
- );
113
+ )
114
114
  }
115
115
 
116
116
  // Normalize database values (e.g., convert 1/0 to true/false for booleans)
117
- context.record = normalizeRecordAttributes(context.record, scopes);
117
+ context.record = normalizeRecordAttributes(context.record, scopes)
118
+
119
+ await runHooks('checkDataPermissions')
120
+ await runHooks('checkDataPermissionsGet')
118
121
 
119
- await runHooks('checkDataPermissions');
120
- await runHooks('checkDataPermissionsGet');
121
-
122
122
  // Make a backup
123
- context.originalRecord = structuredClone(context.record);
123
+ context.originalRecord = structuredClone(context.record)
124
124
 
125
125
  // This will enhance record, which is the WHOLE JSON:API record
126
- await runHooks('enrichRecord');
127
-
126
+ await runHooks('enrichRecord')
127
+
128
128
  // Get computed field information for main resource
129
- const computedFields = scope.vars.schemaInfo?.computed || {};
130
- const requestedFields = context.queryParams.fields?.[scopeName];
131
- const requestedComputedFields = getRequestedComputedFields(scopeName, requestedFields, computedFields);
132
-
129
+ const computedFields = scope.vars.schemaInfo?.computed || {}
130
+ const requestedFields = context.queryParams.fields?.[scopeName]
131
+ const requestedComputedFields = getRequestedComputedFields(scopeName, requestedFields, computedFields)
132
+
133
133
  // Enrich attributes for the main resource
134
134
  // Pass computedDependencies from context (set by dataGet in Knex plugin)
135
135
  // This tells enrichAttributes which fields to remove after computation
136
- context.record.data.attributes = await scope.enrichAttributes({
137
- attributes: context.record.data.attributes,
136
+ context.record.data.attributes = await scope.enrichAttributes({
137
+ attributes: context.record.data.attributes,
138
138
  parentContext: context,
139
- requestedComputedFields: requestedComputedFields,
139
+ requestedComputedFields,
140
140
  isMainResource: true,
141
141
  computedDependencies: context.computedDependencies // Fields to remove if not requested
142
- });
143
-
142
+ })
143
+
144
144
  for (const entry of (context.record.included || [])) {
145
- const entryScope = scopes[entry.type];
146
- const entryComputed = entryScope.vars.schemaInfo?.computed || {};
147
- const entryRequestedFields = context.queryParams.fields?.[entry.type];
145
+ const entryScope = scopes[entry.type]
146
+ const entryComputed = entryScope.vars.schemaInfo?.computed || {}
147
+ const entryRequestedFields = context.queryParams.fields?.[entry.type]
148
148
  const entryRequestedComputed = getRequestedComputedFields(
149
- entry.type,
150
- entryRequestedFields,
149
+ entry.type,
150
+ entryRequestedFields,
151
151
  entryComputed
152
- );
153
-
154
- entry.attributes = await entryScope.enrichAttributes({
155
- attributes: entry.attributes,
152
+ )
153
+
154
+ entry.attributes = await entryScope.enrichAttributes({
155
+ attributes: entry.attributes,
156
156
  parentContext: context,
157
157
  requestedComputedFields: entryRequestedComputed,
158
158
  isMainResource: false,
159
159
  computedDependencies: entry.__$jsonrestapi_computed_deps$__
160
- });
160
+ })
161
161
  }
162
-
163
- await runHooks('enrichRecordWithRelationships');
162
+
163
+ await runHooks('enrichRecordWithRelationships')
164
164
 
165
165
  // The called hooks should NOT change context.record
166
- await runHooks('finish');
167
- await runHooks('finishGet');
168
-
166
+ await runHooks('finish')
167
+ await runHooks('finishGet')
168
+
169
169
  // Get schema info for transformation
170
- context.schemaInfo = scopes[scopeName].vars.schemaInfo;
171
-
170
+ context.schemaInfo = scopes[scopeName].vars.schemaInfo
171
+
172
172
  // Transform output if in simplified mode
173
173
  if (context.simplified) {
174
174
  // Convert JSON:API response back to simplified format
175
- // Example: {data: {type: 'posts', id: '1', attributes: {title: 'My Post'}, relationships: {author: {data: {type: 'users', id: '123'}}}}}
175
+ // Example: {data: {type: 'posts', id: '1', attributes: {title: 'My Post'}, relationships: {author: {data: {type: 'users', id: '123'}}}}}
176
176
  // becomes: {id: '1', title: 'My Post', author_id: '123'} - flattens structure and restores foreign keys
177
177
  return transformJsonApiToSimplified(
178
178
  { record: context.record },
179
179
  { context: { schemaStructure, schemaRelationships, scopes } }
180
- );
180
+ )
181
181
  }
182
-
183
- return context.record;
184
- }
182
+
183
+ return context.record
184
+ }
@@ -2,27 +2,27 @@
2
2
  * PATCH RELATIONSHIP
3
3
  * Completely replaces a relationship
4
4
  * PATCH /api/articles/1/relationships/author
5
- *
5
+ *
6
6
  * @param {string} id - The ID of the resource
7
7
  * @param {string} relationshipName - The name of the relationship
8
8
  * @param {object|array} relationshipData - New relationship data
9
9
  * @returns {Promise<void>} 204 No Content
10
10
  */
11
11
  export default async function patchRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api }) {
12
- context.method = 'patchRelationship';
13
- context.id = params.id;
14
- context.relationshipName = params.relationshipName;
12
+ context.method = 'patchRelationship'
13
+ context.id = params.id
14
+ context.relationshipName = params.relationshipName
15
15
 
16
16
  // Transaction handling
17
- context.transaction = params.transaction ||
18
- (helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null);
19
- context.shouldCommit = !params.transaction && !!context.transaction;
17
+ context.transaction = params.transaction ||
18
+ (helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null)
19
+ context.shouldCommit = !params.transaction && !!context.transaction
20
20
  context.db = context.transaction || api.knex.instance
21
21
 
22
22
  try {
23
23
  // Check permissions
24
- await runHooks('checkPermissions');
25
- await runHooks('checkPermissionsPatchRelationship');
24
+ await runHooks('checkPermissions')
25
+ await runHooks('checkPermissionsPatchRelationship')
26
26
 
27
27
  // Reuse existing patch with relationship data
28
28
  await scope.patch({
@@ -39,18 +39,17 @@ export default async function patchRelationshipMethod ({ params, context, vars,
39
39
  transaction: context.transaction,
40
40
  simplified: false,
41
41
  isTransport: params.isTransport
42
- });
42
+ })
43
43
 
44
- await runHooks('finish');
45
- await runHooks('finishPatchRelationship');
44
+ await runHooks('finish')
45
+ await runHooks('finishPatchRelationship')
46
46
 
47
47
  if (context.shouldCommit) {
48
- await context.transaction.commit();
48
+ await context.transaction.commit()
49
49
  }
50
50
 
51
- return; // 204 No Content
52
-
51
+ // 204 No Content
53
52
  } catch (error) {
54
- await handleWriteMethodError(error, context, 'PATCH_RELATIONSHIP', scopeName, log);
53
+ await handleWriteMethodError(error, context, 'PATCH_RELATIONSHIP', scopeName, log)
55
54
  }
56
- }
55
+ }
@@ -1,17 +1,17 @@
1
- import { RestApiResourceError } from '../../../lib/rest-api-errors.js';
2
- import { validatePatchPayload } from '../lib/querying-writing/payload-validators.js';
3
- import { processRelationships } from '../lib/writing/relationship-processor.js';
4
- import { updateManyToManyRelationship } 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 { validatePatchPayload } from '../lib/querying-writing/payload-validators.js'
3
+ import { processRelationships } from '../lib/writing/relationship-processor.js'
4
+ import { updateManyToManyRelationship } 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
  * PATCH
@@ -21,39 +21,39 @@ import {
21
21
  * For relationships, only the ones explicitly provided will be updated.
22
22
  * Just like PUT, it CANNOT have the `included` array in data.
23
23
  */
24
- export default async function patchMethod({
25
- params,
26
- context,
27
- vars,
28
- helpers,
29
- scope,
30
- scopes,
31
- runHooks,
32
- apiOptions,
33
- pluginOptions,
34
- scopeOptions,
24
+ export default async function patchMethod ({
25
+ params,
26
+ context,
27
+ vars,
28
+ helpers,
29
+ scope,
30
+ scopes,
31
+ runHooks,
32
+ apiOptions,
33
+ pluginOptions,
34
+ scopeOptions,
35
35
  scopeName,
36
36
  api,
37
37
  log
38
38
  }) {
39
- context.method = 'patch';
40
-
39
+ context.method = 'patch'
40
+
41
41
  try {
42
42
  const { schema, schemaStructure, schemaRelationships } = await setupCommonRequest({
43
- params,
44
- context,
45
- vars,
46
- scopes,
47
- scopeOptions,
43
+ params,
44
+ context,
45
+ vars,
46
+ scopes,
47
+ scopeOptions,
48
48
  scopeName,
49
- api,
49
+ api,
50
50
  helpers
51
- });
52
- context.id = context.inputRecord.data.id;
53
-
51
+ })
52
+ context.id = context.inputRecord.data.id
53
+
54
54
  // Run early hooks for pre-processing (e.g., file handling)
55
- await runHooks('beforeProcessing');
56
- await runHooks('beforeProcessingPatch');
55
+ await runHooks('beforeProcessing')
56
+ await runHooks('beforeProcessingPatch')
57
57
 
58
58
  // Validate PATCH payload to ensure the partial update actually contains changes.
59
59
  // PATCH requests must include either attributes to update or relationships to modify -
@@ -61,11 +61,11 @@ export default async function patchMethod({
61
61
  // are explicit about what they want to change. Unlike PUT, PATCH preserves all fields
62
62
  // not mentioned in the request.
63
63
  // Example: data must have either attributes: {title: 'New'} or relationships: {author: {...}}
64
- validatePatchPayload(context.inputRecord, scopes);
65
-
64
+ validatePatchPayload(context.inputRecord, scopes)
65
+
66
66
  // Validate that user has read access to all related resources
67
67
  // This ensures users can only create relationships to resources they can access
68
- await validateRelationshipAccess(context, context.inputRecord, helpers, runHooks, api);
68
+ await validateRelationshipAccess(context, context.inputRecord, helpers, runHooks, api)
69
69
 
70
70
  // Extract foreign keys from JSON:API relationships and prepare many-to-many operations
71
71
  // Example: relationships.author -> author_id: '123' for storage
@@ -73,48 +73,48 @@ export default async function patchMethod({
73
73
  const { belongsToUpdates, manyToManyRelationships } = processRelationships(
74
74
  scope,
75
75
  { context }
76
- );
76
+ )
77
77
 
78
- await validateResourceAttributesBeforeWrite({
79
- context,
80
- schema,
81
- belongsToUpdates,
78
+ await validateResourceAttributesBeforeWrite({
79
+ context,
80
+ schema,
81
+ belongsToUpdates,
82
82
  runHooks,
83
- isPartialValidation: true
84
- });
83
+ isPartialValidation: true
84
+ })
85
85
 
86
86
  // Fetch minimal record for authorization checks
87
87
  const minimalRecord = await helpers.dataGetMinimal({
88
88
  scopeName,
89
89
  context,
90
90
  runHooks
91
- });
91
+ })
92
92
 
93
93
  if (!minimalRecord) {
94
94
  throw new RestApiResourceError(
95
95
  `Resource not found: ${scopeName}/${context.id}`,
96
96
  ERROR_SUBTYPES.NOT_FOUND
97
- );
97
+ )
98
98
  }
99
99
 
100
- context.minimalRecord = minimalRecord;
100
+ context.minimalRecord = minimalRecord
101
101
 
102
102
  // Centralised checkPermissions function
103
103
  await scope.checkPermissions({
104
104
  method: 'patch',
105
105
  originalContext: context,
106
- });
106
+ })
107
107
 
108
108
  // Merge belongsTo updates into attributes before patching the record
109
109
  if (Object.keys(belongsToUpdates).length > 0) {
110
110
  context.inputRecord.data.attributes = {
111
111
  ...context.inputRecord.data.attributes,
112
112
  ...belongsToUpdates
113
- };
113
+ }
114
114
  }
115
115
 
116
- await runHooks('beforeDataCall');
117
- await runHooks('beforeDataCallPatch');
116
+ await runHooks('beforeDataCall')
117
+ await runHooks('beforeDataCallPatch')
118
118
 
119
119
  // Apply field setters after validation and before storage
120
120
  if (context.inputRecord?.data?.attributes) {
@@ -124,17 +124,17 @@ export default async function patchMethod({
124
124
  context,
125
125
  api,
126
126
  helpers
127
- );
127
+ )
128
128
  }
129
129
 
130
130
  // Call the storage helper - should return the patched record
131
131
  await helpers.dataPatch({
132
132
  scopeName,
133
133
  context
134
- });
134
+ })
135
135
 
136
- await runHooks('afterDataCallPatch');
137
- await runHooks('afterDataCall');
136
+ await runHooks('afterDataCallPatch')
137
+ await runHooks('afterDataCall')
138
138
 
139
139
  // Process many-to-many relationships after main record update
140
140
  // For PATCH, we only update the relationships that were explicitly provided
@@ -147,11 +147,11 @@ export default async function patchMethod({
147
147
  relDef,
148
148
  relData,
149
149
  isUpdate: true,
150
- });
151
- continue;
150
+ })
151
+ continue
152
152
  }
153
153
 
154
- validatePivotResource(scopes, relDef, relName);
154
+ validatePivotResource(scopes, relDef, relName)
155
155
 
156
156
  await updateManyToManyRelationship(null, {
157
157
  api,
@@ -161,9 +161,9 @@ export default async function patchMethod({
161
161
  relData,
162
162
  transaction: context.transaction
163
163
  }
164
- });
164
+ })
165
165
  }
166
-
166
+
167
167
  const ret = await handleRecordReturnAfterWrite({
168
168
  context,
169
169
  scopeName,
@@ -176,17 +176,16 @@ export default async function patchMethod({
176
176
  runHooks,
177
177
  helpers,
178
178
  log
179
- });
179
+ })
180
180
 
181
181
  // Commit transaction if we created it
182
182
  if (context.shouldCommit) {
183
- await context.transaction.commit();
184
- await runHooks('afterCommit');
183
+ await context.transaction.commit()
184
+ await runHooks('afterCommit')
185
185
  }
186
186
 
187
- return ret;
188
-
187
+ return ret
189
188
  } catch (error) {
190
- await handleWriteMethodError(error, context, 'PATCH', scopeName, log, runHooks);
189
+ await handleWriteMethodError(error, context, 'PATCH', scopeName, log, runHooks)
191
190
  }
192
191
  }