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,370 @@
1
+ # Knex Schema and Migrations
2
+
3
+ `RestApiKnexPlugin` exposes a small schema-management surface for table-backed resources.
4
+
5
+ Use it when you want to:
6
+
7
+ - create tables directly from resource definitions
8
+ - add or alter plain columns on existing tables
9
+ - inspect a live table snapshot
10
+ - generate a create migration from a resource
11
+ - generate an additive diff migration from a live table
12
+
13
+ This is a Knex/table feature surface. It does not exist for non-table resources.
14
+
15
+ ## Overview
16
+
17
+ Once a resource is backed by `RestApiKnexPlugin`, these scope methods are available:
18
+
19
+ - `createKnexTable()`
20
+ - `addKnexFields({ fields })`
21
+ - `alterKnexFields({ fields })`
22
+ - `introspectKnexTableSnapshot()`
23
+ - `generateKnexMigration()`
24
+ - `generateKnexMigrationDiff()`
25
+
26
+ Example:
27
+
28
+ ```js
29
+ await api.addResource('memberships', {
30
+ schema: {
31
+ id: { type: 'id' },
32
+ workspaceId: { type: 'id', required: true },
33
+ userId: { type: 'id', required: true },
34
+ role: { type: 'string', enum: ['owner', 'member'], required: true, defaultTo: 'member' }
35
+ },
36
+
37
+ indexes: [
38
+ {
39
+ name: 'uq_memberships_workspace_user',
40
+ unique: true,
41
+ columns: ['workspaceId', 'userId']
42
+ }
43
+ ],
44
+
45
+ foreignKeys: [
46
+ {
47
+ name: 'fk_memberships_workspace_user',
48
+ columns: ['workspaceId', 'userId'],
49
+ referencedTableName: 'workspace_users',
50
+ referencedColumns: ['workspace_id', 'user_id'],
51
+ deleteRule: 'CASCADE',
52
+ updateRule: 'RESTRICT'
53
+ }
54
+ ],
55
+
56
+ checkConstraints: [
57
+ {
58
+ name: 'chk_memberships_workspace_positive',
59
+ clause: 'workspace_id > 0'
60
+ }
61
+ ],
62
+
63
+ tableName: 'memberships'
64
+ })
65
+ ```
66
+
67
+ ## Default Column Naming
68
+
69
+ Table-backed resources keep logical field names in the resource schema and map them to physical columns for Knex operations.
70
+
71
+ By default, physical columns use snake_case:
72
+
73
+ - `workspaceId` -> `workspace_id`
74
+ - `userId` -> `user_id`
75
+ - `createdAt` -> `created_at`
76
+
77
+ This keeps the API surface expressive without repeating `storage.column` on every camelCase field.
78
+
79
+ You only need `storage.column` when a field should use a non-standard column name:
80
+
81
+ ```js
82
+ await api.addResource('profiles', {
83
+ schema: {
84
+ id: { type: 'id' },
85
+ displayName: { type: 'string', required: true },
86
+ legacyRef: { type: 'string', storage: { column: 'legacy_profile_ref' } }
87
+ },
88
+ tableName: 'profiles'
89
+ })
90
+ ```
91
+
92
+ If you need physical columns to match the logical field names exactly for a whole resource, opt out explicitly:
93
+
94
+ ```js
95
+ await api.addResource('verbatim_profiles', {
96
+ storage: { naming: 'exact' },
97
+ schema: {
98
+ id: { type: 'id' },
99
+ displayName: { type: 'string', required: true }
100
+ },
101
+ tableName: 'verbatim_profiles'
102
+ })
103
+ ```
104
+
105
+ ## Logical IDs and `idProperty`
106
+
107
+ `idProperty` names the physical primary-key column for table-backed resources. The API contract still uses the logical resource id.
108
+
109
+ ```js
110
+ await api.addResource('profiles', {
111
+ idProperty: 'user_id',
112
+ schema: {
113
+ id: { type: 'id', required: true, storage: { column: 'user_id' } },
114
+ displayName: { type: 'string', required: true },
115
+ loginCount: { type: 'number', defaultTo: 0 }
116
+ },
117
+ tableName: 'profiles'
118
+ })
119
+ ```
120
+
121
+ With this definition:
122
+
123
+ - writes send the resource id as `id` or `data.id`
124
+ - reads return the resource id as `id` or `data.id`
125
+ - the primary key is stored in `user_id`
126
+ - `displayName` and `loginCount` are stored in `display_name` and `login_count`
127
+
128
+ The resource id is not part of `attributes`:
129
+
130
+ ```js
131
+ await api.resources.profiles.post({
132
+ inputRecord: {
133
+ data: {
134
+ type: 'profiles',
135
+ id: '42',
136
+ attributes: {
137
+ displayName: 'Mercury'
138
+ }
139
+ }
140
+ }
141
+ })
142
+ ```
143
+
144
+ ### ID normalization
145
+
146
+ If your primary keys need canonicalization beyond the default trim-and-stringify behavior, configure `normalizeId`.
147
+
148
+ ```js
149
+ await api.use(RestApiPlugin, {
150
+ normalizeId: (value) => {
151
+ if (value === null || value === undefined) return null
152
+
153
+ const normalized = String(value).trim()
154
+ return normalized ? normalized.toUpperCase() : null
155
+ }
156
+ })
157
+ ```
158
+
159
+ You can override that per resource:
160
+
161
+ ```js
162
+ await api.addResource('profiles', {
163
+ idProperty: 'user_id',
164
+ normalizeId: (value) => {
165
+ if (value === null || value === undefined) return null
166
+
167
+ const normalized = String(value).trim()
168
+ return normalized ? normalized.toLowerCase() : null
169
+ },
170
+ schema: {
171
+ id: { type: 'id', required: true, storage: { column: 'user_id' } },
172
+ displayName: { type: 'string', required: true }
173
+ },
174
+ tableName: 'profiles'
175
+ })
176
+ ```
177
+
178
+ For table-backed resources, `normalizeId` is applied before:
179
+
180
+ - reading a record by id
181
+ - replacing, patching, or deleting a record
182
+ - persisting an explicit `POST` resource id
183
+ - validating or writing relationship identifiers that point at the resource
184
+
185
+ If the normalizer returns an empty value, existing-resource operations fail as `not_found`, while explicit `POST` document ids fail validation on `data.id`.
186
+
187
+ ## Create Tables
188
+
189
+ Create the table directly from the resource definition:
190
+
191
+ ```js
192
+ await api.resources.memberships.createKnexTable()
193
+ ```
194
+
195
+ `createKnexTable()` understands:
196
+
197
+ - storage-mapped column names
198
+ - mapped logical id columns
199
+ - top-level `indexes`
200
+ - top-level `foreignKeys`
201
+ - top-level `checkConstraints`
202
+ - enum columns
203
+ - MySQL-style `setValues`
204
+
205
+ This is the same table metadata used by migration generation and diffing.
206
+
207
+ ## Field-Only Helpers
208
+
209
+ Two helpers only operate on columns:
210
+
211
+ ```js
212
+ await api.resources.memberships.addKnexFields({
213
+ fields: {
214
+ noteCount: { type: 'number', defaultTo: 0, storage: { column: 'note_count' } }
215
+ }
216
+ })
217
+
218
+ await api.resources.memberships.alterKnexFields({
219
+ fields: {
220
+ role: { type: 'string', enum: ['owner', 'member', 'viewer'] }
221
+ }
222
+ })
223
+ ```
224
+
225
+ Important:
226
+
227
+ - these helpers are field-only
228
+ - they reject top-level `indexes`, `foreignKeys`, and `checkConstraints`
229
+ - use `createKnexTable()`, `generateKnexMigration()`, or `generateKnexMigrationDiff()` for table metadata
230
+
231
+ ## Live Table Snapshots
232
+
233
+ Use `introspectKnexTableSnapshot()` to inspect the physical table shape:
234
+
235
+ ```js
236
+ const snapshot = await api.resources.memberships.introspectKnexTableSnapshot()
237
+ ```
238
+
239
+ Returned shape:
240
+
241
+ ```js
242
+ {
243
+ dialect: 'sqlite',
244
+ schemaName: 'main',
245
+ tableName: 'memberships',
246
+ tableCollation: '',
247
+ idColumn: 'id',
248
+ primaryKeyColumns: ['id'],
249
+ hasWorkspaceIdColumn: true,
250
+ hasUserIdColumn: true,
251
+ columns: [...],
252
+ indexes: [...],
253
+ foreignKeys: [...],
254
+ checkConstraints: [...]
255
+ }
256
+ ```
257
+
258
+ Column entries are normalized and include information such as:
259
+
260
+ - physical column name
261
+ - logical key
262
+ - type kind
263
+ - nullability
264
+ - default value
265
+ - precision / scale / datetime precision
266
+ - enum values
267
+ - set values
268
+
269
+ Current live introspection support:
270
+
271
+ - SQLite
272
+ - MySQL / MariaDB via `mysql2`
273
+
274
+ ## Create Migrations
275
+
276
+ Use `generateKnexMigration()` to emit a Knex migration string from the resource definition:
277
+
278
+ ```js
279
+ const migration = await api.resources.memberships.generateKnexMigration()
280
+ console.log(migration)
281
+ ```
282
+
283
+ This generates `exports.up` and `exports.down` for full table creation/drop.
284
+
285
+ It includes the same table metadata as `createKnexTable()`:
286
+
287
+ - mapped columns
288
+ - mapped id column
289
+ - indexes
290
+ - foreign keys
291
+ - check constraints
292
+ - enum columns
293
+ - supported MySQL-specific set columns
294
+
295
+ ## Diff Migrations
296
+
297
+ Use `generateKnexMigrationDiff()` to compare the desired schema against the live table snapshot:
298
+
299
+ ```js
300
+ const diff = await api.resources.memberships.generateKnexMigrationDiff()
301
+ ```
302
+
303
+ Returned shape:
304
+
305
+ ```js
306
+ {
307
+ migration,
308
+ warnings,
309
+ plan
310
+ }
311
+ ```
312
+
313
+ `plan` is normalized into:
314
+
315
+ - `addColumns`
316
+ - `alterColumns`
317
+ - `dropColumns`
318
+ - `addIndexes`
319
+ - `dropIndexes`
320
+ - `addForeignKeys`
321
+ - `dropForeignKeys`
322
+ - `addCheckConstraints`
323
+
324
+ The generated migration is intentionally additive-first:
325
+
326
+ - create missing columns
327
+ - alter columns when supported
328
+ - recreate changed indexes when needed
329
+ - add missing foreign keys
330
+ - add supported check constraints
331
+
332
+ ## Important Limits
333
+
334
+ This is a schema-vs-live-table diff, not a migration-history engine.
335
+
336
+ It answers:
337
+
338
+ - what the table looks like now
339
+ - what the resource says it should look like
340
+ - what Knex migration can safely move the table toward that shape
341
+
342
+ It does not answer:
343
+
344
+ - what changed since a previous migration file
345
+ - how to reconstruct migration history
346
+
347
+ Other important limits:
348
+
349
+ - destructive changes are surfaced as warnings
350
+ - dropped columns are skipped unless explicitly allowed by diff options
351
+ - SQLite check-constraint add/drop/alter support is warning-only
352
+ - `setValues` diffing is only emitted for MySQL-compatible targets
353
+ - diff migrations do not auto-generate a destructive `down`
354
+
355
+ ## When to Use Which Helper
356
+
357
+ - `createKnexTable()`
358
+ - bootstrap a table directly from the resource
359
+
360
+ - `addKnexFields()` / `alterKnexFields()`
361
+ - small column-only local changes
362
+
363
+ - `introspectKnexTableSnapshot()`
364
+ - inspect the real physical table
365
+
366
+ - `generateKnexMigration()`
367
+ - emit a full create migration from the current resource definition
368
+
369
+ - `generateKnexMigrationDiff()`
370
+ - emit an additive migration against the live table
@@ -0,0 +1,159 @@
1
+ # Query Projections
2
+
3
+ Query projections are SQL-backed, output-only query fields provided by the optional `QueryProjectionsPlugin`. They are different from normal computed fields.
4
+
5
+ If you want to build your own plugin on the same query-field seam, see [Writing Plugins](GUIDE_X_Writing_Plugins.md).
6
+
7
+ - **Computed fields** run after the row is fetched, in JavaScript.
8
+ - **Projected fields** are selected inside the query, so they can participate in `SELECT`, `ORDER BY`, and cursor pagination.
9
+
10
+ Use query projection fields when a derived value must behave like a real list field.
11
+
12
+ Typical cases:
13
+ - sorting by a derived label such as `full_name`
14
+ - cursor pagination over a derived value
15
+ - returning a derived SQL value without storing it
16
+
17
+ Do **not** use query projection fields for normal response decoration. Regular computed fields are simpler and remain the right default for that.
18
+
19
+ ## Setup
20
+
21
+ ```javascript
22
+ import { RestApiPlugin, QueryProjectionsPlugin } from 'json-rest-api'
23
+
24
+ await api.use(RestApiPlugin, { simplifiedApi: false, simplifiedTransport: false })
25
+ await api.use(QueryProjectionsPlugin)
26
+ ```
27
+
28
+ ## Defining a query field
29
+
30
+ ```javascript
31
+ await api.addResource('authors', {
32
+ schema: {
33
+ id: { type: 'id' },
34
+ first_name: { type: 'string', required: true },
35
+ last_name: { type: 'string', required: true }
36
+ },
37
+
38
+ queryFields: {
39
+ full_name: {
40
+ type: 'string',
41
+ sortable: true,
42
+ select: ({ knex, column }) => knex.raw(
43
+ "trim(coalesce(??, '') || ' ' || coalesce(??, ''))",
44
+ [column('first_name'), column('last_name')]
45
+ )
46
+ }
47
+ },
48
+
49
+ sortableFields: ['id', 'first_name', 'last_name'],
50
+ defaultSort: ['full_name']
51
+ })
52
+ ```
53
+
54
+ `select()` is called at query time. It can return:
55
+
56
+ - a `knex.raw(...)` expression
57
+ - a Knex reference
58
+ - a Knex query/subquery expression
59
+
60
+ The callback receives:
61
+
62
+ ```javascript
63
+ {
64
+ knex, // current knex/db handle
65
+ db, // same as knex
66
+ context, // request context
67
+ scopeName,
68
+ tableName,
69
+ fieldName,
70
+ schemaInfo,
71
+ adapter, // storage adapter
72
+ column, // helper: logical field -> qualified storage column
73
+ ref // helper: logical field -> knex ref
74
+ }
75
+ ```
76
+
77
+ ## Behavior
78
+
79
+ Query projection fields are:
80
+
81
+ - returned by default in `get()` and `query()`
82
+ - available in sparse fieldsets
83
+ - usable in sorting and cursor pagination
84
+ - available on included resources
85
+ - ignored on writes
86
+
87
+ They are **not** stored columns, and they are **not** part of write validation.
88
+
89
+ ## Sparse fieldsets
90
+
91
+ Query projection fields can be requested explicitly:
92
+
93
+ ```javascript
94
+ const result = await api.resources.authors.get({
95
+ id: '1',
96
+ queryParams: {
97
+ fields: {
98
+ authors: 'first_name,full_name'
99
+ }
100
+ }
101
+ })
102
+ ```
103
+
104
+ If a query field is needed only for sorting, it is still selected internally, but it is removed from the response unless the sparse fieldset asked for it.
105
+
106
+ ## Sorting and cursor pagination
107
+
108
+ Query projection fields are useful when a derived value must drive list behavior:
109
+
110
+ ```javascript
111
+ const result = await api.resources.authors.query({
112
+ queryParams: {
113
+ sort: ['full_name'],
114
+ page: { size: 20 }
115
+ }
116
+ })
117
+ ```
118
+
119
+ `json-rest-api` automatically appends `id` as a stable tie-breaker for cursor ordering. That prevents duplicate or skipped rows when multiple records share the same projected value.
120
+
121
+ ## Included resources
122
+
123
+ Query projection fields can be selected for included resources too:
124
+
125
+ ```javascript
126
+ const result = await api.resources.books.get({
127
+ id: '1',
128
+ queryParams: {
129
+ include: ['author'],
130
+ fields: {
131
+ books: 'title',
132
+ authors: 'full_name'
133
+ }
134
+ }
135
+ })
136
+ ```
137
+
138
+ ## Write behavior
139
+
140
+ Query projection fields are output-only. If a client sends them in `POST`, `PUT`, or `PATCH`, they are ignored.
141
+
142
+ That keeps the boundary clean:
143
+
144
+ - persisted fields own storage
145
+ - computed fields own response-layer enrichment
146
+ - query projection fields own query-layer derivation
147
+
148
+ ## Limits
149
+
150
+ Projected fields are an advanced query feature. They are intentionally **backend-sensitive**.
151
+
152
+ Important limits:
153
+
154
+ - projection SQL can be dialect-specific
155
+ - this is not a portability layer across every SQL engine
156
+ - query projection fields are not auto-searchable
157
+ - if you want to sort by one, mark it `sortable: true`
158
+
159
+ For display-only derived values, prefer normal computed fields from [Field Transformations](GUIDE_3_Field_Transformations.md).
@@ -0,0 +1,199 @@
1
+ # Writing Plugins
2
+
3
+ `json-rest-api` is built as a set of plugins on top of `hooked-api`. This guide covers the public extension surface for plugin authors.
4
+
5
+ Use this guide when you want to:
6
+
7
+ - add resource-level behavior across many resources
8
+ - add new scope methods
9
+ - compile resource metadata into reusable runtime state
10
+ - hook into query or write processing
11
+
12
+ For internal architecture notes and maintainer-oriented details, see [docs/ONBOARDING.md](../ONBOARDING.md).
13
+
14
+ ## Minimal plugin shape
15
+
16
+ ```javascript
17
+ export const MyPlugin = {
18
+ name: 'my-plugin',
19
+ dependencies: ['rest-api'],
20
+
21
+ install ({ addHook, addScopeMethod, helpers, log, pluginOptions = {} }) {
22
+ addHook('scope:added', 'compile-my-plugin', {}, ({ context, scopes }) => {
23
+ const scope = scopes[context.scopeName]
24
+ scope.vars.myPlugin = { enabled: true }
25
+ })
26
+
27
+ addScopeMethod('doSomething', async ({ scopeName, scope, params, context }) => {
28
+ return { scopeName, enabled: scope.vars.myPlugin?.enabled === true }
29
+ })
30
+ }
31
+ }
32
+ ```
33
+
34
+ The usual pattern is:
35
+
36
+ 1. compile resource metadata during `scope:added`
37
+ 2. store normalized state in `scope.vars`
38
+ 3. use hooks or scope methods to apply behavior at runtime
39
+
40
+ ## The stable extension points
41
+
42
+ ### `scope:added`
43
+
44
+ Use this to inspect `scopeOptions`, validate configuration, and compile resource-specific runtime state into `scope.vars`.
45
+
46
+ Typical uses:
47
+
48
+ - compile autofilter presets
49
+ - compile query-field definitions
50
+ - validate resource-specific options
51
+
52
+ Example:
53
+
54
+ ```javascript
55
+ addHook('scope:added', 'compile-example', {}, ({ context, scopes }) => {
56
+ const scope = scopes[context.scopeName]
57
+ const options = scope.scopeOptions || {}
58
+
59
+ scope.vars.example = {
60
+ flag: options.exampleFlag === true
61
+ }
62
+ })
63
+ ```
64
+
65
+ ### `beforeSchemaValidate`
66
+
67
+ Use this to normalize or strip write input before schema validation runs.
68
+
69
+ Typical uses:
70
+
71
+ - inject derived input values
72
+ - remove output-only fields from `POST`/`PUT`/`PATCH`
73
+ - translate alternate request forms into canonical attributes/relationships
74
+
75
+ Example:
76
+
77
+ ```javascript
78
+ addHook('beforeSchemaValidate', 'strip-output-only-input', {}, ({ context }) => {
79
+ const attributes = context.inputRecord?.data?.attributes
80
+ if (!attributes) return
81
+
82
+ delete attributes.output_only_field
83
+ })
84
+ ```
85
+
86
+ ### `knexQueryFiltering`
87
+
88
+ Use this to add query constraints to the generated Knex query.
89
+
90
+ Typical uses:
91
+
92
+ - automatic scoping
93
+ - cross-table filters
94
+ - custom public filter semantics
95
+
96
+ Example:
97
+
98
+ ```javascript
99
+ addHook('knexQueryFiltering', 'scope-by-workspace', {}, async ({ context }) => {
100
+ const query = context.knexQuery?.query
101
+ if (!query) return
102
+
103
+ query.where('workspace_id', context.session.workspaceId)
104
+ })
105
+ ```
106
+
107
+ `knexQueryFiltering` is the right seam for filtering. It is **not** the right seam for turning ad hoc SQL aliases into first-class fields.
108
+
109
+ ### `addScopeMethod`
110
+
111
+ Use this when a plugin needs a reusable method on every resource or selected resources.
112
+
113
+ Example:
114
+
115
+ ```javascript
116
+ addScopeMethod('introspect', async ({ vars }) => {
117
+ return {
118
+ tableName: vars.schemaInfo?.tableName,
119
+ fields: Object.keys(vars.schemaInfo?.schemaStructure || {})
120
+ }
121
+ })
122
+ ```
123
+
124
+ ## The query-field seam
125
+
126
+ `json-rest-api` now supports a small, explicit seam for **query-only read fields**.
127
+
128
+ This is the seam used by `QueryProjectionsPlugin`, and it is the recommended pattern for plugins that need derived SQL-backed fields.
129
+
130
+ ### What a plugin should provide
131
+
132
+ Compile resource-level definitions into:
133
+
134
+ ```javascript
135
+ scope.vars.queryFields = {
136
+ full_name: {
137
+ type: 'string',
138
+ sortable: true,
139
+ hidden: false,
140
+ normallyHidden: false,
141
+ select: ({ knex, db, context, scopeName, tableName, fieldName, schemaInfo, adapter, column, ref }) => {
142
+ return knex.raw(
143
+ "trim(coalesce(??, '') || ' ' || coalesce(??, ''))",
144
+ [column('first_name'), column('last_name')]
145
+ )
146
+ }
147
+ }
148
+ }
149
+ ```
150
+
151
+ ### What core will do with `scope.vars.queryFields`
152
+
153
+ Once a plugin sets `scope.vars.queryFields`, core will:
154
+
155
+ - include visible query fields by default in `get()` and `query()`
156
+ - allow them in sparse fieldsets
157
+ - allow them in sorting when the plugin marks them sortable
158
+ - use them in cursor pagination with a stable `id` tie-breaker
159
+ - carry them through included-resource selection
160
+ - only expose declared query-field aliases on the AnyAPI path
161
+
162
+ ### What this seam is for
163
+
164
+ Use it for:
165
+
166
+ - SQL-backed projections
167
+ - aggregate list fields
168
+ - joined display labels
169
+ - derived fields that must participate in `ORDER BY`
170
+
171
+ Do **not** use it for:
172
+
173
+ - writable fields
174
+ - storage/schema ownership
175
+ - migrations or introspection
176
+ - normal response-only computed fields
177
+
178
+ For the concrete projection example, see [Query Projections](GUIDE_X_Query_Projections.md).
179
+
180
+ ## Recommended plugin pattern
181
+
182
+ When you add a new plugin feature, prefer this flow:
183
+
184
+ 1. read plugin options at install time
185
+ 2. validate and compile per-resource config in `scope:added`
186
+ 3. store only normalized runtime state in `scope.vars`
187
+ 4. use hooks or scope methods to apply behavior
188
+
189
+ That keeps the plugin declarative and avoids re-parsing configuration during requests.
190
+
191
+ ## Boundaries to keep clean
192
+
193
+ - Keep **query-layer** extensions out of `schema`.
194
+ - Keep **write/input** reshaping in `beforeSchemaValidate`.
195
+ - Keep **filtering** in `knexQueryFiltering`.
196
+ - Keep **response-only** enrichment in `enrichAttributes`.
197
+ - Do not rely on arbitrary internal helper filenames as public API.
198
+
199
+ If a feature requires deeper integration than the public seams provide, treat that as a core extension discussion rather than a plugin hack.