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,488 +0,0 @@
1
- 1 /**
2
- 2 * Schema processing utilities for search and field dependencies
3
- 3 *
4
- 4 * @description
5
- 5 * This module provides utilities for:
6
- 6 * - Marking search fields as indexed for database optimization
7
- 7 * - Generating search schemas from various configuration sources
8
- 8 * - Sorting fields by their dependencies (for getters/setters)
9
- 9 * - Detecting circular dependencies in field definitions
10
- 10 */
11
- 11
12
- 12 import { RestApiValidationError } from '../../../../lib/rest-api-errors.js'
13
- 13
14
- 14 /**
15
- 15 * Marks all fields in a searchSchema as indexed for database optimization
16
- 16 *
17
- 17 * @param {Object} searchSchema - Search schema object to process
18
- 18 * @returns {void} Modifies the searchSchema in-place
19
- 19 *
20
- 20 * @example
21
- 21 * // Input: Search fields without index flags
22
- 22 * const searchSchema = {
23
- 23 * title: { type: 'string', filterOperator: 'contains' },
24
- 24 * status: { type: 'string', filterOperator: '=' },
25
- 25 * created_at: { type: 'datetime', filterOperator: '>=' }
26
- 26 * };
27
- 27 * ensureSearchFieldsAreIndexed(searchSchema);
28
- 28 *
29
- 29 * // Output: All fields marked as indexed
30
- 30 * // {
31
- 31 * // title: { type: 'string', filterOperator: 'contains', indexed: true },
32
- 32 * // status: { type: 'string', filterOperator: '=', indexed: true },
33
- 33 * // created_at: { type: 'datetime', filterOperator: '>=', indexed: true }
34
- 34 * // }
35
- 35 *
36
- 36 * @example
37
- 37 * // Input: Virtual field for cross-table search
38
- 38 * const searchSchema = {
39
- 39 * name: { type: 'string', filterOperator: 'contains' },
40
- 40 * author_name: { // Virtual field
41
- 41 * type: 'string',
42
- 42 * filterOperator: 'contains',
43
- 43 * virtualField: {
44
- 44 * joinTo: 'users',
45
- 45 * joinOn: ['author_id', 'id'],
46
- 46 * searchField: 'name'
47
- 47 * }
48
- 48 * }
49
- 49 * };
50
- 50 * ensureSearchFieldsAreIndexed(searchSchema);
51
- 51 *
52
- 52 * // Output: Both regular and virtual fields indexed
53
- 53 * // {
54
- 54 * // name: { ..., indexed: true },
55
- 55 * // author_name: { ..., indexed: true } // Enables efficient JOIN
56
- 56 * // }
57
- 57 *
58
- 58 * @example
59
- 59 * // Input: Already indexed or explicitly false
60
- 60 * const searchSchema = {
61
- 61 * email: { type: 'string', indexed: false }, // Explicitly false
62
- 62 * username: { type: 'string', indexed: true } // Already true
63
- 63 * };
64
- 64 * ensureSearchFieldsAreIndexed(searchSchema);
65
- 65 *
66
- 66 * // Output: All forced to indexed: true
67
- 67 * // {
68
- 68 * // email: { type: 'string', indexed: true }, // Overridden
69
- 69 * // username: { type: 'string', indexed: true } // Unchanged
70
- 70 * // }
71
- 71 *
72
- 72 * @description
73
- 73 * Used by:
74
- 74 * - rest-api-plugin during scope initialization
75
- 75 * - Applied to all searchSchema fields before storage setup
76
- 76 *
77
- 77 * Purpose:
78
- 78 * - Signals storage plugins which fields need database indexes
79
- 79 * - Enables efficient filtering without full table scans
80
- 80 * - Supports cross-table searches via indexed JOINs
81
- 81 * - Improves query performance for filtered API requests
82
- 82 *
83
- 83 * Data flow:
84
- 84 * 1. Receives searchSchema object (or null)
85
- 85 * 2. Iterates through each field definition
86
- 86 * 3. Sets indexed: true on all fields
87
- 87 * 4. Storage plugins use this flag for index creation
88
- 88 */
89
- 89 export function ensureSearchFieldsAreIndexed (searchSchema) {
90
- 90 if (!searchSchema) return
91
- 91
92
- 92 Object.keys(searchSchema).forEach(fieldName => {
93
- 93 const fieldDef = searchSchema[fieldName]
94
- 94 if (fieldDef && typeof fieldDef === 'object') {
95
- 95 // Mark the field as indexed for cross-table search support
96
- 96 fieldDef.indexed = true
97
- 97 }
98
- 98 })
99
- 99 }
100
- 100
101
- 101 /**
102
- 102 * Generates complete searchSchema by merging schema search definitions with explicit searchSchema
103
- 103 *
104
- 104 * @param {Object} schema - Main resource schema with optional 'search' properties
105
- 105 * @param {Object} explicitSearchSchema - Optional explicit searchSchema to merge
106
- 106 * @returns {Object|null} Merged searchSchema or null if no search fields
107
- 107 * @throws {RestApiValidationError} If same field defined in multiple places
108
- 108 *
109
- 109 * @example
110
- 110 * // Input: Simple search fields in schema
111
- 111 * const schema = {
112
- 112 * title: { type: 'string', search: true }, // Simple
113
- 113 * content: { type: 'text' }, // Not searchable
114
- 114 * status: { type: 'string', search: { filterOperator: '=' } } // Custom
115
- 115 * };
116
- 116 * const result = generateSearchSchemaFromSchema(schema, null);
117
- 117 *
118
- 118 * // Output: Generated search schema
119
- 119 * // {
120
- 120 * // title: { type: 'string' }, // No operator set; resolver applies defaults
121
- 121 * // status: { type: 'string', filterOperator: '=' } // Specified operator
122
- 122 * // }
123
- 123 *
124
- 124 * @example
125
- 125 * // Input: Multiple filters from one field
126
- 126 * const schema = {
127
- 127 * published_at: {
128
- 128 * type: 'datetime',
129
- 129 * search: {
130
- 130 * published_after: { filterOperator: '>=', type: 'datetime' },
131
- 131 * published_before: { filterOperator: '<=', type: 'datetime' }
132
- 132 * }
133
- 133 * }
134
- 134 * };
135
- 135 * const result = generateSearchSchemaFromSchema(schema, null);
136
- 136 *
137
- 137 * // Output: Two search fields from one database field
138
- 138 * // {
139
- 139 * // published_after: {
140
- 140 * // type: 'datetime',
141
- 141 * // actualField: 'published_at', // Maps to real field
142
- 142 * // filterOperator: '>='
143
- 143 * // },
144
- 144 * // published_before: {
145
- 145 * // type: 'datetime',
146
- 146 * // actualField: 'published_at', // Same field, different operator
147
- 147 * // filterOperator: '<='
148
- 148 * // }
149
- 149 * // }
150
- 150 *
151
- 151 * @example
152
- 152 * // Input: Merging with explicit searchSchema
153
- 153 * const schema = {
154
- 154 * name: { type: 'string', search: true }
155
- 155 * };
156
- 156 * const explicitSearchSchema = {
157
- 157 * email: { type: 'string', filterOperator: '=' },
158
- 158 * age: { type: 'number', filterOperator: '>=' }
159
- 159 * };
160
- 160 * const result = generateSearchSchemaFromSchema(schema, explicitSearchSchema);
161
- 161 *
162
- 162 * // Output: Combined from both sources
163
- 163 * // {
164
- 164 * // email: { type: 'string', filterOperator: '=' }, // Explicit
165
- 165 * // age: { type: 'number', filterOperator: '>=' }, // Explicit
166
- 166 * // name: { type: 'string' } // From schema (no operator set)
167
- 167 * // }
168
- 168 *
169
- 169 * @example
170
- 170 * // Input: Explicit searchSchema takes precedence
171
- 171 * const schema = {
172
- 172 * email: { type: 'string', search: true }
173
- 173 * };
174
- 174 * const explicitSearchSchema = {
175
- 175 * email: { type: 'string', filterOperator: 'contains' }
176
- 176 * };
177
- 177 * const result = generateSearchSchemaFromSchema(schema, explicitSearchSchema);
178
- 178 * // Output: Explicit searchSchema wins
179
- 179 * // {
180
- 180 * // email: { type: 'string', filterOperator: 'contains' } // Uses explicit definition
181
- 181 * // }
182
- 182 *
183
- 183 * @example
184
- 184 * // Input: Virtual search fields
185
- 185 * const schema = {
186
- 186 * title: { type: 'string' },
187
- 187 * _virtual: {
188
- 188 * search: {
189
- 189 * category_name: { // Doesn't exist in DB
190
- 190 * type: 'string',
191
- 191 * filterOperator: 'contains',
192
- 192 * virtualField: {
193
- 193 * joinTo: 'categories',
194
- 194 * joinOn: ['category_id', 'id'], // JOIN condition
195
- 195 * searchField: 'name' // Field in related table
196
- 196 * }
197
- 197 * }
198
- 198 * }
199
- 199 * }
200
- 200 * };
201
- 201 *
202
- 202 * // Output: Virtual field for cross-table search
203
- 203 * // {
204
- 204 * // category_name: {
205
- 205 * // type: 'string',
206
- 206 * // filterOperator: 'contains',
207
- 207 * // virtualField: { ... join config ... }
208
- 208 * // }
209
- 209 * // }
210
- 210 *
211
- 211 * @description
212
- 212 * Used by:
213
- 213 * - rest-api-plugin during scope initialization
214
- 214 * - Generates searchSchema from various sources
215
- 215 *
216
- 216 * Purpose:
217
- 217 * - Provides flexible search configuration options
218
- 218 * - Enables virtual fields for cross-table searches
219
- 219 * - Supports range queries (before/after patterns)
220
- 220 * - Merges search:true fields with explicit searchSchema
221
- 221 * - Explicit searchSchema always takes precedence
222
- 222 * - Allows storage plugins to optimize queries
223
- 223 *
224
- 224 * Data flow:
225
- 225 * 1. Starts with explicit searchSchema (if any)
226
- 226 * 2. Processes schema fields with 'search' property
227
- 227 * 3. Skips fields already in explicit searchSchema (no conflicts)
228
- 228 * 4. Handles multiple filters from single field
229
- 229 * 5. Processes virtual search definitions
230
- 230 * 6. Returns merged searchSchema or null
231
- 231 */
232
- 232 export const generateSearchSchemaFromSchema = (schema, explicitSearchSchema) => {
233
- 233 // Start with explicit searchSchema or empty object
234
- 234 const searchSchema = explicitSearchSchema ? { ...explicitSearchSchema } : {}
235
- 235
236
- 236 if (!schema) {
237
- 237 return Object.keys(searchSchema).length > 0 ? searchSchema : null
238
- 238 }
239
- 239
240
- 240 // Process schema fields with 'search' property
241
- 241 Object.entries(schema).forEach(([fieldName, fieldDef]) => {
242
- 242 const effectiveSearch = fieldDef.search
243
- 243
244
- 244 if (effectiveSearch) {
245
- 245 if (effectiveSearch === true) {
246
- 246 // For belongsTo relationships, use the relationship name (as property) instead of the field name
247
- 247 const searchFieldName = (fieldDef.belongsTo && fieldDef.as) ? fieldDef.as : fieldName
248
- 248
249
- 249 // Check if field already exists in explicit searchSchema
250
- 250 if (searchSchema[searchFieldName]) {
251
- 251 // Skip - explicit searchSchema takes precedence
252
- 252 // This allows searchSchema to override fields marked with search:true
253
- 253 return
254
- 254 }
255
- 255
256
- 256 // Simple boolean - copy entire field definition (except search)
257
- 257 const { search, ...fieldDefWithoutSearch } = fieldDef
258
- 258 const searchEntry = {
259
- 259 ...fieldDefWithoutSearch,
260
- 260 // Do not set filterOperator here; centralized resolver will apply sensible defaults
261
- 261 }
262
- 262
263
- 263 // For belongsTo relationships, add metadata to map back to the actual field
264
- 264 if (fieldDef.belongsTo && fieldDef.as) {
265
- 265 searchEntry.actualField = fieldName // Maps to the DB column
266
- 266 searchEntry.isRelationship = true
267
- 267 searchEntry.targetResource = fieldDef.belongsTo
268
- 268 }
269
- 269
270
- 270 searchSchema[searchFieldName] = searchEntry
271
- 271 } else if (typeof effectiveSearch === 'object') {
272
- 272 // Check if search defines multiple filter fields
273
- 273 const hasNestedFilters = Object.values(effectiveSearch).some(
274
- 274 v => typeof v === 'object' && v.filterOperator
275
- 275 )
276
- 276
277
- 277 if (hasNestedFilters) {
278
- 278 // Multiple filters from one field (like published_after/before)
279
- 279 Object.entries(effectiveSearch).forEach(([filterName, filterDef]) => {
280
- 280 // Check if filter already exists in explicit searchSchema
281
- 281 if (searchSchema[filterName]) {
282
- 282 // Skip - explicit searchSchema takes precedence
283
- 283 return
284
- 284 }
285
- 285
286
- 286 searchSchema[filterName] = {
287
- 287 type: fieldDef.type,
288
- 288 actualField: fieldName,
289
- 289 ...filterDef
290
- 290 }
291
- 291 })
292
- 292 } else {
293
- 293 // For belongsTo relationships, use the relationship name (as property) instead of the field name
294
- 294 const searchFieldName = (fieldDef.belongsTo && fieldDef.as) ? fieldDef.as : fieldName
295
- 295
296
- 296 // Check if field already exists in explicit searchSchema
297
- 297 if (searchSchema[searchFieldName]) {
298
- 298 // Skip - explicit searchSchema takes precedence
299
- 299 return
300
- 300 }
301
- 301
302
- 302 // Single filter with config
303
- 303 const searchEntry = {
304
- 304 type: fieldDef.type,
305
- 305 ...effectiveSearch
306
- 306 }
307
- 307
308
- 308 // For belongsTo relationships, add metadata to map back to the actual field
309
- 309 if (fieldDef.belongsTo && fieldDef.as) {
310
- 310 searchEntry.actualField = fieldName // Maps to the DB column
311
- 311 searchEntry.isRelationship = true
312
- 312 searchEntry.targetResource = fieldDef.belongsTo
313
- 313 }
314
- 314
315
- 315 searchSchema[searchFieldName] = searchEntry
316
- 316 }
317
- 317 }
318
- 318 }
319
- 319 })
320
- 320
321
- 321 // Handle _virtual search definitions
322
- 322 if (schema._virtual?.search) {
323
- 323 Object.entries(schema._virtual.search).forEach(([filterName, filterDef]) => {
324
- 324 // Check if filter already exists in explicit searchSchema
325
- 325 if (searchSchema[filterName]) {
326
- 326 // Skip - explicit searchSchema takes precedence
327
- 327 return
328
- 328 }
329
- 329
330
- 330 searchSchema[filterName] = filterDef
331
- 331 })
332
- 332 }
333
- 333
334
- 334 return Object.keys(searchSchema).length > 0 ? searchSchema : null
335
- 335 }
336
- 336
337
- 337 /**
338
- 338 * Generic topological sort for handling dependencies
339
- 339 *
340
- 340 * @param {Array} items - Array of items to sort
341
- 341 * @param {Function} getDependencies - Function that returns dependencies for an item
342
- 342 * @returns {Array} Sorted array respecting dependencies
343
- 343 * @throws {Error} If circular dependencies or unknown dependencies detected
344
- 344 *
345
- 345 * @example
346
- 346 * // Input: Simple dependency chain
347
- 347 * const items = ['a', 'b', 'c'];
348
- 348 * const deps = { a: ['b'], b: ['c'], c: [] };
349
- 349 * const sorted = topologicalSort(items, item => deps[item]);
350
- 350 * // Output: ['c', 'b', 'a']
351
- 351 * // c first (no deps), then b (depends on c), then a (depends on b)
352
- 352 *
353
- 353 * @example
354
- 354 * // Input: Circular dependency
355
- 355 * const items = ['a', 'b'];
356
- 356 * const deps = { a: ['b'], b: ['a'] }; // Circular!
357
- 357 * topologicalSort(items, item => deps[item]);
358
- 358 * // Throws: Error "Circular dependency detected: b"
359
- 359 *
360
- 360 * @example
361
- 361 * // Input: Unknown dependency
362
- 362 * const items = ['a', 'b'];
363
- 363 * const deps = { a: ['c'], b: [] }; // 'c' not in items
364
- 364 * topologicalSort(items, item => deps[item]);
365
- 365 * // Throws: Error "Unknown dependency 'c' for 'a'"
366
- 366 *
367
- 367 * @description
368
- 368 * Used by:
369
- 369 * - sortFieldsByDependencies for ordering field operations
370
- 370 * - Any code needing dependency-based ordering
371
- 371 *
372
- 372 * Purpose:
373
- 373 * - Orders items so dependencies come before dependents
374
- 374 * - Detects circular dependencies early
375
- 375 * - Validates all dependencies exist
376
- 376 * - Uses depth-first search algorithm
377
- 377 */
378
- 378 export function topologicalSort (items, getDependencies) {
379
- 379 const sorted = []
380
- 380 const visited = new Set()
381
- 381 const visiting = new Set()
382
- 382
383
- 383 function visit (item) {
384
- 384 if (visited.has(item)) return
385
- 385
386
- 386 if (visiting.has(item)) {
387
- 387 throw new Error(`Circular dependency detected: ${item}`)
388
- 388 }
389
- 389
390
- 390 visiting.add(item)
391
- 391
392
- 392 const dependencies = getDependencies(item) || []
393
- 393 for (const dep of dependencies) {
394
- 394 if (!items.includes(dep)) {
395
- 395 throw new Error(`Unknown dependency '${dep}' for '${item}'`)
396
- 396 }
397
- 397 visit(dep)
398
- 398 }
399
- 399
400
- 400 visiting.delete(item)
401
- 401 visited.add(item)
402
- 402 sorted.push(item)
403
- 403 }
404
- 404
405
- 405 for (const item of items) {
406
- 406 visit(item)
407
- 407 }
408
- 408
409
- 409 return sorted
410
- 410 }
411
- 411
412
- 412 /**
413
- 413 * Sorts fields by their dependencies using topological sort
414
- 414 *
415
- 415 * @param {Object} fields - Object with field definitions
416
- 416 * @param {string} dependencyProperty - Property name containing dependencies
417
- 417 * @returns {Array} Field names sorted by dependencies
418
- 418 * @throws {Error} If circular dependencies or unknown fields detected
419
- 419 *
420
- 420 * @example
421
- 421 * // Input: Getter dependencies (fullName needs firstName and lastName)
422
- 422 * const fields = {
423
- 423 * firstName: { getter: v => v.trim() },
424
- 424 * lastName: { getter: v => v.trim() },
425
- 425 * fullName: {
426
- 426 * getter: (v, ctx) => `${ctx.attributes.firstName} ${ctx.attributes.lastName}`,
427
- 427 * runGetterAfter: ['firstName', 'lastName']
428
- 428 * }
429
- 429 * };
430
- 430 * const sorted = sortFieldsByDependencies(fields, 'runGetterAfter');
431
- 431 * // Output: ['firstName', 'lastName', 'fullName']
432
- 432 * // Ensures firstName/lastName getters run before fullName
433
- 433 *
434
- 434 * @example
435
- 435 * // Input: Complex dependency chain
436
- 436 * const fields = {
437
- 437 * a: { runGetterAfter: ['b', 'c'] }, // a needs b and c
438
- 438 * b: { runGetterAfter: ['d'] }, // b needs d
439
- 439 * c: { runGetterAfter: ['d'] }, // c needs d
440
- 440 * d: { runGetterAfter: [] } // d needs nothing
441
- 441 * };
442
- 442 * const sorted = sortFieldsByDependencies(fields, 'runGetterAfter');
443
- 443 * // Output: ['d', 'b', 'c', 'a']
444
- 444 * // d first, then b/c (both need d), then a (needs b/c)
445
- 445 *
446
- 446 * @example
447
- 447 * // Input: Circular dependency error
448
- 448 * const fields = {
449
- 449 * a: { runGetterAfter: ['b'] },
450
- 450 * b: { runGetterAfter: ['a'] } // Circular!
451
- 451 * };
452
- 452 * sortFieldsByDependencies(fields, 'runGetterAfter');
453
- 453 * // Throws: Error "Circular dependency detected: a in runGetterAfter"
454
- 454 *
455
- 455 * @description
456
- 456 * Used by:
457
- 457 * - Schema processing for getter/setter ordering
458
- 458 * - Ensures dependent fields process after dependencies
459
- 459 *
460
- 460 * Purpose:
461
- 461 * - Orders field operations by dependencies
462
- 462 * - Enables computed fields that depend on other fields
463
- 463 * - Validates dependency graph is acyclic
464
- 464 * - Provides clear error messages for debugging
465
- 465 *
466
- 466 * Data flow:
467
- 467 * 1. Extracts field names from object
468
- 468 * 2. Calls topologicalSort with dependency function
469
- 469 * 3. Returns ordered field names
470
- 470 * 4. Enhances error messages with property name
471
- 471 */
472
- 472 export function sortFieldsByDependencies (fields, dependencyProperty) {
473
- 473 const fieldNames = Object.keys(fields)
474
- 474
475
- 475 if (fieldNames.length === 0) return []
476
- 476
477
- 477 try {
478
- 478 return topologicalSort(fieldNames, (fieldName) => {
479
- 479 const field = fields[fieldName]
480
- 480 return field[dependencyProperty] || []
481
- 481 })
482
- 482 } catch (error) {
483
- 483 if (error.message.includes('Circular dependency')) {
484
- 484 throw new Error(`${error.message} in ${dependencyProperty}`)
485
- 485 }
486
- 486 throw error
487
- 487 }
488
- 488 }