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,621 +0,0 @@
1
- 1 import { requirePackage } from 'hooked-api'
2
- 2
3
- 3 const pendingBroadcasts = new WeakMap()
4
- 4
5
- 5 function normalizeAuthContext (auth) {
6
- 6 if (!auth) return null
7
- 7 if (typeof auth !== 'object') return null
8
- 8 const normalized = { ...auth }
9
- 9 if (normalized.userId !== undefined && normalized.userId !== null) {
10
- 10 normalized.userId = String(normalized.userId)
11
- 11 }
12
- 12 if (normalized.roles && !Array.isArray(normalized.roles)) {
13
- 13 normalized.roles = [normalized.roles]
14
- 14 }
15
- 15 return normalized
16
- 16 }
17
- 17
18
- 18 function matchesFilters (record, filters, searchSchemaStructure) {
19
- 19 if (!filters || Object.keys(filters).length === 0) return true
20
- 20
21
- 21 if (!record) return false
22
- 22
23
- 23 if (!searchSchemaStructure || Object.keys(searchSchemaStructure).length === 0) {
24
- 24 return false
25
- 25 }
26
- 26
27
- 27 for (const [filterKey, filterValue] of Object.entries(filters)) {
28
- 28 const fieldDef = searchSchemaStructure[filterKey]
29
- 29 if (!fieldDef) continue
30
- 30
31
- 31 const fieldName = fieldDef.actualField || filterKey
32
- 32
33
- 33 let recordValue
34
- 34 if (fieldDef.isRelationship) {
35
- 35 const relationshipData = record.relationships?.[filterKey]?.data
36
- 36 recordValue = relationshipData?.id
37
- 37 } else {
38
- 38 recordValue = record.attributes?.[fieldName] ?? record[fieldName]
39
- 39 }
40
- 40
41
- 41 if (recordValue === null || recordValue === undefined) {
42
- 42 if (filterValue !== null && filterValue !== undefined) {
43
- 43 return false
44
- 44 }
45
- 45 continue
46
- 46 }
47
- 47
48
- 48 if (typeof fieldDef.filterOperator === 'function') {
49
- 49 if (!fieldDef.filterRecord?.(record, filterValue)) {
50
- 50 return false
51
- 51 }
52
- 52 continue
53
- 53 }
54
- 54
55
- 55 const operator = fieldDef.filterOperator || '='
56
- 56
57
- 57 switch (operator) {
58
- 58 case 'like':
59
- 59 if (!String(recordValue).toLowerCase().includes(String(filterValue).toLowerCase())) {
60
- 60 return false
61
- 61 }
62
- 62 break
63
- 63 case 'in':
64
- 64 if (Array.isArray(filterValue)) {
65
- 65 if (!filterValue.includes(recordValue)) {
66
- 66 return false
67
- 67 }
68
- 68 } else if (recordValue !== filterValue) {
69
- 69 return false
70
- 70 }
71
- 71 break
72
- 72 case 'between':
73
- 73 if (Array.isArray(filterValue) && filterValue.length === 2) {
74
- 74 if (recordValue < filterValue[0] || recordValue > filterValue[1]) {
75
- 75 return false
76
- 76 }
77
- 77 }
78
- 78 break
79
- 79 case '>':
80
- 80 if (!(recordValue > filterValue)) return false
81
- 81 break
82
- 82 case '>=':
83
- 83 if (!(recordValue >= filterValue)) return false
84
- 84 break
85
- 85 case '<':
86
- 86 if (!(recordValue < filterValue)) return false
87
- 87 break
88
- 88 case '<=':
89
- 89 if (!(recordValue <= filterValue)) return false
90
- 90 break
91
- 91 case '!=':
92
- 92 case '<>':
93
- 93 if (recordValue === filterValue) return false
94
- 94 break
95
- 95 case '=':
96
- 96 default:
97
- 97 if (fieldDef.isRelationship) {
98
- 98 if (String(recordValue) !== String(filterValue)) {
99
- 99 return false
100
- 100 }
101
- 101 } else if (recordValue !== filterValue) {
102
- 102 return false
103
- 103 }
104
- 104 }
105
- 105 }
106
- 106
107
- 107 return true
108
- 108 }
109
- 109
110
- 110 function buildConfig (pluginOptions = {}) {
111
- 111 const authOptions = pluginOptions.auth || {}
112
- 112 const subscriptionOptions = pluginOptions.subscriptions || {}
113
- 113
114
- 114 return {
115
- 115 auth: {
116
- 116 authenticate: authOptions.authenticate || null,
117
- 117 requireAuth: authOptions.requireAuth === true,
118
- 118 allowClientProvidedAuth: authOptions.allowClientProvidedAuth === true,
119
- 119 anonymousContext: authOptions.anonymousContext || null,
120
- 120 onAuthenticationFailed: authOptions.onAuthenticationFailed || null
121
- 121 },
122
- 122 subscriptions: {
123
- 123 maxPerSocket: subscriptionOptions.maxPerSocket ?? 100
124
- 124 },
125
- 125 transport: pluginOptions.transport || {}
126
- 126 }
127
- 127 }
128
- 128
129
- 129 async function authenticateSocket ({ socket, api, helpers, log, config }) {
130
- 130 const { authenticate, allowClientProvidedAuth, anonymousContext, requireAuth, onAuthenticationFailed } = config.auth
131
- 131
132
- 132 try {
133
- 133 let authContext = null
134
- 134
135
- 135 if (authenticate) {
136
- 136 authContext = await authenticate({ socket, api, helpers, log })
137
- 137 } else if (allowClientProvidedAuth && socket.handshake.auth && typeof socket.handshake.auth === 'object') {
138
- 138 authContext = socket.handshake.auth
139
- 139 } else if (anonymousContext) {
140
- 140 authContext = anonymousContext
141
- 141 }
142
- 142
143
- 143 const normalized = normalizeAuthContext(authContext)
144
- 144 if (!normalized && requireAuth) {
145
- 145 throw new Error('Authentication required')
146
- 146 }
147
- 147
148
- 148 return normalized
149
- 149 } catch (error) {
150
- 150 if (onAuthenticationFailed) {
151
- 151 try {
152
- 152 await onAuthenticationFailed({ socket, error, log })
153
- 153 } catch (hookError) {
154
- 154 log.error('socketio auth failure handler threw error', hookError)
155
- 155 }
156
- 156 }
157
- 157 throw error
158
- 158 }
159
- 159 }
160
- 160
161
- 161 async function registerSubscription ({
162
- 162 socket,
163
- 163 data,
164
- 164 scopes,
165
- 165 runHooks,
166
- 166 log,
167
- 167 config
168
- 168 }) {
169
- 169 const { resource, filters = {}, include, fields, subscriptionId } = data || {}
170
- 170
171
- 171 if (!resource || typeof resource !== 'string') {
172
- 172 throw Object.assign(new Error('Resource is required'), { code: 'RESOURCE_REQUIRED' })
173
- 173 }
174
- 174
175
- 175 const scope = scopes[resource]
176
- 176 if (!scope) {
177
- 177 throw Object.assign(new Error(`Resource '${resource}' not found`), { code: 'RESOURCE_NOT_FOUND' })
178
- 178 }
179
- 179
180
- 180 const auth = socket.data.auth || null
181
- 181
182
- 182 if (scope.checkPermissions) {
183
- 183 await scope.checkPermissions({
184
- 184 method: 'query',
185
- 185 originalContext: { auth }
186
- 186 })
187
- 187 }
188
- 188
189
- 189 const schemaInfo = scope.vars?.schemaInfo
190
- 190 const searchSchemaStructure = schemaInfo?.searchSchemaStructure
191
- 191 const searchSchemaInstance = schemaInfo?.searchSchemaInstance
192
- 192
193
- 193 const validatedFilters = { ...filters }
194
- 194 if (Object.keys(validatedFilters).length > 0) {
195
- 195 if (!searchSchemaStructure || Object.keys(searchSchemaStructure).length === 0 || !searchSchemaInstance) {
196
- 196 throw Object.assign(new Error(`Filtering is not enabled for resource '${resource}'`), { code: 'FILTERING_NOT_ENABLED' })
197
- 197 }
198
- 198
199
- 199 for (const filterKey of Object.keys(validatedFilters)) {
200
- 200 const fieldDef = searchSchemaStructure[filterKey]
201
- 201 if (fieldDef && typeof fieldDef.filterOperator === 'function' && !fieldDef.filterRecord) {
202
- 202 throw Object.assign(
203
- 203 new Error(`Filter '${filterKey}' uses custom SQL logic and requires 'filterRecord' for real-time subscriptions`),
204
- 204 { code: 'UNSUPPORTED_FILTER' }
205
- 205 )
206
- 206 }
207
- 207 }
208
- 208
209
- 209 const { validatedObject, errors } = await searchSchemaInstance.validate(validatedFilters, {
210
- 210 onlyObjectValues: true
211
- 211 })
212
- 212
213
- 213 if (errors && Object.keys(errors).length > 0) {
214
- 214 const error = new Error('Invalid filter values')
215
- 215 error.code = 'INVALID_FILTERS'
216
- 216 error.details = errors
217
- 217 throw error
218
- 218 }
219
- 219
220
- 220 Object.assign(validatedFilters, validatedObject)
221
- 221 }
222
- 222
223
- 223 if (include !== undefined) {
224
- 224 if (!Array.isArray(include)) {
225
- 225 throw Object.assign(new Error('Include parameter must be an array'), { code: 'INVALID_INCLUDE' })
226
- 226 }
227
- 227
228
- 228 const relationships = schemaInfo?.schemaRelationships || {}
229
- 229 for (const includePath of include) {
230
- 230 const baseName = includePath.split('.')[0]
231
- 231 if (!relationships[baseName]) {
232
- 232 throw Object.assign(new Error(`Invalid relationship '${baseName}' for resource '${resource}'`), {
233
- 233 code: 'INVALID_INCLUDE'
234
- 234 })
235
- 235 }
236
- 236 }
237
- 237 }
238
- 238
239
- 239 if (fields !== undefined) {
240
- 240 if (typeof fields !== 'object' || Array.isArray(fields) || fields === null) {
241
- 241 throw Object.assign(new Error('Fields parameter must be an object'), { code: 'INVALID_FIELDS' })
242
- 242 }
243
- 243
244
- 244 for (const [resourceType, fieldList] of Object.entries(fields)) {
245
- 245 if (!Array.isArray(fieldList)) {
246
- 246 throw Object.assign(new Error(`Fields for '${resourceType}' must be an array`), { code: 'INVALID_FIELDS' })
247
- 247 }
248
- 248 }
249
- 249 }
250
- 250
251
- 251 if (!socket.data.subscriptions) {
252
- 252 socket.data.subscriptions = new Map()
253
- 253 }
254
- 254
255
- 255 if (socket.data.subscriptions.size >= config.subscriptions.maxPerSocket) {
256
- 256 throw Object.assign(new Error('Subscription limit reached for this connection'), { code: 'SUBSCRIPTION_LIMIT' })
257
- 257 }
258
- 258
259
- 259 const subscription = {
260
- 260 id: subscriptionId || `${resource}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`,
261
- 261 resource,
262
- 262 filters: validatedFilters,
263
- 263 include: include || [],
264
- 264 fields: fields || {},
265
- 265 auth,
266
- 266 createdAt: new Date()
267
- 267 }
268
- 268
269
- 269 await runHooks('subscriptionFilters', { subscription, auth })
270
- 270
271
- 271 const roomName = `${resource}:updates`
272
- 272 socket.join(roomName)
273
- 273 socket.data.subscriptions.set(subscription.id, subscription)
274
- 274
275
- 275 log.info(`Socket ${socket.id} subscribed to ${resource}`, {
276
- 276 subscriptionId: subscription.id,
277
- 277 filters: subscription.filters
278
- 278 })
279
- 279
280
- 280 return {
281
- 281 subscriptionId: subscription.id,
282
- 282 resource: subscription.resource,
283
- 283 filters: subscription.filters,
284
- 284 include: subscription.include,
285
- 285 fields: subscription.fields,
286
- 286 status: 'active'
287
- 287 }
288
- 288 }
289
- 289
290
- 290 async function handleRestoreSubscriptions ({ socket, subscriptions, scopes, runHooks, log, config }) {
291
- 291 const restored = []
292
- 292 const failed = []
293
- 293
294
- 294 for (const sub of subscriptions) {
295
- 295 try {
296
- 296 const response = await registerSubscription({
297
- 297 socket,
298
- 298 data: sub,
299
- 299 scopes,
300
- 300 runHooks,
301
- 301 log,
302
- 302 config
303
- 303 })
304
- 304 restored.push(response.subscriptionId)
305
- 305 } catch (error) {
306
- 306 failed.push({
307
- 307 subscriptionId: sub?.subscriptionId || null,
308
- 308 error: {
309
- 309 code: error.code || 'SUBSCRIBE_ERROR',
310
- 310 message: error.message
311
- 311 }
312
- 312 })
313
- 313 }
314
- 314 }
315
- 315
316
- 316 return { restored, failed }
317
- 317 }
318
- 318
319
- 319 function getRecordForFiltering (context) {
320
- 320 return context?.minimalRecord || context?.originalMinimalRecord || null
321
- 321 }
322
- 322
323
- 323 async function performBroadcast ({ method, scopeName, id, context, api, io, log }) {
324
- 324 if (!io) return
325
- 325
326
- 326 const scope = api.resources[scopeName]
327
- 327 if (!scope) {
328
- 328 log.warn(`Socket.IO broadcast skipped: unknown resource ${scopeName}`)
329
- 329 return
330
- 330 }
331
- 331
332
- 332 const roomName = `${scopeName}:updates`
333
- 333 const socketsInRoom = await io.in(roomName).fetchSockets()
334
- 334 if (!socketsInRoom || socketsInRoom.length === 0) {
335
- 335 log.debug(`Socket.IO broadcast skipped: no subscribers for ${roomName}`)
336
- 336 return
337
- 337 }
338
- 338
339
- 339 const schemaInfo = scope.vars?.schemaInfo
340
- 340 const searchSchemaStructure = schemaInfo?.searchSchemaStructure
341
- 341 const recordForFiltering = getRecordForFiltering(context)
342
- 342
343
- 343 for (const socket of socketsInRoom) {
344
- 344 const subscriptions = socket.data.subscriptions
345
- 345 if (!subscriptions || subscriptions.size === 0) continue
346
- 346
347
- 347 for (const subscription of subscriptions.values()) {
348
- 348 if (subscription.resource !== scopeName) continue
349
- 349
350
- 350 if (recordForFiltering && subscription.filters && Object.keys(subscription.filters).length > 0) {
351
- 351 if (!matchesFilters(recordForFiltering, subscription.filters, searchSchemaStructure)) {
352
- 352 continue
353
- 353 }
354
- 354 }
355
- 355
356
- 356 const notification = {
357
- 357 type: `resource.${method}d`,
358
- 358 resource: scopeName,
359
- 359 id,
360
- 360 action: method,
361
- 361 subscriptionId: subscription.id,
362
- 362 meta: {
363
- 363 timestamp: new Date().toISOString()
364
- 364 }
365
- 365 }
366
- 366
367
- 367 if (method === 'delete') {
368
- 368 notification.deletedRecord = { id }
369
- 369 }
370
- 370
371
- 371 socket.emit('subscription.update', notification)
372
- 372 log.debug(`Socket.IO broadcast: ${scopeName}/${id} -> socket ${socket.id}`)
373
- 373 break
374
- 374 }
375
- 375 }
376
- 376 }
377
- 377
378
- 378 export const SocketIOPlugin = {
379
- 379 name: 'socketio',
380
- 380 dependencies: ['rest-api'],
381
- 381
382
- 382 async install ({ api, addHook, log, scopes, helpers, vars, runHooks, pluginOptions = {} }) {
383
- 383 const config = buildConfig(pluginOptions)
384
- 384
385
- 385 let Server
386
- 386 try {
387
- 387 ({ Server } = await import('socket.io'))
388
- 388 } catch (error) {
389
- 389 requirePackage('socket.io', 'socketio', 'Socket.IO is required for WebSocket support. This is a peer dependency.')
390
- 390 throw error
391
- 391 }
392
- 392
393
- 393 let createAdapter
394
- 394 let createClient
395
- 395 let io
396
- 396
397
- 397 api.startSocketServer = async (server, startOptions = {}) => {
398
- 398 const transportConfig = { ...config.transport, ...startOptions.transport }
399
- 399 const authConfig = { ...config.auth, ...(startOptions.auth || {}) }
400
- 400 const redisConfig = startOptions.redis ?? pluginOptions.redis ?? null
401
- 401
402
- 402 config.auth = authConfig
403
- 403
404
- 404 const defaultPath = vars.transport?.mountPath ? `${vars.transport.mountPath}/socket.io` : '/socket.io'
405
- 405 const path = startOptions.path || config.transport.path || defaultPath
406
- 406 const cors = startOptions.cors || config.transport.cors || { origin: '*', methods: ['GET', 'POST'] }
407
- 407
408
- 408 io = new Server(server, {
409
- 409 path,
410
- 410 cors,
411
- 411 transports: ['websocket', 'polling']
412
- 412 })
413
- 413
414
- 414 vars.socketIO = io
415
- 415 api.io = io
416
- 416
417
- 417 if (redisConfig) {
418
- 418 try {
419
- 419 ({ createClient } = await import('redis'))
420
- 420 } catch (error) {
421
- 421 requirePackage('redis', 'socketio', 'Redis is required for Socket.IO horizontal scaling. This is a peer dependency.')
422
- 422 throw error
423
- 423 }
424
- 424
425
- 425 try {
426
- 426 ({ createAdapter } = await import('@socket.io/redis-adapter'))
427
- 427 } catch (error) {
428
- 428 requirePackage('@socket.io/redis-adapter', 'socketio',
429
- 429 'Socket.IO Redis adapter is required for horizontal scaling. This is a peer dependency.')
430
- 430 throw error
431
- 431 }
432
- 432
433
- 433 const pubClient = createClient(redisConfig)
434
- 434 const subClient = pubClient.duplicate()
435
- 435
436
- 436 await Promise.all([
437
- 437 pubClient.connect(),
438
- 438 subClient.connect()
439
- 439 ])
440
- 440
441
- 441 io.adapter(createAdapter(pubClient, subClient))
442
- 442 vars.socketIORedisClients = { pubClient, subClient }
443
- 443 log.info('Socket.IO configured with Redis adapter')
444
- 444 }
445
- 445
446
- 446 io.use(async (socket, next) => {
447
- 447 try {
448
- 448 const authContext = await authenticateSocket({ socket, api, helpers, log, config })
449
- 449 socket.data.auth = authContext
450
- 450 socket.data.subscriptions = new Map()
451
- 451 next()
452
- 452 } catch (error) {
453
- 453 log.warn('Socket.IO authentication failed', error)
454
- 454 next(new Error(error.message || 'Authentication failed'))
455
- 455 }
456
- 456 })
457
- 457
458
- 458 io.on('connection', (socket) => {
459
- 459 log.info(`Socket connected: ${socket.id}`, {
460
- 460 userId: socket.data.auth?.userId ?? null
461
- 461 })
462
- 462
463
- 463 socket.emit('connected', {
464
- 464 socketId: socket.id,
465
- 465 serverTime: new Date().toISOString()
466
- 466 })
467
- 467
468
- 468 socket.on('subscribe', async (payload, callback) => {
469
- 469 try {
470
- 470 const result = await registerSubscription({
471
- 471 socket,
472
- 472 data: payload,
473
- 473 scopes,
474
- 474 runHooks,
475
- 475 log,
476
- 476 config
477
- 477 })
478
- 478
479
- 479 if (callback) callback({ success: true, data: result })
480
- 480 else socket.emit('subscription.created', result)
481
- 481 } catch (error) {
482
- 482 log.error('Socket.IO subscribe error', error)
483
- 483 const response = {
484
- 484 error: {
485
- 485 code: error.code || 'SUBSCRIBE_ERROR',
486
- 486 message: error.message
487
- 487 }
488
- 488 }
489
- 489 if (callback) callback(response)
490
- 490 else socket.emit('subscription.error', response.error)
491
- 491 }
492
- 492 })
493
- 493
494
- 494 socket.on('unsubscribe', (payload, callback) => {
495
- 495 try {
496
- 496 const subscriptionId = payload?.subscriptionId
497
- 497 if (!subscriptionId) {
498
- 498 const error = { code: 'MISSING_SUBSCRIPTION_ID', message: 'Subscription ID is required' }
499
- 499 if (callback) callback({ error })
500
- 500 return
501
- 501 }
502
- 502
503
- 503 const subscription = socket.data.subscriptions?.get(subscriptionId)
504
- 504 if (!subscription) {
505
- 505 const error = { code: 'SUBSCRIPTION_NOT_FOUND', message: 'Subscription not found' }
506
- 506 if (callback) callback({ error })
507
- 507 return
508
- 508 }
509
- 509
510
- 510 socket.data.subscriptions.delete(subscriptionId)
511
- 511 const hasOther = Array.from(socket.data.subscriptions.values())
512
- 512 .some((sub) => sub.resource === subscription.resource)
513
- 513 if (!hasOther) {
514
- 514 socket.leave(`${subscription.resource}:updates`)
515
- 515 }
516
- 516
517
- 517 if (callback) callback({ success: true })
518
- 518 log.info(`Socket ${socket.id} unsubscribed from ${subscription.resource}`, { subscriptionId })
519
- 519 } catch (error) {
520
- 520 log.error('Socket.IO unsubscribe error', error)
521
- 521 if (callback) {
522
- 522 callback({
523
- 523 error: {
524
- 524 code: 'UNSUBSCRIBE_ERROR',
525
- 525 message: error.message
526
- 526 }
527
- 527 })
528
- 528 }
529
- 529 }
530
- 530 })
531
- 531
532
- 532 socket.on('restore-subscriptions', async (payload, callback) => {
533
- 533 try {
534
- 534 const subscriptions = payload?.subscriptions
535
- 535 if (!Array.isArray(subscriptions)) {
536
- 536 const error = { code: 'INVALID_DATA', message: 'Subscriptions must be an array' }
537
- 537 if (callback) callback({ error })
538
- 538 return
539
- 539 }
540
- 540
541
- 541 const result = await handleRestoreSubscriptions({
542
- 542 socket,
543
- 543 subscriptions,
544
- 544 scopes,
545
- 545 runHooks,
546
- 546 log,
547
- 547 config
548
- 548 })
549
- 549
550
- 550 if (callback) callback({ success: true, ...result })
551
- 551 } catch (error) {
552
- 552 log.error('Socket.IO restore subscriptions error', error)
553
- 553 if (callback) {
554
- 554 callback({
555
- 555 error: {
556
- 556 code: 'RESTORE_ERROR',
557
- 557 message: error.message
558
- 558 }
559
- 559 })
560
- 560 }
561
- 561 }
562
- 562 })
563
- 563
564
- 564 socket.on('disconnect', (reason) => {
565
- 565 log.info(`Socket disconnected: ${socket.id}`, {
566
- 566 reason,
567
- 567 subscriptionCount: socket.data.subscriptions?.size || 0
568
- 568 })
569
- 569 })
570
- 570 })
571
- 571
572
- 572 log.info('Socket.IO server started', { path })
573
- 573 return io
574
- 574 }
575
- 575
576
- 576 addHook('finish', 'socketio-broadcast', {}, async ({ context }) => {
577
- 577 const { method, scopeName, id } = context
578
- 578
579
- 579 if (!io) {
580
- 580 log.debug('Socket.IO broadcast skipped: server not started')
581
- 581 return
582
- 582 }
583
- 583
584
- 584 if (!['post', 'put', 'patch', 'delete'].includes(method)) {
585
- 585 return
586
- 586 }
587
- 587
588
- 588 if (!id && method !== 'delete') {
589
- 589 log.debug('Socket.IO broadcast skipped: missing record id')
590
- 590 return
591
- 591 }
592
- 592
593
- 593 if (context.transaction) {
594
- 594 if (!pendingBroadcasts.has(context.transaction)) {
595
- 595 pendingBroadcasts.set(context.transaction, [])
596
- 596 }
597
- 597 pendingBroadcasts.get(context.transaction).push({ method, scopeName, id, context })
598
- 598 return
599
- 599 }
600
- 600
601
- 601 await performBroadcast({ method, scopeName, id, context, api, io, log })
602
- 602 })
603
- 603
604
- 604 addHook('afterCommit', 'socketio-broadcast-deferred', {}, async ({ context }) => {
605
- 605 if (!context?.transaction) return
606
- 606 const broadcasts = pendingBroadcasts.get(context.transaction)
607
- 607 if (!broadcasts) return
608
- 608
609
- 609 for (const broadcast of broadcasts) {
610
- 610 await performBroadcast({ ...broadcast, api, io, log })
611
- 611 }
612
- 612
613
- 613 pendingBroadcasts.delete(context.transaction)
614
- 614 })
615
- 615
616
- 616 addHook('afterRollback', 'socketio-cleanup-broadcasts', {}, async ({ context }) => {
617
- 617 if (!context?.transaction) return
618
- 618 pendingBroadcasts.delete(context.transaction)
619
- 619 })
620
- 620 }
621
- 621 }