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
@@ -0,0 +1,19 @@
1
+ export const unwrapQueryBuilderState = (value, fallback = null) => {
2
+ let current = value
3
+ const seen = new Set()
4
+
5
+ while (
6
+ current &&
7
+ typeof current === 'object' &&
8
+ !Array.isArray(current) &&
9
+ Object.hasOwn(current, 'query') &&
10
+ current.query &&
11
+ current.query !== current &&
12
+ !seen.has(current)
13
+ ) {
14
+ seen.add(current)
15
+ current = current.query
16
+ }
17
+
18
+ return current || fallback
19
+ }
@@ -0,0 +1,135 @@
1
+ import { normalizeStableSort, parseSortEntry } from './sort-helpers.js'
2
+
3
+ export const getEffectiveSortableFields = (vars = {}) => {
4
+ const baseSortableFields = Array.isArray(vars.sortableFields)
5
+ ? vars.sortableFields
6
+ : []
7
+ const queryFieldSortableFields = Object.entries(vars.queryFields || {})
8
+ .filter(([, fieldDef]) => fieldDef?.sortable === true)
9
+ .map(([fieldName]) => fieldName)
10
+
11
+ return Array.from(new Set([...baseSortableFields, ...queryFieldSortableFields]))
12
+ }
13
+
14
+ export const buildEffectiveSortList = (sort, { defaultSort, idField = 'id' } = {}) => {
15
+ let sortList = Array.isArray(sort) ? [...sort] : (sort ? [sort] : [])
16
+
17
+ if (sortList.length === 0 && defaultSort) {
18
+ if (Array.isArray(defaultSort)) {
19
+ sortList = [...defaultSort]
20
+ } else if (typeof defaultSort === 'string') {
21
+ sortList = [defaultSort]
22
+ } else if (typeof defaultSort === 'object') {
23
+ const field = defaultSort.field || defaultSort.column || idField
24
+ const direction = (defaultSort.direction || '').toLowerCase() === 'desc' ? '-' : ''
25
+ sortList = [`${direction}${field}`]
26
+ }
27
+ }
28
+
29
+ return normalizeStableSort(sortList.length > 0 ? sortList : [idField], { idField })
30
+ }
31
+
32
+ export const applyQueryFieldOrder = (query, queryFieldRuntime, direction) => {
33
+ query.orderByRaw(`(${queryFieldRuntime.sql}) ${direction}`, queryFieldRuntime.bindings)
34
+ }
35
+
36
+ export const applyQueryFieldPredicate = (builder, queryFieldRuntime, operator, value) => {
37
+ builder.whereRaw(`(${queryFieldRuntime.sql}) ${operator} ?`, [...queryFieldRuntime.bindings, value])
38
+ }
39
+
40
+ export const applySortDescriptorPredicate = (builder, descriptor, operator, value, applyPlainPredicate) => {
41
+ if (descriptor?.queryFieldRuntime) {
42
+ return applyQueryFieldPredicate(builder, descriptor.queryFieldRuntime, operator, value)
43
+ }
44
+
45
+ return applyPlainPredicate(builder, descriptor, operator, value)
46
+ }
47
+
48
+ export const buildCursorPredicateChains = (
49
+ descriptors,
50
+ cursorValues,
51
+ operatorSelector,
52
+ { onMissingValue } = {}
53
+ ) => {
54
+ if (!Array.isArray(descriptors) || descriptors.length === 0) {
55
+ return []
56
+ }
57
+
58
+ const predicateChains = []
59
+
60
+ descriptors.forEach((descriptor, index) => {
61
+ const cursorValue = cursorValues?.[descriptor.field]
62
+
63
+ if (cursorValue === undefined) {
64
+ onMissingValue?.(descriptor.field)
65
+ return
66
+ }
67
+
68
+ const chain = []
69
+ for (let i = 0; i < index; i += 1) {
70
+ const previousDescriptor = descriptors[i]
71
+ const previousValue = cursorValues?.[previousDescriptor.field]
72
+
73
+ if (previousValue === undefined) {
74
+ onMissingValue?.(previousDescriptor.field)
75
+ return
76
+ }
77
+
78
+ chain.push({
79
+ descriptor: previousDescriptor,
80
+ operator: '=',
81
+ value: previousValue
82
+ })
83
+ }
84
+
85
+ chain.push({
86
+ descriptor,
87
+ operator: operatorSelector(descriptor.direction),
88
+ value: cursorValue
89
+ })
90
+
91
+ predicateChains.push(chain)
92
+ })
93
+
94
+ return predicateChains
95
+ }
96
+
97
+ export const applyCursorPredicate = (
98
+ query,
99
+ descriptors,
100
+ cursorValues,
101
+ operatorSelector,
102
+ applyPlainPredicate,
103
+ { onMissingValue } = {}
104
+ ) => {
105
+ const predicateChains = buildCursorPredicateChains(
106
+ descriptors,
107
+ cursorValues,
108
+ operatorSelector,
109
+ { onMissingValue }
110
+ )
111
+
112
+ if (predicateChains.length === 0) {
113
+ return false
114
+ }
115
+
116
+ query.where(function () {
117
+ predicateChains.forEach((chain) => {
118
+ this.orWhere(function () {
119
+ chain.forEach(({ descriptor, operator, value }) => {
120
+ applySortDescriptorPredicate(
121
+ this,
122
+ descriptor,
123
+ operator,
124
+ value,
125
+ applyPlainPredicate
126
+ )
127
+ })
128
+ })
129
+ })
130
+ })
131
+
132
+ return true
133
+ }
134
+
135
+ export { parseSortEntry }
@@ -0,0 +1,33 @@
1
+ export function normalizeStableSort (sort = [], { idField = 'id' } = {}) {
2
+ const sortList = Array.isArray(sort)
3
+ ? sort.filter((entry) => typeof entry === 'string' && entry.trim())
4
+ : []
5
+
6
+ if (sortList.length === 0) {
7
+ return [idField]
8
+ }
9
+
10
+ const hasId = sortList.some((entry) => {
11
+ const fieldName = entry.startsWith('-') ? entry.slice(1) : entry
12
+ return fieldName === idField
13
+ })
14
+
15
+ if (!hasId) {
16
+ sortList.push(idField)
17
+ }
18
+
19
+ return sortList
20
+ }
21
+
22
+ export function parseSortEntry (sortEntry) {
23
+ const desc = typeof sortEntry === 'string' && sortEntry.startsWith('-')
24
+ const field = desc ? sortEntry.slice(1) : sortEntry
25
+
26
+ return {
27
+ raw: sortEntry,
28
+ field,
29
+ desc,
30
+ direction: desc ? 'desc' : 'asc',
31
+ sqlDirection: desc ? 'DESC' : 'ASC'
32
+ }
33
+ }
@@ -0,0 +1,51 @@
1
+ export const createStorageAdapterUtilities = (hookParams, { getStorageAdapter } = {}) => {
2
+ const context = hookParams?.context || {}
3
+ const storageCache = new Map()
4
+
5
+ const fetchStorageAdapter = (scopeName) => {
6
+ if (!scopeName) return null
7
+ if (storageCache.has(scopeName)) return storageCache.get(scopeName)
8
+
9
+ let adapter = null
10
+ if (scopeName === context?.knexQuery?.scopeName) {
11
+ adapter = context.storageAdapter ||
12
+ context.knexQuery?.storageAdapter ||
13
+ (typeof getStorageAdapter === 'function' ? getStorageAdapter(scopeName) : null)
14
+ if (adapter && !context.storageAdapter) {
15
+ context.storageAdapter = adapter
16
+ }
17
+ } else {
18
+ adapter = typeof getStorageAdapter === 'function' ? getStorageAdapter(scopeName) : null
19
+ }
20
+
21
+ storageCache.set(scopeName, adapter)
22
+ return adapter
23
+ }
24
+
25
+ const defaultAliasForScope = (scopeName) => {
26
+ if (scopeName === context?.knexQuery?.scopeName) {
27
+ return context.knexQuery?.tableName || scopeName
28
+ }
29
+ return scopeName
30
+ }
31
+
32
+ const translateColumn = (scopeName, field, alias = defaultAliasForScope(scopeName)) => {
33
+ const adapter = fetchStorageAdapter(scopeName)
34
+ const translated = adapter?.translateColumn?.(field) ?? field
35
+ if (!alias) return translated
36
+ return `${alias}.${translated}`
37
+ }
38
+
39
+ const translateFilterValue = (scopeName, field, value) => {
40
+ const adapter = fetchStorageAdapter(scopeName)
41
+ if (!adapter?.translateFilterValue) return value
42
+ return adapter.translateFilterValue(field, value)
43
+ }
44
+
45
+ return {
46
+ fetchStorageAdapter,
47
+ defaultAliasForScope,
48
+ translateColumn,
49
+ translateFilterValue,
50
+ }
51
+ }
@@ -41,8 +41,14 @@ export function getUrlPrefix (context, scope, req = null) {
41
41
 
42
42
  // Priority 3: Calculate from request if provided
43
43
  if (req) {
44
- const protocol = req.get?.('x-forwarded-proto') || req.protocol || 'http'
45
- const host = req.get?.('x-forwarded-host') || req.get?.('host')
44
+ const protocol = req.get?.('x-forwarded-proto') ||
45
+ req.headers?.['x-forwarded-proto'] ||
46
+ req.protocol ||
47
+ 'http'
48
+ const host = req.get?.('x-forwarded-host') ||
49
+ req.headers?.['x-forwarded-host'] ||
50
+ req.get?.('host') ||
51
+ req.headers?.host
46
52
  const mountPath = scope?.vars?.transport?.mountPath || ''
47
53
 
48
54
  if (host) {
@@ -1,5 +1,6 @@
1
1
  import { createSchema } from 'json-rest-schema'
2
2
  import { ensureSearchFieldsAreIndexed, generateSearchSchemaFromSchema, sortFieldsByDependencies } from './schema-helpers.js'
3
+ import { buildStorageInfo } from '../storage/storage-mapping.js'
3
4
 
4
5
  /**
5
6
  * Compiles and enriches schemas for a resource scope
@@ -315,6 +316,12 @@ export async function compileSchemas (scope, deps) {
315
316
  }
316
317
  }
317
318
 
319
+ const idProperty = scope.scopeOptions.idProperty || scope.vars.idProperty || 'id'
320
+ const storageInfo = buildStorageInfo({
321
+ schemaStructure: schemaObject.structure,
322
+ idProperty
323
+ })
324
+
318
325
  // Cache everything
319
326
  scope.vars.schemaInfo = {
320
327
 
@@ -327,7 +334,11 @@ export async function compileSchemas (scope, deps) {
327
334
  computed: computedFields,
328
335
  schemaRelationships,
329
336
  tableName: scope.scopeOptions.tableName || scopeName,
330
- idProperty: scope.scopeOptions.idProperty || scope.vars.idProperty,
337
+ idProperty,
338
+ storageInfo,
339
+ indexes: Array.isArray(scope.scopeOptions.indexes) ? [...scope.scopeOptions.indexes] : [],
340
+ foreignKeys: Array.isArray(scope.scopeOptions.foreignKeys) ? [...scope.scopeOptions.foreignKeys] : [],
341
+ checkConstraints: Array.isArray(scope.scopeOptions.checkConstraints) ? [...scope.scopeOptions.checkConstraints] : [],
331
342
  fieldGetters,
332
343
  sortedGetterFields,
333
344
  fieldSetters,
@@ -1,3 +1,7 @@
1
+ import { buildQuerySelection } from '../querying/knex-query-helpers-base.js'
2
+ import { createSelectTranslator } from '../storage/storage-adapter.js'
3
+ import { buildQueryFieldRuntimes } from './query-field-helpers.js'
4
+
1
5
  /**
2
6
  * Checks if a field exists only in code, not in the database
3
7
  *
@@ -143,25 +147,21 @@ export const buildFieldSelection = async (scope, deps) => {
143
147
  const computedDependencies = new Set()
144
148
 
145
149
  // Extract values from scope
150
+ const scopeSchemaInfo = scope?.vars?.schemaInfo || {}
146
151
  const {
147
- vars: {
148
- schemaInfo: { schemaInstance, computed: computedFields = {}, schemaStructure }
149
- }
150
- } = scope
152
+ schemaInstance,
153
+ computed: computedFields = {}
154
+ } = scopeSchemaInfo
155
+ let schemaStructure = scopeSchemaInfo.schemaStructure
156
+ const queryFields = scope?.vars?.queryFields || {}
151
157
 
152
158
  // Extract values from deps
153
159
  const { context } = deps
154
160
  const scopeName = context.scopeName
155
161
  const requestedFields = context.queryParams?.fields?.[scopeName]
156
- const idProperty = context.schemaInfo.idProperty
157
162
 
158
- // Always include the ID field - required for JSON:API
159
- // Handle aliasing if idProperty is not 'id'
160
- if (idProperty !== 'id') {
161
- fieldsToSelect.add(`${idProperty} as id`)
162
- } else {
163
- fieldsToSelect.add('id')
164
- }
163
+ // Always include the logical ID field - the storage adapter translates it.
164
+ fieldsToSelect.add('id')
165
165
 
166
166
  // Handle both Schema objects and plain objects
167
167
  if (!schemaStructure) {
@@ -170,6 +170,8 @@ export const buildFieldSelection = async (scope, deps) => {
170
170
 
171
171
  // Get computed fields and virtual fields from schema
172
172
  const computedFieldNames = new Set(Object.keys(computedFields))
173
+ const queryFieldNames = new Set(Object.keys(queryFields))
174
+ const queryFieldsToSelect = new Set()
173
175
 
174
176
  // Find fields marked as virtual in the schema
175
177
  const virtualFieldNames = new Set()
@@ -194,6 +196,12 @@ export const buildFieldSelection = async (scope, deps) => {
194
196
  // Sparse fieldsets requested - only select specified fields
195
197
  // Example: ?fields[products]=name,price,profit_margin
196
198
  requested.forEach(field => {
199
+ if (queryFieldNames.has(field)) {
200
+ if (queryFields[field]?.hidden === true) return
201
+ queryFieldsToSelect.add(field)
202
+ return
203
+ }
204
+
197
205
  // Skip computed and virtual fields - they don't exist in database
198
206
  // Computed fields will be calculated later in enrichAttributes
199
207
  // Virtual fields are handled separately (from request input)
@@ -249,6 +257,7 @@ export const buildFieldSelection = async (scope, deps) => {
249
257
  }
250
258
  })
251
259
  }
260
+
252
261
  } else {
253
262
  // No sparse fieldsets - return all visible fields
254
263
  // This is the default behavior when no ?fields parameter is provided
@@ -289,6 +298,24 @@ export const buildFieldSelection = async (scope, deps) => {
289
298
  }
290
299
  }
291
300
  }
301
+
302
+ for (const [fieldName, fieldDef] of Object.entries(queryFields)) {
303
+ if (fieldDef.hidden === true || fieldDef.normallyHidden === true) continue
304
+ queryFieldsToSelect.add(fieldName)
305
+ }
306
+ }
307
+
308
+ const sortFields = Array.isArray(context.queryParams?.sort)
309
+ ? context.queryParams.sort
310
+ : []
311
+
312
+ for (const sortField of sortFields) {
313
+ if (typeof sortField !== 'string') continue
314
+ const normalizedField = sortField.startsWith('-') ? sortField.slice(1) : sortField
315
+
316
+ if (queryFieldNames.has(normalizedField)) {
317
+ queryFieldsToSelect.add(normalizedField)
318
+ }
292
319
  }
293
320
 
294
321
  // Always include foreign keys for relationships (unless hidden)
@@ -318,9 +345,52 @@ export const buildFieldSelection = async (scope, deps) => {
318
345
  // 3. enrichAttributes to remove dependencies from final response
319
346
  return {
320
347
  fieldsToSelect: Array.from(fieldsToSelect), // Fields to SELECT from database
348
+ queryFieldsToSelect: Array.from(queryFieldsToSelect),
321
349
  requestedFields: requested, // Fields explicitly requested by user
322
350
  computedDependencies: Array.from(computedDependencies), // Dependencies to remove from response
323
- idProperty // Pass idProperty for reference
351
+ idProperty: 'id' // Logical field name reference
352
+ }
353
+ }
354
+
355
+ export const applyFieldSelectionToQuery = async ({
356
+ query,
357
+ scope,
358
+ fieldSelectionInfo,
359
+ tableName,
360
+ useTablePrefix = false,
361
+ storageAdapter = null,
362
+ db = null,
363
+ context = null,
364
+ scopeName = ''
365
+ }) => {
366
+ const selectTranslator = createSelectTranslator(storageAdapter)
367
+
368
+ const selectedQuery = buildQuerySelection(
369
+ query,
370
+ tableName,
371
+ fieldSelectionInfo?.fieldsToSelect || [],
372
+ useTablePrefix,
373
+ selectTranslator ? { translateColumn: selectTranslator } : undefined
374
+ )
375
+
376
+ const queryFieldRuntimeByField = await buildQueryFieldRuntimes({
377
+ queryFieldNames: fieldSelectionInfo?.queryFieldsToSelect || [],
378
+ queryFields: scope?.vars?.queryFields || {},
379
+ schemaInfo: scope?.vars?.schemaInfo || {},
380
+ tableName,
381
+ storageAdapter,
382
+ db,
383
+ context,
384
+ scopeName
385
+ })
386
+
387
+ for (const [fieldName, runtime] of queryFieldRuntimeByField.entries()) {
388
+ selectedQuery.select({ [fieldName]: runtime.expression })
389
+ }
390
+
391
+ return {
392
+ query: selectedQuery,
393
+ queryFieldRuntimeByField
324
394
  }
325
395
  }
326
396
 
@@ -1,5 +1,6 @@
1
1
  import { toJsonApiRecord } from '../querying/knex-json-api-transformers-querying.js'
2
2
  import { getSchemaStructure } from './knex-constants.js'
3
+ import { translateRecordFromStorage } from '../storage/storage-mapping.js'
3
4
 
4
5
  /**
5
6
  * Converts database record to JSON:API format with belongsTo relationships
@@ -92,17 +93,17 @@ import { getSchemaStructure } from './knex-constants.js'
92
93
  export const toJsonApiRecordWithBelongsTo = (scope, record, scopeName) => {
93
94
  if (!record) return null
94
95
 
96
+ const schemaInfo = scope.vars.schemaInfo
97
+ const logicalRecord = translateRecordFromStorage(record, schemaInfo)
98
+
95
99
  // Get the basic JSON:API structure (without relationships)
96
- const jsonApiRecord = toJsonApiRecord(scope, record, scopeName)
100
+ const jsonApiRecord = toJsonApiRecord(scope, logicalRecord, scopeName)
97
101
 
98
102
  // Extract schema info from scope
99
103
  const {
100
- vars: {
101
- schemaInfo: { schemaInstance, schemaRelationships: relationships, idProperty }
102
- }
103
- } = scope
104
-
105
- const idField = idProperty || 'id'
104
+ schemaInstance,
105
+ schemaRelationships: relationships
106
+ } = schemaInfo
106
107
 
107
108
  // Get schema structure
108
109
  const schemaStructure = getSchemaStructure(schemaInstance)
@@ -113,13 +114,13 @@ export const toJsonApiRecordWithBelongsTo = (scope, record, scopeName) => {
113
114
  // Process regular belongsTo relationships from schema
114
115
  for (const [fieldName, fieldDef] of Object.entries(schemaStructure)) {
115
116
  if (fieldDef.belongsTo && fieldDef.as) {
116
- const foreignKeyValue = record[fieldName]
117
+ const fieldValue = logicalRecord[fieldName]
117
118
 
118
- if (foreignKeyValue !== null && foreignKeyValue !== undefined) {
119
+ if (fieldValue !== null && fieldValue !== undefined) {
119
120
  jsonApiRecord.relationships[fieldDef.as] = {
120
121
  data: {
121
122
  type: fieldDef.belongsTo,
122
- id: String(foreignKeyValue)
123
+ id: String(fieldValue)
123
124
  }
124
125
  }
125
126
  } else {
@@ -134,8 +135,8 @@ export const toJsonApiRecordWithBelongsTo = (scope, record, scopeName) => {
134
135
  // Process polymorphic belongsTo relationships
135
136
  Object.entries(relationships || {}).forEach(([relName, relDef]) => {
136
137
  if (relDef.belongsToPolymorphic) {
137
- const typeValue = record[relDef.typeField]
138
- const idValue = record[relDef.idField]
138
+ const typeValue = logicalRecord[relDef.typeField]
139
+ const idValue = logicalRecord[relDef.idField]
139
140
 
140
141
  if (typeValue && idValue) {
141
142
  jsonApiRecord.relationships[relName] = {
@@ -0,0 +1,88 @@
1
+ import { getStorageColumn } from '../storage/storage-mapping.js'
2
+
3
+ function qualifyColumnReference (columnName, tableName) {
4
+ if (!tableName || !columnName || columnName === '*' || String(columnName).includes('.')) {
5
+ return columnName
6
+ }
7
+
8
+ return `${tableName}.${columnName}`
9
+ }
10
+
11
+ function normalizeQueryFieldSql (expression) {
12
+ if (expression && typeof expression.toSQL === 'function') {
13
+ const compiled = expression.toSQL()
14
+ return {
15
+ sql: compiled.sql,
16
+ bindings: compiled.bindings || []
17
+ }
18
+ }
19
+
20
+ throw new Error('Query field expressions must return knex raw, knex ref, or a knex query builder.')
21
+ }
22
+
23
+ export async function buildQueryFieldRuntimes ({
24
+ queryFieldNames = [],
25
+ queryFields = {},
26
+ schemaInfo = {},
27
+ tableName = '',
28
+ storageAdapter = null,
29
+ db = null,
30
+ context = null,
31
+ scopeName = ''
32
+ } = {}) {
33
+ const runtimes = new Map()
34
+ const seen = new Set()
35
+
36
+ for (const fieldName of queryFieldNames) {
37
+ if (!fieldName || seen.has(fieldName)) {
38
+ continue
39
+ }
40
+ seen.add(fieldName)
41
+
42
+ const fieldDef = queryFields[fieldName]
43
+ if (!fieldDef) {
44
+ throw new Error(`Unknown query field '${fieldName}' requested for '${scopeName}'.`)
45
+ }
46
+
47
+ const column = (logicalField) => {
48
+ const translated = storageAdapter?.translateColumn?.(logicalField) || getStorageColumn(schemaInfo, logicalField)
49
+ return qualifyColumnReference(translated, tableName)
50
+ }
51
+
52
+ const selectResult = fieldDef.select({
53
+ knex: db,
54
+ db,
55
+ context,
56
+ scopeName,
57
+ tableName,
58
+ fieldName,
59
+ schemaInfo,
60
+ adapter: storageAdapter,
61
+ column,
62
+ ref: (logicalField) => db?.ref ? db.ref(column(logicalField)) : column(logicalField)
63
+ })
64
+
65
+ const expression = (
66
+ selectResult &&
67
+ typeof selectResult === 'object' &&
68
+ typeof selectResult.toSQL === 'function'
69
+ )
70
+ ? selectResult
71
+ : await selectResult
72
+
73
+ if (expression === undefined || expression === null) {
74
+ throw new Error(`Query field '${fieldName}' in scope '${scopeName}' returned an empty select expression.`)
75
+ }
76
+
77
+ const { sql, bindings } = normalizeQueryFieldSql(expression)
78
+ runtimes.set(fieldName, {
79
+ fieldName,
80
+ definition: fieldDef,
81
+ expression,
82
+ sql,
83
+ bindings
84
+ })
85
+ }
86
+
87
+ return runtimes
88
+ }