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
@@ -15,16 +15,13 @@ Welcome to the comprehensive guide for JSON REST API. This guide will walk you t
15
15
  3. **[Field Transformations](GUIDE_3_Field_Transformations.md)**
16
16
  Transform data with virtual fields, getters, setters, computed fields, and visibility control.
17
17
 
18
- 4. **[Authentication and Permissions](GUIDE_4_Authentication_And_Permissions.md)**
19
- Implement security with authentication strategies and fine-grained permissions.
20
-
21
- 5. **[Non-Database Resources](GUIDE_5_Non-Db_Resources.md)**
18
+ 4. **[Non-Database Resources](GUIDE_5_Non-Db_Resources.md)**
22
19
  Connect your API to alternative data sources beyond traditional databases.
23
20
 
24
- 6. **[Positioning](GUIDE_6_Positioning.md)**
21
+ 5. **[Positioning](GUIDE_6_Positioning.md)**
25
22
  Implement drag-and-drop functionality with automatic position management.
26
23
 
27
- 7. **[Hooks, Data Management, and Plugins](GUIDE_7_Hooks_Data_Management_And_Plugins.md)**
24
+ 6. **[Hooks, Data Management, and Plugins](GUIDE_7_Hooks_Data_Management_And_Plugins.md)**
28
25
  Master the hook system for data validation, transformation, and business logic.
29
26
 
30
27
 
@@ -39,8 +36,20 @@ Welcome to the comprehensive guide for JSON REST API. This guide will walk you t
39
36
  - **[CORS Configuration](GUIDE_X_Cors.md)**
40
37
  Set up Cross-Origin Resource Sharing for browser-based applications.
41
38
 
42
- - **[Multihome Support](GUIDE_X_Multihome.md)**
43
- Run multiple API instances or serve different domains from a single server.
39
+ - **[Autofiltering](GUIDE_X_Autofiltering.md)**
40
+ Scope datasets automatically using configured context values, without baking in auth semantics.
41
+
42
+ - **[Knex Schema and Migrations](GUIDE_X_Knex_Schema_And_Migrations.md)**
43
+ Create tables, inspect live table snapshots, and generate create or additive diff migrations from table-backed resource scopes.
44
+
45
+ - **[Query Projections](GUIDE_X_Query_Projections.md)**
46
+ Use the optional query-projections plugin to add SQL-backed, output-only derived fields that can participate in sorting and cursor pagination.
47
+
48
+ - **[Writing Plugins](GUIDE_X_Writing_Plugins.md)**
49
+ Build plugins against the supported extension surface, including hooks, scope methods, and query-only read fields.
50
+
51
+ - **[Fastify Integration](GUIDE_X_Fastify.md)**
52
+ Register REST routes on Fastify and reject malformed write payloads at the transport layer using exported resource schemas.
44
53
 
45
54
  - **[Socket.IO Integration](GUIDE_X_SocketIO.md)**
46
55
  Add real-time capabilities to your API with WebSocket support.
@@ -61,4 +70,4 @@ Before starting this guide, you should have:
61
70
 
62
71
  ---
63
72
 
64
- Ready to get started? Head to [Chapter 1: Initial Setup](GUIDE_1_Initial_Setup.md) →
73
+ Ready to get started? Head to [Chapter 1: Initial Setup](GUIDE_1_Initial_Setup.md) →
@@ -103,6 +103,10 @@ await api.addResource('authors', {
103
103
  await api.resources.authors.createKnexTable();
104
104
  ```
105
105
 
106
+ `searchSchema` defines the public filter names your API accepts. Those keys do not need to match database columns or even resource field names. Keep the quickstart mental model simple: `searchSchema` is the resource's public filter contract. For advanced mapping, multi-field search, and custom backend filtering, see [Manipulating and Searching Tables](GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md).
107
+
108
+ If you want to inspect the physical table shape or generate Knex migrations directly from your resource definitions, see [Knex Schema and Migrations](GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md).
109
+
106
110
  ## Run the program
107
111
 
108
112
  ```bash
@@ -332,7 +336,7 @@ You've successfully set up a basic JSON REST API! This quickstart covered the es
332
336
 
333
337
  Check out the [full guide](./GUIDE) to learn about:
334
338
  - Advanced relationships (many-to-many, polymorphic)
335
- - Authentication and authorization
339
+ - Permission hooks, request auth context, and autofiltering
336
340
  - Hooks and middleware
337
341
  - Custom validation
338
342
  - Pagination strategies
package/docs/index.md CHANGED
@@ -27,7 +27,7 @@ JSON REST API is a lightweight, plugin-based framework that makes building REST
27
27
 
28
28
  <div class="feature-card">
29
29
  <h3>🔌 Plugin Architecture</h3>
30
- <p>Extend your API with powerful plugins. Authentication, validation, CORS, and more - just plug and play.</p>
30
+ <p>Extend your API with focused plugins for transports, dataset scoping, query fields, CORS, and more.</p>
31
31
  </div>
32
32
 
33
33
  <div class="feature-card">
package/index.js CHANGED
@@ -10,9 +10,10 @@
10
10
 
11
11
  // Core plugins
12
12
  export { RestApiPlugin } from './plugins/core/rest-api-plugin.js'
13
+ export { AutoFilterPlugin } from './plugins/core/rest-api-autofilter-plugin.js'
14
+ export { QueryProjectionsPlugin } from './plugins/core/rest-api-query-projections-plugin.js'
13
15
  export { FileHandlingPlugin } from './plugins/core/file-handling-plugin.js'
14
16
  export { CorsPlugin } from './plugins/core/rest-api-cors-plugin.js'
15
- export { AccessPlugin } from './plugins/core/rest-api-access.js'
16
17
  export { LabelPlugin } from './plugins/core/rest-api-label-plugin.js'
17
18
 
18
19
  // Database plugins
@@ -21,7 +22,7 @@ export { RestApiAnyapiKnexPlugin } from './plugins/core/rest-api-anyapi-knex-plu
21
22
 
22
23
  // Connector plugins
23
24
  export { ExpressPlugin } from './plugins/core/connectors/express-plugin.js'
24
- // Future: export { FastifyPlugin } from './plugins/core/connectors/fastify-plugin.js';
25
+ export { FastifyPlugin } from './plugins/core/connectors/fastify-plugin.js'
25
26
  // Future: export { KoaPlugin } from './plugins/core/connectors/koa-plugin.js';
26
27
 
27
28
  // Storage plugins for file handling
@@ -3,7 +3,7 @@
3
3
  * Wraps any logger to provide comprehensive error logging
4
4
  */
5
5
 
6
- import { formatError, formatErrorSummary, formatErrorString } from './error-formatter.js'
6
+ import { formatError, formatErrorSummary } from './error-formatter.js'
7
7
 
8
8
  /**
9
9
  * Creates an enhanced logger that automatically formats errors
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "json-rest-api",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "test": "node --test tests/*.test.js",
8
8
  "test:anyapi": "JSON_REST_API_STORAGE=anyapi node --test tests/*.test.js",
9
+ "test:id-contracts": "node --test tests/id-normalization.test.js tests/logical-id-write-validation.test.js tests/custom-id-simplified-post.test.js",
10
+ "test:id-contracts:anyapi": "JSON_REST_API_STORAGE=anyapi node --test tests/id-normalization.test.js",
9
11
  "docs": "node scripts/run-docs.js",
12
+ "docs:dev": "node scripts/run-docs.js --serve",
13
+ "verify": "npm run test:id-contracts && npm run test:id-contracts:anyapi && npm run docs",
10
14
  "sloc": "sloc --exclude \"(_site|node_modules|\\.git|vendor|docs)\" .",
11
15
  "slod": "cd docs;echo 'DOC LINES:'; cat `find . -iname \\*md | grep -v _site | grep -v vendor` | wc",
12
16
  "lint": "eslint .",
@@ -25,7 +29,7 @@
25
29
  "description": "REST API plugin for hooked-api with JSON:API compliance",
26
30
  "dependencies": {
27
31
  "hooked-api": "^1.0.24",
28
- "json-rest-schema": "^1.0.12",
32
+ "json-rest-schema": "^1.0.14",
29
33
  "semver": "^7.7.2"
30
34
  },
31
35
  "peerDependencies": {
@@ -1,4 +1,4 @@
1
- import { RestApiValidationError, RestApiResourceError } from '../../lib/rest-api-errors.js'
1
+ import { RestApiValidationError } from '../../lib/rest-api-errors.js'
2
2
 
3
3
  export const BulkOperationsPlugin = {
4
4
  name: 'bulk-operations',
@@ -353,41 +353,33 @@ export const BulkOperationsPlugin = {
353
353
  // Create route handlers
354
354
  const createBulkRouteHandler = (method) => {
355
355
  return async ({ context, body, query }) => {
356
- try {
357
- // Parse query params for atomic mode override
358
- const atomic = query?.atomic !== undefined
359
- ? query.atomic === 'true'
360
- : defaultAtomic
361
-
362
- let params
363
- if (method === 'bulkPost') {
364
- // For bulk create, expect array in data field (JSON:API style)
365
- params = {
366
- inputRecords: body?.data || body,
367
- atomic
368
- }
369
- } else if (method === 'bulkPatch') {
370
- // For bulk update, expect operations array
371
- params = {
372
- operations: body?.operations || body,
373
- atomic
374
- }
375
- } else if (method === 'bulkDelete') {
376
- // For bulk delete, expect IDs array
377
- params = {
378
- ids: body?.data || body?.ids || body,
379
- atomic
380
- }
356
+ // Parse query params for atomic mode override
357
+ const atomic = query?.atomic !== undefined
358
+ ? query.atomic === 'true'
359
+ : defaultAtomic
360
+
361
+ let params
362
+ if (method === 'bulkPost') {
363
+ // For bulk create, expect array in data field (JSON:API style)
364
+ params = {
365
+ inputRecords: body?.data || body,
366
+ atomic
367
+ }
368
+ } else if (method === 'bulkPatch') {
369
+ // For bulk update, expect operations array
370
+ params = {
371
+ operations: body?.operations || body,
372
+ atomic
373
+ }
374
+ } else if (method === 'bulkDelete') {
375
+ // For bulk delete, expect IDs array
376
+ params = {
377
+ ids: body?.data || body?.ids || body,
378
+ atomic
381
379
  }
382
-
383
- // Call the scope method
384
- const result = await api.scopes[scopeName][method](params)
385
-
386
- return result
387
- } catch (error) {
388
- // Let transport plugin handle error mapping
389
- throw error
390
380
  }
381
+
382
+ return await api.scopes[scopeName][method](params)
391
383
  }
392
384
  }
393
385
 
@@ -15,9 +15,22 @@
15
15
  */
16
16
 
17
17
  import { requirePackage } from 'hooked-api'
18
- import { parseJsonApiQuery } from '../lib/querying-writing/connectors-query-parser.js'
19
18
  import { createContext } from './lib/request-helpers.js'
20
19
  import { createEnhancedLogger } from '../../../lib/enhanced-logger.js'
20
+ import {
21
+ isWriteMethod,
22
+ isAllowedWriteContentType,
23
+ getUnsupportedMediaTypeErrorBody
24
+ } from './lib/transport-http-helpers.js'
25
+ import {
26
+ extractQueryString,
27
+ buildTransportRequestData,
28
+ createConnectorContext,
29
+ runTransportRequestLifecycle,
30
+ buildTransportRejectionBody,
31
+ executeConnectorRoute,
32
+ handleConnectorError
33
+ } from './lib/connector-core.js'
21
34
 
22
35
  export const ExpressPlugin = {
23
36
  name: 'express',
@@ -115,58 +128,39 @@ export const ExpressPlugin = {
115
128
 
116
129
  // Add transport hook middleware
117
130
  router.use(async (req, res, next) => {
118
- const context = createContext(req, res, 'express')
119
-
120
- // Check for Express middleware override first
121
- if (req.urlPrefixOverride) {
122
- context.urlPrefixOverride = req.urlPrefixOverride
123
- }
124
-
125
- // Calculate and cache URL prefix (getUrlPrefix handles all logic)
126
- const { getUrlPrefix } = await import('../lib/querying/url-helpers.js')
127
- context.urlPrefix = getUrlPrefix(context, { vars: { transport: { mountPath: basePath } } }, req)
128
-
129
- // Transport-specific data for hooks
130
- const transportData = {
131
- request: {
132
- method: req.method,
133
- url: req.url,
134
- path: req.path,
135
- headers: req.headers,
136
- body: req.body,
137
- params: req.params,
138
- query: req.query
139
- },
140
- response: {
141
- headers: {},
142
- status: null
143
- }
144
- }
145
-
146
- // Add transport data to context
147
- context.transport = transportData
148
-
149
- // Run transport hooks - don't check return value for consistency with other hooks
150
- // Hooks communicate via context flags (rejection, handled) not return values
151
- await runHooks('transport:request', context)
131
+ const requestData = buildTransportRequestData({
132
+ method: req.method,
133
+ url: req.url,
134
+ path: req.path,
135
+ headers: req.headers,
136
+ body: req.body,
137
+ params: req.params,
138
+ query: req.query
139
+ })
140
+ const { context, transportData } = createConnectorContext({
141
+ request: req,
142
+ reply: res,
143
+ source: 'express',
144
+ mountPath: basePath,
145
+ requestData,
146
+ createContext,
147
+ urlPrefixOverride: req.urlPrefixOverride
148
+ })
149
+ const { rejected, handled } = await runTransportRequestLifecycle({
150
+ context,
151
+ runHooks
152
+ })
152
153
 
153
- // Check if request was rejected (e.g., authentication failure)
154
- if (context.rejection) {
155
- // Apply response headers from hooks
154
+ if (rejected) {
156
155
  if (transportData.response.headers) {
157
156
  res.set(transportData.response.headers)
158
157
  }
159
- return res.status(context.rejection.status || 500).json({
160
- errors: [{
161
- status: String(context.rejection.status || 500),
162
- title: context.rejection.title || 'Request Rejected',
163
- detail: context.rejection.message
164
- }]
165
- })
158
+ return res.status(context.rejection.status || 500).json(
159
+ buildTransportRejectionBody(context)
160
+ )
166
161
  }
167
162
 
168
- // Check if request was already handled by a hook
169
- if (context.handled) {
163
+ if (handled) {
170
164
  return
171
165
  }
172
166
 
@@ -179,18 +173,13 @@ export const ExpressPlugin = {
179
173
  // Content type validation middleware
180
174
  if (strictContentType) {
181
175
  router.use((req, res, next) => {
182
- if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
176
+ if (isWriteMethod(req.method)) {
183
177
  const contentType = req.get('Content-Type')
184
178
 
185
- if (contentType && !contentType.includes('application/vnd.api+json') &&
186
- !contentType.includes('application/json') && !contentType.includes('multipart/form-data')) {
187
- return res.status(415).json({
188
- errors: [{
189
- status: '415',
190
- title: 'Unsupported Media Type',
191
- detail: 'Content-Type must be application/vnd.api+json or application/json'
192
- }]
193
- })
179
+ if (!isAllowedWriteContentType(contentType, { allowMultipart: true })) {
180
+ return res.status(415).json(
181
+ getUnsupportedMediaTypeErrorBody({ allowMultipart: true })
182
+ )
194
183
  }
195
184
  }
196
185
  next()
@@ -207,77 +196,16 @@ export const ExpressPlugin = {
207
196
  url: req.url
208
197
  })
209
198
 
210
- let status = 500
211
- const errorResponse = {
212
- errors: [{
213
- status: '500',
214
- title: 'Internal Server Error',
215
- detail: error.message
216
- }]
217
- }
218
-
219
- // Map error codes to HTTP status
220
- if (error.code === 'REST_API_VALIDATION') {
221
- status = 422
222
- errorResponse.errors = [{
223
- status: '422',
224
- title: 'Validation Error',
225
- detail: error.message,
226
- source: error.details
227
- }]
228
- if (error.details?.violations) {
229
- errorResponse.errors = error.details.violations.map(v => ({
230
- status: '422',
231
- title: 'Validation Error',
232
- detail: v.message,
233
- source: { pointer: v.field }
234
- }))
235
- }
236
- } else if (error.code === 'REST_API_RESOURCE') {
237
- switch (error.subtype) {
238
- case 'not_found':
239
- status = 404
240
- errorResponse.errors[0].status = '404'
241
- errorResponse.errors[0].title = 'Not Found'
242
- break
243
- case 'conflict':
244
- status = 409
245
- errorResponse.errors[0].status = '409'
246
- errorResponse.errors[0].title = 'Conflict'
247
- break
248
- case 'forbidden':
249
- status = 403
250
- errorResponse.errors[0].status = '403'
251
- errorResponse.errors[0].title = 'Forbidden'
252
- break
253
- default:
254
- status = 400
255
- errorResponse.errors[0].status = '400'
256
- errorResponse.errors[0].title = 'Bad Request'
257
- }
258
- } else if (error.code === 'REST_API_PAYLOAD') {
259
- status = 400
260
- errorResponse.errors = [{
261
- status: '400',
262
- title: 'Bad Request',
263
- detail: error.message,
264
- source: { pointer: error.path }
265
- }]
266
- }
267
-
268
- // Run transport:response hook for errors
269
- if (req.transportData && req.context) {
270
- req.transportData.response.status = status
271
- req.transportData.response.body = errorResponse
272
- req.context.transport = req.transportData
273
- await runHooks('transport:response', req.context)
199
+ const { status, body: errorResponse, headers } = await handleConnectorError({
200
+ error,
201
+ context: req.context,
202
+ transportData: req.transportData,
203
+ runHooks
204
+ })
274
205
 
275
- // Apply response headers from hooks
276
- if (req.transportData.response.headers) {
277
- res.set(req.transportData.response.headers)
278
- }
206
+ if (headers) {
207
+ res.set(headers)
279
208
  }
280
-
281
209
  res.status(status).json(errorResponse)
282
210
  }
283
211
 
@@ -285,7 +213,7 @@ export const ExpressPlugin = {
285
213
  * Listen to addRoute hook to create Express routes
286
214
  */
287
215
  addHook('addRoute', 'expressRouteCreator', {}, async ({ context }) => {
288
- const { method, path, handler } = context
216
+ const { method, path, handler, routeMeta } = context
289
217
 
290
218
  // Apply any global before middleware
291
219
  const beforeMiddleware = expressOptions.middleware?.beforeAll || []
@@ -294,78 +222,62 @@ export const ExpressPlugin = {
294
222
  // Extract the handler logic into a shared function to keep it DRY (Don't Repeat Yourself).
295
223
  const expressHandler = async (req, res) => {
296
224
  try {
297
- // Extract request data
298
- const queryString = req.url.split('?')[1] || ''
299
- const context = req.context || createContext(req, res, 'express')
225
+ let context = req.context
226
+ let transportData = req.transportData
227
+
228
+ if (!context || !transportData) {
229
+ const requestData = buildTransportRequestData({
230
+ method: req.method,
231
+ url: req.url,
232
+ path: req.path,
233
+ headers: req.headers,
234
+ body: req.body,
235
+ params: req.params,
236
+ query: req.query
237
+ })
238
+ const setup = createConnectorContext({
239
+ request: req,
240
+ reply: res,
241
+ source: 'express',
242
+ mountPath: basePath,
243
+ requestData,
244
+ createContext,
245
+ urlPrefixOverride: req.urlPrefixOverride
246
+ })
247
+ context = setup.context
248
+ transportData = setup.transportData
249
+ req.context = context
250
+ req.transportData = transportData
251
+ }
300
252
 
301
- // Call the generic handler
302
- const result = await handler({
303
- queryString,
253
+ const outcome = await executeConnectorRoute({
254
+ method: req.method,
255
+ handler,
256
+ queryString: extractQueryString(req.url),
304
257
  headers: req.headers,
305
258
  params: req.params,
306
259
  body: req.body,
307
- context
260
+ context,
261
+ transportData,
262
+ routeMeta,
263
+ helpers,
264
+ mountPath: basePath,
265
+ runHooks
308
266
  })
309
-
310
- // Prepare response status
311
- let responseStatus = 200
312
- if (result && typeof result.statusCode === 'number') {
313
- responseStatus = result.statusCode
314
- } else if (req.method === 'POST' && result) {
315
- // POST with content returns 201 Created
316
- responseStatus = 201
317
- } else if (req.method === 'POST' && !result) {
318
- // POST without content returns 204 No Content
319
- responseStatus = 204
320
- } else if (req.method === 'DELETE') {
321
- // DELETE always returns 204 No Content
322
- responseStatus = 204
323
- } else if ((req.method === 'PUT' || req.method === 'PATCH') && !result) {
324
- // PUT/PATCH return 204 when no content is returned
325
- responseStatus = 204
326
- }
327
-
328
- // Apply headers from the handler result
329
- if (result && result.headers) {
330
- res.set(result.headers)
331
- }
332
-
333
- // Update transport data for response (if transport data exists)
334
- if (req.transportData) {
335
- req.transportData.response.status = responseStatus
336
- req.transportData.response.body = result
337
- context.transport = req.transportData
338
- await runHooks('transport:response', context)
339
- if (req.transportData.response.headers) {
340
- res.set(req.transportData.response.headers)
341
- }
342
- }
267
+ res.set(outcome.headers)
343
268
 
344
269
  // Set content type
345
270
  res.set('Content-Type', 'application/vnd.api+json')
346
271
 
347
- // Set Location header for successful POST requests
348
- if (req.method === 'POST' && context.id) {
349
- // Extract scopeName from the path (e.g., /api/countries -> countries)
350
- const pathParts = path.split('/')
351
- const scopeName = pathParts[pathParts.length - 1]
352
-
353
- // Set Location header for any successful POST (201 or 204)
354
- if (helpers.getLocation) {
355
- const location = helpers.getLocation({ scopeName, id: context.id })
356
- const baseUrl = context.urlPrefix || basePath
357
- res.set('Location', `${baseUrl}${location}`)
358
- }
272
+ if (outcome.location) {
273
+ res.set('Location', outcome.location)
359
274
  }
360
275
 
361
276
  // Handle response based on status
362
- if (responseStatus === 204) {
277
+ if (outcome.status === 204) {
363
278
  res.sendStatus(204)
364
279
  } else {
365
- // If result has a body property, that's what we should send
366
- // This happens when handler returns { statusCode, body, headers }
367
- const responseBody = result && result.body !== undefined ? result.body : result
368
- res.status(responseStatus).json(responseBody)
280
+ res.status(outcome.status).json(outcome.body)
369
281
  }
370
282
  } catch (error) {
371
283
  handleError(error, req, res)
@@ -424,11 +336,6 @@ export const ExpressPlugin = {
424
336
  router[method.toLowerCase()](path, ...beforeMiddleware, expressHandler)
425
337
 
426
338
  // Debug logging for route registration
427
-
428
- // Check if the route was actually added
429
- if (router.stack) {
430
- const lastRoute = router.stack[router.stack.length - 1]
431
- }
432
339
  }
433
340
  } catch (routeError) {
434
341
  log.error('[EXPRESS DEBUG] Error creating route:', {