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,67 +1,67 @@
1
- import { RestApiResourceError, RestApiValidationError, RestApiPayloadError } from "../../../lib/rest-api-errors.js";
2
- import { findRelationshipDefinition, handleWriteMethodError } from "./common.js";
3
- import { createPivotRecords } from "../lib/writing/many-to-many-manipulations.js";
1
+ import { RestApiResourceError, RestApiValidationError, RestApiPayloadError } from '../../../lib/rest-api-errors.js'
2
+ import { findRelationshipDefinition, handleWriteMethodError } from './common.js'
3
+ import { createPivotRecords } from '../lib/writing/many-to-many-manipulations.js'
4
4
 
5
5
  /**
6
6
  * POST RELATIONSHIP
7
7
  * Adds members to a to-many relationship
8
8
  * POST /api/articles/1/relationships/tags
9
- *
9
+ *
10
10
  * @param {string} id - The ID of the resource
11
11
  * @param {string} relationshipName - The name of the relationship
12
12
  * @param {array} relationshipData - Array of resource identifiers to add
13
13
  * @returns {Promise<void>} 204 No Content
14
- */
15
- export default async function postRelationshipMethod({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api, log }) {
16
- context.method = 'postRelationship';
17
- context.id = params.id;
18
- context.relationshipName = params.relationshipName;
19
- context.schemaInfo = scopes[scopeName].vars.schemaInfo;
14
+ */
15
+ export default async function postRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api, log }) {
16
+ context.method = 'postRelationship'
17
+ context.id = params.id
18
+ context.relationshipName = params.relationshipName
19
+ context.schemaInfo = scopes[scopeName].vars.schemaInfo
20
20
 
21
21
  // Transaction handling
22
- context.transaction = params.transaction ||
23
- (helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null);
24
- context.shouldCommit = !params.transaction && !!context.transaction;
22
+ context.transaction = params.transaction ||
23
+ (helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null)
24
+ context.shouldCommit = !params.transaction && !!context.transaction
25
25
 
26
26
  context.db = context.transaction || api.knex.instance
27
27
 
28
28
  try {
29
29
  // Validate
30
- const relDef = findRelationshipDefinition(context.schemaInfo, context.relationshipName);
31
- if (!relDef) {
30
+ const relDef = findRelationshipDefinition(context.schemaInfo, context.relationshipName)
31
+ if (!relDef) {
32
32
  throw new RestApiResourceError(
33
33
  `Relationship '${context.relationshipName}' not found on resource '${scopeName}'`,
34
34
  { subtype: 'relationship_not_found' }
35
- );
36
- }
35
+ )
36
+ }
37
37
 
38
- if (relDef.type !== 'hasMany' && relDef.type !== 'manyToMany') {
38
+ if (relDef.type !== 'hasMany' && relDef.type !== 'manyToMany') {
39
39
  throw new RestApiValidationError(
40
40
  `Cannot POST to to-one relationship '${context.relationshipName}'. Use PATCH instead.`,
41
41
  { fields: ['data'] }
42
- );
43
- }
42
+ )
43
+ }
44
44
 
45
- if (!Array.isArray(params.relationshipData)) {
46
- throw new RestApiPayloadError('POST to relationship requires array of resource identifiers');
47
- }
45
+ if (!Array.isArray(params.relationshipData)) {
46
+ throw new RestApiPayloadError('POST to relationship requires array of resource identifiers')
47
+ }
48
48
 
49
- // Check permissions
50
- await runHooks('checkPermissions');
51
- await runHooks('checkPermissionsPostRelationship');
49
+ // Check permissions
50
+ await runHooks('checkPermissions')
51
+ await runHooks('checkPermissionsPostRelationship')
52
52
 
53
- // Verify parent exists
54
- const exists = await helpers.dataExists({
53
+ // Verify parent exists
54
+ const exists = await helpers.dataExists({
55
55
  scopeName,
56
56
  context: { db: context.db, id: context.id, schemaInfo: context.schemaInfo },
57
- });
57
+ })
58
58
 
59
- if (!exists) {
60
- throw new RestApiResourceError('Resource not found', { subtype: 'not_found' });
61
- }
59
+ if (!exists) {
60
+ throw new RestApiResourceError('Resource not found', { subtype: 'not_found' })
61
+ }
62
62
 
63
- // Add relationships
64
- if (relDef?.through) {
63
+ // Add relationships
64
+ if (relDef?.through) {
65
65
  if (api.anyapi?.links?.attachMany) {
66
66
  await api.anyapi.links.attachMany({
67
67
  context,
@@ -69,43 +69,42 @@ import { createPivotRecords } from "../lib/writing/many-to-many-manipulations.js
69
69
  relName: context.relationshipName,
70
70
  relDef,
71
71
  relData: params.relationshipData,
72
- });
72
+ })
73
73
  } else {
74
74
  await createPivotRecords(api, context.id, {
75
- through: relDef.through,
76
- foreignKey: relDef.foreignKey,
77
- otherKey: relDef.otherKey
78
- }, params.relationshipData, context.transaction);
75
+ through: relDef.through,
76
+ foreignKey: relDef.foreignKey,
77
+ otherKey: relDef.otherKey
78
+ }, params.relationshipData, context.transaction)
79
79
  }
80
- } else if (relDef.type === 'hasMany') {
80
+ } else if (relDef.type === 'hasMany') {
81
81
  // Update foreign keys for hasMany
82
- const targetType = relDef.target;
82
+ const targetType = relDef.target
83
83
  for (const identifier of params.relationshipData) {
84
- await api.resources[targetType].patch({
84
+ await api.resources[targetType].patch({
85
85
  id: identifier.id,
86
86
  inputRecord: {
87
- data: {
87
+ data: {
88
88
  type: targetType,
89
89
  id: identifier.id,
90
90
  attributes: { [relDef.foreignKey]: context.id }
91
- }
91
+ }
92
92
  },
93
93
  transaction: context.transaction,
94
94
  simplified: false
95
- });
95
+ })
96
96
  }
97
- }
98
-
99
- await runHooks('finish');
100
- await runHooks('finishPostRelationship');
97
+ }
101
98
 
102
- if (context.shouldCommit) {
103
- await context.transaction.commit();
104
- }
99
+ await runHooks('finish')
100
+ await runHooks('finishPostRelationship')
105
101
 
106
- return; // 204 No Content
102
+ if (context.shouldCommit) {
103
+ await context.transaction.commit()
104
+ }
107
105
 
106
+ // 204 No Content
108
107
  } catch (error) {
109
- await handleWriteMethodError(error, context, 'POST_RELATIONSHIP', scopeName, log, runHooks);
110
- }
108
+ await handleWriteMethodError(error, context, 'POST_RELATIONSHIP', scopeName, log, runHooks)
109
+ }
111
110
  };
@@ -1,15 +1,15 @@
1
- import { validatePostPayload } from '../lib/querying-writing/payload-validators.js';
2
- import { processRelationships } from '../lib/writing/relationship-processor.js';
3
- import { createPivotRecords } from '../lib/writing/many-to-many-manipulations.js';
4
- import {
5
- setupCommonRequest,
1
+ import { validatePostPayload } from '../lib/querying-writing/payload-validators.js'
2
+ import { processRelationships } from '../lib/writing/relationship-processor.js'
3
+ import { createPivotRecords } from '../lib/writing/many-to-many-manipulations.js'
4
+ import {
5
+ setupCommonRequest,
6
6
  validateResourceAttributesBeforeWrite,
7
7
  validateRelationshipAccess,
8
8
  applyFieldSetters,
9
9
  validatePivotResource,
10
10
  handleRecordReturnAfterWrite,
11
11
  handleWriteMethodError
12
- } from './common.js';
12
+ } from './common.js'
13
13
 
14
14
  /**
15
15
  * POST
@@ -17,92 +17,92 @@ import {
17
17
  * containing 'type' and 'attributes'. It can also establish relationships to existing resources.
18
18
  * The returned document contains the created resource with its server-assigned ID.
19
19
  */
20
- export default async function postMethod({
21
- params,
22
- context,
23
- vars,
24
- helpers,
25
- scope,
26
- scopes,
27
- runHooks,
28
- apiOptions,
29
- pluginOptions,
30
- scopeOptions,
20
+ export default async function postMethod ({
21
+ params,
22
+ context,
23
+ vars,
24
+ helpers,
25
+ scope,
26
+ scopes,
27
+ runHooks,
28
+ apiOptions,
29
+ pluginOptions,
30
+ scopeOptions,
31
31
  scopeName,
32
32
  api,
33
33
  log
34
34
  }) {
35
- context.method = 'post';
35
+ context.method = 'post'
36
36
 
37
37
  try {
38
38
  const { schema, schemaStructure, schemaRelationships } = await setupCommonRequest({
39
- params,
40
- context,
41
- vars,
42
- scopes,
43
- scopeOptions,
44
- scopeName,
39
+ params,
40
+ context,
41
+ vars,
42
+ scopes,
43
+ scopeOptions,
44
+ scopeName,
45
45
  api,
46
46
  helpers
47
- });
47
+ })
48
48
 
49
49
  // Run early hooks for pre-processing (e.g., file handling)
50
- await runHooks('beforeProcessing');
51
- await runHooks('beforeProcessingPost');
50
+ await runHooks('beforeProcessing')
51
+ await runHooks('beforeProcessingPost')
52
52
 
53
53
  // Validate POST payload to ensure it follows JSON:API format and references valid resources.
54
54
  // This checks the payload has required 'data' object with 'type' and 'attributes', validates
55
55
  // that data.type matches a real resource type (preventing creation of non-existent resources),
56
56
  // and ensures any relationships reference valid resource types with proper ID format.
57
57
  // Example: data.type: 'articles' must be a registered scope, relationships.author must reference 'users'.
58
- validatePostPayload(context.inputRecord, scopes);
59
-
58
+ validatePostPayload(context.inputRecord, scopes)
59
+
60
60
  // Validate that user has read access to all related resources
61
61
  // This ensures users can only create relationships to resources they can access
62
- await validateRelationshipAccess(context, context.inputRecord, helpers, runHooks, api);
63
-
62
+ await validateRelationshipAccess(context, context.inputRecord, helpers, runHooks, api)
63
+
64
64
  // Extract foreign keys from JSON:API relationships and prepare many-to-many operations
65
65
  // Example: relationships.author -> author_id: '123' for storage
66
66
  // Example: relationships.tags -> array of pivot records to create later
67
67
  const { belongsToUpdates, manyToManyRelationships } = await processRelationships(
68
68
  scope,
69
69
  { context }
70
- );
70
+ )
71
71
 
72
72
  // Merge belongsTo updates into attributes before validation (like PUT/PATCH do)
73
73
  if (Object.keys(belongsToUpdates).length > 0) {
74
74
  context.inputRecord.data.attributes = {
75
75
  ...context.inputRecord.data.attributes,
76
76
  ...belongsToUpdates
77
- };
77
+ }
78
78
  }
79
79
 
80
- await validateResourceAttributesBeforeWrite({
81
- context,
82
- schema,
83
- belongsToUpdates,
80
+ await validateResourceAttributesBeforeWrite({
81
+ context,
82
+ schema,
83
+ belongsToUpdates,
84
84
  runHooks
85
- });
85
+ })
86
86
 
87
87
  if (context.inputRecord?.data) {
88
- const inputData = context.inputRecord.data;
88
+ const inputData = context.inputRecord.data
89
89
  context.minimalRecord = {
90
90
  type: scopeName,
91
91
  ...(inputData.id !== undefined && inputData.id !== null ? { id: String(inputData.id) } : {}),
92
92
  attributes: structuredClone(inputData.attributes || {}),
93
93
  relationships: structuredClone(inputData.relationships || {})
94
- };
94
+ }
95
95
  }
96
96
 
97
97
  // Centralised checkPermissions function
98
98
  await scope.checkPermissions({
99
99
  method: 'post',
100
100
  originalContext: context,
101
- });
101
+ })
102
+
103
+ await runHooks('beforeDataCall')
104
+ await runHooks('beforeDataCallPost')
102
105
 
103
- await runHooks('beforeDataCall');
104
- await runHooks('beforeDataCallPost');
105
-
106
106
  // Apply field setters after validation and before storage
107
107
  if (context.inputRecord?.data?.attributes) {
108
108
  context.inputRecord.data.attributes = await applyFieldSetters(
@@ -111,18 +111,18 @@ export default async function postMethod({
111
111
  context,
112
112
  api,
113
113
  helpers
114
- );
114
+ )
115
115
  }
116
-
116
+
117
117
  // Create the main record - storage helper should return the created record with its ID
118
118
  context.id = await helpers.dataPost({
119
119
  scopeName,
120
120
  context
121
- });
122
-
123
- await runHooks('afterDataCallPost');
124
- await runHooks('afterDataCall');
125
-
121
+ })
122
+
123
+ await runHooks('afterDataCallPost')
124
+ await runHooks('afterDataCall')
125
+
126
126
  // Process many-to-many relationships after main record creation
127
127
  for (const { relName, relDef, relData } of manyToManyRelationships) {
128
128
  if (api.anyapi?.links?.attachMany && relDef?.through) {
@@ -132,15 +132,15 @@ export default async function postMethod({
132
132
  relName,
133
133
  relDef,
134
134
  relData,
135
- });
136
- continue;
135
+ })
136
+ continue
137
137
  }
138
138
 
139
139
  // Validate pivot resource exists
140
- validatePivotResource(scopes, relDef, relName);
141
- await createPivotRecords(api, context.id, relDef, relData, context.transaction);
140
+ validatePivotResource(scopes, relDef, relName)
141
+ await createPivotRecords(api, context.id, relDef, relData, context.transaction)
142
142
  }
143
-
143
+
144
144
  const ret = await handleRecordReturnAfterWrite({
145
145
  context,
146
146
  scopeName,
@@ -153,17 +153,16 @@ export default async function postMethod({
153
153
  runHooks,
154
154
  helpers,
155
155
  log
156
- });
156
+ })
157
157
 
158
158
  // Commit transaction if we created it
159
159
  if (context.shouldCommit) {
160
- await context.transaction.commit();
161
- await runHooks('afterCommit');
160
+ await context.transaction.commit()
161
+ await runHooks('afterCommit')
162
162
  }
163
163
 
164
- return ret;
165
-
164
+ return ret
166
165
  } catch (error) {
167
- await handleWriteMethodError(error, context, 'POST', scopeName, log, runHooks);
166
+ await handleWriteMethodError(error, context, 'POST', scopeName, log, runHooks)
168
167
  }
169
168
  }