json-rest-api 1.0.22 → 1.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/CLAUDE.md +1 -2
  2. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
  3. package/README.md +22 -1
  4. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
  5. package/docs/API.md +196 -38
  6. package/docs/COMPARISON.md +2 -2
  7. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +111 -5
  8. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
  9. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
  10. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
  11. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
  12. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
  13. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
  14. package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
  15. package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
  16. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +370 -0
  17. package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
  18. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
  19. package/docs/GUIDE/index.md +18 -9
  20. package/docs/QUICKSTART.md +5 -1
  21. package/docs/index.md +1 -1
  22. package/index.js +3 -2
  23. package/lib/enhanced-logger.js +1 -1
  24. package/package.json +6 -2
  25. package/plugins/core/bulk-operations-plugin.js +26 -34
  26. package/plugins/core/connectors/express-plugin.js +97 -190
  27. package/plugins/core/connectors/fastify-plugin.js +206 -0
  28. package/plugins/core/connectors/lib/busboy-detector.js +4 -4
  29. package/plugins/core/connectors/lib/connector-core.js +195 -0
  30. package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
  31. package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
  32. package/plugins/core/lib/anyapi/schema-utils.js +43 -4
  33. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +4 -1
  34. package/plugins/core/lib/dbIntrospection.js +1078 -0
  35. package/plugins/core/lib/dbTablesOperations.js +1111 -160
  36. package/plugins/core/lib/querying/knex-cross-table-search.js +0 -7
  37. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -3
  38. package/plugins/core/lib/querying/knex-process-includes.js +3 -4
  39. package/plugins/core/lib/querying/knex-query-helpers.js +21 -76
  40. package/plugins/core/lib/querying/knex-relationship-includes.js +468 -215
  41. package/plugins/core/lib/querying/query-builder-utils.js +19 -0
  42. package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
  43. package/plugins/core/lib/querying/sort-helpers.js +33 -0
  44. package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
  45. package/plugins/core/lib/querying/url-helpers.js +8 -2
  46. package/plugins/core/lib/querying-writing/compile-schemas.js +20 -5
  47. package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -14
  48. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
  49. package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
  50. package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
  51. package/plugins/core/lib/querying-writing/resource-id-normalization.js +131 -0
  52. package/plugins/core/lib/querying-writing/schema-helpers.js +0 -2
  53. package/plugins/core/lib/querying-writing/simplified-helpers.js +4 -11
  54. package/plugins/core/lib/storage/canonical-storage-mapping.js +133 -0
  55. package/plugins/core/lib/storage/storage-adapter.js +82 -4
  56. package/plugins/core/lib/storage/storage-mapping.js +274 -0
  57. package/plugins/core/lib/writing/many-to-many-manipulations.js +4 -2
  58. package/plugins/core/rest-api-anyapi-knex-plugin.js +531 -312
  59. package/plugins/core/rest-api-autofilter-plugin.js +414 -0
  60. package/plugins/core/rest-api-knex-plugin.js +271 -226
  61. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
  62. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
  63. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +1 -0
  64. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
  65. package/plugins/core/rest-api-plugin-methods/common.js +148 -92
  66. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +11 -2
  67. package/plugins/core/rest-api-plugin-methods/delete.js +6 -1
  68. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
  69. package/plugins/core/rest-api-plugin-methods/get-related.js +7 -2
  70. package/plugins/core/rest-api-plugin-methods/get-relationship.js +7 -2
  71. package/plugins/core/rest-api-plugin-methods/get.js +29 -14
  72. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +10 -3
  73. package/plugins/core/rest-api-plugin-methods/patch.js +56 -12
  74. package/plugins/core/rest-api-plugin-methods/post-relationship.js +11 -2
  75. package/plugins/core/rest-api-plugin-methods/post.js +21 -8
  76. package/plugins/core/rest-api-plugin-methods/put.js +73 -21
  77. package/plugins/core/rest-api-plugin-methods/query.js +15 -53
  78. package/plugins/core/rest-api-plugin-methods/release.js +0 -2
  79. package/plugins/core/rest-api-plugin.js +6 -0
  80. package/plugins/core/rest-api-positioning-plugin.js +2 -5
  81. package/plugins/core/rest-api-query-projections-plugin.js +130 -0
  82. package/plugins/core/socketio-plugin.js +1 -4
  83. package/plugins/storage/local-storage.js +1 -1
  84. package/plugins/storage/s3-storage.js +5 -2
  85. package/quickTest.js +1 -0
  86. package/scripts/run-docs.js +80 -84
  87. package/test-debug.js +1 -1
  88. package/tests/README.md +7 -0
  89. package/tests/anyapi-basic.test.js +3 -0
  90. package/tests/autofilter.test.js +463 -0
  91. package/tests/custom-id-mysql-returning-fallback.test.js +128 -0
  92. package/tests/custom-id-simplified-post.test.js +75 -0
  93. package/tests/custom-idproperty-relationships.test.js +33 -0
  94. package/tests/db-introspection.test.js +521 -0
  95. package/tests/db-table-operations.test.js +786 -0
  96. package/tests/fastify-plugin.test.js +156 -0
  97. package/tests/field-getters.test.js +1 -2
  98. package/tests/field-setters.test.js +0 -4
  99. package/tests/fixtures/api-configs.js +278 -176
  100. package/tests/helpers/fake-fastify.js +99 -0
  101. package/tests/http-connectors-parity.test.js +235 -0
  102. package/tests/id-normalization.test.js +444 -0
  103. package/tests/logical-id-write-validation.test.js +151 -0
  104. package/tests/pagination-cursor-multifield.test.js +11 -11
  105. package/tests/projected-fields.test.js +319 -0
  106. package/tests/relationships.test.js +3 -3
  107. package/tests/searchschema-knex-behavior.test.js +187 -0
  108. package/tests/searchschema-merge.test.js +21 -69
  109. package/tests/storage-mapping.test.js +259 -0
  110. package/audit/00-charter.md +0 -103
  111. package/audit/01-scope-map.md +0 -84
  112. package/audit/02-coverage-matrix.csv +0 -174
  113. package/audit/03-invariants.md +0 -123
  114. package/audit/04-standards-rubric.md +0 -56
  115. package/audit/05-competitor-rubric.md +0 -77
  116. package/audit/06-findings.md +0 -160
  117. package/audit/07-risk-register.csv +0 -10
  118. package/audit/08-remediation-roadmap.md +0 -177
  119. package/audit/09-signoff-check.md +0 -23
  120. package/audit/10-final-synthesis.md +0 -29
  121. package/audit/evidence/code/all-files-json-rest-api.txt +0 -168
  122. package/audit/evidence/code/all-files-json-rest-schema.txt +0 -12
  123. package/audit/evidence/code/boundary-leak-checks.txt +0 -72
  124. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__bulk-operations-plugin.js.txt +0 -480
  125. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__express-plugin.js.txt +0 -532
  126. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__busboy-detector.js.txt +0 -175
  127. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__formidable-detector.js.txt +0 -138
  128. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__request-helpers.js.txt +0 -222
  129. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt +0 -342
  130. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__dbTablesOperations.js.txt +0 -304
  131. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__fractional-positioning.js.txt +0 -188
  132. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__compile-schemas.js.txt +0 -336
  133. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__connectors-query-parser.js.txt +0 -165
  134. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-capabilities.js.txt +0 -265
  135. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-value-normalizers.js.txt +0 -369
  136. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__default-data-helpers.js.txt +0 -72
  137. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__field-utils.js.txt +0 -139
  138. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-constants.js.txt +0 -45
  139. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-field-helpers.js.txt +0 -396
  140. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-json-api-transformers.js.txt +0 -162
  141. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__payload-validators.js.txt +0 -1314
  142. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__schema-helpers.js.txt +0 -488
  143. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__scope-validations.js.txt +0 -484
  144. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__simplified-helpers.js.txt +0 -857
  145. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-cross-table-search.js.txt +0 -565
  146. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-json-api-transformers-querying.js.txt +0 -485
  147. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-pagination-helpers.js.txt +0 -560
  148. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-process-includes.js.txt +0 -167
  149. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers-base.js.txt +0 -109
  150. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers.js.txt +0 -1064
  151. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-relationship-includes.js.txt +0 -1983
  152. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-window-queries.js.txt +0 -328
  153. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__url-helpers.js.txt +0 -119
  154. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__storage__storage-adapter.js.txt +0 -160
  155. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__many-to-many-manipulations.js.txt +0 -300
  156. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__relationship-processor.js.txt +0 -245
  157. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-access.js.txt +0 -459
  158. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-cors-plugin.js.txt +0 -237
  159. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-knex-plugin.js.txt +0 -1135
  160. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-multihome-plugin.js.txt +0 -314
  161. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__compile-resource-schemas.js.txt +0 -7
  162. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-relationship-routes.js.txt +0 -71
  163. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-scope-routes.js.txt +0 -102
  164. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__turn-scope-init-into-vars.js.txt +0 -49
  165. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__validate-include-configurations.js.txt +0 -39
  166. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__add-route.js.txt +0 -23
  167. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__check-permissions.js.txt +0 -13
  168. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__common.js.txt +0 -639
  169. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete-relationship.js.txt +0 -117
  170. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete.js.txt +0 -104
  171. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__enrich-attributes.js.txt +0 -199
  172. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-related.js.txt +0 -353
  173. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-relationship.js.txt +0 -61
  174. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get.js.txt +0 -184
  175. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch-relationship.js.txt +0 -55
  176. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch.js.txt +0 -191
  177. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post-relationship.js.txt +0 -110
  178. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post.js.txt +0 -168
  179. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__put.js.txt +0 -253
  180. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__query.js.txt +0 -207
  181. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__release.js.txt +0 -12
  182. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin.js.txt +0 -165
  183. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-positioning-plugin.js.txt +0 -668
  184. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__socketio-plugin.js.txt +0 -621
  185. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__local-storage.js.txt +0 -218
  186. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__s3-storage.js.txt +0 -124
  187. package/audit/evidence/code/critical-pass1/json-rest-schema__src__CorePlugin.js.txt +0 -269
  188. package/audit/evidence/code/critical-pass1/json-rest-schema__src__Schema.js.txt +0 -242
  189. package/audit/evidence/code/critical-pass1/json-rest-schema__src__index.js.txt +0 -77
  190. package/audit/evidence/code/critical-pass1-risk-keywords.txt +0 -238
  191. package/audit/evidence/code/critical-pass1-summary.csv +0 -67
  192. package/audit/evidence/code/critical-pass2-summary.csv +0 -67
  193. package/audit/evidence/code/early-silent-zones-no-test-token.txt +0 -57
  194. package/audit/evidence/code/error-model-signals.txt +0 -98
  195. package/audit/evidence/code/git-files-json-rest-api.txt +0 -171
  196. package/audit/evidence/code/git-files-json-rest-schema.txt +0 -27
  197. package/audit/evidence/code/hook-flow-signals.txt +0 -282
  198. package/audit/evidence/code/idproperty-signals.txt +0 -157
  199. package/audit/evidence/code/import-cycles-non-anyapi.txt +0 -3
  200. package/audit/evidence/code/import-hotspots-non-anyapi.txt +0 -96
  201. package/audit/evidence/code/imports-json-rest-api.txt +0 -354
  202. package/audit/evidence/code/imports-json-rest-schema.txt +0 -15
  203. package/audit/evidence/code/in-scope-files-json-rest-api.txt +0 -160
  204. package/audit/evidence/code/in-scope-files-json-rest-schema.txt +0 -13
  205. package/audit/evidence/code/non-anyapi-core-files.txt +0 -101
  206. package/audit/evidence/code/permission-signals.txt +0 -193
  207. package/audit/evidence/code/query-include-signals.txt +0 -687
  208. package/audit/evidence/code/transaction-signals.txt +0 -28
  209. package/audit/evidence/code/validation-signals.txt +0 -650
  210. package/audit/evidence/code/write-path-signals.txt +0 -61
  211. package/audit/evidence/docs/docs-claims-signals.txt +0 -1075
  212. package/audit/evidence/docs/schema-docs-claims-signals.txt +0 -179
  213. package/audit/evidence/external/corpus-freeze.md +0 -21
  214. package/audit/evidence/external/npm-competitor-metadata.txt +0 -70
  215. package/audit/evidence/external/npm-download-signals.txt +0 -23
  216. package/audit/evidence/external/npm-jsonapi-server.txt +0 -8
  217. package/audit/evidence/external/npm-modern-jsonapi-metadata.txt +0 -38
  218. package/audit/evidence/external/readme-fortune-signals.txt +0 -26
  219. package/audit/evidence/external/readme-fortune.md +0 -165
  220. package/audit/evidence/external/readme-json-api-serializer-signals.txt +0 -80
  221. package/audit/evidence/external/readme-json-api-serializer.md +0 -690
  222. package/audit/evidence/external/readme-jsonapi-serde-server-signals.txt +0 -0
  223. package/audit/evidence/external/readme-jsonapi-serde-server.md +0 -22
  224. package/audit/evidence/external/readme-jsonapi-serializer-signals.txt +0 -63
  225. package/audit/evidence/external/readme-jsonapi-serializer.md +0 -315
  226. package/audit/evidence/external/readme-jsonapi-server-signals.txt +0 -19
  227. package/audit/evidence/external/readme-jsonapi-server.md +0 -104
  228. package/audit/evidence/external/readme-nestjs-json-api-signals.txt +0 -4
  229. package/audit/evidence/external/readme-nestjs-json-api.md +0 -94
  230. package/audit/evidence/external/readme-yaser-json-api-nestjs-signals.txt +0 -47
  231. package/audit/evidence/external/readme-yaser-json-api-nestjs.md +0 -447
  232. package/audit/evidence/external/standard-jsonapi-snippets.txt +0 -84
  233. package/audit/evidence/external/standard-rfc-snippets.txt +0 -168
  234. package/audit/evidence/external/standards-sources.txt +0 -6
  235. package/audit/evidence/runtime/baseline-commits.txt +0 -2
  236. package/audit/evidence/runtime/db-adapter-assumptions.txt +0 -3
  237. package/audit/evidence/runtime/environment.txt +0 -4
  238. package/audit/evidence/runtime/invariant-runtime-mapping.md +0 -38
  239. package/audit/evidence/runtime/probe-patch-relationship-error.exitcode +0 -1
  240. package/audit/evidence/runtime/probe-patch-relationship-error.log +0 -76
  241. package/audit/evidence/runtime/probe-patch-relationship-error.mjs +0 -67
  242. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.exitcode +0 -1
  243. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.log +0 -62
  244. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.mjs +0 -47
  245. package/audit/evidence/runtime/test-exitcodes.txt +0 -4
  246. package/audit/evidence/runtime/test-summary.md +0 -107
  247. package/audit/evidence/runtime/trace-anchors.txt +0 -61
  248. package/audit/evidence/tests/access-multitenancy-test-signals.txt +0 -125
  249. package/audit/evidence/tests/failing-tests-summary.txt +0 -25
  250. package/audit/evidence/tests/npm-test-json-rest-api.exitcode +0 -1
  251. package/audit/evidence/tests/npm-test-json-rest-api.log +0 -5885
  252. package/audit/evidence/tests/npm-test-json-rest-schema.exitcode +0 -1
  253. package/audit/evidence/tests/npm-test-json-rest-schema.log +0 -356
  254. package/audit/evidence/tests/query-include-test-signals.txt +0 -948
  255. package/audit/evidence/tests/relationship-test-signals.txt +0 -223
  256. package/audit/evidence/tests/skipped-tests.txt +0 -10
  257. package/audit/evidence/tests/test-suite-list-non-anyapi.txt +0 -30
  258. package/audit/evidence/tests/test-suite-list.txt +0 -32
  259. package/audit/subsystems/docs-drift.md +0 -49
  260. package/audit/subsystems/pass2-cross-review.md +0 -131
  261. package/audit/subsystems/plugins-access.md +0 -51
  262. package/audit/subsystems/plugins-bulk.md +0 -50
  263. package/audit/subsystems/plugins-cors.md +0 -48
  264. package/audit/subsystems/plugins-file.md +0 -53
  265. package/audit/subsystems/plugins-multihome.md +0 -50
  266. package/audit/subsystems/plugins-positioning.md +0 -54
  267. package/audit/subsystems/plugins-socketio.md +0 -50
  268. package/audit/subsystems/query-engine.md +0 -61
  269. package/audit/subsystems/relationships.md +0 -55
  270. package/audit/subsystems/rest-orchestration.md +0 -66
  271. package/audit/subsystems/schema-core.md +0 -64
  272. package/audit/subsystems/storage-knex.md +0 -57
  273. package/audit/subsystems/tests.md +0 -48
  274. package/audit/subsystems/transport-express.md +0 -55
  275. package/audit/traces/trace-read-get.md +0 -22
  276. package/audit/traces/trace-read-query.md +0 -25
  277. package/audit/traces/trace-relationship-endpoints.md +0 -31
  278. package/audit/traces/trace-write-delete.md +0 -17
  279. package/audit/traces/trace-write-patch.md +0 -26
  280. package/audit/traces/trace-write-post.md +0 -22
  281. package/audit/traces/trace-write-put.md +0 -23
  282. package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
  283. package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
  284. package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
  285. package/plugins/core/rest-api-access.js +0 -459
  286. package/plugins/core/rest-api-multihome-plugin.js +0 -314
  287. package/tests/multihome.test.js +0 -553
  288. package/tests/multihome.test.js.bak +0 -550
  289. package/tests/ownership-optout.test.js +0 -76
  290. package/tests/rest-api-access.test.js +0 -206
@@ -1,666 +0,0 @@
1
- # Multi-Tenancy with the MultiHome Plugin
2
-
3
- The MultiHome plugin provides automatic data isolation for multi-tenant applications. It ensures that users can only access data belonging to their tenant, making it impossible to accidentally or maliciously access data from other tenants.
4
-
5
- ## Table of Contents
6
-
7
- - [Overview](#overview)
8
- - [How It Works](#how-it-works)
9
- - [Installation](#installation)
10
- - [Configuration](#configuration)
11
- - [Usage Examples](#usage-examples)
12
- - [Security Features](#security-features)
13
- - [Integration with Other Plugins](#integration-with-other-plugins)
14
- - [API Reference](#api-reference)
15
- - [Troubleshooting](#troubleshooting)
16
- - [Best Practices](#best-practices)
17
-
18
- ## Overview
19
-
20
- Multi-tenancy is a critical requirement for SaaS applications where multiple customers (tenants) share the same application instance but must have completely isolated data. The MultiHome plugin makes this easy by:
21
-
22
- - **Automatic Query Filtering**: Every database query automatically includes a WHERE clause for the tenant ID
23
- - **Automatic Record Assignment**: New records are automatically assigned to the current tenant
24
- - **Request-Based Tenant Detection**: Extracts tenant ID from subdomain, header, path, or custom logic
25
- - **Zero Data Leakage**: Makes it impossible to access data from the wrong tenant
26
- - **Flexible Configuration**: Supports various multi-tenancy strategies
27
-
28
- ## How It Works
29
-
30
- The plugin operates at multiple levels to ensure complete data isolation:
31
-
32
- ### 1. Tenant Extraction (Transport Layer)
33
-
34
- When a request arrives, the plugin extracts the tenant ID using a configurable extractor function:
35
-
36
- ```javascript
37
- // Default: Extract from subdomain
38
- // mobily.app.com → tenant_id = 'mobily'
39
- // acme.app.com → tenant_id = 'acme'
40
- ```
41
-
42
- ### 2. Query Filtering (Database Layer)
43
-
44
- Every database query is automatically modified to include the tenant filter:
45
-
46
- ```sql
47
- -- Original query
48
- SELECT * FROM posts WHERE status = 'published'
49
-
50
- -- Modified query (automatic)
51
- SELECT * FROM posts WHERE status = 'published' AND multihome_id = 'mobily'
52
- ```
53
-
54
- ### 3. Record Creation (API Layer)
55
-
56
- When creating new records, the tenant ID is automatically set:
57
-
58
- ```javascript
59
- // User sends:
60
- POST /api/posts
61
- { "title": "My Post", "content": "..." }
62
-
63
- // Plugin automatically adds:
64
- { "title": "My Post", "content": "...", "multihome_id": "mobily" }
65
- ```
66
-
67
- ## Installation
68
-
69
- 1. First, ensure you have the required dependencies:
70
-
71
- ```javascript
72
- import { Api } from 'hooked-api';
73
- import { RestApiPlugin } from './plugins/core/rest-api-plugin.js';
74
- import { RestApiKnexPlugin } from './plugins/core/rest-api-knex-plugin.js';
75
- import { ExpressPlugin } from './plugins/core/connectors/express-plugin.js';
76
- import { MultiHomePlugin } from './plugins/core/multihome-plugin.js';
77
- ```
78
-
79
- 2. Create your API and install the plugins:
80
-
81
- ```javascript
82
- const api = new Api({
83
- name: 'my-multi-tenant-api',
84
- });
85
-
86
- // Install required plugins first
87
- await api.use(RestApiPlugin);
88
- await api.use(RestApiKnexPlugin, { knex: knexInstance });
89
- await api.use(ExpressPlugin, { app: expressApp });
90
-
91
- // Install MultiHome plugin
92
- await api.use(MultiHomePlugin, {
93
- field: 'tenant_id', // The database field name
94
- excludeResources: ['migrations'], // Resources to exclude
95
- requireAuth: true, // Require tenant context
96
- allowMissing: false, // Require field in schema
97
- extractor: (request) => { // Custom extraction logic
98
- // Extract from subdomain
99
- const host = request.headers.host;
100
- const subdomain = host.split('.')[0];
101
- return subdomain;
102
- }
103
- });
104
- ```
105
-
106
- 3. Add the tenant field to your resource schemas:
107
-
108
- ```javascript
109
- api.addResource('posts', {
110
- schema: {
111
- title: { type: 'string', required: true },
112
- content: { type: 'string' },
113
- tenant_id: { type: 'string', required: true } // Required field
114
- }
115
- });
116
- ```
117
-
118
- ## Configuration
119
-
120
- ### Plugin Options
121
-
122
- | Option | Type | Default | Description |
123
- |--------|------|---------|-------------|
124
- | `field` | string | `'multihome_id'` | The database field name for tenant ID |
125
- | `excludeResources` | array | `['system_migrations', 'system_logs']` | Resources that don't need tenant isolation |
126
- | `requireAuth` | boolean | `true` | Whether to require tenant context for all operations |
127
- | `allowMissing` | boolean | `false` | Whether to allow resources without the tenant field |
128
- | `extractor` | function | Subdomain extractor | Function to extract tenant ID from request |
129
-
130
- ### Default Extractor
131
-
132
- The default extractor tries multiple sources:
133
-
134
- 1. **Subdomain**: `tenant1.app.com` → `tenant1`
135
- 2. **Header**: `X-Multihome-ID: tenant1`
136
- 3. Returns `null` if no tenant found
137
-
138
- Common subdomains like 'www', 'api', 'app' are ignored.
139
-
140
- ### Custom Extractors
141
-
142
- You can provide your own extractor function:
143
-
144
- ```javascript
145
- // Extract from JWT token
146
- extractor: (request) => {
147
- const token = request.headers.authorization?.split(' ')[1];
148
- if (token) {
149
- const decoded = jwt.verify(token, secret);
150
- return decoded.tenant_id;
151
- }
152
- return null;
153
- }
154
-
155
- // Extract from URL path
156
- extractor: (request) => {
157
- // /api/tenants/acme/posts → 'acme'
158
- const match = request.path.match(/\/tenants\/([^\/]+)/);
159
- return match ? match[1] : null;
160
- }
161
-
162
- // Extract from custom header
163
- extractor: (request) => {
164
- return request.headers['x-tenant-id'];
165
- }
166
-
167
- // Complex logic with fallbacks
168
- extractor: (request) => {
169
- // Try JWT first
170
- if (request.auth?.claims?.tenant_id) {
171
- return request.auth.claims.tenant_id;
172
- }
173
-
174
- // Then try subdomain
175
- const subdomain = request.headers.host?.split('.')[0];
176
- if (subdomain && !['www', 'api'].includes(subdomain)) {
177
- return subdomain;
178
- }
179
-
180
- // Finally try header
181
- return request.headers['x-customer-id'];
182
- }
183
- ```
184
-
185
- ## Usage Examples
186
-
187
- ### Basic Setup with Subdomain-Based Tenancy
188
-
189
- ```javascript
190
- // Configuration
191
- await api.use(MultiHomePlugin, {
192
- field: 'tenant_id',
193
- extractor: (request) => {
194
- const host = request.headers.host;
195
- return host.split('.')[0]; // acme.myapp.com → 'acme'
196
- }
197
- });
198
-
199
- // Define resources with tenant field
200
- api.addResource('projects', {
201
- schema: {
202
- name: { type: 'string', required: true },
203
- description: { type: 'string' },
204
- tenant_id: { type: 'string', required: true }
205
- }
206
- });
207
-
208
- api.addResource('users', {
209
- schema: {
210
- email: { type: 'string', required: true },
211
- name: { type: 'string', required: true },
212
- tenant_id: { type: 'string', required: true }
213
- }
214
- });
215
- ```
216
-
217
- ### Header-Based Tenancy for APIs
218
-
219
- ```javascript
220
- // Configuration for API clients that send tenant ID in header
221
- await api.use(MultiHomePlugin, {
222
- field: 'organization_id',
223
- extractor: (request) => {
224
- const tenantId = request.headers['x-organization-id'];
225
- if (!tenantId) {
226
- throw new Error('X-Organization-ID header is required');
227
- }
228
- return tenantId;
229
- }
230
- });
231
- ```
232
-
233
- ### JWT-Based Tenancy
234
-
235
- ```javascript
236
- // Works with the JWT Auth plugin
237
- await api.use(JwtAuthPlugin, { secret: process.env.JWT_SECRET });
238
- await api.use(MultiHomePlugin, {
239
- field: 'company_id',
240
- extractor: (request) => {
241
- // JWT plugin sets request.auth
242
- if (!request.auth?.claims?.company_id) {
243
- throw new Error('No company context in JWT token');
244
- }
245
- return request.auth.claims.company_id;
246
- }
247
- });
248
- ```
249
-
250
- ### Mixed Mode with System Resources
251
-
252
- ```javascript
253
- // Some resources are tenant-specific, others are global
254
- await api.use(MultiHomePlugin, {
255
- field: 'tenant_id',
256
- excludeResources: [
257
- 'system_settings', // Global settings
258
- 'countries', // Shared reference data
259
- 'currencies', // Shared reference data
260
- 'audit_logs' // System-wide audit trail
261
- ],
262
- allowMissing: true // Allow resources without tenant_id field
263
- });
264
-
265
- // Tenant-specific resource
266
- api.addResource('invoices', {
267
- schema: {
268
- number: { type: 'string', required: true },
269
- amount: { type: 'number', required: true },
270
- tenant_id: { type: 'string', required: true }
271
- }
272
- });
273
-
274
- // Global resource (no tenant_id)
275
- api.addResource('countries', {
276
- schema: {
277
- code: { type: 'string', required: true },
278
- name: { type: 'string', required: true }
279
- // No tenant_id field
280
- }
281
- });
282
- ```
283
-
284
- ## Security Features
285
-
286
- ### 1. Automatic Query Filtering
287
-
288
- Every query is automatically filtered at the database level:
289
-
290
- ```javascript
291
- // User tries to access another tenant's data
292
- GET /api/posts/123
293
-
294
- // Even if post 123 belongs to another tenant, the query becomes:
295
- SELECT * FROM posts WHERE id = 123 AND tenant_id = 'current-tenant'
296
- // Result: 404 Not Found (not a security error message)
297
- ```
298
-
299
- ### 2. Validation on Write Operations
300
-
301
- The plugin validates tenant context on all write operations:
302
-
303
- ```javascript
304
- // User tries to create a record with wrong tenant_id
305
- POST /api/posts
306
- {
307
- "title": "Hacking attempt",
308
- "tenant_id": "other-tenant" // This will be rejected
309
- }
310
-
311
- // Error: Cannot set tenant_id to 'other-tenant' - must match current context
312
- ```
313
-
314
- ### 3. Security Logging
315
-
316
- Security violations are logged for monitoring:
317
-
318
- ```javascript
319
- // When someone tries to access wrong tenant data
320
- log.error('Multihome security violation attempt', {
321
- scopeName: 'posts',
322
- recordId: 123,
323
- recordMultihomeId: 'tenant-a',
324
- contextMultihomeId: 'tenant-b'
325
- });
326
- ```
327
-
328
- ### 4. Fail-Safe Design
329
-
330
- If tenant context is missing and `requireAuth: true`:
331
-
332
- ```javascript
333
- // No tenant context available
334
- GET /api/posts
335
- // Error: No multihome context available - cannot execute query
336
- ```
337
-
338
- ## Integration with Other Plugins
339
-
340
- ### With JWT Auth Plugin
341
-
342
- The MultiHome plugin works seamlessly with JWT authentication:
343
-
344
- ```javascript
345
- // JWT token contains tenant information
346
- {
347
- "sub": "user123",
348
- "email": "user@example.com",
349
- "tenant_id": "acme-corp",
350
- "exp": 1234567890
351
- }
352
-
353
- // MultiHome extractor uses the JWT claim
354
- await api.use(MultiHomePlugin, {
355
- extractor: (request) => request.auth?.claims?.tenant_id
356
- });
357
- ```
358
-
359
- ### With Express Plugin
360
-
361
- The Express plugin provides the request object that MultiHome uses:
362
-
363
- ```javascript
364
- // Express middleware sets up request
365
- app.use('/api', (req, res) => {
366
- // MultiHome extractor receives the Express request object
367
- // with headers, path, auth, etc.
368
- });
369
- ```
370
-
371
- ### With REST API Plugin
372
-
373
- MultiHome integrates at multiple points in the REST API lifecycle:
374
-
375
- 1. **Before Schema Validation**: Sets tenant_id on new records
376
- 2. **Before Data Operations**: Validates tenant access
377
- 3. **During Query Building**: Adds WHERE clauses
378
-
379
- ## API Reference
380
-
381
- ### Configuration API
382
-
383
- ```javascript
384
- // Access current configuration
385
- const config = api.multihome.getConfig();
386
- console.log(config);
387
- // {
388
- // field: 'tenant_id',
389
- // excludeResources: ['migrations'],
390
- // requireAuth: true,
391
- // allowMissing: false,
392
- // hasCustomExtractor: true
393
- // }
394
- ```
395
-
396
- ### Variables
397
-
398
- The plugin sets these variables accessible via `api.vars.multihome`:
399
-
400
- - `field`: The tenant ID field name
401
- - `excludeResources`: Array of excluded resource names
402
- - `requireAuth`: Whether tenant context is required
403
- - `allowMissing`: Whether resources can omit the tenant field
404
-
405
- ### Helpers
406
-
407
- - `helpers.extractMultihomeId(request)`: The configured extractor function
408
-
409
- ### Hooks
410
-
411
- The plugin adds these hooks:
412
-
413
- | Hook | When | Purpose |
414
- |------|------|---------|
415
- | `transport:request` | Every request | Extract tenant ID from request |
416
- | `scope:added` | Resource creation | Validate tenant field exists |
417
- | `knexQueryFiltering` | Database queries | Add WHERE clause for tenant |
418
- | `beforeSchemaValidate` | Before validation | Set tenant_id on new records |
419
- | `beforeDataGet/Put/Patch/Delete` | Before operations | Additional security validation |
420
-
421
- ## Troubleshooting
422
-
423
- ### Common Issues
424
-
425
- #### 1. "No multihome context available"
426
-
427
- **Cause**: The extractor couldn't find a tenant ID in the request.
428
-
429
- **Solutions**:
430
- - Check your extractor function is returning a value
431
- - Verify the subdomain/header/token contains tenant information
432
- - Set `requireAuth: false` if some operations don't need tenant context
433
-
434
- #### 2. "Resource must have 'tenant_id' field in schema"
435
-
436
- **Cause**: A resource is missing the tenant field in its schema.
437
-
438
- **Solutions**:
439
- - Add the field to the schema
440
- - Add the resource to `excludeResources` if it's global
441
- - Set `allowMissing: true` if you have mixed resources
442
-
443
- #### 3. "Cannot set tenant_id to X - must match current context Y"
444
-
445
- **Cause**: Trying to set a different tenant_id than the current context.
446
-
447
- **Solution**: Don't include tenant_id in your requests - it's set automatically.
448
-
449
- #### 4. Queries returning no results
450
-
451
- **Cause**: Data exists but with different tenant_id.
452
-
453
- **Debugging**:
454
- ```javascript
455
- // Check current tenant context
456
- api.on('transport:request', (context) => {
457
- console.log('Current tenant:', context.auth?.multihome_id);
458
- });
459
-
460
- // Check query modifications
461
- api.on('knexQueryFiltering', (context) => {
462
- console.log('Query SQL:', context.knexQuery.query.toString());
463
- });
464
- ```
465
-
466
- ### Debug Mode
467
-
468
- Enable detailed logging to troubleshoot:
469
-
470
- ```javascript
471
- const api = new Api({
472
- name: 'my-api',
473
- logging: { level: 'trace' }
474
- });
475
- ```
476
-
477
- ## Best Practices
478
-
479
- ### 1. Schema Design
480
-
481
- Always include the tenant field in your schemas:
482
-
483
- ```javascript
484
- // Good
485
- api.addResource('orders', {
486
- schema: {
487
- order_number: { type: 'string', required: true },
488
- total: { type: 'number', required: true },
489
- tenant_id: { type: 'string', required: true } // Always include
490
- }
491
- });
492
- ```
493
-
494
- ### 2. Consistent Field Naming
495
-
496
- Use the same tenant field name across all resources:
497
-
498
- ```javascript
499
- // Configure once
500
- await api.use(MultiHomePlugin, { field: 'tenant_id' });
501
-
502
- // Use everywhere
503
- // ✓ Good: All resources use 'tenant_id'
504
- // ✗ Bad: Some use 'tenant_id', others use 'company_id'
505
- ```
506
-
507
- ### 3. Validation in Extractors
508
-
509
- Add validation to your extractor functions:
510
-
511
- ```javascript
512
- extractor: (request) => {
513
- const tenantId = request.headers['x-tenant-id'];
514
-
515
- if (!tenantId) {
516
- throw new Error('X-Tenant-ID header is required');
517
- }
518
-
519
- if (!/^[a-z0-9-]+$/.test(tenantId)) {
520
- throw new Error('Invalid tenant ID format');
521
- }
522
-
523
- return tenantId;
524
- }
525
- ```
526
-
527
- ### 4. Migration Strategy
528
-
529
- When adding multi-tenancy to an existing application:
530
-
531
- 1. Add the tenant field to all tables
532
- 2. Populate existing data with a default tenant
533
- 3. Enable the plugin with `allowMissing: true` initially
534
- 4. Gradually update all resources
535
- 5. Switch to `allowMissing: false` when complete
536
-
537
- ### 5. Testing
538
-
539
- Test with multiple tenants:
540
-
541
- ```javascript
542
- describe('Multi-tenancy', () => {
543
- it('isolates data between tenants', async () => {
544
- // Create data for tenant A
545
- const resA = await fetch('https://tenant-a.app.com/api/posts', {
546
- method: 'POST',
547
- body: JSON.stringify({ title: 'Tenant A Post' })
548
- });
549
-
550
- // Try to access from tenant B
551
- const resB = await fetch('https://tenant-b.app.com/api/posts/' + resA.id);
552
- expect(resB.status).toBe(404); // Should not find
553
- });
554
- });
555
- ```
556
-
557
- ### 6. Performance Considerations
558
-
559
- The tenant field should be indexed for performance:
560
-
561
- ```sql
562
- CREATE INDEX idx_posts_tenant_id ON posts(tenant_id);
563
- CREATE INDEX idx_posts_tenant_status ON posts(tenant_id, status);
564
- ```
565
-
566
- ## Complete Example
567
-
568
- Here's a complete multi-tenant API setup:
569
-
570
- ```javascript
571
- import { Api } from 'hooked-api';
572
- import { RestApiPlugin } from './plugins/core/rest-api-plugin.js';
573
- import { RestApiKnexPlugin } from './plugins/core/rest-api-knex-plugin.js';
574
- import { ExpressPlugin } from './plugins/core/connectors/express-plugin.js';
575
- import { JwtAuthPlugin } from './plugins/core/jwt-auth-plugin.js';
576
- import { MultiHomePlugin } from './plugins/core/multihome-plugin.js';
577
- import knex from 'knex';
578
- import express from 'express';
579
-
580
- // Initialize
581
- const app = express();
582
- const db = knex({
583
- client: 'postgresql',
584
- connection: process.env.DATABASE_URL
585
- });
586
-
587
- // Create API
588
- const api = new Api({
589
- name: 'saas-api',
590
- logging: { level: 'info' }
591
- });
592
-
593
- // Install plugins
594
- await api.use(RestApiPlugin);
595
- await api.use(RestApiKnexPlugin, { knex: db });
596
- await api.use(ExpressPlugin, { app });
597
- await api.use(JwtAuthPlugin, {
598
- secret: process.env.JWT_SECRET
599
- });
600
-
601
- // Configure multi-tenancy
602
- await api.use(MultiHomePlugin, {
603
- field: 'tenant_id',
604
- excludeResources: ['system_health', 'public_content'],
605
- requireAuth: true,
606
- allowMissing: false,
607
- extractor: (request) => {
608
- // Try multiple sources
609
- // 1. JWT token (preferred)
610
- if (request.auth?.claims?.tenant_id) {
611
- return request.auth.claims.tenant_id;
612
- }
613
-
614
- // 2. Subdomain (fallback)
615
- const host = request.headers.host || '';
616
- const subdomain = host.split('.')[0];
617
- if (subdomain && !['www', 'api', 'app'].includes(subdomain)) {
618
- return subdomain;
619
- }
620
-
621
- // 3. Header (API clients)
622
- if (request.headers['x-tenant-id']) {
623
- return request.headers['x-tenant-id'];
624
- }
625
-
626
- // No tenant found
627
- throw new Error('Unable to determine tenant context');
628
- }
629
- });
630
-
631
- // Define tenant-specific resources
632
- api.addResource('projects', {
633
- schema: {
634
- name: { type: 'string', required: true },
635
- description: { type: 'string' },
636
- status: { type: 'string', defaultTo: 'active' },
637
- tenant_id: { type: 'string', required: true }
638
- }
639
- });
640
-
641
- api.addResource('team_members', {
642
- schema: {
643
- email: { type: 'string', required: true },
644
- name: { type: 'string', required: true },
645
- role: { type: 'string', defaultTo: 'member' },
646
- tenant_id: { type: 'string', required: true }
647
- }
648
- });
649
-
650
- // Start server
651
- app.listen(3000, () => {
652
- console.log('Multi-tenant API running on port 3000');
653
- }).on('error', (err) => {
654
- console.error('Failed to start server:', err);
655
- process.exit(1)
656
- });
657
- ```
658
-
659
- Now your API automatically:
660
- - Extracts tenant ID from JWT tokens, subdomains, or headers
661
- - Filters all queries by tenant
662
- - Sets tenant_id on new records
663
- - Prevents cross-tenant data access
664
- - Logs security violations
665
-
666
- The MultiHome plugin makes multi-tenancy transparent to your application logic while ensuring complete data isolation.