json-rest-api 1.0.22 → 1.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/CLAUDE.md +1 -2
  2. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
  3. package/README.md +22 -1
  4. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
  5. package/docs/API.md +196 -38
  6. package/docs/COMPARISON.md +2 -2
  7. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +111 -5
  8. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
  9. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
  10. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
  11. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
  12. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
  13. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
  14. package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
  15. package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
  16. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +370 -0
  17. package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
  18. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
  19. package/docs/GUIDE/index.md +18 -9
  20. package/docs/QUICKSTART.md +5 -1
  21. package/docs/index.md +1 -1
  22. package/index.js +3 -2
  23. package/lib/enhanced-logger.js +1 -1
  24. package/package.json +6 -2
  25. package/plugins/core/bulk-operations-plugin.js +26 -34
  26. package/plugins/core/connectors/express-plugin.js +97 -190
  27. package/plugins/core/connectors/fastify-plugin.js +206 -0
  28. package/plugins/core/connectors/lib/busboy-detector.js +4 -4
  29. package/plugins/core/connectors/lib/connector-core.js +195 -0
  30. package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
  31. package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
  32. package/plugins/core/lib/anyapi/schema-utils.js +43 -4
  33. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +4 -1
  34. package/plugins/core/lib/dbIntrospection.js +1078 -0
  35. package/plugins/core/lib/dbTablesOperations.js +1111 -160
  36. package/plugins/core/lib/querying/knex-cross-table-search.js +0 -7
  37. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -3
  38. package/plugins/core/lib/querying/knex-process-includes.js +3 -4
  39. package/plugins/core/lib/querying/knex-query-helpers.js +21 -76
  40. package/plugins/core/lib/querying/knex-relationship-includes.js +468 -215
  41. package/plugins/core/lib/querying/query-builder-utils.js +19 -0
  42. package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
  43. package/plugins/core/lib/querying/sort-helpers.js +33 -0
  44. package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
  45. package/plugins/core/lib/querying/url-helpers.js +8 -2
  46. package/plugins/core/lib/querying-writing/compile-schemas.js +20 -5
  47. package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -14
  48. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
  49. package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
  50. package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
  51. package/plugins/core/lib/querying-writing/resource-id-normalization.js +131 -0
  52. package/plugins/core/lib/querying-writing/schema-helpers.js +0 -2
  53. package/plugins/core/lib/querying-writing/simplified-helpers.js +4 -11
  54. package/plugins/core/lib/storage/canonical-storage-mapping.js +133 -0
  55. package/plugins/core/lib/storage/storage-adapter.js +82 -4
  56. package/plugins/core/lib/storage/storage-mapping.js +274 -0
  57. package/plugins/core/lib/writing/many-to-many-manipulations.js +4 -2
  58. package/plugins/core/rest-api-anyapi-knex-plugin.js +531 -312
  59. package/plugins/core/rest-api-autofilter-plugin.js +414 -0
  60. package/plugins/core/rest-api-knex-plugin.js +271 -226
  61. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
  62. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
  63. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +1 -0
  64. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
  65. package/plugins/core/rest-api-plugin-methods/common.js +148 -92
  66. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +11 -2
  67. package/plugins/core/rest-api-plugin-methods/delete.js +6 -1
  68. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
  69. package/plugins/core/rest-api-plugin-methods/get-related.js +7 -2
  70. package/plugins/core/rest-api-plugin-methods/get-relationship.js +7 -2
  71. package/plugins/core/rest-api-plugin-methods/get.js +29 -14
  72. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +10 -3
  73. package/plugins/core/rest-api-plugin-methods/patch.js +56 -12
  74. package/plugins/core/rest-api-plugin-methods/post-relationship.js +11 -2
  75. package/plugins/core/rest-api-plugin-methods/post.js +21 -8
  76. package/plugins/core/rest-api-plugin-methods/put.js +73 -21
  77. package/plugins/core/rest-api-plugin-methods/query.js +15 -53
  78. package/plugins/core/rest-api-plugin-methods/release.js +0 -2
  79. package/plugins/core/rest-api-plugin.js +6 -0
  80. package/plugins/core/rest-api-positioning-plugin.js +2 -5
  81. package/plugins/core/rest-api-query-projections-plugin.js +130 -0
  82. package/plugins/core/socketio-plugin.js +1 -4
  83. package/plugins/storage/local-storage.js +1 -1
  84. package/plugins/storage/s3-storage.js +5 -2
  85. package/quickTest.js +1 -0
  86. package/scripts/run-docs.js +80 -84
  87. package/test-debug.js +1 -1
  88. package/tests/README.md +7 -0
  89. package/tests/anyapi-basic.test.js +3 -0
  90. package/tests/autofilter.test.js +463 -0
  91. package/tests/custom-id-mysql-returning-fallback.test.js +128 -0
  92. package/tests/custom-id-simplified-post.test.js +75 -0
  93. package/tests/custom-idproperty-relationships.test.js +33 -0
  94. package/tests/db-introspection.test.js +521 -0
  95. package/tests/db-table-operations.test.js +786 -0
  96. package/tests/fastify-plugin.test.js +156 -0
  97. package/tests/field-getters.test.js +1 -2
  98. package/tests/field-setters.test.js +0 -4
  99. package/tests/fixtures/api-configs.js +278 -176
  100. package/tests/helpers/fake-fastify.js +99 -0
  101. package/tests/http-connectors-parity.test.js +235 -0
  102. package/tests/id-normalization.test.js +444 -0
  103. package/tests/logical-id-write-validation.test.js +151 -0
  104. package/tests/pagination-cursor-multifield.test.js +11 -11
  105. package/tests/projected-fields.test.js +319 -0
  106. package/tests/relationships.test.js +3 -3
  107. package/tests/searchschema-knex-behavior.test.js +187 -0
  108. package/tests/searchschema-merge.test.js +21 -69
  109. package/tests/storage-mapping.test.js +259 -0
  110. package/audit/00-charter.md +0 -103
  111. package/audit/01-scope-map.md +0 -84
  112. package/audit/02-coverage-matrix.csv +0 -174
  113. package/audit/03-invariants.md +0 -123
  114. package/audit/04-standards-rubric.md +0 -56
  115. package/audit/05-competitor-rubric.md +0 -77
  116. package/audit/06-findings.md +0 -160
  117. package/audit/07-risk-register.csv +0 -10
  118. package/audit/08-remediation-roadmap.md +0 -177
  119. package/audit/09-signoff-check.md +0 -23
  120. package/audit/10-final-synthesis.md +0 -29
  121. package/audit/evidence/code/all-files-json-rest-api.txt +0 -168
  122. package/audit/evidence/code/all-files-json-rest-schema.txt +0 -12
  123. package/audit/evidence/code/boundary-leak-checks.txt +0 -72
  124. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__bulk-operations-plugin.js.txt +0 -480
  125. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__express-plugin.js.txt +0 -532
  126. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__busboy-detector.js.txt +0 -175
  127. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__formidable-detector.js.txt +0 -138
  128. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__request-helpers.js.txt +0 -222
  129. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt +0 -342
  130. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__dbTablesOperations.js.txt +0 -304
  131. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__fractional-positioning.js.txt +0 -188
  132. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__compile-schemas.js.txt +0 -336
  133. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__connectors-query-parser.js.txt +0 -165
  134. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-capabilities.js.txt +0 -265
  135. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-value-normalizers.js.txt +0 -369
  136. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__default-data-helpers.js.txt +0 -72
  137. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__field-utils.js.txt +0 -139
  138. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-constants.js.txt +0 -45
  139. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-field-helpers.js.txt +0 -396
  140. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-json-api-transformers.js.txt +0 -162
  141. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__payload-validators.js.txt +0 -1314
  142. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__schema-helpers.js.txt +0 -488
  143. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__scope-validations.js.txt +0 -484
  144. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__simplified-helpers.js.txt +0 -857
  145. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-cross-table-search.js.txt +0 -565
  146. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-json-api-transformers-querying.js.txt +0 -485
  147. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-pagination-helpers.js.txt +0 -560
  148. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-process-includes.js.txt +0 -167
  149. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers-base.js.txt +0 -109
  150. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers.js.txt +0 -1064
  151. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-relationship-includes.js.txt +0 -1983
  152. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-window-queries.js.txt +0 -328
  153. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__url-helpers.js.txt +0 -119
  154. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__storage__storage-adapter.js.txt +0 -160
  155. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__many-to-many-manipulations.js.txt +0 -300
  156. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__relationship-processor.js.txt +0 -245
  157. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-access.js.txt +0 -459
  158. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-cors-plugin.js.txt +0 -237
  159. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-knex-plugin.js.txt +0 -1135
  160. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-multihome-plugin.js.txt +0 -314
  161. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__compile-resource-schemas.js.txt +0 -7
  162. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-relationship-routes.js.txt +0 -71
  163. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-scope-routes.js.txt +0 -102
  164. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__turn-scope-init-into-vars.js.txt +0 -49
  165. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__validate-include-configurations.js.txt +0 -39
  166. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__add-route.js.txt +0 -23
  167. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__check-permissions.js.txt +0 -13
  168. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__common.js.txt +0 -639
  169. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete-relationship.js.txt +0 -117
  170. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete.js.txt +0 -104
  171. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__enrich-attributes.js.txt +0 -199
  172. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-related.js.txt +0 -353
  173. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-relationship.js.txt +0 -61
  174. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get.js.txt +0 -184
  175. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch-relationship.js.txt +0 -55
  176. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch.js.txt +0 -191
  177. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post-relationship.js.txt +0 -110
  178. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post.js.txt +0 -168
  179. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__put.js.txt +0 -253
  180. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__query.js.txt +0 -207
  181. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__release.js.txt +0 -12
  182. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin.js.txt +0 -165
  183. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-positioning-plugin.js.txt +0 -668
  184. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__socketio-plugin.js.txt +0 -621
  185. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__local-storage.js.txt +0 -218
  186. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__s3-storage.js.txt +0 -124
  187. package/audit/evidence/code/critical-pass1/json-rest-schema__src__CorePlugin.js.txt +0 -269
  188. package/audit/evidence/code/critical-pass1/json-rest-schema__src__Schema.js.txt +0 -242
  189. package/audit/evidence/code/critical-pass1/json-rest-schema__src__index.js.txt +0 -77
  190. package/audit/evidence/code/critical-pass1-risk-keywords.txt +0 -238
  191. package/audit/evidence/code/critical-pass1-summary.csv +0 -67
  192. package/audit/evidence/code/critical-pass2-summary.csv +0 -67
  193. package/audit/evidence/code/early-silent-zones-no-test-token.txt +0 -57
  194. package/audit/evidence/code/error-model-signals.txt +0 -98
  195. package/audit/evidence/code/git-files-json-rest-api.txt +0 -171
  196. package/audit/evidence/code/git-files-json-rest-schema.txt +0 -27
  197. package/audit/evidence/code/hook-flow-signals.txt +0 -282
  198. package/audit/evidence/code/idproperty-signals.txt +0 -157
  199. package/audit/evidence/code/import-cycles-non-anyapi.txt +0 -3
  200. package/audit/evidence/code/import-hotspots-non-anyapi.txt +0 -96
  201. package/audit/evidence/code/imports-json-rest-api.txt +0 -354
  202. package/audit/evidence/code/imports-json-rest-schema.txt +0 -15
  203. package/audit/evidence/code/in-scope-files-json-rest-api.txt +0 -160
  204. package/audit/evidence/code/in-scope-files-json-rest-schema.txt +0 -13
  205. package/audit/evidence/code/non-anyapi-core-files.txt +0 -101
  206. package/audit/evidence/code/permission-signals.txt +0 -193
  207. package/audit/evidence/code/query-include-signals.txt +0 -687
  208. package/audit/evidence/code/transaction-signals.txt +0 -28
  209. package/audit/evidence/code/validation-signals.txt +0 -650
  210. package/audit/evidence/code/write-path-signals.txt +0 -61
  211. package/audit/evidence/docs/docs-claims-signals.txt +0 -1075
  212. package/audit/evidence/docs/schema-docs-claims-signals.txt +0 -179
  213. package/audit/evidence/external/corpus-freeze.md +0 -21
  214. package/audit/evidence/external/npm-competitor-metadata.txt +0 -70
  215. package/audit/evidence/external/npm-download-signals.txt +0 -23
  216. package/audit/evidence/external/npm-jsonapi-server.txt +0 -8
  217. package/audit/evidence/external/npm-modern-jsonapi-metadata.txt +0 -38
  218. package/audit/evidence/external/readme-fortune-signals.txt +0 -26
  219. package/audit/evidence/external/readme-fortune.md +0 -165
  220. package/audit/evidence/external/readme-json-api-serializer-signals.txt +0 -80
  221. package/audit/evidence/external/readme-json-api-serializer.md +0 -690
  222. package/audit/evidence/external/readme-jsonapi-serde-server-signals.txt +0 -0
  223. package/audit/evidence/external/readme-jsonapi-serde-server.md +0 -22
  224. package/audit/evidence/external/readme-jsonapi-serializer-signals.txt +0 -63
  225. package/audit/evidence/external/readme-jsonapi-serializer.md +0 -315
  226. package/audit/evidence/external/readme-jsonapi-server-signals.txt +0 -19
  227. package/audit/evidence/external/readme-jsonapi-server.md +0 -104
  228. package/audit/evidence/external/readme-nestjs-json-api-signals.txt +0 -4
  229. package/audit/evidence/external/readme-nestjs-json-api.md +0 -94
  230. package/audit/evidence/external/readme-yaser-json-api-nestjs-signals.txt +0 -47
  231. package/audit/evidence/external/readme-yaser-json-api-nestjs.md +0 -447
  232. package/audit/evidence/external/standard-jsonapi-snippets.txt +0 -84
  233. package/audit/evidence/external/standard-rfc-snippets.txt +0 -168
  234. package/audit/evidence/external/standards-sources.txt +0 -6
  235. package/audit/evidence/runtime/baseline-commits.txt +0 -2
  236. package/audit/evidence/runtime/db-adapter-assumptions.txt +0 -3
  237. package/audit/evidence/runtime/environment.txt +0 -4
  238. package/audit/evidence/runtime/invariant-runtime-mapping.md +0 -38
  239. package/audit/evidence/runtime/probe-patch-relationship-error.exitcode +0 -1
  240. package/audit/evidence/runtime/probe-patch-relationship-error.log +0 -76
  241. package/audit/evidence/runtime/probe-patch-relationship-error.mjs +0 -67
  242. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.exitcode +0 -1
  243. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.log +0 -62
  244. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.mjs +0 -47
  245. package/audit/evidence/runtime/test-exitcodes.txt +0 -4
  246. package/audit/evidence/runtime/test-summary.md +0 -107
  247. package/audit/evidence/runtime/trace-anchors.txt +0 -61
  248. package/audit/evidence/tests/access-multitenancy-test-signals.txt +0 -125
  249. package/audit/evidence/tests/failing-tests-summary.txt +0 -25
  250. package/audit/evidence/tests/npm-test-json-rest-api.exitcode +0 -1
  251. package/audit/evidence/tests/npm-test-json-rest-api.log +0 -5885
  252. package/audit/evidence/tests/npm-test-json-rest-schema.exitcode +0 -1
  253. package/audit/evidence/tests/npm-test-json-rest-schema.log +0 -356
  254. package/audit/evidence/tests/query-include-test-signals.txt +0 -948
  255. package/audit/evidence/tests/relationship-test-signals.txt +0 -223
  256. package/audit/evidence/tests/skipped-tests.txt +0 -10
  257. package/audit/evidence/tests/test-suite-list-non-anyapi.txt +0 -30
  258. package/audit/evidence/tests/test-suite-list.txt +0 -32
  259. package/audit/subsystems/docs-drift.md +0 -49
  260. package/audit/subsystems/pass2-cross-review.md +0 -131
  261. package/audit/subsystems/plugins-access.md +0 -51
  262. package/audit/subsystems/plugins-bulk.md +0 -50
  263. package/audit/subsystems/plugins-cors.md +0 -48
  264. package/audit/subsystems/plugins-file.md +0 -53
  265. package/audit/subsystems/plugins-multihome.md +0 -50
  266. package/audit/subsystems/plugins-positioning.md +0 -54
  267. package/audit/subsystems/plugins-socketio.md +0 -50
  268. package/audit/subsystems/query-engine.md +0 -61
  269. package/audit/subsystems/relationships.md +0 -55
  270. package/audit/subsystems/rest-orchestration.md +0 -66
  271. package/audit/subsystems/schema-core.md +0 -64
  272. package/audit/subsystems/storage-knex.md +0 -57
  273. package/audit/subsystems/tests.md +0 -48
  274. package/audit/subsystems/transport-express.md +0 -55
  275. package/audit/traces/trace-read-get.md +0 -22
  276. package/audit/traces/trace-read-query.md +0 -25
  277. package/audit/traces/trace-relationship-endpoints.md +0 -31
  278. package/audit/traces/trace-write-delete.md +0 -17
  279. package/audit/traces/trace-write-patch.md +0 -26
  280. package/audit/traces/trace-write-post.md +0 -22
  281. package/audit/traces/trace-write-put.md +0 -23
  282. package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
  283. package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
  284. package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
  285. package/plugins/core/rest-api-access.js +0 -459
  286. package/plugins/core/rest-api-multihome-plugin.js +0 -314
  287. package/tests/multihome.test.js +0 -553
  288. package/tests/multihome.test.js.bak +0 -550
  289. package/tests/ownership-optout.test.js +0 -76
  290. package/tests/rest-api-access.test.js +0 -206
@@ -1,532 +0,0 @@
1
- 1 /**
2
- 2 * Express Plugin for Hooked API
3
- 3 *
4
- 4 * This plugin creates HTTP endpoints for your REST API by listening to
5
- 5 * route registrations from the REST API plugin and creating Express routes.
6
- 6 *
7
- 7 * Features:
8
- 8 * - Automatic route creation via addRoute hook
9
- 9 * - JSON:API compliant request/response handling
10
- 10 * - Query parameter parsing
11
- 11 * - Error mapping to HTTP status codes
12
- 12 * - Content type validation
13
- 13 * - Middleware injection points
14
- 14 * - File upload support with busboy or formidable
15
- 15 */
16
- 16
17
- 17 import { requirePackage } from 'hooked-api'
18
- 18 import { parseJsonApiQuery } from '../lib/querying-writing/connectors-query-parser.js'
19
- 19 import { createContext } from './lib/request-helpers.js'
20
- 20 import { createEnhancedLogger } from '../../../lib/enhanced-logger.js'
21
- 21
22
- 22 export const ExpressPlugin = {
23
- 23 name: 'express',
24
- 24 dependencies: ['rest-api'],
25
- 25
26
- 26 async install ({ on, vars, helpers, pluginOptions, log, scopes, api, runHooks, addHook }) {
27
- 27 addHook('release', 'releaseHook', {},
28
- 28 async ({ api }) => {} // TODO: Anything to do here?
29
- 29 )
30
- 30
31
- 31 // Dynamic import for Express
32
- 32 let express
33
- 33 try {
34
- 34 express = (await import('express')).default
35
- 35 } catch (e) {
36
- 36 requirePackage('express', 'express',
37
- 37 'Express.js is required for HTTP server functionality. This is a peer dependency.')
38
- 38 }
39
- 39 // Enhance the logger
40
- 40 const enhancedLog = createEnhancedLogger(log, {
41
- 41 logFullErrors: true,
42
- 42 includeStack: true
43
- 43 })
44
- 44
45
- 45 // Initialize express namespace
46
- 46 if (!api.http) {
47
- 47 api.http = {}
48
- 48 }
49
- 49 api.http.express = {}
50
- 50
51
- 51 const expressOptions = pluginOptions || {}
52
- 52
53
- 53 // Get mountPath from options (this is now a transport concern)
54
- 54 const mountPath = expressOptions.mountPath || ''
55
- 55 const basePath = mountPath // Keep basePath for internal use
56
- 56 const strictContentType = expressOptions.strictContentType !== false
57
- 57 const requestSizeLimit = expressOptions.requestSizeLimit || '1mb'
58
- 58
59
- 59 // Set transport information for other plugins
60
- 60 vars.transport = {
61
- 61 type: 'express',
62
- 62 matchAll: '*', // Express wildcard pattern for matching all routes
63
- 63 mountPath // Transport-specific mount path
64
- 64 }
65
- 65
66
- 66 // Register file detector if enabled
67
- 67 if (expressOptions.enableFileUploads !== false && api.rest?.registerFileDetector) {
68
- 68 const parserLib = expressOptions.fileParser || 'busboy'
69
- 69 const parserOptions = expressOptions.fileParserOptions || {}
70
- 70
71
- 71 let detector
72
- 72
73
- 73 if (parserLib === 'busboy') {
74
- 74 try {
75
- 75 const { createBusboyDetector } = await import('../lib/busboy-detector.js')
76
- 76 detector = createBusboyDetector(parserOptions)
77
- 77 } catch (e) {
78
- 78 log.warn('Busboy not installed. Install with: npm install busboy')
79
- 79 }
80
- 80 } else if (parserLib === 'formidable') {
81
- 81 try {
82
- 82 const { createFormidableDetector } = await import('../lib/formidable-detector.js')
83
- 83 detector = createFormidableDetector(parserOptions)
84
- 84 } catch (e) {
85
- 85 log.warn('Formidable not installed. Install with: npm install formidable')
86
- 86 }
87
- 87 }
88
- 88
89
- 89 if (detector) {
90
- 90 api.rest.registerFileDetector({
91
- 91 name: `express-${detector.name}`,
92
- 92 detect: (params, context) => {
93
- 93 if (!context || !context.raw || !context.raw.req) return false
94
- 94 const detectParams = { ...params, _expressReq: context.raw.req }
95
- 95 return detector.detect(detectParams)
96
- 96 },
97
- 97 parse: (params, context) => {
98
- 98 const parseParams = { ...params, _expressReq: context.raw.req, _expressRes: context.raw.res }
99
- 99 return detector.parse(parseParams)
100
- 100 }
101
- 101 })
102
- 102 log.info(`Express plugin registered file detector: ${detector.name}`)
103
- 103 }
104
- 104 }
105
- 105
106
- 106 // Create Express routers
107
- 107 const router = expressOptions.router || express.Router()
108
- 108 const notFoundRouter = express.Router()
109
- 109
110
- 110 // Add body parsing middleware
111
- 111 router.use(express.json({
112
- 112 limit: requestSizeLimit,
113
- 113 type: ['application/json', 'application/vnd.api+json']
114
- 114 }))
115
- 115
116
- 116 // Add transport hook middleware
117
- 117 router.use(async (req, res, next) => {
118
- 118 const context = createContext(req, res, 'express')
119
- 119
120
- 120 // Check for Express middleware override first
121
- 121 if (req.urlPrefixOverride) {
122
- 122 context.urlPrefixOverride = req.urlPrefixOverride
123
- 123 }
124
- 124
125
- 125 // Calculate and cache URL prefix (getUrlPrefix handles all logic)
126
- 126 const { getUrlPrefix } = await import('../lib/querying/url-helpers.js')
127
- 127 context.urlPrefix = getUrlPrefix(context, { vars: { transport: { mountPath: basePath } } }, req)
128
- 128
129
- 129 // Transport-specific data for hooks
130
- 130 const transportData = {
131
- 131 request: {
132
- 132 method: req.method,
133
- 133 url: req.url,
134
- 134 path: req.path,
135
- 135 headers: req.headers,
136
- 136 body: req.body,
137
- 137 params: req.params,
138
- 138 query: req.query
139
- 139 },
140
- 140 response: {
141
- 141 headers: {},
142
- 142 status: null
143
- 143 }
144
- 144 }
145
- 145
146
- 146 // Add transport data to context
147
- 147 context.transport = transportData
148
- 148
149
- 149 // Run transport hooks - don't check return value for consistency with other hooks
150
- 150 // Hooks communicate via context flags (rejection, handled) not return values
151
- 151 await runHooks('transport:request', context)
152
- 152
153
- 153 // Check if request was rejected (e.g., authentication failure)
154
- 154 if (context.rejection) {
155
- 155 // Apply response headers from hooks
156
- 156 if (transportData.response.headers) {
157
- 157 res.set(transportData.response.headers)
158
- 158 }
159
- 159 return res.status(context.rejection.status || 500).json({
160
- 160 errors: [{
161
- 161 status: String(context.rejection.status || 500),
162
- 162 title: context.rejection.title || 'Request Rejected',
163
- 163 detail: context.rejection.message
164
- 164 }]
165
- 165 })
166
- 166 }
167
- 167
168
- 168 // Check if request was already handled by a hook
169
- 169 if (context.handled) {
170
- 170 return
171
- 171 }
172
- 172
173
- 173 // Store transport data and context for later use
174
- 174 req.transportData = transportData
175
- 175 req.context = context
176
- 176 next()
177
- 177 })
178
- 178
179
- 179 // Content type validation middleware
180
- 180 if (strictContentType) {
181
- 181 router.use((req, res, next) => {
182
- 182 if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
183
- 183 const contentType = req.get('Content-Type')
184
- 184
185
- 185 if (contentType && !contentType.includes('application/vnd.api+json') &&
186
- 186 !contentType.includes('application/json') && !contentType.includes('multipart/form-data')) {
187
- 187 return res.status(415).json({
188
- 188 errors: [{
189
- 189 status: '415',
190
- 190 title: 'Unsupported Media Type',
191
- 191 detail: 'Content-Type must be application/vnd.api+json or application/json'
192
- 192 }]
193
- 193 })
194
- 194 }
195
- 195 }
196
- 196 next()
197
- 197 })
198
- 198 }
199
- 199
200
- 200 /**
201
- 201 * Error handler - maps REST API errors to HTTP responses
202
- 202 */
203
- 203 const handleError = async (error, req, res) => {
204
- 204 enhancedLog.logError('HTTP request error', error, {
205
- 205 method: req.method,
206
- 206 path: req.path,
207
- 207 url: req.url
208
- 208 })
209
- 209
210
- 210 let status = 500
211
- 211 const errorResponse = {
212
- 212 errors: [{
213
- 213 status: '500',
214
- 214 title: 'Internal Server Error',
215
- 215 detail: error.message
216
- 216 }]
217
- 217 }
218
- 218
219
- 219 // Map error codes to HTTP status
220
- 220 if (error.code === 'REST_API_VALIDATION') {
221
- 221 status = 422
222
- 222 errorResponse.errors = [{
223
- 223 status: '422',
224
- 224 title: 'Validation Error',
225
- 225 detail: error.message,
226
- 226 source: error.details
227
- 227 }]
228
- 228 if (error.details?.violations) {
229
- 229 errorResponse.errors = error.details.violations.map(v => ({
230
- 230 status: '422',
231
- 231 title: 'Validation Error',
232
- 232 detail: v.message,
233
- 233 source: { pointer: v.field }
234
- 234 }))
235
- 235 }
236
- 236 } else if (error.code === 'REST_API_RESOURCE') {
237
- 237 switch (error.subtype) {
238
- 238 case 'not_found':
239
- 239 status = 404
240
- 240 errorResponse.errors[0].status = '404'
241
- 241 errorResponse.errors[0].title = 'Not Found'
242
- 242 break
243
- 243 case 'conflict':
244
- 244 status = 409
245
- 245 errorResponse.errors[0].status = '409'
246
- 246 errorResponse.errors[0].title = 'Conflict'
247
- 247 break
248
- 248 case 'forbidden':
249
- 249 status = 403
250
- 250 errorResponse.errors[0].status = '403'
251
- 251 errorResponse.errors[0].title = 'Forbidden'
252
- 252 break
253
- 253 default:
254
- 254 status = 400
255
- 255 errorResponse.errors[0].status = '400'
256
- 256 errorResponse.errors[0].title = 'Bad Request'
257
- 257 }
258
- 258 } else if (error.code === 'REST_API_PAYLOAD') {
259
- 259 status = 400
260
- 260 errorResponse.errors = [{
261
- 261 status: '400',
262
- 262 title: 'Bad Request',
263
- 263 detail: error.message,
264
- 264 source: { pointer: error.path }
265
- 265 }]
266
- 266 }
267
- 267
268
- 268 // Run transport:response hook for errors
269
- 269 if (req.transportData && req.context) {
270
- 270 req.transportData.response.status = status
271
- 271 req.transportData.response.body = errorResponse
272
- 272 req.context.transport = req.transportData
273
- 273 await runHooks('transport:response', req.context)
274
- 274
275
- 275 // Apply response headers from hooks
276
- 276 if (req.transportData.response.headers) {
277
- 277 res.set(req.transportData.response.headers)
278
- 278 }
279
- 279 }
280
- 280
281
- 281 res.status(status).json(errorResponse)
282
- 282 }
283
- 283
284
- 284 /**
285
- 285 * Listen to addRoute hook to create Express routes
286
- 286 */
287
- 287 addHook('addRoute', 'expressRouteCreator', {}, async ({ context }) => {
288
- 288 const { method, path, handler } = context
289
- 289
290
- 290 // Apply any global before middleware
291
- 291 const beforeMiddleware = expressOptions.middleware?.beforeAll || []
292
- 292
293
- 293 try {
294
- 294 // Extract the handler logic into a shared function to keep it DRY (Don't Repeat Yourself).
295
- 295 const expressHandler = async (req, res) => {
296
- 296 try {
297
- 297 // Extract request data
298
- 298 const queryString = req.url.split('?')[1] || ''
299
- 299 const context = req.context || createContext(req, res, 'express')
300
- 300
301
- 301 // Call the generic handler
302
- 302 const result = await handler({
303
- 303 queryString,
304
- 304 headers: req.headers,
305
- 305 params: req.params,
306
- 306 body: req.body,
307
- 307 context
308
- 308 })
309
- 309
310
- 310 // Prepare response status
311
- 311 let responseStatus = 200
312
- 312 if (result && typeof result.statusCode === 'number') {
313
- 313 responseStatus = result.statusCode
314
- 314 } else if (req.method === 'POST' && result) {
315
- 315 // POST with content returns 201 Created
316
- 316 responseStatus = 201
317
- 317 } else if (req.method === 'POST' && !result) {
318
- 318 // POST without content returns 204 No Content
319
- 319 responseStatus = 204
320
- 320 } else if (req.method === 'DELETE') {
321
- 321 // DELETE always returns 204 No Content
322
- 322 responseStatus = 204
323
- 323 } else if ((req.method === 'PUT' || req.method === 'PATCH') && !result) {
324
- 324 // PUT/PATCH return 204 when no content is returned
325
- 325 responseStatus = 204
326
- 326 }
327
- 327
328
- 328 // Apply headers from the handler result
329
- 329 if (result && result.headers) {
330
- 330 res.set(result.headers)
331
- 331 }
332
- 332
333
- 333 // Update transport data for response (if transport data exists)
334
- 334 if (req.transportData) {
335
- 335 req.transportData.response.status = responseStatus
336
- 336 req.transportData.response.body = result
337
- 337 context.transport = req.transportData
338
- 338 await runHooks('transport:response', context)
339
- 339 if (req.transportData.response.headers) {
340
- 340 res.set(req.transportData.response.headers)
341
- 341 }
342
- 342 }
343
- 343
344
- 344 // Set content type
345
- 345 res.set('Content-Type', 'application/vnd.api+json')
346
- 346
347
- 347 // Set Location header for successful POST requests
348
- 348 if (req.method === 'POST' && context.id) {
349
- 349 // Extract scopeName from the path (e.g., /api/countries -> countries)
350
- 350 const pathParts = path.split('/')
351
- 351 const scopeName = pathParts[pathParts.length - 1]
352
- 352
353
- 353 // Set Location header for any successful POST (201 or 204)
354
- 354 if (helpers.getLocation) {
355
- 355 const location = helpers.getLocation({ scopeName, id: context.id })
356
- 356 const baseUrl = context.urlPrefix || basePath
357
- 357 res.set('Location', `${baseUrl}${location}`)
358
- 358 }
359
- 359 }
360
- 360
361
- 361 // Handle response based on status
362
- 362 if (responseStatus === 204) {
363
- 363 res.sendStatus(204)
364
- 364 } else {
365
- 365 // If result has a body property, that's what we should send
366
- 366 // This happens when handler returns { statusCode, body, headers }
367
- 367 const responseBody = result && result.body !== undefined ? result.body : result
368
- 368 res.status(responseStatus).json(responseBody)
369
- 369 }
370
- 370 } catch (error) {
371
- 371 handleError(error, req, res)
372
- 372 }
373
- 373 }
374
- 374
375
- 375 // CRITICAL: Express routing method selection - wildcard vs specific routes
376
- 376 //
377
- 377 // Express provides two different ways to register routes:
378
- 378 // 1. router.METHOD(path, handler) - e.g., router.get('/users', handler)
379
- 379 // - Only responds to the SPECIFIC HTTP method
380
- 380 // - Perfect for normal REST endpoints
381
- 381 //
382
- 382 // 2. router.use(path, handler)
383
- 383 // - Responds to ALL HTTP methods
384
- 384 // - Needed for wildcard paths that must handle any method
385
- 385 //
386
- 386 // The CORS plugin needs wildcard routes because it must handle OPTIONS
387
- 387 // requests for ANY path under the API prefix, even paths that don't exist
388
- 388 // as defined routes. For example:
389
- 389 // - Defined route: GET /api/users
390
- 390 // - Browser might send: OPTIONS /api/users/invalid/path
391
- 391 // - CORS must still respond with proper headers
392
- 392 //
393
- 393 if (path === vars.transport.matchAll) {
394
- 394 // This is a wildcard route (path = '*')
395
- 395 // We MUST use router.use() because:
396
- 396 // - We need to catch ALL paths (using '*' or '/api/*')
397
- 397 // - We need to handle a SPECIFIC method (e.g., OPTIONS)
398
- 398 // - router.options('*') would NOT work for paths like '/api/some/nested/path'
399
- 399
400
- 400 // Since router.use() responds to ALL methods, we need a wrapper
401
- 401 // that only handles our specific method (e.g., OPTIONS)
402
- 402 const methodSpecificMiddleware = (req, res, next) => {
403
- 403 if (req.method.toLowerCase() === method.toLowerCase()) {
404
- 404 expressHandler(req, res)
405
- 405 } else {
406
- 406 // Not our method, pass to next middleware
407
- 407 next()
408
- 408 }
409
- 409 }
410
- 410 // IMPORTANT: We do NOT need to pass a path to router.use() here!
411
- 411 // When no path is provided, router.use() matches ALL requests
412
- 412 // This is exactly what we want for wildcard routes
413
- 413 router.use(...beforeMiddleware, methodSpecificMiddleware)
414
- 414 } else {
415
- 415 // This is a normal route with a specific path (e.g., '/api/users')
416
- 416 // We use router.METHOD() because:
417
- 417 // - We want to respond to ONLY this specific HTTP method
418
- 418 // - The path is exact, not a wildcard
419
- 419 // - This is more efficient than router.use() with method checking
420
- 420 //
421
- 421 // Note: Routes from RestApiPlugin already include the full path with mountPath
422
- 422 // So we use them as-is without adding basePath to avoid double-prefixing
423
- 423
424
- 424 router[method.toLowerCase()](path, ...beforeMiddleware, expressHandler)
425
- 425
426
- 426 // Debug logging for route registration
427
- 427
428
- 428 // Check if the route was actually added
429
- 429 if (router.stack) {
430
- 430 const lastRoute = router.stack[router.stack.length - 1]
431
- 431 }
432
- 432 }
433
- 433 } catch (routeError) {
434
- 434 log.error('[EXPRESS DEBUG] Error creating route:', {
435
- 435 error: routeError.message,
436
- 436 stack: routeError.stack,
437
- 437 path,
438
- 438 method: method.toLowerCase()
439
- 439 })
440
- 440 throw routeError
441
- 441 }
442
- 442
443
- 443 const logPath = path === vars.transport.matchAll
444
- 444 ? '(all paths)' // router.use() with no path matches everything
445
- 445 : path
446
- 446 log.trace(`Express route created: ${method} ${logPath}`)
447
- 447 })
448
- 448
449
- 449 // Apply global middleware if configured
450
- 450 let finalRouter = router
451
- 451 if (expressOptions.middleware?.beforeAll) {
452
- 452 const globalRouter = express.Router()
453
- 453 globalRouter.use(...expressOptions.middleware.beforeAll)
454
- 454 globalRouter.use(router)
455
- 455 finalRouter = globalRouter
456
- 456 }
457
- 457
458
- 458 // Set up 404 handler in separate router (unless disabled)
459
- 459 if (expressOptions.handle404 !== false) {
460
- 460 notFoundRouter.use(async (req, res, next) => {
461
- 461 if (basePath && req.path.startsWith(basePath)) {
462
- 462 // Create minimal context for 404
463
- 463 const context = createContext(req, res, 'express')
464
- 464 const transportData = {
465
- 465 request: {
466
- 466 method: req.method,
467
- 467 url: req.url,
468
- 468 path: req.path,
469
- 469 headers: req.headers
470
- 470 },
471
- 471 response: {
472
- 472 headers: {},
473
- 473 status: 404,
474
- 474 body: {
475
- 475 errors: [{
476
- 476 status: '404',
477
- 477 title: 'Not Found',
478
- 478 detail: `The requested endpoint ${req.method} ${req.path} does not exist`
479
- 479 }]
480
- 480 }
481
- 481 }
482
- 482 }
483
- 483
484
- 484 // Add transport data to context
485
- 485 context.transport = transportData
486
- 486
487
- 487 // Run transport:response hook for 404
488
- 488 await runHooks('transport:response', context)
489
- 489
490
- 490 // Apply response headers from hooks
491
- 491 if (transportData.response.headers) {
492
- 492 res.set(transportData.response.headers)
493
- 493 }
494
- 494
495
- 495 res.status(404).json(transportData.response.body)
496
- 496 } else {
497
- 497 next()
498
- 498 }
499
- 499 })
500
- 500 }
501
- 501
502
- 502 // Store routers in api.http.express namespace
503
- 503 api.http.express.router = finalRouter
504
- 504 api.http.express.notFoundRouter = notFoundRouter
505
- 505
506
- 506 // Allow other plugins to add middleware before 404 handler
507
- 507 const beforeNotFoundMiddleware = []
508
- 508 api.http.express.beforeNotFound = (middleware) => {
509
- 509 beforeNotFoundMiddleware.push(middleware)
510
- 510 }
511
- 511
512
- 512 // Add convenient mounting method
513
- 513 api.http.express.mount = (app, path = '') => {
514
- 514 // Mount main router with all routes
515
- 515 app.use(path, finalRouter)
516
- 516
517
- 517 // Mount any middleware that should come before 404
518
- 518 beforeNotFoundMiddleware.forEach(middleware => {
519
- 519 app.use(path, middleware)
520
- 520 })
521
- 521
522
- 522 // Mount 404 handler router after all other routes (if enabled)
523
- 523 if (expressOptions.handle404 !== false) {
524
- 524 app.use(path, notFoundRouter)
525
- 525 }
526
- 526
527
- 527 log.info(`Express routes mounted at ${path || '/'}`)
528
- 528 }
529
- 529
530
- 530 log.info('Express plugin initialized successfully')
531
- 531 }
532
- 532 }