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.
- package/LICENSE +10 -670
- package/LICENSE-GPL-3.0 +674 -0
- package/LICENSE-MIT +21 -0
- package/README.md +39 -30
- package/docs/API.md +744 -1273
- package/docs/GUIDE/01-getting-started.md +338 -0
- package/docs/GUIDE/02-resources-and-relationships.md +33 -0
- package/docs/GUIDE/03-running-example.md +87 -0
- package/docs/GUIDE/04-creating-and-querying.md +181 -0
- package/docs/GUIDE/05-belongs-to.md +177 -0
- package/docs/GUIDE/06-has-many.md +184 -0
- package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
- package/docs/GUIDE/08-many-to-many.md +184 -0
- package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
- package/docs/GUIDE/10-put-and-patch.md +174 -0
- package/docs/GUIDE/11-relationship-endpoints.md +174 -0
- package/docs/GUIDE/12-field-transformations.md +287 -0
- package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
- package/docs/GUIDE/14-custom-resource-methods.md +125 -0
- package/docs/GUIDE/15-query-projections.md +266 -0
- package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
- package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
- package/docs/GUIDE/18-labels.md +86 -0
- package/docs/GUIDE/19-managed-transactions.md +179 -0
- package/docs/GUIDE/20-transaction-outcomes.md +207 -0
- package/docs/GUIDE/21-schema-and-migrations.md +634 -0
- package/docs/GUIDE/22-version-field-migrations.md +208 -0
- package/docs/GUIDE/23-fastify.md +179 -0
- package/docs/GUIDE/24-cors.md +172 -0
- package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
- package/docs/GUIDE/26-file-uploads.md +278 -0
- package/docs/GUIDE/27-bulk-operations.md +277 -0
- package/docs/GUIDE/28-socketio.md +325 -0
- package/docs/GUIDE/29-writing-plugins.md +278 -0
- package/docs/GUIDE/30-backend-capabilities.md +100 -0
- package/docs/GUIDE/31-positioning.md +181 -0
- package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +74 -30
- package/docs/GUIDE/33-migrating-to-v2.md +3827 -0
- package/docs/GUIDE/index.md +51 -59
- package/docs/QUICKSTART.md +120 -287
- package/docs/architecture.md +75 -0
- package/docs/contributing.md +178 -0
- package/index.d.ts +36 -0
- package/index.js +1 -22
- package/lib/enhanced-logger.js +91 -85
- package/lib/error-context.js +445 -5
- package/lib/error-formatter.js +219 -59
- package/lib/knex-transaction.js +64 -0
- package/lib/logger-types.d.ts +22 -0
- package/lib/missing-package.js +4 -0
- package/lib/rest-api-errors.js +103 -8
- package/lib/runtime/json-rest-api.js +172 -0
- package/lib/transaction-types.d.ts +59 -0
- package/package.json +97 -27
- package/plugins/core/bulk-operations-plugin.js +191 -184
- package/plugins/core/connectors/express-plugin.js +148 -219
- package/plugins/core/connectors/fastify-plugin.js +125 -82
- package/plugins/core/connectors/lib/busboy-detector.js +74 -156
- package/plugins/core/connectors/lib/connector-core.js +144 -20
- package/plugins/core/connectors/lib/formidable-detector.js +63 -120
- package/plugins/core/connectors/lib/http-validators.js +77 -0
- package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
- package/plugins/core/connectors/lib/request-helpers.js +10 -1
- package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
- package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
- package/plugins/core/file-handling-plugin.js +87 -177
- package/plugins/core/lib/anyapi/anyapi-registry.js +213 -164
- package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
- package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
- package/plugins/core/lib/anyapi/schema-utils.js +25 -16
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
- package/plugins/core/lib/dbIntrospection.js +228 -21
- package/plugins/core/lib/dbTablesOperations.js +409 -92
- package/plugins/core/lib/fractional-positioning.js +12 -48
- package/plugins/core/lib/querying/identifier-query.js +19 -0
- package/plugins/core/lib/querying/include-query-helpers.js +364 -0
- package/plugins/core/lib/querying/include-to-many.js +428 -0
- package/plugins/core/lib/querying/include-to-one.js +579 -0
- package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
- package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
- package/plugins/core/lib/querying/knex-process-includes.js +5 -110
- package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
- package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
- package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
- package/plugins/core/lib/querying/knex-window-queries.js +62 -253
- package/plugins/core/lib/querying/query-builder-utils.js +44 -4
- package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
- package/plugins/core/lib/querying/query-constraint.js +27 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
- package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
- package/plugins/core/lib/querying/sort-helpers.js +13 -1
- package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
- package/plugins/core/lib/querying/url-helpers.js +11 -4
- package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
- package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
- package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
- package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
- package/plugins/core/lib/querying-writing/field-utils.js +113 -92
- package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
- package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
- package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
- package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
- package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
- package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
- package/plugins/core/lib/querying-writing/response-options.js +35 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
- package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
- package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
- package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
- package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
- package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
- package/plugins/core/lib/storage/storage-adapter.js +128 -62
- package/plugins/core/lib/storage/storage-mapping.js +78 -28
- package/plugins/core/lib/storage/storage-types.d.ts +502 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
- package/plugins/core/lib/writing/relationship-processor.js +94 -158
- package/plugins/core/lib/writing/resource-version.js +172 -0
- package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +116 -2485
- package/plugins/core/rest-api-autofilter-plugin.js +17 -19
- package/plugins/core/rest-api-cors-plugin.js +52 -205
- package/plugins/core/rest-api-knex-plugin.js +45 -1077
- package/plugins/core/rest-api-label-plugin.js +25 -38
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
- package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
- package/plugins/core/rest-api-plugin-methods/common.js +291 -395
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
- package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
- package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
- package/plugins/core/rest-api-plugin-methods/get.js +36 -68
- package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
- package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
- package/plugins/core/rest-api-plugin-methods/post.js +51 -51
- package/plugins/core/rest-api-plugin-methods/put.js +69 -148
- package/plugins/core/rest-api-plugin-methods/query.js +37 -81
- package/plugins/core/rest-api-plugin-methods/release.js +2 -1
- package/plugins/core/rest-api-plugin.js +32 -46
- package/plugins/core/rest-api-positioning-plugin.js +183 -628
- package/plugins/core/rest-api-query-projections-plugin.js +19 -102
- package/plugins/core/rest-api-row-policy-plugin.js +3 -3
- package/plugins/core/socketio-plugin.js +364 -344
- package/plugins/storage/local-storage.js +40 -36
- package/types/autofilter.d.ts +57 -0
- package/types/bulk-methods.d.ts +63 -0
- package/types/errors.d.ts +118 -0
- package/types/fastify-options.d.ts +19 -0
- package/types/file-detectors.d.ts +33 -0
- package/types/file-storage.d.ts +48 -0
- package/types/hook-context.d.ts +180 -0
- package/types/plugin-options.d.ts +53 -0
- package/types/relationship-methods.d.ts +74 -0
- package/types/representations.d.ts +50 -0
- package/types/resource-methods.d.ts +95 -0
- package/types/resource-schema.d.ts +157 -0
- package/types/row-policy.d.ts +31 -0
- package/types/runtime.d.ts +132 -0
- package/types/transactions.d.ts +21 -0
- package/.claude/agents/library-guide-expert.md +0 -30
- package/.claude/agents/rest-api-internals-expert.md +0 -54
- package/.claude/settings.local.json +0 -70
- package/AGENTS.md +0 -34
- package/CLAUDE.md +0 -160
- package/CRUSH.md +0 -55
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
- package/TODO.todo +0 -13
- package/connectors_ideas/fastify.js +0 -217
- package/connectors_ideas/koa-plugin.js +0 -112
- package/docs/.bundle/config +0 -2
- package/docs/COMPARISON.md +0 -433
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
- package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
- package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
- package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
- package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
- package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
- package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
- package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
- package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
- package/docs/Gemfile +0 -6
- package/docs/Gemfile.lock +0 -308
- package/docs/ONBOARDING.md +0 -315
- package/docs/_config.yml +0 -36
- package/docs/_layouts/default.html +0 -123
- package/docs/apple-touch-icon.svg +0 -4
- package/docs/assets/css/style.scss +0 -392
- package/docs/favicon.svg +0 -4
- package/docs/index.md +0 -164
- package/docs/mini-guides/file-uploads-complete.md +0 -161
- package/eslint.config.js +0 -13
- package/examples/url-override-example.js +0 -137
- package/fixing_plan.md +0 -112
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
- package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
- package/plugins/storage/s3-storage.js +0 -93
- package/quickTest.js +0 -136
- package/scripts/debug-anyapi-review-filter.mjs +0 -63
- package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
- package/scripts/inspect-anyapi-descriptor.mjs +0 -19
- package/scripts/run-docs.js +0 -133
- package/test-debug.js +0 -59
- package/tests/README.md +0 -202
- package/tests/TEST_TEMPLATE.test.js +0 -178
- package/tests/anyapi-basic.test.js +0 -105
- package/tests/anyapi-cursor-pagination.test.js +0 -148
- package/tests/anyapi-custom-idproperty.test.js +0 -163
- package/tests/autofilter.test.js +0 -463
- package/tests/bulk-operations.test.js +0 -657
- package/tests/computed-fields.test.js +0 -586
- package/tests/cors.test.js +0 -834
- package/tests/curl.test.js +0 -467
- package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
- package/tests/custom-id-simplified-post.test.js +0 -75
- package/tests/custom-idproperty-relationships.test.js +0 -932
- package/tests/db-introspection.test.js +0 -521
- package/tests/db-table-operations.test.js +0 -798
- package/tests/fastify-plugin.test.js +0 -184
- package/tests/field-getters.test.js +0 -370
- package/tests/field-setters.test.js +0 -306
- package/tests/file-handling.test.js +0 -155
- package/tests/fixtures/api-configs.js +0 -2566
- package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
- package/tests/helpers/fake-fastify.js +0 -99
- package/tests/helpers/storage-mode.js +0 -65
- package/tests/helpers/test-utils.js +0 -203
- package/tests/http-connectors-parity.test.js +0 -395
- package/tests/id-normalization.test.js +0 -444
- package/tests/include-depth-validation.test.js +0 -331
- package/tests/includes.test.js +0 -427
- package/tests/local-storage.test.js +0 -96
- package/tests/logical-id-write-validation.test.js +0 -151
- package/tests/nested-includes.test.js +0 -617
- package/tests/pagination-boundaries.test.js +0 -154
- package/tests/pagination-contract.test.js +0 -122
- package/tests/pagination-cursor-multifield.test.js +0 -641
- package/tests/pagination-enhanced.test.js +0 -563
- package/tests/polymorphic-relationships.test.js +0 -347
- package/tests/polymorphic-simple.test.js +0 -148
- package/tests/positioning-advanced.test.js +0 -692
- package/tests/positioning-extended.test.js +0 -915
- package/tests/positioning-multifilter.test.js +0 -699
- package/tests/positioning.test.js +0 -560
- package/tests/post-commit-hooks.test.js +0 -120
- package/tests/projected-fields.test.js +0 -319
- package/tests/public-exports.test.js +0 -15
- package/tests/put-create-parity.test.js +0 -216
- package/tests/queries.test.js +0 -521
- package/tests/query-limits.test.js +0 -584
- package/tests/relationship-endpoints.test.js +0 -620
- package/tests/relationship-include-storage-adapter.test.js +0 -56
- package/tests/relationships.test.js +0 -681
- package/tests/rest-api.test.js +0 -337
- package/tests/return-record-settings.test.js +0 -184
- package/tests/row-policy.test.js +0 -489
- package/tests/s3-storage.test.js +0 -38
- package/tests/searchschema-knex-behavior.test.js +0 -187
- package/tests/searchschema-merge.test.js +0 -151
- package/tests/socketio.test.js +0 -860
- package/tests/storage-mapping.test.js +0 -362
- package/tests/temporal-boundaries.test.js +0 -208
- package/tests/test-development-guide.md +0 -370
- 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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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 (
|
|
159
|
-
const validation = validateHasManyRelationship(
|
|
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 '${
|
|
45
|
+
`Invalid hasMany relationship '${name}' in scope '${scopeName}': ${validation.error}`
|
|
163
46
|
)
|
|
164
47
|
}
|
|
165
48
|
}
|
|
166
49
|
|
|
167
|
-
if (
|
|
168
|
-
const validation = validateHasOneRelationship(
|
|
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 '${
|
|
54
|
+
`Invalid hasOne relationship '${name}' in scope '${scopeName}': ${validation.error}`
|
|
172
55
|
)
|
|
173
56
|
}
|
|
174
57
|
}
|
|
175
58
|
|
|
176
|
-
if (
|
|
177
|
-
const validation = validateManyToManyRelationship(
|
|
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 '${
|
|
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
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
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
|
-
* @
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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 (!
|
|
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
|
-
*
|
|
291
|
-
*
|
|
292
|
-
* @param {
|
|
293
|
-
* @param {string}
|
|
294
|
-
* @
|
|
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
|
-
*
|
|
412
|
-
*
|
|
413
|
-
* @param {
|
|
414
|
-
* @param {string}
|
|
415
|
-
* @
|
|
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
|