json-rest-api 1.0.7 → 1.0.8
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.
- package/.claude/agents/library-guide-expert.md +29 -0
- package/.claude/agents/rest-api-internals-expert.md +53 -0
- package/CLAUDE.md +139 -1
- package/README.md +2 -5
- package/docTest.js +238 -0
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +955 -0
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +54 -0
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +750 -0
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +494 -0
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +398 -0
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +882 -0
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +709 -0
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +1233 -0
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +796 -0
- package/{README_X_Relationship_Plugin.md → docs/GUIDE/GUIDE_2_9_Relationships_Urls.md} +1 -4
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +66 -0
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +1246 -0
- package/docs/GUIDE/GUIDE_5_Non-Db_Stores.md +0 -0
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +1490 -0
- package/{README_X_SocketIO.md → docs/GUIDE/GUIDE_X_SocketIO.md} +17 -17
- package/docs/GUIDE/index.md +58 -0
- package/docs/GUIDE.md +61 -2238
- package/docs/ONBOARDING/Computed_Properties_And_Sparse_Fields.md +398 -0
- package/docs/README.md +57 -2239
- package/{README_2_Computed_and_hidden_fields.md → docs/README_VARIOUS/GUIDE_2_Computed_and_hidden_fields.md} +22 -17
- package/docs/README_VARIOUS/GUIDE_IN_DOCS.md +2277 -0
- package/{README.ORIG.md → docs/README_VARIOUS/README.ORIG.md} +8 -8
- package/{README_8_Polymorphic_Relationships.md → docs/README_VARIOUS/README_8_Polymorphic_Relationships.md} +1 -1
- package/docs/README_VARIOUS/README_IN_DOCS.ms +2277 -0
- package/{RELATIONSHIPS_ADVANCED.md → docs/README_VARIOUS/RELATIONSHIPS_ADVANCED.md} +3 -3
- package/{correct-schema-format.md → docs/README_VARIOUS/correct-schema-format.md} +13 -13
- package/{pagination.md → docs/README_VARIOUS/pagination.md} +11 -8
- package/docs/assets/css/style.scss +78 -5
- package/docs/index.md +2 -12
- package/helper-reorganization-plan.md +85 -0
- package/helper-usage-analysis.md +94 -0
- package/package.json +5 -4
- package/plugins/core/bulk-operations-plugin.js +2 -2
- package/plugins/core/connectors/express-plugin.js +90 -32
- package/plugins/core/lib/querying/knex-cross-table-search.js +609 -0
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +491 -0
- package/plugins/core/lib/querying/knex-pagination-helpers.js +544 -0
- package/plugins/core/lib/{knex-process-includes.js → querying/knex-process-includes.js} +65 -50
- package/plugins/core/lib/querying/knex-query-helpers-base.js +74 -0
- package/plugins/core/lib/{knex-query-helpers.js → querying/knex-query-helpers.js} +416 -180
- package/plugins/core/lib/{knex-relationship-includes.js → querying/knex-relationship-includes.js} +430 -114
- package/plugins/core/lib/querying/knex-window-queries.js +328 -0
- package/plugins/core/lib/querying-writing/compile-schemas.js +328 -0
- package/plugins/core/{utils → lib/querying-writing}/connectors-query-parser.js +62 -57
- package/plugins/core/lib/{database-capabilities.js → querying-writing/database-capabilities.js} +113 -7
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +369 -0
- package/plugins/core/lib/querying-writing/default-data-helpers.js +72 -0
- package/plugins/core/{utils → lib/querying-writing}/field-utils.js +41 -31
- package/plugins/core/{utils → lib/querying-writing}/knex-constants.js +1 -0
- package/plugins/core/lib/{knex-field-helpers.js → querying-writing/knex-field-helpers.js} +190 -57
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +162 -0
- package/plugins/core/lib/{payload-validators.js → querying-writing/payload-validators.js} +297 -217
- package/plugins/core/lib/querying-writing/schema-helpers.js +497 -0
- package/plugins/core/lib/querying-writing/scope-validations.js +431 -0
- package/plugins/core/lib/{simplified-helpers.js → querying-writing/simplified-helpers.js} +216 -176
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +110 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +300 -0
- package/plugins/core/lib/writing/relationship-processor.js +252 -0
- package/plugins/core/rest-api-knex-plugin.js +183 -62
- package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +7 -0
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +72 -0
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +102 -0
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +50 -0
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +39 -0
- package/plugins/core/rest-api-plugin-methods/add-route.js +23 -0
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +18 -0
- package/plugins/core/rest-api-plugin-methods/common.js +605 -0
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +115 -0
- package/plugins/core/rest-api-plugin-methods/delete.js +109 -0
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +186 -0
- package/plugins/core/rest-api-plugin-methods/get-related.js +295 -0
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +62 -0
- package/plugins/core/rest-api-plugin-methods/get.js +187 -0
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +56 -0
- package/plugins/core/rest-api-plugin-methods/patch.js +193 -0
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +104 -0
- package/plugins/core/rest-api-plugin-methods/post.js +157 -0
- package/plugins/core/rest-api-plugin-methods/put.js +257 -0
- package/plugins/core/rest-api-plugin-methods/query.js +208 -0
- package/plugins/core/rest-api-plugin.js +108 -2498
- package/plugins/core/socketio-plugin.js +28 -5
- package/plugins/storage/local-storage.js +2 -2
- package/tests/computed-fields.test.js +483 -0
- package/tests/cors.test.js +4 -4
- package/tests/curl.test.js +5 -4
- package/tests/custom-idproperty-relationships.test.js +891 -0
- package/tests/field-getters.test.js +353 -0
- package/tests/field-setters.test.js +310 -0
- package/tests/fixtures/api-configs.js +800 -28
- package/tests/jwt-auth.test.js +0 -2
- package/tests/nested-includes.test.js +1 -1
- package/tests/pagination-cursor-multifield.test.js +514 -0
- package/tests/pagination-enhanced.test.js +12 -9
- package/tests/polymorphic-relationships.test.js +10 -8
- package/tests/polymorphic-simple.test.js +6 -4
- package/tests/positioning-advanced.test.js +12 -12
- package/tests/positioning-extended.test.js +25 -25
- package/tests/positioning-multifilter.test.js +24 -24
- package/tests/positioning.test.js +3 -3
- package/tests/query-limits.test.js +24 -8
- package/tests/relationship-endpoints.test.js +4 -5
- package/tests/relationships.test.js +0 -1
- package/tests/socketio.test.js +12 -1
- package/tests/virtual-fields.test.js +13 -13
- package/PLAN_FOR_WEBSOCKETS.md +0 -398
- package/README_1_Initial_Setup.md +0 -393
- package/README_X_Hooks.md +0 -647
- package/TODO_rest-api-plugin-extras.todo +0 -23
- package/connectors_ideas/express-plugin.js +0 -120
- package/connectors_ideas/http-plugin.js +0 -138
- package/docs/examples/express-plugin.js +0 -366
- package/example.js +0 -301
- package/plugins/core/HELPER_MODULES.md +0 -285
- package/plugins/core/lib/compile-schemas.js +0 -245
- package/plugins/core/lib/database-value-normalizers.js +0 -192
- package/plugins/core/lib/default-data-helpers.js +0 -100
- package/plugins/core/lib/knex-cross-table-search.js +0 -442
- package/plugins/core/lib/knex-json-api-helpers.js +0 -84
- package/plugins/core/lib/knex-json-api-transformers.js +0 -449
- package/plugins/core/lib/knex-pagination-helpers.js +0 -193
- package/plugins/core/lib/knex-window-queries.js +0 -142
- package/plugins/core/lib/many-to-many-manipulations.js +0 -343
- package/plugins/core/lib/relationship-processor.js +0 -278
- package/plugins/core/lib/schema-helpers.js +0 -343
- package/plugins/core/lib/scope-validations.js +0 -252
- package/plugins/core/rest-api-relationships-plugin.js +0 -700
- package/plugins/core/utils/knex-query-helpers-base.js +0 -56
- /package/{README_X_Permissions.md → docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md} +0 -0
- /package/{README_X_Positioning.md → docs/GUIDE/GUIDE_6_Positioning.md} +0 -0
- /package/{README_X_Bulk_Operations.md → docs/GUIDE/GUIDE_X_Bulk_Operations.md} +0 -0
- /package/{README_X_Cors.md → docs/GUIDE/GUIDE_X_Cors.md} +0 -0
- /package/{README_X_Multihome.md → docs/GUIDE/GUIDE_X_Multihome.md} +0 -0
- /package/{README_X_Appendices.md → docs/GUIDE/GUIDE_Y_Appendices.md} +0 -0
- /package/{QUERY_DEPTH_PROTECTION.md → docs/README_VARIOUS/QUERY_DEPTH_PROTECTION.md} +0 -0
- /package/{RELATIONSHIPS.md → docs/README_VARIOUS/RELATIONSHIPS.md} +0 -0
- /package/{WEBSOCKET_IMPLEMENTATION_STATUS.md → docs/README_VARIOUS/WEBSOCKET_IMPLEMENTATION_STATUS.md} +0 -0
- /package/{helper-functions-chart.md → docs/README_VARIOUS/helper-functions-chart.md} +0 -0
- /package/docs/{implementation-hidden-computed-fields.md → README_VARIOUS/implementation-hidden-computed-fields.md} +0 -0
- /package/docs/{include-limits-migration-guide.md → README_VARIOUS/include-limits-migration-guide.md} +0 -0
- /package/{readmeSnippets → docs/README_VARIOUS}/test_put_behavior.js +0 -0
- /package/{readmeSnippets → docs/README_VARIOUS}/test_put_fields_without_defaults.js +0 -0
|
@@ -0,0 +1,431 @@
|
|
|
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
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validates all relationships in a scope configuration
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} params - Validation parameters
|
|
17
|
+
* @param {Object} params.context - Event context with scopeName and scopeOptions
|
|
18
|
+
* @param {Object} params.scopes - All registered scopes for validation
|
|
19
|
+
* @throws {Error} If any relationship is misconfigured
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Input: Valid polymorphic relationship
|
|
23
|
+
* validateRelationships({
|
|
24
|
+
* context: {
|
|
25
|
+
* scopeName: 'comments',
|
|
26
|
+
* scopeOptions: {
|
|
27
|
+
* schema: {
|
|
28
|
+
* commentable_type: { type: 'string' },
|
|
29
|
+
* commentable_id: { type: 'integer' }
|
|
30
|
+
* },
|
|
31
|
+
* relationships: {
|
|
32
|
+
* commentable: {
|
|
33
|
+
* belongsToPolymorphic: {
|
|
34
|
+
* types: ['posts', 'videos'], // Must exist
|
|
35
|
+
* typeField: 'commentable_type',
|
|
36
|
+
* idField: 'commentable_id'
|
|
37
|
+
* }
|
|
38
|
+
* }
|
|
39
|
+
* }
|
|
40
|
+
* }
|
|
41
|
+
* },
|
|
42
|
+
* scopes: { posts: {}, videos: {} } // Referenced types exist
|
|
43
|
+
* });
|
|
44
|
+
* // Output: No error (valid configuration)
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* // Input: Missing required field
|
|
48
|
+
* validateRelaationships({
|
|
49
|
+
* context: {
|
|
50
|
+
* scopeName: 'attachments',
|
|
51
|
+
* scopeOptions: {
|
|
52
|
+
* relationships: {
|
|
53
|
+
* attachable: {
|
|
54
|
+
* belongsToPolymorphic: {
|
|
55
|
+
* types: ['documents'],
|
|
56
|
+
* typeField: 'attachable_type'
|
|
57
|
+
* // Missing: idField
|
|
58
|
+
* }
|
|
59
|
+
* }
|
|
60
|
+
* }
|
|
61
|
+
* }
|
|
62
|
+
* },
|
|
63
|
+
* scopes: { documents: {} }
|
|
64
|
+
* });
|
|
65
|
+
* // Throws: Error
|
|
66
|
+
* // "Invalid polymorphic relationship 'attachable' in scope 'attachments':
|
|
67
|
+
* // belongsToPolymorphic.idField must be specified"
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* // Input: HasMany without foreignKey
|
|
71
|
+
* validateRelationships({
|
|
72
|
+
* context: {
|
|
73
|
+
* scopeName: 'users',
|
|
74
|
+
* scopeOptions: {
|
|
75
|
+
* relationships: {
|
|
76
|
+
* posts: {
|
|
77
|
+
* hasMany: '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 || {};
|
|
146
|
+
|
|
147
|
+
for (const [relName, relDef] of Object.entries(relationships)) {
|
|
148
|
+
if (relDef.belongsToPolymorphic) {
|
|
149
|
+
const validation = validatePolymorphicRelationship(relDef, scopeName, scopes);
|
|
150
|
+
if (!validation.valid) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Invalid polymorphic relationship '${relName}' in scope '${scopeName}': ${validation.error}`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Validate hasMany relationships require foreignKey
|
|
158
|
+
if (relDef.hasMany) {
|
|
159
|
+
const validation = validateHasManyRelationship(relDef, relName, scopeName);
|
|
160
|
+
if (!validation.valid) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`Invalid hasMany relationship '${relName}' in scope '${scopeName}': ${validation.error}`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Validate manyToMany relationships require both foreignKey and otherKey
|
|
168
|
+
if (relDef.manyToMany) {
|
|
169
|
+
const validation = validateManyToManyRelationship(relDef, relName, scopeName);
|
|
170
|
+
if (!validation.valid) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
`Invalid manyToMany relationship '${relName}' in scope '${scopeName}': ${validation.error}`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Validates a polymorphic relationship definition
|
|
182
|
+
*
|
|
183
|
+
* @param {Object} relDef - Relationship definition with belongsToPolymorphic
|
|
184
|
+
* @param {string} scopeName - Scope being registered (for error messages)
|
|
185
|
+
* @param {Object} scopes - All registered scopes for validation
|
|
186
|
+
* @returns {Object} Validation result {valid: boolean, error?: string}
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* // Input: Valid polymorphic configuration
|
|
190
|
+
* const relDef = {
|
|
191
|
+
* belongsToPolymorphic: {
|
|
192
|
+
* types: ['articles', 'videos', 'products'],
|
|
193
|
+
* typeField: 'commentable_type',
|
|
194
|
+
* idField: 'commentable_id'
|
|
195
|
+
* }
|
|
196
|
+
* };
|
|
197
|
+
* validatePolymorphicRelationship(relDef, 'comments', scopes);
|
|
198
|
+
* // Output: { valid: true }
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* // Input: Missing required types array
|
|
202
|
+
* const relDef = {
|
|
203
|
+
* belongsToPolymorphic: {
|
|
204
|
+
* typeField: 'commentable_type',
|
|
205
|
+
* idField: 'commentable_id'
|
|
206
|
+
* // Missing: types
|
|
207
|
+
* }
|
|
208
|
+
* };
|
|
209
|
+
* validatePolymorphicRelationship(relDef, 'comments', scopes);
|
|
210
|
+
* // Output: {
|
|
211
|
+
* // valid: false,
|
|
212
|
+
* // error: 'belongsToPolymorphic.types must be a non-empty array'
|
|
213
|
+
* // }
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* // Input: References non-existent scope
|
|
217
|
+
* const relDef = {
|
|
218
|
+
* belongsToPolymorphic: {
|
|
219
|
+
* types: ['articles', 'unicorns'], // 'unicorns' doesn't exist
|
|
220
|
+
* typeField: 'attachable_type',
|
|
221
|
+
* idField: 'attachable_id'
|
|
222
|
+
* }
|
|
223
|
+
* };
|
|
224
|
+
* validatePolymorphicRelationship(relDef, 'attachments', scopes);
|
|
225
|
+
* // Output: {
|
|
226
|
+
* // valid: false,
|
|
227
|
+
* // error: "Polymorphic type 'unicorns' is not a registered scope"
|
|
228
|
+
* // }
|
|
229
|
+
*
|
|
230
|
+
* @private
|
|
231
|
+
*/
|
|
232
|
+
const validatePolymorphicRelationship = (relDef, scopeName, scopes) => {
|
|
233
|
+
// Validation logic:
|
|
234
|
+
// 1. Check relDef.belongsToPolymorphic exists
|
|
235
|
+
// 2. Validate required properties: types, typeField, idField
|
|
236
|
+
// 3. Ensure types is non-empty array
|
|
237
|
+
// 4. Verify all types are registered scopes
|
|
238
|
+
// 5. Check that typeField and idField exist in the schema
|
|
239
|
+
|
|
240
|
+
const { belongsToPolymorphic } = relDef;
|
|
241
|
+
|
|
242
|
+
if (!belongsToPolymorphic) {
|
|
243
|
+
return { valid: false, error: 'Missing belongsToPolymorphic definition' };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const { types, typeField, idField } = belongsToPolymorphic;
|
|
247
|
+
|
|
248
|
+
if (!types || !Array.isArray(types) || types.length === 0) {
|
|
249
|
+
return {
|
|
250
|
+
valid: false,
|
|
251
|
+
error: 'belongsToPolymorphic.types must be a non-empty array'
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!typeField || typeof typeField !== 'string') {
|
|
256
|
+
return {
|
|
257
|
+
valid: false,
|
|
258
|
+
error: 'belongsToPolymorphic.typeField must be specified'
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (!idField || typeof idField !== 'string') {
|
|
263
|
+
return {
|
|
264
|
+
valid: false,
|
|
265
|
+
error: 'belongsToPolymorphic.idField must be specified'
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Check that all types are valid scopes
|
|
270
|
+
for (const type of types) {
|
|
271
|
+
if (!scopes[type]) {
|
|
272
|
+
return {
|
|
273
|
+
valid: false,
|
|
274
|
+
error: `Polymorphic type '${type}' is not a registered scope`
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return { valid: true };
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Validates a hasMany relationship definition
|
|
284
|
+
*
|
|
285
|
+
* @param {Object} relDef - Relationship definition with hasMany
|
|
286
|
+
* @param {string} relName - Relationship name
|
|
287
|
+
* @param {string} scopeName - Scope being registered
|
|
288
|
+
* @returns {Object} Validation result {valid: boolean, error?: string}
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
* // Input: Valid hasMany configuration
|
|
292
|
+
* const relDef = {
|
|
293
|
+
* hasMany: 'posts',
|
|
294
|
+
* foreignKey: 'author_id' // Required!
|
|
295
|
+
* };
|
|
296
|
+
* validateHasManyRelationship(relDef, 'posts', 'users');
|
|
297
|
+
* // Output: { valid: true }
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* // Input: Missing required foreignKey
|
|
301
|
+
* const relDef = {
|
|
302
|
+
* hasMany: 'posts'
|
|
303
|
+
* // Missing: foreignKey
|
|
304
|
+
* };
|
|
305
|
+
* validateHasManyRelationship(relDef, 'posts', 'users');
|
|
306
|
+
* // Output: {
|
|
307
|
+
* // valid: false,
|
|
308
|
+
* // error: 'hasMany relationship requires foreignKey to be specified...'
|
|
309
|
+
* // }
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* // Input: Polymorphic hasMany with 'via' (doesn't need foreignKey)
|
|
313
|
+
* const relDef = {
|
|
314
|
+
* hasMany: 'comments',
|
|
315
|
+
* via: 'commentable' // Uses polymorphic relationship
|
|
316
|
+
* };
|
|
317
|
+
* validateHasManyRelationship(relDef, 'comments', 'posts');
|
|
318
|
+
* // Output: { valid: true }
|
|
319
|
+
*
|
|
320
|
+
* @private
|
|
321
|
+
*/
|
|
322
|
+
const validateHasManyRelationship = (relDef, relName, scopeName) => {
|
|
323
|
+
// Polymorphic hasMany relationships using 'via' don't need foreignKey
|
|
324
|
+
// They use the polymorphic fields (typeField, idField) from the belongsToPolymorphic relationship
|
|
325
|
+
if (relDef.via) {
|
|
326
|
+
return { valid: true };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (!relDef.foreignKey) {
|
|
330
|
+
return {
|
|
331
|
+
valid: false,
|
|
332
|
+
error: `hasMany relationship requires foreignKey to be specified. Add foreignKey: '<field_name>' to the relationship definition.`
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (typeof relDef.foreignKey !== 'string') {
|
|
337
|
+
return {
|
|
338
|
+
valid: false,
|
|
339
|
+
error: `hasMany relationship foreignKey must be a string, got ${typeof relDef.foreignKey}`
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// If through is specified, also require otherKey for many-to-many
|
|
344
|
+
if (relDef.through && !relDef.otherKey) {
|
|
345
|
+
return {
|
|
346
|
+
valid: false,
|
|
347
|
+
error: `hasMany relationship with 'through' requires both foreignKey and otherKey to be specified`
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return { valid: true };
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Validates a manyToMany relationship definition
|
|
356
|
+
*
|
|
357
|
+
* @param {Object} relDef - Relationship definition with manyToMany
|
|
358
|
+
* @param {string} relName - Relationship name
|
|
359
|
+
* @param {string} scopeName - Scope being registered
|
|
360
|
+
* @returns {Object} Validation result {valid: boolean, error?: string}
|
|
361
|
+
*
|
|
362
|
+
* @example
|
|
363
|
+
* // Input: Valid manyToMany configuration
|
|
364
|
+
* const relDef = {
|
|
365
|
+
* manyToMany: {
|
|
366
|
+
* through: 'article_tags', // Pivot table
|
|
367
|
+
* foreignKey: 'article_id', // This scope's FK
|
|
368
|
+
* otherKey: 'tag_id' // Other scope's FK
|
|
369
|
+
* }
|
|
370
|
+
* };
|
|
371
|
+
* validateManyToManyRelationship(relDef, 'tags', 'articles');
|
|
372
|
+
* // Output: { valid: true }
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* // Input: Missing required fields
|
|
376
|
+
* const relDef = {
|
|
377
|
+
* manyToMany: {
|
|
378
|
+
* through: 'article_tags',
|
|
379
|
+
* foreignKey: 'article_id'
|
|
380
|
+
* // Missing: otherKey
|
|
381
|
+
* }
|
|
382
|
+
* };
|
|
383
|
+
* validateManyToManyRelationship(relDef, 'tags', 'articles');
|
|
384
|
+
* // Output: {
|
|
385
|
+
* // valid: false,
|
|
386
|
+
* // error: 'manyToMany relationship requires otherKey to be specified...'
|
|
387
|
+
* // }
|
|
388
|
+
*
|
|
389
|
+
* @private
|
|
390
|
+
*/
|
|
391
|
+
const validateManyToManyRelationship = (relDef, relName, scopeName) => {
|
|
392
|
+
const { through, foreignKey, otherKey } = relDef.manyToMany;
|
|
393
|
+
|
|
394
|
+
if (!through) {
|
|
395
|
+
return {
|
|
396
|
+
valid: false,
|
|
397
|
+
error: `manyToMany relationship requires 'through' table to be specified`
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (!foreignKey) {
|
|
402
|
+
return {
|
|
403
|
+
valid: false,
|
|
404
|
+
error: `manyToMany relationship requires foreignKey to be specified. Add foreignKey: '<field_name>' to the manyToMany configuration.`
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (!otherKey) {
|
|
409
|
+
return {
|
|
410
|
+
valid: false,
|
|
411
|
+
error: `manyToMany relationship requires otherKey to be specified. Add otherKey: '<field_name>' to the manyToMany configuration.`
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (typeof foreignKey !== 'string') {
|
|
416
|
+
return {
|
|
417
|
+
valid: false,
|
|
418
|
+
error: `manyToMany relationship foreignKey must be a string, got ${typeof foreignKey}`
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (typeof otherKey !== 'string') {
|
|
423
|
+
return {
|
|
424
|
+
valid: false,
|
|
425
|
+
error: `manyToMany relationship otherKey must be a string, got ${typeof otherKey}`
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return { valid: true };
|
|
430
|
+
};
|
|
431
|
+
|