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,328 +0,0 @@
1
- 1 import { RestApiResourceError } from '../../../../lib/rest-api-errors.js'
2
- 2 import { ROW_NUMBER_KEY, DEFAULT_QUERY_LIMIT, DEFAULT_MAX_QUERY_LIMIT, DEFAULT_MAX_INCLUDE_LIMIT } from '../querying-writing/knex-constants.js'
3
- 3
4
- 4 /**
5
- 5 * Builds a window function query for limited includes per parent record
6
- 6 *
7
- 7 * @param {Object} knex - Knex instance
8
- 8 * @param {string} tableName - Target table name
9
- 9 * @param {string} foreignKey - Foreign key field
10
- 10 * @param {Array} parentIds - Parent record IDs
11
- 11 * @param {Array} fieldsToSelect - Fields to select
12
- 12 * @param {Object} includeConfig - Include configuration (limit, orderBy)
13
- 13 * @param {Object} capabilities - Database capabilities
14
- 14 * @param {Object} scopeVars - Scope variables for defaults and limits
15
- 15 * @returns {Object} Knex query
16
- 16 *
17
- 17 * @example
18
- 18 * // Input: Load max 3 comments per article, ordered by newest first
19
- 19 * const query = buildWindowedIncludeQuery(
20
- 20 * knex,
21
- 21 * 'comments',
22
- 22 * 'article_id',
23
- 23 * [1, 2, 3], // Article IDs
24
- 24 * ['id', 'text', 'created_at'],
25
- 25 * { limit: 3, orderBy: ['-created_at'] },
26
- 26 * { windowFunctions: true },
27
- 27 * { queryDefaultLimit: 10 }
28
- 28 * );
29
- 29 *
30
- 30 * // Generated SQL:
31
- 31 * // WITH _windowed AS (
32
- 32 * // SELECT id, text, created_at, article_id,
33
- 33 * // ROW_NUMBER() OVER (
34
- 34 * // PARTITION BY article_id
35
- 35 * // ORDER BY created_at DESC
36
- 36 * // ) as __$jsonrestapi_rn$__
37
- 37 * // FROM comments
38
- 38 * // WHERE article_id IN (1, 2, 3)
39
- 39 * // )
40
- 40 * // SELECT * FROM _windowed
41
- 41 * // WHERE __$jsonrestapi_rn$__ <= 3
42
- 42 *
43
- 43 * // Result: Each article gets max 3 comments, not 3 total
44
- 44 *
45
- 45 * @example
46
- 46 * // Input: Database doesn't support window functions
47
- 47 * const query = buildWindowedIncludeQuery(
48
- 48 * knex,
49
- 49 * 'comments',
50
- 50 * 'article_id',
51
- 51 * [1, 2, 3],
52
- 52 * '*',
53
- 53 * { limit: 5 },
54
- 54 * { windowFunctions: false, dbInfo: { client: 'mysql', version: '5.7' } }
55
- 55 * );
56
- 56 *
57
- 57 * // Throws RestApiResourceError:
58
- 58 * // "Include limits require window function support. Your database (mysql 5.7)
59
- 59 * // does not support this feature. Window functions are supported in:
60
- 60 * // PostgreSQL 8.4+, MySQL 8.0+, MariaDB 10.2+, SQLite 3.25+, SQL Server 2005+"
61
- 61 *
62
- 62 * @description
63
- 63 * Used by:
64
- 64 * - loadHasMany when strategy: 'window' and database supports window functions
65
- 65 * - Enables per-parent limits for one-to-many relationships
66
- 66 *
67
- 67 * Purpose:
68
- 68 * - Solves the "N+1 limit" problem where you want X records per parent
69
- 69 * - Without window functions, LIMIT 10 gives 10 total across all parents
70
- 70 * - With window functions, each parent gets up to 10 related records
71
- 71 * - Critical for consistent API responses with includes
72
- 72 *
73
- 73 * Data flow:
74
- 74 * 1. Creates subquery with ROW_NUMBER() partitioned by foreign key
75
- 75 * 2. Numbers rows within each partition based on orderBy
76
- 76 * 3. Outer query filters to keep only rows within limit
77
- 77 * 4. Returns query ready for execution
78
- 78 * 5. Caller removes the ROW_NUMBER column from results
79
- 79 */
80
- 80 export const buildWindowedIncludeQuery = (
81
- 81 knex,
82
- 82 tableName,
83
- 83 foreignKey,
84
- 84 parentIds,
85
- 85 fieldsToSelect,
86
- 86 includeConfig,
87
- 87 capabilities,
88
- 88 scopeVars = {}
89
- 89 ) => {
90
- 90 const { orderBy = [] } = includeConfig
91
- 91
92
- 92 // Apply defaults for limit
93
- 93 const effectiveLimit = includeConfig.limit ?? scopeVars.queryDefaultLimit ?? DEFAULT_QUERY_LIMIT
94
- 94
95
- 95 // Validate against max
96
- 96 if (scopeVars.queryMaxLimit && effectiveLimit > scopeVars.queryMaxLimit) {
97
- 97 throw new RestApiResourceError({
98
- 98 title: 'Include Limit Exceeds Maximum',
99
- 99 detail: `Requested include limit (${effectiveLimit}) exceeds queryMaxLimit (${scopeVars.queryMaxLimit})`,
100
- 100 status: 400
101
- 101 })
102
- 102 }
103
- 103
104
- 104 // Check if window functions are supported
105
- 105 if (!capabilities.windowFunctions) {
106
- 106 const { dbInfo } = capabilities
107
- 107 throw new RestApiResourceError(
108
- 108 `Include limits require window function support. Your database (${dbInfo.client} ${dbInfo.version}) does not support this feature. ` +
109
- 109 'Window functions are supported in: PostgreSQL 8.4+, MySQL 8.0+, MariaDB 10.2+, SQLite 3.25+, SQL Server 2005+',
110
- 110 {
111
- 111 subtype: 'unsupported_operation',
112
- 112 database: dbInfo.client,
113
- 113 version: dbInfo.version,
114
- 114 requiredFeature: 'window_functions'
115
- 115 }
116
- 116 )
117
- 117 }
118
- 118
119
- 119 // Build the window function query
120
- 120 // This creates a subquery that partitions by the foreign key and numbers rows
121
- 121 const subquery = knex(tableName)
122
- 122 .select('*')
123
- 123 .select(
124
- 124 knex.raw(
125
- 125 'ROW_NUMBER() OVER (PARTITION BY ?? ORDER BY ' +
126
- 126 buildOrderByClause(orderBy) +
127
- 127 ') as ' + ROW_NUMBER_KEY,
128
- 128 [foreignKey]
129
- 129 )
130
- 130 )
131
- 131 .whereIn(foreignKey, parentIds)
132
- 132
133
- 133 // Apply field selection if specified
134
- 134 if (fieldsToSelect !== '*' && Array.isArray(fieldsToSelect)) {
135
- 135 // Include the foreign key and row number in selection
136
- 136 const fieldsWithFK = [...new Set([...fieldsToSelect, foreignKey, ROW_NUMBER_KEY])]
137
- 137 subquery.select(fieldsWithFK)
138
- 138 }
139
- 139
140
- 140 // Wrap in outer query to filter by row number
141
- 141 const query = knex
142
- 142 .select('*')
143
- 143 .from(subquery.as('_windowed'))
144
- 144 .where(ROW_NUMBER_KEY, '<=', effectiveLimit)
145
- 145
146
- 146 return query
147
- 147 }
148
- 148
149
- 149 /**
150
- 150 * Builds ORDER BY clause from array of sort fields
151
- 151 *
152
- 152 * @param {Array<string>} orderBy - Array of field names, prefix with '-' for DESC
153
- 153 * @param {string} tablePrefix - Optional table name to prefix fields
154
- 154 * @returns {string} SQL ORDER BY clause
155
- 155 *
156
- 156 * @example
157
- 157 * // Input: Simple ascending sort
158
- 158 * const clause = buildOrderByClause(['name', 'created_at']);
159
- 159 * // Output: "name ASC, created_at ASC"
160
- 160 *
161
- 161 * @example
162
- 162 * // Input: Mixed ascending/descending with '-' prefix
163
- 163 * const clause = buildOrderByClause(['name', '-created_at', 'status']);
164
- 164 * // Output: "name ASC, created_at DESC, status ASC"
165
- 165 *
166
- 166 * @example
167
- 167 * // Input: With table prefix for joins
168
- 168 * const clause = buildOrderByClause(['-updated_at', 'title'], 'articles');
169
- 169 * // Output: "articles.updated_at DESC, articles.title ASC"
170
- 170 *
171
- 171 * @example
172
- 172 * // Input: Empty array (default to id)
173
- 173 * const clause = buildOrderByClause([]);
174
- 174 * // Output: "id ASC"
175
- 175 *
176
- 176 * @description
177
- 177 * Used by:
178
- 178 * - buildWindowedIncludeQuery for PARTITION BY ordering
179
- 179 * - applyStandardIncludeConfig for regular ORDER BY
180
- 180 * - Query builders that need consistent sort syntax
181
- 181 *
182
- 182 * Purpose:
183
- 183 * - Converts API sort syntax (with '-' prefix) to SQL ORDER BY
184
- 184 * - Handles table prefixing for queries with joins
185
- 185 * - Provides default ordering by id when none specified
186
- 186 * - Ensures consistent sort behavior across the API
187
- 187 *
188
- 188 * Data flow:
189
- 189 * 1. Receives array of sort fields from API parameters
190
- 190 * 2. Detects DESC sorts by '-' prefix
191
- 191 * 3. Optionally prefixes with table name
192
- 192 * 4. Joins into SQL-compatible ORDER BY clause
193
- 193 */
194
- 194 export const buildOrderByClause = (orderBy, tablePrefix) => {
195
- 195 if (!orderBy || orderBy.length === 0) {
196
- 196 const defaultField = tablePrefix ? `${tablePrefix}.id` : 'id'
197
- 197 return `${defaultField} ASC` // Default ordering
198
- 198 }
199
- 199
200
- 200 return orderBy.map(field => {
201
- 201 const isDesc = field.startsWith('-')
202
- 202 const fieldName = isDesc ? field.substring(1) : field
203
- 203 const qualifiedField = tablePrefix ? `${tablePrefix}.${fieldName}` : fieldName
204
- 204 return `${qualifiedField} ${isDesc ? 'DESC' : 'ASC'}`
205
- 205 }).join(', ')
206
- 206 }
207
- 207
208
- 208 /**
209
- 209 * Applies standard (non-windowed) include configuration to a query
210
- 210 *
211
- 211 * @param {Object} query - Knex query builder instance
212
- 212 * @param {Object} includeConfig - Include configuration object
213
- 213 * @param {Object} scopeVars - Scope variables with defaults and limits
214
- 214 * @param {Object} log - Logger instance
215
- 215 * @returns {Object} Modified query builder
216
- 216 *
217
- 217 * @example
218
- 218 * // Input: Basic include with limit and ordering
219
- 219 * let query = knex('comments').whereIn('article_id', [1, 2, 3]);
220
- 220 * query = applyStandardIncludeConfig(
221
- 221 * query,
222
- 222 * { limit: 20, orderBy: ['-created_at', 'id'] },
223
- 223 * { queryDefaultLimit: 100, queryMaxLimit: 1000 },
224
- 224 * logger
225
- 225 * );
226
- 226 *
227
- 227 * // Result: Query modified with:
228
- 228 * // ORDER BY created_at DESC, id ASC
229
- 229 * // LIMIT 20
230
- 230 *
231
- 231 * @example
232
- 232 * // Input: No explicit limit, uses defaults
233
- 233 * let query = knex('tags');
234
- 234 * query = applyStandardIncludeConfig(
235
- 235 * query,
236
- 236 * { orderBy: ['name'] }, // No limit specified
237
- 237 * { queryDefaultLimit: 50, queryMaxLimit: 500 },
238
- 238 * logger
239
- 239 * );
240
- 240 *
241
- 241 * // Result: Uses default limit
242
- 242 * // ORDER BY name ASC
243
- 243 * // LIMIT 50
244
- 244 * // Log: "Using default limit"
245
- 245 *
246
- 246 * @example
247
- 247 * // Input: Limit exceeds maximum
248
- 248 * let query = knex('reviews');
249
- 249 * query = applyStandardIncludeConfig(
250
- 250 * query,
251
- 251 * { limit: 5000 }, // Exceeds max
252
- 252 * { queryMaxLimit: 1000, maxIncludeLimit: 500 },
253
- 253 * logger
254
- 254 * );
255
- 255 *
256
- 256 * // Result: Clamped to effective maximum
257
- 257 * // LIMIT 500 (min of maxIncludeLimit and queryMaxLimit)
258
- 258 *
259
- 259 * @example
260
- 260 * // Input: Explicitly disable limit
261
- 261 * let query = knex('categories');
262
- 262 * query = applyStandardIncludeConfig(
263
- 263 * query,
264
- 264 * { limit: null }, // Explicitly no limit
265
- 265 * { queryDefaultLimit: 100 },
266
- 266 * logger
267
- 267 * );
268
- 268 *
269
- 269 * // Result: No LIMIT clause added
270
- 270 * // Log: "No limit applied to include query (explicitly disabled)"
271
- 271 *
272
- 272 * @description
273
- 273 * Used by:
274
- 274 * - loadHasMany when window functions not available or not requested
275
- 275 * - loadReversePolymorphic for standard relationship queries
276
- 276 * - Any include loader that needs consistent limit/order handling
277
- 277 *
278
- 278 * Purpose:
279
- 279 * - Provides fallback when window functions unavailable
280
- 280 * - Applies consistent ordering based on API sort syntax
281
- 281 * - Enforces limit hierarchy: explicit > default > max
282
- 282 * - Respects both queryMaxLimit and maxIncludeLimit
283
- 283 * - Logs decisions for debugging
284
- 284 *
285
- 285 * Data flow:
286
- 286 * 1. Applies ORDER BY for each field in orderBy array
287
- 287 * 2. Determines effective limit from explicit/default/max values
288
- 288 * 3. Clamps limit to maximum allowed values
289
- 289 * 4. Adds LIMIT clause unless explicitly disabled
290
- 290 * 5. Logs the reasoning for the applied limit
291
- 291 */
292
- 292 export const applyStandardIncludeConfig = (query, includeConfig, scopeVars, log) => {
293
- 293 const { orderBy = [] } = includeConfig
294
- 294
295
- 295 // Apply ordering
296
- 296 orderBy.forEach(field => {
297
- 297 const desc = field.startsWith('-')
298
- 298 const column = desc ? field.substring(1) : field
299
- 299 query = query.orderBy(column, desc ? 'desc' : 'asc')
300
- 300 })
301
- 301
302
- 302 // Apply limit with defaults
303
- 303 const requestedLimit = includeConfig.limit
304
- 304 const defaultLimit = scopeVars.queryDefaultLimit ?? DEFAULT_QUERY_LIMIT
305
- 305 const limit = requestedLimit ?? defaultLimit
306
- 306
307
- 307 // Allow explicit null/false to mean no limit
308
- 308 if (limit !== null && limit !== false) {
309
- 309 const maxInclude = scopeVars.maxIncludeLimit || DEFAULT_MAX_INCLUDE_LIMIT
310
- 310 const maxQuery = scopeVars.queryMaxLimit || DEFAULT_MAX_QUERY_LIMIT
311
- 311 const effectiveMax = Math.min(maxInclude, maxQuery)
312
- 312 const effectiveLimit = Math.min(limit, effectiveMax)
313
- 313
314
- 314 query = query.limit(effectiveLimit)
315
- 315
316
- 316 log.debug('Applied include limit:', {
317
- 317 requested: requestedLimit,
318
- 318 default: defaultLimit,
319
- 319 effective: effectiveLimit,
320
- 320 maxAllowed: effectiveMax,
321
- 321 note: requestedLimit !== undefined ? 'Using explicit limit' : 'Using default limit'
322
- 322 })
323
- 323 } else {
324
- 324 log.debug('No limit applied to include query (explicitly disabled)')
325
- 325 }
326
- 326
327
- 327 return query
328
- 328 }
@@ -1,119 +0,0 @@
1
- 1 /**
2
- 2 * URL Helper Functions
3
- 3 *
4
- 4 * Centralized functions for URL generation to avoid code duplication
5
- 5 * and support flexible URL prefix override capabilities.
6
- 6 */
7
- 7
8
- 8 /**
9
- 9 * Get the complete URL prefix, handling all calculation and override logic
10
- 10 *
11
- 11 * Priority order:
12
- 12 * 1. context.urlPrefixOverride - Explicit override (from hooks or middleware)
13
- 13 * 2. context.urlPrefix - Pre-calculated URL prefix
14
- 14 * 3. Calculate from request if provided
15
- 15 * 4. scope.vars.transport?.mountPath - Fallback to mount path
16
- 16 * 5. '' - Empty string as final fallback
17
- 17 *
18
- 18 * @param {Object} context - Request context (may have urlPrefixOverride or urlPrefix)
19
- 19 * @param {Object} scope - Scope/resource object with transport vars
20
- 20 * @param {Object} req - Optional Express request object for auto-calculation
21
- 21 * @returns {string} The final URL prefix to use
22
- 22 *
23
- 23 * @example
24
- 24 * // With full context
25
- 25 * const urlPrefix = getUrlPrefix(context, scope);
26
- 26 *
27
- 27 * @example
28
- 28 * // With request object for auto-calculation
29
- 29 * const urlPrefix = getUrlPrefix({}, api, req);
30
- 30 */
31
- 31 export function getUrlPrefix (context, scope, req = null) {
32
- 32 // Priority 1: Explicit override
33
- 33 if (context?.urlPrefixOverride) {
34
- 34 return context.urlPrefixOverride
35
- 35 }
36
- 36
37
- 37 // Priority 2: Pre-calculated urlPrefix
38
- 38 if (context?.urlPrefix) {
39
- 39 return context.urlPrefix
40
- 40 }
41
- 41
42
- 42 // Priority 3: Calculate from request if provided
43
- 43 if (req) {
44
- 44 const protocol = req.get?.('x-forwarded-proto') || req.protocol || 'http'
45
- 45 const host = req.get?.('x-forwarded-host') || req.get?.('host')
46
- 46 const mountPath = scope?.vars?.transport?.mountPath || ''
47
- 47
48
- 48 if (host) {
49
- 49 return `${protocol}://${host}${mountPath}`
50
- 50 }
51
- 51 }
52
- 52
53
- 53 // Priority 4: Fallback to mount path
54
- 54 return scope?.vars?.transport?.mountPath || ''
55
- 55 }
56
- 56
57
- 57 /**
58
- 58 * Build a complete resource URL
59
- 59 *
60
- 60 * @param {Object} context - Request context
61
- 61 * @param {Object} scope - Scope containing transport vars
62
- 62 * @param {string} scopeName - Resource type name
63
- 63 * @param {string} id - Resource ID
64
- 64 * @returns {string} Complete resource URL
65
- 65 *
66
- 66 * @example
67
- 67 * const url = buildResourceUrl(context, scope, 'books', '123');
68
- 68 * // Returns: "https://api.example.com/api/books/123"
69
- 69 */
70
- 70 export function buildResourceUrl (context, scope, scopeName, id) {
71
- 71 const urlPrefix = getUrlPrefix(context, scope)
72
- 72 return `${urlPrefix}/${scopeName}/${id}`
73
- 73 }
74
- 74
75
- 75 /**
76
- 76 * Build a relationship URL
77
- 77 *
78
- 78 * @param {Object} context - Request context
79
- 79 * @param {Object} scope - Scope containing transport vars
80
- 80 * @param {string} scopeName - Resource type name
81
- 81 * @param {string} id - Resource ID
82
- 82 * @param {string} relationshipName - Name of the relationship
83
- 83 * @param {boolean} isRelationshipEndpoint - If true, builds /relationships/ URL
84
- 84 * @returns {string} Complete relationship URL
85
- 85 *
86
- 86 * @example
87
- 87 * // For relationship linkage endpoint
88
- 88 * buildRelationshipUrl(context, scope, 'books', '1', 'author', true);
89
- 89 * // Returns: "https://api.example.com/api/books/1/relationships/author"
90
- 90 *
91
- 91 * // For related resource endpoint
92
- 92 * buildRelationshipUrl(context, scope, 'books', '1', 'author', false);
93
- 93 * // Returns: "https://api.example.com/api/books/1/author"
94
- 94 */
95
- 95 export function buildRelationshipUrl (context, scope, scopeName, id, relationshipName, isRelationshipEndpoint = false) {
96
- 96 const urlPrefix = getUrlPrefix(context, scope)
97
- 97 if (isRelationshipEndpoint) {
98
- 98 return `${urlPrefix}/${scopeName}/${id}/relationships/${relationshipName}`
99
- 99 }
100
- 100 return `${urlPrefix}/${scopeName}/${id}/${relationshipName}`
101
- 101 }
102
- 102
103
- 103 /**
104
- 104 * Build a collection URL with optional query string
105
- 105 *
106
- 106 * @param {Object} context - Request context
107
- 107 * @param {Object} scope - Scope containing transport vars
108
- 108 * @param {string} scopeName - Resource type name
109
- 109 * @param {string} queryString - Optional query string (with leading ?)
110
- 110 * @returns {string} Complete collection URL
111
- 111 *
112
- 112 * @example
113
- 113 * buildCollectionUrl(context, scope, 'books', '?page[size]=10');
114
- 114 * // Returns: "https://api.example.com/api/books?page[size]=10"
115
- 115 */
116
- 116 export function buildCollectionUrl (context, scope, scopeName, queryString = '') {
117
- 117 const urlPrefix = getUrlPrefix(context, scope)
118
- 118 return `${urlPrefix}/${scopeName}${queryString}`
119
- 119 }
@@ -1,160 +0,0 @@
1
- 1 const passthrough = (value) => value
2
- 2 const identityTranslate = (_field, value) => value
3
- 3 const identityScope = (query) => query
4
- 4
5
- 5 const selectColumnsOnBuilder = (builder, columns) => {
6
- 6 if (!columns) return builder
7
- 7 if (Array.isArray(columns)) {
8
- 8 columns.forEach((column) => builder.select(column))
9
- 9 } else if (typeof columns === 'object') {
10
- 10 builder.select(columns)
11
- 11 }
12
- 12 return builder
13
- 13 }
14
- 14
15
- 15 const normalizeArray = (value, normalizeFn) => value.map((entry) => normalizeFn(entry))
16
- 16
17
- 17 const normalizeBelongsToValue = (value) => {
18
- 18 if (value === null || value === undefined) return value
19
- 19 if (Array.isArray(value)) {
20
- 20 return normalizeArray(value, normalizeBelongsToValue)
21
- 21 }
22
- 22 return String(value)
23
- 23 }
24
- 24
25
- 25 const baseAdapter = ({
26
- 26 knex,
27
- 27 tableName,
28
- 28 idColumn,
29
- 29 translateColumn,
30
- 30 translateFilterValue = identityTranslate,
31
- 31 applyResourceScope = identityScope,
32
- 32 isCanonical = false,
33
- 33 }) => {
34
- 34 const buildBaseQuery = ({ transaction } = {}) => {
35
- 35 const query = (transaction || knex)(tableName)
36
- 36 return applyResourceScope(query)
37
- 37 }
38
- 38
39
- 39 return {
40
- 40 isCanonical: () => isCanonical,
41
- 41 getTableName: () => tableName,
42
- 42 getIdColumn: () => idColumn,
43
- 43 translateColumn,
44
- 44 translateFilterValue,
45
- 45 applyResourceScope,
46
- 46 buildBaseQuery,
47
- 47 selectColumns: (builder, columns) => selectColumnsOnBuilder(builder, columns),
48
- 48 }
49
- 49 }
50
- 50
51
- 51 const createLegacyAdapter = ({ knex, schemaInfo }) => {
52
- 52 const tableName = schemaInfo.tableName
53
- 53 const idColumn = schemaInfo.idProperty || 'id'
54
- 54
55
- 55 const translateColumn = (column) => column
56
- 56
57
- 57 return baseAdapter({
58
- 58 knex,
59
- 59 tableName,
60
- 60 idColumn,
61
- 61 translateColumn,
62
- 62 translateFilterValue: identityTranslate,
63
- 63 applyResourceScope: identityScope,
64
- 64 isCanonical: false,
65
- 65 })
66
- 66 }
67
- 67
68
- 68 const createCanonicalAdapter = ({ knex, schemaInfo }) => {
69
- 69 const descriptor = schemaInfo.descriptor || {}
70
- 70 const canonical = descriptor.canonical || {}
71
- 71 const canonicalFieldMap = descriptor.canonicalFieldMap || {}
72
- 72 const fieldsInfo = descriptor.fields || {}
73
- 73 const belongsToInfo = descriptor.belongsTo || {}
74
- 74
75
- 75 const translateColumn = (field) => {
76
- 76 if (!field) return field
77
- 77 if (field === 'id') return 'id'
78
- 78
79
- 79 const canonicalEntry = canonicalFieldMap[field]
80
- 80 if (typeof canonicalEntry === 'string') {
81
- 81 return canonicalEntry
82
- 82 }
83
- 83 if (canonicalEntry && typeof canonicalEntry === 'object') {
84
- 84 if (canonicalEntry.slot) return canonicalEntry.slot
85
- 85 if (canonicalEntry.slotColumn) return canonicalEntry.slotColumn
86
- 86 if (canonicalEntry.idSlot) return canonicalEntry.idSlot
87
- 87 if (canonicalEntry.typeSlot && field.endsWith('_type')) return canonicalEntry.typeSlot
88
- 88 }
89
- 89
90
- 90 if (!canonicalEntry && field.endsWith('_id')) {
91
- 91 const alias = field.slice(0, -3)
92
- 92 const aliasEntry = canonicalFieldMap[alias]
93
- 93 if (typeof aliasEntry === 'string') return aliasEntry
94
- 94 if (aliasEntry?.idSlot) return aliasEntry.idSlot
95
- 95 }
96
- 96
97
- 97 if (!canonicalEntry && field.endsWith('_type')) {
98
- 98 const alias = field.slice(0, -5)
99
- 99 const aliasEntry = canonicalFieldMap[alias]
100
- 100 if (aliasEntry?.typeSlot) return aliasEntry.typeSlot
101
- 101 }
102
- 102
103
- 103 const fieldInfo = fieldsInfo[field]
104
- 104 if (fieldInfo?.slot) return fieldInfo.slot
105
- 105
106
- 106 const belongsInfo = belongsToInfo[field]
107
- 107 if (belongsInfo?.idColumn) return belongsInfo.idColumn
108
- 108
109
- 109 return field
110
- 110 }
111
- 111
112
- 112 const translateFilterValue = (field, value) => {
113
- 113 if (value === null || value === undefined) return value
114
- 114
115
- 115 const searchField = schemaInfo.searchSchemaStructure?.[field]
116
- 116 const schemaField = schemaInfo.schemaStructure?.[field]
117
- 117 const isRelationship = Boolean(
118
- 118 searchField?.isRelationship ||
119
- 119 schemaField?.belongsTo ||
120
- 120 schemaField?.belongsToPolymorphic
121
- 121 )
122
- 122
123
- 123 if (!isRelationship) {
124
- 124 if (Array.isArray(value)) {
125
- 125 return normalizeArray(value, passthrough)
126
- 126 }
127
- 127 return value
128
- 128 }
129
- 129
130
- 130 return normalizeBelongsToValue(value)
131
- 131 }
132
- 132
133
- 133 const applyResourceScope = (query) => {
134
- 134 return query
135
- 135 .where(canonical.tenantColumn, descriptor.tenant)
136
- 136 .where(canonical.resourceColumn, descriptor.resource)
137
- 137 }
138
- 138
139
- 139 return baseAdapter({
140
- 140 knex,
141
- 141 tableName: canonical.tableName,
142
- 142 idColumn: 'id',
143
- 143 translateColumn,
144
- 144 translateFilterValue,
145
- 145 applyResourceScope,
146
- 146 isCanonical: true,
147
- 147 })
148
- 148 }
149
- 149
150
- 150 export const createStorageAdapter = ({ knex, schemaInfo }) => {
151
- 151 if (!schemaInfo) {
152
- 152 throw new Error('createStorageAdapter requires schemaInfo')
153
- 153 }
154
- 154
155
- 155 if (schemaInfo?.descriptor?.canonical?.tableName) {
156
- 156 return createCanonicalAdapter({ knex, schemaInfo })
157
- 157 }
158
- 158
159
- 159 return createLegacyAdapter({ knex, schemaInfo })
160
- 160 }