json-rest-api 1.0.22 → 1.0.23

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 (260) hide show
  1. package/CLAUDE.md +1 -2
  2. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
  3. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
  4. package/docs/API.md +51 -14
  5. package/docs/COMPARISON.md +2 -2
  6. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +28 -1
  7. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
  8. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
  9. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
  10. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
  11. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
  12. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
  13. package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
  14. package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
  15. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +250 -0
  16. package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
  17. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
  18. package/docs/GUIDE/index.md +18 -9
  19. package/docs/QUICKSTART.md +5 -1
  20. package/docs/index.md +1 -1
  21. package/index.js +3 -2
  22. package/package.json +2 -2
  23. package/plugins/core/connectors/express-plugin.js +97 -184
  24. package/plugins/core/connectors/fastify-plugin.js +206 -0
  25. package/plugins/core/connectors/lib/connector-core.js +195 -0
  26. package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
  27. package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
  28. package/plugins/core/lib/dbIntrospection.js +1078 -0
  29. package/plugins/core/lib/dbTablesOperations.js +1104 -160
  30. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -2
  31. package/plugins/core/lib/querying/knex-process-includes.js +3 -4
  32. package/plugins/core/lib/querying/knex-query-helpers.js +17 -71
  33. package/plugins/core/lib/querying/knex-relationship-includes.js +466 -206
  34. package/plugins/core/lib/querying/query-builder-utils.js +19 -0
  35. package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
  36. package/plugins/core/lib/querying/sort-helpers.js +33 -0
  37. package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
  38. package/plugins/core/lib/querying/url-helpers.js +8 -2
  39. package/plugins/core/lib/querying-writing/compile-schemas.js +12 -1
  40. package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -13
  41. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
  42. package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
  43. package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
  44. package/plugins/core/lib/storage/canonical-storage-mapping.js +113 -0
  45. package/plugins/core/lib/storage/storage-adapter.js +79 -2
  46. package/plugins/core/lib/storage/storage-mapping.js +204 -0
  47. package/plugins/core/rest-api-anyapi-knex-plugin.js +355 -228
  48. package/plugins/core/rest-api-autofilter-plugin.js +414 -0
  49. package/plugins/core/rest-api-knex-plugin.js +249 -211
  50. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
  51. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
  52. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
  53. package/plugins/core/rest-api-plugin-methods/common.js +121 -83
  54. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
  55. package/plugins/core/rest-api-plugin-methods/get.js +23 -14
  56. package/plugins/core/rest-api-plugin-methods/patch.js +28 -12
  57. package/plugins/core/rest-api-plugin-methods/post.js +13 -8
  58. package/plugins/core/rest-api-plugin-methods/put.js +45 -21
  59. package/plugins/core/rest-api-plugin-methods/query.js +15 -53
  60. package/plugins/core/rest-api-plugin.js +2 -0
  61. package/plugins/core/rest-api-positioning-plugin.js +1 -1
  62. package/plugins/core/rest-api-query-projections-plugin.js +130 -0
  63. package/plugins/core/socketio-plugin.js +1 -3
  64. package/tests/autofilter.test.js +463 -0
  65. package/tests/custom-id-simplified-post.test.js +75 -0
  66. package/tests/custom-idproperty-relationships.test.js +33 -0
  67. package/tests/db-introspection.test.js +518 -0
  68. package/tests/db-table-operations.test.js +781 -0
  69. package/tests/fastify-plugin.test.js +141 -0
  70. package/tests/field-getters.test.js +1 -2
  71. package/tests/field-setters.test.js +0 -4
  72. package/tests/fixtures/api-configs.js +188 -219
  73. package/tests/helpers/fake-fastify.js +99 -0
  74. package/tests/http-connectors-parity.test.js +235 -0
  75. package/tests/projected-fields.test.js +319 -0
  76. package/tests/relationships.test.js +3 -3
  77. package/tests/searchschema-knex-behavior.test.js +187 -0
  78. package/tests/searchschema-merge.test.js +21 -69
  79. package/tests/storage-mapping.test.js +210 -0
  80. package/audit/00-charter.md +0 -103
  81. package/audit/01-scope-map.md +0 -84
  82. package/audit/02-coverage-matrix.csv +0 -174
  83. package/audit/03-invariants.md +0 -123
  84. package/audit/04-standards-rubric.md +0 -56
  85. package/audit/05-competitor-rubric.md +0 -77
  86. package/audit/06-findings.md +0 -160
  87. package/audit/07-risk-register.csv +0 -10
  88. package/audit/08-remediation-roadmap.md +0 -177
  89. package/audit/09-signoff-check.md +0 -23
  90. package/audit/10-final-synthesis.md +0 -29
  91. package/audit/evidence/code/all-files-json-rest-api.txt +0 -168
  92. package/audit/evidence/code/all-files-json-rest-schema.txt +0 -12
  93. package/audit/evidence/code/boundary-leak-checks.txt +0 -72
  94. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__bulk-operations-plugin.js.txt +0 -480
  95. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__express-plugin.js.txt +0 -532
  96. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__busboy-detector.js.txt +0 -175
  97. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__formidable-detector.js.txt +0 -138
  98. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__request-helpers.js.txt +0 -222
  99. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt +0 -342
  100. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__dbTablesOperations.js.txt +0 -304
  101. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__fractional-positioning.js.txt +0 -188
  102. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__compile-schemas.js.txt +0 -336
  103. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__connectors-query-parser.js.txt +0 -165
  104. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-capabilities.js.txt +0 -265
  105. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-value-normalizers.js.txt +0 -369
  106. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__default-data-helpers.js.txt +0 -72
  107. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__field-utils.js.txt +0 -139
  108. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-constants.js.txt +0 -45
  109. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-field-helpers.js.txt +0 -396
  110. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-json-api-transformers.js.txt +0 -162
  111. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__payload-validators.js.txt +0 -1314
  112. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__schema-helpers.js.txt +0 -488
  113. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__scope-validations.js.txt +0 -484
  114. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__simplified-helpers.js.txt +0 -857
  115. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-cross-table-search.js.txt +0 -565
  116. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-json-api-transformers-querying.js.txt +0 -485
  117. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-pagination-helpers.js.txt +0 -560
  118. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-process-includes.js.txt +0 -167
  119. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers-base.js.txt +0 -109
  120. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers.js.txt +0 -1064
  121. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-relationship-includes.js.txt +0 -1983
  122. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-window-queries.js.txt +0 -328
  123. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__url-helpers.js.txt +0 -119
  124. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__storage__storage-adapter.js.txt +0 -160
  125. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__many-to-many-manipulations.js.txt +0 -300
  126. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__relationship-processor.js.txt +0 -245
  127. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-access.js.txt +0 -459
  128. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-cors-plugin.js.txt +0 -237
  129. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-knex-plugin.js.txt +0 -1135
  130. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-multihome-plugin.js.txt +0 -314
  131. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__compile-resource-schemas.js.txt +0 -7
  132. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-relationship-routes.js.txt +0 -71
  133. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-scope-routes.js.txt +0 -102
  134. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__turn-scope-init-into-vars.js.txt +0 -49
  135. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__validate-include-configurations.js.txt +0 -39
  136. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__add-route.js.txt +0 -23
  137. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__check-permissions.js.txt +0 -13
  138. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__common.js.txt +0 -639
  139. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete-relationship.js.txt +0 -117
  140. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete.js.txt +0 -104
  141. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__enrich-attributes.js.txt +0 -199
  142. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-related.js.txt +0 -353
  143. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-relationship.js.txt +0 -61
  144. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get.js.txt +0 -184
  145. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch-relationship.js.txt +0 -55
  146. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch.js.txt +0 -191
  147. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post-relationship.js.txt +0 -110
  148. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post.js.txt +0 -168
  149. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__put.js.txt +0 -253
  150. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__query.js.txt +0 -207
  151. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__release.js.txt +0 -12
  152. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin.js.txt +0 -165
  153. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-positioning-plugin.js.txt +0 -668
  154. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__socketio-plugin.js.txt +0 -621
  155. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__local-storage.js.txt +0 -218
  156. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__s3-storage.js.txt +0 -124
  157. package/audit/evidence/code/critical-pass1/json-rest-schema__src__CorePlugin.js.txt +0 -269
  158. package/audit/evidence/code/critical-pass1/json-rest-schema__src__Schema.js.txt +0 -242
  159. package/audit/evidence/code/critical-pass1/json-rest-schema__src__index.js.txt +0 -77
  160. package/audit/evidence/code/critical-pass1-risk-keywords.txt +0 -238
  161. package/audit/evidence/code/critical-pass1-summary.csv +0 -67
  162. package/audit/evidence/code/critical-pass2-summary.csv +0 -67
  163. package/audit/evidence/code/early-silent-zones-no-test-token.txt +0 -57
  164. package/audit/evidence/code/error-model-signals.txt +0 -98
  165. package/audit/evidence/code/git-files-json-rest-api.txt +0 -171
  166. package/audit/evidence/code/git-files-json-rest-schema.txt +0 -27
  167. package/audit/evidence/code/hook-flow-signals.txt +0 -282
  168. package/audit/evidence/code/idproperty-signals.txt +0 -157
  169. package/audit/evidence/code/import-cycles-non-anyapi.txt +0 -3
  170. package/audit/evidence/code/import-hotspots-non-anyapi.txt +0 -96
  171. package/audit/evidence/code/imports-json-rest-api.txt +0 -354
  172. package/audit/evidence/code/imports-json-rest-schema.txt +0 -15
  173. package/audit/evidence/code/in-scope-files-json-rest-api.txt +0 -160
  174. package/audit/evidence/code/in-scope-files-json-rest-schema.txt +0 -13
  175. package/audit/evidence/code/non-anyapi-core-files.txt +0 -101
  176. package/audit/evidence/code/permission-signals.txt +0 -193
  177. package/audit/evidence/code/query-include-signals.txt +0 -687
  178. package/audit/evidence/code/transaction-signals.txt +0 -28
  179. package/audit/evidence/code/validation-signals.txt +0 -650
  180. package/audit/evidence/code/write-path-signals.txt +0 -61
  181. package/audit/evidence/docs/docs-claims-signals.txt +0 -1075
  182. package/audit/evidence/docs/schema-docs-claims-signals.txt +0 -179
  183. package/audit/evidence/external/corpus-freeze.md +0 -21
  184. package/audit/evidence/external/npm-competitor-metadata.txt +0 -70
  185. package/audit/evidence/external/npm-download-signals.txt +0 -23
  186. package/audit/evidence/external/npm-jsonapi-server.txt +0 -8
  187. package/audit/evidence/external/npm-modern-jsonapi-metadata.txt +0 -38
  188. package/audit/evidence/external/readme-fortune-signals.txt +0 -26
  189. package/audit/evidence/external/readme-fortune.md +0 -165
  190. package/audit/evidence/external/readme-json-api-serializer-signals.txt +0 -80
  191. package/audit/evidence/external/readme-json-api-serializer.md +0 -690
  192. package/audit/evidence/external/readme-jsonapi-serde-server-signals.txt +0 -0
  193. package/audit/evidence/external/readme-jsonapi-serde-server.md +0 -22
  194. package/audit/evidence/external/readme-jsonapi-serializer-signals.txt +0 -63
  195. package/audit/evidence/external/readme-jsonapi-serializer.md +0 -315
  196. package/audit/evidence/external/readme-jsonapi-server-signals.txt +0 -19
  197. package/audit/evidence/external/readme-jsonapi-server.md +0 -104
  198. package/audit/evidence/external/readme-nestjs-json-api-signals.txt +0 -4
  199. package/audit/evidence/external/readme-nestjs-json-api.md +0 -94
  200. package/audit/evidence/external/readme-yaser-json-api-nestjs-signals.txt +0 -47
  201. package/audit/evidence/external/readme-yaser-json-api-nestjs.md +0 -447
  202. package/audit/evidence/external/standard-jsonapi-snippets.txt +0 -84
  203. package/audit/evidence/external/standard-rfc-snippets.txt +0 -168
  204. package/audit/evidence/external/standards-sources.txt +0 -6
  205. package/audit/evidence/runtime/baseline-commits.txt +0 -2
  206. package/audit/evidence/runtime/db-adapter-assumptions.txt +0 -3
  207. package/audit/evidence/runtime/environment.txt +0 -4
  208. package/audit/evidence/runtime/invariant-runtime-mapping.md +0 -38
  209. package/audit/evidence/runtime/probe-patch-relationship-error.exitcode +0 -1
  210. package/audit/evidence/runtime/probe-patch-relationship-error.log +0 -76
  211. package/audit/evidence/runtime/probe-patch-relationship-error.mjs +0 -67
  212. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.exitcode +0 -1
  213. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.log +0 -62
  214. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.mjs +0 -47
  215. package/audit/evidence/runtime/test-exitcodes.txt +0 -4
  216. package/audit/evidence/runtime/test-summary.md +0 -107
  217. package/audit/evidence/runtime/trace-anchors.txt +0 -61
  218. package/audit/evidence/tests/access-multitenancy-test-signals.txt +0 -125
  219. package/audit/evidence/tests/failing-tests-summary.txt +0 -25
  220. package/audit/evidence/tests/npm-test-json-rest-api.exitcode +0 -1
  221. package/audit/evidence/tests/npm-test-json-rest-api.log +0 -5885
  222. package/audit/evidence/tests/npm-test-json-rest-schema.exitcode +0 -1
  223. package/audit/evidence/tests/npm-test-json-rest-schema.log +0 -356
  224. package/audit/evidence/tests/query-include-test-signals.txt +0 -948
  225. package/audit/evidence/tests/relationship-test-signals.txt +0 -223
  226. package/audit/evidence/tests/skipped-tests.txt +0 -10
  227. package/audit/evidence/tests/test-suite-list-non-anyapi.txt +0 -30
  228. package/audit/evidence/tests/test-suite-list.txt +0 -32
  229. package/audit/subsystems/docs-drift.md +0 -49
  230. package/audit/subsystems/pass2-cross-review.md +0 -131
  231. package/audit/subsystems/plugins-access.md +0 -51
  232. package/audit/subsystems/plugins-bulk.md +0 -50
  233. package/audit/subsystems/plugins-cors.md +0 -48
  234. package/audit/subsystems/plugins-file.md +0 -53
  235. package/audit/subsystems/plugins-multihome.md +0 -50
  236. package/audit/subsystems/plugins-positioning.md +0 -54
  237. package/audit/subsystems/plugins-socketio.md +0 -50
  238. package/audit/subsystems/query-engine.md +0 -61
  239. package/audit/subsystems/relationships.md +0 -55
  240. package/audit/subsystems/rest-orchestration.md +0 -66
  241. package/audit/subsystems/schema-core.md +0 -64
  242. package/audit/subsystems/storage-knex.md +0 -57
  243. package/audit/subsystems/tests.md +0 -48
  244. package/audit/subsystems/transport-express.md +0 -55
  245. package/audit/traces/trace-read-get.md +0 -22
  246. package/audit/traces/trace-read-query.md +0 -25
  247. package/audit/traces/trace-relationship-endpoints.md +0 -31
  248. package/audit/traces/trace-write-delete.md +0 -17
  249. package/audit/traces/trace-write-patch.md +0 -26
  250. package/audit/traces/trace-write-post.md +0 -22
  251. package/audit/traces/trace-write-put.md +0 -23
  252. package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
  253. package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
  254. package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
  255. package/plugins/core/rest-api-access.js +0 -459
  256. package/plugins/core/rest-api-multihome-plugin.js +0 -314
  257. package/tests/multihome.test.js +0 -553
  258. package/tests/multihome.test.js.bak +0 -550
  259. package/tests/ownership-optout.test.js +0 -76
  260. package/tests/rest-api-access.test.js +0 -206
@@ -1,353 +0,0 @@
1
- 1 import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
2
- 2 import { findRelationshipDefinition } from './common.js'
3
- 3 import { buildRelationshipUrl } from '../lib/querying/url-helpers.js'
4
- 4
5
- 5 /**
6
- 6 * GET RELATED
7
- 7 * Retrieves the actual related resources (full data)
8
- 8 * GET /api/articles/1/comments
9
- 9 *
10
- 10 * @param {string} id - The ID of the parent resource
11
- 11 * @param {string} relationshipName - The name of the relationship
12
- 12 * @param {object} queryParams - Standard query parameters
13
- 13 * @returns {Promise<object>} Related resources with full data
14
- 14 */
15
- 15 export default async function getRelatedMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api }) {
16
- 16 context.method = 'getRelated'
17
- 17 context.id = params.id
18
- 18 context.relationshipName = params.relationshipName
19
- 19 context.queryParams = params.queryParams || {}
20
- 20 context.schemaInfo = scopes[scopeName].vars.schemaInfo
21
- 21 context.transaction = params.transaction
22
- 22 context.db = context.transaction || api.knex.instance
23
- 23
24
- 24 // Validate the relationship exists
25
- 25 const relDef = findRelationshipDefinition(context.schemaInfo, context.relationshipName)
26
- 26
27
- 27 if (!relDef) {
28
- 28 throw new RestApiResourceError(
29
- 29 `Relationship '${context.relationshipName}' not found on resource '${scopeName}'`,
30
- 30 { subtype: 'relationship_not_found' }
31
- 31 )
32
- 32 }
33
- 33
34
- 34 // Determine target type based on relationship type
35
- 35 let targetType
36
- 36 if (relDef.type === 'hasMany' || relDef.type === 'hasOne') {
37
- 37 targetType = relDef.target
38
- 38 } else if (relDef.type === 'manyToMany') {
39
- 39 targetType = context.relationshipName // For manyToMany, use the relationship name
40
- 40 } else if (relDef.belongsTo) {
41
- 41 targetType = relDef.belongsTo // belongsTo still in schema
42
- 42 }
43
- 43
44
- 44 if (!targetType || !scopes[targetType]) {
45
- 45 throw new RestApiResourceError(
46
- 46 `Related resource type '${targetType}' not found`,
47
- 47 { subtype: 'related_type_not_found' }
48
- 48 )
49
- 49 }
50
- 50
51
- 51 // Check permissions
52
- 52 await runHooks('checkPermissions')
53
- 53 await runHooks('checkPermissionsGetRelated')
54
- 54
55
- 55 // Verify parent exists
56
- 56 const exists = await helpers.dataExists({
57
- 57 scopeName,
58
- 58 context: { db: context.db, id: context.id, schemaInfo: context.schemaInfo }
59
- 59 })
60
- 60
61
- 61 if (!exists) {
62
- 62 throw new RestApiResourceError('Resource not found', { subtype: 'not_found' })
63
- 63 }
64
- 64
65
- 65 // Handle to-one relationships (belongsTo and hasOne)
66
- 66 // For example: GET /api/books/1/country or GET /api/books/1/publisher
67
- 67 if (relDef.belongsTo || relDef.type === 'hasOne') {
68
- 68 // OPTIMIZATION: Detect if we actually need to make two API calls
69
- 69 //
70
- 70 // The naive approach always makes 2 calls:
71
- 71 // 1. Get parent with relationship included (fetches FULL related record)
72
- 72 // 2. Extract just the ID and fetch the same record again with queryParams
73
- 73 //
74
- 74 // This optimization checks if there are queryParams that would affect
75
- 75 // the related resource. If not, we can use the data from the first call.
76
- 76 const hasRelevantQueryParams = context.queryParams && (
77
- 77 // Check for includes on the related resource (e.g., ?include=some.nested.relation)
78
- 78 context.queryParams.include?.length > 0 ||
79
- 79 // Check for field selection on the related resource (e.g., ?fields[countries]=name,code)
80
- 80 context.queryParams.fields?.[targetType] ||
81
- 81 // Note: Filters and sorting don't make sense for a single to-one relationship
82
- 82 // so we don't check for them
83
- 83 false
84
- 84 )
85
- 85
86
- 86 if (hasRelevantQueryParams) {
87
- 87 // CASE 1: Has queryParams that affect the related resource
88
- 88 // We need to make two calls to properly apply the queryParams
89
- 89
90
- 90 // First call: Get parent with minimal data (just need the related ID)
91
- 91 const parent = await scope.get({
92
- 92 id: context.id,
93
- 93 queryParams: {
94
- 94 include: [context.relationshipName],
95
- 95 fields: { [scopeName]: vars.idProperty || 'id' } // Only fetch parent ID to minimize data
96
- 96 },
97
- 97 transaction: context.transaction,
98
- 98 simplified: false,
99
- 99 isTransport: params.isTransport
100
- 100 })
101
- 101
102
- 102 const relatedId = parent.data.relationships?.[context.relationshipName]?.data?.id
103
- 103 if (!relatedId) {
104
- 104 return {
105
- 105 links: { self: buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false) },
106
- 106 data: null
107
- 107 }
108
- 108 }
109
- 109
110
- 110 // Second call: Get the related resource with all queryParams applied
111
- 111 const related = await api.resources[targetType].get({
112
- 112 id: relatedId,
113
- 113 queryParams: context.queryParams,
114
- 114 transaction: context.transaction,
115
- 115 simplified: false,
116
- 116 isTransport: params.isTransport
117
- 117 })
118
- 118
119
- 119 return {
120
- 120 links: { self: buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false) },
121
- 121 data: related.data,
122
- 122 included: related.included
123
- 123 }
124
- 124 } else {
125
- 125 // CASE 2: No queryParams that affect the related resource
126
- 126 // We can get everything in one call and extract from included
127
- 127
128
- 128 // Single call: Get parent with full related resource included
129
- 129 const parent = await scope.get({
130
- 130 id: context.id,
131
- 131 queryParams: {
132
- 132 include: [context.relationshipName],
133
- 133 fields: context.queryParams.fields // Respect any field selections for the parent
134
- 134 },
135
- 135 transaction: context.transaction,
136
- 136 simplified: false,
137
- 137 isTransport: params.isTransport
138
- 138 })
139
- 139
140
- 140 // Extract the related resource from the parent's relationships
141
- 141 const relatedId = parent.data.relationships?.[context.relationshipName]?.data?.id
142
- 142 if (!relatedId) {
143
- 143 return {
144
- 144 links: { self: buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false) },
145
- 145 data: null
146
- 146 }
147
- 147 }
148
- 148
149
- 149 // Find the full related resource in the included array
150
- 150 // The include system already fetched it for us!
151
- 151 const relatedResource = parent.included?.find(
152
- 152 r => r.type === targetType && r.id === relatedId
153
- 153 )
154
- 154
155
- 155 return {
156
- 156 links: { self: buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false) },
157
- 157 data: relatedResource || null
158
- 158 }
159
- 159 }
160
- 160 }
161
- 161
162
- 162 // Handle simple hasMany (one-to-many, NOT many-to-many)
163
- 163 if (relDef.type === 'hasMany') {
164
- 164 // Check if this is a polymorphic relationship using 'via'
165
- 165 if (relDef.via) {
166
- 166 // Polymorphic hasMany relationship
167
- 167 // Example: publishers hasMany reviews via reviewable
168
- 168 const targetRelationships = scopes[targetType].vars.schemaInfo.schemaRelationships
169
- 169 const viaRel = targetRelationships?.[relDef.via]
170
- 170
171
- 171 if (!viaRel?.belongsToPolymorphic) {
172
- 172 throw new RestApiResourceError(
173
- 173 `Via relationship '${relDef.via}' not found or not polymorphic in '${targetType}'`,
174
- 174 { subtype: 'invalid_via_relationship' }
175
- 175 )
176
- 176 }
177
- 177
178
- 178 const { typeField, idField } = viaRel.belongsToPolymorphic
179
- 179
180
- 180 // Add polymorphic filters
181
- 181 const filters = {
182
- 182 ...context.queryParams.filters,
183
- 183 [typeField]: scopeName,
184
- 184 [idField]: context.id
185
- 185 }
186
- 186
187
- 187 const result = await api.resources[targetType].query({
188
- 188 queryParams: { ...context.queryParams, filters },
189
- 189 transaction: context.transaction,
190
- 190 simplified: false,
191
- 191 isTransport: params.isTransport
192
- 192 })
193
- 193
194
- 194 if (!result.links) {
195
- 195 result.links = {}
196
- 196 }
197
- 197 result.links.self = buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false)
198
- 198 return result
199
- 199 } else {
200
- 200 // Regular hasMany with foreignKey
201
- 201 // Need to find the relationship name in the target resource that points back to this resource
202
- 202 const targetSchema = scopes[targetType].vars.schemaInfo.schemaStructure
203
- 203 let relationshipFilterName = null
204
- 204
205
- 205 // Find the field in target schema that has the foreign key and get its relationship name
206
- 206 for (const [fieldName, fieldDef] of Object.entries(targetSchema)) {
207
- 207 if (fieldName === relDef.foreignKey && fieldDef.belongsTo === scopeName && fieldDef.as) {
208
- 208 relationshipFilterName = fieldDef.as
209
- 209 break
210
- 210 }
211
- 211 }
212
- 212
213
- 213 // Fall back to foreign key if no relationship name found (shouldn't happen with proper schema)
214
- 214 const filterKey = relationshipFilterName || relDef.foreignKey
215
- 215
216
- 216 const filters = {
217
- 217 ...context.queryParams.filters,
218
- 218 [filterKey]: context.id
219
- 219 }
220
- 220
221
- 221 const result = await api.resources[targetType].query({
222
- 222 queryParams: { ...context.queryParams, filters },
223
- 223 transaction: context.transaction,
224
- 224 simplified: false,
225
- 225 isTransport: params.isTransport
226
- 226 })
227
- 227
228
- 228 if (!result.links) {
229
- 229 result.links = {}
230
- 230 }
231
- 231 result.links.self = buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false)
232
- 232 return result
233
- 233 }
234
- 234 }
235
- 235
236
- 236 // Handle many-to-many relationships
237
- 237 // For example: GET /api/authors/1/books (where authors and books are linked via book_authors)
238
- 238 if (relDef?.through) {
239
- 239 if (api.anyapi?.links?.listMany) {
240
- 240 const identifiers = await api.anyapi.links.listMany({
241
- 241 context,
242
- 242 scopeName,
243
- 243 relName: context.relationshipName,
244
- 244 })
245
- 245
246
- 246 const results = []
247
- 247 for (const identifier of identifiers) {
248
- 248 const related = await api.resources[targetType].get({
249
- 249 id: identifier.id,
250
- 250 queryParams: context.queryParams,
251
- 251 transaction: context.transaction,
252
- 252 simplified: false,
253
- 253 isTransport: params.isTransport,
254
- 254 })
255
- 255 if (related?.data) {
256
- 256 results.push(related.data)
257
- 257 }
258
- 258 }
259
- 259
260
- 260 return {
261
- 261 links: {
262
- 262 self: buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false)
263
- 263 },
264
- 264 data: results,
265
- 265 }
266
- 266 }
267
- 267
268
- 268 // Legacy fallback
269
- 269 const pivotResource = relDef.through
270
- 270 const foreignKey = relDef.foreignKey
271
- 271 const otherKey = relDef.otherKey
272
- 272 if (!foreignKey || !otherKey) {
273
- 273 throw new Error('Missing foreignKey or otherKey in many-to-many relationship')
274
- 274 }
275
- 275 const pivotScope = scopes[pivotResource]
276
- 276 if (!pivotScope) {
277
- 277 throw new RestApiResourceError(
278
- 278 `Pivot table resource '${pivotResource}' not found`,
279
- 279 { subtype: 'pivot_table_not_found' }
280
- 280 )
281
- 281 }
282
- 282 const pivotSchema = pivotScope.vars.schemaInfo.schemaStructure
283
- 283 let parentRelationshipName = null
284
- 284 for (const [fieldName, fieldDef] of Object.entries(pivotSchema)) {
285
- 285 if (fieldName === foreignKey && fieldDef.belongsTo === scopeName && fieldDef.as) {
286
- 286 parentRelationshipName = fieldDef.as
287
- 287 break
288
- 288 }
289
- 289 }
290
- 290 const filterKey = parentRelationshipName || foreignKey
291
- 291 const pivotFilters = {
292
- 292 [filterKey]: context.id
293
- 293 }
294
- 294 const pivotResult = await api.resources[pivotResource].query({
295
- 295 queryParams: {
296
- 296 filters: pivotFilters,
297
- 297 include: [context.relationshipName],
298
- 298 fields: context.queryParams.fields,
299
- 299 sort: context.queryParams.sort,
300
- 300 page: context.queryParams.page
301
- 301 },
302
- 302 transaction: context.transaction,
303
- 303 simplified: false,
304
- 304 isTransport: params.isTransport
305
- 305 })
306
- 306 let includedResources = pivotResult.included?.filter((r) => r.type === targetType) || []
307
- 307
308
- 308 if (includedResources.length === 0) {
309
- 309 const pivotData = pivotResult.data || []
310
- 310 const relatedIds = [...new Set(pivotData
311
- 311 .map((item) => {
312
- 312 const attrId = item?.attributes?.[otherKey]
313
- 313 if (attrId !== null && attrId !== undefined) {
314
- 314 return String(attrId)
315
- 315 }
316
- 316 const relationships = item?.relationships || {}
317
- 317 for (const rel of Object.values(relationships)) {
318
- 318 const data = rel?.data
319
- 319 if (data?.type === targetType && data?.id != null) {
320
- 320 return String(data.id)
321
- 321 }
322
- 322 }
323
- 323 return null
324
- 324 })
325
- 325 .filter((id) => id !== null)
326
- 326 )]
327
- 327
328
- 328 if (relatedIds.length > 0) {
329
- 329 includedResources = []
330
- 330 for (const relatedId of relatedIds) {
331
- 331 const related = await api.resources[targetType].get({
332
- 332 id: relatedId,
333
- 333 queryParams: context.queryParams,
334
- 334 transaction: context.transaction,
335
- 335 simplified: false,
336
- 336 isTransport: params.isTransport,
337
- 337 })
338
- 338 if (related?.data) {
339
- 339 includedResources.push(related.data)
340
- 340 }
341
- 341 }
342
- 342 }
343
- 343 }
344
- 344
345
- 345 return {
346
- 346 links: {
347
- 347 self: buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false)
348
- 348 },
349
- 349 data: includedResources,
350
- 350 meta: pivotResult.meta
351
- 351 }
352
- 352 }
353
- 353 }
@@ -1,61 +0,0 @@
1
- 1 import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
2
- 2 import { findRelationshipDefinition } from './common.js'
3
- 3 import { buildRelationshipUrl } from '../lib/querying/url-helpers.js'
4
- 4
5
- 5 /**
6
- 6 * GET RELATIONSHIP
7
- 7 * Retrieves relationship linkage data (just resource identifiers)
8
- 8 * GET /api/articles/1/relationships/author
9
- 9 *
10
- 10 * @param {string} id - The ID of the resource
11
- 11 * @param {string} relationshipName - The name of the relationship
12
- 12 * @returns {Promise<object>} Relationship linkage with links
13
- 13 */
14
- 14 export default async function getRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api }) {
15
- 15 context.method = 'getRelationship'
16
- 16 context.id = params.id
17
- 17 context.relationshipName = params.relationshipName
18
- 18 context.schemaInfo = scopes[scopeName].vars.schemaInfo
19
- 19
20
- 20 // Validate the relationship exists
21
- 21 const relDef = findRelationshipDefinition(context.schemaInfo, context.relationshipName)
22
- 22
23
- 23 if (!relDef) {
24
- 24 throw new RestApiResourceError(
25
- 25 `Relationship '${context.relationshipName}' not found on resource '${scopeName}'`,
26
- 26 { subtype: 'relationship_not_found' }
27
- 27 )
28
- 28 }
29
- 29
30
- 30 // Check permissions
31
- 31 await runHooks('checkPermissions')
32
- 32 await runHooks('checkPermissionsGetRelationship')
33
- 33
34
- 34 // Reuse existing get method with minimal fields
35
- 35 const fullRecord = await scope.get({
36
- 36 id: context.id,
37
- 37 queryParams: {
38
- 38 include: [context.relationshipName],
39
- 39 fields: { [scopeName]: vars.idProperty || 'id' }
40
- 40 },
41
- 41 transaction: context.transaction,
42
- 42 simplified: false,
43
- 43 isTransport: params.isTransport
44
- 44 })
45
- 45
46
- 46 if (!fullRecord || !fullRecord.data) {
47
- 47 throw new RestApiResourceError('Resource not found', { subtype: 'not_found' })
48
- 48 }
49
- 49
50
- 50 // Extract just the relationship data
51
- 51 const relationshipData = fullRecord.data.relationships?.[context.relationshipName]
52
- 52
53
- 53 // Build response with links
54
- 54 return {
55
- 55 links: {
56
- 56 self: buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, true),
57
- 57 related: buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false)
58
- 58 },
59
- 59 data: relationshipData?.data || (relDef.type === 'hasMany' || relDef.type === 'manyToMany' ? [] : null)
60
- 60 }
61
- 61 };
@@ -1,184 +0,0 @@
1
- 1 import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
2
- 2 import { validateGetPayload } from '../lib/querying-writing/payload-validators.js'
3
- 3 import { normalizeRecordAttributes } from '../lib/querying-writing/database-value-normalizers.js'
4
- 4 import { getRequestedComputedFields } from '../lib/querying-writing/knex-field-helpers.js'
5
- 5 import { transformJsonApiToSimplified } from '../lib/querying-writing/simplified-helpers.js'
6
- 6 import { cascadeConfig } from './common.js'
7
- 7
8
- 8 /**
9
- 9 * GET
10
- 10 * Retrieves a single resource by its type and ID.
11
- 11 * @param {(string|number)} id - The unique ID of the resource to fetch.
12
- 12 * @param {object} [queryParams={}] - Optional. An object to customize the query.
13
- 13 * @param {string} [queryParams.include] - A comma-separated string of relationship paths to sideload (e.g., "authors,publisher").
14
- 14 * @param {object} [queryParams.fields] - An object to request only specific fields (sparse fieldsets). Keys are resource types, values are comma-separated field names.
15
- 15 * @returns {Promise<object>} A Promise that resolves to the JSON:API response document.
16
- 16 */
17
- 17 export default async function getMethod ({
18
- 18 params,
19
- 19 context,
20
- 20 vars,
21
- 21 helpers,
22
- 22 scope,
23
- 23 scopes,
24
- 24 runHooks,
25
- 25 apiOptions,
26
- 26 pluginOptions,
27
- 27 scopeOptions,
28
- 28 scopeName,
29
- 29 api
30
- 30 }) {
31
- 31 context.method = 'get'
32
- 32
33
- 33 // Determine which simplified setting to use based on transport
34
- 34 const isTransport = params.isTransport === true
35
- 35
36
- 36 // Use vars which automatically cascade from scope to global
37
- 37 const defaultSimplified = isTransport ? vars.simplifiedTransport : vars.simplifiedApi
38
- 38
39
- 39 // Get simplified setting - from params only (per-call override) or use default
40
- 40 context.simplified = params.simplified !== undefined ? params.simplified : defaultSimplified
41
- 41
42
- 42 // Assign common context properties
43
- 43 context.schemaInfo = scopes[scopeName].vars.schemaInfo
44
- 44 context.queryParams = params.queryParams || {}
45
- 45
46
- 46 // These only make sense as parameter per query
47
- 47 context.queryParams.fields = cascadeConfig('fields', [context.queryParams], {})
48
- 48 context.queryParams.include = cascadeConfig('include', [context.queryParams], [])
49
- 49
50
- 50 context.transaction = params.transaction
51
- 51 context.db = context.transaction || api.knex.instance
52
- 52
53
- 53 context.scopeName = scopeName
54
- 54
55
- 55 // These are just shortcuts used in this function and will be returned
56
- 56 const schemaStructure = context.schemaInfo.schemaInstance.structure
57
- 57 const schemaRelationships = context.schemaInfo.schemaRelationships
58
- 58
59
- 59 context.id = params.id
60
- 60
61
- 61 // Validate GET request to ensure required parameters are present and properly formatted.
62
- 62 // This checks that 'id' parameter exists and is not empty (you can't GET without an ID),
63
- 63 // validates 'include' contains valid relationship names (not arbitrary fields), and ensures
64
- 64 // 'fields' for sparse fieldsets follow the format fields[type]=comma,separated,list.
65
- 65 // Example: validates id: '123' exists, include: ['author', 'tags'] are real relationships.
66
- 66 validateGetPayload({ id: context.id, queryParams: context.queryParams }, vars.includeDepthLimit)
67
- 67
68
- 68 // Fetch minimal record for authorization checks
69
- 69 const minimalRecord = await helpers.dataGetMinimal({
70
- 70 scopeName,
71
- 71 context,
72
- 72 runHooks
73
- 73 })
74
- 74
75
- 75 if (!minimalRecord) {
76
- 76 throw new RestApiResourceError(
77
- 77 'Resource not found',
78
- 78 {
79
- 79 subtype: 'not_found',
80
- 80 resourceType: scopeName,
81
- 81 resourceId: context.id
82
- 82 }
83
- 83 )
84
- 84 }
85
- 85
86
- 86 context.minimalRecord = minimalRecord
87
- 87
88
- 88 // Centralised checkPermissions function
89
- 89 await scope.checkPermissions({
90
- 90 method: 'get',
91
- 91 originalContext: context,
92
- 92 })
93
- 93
94
- 94 await runHooks('beforeData')
95
- 95 await runHooks('beforeDataGet')
96
- 96
97
- 97 context.record = await helpers.dataGet({
98
- 98 scopeName,
99
- 99 context,
100
- 100 runHooks
101
- 101 })
102
- 102
103
- 103 // Check if record was found - storage layer returns null/undefined for non-existent records.
104
- 104 // This generates a proper 404 error with JSON:API error format instead of returning empty data.
105
- 105 if (!context.record || !context.record.data) {
106
- 106 throw new RestApiResourceError(
107
- 107 'Resource not found',
108
- 108 {
109
- 109 subtype: 'not_found',
110
- 110 resourceType: scopeName,
111
- 111 resourceId: context.id
112
- 112 }
113
- 113 )
114
- 114 }
115
- 115
116
- 116 // Normalize database values (e.g., convert 1/0 to true/false for booleans)
117
- 117 context.record = normalizeRecordAttributes(context.record, scopes)
118
- 118
119
- 119 await runHooks('checkDataPermissions')
120
- 120 await runHooks('checkDataPermissionsGet')
121
- 121
122
- 122 // Make a backup
123
- 123 context.originalRecord = structuredClone(context.record)
124
- 124
125
- 125 // This will enhance record, which is the WHOLE JSON:API record
126
- 126 await runHooks('enrichRecord')
127
- 127
128
- 128 // Get computed field information for main resource
129
- 129 const computedFields = scope.vars.schemaInfo?.computed || {}
130
- 130 const requestedFields = context.queryParams.fields?.[scopeName]
131
- 131 const requestedComputedFields = getRequestedComputedFields(scopeName, requestedFields, computedFields)
132
- 132
133
- 133 // Enrich attributes for the main resource
134
- 134 // Pass computedDependencies from context (set by dataGet in Knex plugin)
135
- 135 // This tells enrichAttributes which fields to remove after computation
136
- 136 context.record.data.attributes = await scope.enrichAttributes({
137
- 137 attributes: context.record.data.attributes,
138
- 138 parentContext: context,
139
- 139 requestedComputedFields,
140
- 140 isMainResource: true,
141
- 141 computedDependencies: context.computedDependencies // Fields to remove if not requested
142
- 142 })
143
- 143
144
- 144 for (const entry of (context.record.included || [])) {
145
- 145 const entryScope = scopes[entry.type]
146
- 146 const entryComputed = entryScope.vars.schemaInfo?.computed || {}
147
- 147 const entryRequestedFields = context.queryParams.fields?.[entry.type]
148
- 148 const entryRequestedComputed = getRequestedComputedFields(
149
- 149 entry.type,
150
- 150 entryRequestedFields,
151
- 151 entryComputed
152
- 152 )
153
- 153
154
- 154 entry.attributes = await entryScope.enrichAttributes({
155
- 155 attributes: entry.attributes,
156
- 156 parentContext: context,
157
- 157 requestedComputedFields: entryRequestedComputed,
158
- 158 isMainResource: false,
159
- 159 computedDependencies: entry.__$jsonrestapi_computed_deps$__
160
- 160 })
161
- 161 }
162
- 162
163
- 163 await runHooks('enrichRecordWithRelationships')
164
- 164
165
- 165 // The called hooks should NOT change context.record
166
- 166 await runHooks('finish')
167
- 167 await runHooks('finishGet')
168
- 168
169
- 169 // Get schema info for transformation
170
- 170 context.schemaInfo = scopes[scopeName].vars.schemaInfo
171
- 171
172
- 172 // Transform output if in simplified mode
173
- 173 if (context.simplified) {
174
- 174 // Convert JSON:API response back to simplified format
175
- 175 // Example: {data: {type: 'posts', id: '1', attributes: {title: 'My Post'}, relationships: {author: {data: {type: 'users', id: '123'}}}}}
176
- 176 // becomes: {id: '1', title: 'My Post', author_id: '123'} - flattens structure and restores foreign keys
177
- 177 return transformJsonApiToSimplified(
178
- 178 { record: context.record },
179
- 179 { context: { schemaStructure, schemaRelationships, scopes } }
180
- 180 )
181
- 181 }
182
- 182
183
- 183 return context.record
184
- 184 }
@@ -1,55 +0,0 @@
1
- 1 /**
2
- 2 * PATCH RELATIONSHIP
3
- 3 * Completely replaces a relationship
4
- 4 * PATCH /api/articles/1/relationships/author
5
- 5 *
6
- 6 * @param {string} id - The ID of the resource
7
- 7 * @param {string} relationshipName - The name of the relationship
8
- 8 * @param {object|array} relationshipData - New relationship data
9
- 9 * @returns {Promise<void>} 204 No Content
10
- 10 */
11
- 11 export default async function patchRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api }) {
12
- 12 context.method = 'patchRelationship'
13
- 13 context.id = params.id
14
- 14 context.relationshipName = params.relationshipName
15
- 15
16
- 16 // Transaction handling
17
- 17 context.transaction = params.transaction ||
18
- 18 (helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null)
19
- 19 context.shouldCommit = !params.transaction && !!context.transaction
20
- 20 context.db = context.transaction || api.knex.instance
21
- 21
22
- 22 try {
23
- 23 // Check permissions
24
- 24 await runHooks('checkPermissions')
25
- 25 await runHooks('checkPermissionsPatchRelationship')
26
- 26
27
- 27 // Reuse existing patch with relationship data
28
- 28 await scope.patch({
29
- 29 id: context.id,
30
- 30 inputRecord: {
31
- 31 data: {
32
- 32 type: scopeName,
33
- 33 id: context.id,
34
- 34 relationships: {
35
- 35 [params.relationshipName]: { data: params.relationshipData }
36
- 36 }
37
- 37 }
38
- 38 },
39
- 39 transaction: context.transaction,
40
- 40 simplified: false,
41
- 41 isTransport: params.isTransport
42
- 42 })
43
- 43
44
- 44 await runHooks('finish')
45
- 45 await runHooks('finishPatchRelationship')
46
- 46
47
- 47 if (context.shouldCommit) {
48
- 48 await context.transaction.commit()
49
- 49 }
50
- 50
51
- 51 // 204 No Content
52
- 52 } catch (error) {
53
- 53 await handleWriteMethodError(error, context, 'PATCH_RELATIONSHIP', scopeName, log)
54
- 54 }
55
- 55 }