json-rest-api 1.0.29 → 2.0.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 (292) 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 +634 -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 +100 -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 +3827 -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 +36 -0
  44. package/index.js +1 -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 +64 -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 +213 -164
  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 +25 -16
  71. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
  72. package/plugins/core/lib/dbIntrospection.js +228 -21
  73. package/plugins/core/lib/dbTablesOperations.js +409 -92
  74. package/plugins/core/lib/fractional-positioning.js +12 -48
  75. package/plugins/core/lib/querying/identifier-query.js +19 -0
  76. package/plugins/core/lib/querying/include-query-helpers.js +364 -0
  77. package/plugins/core/lib/querying/include-to-many.js +428 -0
  78. package/plugins/core/lib/querying/include-to-one.js +579 -0
  79. package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
  80. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
  81. package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
  82. package/plugins/core/lib/querying/knex-process-includes.js +5 -110
  83. package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
  84. package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
  85. package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
  86. package/plugins/core/lib/querying/knex-window-queries.js +62 -253
  87. package/plugins/core/lib/querying/query-builder-utils.js +44 -4
  88. package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
  89. package/plugins/core/lib/querying/query-constraint.js +27 -0
  90. package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
  91. package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
  92. package/plugins/core/lib/querying/sort-helpers.js +13 -1
  93. package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
  94. package/plugins/core/lib/querying/url-helpers.js +11 -4
  95. package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
  96. package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
  97. package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
  98. package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
  99. package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
  100. package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
  101. package/plugins/core/lib/querying-writing/field-utils.js +113 -92
  102. package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
  103. package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
  104. package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
  105. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
  106. package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
  107. package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
  108. package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
  109. package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
  110. package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
  111. package/plugins/core/lib/querying-writing/response-options.js +35 -0
  112. package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
  113. package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
  114. package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
  115. package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
  116. package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
  117. package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
  118. package/plugins/core/lib/storage/storage-adapter.js +128 -62
  119. package/plugins/core/lib/storage/storage-mapping.js +78 -28
  120. package/plugins/core/lib/storage/storage-types.d.ts +502 -0
  121. package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
  122. package/plugins/core/lib/writing/relationship-processor.js +94 -158
  123. package/plugins/core/lib/writing/resource-version.js +172 -0
  124. package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
  125. package/plugins/core/rest-api-anyapi-knex-plugin.js +116 -2485
  126. package/plugins/core/rest-api-autofilter-plugin.js +17 -19
  127. package/plugins/core/rest-api-cors-plugin.js +52 -205
  128. package/plugins/core/rest-api-knex-plugin.js +45 -1077
  129. package/plugins/core/rest-api-label-plugin.js +25 -38
  130. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
  131. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
  132. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
  133. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
  134. package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
  135. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
  136. package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
  137. package/plugins/core/rest-api-plugin-methods/common.js +291 -395
  138. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
  139. package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
  140. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
  141. package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
  142. package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
  143. package/plugins/core/rest-api-plugin-methods/get.js +36 -68
  144. package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
  145. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
  146. package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
  147. package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
  148. package/plugins/core/rest-api-plugin-methods/post.js +51 -51
  149. package/plugins/core/rest-api-plugin-methods/put.js +69 -148
  150. package/plugins/core/rest-api-plugin-methods/query.js +37 -81
  151. package/plugins/core/rest-api-plugin-methods/release.js +2 -1
  152. package/plugins/core/rest-api-plugin.js +32 -46
  153. package/plugins/core/rest-api-positioning-plugin.js +183 -628
  154. package/plugins/core/rest-api-query-projections-plugin.js +19 -102
  155. package/plugins/core/rest-api-row-policy-plugin.js +3 -3
  156. package/plugins/core/socketio-plugin.js +364 -344
  157. package/plugins/storage/local-storage.js +40 -36
  158. package/types/autofilter.d.ts +57 -0
  159. package/types/bulk-methods.d.ts +63 -0
  160. package/types/errors.d.ts +118 -0
  161. package/types/fastify-options.d.ts +19 -0
  162. package/types/file-detectors.d.ts +33 -0
  163. package/types/file-storage.d.ts +48 -0
  164. package/types/hook-context.d.ts +180 -0
  165. package/types/plugin-options.d.ts +53 -0
  166. package/types/relationship-methods.d.ts +74 -0
  167. package/types/representations.d.ts +50 -0
  168. package/types/resource-methods.d.ts +95 -0
  169. package/types/resource-schema.d.ts +157 -0
  170. package/types/row-policy.d.ts +31 -0
  171. package/types/runtime.d.ts +132 -0
  172. package/types/transactions.d.ts +21 -0
  173. package/.claude/agents/library-guide-expert.md +0 -30
  174. package/.claude/agents/rest-api-internals-expert.md +0 -54
  175. package/.claude/settings.local.json +0 -70
  176. package/AGENTS.md +0 -34
  177. package/CLAUDE.md +0 -160
  178. package/CRUSH.md +0 -55
  179. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
  180. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
  181. package/TODO.todo +0 -13
  182. package/connectors_ideas/fastify.js +0 -217
  183. package/connectors_ideas/koa-plugin.js +0 -112
  184. package/docs/.bundle/config +0 -2
  185. package/docs/COMPARISON.md +0 -433
  186. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
  187. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
  188. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
  189. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
  190. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
  191. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
  192. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
  193. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
  194. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
  195. package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
  196. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
  197. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
  198. package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
  199. package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
  200. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
  201. package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
  202. package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
  203. package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
  204. package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
  205. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
  206. package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
  207. package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
  208. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
  209. package/docs/Gemfile +0 -6
  210. package/docs/Gemfile.lock +0 -308
  211. package/docs/ONBOARDING.md +0 -315
  212. package/docs/_config.yml +0 -36
  213. package/docs/_layouts/default.html +0 -123
  214. package/docs/apple-touch-icon.svg +0 -4
  215. package/docs/assets/css/style.scss +0 -392
  216. package/docs/favicon.svg +0 -4
  217. package/docs/index.md +0 -164
  218. package/docs/mini-guides/file-uploads-complete.md +0 -161
  219. package/eslint.config.js +0 -13
  220. package/examples/url-override-example.js +0 -137
  221. package/fixing_plan.md +0 -112
  222. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
  223. package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
  224. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
  225. package/plugins/storage/s3-storage.js +0 -93
  226. package/quickTest.js +0 -136
  227. package/scripts/debug-anyapi-review-filter.mjs +0 -63
  228. package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
  229. package/scripts/inspect-anyapi-descriptor.mjs +0 -19
  230. package/scripts/run-docs.js +0 -133
  231. package/test-debug.js +0 -59
  232. package/tests/README.md +0 -202
  233. package/tests/TEST_TEMPLATE.test.js +0 -178
  234. package/tests/anyapi-basic.test.js +0 -105
  235. package/tests/anyapi-cursor-pagination.test.js +0 -148
  236. package/tests/anyapi-custom-idproperty.test.js +0 -163
  237. package/tests/autofilter.test.js +0 -463
  238. package/tests/bulk-operations.test.js +0 -657
  239. package/tests/computed-fields.test.js +0 -586
  240. package/tests/cors.test.js +0 -834
  241. package/tests/curl.test.js +0 -467
  242. package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
  243. package/tests/custom-id-simplified-post.test.js +0 -75
  244. package/tests/custom-idproperty-relationships.test.js +0 -932
  245. package/tests/db-introspection.test.js +0 -521
  246. package/tests/db-table-operations.test.js +0 -798
  247. package/tests/fastify-plugin.test.js +0 -184
  248. package/tests/field-getters.test.js +0 -370
  249. package/tests/field-setters.test.js +0 -306
  250. package/tests/file-handling.test.js +0 -155
  251. package/tests/fixtures/api-configs.js +0 -2566
  252. package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
  253. package/tests/helpers/fake-fastify.js +0 -99
  254. package/tests/helpers/storage-mode.js +0 -65
  255. package/tests/helpers/test-utils.js +0 -203
  256. package/tests/http-connectors-parity.test.js +0 -395
  257. package/tests/id-normalization.test.js +0 -444
  258. package/tests/include-depth-validation.test.js +0 -331
  259. package/tests/includes.test.js +0 -427
  260. package/tests/local-storage.test.js +0 -96
  261. package/tests/logical-id-write-validation.test.js +0 -151
  262. package/tests/nested-includes.test.js +0 -617
  263. package/tests/pagination-boundaries.test.js +0 -154
  264. package/tests/pagination-contract.test.js +0 -122
  265. package/tests/pagination-cursor-multifield.test.js +0 -641
  266. package/tests/pagination-enhanced.test.js +0 -563
  267. package/tests/polymorphic-relationships.test.js +0 -347
  268. package/tests/polymorphic-simple.test.js +0 -148
  269. package/tests/positioning-advanced.test.js +0 -692
  270. package/tests/positioning-extended.test.js +0 -915
  271. package/tests/positioning-multifilter.test.js +0 -699
  272. package/tests/positioning.test.js +0 -560
  273. package/tests/post-commit-hooks.test.js +0 -120
  274. package/tests/projected-fields.test.js +0 -319
  275. package/tests/public-exports.test.js +0 -15
  276. package/tests/put-create-parity.test.js +0 -216
  277. package/tests/queries.test.js +0 -521
  278. package/tests/query-limits.test.js +0 -584
  279. package/tests/relationship-endpoints.test.js +0 -620
  280. package/tests/relationship-include-storage-adapter.test.js +0 -56
  281. package/tests/relationships.test.js +0 -681
  282. package/tests/rest-api.test.js +0 -337
  283. package/tests/return-record-settings.test.js +0 -184
  284. package/tests/row-policy.test.js +0 -489
  285. package/tests/s3-storage.test.js +0 -38
  286. package/tests/searchschema-knex-behavior.test.js +0 -187
  287. package/tests/searchschema-merge.test.js +0 -151
  288. package/tests/socketio.test.js +0 -860
  289. package/tests/storage-mapping.test.js +0 -362
  290. package/tests/temporal-boundaries.test.js +0 -208
  291. package/tests/test-development-guide.md +0 -370
  292. 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