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
package/docs/API.md CHANGED
@@ -4,13 +4,17 @@ This reference provides comprehensive documentation for all methods, parameters,
4
4
 
5
5
  ## Important Notes
6
6
 
7
- 1. **Context Parameter**: The `context` parameter shown in method signatures is typically managed internally by the framework. When using the API programmatically, you usually don't need to provide it unless you're implementing custom authentication or request-specific data.
7
+ 1. **Context Parameter**: The `context` parameter shown in method signatures is typically managed internally by the framework. When using the API programmatically, you usually don't need to provide it unless you're implementing custom request identity/auth context or other request-specific data.
8
8
 
9
- 2. **Simplified Mode Defaults**:
9
+ 2. **App-Owned Auth Context**: `json-rest-api` does not define authentication semantics. If you use `context.auth` in hooks, treat it as application-owned data supplied by your transport or surrounding stack.
10
+
11
+ 3. **Simplified Mode Defaults**:
10
12
  - `simplifiedApi`: `true` (default for programmatic API calls)
11
13
  - `simplifiedTransport`: `false` (default for HTTP transport)
12
14
 
13
- 3. **All parameters must be passed within a single params object** as the first argument to each method.
15
+ 4. **All parameters must be passed within a single params object** as the first argument to each method.
16
+
17
+ 5. **Simplified shorthand is payload-only**: when you pass attributes directly (for example, `post({ title: 'Hello' })`), that plain object is treated as the resource payload. If you also need per-call options such as `transaction`, `queryParams`, `simplified`, or `returnFullRecord`, put the record under `inputRecord` and keep those options at the top level of `params`.
14
18
 
15
19
  ## Table of Contents
16
20
 
@@ -289,6 +293,8 @@ const result = await api.resources.[resourceType].get(params, context)
289
293
  | `simplified` | Boolean | No | Override simplified mode setting (default: true) |
290
294
  | `transaction` | Object | No | Database transaction object |
291
295
 
296
+ `id` is normalized with the effective `normalizeId` function before validation and lookup. If it normalizes to an empty value, the operation fails as `REST_API_RESOURCE` with subtype `not_found`.
297
+
292
298
  #### Return Value
293
299
 
294
300
  **JSON:API Mode (simplified: false):**
@@ -428,7 +434,9 @@ const result = await api.resources.[resourceType].post(params, context)
428
434
  | Parameter | Type | Required | Description |
429
435
  |-----------|------|----------|-------------|
430
436
  | `inputRecord` | Object | Yes | Resource data to create |
437
+ | `inputRecord.id` | String | No (simplified) | Optional explicit logical resource ID |
431
438
  | `inputRecord.data` | Object | Yes (JSON:API) | Resource data container |
439
+ | `inputRecord.data.id` | String | No (JSON:API) | Optional explicit logical resource ID |
432
440
  | `inputRecord.data.type` | String | Yes (JSON:API) | Resource type |
433
441
  | `inputRecord.data.attributes` | Object | Yes (JSON:API) | Resource attributes |
434
442
  | `inputRecord.data.relationships` | Object | No | Related resources |
@@ -437,6 +445,8 @@ const result = await api.resources.[resourceType].post(params, context)
437
445
  | `transaction` | Object | No | Database transaction object |
438
446
  | `returnFullRecord` | String | No | Override return setting ('no', 'minimal', 'full') |
439
447
 
448
+ When an explicit resource id is provided, `normalizeId` runs before persistence and before any follow-up record fetch used for the return payload. If the normalized id is empty, the request fails as `REST_API_VALIDATION` on `data.id`.
449
+
440
450
  #### Return Value Behavior
441
451
 
442
452
  The return value depends on `returnFullRecord` setting and whether it's an API or transport call:
@@ -513,6 +523,25 @@ const result = await api.resources.articles.post({
513
523
  });
514
524
  ```
515
525
 
526
+ **Create with Explicit ID and Normalization:**
527
+ ```javascript
528
+ // With normalizeId configured to trim and uppercase ids,
529
+ // this record is stored as ARTICLE-42
530
+ const result = await api.resources.articles.post({
531
+ inputRecord: {
532
+ data: {
533
+ type: 'articles',
534
+ id: ' article-42 ',
535
+ attributes: {
536
+ title: 'Canonical ID Example',
537
+ content: 'Stored under the normalized id'
538
+ }
539
+ }
540
+ },
541
+ simplified: false
542
+ });
543
+ ```
544
+
516
545
  **Create with Multiple Relationships:**
517
546
  ```javascript
518
547
  // Create article with author and tags (simplified)
@@ -589,8 +618,8 @@ const result = await api.resources.[resourceType].put(params, context)
589
618
  | Parameter | Type | Required | Description |
590
619
  |-----------|------|----------|-------------|
591
620
  | `inputRecord` | Object | Yes | Complete resource data |
592
- | `inputRecord.id` | String | Yes (simplified) | Resource ID |
593
- | `inputRecord.data.id` | String | Yes (JSON:API) | Must match the resource ID |
621
+ | `inputRecord.id` | String | Yes (simplified) | Logical resource ID |
622
+ | `inputRecord.data.id` | String | Yes (JSON:API) | Logical resource ID |
594
623
  | `inputRecord.data.type` | String | Yes (JSON:API) | Resource type |
595
624
  | `inputRecord.data.attributes` | Object | Yes (JSON:API) | All resource attributes |
596
625
  | `inputRecord.data.relationships` | Object | No | All relationships (missing ones are nulled) |
@@ -599,6 +628,10 @@ const result = await api.resources.[resourceType].put(params, context)
599
628
  | `transaction` | Object | No | Database transaction object |
600
629
  | `returnFullRecord` | String | No | Override return setting |
601
630
 
631
+ `inputRecord.id` and `inputRecord.data.id` always refer to the logical resource id. `idProperty` and storage mapping affect the backing column name, not the API field name, and the resource id is not part of `attributes`.
632
+
633
+ If both the URL id and body id are present, both are normalized before the equality check. If either id normalizes to an empty value, the operation fails before storage is touched.
634
+
602
635
  #### Return Value
603
636
 
604
637
  Updated resource based on `returnFullRecord` setting (defaults: API='full', transport='no').
@@ -716,8 +749,8 @@ const result = await api.resources.[resourceType].patch(params, context)
716
749
  | Parameter | Type | Required | Description |
717
750
  |-----------|------|----------|-------------|
718
751
  | `inputRecord` | Object | Yes | Partial resource data |
719
- | `inputRecord.id` | String | Yes (simplified) | Resource ID |
720
- | `inputRecord.data.id` | String | Yes (JSON:API) | Resource ID |
752
+ | `inputRecord.id` | String | Yes (simplified) | Logical resource ID |
753
+ | `inputRecord.data.id` | String | Yes (JSON:API) | Logical resource ID |
721
754
  | `inputRecord.data.type` | String | Yes (JSON:API) | Resource type |
722
755
  | `inputRecord.data.attributes` | Object | No | Attributes to update |
723
756
  | `inputRecord.data.relationships` | Object | No | Relationships to update |
@@ -726,6 +759,8 @@ const result = await api.resources.[resourceType].patch(params, context)
726
759
  | `transaction` | Object | No | Database transaction object |
727
760
  | `returnFullRecord` | String | No | Override return setting |
728
761
 
762
+ If both the URL id and body id are present, both are normalized before the equality check. If either id normalizes to an empty value, the operation fails before storage is touched.
763
+
729
764
  #### Return Value
730
765
 
731
766
  Updated resource based on `returnFullRecord` setting (defaults: API='full', transport='no').
@@ -847,6 +882,8 @@ const result = await api.resources.[resourceType].delete(params, context)
847
882
  | `id` | String\|Number | Yes | ID of resource to delete |
848
883
  | `transaction` | Object | No | Database transaction object |
849
884
 
885
+ `id` is normalized with the effective `normalizeId` function before validation and lookup. If it normalizes to an empty value, the operation fails as `REST_API_RESOURCE` with subtype `not_found`.
886
+
850
887
  #### Return Value
851
888
 
852
889
  Returns `undefined` (204 No Content)
@@ -933,6 +970,8 @@ const result = await api.resources.[resourceType].getRelated(params, context)
933
970
  | `queryParams` | Object | No | Standard query parameters for related resources |
934
971
  | `transaction` | Object | No | Database transaction object |
935
972
 
973
+ `id` is normalized with the effective `normalizeId` function before validation and lookup. If it normalizes to an empty value, the operation fails as `REST_API_RESOURCE` with subtype `not_found`.
974
+
936
975
  #### Return Value
937
976
 
938
977
  JSON:API response with related resources (supports all query features like filtering, pagination, etc.)
@@ -1005,6 +1044,8 @@ const result = await api.resources.[resourceType].getRelationship(params, contex
1005
1044
  }
1006
1045
  ```
1007
1046
 
1047
+ `id` is normalized with the effective `normalizeId` function before validation and lookup. If it normalizes to an empty value, the operation fails as `REST_API_RESOURCE` with subtype `not_found`.
1048
+
1008
1049
  #### Return Value
1009
1050
 
1010
1051
  JSON:API relationship document with resource identifiers only
@@ -1079,6 +1120,8 @@ const result = await api.resources.[resourceType].postRelationship(params, conte
1079
1120
  }
1080
1121
  ```
1081
1122
 
1123
+ The parent `id` is normalized with the parent resource's normalizer. Each `relationshipData[*].id` is normalized with the related target resource's normalizer before validation and persistence.
1124
+
1082
1125
  #### Return Value
1083
1126
 
1084
1127
  Returns `undefined` (204 No Content)
@@ -1114,6 +1157,8 @@ await api.resources.articles.postRelationship({
1114
1157
  // Existing tags remain, new tags are added
1115
1158
  ```
1116
1159
 
1160
+ If `normalizeId` is configured, both the parent id and each relationship identifier are normalized before the relationship is written.
1161
+
1117
1162
  ---
1118
1163
 
1119
1164
  ### patchRelationship - Replace Relationship
@@ -1136,6 +1181,8 @@ const result = await api.resources.[resourceType].patchRelationship(params, cont
1136
1181
  }
1137
1182
  ```
1138
1183
 
1184
+ The parent `id` is normalized with the parent resource's normalizer. Each relationship identifier inside `relationshipData` is normalized with the related target resource's normalizer before validation and persistence.
1185
+
1139
1186
  #### Return Value
1140
1187
 
1141
1188
  Returns `undefined` (204 No Content)
@@ -1217,6 +1264,8 @@ const result = await api.resources.[resourceType].deleteRelationship(params, con
1217
1264
  }
1218
1265
  ```
1219
1266
 
1267
+ The parent `id` is normalized with the parent resource's normalizer. Each `relationshipData[*].id` is normalized with the related target resource's normalizer before validation and persistence.
1268
+
1220
1269
  #### Return Value
1221
1270
 
1222
1271
  Returns `undefined` (204 No Content)
@@ -1400,7 +1449,7 @@ Each API method has its own specific hook execution order. Here's the exact sequ
1400
1449
 
1401
1450
  1. **Processing Hooks**: Only POST, PUT, and PATCH have `beforeProcessing` hooks
1402
1451
  2. **Schema Validation**: Only POST, PUT, and PATCH have schema validation hooks
1403
- 3. **Permission Checks**: GET uses `checkDataPermissions`, while relationship methods use `checkPermissions`
1452
+ 3. **Permission Hooks**: GET uses `checkDataPermissions`, while relationship methods use `checkPermissions`
1404
1453
  4. **Query Filtering**: Only QUERY method triggers `knexQueryFiltering` hooks
1405
1454
  5. **Enrichment**: Only GET and QUERY have `enrichRecord` hooks
1406
1455
  6. **Relationships**: Only GET has `enrichRecordWithRelationships`
@@ -1421,7 +1470,7 @@ Each hook receives a context object with different properties based on the hook
1421
1470
  queryParams: {...},
1422
1471
  simplified: true
1423
1472
  },
1424
- auth: {...}, // Authentication info
1473
+ auth: {...}, // App-provided auth/identity context if present
1425
1474
  transaction: {...}, // Database transaction
1426
1475
  schemaInfo: {...}, // Resource schema
1427
1476
  db: {...} // Database connection
@@ -1490,7 +1539,7 @@ api.resource('articles').hook('afterSchemaValidate', async (context) => {
1490
1539
  }
1491
1540
  ```
1492
1541
 
1493
- **Purpose:** Authorization checks - throw error to deny access
1542
+ **Purpose:** Permission or visibility checks implemented by your app hooks. Throw an error to deny the operation.
1494
1543
 
1495
1544
  **Example:**
1496
1545
  ```javascript
@@ -2073,24 +2122,30 @@ api.resource('articles').hook('afterDataQuery', async (context) => {
2073
2122
  ### Plugin Configuration
2074
2123
 
2075
2124
  ```javascript
2076
- const restApiPlugin = new RestApiPlugin({
2125
+ await api.use(RestApiPlugin, {
2077
2126
  // API behavior
2078
2127
  simplifiedApi: true, // Use simplified mode for programmatic calls (default: true)
2079
2128
  simplifiedTransport: false, // Use JSON:API for HTTP transport (default: false)
2129
+ normalizeId: (value) => {
2130
+ if (value === null || value === undefined) {
2131
+ return null
2132
+ }
2133
+
2134
+ const normalized = String(value).trim()
2135
+ return normalized ? normalized.toUpperCase() : null
2136
+ },
2080
2137
 
2081
2138
  // Return record configuration
2082
2139
  returnRecordApi: {
2083
2140
  post: 'full', // Return full record after create (default)
2084
2141
  put: 'full', // Return full record after replace (default)
2085
- patch: 'full', // Return full record after update (default)
2086
- delete: 'no' // Return nothing after delete (default)
2142
+ patch: 'full' // Return full record after update (default)
2087
2143
  },
2088
2144
 
2089
2145
  returnRecordTransport: {
2090
2146
  post: 'no', // Return 204 for HTTP POST (default)
2091
2147
  put: 'no', // Return 204 for HTTP PUT (default)
2092
- patch: 'no', // Return 204 for HTTP PATCH (default)
2093
- delete: 'no' // Return 204 for HTTP DELETE (default)
2148
+ patch: 'no' // Return 204 for HTTP PATCH (default)
2094
2149
  },
2095
2150
 
2096
2151
  // Query limits
@@ -2101,15 +2156,7 @@ const restApiPlugin = new RestApiPlugin({
2101
2156
  includeDepthLimit: 3, // Maximum relationship nesting depth
2102
2157
 
2103
2158
  // Performance
2104
- enablePaginationCounts: true, // Execute count queries for total pages
2105
-
2106
- // Error handling
2107
- exposeErrors: false, // Include error details in responses
2108
-
2109
- // Custom serializers
2110
- serializers: {
2111
- articles: customArticleSerializer
2112
- }
2159
+ enablePaginationCounts: true // Execute count queries for total pages
2113
2160
  });
2114
2161
  ```
2115
2162
 
@@ -2124,7 +2171,7 @@ api.addResource({
2124
2171
  name: 'articles',
2125
2172
 
2126
2173
  // Primary key configuration
2127
- idProperty: 'id', // Custom primary key field (default: 'id')
2174
+ idProperty: 'id', // Physical primary-key column name for table-backed resources
2128
2175
 
2129
2176
  schema: {
2130
2177
  // Attributes
@@ -2243,13 +2290,9 @@ api.addResource({
2243
2290
  }
2244
2291
  },
2245
2292
 
2246
- // Permissions
2247
- permissions: {
2248
- create: ['author', 'admin'],
2249
- read: ['*'],
2250
- update: ['author', 'editor', 'admin'],
2251
- delete: ['admin']
2252
- },
2293
+ // No built-in permissions option exists here.
2294
+ // Use permission hooks for access decisions, and optional plugins
2295
+ // such as AutoFilterPlugin for dataset scoping.
2253
2296
 
2254
2297
  // Soft delete configuration
2255
2298
  softDelete: {
@@ -2275,19 +2318,95 @@ api.addResource({
2275
2318
 
2276
2319
  #### ID Property Configuration
2277
2320
  ```javascript
2278
- // Custom primary key
2321
+ // Physical primary key column: user_id
2279
2322
  api.addResource({
2280
2323
  name: 'users',
2281
- idProperty: 'user_id', // Use 'user_id' instead of 'id'
2324
+ idProperty: 'user_id',
2282
2325
  schema: {
2283
- attributes: {
2284
- user_id: { type: 'number', required: true },
2285
- email: { type: 'string', required: true }
2326
+ id: { type: 'id', required: true, storage: { column: 'user_id' } },
2327
+ email: { type: 'string', required: true },
2328
+ displayName: { type: 'string' }
2329
+ }
2330
+ });
2331
+ ```
2332
+
2333
+ `idProperty` names the physical primary-key column for table-backed resources. At the API layer, the resource id remains `id`, so writes use `inputRecord.id` or `inputRecord.data.id`, reads return `id` or `data.id`, and the resource id is not part of `attributes`.
2334
+
2335
+ #### Resource ID Normalization
2336
+
2337
+ `normalizeId` canonicalizes resource identifiers before the library reads, writes, validates, or links records.
2338
+
2339
+ The default normalizer:
2340
+
2341
+ - trims surrounding whitespace from strings
2342
+ - converts finite numbers and bigints to strings
2343
+ - rejects values that normalize to an empty identifier
2344
+
2345
+ Provide a custom normalizer when your ids need additional canonicalization:
2346
+
2347
+ ```javascript
2348
+ await api.use(RestApiPlugin, {
2349
+ normalizeId: (value) => {
2350
+ if (value === null || value === undefined) {
2351
+ return null
2352
+ }
2353
+
2354
+ const normalized = String(value).trim()
2355
+ return normalized ? normalized.toUpperCase() : null
2356
+ }
2357
+ });
2358
+ ```
2359
+
2360
+ Resource-level overrides take precedence over the plugin-level default:
2361
+
2362
+ ```javascript
2363
+ await api.addResource('countries', {
2364
+ normalizeId: (value) => {
2365
+ if (value === null || value === undefined) {
2366
+ return null
2286
2367
  }
2368
+
2369
+ const normalized = String(value).trim()
2370
+ return normalized ? normalized.toLowerCase() : null
2371
+ },
2372
+ schema: {
2373
+ name: { type: 'string', required: true }
2374
+ }
2375
+ });
2376
+ ```
2377
+
2378
+ Normalization is applied to:
2379
+
2380
+ - parent ids used by `get`, `put`, `patch`, `delete`, `getRelated`, and `getRelationship`
2381
+ - explicit `POST`, `PUT`, and `PATCH` document ids
2382
+ - relationship identifiers used by `postRelationship`, `patchRelationship`, and `deleteRelationship`
2383
+
2384
+ Relationship identifiers use the normalizer of the related target resource, not the parent resource.
2385
+
2386
+ If an id normalizes to an empty value:
2387
+
2388
+ - existing-resource operations fail as `REST_API_RESOURCE` with subtype `not_found`
2389
+ - explicit `POST` document ids fail as `REST_API_VALIDATION` on `data.id`
2390
+
2391
+ #### Storage Column Naming
2392
+
2393
+ Table-backed resources use snake_case physical columns by default. For example, `displayName` maps to `display_name`.
2394
+
2395
+ Use `storage.column` when a field needs a specific column name:
2396
+
2397
+ ```javascript
2398
+ api.addResource({
2399
+ name: 'profiles',
2400
+ schema: {
2401
+ id: { type: 'id' },
2402
+ displayName: { type: 'string', required: true },
2403
+ legacyRef: { type: 'string', storage: { column: 'legacy_profile_ref' } }
2287
2404
  }
2288
2405
  });
2289
2406
  ```
2290
2407
 
2408
+ If physical columns should match logical field names exactly for an entire resource, use `storage: { naming: 'exact' }`.
2409
+
2291
2410
  #### Virtual Fields
2292
2411
  Virtual fields are excluded from database operations but can be used for temporary UI state:
2293
2412
 
@@ -2499,6 +2618,45 @@ try {
2499
2618
 
2500
2619
  **Important:** When you provide a transaction, you're responsible for committing/rolling back. When you don't provide one, the library auto-commits.
2501
2620
 
2621
+ ### Knex Schema Helpers
2622
+
2623
+ When a resource is backed by `RestApiKnexPlugin`, its scope also exposes table-management helpers:
2624
+
2625
+ ```javascript
2626
+ await api.resources.articles.createKnexTable()
2627
+
2628
+ const snapshot = await api.resources.articles.introspectKnexTableSnapshot()
2629
+ const createMigration = await api.resources.articles.generateKnexMigration()
2630
+ const diff = await api.resources.articles.generateKnexMigrationDiff()
2631
+ ```
2632
+
2633
+ Available helpers:
2634
+
2635
+ - `createKnexTable()`
2636
+ - `addKnexFields({ fields })`
2637
+ - `alterKnexFields({ fields })`
2638
+ - `introspectKnexTableSnapshot()`
2639
+ - `generateKnexMigration()`
2640
+ - `generateKnexMigrationDiff()`
2641
+
2642
+ `generateKnexMigrationDiff()` returns:
2643
+
2644
+ ```javascript
2645
+ {
2646
+ migration,
2647
+ warnings,
2648
+ plan
2649
+ }
2650
+ ```
2651
+
2652
+ Important boundaries:
2653
+
2654
+ - this is a live-table snapshot diff, not migration-history reconstruction
2655
+ - `addKnexFields()` and `alterKnexFields()` are field-only helpers
2656
+ - indexes, foreign keys, and check constraints belong on the full table schema surface
2657
+
2658
+ For full examples and dialect notes, see [Knex Schema and Migrations](GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md).
2659
+
2502
2660
  ### Batch Operations
2503
2661
 
2504
2662
  Process multiple operations efficiently:
@@ -2746,4 +2904,4 @@ api.resource('articles').hook('knexQueryFiltering', async (context) => {
2746
2904
  query.whereRaw("metadata->>'key' = ?", [filters.metadata_key]);
2747
2905
  }
2748
2906
  });
2749
- ```
2907
+ ```
@@ -30,7 +30,7 @@ json-rest-api is a full-featured, plugin-based JSON:API implementation built on
30
30
  | WebSocket support | ✅ | ❌ | ❌ | ❌ | ❌ |
31
31
  | Bulk operations | ✅ | ❌ | ❌ | ❌ | ❌ |
32
32
  | Positioning/sorting | ✅ | ❌ | ❌ | ❌ | ❌ |
33
- | Authentication | ✅ | ❌ | ⚠️ | ❌ | ❌ |
33
+ | Permission hooks / app-owned auth integration | ✅ | ❌ | ⚠️ | ❌ | ❌ |
34
34
  | **Developer Experience** |
35
35
  | TypeScript support | ✅ | ✅ | ✅ | ❌ | ❌ |
36
36
  | Active maintenance | ✅ (2025) | ✅ (2024) | ✅ (2024) | 🟡 (2022) | ❌ (2022) |
@@ -430,4 +430,4 @@ json-rest-api stands out as the most comprehensive JSON:API solution for Node.js
430
430
  4. **Active Development**: Regular updates and community support
431
431
  5. **Production Ready**: Battle-tested with extensive test coverage
432
432
 
433
- For teams building JSON:API services in 2025, json-rest-api offers the best combination of compliance, features, and developer experience.
433
+ For teams building JSON:API services in 2025, json-rest-api offers the best combination of compliance, features, and developer experience.
@@ -325,6 +325,8 @@ const result = await api.resources.countries.post({
325
325
  });
326
326
  ```
327
327
 
328
+ If you need per-call options such as `transaction`, `returnFullRecord`, or `queryParams`, move the record under `inputRecord` and keep those options at the top level of the params object.
329
+
328
330
  By default, `simplifiedApi` is `true` for programmatic usage, making it easier to work with the API in your code while still maintaining full JSON:API compliance internally.
329
331
 
330
332
  #### API usage and returning records
@@ -778,6 +780,7 @@ Here is a full list of parameters and their respective variables:
778
780
  | `simplifiedApi` | `vars.simplifiedApi` | `true` | Use simplified format for programmatic API calls | ✓ |
779
781
  | `simplifiedTransport` | `vars.simplifiedTransport` | `false` | Use simplified format for HTTP/REST endpoints | ✓ |
780
782
  | `idProperty` | `vars.idProperty` | `'id'` | Name of the ID field in resources | ✓ |
783
+ | `normalizeId` | `vars.normalizeId` | trim strings, stringify finite numbers | Normalize resource identifiers before lookups and writes | ✓ |
781
784
  | `returnRecordApi` | `vars.returnRecordApi` | `{ post: 'full', put: 'full', patch: 'full' }` | What to return for programmatic API write operations | ✓ |
782
785
  | `returnRecordTransport` | `vars.returnRecordTransport` | `{ post: 'no', put: 'no', patch: 'no' }` | What to return for HTTP/REST write operations | ✓ |
783
786
 
@@ -795,9 +798,79 @@ Here is a full list of parameters and their respective variables:
795
798
  - An object: `{ post: 'full', put: 'minimal', patch: 'no' }` (per-method configuration)
796
799
  - All parameters support the cascade: per-call → resource-level → plugin-level default
797
800
 
798
- # Custom ID parameter
801
+ # Logical IDs and Physical Columns
802
+
803
+ For table-backed resources, `idProperty` defines the physical primary-key column. The API surface still uses the logical resource id.
804
+
805
+ - simplified writes use `inputRecord.id`
806
+ - JSON:API writes use `inputRecord.data.id`
807
+ - responses expose `id` or `data.id`
808
+ - the resource id is not part of `attributes`
809
+
810
+ ```javascript
811
+ await api.addResource('profiles', {
812
+ idProperty: 'user_id',
813
+ schema: {
814
+ id: { type: 'id', required: true, storage: { column: 'user_id' } },
815
+ displayName: { type: 'string', required: true },
816
+ loginCount: { type: 'number', defaultTo: 0 }
817
+ },
818
+ tableName: 'profiles'
819
+ });
820
+ ```
821
+
822
+ For table-backed resources, storage columns default to snake_case, so `displayName` maps to `display_name` and `loginCount` maps to `login_count`. Use `storage.column` for a per-field override, or `storage: { naming: 'exact' }` when physical column names should match logical field names exactly.
823
+
824
+ ## ID normalization
825
+
826
+ `normalizeId` lets you canonicalize resource identifiers before the library reads, writes, validates, or links records.
799
827
 
800
- TODO: Explain how idParam works, clarify that for the api it's always 'id' and there is no ID in the attributes
828
+ The default normalizer already handles the common cases:
829
+
830
+ - trims surrounding whitespace from strings
831
+ - converts finite numbers and bigints to strings
832
+ - rejects values that normalize to an empty identifier
833
+
834
+ You only need to provide `normalizeId` when your resource ids need additional canonicalization, such as uppercasing, lowercasing, or removing formatting characters.
835
+
836
+ ```javascript
837
+ await api.use(RestApiPlugin, {
838
+ normalizeId: (value) => {
839
+ if (value === null || value === undefined) return null
840
+
841
+ const normalized = String(value).trim()
842
+ return normalized ? normalized.toUpperCase() : null
843
+ }
844
+ })
845
+
846
+ await api.addResource('countries', {
847
+ // Override the plugin-wide normalizer for this one resource
848
+ normalizeId: (value) => {
849
+ if (value === null || value === undefined) return null
850
+
851
+ const normalized = String(value).trim()
852
+ return normalized ? normalized.toLowerCase() : null
853
+ },
854
+ schema: {
855
+ name: { type: 'string', required: true }
856
+ }
857
+ })
858
+ ```
859
+
860
+ Normalization is applied consistently to:
861
+
862
+ - `get`, `put`, `patch`, `delete`, `getRelated`, and `getRelationship` parent ids
863
+ - explicit resource ids in `POST`, `PUT`, and `PATCH` documents
864
+ - relationship identifiers supplied to `postRelationship`, `patchRelationship`, and `deleteRelationship`
865
+
866
+ The resource-level `normalizeId` override also applies to relationship identifiers when that resource appears as the related target.
867
+
868
+ If an id normalizes to an empty value:
869
+
870
+ - existing-resource operations behave as `not_found`
871
+ - explicit `POST` document ids are rejected as validation errors on `data.id`
872
+
873
+ For full examples of table creation, physical column naming, and migration generation, see [Knex Schema and Migrations](GUIDE_X_Knex_Schema_And_Migrations.md).
801
874
 
802
875
  # Helpers and Methods Provided by REST API Plugins
803
876
 
@@ -828,8 +901,14 @@ When you install the REST API plugins, the following helpers are added to `api.h
828
901
  const trx = await api.helpers.newTransaction();
829
902
  try {
830
903
  // Use transaction in multiple operations
831
- await api.resources.countries.post({ name: 'France', code: 'FR' }, { transaction: trx });
832
- await api.resources.publishers.post({ name: 'French Press', country: 1 }, { transaction: trx });
904
+ await api.resources.countries.post({
905
+ inputRecord: { name: 'France', code: 'FR' },
906
+ transaction: trx
907
+ });
908
+ await api.resources.publishers.post({
909
+ inputRecord: { name: 'French Press', country: 1 },
910
+ transaction: trx
911
+ });
833
912
  await trx.commit();
834
913
  } catch (error) {
835
914
  await trx.rollback();
@@ -928,6 +1007,32 @@ Each resource (added via `api.addResource()`) gets these methods automatically:
928
1007
  await api.resources.countries.createKnexTable();
929
1008
  ```
930
1009
 
1010
+ - **`api.resources.{resourceName}.introspectKnexTableSnapshot()`** - Returns a normalized snapshot of the live physical table
1011
+ ```javascript
1012
+ const snapshot = await api.resources.countries.introspectKnexTableSnapshot();
1013
+ ```
1014
+
1015
+ - **`api.resources.{resourceName}.generateKnexMigration()`** - Generates a Knex create migration string for the resource
1016
+ ```javascript
1017
+ const migration = await api.resources.countries.generateKnexMigration();
1018
+ ```
1019
+
1020
+ - **`api.resources.{resourceName}.generateKnexMigrationDiff()`** - Generates an additive diff migration from the live table snapshot
1021
+ ```javascript
1022
+ const diff = await api.resources.countries.generateKnexMigrationDiff();
1023
+ ```
1024
+
1025
+ - **`api.resources.{resourceName}.addKnexFields({ fields })`** / **`alterKnexFields({ fields })`** - Column-only helpers for existing tables
1026
+ ```javascript
1027
+ await api.resources.countries.addKnexFields({
1028
+ fields: {
1029
+ iso3: { type: 'string', maxLength: 3 }
1030
+ }
1031
+ });
1032
+ ```
1033
+
1034
+ For full examples of snapshots, migration generation, diffing, and table metadata such as indexes / foreign keys / checks, see [Knex Schema and Migrations](GUIDE_X_Knex_Schema_And_Migrations.md).
1035
+
931
1036
 
932
1037
  ## API Namespaces (internal, for plugin developers)
933
1038
 
@@ -943,6 +1048,7 @@ These helpers and methods provide a complete toolkit for:
943
1048
  - Managing database transactions
944
1049
  - Generating proper URLs and links
945
1050
  - Accessing the underlying database when needed
1051
+ - Inspecting live tables and generating Knex migrations when using table-backed resources
946
1052
  - Integrating with web frameworks like Express
947
1053
 
948
- The architecture ensures clean separation between HTTP transport, business logic, and data persistence layers.
1054
+ The architecture ensures clean separation between HTTP transport, business logic, and data persistence layers.
@@ -306,6 +306,10 @@ The `searchSchema` gives you:
306
306
  * Flexibility to extend: Add virtual fields or complex filters via searchSchema while basic fields use `search: true`
307
307
  * Best of both worlds: Explicit control for complex cases, automatic setup for simple ones
308
308
 
309
+ `searchSchema` also defines the public filter names your API accepts. Those keys do not need to match database columns or even resource field names. Treat `searchSchema` as an allowlist plus normalization layer for filter input: the actual meaning of each key is still implemented by the backend through mappings such as `actualField`, multi-field rules such as `oneOf`, standard operators, or custom `applyFilter` logic. It is not a portable filter DSL.
310
+
311
+ The merge rules and validation surface are shared across storage backends. Advanced execution semantics are still backend-owned. In practice, that means alias filters such as `oneOf` and custom `applyFilter` functions should be validated against the backend you actually run, and the SQL-heavy examples in this chapter assume the Knex-backed query layer.
312
+
309
313
  `searchSchema` also gives you the ability to define a search field that will search in multiple fields. For example:
310
314
 
311
315
  ```javascript
@@ -801,4 +805,4 @@ GET /api/countries?filter[search]=united+states&fields[countries]=code
801
805
 
802
806
  ---
803
807
 
804
- [Previous: 2.1 The starting point](./GUIDE_2_1_The_Starting_Point.md) | [Back to Guide](./README.md) | [Next: 2.3 `belongsTo` Relationships](./GUIDE_2_3_BelongsTo_Relationships.md)
808
+ [Previous: 2.1 The starting point](./GUIDE_2_1_The_Starting_Point.md) | [Back to Guide](./README.md) | [Next: 2.3 `belongsTo` Relationships](./GUIDE_2_3_BelongsTo_Relationships.md)
@@ -2,6 +2,8 @@
2
2
 
3
3
  Pagination and ordering are essential features for working with large datasets. The json-rest-api library provides powerful pagination that applies not just to main resources, but also to included relationships. This means you can limit and order both parent records AND their children independently.
4
4
 
5
+ For derived fields that must participate in `ORDER BY` and cursor pagination, see [Query Projections](GUIDE_X_Query_Projections.md). Regular computed fields are response-layer only and are not the right tool for stable SQL ordering.
6
+
5
7
  Let's create a comprehensive example with posts, comments, and tags to demonstrate all pagination and ordering features:
6
8
 
7
9
  ```javascript
@@ -1233,4 +1235,4 @@ The pagination and ordering system is designed to be efficient and predictable:
1233
1235
 
1234
1236
  ---
1235
1237
 
1236
- [Previous: 2.6 Many to Many Relationships](./GUIDE_2_6_Many_To_Many.md) | [Back to Guide](./README.md)
1238
+ [Previous: 2.6 Many to Many Relationships](./GUIDE_2_6_Many_To_Many.md) | [Back to Guide](./README.md)