json-rest-api 1.0.22 → 1.0.24

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 (290) hide show
  1. package/CLAUDE.md +1 -2
  2. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
  3. package/README.md +22 -1
  4. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
  5. package/docs/API.md +196 -38
  6. package/docs/COMPARISON.md +2 -2
  7. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +111 -5
  8. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
  9. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
  10. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
  11. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
  12. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
  13. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
  14. package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
  15. package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
  16. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +370 -0
  17. package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
  18. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
  19. package/docs/GUIDE/index.md +18 -9
  20. package/docs/QUICKSTART.md +5 -1
  21. package/docs/index.md +1 -1
  22. package/index.js +3 -2
  23. package/lib/enhanced-logger.js +1 -1
  24. package/package.json +6 -2
  25. package/plugins/core/bulk-operations-plugin.js +26 -34
  26. package/plugins/core/connectors/express-plugin.js +97 -190
  27. package/plugins/core/connectors/fastify-plugin.js +206 -0
  28. package/plugins/core/connectors/lib/busboy-detector.js +4 -4
  29. package/plugins/core/connectors/lib/connector-core.js +195 -0
  30. package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
  31. package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
  32. package/plugins/core/lib/anyapi/schema-utils.js +43 -4
  33. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +4 -1
  34. package/plugins/core/lib/dbIntrospection.js +1078 -0
  35. package/plugins/core/lib/dbTablesOperations.js +1111 -160
  36. package/plugins/core/lib/querying/knex-cross-table-search.js +0 -7
  37. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -3
  38. package/plugins/core/lib/querying/knex-process-includes.js +3 -4
  39. package/plugins/core/lib/querying/knex-query-helpers.js +21 -76
  40. package/plugins/core/lib/querying/knex-relationship-includes.js +468 -215
  41. package/plugins/core/lib/querying/query-builder-utils.js +19 -0
  42. package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
  43. package/plugins/core/lib/querying/sort-helpers.js +33 -0
  44. package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
  45. package/plugins/core/lib/querying/url-helpers.js +8 -2
  46. package/plugins/core/lib/querying-writing/compile-schemas.js +20 -5
  47. package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -14
  48. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
  49. package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
  50. package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
  51. package/plugins/core/lib/querying-writing/resource-id-normalization.js +131 -0
  52. package/plugins/core/lib/querying-writing/schema-helpers.js +0 -2
  53. package/plugins/core/lib/querying-writing/simplified-helpers.js +4 -11
  54. package/plugins/core/lib/storage/canonical-storage-mapping.js +133 -0
  55. package/plugins/core/lib/storage/storage-adapter.js +82 -4
  56. package/plugins/core/lib/storage/storage-mapping.js +274 -0
  57. package/plugins/core/lib/writing/many-to-many-manipulations.js +4 -2
  58. package/plugins/core/rest-api-anyapi-knex-plugin.js +531 -312
  59. package/plugins/core/rest-api-autofilter-plugin.js +414 -0
  60. package/plugins/core/rest-api-knex-plugin.js +271 -226
  61. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
  62. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
  63. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +1 -0
  64. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
  65. package/plugins/core/rest-api-plugin-methods/common.js +148 -92
  66. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +11 -2
  67. package/plugins/core/rest-api-plugin-methods/delete.js +6 -1
  68. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
  69. package/plugins/core/rest-api-plugin-methods/get-related.js +7 -2
  70. package/plugins/core/rest-api-plugin-methods/get-relationship.js +7 -2
  71. package/plugins/core/rest-api-plugin-methods/get.js +29 -14
  72. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +10 -3
  73. package/plugins/core/rest-api-plugin-methods/patch.js +56 -12
  74. package/plugins/core/rest-api-plugin-methods/post-relationship.js +11 -2
  75. package/plugins/core/rest-api-plugin-methods/post.js +21 -8
  76. package/plugins/core/rest-api-plugin-methods/put.js +73 -21
  77. package/plugins/core/rest-api-plugin-methods/query.js +15 -53
  78. package/plugins/core/rest-api-plugin-methods/release.js +0 -2
  79. package/plugins/core/rest-api-plugin.js +6 -0
  80. package/plugins/core/rest-api-positioning-plugin.js +2 -5
  81. package/plugins/core/rest-api-query-projections-plugin.js +130 -0
  82. package/plugins/core/socketio-plugin.js +1 -4
  83. package/plugins/storage/local-storage.js +1 -1
  84. package/plugins/storage/s3-storage.js +5 -2
  85. package/quickTest.js +1 -0
  86. package/scripts/run-docs.js +80 -84
  87. package/test-debug.js +1 -1
  88. package/tests/README.md +7 -0
  89. package/tests/anyapi-basic.test.js +3 -0
  90. package/tests/autofilter.test.js +463 -0
  91. package/tests/custom-id-mysql-returning-fallback.test.js +128 -0
  92. package/tests/custom-id-simplified-post.test.js +75 -0
  93. package/tests/custom-idproperty-relationships.test.js +33 -0
  94. package/tests/db-introspection.test.js +521 -0
  95. package/tests/db-table-operations.test.js +786 -0
  96. package/tests/fastify-plugin.test.js +156 -0
  97. package/tests/field-getters.test.js +1 -2
  98. package/tests/field-setters.test.js +0 -4
  99. package/tests/fixtures/api-configs.js +278 -176
  100. package/tests/helpers/fake-fastify.js +99 -0
  101. package/tests/http-connectors-parity.test.js +235 -0
  102. package/tests/id-normalization.test.js +444 -0
  103. package/tests/logical-id-write-validation.test.js +151 -0
  104. package/tests/pagination-cursor-multifield.test.js +11 -11
  105. package/tests/projected-fields.test.js +319 -0
  106. package/tests/relationships.test.js +3 -3
  107. package/tests/searchschema-knex-behavior.test.js +187 -0
  108. package/tests/searchschema-merge.test.js +21 -69
  109. package/tests/storage-mapping.test.js +259 -0
  110. package/audit/00-charter.md +0 -103
  111. package/audit/01-scope-map.md +0 -84
  112. package/audit/02-coverage-matrix.csv +0 -174
  113. package/audit/03-invariants.md +0 -123
  114. package/audit/04-standards-rubric.md +0 -56
  115. package/audit/05-competitor-rubric.md +0 -77
  116. package/audit/06-findings.md +0 -160
  117. package/audit/07-risk-register.csv +0 -10
  118. package/audit/08-remediation-roadmap.md +0 -177
  119. package/audit/09-signoff-check.md +0 -23
  120. package/audit/10-final-synthesis.md +0 -29
  121. package/audit/evidence/code/all-files-json-rest-api.txt +0 -168
  122. package/audit/evidence/code/all-files-json-rest-schema.txt +0 -12
  123. package/audit/evidence/code/boundary-leak-checks.txt +0 -72
  124. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__bulk-operations-plugin.js.txt +0 -480
  125. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__express-plugin.js.txt +0 -532
  126. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__busboy-detector.js.txt +0 -175
  127. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__formidable-detector.js.txt +0 -138
  128. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__request-helpers.js.txt +0 -222
  129. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt +0 -342
  130. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__dbTablesOperations.js.txt +0 -304
  131. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__fractional-positioning.js.txt +0 -188
  132. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__compile-schemas.js.txt +0 -336
  133. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__connectors-query-parser.js.txt +0 -165
  134. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-capabilities.js.txt +0 -265
  135. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-value-normalizers.js.txt +0 -369
  136. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__default-data-helpers.js.txt +0 -72
  137. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__field-utils.js.txt +0 -139
  138. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-constants.js.txt +0 -45
  139. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-field-helpers.js.txt +0 -396
  140. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-json-api-transformers.js.txt +0 -162
  141. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__payload-validators.js.txt +0 -1314
  142. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__schema-helpers.js.txt +0 -488
  143. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__scope-validations.js.txt +0 -484
  144. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__simplified-helpers.js.txt +0 -857
  145. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-cross-table-search.js.txt +0 -565
  146. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-json-api-transformers-querying.js.txt +0 -485
  147. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-pagination-helpers.js.txt +0 -560
  148. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-process-includes.js.txt +0 -167
  149. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers-base.js.txt +0 -109
  150. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers.js.txt +0 -1064
  151. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-relationship-includes.js.txt +0 -1983
  152. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-window-queries.js.txt +0 -328
  153. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__url-helpers.js.txt +0 -119
  154. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__storage__storage-adapter.js.txt +0 -160
  155. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__many-to-many-manipulations.js.txt +0 -300
  156. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__relationship-processor.js.txt +0 -245
  157. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-access.js.txt +0 -459
  158. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-cors-plugin.js.txt +0 -237
  159. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-knex-plugin.js.txt +0 -1135
  160. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-multihome-plugin.js.txt +0 -314
  161. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__compile-resource-schemas.js.txt +0 -7
  162. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-relationship-routes.js.txt +0 -71
  163. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-scope-routes.js.txt +0 -102
  164. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__turn-scope-init-into-vars.js.txt +0 -49
  165. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__validate-include-configurations.js.txt +0 -39
  166. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__add-route.js.txt +0 -23
  167. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__check-permissions.js.txt +0 -13
  168. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__common.js.txt +0 -639
  169. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete-relationship.js.txt +0 -117
  170. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete.js.txt +0 -104
  171. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__enrich-attributes.js.txt +0 -199
  172. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-related.js.txt +0 -353
  173. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-relationship.js.txt +0 -61
  174. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get.js.txt +0 -184
  175. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch-relationship.js.txt +0 -55
  176. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch.js.txt +0 -191
  177. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post-relationship.js.txt +0 -110
  178. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post.js.txt +0 -168
  179. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__put.js.txt +0 -253
  180. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__query.js.txt +0 -207
  181. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__release.js.txt +0 -12
  182. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin.js.txt +0 -165
  183. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-positioning-plugin.js.txt +0 -668
  184. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__socketio-plugin.js.txt +0 -621
  185. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__local-storage.js.txt +0 -218
  186. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__s3-storage.js.txt +0 -124
  187. package/audit/evidence/code/critical-pass1/json-rest-schema__src__CorePlugin.js.txt +0 -269
  188. package/audit/evidence/code/critical-pass1/json-rest-schema__src__Schema.js.txt +0 -242
  189. package/audit/evidence/code/critical-pass1/json-rest-schema__src__index.js.txt +0 -77
  190. package/audit/evidence/code/critical-pass1-risk-keywords.txt +0 -238
  191. package/audit/evidence/code/critical-pass1-summary.csv +0 -67
  192. package/audit/evidence/code/critical-pass2-summary.csv +0 -67
  193. package/audit/evidence/code/early-silent-zones-no-test-token.txt +0 -57
  194. package/audit/evidence/code/error-model-signals.txt +0 -98
  195. package/audit/evidence/code/git-files-json-rest-api.txt +0 -171
  196. package/audit/evidence/code/git-files-json-rest-schema.txt +0 -27
  197. package/audit/evidence/code/hook-flow-signals.txt +0 -282
  198. package/audit/evidence/code/idproperty-signals.txt +0 -157
  199. package/audit/evidence/code/import-cycles-non-anyapi.txt +0 -3
  200. package/audit/evidence/code/import-hotspots-non-anyapi.txt +0 -96
  201. package/audit/evidence/code/imports-json-rest-api.txt +0 -354
  202. package/audit/evidence/code/imports-json-rest-schema.txt +0 -15
  203. package/audit/evidence/code/in-scope-files-json-rest-api.txt +0 -160
  204. package/audit/evidence/code/in-scope-files-json-rest-schema.txt +0 -13
  205. package/audit/evidence/code/non-anyapi-core-files.txt +0 -101
  206. package/audit/evidence/code/permission-signals.txt +0 -193
  207. package/audit/evidence/code/query-include-signals.txt +0 -687
  208. package/audit/evidence/code/transaction-signals.txt +0 -28
  209. package/audit/evidence/code/validation-signals.txt +0 -650
  210. package/audit/evidence/code/write-path-signals.txt +0 -61
  211. package/audit/evidence/docs/docs-claims-signals.txt +0 -1075
  212. package/audit/evidence/docs/schema-docs-claims-signals.txt +0 -179
  213. package/audit/evidence/external/corpus-freeze.md +0 -21
  214. package/audit/evidence/external/npm-competitor-metadata.txt +0 -70
  215. package/audit/evidence/external/npm-download-signals.txt +0 -23
  216. package/audit/evidence/external/npm-jsonapi-server.txt +0 -8
  217. package/audit/evidence/external/npm-modern-jsonapi-metadata.txt +0 -38
  218. package/audit/evidence/external/readme-fortune-signals.txt +0 -26
  219. package/audit/evidence/external/readme-fortune.md +0 -165
  220. package/audit/evidence/external/readme-json-api-serializer-signals.txt +0 -80
  221. package/audit/evidence/external/readme-json-api-serializer.md +0 -690
  222. package/audit/evidence/external/readme-jsonapi-serde-server-signals.txt +0 -0
  223. package/audit/evidence/external/readme-jsonapi-serde-server.md +0 -22
  224. package/audit/evidence/external/readme-jsonapi-serializer-signals.txt +0 -63
  225. package/audit/evidence/external/readme-jsonapi-serializer.md +0 -315
  226. package/audit/evidence/external/readme-jsonapi-server-signals.txt +0 -19
  227. package/audit/evidence/external/readme-jsonapi-server.md +0 -104
  228. package/audit/evidence/external/readme-nestjs-json-api-signals.txt +0 -4
  229. package/audit/evidence/external/readme-nestjs-json-api.md +0 -94
  230. package/audit/evidence/external/readme-yaser-json-api-nestjs-signals.txt +0 -47
  231. package/audit/evidence/external/readme-yaser-json-api-nestjs.md +0 -447
  232. package/audit/evidence/external/standard-jsonapi-snippets.txt +0 -84
  233. package/audit/evidence/external/standard-rfc-snippets.txt +0 -168
  234. package/audit/evidence/external/standards-sources.txt +0 -6
  235. package/audit/evidence/runtime/baseline-commits.txt +0 -2
  236. package/audit/evidence/runtime/db-adapter-assumptions.txt +0 -3
  237. package/audit/evidence/runtime/environment.txt +0 -4
  238. package/audit/evidence/runtime/invariant-runtime-mapping.md +0 -38
  239. package/audit/evidence/runtime/probe-patch-relationship-error.exitcode +0 -1
  240. package/audit/evidence/runtime/probe-patch-relationship-error.log +0 -76
  241. package/audit/evidence/runtime/probe-patch-relationship-error.mjs +0 -67
  242. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.exitcode +0 -1
  243. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.log +0 -62
  244. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.mjs +0 -47
  245. package/audit/evidence/runtime/test-exitcodes.txt +0 -4
  246. package/audit/evidence/runtime/test-summary.md +0 -107
  247. package/audit/evidence/runtime/trace-anchors.txt +0 -61
  248. package/audit/evidence/tests/access-multitenancy-test-signals.txt +0 -125
  249. package/audit/evidence/tests/failing-tests-summary.txt +0 -25
  250. package/audit/evidence/tests/npm-test-json-rest-api.exitcode +0 -1
  251. package/audit/evidence/tests/npm-test-json-rest-api.log +0 -5885
  252. package/audit/evidence/tests/npm-test-json-rest-schema.exitcode +0 -1
  253. package/audit/evidence/tests/npm-test-json-rest-schema.log +0 -356
  254. package/audit/evidence/tests/query-include-test-signals.txt +0 -948
  255. package/audit/evidence/tests/relationship-test-signals.txt +0 -223
  256. package/audit/evidence/tests/skipped-tests.txt +0 -10
  257. package/audit/evidence/tests/test-suite-list-non-anyapi.txt +0 -30
  258. package/audit/evidence/tests/test-suite-list.txt +0 -32
  259. package/audit/subsystems/docs-drift.md +0 -49
  260. package/audit/subsystems/pass2-cross-review.md +0 -131
  261. package/audit/subsystems/plugins-access.md +0 -51
  262. package/audit/subsystems/plugins-bulk.md +0 -50
  263. package/audit/subsystems/plugins-cors.md +0 -48
  264. package/audit/subsystems/plugins-file.md +0 -53
  265. package/audit/subsystems/plugins-multihome.md +0 -50
  266. package/audit/subsystems/plugins-positioning.md +0 -54
  267. package/audit/subsystems/plugins-socketio.md +0 -50
  268. package/audit/subsystems/query-engine.md +0 -61
  269. package/audit/subsystems/relationships.md +0 -55
  270. package/audit/subsystems/rest-orchestration.md +0 -66
  271. package/audit/subsystems/schema-core.md +0 -64
  272. package/audit/subsystems/storage-knex.md +0 -57
  273. package/audit/subsystems/tests.md +0 -48
  274. package/audit/subsystems/transport-express.md +0 -55
  275. package/audit/traces/trace-read-get.md +0 -22
  276. package/audit/traces/trace-read-query.md +0 -25
  277. package/audit/traces/trace-relationship-endpoints.md +0 -31
  278. package/audit/traces/trace-write-delete.md +0 -17
  279. package/audit/traces/trace-write-patch.md +0 -26
  280. package/audit/traces/trace-write-post.md +0 -22
  281. package/audit/traces/trace-write-put.md +0 -23
  282. package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
  283. package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
  284. package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
  285. package/plugins/core/rest-api-access.js +0 -459
  286. package/plugins/core/rest-api-multihome-plugin.js +0 -314
  287. package/tests/multihome.test.js +0 -553
  288. package/tests/multihome.test.js.bak +0 -550
  289. package/tests/ownership-optout.test.js +0 -76
  290. package/tests/rest-api-access.test.js +0 -206
@@ -1,639 +0,0 @@
1
- 1 import {
2
- 2 RestApiValidationError,
3
- 3 RestApiPayloadError
4
- 4 } from '../../../lib/rest-api-errors.js'
5
- 5 import { transformSimplifiedToJsonApi } from '../lib/querying-writing/simplified-helpers.js'
6
- 6 import { createEnhancedLogger } from '../../../lib/enhanced-logger.js'
7
- 7
8
- 8 /**
9
- 9 * Gets an enhanced logger instance with full error details and stack traces
10
- 10 * @param {Object} log - The base logger instance
11
- 11 * @returns {Object} Enhanced logger instance
12
- 12 */
13
- 13 export const getEnhancedLogger = (log) => {
14
- 14 return createEnhancedLogger(log, {
15
- 15 logFullErrors: true,
16
- 16 includeStack: true
17
- 17 })
18
- 18 }
19
- 19
20
- 20 /**
21
- 21 * Cascades configuration values through multiple sources with fallback to default
22
- 22 * @param {string} settingName - The setting name to look for
23
- 23 * @param {Array} sources - Array of objects to search through
24
- 24 * @param {*} defaultValue - Default value if not found
25
- 25 * @returns {*} The first defined value found or the default
26
- 26 */
27
- 27 export const cascadeConfig = (settingName, sources, defaultValue) =>
28
- 28 sources.find(source => source?.[settingName] !== undefined)?.[settingName] ?? defaultValue
29
- 29
30
- 30 /**
31
- 31 * Normalizes return record settings to valid values
32
- 32 * @param {string|boolean} value - The value to normalize
33
- 33 * @returns {string} Normalized value: 'no', 'minimal', or 'full'
34
- 34 */
35
- 35 export function normalizeReturnValue (value) {
36
- 36 if (['no', 'minimal', 'full'].includes(value)) return value
37
- 37 return 'no' // default
38
- 38 }
39
- 39
40
- 40 /**
41
- 41 * Sets up common request context for REST API methods
42
- 42 * Handles simplified mode, transaction setup, and initial validation
43
- 43 *
44
- 44 * @param {Object} params - The method parameters
45
- 45 * @param {Object} context - The request context
46
- 46 * @param {Object} vars - Plugin variables
47
- 47 * @param {Object} scopes - All available scopes
48
- 48 * @param {Object} scopeOptions - Scope-specific options
49
- 49 * @param {string} scopeName - The name of the current scope
50
- 50 * @param {Object} api - The API instance
51
- 51 * @param {Object} helpers - Helper functions
52
- 52 * @returns {Object} An object containing schema-related shortcuts
53
- 53 */
54
- 54 export async function setupCommonRequest ({ params, context, vars, scopes, scopeOptions, scopeName, api, helpers }) {
55
- 55 // Determine which simplified setting to use based on transport
56
- 56 const isTransport = params.isTransport === true
57
- 57
58
- 58 // Use vars which automatically cascade from scope to global
59
- 59 const defaultSimplified = isTransport ? vars.simplifiedTransport : vars.simplifiedApi
60
- 60
61
- 61 // Get simplified setting - from params only (per-call override) or use default
62
- 62 context.simplified = params.simplified !== undefined ? params.simplified : defaultSimplified
63
- 63
64
- 64 // Special case: if no inputRecord provided, force simplified mode
65
- 65 if (!params.inputRecord && context.simplified === false) {
66
- 66 context.simplified = true
67
- 67 }
68
- 68
69
- 69 // Params is totally bypassed in simplified mode
70
- 70 if (context.simplified) {
71
- 71 if (params.inputRecord) {
72
- 72 context.inputRecord = params.inputRecord
73
- 73 context.params = params
74
- 74 } else {
75
- 75 context.inputRecord = params
76
- 76 // Preserve returnFullRecord if specified in params
77
- 77 context.params = params.returnFullRecord ? { returnFullRecord: params.returnFullRecord } : {}
78
- 78 }
79
- 79 } else {
80
- 80 context.inputRecord = params.inputRecord
81
- 81 context.params = params
82
- 82 }
83
- 83
84
- 84 // Assign common context properties
85
- 85 context.schemaInfo = scopes[scopeName].vars.schemaInfo
86
- 86
87
- 87 // Use vars which automatically cascade from scope to global
88
- 88 const defaultReturnFullRecord = isTransport ? vars.returnRecordTransport : vars.returnRecordApi
89
- 89
90
- 90 // Get return record setting - from params only (per-call override) or use default
91
- 91 const returnFullRecordRaw = context.params.returnFullRecord !== undefined
92
- 92 ? context.params.returnFullRecord
93
- 93 : defaultReturnFullRecord
94
- 94
95
- 95 // Normalize return record setting to always be an object with method keys
96
- 96 if (typeof returnFullRecordRaw === 'object' && returnFullRecordRaw !== null) {
97
- 97 // It's already an object, normalize the values
98
- 98 context.returnRecordSetting = {
99
- 99 post: normalizeReturnValue(returnFullRecordRaw.post),
100
- 100 put: normalizeReturnValue(returnFullRecordRaw.put),
101
- 101 patch: normalizeReturnValue(returnFullRecordRaw.patch)
102
- 102 }
103
- 103 } else {
104
- 104 // It's a single value (string or boolean), apply to all methods
105
- 105 const normalized = normalizeReturnValue(returnFullRecordRaw)
106
- 106 context.returnRecordSetting = {
107
- 107 post: normalized,
108
- 108 put: normalized,
109
- 109 patch: normalized
110
- 110 }
111
- 111 }
112
- 112
113
- 113 // Helper function to normalize return values (same as above)
114
- 114 function normalizeReturnValue (value) {
115
- 115 if (['no', 'minimal', 'full'].includes(value)) return value
116
- 116 return 'no' // default
117
- 117 }
118
- 118
119
- 119 // These only make sense as parameter per query, not in vars etc.
120
- 120 context.queryParams = params.queryParams || {}
121
- 121 context.queryParams.fields = cascadeConfig('fields', [context.queryParams], {})
122
- 122 context.queryParams.include = cascadeConfig('include', [context.queryParams], [])
123
- 123
124
- 124 context.scopeName = scopeName
125
- 125
126
- 126 // Transaction handling
127
- 127 context.transaction = params.transaction ||
128
- 128 (helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null)
129
- 129 context.shouldCommit = !params.transaction && !!context.transaction
130
- 130 context.db = context.transaction || api.knex.instance
131
- 131
132
- 132 // These are just shortcuts used in this function and will be returned
133
- 133 const schema = context.schemaInfo.schemaInstance
134
- 134 const schemaStructure = context.schemaInfo.schemaInstance.structure
135
- 135 const schemaRelationships = context.schemaInfo.schemaRelationships
136
- 136
137
- 137 // Transform input if in simplified mode
138
- 138 if (context.simplified) {
139
- 139 context.inputRecord = transformSimplifiedToJsonApi(
140
- 140 { inputRecord: context.inputRecord },
141
- 141 { context: { scopeName, schemaStructure, schemaRelationships } }
142
- 142 )
143
- 143 } else {
144
- 144 // Strict mode: validate no belongsTo fields in attributes
145
- 145 if (context.inputRecord?.data?.attributes) {
146
- 146 for (const [key, fieldDef] of Object.entries(schemaStructure)) {
147
- 147 if (fieldDef.belongsTo && key in context.inputRecord.data.attributes) {
148
- 148 throw new RestApiValidationError(
149
- 149 `Field '${key}' is a foreign key and must be set via relationships, not attributes`,
150
- 150 { fields: [`data.attributes.${key}`] }
151
- 151 )
152
- 152 }
153
- 153 }
154
- 154 }
155
- 155 }
156
- 156
157
- 157 if (context.inputRecord.data.type !== scopeName) {
158
- 158 throw new RestApiValidationError(
159
- 159 `Resource type mismatch. Expected '${scopeName}' but got '${context.inputRecord.data.type}'`,
160
- 160 {
161
- 161 fields: ['data.type'],
162
- 162 violations: [{
163
- 163 field: 'data.type',
164
- 164 rule: 'resource_type_match',
165
- 165 message: `Resource type must be '${scopeName}'`
166
- 166 }]
167
- 167 }
168
- 168 )
169
- 169 }
170
- 170
171
- 171 // Remove included validation since JSON:API doesn't support it
172
- 172 if (context.inputRecord.included) {
173
- 173 throw new RestApiPayloadError(
174
- 174 context.method + ' requests cannot include an "included" array. JSON:API does not support creating multiple resources in a single request.',
175
- 175 { path: 'included', expected: 'undefined', received: 'array' }
176
- 176 )
177
- 177 }
178
- 178
179
- 179 // If both URL path ID and request body ID are provided, they must match
180
- 180 // Convert both to strings for comparison since databases may return numeric IDs
181
- 181 if (context.id && context.inputRecord.data.id && String(context.id) !== String(context.inputRecord.data.id)) {
182
- 182 throw new RestApiValidationError(
183
- 183 `ID mismatch. URL path ID '${context.id}' does not match request body ID '${context.inputRecord.data.id}'`,
184
- 184 {
185
- 185 fields: ['data.id'],
186
- 186 violations: [{
187
- 187 field: 'data.id',
188
- 188 rule: 'id_consistency',
189
- 189 message: 'Request body ID must match URL path ID when both are provided'
190
- 190 }]
191
- 191 }
192
- 192 )
193
- 193 }
194
- 194
195
- 195 // Return key schema-related objects for direct use in the main methods
196
- 196 return { schema, schemaStructure, schemaRelationships }
197
- 197 }
198
- 198
199
- 199 /**
200
- 200 * Handles error cleanup and logging for write methods (POST, PUT, PATCH)
201
- 201 *
202
- 202 * @param {Error} error - The error that was caught
203
- 203 * @param {Object} context - The request context
204
- 204 * @param {string} method - The HTTP method name (POST, PUT, PATCH)
205
- 205 * @param {string} scopeName - The name of the resource scope
206
- 206 * @param {Object} log - The logger instance
207
- 207 * @param {Function} runHooks - Function to run hooks
208
- 208 * @throws {Error} Re-throws the original error after cleanup
209
- 209 */
210
- 210 export const handleWriteMethodError = async (error, context, method, scopeName, log, runHooks) => {
211
- 211 // Rollback transaction if we created it
212
- 212 if (context.shouldCommit) {
213
- 213 await context.transaction.rollback()
214
- 214 await runHooks('afterRollback')
215
- 215 }
216
- 216
217
- 217 // Create enhanced logger for error logging
218
- 218 const enhancedLog = getEnhancedLogger(log)
219
- 219
220
- 220 // Log the full error details
221
- 221 enhancedLog.logError(`Error in ${method} method`, error, {
222
- 222 scopeName,
223
- 223 method: method.toLowerCase(),
224
- 224 inputRecord: context.inputRecord
225
- 225 })
226
- 226
227
- 227 throw error
228
- 228 }
229
- 229
230
- 230 /**
231
- 231 * Validates that a pivot resource exists for many-to-many relationships
232
- 232 *
233
- 233 * @param {Object} scopes - All available scopes/resources
234
- 234 * @param {Object} relDef - The relationship definition
235
- 235 * @param {string} relName - The relationship name
236
- 236 * @throws {RestApiValidationError} If the pivot resource doesn't exist
237
- 237 */
238
- 238 export const validatePivotResource = (scopes, relDef, relName) => {
239
- 239 if (!scopes[relDef.through]) {
240
- 240 throw new RestApiValidationError(
241
- 241 `Pivot resource '${relDef.through}' not found for relationship '${relName}'`,
242
- 242 {
243
- 243 fields: [`relationships.${relName}`],
244
- 244 violations: [{
245
- 245 field: `relationships.${relName}`,
246
- 246 rule: 'missing_pivot_resource',
247
- 247 message: `Pivot resource '${relDef.through}' must be defined`
248
- 248 }]
249
- 249 }
250
- 250 )
251
- 251 }
252
- 252 }
253
- 253
254
- 254 /**
255
- 255 * Gets the appropriate hook suffix based on HTTP method
256
- 256 *
257
- 257 * @param {string} method - The HTTP method (e.g., 'post', 'get')
258
- 258 * @returns {string} The capitalized method name for hook naming
259
- 259 */
260
- 260 export const getMethodHookSuffix = (method) => {
261
- 261 return method.charAt(0).toUpperCase() + method.slice(1)
262
- 262 }
263
- 263
264
- 264 /**
265
- 265 * Validates resource attributes before write operations
266
- 266 *
267
- 267 * @param {Object} params - Validation parameters
268
- 268 * @param {Object} params.context - The request context
269
- 269 * @param {Object} params.schema - The resource schema
270
- 270 * @param {Object} params.belongsToUpdates - BelongsTo relationship updates
271
- 271 * @param {Function} params.runHooks - Function to run hooks
272
- 272 * @param {boolean} params.isPartialValidation - Whether this is partial validation (for PATCH)
273
- 273 * @throws {RestApiValidationError} If validation fails
274
- 274 */
275
- 275 export const validateResourceAttributesBeforeWrite = async ({
276
- 276 context,
277
- 277 schema,
278
- 278 belongsToUpdates,
279
- 279 runHooks,
280
- 280 isPartialValidation = false
281
- 281 }) => {
282
- 282 // Dynamically get the method suffix
283
- 283 const methodSpecificHookSuffix = getMethodHookSuffix(context.method)
284
- 284
285
- 285 await runHooks('beforeSchemaValidate')
286
- 286 await runHooks(`beforeSchemaValidate${methodSpecificHookSuffix}`)
287
- 287
288
- 288 // Store original input attributes before validation adds defaults (primarily for POST)
289
- 289 // Only if it's not already set and the current method is POST
290
- 290 if (!context.originalInputAttributes && context.method === 'post') {
291
- 291 context.originalInputAttributes = { ...(context.inputRecord.data.attributes || {}) }
292
- 292 }
293
- 293
294
- 294 // Merge belongsTo updates with attributes for validation
295
- 295 const attributesToValidate = {
296
- 296 ...context.inputRecord.data.attributes,
297
- 297 ...belongsToUpdates
298
- 298 }
299
- 299
300
- 300 // Extract computed fields and foreign key fields before validation
301
- 301 const computedFields = {}
302
- 302 const foreignKeyFields = {}
303
- 303 const schemaStructure = context.schemaInfo.schemaStructure || {}
304
- 304 Object.entries(attributesToValidate).forEach(([key, value]) => {
305
- 305 const fieldDef = schemaStructure[key]
306
- 306 // Computed fields should be stripped from input
307
- 307 if (fieldDef && fieldDef.computed === true && value !== undefined) {
308
- 308 computedFields[key] = value
309
- 309 }
310
- 310 // Foreign key fields (belongsTo with 'as' property) should NOT be in attributes
311
- 311 // They should only come through relationships or belongsToUpdates
312
- 312 // Check if key exists in belongsToUpdates (not just truthy value)
313
- 313 if (fieldDef && fieldDef.belongsTo && fieldDef.as && value !== undefined && !(key in belongsToUpdates)) {
314
- 314 foreignKeyFields[key] = value
315
- 315 }
316
- 316 })
317
- 317
318
- 318 // Warn if computed fields were sent in the input
319
- 319 if (Object.keys(computedFields).length > 0) {
320
- 320 const fieldNames = Object.keys(computedFields).join(', ')
321
- 321 console.warn(`Computed fields [${fieldNames}] were sent in input for resource '${context.scopeName}' but will be ignored as they are output-only`)
322
- 322 }
323
- 323
324
- 324 // Reject if foreign key fields were sent directly in attributes
325
- 325 if (Object.keys(foreignKeyFields).length > 0) {
326
- 326 const violations = Object.keys(foreignKeyFields).map(field => {
327
- 327 const fieldDef = schemaStructure[field]
328
- 328 return {
329
- 329 field: `data.attributes.${field}`,
330
- 330 rule: 'foreign_key_in_attributes',
331
- 331 message: `Foreign key field '${field}' should not be in attributes. Use 'data.relationships.${fieldDef.as}' instead.`
332
- 332 }
333
- 333 })
334
- 334
335
- 335 throw new RestApiValidationError(
336
- 336 'Foreign key fields cannot be set directly in attributes',
337
- 337 {
338
- 338 fields: violations.map(v => v.field),
339
- 339 violations
340
- 340 }
341
- 341 )
342
- 342 }
343
- 343
344
- 344 // Filter out computed fields and foreign key fields from validation
345
- 345 // Virtual fields MUST go through validation
346
- 346 const attributesForValidation = Object.entries(attributesToValidate)
347
- 347 .filter(([key, _]) => {
348
- 348 const fieldDef = schemaStructure[key]
349
- 349 // Exclude computed fields
350
- 350 if (fieldDef && fieldDef.computed === true) {
351
- 351 return false
352
- 352 }
353
- 353 // Exclude foreign key fields that weren't provided via belongsToUpdates
354
- 354 if (fieldDef && fieldDef.belongsTo && fieldDef.as && !belongsToUpdates[key]) {
355
- 355 return false
356
- 356 }
357
- 357 return true
358
- 358 })
359
- 359 .reduce((acc, [key, value]) => {
360
- 360 acc[key] = value
361
- 361 return acc
362
- 362 }, {})
363
- 363
364
- 364 const validationOptions = isPartialValidation ? { onlyObjectValues: true } : {}
365
- 365
366
- 366 const { validatedObject, errors } = await schema.validate(attributesForValidation, validationOptions)
367
- 367
368
- 368 if (Object.keys(errors).length > 0) {
369
- 369 // --- START OF MODIFICATION ---
370
- 370 const schemaStructure = context.schemaInfo.schemaInstance.structure // Get the schema structure for lookup
371
- 371
372
- 372 const violations = Object.entries(errors).map(([field, error]) => {
373
- 373 let fieldPath = `data.attributes.${field}` // Default path for attributes
374
- 374
375
- 375 // Check if this field is a foreign key that has an 'as' alias
376
- 376 const fieldDef = schemaStructure[field]
377
- 377 if (fieldDef && fieldDef.belongsTo && fieldDef.as) {
378
- 378 // If it's a belongsTo field with an alias, rewrite the path to the relationship alias
379
- 379 fieldPath = `data.relationships.${fieldDef.as}.data.id`
380
- 380 }
381
- 381 // For many-to-many relationships, the original `transformSimplifiedToJsonApi`
382
- 382 // already puts them under `relationships.relName.data`, so `field` here
383
- 383 // would already be the relationship name, not a foreign key.
384
- 384 // However, if a validation error somehow slips through for a pivot table field
385
- 385 // that doesn't have an 'as' alias but is a foreign key, you might need
386
- 386 // more sophisticated mapping. For now, this covers belongsTo.
387
- 387
388
- 388 return {
389
- 389 field: fieldPath,
390
- 390 rule: error.code || 'invalid_value',
391
- 391 message: error.message
392
- 392 }
393
- 393 })
394
- 394 // --- END OF MODIFICATION ---
395
- 395
396
- 396 throw new RestApiValidationError(
397
- 397 'Schema validation failed for resource attributes',
398
- 398 {
399
- 399 fields: violations.map(v => v.field), // Use the potentially rewritten fields
400
- 400 violations
401
- 401 }
402
- 402 )
403
- 403 }
404
- 404
405
- 405 // Update attributes with validated values
406
- 406 // Virtual fields have now been validated and cast properly
407
- 407 context.inputRecord.data.attributes = validatedObject
408
- 408
409
- 409 await runHooks(`afterSchemaValidate${methodSpecificHookSuffix}`)
410
- 410 await runHooks('afterSchemaValidate')
411
- 411 }
412
- 412
413
- 413 /**
414
- 414 * Validates that the user has access to all resources referenced in relationships
415
- 415 *
416
- 416 * @param {object} context - The context object containing authentication info
417
- 417 * @param {object} inputRecord - The input record containing relationships to validate
418
- 418 * @param {object} helpers - Data helpers including dataGetMinimal
419
- 419 * @param {function} runHooks - Function to run hooks
420
- 420 * @param {object} api - API instance to access resources
421
- 421 * @throws {Error} If user doesn't have access to any related resource
422
- 422 */
423
- 423 export const validateRelationshipAccess = async (context, inputRecord, helpers, runHooks, api) => {
424
- 424 if (!inputRecord?.data?.relationships) return
425
- 425
426
- 426 for (const [relName, relData] of Object.entries(inputRecord.data.relationships)) {
427
- 427 if (!relData?.data) continue
428
- 428
429
- 429 // Handle both single and array relationships
430
- 430 const relatedItems = Array.isArray(relData.data) ? relData.data : [relData.data]
431
- 431
432
- 432 for (const item of relatedItems) {
433
- 433 // Get the scope for the related resource
434
- 434 const relatedScope = api.resources[item.type]
435
- 435 if (!relatedScope) {
436
- 436 throw new Error(`Unknown resource type: ${item.type}`)
437
- 437 }
438
- 438
439
- 439 // Create context for dataGetMinimal
440
- 440 const getContext = {
441
- 441 ...context,
442
- 442 id: item.id,
443
- 443 schemaInfo: relatedScope.vars.schemaInfo,
444
- 444 scopeName: item.type,
445
- 445 method: 'get', // We're checking read permission
446
- 446 isUpdate: false
447
- 447 }
448
- 448
449
- 449 // Get the minimal record
450
- 450 const record = await helpers.dataGetMinimal({
451
- 451 scopeName: item.type,
452
- 452 context: getContext,
453
- 453 runHooks
454
- 454 })
455
- 455
456
- 456 if (!record) {
457
- 457 throw new Error(`Cannot create relationship to non-existent ${item.type} with id ${item.id}`)
458
- 458 }
459
- 459
460
- 460 // Check permissions using the related scope's checkPermissions
461
- 461 await relatedScope.checkPermissions({
462
- 462 method: 'get',
463
- 463 originalContext: {
464
- 464 ...context,
465
- 465 id: item.id,
466
- 466 minimalRecord: record
467
- 467 }
468
- 468 })
469
- 469 }
470
- 470 }
471
- 471 }
472
- 472
473
- 473 /**
474
- 474 * Applies field setters to transform attribute values before storage
475
- 475 * Setters are applied in dependency order to handle interdependent fields
476
- 476 *
477
- 477 * @param {object} attributes - The validated attributes to transform
478
- 478 * @param {object} schemaInfo - Schema information including fieldSetters
479
- 479 * @param {object} context - The request context
480
- 480 * @param {object} api - The API instance
481
- 481 * @param {object} helpers - Helper functions
482
- 482 * @returns {object} Transformed attributes ready for storage
483
- 483 */
484
- 484 export const applyFieldSetters = async (attributes, schemaInfo, context, api, helpers) => {
485
- 485 const fieldSetters = schemaInfo.fieldSetters || {}
486
- 486 const sortedSetterFields = schemaInfo.sortedSetterFields || []
487
- 487
488
- 488 // No setters to apply
489
- 489 if (sortedSetterFields.length === 0) {
490
- 490 return attributes
491
- 491 }
492
- 492
493
- 493 const transformedAttributes = { ...attributes }
494
- 494
495
- 495 for (const fieldName of sortedSetterFields) {
496
- 496 // Only process fields that exist in the attributes
497
- 497 if (fieldName in transformedAttributes) {
498
- 498 const setterInfo = fieldSetters[fieldName]
499
- 499 try {
500
- 500 const setterContext = {
501
- 501 attributes: transformedAttributes, // Current state with previous setters applied
502
- 502 fieldName,
503
- 503 originalValue: attributes[fieldName],
504
- 504 originalAttributes: attributes,
505
- 505 scopeName: context.scopeName,
506
- 506 method: context.method,
507
- 507 api,
508
- 508 helpers,
509
- 509 auth: context.auth
510
- 510 }
511
- 511 transformedAttributes[fieldName] = await setterInfo.setter(
512
- 512 transformedAttributes[fieldName],
513
- 513 setterContext
514
- 514 )
515
- 515 } catch (error) {
516
- 516 console.warn(`Setter for field '${fieldName}' failed:`, error)
517
- 517 // Keep original value if setter fails
518
- 518 }
519
- 519 }
520
- 520 }
521
- 521
522
- 522 return transformedAttributes
523
- 523 }
524
- 524
525
- 525 export async function handleRecordReturnAfterWrite ({
526
- 526 context,
527
- 527 scopeName,
528
- 528 api,
529
- 529 scopes,
530
- 530 schemaStructure,
531
- 531 schemaRelationships,
532
- 532 scopeOptions,
533
- 533 vars,
534
- 534 runHooks,
535
- 535 helpers,
536
- 536 log
537
- 537 }) {
538
- 538 // Create enhanced logger for warnings
539
- 539 const enhancedLog = getEnhancedLogger(log)
540
- 540 const methodSpecificHookSuffix = getMethodHookSuffix(context.method)
541
- 541
542
- 542 // Step 1: Set up record state for hooks
543
- 543 // Handle originalMinimalRecord and minimalRecord based on method type
544
- 544 if (context.method === 'DELETE') {
545
- 545 // For DELETE, keep the deleted record reference
546
- 546 if (context.minimalRecord) {
547
- 547 context.originalMinimalRecord = context.minimalRecord
548
- 548 }
549
- 549 } else {
550
- 550 // For POST, PUT, PATCH - save the original state if it exists
551
- 551 if (context.minimalRecord) {
552
- 552 context.originalMinimalRecord = context.minimalRecord
553
- 553 }
554
- 554
555
- 555 // Fetch the current state of the record after the write operation
556
- 556 try {
557
- 557 const currentRecord = await helpers.dataGetMinimal({
558
- 558 scopeName,
559
- 559 context,
560
- 560 runHooks
561
- 561 })
562
- 562 context.minimalRecord = currentRecord
563
- 563 } catch (error) {
564
- 564 enhancedLog.warn(`Could not fetch minimal record after ${context.method} operation`, { error, id: context.id })
565
- 565 }
566
- 566 }
567
- 567
568
- 568 // Step 2: Determine what to return based on configuration
569
- 569 const returnMode = context.returnRecordSetting[context.method]
570
- 570
571
- 571 // Case 1: Return nothing (204 No Content)
572
- 572 if (returnMode === 'no') {
573
- 573 context.responseRecord = undefined
574
- 574 await runHooks('finish')
575
- 575 await runHooks(`finish${methodSpecificHookSuffix}`)
576
- 576 return undefined
577
- 577 }
578
- 578
579
- 579 // Case 2: Return minimal record (just type and id)
580
- 580 if (returnMode === 'minimal') {
581
- 581 if (context.simplified) {
582
- 582 context.responseRecord = {
583
- 583 id: String(context.id),
584
- 584 type: scopeName
585
- 585 }
586
- 586 } else {
587
- 587 context.responseRecord = {
588
- 588 data: {
589
- 589 type: scopeName,
590
- 590 id: String(context.id)
591
- 591 }
592
- 592 }
593
- 593 }
594
- 594 await runHooks('finish')
595
- 595 await runHooks(`finish${methodSpecificHookSuffix}`)
596
- 596 return context.responseRecord
597
- 597 }
598
- 598
599
- 599 // Case 3: Return full record
600
- 600 if (returnMode === 'full') {
601
- 601 // Fetch the complete record using the GET method
602
- 602 const fullRecord = await api.resources[scopeName].get({
603
- 603 id: context.id,
604
- 604 queryParams: context.queryParams,
605
- 605 transaction: context.transaction,
606
- 606 simplified: context.simplified
607
- 607 }, { ...context })
608
- 608
609
- 609 context.responseRecord = fullRecord || undefined
610
- 610
611
- 611 // Run finish hooks
612
- 612 await runHooks('finish')
613
- 613 await runHooks(`finish${methodSpecificHookSuffix}`)
614
- 614
615
- 615 // No transformation needed - GET already returns the correct format
616
- 616 // based on context.simplified
617
- 617 return context.responseRecord
618
- 618 }
619
- 619
620
- 620 // This should never be reached, but just in case
621
- 621 throw new Error(`Invalid returnMode: ${returnMode}`)
622
- 622 }
623
- 623
624
- 624 export const findRelationshipDefinition = (schemaInfo, relationshipName) => {
625
- 625 // First check schemaRelationships (for relationships defined in relationships object)
626
- 626 const relDef = schemaInfo.schemaRelationships?.[relationshipName]
627
- 627 if (relDef) {
628
- 628 return relDef
629
- 629 }
630
- 630
631
- 631 // Then check schema fields for belongsTo relationships with matching 'as' property
632
- 632 for (const [fieldName, fieldDef] of Object.entries(schemaInfo.schemaStructure || {})) {
633
- 633 if (fieldDef.as === relationshipName && fieldDef.belongsTo) {
634
- 634 return fieldDef
635
- 635 }
636
- 636 }
637
- 637
638
- 638 return null
639
- 639 }