json-rest-api 1.0.21 → 1.0.22

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 (173) hide show
  1. package/audit/00-charter.md +103 -0
  2. package/audit/01-scope-map.md +84 -0
  3. package/audit/02-coverage-matrix.csv +174 -0
  4. package/audit/03-invariants.md +123 -0
  5. package/audit/04-standards-rubric.md +56 -0
  6. package/audit/05-competitor-rubric.md +77 -0
  7. package/audit/06-findings.md +160 -0
  8. package/audit/07-risk-register.csv +10 -0
  9. package/audit/08-remediation-roadmap.md +177 -0
  10. package/audit/09-signoff-check.md +23 -0
  11. package/audit/10-final-synthesis.md +29 -0
  12. package/audit/evidence/code/all-files-json-rest-api.txt +168 -0
  13. package/audit/evidence/code/all-files-json-rest-schema.txt +12 -0
  14. package/audit/evidence/code/boundary-leak-checks.txt +72 -0
  15. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__bulk-operations-plugin.js.txt +480 -0
  16. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__express-plugin.js.txt +532 -0
  17. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__busboy-detector.js.txt +175 -0
  18. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__formidable-detector.js.txt +138 -0
  19. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__request-helpers.js.txt +222 -0
  20. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt +342 -0
  21. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__dbTablesOperations.js.txt +304 -0
  22. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__fractional-positioning.js.txt +188 -0
  23. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__compile-schemas.js.txt +336 -0
  24. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__connectors-query-parser.js.txt +165 -0
  25. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-capabilities.js.txt +265 -0
  26. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-value-normalizers.js.txt +369 -0
  27. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__default-data-helpers.js.txt +72 -0
  28. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__field-utils.js.txt +139 -0
  29. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-constants.js.txt +45 -0
  30. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-field-helpers.js.txt +396 -0
  31. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-json-api-transformers.js.txt +162 -0
  32. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__payload-validators.js.txt +1314 -0
  33. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__schema-helpers.js.txt +488 -0
  34. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__scope-validations.js.txt +484 -0
  35. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__simplified-helpers.js.txt +857 -0
  36. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-cross-table-search.js.txt +565 -0
  37. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-json-api-transformers-querying.js.txt +485 -0
  38. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-pagination-helpers.js.txt +560 -0
  39. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-process-includes.js.txt +167 -0
  40. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers-base.js.txt +109 -0
  41. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers.js.txt +1064 -0
  42. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-relationship-includes.js.txt +1983 -0
  43. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-window-queries.js.txt +328 -0
  44. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__url-helpers.js.txt +119 -0
  45. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__storage__storage-adapter.js.txt +160 -0
  46. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__many-to-many-manipulations.js.txt +300 -0
  47. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__relationship-processor.js.txt +245 -0
  48. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-access.js.txt +459 -0
  49. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-cors-plugin.js.txt +237 -0
  50. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-knex-plugin.js.txt +1135 -0
  51. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-multihome-plugin.js.txt +314 -0
  52. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__compile-resource-schemas.js.txt +7 -0
  53. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-relationship-routes.js.txt +71 -0
  54. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-scope-routes.js.txt +102 -0
  55. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__turn-scope-init-into-vars.js.txt +49 -0
  56. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__validate-include-configurations.js.txt +39 -0
  57. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__add-route.js.txt +23 -0
  58. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__check-permissions.js.txt +13 -0
  59. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__common.js.txt +639 -0
  60. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete-relationship.js.txt +117 -0
  61. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete.js.txt +104 -0
  62. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__enrich-attributes.js.txt +199 -0
  63. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-related.js.txt +353 -0
  64. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-relationship.js.txt +61 -0
  65. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get.js.txt +184 -0
  66. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch-relationship.js.txt +55 -0
  67. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch.js.txt +191 -0
  68. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post-relationship.js.txt +110 -0
  69. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post.js.txt +168 -0
  70. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__put.js.txt +253 -0
  71. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__query.js.txt +207 -0
  72. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__release.js.txt +12 -0
  73. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin.js.txt +165 -0
  74. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-positioning-plugin.js.txt +668 -0
  75. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__socketio-plugin.js.txt +621 -0
  76. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__local-storage.js.txt +218 -0
  77. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__s3-storage.js.txt +124 -0
  78. package/audit/evidence/code/critical-pass1/json-rest-schema__src__CorePlugin.js.txt +269 -0
  79. package/audit/evidence/code/critical-pass1/json-rest-schema__src__Schema.js.txt +242 -0
  80. package/audit/evidence/code/critical-pass1/json-rest-schema__src__index.js.txt +77 -0
  81. package/audit/evidence/code/critical-pass1-risk-keywords.txt +238 -0
  82. package/audit/evidence/code/critical-pass1-summary.csv +67 -0
  83. package/audit/evidence/code/critical-pass2-summary.csv +67 -0
  84. package/audit/evidence/code/early-silent-zones-no-test-token.txt +57 -0
  85. package/audit/evidence/code/error-model-signals.txt +98 -0
  86. package/audit/evidence/code/git-files-json-rest-api.txt +171 -0
  87. package/audit/evidence/code/git-files-json-rest-schema.txt +27 -0
  88. package/audit/evidence/code/hook-flow-signals.txt +282 -0
  89. package/audit/evidence/code/idproperty-signals.txt +157 -0
  90. package/audit/evidence/code/import-cycles-non-anyapi.txt +3 -0
  91. package/audit/evidence/code/import-hotspots-non-anyapi.txt +96 -0
  92. package/audit/evidence/code/imports-json-rest-api.txt +354 -0
  93. package/audit/evidence/code/imports-json-rest-schema.txt +15 -0
  94. package/audit/evidence/code/in-scope-files-json-rest-api.txt +160 -0
  95. package/audit/evidence/code/in-scope-files-json-rest-schema.txt +13 -0
  96. package/audit/evidence/code/non-anyapi-core-files.txt +101 -0
  97. package/audit/evidence/code/permission-signals.txt +193 -0
  98. package/audit/evidence/code/query-include-signals.txt +687 -0
  99. package/audit/evidence/code/transaction-signals.txt +28 -0
  100. package/audit/evidence/code/validation-signals.txt +650 -0
  101. package/audit/evidence/code/write-path-signals.txt +61 -0
  102. package/audit/evidence/docs/docs-claims-signals.txt +1075 -0
  103. package/audit/evidence/docs/schema-docs-claims-signals.txt +179 -0
  104. package/audit/evidence/external/corpus-freeze.md +21 -0
  105. package/audit/evidence/external/npm-competitor-metadata.txt +70 -0
  106. package/audit/evidence/external/npm-download-signals.txt +23 -0
  107. package/audit/evidence/external/npm-jsonapi-server.txt +8 -0
  108. package/audit/evidence/external/npm-modern-jsonapi-metadata.txt +38 -0
  109. package/audit/evidence/external/readme-fortune-signals.txt +26 -0
  110. package/audit/evidence/external/readme-fortune.md +165 -0
  111. package/audit/evidence/external/readme-json-api-serializer-signals.txt +80 -0
  112. package/audit/evidence/external/readme-json-api-serializer.md +690 -0
  113. package/audit/evidence/external/readme-jsonapi-serde-server-signals.txt +0 -0
  114. package/audit/evidence/external/readme-jsonapi-serde-server.md +22 -0
  115. package/audit/evidence/external/readme-jsonapi-serializer-signals.txt +63 -0
  116. package/audit/evidence/external/readme-jsonapi-serializer.md +315 -0
  117. package/audit/evidence/external/readme-jsonapi-server-signals.txt +19 -0
  118. package/audit/evidence/external/readme-jsonapi-server.md +104 -0
  119. package/audit/evidence/external/readme-nestjs-json-api-signals.txt +4 -0
  120. package/audit/evidence/external/readme-nestjs-json-api.md +94 -0
  121. package/audit/evidence/external/readme-yaser-json-api-nestjs-signals.txt +47 -0
  122. package/audit/evidence/external/readme-yaser-json-api-nestjs.md +447 -0
  123. package/audit/evidence/external/standard-jsonapi-snippets.txt +84 -0
  124. package/audit/evidence/external/standard-rfc-snippets.txt +168 -0
  125. package/audit/evidence/external/standards-sources.txt +6 -0
  126. package/audit/evidence/runtime/baseline-commits.txt +2 -0
  127. package/audit/evidence/runtime/db-adapter-assumptions.txt +3 -0
  128. package/audit/evidence/runtime/environment.txt +4 -0
  129. package/audit/evidence/runtime/invariant-runtime-mapping.md +38 -0
  130. package/audit/evidence/runtime/probe-patch-relationship-error.exitcode +1 -0
  131. package/audit/evidence/runtime/probe-patch-relationship-error.log +76 -0
  132. package/audit/evidence/runtime/probe-patch-relationship-error.mjs +67 -0
  133. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.exitcode +1 -0
  134. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.log +62 -0
  135. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.mjs +47 -0
  136. package/audit/evidence/runtime/test-exitcodes.txt +4 -0
  137. package/audit/evidence/runtime/test-summary.md +107 -0
  138. package/audit/evidence/runtime/trace-anchors.txt +61 -0
  139. package/audit/evidence/tests/access-multitenancy-test-signals.txt +125 -0
  140. package/audit/evidence/tests/failing-tests-summary.txt +25 -0
  141. package/audit/evidence/tests/npm-test-json-rest-api.exitcode +1 -0
  142. package/audit/evidence/tests/npm-test-json-rest-api.log +5885 -0
  143. package/audit/evidence/tests/npm-test-json-rest-schema.exitcode +1 -0
  144. package/audit/evidence/tests/npm-test-json-rest-schema.log +356 -0
  145. package/audit/evidence/tests/query-include-test-signals.txt +948 -0
  146. package/audit/evidence/tests/relationship-test-signals.txt +223 -0
  147. package/audit/evidence/tests/skipped-tests.txt +10 -0
  148. package/audit/evidence/tests/test-suite-list-non-anyapi.txt +30 -0
  149. package/audit/evidence/tests/test-suite-list.txt +32 -0
  150. package/audit/subsystems/docs-drift.md +49 -0
  151. package/audit/subsystems/pass2-cross-review.md +131 -0
  152. package/audit/subsystems/plugins-access.md +51 -0
  153. package/audit/subsystems/plugins-bulk.md +50 -0
  154. package/audit/subsystems/plugins-cors.md +48 -0
  155. package/audit/subsystems/plugins-file.md +53 -0
  156. package/audit/subsystems/plugins-multihome.md +50 -0
  157. package/audit/subsystems/plugins-positioning.md +54 -0
  158. package/audit/subsystems/plugins-socketio.md +50 -0
  159. package/audit/subsystems/query-engine.md +61 -0
  160. package/audit/subsystems/relationships.md +55 -0
  161. package/audit/subsystems/rest-orchestration.md +66 -0
  162. package/audit/subsystems/schema-core.md +64 -0
  163. package/audit/subsystems/storage-knex.md +57 -0
  164. package/audit/subsystems/tests.md +48 -0
  165. package/audit/subsystems/transport-express.md +55 -0
  166. package/audit/traces/trace-read-get.md +22 -0
  167. package/audit/traces/trace-read-query.md +25 -0
  168. package/audit/traces/trace-relationship-endpoints.md +31 -0
  169. package/audit/traces/trace-write-delete.md +17 -0
  170. package/audit/traces/trace-write-patch.md +26 -0
  171. package/audit/traces/trace-write-post.md +22 -0
  172. package/audit/traces/trace-write-put.md +23 -0
  173. package/package.json +3 -3
@@ -0,0 +1,857 @@
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:
7
+ 7 * - Accept plain JavaScript objects as input
8
+ 8 * - Return flat response objects instead of nested JSON:API
9
+ 9 * - Automatically convert between formats transparently
10
+ 10 * - Maintain JSON:API compliance internally
11
+ 11 */
12
+ 12
13
+ 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
21
+ 21 * @param {string} deps.context.scopeName - Resource type name
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 = {
29
+ 29 * id: '123',
30
+ 30 * title: 'My Article',
31
+ 31 * content: 'Article content here'
32
+ 32 * };
33
+ 33 * const result = transformSimplifiedToJsonApi(
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: {
41
+ 41 * // type: 'articles',
42
+ 42 * // id: '123',
43
+ 43 * // attributes: {
44
+ 44 * // title: 'My Article',
45
+ 45 * // content: 'Article content here'
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
+ 54 * author_id: {
55
+ 55 * type: 'number',
56
+ 56 * belongsTo: 'users',
57
+ 57 * as: 'author'
58
+ 58 * }
59
+ 59 * };
60
+ 60 * const input = {
61
+ 61 * id: '456',
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: {
69
+ 69 * // type: 'articles',
70
+ 70 * // id: '456',
71
+ 71 * // attributes: {
72
+ 72 * // title: 'Another Article' // author removed from attributes
73
+ 73 * // },
74
+ 74 * // relationships: {
75
+ 75 * // author: {
76
+ 76 * // data: { type: 'users', id: '789' }
77
+ 77 * // }
78
+ 78 * // }
79
+ 79 * // }
80
+ 80 * // }
81
+ 81 *
82
+ 82 * @example
83
+ 83 * // Input: Object with relationship name (only supported way)
84
+ 84 * const input = {
85
+ 85 * id: '456',
86
+ 86 * title: 'Another Article',
87
+ 87 * author: 789 // Using relationship name
88
+ 88 * };
89
+ 89 * // Output: Same JSON:API structure as above
90
+ 90 *
91
+ 91 * @example
92
+ 92 * // Input: Many-to-many relationship
93
+ 93 * const relationships = {
94
+ 94 * tags: {
95
+ 95 * type: 'manyToMany',
96
+ 96 * through: 'article_tags',
97
+ 97 * foreignKey: 'article_id',
98
+ 98 * otherKey: 'tag_id',
99
+ 99 * target: 'tags'
100
+ 100 * }
101
+ 101 * };
102
+ 102 * const input = {
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: {
110
+ 110 * // type: 'articles',
111
+ 111 * // attributes: {
112
+ 112 * // title: 'Tagged Article'
113
+ 113 * // },
114
+ 114 * // relationships: {
115
+ 115 * // tags: {
116
+ 116 * // data: [
117
+ 117 * // { type: 'tags', id: '10' },
118
+ 118 * // { type: 'tags', id: '20' },
119
+ 119 * // { type: 'tags', id: '30' }
120
+ 120 * // ]
121
+ 121 * // }
122
+ 122 * // }
123
+ 123 * // }
124
+ 124 * // }
125
+ 125 *
126
+ 126 * @example
127
+ 127 * // Input: Already JSON:API formatted
128
+ 128 * const input = {
129
+ 129 * data: {
130
+ 130 * type: 'articles',
131
+ 131 * id: '1',
132
+ 132 * attributes: { title: 'Already formatted' }
133
+ 133 * }
134
+ 134 * };
135
+ 135 * // Output: Returned unchanged (detected by data.type)
136
+ 136 *
137
+ 137 * @example
138
+ 138 * // Input: Polymorphic relationship with minimal object format
139
+ 139 * const relationships = {
140
+ 140 * commentable: {
141
+ 141 * belongsToPolymorphic: {
142
+ 142 * types: ['posts', 'videos'],
143
+ 143 * typeField: 'commentable_type',
144
+ 144 * idField: 'commentable_id'
145
+ 145 * }
146
+ 146 * }
147
+ 147 * };
148
+ 148 * const input = {
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: {
156
+ 156 * // type: 'comments',
157
+ 157 * // attributes: {
158
+ 158 * // text: 'Great content!'
159
+ 159 * // },
160
+ 160 * // relationships: {
161
+ 161 * // commentable: {
162
+ 162 * // data: { type: 'posts', id: '456' }
163
+ 163 * // }
164
+ 164 * // }
165
+ 165 * // }
166
+ 166 * // }
167
+ 167 *
168
+ 168 * @example
169
+ 169 * // Input: Clearing polymorphic relationship
170
+ 170 * const input = {
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: {
178
+ 178 * // type: 'comments',
179
+ 179 * // attributes: {
180
+ 180 * // text: 'Updated comment'
181
+ 181 * // },
182
+ 182 * // relationships: {
183
+ 183 * // commentable: { data: null }
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
196
+ 196 * - Supports relationship alias notation
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
204
+ 204 * 3. Processes belongsTo relationships from schema
205
+ 205 * 4. Processes hasMany/manyToMany from relationships
206
+ 206 * 5. Processes polymorphic belongsTo from relationships
207
+ 207 * 6. Remaining fields become attributes
208
+ 208 * 6. Builds proper JSON:API document structure
209
+ 209 */
210
+ 210 export const transformSimplifiedToJsonApi = (scope, deps) => {
211
+ 211 // Extract needed values from scope and deps
212
+ 212 const input = scope.inputRecord
213
+ 213 const scopeName = deps.context.scopeName
214
+ 214 const schema = deps.context.schemaStructure
215
+ 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
+ 219 return input
220
+ 220 }
221
+ 221
222
+ 222 const attributes = {}
223
+ 223 const relationshipsData = {}
224
+ 224 const tempInput = { ...input } // Create a mutable copy of the input
225
+ 225
226
+ 226 const id = tempInput.id
227
+ 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
+ 233 const relAlias = fieldDef.as || fieldName // Use 'as' alias or foreign key name as default alias
234
+ 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) {
238
+ 238 throw new RestApiValidationError(
239
+ 239 `Foreign key field '${fieldName}' is no longer supported for relationship input. ` +
240
+ 240 `Use the relationship name '${fieldDef.as || fieldName}' instead.`,
241
+ 241 {
242
+ 242 fields: [fieldName],
243
+ 243 violations: [{
244
+ 244 field: fieldName,
245
+ 245 rule: 'deprecated_foreign_key',
246
+ 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
+ 253 const relName = fieldDef.as || fieldName
254
+ 254 if (tempInput[relName] !== undefined) {
255
+ 255 valueToProcess = tempInput[relName]
256
+ 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 }
266
+ 266
267
+ 267 // 2. Process hasMany/manyToMany relationships (from relationships object)
268
+ 268 // Iterate through the relationships config to find hasMany/manyToMany
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
+ 272 const value = tempInput[relName]
273
+ 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
+ 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
+ 293 relationshipsData[relName] = {
294
+ 294 data: value ? { type: relConfig.belongsTo, id: String(value) } : null
295
+ 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
+ 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`,
308
+ 308 {
309
+ 309 fields: [relName],
310
+ 310 violations: [{
311
+ 311 field: relName,
312
+ 312 rule: 'polymorphic_format',
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
+ 323 Object.assign(attributes, tempInput)
324
+ 324
325
+ 325 return {
326
+ 326 data: {
327
+ 327 type: scopeName,
328
+ 328 ...(id && { id: String(id) }),
329
+ 329 ...(Object.keys(attributes).length > 0 && { attributes }),
330
+ 330 ...(Object.keys(relationshipsData).length > 0 && { relationships: relationshipsData })
331
+ 331 }
332
+ 332 }
333
+ 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
341
+ 341 * @param {Object} deps.context.schemaStructure - Schema for mapping relationships
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 = {
349
+ 349 * data: {
350
+ 350 * type: 'articles',
351
+ 351 * id: '123',
352
+ 352 * attributes: {
353
+ 353 * title: 'My Article',
354
+ 354 * content: 'Content here'
355
+ 355 * }
356
+ 356 * }
357
+ 357 * };
358
+ 358 * const result = transformJsonApiToSimplified(
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 = {
373
+ 373 * title: { type: 'string' },
374
+ 374 * author_id: { type: 'number', belongsTo: 'users', as: 'author' }
375
+ 375 * };
376
+ 376 * const jsonApi = {
377
+ 377 * data: {
378
+ 378 * type: 'articles',
379
+ 379 * id: '456',
380
+ 380 * attributes: { title: 'Article with Author' },
381
+ 381 * relationships: {
382
+ 382 * author: {
383
+ 383 * data: { type: 'users', id: '789' }
384
+ 384 * }
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 = {
399
+ 399 * data: [
400
+ 400 * {
401
+ 401 * type: 'articles',
402
+ 402 * id: '1',
403
+ 403 * attributes: { title: 'First Article' }
404
+ 404 * },
405
+ 405 * {
406
+ 406 * type: 'articles',
407
+ 407 * id: '2',
408
+ 408 * attributes: { title: 'Second Article' }
409
+ 409 * }
410
+ 410 * ],
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: [
418
+ 418 * // { id: '1', title: 'First Article' },
419
+ 419 * // { id: '2', title: 'Second Article' }
420
+ 420 * // ],
421
+ 421 * // meta: { total: 2 },
422
+ 422 * // links: { next: '/articles?page=2' }
423
+ 423 * // }
424
+ 424 *
425
+ 425 * @example
426
+ 426 * // Input: With included resources
427
+ 427 * const jsonApi = {
428
+ 428 * data: {
429
+ 429 * type: 'articles',
430
+ 430 * id: '400',
431
+ 431 * attributes: { title: 'JavaScript Guide' },
432
+ 432 * relationships: {
433
+ 433 * author: {
434
+ 434 * data: { type: 'authors', id: '75' }
435
+ 435 * },
436
+ 436 * chapters: {
437
+ 437 * data: [
438
+ 438 * { type: 'chapters', id: '1001' },
439
+ 439 * { type: 'chapters', id: '1002' }
440
+ 440 * ]
441
+ 441 * }
442
+ 442 * }
443
+ 443 * },
444
+ 444 * included: [
445
+ 445 * {
446
+ 446 * type: 'authors',
447
+ 447 * id: '75',
448
+ 448 * attributes: { name: 'Jane Smith' }
449
+ 449 * },
450
+ 450 * {
451
+ 451 * type: 'chapters',
452
+ 452 * id: '1001',
453
+ 453 * attributes: { number: 1, title: 'Introduction' }
454
+ 454 * },
455
+ 455 * {
456
+ 456 * type: 'chapters',
457
+ 457 * id: '1002',
458
+ 458 * attributes: { number: 2, title: 'Basics' }
459
+ 459 * }
460
+ 460 * ]
461
+ 461 * };
462
+ 462 *
463
+ 463 * // Output: Expands included resources
464
+ 464 * // {
465
+ 465 * // id: '400',
466
+ 466 * // title: 'JavaScript Guide',
467
+ 467 * // author_id: '75',
468
+ 468 * // author: { // Expanded from included
469
+ 469 * // id: '75',
470
+ 470 * // name: 'Jane Smith'
471
+ 471 * // },
472
+ 472 * // chapters: [{ id: '1001' }, { id: '1002' }],
473
+ 473 * // chapters: [ // Expanded array
474
+ 474 * // { id: '1001', number: 1, title: 'Introduction' },
475
+ 475 * // { id: '1002', number: 2, title: 'Basics' }
476
+ 476 * // ]
477
+ 477 * // }
478
+ 478 *
479
+ 479 * @description
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
494
+ 494 * 3. Calls transformSingleJsonApiToSimplified for each
495
+ 495 * 4. Returns simplified structure
496
+ 496 */
497
+ 497 export const transformJsonApiToSimplified = (scope, deps) => {
498
+ 498 // Extract needed values from scope and deps
499
+ 499 const jsonApi = scope.record
500
+ 500 const schema = deps.context.schemaStructure
501
+ 501 const relationships = deps.context.schemaRelationships
502
+ 502 const scopes = deps.context.scopes
503
+ 503
504
+ 504 if (!jsonApi?.data) return jsonApi
505
+ 505
506
+ 506 // Handle array response (QUERY)
507
+ 507 if (Array.isArray(jsonApi.data)) {
508
+ 508 const simplifiedData = jsonApi.data.map(item =>
509
+ 509 transformSingleJsonApiToSimplified(
510
+ 510 { data: item, included: jsonApi.included },
511
+ 511 { context: { schemaStructure: schema, schemaRelationships: relationships, scopes } }
512
+ 512 )
513
+ 513 )
514
+ 514
515
+ 515 // For query results, return object with data, meta, and links
516
+ 516 const result = { data: simplifiedData }
517
+ 517
518
+ 518 // Add meta if present
519
+ 519 if (jsonApi.meta) {
520
+ 520 result.meta = jsonApi.meta
521
+ 521 }
522
+ 522
523
+ 523 // Add links if present
524
+ 524 if (jsonApi.links) {
525
+ 525 result.links = jsonApi.links
526
+ 526 }
527
+ 527
528
+ 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
+ 535 )
536
+ 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
544
+ 544 * @param {Object} deps - Dependencies with context
545
+ 545 * @param {Object} deps.context.schemaStructure - Resource schema
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 = {
553
+ 553 * type: 'comments',
554
+ 554 * id: '100',
555
+ 555 * attributes: {
556
+ 556 * text: 'Great article!',
557
+ 557 * created_at: '2024-01-15'
558
+ 558 * },
559
+ 559 * relationships: {
560
+ 560 * author: {
561
+ 561 * data: { type: 'users', id: '50' }
562
+ 562 * },
563
+ 563 * article: {
564
+ 564 * data: { type: 'articles', id: '200' }
565
+ 565 * }
566
+ 566 * }
567
+ 567 * };
568
+ 568 * const schema = {
569
+ 569 * text: { type: 'string' },
570
+ 570 * created_at: { type: 'datetime' },
571
+ 571 * author_id: { type: 'number', belongsTo: 'users', as: 'author' },
572
+ 572 * article_id: { type: 'number', belongsTo: 'articles', as: 'article' }
573
+ 573 * };
574
+ 574 * const result = transformSingleJsonApiToSimplified(
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',
582
+ 582 * // text: 'Great article!',
583
+ 583 * // created_at: '2024-01-15',
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 = {
591
+ 591 * type: 'articles',
592
+ 592 * id: '300',
593
+ 593 * attributes: { title: 'My Article' },
594
+ 594 * relationships: {
595
+ 595 * tags: {
596
+ 596 * data: [
597
+ 597 * { type: 'tags', id: '1' },
598
+ 598 * { type: 'tags', id: '2' },
599
+ 599 * { type: 'tags', id: '3' }
600
+ 600 * ]
601
+ 601 * }
602
+ 602 * }
603
+ 603 * };
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 = {
618
+ 618 * type: 'reviews',
619
+ 619 * id: '500',
620
+ 620 * attributes: { rating: 5, comment: 'Great!' },
621
+ 621 * relationships: {
622
+ 622 * reviewable: {
623
+ 623 * data: { type: 'authors', id: '75' }
624
+ 624 * }
625
+ 625 * }
626
+ 626 * };
627
+ 627 * const relationships = {
628
+ 628 * reviewable: {
629
+ 629 * belongsToPolymorphic: {
630
+ 630 * types: ['authors', 'publishers'],
631
+ 631 * typeField: 'reviewable_type',
632
+ 632 * idField: 'reviewable_id'
633
+ 633 * }
634
+ 634 * }
635
+ 635 * };
636
+ 636 *
637
+ 637 * // Output: Minimal polymorphic object with type
638
+ 638 * // {
639
+ 639 * // id: '500',
640
+ 640 * // rating: 5,
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 = {
648
+ 648 * type: 'reviews',
649
+ 649 * id: '500',
650
+ 650 * attributes: { rating: 5, comment: 'Great!' },
651
+ 651 * relationships: {
652
+ 652 * reviewable: {
653
+ 653 * data: { type: 'authors', id: '75' }
654
+ 654 * }
655
+ 655 * }
656
+ 656 * };
657
+ 657 * const included = [
658
+ 658 * {
659
+ 659 * type: 'authors',
660
+ 660 * id: '75',
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',
668
+ 668 * // rating: 5,
669
+ 669 * // comment: 'Great!',
670
+ 670 * // reviewable: { // Full object when included
671
+ 671 * // id: '75',
672
+ 672 * // name: 'Jane Smith',
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 = {
680
+ 680 * type: 'books',
681
+ 681 * id: '400',
682
+ 682 * attributes: { title: 'JavaScript Guide' },
683
+ 683 * relationships: {
684
+ 684 * author: {
685
+ 685 * data: { type: 'authors', id: '75' }
686
+ 686 * },
687
+ 687 * chapters: {
688
+ 688 * data: [
689
+ 689 * { type: 'chapters', id: '1001' },
690
+ 690 * { type: 'chapters', id: '1002' }
691
+ 691 * ]
692
+ 692 * }
693
+ 693 * }
694
+ 694 * };
695
+ 695 * const included = [
696
+ 696 * {
697
+ 697 * type: 'authors',
698
+ 698 * id: '75',
699
+ 699 * attributes: { name: 'Jane Smith' },
700
+ 700 * relationships: {
701
+ 701 * company: {
702
+ 702 * data: { type: 'companies', id: '5' }
703
+ 703 * }
704
+ 704 * }
705
+ 705 * },
706
+ 706 * {
707
+ 707 * type: 'chapters',
708
+ 708 * id: '1001',
709
+ 709 * attributes: { number: 1, title: 'Introduction' }
710
+ 710 * },
711
+ 711 * {
712
+ 712 * type: 'chapters',
713
+ 713 * id: '1002',
714
+ 714 * attributes: { number: 2, title: 'Basics' }
715
+ 715 * }
716
+ 716 * ];
717
+ 717 *
718
+ 718 * // Output: Recursively expanded
719
+ 719 * // {
720
+ 720 * // id: '400',
721
+ 721 * // title: 'JavaScript Guide',
722
+ 722 * // author: { // Expanded and transformed
723
+ 723 * // id: '75',
724
+ 724 * // name: 'Jane Smith',
725
+ 725 * // company: { id: '5' } // Author's relationships processed as minimal objects!
726
+ 726 * // },
727
+ 727 * // chapters: [
728
+ 728 * // { id: '1001', number: 1, title: 'Introduction' },
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:
748
+ 748 * - BelongsTo: creates minimal relationship object
749
+ 749 * - Polymorphic: creates minimal relationship object with type
750
+ 750 * - HasMany/ManyToMany: creates array of minimal relationship objects
751
+ 751 * 3. If included data exists:
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
+ 760 const data = scope.data
761
+ 761 const included = scope.included
762
+ 762 const schema = deps.context.schemaStructure
763
+ 763 const relationships = deps.context.schemaRelationships
764
+ 764 const scopes = deps.context.scopes
765
+ 765
766
+ 766 const simplified = {}
767
+ 767
768
+ 768 // Add ID
769
+ 769 if (data.id) {
770
+ 770 simplified.id = data.id
771
+ 771 }
772
+ 772
773
+ 773 // Add attributes
774
+ 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
+ 780 const schemaEntry = Object.entries(schema).find(([_, def]) => def.as === relName)
781
+ 781
782
+ 782 if (schemaEntry) {
783
+ 783 const [fieldName, fieldDef] = schemaEntry
784
+ 784 if (fieldDef.belongsTo && relData.data) {
785
+ 785 // Create minimal relationship object (no more foreign key field)
786
+ 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
+ 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
+ 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
+ 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
+ 807 included.find(inc => inc.type === ref.type && inc.id === ref.id)
808
+ 808
809
+ 809 if (Array.isArray(relData.data)) {
810
+ 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
+ 816 {
817
+ 817 context: {
818
+ 818 schemaStructure: scopes?.[item.type]?.vars?.schemaInfo?.schema || {},
819
+ 819 schemaRelationships: scopes?.[item.type]?.vars?.schemaInfo?.schemaRelationships || {},
820
+ 820 scopes
821
+ 821 }
822
+ 822 }
823
+ 823 )
824
+ 824 // For polymorphic relationships, add _type field to identify the resource type
825
+ 825 if (rel?.belongsToPolymorphic) {
826
+ 826 itemSimplified._type = item.type
827
+ 827 }
828
+ 828 return itemSimplified
829
+ 829 })
830
+ 830 }
831
+ 831 } else {
832
+ 832 const nestedData = findIncluded(relData.data)
833
+ 833 if (nestedData) {
834
+ 834 // Transform the single included record to restore its relationship fields
835
+ 835 const itemSimplified = transformSingleJsonApiToSimplified(
836
+ 836 { data: nestedData, included: null },
837
+ 837 {
838
+ 838 context: {
839
+ 839 schemaStructure: scopes?.[nestedData.type]?.vars?.schemaInfo?.schema || {},
840
+ 840 schemaRelationships: scopes?.[nestedData.type]?.vars?.schemaInfo?.schemaRelationships || {},
841
+ 841 scopes
842
+ 842 }
843
+ 843 }
844
+ 844 )
845
+ 845 // For polymorphic relationships, add _type field to identify the resource type
846
+ 846 if (rel?.belongsToPolymorphic) {
847
+ 847 itemSimplified._type = nestedData.type
848
+ 848 }
849
+ 849 simplified[relName] = itemSimplified
850
+ 850 }
851
+ 851 }
852
+ 852 }
853
+ 853 }
854
+ 854 }
855
+ 855
856
+ 856 return simplified
857
+ 857 }