json-rest-api 1.0.29 → 2.1.0

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 (294) hide show
  1. package/LICENSE +10 -670
  2. package/LICENSE-GPL-3.0 +674 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +39 -30
  5. package/docs/API.md +744 -1273
  6. package/docs/GUIDE/01-getting-started.md +338 -0
  7. package/docs/GUIDE/02-resources-and-relationships.md +33 -0
  8. package/docs/GUIDE/03-running-example.md +87 -0
  9. package/docs/GUIDE/04-creating-and-querying.md +181 -0
  10. package/docs/GUIDE/05-belongs-to.md +177 -0
  11. package/docs/GUIDE/06-has-many.md +184 -0
  12. package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
  13. package/docs/GUIDE/08-many-to-many.md +184 -0
  14. package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
  15. package/docs/GUIDE/10-put-and-patch.md +174 -0
  16. package/docs/GUIDE/11-relationship-endpoints.md +174 -0
  17. package/docs/GUIDE/12-field-transformations.md +287 -0
  18. package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
  19. package/docs/GUIDE/14-custom-resource-methods.md +125 -0
  20. package/docs/GUIDE/15-query-projections.md +266 -0
  21. package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
  22. package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
  23. package/docs/GUIDE/18-labels.md +86 -0
  24. package/docs/GUIDE/19-managed-transactions.md +179 -0
  25. package/docs/GUIDE/20-transaction-outcomes.md +207 -0
  26. package/docs/GUIDE/21-schema-and-migrations.md +733 -0
  27. package/docs/GUIDE/22-version-field-migrations.md +208 -0
  28. package/docs/GUIDE/23-fastify.md +179 -0
  29. package/docs/GUIDE/24-cors.md +172 -0
  30. package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
  31. package/docs/GUIDE/26-file-uploads.md +278 -0
  32. package/docs/GUIDE/27-bulk-operations.md +277 -0
  33. package/docs/GUIDE/28-socketio.md +325 -0
  34. package/docs/GUIDE/29-writing-plugins.md +278 -0
  35. package/docs/GUIDE/30-backend-capabilities.md +103 -0
  36. package/docs/GUIDE/31-positioning.md +181 -0
  37. package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +74 -30
  38. package/docs/GUIDE/33-migrating-to-v2.md +3838 -0
  39. package/docs/GUIDE/index.md +51 -59
  40. package/docs/QUICKSTART.md +120 -287
  41. package/docs/architecture.md +75 -0
  42. package/docs/contributing.md +178 -0
  43. package/index.d.ts +37 -0
  44. package/index.js +3 -22
  45. package/lib/enhanced-logger.js +91 -85
  46. package/lib/error-context.js +445 -5
  47. package/lib/error-formatter.js +219 -59
  48. package/lib/knex-transaction.js +70 -0
  49. package/lib/logger-types.d.ts +22 -0
  50. package/lib/missing-package.js +4 -0
  51. package/lib/rest-api-errors.js +103 -8
  52. package/lib/runtime/json-rest-api.js +172 -0
  53. package/lib/transaction-types.d.ts +59 -0
  54. package/package.json +97 -27
  55. package/plugins/core/bulk-operations-plugin.js +191 -184
  56. package/plugins/core/connectors/express-plugin.js +148 -219
  57. package/plugins/core/connectors/fastify-plugin.js +125 -82
  58. package/plugins/core/connectors/lib/busboy-detector.js +74 -156
  59. package/plugins/core/connectors/lib/connector-core.js +144 -20
  60. package/plugins/core/connectors/lib/formidable-detector.js +63 -120
  61. package/plugins/core/connectors/lib/http-validators.js +77 -0
  62. package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
  63. package/plugins/core/connectors/lib/request-helpers.js +10 -1
  64. package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
  65. package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
  66. package/plugins/core/file-handling-plugin.js +87 -177
  67. package/plugins/core/lib/anyapi/anyapi-registry.js +586 -418
  68. package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
  69. package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
  70. package/plugins/core/lib/anyapi/schema-utils.js +120 -56
  71. package/plugins/core/lib/anyapi/stored-definition.js +109 -0
  72. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
  73. package/plugins/core/lib/dbIntrospection.js +228 -21
  74. package/plugins/core/lib/dbTablesOperations.js +409 -92
  75. package/plugins/core/lib/fractional-positioning.js +12 -48
  76. package/plugins/core/lib/querying/identifier-query.js +19 -0
  77. package/plugins/core/lib/querying/include-query-helpers.js +364 -0
  78. package/plugins/core/lib/querying/include-to-many.js +428 -0
  79. package/plugins/core/lib/querying/include-to-one.js +579 -0
  80. package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
  81. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
  82. package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
  83. package/plugins/core/lib/querying/knex-process-includes.js +5 -110
  84. package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
  85. package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
  86. package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
  87. package/plugins/core/lib/querying/knex-window-queries.js +62 -253
  88. package/plugins/core/lib/querying/query-builder-utils.js +44 -4
  89. package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
  90. package/plugins/core/lib/querying/query-constraint.js +27 -0
  91. package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
  92. package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
  93. package/plugins/core/lib/querying/sort-helpers.js +13 -1
  94. package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
  95. package/plugins/core/lib/querying/url-helpers.js +11 -4
  96. package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
  97. package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
  98. package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
  99. package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
  100. package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
  101. package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
  102. package/plugins/core/lib/querying-writing/field-utils.js +113 -92
  103. package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
  104. package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
  105. package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
  106. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
  107. package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
  108. package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
  109. package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
  110. package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
  111. package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
  112. package/plugins/core/lib/querying-writing/response-options.js +35 -0
  113. package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
  114. package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
  115. package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
  116. package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
  117. package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
  118. package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
  119. package/plugins/core/lib/storage/storage-adapter.js +128 -62
  120. package/plugins/core/lib/storage/storage-mapping.js +78 -28
  121. package/plugins/core/lib/storage/storage-types.d.ts +502 -0
  122. package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
  123. package/plugins/core/lib/writing/relationship-processor.js +94 -158
  124. package/plugins/core/lib/writing/resource-version.js +172 -0
  125. package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
  126. package/plugins/core/rest-api-anyapi-knex-plugin.js +290 -2475
  127. package/plugins/core/rest-api-autofilter-plugin.js +17 -19
  128. package/plugins/core/rest-api-cors-plugin.js +52 -205
  129. package/plugins/core/rest-api-knex-plugin.js +45 -1077
  130. package/plugins/core/rest-api-label-plugin.js +25 -38
  131. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
  132. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
  133. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
  134. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
  135. package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
  136. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
  137. package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
  138. package/plugins/core/rest-api-plugin-methods/common.js +291 -395
  139. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
  140. package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
  141. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
  142. package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
  143. package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
  144. package/plugins/core/rest-api-plugin-methods/get.js +36 -68
  145. package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
  146. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
  147. package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
  148. package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
  149. package/plugins/core/rest-api-plugin-methods/post.js +51 -51
  150. package/plugins/core/rest-api-plugin-methods/put.js +69 -148
  151. package/plugins/core/rest-api-plugin-methods/query.js +37 -81
  152. package/plugins/core/rest-api-plugin-methods/release.js +2 -1
  153. package/plugins/core/rest-api-plugin.js +32 -46
  154. package/plugins/core/rest-api-positioning-plugin.js +183 -628
  155. package/plugins/core/rest-api-query-projections-plugin.js +19 -102
  156. package/plugins/core/rest-api-row-policy-plugin.js +3 -3
  157. package/plugins/core/socketio-plugin.js +364 -344
  158. package/plugins/storage/local-storage.js +40 -36
  159. package/types/anyapi.d.ts +30 -0
  160. package/types/autofilter.d.ts +57 -0
  161. package/types/bulk-methods.d.ts +63 -0
  162. package/types/errors.d.ts +118 -0
  163. package/types/fastify-options.d.ts +19 -0
  164. package/types/file-detectors.d.ts +33 -0
  165. package/types/file-storage.d.ts +48 -0
  166. package/types/hook-context.d.ts +180 -0
  167. package/types/plugin-options.d.ts +65 -0
  168. package/types/relationship-methods.d.ts +74 -0
  169. package/types/representations.d.ts +50 -0
  170. package/types/resource-methods.d.ts +95 -0
  171. package/types/resource-schema.d.ts +157 -0
  172. package/types/row-policy.d.ts +31 -0
  173. package/types/runtime.d.ts +132 -0
  174. package/types/transactions.d.ts +21 -0
  175. package/.claude/agents/library-guide-expert.md +0 -30
  176. package/.claude/agents/rest-api-internals-expert.md +0 -54
  177. package/.claude/settings.local.json +0 -70
  178. package/AGENTS.md +0 -34
  179. package/CLAUDE.md +0 -160
  180. package/CRUSH.md +0 -55
  181. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
  182. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
  183. package/TODO.todo +0 -13
  184. package/connectors_ideas/fastify.js +0 -217
  185. package/connectors_ideas/koa-plugin.js +0 -112
  186. package/docs/.bundle/config +0 -2
  187. package/docs/COMPARISON.md +0 -433
  188. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
  189. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
  190. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
  191. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
  192. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
  193. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
  194. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
  195. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
  196. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
  197. package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
  198. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
  199. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
  200. package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
  201. package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
  202. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
  203. package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
  204. package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
  205. package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
  206. package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
  207. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
  208. package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
  209. package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
  210. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
  211. package/docs/Gemfile +0 -6
  212. package/docs/Gemfile.lock +0 -308
  213. package/docs/ONBOARDING.md +0 -315
  214. package/docs/_config.yml +0 -36
  215. package/docs/_layouts/default.html +0 -123
  216. package/docs/apple-touch-icon.svg +0 -4
  217. package/docs/assets/css/style.scss +0 -392
  218. package/docs/favicon.svg +0 -4
  219. package/docs/index.md +0 -164
  220. package/docs/mini-guides/file-uploads-complete.md +0 -161
  221. package/eslint.config.js +0 -13
  222. package/examples/url-override-example.js +0 -137
  223. package/fixing_plan.md +0 -112
  224. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
  225. package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
  226. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
  227. package/plugins/storage/s3-storage.js +0 -93
  228. package/quickTest.js +0 -136
  229. package/scripts/debug-anyapi-review-filter.mjs +0 -63
  230. package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
  231. package/scripts/inspect-anyapi-descriptor.mjs +0 -19
  232. package/scripts/run-docs.js +0 -133
  233. package/test-debug.js +0 -59
  234. package/tests/README.md +0 -202
  235. package/tests/TEST_TEMPLATE.test.js +0 -178
  236. package/tests/anyapi-basic.test.js +0 -105
  237. package/tests/anyapi-cursor-pagination.test.js +0 -148
  238. package/tests/anyapi-custom-idproperty.test.js +0 -163
  239. package/tests/autofilter.test.js +0 -463
  240. package/tests/bulk-operations.test.js +0 -657
  241. package/tests/computed-fields.test.js +0 -586
  242. package/tests/cors.test.js +0 -834
  243. package/tests/curl.test.js +0 -467
  244. package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
  245. package/tests/custom-id-simplified-post.test.js +0 -75
  246. package/tests/custom-idproperty-relationships.test.js +0 -932
  247. package/tests/db-introspection.test.js +0 -521
  248. package/tests/db-table-operations.test.js +0 -798
  249. package/tests/fastify-plugin.test.js +0 -184
  250. package/tests/field-getters.test.js +0 -370
  251. package/tests/field-setters.test.js +0 -306
  252. package/tests/file-handling.test.js +0 -155
  253. package/tests/fixtures/api-configs.js +0 -2566
  254. package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
  255. package/tests/helpers/fake-fastify.js +0 -99
  256. package/tests/helpers/storage-mode.js +0 -65
  257. package/tests/helpers/test-utils.js +0 -203
  258. package/tests/http-connectors-parity.test.js +0 -395
  259. package/tests/id-normalization.test.js +0 -444
  260. package/tests/include-depth-validation.test.js +0 -331
  261. package/tests/includes.test.js +0 -427
  262. package/tests/local-storage.test.js +0 -96
  263. package/tests/logical-id-write-validation.test.js +0 -151
  264. package/tests/nested-includes.test.js +0 -617
  265. package/tests/pagination-boundaries.test.js +0 -154
  266. package/tests/pagination-contract.test.js +0 -122
  267. package/tests/pagination-cursor-multifield.test.js +0 -641
  268. package/tests/pagination-enhanced.test.js +0 -563
  269. package/tests/polymorphic-relationships.test.js +0 -347
  270. package/tests/polymorphic-simple.test.js +0 -148
  271. package/tests/positioning-advanced.test.js +0 -692
  272. package/tests/positioning-extended.test.js +0 -915
  273. package/tests/positioning-multifilter.test.js +0 -699
  274. package/tests/positioning.test.js +0 -560
  275. package/tests/post-commit-hooks.test.js +0 -120
  276. package/tests/projected-fields.test.js +0 -319
  277. package/tests/public-exports.test.js +0 -15
  278. package/tests/put-create-parity.test.js +0 -216
  279. package/tests/queries.test.js +0 -521
  280. package/tests/query-limits.test.js +0 -584
  281. package/tests/relationship-endpoints.test.js +0 -620
  282. package/tests/relationship-include-storage-adapter.test.js +0 -56
  283. package/tests/relationships.test.js +0 -681
  284. package/tests/rest-api.test.js +0 -337
  285. package/tests/return-record-settings.test.js +0 -184
  286. package/tests/row-policy.test.js +0 -489
  287. package/tests/s3-storage.test.js +0 -38
  288. package/tests/searchschema-knex-behavior.test.js +0 -187
  289. package/tests/searchschema-merge.test.js +0 -151
  290. package/tests/socketio.test.js +0 -860
  291. package/tests/storage-mapping.test.js +0 -362
  292. package/tests/temporal-boundaries.test.js +0 -208
  293. package/tests/test-development-guide.md +0 -370
  294. package/tests/virtual-fields.test.js +0 -404
@@ -1,249 +1,112 @@
1
- /**
2
- * Scope configuration validators for relationship definitions
3
- *
4
- * @description
5
- * These validators run at scope registration time to ensure:
6
- * - Polymorphic relationships are properly configured
7
- * - HasMany relationships specify required foreign keys
8
- * - ManyToMany relationships have pivot table settings
9
- * - Referenced scopes actually exist in the system
10
- */
1
+ import { assertFieldName, assertFieldNameMap } from './field-utils.js'
11
2
 
12
- /**
13
- * Validates all relationships in a scope configuration
14
- *
15
- * @param {Object} params - Validation parameters
16
- * @param {Object} params.context - Event context with scopeName and scopeOptions
17
- * @param {Object} params.scopes - All registered scopes for validation
18
- * @throws {Error} If any relationship is misconfigured
19
- *
20
- * @example
21
- * // Input: Valid polymorphic relationship
22
- * validateRelationships({
23
- * context: {
24
- * scopeName: 'comments',
25
- * scopeOptions: {
26
- * schema: {
27
- * commentable_type: { type: 'string' },
28
- * commentable_id: { type: 'integer' }
29
- * },
30
- * relationships: {
31
- * commentable: {
32
- * belongsToPolymorphic: {
33
- * types: ['posts', 'videos'], // Must exist
34
- * typeField: 'commentable_type',
35
- * idField: 'commentable_id'
36
- * }
37
- * }
38
- * }
39
- * }
40
- * },
41
- * scopes: { posts: {}, videos: {} } // Referenced types exist
42
- * });
43
- * // Output: No error (valid configuration)
44
- *
45
- * @example
46
- * // Input: Missing required field
47
- * validateRelaationships({
48
- * context: {
49
- * scopeName: 'attachments',
50
- * scopeOptions: {
51
- * relationships: {
52
- * attachable: {
53
- * belongsToPolymorphic: {
54
- * types: ['documents'],
55
- * typeField: 'attachable_type'
56
- * // Missing: idField
57
- * }
58
- * }
59
- * }
60
- * }
61
- * },
62
- * scopes: { documents: {} }
63
- * });
64
- * // Throws: Error
65
- * // "Invalid polymorphic relationship 'attachable' in scope 'attachments':
66
- * // belongsToPolymorphic.idField must be specified"
67
- *
68
- * @example
69
- * // Input: HasMany without foreignKey
70
- * validateRelationships({
71
- * context: {
72
- * scopeName: 'users',
73
- * scopeOptions: {
74
- * relationships: {
75
- * posts: {
76
- * type: 'hasMany',
77
- * target: 'posts'
78
- * // Missing: foreignKey
79
- * }
80
- * }
81
- * }
82
- * },
83
- * scopes: { posts: {} }
84
- * });
85
- * // Throws: Error
86
- * // "Invalid hasMany relationship 'posts' in scope 'users':
87
- * // hasMany relationship requires foreignKey to be specified"
88
- *
89
- * @example
90
- * // Input: Complex activity feed with multiple polymorphic relationships
91
- * validateRelationships({
92
- * context: {
93
- * scopeName: 'activities',
94
- * scopeOptions: {
95
- * schema: {
96
- * trackable_type: { type: 'string' },
97
- * trackable_id: { type: 'integer' },
98
- * actor_type: { type: 'string' },
99
- * actor_id: { type: 'integer' }
100
- * },
101
- * relationships: {
102
- * trackable: { // What was changed
103
- * belongsToPolymorphic: {
104
- * types: ['posts', 'comments', 'users'],
105
- * typeField: 'trackable_type',
106
- * idField: 'trackable_id'
107
- * }
108
- * },
109
- * actor: { // Who made the change
110
- * belongsToPolymorphic: {
111
- * types: ['users', 'api_clients'],
112
- * typeField: 'actor_type',
113
- * idField: 'actor_id'
114
- * }
115
- * }
116
- * }
117
- * }
118
- * },
119
- * scopes: { posts: {}, comments: {}, users: {}, api_clients: {} }
120
- * });
121
- * // Output: No error (both polymorphic relationships valid)
122
- *
123
- * @description
124
- * Used by:
125
- * - rest-api-plugin on 'scope:added' event
126
- * - Runs automatically when scopes are registered
127
- *
128
- * Purpose:
129
- * - Catches configuration errors at startup, not runtime
130
- * - Validates polymorphic relationship structure
131
- * - Ensures referenced scope types exist
132
- * - Validates hasMany/manyToMany foreign keys
133
- * - Provides clear error messages for debugging
134
- *
135
- * Data flow:
136
- * 1. Extracts relationships from scope options
137
- * 2. For each relationship, checks its type
138
- * 3. Validates polymorphic: types, typeField, idField
139
- * 4. Validates hasMany: foreignKey (unless via)
140
- * 5. Validates manyToMany: through, foreignKey, otherKey
141
- * 6. Throws descriptive error on first validation failure
142
- */
143
- export function validateRelationships ({ context, scopes }) {
144
- const { scopeName, scopeOptions } = context
145
- const relationships = scopeOptions.relationships || {}
3
+ export function validateSchemaFieldNames (fields, scopeName) {
4
+ assertFieldNameMap(fields, `schema in '${scopeName}'`)
5
+ for (const [name, definition] of Object.entries(fields || {})) {
6
+ if (definition?.belongsTo !== undefined && typeof definition.belongsTo !== 'string') {
7
+ throw new Error(`Invalid field '${scopeName}.${name}': 'belongsTo' must be a string`)
8
+ }
9
+ if (definition?.as !== undefined && typeof definition.as !== 'string') {
10
+ throw new Error(`Invalid relationship alias for '${scopeName}.${name}': 'as' must be a string`)
11
+ }
12
+ assertFieldName(definition?.as, `relationship alias for '${scopeName}.${name}'`)
13
+ for (const backing of [definition?.belongsToPolymorphic?.typeField, definition?.belongsToPolymorphic?.idField]) {
14
+ assertFieldName(backing, `backing field for relationship '${scopeName}.${name}'`)
15
+ }
16
+ if (definition?.search && typeof definition.search === 'object') {
17
+ assertFieldNameMap(definition.search, `search declaration for '${scopeName}.${name}'`)
18
+ }
19
+ }
20
+ }
146
21
 
147
- for (const [relName, relDef] of Object.entries(relationships)) {
148
- if (relDef.belongsToPolymorphic) {
149
- const validation = validatePolymorphicRelationship(relDef, scopeName, scopes)
22
+ export function validateRelationshipFieldNames (relationships, scopeName) {
23
+ assertFieldNameMap(relationships, `relationships in '${scopeName}'`)
24
+ for (const [name, definition] of Object.entries(relationships || {})) {
25
+ for (const option of ['target', 'through', 'via', 'foreignKey', 'otherKey']) {
26
+ if (definition?.[option] !== undefined && typeof definition[option] !== 'string') {
27
+ throw new Error(`Invalid relationship '${scopeName}.${name}': '${option}' must be a string`)
28
+ }
29
+ }
30
+ for (const field of [definition?.foreignKey, definition?.otherKey, definition?.belongsToPolymorphic?.typeField, definition?.belongsToPolymorphic?.idField]) {
31
+ assertFieldName(field, `backing field for relationship '${scopeName}.${name}'`)
32
+ }
33
+ if (definition?.belongsToPolymorphic !== undefined) {
34
+ const validation = validatePolymorphicRelationship(definition, scopeName)
150
35
  if (!validation.valid) {
151
- throw new Error(
152
- `Invalid polymorphic relationship '${relName}' in scope '${scopeName}': ${validation.error}`
153
- )
36
+ throw new Error(`Invalid polymorphic relationship '${name}' in scope '${scopeName}': ${validation.error}`)
154
37
  }
155
38
  }
156
39
 
157
40
  // Validate based on relationship type
158
- if (relDef.type === 'hasMany') {
159
- const validation = validateHasManyRelationship(relDef, relName, scopeName)
41
+ if (definition.type === 'hasMany') {
42
+ const validation = validateHasManyRelationship(definition, name, scopeName)
160
43
  if (!validation.valid) {
161
44
  throw new Error(
162
- `Invalid hasMany relationship '${relName}' in scope '${scopeName}': ${validation.error}`
45
+ `Invalid hasMany relationship '${name}' in scope '${scopeName}': ${validation.error}`
163
46
  )
164
47
  }
165
48
  }
166
49
 
167
- if (relDef.type === 'hasOne') {
168
- const validation = validateHasOneRelationship(relDef, relName, scopeName)
50
+ if (definition.type === 'hasOne') {
51
+ const validation = validateHasOneRelationship(definition, name, scopeName)
169
52
  if (!validation.valid) {
170
53
  throw new Error(
171
- `Invalid hasOne relationship '${relName}' in scope '${scopeName}': ${validation.error}`
54
+ `Invalid hasOne relationship '${name}' in scope '${scopeName}': ${validation.error}`
172
55
  )
173
56
  }
174
57
  }
175
58
 
176
- if (relDef.type === 'manyToMany') {
177
- const validation = validateManyToManyRelationship(relDef, relName, scopeName)
59
+ if (definition.type === 'manyToMany') {
60
+ const validation = validateManyToManyRelationship(definition, name, scopeName)
178
61
  if (!validation.valid) {
179
62
  throw new Error(
180
- `Invalid manyToMany relationship '${relName}' in scope '${scopeName}': ${validation.error}`
63
+ `Invalid manyToMany relationship '${name}' in scope '${scopeName}': ${validation.error}`
181
64
  )
182
65
  }
183
66
  }
184
67
  }
185
68
  }
186
69
 
70
+ /** Registration-time shape checks for declared relationships and backing names. */
71
+
187
72
  /**
188
- * Validates a polymorphic relationship definition
189
- *
190
- * @param {Object} relDef - Relationship definition with belongsToPolymorphic
191
- * @param {string} scopeName - Scope being registered (for error messages)
192
- * @param {Object} scopes - All registered scopes for validation
193
- * @returns {Object} Validation result {valid: boolean, error?: string}
73
+ * Check declared relationship shapes before resource registration completes.
74
+ * Polymorphic targets must already be registered. Other relationships are
75
+ * checked for required mapping options here; this is not database introspection
76
+ * or complete validation of every target field and referential constraint.
194
77
  *
195
- * @example
196
- * // Input: Valid polymorphic configuration
197
- * const relDef = {
198
- * belongsToPolymorphic: {
199
- * types: ['articles', 'videos', 'products'],
200
- * typeField: 'commentable_type',
201
- * idField: 'commentable_id'
202
- * }
203
- * };
204
- * validatePolymorphicRelationship(relDef, 'comments', scopes);
205
- * // Output: { valid: true }
206
- *
207
- * @example
208
- * // Input: Missing required types array
209
- * const relDef = {
210
- * belongsToPolymorphic: {
211
- * typeField: 'commentable_type',
212
- * idField: 'commentable_id'
213
- * // Missing: types
214
- * }
215
- * };
216
- * validatePolymorphicRelationship(relDef, 'comments', scopes);
217
- * // Output: {
218
- * // valid: false,
219
- * // error: 'belongsToPolymorphic.types must be a non-empty array'
220
- * // }
221
- *
222
- * @example
223
- * // Input: References non-existent scope
224
- * const relDef = {
225
- * belongsToPolymorphic: {
226
- * types: ['articles', 'unicorns'], // 'unicorns' doesn't exist
227
- * typeField: 'attachable_type',
228
- * idField: 'attachable_id'
229
- * }
230
- * };
231
- * validatePolymorphicRelationship(relDef, 'attachments', scopes);
232
- * // Output: {
233
- * // valid: false,
234
- * // error: "Polymorphic type 'unicorns' is not a registered scope"
235
- * // }
236
- *
237
- * @private
78
+ * @param {Object} params
79
+ * @param {Object} params.context - scopeName and scopeOptions being registered
80
+ * @param {Object} params.scopes - Registry used for polymorphic target lookup
81
+ * @returns {void}
82
+ * @throws {Error} On invalid relationship configuration
238
83
  */
239
- const validatePolymorphicRelationship = (relDef, scopeName, scopes) => {
240
- // Validation logic:
241
- // 1. Check relDef.belongsToPolymorphic exists
242
- // 2. Validate required properties: types, typeField, idField
243
- // 3. Ensure types is non-empty array
244
- // 4. Verify all types are registered scopes
245
- // 5. Check that typeField and idField exist in the schema
84
+ export function validateRelationships ({ context, scopes }) {
85
+ const { scopeName, scopeOptions } = context
86
+ const relationships = scopeOptions.relationships || {}
87
+ validateRelationshipFieldNames(relationships, scopeName)
88
+
89
+ for (const [relName, relDef] of Object.entries(relationships)) {
90
+ if (relDef.belongsToPolymorphic) {
91
+ const validation = validatePolymorphicRelationship(relDef, scopeName, scopes)
92
+ if (!validation.valid) {
93
+ throw new Error(
94
+ `Invalid polymorphic relationship '${relName}' in scope '${scopeName}': ${validation.error}`
95
+ )
96
+ }
97
+ }
98
+ }
99
+ }
246
100
 
101
+ /**
102
+ * Check target and backing names, and registered targets when a registry is supplied.
103
+ * This helper does not inspect whether the backing columns exist in a database.
104
+ * @param {Object} relDef
105
+ * @param {string} scopeName
106
+ * @param {Object} [scopes]
107
+ * @returns {Object} Validation result with valid and an optional error
108
+ */
109
+ const validatePolymorphicRelationship = (relDef, scopeName, scopes) => {
247
110
  const { belongsToPolymorphic } = relDef
248
111
 
249
112
  if (!belongsToPolymorphic) {
@@ -275,7 +138,10 @@ const validatePolymorphicRelationship = (relDef, scopeName, scopes) => {
275
138
 
276
139
  // Check that all types are valid scopes
277
140
  for (const type of types) {
278
- if (!scopes[type]) {
141
+ if (typeof type !== 'string' || !type) {
142
+ return { valid: false, error: 'belongsToPolymorphic.types must contain non-empty strings' }
143
+ }
144
+ if (scopes && !scopes[type]) {
279
145
  return {
280
146
  valid: false,
281
147
  error: `Polymorphic type '${type}' is not a registered scope`
@@ -287,47 +153,11 @@ const validatePolymorphicRelationship = (relDef, scopeName, scopes) => {
287
153
  }
288
154
 
289
155
  /**
290
- * Validates a hasMany relationship definition
291
- *
292
- * @param {Object} relDef - Relationship definition with hasMany
293
- * @param {string} relName - Relationship name
294
- * @param {string} scopeName - Scope being registered
295
- * @returns {Object} Validation result {valid: boolean, error?: string}
296
- *
297
- * @example
298
- * // Input: Valid hasMany configuration
299
- * const relDef = {
300
- * type: 'hasMany',
301
- * target: 'posts',
302
- * foreignKey: 'author_id' // Required!
303
- * };
304
- * validateHasManyRelationship(relDef, 'posts', 'users');
305
- * // Output: { valid: true }
306
- *
307
- * @example
308
- * // Input: Missing required foreignKey
309
- * const relDef = {
310
- * type: 'hasMany',
311
- * target: 'posts'
312
- * // Missing: foreignKey
313
- * };
314
- * validateHasManyRelationship(relDef, 'posts', 'users');
315
- * // Output: {
316
- * // valid: false,
317
- * // error: 'hasMany relationship requires foreignKey to be specified...'
318
- * // }
319
- *
320
- * @example
321
- * // Input: Polymorphic hasMany with 'via' (doesn't need foreignKey)
322
- * const relDef = {
323
- * type: 'hasMany',
324
- * target: 'comments',
325
- * via: 'commentable' // Uses polymorphic relationship
326
- * };
327
- * validateHasManyRelationship(relDef, 'comments', 'posts');
328
- * // Output: { valid: true }
329
- *
330
- * @private
156
+ * Require a target and explicit foreign key, except for reverse polymorphic via.
157
+ * @param {Object} relDef
158
+ * @param {string} relName
159
+ * @param {string} scopeName
160
+ * @returns {Object} Validation result with valid and an optional error
331
161
  */
332
162
  const validateHasManyRelationship = (relDef, relName, scopeName) => {
333
163
  // Validate target is specified
@@ -408,39 +238,11 @@ const validateHasOneRelationship = (relDef, relName, scopeName) => {
408
238
  }
409
239
 
410
240
  /**
411
- * Validates a manyToMany relationship definition
412
- *
413
- * @param {Object} relDef - Relationship definition with manyToMany
414
- * @param {string} relName - Relationship name
415
- * @param {string} scopeName - Scope being registered
416
- * @returns {Object} Validation result {valid: boolean, error?: string}
417
- *
418
- * @example
419
- * // Input: Valid manyToMany configuration
420
- * const relDef = {
421
- * type: 'manyToMany',
422
- * through: 'article_tags', // Pivot table
423
- * foreignKey: 'article_id', // This scope's FK
424
- * otherKey: 'tag_id' // Other scope's FK
425
- * };
426
- * validateManyToManyRelationship(relDef, 'tags', 'articles');
427
- * // Output: { valid: true }
428
- *
429
- * @example
430
- * // Input: Missing required fields
431
- * const relDef = {
432
- * type: 'manyToMany',
433
- * through: 'article_tags',
434
- * foreignKey: 'article_id'
435
- * // Missing: otherKey
436
- * };
437
- * validateManyToManyRelationship(relDef, 'tags', 'articles');
438
- * // Output: {
439
- * // valid: false,
440
- * // error: 'manyToMany relationship requires otherKey to be specified...'
441
- * // }
442
- *
443
- * @private
241
+ * Require the pivot table and both explicit foreign-key mappings.
242
+ * @param {Object} relDef
243
+ * @param {string} relName
244
+ * @param {string} scopeName
245
+ * @returns {Object} Validation result with valid and an optional error
444
246
  */
445
247
  const validateManyToManyRelationship = (relDef, relName, scopeName) => {
446
248
  const { through, foreignKey, otherKey } = relDef