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,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Transformation functions for simplified API mode
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* @description
|
|
5
5
|
* This module enables "simplified mode" - a developer-friendly alternative
|
|
6
6
|
* to verbose JSON:API format. It allows APIs to:
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
* - Maintain JSON:API compliance internally
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { RestApiValidationError } from '../../../../lib/rest-api-errors.js'
|
|
13
|
+
import { RestApiValidationError } from '../../../../lib/rest-api-errors.js'
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Transforms simplified plain object into JSON:API format
|
|
17
|
-
*
|
|
17
|
+
*
|
|
18
18
|
* @param {Object} scope - Scope containing input data
|
|
19
19
|
* @param {Object} scope.inputRecord - Plain object or JSON:API document
|
|
20
20
|
* @param {Object} deps - Dependencies with context
|
|
@@ -22,7 +22,7 @@ import { RestApiValidationError } from '../../../../lib/rest-api-errors.js';
|
|
|
22
22
|
* @param {Object} deps.context.schemaStructure - Schema with field definitions
|
|
23
23
|
* @param {Object} deps.context.schemaRelationships - Relationship configurations
|
|
24
24
|
* @returns {Object} JSON:API formatted document
|
|
25
|
-
*
|
|
25
|
+
*
|
|
26
26
|
* @example
|
|
27
27
|
* // Input: Simple object with only attributes
|
|
28
28
|
* const input = {
|
|
@@ -34,7 +34,7 @@ import { RestApiValidationError } from '../../../../lib/rest-api-errors.js';
|
|
|
34
34
|
* { inputRecord: input },
|
|
35
35
|
* { context: { scopeName: 'articles', schemaStructure: schema } }
|
|
36
36
|
* );
|
|
37
|
-
*
|
|
37
|
+
*
|
|
38
38
|
* // Output: Proper JSON:API structure
|
|
39
39
|
* // {
|
|
40
40
|
* // data: {
|
|
@@ -46,15 +46,15 @@ import { RestApiValidationError } from '../../../../lib/rest-api-errors.js';
|
|
|
46
46
|
* // }
|
|
47
47
|
* // }
|
|
48
48
|
* // }
|
|
49
|
-
*
|
|
49
|
+
*
|
|
50
50
|
* @example
|
|
51
51
|
* // Input: Object with relationship name (preferred way)
|
|
52
52
|
* const schema = {
|
|
53
53
|
* title: { type: 'string' },
|
|
54
|
-
* author_id: {
|
|
55
|
-
* type: 'number',
|
|
56
|
-
* belongsTo: 'users',
|
|
57
|
-
* as: 'author'
|
|
54
|
+
* author_id: {
|
|
55
|
+
* type: 'number',
|
|
56
|
+
* belongsTo: 'users',
|
|
57
|
+
* as: 'author'
|
|
58
58
|
* }
|
|
59
59
|
* };
|
|
60
60
|
* const input = {
|
|
@@ -62,7 +62,7 @@ import { RestApiValidationError } from '../../../../lib/rest-api-errors.js';
|
|
|
62
62
|
* title: 'Another Article',
|
|
63
63
|
* author: 789 // Using relationship name
|
|
64
64
|
* };
|
|
65
|
-
*
|
|
65
|
+
*
|
|
66
66
|
* // Output: Relationship becomes JSON:API relationship
|
|
67
67
|
* // {
|
|
68
68
|
* // data: {
|
|
@@ -72,7 +72,7 @@ import { RestApiValidationError } from '../../../../lib/rest-api-errors.js';
|
|
|
72
72
|
* // title: 'Another Article' // author removed from attributes
|
|
73
73
|
* // },
|
|
74
74
|
* // relationships: {
|
|
75
|
-
* // author: {
|
|
75
|
+
* // author: {
|
|
76
76
|
* // data: { type: 'users', id: '789' }
|
|
77
77
|
* // }
|
|
78
78
|
* // }
|
|
@@ -103,7 +103,7 @@ import { RestApiValidationError } from '../../../../lib/rest-api-errors.js';
|
|
|
103
103
|
* title: 'Tagged Article',
|
|
104
104
|
* tags: ['10', '20', '30'] // Array of IDs
|
|
105
105
|
* };
|
|
106
|
-
*
|
|
106
|
+
*
|
|
107
107
|
* // Output: Array becomes relationship data array
|
|
108
108
|
* // {
|
|
109
109
|
* // data: {
|
|
@@ -122,7 +122,7 @@ import { RestApiValidationError } from '../../../../lib/rest-api-errors.js';
|
|
|
122
122
|
* // }
|
|
123
123
|
* // }
|
|
124
124
|
* // }
|
|
125
|
-
*
|
|
125
|
+
*
|
|
126
126
|
* @example
|
|
127
127
|
* // Input: Already JSON:API formatted
|
|
128
128
|
* const input = {
|
|
@@ -149,7 +149,7 @@ import { RestApiValidationError } from '../../../../lib/rest-api-errors.js';
|
|
|
149
149
|
* text: 'Great content!',
|
|
150
150
|
* commentable: { id: '456', _type: 'posts' } // Minimal object format
|
|
151
151
|
* };
|
|
152
|
-
*
|
|
152
|
+
*
|
|
153
153
|
* // Output: Proper JSON:API structure for polymorphic
|
|
154
154
|
* // {
|
|
155
155
|
* // data: {
|
|
@@ -171,7 +171,7 @@ import { RestApiValidationError } from '../../../../lib/rest-api-errors.js';
|
|
|
171
171
|
* text: 'Updated comment',
|
|
172
172
|
* commentable: null // Clear the relationship
|
|
173
173
|
* };
|
|
174
|
-
*
|
|
174
|
+
*
|
|
175
175
|
* // Output: Relationship set to null
|
|
176
176
|
* // {
|
|
177
177
|
* // data: {
|
|
@@ -184,12 +184,12 @@ import { RestApiValidationError } from '../../../../lib/rest-api-errors.js';
|
|
|
184
184
|
* // }
|
|
185
185
|
* // }
|
|
186
186
|
* // }
|
|
187
|
-
*
|
|
187
|
+
*
|
|
188
188
|
* @description
|
|
189
189
|
* Used by:
|
|
190
190
|
* - rest-api-plugin for POST/PUT/PATCH when simplified: true
|
|
191
191
|
* - Processes input before validation and storage
|
|
192
|
-
*
|
|
192
|
+
*
|
|
193
193
|
* Purpose:
|
|
194
194
|
* - Provides developer-friendly plain object API
|
|
195
195
|
* - Automatically detects and converts relationships
|
|
@@ -197,7 +197,7 @@ import { RestApiValidationError } from '../../../../lib/rest-api-errors.js';
|
|
|
197
197
|
* - Supports polymorphic relationships with minimal object format
|
|
198
198
|
* - Maintains JSON:API compliance internally
|
|
199
199
|
* - Allows gradual migration from simple to JSON:API
|
|
200
|
-
*
|
|
200
|
+
*
|
|
201
201
|
* Data flow:
|
|
202
202
|
* 1. Checks if already JSON:API (has data.type)
|
|
203
203
|
* 2. Extracts ID from input object
|
|
@@ -209,29 +209,29 @@ import { RestApiValidationError } from '../../../../lib/rest-api-errors.js';
|
|
|
209
209
|
*/
|
|
210
210
|
export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
211
211
|
// Extract needed values from scope and deps
|
|
212
|
-
const input = scope.inputRecord
|
|
213
|
-
const scopeName = deps.context.scopeName
|
|
214
|
-
const schema = deps.context.schemaStructure
|
|
215
|
-
const relationships = deps.context.schemaRelationships
|
|
212
|
+
const input = scope.inputRecord
|
|
213
|
+
const scopeName = deps.context.scopeName
|
|
214
|
+
const schema = deps.context.schemaStructure
|
|
215
|
+
const relationships = deps.context.schemaRelationships
|
|
216
216
|
|
|
217
217
|
// If already JSON:API format, return as-is
|
|
218
218
|
if (input?.data?.type) {
|
|
219
|
-
return input
|
|
219
|
+
return input
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
const attributes = {}
|
|
223
|
-
const relationshipsData = {}
|
|
224
|
-
const tempInput = { ...input }
|
|
222
|
+
const attributes = {}
|
|
223
|
+
const relationshipsData = {}
|
|
224
|
+
const tempInput = { ...input } // Create a mutable copy of the input
|
|
225
225
|
|
|
226
|
-
const id = tempInput.id
|
|
227
|
-
delete tempInput.id
|
|
226
|
+
const id = tempInput.id
|
|
227
|
+
delete tempInput.id // Extract ID from tempInput
|
|
228
228
|
|
|
229
229
|
// 1. Process belongsTo relationships (from schema fields)
|
|
230
230
|
// Iterate through the schema structure to find belongsTo fields
|
|
231
231
|
for (const [fieldName, fieldDef] of Object.entries(schema)) {
|
|
232
232
|
if (fieldDef.belongsTo) {
|
|
233
|
-
const relAlias = fieldDef.as || fieldName
|
|
234
|
-
let valueToProcess
|
|
233
|
+
const relAlias = fieldDef.as || fieldName // Use 'as' alias or foreign key name as default alias
|
|
234
|
+
let valueToProcess
|
|
235
235
|
|
|
236
236
|
// Check if the user provided the foreign key name (e.g., country_id) - NO LONGER ALLOWED
|
|
237
237
|
if (tempInput[fieldName] !== undefined) {
|
|
@@ -243,23 +243,23 @@ export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
|
243
243
|
violations: [{
|
|
244
244
|
field: fieldName,
|
|
245
245
|
rule: 'deprecated_foreign_key',
|
|
246
|
-
message:
|
|
246
|
+
message: 'Use relationship name instead of foreign key field'
|
|
247
247
|
}]
|
|
248
248
|
}
|
|
249
|
-
)
|
|
249
|
+
)
|
|
250
250
|
}
|
|
251
|
-
|
|
251
|
+
|
|
252
252
|
// Check if the user provided the relationship name (e.g., country)
|
|
253
|
-
const relName = fieldDef.as || fieldName
|
|
253
|
+
const relName = fieldDef.as || fieldName
|
|
254
254
|
if (tempInput[relName] !== undefined) {
|
|
255
|
-
valueToProcess = tempInput[relName]
|
|
256
|
-
delete tempInput[relName]
|
|
255
|
+
valueToProcess = tempInput[relName]
|
|
256
|
+
delete tempInput[relName] // Remove from tempInput once processed
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
if (valueToProcess !== undefined) {
|
|
260
260
|
relationshipsData[relAlias] = {
|
|
261
261
|
data: valueToProcess ? { type: fieldDef.belongsTo, id: String(valueToProcess) } : null
|
|
262
|
-
}
|
|
262
|
+
}
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
265
|
}
|
|
@@ -269,39 +269,39 @@ export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
|
269
269
|
for (const [relName, relConfig] of Object.entries(relationships || {})) {
|
|
270
270
|
// Check if the user provided data for this relationship name
|
|
271
271
|
if (tempInput[relName] !== undefined) {
|
|
272
|
-
const value = tempInput[relName]
|
|
273
|
-
delete tempInput[relName]
|
|
272
|
+
const value = tempInput[relName]
|
|
273
|
+
delete tempInput[relName] // Remove from tempInput once processed
|
|
274
274
|
|
|
275
275
|
if ((relConfig.type === 'hasMany' || relConfig.type === 'manyToMany') && Array.isArray(value)) {
|
|
276
276
|
// Determine the target type based on relationship type:
|
|
277
277
|
// - For manyToMany: the targetType is the relationship name itself
|
|
278
278
|
// - For hasMany/hasOne: the targetType is specified in the target property
|
|
279
|
-
const targetType = relConfig.type === 'manyToMany' ? relName : relConfig.target
|
|
279
|
+
const targetType = relConfig.type === 'manyToMany' ? relName : relConfig.target
|
|
280
280
|
relationshipsData[relName] = {
|
|
281
281
|
data: value.map(relId => ({ type: targetType, id: String(relId) }))
|
|
282
|
-
}
|
|
282
|
+
}
|
|
283
283
|
}
|
|
284
284
|
// Handle hasOne relationships
|
|
285
285
|
else if (relConfig.type === 'hasOne' && value !== undefined) {
|
|
286
286
|
relationshipsData[relName] = {
|
|
287
287
|
data: value ? { type: relConfig.target, id: String(value) } : null
|
|
288
|
-
}
|
|
288
|
+
}
|
|
289
289
|
}
|
|
290
290
|
// Handle single belongsTo relationships if they were also defined in 'relationships'
|
|
291
291
|
// (though typically they are defined in schema.belongsTo)
|
|
292
292
|
else if (relConfig.belongsTo && value !== undefined) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
293
|
+
relationshipsData[relName] = {
|
|
294
|
+
data: value ? { type: relConfig.belongsTo, id: String(value) } : null
|
|
295
|
+
}
|
|
296
296
|
}
|
|
297
297
|
// Handle polymorphic belongsTo relationships with minimal object format
|
|
298
298
|
else if (relConfig.belongsToPolymorphic && value !== undefined) {
|
|
299
299
|
if (value === null) {
|
|
300
|
-
relationshipsData[relName] = { data: null }
|
|
300
|
+
relationshipsData[relName] = { data: null }
|
|
301
301
|
} else if (typeof value === 'object' && value !== null && value.id && value._type) {
|
|
302
302
|
relationshipsData[relName] = {
|
|
303
303
|
data: { type: value._type, id: String(value.id) }
|
|
304
|
-
}
|
|
304
|
+
}
|
|
305
305
|
} else {
|
|
306
306
|
throw new RestApiValidationError(
|
|
307
307
|
`Invalid format for polymorphic relationship '${relName}'. Expected { id: 'value', _type: 'type' } or null`,
|
|
@@ -313,14 +313,14 @@ export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
|
313
313
|
message: 'Must be an object with id and _type properties, or null'
|
|
314
314
|
}]
|
|
315
315
|
}
|
|
316
|
-
)
|
|
316
|
+
)
|
|
317
317
|
}
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
321
|
|
|
322
322
|
// 3. Any remaining fields in tempInput are attributes
|
|
323
|
-
Object.assign(attributes, tempInput)
|
|
323
|
+
Object.assign(attributes, tempInput)
|
|
324
324
|
|
|
325
325
|
return {
|
|
326
326
|
data: {
|
|
@@ -329,12 +329,12 @@ export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
|
329
329
|
...(Object.keys(attributes).length > 0 && { attributes }),
|
|
330
330
|
...(Object.keys(relationshipsData).length > 0 && { relationships: relationshipsData })
|
|
331
331
|
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
334
|
|
|
335
335
|
/**
|
|
336
336
|
* Transforms JSON:API response into simplified plain object
|
|
337
|
-
*
|
|
337
|
+
*
|
|
338
338
|
* @param {Object} scope - Scope containing JSON:API response
|
|
339
339
|
* @param {Object} scope.record - JSON:API document to transform
|
|
340
340
|
* @param {Object} deps - Dependencies with context
|
|
@@ -342,7 +342,7 @@ export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
|
342
342
|
* @param {Object} deps.context.schemaRelationships - Relationship configurations
|
|
343
343
|
* @param {Object} deps.context.scopes - All scopes for nested transformations
|
|
344
344
|
* @returns {Object|Object[]} Simplified object or array of objects
|
|
345
|
-
*
|
|
345
|
+
*
|
|
346
346
|
* @example
|
|
347
347
|
* // Input: Single resource JSON:API
|
|
348
348
|
* const jsonApi = {
|
|
@@ -359,14 +359,14 @@ export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
|
359
359
|
* { record: jsonApi },
|
|
360
360
|
* { context: { schemaStructure: schema } }
|
|
361
361
|
* );
|
|
362
|
-
*
|
|
362
|
+
*
|
|
363
363
|
* // Output: Flattened object
|
|
364
364
|
* // {
|
|
365
365
|
* // id: '123',
|
|
366
366
|
* // title: 'My Article', // Attributes flattened
|
|
367
367
|
* // content: 'Content here'
|
|
368
368
|
* // }
|
|
369
|
-
*
|
|
369
|
+
*
|
|
370
370
|
* @example
|
|
371
371
|
* // Input: Resource with belongsTo relationship
|
|
372
372
|
* const schema = {
|
|
@@ -385,14 +385,14 @@ export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
|
385
385
|
* }
|
|
386
386
|
* }
|
|
387
387
|
* };
|
|
388
|
-
*
|
|
388
|
+
*
|
|
389
389
|
* // Output: Relationship becomes foreign key
|
|
390
390
|
* // {
|
|
391
391
|
* // id: '456',
|
|
392
392
|
* // title: 'Article with Author',
|
|
393
393
|
* // author_id: '789' // Restored from relationship
|
|
394
394
|
* // }
|
|
395
|
-
*
|
|
395
|
+
*
|
|
396
396
|
* @example
|
|
397
397
|
* // Input: Collection response
|
|
398
398
|
* const jsonApi = {
|
|
@@ -411,7 +411,7 @@ export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
|
411
411
|
* meta: { total: 2 },
|
|
412
412
|
* links: { next: '/articles?page=2' }
|
|
413
413
|
* };
|
|
414
|
-
*
|
|
414
|
+
*
|
|
415
415
|
* // Output: Preserves meta and links
|
|
416
416
|
* // {
|
|
417
417
|
* // data: [
|
|
@@ -459,7 +459,7 @@ export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
|
459
459
|
* }
|
|
460
460
|
* ]
|
|
461
461
|
* };
|
|
462
|
-
*
|
|
462
|
+
*
|
|
463
463
|
* // Output: Expands included resources
|
|
464
464
|
* // {
|
|
465
465
|
* // id: '400',
|
|
@@ -480,14 +480,14 @@ export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
|
480
480
|
* Used by:
|
|
481
481
|
* - rest-api-plugin for GET/QUERY when simplified: true
|
|
482
482
|
* - Transforms responses before sending to client
|
|
483
|
-
*
|
|
483
|
+
*
|
|
484
484
|
* Purpose:
|
|
485
485
|
* - Provides cleaner response format for clients
|
|
486
486
|
* - Creates minimal relationship objects from relationships
|
|
487
487
|
* - Expands included resources for convenience
|
|
488
488
|
* - Maintains compatibility with collection metadata
|
|
489
489
|
* - Handles both single and array responses
|
|
490
|
-
*
|
|
490
|
+
*
|
|
491
491
|
* Data flow:
|
|
492
492
|
* 1. Detects array vs single resource
|
|
493
493
|
* 2. For arrays, preserves meta/links at root
|
|
@@ -496,12 +496,12 @@ export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
|
496
496
|
*/
|
|
497
497
|
export const transformJsonApiToSimplified = (scope, deps) => {
|
|
498
498
|
// Extract needed values from scope and deps
|
|
499
|
-
const jsonApi = scope.record
|
|
500
|
-
const schema = deps.context.schemaStructure
|
|
501
|
-
const relationships = deps.context.schemaRelationships
|
|
502
|
-
const scopes = deps.context.scopes
|
|
499
|
+
const jsonApi = scope.record
|
|
500
|
+
const schema = deps.context.schemaStructure
|
|
501
|
+
const relationships = deps.context.schemaRelationships
|
|
502
|
+
const scopes = deps.context.scopes
|
|
503
503
|
|
|
504
|
-
if (!jsonApi?.data) return jsonApi
|
|
504
|
+
if (!jsonApi?.data) return jsonApi
|
|
505
505
|
|
|
506
506
|
// Handle array response (QUERY)
|
|
507
507
|
if (Array.isArray(jsonApi.data)) {
|
|
@@ -510,34 +510,34 @@ export const transformJsonApiToSimplified = (scope, deps) => {
|
|
|
510
510
|
{ data: item, included: jsonApi.included },
|
|
511
511
|
{ context: { schemaStructure: schema, schemaRelationships: relationships, scopes } }
|
|
512
512
|
)
|
|
513
|
-
)
|
|
514
|
-
|
|
513
|
+
)
|
|
514
|
+
|
|
515
515
|
// For query results, return object with data, meta, and links
|
|
516
|
-
const result = { data: simplifiedData }
|
|
517
|
-
|
|
516
|
+
const result = { data: simplifiedData }
|
|
517
|
+
|
|
518
518
|
// Add meta if present
|
|
519
519
|
if (jsonApi.meta) {
|
|
520
|
-
result.meta = jsonApi.meta
|
|
520
|
+
result.meta = jsonApi.meta
|
|
521
521
|
}
|
|
522
|
-
|
|
522
|
+
|
|
523
523
|
// Add links if present
|
|
524
524
|
if (jsonApi.links) {
|
|
525
|
-
result.links = jsonApi.links
|
|
525
|
+
result.links = jsonApi.links
|
|
526
526
|
}
|
|
527
|
-
|
|
528
|
-
return result
|
|
527
|
+
|
|
528
|
+
return result
|
|
529
529
|
}
|
|
530
530
|
|
|
531
531
|
// Handle single response (no change for single resources)
|
|
532
532
|
return transformSingleJsonApiToSimplified(
|
|
533
533
|
{ data: jsonApi.data, included: jsonApi.included },
|
|
534
534
|
{ context: { schemaStructure: schema, schemaRelationships: relationships, scopes } }
|
|
535
|
-
)
|
|
536
|
-
}
|
|
535
|
+
)
|
|
536
|
+
}
|
|
537
537
|
|
|
538
538
|
/**
|
|
539
539
|
* Transforms single JSON:API resource into simplified format
|
|
540
|
-
*
|
|
540
|
+
*
|
|
541
541
|
* @param {Object} scope - Scope with data and included resources
|
|
542
542
|
* @param {Object} scope.data - Single JSON:API resource object
|
|
543
543
|
* @param {Array} scope.included - Array of included resources
|
|
@@ -546,7 +546,7 @@ export const transformJsonApiToSimplified = (scope, deps) => {
|
|
|
546
546
|
* @param {Object} deps.context.schemaRelationships - Relationship configs
|
|
547
547
|
* @param {Object} deps.context.scopes - All scopes for recursion
|
|
548
548
|
* @returns {Object} Simplified plain object
|
|
549
|
-
*
|
|
549
|
+
*
|
|
550
550
|
* @example
|
|
551
551
|
* // Input: Basic resource with relationships
|
|
552
552
|
* const data = {
|
|
@@ -575,7 +575,7 @@ export const transformJsonApiToSimplified = (scope, deps) => {
|
|
|
575
575
|
* { data: data, included: null },
|
|
576
576
|
* { context: { schemaStructure: schema } }
|
|
577
577
|
* );
|
|
578
|
-
*
|
|
578
|
+
*
|
|
579
579
|
* // Output: Minimal relationship objects
|
|
580
580
|
* // {
|
|
581
581
|
* // id: '100',
|
|
@@ -584,7 +584,7 @@ export const transformJsonApiToSimplified = (scope, deps) => {
|
|
|
584
584
|
* // author: { id: '50' }, // Minimal object from relationships.author
|
|
585
585
|
* // article: { id: '200' } // Minimal object from relationships.article
|
|
586
586
|
* // }
|
|
587
|
-
*
|
|
587
|
+
*
|
|
588
588
|
* @example
|
|
589
589
|
* // Input: Many-to-many relationships
|
|
590
590
|
* const data = {
|
|
@@ -604,14 +604,14 @@ export const transformJsonApiToSimplified = (scope, deps) => {
|
|
|
604
604
|
* const relationships = {
|
|
605
605
|
* tags: { manyToMany: { via: 'article_tags' } }
|
|
606
606
|
* };
|
|
607
|
-
*
|
|
607
|
+
*
|
|
608
608
|
* // Output: Minimal relationship objects
|
|
609
609
|
* // {
|
|
610
610
|
* // id: '300',
|
|
611
611
|
* // title: 'My Article',
|
|
612
612
|
* // tags: [{ id: '1' }, { id: '2' }, { id: '3' }] // Minimal objects
|
|
613
613
|
* // }
|
|
614
|
-
*
|
|
614
|
+
*
|
|
615
615
|
* @example
|
|
616
616
|
* // Input: Polymorphic relationship
|
|
617
617
|
* const data = {
|
|
@@ -633,7 +633,7 @@ export const transformJsonApiToSimplified = (scope, deps) => {
|
|
|
633
633
|
* }
|
|
634
634
|
* }
|
|
635
635
|
* };
|
|
636
|
-
*
|
|
636
|
+
*
|
|
637
637
|
* // Output: Minimal polymorphic object with type
|
|
638
638
|
* // {
|
|
639
639
|
* // id: '500',
|
|
@@ -641,7 +641,7 @@ export const transformJsonApiToSimplified = (scope, deps) => {
|
|
|
641
641
|
* // comment: 'Great!',
|
|
642
642
|
* // reviewable: { id: '75', type: 'authors' } // Minimal object with type
|
|
643
643
|
* // }
|
|
644
|
-
*
|
|
644
|
+
*
|
|
645
645
|
* @example
|
|
646
646
|
* // Input: Polymorphic relationship with included resource
|
|
647
647
|
* const dataWithInclude = {
|
|
@@ -661,7 +661,7 @@ export const transformJsonApiToSimplified = (scope, deps) => {
|
|
|
661
661
|
* attributes: { name: 'Jane Smith', bio: 'Fantastic author' }
|
|
662
662
|
* }
|
|
663
663
|
* ];
|
|
664
|
-
*
|
|
664
|
+
*
|
|
665
665
|
* // Output: Full polymorphic object (type field removed as redundant)
|
|
666
666
|
* // {
|
|
667
667
|
* // id: '500',
|
|
@@ -673,7 +673,7 @@ export const transformJsonApiToSimplified = (scope, deps) => {
|
|
|
673
673
|
* // bio: 'Fantastic author'
|
|
674
674
|
* // }
|
|
675
675
|
* // }
|
|
676
|
-
*
|
|
676
|
+
*
|
|
677
677
|
* @example
|
|
678
678
|
* // Input: With included resources (recursive expansion)
|
|
679
679
|
* const data = {
|
|
@@ -714,7 +714,7 @@ export const transformJsonApiToSimplified = (scope, deps) => {
|
|
|
714
714
|
* attributes: { number: 2, title: 'Basics' }
|
|
715
715
|
* }
|
|
716
716
|
* ];
|
|
717
|
-
*
|
|
717
|
+
*
|
|
718
718
|
* // Output: Recursively expanded
|
|
719
719
|
* // {
|
|
720
720
|
* // id: '400',
|
|
@@ -729,19 +729,19 @@ export const transformJsonApiToSimplified = (scope, deps) => {
|
|
|
729
729
|
* // { id: '1002', number: 2, title: 'Basics' }
|
|
730
730
|
* // ]
|
|
731
731
|
* // }
|
|
732
|
-
*
|
|
732
|
+
*
|
|
733
733
|
* @description
|
|
734
734
|
* Used by:
|
|
735
735
|
* - transformJsonApiToSimplified for each resource
|
|
736
736
|
* - Calls itself recursively for included resources
|
|
737
|
-
*
|
|
737
|
+
*
|
|
738
738
|
* Purpose:
|
|
739
739
|
* - Core transformation logic for single resources
|
|
740
740
|
* - Creates minimal relationship objects from relationships
|
|
741
741
|
* - Creates arrays of minimal relationship objects for to-many relationships
|
|
742
742
|
* - Recursively expands and transforms included data
|
|
743
743
|
* - Handles polymorphic relationships
|
|
744
|
-
*
|
|
744
|
+
*
|
|
745
745
|
* Data flow:
|
|
746
746
|
* 1. Copies ID and attributes to result
|
|
747
747
|
* 2. Processes each relationship:
|
|
@@ -752,102 +752,106 @@ export const transformJsonApiToSimplified = (scope, deps) => {
|
|
|
752
752
|
* - Finds matching included resources
|
|
753
753
|
* - Recursively transforms them (preserving their relationships!)
|
|
754
754
|
* - Adds transformed objects/arrays to result
|
|
755
|
-
*
|
|
755
|
+
*
|
|
756
756
|
* @private
|
|
757
757
|
*/
|
|
758
758
|
export const transformSingleJsonApiToSimplified = (scope, deps) => {
|
|
759
759
|
// Extract needed values from scope and deps
|
|
760
|
-
const data = scope.data
|
|
761
|
-
const included = scope.included
|
|
762
|
-
const schema = deps.context.schemaStructure
|
|
763
|
-
const relationships = deps.context.schemaRelationships
|
|
764
|
-
const scopes = deps.context.scopes
|
|
760
|
+
const data = scope.data
|
|
761
|
+
const included = scope.included
|
|
762
|
+
const schema = deps.context.schemaStructure
|
|
763
|
+
const relationships = deps.context.schemaRelationships
|
|
764
|
+
const scopes = deps.context.scopes
|
|
765
765
|
|
|
766
|
-
const simplified = {}
|
|
766
|
+
const simplified = {}
|
|
767
767
|
|
|
768
768
|
// Add ID
|
|
769
769
|
if (data.id) {
|
|
770
|
-
simplified.id = data.id
|
|
770
|
+
simplified.id = data.id
|
|
771
771
|
}
|
|
772
772
|
|
|
773
773
|
// Add attributes
|
|
774
|
-
Object.assign(simplified, data.attributes || {})
|
|
774
|
+
Object.assign(simplified, data.attributes || {})
|
|
775
775
|
|
|
776
776
|
// Process relationships to create minimal objects
|
|
777
777
|
if (data.relationships) {
|
|
778
778
|
for (const [relName, relData] of Object.entries(data.relationships)) {
|
|
779
779
|
// Find schema field for this belongsTo relationship
|
|
780
|
-
const schemaEntry = Object.entries(schema).find(([_, def]) => def.as === relName)
|
|
780
|
+
const schemaEntry = Object.entries(schema).find(([_, def]) => def.as === relName)
|
|
781
781
|
|
|
782
782
|
if (schemaEntry) {
|
|
783
|
-
const [fieldName, fieldDef] = schemaEntry
|
|
783
|
+
const [fieldName, fieldDef] = schemaEntry
|
|
784
784
|
if (fieldDef.belongsTo && relData.data) {
|
|
785
785
|
// Create minimal relationship object (no more foreign key field)
|
|
786
|
-
simplified[relName] = { id: relData.data.id }
|
|
786
|
+
simplified[relName] = { id: relData.data.id }
|
|
787
787
|
}
|
|
788
788
|
}
|
|
789
789
|
|
|
790
790
|
// Check if this is a polymorphic relationship from the relationships config
|
|
791
|
-
const rel = relationships?.[relName]
|
|
791
|
+
const rel = relationships?.[relName]
|
|
792
792
|
if (rel?.belongsToPolymorphic && relData.data) {
|
|
793
793
|
// Create minimal relationship object for polymorphic (no more type/id fields)
|
|
794
|
-
simplified[relName] = { id: relData.data.id, _type: relData.data.type }
|
|
794
|
+
simplified[relName] = { id: relData.data.id, _type: relData.data.type }
|
|
795
795
|
}
|
|
796
796
|
|
|
797
797
|
// Handle to-many relationships (create minimal objects with just IDs)
|
|
798
798
|
if (rel?.type === 'hasMany' || rel?.type === 'manyToMany') {
|
|
799
799
|
if (relData.data && Array.isArray(relData.data)) {
|
|
800
|
-
simplified[relName] = relData.data.map(item => ({ id: item.id }))
|
|
800
|
+
simplified[relName] = relData.data.map(item => ({ id: item.id }))
|
|
801
801
|
}
|
|
802
802
|
}
|
|
803
803
|
|
|
804
804
|
// Handle includes (nested objects)
|
|
805
805
|
if (included && relData.data) {
|
|
806
806
|
const findIncluded = (ref) =>
|
|
807
|
-
included.find(inc => inc.type === ref.type && inc.id === ref.id)
|
|
807
|
+
included.find(inc => inc.type === ref.type && inc.id === ref.id)
|
|
808
808
|
|
|
809
809
|
if (Array.isArray(relData.data)) {
|
|
810
|
-
const nestedData = relData.data.map(findIncluded).filter(Boolean)
|
|
810
|
+
const nestedData = relData.data.map(findIncluded).filter(Boolean)
|
|
811
811
|
if (nestedData.length > 0) {
|
|
812
812
|
simplified[relName] = nestedData.map(item => {
|
|
813
813
|
// For each included record, transform it recursively to restore its relationship fields
|
|
814
814
|
const itemSimplified = transformSingleJsonApiToSimplified(
|
|
815
815
|
{ data: item, included: null },
|
|
816
|
-
{
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
816
|
+
{
|
|
817
|
+
context: {
|
|
818
|
+
schemaStructure: scopes?.[item.type]?.vars?.schemaInfo?.schema || {},
|
|
819
|
+
schemaRelationships: scopes?.[item.type]?.vars?.schemaInfo?.schemaRelationships || {},
|
|
820
|
+
scopes
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
)
|
|
822
824
|
// For polymorphic relationships, add _type field to identify the resource type
|
|
823
825
|
if (rel?.belongsToPolymorphic) {
|
|
824
|
-
itemSimplified._type = item.type
|
|
826
|
+
itemSimplified._type = item.type
|
|
825
827
|
}
|
|
826
|
-
return itemSimplified
|
|
827
|
-
})
|
|
828
|
+
return itemSimplified
|
|
829
|
+
})
|
|
828
830
|
}
|
|
829
831
|
} else {
|
|
830
|
-
const nestedData = findIncluded(relData.data)
|
|
832
|
+
const nestedData = findIncluded(relData.data)
|
|
831
833
|
if (nestedData) {
|
|
832
834
|
// Transform the single included record to restore its relationship fields
|
|
833
835
|
const itemSimplified = transformSingleJsonApiToSimplified(
|
|
834
836
|
{ data: nestedData, included: null },
|
|
835
|
-
{
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
837
|
+
{
|
|
838
|
+
context: {
|
|
839
|
+
schemaStructure: scopes?.[nestedData.type]?.vars?.schemaInfo?.schema || {},
|
|
840
|
+
schemaRelationships: scopes?.[nestedData.type]?.vars?.schemaInfo?.schemaRelationships || {},
|
|
841
|
+
scopes
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
)
|
|
841
845
|
// For polymorphic relationships, add _type field to identify the resource type
|
|
842
846
|
if (rel?.belongsToPolymorphic) {
|
|
843
|
-
itemSimplified._type = nestedData.type
|
|
847
|
+
itemSimplified._type = nestedData.type
|
|
844
848
|
}
|
|
845
|
-
simplified[relName] = itemSimplified
|
|
849
|
+
simplified[relName] = itemSimplified
|
|
846
850
|
}
|
|
847
851
|
}
|
|
848
852
|
}
|
|
849
853
|
}
|
|
850
854
|
}
|
|
851
855
|
|
|
852
|
-
return simplified
|
|
853
|
-
}
|
|
856
|
+
return simplified
|
|
857
|
+
}
|