json-rest-api 1.0.22 → 1.0.23

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 (260) hide show
  1. package/CLAUDE.md +1 -2
  2. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
  3. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
  4. package/docs/API.md +51 -14
  5. package/docs/COMPARISON.md +2 -2
  6. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +28 -1
  7. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
  8. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
  9. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
  10. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
  11. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
  12. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
  13. package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
  14. package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
  15. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +250 -0
  16. package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
  17. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
  18. package/docs/GUIDE/index.md +18 -9
  19. package/docs/QUICKSTART.md +5 -1
  20. package/docs/index.md +1 -1
  21. package/index.js +3 -2
  22. package/package.json +2 -2
  23. package/plugins/core/connectors/express-plugin.js +97 -184
  24. package/plugins/core/connectors/fastify-plugin.js +206 -0
  25. package/plugins/core/connectors/lib/connector-core.js +195 -0
  26. package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
  27. package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
  28. package/plugins/core/lib/dbIntrospection.js +1078 -0
  29. package/plugins/core/lib/dbTablesOperations.js +1104 -160
  30. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -2
  31. package/plugins/core/lib/querying/knex-process-includes.js +3 -4
  32. package/plugins/core/lib/querying/knex-query-helpers.js +17 -71
  33. package/plugins/core/lib/querying/knex-relationship-includes.js +466 -206
  34. package/plugins/core/lib/querying/query-builder-utils.js +19 -0
  35. package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
  36. package/plugins/core/lib/querying/sort-helpers.js +33 -0
  37. package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
  38. package/plugins/core/lib/querying/url-helpers.js +8 -2
  39. package/plugins/core/lib/querying-writing/compile-schemas.js +12 -1
  40. package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -13
  41. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
  42. package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
  43. package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
  44. package/plugins/core/lib/storage/canonical-storage-mapping.js +113 -0
  45. package/plugins/core/lib/storage/storage-adapter.js +79 -2
  46. package/plugins/core/lib/storage/storage-mapping.js +204 -0
  47. package/plugins/core/rest-api-anyapi-knex-plugin.js +355 -228
  48. package/plugins/core/rest-api-autofilter-plugin.js +414 -0
  49. package/plugins/core/rest-api-knex-plugin.js +249 -211
  50. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
  51. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
  52. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
  53. package/plugins/core/rest-api-plugin-methods/common.js +121 -83
  54. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
  55. package/plugins/core/rest-api-plugin-methods/get.js +23 -14
  56. package/plugins/core/rest-api-plugin-methods/patch.js +28 -12
  57. package/plugins/core/rest-api-plugin-methods/post.js +13 -8
  58. package/plugins/core/rest-api-plugin-methods/put.js +45 -21
  59. package/plugins/core/rest-api-plugin-methods/query.js +15 -53
  60. package/plugins/core/rest-api-plugin.js +2 -0
  61. package/plugins/core/rest-api-positioning-plugin.js +1 -1
  62. package/plugins/core/rest-api-query-projections-plugin.js +130 -0
  63. package/plugins/core/socketio-plugin.js +1 -3
  64. package/tests/autofilter.test.js +463 -0
  65. package/tests/custom-id-simplified-post.test.js +75 -0
  66. package/tests/custom-idproperty-relationships.test.js +33 -0
  67. package/tests/db-introspection.test.js +518 -0
  68. package/tests/db-table-operations.test.js +781 -0
  69. package/tests/fastify-plugin.test.js +141 -0
  70. package/tests/field-getters.test.js +1 -2
  71. package/tests/field-setters.test.js +0 -4
  72. package/tests/fixtures/api-configs.js +188 -219
  73. package/tests/helpers/fake-fastify.js +99 -0
  74. package/tests/http-connectors-parity.test.js +235 -0
  75. package/tests/projected-fields.test.js +319 -0
  76. package/tests/relationships.test.js +3 -3
  77. package/tests/searchschema-knex-behavior.test.js +187 -0
  78. package/tests/searchschema-merge.test.js +21 -69
  79. package/tests/storage-mapping.test.js +210 -0
  80. package/audit/00-charter.md +0 -103
  81. package/audit/01-scope-map.md +0 -84
  82. package/audit/02-coverage-matrix.csv +0 -174
  83. package/audit/03-invariants.md +0 -123
  84. package/audit/04-standards-rubric.md +0 -56
  85. package/audit/05-competitor-rubric.md +0 -77
  86. package/audit/06-findings.md +0 -160
  87. package/audit/07-risk-register.csv +0 -10
  88. package/audit/08-remediation-roadmap.md +0 -177
  89. package/audit/09-signoff-check.md +0 -23
  90. package/audit/10-final-synthesis.md +0 -29
  91. package/audit/evidence/code/all-files-json-rest-api.txt +0 -168
  92. package/audit/evidence/code/all-files-json-rest-schema.txt +0 -12
  93. package/audit/evidence/code/boundary-leak-checks.txt +0 -72
  94. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__bulk-operations-plugin.js.txt +0 -480
  95. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__express-plugin.js.txt +0 -532
  96. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__busboy-detector.js.txt +0 -175
  97. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__formidable-detector.js.txt +0 -138
  98. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__request-helpers.js.txt +0 -222
  99. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt +0 -342
  100. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__dbTablesOperations.js.txt +0 -304
  101. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__fractional-positioning.js.txt +0 -188
  102. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__compile-schemas.js.txt +0 -336
  103. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__connectors-query-parser.js.txt +0 -165
  104. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-capabilities.js.txt +0 -265
  105. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-value-normalizers.js.txt +0 -369
  106. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__default-data-helpers.js.txt +0 -72
  107. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__field-utils.js.txt +0 -139
  108. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-constants.js.txt +0 -45
  109. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-field-helpers.js.txt +0 -396
  110. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-json-api-transformers.js.txt +0 -162
  111. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__payload-validators.js.txt +0 -1314
  112. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__schema-helpers.js.txt +0 -488
  113. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__scope-validations.js.txt +0 -484
  114. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__simplified-helpers.js.txt +0 -857
  115. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-cross-table-search.js.txt +0 -565
  116. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-json-api-transformers-querying.js.txt +0 -485
  117. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-pagination-helpers.js.txt +0 -560
  118. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-process-includes.js.txt +0 -167
  119. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers-base.js.txt +0 -109
  120. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers.js.txt +0 -1064
  121. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-relationship-includes.js.txt +0 -1983
  122. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-window-queries.js.txt +0 -328
  123. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__url-helpers.js.txt +0 -119
  124. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__storage__storage-adapter.js.txt +0 -160
  125. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__many-to-many-manipulations.js.txt +0 -300
  126. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__relationship-processor.js.txt +0 -245
  127. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-access.js.txt +0 -459
  128. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-cors-plugin.js.txt +0 -237
  129. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-knex-plugin.js.txt +0 -1135
  130. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-multihome-plugin.js.txt +0 -314
  131. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__compile-resource-schemas.js.txt +0 -7
  132. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-relationship-routes.js.txt +0 -71
  133. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-scope-routes.js.txt +0 -102
  134. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__turn-scope-init-into-vars.js.txt +0 -49
  135. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__validate-include-configurations.js.txt +0 -39
  136. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__add-route.js.txt +0 -23
  137. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__check-permissions.js.txt +0 -13
  138. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__common.js.txt +0 -639
  139. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete-relationship.js.txt +0 -117
  140. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete.js.txt +0 -104
  141. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__enrich-attributes.js.txt +0 -199
  142. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-related.js.txt +0 -353
  143. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-relationship.js.txt +0 -61
  144. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get.js.txt +0 -184
  145. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch-relationship.js.txt +0 -55
  146. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch.js.txt +0 -191
  147. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post-relationship.js.txt +0 -110
  148. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post.js.txt +0 -168
  149. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__put.js.txt +0 -253
  150. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__query.js.txt +0 -207
  151. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__release.js.txt +0 -12
  152. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin.js.txt +0 -165
  153. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-positioning-plugin.js.txt +0 -668
  154. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__socketio-plugin.js.txt +0 -621
  155. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__local-storage.js.txt +0 -218
  156. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__s3-storage.js.txt +0 -124
  157. package/audit/evidence/code/critical-pass1/json-rest-schema__src__CorePlugin.js.txt +0 -269
  158. package/audit/evidence/code/critical-pass1/json-rest-schema__src__Schema.js.txt +0 -242
  159. package/audit/evidence/code/critical-pass1/json-rest-schema__src__index.js.txt +0 -77
  160. package/audit/evidence/code/critical-pass1-risk-keywords.txt +0 -238
  161. package/audit/evidence/code/critical-pass1-summary.csv +0 -67
  162. package/audit/evidence/code/critical-pass2-summary.csv +0 -67
  163. package/audit/evidence/code/early-silent-zones-no-test-token.txt +0 -57
  164. package/audit/evidence/code/error-model-signals.txt +0 -98
  165. package/audit/evidence/code/git-files-json-rest-api.txt +0 -171
  166. package/audit/evidence/code/git-files-json-rest-schema.txt +0 -27
  167. package/audit/evidence/code/hook-flow-signals.txt +0 -282
  168. package/audit/evidence/code/idproperty-signals.txt +0 -157
  169. package/audit/evidence/code/import-cycles-non-anyapi.txt +0 -3
  170. package/audit/evidence/code/import-hotspots-non-anyapi.txt +0 -96
  171. package/audit/evidence/code/imports-json-rest-api.txt +0 -354
  172. package/audit/evidence/code/imports-json-rest-schema.txt +0 -15
  173. package/audit/evidence/code/in-scope-files-json-rest-api.txt +0 -160
  174. package/audit/evidence/code/in-scope-files-json-rest-schema.txt +0 -13
  175. package/audit/evidence/code/non-anyapi-core-files.txt +0 -101
  176. package/audit/evidence/code/permission-signals.txt +0 -193
  177. package/audit/evidence/code/query-include-signals.txt +0 -687
  178. package/audit/evidence/code/transaction-signals.txt +0 -28
  179. package/audit/evidence/code/validation-signals.txt +0 -650
  180. package/audit/evidence/code/write-path-signals.txt +0 -61
  181. package/audit/evidence/docs/docs-claims-signals.txt +0 -1075
  182. package/audit/evidence/docs/schema-docs-claims-signals.txt +0 -179
  183. package/audit/evidence/external/corpus-freeze.md +0 -21
  184. package/audit/evidence/external/npm-competitor-metadata.txt +0 -70
  185. package/audit/evidence/external/npm-download-signals.txt +0 -23
  186. package/audit/evidence/external/npm-jsonapi-server.txt +0 -8
  187. package/audit/evidence/external/npm-modern-jsonapi-metadata.txt +0 -38
  188. package/audit/evidence/external/readme-fortune-signals.txt +0 -26
  189. package/audit/evidence/external/readme-fortune.md +0 -165
  190. package/audit/evidence/external/readme-json-api-serializer-signals.txt +0 -80
  191. package/audit/evidence/external/readme-json-api-serializer.md +0 -690
  192. package/audit/evidence/external/readme-jsonapi-serde-server-signals.txt +0 -0
  193. package/audit/evidence/external/readme-jsonapi-serde-server.md +0 -22
  194. package/audit/evidence/external/readme-jsonapi-serializer-signals.txt +0 -63
  195. package/audit/evidence/external/readme-jsonapi-serializer.md +0 -315
  196. package/audit/evidence/external/readme-jsonapi-server-signals.txt +0 -19
  197. package/audit/evidence/external/readme-jsonapi-server.md +0 -104
  198. package/audit/evidence/external/readme-nestjs-json-api-signals.txt +0 -4
  199. package/audit/evidence/external/readme-nestjs-json-api.md +0 -94
  200. package/audit/evidence/external/readme-yaser-json-api-nestjs-signals.txt +0 -47
  201. package/audit/evidence/external/readme-yaser-json-api-nestjs.md +0 -447
  202. package/audit/evidence/external/standard-jsonapi-snippets.txt +0 -84
  203. package/audit/evidence/external/standard-rfc-snippets.txt +0 -168
  204. package/audit/evidence/external/standards-sources.txt +0 -6
  205. package/audit/evidence/runtime/baseline-commits.txt +0 -2
  206. package/audit/evidence/runtime/db-adapter-assumptions.txt +0 -3
  207. package/audit/evidence/runtime/environment.txt +0 -4
  208. package/audit/evidence/runtime/invariant-runtime-mapping.md +0 -38
  209. package/audit/evidence/runtime/probe-patch-relationship-error.exitcode +0 -1
  210. package/audit/evidence/runtime/probe-patch-relationship-error.log +0 -76
  211. package/audit/evidence/runtime/probe-patch-relationship-error.mjs +0 -67
  212. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.exitcode +0 -1
  213. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.log +0 -62
  214. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.mjs +0 -47
  215. package/audit/evidence/runtime/test-exitcodes.txt +0 -4
  216. package/audit/evidence/runtime/test-summary.md +0 -107
  217. package/audit/evidence/runtime/trace-anchors.txt +0 -61
  218. package/audit/evidence/tests/access-multitenancy-test-signals.txt +0 -125
  219. package/audit/evidence/tests/failing-tests-summary.txt +0 -25
  220. package/audit/evidence/tests/npm-test-json-rest-api.exitcode +0 -1
  221. package/audit/evidence/tests/npm-test-json-rest-api.log +0 -5885
  222. package/audit/evidence/tests/npm-test-json-rest-schema.exitcode +0 -1
  223. package/audit/evidence/tests/npm-test-json-rest-schema.log +0 -356
  224. package/audit/evidence/tests/query-include-test-signals.txt +0 -948
  225. package/audit/evidence/tests/relationship-test-signals.txt +0 -223
  226. package/audit/evidence/tests/skipped-tests.txt +0 -10
  227. package/audit/evidence/tests/test-suite-list-non-anyapi.txt +0 -30
  228. package/audit/evidence/tests/test-suite-list.txt +0 -32
  229. package/audit/subsystems/docs-drift.md +0 -49
  230. package/audit/subsystems/pass2-cross-review.md +0 -131
  231. package/audit/subsystems/plugins-access.md +0 -51
  232. package/audit/subsystems/plugins-bulk.md +0 -50
  233. package/audit/subsystems/plugins-cors.md +0 -48
  234. package/audit/subsystems/plugins-file.md +0 -53
  235. package/audit/subsystems/plugins-multihome.md +0 -50
  236. package/audit/subsystems/plugins-positioning.md +0 -54
  237. package/audit/subsystems/plugins-socketio.md +0 -50
  238. package/audit/subsystems/query-engine.md +0 -61
  239. package/audit/subsystems/relationships.md +0 -55
  240. package/audit/subsystems/rest-orchestration.md +0 -66
  241. package/audit/subsystems/schema-core.md +0 -64
  242. package/audit/subsystems/storage-knex.md +0 -57
  243. package/audit/subsystems/tests.md +0 -48
  244. package/audit/subsystems/transport-express.md +0 -55
  245. package/audit/traces/trace-read-get.md +0 -22
  246. package/audit/traces/trace-read-query.md +0 -25
  247. package/audit/traces/trace-relationship-endpoints.md +0 -31
  248. package/audit/traces/trace-write-delete.md +0 -17
  249. package/audit/traces/trace-write-patch.md +0 -26
  250. package/audit/traces/trace-write-post.md +0 -22
  251. package/audit/traces/trace-write-put.md +0 -23
  252. package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
  253. package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
  254. package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
  255. package/plugins/core/rest-api-access.js +0 -459
  256. package/plugins/core/rest-api-multihome-plugin.js +0 -314
  257. package/tests/multihome.test.js +0 -553
  258. package/tests/multihome.test.js.bak +0 -550
  259. package/tests/ownership-optout.test.js +0 -76
  260. 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.