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,265 +0,0 @@
1
- 1 /**
2
- 2 * Parses database version string to comparable format
3
- 3 *
4
- 4 * @param {string} versionString - Raw version string from database
5
- 5 * @returns {Object|null} Parsed version with major, minor, patch
6
- 6 *
7
- 7 * @example
8
- 8 * // Input: MySQL version
9
- 9 * parseVersion("8.0.33");
10
- 10 * // Output: { major: 8, minor: 0, patch: 33 }
11
- 11 *
12
- 12 * @example
13
- 13 * // Input: PostgreSQL version with extra info
14
- 14 * parseVersion("14.5 (Ubuntu 14.5-1.pgdg20.04+1)");
15
- 15 * // Output: { major: 14, minor: 5, patch: 0 }
16
- 16 *
17
- 17 * @example
18
- 18 * // Input: MariaDB version
19
- 19 * parseVersion("10.6.12-MariaDB");
20
- 20 * // Output: { major: 10, minor: 6, patch: 12 }
21
- 21 */
22
- 22 const parseVersion = (versionString) => {
23
- 23 // MySQL: "8.0.33"
24
- 24 // PostgreSQL: "14.5 (Ubuntu 14.5-1.pgdg20.04+1)"
25
- 25 // MariaDB: "10.6.12-MariaDB"
26
- 26 // SQLite: "3.35.5"
27
- 27
28
- 28 const match = versionString.match(/(\d+)\.(\d+)(?:\.(\d+))?/)
29
- 29 if (!match) return null
30
- 30
31
- 31 return {
32
- 32 major: parseInt(match[1]),
33
- 33 minor: parseInt(match[2]),
34
- 34 patch: match[3] ? parseInt(match[3]) : 0
35
- 35 }
36
- 36 }
37
- 37
38
- 38 /**
39
- 39 * Checks if the database supports window functions based on version
40
- 40 *
41
- 41 * @param {Object} knex - Knex instance
42
- 42 * @returns {Promise<boolean>} True if window functions are supported
43
- 43 *
44
- 44 * @example
45
- 45 * // Input: MySQL 8.0.33
46
- 46 * const supports = await supportsWindowFunctions(knex);
47
- 47 * // Output: true (MySQL 8.0+ supports window functions)
48
- 48 *
49
- 49 * @example
50
- 50 * // Input: MySQL 5.7.42
51
- 51 * const supports = await supportsWindowFunctions(knex);
52
- 52 * // Output: false (MySQL < 8.0 doesn't support window functions)
53
- 53 *
54
- 54 * @example
55
- 55 * // Input: PostgreSQL (any modern version)
56
- 56 * const supports = await supportsWindowFunctions(knex);
57
- 57 * // Output: true (PostgreSQL 8.4+ supports window functions)
58
- 58 *
59
- 59 * @example
60
- 60 * // Input: SQLite 3.24.0
61
- 61 * const supports = await supportsWindowFunctions(knex);
62
- 62 * // Output: false (SQLite needs 3.25.0+)
63
- 63 *
64
- 64 * @description
65
- 65 * Used by:
66
- 66 * - buildWindowedIncludeQuery to validate before using window functions
67
- 67 * - rest-api-knex-plugin to determine query strategies
68
- 68 *
69
- 69 * Purpose:
70
- 70 * - Prevents runtime errors by checking feature support
71
- 71 * - Enables fallback strategies for older databases
72
- 72 * - Provides clear error messages about version requirements
73
- 73 *
74
- 74 * Data flow:
75
- 75 * 1. Identifies database client type from Knex config
76
- 76 * 2. For version-dependent databases, queries version string
77
- 77 * 3. Parses version and compares against known thresholds
78
- 78 * 4. Returns boolean indicating support
79
- 79 * 5. Falls back to false if version can't be determined
80
- 80 *
81
- 81 * Version requirements:
82
- 82 * - PostgreSQL: 8.4+ (all modern versions)
83
- 83 * - MySQL: 8.0+
84
- 84 * - MariaDB: 10.2+
85
- 85 * - SQLite: 3.25.0+
86
- 86 * - SQL Server: 2005+ (all versions)
87
- 87 * - Oracle: Supported (long-standing feature)
88
- 88 */
89
- 89 export const supportsWindowFunctions = async (knex) => {
90
- 90 try {
91
- 91 const client = knex.client.config.client
92
- 92
93
- 93 let versionString
94
- 94
95
- 95 switch (client) {
96
- 96 case 'pg':
97
- 97 case 'postgresql':
98
- 98 // PostgreSQL 8.4+ supports window functions (all modern versions)
99
- 99 return true
100
- 100
101
- 101 case 'mysql':
102
- 102 case 'mysql2':
103
- 103 try {
104
- 104 const row = await knex.first(knex.raw('VERSION() as version'))
105
- 105 const mysqlVersion = parseVersion(row.version)
106
- 106
107
- 107 return mysqlVersion && mysqlVersion.major >= 8
108
- 108 } catch (dbError) {
109
- 109 console.warn(`[supportsWindowFunctions] Failed to get MySQL version: ${dbError.message}`)
110
- 110 return false
111
- 111 }
112
- 112
113
- 113 case 'sqlite3':
114
- 114 case 'better-sqlite3':
115
- 115 try {
116
- 116 versionString = await knex.raw('SELECT sqlite_version() as version')
117
- 117 const sqliteVersion = versionString[0].version
118
- 118 // SQLite 3.25.0+ supports window functions
119
- 119 const parsed = parseVersion(sqliteVersion)
120
- 120 return parsed && (parsed.major > 3 || (parsed.major === 3 && parsed.minor >= 25))
121
- 121 } catch (dbError) {
122
- 122 console.warn(`[supportsWindowFunctions] Failed to get SQLite version: ${dbError.message}`)
123
- 123 return false
124
- 124 }
125
- 125
126
- 126 case 'mssql':
127
- 127 // SQL Server 2005+ supports window functions
128
- 128 return true
129
- 129
130
- 130 case 'oracledb':
131
- 131 // Oracle has supported window functions for a long time
132
- 132 return true
133
- 133
134
- 134 default:
135
- 135 // For MariaDB, we need to check if it's actually MariaDB or MySQL
136
- 136 if (client.includes('maria')) {
137
- 137 try {
138
- 138 versionString = await knex.raw('SELECT VERSION() as version')
139
- 139 const version = versionString[0].version.toLowerCase()
140
- 140 if (version.includes('mariadb')) {
141
- 141 const mariaVersion = parseVersion(version)
142
- 142 // MariaDB 10.2+ supports window functions
143
- 143 return mariaVersion && (mariaVersion.major > 10 ||
144
- 144 (mariaVersion.major === 10 && mariaVersion.minor >= 2))
145
- 145 }
146
- 146 } catch (dbError) {
147
- 147 console.warn(`[supportsWindowFunctions] Failed to get MariaDB version: ${dbError.message}`)
148
- 148 return false
149
- 149 }
150
- 150 }
151
- 151 return false
152
- 152 }
153
- 153 } catch (error) {
154
- 154 // Log error with context
155
- 155 console.error('[supportsWindowFunctions] Unexpected error checking database capabilities:', {
156
- 156 error: error.message,
157
- 157 stack: error.stack,
158
- 158 client: knex?.client?.config?.client || 'unknown'
159
- 159 })
160
- 160 // If we can't determine, assume no support for safety
161
- 161 return false
162
- 162 }
163
- 163 }
164
- 164
165
- 165 /**
166
- 166 * Gets database info for error messages and capability checks
167
- 167 *
168
- 168 * @param {Object} knex - Knex instance
169
- 169 * @returns {Promise<Object>} Database info with client and version
170
- 170 *
171
- 171 * @example
172
- 172 * // Input: MySQL connection
173
- 173 * const info = await getDatabaseInfo(knex);
174
- 174 * // Output: { client: 'MySQL', version: '8.0.33' }
175
- 175 *
176
- 176 * @example
177
- 177 * // Input: PostgreSQL connection
178
- 178 * const info = await getDatabaseInfo(knex);
179
- 179 * // Output: {
180
- 180 * // client: 'PostgreSQL',
181
- 181 * // version: 'PostgreSQL 14.5 (Ubuntu 14.5-1.pgdg20.04+1)'
182
- 182 * // }
183
- 183 *
184
- 184 * @example
185
- 185 * // Input: Connection error case
186
- 186 * const info = await getDatabaseInfo(knexWithError);
187
- 187 * // Output: {
188
- 188 * // client: 'MySQL',
189
- 189 * // version: 'unknown',
190
- 190 * // error: 'Connection refused'
191
- 191 * // }
192
- 192 *
193
- 193 * @description
194
- 194 * Used by:
195
- 195 * - Error messages to show which database/version lacks a feature
196
- 196 * - Capability detection for feature flags
197
- 197 * - Debugging connection issues
198
- 198 *
199
- 199 * Purpose:
200
- 200 * - Provides human-readable database identification
201
- 201 * - Helps users understand version requirements
202
- 202 * - Enables detailed error messages
203
- 203 * - Gracefully handles query failures
204
- 204 *
205
- 205 * Data flow:
206
- 206 * 1. Identifies client type from Knex config
207
- 207 * 2. Runs version query specific to each database
208
- 208 * 3. Returns formatted info object
209
- 209 * 4. Includes error details if version query fails
210
- 210 * 5. Falls back to 'unknown' for unsupported databases
211
- 211 */
212
- 212 export const getDatabaseInfo = async (knex) => {
213
- 213 try {
214
- 214 const client = knex.client.config.client
215
- 215
216
- 216 let versionString
217
- 217 switch (client) {
218
- 218 case 'mysql':
219
- 219 case 'mysql2':
220
- 220 try {
221
- 221 const row = await knex.first(knex.raw('VERSION() as version'))
222
- 222 return { client: 'MySQL', version: row.version }
223
- 223 } catch (dbError) {
224
- 224 console.warn(`[getDatabaseInfo] Failed to get MySQL version: ${dbError.message}`)
225
- 225 return { client: 'MySQL', version: 'unknown', error: dbError.message }
226
- 226 }
227
- 227
228
- 228 case 'sqlite3':
229
- 229 case 'better-sqlite3':
230
- 230 try {
231
- 231 versionString = await knex.raw('SELECT sqlite_version() as version')
232
- 232 return { client: 'SQLite', version: versionString[0].version }
233
- 233 } catch (dbError) {
234
- 234 console.warn(`[getDatabaseInfo] Failed to get SQLite version: ${dbError.message}`)
235
- 235 return { client: 'SQLite', version: 'unknown', error: dbError.message }
236
- 236 }
237
- 237
238
- 238 case 'pg':
239
- 239 case 'postgresql':
240
- 240 try {
241
- 241 versionString = await knex.raw('SELECT version() as version')
242
- 242 return { client: 'PostgreSQL', version: versionString.rows[0].version }
243
- 243 } catch (dbError) {
244
- 244 console.warn(`[getDatabaseInfo] Failed to get PostgreSQL version: ${dbError.message}`)
245
- 245 return { client: 'PostgreSQL', version: 'unknown', error: dbError.message }
246
- 246 }
247
- 247
248
- 248 default:
249
- 249 return { client, version: 'unknown' }
250
- 250 }
251
- 251 } catch (error) {
252
- 252 // Log unexpected errors
253
- 253 console.error('[getDatabaseInfo] Unexpected error getting database info:', {
254
- 254 error: error.message,
255
- 255 stack: error.stack,
256
- 256 client: knex?.client?.config?.client || 'unknown'
257
- 257 })
258
- 258
259
- 259 return {
260
- 260 client: knex?.client?.config?.client || 'unknown',
261
- 261 version: 'unknown',
262
- 262 error: error.message
263
- 263 }
264
- 264 }
265
- 265 }
@@ -1,369 +0,0 @@
1
- 1 /**
2
- 2 * Normalizes a date value to appropriate format based on field type
3
- 3 *
4
- 4 * @param {*} value - The date value from the database
5
- 5 * @param {string} type - The field type ('date', 'dateTime', or 'time')
6
- 6 * @returns {Date|string|null} Date object for date/dateTime, string for time, or null
7
- 7 *
8
- 8 * @example
9
- 9 * // Input: Already a Date object
10
- 10 * normalizeDateValue(new Date('2024-01-15T10:30:00Z'), 'dateTime');
11
- 11 * // Output: Date object (unchanged)
12
- 12 *
13
- 13 * @example
14
- 14 * // Input: MySQL datetime string without timezone
15
- 15 * normalizeDateValue('2024-01-15 10:30:00', 'dateTime');
16
- 16 * // Output: Date object parsed as UTC (2024-01-15T10:30:00Z)
17
- 17 *
18
- 18 * @example
19
- 19 * // Input: Date-only string
20
- 20 * normalizeDateValue('2024-01-15', 'date');
21
- 21 * // Output: Date object at UTC midnight (2024-01-15T00:00:00Z)
22
- 22 *
23
- 23 * @example
24
- 24 * // Input: Time field (always returns string)
25
- 25 * normalizeDateValue('14:30:45', 'time');
26
- 26 * // Output: "14:30:45" (string)
27
- 27 *
28
- 28 * @example
29
- 29 * // Input: Date object for time field
30
- 30 * normalizeDateValue(new Date('2024-01-15T14:30:45Z'), 'time');
31
- 31 * // Output: "14:30:45" (extracts time portion)
32
- 32 *
33
- 33 * @description
34
- 34 * Used by:
35
- 35 * - normalizeAttributes to process individual field values
36
- 36 * - Applied to all date/time fields fetched from database
37
- 37 *
38
- 38 * Purpose:
39
- 39 * - Handles database-specific date formats (especially MySQL)
40
- 40 * - Prevents timezone shifts for date-only fields
41
- 41 * - Ensures consistent Date objects across databases
42
- 42 * - Keeps time fields as HH:MM:SS strings
43
- 43 *
44
- 44 * Data flow:
45
- 45 * 1. Checks for null/undefined (returns null)
46
- 46 * 2. For time fields: ensures HH:MM:SS string format
47
- 47 * 3. For date/dateTime: converts to Date objects
48
- 48 * 4. Handles MySQL datetime format (no T separator)
49
- 49 * 5. Forces UTC interpretation to prevent timezone issues
50
- 50 */
51
- 51 function normalizeDateValue (value, type) {
52
- 52 // Handle null/undefined
53
- 53 if (value === null || value === undefined) {
54
- 54 return null
55
- 55 }
56
- 56
57
- 57 // Handle time type specially - always return as string
58
- 58 if (type === 'time') {
59
- 59 // If it's already a properly formatted time string, return as-is
60
- 60 if (typeof value === 'string' && /^\d{2}:\d{2}:\d{2}/.test(value)) {
61
- 61 return value
62
- 62 }
63
- 63 // If we got a Date object for a time field, extract the time portion
64
- 64 if (value instanceof Date) {
65
- 65 return value.toISOString().slice(11, 19) // Extract HH:MM:SS
66
- 66 }
67
- 67 // Try to parse and extract time
68
- 68 if (typeof value === 'string' || typeof value === 'number') {
69
- 69 const d = new Date(value)
70
- 70 if (!isNaN(d.getTime())) {
71
- 71 return d.toISOString().slice(11, 19)
72
- 72 }
73
- 73 }
74
- 74 return null
75
- 75 }
76
- 76
77
- 77 // For date and dateTime: Already a Date object? Return as-is
78
- 78 if (value instanceof Date) {
79
- 79 return value
80
- 80 }
81
- 81
82
- 82 // Handle string values
83
- 83 if (typeof value === 'string') {
84
- 84 // Detect MySQL datetime format: 'YYYY-MM-DD HH:MM:SS'
85
- 85 // These have no T separator and no timezone indicator
86
- 86 const isMySQLDateTime = type === 'dateTime' &&
87
- 87 /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(value) &&
88
- 88 !value.includes('T') &&
89
- 89 !value.includes('Z') &&
90
- 90 !value.includes('+') &&
91
- 91 !value.includes('-', 10) // Don't match date separators
92
- 92
93
- 93 if (isMySQLDateTime) {
94
- 94 // Convert to ISO format and force UTC interpretation
95
- 95 // '2024-01-15 10:30:00' becomes '2024-01-15T10:30:00Z'
96
- 96 return new Date(value.replace(' ', 'T') + 'Z')
97
- 97 }
98
- 98
99
- 99 // For date-only fields, ensure parsing at UTC midnight
100
- 100 // This prevents timezone shifts when parsing dates
101
- 101 if (type === 'date' && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
102
- 102 return new Date(value + 'T00:00:00Z')
103
- 103 }
104
- 104 }
105
- 105
106
- 106 // Handle numeric values (Unix timestamps)
107
- 107 if (typeof value === 'number') {
108
- 108 return new Date(value)
109
- 109 }
110
- 110
111
- 111 // Try to parse any other format
112
- 112 const dateObj = new Date(value)
113
- 113
114
- 114 // Check if date is valid
115
- 115 if (isNaN(dateObj.getTime())) {
116
- 116 console.warn(`Invalid date value for field type '${type}': ${value}`)
117
- 117 return null // Return null for invalid dates
118
- 118 }
119
- 119
120
- 120 return dateObj
121
- 121 }
122
- 122
123
- 123 /**
124
- 124 * Normalizes database values in an attributes object
125
- 125 *
126
- 126 * @param {Object} attributes - The attributes object to normalize
127
- 127 * @param {Object} schemaStructure - The schema structure defining field types
128
- 128 * @returns {Object} The normalized attributes object
129
- 129 *
130
- 130 * @example
131
- 131 * // Input: Raw database values
132
- 132 * const attributes = {
133
- 133 * id: 1,
134
- 134 * is_active: 1, // Boolean as 0/1
135
- 135 * created_at: '2024-01-15 10:30:00', // MySQL datetime
136
- 136 * birth_date: '1990-05-20', // Date string
137
- 137 * shift_time: '09:00:00' // Time string
138
- 138 * };
139
- 139 *
140
- 140 * const schema = {
141
- 141 * is_active: { type: 'boolean' },
142
- 142 * created_at: { type: 'dateTime' },
143
- 143 * birth_date: { type: 'date' },
144
- 144 * shift_time: { type: 'time' }
145
- 145 * };
146
- 146 *
147
- 147 * const normalized = normalizeAttributes(attributes, schema);
148
- 148 *
149
- 149 * // Output: Properly typed values
150
- 150 * // {
151
- 151 * // id: 1,
152
- 152 * // is_active: true, // 1 → true
153
- 153 * // created_at: Date('2024-01-15T10:30:00Z'), // Date object
154
- 154 * // birth_date: Date('1990-05-20T00:00:00Z'), // Date at midnight UTC
155
- 155 * // shift_time: '09:00:00' // String unchanged
156
- 156 * // }
157
- 157 *
158
- 158 * @example
159
- 159 * // Input: Boolean edge cases
160
- 160 * const attributes = {
161
- 161 * flag1: 0, // Number 0
162
- 162 * flag2: '1', // String '1'
163
- 163 * flag3: true, // Already boolean
164
- 164 * flag4: null // Null value
165
- 165 * };
166
- 166 *
167
- 167 * const normalized = normalizeAttributes(attributes, {
168
- 168 * flag1: { type: 'boolean' },
169
- 169 * flag2: { type: 'boolean' },
170
- 170 * flag3: { type: 'boolean' },
171
- 171 * flag4: { type: 'boolean' }
172
- 172 * });
173
- 173 *
174
- 174 * // Output:
175
- 175 * // {
176
- 176 * // flag1: false, // 0 → false
177
- 177 * // flag2: true, // '1' → true
178
- 178 * // flag3: true, // unchanged
179
- 179 * // flag4: null // null preserved
180
- 180 * // }
181
- 181 *
182
- 182 * @description
183
- 183 * Used by:
184
- 184 * - normalizeRecordAttributes for each record's attributes
185
- 185 * - Applied after fetching data from database
186
- 186 *
187
- 187 * Purpose:
188
- 188 * - Handles databases without native boolean support (SQLite, older MySQL)
189
- 189 * - Normalizes date formats from different databases
190
- 190 * - Ensures consistent data types in API responses
191
- 191 * - Preserves null values appropriately
192
- 192 *
193
- 193 * Data flow:
194
- 194 * 1. Creates copy of attributes to avoid mutation
195
- 195 * 2. Iterates through each field with schema definition
196
- 196 * 3. Normalizes booleans: 1/0 or '1'/'0' to true/false
197
- 197 * 4. Normalizes dates using normalizeDateValue
198
- 198 * 5. Returns new object with normalized values
199
- 199 */
200
- 200 export function normalizeAttributes (attributes, schemaStructure) {
201
- 201 if (!attributes || !schemaStructure) {
202
- 202 return attributes
203
- 203 }
204
- 204
205
- 205 const normalized = { ...attributes }
206
- 206
207
- 207 // Iterate through each attribute
208
- 208 for (const [fieldName, value] of Object.entries(attributes)) {
209
- 209 const fieldDef = schemaStructure[fieldName]
210
- 210
211
- 211 // Skip if no field definition found
212
- 212 if (!fieldDef) continue
213
- 213
214
- 214 // Normalize boolean values
215
- 215 if (fieldDef.type === 'boolean') {
216
- 216 if (value === 1 || value === '1') {
217
- 217 normalized[fieldName] = true
218
- 218 } else if (value === 0 || value === '0') {
219
- 219 normalized[fieldName] = false
220
- 220 }
221
- 221 // null/undefined remain as-is
222
- 222 }
223
- 223
224
- 224 // Normalize date/dateTime/time values
225
- 225 if (fieldDef.type === 'date' || fieldDef.type === 'dateTime' || fieldDef.type === 'time') {
226
- 226 normalized[fieldName] = normalizeDateValue(value, fieldDef.type)
227
- 227 }
228
- 228 }
229
- 229
230
- 230 return normalized
231
- 231 }
232
- 232
233
- 233 /**
234
- 234 * Normalizes all records in a JSON:API response
235
- 235 *
236
- 236 * @param {Object} record - The JSON:API response object
237
- 237 * @param {Object} scopes - All available scopes for schema lookup
238
- 238 * @returns {Object} The response with normalized values
239
- 239 *
240
- 240 * @example
241
- 241 * // Input: JSON:API response with various data types
242
- 242 * const response = {
243
- 243 * data: {
244
- 244 * type: 'articles',
245
- 245 * id: '1',
246
- 246 * attributes: {
247
- 247 * title: 'My Article',
248
- 248 * is_published: 1, // Boolean as number
249
- 249 * published_at: '2024-01-15 10:00:00' // MySQL datetime
250
- 250 * }
251
- 251 * },
252
- 252 * included: [{
253
- 253 * type: 'users',
254
- 254 * id: '10',
255
- 255 * attributes: {
256
- 256 * name: 'John',
257
- 257 * is_admin: 0, // Boolean as number
258
- 258 * last_login: '2024-01-14 15:30:00'
259
- 259 * }
260
- 260 * }]
261
- 261 * };
262
- 262 *
263
- 263 * const normalized = normalizeRecordAttributes(response, scopes);
264
- 264 *
265
- 265 * // Output: All booleans and dates normalized
266
- 266 * // {
267
- 267 * // data: {
268
- 268 * // type: 'articles',
269
- 269 * // id: '1',
270
- 270 * // attributes: {
271
- 271 * // title: 'My Article',
272
- 272 * // is_published: true, // 1 → true
273
- 273 * // published_at: Date('2024-01-15T10:00:00Z') // Date object
274
- 274 * // }
275
- 275 * // },
276
- 276 * // included: [{
277
- 277 * // type: 'users',
278
- 278 * // id: '10',
279
- 279 * // attributes: {
280
- 280 * // name: 'John',
281
- 281 * // is_admin: false, // 0 → false
282
- 282 * // last_login: Date('2024-01-14T15:30:00Z') // Date object
283
- 283 * // }
284
- 284 * // }]
285
- 285 * // }
286
- 286 *
287
- 287 * @example
288
- 288 * // Input: Collection response (array of records)
289
- 289 * const response = {
290
- 290 * data: [
291
- 291 * {
292
- 292 * type: 'comments',
293
- 293 * id: '1',
294
- 294 * attributes: { approved: 1, created_at: '2024-01-01 09:00:00' }
295
- 295 * },
296
- 296 * {
297
- 297 * type: 'comments',
298
- 298 * id: '2',
299
- 299 * attributes: { approved: 0, created_at: '2024-01-02 10:00:00' }
300
- 300 * }
301
- 301 * ]
302
- 302 * };
303
- 303 *
304
- 304 * // Each record in the array is normalized
305
- 305 *
306
- 306 * @description
307
- 307 * Used by:
308
- 308 * - rest-api-knex-plugin after fetching data, before sending response
309
- 309 * - Applied to both GET (single) and QUERY (collection) responses
310
- 310 *
311
- 311 * Purpose:
312
- 312 * - Ensures consistent data types in API responses
313
- 313 * - Handles both primary data and included resources
314
- 314 * - Works with single records and collections
315
- 315 * - Uses schema definitions to determine field types
316
- 316 *
317
- 317 * Data flow:
318
- 318 * 1. Checks if data is array (collection) or object (single)
319
- 319 * 2. For each record, looks up its schema by type
320
- 320 * 3. Calls normalizeAttributes with schema structure
321
- 321 * 4. Processes included array the same way
322
- 322 * 5. Returns complete response with normalized values
323
- 323 */
324
- 324 export function normalizeRecordAttributes (record, scopes) {
325
- 325 if (!record || !scopes) {
326
- 326 return record
327
- 327 }
328
- 328
329
- 329 // Normalize main data records
330
- 330 if (record.data) {
331
- 331 if (Array.isArray(record.data)) {
332
- 332 // Handle array of records (query result)
333
- 333 for (const entry of record.data) {
334
- 334 const scope = scopes[entry.type]
335
- 335 if (scope?.vars?.schemaInfo?.schemaStructure && entry.attributes) {
336
- 336 entry.attributes = normalizeAttributes(
337
- 337 entry.attributes,
338
- 338 scope.vars.schemaInfo.schemaStructure
339
- 339 )
340
- 340 }
341
- 341 }
342
- 342 } else {
343
- 343 // Handle single record (get result)
344
- 344 const entry = record.data
345
- 345 const scope = scopes[entry.type]
346
- 346 if (scope?.vars?.schemaInfo?.schemaStructure && entry.attributes) {
347
- 347 entry.attributes = normalizeAttributes(
348
- 348 entry.attributes,
349
- 349 scope.vars.schemaInfo.schemaStructure
350
- 350 )
351
- 351 }
352
- 352 }
353
- 353 }
354
- 354
355
- 355 // Normalize included records
356
- 356 if (record.included && Array.isArray(record.included)) {
357
- 357 for (const entry of record.included) {
358
- 358 const scope = scopes[entry.type]
359
- 359 if (scope?.vars?.schemaInfo?.schemaStructure && entry.attributes) {
360
- 360 entry.attributes = normalizeAttributes(
361
- 361 entry.attributes,
362
- 362 scope.vars.schemaInfo.schemaStructure
363
- 363 )
364
- 364 }
365
- 365 }
366
- 366 }
367
- 367
368
- 368 return record
369
- 369 }