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,218 +0,0 @@
1
- 1 /**
2
- 2 * Local File Storage Adapter
3
- 3 *
4
- 4 * Production-ready local storage with secure filename handling
5
- 5 */
6
- 6
7
- 7 import { promises as fs } from 'fs'
8
- 8 import path from 'path'
9
- 9 import crypto from 'crypto'
10
- 10
11
- 11 export class LocalStorage {
12
- 12 constructor (options = {}) {
13
- 13 this.directory = path.resolve(options.directory || './uploads')
14
- 14 this.fileBaseUrl = options.fileBaseUrl || '/uploads'
15
- 15 this.nameStrategy = options.nameStrategy || 'hash' // 'hash', 'timestamp', 'original', 'custom'
16
- 16 this.nameGenerator = options.nameGenerator // Custom function
17
- 17 this.preserveExtension = options.preserveExtension !== false
18
- 18 this.allowedExtensions = options.allowedExtensions // Whitelist
19
- 19 this.maxFilenameLength = options.maxFilenameLength || 255
20
- 20 }
21
- 21
22
- 22 /**
23
- 23 * Generate a safe filename
24
- 24 */
25
- 25 async generateFilename (file) {
26
- 26 let basename, extension
27
- 27
28
- 28 // Extract and validate extension
29
- 29 const originalExt = path.extname(file.filename).toLowerCase()
30
- 30 if (this.preserveExtension && originalExt) {
31
- 31 // Validate extension against whitelist if provided
32
- 32 if (this.allowedExtensions && !this.allowedExtensions.includes(originalExt)) {
33
- 33 throw new Error(`File extension '${originalExt}' is not allowed`)
34
- 34 }
35
- 35 extension = originalExt
36
- 36 } else {
37
- 37 // Determine extension from MIME type for safety
38
- 38 extension = this.getExtensionFromMimeType(file.mimetype)
39
- 39 }
40
- 40
41
- 41 // Generate basename based on strategy
42
- 42 switch (this.nameStrategy) {
43
- 43 case 'hash':
44
- 44 // Cryptographically secure random name
45
- 45 basename = crypto.randomBytes(16).toString('hex')
46
- 46 break
47
- 47
48
- 48 case 'timestamp':
49
- 49 // Timestamp + random suffix to prevent collisions
50
- 50 basename = `${Date.now()}_${crypto.randomBytes(4).toString('hex')}`
51
- 51 break
52
- 52
53
- 53 case 'original':
54
- 54 // Sanitized original filename
55
- 55 basename = this.sanitizeFilename(
56
- 56 path.basename(file.filename, originalExt)
57
- 57 )
58
- 58 break
59
- 59
60
- 60 case 'custom':
61
- 61 if (!this.nameGenerator) {
62
- 62 throw new Error('nameGenerator function required for custom strategy')
63
- 63 }
64
- 64 basename = await this.nameGenerator(file)
65
- 65 break
66
- 66
67
- 67 default:
68
- 68 throw new Error(`Unknown name strategy: ${this.nameStrategy}`)
69
- 69 }
70
- 70
71
- 71 // Combine basename and extension
72
- 72 let filename = extension ? `${basename}${extension}` : basename
73
- 73
74
- 74 // Ensure filename doesn't exceed max length
75
- 75 if (filename.length > this.maxFilenameLength) {
76
- 76 // Truncate basename to fit
77
- 77 const maxBasename = this.maxFilenameLength - (extension?.length || 0)
78
- 78 basename = basename.substring(0, maxBasename)
79
- 79 filename = extension ? `${basename}${extension}` : basename
80
- 80 }
81
- 81
82
- 82 // Handle duplicates
83
- 83 filename = await this.ensureUnique(filename)
84
- 84
85
- 85 return filename
86
- 86 }
87
- 87
88
- 88 /**
89
- 89 * Sanitize filename for safe storage
90
- 90 */
91
- 91 sanitizeFilename (filename) {
92
- 92 return filename
93
- 93 // Remove path traversal attempts
94
- 94 .replace(/\.\./g, '')
95
- 95 .replace(/[\/\\]/g, '')
96
- 96 // Remove control characters and special chars
97
- 97 .replace(/[^\w\s.-]/g, '_')
98
- 98 // Remove leading/trailing dots and spaces
99
- 99 .replace(/^[\s.]+|[\s.]+$/g, '')
100
- 100 // Collapse multiple underscores
101
- 101 .replace(/_+/g, '_') ||
102
- 102 // Default if empty
103
- 103 'unnamed'
104
- 104 }
105
- 105
106
- 106 /**
107
- 107 * Ensure filename is unique
108
- 108 */
109
- 109 async ensureUnique (filename) {
110
- 110 const filepath = path.join(this.directory, filename)
111
- 111
112
- 112 try {
113
- 113 await fs.access(filepath)
114
- 114 // File exists, generate unique name
115
- 115 const ext = path.extname(filename)
116
- 116 const base = path.basename(filename, ext)
117
- 117 let counter = 1
118
- 118 let newFilename
119
- 119
120
- 120 do {
121
- 121 newFilename = `${base}_${counter}${ext}`
122
- 122 counter++
123
- 123 } while (await this.fileExists(path.join(this.directory, newFilename)))
124
- 124
125
- 125 return newFilename
126
- 126 } catch (error) {
127
- 127 // File doesn't exist, name is unique
128
- 128 return filename
129
- 129 }
130
- 130 }
131
- 131
132
- 132 /**
133
- 133 * Check if file exists
134
- 134 */
135
- 135 async fileExists (filepath) {
136
- 136 try {
137
- 137 await fs.access(filepath)
138
- 138 return true
139
- 139 } catch {
140
- 140 return false
141
- 141 }
142
- 142 }
143
- 143
144
- 144 /**
145
- 145 * Get safe extension from MIME type
146
- 146 */
147
- 147 getExtensionFromMimeType (mimetype) {
148
- 148 // Safe mappings only
149
- 149 const mimeToExt = {
150
- 150 'image/jpeg': '.jpg',
151
- 151 'image/png': '.png',
152
- 152 'image/gif': '.gif',
153
- 153 'image/webp': '.webp',
154
- 154 'application/pdf': '.pdf',
155
- 155 'text/plain': '.txt',
156
- 156 'application/json': '.json',
157
- 157 'video/mp4': '.mp4',
158
- 158 'audio/mpeg': '.mp3',
159
- 159 'application/zip': '.zip'
160
- 160 }
161
- 161
162
- 162 return mimeToExt[mimetype] || '.bin'
163
- 163 }
164
- 164
165
- 165 /**
166
- 166 * Upload file with secure filename
167
- 167 */
168
- 168 async upload (file) {
169
- 169 // Ensure directory exists
170
- 170 await fs.mkdir(this.directory, { recursive: true })
171
- 171
172
- 172 // Generate secure filename
173
- 173 const filename = await this.generateFilename(file)
174
- 174 const filepath = path.join(this.directory, filename)
175
- 175
176
- 176 // Ensure we're not writing outside our directory (defense in depth)
177
- 177 const resolvedPath = path.resolve(filepath)
178
- 178 const resolvedDir = path.resolve(this.directory)
179
- 179 if (!resolvedPath.startsWith(resolvedDir)) {
180
- 180 throw new Error('Invalid file path')
181
- 181 }
182
- 182
183
- 183 // Write file
184
- 184 if (file.data) {
185
- 185 await fs.writeFile(filepath, file.data)
186
- 186 } else if (file.filepath) {
187
- 187 await fs.rename(file.filepath, filepath)
188
- 188 } else {
189
- 189 throw new Error('File has no data or filepath')
190
- 190 }
191
- 191
192
- 192 // Return public URL
193
- 193 return `${this.fileBaseUrl}/${filename}`
194
- 194 }
195
- 195
196
- 196 /**
197
- 197 * Delete a file
198
- 198 */
199
- 199 async delete (url) {
200
- 200 const filename = path.basename(url)
201
- 201 const filepath = path.join(this.directory, filename)
202
- 202
203
- 203 // Security check
204
- 204 const resolvedPath = path.resolve(filepath)
205
- 205 const resolvedDir = path.resolve(this.directory)
206
- 206 if (!resolvedPath.startsWith(resolvedDir)) {
207
- 207 throw new Error('Invalid file path')
208
- 208 }
209
- 209
210
- 210 try {
211
- 211 await fs.unlink(filepath)
212
- 212 } catch (error) {
213
- 213 if (error.code !== 'ENOENT') {
214
- 214 throw error
215
- 215 }
216
- 216 }
217
- 217 }
218
- 218 }
@@ -1,124 +0,0 @@
1
- 1 /**
2
- 2 * S3 Storage Adapter
3
- 3 *
4
- 4 * Stores uploaded files in Amazon S3 or S3-compatible storage.
5
- 5 *
6
- 6 * Usage:
7
- 7 * ```javascript
8
- 8 * import { S3Storage } from 'jsonrestapi/lib/storage/s3-storage.js';
9
- 9 *
10
- 10 * const storage = new S3Storage({
11
- 11 * bucket: 'my-uploads',
12
- 12 * region: 'us-east-1',
13
- 13 * accessKeyId: process.env.AWS_ACCESS_KEY_ID,
14
- 14 * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
15
- 15 * prefix: 'uploads/' // optional path prefix
16
- 16 * });
17
- 17 *
18
- 18 * const schema = {
19
- 19 * image: { type: 'file', storage }
20
- 20 * };
21
- 21 * ```
22
- 22 */
23
- 23
24
- 24 import crypto from 'crypto'
25
- 25 import path from 'path'
26
- 26
27
- 27 export class S3Storage {
28
- 28 constructor (options = {}) {
29
- 29 this.bucket = options.bucket
30
- 30 this.region = options.region || 'us-east-1'
31
- 31 this.prefix = options.prefix || ''
32
- 32 this.acl = options.acl || 'public-read'
33
- 33
34
- 34 if (!this.bucket) {
35
- 35 throw new Error('S3Storage requires a bucket name')
36
- 36 }
37
- 37
38
- 38 // Note: In a real implementation, you would initialize the AWS SDK here
39
- 39 // For this example, we'll just mock the behavior
40
- 40 this.mockMode = options.mockMode !== false // Default to mock mode
41
- 41
42
- 42 if (!this.mockMode) {
43
- 43 // Real S3 initialization would go here
44
- 44 // const { S3Client } = require('@aws-sdk/client-s3');
45
- 45 // this.s3 = new S3Client({ region: this.region });
46
- 46 }
47
- 47 }
48
- 48
49
- 49 /**
50
- 50 * Upload a file to S3
51
- 51 * @param {Object} file - File object from detector
52
- 52 * @returns {Promise<string>} URL of the uploaded file
53
- 53 */
54
- 54 async upload (file) {
55
- 55 // Generate S3 key
56
- 56 const ext = path.extname(file.filename)
57
- 57 const hash = crypto.randomBytes(16).toString('hex')
58
- 58 const key = `${this.prefix}${hash}${ext}`
59
- 59
60
- 60 if (this.mockMode) {
61
- 61 // Mock mode - just return a fake URL
62
- 62 const url = `https://${this.bucket}.s3.${this.region}.amazonaws.com/${key}`
63
- 63
64
- 64 // Simulate upload delay
65
- 65 await new Promise(resolve => setTimeout(resolve, 100))
66
- 66
67
- 67 return url
68
- 68 }
69
- 69
70
- 70 // Real S3 upload would go here
71
- 71 /*
72
- 72 const { PutObjectCommand } = require('@aws-sdk/client-s3');
73
- 73
74
- 74 const command = new PutObjectCommand({
75
- 75 Bucket: this.bucket,
76
- 76 Key: key,
77
- 77 Body: file.data,
78
- 78 ContentType: file.mimetype,
79
- 79 ACL: this.acl,
80
- 80 Metadata: {
81
- 81 originalName: file.filename
82
- 82 }
83
- 83 });
84
- 84
85
- 85 await this.s3.send(command);
86
- 86
87
- 87 // Return public URL
88
- 88 if (this.acl === 'public-read') {
89
- 89 return `https://${this.bucket}.s3.${this.region}.amazonaws.com/${key}`;
90
- 90 } else {
91
- 91 // For private files, you might return a signed URL
92
- 92 return await this.getSignedUrl(key);
93
- 93 }
94
- 94 */
95
- 95 }
96
- 96
97
- 97 /**
98
- 98 * Delete a file from S3
99
- 99 * @param {string} url - URL returned by upload()
100
- 100 * @returns {Promise<void>}
101
- 101 */
102
- 102 async delete (url) {
103
- 103 // Extract key from URL
104
- 104 const urlObj = new URL(url)
105
- 105 const key = urlObj.pathname.substring(1) // Remove leading /
106
- 106
107
- 107 if (this.mockMode) {
108
- 108 // Mock mode - just simulate deletion
109
- 109 await new Promise(resolve => setTimeout(resolve, 50))
110
- 110 }
111
- 111
112
- 112 // Real S3 delete would go here
113
- 113 /*
114
- 114 const { DeleteObjectCommand } = require('@aws-sdk/client-s3');
115
- 115
116
- 116 const command = new DeleteObjectCommand({
117
- 117 Bucket: this.bucket,
118
- 118 Key: key
119
- 119 });
120
- 120
121
- 121 await this.s3.send(command);
122
- 122 */
123
- 123 }
124
- 124 }
@@ -1,269 +0,0 @@
1
- 1 /**
2
- 2 * @file Contains all the built-in type and validator handlers for the schema library.
3
- 3 */
4
- 4
5
- 5 import * as flatted from 'flatted'
6
- 6
7
- 7 /**
8
- 8 * @typedef {import('./Schema.js').ValidationContext} ValidationContext
9
- 9 */
10
- 10
11
- 11 /**
12
- 12 * The CorePlugin provides the default set of types and validators.
13
- 13 */
14
- 14 const CorePlugin = {
15
- 15 /**
16
- 16 * Installs the core types and validators.
17
- 17 * @param {{addType: Function, addValidator: Function}} api - An object containing registration functions.
18
- 18 */
19
- 19 install (api) {
20
- 20 const { addType, addValidator } = api // Destructure the API functions
21
- 21
22
- 22 // --- Type Handlers ---
23
- 23
24
- 24 addType('none', context => context.value)
25
- 25
26
- 26 addType('file', context => {
27
- 27 const val = context.value
28
- 28 if (val === undefined || val === null) context.throwTypeError()
29
- 29
30
- 30 // Only attempt to convert primitives. Fail on complex objects/arrays.
31
- 31 // A file is expected to be just a file handle
32
- 32 const valType = typeof val
33
- 33 if (valType === 'string' || valType === 'number' || valType === 'boolean') {
34
- 34 const s = val.toString()
35
- 35 return context.definition.noTrim ? s : s.trim()
36
- 36 }
37
- 37
38
- 38 // If it's not a primitive that can be safely converted, it's a type error.
39
- 39 context.throwTypeError()
40
- 40 })
41
- 41
42
- 42 addType('string', context => {
43
- 43 const val = context.value
44
- 44 if (val === undefined || val === null) return ''
45
- 45
46
- 46 // Only attempt to convert primitives. Fail on complex objects/arrays.
47
- 47 const valType = typeof val
48
- 48 if (valType === 'string' || valType === 'number' || valType === 'boolean') {
49
- 49 const s = val.toString()
50
- 50 return context.definition.noTrim ? s : s.trim()
51
- 51 }
52
- 52
53
- 53 // If it's not a primitive that can be safely converted, it's a type error.
54
- 54 context.throwTypeError()
55
- 55 })
56
- 56
57
- 57 addType('blob', context => context.value)
58
- 58 addType('number', context => {
59
- 59 if (context.value === undefined || context.value === null || context.value === '') return 0
60
- 60 const r = Number(context.value)
61
- 61 if (isNaN(r)) context.throwTypeError()
62
- 62 return r
63
- 63 })
64
- 64 addType('timestamp', context => {
65
- 65 const r = Number(context.value)
66
- 66 if (isNaN(r)) context.throwTypeError()
67
- 67 if (!r && context.computedOptions.nullable) return null
68
- 68 return r
69
- 69 })
70
- 70 addType('dateTime', context => {
71
- 71 if (!context.value || context.value === '') return null
72
- 72
73
- 73 // If already a Date object, return it
74
- 74 if (context.value instanceof Date) {
75
- 75 return isNaN(context.value.getTime()) ? null : context.value
76
- 76 }
77
- 77
78
- 78 // Handle string values
79
- 79 if (typeof context.value === 'string') {
80
- 80 // Detect MySQL datetime format: 'YYYY-MM-DD HH:MM:SS'
81
- 81 const isMySQLFormat = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(context.value) &&
82
- 82 !context.value.includes('T') &&
83
- 83 !context.value.includes('Z')
84
- 84
85
- 85 if (isMySQLFormat) {
86
- 86 // Convert to ISO format and force UTC interpretation
87
- 87 const d = new Date(context.value.replace(' ', 'T') + 'Z')
88
- 88 if (isNaN(d.getTime())) {
89
- 89 context.throwTypeError()
90
- 90 }
91
- 91 return d
92
- 92 }
93
- 93 }
94
- 94
95
- 95 // Try to parse the value normally
96
- 96 const d = new Date(context.value)
97
- 97 if (isNaN(d.getTime())) {
98
- 98 return null
99
- 99 }
100
- 100
101
- 101 // Return the Date object directly - let Knex handle the database formatting
102
- 102 return d
103
- 103 })
104
- 104 addType('date', context => {
105
- 105 if (!context.value || context.value === '') return null
106
- 106
107
- 107 // Parse the input value to a Date object
108
- 108 let d
109
- 109 if (context.value instanceof Date) {
110
- 110 d = context.value
111
- 111 } else {
112
- 112 let dateStr = String(context.value)
113
- 113
114
- 114 // If it's just a date (YYYY-MM-DD), add time at UTC midnight
115
- 115 if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
116
- 116 dateStr += 'T00:00:00Z'
117
- 117 }
118
- 118
119
- 119 d = new Date(dateStr)
120
- 120 }
121
- 121
122
- 122 if (isNaN(d.getTime())) {
123
- 123 context.throwTypeError()
124
- 124 }
125
- 125
126
- 126 // Normalize to midnight UTC
127
- 127 const normalized = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()))
128
- 128 return normalized
129
- 129 })
130
- 130 addType('time', context => {
131
- 131 if (!context.value || context.value === '') return null
132
- 132
133
- 133 // Try to parse as time string (HH:MM:SS or HH:MM)
134
- 134 if (typeof context.value === 'string') {
135
- 135 // Match HH:MM:SS or HH:MM format
136
- 136 const timeMatch = context.value.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?$/)
137
- 137 if (timeMatch) {
138
- 138 const hours = parseInt(timeMatch[1], 10)
139
- 139 const minutes = parseInt(timeMatch[2], 10)
140
- 140 const seconds = timeMatch[3] ? parseInt(timeMatch[3], 10) : 0
141
- 141
142
- 142 // Validate ranges
143
- 143 if (hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59 && seconds >= 0 && seconds <= 59) {
144
- 144 // Return normalized HH:MM:SS format as string
145
- 145 // Note: We return string because most databases don't have a true time-only type
146
- 146 return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
147
- 147 }
148
- 148 }
149
- 149 }
150
- 150
151
- 151 // Try to extract time from a Date object or datetime string
152
- 152 try {
153
- 153 const d = new Date(context.value)
154
- 154 if (!isNaN(d.getTime())) {
155
- 155 // Extract time portion in HH:MM:SS format
156
- 156 return d.toISOString().slice(11, 19)
157
- 157 }
158
- 158 } catch (e) {
159
- 159 // Invalid date
160
- 160 }
161
- 161
162
- 162 context.throwTypeError()
163
- 163 })
164
- 164 addType('array', context => {
165
- 165 if (context.definition.type === 'array' && !Array.isArray(context.value)) {
166
- 166 return [context.value]
167
- 167 }
168
- 168 return context.value
169
- 169 })
170
- 170 addType('object', context => context.value)
171
- 171 addType('serialize', context => {
172
- 172 try {
173
- 173 // First try regular JSON.stringify for non-circular objects
174
- 174 return JSON.stringify(context.value)
175
- 175 } catch (e) {
176
- 176 // If that fails (likely circular reference), use flatted
177
- 177 try {
178
- 178 return flatted.stringify(context.value)
179
- 179 } catch (e2) {
180
- 180 context.throwTypeError()
181
- 181 }
182
- 182 }
183
- 183 })
184
- 184 addType('boolean', context => {
185
- 185 if (typeof context.value === 'string') {
186
- 186 const falseVal = context.definition.stringFalseWhen || 'false'
187
- 187 const trueVal = context.definition.stringTrueWhen || 'true'
188
- 188 const lowerValue = context.value.toLowerCase()
189
- 189 if (lowerValue === falseVal) return false
190
- 190 if ([trueVal, 'on'].includes(lowerValue)) return true
191
- 191 return false
192
- 192 }
193
- 193 return !!context.value
194
- 194 })
195
- 195 addType('id', context => {
196
- 196 const n = parseInt(context.value, 10)
197
- 197 if (isNaN(n)) context.throwTypeError()
198
- 198 return n
199
- 199 })
200
- 200
201
- 201 // --- Validator Handlers ---
202
- 202 addValidator('minLength', context => {
203
- 203 if (context.value === undefined) return
204
- 204 if (context.definition.type === 'string' && context.value.toString && context.value.toString().length < context.parameterValue) {
205
- 205 context.throwParamError('MIN_LENGTH', `Length must be at least ${context.parameterValue} characters.`, { min: context.parameterValue, actual: context.value.toString().length })
206
- 206 }
207
- 207 })
208
- 208
209
- 209 addValidator('min', context => {
210
- 210 if (context.value === undefined) return
211
- 211 if (context.definition.type === 'number' && typeof context.value === 'number' && context.value < context.parameterValue) {
212
- 212 context.throwParamError('MIN_VALUE', `Value must be at least ${context.parameterValue}.`, { min: context.parameterValue, actual: context.value })
213
- 213 }
214
- 214 })
215
- 215 addValidator('maxLength', context => {
216
- 216 if (context.value === undefined) return
217
- 217 if (context.definition.type === 'string' && context.value.toString && context.value.toString().length > context.parameterValue) {
218
- 218 context.throwParamError('MAX_LENGTH', `Length must be no more than ${context.parameterValue} characters.`, { max: context.parameterValue, actual: context.value.toString().length })
219
- 219 }
220
- 220 })
221
- 221
222
- 222 addValidator('max', context => {
223
- 223 if (context.value === undefined) return
224
- 224 if (context.definition.type === 'number' && typeof context.value === 'number' && context.value > context.parameterValue) {
225
- 225 context.throwParamError('MAX_VALUE', `Value must be no more than ${context.parameterValue}.`, { max: context.parameterValue, actual: context.value })
226
- 226 }
227
- 227 })
228
- 228 addValidator('validator', async context => {
229
- 229 if (typeof context.parameterValue !== 'function') {
230
- 230 throw new Error(`Validator for ${context.fieldName} must be a function.`)
231
- 231 }
232
- 232 const r = await context.parameterValue(context.value, context.object, context)
233
- 233 if (typeof r === 'string') {
234
- 234 context.throwParamError('CUSTOM_VALIDATOR_FAILED', r)
235
- 235 }
236
- 236 })
237
- 237 addValidator('uppercase', context => {
238
- 238 if (typeof context.value === 'string') return context.value.toUpperCase()
239
- 239 })
240
- 240 addValidator('lowercase', context => {
241
- 241 if (typeof context.value === 'string') return context.value.toLowerCase()
242
- 242 })
243
- 243 addValidator('length', context => {
244
- 244 if (typeof context.value === 'string') {
245
- 245 return context.value.substr(0, context.parameterValue)
246
- 246 } else if (Number.isInteger(Number(context.valueBeforeCast)) && String(context.valueBeforeCast).length > context.parameterValue) {
247
- 247 context.throwParamError('RANGE_EXCEEDED', 'Numeric value is out of the allowed character range.', { max: context.parameterValue, actual: String(context.valueBeforeCast).length })
248
- 248 }
249
- 249 })
250
- 250 addValidator('notEmpty', context => {
251
- 251 const bc = context.valueBeforeCast
252
- 252 const bcs = (bc !== undefined && bc !== null && bc.toString) ? bc.toString() : ''
253
- 253 if (context.parameterValue && !Array.isArray(context.value) && bc !== undefined && bcs === '') {
254
- 254 context.throwParamError('NOT_EMPTY', 'Field cannot be empty.')
255
- 255 }
256
- 256 })
257
- 257 addValidator('required', () => {}) // Stays as a no-op, logic is handled in _validateField
258
- 258
259
- 259 // Add new validators for database alignment
260
- 260 addValidator('unsigned', () => {}) // No-op, used for schema metadata
261
- 261 addValidator('precision', () => {}) // No-op, used for schema metadata
262
- 262 addValidator('scale', () => {}) // No-op, used for schema metadata
263
- 263 addValidator('nullable', () => {}) // No-op, handled in Schema.js
264
- 264 addValidator('nullOnEmpty', () => {}) // No-op, handled in Schema.js
265
- 265 addValidator('defaultTo', () => {}) // No-op, handled in Schema.js
266
- 266 }
267
- 267 }
268
- 268
269
- 269 export default CorePlugin