json-rest-api 1.0.22 → 1.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/CLAUDE.md +1 -2
  2. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
  3. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
  4. package/docs/API.md +51 -14
  5. package/docs/COMPARISON.md +2 -2
  6. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +28 -1
  7. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
  8. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
  9. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
  10. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
  11. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
  12. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
  13. package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
  14. package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
  15. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +250 -0
  16. package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
  17. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
  18. package/docs/GUIDE/index.md +18 -9
  19. package/docs/QUICKSTART.md +5 -1
  20. package/docs/index.md +1 -1
  21. package/index.js +3 -2
  22. package/package.json +2 -2
  23. package/plugins/core/connectors/express-plugin.js +97 -184
  24. package/plugins/core/connectors/fastify-plugin.js +206 -0
  25. package/plugins/core/connectors/lib/connector-core.js +195 -0
  26. package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
  27. package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
  28. package/plugins/core/lib/dbIntrospection.js +1078 -0
  29. package/plugins/core/lib/dbTablesOperations.js +1104 -160
  30. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -2
  31. package/plugins/core/lib/querying/knex-process-includes.js +3 -4
  32. package/plugins/core/lib/querying/knex-query-helpers.js +17 -71
  33. package/plugins/core/lib/querying/knex-relationship-includes.js +466 -206
  34. package/plugins/core/lib/querying/query-builder-utils.js +19 -0
  35. package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
  36. package/plugins/core/lib/querying/sort-helpers.js +33 -0
  37. package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
  38. package/plugins/core/lib/querying/url-helpers.js +8 -2
  39. package/plugins/core/lib/querying-writing/compile-schemas.js +12 -1
  40. package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -13
  41. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
  42. package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
  43. package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
  44. package/plugins/core/lib/storage/canonical-storage-mapping.js +113 -0
  45. package/plugins/core/lib/storage/storage-adapter.js +79 -2
  46. package/plugins/core/lib/storage/storage-mapping.js +204 -0
  47. package/plugins/core/rest-api-anyapi-knex-plugin.js +355 -228
  48. package/plugins/core/rest-api-autofilter-plugin.js +414 -0
  49. package/plugins/core/rest-api-knex-plugin.js +249 -211
  50. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
  51. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
  52. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
  53. package/plugins/core/rest-api-plugin-methods/common.js +121 -83
  54. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
  55. package/plugins/core/rest-api-plugin-methods/get.js +23 -14
  56. package/plugins/core/rest-api-plugin-methods/patch.js +28 -12
  57. package/plugins/core/rest-api-plugin-methods/post.js +13 -8
  58. package/plugins/core/rest-api-plugin-methods/put.js +45 -21
  59. package/plugins/core/rest-api-plugin-methods/query.js +15 -53
  60. package/plugins/core/rest-api-plugin.js +2 -0
  61. package/plugins/core/rest-api-positioning-plugin.js +1 -1
  62. package/plugins/core/rest-api-query-projections-plugin.js +130 -0
  63. package/plugins/core/socketio-plugin.js +1 -3
  64. package/tests/autofilter.test.js +463 -0
  65. package/tests/custom-id-simplified-post.test.js +75 -0
  66. package/tests/custom-idproperty-relationships.test.js +33 -0
  67. package/tests/db-introspection.test.js +518 -0
  68. package/tests/db-table-operations.test.js +781 -0
  69. package/tests/fastify-plugin.test.js +141 -0
  70. package/tests/field-getters.test.js +1 -2
  71. package/tests/field-setters.test.js +0 -4
  72. package/tests/fixtures/api-configs.js +188 -219
  73. package/tests/helpers/fake-fastify.js +99 -0
  74. package/tests/http-connectors-parity.test.js +235 -0
  75. package/tests/projected-fields.test.js +319 -0
  76. package/tests/relationships.test.js +3 -3
  77. package/tests/searchschema-knex-behavior.test.js +187 -0
  78. package/tests/searchschema-merge.test.js +21 -69
  79. package/tests/storage-mapping.test.js +210 -0
  80. package/audit/00-charter.md +0 -103
  81. package/audit/01-scope-map.md +0 -84
  82. package/audit/02-coverage-matrix.csv +0 -174
  83. package/audit/03-invariants.md +0 -123
  84. package/audit/04-standards-rubric.md +0 -56
  85. package/audit/05-competitor-rubric.md +0 -77
  86. package/audit/06-findings.md +0 -160
  87. package/audit/07-risk-register.csv +0 -10
  88. package/audit/08-remediation-roadmap.md +0 -177
  89. package/audit/09-signoff-check.md +0 -23
  90. package/audit/10-final-synthesis.md +0 -29
  91. package/audit/evidence/code/all-files-json-rest-api.txt +0 -168
  92. package/audit/evidence/code/all-files-json-rest-schema.txt +0 -12
  93. package/audit/evidence/code/boundary-leak-checks.txt +0 -72
  94. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__bulk-operations-plugin.js.txt +0 -480
  95. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__express-plugin.js.txt +0 -532
  96. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__busboy-detector.js.txt +0 -175
  97. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__formidable-detector.js.txt +0 -138
  98. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__request-helpers.js.txt +0 -222
  99. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt +0 -342
  100. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__dbTablesOperations.js.txt +0 -304
  101. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__fractional-positioning.js.txt +0 -188
  102. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__compile-schemas.js.txt +0 -336
  103. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__connectors-query-parser.js.txt +0 -165
  104. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-capabilities.js.txt +0 -265
  105. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-value-normalizers.js.txt +0 -369
  106. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__default-data-helpers.js.txt +0 -72
  107. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__field-utils.js.txt +0 -139
  108. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-constants.js.txt +0 -45
  109. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-field-helpers.js.txt +0 -396
  110. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-json-api-transformers.js.txt +0 -162
  111. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__payload-validators.js.txt +0 -1314
  112. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__schema-helpers.js.txt +0 -488
  113. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__scope-validations.js.txt +0 -484
  114. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__simplified-helpers.js.txt +0 -857
  115. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-cross-table-search.js.txt +0 -565
  116. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-json-api-transformers-querying.js.txt +0 -485
  117. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-pagination-helpers.js.txt +0 -560
  118. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-process-includes.js.txt +0 -167
  119. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers-base.js.txt +0 -109
  120. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers.js.txt +0 -1064
  121. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-relationship-includes.js.txt +0 -1983
  122. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-window-queries.js.txt +0 -328
  123. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__url-helpers.js.txt +0 -119
  124. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__storage__storage-adapter.js.txt +0 -160
  125. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__many-to-many-manipulations.js.txt +0 -300
  126. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__relationship-processor.js.txt +0 -245
  127. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-access.js.txt +0 -459
  128. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-cors-plugin.js.txt +0 -237
  129. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-knex-plugin.js.txt +0 -1135
  130. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-multihome-plugin.js.txt +0 -314
  131. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__compile-resource-schemas.js.txt +0 -7
  132. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-relationship-routes.js.txt +0 -71
  133. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-scope-routes.js.txt +0 -102
  134. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__turn-scope-init-into-vars.js.txt +0 -49
  135. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__validate-include-configurations.js.txt +0 -39
  136. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__add-route.js.txt +0 -23
  137. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__check-permissions.js.txt +0 -13
  138. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__common.js.txt +0 -639
  139. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete-relationship.js.txt +0 -117
  140. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete.js.txt +0 -104
  141. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__enrich-attributes.js.txt +0 -199
  142. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-related.js.txt +0 -353
  143. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-relationship.js.txt +0 -61
  144. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get.js.txt +0 -184
  145. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch-relationship.js.txt +0 -55
  146. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch.js.txt +0 -191
  147. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post-relationship.js.txt +0 -110
  148. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post.js.txt +0 -168
  149. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__put.js.txt +0 -253
  150. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__query.js.txt +0 -207
  151. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__release.js.txt +0 -12
  152. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin.js.txt +0 -165
  153. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-positioning-plugin.js.txt +0 -668
  154. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__socketio-plugin.js.txt +0 -621
  155. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__local-storage.js.txt +0 -218
  156. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__s3-storage.js.txt +0 -124
  157. package/audit/evidence/code/critical-pass1/json-rest-schema__src__CorePlugin.js.txt +0 -269
  158. package/audit/evidence/code/critical-pass1/json-rest-schema__src__Schema.js.txt +0 -242
  159. package/audit/evidence/code/critical-pass1/json-rest-schema__src__index.js.txt +0 -77
  160. package/audit/evidence/code/critical-pass1-risk-keywords.txt +0 -238
  161. package/audit/evidence/code/critical-pass1-summary.csv +0 -67
  162. package/audit/evidence/code/critical-pass2-summary.csv +0 -67
  163. package/audit/evidence/code/early-silent-zones-no-test-token.txt +0 -57
  164. package/audit/evidence/code/error-model-signals.txt +0 -98
  165. package/audit/evidence/code/git-files-json-rest-api.txt +0 -171
  166. package/audit/evidence/code/git-files-json-rest-schema.txt +0 -27
  167. package/audit/evidence/code/hook-flow-signals.txt +0 -282
  168. package/audit/evidence/code/idproperty-signals.txt +0 -157
  169. package/audit/evidence/code/import-cycles-non-anyapi.txt +0 -3
  170. package/audit/evidence/code/import-hotspots-non-anyapi.txt +0 -96
  171. package/audit/evidence/code/imports-json-rest-api.txt +0 -354
  172. package/audit/evidence/code/imports-json-rest-schema.txt +0 -15
  173. package/audit/evidence/code/in-scope-files-json-rest-api.txt +0 -160
  174. package/audit/evidence/code/in-scope-files-json-rest-schema.txt +0 -13
  175. package/audit/evidence/code/non-anyapi-core-files.txt +0 -101
  176. package/audit/evidence/code/permission-signals.txt +0 -193
  177. package/audit/evidence/code/query-include-signals.txt +0 -687
  178. package/audit/evidence/code/transaction-signals.txt +0 -28
  179. package/audit/evidence/code/validation-signals.txt +0 -650
  180. package/audit/evidence/code/write-path-signals.txt +0 -61
  181. package/audit/evidence/docs/docs-claims-signals.txt +0 -1075
  182. package/audit/evidence/docs/schema-docs-claims-signals.txt +0 -179
  183. package/audit/evidence/external/corpus-freeze.md +0 -21
  184. package/audit/evidence/external/npm-competitor-metadata.txt +0 -70
  185. package/audit/evidence/external/npm-download-signals.txt +0 -23
  186. package/audit/evidence/external/npm-jsonapi-server.txt +0 -8
  187. package/audit/evidence/external/npm-modern-jsonapi-metadata.txt +0 -38
  188. package/audit/evidence/external/readme-fortune-signals.txt +0 -26
  189. package/audit/evidence/external/readme-fortune.md +0 -165
  190. package/audit/evidence/external/readme-json-api-serializer-signals.txt +0 -80
  191. package/audit/evidence/external/readme-json-api-serializer.md +0 -690
  192. package/audit/evidence/external/readme-jsonapi-serde-server-signals.txt +0 -0
  193. package/audit/evidence/external/readme-jsonapi-serde-server.md +0 -22
  194. package/audit/evidence/external/readme-jsonapi-serializer-signals.txt +0 -63
  195. package/audit/evidence/external/readme-jsonapi-serializer.md +0 -315
  196. package/audit/evidence/external/readme-jsonapi-server-signals.txt +0 -19
  197. package/audit/evidence/external/readme-jsonapi-server.md +0 -104
  198. package/audit/evidence/external/readme-nestjs-json-api-signals.txt +0 -4
  199. package/audit/evidence/external/readme-nestjs-json-api.md +0 -94
  200. package/audit/evidence/external/readme-yaser-json-api-nestjs-signals.txt +0 -47
  201. package/audit/evidence/external/readme-yaser-json-api-nestjs.md +0 -447
  202. package/audit/evidence/external/standard-jsonapi-snippets.txt +0 -84
  203. package/audit/evidence/external/standard-rfc-snippets.txt +0 -168
  204. package/audit/evidence/external/standards-sources.txt +0 -6
  205. package/audit/evidence/runtime/baseline-commits.txt +0 -2
  206. package/audit/evidence/runtime/db-adapter-assumptions.txt +0 -3
  207. package/audit/evidence/runtime/environment.txt +0 -4
  208. package/audit/evidence/runtime/invariant-runtime-mapping.md +0 -38
  209. package/audit/evidence/runtime/probe-patch-relationship-error.exitcode +0 -1
  210. package/audit/evidence/runtime/probe-patch-relationship-error.log +0 -76
  211. package/audit/evidence/runtime/probe-patch-relationship-error.mjs +0 -67
  212. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.exitcode +0 -1
  213. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.log +0 -62
  214. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.mjs +0 -47
  215. package/audit/evidence/runtime/test-exitcodes.txt +0 -4
  216. package/audit/evidence/runtime/test-summary.md +0 -107
  217. package/audit/evidence/runtime/trace-anchors.txt +0 -61
  218. package/audit/evidence/tests/access-multitenancy-test-signals.txt +0 -125
  219. package/audit/evidence/tests/failing-tests-summary.txt +0 -25
  220. package/audit/evidence/tests/npm-test-json-rest-api.exitcode +0 -1
  221. package/audit/evidence/tests/npm-test-json-rest-api.log +0 -5885
  222. package/audit/evidence/tests/npm-test-json-rest-schema.exitcode +0 -1
  223. package/audit/evidence/tests/npm-test-json-rest-schema.log +0 -356
  224. package/audit/evidence/tests/query-include-test-signals.txt +0 -948
  225. package/audit/evidence/tests/relationship-test-signals.txt +0 -223
  226. package/audit/evidence/tests/skipped-tests.txt +0 -10
  227. package/audit/evidence/tests/test-suite-list-non-anyapi.txt +0 -30
  228. package/audit/evidence/tests/test-suite-list.txt +0 -32
  229. package/audit/subsystems/docs-drift.md +0 -49
  230. package/audit/subsystems/pass2-cross-review.md +0 -131
  231. package/audit/subsystems/plugins-access.md +0 -51
  232. package/audit/subsystems/plugins-bulk.md +0 -50
  233. package/audit/subsystems/plugins-cors.md +0 -48
  234. package/audit/subsystems/plugins-file.md +0 -53
  235. package/audit/subsystems/plugins-multihome.md +0 -50
  236. package/audit/subsystems/plugins-positioning.md +0 -54
  237. package/audit/subsystems/plugins-socketio.md +0 -50
  238. package/audit/subsystems/query-engine.md +0 -61
  239. package/audit/subsystems/relationships.md +0 -55
  240. package/audit/subsystems/rest-orchestration.md +0 -66
  241. package/audit/subsystems/schema-core.md +0 -64
  242. package/audit/subsystems/storage-knex.md +0 -57
  243. package/audit/subsystems/tests.md +0 -48
  244. package/audit/subsystems/transport-express.md +0 -55
  245. package/audit/traces/trace-read-get.md +0 -22
  246. package/audit/traces/trace-read-query.md +0 -25
  247. package/audit/traces/trace-relationship-endpoints.md +0 -31
  248. package/audit/traces/trace-write-delete.md +0 -17
  249. package/audit/traces/trace-write-patch.md +0 -26
  250. package/audit/traces/trace-write-post.md +0 -22
  251. package/audit/traces/trace-write-put.md +0 -23
  252. package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
  253. package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
  254. package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
  255. package/plugins/core/rest-api-access.js +0 -459
  256. package/plugins/core/rest-api-multihome-plugin.js +0 -314
  257. package/tests/multihome.test.js +0 -553
  258. package/tests/multihome.test.js.bak +0 -550
  259. package/tests/ownership-optout.test.js +0 -76
  260. package/tests/rest-api-access.test.js +0 -206
@@ -1,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 }