json-rest-api 1.0.6 → 1.0.8
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/.claude/agents/library-guide-expert.md +29 -0
- package/.claude/agents/rest-api-internals-expert.md +53 -0
- package/CLAUDE.md +139 -1
- package/README.md +2 -5
- package/docTest.js +238 -0
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +955 -0
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +54 -0
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +750 -0
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +494 -0
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +398 -0
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +882 -0
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +709 -0
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +1233 -0
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +796 -0
- package/{README_X_Relationship_Plugin.md → docs/GUIDE/GUIDE_2_9_Relationships_Urls.md} +1 -4
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +66 -0
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +1246 -0
- package/docs/GUIDE/GUIDE_5_Non-Db_Stores.md +0 -0
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +1490 -0
- package/{README_X_SocketIO.md → docs/GUIDE/GUIDE_X_SocketIO.md} +17 -17
- package/docs/GUIDE/index.md +58 -0
- package/docs/GUIDE.md +61 -2238
- package/docs/ONBOARDING/Computed_Properties_And_Sparse_Fields.md +398 -0
- package/docs/README.md +57 -2239
- package/{README_2_Computed_and_hidden_fields.md → docs/README_VARIOUS/GUIDE_2_Computed_and_hidden_fields.md} +22 -17
- package/docs/README_VARIOUS/GUIDE_IN_DOCS.md +2277 -0
- package/{README.ORIG.md → docs/README_VARIOUS/README.ORIG.md} +8 -8
- package/{README_8_Polymorphic_Relationships.md → docs/README_VARIOUS/README_8_Polymorphic_Relationships.md} +1 -1
- package/docs/README_VARIOUS/README_IN_DOCS.ms +2277 -0
- package/{RELATIONSHIPS_ADVANCED.md → docs/README_VARIOUS/RELATIONSHIPS_ADVANCED.md} +3 -3
- package/{correct-schema-format.md → docs/README_VARIOUS/correct-schema-format.md} +13 -13
- package/{pagination.md → docs/README_VARIOUS/pagination.md} +11 -8
- package/docs/assets/css/style.scss +78 -5
- package/docs/index.md +2 -12
- package/helper-reorganization-plan.md +85 -0
- package/helper-usage-analysis.md +94 -0
- package/package.json +5 -4
- package/plugins/core/bulk-operations-plugin.js +2 -2
- package/plugins/core/connectors/express-plugin.js +99 -33
- package/plugins/core/lib/querying/knex-cross-table-search.js +609 -0
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +491 -0
- package/plugins/core/lib/querying/knex-pagination-helpers.js +544 -0
- package/plugins/core/lib/{knex-process-includes.js → querying/knex-process-includes.js} +65 -50
- package/plugins/core/lib/querying/knex-query-helpers-base.js +74 -0
- package/plugins/core/lib/{knex-query-helpers.js → querying/knex-query-helpers.js} +416 -180
- package/plugins/core/lib/{knex-relationship-includes.js → querying/knex-relationship-includes.js} +430 -114
- package/plugins/core/lib/querying/knex-window-queries.js +328 -0
- package/plugins/core/lib/querying-writing/compile-schemas.js +328 -0
- package/plugins/core/{utils → lib/querying-writing}/connectors-query-parser.js +62 -57
- package/plugins/core/lib/{database-capabilities.js → querying-writing/database-capabilities.js} +113 -7
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +369 -0
- package/plugins/core/lib/querying-writing/default-data-helpers.js +72 -0
- package/plugins/core/{utils → lib/querying-writing}/field-utils.js +41 -31
- package/plugins/core/{utils → lib/querying-writing}/knex-constants.js +1 -0
- package/plugins/core/lib/{knex-field-helpers.js → querying-writing/knex-field-helpers.js} +190 -57
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +162 -0
- package/plugins/core/lib/{payload-validators.js → querying-writing/payload-validators.js} +297 -217
- package/plugins/core/lib/querying-writing/schema-helpers.js +497 -0
- package/plugins/core/lib/querying-writing/scope-validations.js +431 -0
- package/plugins/core/lib/{simplified-helpers.js → querying-writing/simplified-helpers.js} +216 -176
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +110 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +300 -0
- package/plugins/core/lib/writing/relationship-processor.js +252 -0
- package/plugins/core/rest-api-knex-plugin.js +183 -62
- package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +7 -0
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +72 -0
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +102 -0
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +50 -0
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +39 -0
- package/plugins/core/rest-api-plugin-methods/add-route.js +23 -0
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +18 -0
- package/plugins/core/rest-api-plugin-methods/common.js +605 -0
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +115 -0
- package/plugins/core/rest-api-plugin-methods/delete.js +109 -0
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +186 -0
- package/plugins/core/rest-api-plugin-methods/get-related.js +295 -0
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +62 -0
- package/plugins/core/rest-api-plugin-methods/get.js +187 -0
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +56 -0
- package/plugins/core/rest-api-plugin-methods/patch.js +193 -0
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +104 -0
- package/plugins/core/rest-api-plugin-methods/post.js +157 -0
- package/plugins/core/rest-api-plugin-methods/put.js +257 -0
- package/plugins/core/rest-api-plugin-methods/query.js +208 -0
- package/plugins/core/rest-api-plugin.js +108 -2498
- package/plugins/core/socketio-plugin.js +28 -5
- package/plugins/storage/local-storage.js +2 -2
- package/tests/computed-fields.test.js +483 -0
- package/tests/cors.test.js +4 -4
- package/tests/curl.test.js +5 -4
- package/tests/custom-idproperty-relationships.test.js +891 -0
- package/tests/field-getters.test.js +353 -0
- package/tests/field-setters.test.js +310 -0
- package/tests/fixtures/api-configs.js +800 -28
- package/tests/jwt-auth.test.js +0 -2
- package/tests/nested-includes.test.js +1 -1
- package/tests/pagination-cursor-multifield.test.js +514 -0
- package/tests/pagination-enhanced.test.js +12 -9
- package/tests/polymorphic-relationships.test.js +10 -8
- package/tests/polymorphic-simple.test.js +6 -4
- package/tests/positioning-advanced.test.js +12 -12
- package/tests/positioning-extended.test.js +25 -25
- package/tests/positioning-multifilter.test.js +24 -24
- package/tests/positioning.test.js +3 -3
- package/tests/query-limits.test.js +24 -8
- package/tests/relationship-endpoints.test.js +4 -5
- package/tests/relationships.test.js +0 -1
- package/tests/socketio.test.js +12 -1
- package/tests/virtual-fields.test.js +13 -13
- package/PLAN_FOR_WEBSOCKETS.md +0 -398
- package/README_1_Initial_Setup.md +0 -393
- package/README_X_Hooks.md +0 -647
- package/TODO_rest-api-plugin-extras.todo +0 -23
- package/connectors_ideas/express-plugin.js +0 -120
- package/connectors_ideas/http-plugin.js +0 -138
- package/docs/examples/express-plugin.js +0 -366
- package/example.js +0 -301
- package/plugins/core/HELPER_MODULES.md +0 -285
- package/plugins/core/lib/compile-schemas.js +0 -245
- package/plugins/core/lib/database-value-normalizers.js +0 -192
- package/plugins/core/lib/default-data-helpers.js +0 -100
- package/plugins/core/lib/knex-cross-table-search.js +0 -442
- package/plugins/core/lib/knex-json-api-helpers.js +0 -84
- package/plugins/core/lib/knex-json-api-transformers.js +0 -449
- package/plugins/core/lib/knex-pagination-helpers.js +0 -193
- package/plugins/core/lib/knex-window-queries.js +0 -142
- package/plugins/core/lib/many-to-many-manipulations.js +0 -343
- package/plugins/core/lib/relationship-processor.js +0 -278
- package/plugins/core/lib/schema-helpers.js +0 -343
- package/plugins/core/lib/scope-validations.js +0 -252
- package/plugins/core/rest-api-relationships-plugin.js +0 -700
- package/plugins/core/utils/knex-query-helpers-base.js +0 -56
- /package/{README_X_Permissions.md → docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md} +0 -0
- /package/{README_X_Positioning.md → docs/GUIDE/GUIDE_6_Positioning.md} +0 -0
- /package/{README_X_Bulk_Operations.md → docs/GUIDE/GUIDE_X_Bulk_Operations.md} +0 -0
- /package/{README_X_Cors.md → docs/GUIDE/GUIDE_X_Cors.md} +0 -0
- /package/{README_X_Multihome.md → docs/GUIDE/GUIDE_X_Multihome.md} +0 -0
- /package/{README_X_Appendices.md → docs/GUIDE/GUIDE_Y_Appendices.md} +0 -0
- /package/{QUERY_DEPTH_PROTECTION.md → docs/README_VARIOUS/QUERY_DEPTH_PROTECTION.md} +0 -0
- /package/{RELATIONSHIPS.md → docs/README_VARIOUS/RELATIONSHIPS.md} +0 -0
- /package/{WEBSOCKET_IMPLEMENTATION_STATUS.md → docs/README_VARIOUS/WEBSOCKET_IMPLEMENTATION_STATUS.md} +0 -0
- /package/{helper-functions-chart.md → docs/README_VARIOUS/helper-functions-chart.md} +0 -0
- /package/docs/{implementation-hidden-computed-fields.md → README_VARIOUS/implementation-hidden-computed-fields.md} +0 -0
- /package/docs/{include-limits-migration-guide.md → README_VARIOUS/include-limits-migration-guide.md} +0 -0
- /package/{readmeSnippets → docs/README_VARIOUS}/test_put_behavior.js +0 -0
- /package/{readmeSnippets → docs/README_VARIOUS}/test_put_fields_without_defaults.js +0 -0
|
@@ -1,34 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* @description Transformation functions for converting between simplified and JSON:API formats
|
|
2
|
+
* Transformation functions for simplified API mode
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* @description
|
|
5
|
+
* This module enables "simplified mode" - a developer-friendly alternative
|
|
6
|
+
* to verbose JSON:API format. It allows APIs to:
|
|
7
|
+
* - Accept plain JavaScript objects as input
|
|
8
|
+
* - Return flat response objects instead of nested JSON:API
|
|
9
|
+
* - Automatically convert between formats transparently
|
|
10
|
+
* - Maintain JSON:API compliance internally
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
|
-
import { RestApiValidationError } from '
|
|
13
|
+
import { RestApiValidationError } from '../../../../lib/rest-api-errors.js';
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
|
-
* Transforms
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* @param {Object}
|
|
22
|
-
* @
|
|
23
|
-
* @param {Object} deps - The dependencies object
|
|
24
|
-
* @param {Object} deps.context - The context object containing configuration
|
|
25
|
-
* @param {string} deps.context.scopeName - The resource type name (e.g., 'articles', 'users')
|
|
26
|
-
* @param {Object} deps.context.schemaStructure - The resource schema defining field types and relationships
|
|
27
|
-
* @param {Object} deps.context.schemaRelationships - The relationships configuration for the resource
|
|
28
|
-
* @returns {Object} A properly formatted JSON:API document
|
|
16
|
+
* Transforms simplified plain object into JSON:API format
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} scope - Scope containing input data
|
|
19
|
+
* @param {Object} scope.inputRecord - Plain object or JSON:API document
|
|
20
|
+
* @param {Object} deps - Dependencies with context
|
|
21
|
+
* @param {string} deps.context.scopeName - Resource type name
|
|
22
|
+
* @param {Object} deps.context.schemaStructure - Schema with field definitions
|
|
23
|
+
* @param {Object} deps.context.schemaRelationships - Relationship configurations
|
|
24
|
+
* @returns {Object} JSON:API formatted document
|
|
29
25
|
*
|
|
30
26
|
* @example
|
|
31
|
-
* //
|
|
27
|
+
* // Input: Simple object with only attributes
|
|
32
28
|
* const input = {
|
|
33
29
|
* id: '123',
|
|
34
30
|
* title: 'My Article',
|
|
@@ -36,9 +32,10 @@ import { RestApiValidationError } from '../../../lib/rest-api-errors.js';
|
|
|
36
32
|
* };
|
|
37
33
|
* const result = transformSimplifiedToJsonApi(
|
|
38
34
|
* { inputRecord: input },
|
|
39
|
-
* { context: { scopeName: 'articles', schemaStructure: schema
|
|
35
|
+
* { context: { scopeName: 'articles', schemaStructure: schema } }
|
|
40
36
|
* );
|
|
41
|
-
*
|
|
37
|
+
*
|
|
38
|
+
* // Output: Proper JSON:API structure
|
|
42
39
|
* // {
|
|
43
40
|
* // data: {
|
|
44
41
|
* // type: 'articles',
|
|
@@ -51,7 +48,7 @@ import { RestApiValidationError } from '../../../lib/rest-api-errors.js';
|
|
|
51
48
|
* // }
|
|
52
49
|
*
|
|
53
50
|
* @example
|
|
54
|
-
* //
|
|
51
|
+
* // Input: Object with foreign key (traditional way)
|
|
55
52
|
* const schema = {
|
|
56
53
|
* title: { type: 'string' },
|
|
57
54
|
* author_id: {
|
|
@@ -63,22 +60,19 @@ import { RestApiValidationError } from '../../../lib/rest-api-errors.js';
|
|
|
63
60
|
* const input = {
|
|
64
61
|
* id: '456',
|
|
65
62
|
* title: 'Another Article',
|
|
66
|
-
* author_id: 789
|
|
63
|
+
* author_id: 789 // Using foreign key name
|
|
67
64
|
* };
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* { context: { scopeName: 'articles', schemaStructure: schema, schemaRelationships: relationships } }
|
|
71
|
-
* );
|
|
72
|
-
* // Returns:
|
|
65
|
+
*
|
|
66
|
+
* // Output: Foreign key becomes relationship
|
|
73
67
|
* // {
|
|
74
68
|
* // data: {
|
|
75
69
|
* // type: 'articles',
|
|
76
70
|
* // id: '456',
|
|
77
71
|
* // attributes: {
|
|
78
|
-
* // title: 'Another Article'
|
|
72
|
+
* // title: 'Another Article' // author_id removed
|
|
79
73
|
* // },
|
|
80
74
|
* // relationships: {
|
|
81
|
-
* // author: {
|
|
75
|
+
* // author: { // Uses 'as' name
|
|
82
76
|
* // data: { type: 'users', id: '789' }
|
|
83
77
|
* // }
|
|
84
78
|
* // }
|
|
@@ -86,28 +80,16 @@ import { RestApiValidationError } from '../../../lib/rest-api-errors.js';
|
|
|
86
80
|
* // }
|
|
87
81
|
*
|
|
88
82
|
* @example
|
|
89
|
-
* //
|
|
90
|
-
* const schema = {
|
|
91
|
-
* title: { type: 'string' },
|
|
92
|
-
* author_id: {
|
|
93
|
-
* type: 'number',
|
|
94
|
-
* belongsTo: 'users',
|
|
95
|
-
* as: 'author'
|
|
96
|
-
* }
|
|
97
|
-
* };
|
|
83
|
+
* // Input: Object with relationship alias (preferred way)
|
|
98
84
|
* const input = {
|
|
99
85
|
* id: '456',
|
|
100
86
|
* title: 'Another Article',
|
|
101
|
-
* author: 789
|
|
87
|
+
* author: 789 // Using 'as' alias directly
|
|
102
88
|
* };
|
|
103
|
-
*
|
|
104
|
-
* { inputRecord: input },
|
|
105
|
-
* { context: { scopeName: 'articles', schemaStructure: schema, schemaRelationships: relationships } }
|
|
106
|
-
* );
|
|
107
|
-
* // Returns the same JSON:API structure as above
|
|
89
|
+
* // Output: Same JSON:API structure as above
|
|
108
90
|
*
|
|
109
91
|
* @example
|
|
110
|
-
* //
|
|
92
|
+
* // Input: Many-to-many relationship
|
|
111
93
|
* const relationships = {
|
|
112
94
|
* tags: {
|
|
113
95
|
* manyToMany: {
|
|
@@ -120,13 +102,10 @@ import { RestApiValidationError } from '../../../lib/rest-api-errors.js';
|
|
|
120
102
|
* };
|
|
121
103
|
* const input = {
|
|
122
104
|
* title: 'Tagged Article',
|
|
123
|
-
* tags: ['10', '20', '30']
|
|
105
|
+
* tags: ['10', '20', '30'] // Array of IDs
|
|
124
106
|
* };
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
* { context: { scopeName: 'articles', schemaStructure: schema, schemaRelationships: relationships } }
|
|
128
|
-
* );
|
|
129
|
-
* // Returns:
|
|
107
|
+
*
|
|
108
|
+
* // Output: Array becomes relationship data array
|
|
130
109
|
* // {
|
|
131
110
|
* // data: {
|
|
132
111
|
* // type: 'articles',
|
|
@@ -146,7 +125,7 @@ import { RestApiValidationError } from '../../../lib/rest-api-errors.js';
|
|
|
146
125
|
* // }
|
|
147
126
|
*
|
|
148
127
|
* @example
|
|
149
|
-
* //
|
|
128
|
+
* // Input: Already JSON:API formatted
|
|
150
129
|
* const input = {
|
|
151
130
|
* data: {
|
|
152
131
|
* type: 'articles',
|
|
@@ -154,19 +133,27 @@ import { RestApiValidationError } from '../../../lib/rest-api-errors.js';
|
|
|
154
133
|
* attributes: { title: 'Already formatted' }
|
|
155
134
|
* }
|
|
156
135
|
* };
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
136
|
+
* // Output: Returned unchanged (detected by data.type)
|
|
137
|
+
*
|
|
138
|
+
* @description
|
|
139
|
+
* Used by:
|
|
140
|
+
* - rest-api-plugin for POST/PUT/PATCH when simplified: true
|
|
141
|
+
* - Processes input before validation and storage
|
|
142
|
+
*
|
|
143
|
+
* Purpose:
|
|
144
|
+
* - Provides developer-friendly plain object API
|
|
145
|
+
* - Automatically detects and converts relationships
|
|
146
|
+
* - Supports both foreign key and alias notation
|
|
147
|
+
* - Maintains JSON:API compliance internally
|
|
148
|
+
* - Allows gradual migration from simple to JSON:API
|
|
149
|
+
*
|
|
150
|
+
* Data flow:
|
|
151
|
+
* 1. Checks if already JSON:API (has data.type)
|
|
152
|
+
* 2. Extracts ID from input object
|
|
153
|
+
* 3. Processes belongsTo relationships from schema
|
|
154
|
+
* 4. Processes hasMany/manyToMany from relationships
|
|
155
|
+
* 5. Remaining fields become attributes
|
|
156
|
+
* 6. Builds proper JSON:API document structure
|
|
170
157
|
*/
|
|
171
158
|
export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
172
159
|
// Extract needed values from scope and deps
|
|
@@ -267,29 +254,18 @@ export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
|
267
254
|
};
|
|
268
255
|
|
|
269
256
|
/**
|
|
270
|
-
* Transforms
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
* 2. Converts belongsTo relationships back to foreign key fields (e.g., author -> author_id)
|
|
280
|
-
* 3. Extracts many-to-many relationship IDs into arrays (e.g., tags -> tags_ids)
|
|
281
|
-
* 4. Optionally expands included resources inline for convenient access
|
|
282
|
-
*
|
|
283
|
-
* @param {Object} scope - The scope object containing the JSON:API response
|
|
284
|
-
* @param {Object} scope.record - The JSON:API response to transform
|
|
285
|
-
* @param {Object} deps - The dependencies object
|
|
286
|
-
* @param {Object} deps.context - The context object containing configuration
|
|
287
|
-
* @param {Object} deps.context.schemaStructure - The resource schema for mapping relationships back to fields
|
|
288
|
-
* @param {Object} deps.context.schemaRelationships - The relationships configuration
|
|
289
|
-
* @returns {Object|Array} Simplified object(s) - single object or array depending on input
|
|
257
|
+
* Transforms JSON:API response into simplified plain object
|
|
258
|
+
*
|
|
259
|
+
* @param {Object} scope - Scope containing JSON:API response
|
|
260
|
+
* @param {Object} scope.record - JSON:API document to transform
|
|
261
|
+
* @param {Object} deps - Dependencies with context
|
|
262
|
+
* @param {Object} deps.context.schemaStructure - Schema for mapping relationships
|
|
263
|
+
* @param {Object} deps.context.schemaRelationships - Relationship configurations
|
|
264
|
+
* @param {Object} deps.context.scopes - All scopes for nested transformations
|
|
265
|
+
* @returns {Object|Object[]} Simplified object or array of objects
|
|
290
266
|
*
|
|
291
267
|
* @example
|
|
292
|
-
* //
|
|
268
|
+
* // Input: Single resource JSON:API
|
|
293
269
|
* const jsonApi = {
|
|
294
270
|
* data: {
|
|
295
271
|
* type: 'articles',
|
|
@@ -302,17 +278,18 @@ export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
|
302
278
|
* };
|
|
303
279
|
* const result = transformJsonApiToSimplified(
|
|
304
280
|
* { record: jsonApi },
|
|
305
|
-
* { context: { schemaStructure: schema
|
|
281
|
+
* { context: { schemaStructure: schema } }
|
|
306
282
|
* );
|
|
307
|
-
*
|
|
283
|
+
*
|
|
284
|
+
* // Output: Flattened object
|
|
308
285
|
* // {
|
|
309
286
|
* // id: '123',
|
|
310
|
-
* // title: 'My Article',
|
|
287
|
+
* // title: 'My Article', // Attributes flattened
|
|
311
288
|
* // content: 'Content here'
|
|
312
289
|
* // }
|
|
313
290
|
*
|
|
314
291
|
* @example
|
|
315
|
-
* //
|
|
292
|
+
* // Input: Resource with belongsTo relationship
|
|
316
293
|
* const schema = {
|
|
317
294
|
* title: { type: 'string' },
|
|
318
295
|
* author_id: { type: 'number', belongsTo: 'users', as: 'author' }
|
|
@@ -329,19 +306,16 @@ export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
|
329
306
|
* }
|
|
330
307
|
* }
|
|
331
308
|
* };
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
* { context: { schemaStructure: schema, schemaRelationships: relationships } }
|
|
335
|
-
* );
|
|
336
|
-
* // Returns:
|
|
309
|
+
*
|
|
310
|
+
* // Output: Relationship becomes foreign key
|
|
337
311
|
* // {
|
|
338
312
|
* // id: '456',
|
|
339
313
|
* // title: 'Article with Author',
|
|
340
|
-
* // author_id: '789'
|
|
314
|
+
* // author_id: '789' // Restored from relationship
|
|
341
315
|
* // }
|
|
342
316
|
*
|
|
343
317
|
* @example
|
|
344
|
-
* //
|
|
318
|
+
* // Input: Collection response
|
|
345
319
|
* const jsonApi = {
|
|
346
320
|
* data: [
|
|
347
321
|
* {
|
|
@@ -354,20 +328,23 @@ export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
|
354
328
|
* id: '2',
|
|
355
329
|
* attributes: { title: 'Second Article' }
|
|
356
330
|
* }
|
|
357
|
-
* ]
|
|
331
|
+
* ],
|
|
332
|
+
* meta: { total: 2 },
|
|
333
|
+
* links: { next: '/articles?page=2' }
|
|
358
334
|
* };
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
* //
|
|
364
|
-
* //
|
|
365
|
-
* //
|
|
366
|
-
* // {
|
|
367
|
-
* //
|
|
335
|
+
*
|
|
336
|
+
* // Output: Preserves meta and links
|
|
337
|
+
* // {
|
|
338
|
+
* // data: [
|
|
339
|
+
* // { id: '1', title: 'First Article' },
|
|
340
|
+
* // { id: '2', title: 'Second Article' }
|
|
341
|
+
* // ],
|
|
342
|
+
* // meta: { total: 2 },
|
|
343
|
+
* // links: { next: '/articles?page=2' }
|
|
344
|
+
* // }
|
|
368
345
|
*
|
|
369
346
|
* @example
|
|
370
|
-
* //
|
|
347
|
+
* // Input: With included resources
|
|
371
348
|
* const jsonApi = {
|
|
372
349
|
* data: {
|
|
373
350
|
* type: 'articles',
|
|
@@ -389,7 +366,7 @@ export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
|
389
366
|
* {
|
|
390
367
|
* type: 'authors',
|
|
391
368
|
* id: '75',
|
|
392
|
-
* attributes: { name: 'Jane Smith'
|
|
369
|
+
* attributes: { name: 'Jane Smith' }
|
|
393
370
|
* },
|
|
394
371
|
* {
|
|
395
372
|
* type: 'chapters',
|
|
@@ -403,78 +380,96 @@ export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
|
403
380
|
* }
|
|
404
381
|
* ]
|
|
405
382
|
* };
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
* { context: { schemaStructure: schema, schemaRelationships: relationships } }
|
|
409
|
-
* );
|
|
410
|
-
* // Returns:
|
|
383
|
+
*
|
|
384
|
+
* // Output: Expands included resources
|
|
411
385
|
* // {
|
|
412
386
|
* // id: '400',
|
|
413
387
|
* // title: 'JavaScript Guide',
|
|
414
388
|
* // author_id: '75',
|
|
415
|
-
* // author: {
|
|
389
|
+
* // author: { // Expanded from included
|
|
416
390
|
* // id: '75',
|
|
417
|
-
* // name: 'Jane Smith'
|
|
418
|
-
* // bio: 'Expert developer'
|
|
391
|
+
* // name: 'Jane Smith'
|
|
419
392
|
* // },
|
|
420
393
|
* // chapters_ids: ['1001', '1002'],
|
|
421
|
-
* // chapters: [
|
|
394
|
+
* // chapters: [ // Expanded array
|
|
422
395
|
* // { id: '1001', number: 1, title: 'Introduction' },
|
|
423
396
|
* // { id: '1002', number: 2, title: 'Basics' }
|
|
424
397
|
* // ]
|
|
425
398
|
* // }
|
|
426
399
|
*
|
|
427
|
-
* @
|
|
400
|
+
* @description
|
|
401
|
+
* Used by:
|
|
402
|
+
* - rest-api-plugin for GET/QUERY when simplified: true
|
|
403
|
+
* - Transforms responses before sending to client
|
|
404
|
+
*
|
|
405
|
+
* Purpose:
|
|
406
|
+
* - Provides cleaner response format for clients
|
|
407
|
+
* - Restores foreign keys from relationships
|
|
408
|
+
* - Expands included resources for convenience
|
|
409
|
+
* - Maintains compatibility with collection metadata
|
|
410
|
+
* - Handles both single and array responses
|
|
411
|
+
*
|
|
412
|
+
* Data flow:
|
|
413
|
+
* 1. Detects array vs single resource
|
|
414
|
+
* 2. For arrays, preserves meta/links at root
|
|
415
|
+
* 3. Calls transformSingleJsonApiToSimplified for each
|
|
416
|
+
* 4. Returns simplified structure
|
|
428
417
|
*/
|
|
429
418
|
export const transformJsonApiToSimplified = (scope, deps) => {
|
|
430
419
|
// Extract needed values from scope and deps
|
|
431
420
|
const jsonApi = scope.record;
|
|
432
421
|
const schema = deps.context.schemaStructure;
|
|
433
422
|
const relationships = deps.context.schemaRelationships;
|
|
423
|
+
const scopes = deps.context.scopes;
|
|
434
424
|
|
|
435
425
|
if (!jsonApi?.data) return jsonApi;
|
|
436
426
|
|
|
437
427
|
// Handle array response (QUERY)
|
|
438
428
|
if (Array.isArray(jsonApi.data)) {
|
|
439
|
-
|
|
429
|
+
const simplifiedData = jsonApi.data.map(item =>
|
|
440
430
|
transformSingleJsonApiToSimplified(
|
|
441
431
|
{ data: item, included: jsonApi.included },
|
|
442
|
-
{ context: { schemaStructure: schema, schemaRelationships: relationships } }
|
|
432
|
+
{ context: { schemaStructure: schema, schemaRelationships: relationships, scopes } }
|
|
443
433
|
)
|
|
444
434
|
);
|
|
435
|
+
|
|
436
|
+
// For query results, return object with data, meta, and links
|
|
437
|
+
const result = { data: simplifiedData };
|
|
438
|
+
|
|
439
|
+
// Add meta if present
|
|
440
|
+
if (jsonApi.meta) {
|
|
441
|
+
result.meta = jsonApi.meta;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Add links if present
|
|
445
|
+
if (jsonApi.links) {
|
|
446
|
+
result.links = jsonApi.links;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return result;
|
|
445
450
|
}
|
|
446
451
|
|
|
447
|
-
// Handle single response
|
|
452
|
+
// Handle single response (no change for single resources)
|
|
448
453
|
return transformSingleJsonApiToSimplified(
|
|
449
454
|
{ data: jsonApi.data, included: jsonApi.included },
|
|
450
|
-
{ context: { schemaStructure: schema, schemaRelationships: relationships } }
|
|
455
|
+
{ context: { schemaStructure: schema, schemaRelationships: relationships, scopes } }
|
|
451
456
|
);
|
|
452
457
|
};
|
|
453
458
|
|
|
454
459
|
/**
|
|
455
|
-
* Transforms
|
|
460
|
+
* Transforms single JSON:API resource into simplified format
|
|
456
461
|
*
|
|
457
|
-
*
|
|
458
|
-
* individual resources. It's used by transformJsonApiToSimplified for both single
|
|
459
|
-
* resources and each item in a collection.
|
|
460
|
-
*
|
|
461
|
-
* The transformation includes:
|
|
462
|
-
* - Flattening the id and attributes onto the root object
|
|
463
|
-
* - Converting relationship references back to foreign key fields
|
|
464
|
-
* - Extracting many-to-many relationship IDs into _ids arrays
|
|
465
|
-
* - Expanding included related resources when available
|
|
466
|
-
*
|
|
467
|
-
* @param {Object} scope - The scope object containing the data
|
|
462
|
+
* @param {Object} scope - Scope with data and included resources
|
|
468
463
|
* @param {Object} scope.data - Single JSON:API resource object
|
|
469
|
-
* @param {Array} scope.included - Array of included resources
|
|
470
|
-
* @param {Object} deps -
|
|
471
|
-
* @param {Object} deps.context -
|
|
472
|
-
* @param {Object} deps.context.
|
|
473
|
-
* @param {Object} deps.context.
|
|
464
|
+
* @param {Array} scope.included - Array of included resources
|
|
465
|
+
* @param {Object} deps - Dependencies with context
|
|
466
|
+
* @param {Object} deps.context.schemaStructure - Resource schema
|
|
467
|
+
* @param {Object} deps.context.schemaRelationships - Relationship configs
|
|
468
|
+
* @param {Object} deps.context.scopes - All scopes for recursion
|
|
474
469
|
* @returns {Object} Simplified plain object
|
|
475
470
|
*
|
|
476
471
|
* @example
|
|
477
|
-
* //
|
|
472
|
+
* // Input: Basic resource with relationships
|
|
478
473
|
* const data = {
|
|
479
474
|
* type: 'comments',
|
|
480
475
|
* id: '100',
|
|
@@ -499,19 +494,20 @@ export const transformJsonApiToSimplified = (scope, deps) => {
|
|
|
499
494
|
* };
|
|
500
495
|
* const result = transformSingleJsonApiToSimplified(
|
|
501
496
|
* { data: data, included: null },
|
|
502
|
-
* { context: { schemaStructure: schema
|
|
497
|
+
* { context: { schemaStructure: schema } }
|
|
503
498
|
* );
|
|
504
|
-
*
|
|
499
|
+
*
|
|
500
|
+
* // Output: Flattened with foreign keys restored
|
|
505
501
|
* // {
|
|
506
502
|
* // id: '100',
|
|
507
503
|
* // text: 'Great article!',
|
|
508
504
|
* // created_at: '2024-01-15',
|
|
509
|
-
* // author_id: '50',
|
|
510
|
-
* // article_id: '200'
|
|
505
|
+
* // author_id: '50', // From relationships.author
|
|
506
|
+
* // article_id: '200' // From relationships.article
|
|
511
507
|
* // }
|
|
512
508
|
*
|
|
513
509
|
* @example
|
|
514
|
-
* //
|
|
510
|
+
* // Input: Many-to-many relationships
|
|
515
511
|
* const data = {
|
|
516
512
|
* type: 'articles',
|
|
517
513
|
* id: '300',
|
|
@@ -529,19 +525,16 @@ export const transformJsonApiToSimplified = (scope, deps) => {
|
|
|
529
525
|
* const relationships = {
|
|
530
526
|
* tags: { manyToMany: { via: 'article_tags' } }
|
|
531
527
|
* };
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
* { context: { schemaStructure: {}, schemaRelationships: relationships } }
|
|
535
|
-
* );
|
|
536
|
-
* // Returns:
|
|
528
|
+
*
|
|
529
|
+
* // Output: Array of IDs with _ids suffix
|
|
537
530
|
* // {
|
|
538
531
|
* // id: '300',
|
|
539
532
|
* // title: 'My Article',
|
|
540
|
-
* // tags_ids: ['1', '2', '3']
|
|
533
|
+
* // tags_ids: ['1', '2', '3'] // Relationship name + _ids
|
|
541
534
|
* // }
|
|
542
535
|
*
|
|
543
536
|
* @example
|
|
544
|
-
* //
|
|
537
|
+
* // Input: With included resources (recursive expansion)
|
|
545
538
|
* const data = {
|
|
546
539
|
* type: 'books',
|
|
547
540
|
* id: '400',
|
|
@@ -562,7 +555,12 @@ export const transformJsonApiToSimplified = (scope, deps) => {
|
|
|
562
555
|
* {
|
|
563
556
|
* type: 'authors',
|
|
564
557
|
* id: '75',
|
|
565
|
-
* attributes: { name: 'Jane Smith'
|
|
558
|
+
* attributes: { name: 'Jane Smith' },
|
|
559
|
+
* relationships: {
|
|
560
|
+
* company: {
|
|
561
|
+
* data: { type: 'companies', id: '5' }
|
|
562
|
+
* }
|
|
563
|
+
* }
|
|
566
564
|
* },
|
|
567
565
|
* {
|
|
568
566
|
* type: 'chapters',
|
|
@@ -575,27 +573,47 @@ export const transformJsonApiToSimplified = (scope, deps) => {
|
|
|
575
573
|
* attributes: { number: 2, title: 'Basics' }
|
|
576
574
|
* }
|
|
577
575
|
* ];
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
* { context: { schemaStructure: schema, schemaRelationships: relationships } }
|
|
581
|
-
* );
|
|
582
|
-
* // Returns:
|
|
576
|
+
*
|
|
577
|
+
* // Output: Recursively expanded (note author.company_id)
|
|
583
578
|
* // {
|
|
584
579
|
* // id: '400',
|
|
585
580
|
* // title: 'JavaScript Guide',
|
|
586
581
|
* // author_id: '75',
|
|
587
|
-
* // author: {
|
|
582
|
+
* // author: { // Expanded and transformed
|
|
588
583
|
* // id: '75',
|
|
589
584
|
* // name: 'Jane Smith',
|
|
590
|
-
* //
|
|
585
|
+
* // company_id: '5' // Author's relationships processed!
|
|
591
586
|
* // },
|
|
592
587
|
* // chapters_ids: ['1001', '1002'],
|
|
593
|
-
* // chapters: [
|
|
588
|
+
* // chapters: [
|
|
594
589
|
* // { id: '1001', number: 1, title: 'Introduction' },
|
|
595
590
|
* // { id: '1002', number: 2, title: 'Basics' }
|
|
596
591
|
* // ]
|
|
597
592
|
* // }
|
|
598
593
|
*
|
|
594
|
+
* @description
|
|
595
|
+
* Used by:
|
|
596
|
+
* - transformJsonApiToSimplified for each resource
|
|
597
|
+
* - Calls itself recursively for included resources
|
|
598
|
+
*
|
|
599
|
+
* Purpose:
|
|
600
|
+
* - Core transformation logic for single resources
|
|
601
|
+
* - Restores foreign keys from relationships
|
|
602
|
+
* - Creates _ids arrays for to-many relationships
|
|
603
|
+
* - Recursively expands and transforms included data
|
|
604
|
+
* - Handles polymorphic relationships
|
|
605
|
+
*
|
|
606
|
+
* Data flow:
|
|
607
|
+
* 1. Copies ID and attributes to result
|
|
608
|
+
* 2. Processes each relationship:
|
|
609
|
+
* - BelongsTo: restores foreign key field
|
|
610
|
+
* - Polymorphic: restores type and ID fields
|
|
611
|
+
* - HasMany/ManyToMany: creates _ids array
|
|
612
|
+
* 3. If included data exists:
|
|
613
|
+
* - Finds matching included resources
|
|
614
|
+
* - Recursively transforms them (preserving their relationships!)
|
|
615
|
+
* - Adds transformed objects/arrays to result
|
|
616
|
+
*
|
|
599
617
|
* @private
|
|
600
618
|
*/
|
|
601
619
|
export const transformSingleJsonApiToSimplified = (scope, deps) => {
|
|
@@ -604,6 +622,7 @@ export const transformSingleJsonApiToSimplified = (scope, deps) => {
|
|
|
604
622
|
const included = scope.included;
|
|
605
623
|
const schema = deps.context.schemaStructure;
|
|
606
624
|
const relationships = deps.context.schemaRelationships;
|
|
625
|
+
const scopes = deps.context.scopes;
|
|
607
626
|
|
|
608
627
|
const simplified = {};
|
|
609
628
|
|
|
@@ -628,8 +647,16 @@ export const transformSingleJsonApiToSimplified = (scope, deps) => {
|
|
|
628
647
|
}
|
|
629
648
|
}
|
|
630
649
|
|
|
631
|
-
//
|
|
650
|
+
// Check if this is a polymorphic relationship from the relationships config
|
|
632
651
|
const rel = relationships?.[relName];
|
|
652
|
+
if (rel?.belongsToPolymorphic && relData.data) {
|
|
653
|
+
// For polymorphic relationships, restore both the type and id fields
|
|
654
|
+
const { typeField, idField } = rel.belongsToPolymorphic;
|
|
655
|
+
simplified[typeField] = relData.data.type;
|
|
656
|
+
simplified[idField] = relData.data.id;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Handle many-to-many (just IDs, not nested)
|
|
633
660
|
if (rel?.hasMany || rel?.manyToMany) {
|
|
634
661
|
if (relData.data && Array.isArray(relData.data)) {
|
|
635
662
|
simplified[`${relName}_ids`] = relData.data.map(item => item.id);
|
|
@@ -644,18 +671,31 @@ export const transformSingleJsonApiToSimplified = (scope, deps) => {
|
|
|
644
671
|
if (Array.isArray(relData.data)) {
|
|
645
672
|
const nestedData = relData.data.map(findIncluded).filter(Boolean);
|
|
646
673
|
if (nestedData.length > 0) {
|
|
647
|
-
simplified[relName] = nestedData.map(item =>
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
674
|
+
simplified[relName] = nestedData.map(item => {
|
|
675
|
+
// For each included record, transform it recursively to restore its relationship fields
|
|
676
|
+
const itemSimplified = transformSingleJsonApiToSimplified(
|
|
677
|
+
{ data: item, included: null },
|
|
678
|
+
{ context: {
|
|
679
|
+
schemaStructure: scopes?.[item.type]?.vars?.schemaInfo?.schema || {},
|
|
680
|
+
schemaRelationships: scopes?.[item.type]?.vars?.schemaInfo?.schemaRelationships || {},
|
|
681
|
+
scopes
|
|
682
|
+
} }
|
|
683
|
+
);
|
|
684
|
+
return itemSimplified;
|
|
685
|
+
});
|
|
651
686
|
}
|
|
652
687
|
} else {
|
|
653
688
|
const nestedData = findIncluded(relData.data);
|
|
654
689
|
if (nestedData) {
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
690
|
+
// Transform the single included record to restore its relationship fields
|
|
691
|
+
simplified[relName] = transformSingleJsonApiToSimplified(
|
|
692
|
+
{ data: nestedData, included: null },
|
|
693
|
+
{ context: {
|
|
694
|
+
schemaStructure: scopes?.[nestedData.type]?.vars?.schemaInfo?.schema || {},
|
|
695
|
+
schemaRelationships: scopes?.[nestedData.type]?.vars?.schemaInfo?.schemaRelationships || {},
|
|
696
|
+
scopes
|
|
697
|
+
} }
|
|
698
|
+
);
|
|
659
699
|
}
|
|
660
700
|
}
|
|
661
701
|
}
|