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,314 +0,0 @@
1
- export const MultiHomePlugin = {
2
- name: 'multihome',
3
- dependencies: ['rest-api'],
4
-
5
- install ({ api, addHook, vars, helpers, log, scopes, pluginOptions }) {
6
- if (!api.knex?.instance) {
7
- throw new Error('Multihome plugin requires a storage plugin with knex support (rest-api-knex or rest-api-anyapi-knex)')
8
- }
9
-
10
- // Get configuration - hooked-api namespaces options by plugin name
11
- const multihomeOptions = pluginOptions || {}
12
-
13
- // Store configuration in vars (data only)
14
- vars.multihome = {
15
- field: multihomeOptions.field || 'multihome_id',
16
- excludeResources: multihomeOptions.excludeResources || ['system_migrations', 'system_logs'],
17
- requireAuth: multihomeOptions.requireAuth !== undefined ? multihomeOptions.requireAuth : true,
18
- allowMissing: multihomeOptions.allowMissing || false
19
- }
20
-
21
- const resolveStorageAdapter = (scopeName, hookContext = {}) => {
22
- if (!scopeName) return null
23
- if (hookContext.storageAdapter) return hookContext.storageAdapter
24
- if (hookContext.knexQuery?.storageAdapter) return hookContext.knexQuery.storageAdapter
25
- if (helpers.getStorageAdapter) {
26
- return helpers.getStorageAdapter(scopeName)
27
- }
28
- return null
29
- }
30
-
31
- const translateColumnReference = ({ field, tableName, scopeName, hookContext }) => {
32
- const storageAdapter = resolveStorageAdapter(scopeName, hookContext)
33
- const translated = storageAdapter?.translateColumn?.(field) ?? field
34
- if (tableName) {
35
- return `${tableName}.${translated}`
36
- }
37
- return translated
38
- }
39
-
40
- const translateFilterValue = ({ field, value, scopeName, hookContext }) => {
41
- const storageAdapter = resolveStorageAdapter(scopeName, hookContext)
42
- if (!storageAdapter?.translateFilterValue) return value
43
- return storageAdapter.translateFilterValue(field, value)
44
- }
45
-
46
- // Default extractor function
47
- function defaultSubdomainExtractor (request) {
48
- // Extract from subdomain (e.g., 'mobily' from 'mobily.app.com')
49
- const host = request.headers?.host || request.hostname || ''
50
- const subdomain = host.split('.')[0]
51
-
52
- // Don't use 'www' or 'api' as tenant IDs
53
- if (subdomain && !['www', 'api', 'app'].includes(subdomain)) {
54
- return subdomain
55
- }
56
-
57
- // Fallback to header
58
- return request.headers?.['x-multihome-id'] || null
59
- }
60
-
61
- // Store extractor function in helpers
62
- helpers.extractMultihomeId = multihomeOptions.extractor || defaultSubdomainExtractor
63
-
64
- // Hook into transport layer to extract multihome_id
65
- addHook('transport:request', 'extract-multihome-id', {}, async ({ context, request, helpers }) => {
66
- // Extract multihome_id using configured extractor
67
- const multihomeId = helpers.extractMultihomeId(request)
68
-
69
- if (multihomeId) {
70
- // Store in auth context (even if no user auth)
71
- context.auth = context.auth || {}
72
- context.auth.multihome_id = multihomeId
73
-
74
- log.debug('Multihome ID extracted', {
75
- multihome_id: multihomeId,
76
- source: request.headers?.host
77
- })
78
- } else if (vars.multihome.requireAuth) {
79
- log.warn('No multihome ID found in request', {
80
- host: request.headers?.host,
81
- headers: Object.keys(request.headers || {})
82
- })
83
- }
84
- })
85
-
86
- // Validate that resources have multihome field when added
87
- addHook('scope:added', 'validate-multihome-field', {}, ({ context, vars }) => {
88
- const { scopeName, scopeOptions } = context
89
-
90
- // Skip excluded resources
91
- if (vars.multihome.excludeResources.includes(scopeName)) {
92
- log.debug(`Resource ${scopeName} excluded from multihome validation`)
93
- return
94
- }
95
-
96
- // Check if schema has multihome field
97
- const schema = scopeOptions.schema
98
- if (schema && !schema[vars.multihome.field]) {
99
- if (vars.multihome.allowMissing) {
100
- log.warn(`Resource ${scopeName} missing ${vars.multihome.field} field - multihome filtering disabled for this
101
- resource`)
102
- } else {
103
- throw new Error(
104
- `Resource '${scopeName}' must have '${vars.multihome.field}' field in schema for multi-tenancy`
105
- )
106
- }
107
- }
108
- })
109
-
110
- // Main filtering hook - adds WHERE clause to all queries
111
- log.debug('Registering multihome filter hook')
112
- addHook('knexQueryFiltering', 'multihome-filter', {}, async ({ context, vars }) => {
113
- // Get query info from context
114
- const { query, tableName, scopeName } = context.knexQuery
115
-
116
- // Skip excluded resources first
117
- if (vars.multihome.excludeResources.includes(scopeName)) {
118
- log.trace(`Skipping multihome filter for excluded resource: ${scopeName}`)
119
- return
120
- }
121
-
122
- // Skip if no multihome context
123
- if (!context.auth?.multihome_id) {
124
- if (vars.multihome.requireAuth) {
125
- throw new Error('No multihome context available - cannot execute query')
126
- }
127
- return
128
- }
129
-
130
- log.debug('Applying multihome tenant filter', {
131
- scopeName,
132
- tableName,
133
- tenant: context.auth.multihome_id,
134
- })
135
-
136
- // Check if this resource has multihome field
137
- const scope = scopes[scopeName]
138
- const hasMultihomeField = scope?.vars?.schemaInfo?.schemaStructure?.[vars.multihome.field]
139
-
140
- if (!hasMultihomeField) {
141
- if (vars.multihome.allowMissing) {
142
- log.trace(`Resource ${scopeName} has no ${vars.multihome.field} field - skipping filter`)
143
- return
144
- } else {
145
- throw new Error(`Resource ${scopeName} missing required ${vars.multihome.field} field`)
146
- }
147
- }
148
-
149
- const storageAdapter = resolveStorageAdapter(scopeName, context)
150
- if (storageAdapter && !context.storageAdapter) {
151
- context.storageAdapter = storageAdapter
152
- }
153
-
154
- const columnRef = translateColumnReference({
155
- field: vars.multihome.field,
156
- tableName,
157
- scopeName,
158
- hookContext: context,
159
- })
160
-
161
- const tenantValue = translateFilterValue({
162
- field: vars.multihome.field,
163
- value: context.auth.multihome_id,
164
- scopeName,
165
- hookContext: context,
166
- })
167
-
168
- if (tenantValue === null) {
169
- query.whereNull(columnRef)
170
- } else {
171
- query.where(columnRef, tenantValue)
172
- }
173
-
174
- log.trace('Added multihome filter', {
175
- scopeName,
176
- tableName,
177
- multihome_id: context.auth.multihome_id
178
- })
179
- })
180
-
181
- // Set multihome_id on new records
182
- addHook('beforeSchemaValidate', 'set-multihome-id', {}, async ({ context, scopeName, vars }) => {
183
- // Skip excluded resources first
184
- if (vars.multihome.excludeResources.includes(scopeName)) {
185
- return
186
- }
187
-
188
- // Skip if no multihome context
189
- if (!context.auth?.multihome_id) {
190
- if (vars.multihome.requireAuth) {
191
- throw new Error('Cannot create record without multihome context')
192
- }
193
- return
194
- }
195
-
196
- // Check if this resource has multihome field
197
- const scope = scopes[scopeName]
198
- const hasMultihomeField = scope?.vars?.schemaInfo?.schemaStructure?.[vars.multihome.field]
199
-
200
- if (!hasMultihomeField) {
201
- if (!vars.multihome.allowMissing) {
202
- throw new Error(`Resource ${scopeName} missing required ${vars.multihome.field} field`)
203
- }
204
- return
205
- }
206
-
207
- // For POST, always set multihome_id
208
- if (context.method === 'post') {
209
- context.inputRecord.data.attributes = context.inputRecord.data.attributes || {}
210
- context.inputRecord.data.attributes[vars.multihome.field] = context.auth.multihome_id
211
-
212
- log.debug('Set multihome_id on new record', {
213
- scopeName,
214
- multihome_id: context.auth.multihome_id
215
- })
216
- }
217
-
218
- // For PUT/PATCH, validate multihome_id if provided
219
- if ((context.method === 'put' || context.method === 'patch') &&
220
- context.inputRecord.data.attributes?.[vars.multihome.field]) {
221
- const providedId = context.inputRecord.data.attributes[vars.multihome.field]
222
- if (providedId !== context.auth.multihome_id) {
223
- throw new Error(
224
- `Cannot set ${vars.multihome.field} to '${providedId}' - must match current context '${context.auth.multihome_id}'`
225
- )
226
- }
227
- }
228
- })
229
-
230
- // Add checkPermissions hook to enforce tenant isolation
231
- // This is the single source of truth for single-record access control
232
- addHook('checkPermissions', 'multihome-check-permissions', {}, async ({ context, scopeName }) => {
233
- // Extract the needed values from originalContext
234
- const auth = context.originalContext?.auth
235
- const minimalRecord = context.originalContext?.minimalRecord
236
- const id = context.originalContext?.id
237
-
238
- // Skip excluded resources
239
- if (vars.multihome.excludeResources.includes(scopeName)) {
240
- return
241
- }
242
-
243
- // Skip if no multihome context
244
- if (!auth?.multihome_id) {
245
- if (vars.multihome.requireAuth) {
246
- throw new Error('No multihome context available')
247
- }
248
- return
249
- }
250
-
251
- // Get the scope to check if it has multihome field
252
- const scope = scopes[scopeName]
253
- const hasMultihomeField = scope?.vars?.schemaInfo?.schemaStructure?.[vars.multihome.field]
254
-
255
- if (!hasMultihomeField) {
256
- return // Resource doesn't support multihome
257
- }
258
-
259
- // For operations on existing records, verify tenant ownership
260
- if (minimalRecord) {
261
- // minimalRecord is in JSON:API format, so tenant_id is in attributes
262
- const recordTenant = minimalRecord.attributes?.[vars.multihome.field]
263
- const userTenant = auth.multihome_id
264
-
265
- if (recordTenant !== userTenant) {
266
- log.error('Multihome permission violation', {
267
- scopeName,
268
- recordId: id,
269
- recordTenant,
270
- userTenant,
271
- method: context.method
272
- })
273
-
274
- // Return 404 for GET to prevent information leakage
275
- // Return 403 for other operations
276
- if (context.method === 'get') {
277
- const error = new Error('Resource not found')
278
- error.code = 'REST_API_RESOURCE'
279
- throw error
280
- } else {
281
- const error = new Error('Access denied - insufficient permissions')
282
- error.code = 'REST_API_FORBIDDEN'
283
- throw error
284
- }
285
- }
286
- }
287
- })
288
-
289
- // Note: PUT, PATCH, and DELETE operations will fail naturally if the record
290
- // doesn't exist due to tenant filtering in the GET operation that precedes them
291
-
292
- // Add API method to get current multihome context
293
- api.multihome = {
294
- getCurrentTenant: () => {
295
- // This would need to be called within a request context
296
- // Real implementation would need access to current request context
297
- return null
298
- },
299
-
300
- // Runtime configuration helper (for debugging/testing)
301
- getConfig: () => ({
302
- ...vars.multihome,
303
- hasCustomExtractor: helpers.extractMultihomeId !== defaultSubdomainExtractor
304
- })
305
- }
306
-
307
- log.info('MultiHome plugin installed', {
308
- field: vars.multihome.field,
309
- excludedResources: vars.multihome.excludeResources,
310
- requireAuth: vars.multihome.requireAuth,
311
- hasCustomExtractor: helpers.extractMultihomeId !== defaultSubdomainExtractor
312
- })
313
- }
314
- }