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
@@ -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, normalizeStorageConfig } from '../storage/storage-mapping.js'
3
4
 
4
5
  /**
5
6
  * Compiles and enriches schemas for a resource scope
@@ -154,7 +155,7 @@ export async function compileSchemas (scope, deps) {
154
155
  }
155
156
 
156
157
  // Default enrichment: Add type to belongsTo fields
157
- for (const [fieldName, fieldDef] of Object.entries(enrichedFields)) {
158
+ for (const [, fieldDef] of Object.entries(enrichedFields)) {
158
159
  if (fieldDef.belongsTo && !fieldDef.type) {
159
160
  fieldDef.type = 'id'
160
161
  }
@@ -173,7 +174,7 @@ export async function compileSchemas (scope, deps) {
173
174
 
174
175
  if (isProbablyPivot) {
175
176
  // Make all belongsTo fields searchable for pivot operations
176
- for (const [fieldName, fieldDef] of belongsToFields) {
177
+ for (const [, fieldDef] of belongsToFields) {
177
178
  if (!fieldDef.search) {
178
179
  fieldDef.search = true
179
180
  }
@@ -205,6 +206,7 @@ export async function compileSchemas (scope, deps) {
205
206
  scope.scopeOptions.searchSchema
206
207
  )
207
208
 
209
+ let searchSchemaObject
208
210
  if (rawSearchFields) {
209
211
  // Mark all search fields as indexed for database optimization.
210
212
  // This ensures that any field used for filtering will have a database index created,
@@ -223,9 +225,9 @@ export async function compileSchemas (scope, deps) {
223
225
  await runHooks('searchSchema:enrich', searchSchemaContext)
224
226
 
225
227
  // Create searchSchema object
226
- var searchSchemaObject = createSchema(rawSearchFields)
228
+ searchSchemaObject = createSchema(rawSearchFields)
227
229
  } else {
228
- var searchSchemaObject = createSchema({})
230
+ searchSchemaObject = createSchema({})
229
231
  }
230
232
 
231
233
  // Build schemaRelationships including polymorphic fields from schema
@@ -315,6 +317,14 @@ export async function compileSchemas (scope, deps) {
315
317
  }
316
318
  }
317
319
 
320
+ const idProperty = scope.scopeOptions.idProperty || scope.vars.idProperty || 'id'
321
+ const storage = normalizeStorageConfig(scope.scopeOptions.storage)
322
+ const storageInfo = buildStorageInfo({
323
+ schemaStructure: schemaObject.structure,
324
+ idProperty,
325
+ storage
326
+ })
327
+
318
328
  // Cache everything
319
329
  scope.vars.schemaInfo = {
320
330
 
@@ -327,7 +337,12 @@ export async function compileSchemas (scope, deps) {
327
337
  computed: computedFields,
328
338
  schemaRelationships,
329
339
  tableName: scope.scopeOptions.tableName || scopeName,
330
- idProperty: scope.scopeOptions.idProperty || scope.vars.idProperty,
340
+ idProperty,
341
+ storage,
342
+ storageInfo,
343
+ indexes: Array.isArray(scope.scopeOptions.indexes) ? [...scope.scopeOptions.indexes] : [],
344
+ foreignKeys: Array.isArray(scope.scopeOptions.foreignKeys) ? [...scope.scopeOptions.foreignKeys] : [],
345
+ checkConstraints: Array.isArray(scope.scopeOptions.checkConstraints) ? [...scope.scopeOptions.checkConstraints] : [],
331
346
  fieldGetters,
332
347
  sortedGetterFields,
333
348
  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)
@@ -274,7 +282,7 @@ export const buildFieldSelection = async (scope, deps) => {
274
282
  // So we need to include their dependencies even if normallyHidden
275
283
  // Example: profit_margin depends on 'cost' which is normallyHidden
276
284
  // We fetch 'cost' for calculation but don't return it in response
277
- for (const [fieldName, fieldDef] of Object.entries(computedFields)) {
285
+ for (const [, fieldDef] of Object.entries(computedFields)) {
278
286
  if (fieldDef.dependencies) {
279
287
  for (const dep of fieldDef.dependencies) {
280
288
  const depFieldDef = schemaStructure[dep]
@@ -289,6 +297,24 @@ export const buildFieldSelection = async (scope, deps) => {
289
297
  }
290
298
  }
291
299
  }
300
+
301
+ for (const [fieldName, fieldDef] of Object.entries(queryFields)) {
302
+ if (fieldDef.hidden === true || fieldDef.normallyHidden === true) continue
303
+ queryFieldsToSelect.add(fieldName)
304
+ }
305
+ }
306
+
307
+ const sortFields = Array.isArray(context.queryParams?.sort)
308
+ ? context.queryParams.sort
309
+ : []
310
+
311
+ for (const sortField of sortFields) {
312
+ if (typeof sortField !== 'string') continue
313
+ const normalizedField = sortField.startsWith('-') ? sortField.slice(1) : sortField
314
+
315
+ if (queryFieldNames.has(normalizedField)) {
316
+ queryFieldsToSelect.add(normalizedField)
317
+ }
292
318
  }
293
319
 
294
320
  // Always include foreign keys for relationships (unless hidden)
@@ -318,9 +344,52 @@ export const buildFieldSelection = async (scope, deps) => {
318
344
  // 3. enrichAttributes to remove dependencies from final response
319
345
  return {
320
346
  fieldsToSelect: Array.from(fieldsToSelect), // Fields to SELECT from database
347
+ queryFieldsToSelect: Array.from(queryFieldsToSelect),
321
348
  requestedFields: requested, // Fields explicitly requested by user
322
349
  computedDependencies: Array.from(computedDependencies), // Dependencies to remove from response
323
- idProperty // Pass idProperty for reference
350
+ idProperty: 'id' // Logical field name reference
351
+ }
352
+ }
353
+
354
+ export const applyFieldSelectionToQuery = async ({
355
+ query,
356
+ scope,
357
+ fieldSelectionInfo,
358
+ tableName,
359
+ useTablePrefix = false,
360
+ storageAdapter = null,
361
+ db = null,
362
+ context = null,
363
+ scopeName = ''
364
+ }) => {
365
+ const selectTranslator = createSelectTranslator(storageAdapter)
366
+
367
+ const selectedQuery = buildQuerySelection(
368
+ query,
369
+ tableName,
370
+ fieldSelectionInfo?.fieldsToSelect || [],
371
+ useTablePrefix,
372
+ selectTranslator ? { translateColumn: selectTranslator } : undefined
373
+ )
374
+
375
+ const queryFieldRuntimeByField = await buildQueryFieldRuntimes({
376
+ queryFieldNames: fieldSelectionInfo?.queryFieldsToSelect || [],
377
+ queryFields: scope?.vars?.queryFields || {},
378
+ schemaInfo: scope?.vars?.schemaInfo || {},
379
+ tableName,
380
+ storageAdapter,
381
+ db,
382
+ context,
383
+ scopeName
384
+ })
385
+
386
+ for (const [fieldName, runtime] of queryFieldRuntimeByField.entries()) {
387
+ selectedQuery.select({ [fieldName]: runtime.expression })
388
+ }
389
+
390
+ return {
391
+ query: selectedQuery,
392
+ queryFieldRuntimeByField
324
393
  }
325
394
  }
326
395
 
@@ -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
+ }