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,151 @@
1
+ import { describe, it, before, after, beforeEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import knexLib from 'knex'
4
+ import { Api } from 'hooked-api'
5
+ import { RestApiPlugin } from '../plugins/core/rest-api-plugin.js'
6
+ import { RestApiKnexPlugin } from '../plugins/core/rest-api-knex-plugin.js'
7
+ import {
8
+ cleanTables,
9
+ validateJsonApiStructure,
10
+ } from './helpers/test-utils.js'
11
+
12
+ const knex = knexLib({
13
+ client: 'better-sqlite3',
14
+ connection: {
15
+ filename: ':memory:'
16
+ },
17
+ useNullAsDefault: true
18
+ })
19
+
20
+ let api
21
+
22
+ describe('Logical resource ids in write validation', () => {
23
+ before(async () => {
24
+ api = new Api({
25
+ name: 'logical-id-write-validation-test'
26
+ })
27
+
28
+ await api.use(RestApiPlugin, {
29
+ simplifiedApi: false,
30
+ simplifiedTransport: false,
31
+ returnRecordApi: {
32
+ post: 'full',
33
+ put: 'full',
34
+ patch: 'full'
35
+ }
36
+ })
37
+ await api.use(RestApiKnexPlugin, { knex })
38
+
39
+ await api.addResource('profiles', {
40
+ tableName: 'logical_id_profiles',
41
+ idProperty: 'user_id',
42
+ schema: {
43
+ id: { type: 'id', required: true, storage: { column: 'user_id' } },
44
+ name: { type: 'string', required: true, max: 64 }
45
+ }
46
+ })
47
+ await api.resources.profiles.createKnexTable()
48
+ })
49
+
50
+ after(async () => {
51
+ await knex.destroy()
52
+ })
53
+
54
+ beforeEach(async () => {
55
+ await cleanTables(knex, ['logical_id_profiles'])
56
+ })
57
+
58
+ it('accepts explicit resource ids in non-simplified POST without exposing id as an attribute', async () => {
59
+ const created = await api.resources.profiles.post({
60
+ inputRecord: {
61
+ data: {
62
+ type: 'profiles',
63
+ id: '101',
64
+ attributes: {
65
+ name: 'Alice'
66
+ }
67
+ }
68
+ }
69
+ })
70
+
71
+ validateJsonApiStructure(created)
72
+ assert.equal(created.data.id, '101')
73
+ assert.equal(created.data.attributes.name, 'Alice')
74
+ assert.ok(!('id' in created.data.attributes))
75
+
76
+ const dbRow = await knex('logical_id_profiles').where('user_id', 101).first()
77
+ assert.ok(dbRow)
78
+ assert.equal(String(dbRow.user_id), '101')
79
+ assert.equal(dbRow.name, 'Alice')
80
+ })
81
+
82
+ it('reports missing logical ids against data.id during POST validation', async () => {
83
+ await assert.rejects(
84
+ () => api.resources.profiles.post({
85
+ inputRecord: {
86
+ data: {
87
+ type: 'profiles',
88
+ attributes: {
89
+ name: 'Missing Id'
90
+ }
91
+ }
92
+ }
93
+ }),
94
+ (error) => {
95
+ assert.equal(error.code, 'REST_API_VALIDATION')
96
+ assert.deepEqual(error.details?.fields, ['data.id'])
97
+ assert.equal(error.details?.violations?.[0]?.field, 'data.id')
98
+ return true
99
+ }
100
+ )
101
+ })
102
+
103
+ it('accepts explicit resource ids in simplified POST and still keeps id out of attributes', async () => {
104
+ const created = await api.resources.profiles.post({
105
+ id: '202',
106
+ name: 'Bob'
107
+ })
108
+
109
+ assert.equal(created.id, '202')
110
+ assert.equal(created.name, 'Bob')
111
+
112
+ const fetched = await api.resources.profiles.get({
113
+ id: '202',
114
+ simplified: false
115
+ })
116
+
117
+ validateJsonApiStructure(fetched)
118
+ assert.equal(fetched.data.id, '202')
119
+ assert.equal(fetched.data.attributes.name, 'Bob')
120
+ assert.ok(!('id' in fetched.data.attributes))
121
+ })
122
+
123
+ it('accepts logical id validation through PUT full replacement', async () => {
124
+ await knex('logical_id_profiles').insert({
125
+ user_id: 303,
126
+ name: 'Before'
127
+ })
128
+
129
+ const updated = await api.resources.profiles.put({
130
+ id: '303',
131
+ inputRecord: {
132
+ data: {
133
+ type: 'profiles',
134
+ id: '303',
135
+ attributes: {
136
+ name: 'After'
137
+ }
138
+ }
139
+ }
140
+ })
141
+
142
+ validateJsonApiStructure(updated)
143
+ assert.equal(updated.data.id, '303')
144
+ assert.equal(updated.data.attributes.name, 'After')
145
+ assert.ok(!('id' in updated.data.attributes))
146
+
147
+ const dbRow = await knex('logical_id_profiles').where('user_id', 303).first()
148
+ assert.ok(dbRow)
149
+ assert.equal(dbRow.name, 'After')
150
+ })
151
+ })
@@ -55,7 +55,7 @@ describe('Multi-field Cursor Pagination', () => {
55
55
  }
56
56
  })
57
57
 
58
- it.skip('should handle cursor pagination with multi-field sorting without skipping records', async () => {
58
+ it('should handle cursor pagination with multi-field sorting without skipping records', async () => {
59
59
  // Sort by category first (which has duplicates), then by name
60
60
  const pageSize = 3
61
61
  const allProducts = []
@@ -102,7 +102,7 @@ describe('Multi-field Cursor Pagination', () => {
102
102
  assert.equal(ids.length, uniqueIds.length, 'Should have no duplicate products')
103
103
  })
104
104
 
105
- it.skip('should handle DESC sorting with duplicates correctly', async () => {
105
+ it('should handle DESC sorting with duplicates correctly', async () => {
106
106
  // Sort by category DESC, then name ASC
107
107
  const firstPage = await api.resources.products.query({
108
108
  queryParams: {
@@ -189,7 +189,7 @@ describe('Multi-field Cursor Pagination', () => {
189
189
  'Green Gardens vegetables should be in alphabetical order by name')
190
190
  })
191
191
 
192
- it.skip('should handle cursor pagination with numeric and text fields', async () => {
192
+ it('should handle cursor pagination with numeric and text fields', async () => {
193
193
  // Sort by price (numeric), then category (text), then name (text)
194
194
  const firstPage = await api.resources.products.query({
195
195
  queryParams: {
@@ -222,7 +222,7 @@ describe('Multi-field Cursor Pagination', () => {
222
222
  'Prices should be non-decreasing across pages')
223
223
  })
224
224
 
225
- it.skip('should generate correct cursor values for multi-field sorting', async () => {
225
+ it('should generate correct cursor values for multi-field sorting', async () => {
226
226
  const result = await api.resources.products.query({
227
227
  queryParams: {
228
228
  page: { size: 3 },
@@ -244,7 +244,7 @@ describe('Multi-field Cursor Pagination', () => {
244
244
  assert(cursor.includes('name:'), 'Cursor should include name field')
245
245
  })
246
246
 
247
- it.skip('should handle page[before] with multi-field sorting', async () => {
247
+ it('should handle page[before] with multi-field sorting', async () => {
248
248
  // First, get to the middle of the dataset
249
249
  const firstPage = await api.resources.products.query({
250
250
  queryParams: {
@@ -272,7 +272,7 @@ describe('Multi-field Cursor Pagination', () => {
272
272
  size: 4,
273
273
  before: secondPage.meta.pagination.cursor?.next ||
274
274
  // If no next cursor, create one from the last record
275
- `category:${encodeURIComponent(secondPage.data[0].attributes.category)},name:${encodeURIComponent(secondPage.data[0].attributes.name)}`
275
+ `category:${encodeURIComponent(secondPage.data[0].attributes.category)},name:${encodeURIComponent(secondPage.data[0].attributes.name)},id:${encodeURIComponent(secondPage.data[0].id)}`
276
276
  },
277
277
  sort: ['category', 'name']
278
278
  },
@@ -292,7 +292,7 @@ describe('Multi-field Cursor Pagination', () => {
292
292
  await cleanTables(knex, ['cursor_products', 'cursor_items'])
293
293
  })
294
294
 
295
- it.skip('should handle all records having the same value in first sort field', async () => {
295
+ it('should handle all records having the same value in first sort field', async () => {
296
296
  // Create products all in the same category
297
297
  const products = [
298
298
  { name: 'Alpha', category: 'Electronics', brand: 'TechCo', price: 100, sku: 'TC-001' },
@@ -348,7 +348,7 @@ describe('Multi-field Cursor Pagination', () => {
348
348
  assert(gadgetIncIndex < firstTechCoIndex, 'GadgetInc products should come before TechCo')
349
349
  })
350
350
 
351
- it.skip('should handle empty results with multi-field cursor', async () => {
351
+ it('should handle empty results with multi-field cursor', async () => {
352
352
  // Create one product
353
353
  await api.resources.products.post({
354
354
  inputRecord: createJsonApiDocument('products', {
@@ -375,7 +375,7 @@ describe('Multi-field Cursor Pagination', () => {
375
375
  assert(!firstPage.meta.pagination.cursor?.next, 'Should not have next cursor on last page')
376
376
  })
377
377
 
378
- it.skip('should handle special characters in multi-field cursors', async () => {
378
+ it('should handle special characters in multi-field cursors', async () => {
379
379
  // Create products with special characters
380
380
  const products = [
381
381
  { name: 'Product: Special', category: 'Cat:1', brand: 'Brand,A', price: 10, sku: 'SP-001' },
@@ -418,7 +418,7 @@ describe('Multi-field Cursor Pagination', () => {
418
418
  'Should get different record on second page')
419
419
  })
420
420
 
421
- it.skip('should maintain stable sorting with custom id property', async () => {
421
+ it('should maintain stable sorting with custom id property', async () => {
422
422
  // Create items using the resource with custom ID property
423
423
  const items = [
424
424
  { code: 'A001', name: 'Apple', category: 'Fruit', type: 'Fresh' },
@@ -464,7 +464,7 @@ describe('Multi-field Cursor Pagination', () => {
464
464
  }
465
465
  })
466
466
 
467
- it.skip('should handle the exact bug scenario - multiple fruits with same category', async () => {
467
+ it('should handle the exact bug scenario - multiple fruits with same category', async () => {
468
468
  // This is the exact scenario from the bug report
469
469
  await cleanTables(knex, ['cursor_products'])
470
470
 
@@ -0,0 +1,319 @@
1
+ import { describe, it, before, beforeEach, after } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { Api } from 'hooked-api'
4
+ import knexLib from 'knex'
5
+ import { LabelPlugin, QueryProjectionsPlugin, RestApiKnexPlugin, RestApiPlugin } from '../index.js'
6
+ import {
7
+ cleanTables,
8
+ createJsonApiDocument,
9
+ createRelationship,
10
+ resourceIdentifier,
11
+ validateJsonApiStructure,
12
+ } from './helpers/test-utils.js'
13
+ import { createProjectedFieldsApi } from './fixtures/api-configs.js'
14
+
15
+ const knex = knexLib({
16
+ client: 'better-sqlite3',
17
+ connection: {
18
+ filename: ':memory:'
19
+ },
20
+ useNullAsDefault: true
21
+ })
22
+
23
+ describe('Query Projections', () => {
24
+ let api
25
+ const testData = {}
26
+
27
+ before(async () => {
28
+ api = await createProjectedFieldsApi(knex)
29
+ })
30
+
31
+ after(async () => {
32
+ await knex.destroy()
33
+ })
34
+
35
+ beforeEach(async () => {
36
+ await cleanTables(knex, ['projected_books', 'projected_authors'])
37
+
38
+ const authorPayloads = [
39
+ { first_name: 'Alex', last_name: 'Carter' },
40
+ { first_name: 'Alex', last_name: 'Carter' },
41
+ { first_name: 'Bella', last_name: 'Stone' },
42
+ { first_name: 'Aaron', last_name: 'Zephyr' }
43
+ ]
44
+
45
+ testData.authors = []
46
+ for (const payload of authorPayloads) {
47
+ const result = await api.resources.authors.post({
48
+ inputRecord: createJsonApiDocument('authors', payload),
49
+ simplified: false
50
+ })
51
+ testData.authors.push(result.data)
52
+ }
53
+
54
+ testData.expectedAuthorOrder = [
55
+ testData.authors[3].id,
56
+ testData.authors[0].id,
57
+ testData.authors[1].id,
58
+ testData.authors[2].id
59
+ ]
60
+
61
+ const bookOne = await api.resources.books.post({
62
+ inputRecord: createJsonApiDocument(
63
+ 'books',
64
+ { title: 'Derived Fields Handbook' },
65
+ { author: createRelationship(resourceIdentifier('authors', testData.authors[0].id)) }
66
+ ),
67
+ simplified: false
68
+ })
69
+
70
+ const bookTwo = await api.resources.books.post({
71
+ inputRecord: createJsonApiDocument(
72
+ 'books',
73
+ { title: 'Cursor Patterns' },
74
+ { author: createRelationship(resourceIdentifier('authors', testData.authors[2].id)) }
75
+ ),
76
+ simplified: false
77
+ })
78
+
79
+ testData.books = [bookOne.data, bookTwo.data]
80
+ })
81
+
82
+ async function createEphemeralProjectionApi (resourceName, resourceOptions) {
83
+ const api = new Api({
84
+ name: `${resourceName}-projection-test-api`,
85
+ log: { level: process.env.LOG_LEVEL || 'info' }
86
+ })
87
+
88
+ await api.use(RestApiPlugin, {
89
+ simplifiedApi: true,
90
+ simplifiedTransport: true,
91
+ returnRecordApi: {
92
+ post: true,
93
+ put: true,
94
+ patch: true
95
+ }
96
+ })
97
+ await api.use(QueryProjectionsPlugin)
98
+ await api.use(RestApiKnexPlugin, { knex })
99
+ await api.addResource(resourceName, resourceOptions)
100
+
101
+ return api
102
+ }
103
+
104
+ it('should return query projection fields by default and ignore query projection input on writes', async () => {
105
+ const created = await api.resources.authors.post({
106
+ inputRecord: createJsonApiDocument('authors', {
107
+ first_name: 'Nina',
108
+ last_name: 'Simone',
109
+ full_name: 'Injected Value'
110
+ }),
111
+ simplified: false
112
+ })
113
+
114
+ validateJsonApiStructure(created)
115
+ assert.equal(created.data.attributes.first_name, 'Nina')
116
+ assert.equal(created.data.attributes.last_name, 'Simone')
117
+ assert.equal(created.data.attributes.full_name, 'Nina Simone')
118
+
119
+ const fetched = await api.resources.authors.get({
120
+ id: created.data.id,
121
+ simplified: false
122
+ })
123
+
124
+ validateJsonApiStructure(fetched)
125
+ assert.equal(fetched.data.attributes.full_name, 'Nina Simone')
126
+ })
127
+
128
+ it('should allow query projection fields in sparse fieldsets', async () => {
129
+ const result = await api.resources.authors.get({
130
+ id: testData.authors[0].id,
131
+ queryParams: {
132
+ fields: {
133
+ authors: 'first_name,full_name'
134
+ }
135
+ },
136
+ simplified: false
137
+ })
138
+
139
+ validateJsonApiStructure(result)
140
+ assert.equal(result.data.attributes.first_name, 'Alex')
141
+ assert.equal(result.data.attributes.full_name, 'Alex Carter')
142
+ assert.equal(result.data.attributes.last_name, undefined)
143
+ })
144
+
145
+ it('should expose query projection fields on included resources', async () => {
146
+ const result = await api.resources.books.get({
147
+ id: testData.books[0].id,
148
+ queryParams: {
149
+ include: ['author'],
150
+ fields: {
151
+ books: 'title',
152
+ authors: 'full_name'
153
+ }
154
+ },
155
+ simplified: false
156
+ })
157
+
158
+ validateJsonApiStructure(result)
159
+ assert.ok(Array.isArray(result.included))
160
+
161
+ const includedAuthor = result.included.find((resource) => resource.type === 'authors')
162
+ assert.ok(includedAuthor, 'expected included author resource')
163
+ assert.equal(includedAuthor.attributes.full_name, 'Alex Carter')
164
+ assert.equal(includedAuthor.attributes.first_name, undefined)
165
+ assert.equal(includedAuthor.attributes.last_name, undefined)
166
+ })
167
+
168
+ it('should support stable cursor pagination when sorting by a query projection field', async () => {
169
+ const seenIds = []
170
+ const seenFirstNames = []
171
+ let nextCursor = null
172
+ let iterations = 0
173
+ let firstCursor = null
174
+
175
+ while (iterations < 10) {
176
+ const result = await api.resources.authors.query({
177
+ queryParams: {
178
+ sort: ['full_name'],
179
+ fields: {
180
+ authors: 'first_name'
181
+ },
182
+ page: {
183
+ size: 1,
184
+ ...(nextCursor ? { after: nextCursor } : {})
185
+ }
186
+ },
187
+ simplified: false
188
+ })
189
+
190
+ validateJsonApiStructure(result, true)
191
+ assert.equal(result.data.length, 1)
192
+
193
+ const resource = result.data[0]
194
+ seenIds.push(resource.id)
195
+ seenFirstNames.push(resource.attributes.first_name)
196
+ assert.equal(resource.attributes.full_name, undefined)
197
+
198
+ if (!firstCursor && result.meta?.pagination?.cursor?.next) {
199
+ firstCursor = result.meta.pagination.cursor.next
200
+ }
201
+
202
+ if (!result.meta?.pagination?.hasMore) {
203
+ break
204
+ }
205
+
206
+ nextCursor = result.meta.pagination.cursor.next
207
+ iterations += 1
208
+ }
209
+
210
+ assert.deepEqual(seenIds, testData.expectedAuthorOrder)
211
+ assert.equal(new Set(seenIds).size, seenIds.length, 'cursor pagination should not duplicate records')
212
+ assert.ok(firstCursor?.includes('full_name:'), 'cursor should encode query projection sort field')
213
+ assert.ok(firstCursor?.includes('id:'), 'cursor should append id as deterministic tie-breaker')
214
+ assert.deepEqual(seenFirstNames, ['Aaron', 'Alex', 'Alex', 'Bella'])
215
+ })
216
+
217
+ it('should allow sorting by a normallyHidden query projection field without returning it by default', async () => {
218
+ const result = await api.resources.authors.query({
219
+ queryParams: {
220
+ sort: ['sort_name']
221
+ },
222
+ simplified: false
223
+ })
224
+
225
+ validateJsonApiStructure(result, true)
226
+ assert.deepEqual(
227
+ result.data.map((resource) => resource.id),
228
+ testData.expectedAuthorOrder
229
+ )
230
+ result.data.forEach((resource) => {
231
+ assert.equal(resource.attributes.sort_name, undefined)
232
+ })
233
+ })
234
+
235
+ it('should reject query projection expressions that return plain SQL strings', async () => {
236
+ const stringApi = await createEphemeralProjectionApi('string_projection_authors', {
237
+ schema: {
238
+ id: { type: 'id' },
239
+ first_name: { type: 'string', required: true },
240
+ last_name: { type: 'string', required: true }
241
+ },
242
+ queryFields: {
243
+ full_name: {
244
+ type: 'string',
245
+ select: () => "trim(coalesce(first_name, '') || ' ' || coalesce(last_name, ''))"
246
+ }
247
+ },
248
+ tableName: 'projected_authors_string_expr'
249
+ })
250
+
251
+ await stringApi.resources.string_projection_authors.createKnexTable()
252
+ await knex('projected_authors_string_expr').insert({
253
+ first_name: 'Nina',
254
+ last_name: 'Simone'
255
+ })
256
+
257
+ await assert.rejects(
258
+ () => stringApi.resources.string_projection_authors.query({
259
+ queryParams: {},
260
+ simplified: false
261
+ }),
262
+ /Query field expressions must return knex raw, knex ref, or a knex query builder/
263
+ )
264
+ })
265
+
266
+ it('should reject query projection names that conflict with relationship names', async () => {
267
+ await assert.rejects(
268
+ () => createEphemeralProjectionApi('conflicting_projection_authors', {
269
+ schema: {
270
+ id: { type: 'id' },
271
+ name: { type: 'string', required: true }
272
+ },
273
+ queryFields: {
274
+ books: {
275
+ type: 'string',
276
+ select: ({ knex }) => knex.raw("'X'")
277
+ }
278
+ },
279
+ relationships: {
280
+ books: { type: 'hasMany', target: 'books', foreignKey: 'author_id' }
281
+ },
282
+ tableName: 'projected_authors_conflict'
283
+ }),
284
+ /conflicts with an existing schema/
285
+ )
286
+ })
287
+
288
+ it('should reject query projection names that conflict with computed fields injected by plugins', async () => {
289
+ const labeledApi = new Api({
290
+ name: 'label-projection-conflict-api',
291
+ log: { level: process.env.LOG_LEVEL || 'info' }
292
+ })
293
+
294
+ await labeledApi.use(RestApiPlugin, {
295
+ simplifiedApi: true,
296
+ simplifiedTransport: true
297
+ })
298
+ await labeledApi.use(LabelPlugin)
299
+ await labeledApi.use(QueryProjectionsPlugin)
300
+ await labeledApi.use(RestApiKnexPlugin, { knex })
301
+
302
+ await assert.rejects(
303
+ () => labeledApi.addResource('labeled_projection_authors', {
304
+ schema: {
305
+ id: { type: 'id' },
306
+ name: { type: 'string', required: true }
307
+ },
308
+ queryFields: {
309
+ label: {
310
+ type: 'string',
311
+ select: ({ knex }) => knex.raw("'X'")
312
+ }
313
+ },
314
+ tableName: 'projected_authors_label_conflict'
315
+ }),
316
+ /conflicts with an existing schema, computed, or relationship name/
317
+ )
318
+ })
319
+ })
@@ -330,7 +330,6 @@ describe('Relationship Operations', () => {
330
330
  }
331
331
  }
332
332
  }
333
- console.log('THE NEXT LINE WILL BLOCK THE TEST')
334
333
  await basicApi.resources.books.put({
335
334
  id: bookResult.data.id,
336
335
  inputRecord: putDoc,
@@ -590,7 +589,7 @@ describe('Relationship Operations', () => {
590
589
  assert(publisher.relationships.country)
591
590
  })
592
591
 
593
- it('should handle PUT with partial relationships object', async () => {
592
+ it('should clear omitted relationships when PUT provides a relationships object', async () => {
594
593
  // Create resources
595
594
  const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
596
595
  const countryResult = await basicApi.resources.countries.post({
@@ -640,7 +639,8 @@ describe('Relationship Operations', () => {
640
639
  simplified: false
641
640
  })
642
641
 
643
- // PUT with only publisher relationship (should preserve authors since relationships object is partial)
642
+ // PUT with only publisher relationship. Omitted relationships are cleared
643
+ // once a relationships object is provided for a complete replacement.
644
644
  const putDoc = {
645
645
  data: {
646
646
  type: 'books',