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.
- package/connectors_ideas/fastify.js +66 -68
- package/connectors_ideas/koa-plugin.js +50 -51
- package/eslint.config.js +11 -2
- package/examples/url-override-example.js +63 -63
- package/index.js +13 -13
- package/lib/enhanced-logger.js +51 -51
- package/lib/error-formatter.js +61 -61
- package/lib/rest-api-errors.js +28 -28
- package/package.json +1 -1
- package/plugins/core/bulk-operations-plugin.js +142 -145
- package/plugins/core/connectors/express-plugin.js +210 -217
- package/plugins/core/connectors/lib/busboy-detector.js +84 -84
- package/plugins/core/connectors/lib/formidable-detector.js +50 -50
- package/plugins/core/connectors/lib/request-helpers.js +74 -74
- package/plugins/core/file-handling-plugin.js +125 -125
- package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
- package/plugins/core/lib/anyapi/schema-utils.js +101 -101
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
- package/plugins/core/lib/dbTablesOperations.js +134 -135
- package/plugins/core/lib/fractional-positioning.js +74 -76
- package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
- package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
- package/plugins/core/lib/querying/knex-process-includes.js +50 -50
- package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
- package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
- package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
- package/plugins/core/lib/querying/knex-window-queries.js +89 -89
- package/plugins/core/lib/querying/url-helpers.js +35 -35
- package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
- package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
- package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
- package/plugins/core/lib/querying-writing/field-utils.js +44 -42
- package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
- package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
- package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
- package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
- package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
- package/plugins/core/lib/storage/storage-adapter.js +68 -68
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
- package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
- package/plugins/core/lib/writing/relationship-processor.js +58 -58
- package/plugins/core/rest-api-access.js +8 -8
- package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
- package/plugins/core/rest-api-cors-plugin.js +170 -171
- package/plugins/core/rest-api-knex-plugin.js +482 -493
- package/plugins/core/rest-api-multihome-plugin.js +247 -251
- package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
- package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
- package/plugins/core/rest-api-plugin-methods/common.js +295 -297
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
- package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
- package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
- package/plugins/core/rest-api-plugin-methods/get.js +86 -86
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
- package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
- package/plugins/core/rest-api-plugin-methods/post.js +61 -62
- package/plugins/core/rest-api-plugin-methods/put.js +86 -87
- package/plugins/core/rest-api-plugin-methods/query.js +88 -88
- package/plugins/core/rest-api-plugin-methods/release.js +5 -6
- package/plugins/core/rest-api-plugin.js +76 -78
- package/plugins/core/rest-api-positioning-plugin.js +251 -252
- package/plugins/core/socketio-plugin.js +240 -236
- package/plugins/storage/local-storage.js +103 -103
- package/plugins/storage/s3-storage.js +41 -42
- package/quickTest.js +37 -37
- package/scripts/debug-anyapi-review-filter.mjs +18 -18
- package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
- package/scripts/inspect-anyapi-descriptor.mjs +10 -10
- package/scripts/run-docs.js +112 -113
- package/test-debug.js +28 -28
- package/tests/TEST_TEMPLATE.test.js +51 -51
- package/tests/anyapi-basic.test.js +42 -42
- package/tests/anyapi-cursor-pagination.test.js +48 -48
- package/tests/bulk-operations.test.js +208 -208
- package/tests/computed-fields.test.js +204 -204
- package/tests/cors.test.js +412 -413
- package/tests/curl.test.js +275 -276
- package/tests/custom-idproperty-relationships.test.js +266 -266
- package/tests/field-getters.test.js +132 -132
- package/tests/field-setters.test.js +110 -110
- package/tests/fixtures/api-configs.js +699 -699
- package/tests/helpers/storage-mode.js +32 -32
- package/tests/helpers/test-utils.js +76 -76
- package/tests/include-depth-validation.test.js +76 -76
- package/tests/includes.test.js +145 -145
- package/tests/multihome.test.js +175 -175
- package/tests/nested-includes.test.js +255 -255
- package/tests/pagination-cursor-multifield.test.js +164 -164
- package/tests/pagination-enhanced.test.js +225 -225
- package/tests/polymorphic-relationships.test.js +96 -96
- package/tests/polymorphic-simple.test.js +44 -44
- package/tests/positioning-advanced.test.js +226 -226
- package/tests/positioning-extended.test.js +399 -399
- package/tests/positioning-multifilter.test.js +204 -204
- package/tests/positioning.test.js +266 -266
- package/tests/queries.test.js +184 -184
- package/tests/query-limits.test.js +154 -155
- package/tests/relationship-endpoints.test.js +127 -127
- package/tests/relationships.test.js +178 -179
- package/tests/rest-api-access.test.js +86 -86
- package/tests/rest-api.test.js +103 -103
- package/tests/searchschema-merge.test.js +93 -93
- package/tests/socketio.test.js +326 -326
- package/tests/virtual-fields.test.js +164 -164
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import { getForeignKeyFields as getForeignKeyFieldsFromUtils } from '../querying-writing/field-utils.js'
|
|
2
|
-
import { RELATIONSHIPS_KEY, RELATIONSHIP_METADATA_KEY, ROW_NUMBER_KEY, COMPUTED_DEPENDENCIES_KEY, getSchemaStructure } from '../querying-writing/knex-constants.js'
|
|
3
|
-
import { getUrlPrefix, buildResourceUrl, buildRelationshipUrl } from './url-helpers.js'
|
|
1
|
+
import { getForeignKeyFields as getForeignKeyFieldsFromUtils } from '../querying-writing/field-utils.js'
|
|
2
|
+
import { RELATIONSHIPS_KEY, RELATIONSHIP_METADATA_KEY, ROW_NUMBER_KEY, COMPUTED_DEPENDENCIES_KEY, getSchemaStructure } from '../querying-writing/knex-constants.js'
|
|
3
|
+
import { getUrlPrefix, buildResourceUrl, buildRelationshipUrl } from './url-helpers.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Transforms a flat database record into JSON:API resource format
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* @private
|
|
9
9
|
* @param {Object} scope - Scope containing schema and configuration
|
|
10
10
|
* @param {Object} record - Database record to transform
|
|
11
11
|
* @param {Object} deps - Dependencies including context with scopeName and polymorphicFields
|
|
12
12
|
* @returns {Object|null} JSON:API resource object or null if no record
|
|
13
|
-
*
|
|
13
|
+
*
|
|
14
14
|
* @example
|
|
15
15
|
* // Input: Database record with foreign keys and internal fields
|
|
16
|
-
* const record = {
|
|
17
|
-
* id: 1,
|
|
18
|
-
* title: 'Hello World',
|
|
16
|
+
* const record = {
|
|
17
|
+
* id: 1,
|
|
18
|
+
* title: 'Hello World',
|
|
19
19
|
* content: 'Article content',
|
|
20
20
|
* author_id: 2, // Foreign key
|
|
21
21
|
* category_id: 5, // Foreign key
|
|
22
22
|
* __$jsonrestapi_rn$__: 1 // Internal field
|
|
23
23
|
* };
|
|
24
|
-
*
|
|
24
|
+
*
|
|
25
25
|
* const jsonApiResource = toJsonApi(scope, record, deps);
|
|
26
|
-
*
|
|
26
|
+
*
|
|
27
27
|
* // Output: Clean JSON:API resource
|
|
28
28
|
* // {
|
|
29
29
|
* // type: 'articles',
|
|
@@ -34,7 +34,7 @@ import { getUrlPrefix, buildResourceUrl, buildRelationshipUrl } from './url-help
|
|
|
34
34
|
* // // Note: author_id, category_id, and internal fields are removed
|
|
35
35
|
* // }
|
|
36
36
|
* // }
|
|
37
|
-
*
|
|
37
|
+
*
|
|
38
38
|
* @example
|
|
39
39
|
* // Input: Polymorphic record
|
|
40
40
|
* const record = {
|
|
@@ -44,9 +44,9 @@ import { getUrlPrefix, buildResourceUrl, buildRelationshipUrl } from './url-help
|
|
|
44
44
|
* commentable_id: 1 // Polymorphic id field
|
|
45
45
|
* };
|
|
46
46
|
* // deps.context.polymorphicFields = Set(['commentable_type', 'commentable_id'])
|
|
47
|
-
*
|
|
47
|
+
*
|
|
48
48
|
* const jsonApiResource = toJsonApi(scope, record, deps);
|
|
49
|
-
*
|
|
49
|
+
*
|
|
50
50
|
* // Output: Polymorphic fields filtered out
|
|
51
51
|
* // {
|
|
52
52
|
* // type: 'comments',
|
|
@@ -55,79 +55,79 @@ import { getUrlPrefix, buildResourceUrl, buildRelationshipUrl } from './url-help
|
|
|
55
55
|
* // text: 'Great article!'
|
|
56
56
|
* // }
|
|
57
57
|
* // }
|
|
58
|
-
*
|
|
58
|
+
*
|
|
59
59
|
* @description
|
|
60
60
|
* Used by:
|
|
61
61
|
* - toJsonApiRecord calls this as the core transformation logic
|
|
62
62
|
* - buildJsonApiResponse uses this indirectly through toJsonApiRecord
|
|
63
|
-
*
|
|
63
|
+
*
|
|
64
64
|
* Purpose:
|
|
65
65
|
* - Separates database structure from API structure
|
|
66
66
|
* - Filters out foreign keys that become relationships in JSON:API
|
|
67
67
|
* - Removes internal fields used for query optimization
|
|
68
68
|
* - Ensures clean attribute objects without implementation details
|
|
69
|
-
*
|
|
69
|
+
*
|
|
70
70
|
* Data flow:
|
|
71
71
|
* - Called after database query returns flat records
|
|
72
72
|
* - Transforms each record individually
|
|
73
73
|
* - Output feeds into relationship building and response assembly
|
|
74
74
|
*/
|
|
75
75
|
const toJsonApi = (scope, record, deps) => {
|
|
76
|
-
if (!record) return null
|
|
77
|
-
|
|
76
|
+
if (!record) return null
|
|
77
|
+
|
|
78
78
|
const schemaInstance = scope.vars.schemaInfo.schemaInstance
|
|
79
|
-
|
|
80
|
-
const { context } = deps
|
|
81
|
-
const scopeName = context.scopeName
|
|
82
|
-
const idProperty = context.schemaInfo?.idProperty || 'id'
|
|
83
|
-
const polymorphicFields = context.polymorphicFields || new Set()
|
|
84
|
-
|
|
85
|
-
const { id, ...allAttributes } = record
|
|
86
|
-
|
|
87
|
-
const foreignKeys = schemaInstance ? getForeignKeyFieldsFromUtils(schemaInstance) : new Set()
|
|
88
|
-
|
|
79
|
+
|
|
80
|
+
const { context } = deps
|
|
81
|
+
const scopeName = context.scopeName
|
|
82
|
+
const idProperty = context.schemaInfo?.idProperty || 'id'
|
|
83
|
+
const polymorphicFields = context.polymorphicFields || new Set()
|
|
84
|
+
|
|
85
|
+
const { id, ...allAttributes } = record
|
|
86
|
+
|
|
87
|
+
const foreignKeys = schemaInstance ? getForeignKeyFieldsFromUtils(schemaInstance) : new Set()
|
|
88
|
+
|
|
89
89
|
const internalFields = new Set([
|
|
90
90
|
RELATIONSHIPS_KEY,
|
|
91
91
|
RELATIONSHIP_METADATA_KEY,
|
|
92
92
|
ROW_NUMBER_KEY,
|
|
93
93
|
COMPUTED_DEPENDENCIES_KEY
|
|
94
|
-
])
|
|
95
|
-
|
|
96
|
-
const attributes = {}
|
|
94
|
+
])
|
|
95
|
+
|
|
96
|
+
const attributes = {}
|
|
97
97
|
Object.entries(allAttributes).forEach(([key, value]) => {
|
|
98
98
|
if (!foreignKeys.has(key) && !polymorphicFields.has(key) && !internalFields.has(key) && key !== idProperty) {
|
|
99
|
-
attributes[key] = value
|
|
99
|
+
attributes[key] = value
|
|
100
100
|
}
|
|
101
|
-
})
|
|
102
|
-
|
|
101
|
+
})
|
|
102
|
+
|
|
103
103
|
return {
|
|
104
104
|
type: scopeName,
|
|
105
105
|
id: String(id),
|
|
106
106
|
attributes
|
|
107
|
-
}
|
|
108
|
-
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
109
|
|
|
110
110
|
/**
|
|
111
111
|
* Converts a database record to JSON:API format with proper field filtering
|
|
112
|
-
*
|
|
112
|
+
*
|
|
113
113
|
* @param {Object} scope - Scope containing schema and relationship definitions
|
|
114
114
|
* @param {Object} record - Raw database record
|
|
115
115
|
* @param {string} scopeName - Resource type name
|
|
116
116
|
* @returns {Object} JSON:API formatted resource
|
|
117
|
-
*
|
|
117
|
+
*
|
|
118
118
|
* @example
|
|
119
119
|
* // Input: Database record with various field types
|
|
120
|
-
* const record = {
|
|
121
|
-
* id: 1,
|
|
122
|
-
* title: 'My Article',
|
|
120
|
+
* const record = {
|
|
121
|
+
* id: 1,
|
|
122
|
+
* title: 'My Article',
|
|
123
123
|
* author_id: 2, // belongsTo relationship
|
|
124
124
|
* category_id: 3, // belongsTo relationship
|
|
125
125
|
* views: 150,
|
|
126
126
|
* published: true
|
|
127
127
|
* };
|
|
128
|
-
*
|
|
128
|
+
*
|
|
129
129
|
* const jsonApi = toJsonApiRecord(scope, record, 'articles');
|
|
130
|
-
*
|
|
130
|
+
*
|
|
131
131
|
* // Output: Foreign keys filtered out
|
|
132
132
|
* // {
|
|
133
133
|
* // type: 'articles',
|
|
@@ -138,7 +138,7 @@ const toJsonApi = (scope, record, deps) => {
|
|
|
138
138
|
* // published: true
|
|
139
139
|
* // }
|
|
140
140
|
* // }
|
|
141
|
-
*
|
|
141
|
+
*
|
|
142
142
|
* @example
|
|
143
143
|
* // Input: Record with polymorphic relationship
|
|
144
144
|
* const record = {
|
|
@@ -149,9 +149,9 @@ const toJsonApi = (scope, record, deps) => {
|
|
|
149
149
|
* commentable_id: 3 // Polymorphic
|
|
150
150
|
* };
|
|
151
151
|
* // Schema has polymorphic relationship defined
|
|
152
|
-
*
|
|
152
|
+
*
|
|
153
153
|
* const jsonApi = toJsonApiRecord(scope, record, 'comments');
|
|
154
|
-
*
|
|
154
|
+
*
|
|
155
155
|
* // Output: Both foreign keys and polymorphic fields filtered
|
|
156
156
|
* // {
|
|
157
157
|
* // type: 'comments',
|
|
@@ -160,20 +160,20 @@ const toJsonApi = (scope, record, deps) => {
|
|
|
160
160
|
* // body: 'Nice post!'
|
|
161
161
|
* // }
|
|
162
162
|
* // }
|
|
163
|
-
*
|
|
163
|
+
*
|
|
164
164
|
* @description
|
|
165
165
|
* Used by:
|
|
166
166
|
* - rest-api-knex-plugin's dataGet method for single resource responses
|
|
167
167
|
* - rest-api-knex-plugin's dataQuery method for collection responses
|
|
168
168
|
* - processIncludes when transforming included resources
|
|
169
169
|
* - buildJsonApiResponse as the primary transformation function
|
|
170
|
-
*
|
|
170
|
+
*
|
|
171
171
|
* Purpose:
|
|
172
172
|
* - Provides consistent JSON:API transformation across all query operations
|
|
173
173
|
* - Automatically identifies and filters foreign keys based on schema
|
|
174
174
|
* - Handles polymorphic relationship fields (type/id pairs)
|
|
175
175
|
* - Ensures IDs are always strings as required by JSON:API spec
|
|
176
|
-
*
|
|
176
|
+
*
|
|
177
177
|
* Data flow:
|
|
178
178
|
* 1. Database query returns flat records with all fields
|
|
179
179
|
* 2. toJsonApiRecord identifies foreign keys from schema
|
|
@@ -182,39 +182,39 @@ const toJsonApi = (scope, record, deps) => {
|
|
|
182
182
|
* 5. buildJsonApiResponse adds relationships and links to complete the response
|
|
183
183
|
*/
|
|
184
184
|
export const toJsonApiRecord = (scope, record, scopeName) => {
|
|
185
|
-
const {
|
|
186
|
-
vars: {
|
|
185
|
+
const {
|
|
186
|
+
vars: {
|
|
187
187
|
schemaInfo: { schemaRelationships: relationships, idProperty }
|
|
188
188
|
}
|
|
189
|
-
} = scope
|
|
190
|
-
|
|
191
|
-
const polymorphicFields = new Set()
|
|
189
|
+
} = scope
|
|
190
|
+
|
|
191
|
+
const polymorphicFields = new Set()
|
|
192
192
|
try {
|
|
193
193
|
Object.entries(relationships || {}).forEach(([relName, relDef]) => {
|
|
194
194
|
if (relDef.belongsToPolymorphic) {
|
|
195
|
-
const typeFieldName = relDef.belongsToPolymorphic.typeField || `${relName}_type
|
|
196
|
-
const idFieldName = relDef.belongsToPolymorphic.idField || `${relName}_id
|
|
197
|
-
polymorphicFields.add(typeFieldName)
|
|
198
|
-
polymorphicFields.add(idFieldName)
|
|
195
|
+
const typeFieldName = relDef.belongsToPolymorphic.typeField || `${relName}_type`
|
|
196
|
+
const idFieldName = relDef.belongsToPolymorphic.idField || `${relName}_id`
|
|
197
|
+
polymorphicFields.add(typeFieldName)
|
|
198
|
+
polymorphicFields.add(idFieldName)
|
|
199
199
|
}
|
|
200
|
-
})
|
|
200
|
+
})
|
|
201
201
|
} catch (e) {
|
|
202
202
|
}
|
|
203
|
-
|
|
203
|
+
|
|
204
204
|
const deps = {
|
|
205
205
|
context: {
|
|
206
206
|
scopeName,
|
|
207
207
|
schemaInfo: { idProperty },
|
|
208
208
|
polymorphicFields
|
|
209
209
|
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return toJsonApi(scope, record, deps)
|
|
213
|
-
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return toJsonApi(scope, record, deps)
|
|
213
|
+
}
|
|
214
214
|
|
|
215
215
|
/**
|
|
216
216
|
* Builds complete JSON:API response with data, relationships, links, and optional includes
|
|
217
|
-
*
|
|
217
|
+
*
|
|
218
218
|
* @async
|
|
219
219
|
* @param {Object} scope - Scope containing schema and configuration
|
|
220
220
|
* @param {Array<Object>} records - Primary records to include in response
|
|
@@ -223,7 +223,7 @@ export const toJsonApiRecord = (scope, record, scopeName) => {
|
|
|
223
223
|
* @param {string} scopeName - Resource type name
|
|
224
224
|
* @param {Object} context - Request context with pagination metadata
|
|
225
225
|
* @returns {Promise<Object>} Complete JSON:API response document
|
|
226
|
-
*
|
|
226
|
+
*
|
|
227
227
|
* @example
|
|
228
228
|
* // Input: Single article with author include
|
|
229
229
|
* const records = [{
|
|
@@ -233,15 +233,15 @@ export const toJsonApiRecord = (scope, record, scopeName) => {
|
|
|
233
233
|
* author_id: 2,
|
|
234
234
|
* category_id: 3
|
|
235
235
|
* }];
|
|
236
|
-
*
|
|
236
|
+
*
|
|
237
237
|
* const included = [{
|
|
238
238
|
* type: 'authors',
|
|
239
239
|
* id: '2',
|
|
240
240
|
* attributes: { name: 'John Doe', email: 'john@example.com' }
|
|
241
241
|
* }];
|
|
242
|
-
*
|
|
242
|
+
*
|
|
243
243
|
* const response = await buildJsonApiResponse(scope, records, included, true, 'articles', context);
|
|
244
|
-
*
|
|
244
|
+
*
|
|
245
245
|
* // Output: Complete JSON:API document
|
|
246
246
|
* // {
|
|
247
247
|
* // data: {
|
|
@@ -281,7 +281,7 @@ export const toJsonApiRecord = (scope, record, scopeName) => {
|
|
|
281
281
|
* // self: '/articles/1'
|
|
282
282
|
* // }
|
|
283
283
|
* // }
|
|
284
|
-
*
|
|
284
|
+
*
|
|
285
285
|
* @example
|
|
286
286
|
* // Input: Collection with pagination
|
|
287
287
|
* const records = [
|
|
@@ -289,7 +289,7 @@ export const toJsonApiRecord = (scope, record, scopeName) => {
|
|
|
289
289
|
* { id: 2, title: 'Article 2', author_id: 10 },
|
|
290
290
|
* { id: 3, title: 'Article 3', author_id: 11 }
|
|
291
291
|
* ];
|
|
292
|
-
*
|
|
292
|
+
*
|
|
293
293
|
* context.returnMeta = {
|
|
294
294
|
* paginationMeta: { page: 2, pageSize: 3, pageCount: 10, total: 30 },
|
|
295
295
|
* paginationLinks: {
|
|
@@ -300,9 +300,9 @@ export const toJsonApiRecord = (scope, record, scopeName) => {
|
|
|
300
300
|
* last: '/articles?page[number]=10&page[size]=3'
|
|
301
301
|
* }
|
|
302
302
|
* };
|
|
303
|
-
*
|
|
303
|
+
*
|
|
304
304
|
* const response = await buildJsonApiResponse(scope, records, [], false, 'articles', context);
|
|
305
|
-
*
|
|
305
|
+
*
|
|
306
306
|
* // Output: Collection with pagination metadata
|
|
307
307
|
* // {
|
|
308
308
|
* // data: [
|
|
@@ -321,20 +321,20 @@ export const toJsonApiRecord = (scope, record, scopeName) => {
|
|
|
321
321
|
* // last: '/articles?page[number]=10&page[size]=3'
|
|
322
322
|
* // }
|
|
323
323
|
* // }
|
|
324
|
-
*
|
|
324
|
+
*
|
|
325
325
|
* @description
|
|
326
326
|
* Used by:
|
|
327
327
|
* - rest-api-knex-plugin's dataGet method for single resource responses
|
|
328
328
|
* - rest-api-knex-plugin's dataQuery method for collection responses
|
|
329
329
|
* - Called at the end of the query pipeline to assemble the final response
|
|
330
|
-
*
|
|
330
|
+
*
|
|
331
331
|
* Purpose:
|
|
332
332
|
* - Assembles all parts of a JSON:API response in the correct structure
|
|
333
333
|
* - Adds relationship objects with proper links for each foreign key
|
|
334
334
|
* - Handles both regular belongsTo and polymorphic relationships
|
|
335
335
|
* - Adds self links to all resources as required by JSON:API
|
|
336
336
|
* - Includes pagination metadata and links when applicable
|
|
337
|
-
*
|
|
337
|
+
*
|
|
338
338
|
* Data flow:
|
|
339
339
|
* 1. Query operations fetch primary records and optional includes
|
|
340
340
|
* 2. Records are transformed to JSON:API format via toJsonApiRecord
|
|
@@ -345,27 +345,27 @@ export const toJsonApiRecord = (scope, record, scopeName) => {
|
|
|
345
345
|
* 7. Returns complete JSON:API document ready for HTTP response
|
|
346
346
|
*/
|
|
347
347
|
export const buildJsonApiResponse = async (scope, records, included = [], isSingle = false, scopeName, context) => {
|
|
348
|
-
const {
|
|
349
|
-
vars: {
|
|
350
|
-
schemaInfo: { schemaInstance
|
|
348
|
+
const {
|
|
349
|
+
vars: {
|
|
350
|
+
schemaInfo: { schemaInstance, schemaRelationships: relationships, idProperty }
|
|
351
351
|
}
|
|
352
|
-
} = scope
|
|
353
|
-
|
|
354
|
-
const idField = idProperty || 'id'
|
|
355
|
-
|
|
356
|
-
const schemaStructure = getSchemaStructure(schemaInstance)
|
|
357
|
-
|
|
352
|
+
} = scope
|
|
353
|
+
|
|
354
|
+
const idField = idProperty || 'id'
|
|
355
|
+
|
|
356
|
+
const schemaStructure = getSchemaStructure(schemaInstance)
|
|
357
|
+
|
|
358
358
|
const processedRecords = records.map(record => {
|
|
359
|
-
const { [RELATIONSHIPS_KEY]: _relationships, ...cleanRecord } = record
|
|
360
|
-
const jsonApiRecord = toJsonApiRecord(scope, cleanRecord, scopeName)
|
|
361
|
-
|
|
359
|
+
const { [RELATIONSHIPS_KEY]: _relationships, ...cleanRecord } = record
|
|
360
|
+
const jsonApiRecord = toJsonApiRecord(scope, cleanRecord, scopeName)
|
|
361
|
+
|
|
362
362
|
if (_relationships) {
|
|
363
|
-
jsonApiRecord.relationships = _relationships
|
|
363
|
+
jsonApiRecord.relationships = _relationships
|
|
364
364
|
}
|
|
365
|
-
|
|
365
|
+
|
|
366
366
|
for (const [fieldName, fieldDef] of Object.entries(schemaStructure)) {
|
|
367
367
|
if (fieldDef.belongsTo && fieldDef.as && fieldName in cleanRecord) {
|
|
368
|
-
jsonApiRecord.relationships = jsonApiRecord.relationships || {}
|
|
368
|
+
jsonApiRecord.relationships = jsonApiRecord.relationships || {}
|
|
369
369
|
if (!jsonApiRecord.relationships[fieldDef.as]) {
|
|
370
370
|
if (cleanRecord[fieldName] !== null && cleanRecord[fieldName] !== undefined) {
|
|
371
371
|
const relationshipObject = {
|
|
@@ -373,113 +373,113 @@ export const buildJsonApiResponse = async (scope, records, included = [], isSing
|
|
|
373
373
|
type: fieldDef.belongsTo,
|
|
374
374
|
id: String(cleanRecord[fieldName])
|
|
375
375
|
}
|
|
376
|
-
}
|
|
377
|
-
|
|
376
|
+
}
|
|
377
|
+
|
|
378
378
|
relationshipObject.links = {
|
|
379
379
|
self: buildRelationshipUrl(context, scope, scopeName, record[idField], fieldDef.as, true),
|
|
380
380
|
related: buildRelationshipUrl(context, scope, scopeName, record[idField], fieldDef.as, false)
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
jsonApiRecord.relationships[fieldDef.as] = relationshipObject
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
jsonApiRecord.relationships[fieldDef.as] = relationshipObject
|
|
384
384
|
} else {
|
|
385
385
|
const relationshipObject = {
|
|
386
386
|
data: null
|
|
387
|
-
}
|
|
388
|
-
|
|
387
|
+
}
|
|
388
|
+
|
|
389
389
|
relationshipObject.links = {
|
|
390
390
|
self: buildRelationshipUrl(context, scope, scopeName, record[idField], fieldDef.as, true),
|
|
391
391
|
related: buildRelationshipUrl(context, scope, scopeName, record[idField], fieldDef.as, false)
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
jsonApiRecord.relationships[fieldDef.as] = relationshipObject
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
jsonApiRecord.relationships[fieldDef.as] = relationshipObject
|
|
395
395
|
}
|
|
396
396
|
}
|
|
397
397
|
}
|
|
398
398
|
}
|
|
399
|
-
|
|
399
|
+
|
|
400
400
|
Object.entries(relationships || {}).forEach(([relName, relDef]) => {
|
|
401
401
|
if (relDef.belongsToPolymorphic) {
|
|
402
|
-
const typeValue = cleanRecord[relDef.belongsToPolymorphic.typeField]
|
|
403
|
-
const idValue = cleanRecord[relDef.belongsToPolymorphic.idField]
|
|
404
|
-
|
|
402
|
+
const typeValue = cleanRecord[relDef.belongsToPolymorphic.typeField]
|
|
403
|
+
const idValue = cleanRecord[relDef.belongsToPolymorphic.idField]
|
|
404
|
+
|
|
405
405
|
if (typeValue && idValue) {
|
|
406
|
-
jsonApiRecord.relationships = jsonApiRecord.relationships || {}
|
|
406
|
+
jsonApiRecord.relationships = jsonApiRecord.relationships || {}
|
|
407
407
|
const relationshipObject = {
|
|
408
408
|
data: {
|
|
409
409
|
type: typeValue,
|
|
410
410
|
id: String(idValue)
|
|
411
411
|
}
|
|
412
|
-
}
|
|
413
|
-
|
|
412
|
+
}
|
|
413
|
+
|
|
414
414
|
relationshipObject.links = {
|
|
415
415
|
self: buildRelationshipUrl(context, scope, scopeName, record[idField], relName, true),
|
|
416
416
|
related: buildRelationshipUrl(context, scope, scopeName, record[idField], relName, false)
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
jsonApiRecord.relationships[relName] = relationshipObject
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
jsonApiRecord.relationships[relName] = relationshipObject
|
|
420
420
|
} else if (typeValue === null || idValue === null) {
|
|
421
|
-
jsonApiRecord.relationships = jsonApiRecord.relationships || {}
|
|
421
|
+
jsonApiRecord.relationships = jsonApiRecord.relationships || {}
|
|
422
422
|
const relationshipObject = {
|
|
423
423
|
data: null
|
|
424
|
-
}
|
|
425
|
-
|
|
424
|
+
}
|
|
425
|
+
|
|
426
426
|
relationshipObject.links = {
|
|
427
427
|
self: buildRelationshipUrl(context, scope, scopeName, record[idField], relName, true),
|
|
428
428
|
related: buildRelationshipUrl(context, scope, scopeName, record[idField], relName, false)
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
jsonApiRecord.relationships[relName] = relationshipObject
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
jsonApiRecord.relationships[relName] = relationshipObject
|
|
432
432
|
}
|
|
433
433
|
}
|
|
434
|
-
})
|
|
435
|
-
|
|
436
|
-
return jsonApiRecord
|
|
437
|
-
})
|
|
438
|
-
|
|
439
|
-
const urlPrefix = context.urlPrefix || scope.vars.transport?.mountPath || ''
|
|
440
|
-
const normalizedData = isSingle ? processedRecords[0] : processedRecords
|
|
441
|
-
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
return jsonApiRecord
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
const urlPrefix = context.urlPrefix || scope.vars.transport?.mountPath || ''
|
|
440
|
+
const normalizedData = isSingle ? processedRecords[0] : processedRecords
|
|
441
|
+
|
|
442
442
|
if (normalizedData) {
|
|
443
443
|
if (Array.isArray(normalizedData)) {
|
|
444
444
|
normalizedData.forEach(item => {
|
|
445
|
-
if (!item.links) item.links = {}
|
|
446
|
-
item.links.self = buildResourceUrl(context, scope, scopeName, item.id)
|
|
447
|
-
})
|
|
445
|
+
if (!item.links) item.links = {}
|
|
446
|
+
item.links.self = buildResourceUrl(context, scope, scopeName, item.id)
|
|
447
|
+
})
|
|
448
448
|
} else {
|
|
449
|
-
if (!normalizedData.links) normalizedData.links = {}
|
|
450
|
-
normalizedData.links.self = buildResourceUrl(context, scope, scopeName, normalizedData.id)
|
|
449
|
+
if (!normalizedData.links) normalizedData.links = {}
|
|
450
|
+
normalizedData.links.self = buildResourceUrl(context, scope, scopeName, normalizedData.id)
|
|
451
451
|
}
|
|
452
452
|
}
|
|
453
453
|
|
|
454
454
|
const response = {
|
|
455
455
|
data: normalizedData
|
|
456
|
-
}
|
|
456
|
+
}
|
|
457
457
|
|
|
458
458
|
if (included.length > 0) {
|
|
459
459
|
included.forEach(item => {
|
|
460
|
-
if (!item.links) item.links = {}
|
|
461
|
-
item.links.self = buildResourceUrl(context, scope, item.type, item.id)
|
|
462
|
-
})
|
|
463
|
-
|
|
464
|
-
response.included = included
|
|
460
|
+
if (!item.links) item.links = {}
|
|
461
|
+
item.links.self = buildResourceUrl(context, scope, item.type, item.id)
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
response.included = included
|
|
465
465
|
}
|
|
466
466
|
|
|
467
467
|
if (context?.returnMeta?.paginationMeta) {
|
|
468
468
|
response.meta = {
|
|
469
469
|
pagination: context.returnMeta.paginationMeta
|
|
470
|
-
}
|
|
470
|
+
}
|
|
471
471
|
}
|
|
472
|
-
|
|
472
|
+
|
|
473
473
|
if (context?.returnMeta?.paginationLinks) {
|
|
474
|
-
response.links = context.returnMeta.paginationLinks
|
|
474
|
+
response.links = context.returnMeta.paginationLinks
|
|
475
475
|
} else {
|
|
476
|
-
const urlPrefix = getUrlPrefix(context, scope)
|
|
476
|
+
const urlPrefix = getUrlPrefix(context, scope)
|
|
477
477
|
response.links = {
|
|
478
|
-
self: isSingle
|
|
478
|
+
self: isSingle
|
|
479
479
|
? buildResourceUrl(context, scope, scopeName, normalizedData.id)
|
|
480
480
|
: `${urlPrefix}/${scopeName}${context?.returnMeta?.queryString || ''}`
|
|
481
|
-
}
|
|
481
|
+
}
|
|
482
482
|
}
|
|
483
483
|
|
|
484
|
-
return response
|
|
485
|
-
}
|
|
484
|
+
return response
|
|
485
|
+
}
|