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,6 +1,5 @@
1
1
  import { Api } from 'hooked-api'
2
- import { RestApiPlugin, RestApiKnexPlugin, RestApiAnyapiKnexPlugin } from '../../index.js'
3
- import { AccessPlugin } from '../../plugins/core/rest-api-access.js'
2
+ import { RestApiPlugin, RestApiKnexPlugin, RestApiAnyapiKnexPlugin, QueryProjectionsPlugin } from '../../index.js'
4
3
  import { ExpressPlugin } from '../../plugins/core/connectors/express-plugin.js'
5
4
  import express from 'express'
6
5
  import { createServer } from 'http'
@@ -126,7 +125,8 @@ export async function createBasicApi (knex, pluginOptions = {}) {
126
125
  country_id: { type: 'number', nullable: true, belongsTo: 'countries', as: 'country' }
127
126
  },
128
127
  relationships: {
129
- books: { type: 'hasMany', target: 'books', foreignKey: 'publisher_id' }
128
+ books: { type: 'hasMany', target: 'books', foreignKey: 'publisher_id' },
129
+ authors: { type: 'hasMany', target: 'authors', foreignKey: 'publisher_id' }
130
130
  },
131
131
  tableName: `${tablePrefix}_publishers`
132
132
  })
@@ -137,7 +137,8 @@ export async function createBasicApi (knex, pluginOptions = {}) {
137
137
  await api.addResource('authors', {
138
138
  schema: {
139
139
  id: { type: 'id' },
140
- name: { type: 'string', required: true, max: 200 }
140
+ name: { type: 'string', required: true, max: 200 },
141
+ publisher_id: { type: 'number', nullable: true, belongsTo: 'publishers', as: 'publisher' }
141
142
  },
142
143
  relationships: {
143
144
  books: { type: 'manyToMany', through: 'book_authors', foreignKey: 'author_id', otherKey: 'book_id' }
@@ -197,9 +198,12 @@ export async function createBasicApi (knex, pluginOptions = {}) {
197
198
  }
198
199
  }
199
200
 
200
- export async function createAccessControlApi (knex, pluginOptions = {}) {
201
- const apiName = pluginOptions.apiName || 'access-test-api'
202
- const tablePrefix = pluginOptions.tablePrefix || 'access'
201
+ /**
202
+ * Creates a small API focused on resource ID normalization behavior.
203
+ */
204
+ export async function createIdNormalizationApi (knex, pluginOptions = {}) {
205
+ const apiName = pluginOptions.apiName || 'id-normalization-test-api'
206
+ const tablePrefix = pluginOptions.tablePrefix || 'id_norm'
203
207
  if (storageMode.isAnyApi()) {
204
208
  storageMode.clearRegistry()
205
209
  }
@@ -230,71 +234,94 @@ export async function createAccessControlApi (knex, pluginOptions = {}) {
230
234
  put: 'no',
231
235
  patch: 'minimal'
232
236
  },
233
- sortableFields: ['id', 'name', 'user_id']
237
+ ...(pluginOptions['rest-api'] || {})
234
238
  })
235
- await useStoragePlugin(api, knex, { tenantId })
236
- await resetAnyApiTables(knex)
237
239
 
238
240
  try {
239
- await api.use(AccessPlugin, pluginOptions.access || {})
240
-
241
- await api.addResource('users', {
242
- schema: {
243
- id: { type: 'id' },
244
- email: { type: 'string', required: true },
245
- display_name: { type: 'string', nullable: true }
246
- },
247
- tableName: `${tablePrefix}_users`
248
- })
249
- await api.resources.users.createKnexTable()
250
- mapTable(`${tablePrefix}_users`, 'users')
251
-
252
- await api.addResource('projects', {
253
- schema: {
254
- id: { type: 'id' },
255
- name: { type: 'string', required: true },
256
- status: { type: 'string', nullable: true },
257
- user_id: { type: 'number', belongsTo: 'users', as: 'user', nullable: true }
258
- },
259
- relationships: {
260
- user: { type: 'belongsTo', target: 'users', foreignKey: 'user_id' }
261
- },
262
- auth: {
263
- query: ['authenticated'],
264
- get: ['owns'],
265
- post: ['authenticated'],
266
- patch: ['owns'],
267
- delete: ['owns']
268
- },
269
- ownership: true,
270
- tableName: `${tablePrefix}_projects`
271
- })
272
- await api.resources.projects.createKnexTable()
273
- mapTable(`${tablePrefix}_projects`, 'projects')
274
-
275
- await api.addResource('notes', {
276
- schema: {
277
- id: { type: 'id' },
278
- project_id: { type: 'number', belongsTo: 'projects', as: 'project', nullable: true },
279
- body: { type: 'string', required: true },
280
- user_id: { type: 'number', belongsTo: 'users', as: 'user', nullable: true }
281
- },
282
- relationships: {
283
- project: { type: 'belongsTo', target: 'projects', foreignKey: 'project_id' },
284
- user: { type: 'belongsTo', target: 'users', foreignKey: 'user_id' }
285
- },
286
- auth: {
287
- query: ['authenticated'],
288
- get: ['owns'],
289
- post: ['authenticated'],
290
- patch: ['owns'],
291
- delete: ['owns']
292
- },
293
- ownership: true,
294
- tableName: `${tablePrefix}_notes`
241
+ await withTenantContext(tenantId, async () => {
242
+ await useStoragePlugin(api, knex, { tenantId })
243
+ await resetAnyApiTables(knex)
244
+
245
+ await api.addResource('countries', {
246
+ schema: {
247
+ id: { type: 'string', required: true },
248
+ name: { type: 'string', required: true, max: 100 }
249
+ },
250
+ tableName: `${tablePrefix}_countries`,
251
+ ...(pluginOptions.countryResourceOptions || {})
252
+ })
253
+ await knex.schema.dropTableIfExists(`${tablePrefix}_countries`)
254
+ await knex.schema.createTable(`${tablePrefix}_countries`, (table) => {
255
+ table.string('id').primary()
256
+ table.string('name').notNullable()
257
+ })
258
+ mapTable(`${tablePrefix}_countries`, 'countries')
259
+
260
+ await api.addResource('publishers', {
261
+ schema: {
262
+ id: { type: 'string', required: true },
263
+ name: { type: 'string', required: true, max: 100 },
264
+ country_id: { type: 'string', nullable: true, belongsTo: 'countries', as: 'country' }
265
+ },
266
+ relationships: {
267
+ tags: { type: 'manyToMany', through: 'publisher_tags', foreignKey: 'publisher_id', otherKey: 'tag_id' }
268
+ },
269
+ tableName: `${tablePrefix}_publishers`,
270
+ ...(pluginOptions.publisherResourceOptions || {})
271
+ })
272
+ await knex.schema.dropTableIfExists(`${tablePrefix}_publishers`)
273
+ await knex.schema.createTable(`${tablePrefix}_publishers`, (table) => {
274
+ table.string('id').primary()
275
+ table.string('name').notNullable()
276
+ table.string('country_id').nullable()
277
+ })
278
+ mapTable(`${tablePrefix}_publishers`, 'publishers')
279
+
280
+ await api.addResource('tags', {
281
+ schema: {
282
+ id: { type: 'string', required: true },
283
+ name: { type: 'string', required: true, max: 100 }
284
+ },
285
+ tableName: `${tablePrefix}_tags`,
286
+ ...(pluginOptions.tagResourceOptions || {})
287
+ })
288
+ await knex.schema.dropTableIfExists(`${tablePrefix}_tags`)
289
+ await knex.schema.createTable(`${tablePrefix}_tags`, (table) => {
290
+ table.string('id').primary()
291
+ table.string('name').notNullable()
292
+ })
293
+ mapTable(`${tablePrefix}_tags`, 'tags')
294
+
295
+ await api.addResource('publisher_tags', {
296
+ schema: {
297
+ id: { type: 'id' },
298
+ publisher_id: { type: 'string', required: true, belongsTo: 'publishers', as: 'publisher' },
299
+ tag_id: { type: 'string', required: true, belongsTo: 'tags', as: 'tag' }
300
+ },
301
+ tableName: `${tablePrefix}_publisher_tags`
302
+ })
303
+ await knex.schema.dropTableIfExists(`${tablePrefix}_publisher_tags`)
304
+ await knex.schema.createTable(`${tablePrefix}_publisher_tags`, (table) => {
305
+ table.increments('id').primary()
306
+ table.string('publisher_id').notNullable()
307
+ table.string('tag_id').notNullable()
308
+ })
309
+ mapTable(`${tablePrefix}_publisher_tags`, 'publisher_tags')
310
+ if (storageMode.isAnyApi()) {
311
+ const descriptorTenant = api.anyapi?.tenantId || tenantId
312
+ const publishersDescriptor = await api.anyapi.registry.getDescriptor(descriptorTenant, 'publishers')
313
+ const tagsDescriptor = await api.anyapi.registry.getDescriptor(descriptorTenant, 'tags')
314
+ const relationshipKey = publishersDescriptor?.manyToMany?.tags?.relationship
315
+ const inverseRelationshipKey = tagsDescriptor?.manyToMany?.publishers?.relationship
316
+ storageMode.registerLink(
317
+ `${tablePrefix}_publisher_tags`,
318
+ 'publishers',
319
+ 'tags',
320
+ relationshipKey,
321
+ inverseRelationshipKey
322
+ )
323
+ }
295
324
  })
296
- await api.resources.notes.createKnexTable()
297
- mapTable(`${tablePrefix}_notes`, 'notes')
298
325
 
299
326
  return api
300
327
  } finally {
@@ -1071,6 +1098,92 @@ export async function createFieldGettersApi (knex, pluginOptions = {}) {
1071
1098
  return api
1072
1099
  }
1073
1100
 
1101
+ export async function createProjectedFieldsApi (knex, pluginOptions = {}) {
1102
+ const api = new Api({
1103
+ name: 'projected-fields-test-api',
1104
+ log: { level: process.env.LOG_LEVEL || 'info' }
1105
+ })
1106
+
1107
+ const tenantId = storageMode.isAnyApi() ? 'projected_fields_tenant' : storageMode.defaultTenant
1108
+ const previousTenant = storageMode.currentTenant
1109
+ if (storageMode.isAnyApi()) {
1110
+ storageMode.setCurrentTenant(tenantId)
1111
+ }
1112
+
1113
+ await api.use(RestApiPlugin, {
1114
+ simplifiedApi: false,
1115
+ simplifiedTransport: false,
1116
+ returnRecordApi: {
1117
+ post: true,
1118
+ put: false,
1119
+ patch: false
1120
+ },
1121
+ returnRecordTransport: {
1122
+ post: 'full',
1123
+ put: 'no',
1124
+ patch: 'minimal'
1125
+ },
1126
+ sortableFields: ['id', 'first_name', 'last_name'],
1127
+ ...pluginOptions['rest-api']
1128
+ })
1129
+ await api.use(QueryProjectionsPlugin)
1130
+
1131
+ await useStoragePlugin(api, knex, { tenantId })
1132
+ await resetAnyApiTables(knex)
1133
+
1134
+ await api.addResource('authors', {
1135
+ schema: {
1136
+ id: { type: 'id' },
1137
+ first_name: { type: 'string', required: true, max: 100 },
1138
+ last_name: { type: 'string', required: true, max: 100 }
1139
+ },
1140
+ queryFields: {
1141
+ full_name: {
1142
+ type: 'string',
1143
+ sortable: true,
1144
+ select: ({ knex, column }) => knex.raw(
1145
+ "trim(coalesce(??, '') || ' ' || coalesce(??, ''))",
1146
+ [column('first_name'), column('last_name')]
1147
+ )
1148
+ },
1149
+ sort_name: {
1150
+ type: 'string',
1151
+ sortable: true,
1152
+ normallyHidden: true,
1153
+ select: ({ knex, column }) => knex.raw(
1154
+ "trim(coalesce(??, '') || ' ' || coalesce(??, ''))",
1155
+ [column('first_name'), column('last_name')]
1156
+ )
1157
+ }
1158
+ },
1159
+ relationships: {
1160
+ books: { type: 'hasMany', target: 'books', foreignKey: 'author_id' }
1161
+ },
1162
+ sortableFields: ['id', 'first_name', 'last_name'],
1163
+ defaultSort: ['full_name'],
1164
+ tableName: 'projected_authors'
1165
+ })
1166
+ await api.resources.authors.createKnexTable()
1167
+ mapTable('projected_authors', 'authors')
1168
+
1169
+ await api.addResource('books', {
1170
+ schema: {
1171
+ id: { type: 'id' },
1172
+ title: { type: 'string', required: true, max: 200 },
1173
+ author_id: { type: 'id', required: true, belongsTo: 'authors', as: 'author' }
1174
+ },
1175
+ tableName: 'projected_books'
1176
+ })
1177
+ await api.resources.books.createKnexTable()
1178
+ mapTable('projected_books', 'books')
1179
+
1180
+ if (storageMode.isAnyApi()) {
1181
+ storageMode.setCurrentTenant(previousTenant)
1182
+ }
1183
+
1184
+ return api
1185
+ }
1186
+
1074
1187
  /**
1075
1188
  * Creates an API with field setters for testing
1076
1189
  */
@@ -1288,14 +1401,14 @@ export async function createFieldSettersApi (knex, pluginOptions = {}) {
1288
1401
  return api
1289
1402
  }
1290
1403
 
1291
- export async function createMultiHomeApi (knex, pluginOptions = {}) {
1292
- const { MultiHomePlugin } = await import('../../plugins/core/rest-api-multihome-plugin.js')
1404
+ export async function createAutoFilterApi (knex, pluginOptions = {}) {
1405
+ const { AutoFilterPlugin } = await import('../../plugins/core/rest-api-autofilter-plugin.js')
1293
1406
 
1294
1407
  const api = new Api({
1295
- name: 'multihome-test-api',
1408
+ name: 'autofilter-test-api',
1296
1409
  })
1297
1410
 
1298
- const tenantId = storageMode.isAnyApi() ? 'multihome_tenant' : storageMode.defaultTenant
1411
+ const tenantId = storageMode.isAnyApi() ? 'autofilter_tenant' : storageMode.defaultTenant
1299
1412
  const previousTenant = storageMode.currentTenant
1300
1413
  if (storageMode.isAnyApi()) {
1301
1414
  storageMode.setCurrentTenant(tenantId)
@@ -1314,7 +1427,7 @@ export async function createMultiHomeApi (knex, pluginOptions = {}) {
1314
1427
  put: 'no',
1315
1428
  patch: 'minimal'
1316
1429
  },
1317
- sortableFields: ['id', 'title', 'name', 'tenant_id']
1430
+ sortableFields: ['id', 'title', 'name', 'workspace_id', 'user_id']
1318
1431
  })
1319
1432
 
1320
1433
  await useStoragePlugin(api, knex, { tenantId })
@@ -1325,34 +1438,77 @@ export async function createMultiHomeApi (knex, pluginOptions = {}) {
1325
1438
  await api.use(ExpressPlugin, pluginOptions.express || {})
1326
1439
  }
1327
1440
 
1328
- // Add MultiHome plugin with configuration
1329
- await api.use(MultiHomePlugin, {
1330
- field: pluginOptions.field || 'tenant_id',
1331
- excludeResources: pluginOptions.excludeResources || ['system_settings'],
1332
- requireAuth: pluginOptions.requireAuth !== undefined ? pluginOptions.requireAuth : true,
1333
- allowMissing: pluginOptions.allowMissing || false,
1334
- extractor: pluginOptions.extractor || ((request) => {
1335
- // Default to header extraction for tests
1336
- return request.headers?.['x-tenant-id'] || null
1337
- })
1441
+ const autofilterOptions = pluginOptions.autofilter || {}
1442
+ const workspaceField = pluginOptions.workspaceField || 'workspace_id'
1443
+ const userField = pluginOptions.userField || 'user_id'
1444
+
1445
+ await api.use(AutoFilterPlugin, {
1446
+ ...autofilterOptions,
1447
+ resolvers: {
1448
+ workspace: ({ context }) => context.scopeValues?.workspaceId,
1449
+ user: ({ context }) => context.scopeValues?.userId,
1450
+ currentProject: ({ context }) => context.scopeValues?.projectId ?? null,
1451
+ ...(autofilterOptions.resolvers || {})
1452
+ },
1453
+ presets: {
1454
+ public: { filters: [] },
1455
+ workspace: {
1456
+ filters: [{ field: workspaceField, resolver: 'workspace' }]
1457
+ },
1458
+ user: {
1459
+ filters: [{ field: userField, resolver: 'user' }]
1460
+ },
1461
+ workspace_user: {
1462
+ filters: [
1463
+ { field: workspaceField, resolver: 'workspace' },
1464
+ { field: userField, resolver: 'user' }
1465
+ ]
1466
+ },
1467
+ ...(autofilterOptions.presets || {})
1468
+ }
1469
+ })
1470
+
1471
+ await api.addResource('workspace_reports', {
1472
+ schema: {
1473
+ id: { type: 'id' },
1474
+ title: { type: 'string', required: true, max: 200 },
1475
+ workspace_id: { type: 'string', required: true }
1476
+ },
1477
+ autofilter: 'workspace',
1478
+ tableName: 'autofilter_workspace_reports'
1338
1479
  })
1480
+ await api.resources.workspace_reports.createKnexTable()
1481
+ mapTable('autofilter_workspace_reports', 'workspace_reports')
1482
+
1483
+ await api.addResource('user_notes', {
1484
+ schema: {
1485
+ id: { type: 'id' },
1486
+ body: { type: 'string', required: true, max: 500 },
1487
+ user_id: { type: 'number', required: true }
1488
+ },
1489
+ autofilter: 'user',
1490
+ tableName: 'autofilter_user_notes'
1491
+ })
1492
+ await api.resources.user_notes.createKnexTable()
1493
+ mapTable('autofilter_user_notes', 'user_notes')
1339
1494
 
1340
- // Tenant-specific resources
1341
1495
  await api.addResource('projects', {
1342
1496
  schema: {
1343
1497
  id: { type: 'id' },
1344
1498
  name: { type: 'string', required: true, max: 200 },
1345
1499
  description: { type: 'string', max: 1000 },
1346
1500
  status: { type: 'string', defaultTo: 'active' },
1347
- tenant_id: { type: 'string', required: true }
1501
+ workspace_id: { type: 'string', required: true },
1502
+ user_id: { type: 'number', required: true }
1348
1503
  },
1349
1504
  relationships: {
1350
1505
  tasks: { type: 'hasMany', target: 'tasks', foreignKey: 'project_id' }
1351
1506
  },
1352
- tableName: 'multihome_projects'
1507
+ autofilter: 'workspace_user',
1508
+ tableName: 'autofilter_projects'
1353
1509
  })
1354
1510
  await api.resources.projects.createKnexTable()
1355
- mapTable('multihome_projects', 'projects')
1511
+ mapTable('autofilter_projects', 'projects')
1356
1512
 
1357
1513
  await api.addResource('tasks', {
1358
1514
  schema: {
@@ -1360,37 +1516,40 @@ export async function createMultiHomeApi (knex, pluginOptions = {}) {
1360
1516
  title: { type: 'string', required: true, max: 200 },
1361
1517
  completed: { type: 'boolean', defaultTo: false },
1362
1518
  project_id: { type: 'number', belongsTo: 'projects', as: 'project' },
1363
- tenant_id: { type: 'string', required: true }
1519
+ workspace_id: { type: 'string', required: true },
1520
+ user_id: { type: 'number', required: true }
1364
1521
  },
1365
- tableName: 'multihome_tasks'
1522
+ autofilter: 'workspace_user',
1523
+ tableName: 'autofilter_tasks'
1366
1524
  })
1367
1525
  await api.resources.tasks.createKnexTable()
1368
- mapTable('multihome_tasks', 'tasks')
1526
+ mapTable('autofilter_tasks', 'tasks')
1369
1527
 
1370
- await api.addResource('users', {
1528
+ await api.addResource('optional_tasks', {
1371
1529
  schema: {
1372
1530
  id: { type: 'id' },
1373
- email: { type: 'string', required: true, unique: true },
1374
- name: { type: 'string', required: true },
1375
- role: { type: 'string', defaultTo: 'member', search: true },
1376
- tenant_id: { type: 'string', required: true }
1531
+ title: { type: 'string', required: true, max: 200 },
1532
+ project_id: { type: 'number', belongsTo: 'projects', as: 'project', nullable: true }
1533
+ },
1534
+ autofilter: {
1535
+ filters: [{ field: 'project_id', resolver: 'currentProject' }]
1377
1536
  },
1378
- tableName: 'multihome_users'
1537
+ tableName: 'autofilter_optional_tasks'
1379
1538
  })
1380
- await api.resources.users.createKnexTable()
1381
- mapTable('multihome_users', 'users')
1539
+ await api.resources.optional_tasks.createKnexTable()
1540
+ mapTable('autofilter_optional_tasks', 'optional_tasks')
1382
1541
 
1383
- // Global resource (excluded from multihome)
1384
1542
  await api.addResource('system_settings', {
1385
1543
  schema: {
1386
1544
  id: { type: 'id' },
1387
1545
  key: { type: 'string', required: true, unique: true },
1388
1546
  value: { type: 'string', required: true }
1389
1547
  },
1390
- tableName: 'multihome_system_settings'
1548
+ autofilter: 'public',
1549
+ tableName: 'autofilter_system_settings'
1391
1550
  })
1392
1551
  await api.resources.system_settings.createKnexTable()
1393
- mapTable('multihome_system_settings', 'system_settings')
1552
+ mapTable('autofilter_system_settings', 'system_settings')
1394
1553
 
1395
1554
  if (storageMode.isAnyApi()) {
1396
1555
  storageMode.setCurrentTenant(previousTenant)
@@ -1786,7 +1945,9 @@ export async function createVirtualFieldsApi (knex, pluginOptions = {}) {
1786
1945
  }
1787
1946
 
1788
1947
  /**
1789
- * Creates an API configuration for testing searchSchema merge behavior
1948
+ * Creates an API configuration for testing searchSchema merge behavior.
1949
+ * Shared suites assert merge/validation behavior across storage backends;
1950
+ * backend-specific execution cases live in storage-specific tests.
1790
1951
  */
1791
1952
  export async function createSearchSchemaMergeApi (knex, pluginOptions = {}) {
1792
1953
  const api = new Api({
@@ -1825,12 +1986,27 @@ export async function createSearchSchemaMergeApi (knex, pluginOptions = {}) {
1825
1986
  type: 'array',
1826
1987
  filterOperator: 'between'
1827
1988
  },
1989
+ // Arbitrary public filter name that searches across multiple fields.
1990
+ // The key `term` is part of the API contract even though there is no `term` column.
1991
+ term: {
1992
+ type: 'string',
1993
+ oneOf: ['name', 'description'],
1994
+ filterOperator: 'like'
1995
+ },
1828
1996
  // This is a virtual field
1829
1997
  category_name: {
1830
1998
  type: 'string',
1831
1999
  actualField: 'category.name',
1832
2000
  filterOperator: 'like'
1833
2001
  },
2002
+ // Arbitrary public filter name with fully custom server-side meaning.
2003
+ // The Knex query layer decides what `availability` means.
2004
+ availability: {
2005
+ type: 'boolean',
2006
+ applyFilter: (query, value) => {
2007
+ query.where('status', value ? 'active' : 'inactive')
2008
+ }
2009
+ },
1834
2010
  // This is an explicit field not marked with search:true
1835
2011
  // 'in' operator requires array type for validation
1836
2012
  status: {
@@ -1872,77 +2048,3 @@ export async function createSearchSchemaMergeApi (knex, pluginOptions = {}) {
1872
2048
 
1873
2049
  return api
1874
2050
  }
1875
-
1876
-
1877
- /**
1878
- * Creates an API with resources that OPT OUT of ownership
1879
- * - public_items_ownerfield: uses ownerField: false
1880
- * - public_items_ownership: uses ownership: false
1881
- * Both tables intentionally DO NOT have a user_id column.
1882
- */
1883
- export async function createOwnershipOptOutApi (knex, pluginOptions = {}) {
1884
- const apiName = pluginOptions.apiName || 'ownership-optout-api'
1885
- const tablePrefix = pluginOptions.tablePrefix || 'ownopt'
1886
- if (storageMode.isAnyApi()) {
1887
- storageMode.clearRegistry()
1888
- }
1889
-
1890
- const api = new Api({
1891
- name: apiName,
1892
- log: { level: process.env.LOG_LEVEL || 'info' }
1893
- })
1894
-
1895
- const previousTenant = storageMode.currentTenant
1896
- const tenantId = storageMode.isAnyApi()
1897
- ? (pluginOptions.tenantId || `${tablePrefix}_tenant`)
1898
- : storageMode.defaultTenant
1899
- if (storageMode.isAnyApi()) {
1900
- storageMode.setCurrentTenant(tenantId)
1901
- }
1902
-
1903
- await api.use(RestApiPlugin, {
1904
- simplifiedApi: false,
1905
- simplifiedTransport: false,
1906
- returnRecordApi: { post: true, put: false, patch: false },
1907
- returnRecordTransport: { post: 'full', put: 'no', patch: 'minimal' },
1908
- sortableFields: ['id', 'name']
1909
- })
1910
- await useStoragePlugin(api, knex, { tenantId })
1911
- await resetAnyApiTables(knex)
1912
-
1913
- try {
1914
- // Access plugin (defaults: ownership enabled, filterByOwner true)
1915
- await api.use(AccessPlugin, pluginOptions.access || {})
1916
-
1917
- // Resource A: disable via ownerField:false
1918
- await api.addResource('public_items_ownerfield', {
1919
- schema: {
1920
- id: { type: 'id' },
1921
- name: { type: 'string', required: true }
1922
- },
1923
- // Important: no user_id column in schema; ownerField disables ownership logic
1924
- ownerField: false,
1925
- tableName: `${tablePrefix}_public_items_of`
1926
- })
1927
- await api.resources.public_items_ownerfield.createKnexTable()
1928
- mapTable(`${tablePrefix}_public_items_of`, 'public_items_ownerfield')
1929
-
1930
- // Resource B: disable via ownership:false
1931
- await api.addResource('public_items_ownership', {
1932
- schema: {
1933
- id: { type: 'id' },
1934
- name: { type: 'string', required: true }
1935
- },
1936
- ownership: false,
1937
- tableName: `${tablePrefix}_public_items_own`
1938
- })
1939
- await api.resources.public_items_ownership.createKnexTable()
1940
- mapTable(`${tablePrefix}_public_items_own`, 'public_items_ownership')
1941
-
1942
- return api
1943
- } finally {
1944
- if (storageMode.isAnyApi()) {
1945
- storageMode.setCurrentTenant(previousTenant)
1946
- }
1947
- }
1948
- }
@@ -0,0 +1,99 @@
1
+ export class FakeFastifyApp {
2
+ constructor () {
3
+ this.routes = []
4
+ this.parsers = []
5
+ }
6
+
7
+ route (definition) {
8
+ this.routes.push(definition)
9
+ }
10
+
11
+ addContentTypeParser (contentType, options, parser) {
12
+ this.parsers.push({ contentType, options, parser })
13
+ }
14
+
15
+ hasContentTypeParser (contentType) {
16
+ return this.parsers.some((entry) => entry.contentType === contentType)
17
+ }
18
+ }
19
+
20
+ export class FakeReply {
21
+ constructor () {
22
+ this.statusCode = null
23
+ this.contentType = null
24
+ this.headers = {}
25
+ this.payload = undefined
26
+ }
27
+
28
+ code (statusCode) {
29
+ this.statusCode = statusCode
30
+ return this
31
+ }
32
+
33
+ type (contentType) {
34
+ this.contentType = contentType
35
+ return this
36
+ }
37
+
38
+ header (name, value) {
39
+ this.headers[name] = value
40
+ return this
41
+ }
42
+
43
+ send (payload) {
44
+ this.payload = payload
45
+ return this
46
+ }
47
+ }
48
+
49
+ export function findFastifyRoute (app, method, url) {
50
+ return app.routes.find((route) => route.method === method && route.url === url)
51
+ }
52
+
53
+ export async function invokeFastifyRoute (
54
+ app,
55
+ {
56
+ method,
57
+ routeUrl,
58
+ requestUrl = routeUrl,
59
+ headers = {},
60
+ body,
61
+ params = {},
62
+ query = {}
63
+ }
64
+ ) {
65
+ const route = findFastifyRoute(app, method, routeUrl)
66
+ if (!route) {
67
+ throw new Error(`Fastify route not found: ${method} ${routeUrl}`)
68
+ }
69
+
70
+ const reply = new FakeReply()
71
+ const request = {
72
+ method,
73
+ url: requestUrl,
74
+ raw: { url: requestUrl },
75
+ headers,
76
+ body,
77
+ params,
78
+ query
79
+ }
80
+
81
+ if (typeof route.preValidation === 'function') {
82
+ await route.preValidation(request, reply)
83
+ if (reply.statusCode !== null || reply.payload !== undefined) {
84
+ return { route, request, reply }
85
+ }
86
+ }
87
+
88
+ try {
89
+ await route.handler(request, reply)
90
+ } catch (error) {
91
+ if (typeof route.errorHandler === 'function') {
92
+ await route.errorHandler(error, request, reply)
93
+ } else {
94
+ throw error
95
+ }
96
+ }
97
+
98
+ return { route, request, reply }
99
+ }