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.
Files changed (146) hide show
  1. package/.claude/agents/library-guide-expert.md +29 -0
  2. package/.claude/agents/rest-api-internals-expert.md +53 -0
  3. package/CLAUDE.md +139 -1
  4. package/README.md +2 -5
  5. package/docTest.js +238 -0
  6. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +955 -0
  7. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +54 -0
  8. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +750 -0
  9. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +494 -0
  10. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +398 -0
  11. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +882 -0
  12. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +709 -0
  13. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +1233 -0
  14. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +796 -0
  15. package/{README_X_Relationship_Plugin.md → docs/GUIDE/GUIDE_2_9_Relationships_Urls.md} +1 -4
  16. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +66 -0
  17. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +1246 -0
  18. package/docs/GUIDE/GUIDE_5_Non-Db_Stores.md +0 -0
  19. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +1490 -0
  20. package/{README_X_SocketIO.md → docs/GUIDE/GUIDE_X_SocketIO.md} +17 -17
  21. package/docs/GUIDE/index.md +58 -0
  22. package/docs/GUIDE.md +61 -2238
  23. package/docs/ONBOARDING/Computed_Properties_And_Sparse_Fields.md +398 -0
  24. package/docs/README.md +57 -2239
  25. package/{README_2_Computed_and_hidden_fields.md → docs/README_VARIOUS/GUIDE_2_Computed_and_hidden_fields.md} +22 -17
  26. package/docs/README_VARIOUS/GUIDE_IN_DOCS.md +2277 -0
  27. package/{README.ORIG.md → docs/README_VARIOUS/README.ORIG.md} +8 -8
  28. package/{README_8_Polymorphic_Relationships.md → docs/README_VARIOUS/README_8_Polymorphic_Relationships.md} +1 -1
  29. package/docs/README_VARIOUS/README_IN_DOCS.ms +2277 -0
  30. package/{RELATIONSHIPS_ADVANCED.md → docs/README_VARIOUS/RELATIONSHIPS_ADVANCED.md} +3 -3
  31. package/{correct-schema-format.md → docs/README_VARIOUS/correct-schema-format.md} +13 -13
  32. package/{pagination.md → docs/README_VARIOUS/pagination.md} +11 -8
  33. package/docs/assets/css/style.scss +78 -5
  34. package/docs/index.md +2 -12
  35. package/helper-reorganization-plan.md +85 -0
  36. package/helper-usage-analysis.md +94 -0
  37. package/package.json +5 -4
  38. package/plugins/core/bulk-operations-plugin.js +2 -2
  39. package/plugins/core/connectors/express-plugin.js +99 -33
  40. package/plugins/core/lib/querying/knex-cross-table-search.js +609 -0
  41. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +491 -0
  42. package/plugins/core/lib/querying/knex-pagination-helpers.js +544 -0
  43. package/plugins/core/lib/{knex-process-includes.js → querying/knex-process-includes.js} +65 -50
  44. package/plugins/core/lib/querying/knex-query-helpers-base.js +74 -0
  45. package/plugins/core/lib/{knex-query-helpers.js → querying/knex-query-helpers.js} +416 -180
  46. package/plugins/core/lib/{knex-relationship-includes.js → querying/knex-relationship-includes.js} +430 -114
  47. package/plugins/core/lib/querying/knex-window-queries.js +328 -0
  48. package/plugins/core/lib/querying-writing/compile-schemas.js +328 -0
  49. package/plugins/core/{utils → lib/querying-writing}/connectors-query-parser.js +62 -57
  50. package/plugins/core/lib/{database-capabilities.js → querying-writing/database-capabilities.js} +113 -7
  51. package/plugins/core/lib/querying-writing/database-value-normalizers.js +369 -0
  52. package/plugins/core/lib/querying-writing/default-data-helpers.js +72 -0
  53. package/plugins/core/{utils → lib/querying-writing}/field-utils.js +41 -31
  54. package/plugins/core/{utils → lib/querying-writing}/knex-constants.js +1 -0
  55. package/plugins/core/lib/{knex-field-helpers.js → querying-writing/knex-field-helpers.js} +190 -57
  56. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +162 -0
  57. package/plugins/core/lib/{payload-validators.js → querying-writing/payload-validators.js} +297 -217
  58. package/plugins/core/lib/querying-writing/schema-helpers.js +497 -0
  59. package/plugins/core/lib/querying-writing/scope-validations.js +431 -0
  60. package/plugins/core/lib/{simplified-helpers.js → querying-writing/simplified-helpers.js} +216 -176
  61. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +110 -0
  62. package/plugins/core/lib/writing/many-to-many-manipulations.js +300 -0
  63. package/plugins/core/lib/writing/relationship-processor.js +252 -0
  64. package/plugins/core/rest-api-knex-plugin.js +183 -62
  65. package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +7 -0
  66. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +72 -0
  67. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +102 -0
  68. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +50 -0
  69. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +39 -0
  70. package/plugins/core/rest-api-plugin-methods/add-route.js +23 -0
  71. package/plugins/core/rest-api-plugin-methods/check-permissions.js +18 -0
  72. package/plugins/core/rest-api-plugin-methods/common.js +605 -0
  73. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +115 -0
  74. package/plugins/core/rest-api-plugin-methods/delete.js +109 -0
  75. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +186 -0
  76. package/plugins/core/rest-api-plugin-methods/get-related.js +295 -0
  77. package/plugins/core/rest-api-plugin-methods/get-relationship.js +62 -0
  78. package/plugins/core/rest-api-plugin-methods/get.js +187 -0
  79. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +56 -0
  80. package/plugins/core/rest-api-plugin-methods/patch.js +193 -0
  81. package/plugins/core/rest-api-plugin-methods/post-relationship.js +104 -0
  82. package/plugins/core/rest-api-plugin-methods/post.js +157 -0
  83. package/plugins/core/rest-api-plugin-methods/put.js +257 -0
  84. package/plugins/core/rest-api-plugin-methods/query.js +208 -0
  85. package/plugins/core/rest-api-plugin.js +108 -2498
  86. package/plugins/core/socketio-plugin.js +28 -5
  87. package/plugins/storage/local-storage.js +2 -2
  88. package/tests/computed-fields.test.js +483 -0
  89. package/tests/cors.test.js +4 -4
  90. package/tests/curl.test.js +5 -4
  91. package/tests/custom-idproperty-relationships.test.js +891 -0
  92. package/tests/field-getters.test.js +353 -0
  93. package/tests/field-setters.test.js +310 -0
  94. package/tests/fixtures/api-configs.js +800 -28
  95. package/tests/jwt-auth.test.js +0 -2
  96. package/tests/nested-includes.test.js +1 -1
  97. package/tests/pagination-cursor-multifield.test.js +514 -0
  98. package/tests/pagination-enhanced.test.js +12 -9
  99. package/tests/polymorphic-relationships.test.js +10 -8
  100. package/tests/polymorphic-simple.test.js +6 -4
  101. package/tests/positioning-advanced.test.js +12 -12
  102. package/tests/positioning-extended.test.js +25 -25
  103. package/tests/positioning-multifilter.test.js +24 -24
  104. package/tests/positioning.test.js +3 -3
  105. package/tests/query-limits.test.js +24 -8
  106. package/tests/relationship-endpoints.test.js +4 -5
  107. package/tests/relationships.test.js +0 -1
  108. package/tests/socketio.test.js +12 -1
  109. package/tests/virtual-fields.test.js +13 -13
  110. package/PLAN_FOR_WEBSOCKETS.md +0 -398
  111. package/README_1_Initial_Setup.md +0 -393
  112. package/README_X_Hooks.md +0 -647
  113. package/TODO_rest-api-plugin-extras.todo +0 -23
  114. package/connectors_ideas/express-plugin.js +0 -120
  115. package/connectors_ideas/http-plugin.js +0 -138
  116. package/docs/examples/express-plugin.js +0 -366
  117. package/example.js +0 -301
  118. package/plugins/core/HELPER_MODULES.md +0 -285
  119. package/plugins/core/lib/compile-schemas.js +0 -245
  120. package/plugins/core/lib/database-value-normalizers.js +0 -192
  121. package/plugins/core/lib/default-data-helpers.js +0 -100
  122. package/plugins/core/lib/knex-cross-table-search.js +0 -442
  123. package/plugins/core/lib/knex-json-api-helpers.js +0 -84
  124. package/plugins/core/lib/knex-json-api-transformers.js +0 -449
  125. package/plugins/core/lib/knex-pagination-helpers.js +0 -193
  126. package/plugins/core/lib/knex-window-queries.js +0 -142
  127. package/plugins/core/lib/many-to-many-manipulations.js +0 -343
  128. package/plugins/core/lib/relationship-processor.js +0 -278
  129. package/plugins/core/lib/schema-helpers.js +0 -343
  130. package/plugins/core/lib/scope-validations.js +0 -252
  131. package/plugins/core/rest-api-relationships-plugin.js +0 -700
  132. package/plugins/core/utils/knex-query-helpers-base.js +0 -56
  133. /package/{README_X_Permissions.md → docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md} +0 -0
  134. /package/{README_X_Positioning.md → docs/GUIDE/GUIDE_6_Positioning.md} +0 -0
  135. /package/{README_X_Bulk_Operations.md → docs/GUIDE/GUIDE_X_Bulk_Operations.md} +0 -0
  136. /package/{README_X_Cors.md → docs/GUIDE/GUIDE_X_Cors.md} +0 -0
  137. /package/{README_X_Multihome.md → docs/GUIDE/GUIDE_X_Multihome.md} +0 -0
  138. /package/{README_X_Appendices.md → docs/GUIDE/GUIDE_Y_Appendices.md} +0 -0
  139. /package/{QUERY_DEPTH_PROTECTION.md → docs/README_VARIOUS/QUERY_DEPTH_PROTECTION.md} +0 -0
  140. /package/{RELATIONSHIPS.md → docs/README_VARIOUS/RELATIONSHIPS.md} +0 -0
  141. /package/{WEBSOCKET_IMPLEMENTATION_STATUS.md → docs/README_VARIOUS/WEBSOCKET_IMPLEMENTATION_STATUS.md} +0 -0
  142. /package/{helper-functions-chart.md → docs/README_VARIOUS/helper-functions-chart.md} +0 -0
  143. /package/docs/{implementation-hidden-computed-fields.md → README_VARIOUS/implementation-hidden-computed-fields.md} +0 -0
  144. /package/docs/{include-limits-migration-guide.md → README_VARIOUS/include-limits-migration-guide.md} +0 -0
  145. /package/{readmeSnippets → docs/README_VARIOUS}/test_put_behavior.js +0 -0
  146. /package/{readmeSnippets → docs/README_VARIOUS}/test_put_fields_without_defaults.js +0 -0
@@ -1,34 +1,30 @@
1
1
  /**
2
- * @module simplifiedHelpers
3
- * @description Transformation functions for converting between simplified and JSON:API formats
2
+ * Transformation functions for simplified API mode
4
3
  *
5
- * This module enables the REST API's "simplified mode" feature, which provides a more
6
- * developer-friendly alternative to the verbose JSON:API format. It allows APIs to
7
- * accept and return plain JavaScript objects while still maintaining JSON:API
8
- * compliance internally.
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 '../../../lib/rest-api-errors.js';
13
+ import { RestApiValidationError } from '../../../../lib/rest-api-errors.js';
12
14
 
13
15
  /**
14
- * Transforms a simplified plain JavaScript object into a JSON:API compliant format.
15
- *
16
- * This function is crucial for the REST API's "simplified mode" feature, which allows
17
- * developers to work with familiar plain objects instead of the verbose JSON:API format.
18
- * It intelligently converts plain objects into properly structured JSON:API documents
19
- * by analyzing the schema to determine what fields are attributes vs relationships.
20
- *
21
- * @param {Object} scope - The scope object containing the input data to transform
22
- * @param {Object} scope.inputRecord - The input object to transform (plain object or already JSON:API)
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
- * // Example 1: Simple object with attributes only
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, schemaRelationships: relationships } }
35
+ * { context: { scopeName: 'articles', schemaStructure: schema } }
40
36
  * );
41
- * // Returns:
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
- * // Example 2: Object with belongsTo relationship (using foreign key name)
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 // Original way: using foreign key name
63
+ * author_id: 789 // Using foreign key name
67
64
  * };
68
- * const result = transformSimplifiedToJsonApi(
69
- * { inputRecord: input },
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
- * // Example 3: Object with belongsTo relationship (using 'as' alias - NEW desired behavior)
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 // NEW way: using the 'as' alias
87
+ * author: 789 // Using 'as' alias directly
102
88
  * };
103
- * const result = transformSimplifiedToJsonApi(
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
- * // Example 4: Object with many-to-many relationship
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'] // Array of tag IDs
105
+ * tags: ['10', '20', '30'] // Array of IDs
124
106
  * };
125
- * const result = transformSimplifiedToJsonApi(
126
- * { inputRecord: input },
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
- * // Example 5: Already JSON:API formatted - returns as-is
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
- * const result = transformSimplifiedToJsonApi(
158
- * { inputRecord: input },
159
- * { context: { scopeName: 'articles', schemaStructure: schema, schemaRelationships: relationships } }
160
- * );
161
- * // Returns the same object unchanged
162
- *
163
- * @example <caption>Why this is useful upstream</caption>
164
- * // The REST API plugin uses this to:
165
- * // 1. Accept familiar plain objects from API consumers (better DX)
166
- * // 2. Automatically convert foreign keys to proper relationships
167
- * // 3. Support both simple and JSON:API formats in the same endpoint
168
- * // 4. Reduce boilerplate in client applications
169
- * // 5. Maintain JSON:API compliance internally for consistency
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 a JSON:API formatted response into a simplified plain JavaScript object.
271
- *
272
- * This function reverses the work of transformSimplifiedToJsonApi, converting verbose
273
- * JSON:API documents back into simple, flat JavaScript objects that are easier to work
274
- * with in application code. It handles both single resources and collections, and can
275
- * optionally expand included resources inline.
276
- *
277
- * The transformation process:
278
- * 1. Flattens attributes directly onto the result object
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
- * // Example 1: Single resource with attributes
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, schemaRelationships: relationships } }
281
+ * { context: { schemaStructure: schema } }
306
282
  * );
307
- * // Returns:
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
- * // Example 2: Resource with belongsTo relationship
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
- * const result = transformJsonApiToSimplified(
333
- * { record: jsonApi },
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' // Foreign key restored
314
+ * // author_id: '789' // Restored from relationship
341
315
  * // }
342
316
  *
343
317
  * @example
344
- * // Example 3: Collection of resources
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
- * const result = transformJsonApiToSimplified(
360
- * { record: jsonApi },
361
- * { context: { schemaStructure: schema, schemaRelationships: relationships } }
362
- * );
363
- * // Returns:
364
- * // [
365
- * // { id: '1', title: 'First Article' },
366
- * // { id: '2', title: 'Second Article' }
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
- * // Example 4: Resource with included data expanded
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', bio: 'Expert developer' }
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
- * const result = transformJsonApiToSimplified(
407
- * { record: jsonApi },
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: { // Expanded from included
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: [ // Expanded array from included
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
- * @private
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
- return jsonApi.data.map(item =>
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 a single JSON:API resource object into a simplified format.
460
+ * Transforms single JSON:API resource into simplified format
456
461
  *
457
- * This is the workhorse function that handles the actual transformation logic for
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 from JSON:API response
470
- * @param {Object} deps - The dependencies object
471
- * @param {Object} deps.context - The context object containing configuration
472
- * @param {Object} deps.context.schemaStructure - Resource schema for field mappings
473
- * @param {Object} deps.context.schemaRelationships - Relationships configuration
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
- * // Example 1: Basic transformation with relationships
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, schemaRelationships: {} } }
497
+ * { context: { schemaStructure: schema } }
503
498
  * );
504
- * // Returns:
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', // Restored from relationships.author
510
- * // article_id: '200' // Restored from relationships.article
505
+ * // author_id: '50', // From relationships.author
506
+ * // article_id: '200' // From relationships.article
511
507
  * // }
512
508
  *
513
509
  * @example
514
- * // Example 2: Many-to-many relationships become ID arrays
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
- * const result = transformSingleJsonApiToSimplified(
533
- * { data: data, included: null },
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'] // Array of IDs with _ids suffix
533
+ * // tags_ids: ['1', '2', '3'] // Relationship name + _ids
541
534
  * // }
542
535
  *
543
536
  * @example
544
- * // Example 3: Expanding included resources
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', bio: 'Expert developer' }
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
- * const result = transformSingleJsonApiToSimplified(
579
- * { data: data, included: included },
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: { // Expanded from included
582
+ * // author: { // Expanded and transformed
588
583
  * // id: '75',
589
584
  * // name: 'Jane Smith',
590
- * // bio: 'Expert developer'
585
+ * // company_id: '5' // Author's relationships processed!
591
586
  * // },
592
587
  * // chapters_ids: ['1001', '1002'],
593
- * // chapters: [ // Expanded array from included
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
- // Handle many-to-many (just IDs, not nested)
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
- id: item.id,
649
- ...item.attributes
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
- simplified[relName] = {
656
- id: nestedData.id,
657
- ...nestedData.attributes
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
  }