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
@@ -36,35 +36,40 @@ export default async function registerRelationshipRoutes ({ context, api, log })
36
36
  await api.addRoute({
37
37
  method: 'GET',
38
38
  path: `${scopePath}/:id/relationships/:relationshipName`,
39
- handler: createRouteHandler('getRelationship')
39
+ handler: createRouteHandler('getRelationship'),
40
+ routeMeta: { kind: 'relationship', scopeName, operation: 'getRelationship' }
40
41
  })
41
42
 
42
43
  // GET /api/{scope}/{id}/{relationshipName}
43
44
  await api.addRoute({
44
45
  method: 'GET',
45
46
  path: `${scopePath}/:id/:relationshipName`,
46
- handler: createRouteHandler('getRelated')
47
+ handler: createRouteHandler('getRelated'),
48
+ routeMeta: { kind: 'relationship', scopeName, operation: 'getRelated' }
47
49
  })
48
50
 
49
51
  // POST /api/{scope}/{id}/relationships/{relationshipName}
50
52
  await api.addRoute({
51
53
  method: 'POST',
52
54
  path: `${scopePath}/:id/relationships/:relationshipName`,
53
- handler: createRouteHandler('postRelationship')
55
+ handler: createRouteHandler('postRelationship'),
56
+ routeMeta: { kind: 'relationship', scopeName, operation: 'postRelationship' }
54
57
  })
55
58
 
56
59
  // PATCH /api/{scope}/{id}/relationships/{relationshipName}
57
60
  await api.addRoute({
58
61
  method: 'PATCH',
59
62
  path: `${scopePath}/:id/relationships/:relationshipName`,
60
- handler: createRouteHandler('patchRelationship')
63
+ handler: createRouteHandler('patchRelationship'),
64
+ routeMeta: { kind: 'relationship', scopeName, operation: 'patchRelationship' }
61
65
  })
62
66
 
63
67
  // DELETE /api/{scope}/{id}/relationships/{relationshipName}
64
68
  await api.addRoute({
65
69
  method: 'DELETE',
66
70
  path: `${scopePath}/:id/relationships/:relationshipName`,
67
- handler: createRouteHandler('deleteRelationship')
71
+ handler: createRouteHandler('deleteRelationship'),
72
+ routeMeta: { kind: 'relationship', scopeName, operation: 'deleteRelationship' }
68
73
  })
69
74
 
70
75
  log.trace(`Registered relationship routes for scope: ${scopeName}`)
@@ -60,42 +60,48 @@ export default async function registerScopeRoutes ({ context, api, vars, log })
60
60
  await api.addRoute({
61
61
  method: 'GET',
62
62
  path: scopePath,
63
- handler: createRouteHandler(scopeName, 'query')
63
+ handler: createRouteHandler(scopeName, 'query'),
64
+ routeMeta: { kind: 'resource', scopeName, operation: 'query' }
64
65
  })
65
66
 
66
67
  // GET /api/{scope}/{id} - Get single resource
67
68
  await api.addRoute({
68
69
  method: 'GET',
69
70
  path: `${scopePath}/:id`,
70
- handler: createRouteHandler(scopeName, 'get')
71
+ handler: createRouteHandler(scopeName, 'get'),
72
+ routeMeta: { kind: 'resource', scopeName, operation: 'get' }
71
73
  })
72
74
 
73
75
  // POST /api/{scope} - Create resource
74
76
  await api.addRoute({
75
77
  method: 'POST',
76
78
  path: scopePath,
77
- handler: createRouteHandler(scopeName, 'post')
79
+ handler: createRouteHandler(scopeName, 'post'),
80
+ routeMeta: { kind: 'resource', scopeName, operation: 'post' }
78
81
  })
79
82
 
80
83
  // PUT /api/{scope}/{id} - Replace resource
81
84
  await api.addRoute({
82
85
  method: 'PUT',
83
86
  path: `${scopePath}/:id`,
84
- handler: createRouteHandler(scopeName, 'put')
87
+ handler: createRouteHandler(scopeName, 'put'),
88
+ routeMeta: { kind: 'resource', scopeName, operation: 'put' }
85
89
  })
86
90
 
87
91
  // PATCH /api/{scope}/{id} - Update resource
88
92
  await api.addRoute({
89
93
  method: 'PATCH',
90
94
  path: `${scopePath}/:id`,
91
- handler: createRouteHandler(scopeName, 'patch')
95
+ handler: createRouteHandler(scopeName, 'patch'),
96
+ routeMeta: { kind: 'resource', scopeName, operation: 'patch' }
92
97
  })
93
98
 
94
99
  // DELETE /api/{scope}/{id} - Delete resource
95
100
  await api.addRoute({
96
101
  method: 'DELETE',
97
102
  path: `${scopePath}/:id`,
98
- handler: createRouteHandler(scopeName, 'delete')
103
+ handler: createRouteHandler(scopeName, 'delete'),
104
+ routeMeta: { kind: 'resource', scopeName, operation: 'delete' }
99
105
  })
100
106
 
101
107
  log.info(`Routes registered for scope '${scopeName}'`)
@@ -24,6 +24,7 @@ export default async function turnScopeInitIntoVars ({ context, scopes, vars: ap
24
24
 
25
25
  // Set idProperty as scope var
26
26
  if (typeof scopeOptions.idProperty !== 'undefined') vars.idProperty = scopeOptions.idProperty
27
+ if (typeof scopeOptions.normalizeId === 'function') vars.normalizeId = scopeOptions.normalizeId
27
28
 
28
29
  // Add validation for query limits
29
30
  if (vars.queryDefaultLimit && vars.queryMaxLimit) {
@@ -0,0 +1,41 @@
1
+ export default async function applyQueryFiltersMethod ({ context, params = {}, runHooks }) {
2
+ const { query, filters, storageAdapter, ...knexQuery } = params
3
+
4
+ if (!query) {
5
+ return query
6
+ }
7
+
8
+ const previousKnexQuery = context.knexQuery
9
+ const previousStorageAdapter = context.storageAdapter
10
+
11
+ context.knexQuery = {
12
+ query,
13
+ filters,
14
+ storageAdapter,
15
+ adapter: storageAdapter,
16
+ ...knexQuery
17
+ }
18
+
19
+ if (storageAdapter) {
20
+ context.storageAdapter = storageAdapter
21
+ }
22
+
23
+ try {
24
+ await runHooks('knexQueryFiltering')
25
+ return {
26
+ query: context.knexQuery?.query || query
27
+ }
28
+ } finally {
29
+ if (previousKnexQuery === undefined) {
30
+ delete context.knexQuery
31
+ } else {
32
+ context.knexQuery = previousKnexQuery
33
+ }
34
+
35
+ if (previousStorageAdapter === undefined) {
36
+ delete context.storageAdapter
37
+ } else {
38
+ context.storageAdapter = previousStorageAdapter
39
+ }
40
+ }
41
+ }
@@ -1,9 +1,11 @@
1
1
  import {
2
- RestApiValidationError,
3
- RestApiPayloadError
2
+ RestApiResourceError,
3
+ RestApiValidationError
4
4
  } from '../../../lib/rest-api-errors.js'
5
5
  import { transformSimplifiedToJsonApi } from '../lib/querying-writing/simplified-helpers.js'
6
+ import { normalizeRelationshipIdentifiers } from '../lib/querying-writing/resource-id-normalization.js'
6
7
  import { createEnhancedLogger } from '../../../lib/enhanced-logger.js'
8
+ import { unwrapQueryBuilderState } from '../lib/querying/query-builder-utils.js'
7
9
 
8
10
  /**
9
11
  * Gets an enhanced logger instance with full error details and stack traces
@@ -140,56 +142,6 @@ export async function setupCommonRequest ({ params, context, vars, scopes, scope
140
142
  { inputRecord: context.inputRecord },
141
143
  { context: { scopeName, schemaStructure, schemaRelationships } }
142
144
  )
143
- } else {
144
- // Strict mode: validate no belongsTo fields in attributes
145
- if (context.inputRecord?.data?.attributes) {
146
- for (const [key, fieldDef] of Object.entries(schemaStructure)) {
147
- if (fieldDef.belongsTo && key in context.inputRecord.data.attributes) {
148
- throw new RestApiValidationError(
149
- `Field '${key}' is a foreign key and must be set via relationships, not attributes`,
150
- { fields: [`data.attributes.${key}`] }
151
- )
152
- }
153
- }
154
- }
155
- }
156
-
157
- if (context.inputRecord.data.type !== scopeName) {
158
- throw new RestApiValidationError(
159
- `Resource type mismatch. Expected '${scopeName}' but got '${context.inputRecord.data.type}'`,
160
- {
161
- fields: ['data.type'],
162
- violations: [{
163
- field: 'data.type',
164
- rule: 'resource_type_match',
165
- message: `Resource type must be '${scopeName}'`
166
- }]
167
- }
168
- )
169
- }
170
-
171
- // Remove included validation since JSON:API doesn't support it
172
- if (context.inputRecord.included) {
173
- throw new RestApiPayloadError(
174
- context.method + ' requests cannot include an "included" array. JSON:API does not support creating multiple resources in a single request.',
175
- { path: 'included', expected: 'undefined', received: 'array' }
176
- )
177
- }
178
-
179
- // If both URL path ID and request body ID are provided, they must match
180
- // Convert both to strings for comparison since databases may return numeric IDs
181
- if (context.id && context.inputRecord.data.id && String(context.id) !== String(context.inputRecord.data.id)) {
182
- throw new RestApiValidationError(
183
- `ID mismatch. URL path ID '${context.id}' does not match request body ID '${context.inputRecord.data.id}'`,
184
- {
185
- fields: ['data.id'],
186
- violations: [{
187
- field: 'data.id',
188
- rule: 'id_consistency',
189
- message: 'Request body ID must match URL path ID when both are provided'
190
- }]
191
- }
192
- )
193
145
  }
194
146
 
195
147
  // Return key schema-related objects for direct use in the main methods
@@ -261,6 +213,61 @@ export const getMethodHookSuffix = (method) => {
261
213
  return method.charAt(0).toUpperCase() + method.slice(1)
262
214
  }
263
215
 
216
+ export const validateCompleteReplacePayload = ({
217
+ context,
218
+ belongsToUpdates
219
+ }) => {
220
+ const schemaInfo = context.schemaInfo || {}
221
+ const schemaStructure = schemaInfo.schemaStructure || {}
222
+ const idProperty = schemaInfo.idProperty || 'id'
223
+ const originalInputAttributes = context.originalInputAttributes || context.inputRecord?.data?.attributes || {}
224
+ const missingFields = []
225
+
226
+ for (const [fieldName, fieldDef] of Object.entries(schemaStructure)) {
227
+ if (!fieldDef) continue
228
+ if (fieldName === idProperty) continue
229
+ if (fieldDef.computed === true || fieldDef.virtual === true) continue
230
+ if (fieldDef.type === undefined) continue
231
+
232
+ if (fieldDef.belongsTo && fieldDef.as) {
233
+ if (Object.hasOwn(belongsToUpdates, fieldName)) continue
234
+
235
+ const existingRelationshipData = context.minimalRecord?.relationships?.[fieldDef.as]?.data
236
+ if (existingRelationshipData?.id !== undefined && existingRelationshipData?.id !== null) {
237
+ missingFields.push({
238
+ field: `data.relationships.${fieldDef.as}.data.id`,
239
+ message: `PUT requests must explicitly include relationship '${fieldDef.as}' when it already has a value`
240
+ })
241
+ }
242
+ continue
243
+ }
244
+
245
+ if (Object.hasOwn(originalInputAttributes, fieldName)) continue
246
+
247
+ const existingValue = context.minimalRecord?.attributes?.[fieldName]
248
+ if (existingValue !== undefined && existingValue !== null) {
249
+ missingFields.push({
250
+ field: `data.attributes.${fieldName}`,
251
+ message: `PUT requests must explicitly include attribute '${fieldName}' when it already has a value`
252
+ })
253
+ }
254
+ }
255
+
256
+ if (missingFields.length > 0) {
257
+ throw new RestApiValidationError(
258
+ 'PUT request omits persisted fields that already have stored values',
259
+ {
260
+ fields: missingFields.map(({ field }) => field),
261
+ violations: missingFields.map(({ field, message }) => ({
262
+ field,
263
+ rule: 'complete_replacement',
264
+ message
265
+ }))
266
+ }
267
+ )
268
+ }
269
+ }
270
+
264
271
  /**
265
272
  * Validates resource attributes before write operations
266
273
  *
@@ -285,22 +292,25 @@ export const validateResourceAttributesBeforeWrite = async ({
285
292
  await runHooks('beforeSchemaValidate')
286
293
  await runHooks(`beforeSchemaValidate${methodSpecificHookSuffix}`)
287
294
 
288
- // Store original input attributes before validation adds defaults (primarily for POST)
289
- // Only if it's not already set and the current method is POST
290
- if (!context.originalInputAttributes && context.method === 'post') {
295
+ // Store original input attributes before validation adds defaults or casts.
296
+ if (!context.originalInputAttributes) {
291
297
  context.originalInputAttributes = { ...(context.inputRecord.data.attributes || {}) }
292
298
  }
293
299
 
300
+ const schemaStructure = context.schemaInfo.schemaStructure || {}
301
+ const hasLogicalIdField = Object.hasOwn(schemaStructure, 'id')
302
+ const resourceId = context.inputRecord?.data?.id
303
+
294
304
  // Merge belongsTo updates with attributes for validation
295
305
  const attributesToValidate = {
296
306
  ...context.inputRecord.data.attributes,
297
- ...belongsToUpdates
307
+ ...belongsToUpdates,
308
+ ...(hasLogicalIdField && resourceId !== undefined ? { id: resourceId } : {})
298
309
  }
299
310
 
300
311
  // Extract computed fields and foreign key fields before validation
301
312
  const computedFields = {}
302
313
  const foreignKeyFields = {}
303
- const schemaStructure = context.schemaInfo.schemaStructure || {}
304
314
  Object.entries(attributesToValidate).forEach(([key, value]) => {
305
315
  const fieldDef = schemaStructure[key]
306
316
  // Computed fields should be stripped from input
@@ -361,17 +371,22 @@ export const validateResourceAttributesBeforeWrite = async ({
361
371
  return acc
362
372
  }, {})
363
373
 
364
- const validationOptions = isPartialValidation ? { onlyObjectValues: true } : {}
374
+ const validationMethod = isPartialValidation
375
+ ? 'patch'
376
+ : context.method === 'put'
377
+ ? 'replace'
378
+ : 'create'
365
379
 
366
- const { validatedObject, errors } = await schema.validate(attributesForValidation, validationOptions)
380
+ const { validatedObject, errors } = await schema[validationMethod](attributesForValidation)
367
381
 
368
382
  if (Object.keys(errors).length > 0) {
369
- // --- START OF MODIFICATION ---
370
- const schemaStructure = context.schemaInfo.schemaInstance.structure // Get the schema structure for lookup
371
-
372
383
  const violations = Object.entries(errors).map(([field, error]) => {
373
384
  let fieldPath = `data.attributes.${field}` // Default path for attributes
374
385
 
386
+ if (field === 'id' && hasLogicalIdField) {
387
+ fieldPath = 'data.id'
388
+ }
389
+
375
390
  // Check if this field is a foreign key that has an 'as' alias
376
391
  const fieldDef = schemaStructure[field]
377
392
  if (fieldDef && fieldDef.belongsTo && fieldDef.as) {
@@ -391,7 +406,6 @@ export const validateResourceAttributesBeforeWrite = async ({
391
406
  message: error.message
392
407
  }
393
408
  })
394
- // --- END OF MODIFICATION ---
395
409
 
396
410
  throw new RestApiValidationError(
397
411
  'Schema validation failed for resource attributes',
@@ -402,9 +416,14 @@ export const validateResourceAttributesBeforeWrite = async ({
402
416
  )
403
417
  }
404
418
 
419
+ if (Object.hasOwn(validatedObject, 'id')) {
420
+ context.inputRecord.data.id = validatedObject.id
421
+ }
422
+ const { id: _validatedId, ...validatedAttributes } = validatedObject
423
+
405
424
  // Update attributes with validated values
406
425
  // Virtual fields have now been validated and cast properly
407
- context.inputRecord.data.attributes = validatedObject
426
+ context.inputRecord.data.attributes = validatedAttributes
408
427
 
409
428
  await runHooks(`afterSchemaValidate${methodSpecificHookSuffix}`)
410
429
  await runHooks('afterSchemaValidate')
@@ -416,18 +435,25 @@ export const validateResourceAttributesBeforeWrite = async ({
416
435
  * @param {object} context - The context object containing authentication info
417
436
  * @param {object} inputRecord - The input record containing relationships to validate
418
437
  * @param {object} helpers - Data helpers including dataGetMinimal
419
- * @param {function} runHooks - Function to run hooks
420
438
  * @param {object} api - API instance to access resources
421
439
  * @throws {Error} If user doesn't have access to any related resource
422
440
  */
423
- export const validateRelationshipAccess = async (context, inputRecord, helpers, runHooks, api) => {
441
+ export const validateRelationshipAccess = async (context, inputRecord, helpers, api) => {
424
442
  if (!inputRecord?.data?.relationships) return
425
443
 
426
444
  for (const [relName, relData] of Object.entries(inputRecord.data.relationships)) {
427
445
  if (!relData?.data) continue
428
446
 
447
+ const normalizedRelationshipData = normalizeRelationshipIdentifiers(relData.data, { api })
448
+ inputRecord.data.relationships[relName] = {
449
+ ...relData,
450
+ data: normalizedRelationshipData
451
+ }
452
+
429
453
  // Handle both single and array relationships
430
- const relatedItems = Array.isArray(relData.data) ? relData.data : [relData.data]
454
+ const relatedItems = Array.isArray(normalizedRelationshipData)
455
+ ? normalizedRelationshipData
456
+ : [normalizedRelationshipData]
431
457
 
432
458
  for (const item of relatedItems) {
433
459
  // Get the scope for the related resource
@@ -442,19 +468,47 @@ export const validateRelationshipAccess = async (context, inputRecord, helpers,
442
468
  id: item.id,
443
469
  schemaInfo: relatedScope.vars.schemaInfo,
444
470
  scopeName: item.type,
471
+ queryParams: {},
445
472
  method: 'get', // We're checking read permission
446
473
  isUpdate: false
447
474
  }
448
475
 
476
+ const targetScope = api.resources?.[item.type]
477
+ const applyQueryFilters = targetScope?.applyQueryFilters
478
+ ? async ({ query, filters, tableName, db, scopeName, storageAdapter, isAnyApi }) => {
479
+ const queryState = await targetScope.applyQueryFilters({
480
+ query,
481
+ filters,
482
+ schemaInfo: getContext.schemaInfo,
483
+ scopeName,
484
+ tableName,
485
+ db,
486
+ isAnyApi,
487
+ storageAdapter
488
+ }, getContext)
489
+
490
+ return {
491
+ query: unwrapQueryBuilderState(queryState, query)
492
+ }
493
+ }
494
+ : undefined
495
+
449
496
  // Get the minimal record
450
497
  const record = await helpers.dataGetMinimal({
451
498
  scopeName: item.type,
452
499
  context: getContext,
453
- runHooks
500
+ applyQueryFilters
454
501
  })
455
502
 
456
503
  if (!record) {
457
- throw new Error(`Cannot create relationship to non-existent ${item.type} with id ${item.id}`)
504
+ throw new RestApiResourceError(
505
+ `Cannot create relationship to non-existent ${item.type} with id ${item.id}`,
506
+ {
507
+ subtype: 'not_found',
508
+ resourceType: item.type,
509
+ resourceId: item.id
510
+ }
511
+ )
458
512
  }
459
513
 
460
514
  // Check permissions using the related scope's checkPermissions
@@ -493,29 +547,31 @@ export const applyFieldSetters = async (attributes, schemaInfo, context, api, he
493
547
  const transformedAttributes = { ...attributes }
494
548
 
495
549
  for (const fieldName of sortedSetterFields) {
496
- // Only process fields that exist in the attributes
497
- if (fieldName in transformedAttributes) {
498
- const setterInfo = fieldSetters[fieldName]
499
- try {
500
- const setterContext = {
501
- attributes: transformedAttributes, // Current state with previous setters applied
502
- fieldName,
503
- originalValue: attributes[fieldName],
504
- originalAttributes: attributes,
505
- scopeName: context.scopeName,
506
- method: context.method,
507
- api,
508
- helpers,
509
- auth: context.auth
510
- }
511
- transformedAttributes[fieldName] = await setterInfo.setter(
512
- transformedAttributes[fieldName],
513
- setterContext
514
- )
515
- } catch (error) {
516
- console.warn(`Setter for field '${fieldName}' failed:`, error)
517
- // Keep original value if setter fails
550
+ const setterInfo = fieldSetters[fieldName]
551
+ const fieldPresent = fieldName in transformedAttributes
552
+ const hasDependencies = Array.isArray(setterInfo.runSetterAfter) && setterInfo.runSetterAfter.length > 0
553
+
554
+ if (!fieldPresent && !hasDependencies) continue
555
+
556
+ try {
557
+ const setterContext = {
558
+ attributes: transformedAttributes, // Current state with previous setters applied
559
+ fieldName,
560
+ originalValue: attributes[fieldName],
561
+ originalAttributes: attributes,
562
+ scopeName: context.scopeName,
563
+ method: context.method,
564
+ api,
565
+ helpers,
566
+ auth: context.auth
518
567
  }
568
+ transformedAttributes[fieldName] = await setterInfo.setter(
569
+ transformedAttributes[fieldName],
570
+ setterContext
571
+ )
572
+ } catch (error) {
573
+ console.warn(`Setter for field '${fieldName}' failed:`, error)
574
+ // Keep original value if setter fails
519
575
  }
520
576
  }
521
577
 
@@ -629,7 +685,7 @@ export const findRelationshipDefinition = (schemaInfo, relationshipName) => {
629
685
  }
630
686
 
631
687
  // Then check schema fields for belongsTo relationships with matching 'as' property
632
- for (const [fieldName, fieldDef] of Object.entries(schemaInfo.schemaStructure || {})) {
688
+ for (const [, fieldDef] of Object.entries(schemaInfo.schemaStructure || {})) {
633
689
  if (fieldDef.as === relationshipName && fieldDef.belongsTo) {
634
690
  return fieldDef
635
691
  }
@@ -1,5 +1,9 @@
1
1
  import { RestApiResourceError, RestApiValidationError, RestApiPayloadError } from '../../../lib/rest-api-errors.js'
2
2
  import { findRelationshipDefinition, handleWriteMethodError } from './common.js'
3
+ import {
4
+ normalizeRelationshipIdentifiers,
5
+ requireExistingResourceId
6
+ } from '../lib/querying-writing/resource-id-normalization.js'
3
7
 
4
8
  /**
5
9
  * DELETE RELATIONSHIP
@@ -11,9 +15,13 @@ import { findRelationshipDefinition, handleWriteMethodError } from './common.js'
11
15
  * @param {array} relationshipData - Array of resource identifiers to remove
12
16
  * @returns {Promise<void>} 204 No Content
13
17
  */
14
- export default async function deleteRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api, log }) {
18
+ export default async function deleteRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeOptions, scopeName, api, log }) {
15
19
  context.method = 'deleteRelationship'
16
- context.id = params.id
20
+ context.id = requireExistingResourceId(params.id, {
21
+ scopeOptions,
22
+ vars,
23
+ scopeName
24
+ })
17
25
  context.relationshipName = params.relationshipName
18
26
  context.schemaInfo = scopes[scopeName].vars.schemaInfo
19
27
 
@@ -43,6 +51,7 @@ export default async function deleteRelationshipMethod ({ params, context, vars,
43
51
  if (!Array.isArray(params.relationshipData)) {
44
52
  throw new RestApiPayloadError('DELETE from relationship requires array of resource identifiers')
45
53
  }
54
+ params.relationshipData = normalizeRelationshipIdentifiers(params.relationshipData, { api })
46
55
 
47
56
  // Check permissions
48
57
  await runHooks('checkPermissions')
@@ -1,5 +1,6 @@
1
1
  import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
2
2
  import { handleWriteMethodError } from './common.js'
3
+ import { requireExistingResourceId } from '../lib/querying-writing/resource-id-normalization.js'
3
4
 
4
5
  /**
5
6
  * DELETE
@@ -26,7 +27,11 @@ export default async function deleteMethod ({
26
27
  context.method = 'delete'
27
28
 
28
29
  // Set the ID in context
29
- context.id = params.id
30
+ context.id = requireExistingResourceId(params.id, {
31
+ scopeOptions,
32
+ vars,
33
+ scopeName
34
+ })
30
35
 
31
36
  // Set scopeName in context (needed for broadcasting)
32
37
  context.scopeName = scopeName
@@ -33,6 +33,7 @@ export default async function enrichAttributesMethod ({ context, params, runHook
33
33
  const computedFields = scopes[scopeName]?.vars?.schemaInfo?.computed || {}
34
34
  const fieldGetters = scopes[scopeName]?.vars?.schemaInfo?.fieldGetters || {}
35
35
  const sortedGetterFields = scopes[scopeName]?.vars?.schemaInfo?.sortedGetterFields || []
36
+ const queryFields = scopes[scopeName]?.vars?.queryFields || {}
36
37
 
37
38
  // Make a copy of attributes for transformation
38
39
  const transformedAttributes = { ...attributes }
@@ -72,7 +73,11 @@ export default async function enrichAttributesMethod ({ context, params, runHook
72
73
  // Filter hidden fields from attributes based on visibility rules
73
74
  // This removes hidden:true fields and normallyHidden:true fields (unless requested)
74
75
  const requestedFields = parentContext?.queryParams?.fields?.[scopeName]
75
- const filteredAttributes = filterHiddenFields(transformedAttributes, { structure: schemaStructure }, requestedFields)
76
+ const filteredAttributes = filterHiddenFields(
77
+ transformedAttributes,
78
+ { structure: { ...schemaStructure, ...queryFields } },
79
+ requestedFields
80
+ )
76
81
 
77
82
  // Handle virtual fields - they need to be added from parent context if available
78
83
  // Virtual fields come from input and need to be preserved in responses
@@ -1,6 +1,7 @@
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
+ import { requireExistingResourceId } from '../lib/querying-writing/resource-id-normalization.js'
4
5
 
5
6
  /**
6
7
  * GET RELATED
@@ -12,9 +13,13 @@ import { buildRelationshipUrl } from '../lib/querying/url-helpers.js'
12
13
  * @param {object} queryParams - Standard query parameters
13
14
  * @returns {Promise<object>} Related resources with full data
14
15
  */
15
- export default async function getRelatedMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api }) {
16
+ export default async function getRelatedMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeOptions, scopeName, api }) {
16
17
  context.method = 'getRelated'
17
- context.id = params.id
18
+ context.id = requireExistingResourceId(params.id, {
19
+ scopeOptions,
20
+ vars,
21
+ scopeName
22
+ })
18
23
  context.relationshipName = params.relationshipName
19
24
  context.queryParams = params.queryParams || {}
20
25
  context.schemaInfo = scopes[scopeName].vars.schemaInfo
@@ -1,6 +1,7 @@
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
+ import { requireExistingResourceId } from '../lib/querying-writing/resource-id-normalization.js'
4
5
 
5
6
  /**
6
7
  * GET RELATIONSHIP
@@ -11,9 +12,13 @@ import { buildRelationshipUrl } from '../lib/querying/url-helpers.js'
11
12
  * @param {string} relationshipName - The name of the relationship
12
13
  * @returns {Promise<object>} Relationship linkage with links
13
14
  */
14
- export default async function getRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api }) {
15
+ export default async function getRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeOptions, scopeName, api }) {
15
16
  context.method = 'getRelationship'
16
- context.id = params.id
17
+ context.id = requireExistingResourceId(params.id, {
18
+ scopeOptions,
19
+ vars,
20
+ scopeName
21
+ })
17
22
  context.relationshipName = params.relationshipName
18
23
  context.schemaInfo = scopes[scopeName].vars.schemaInfo
19
24