json-rest-api 1.0.22 → 1.0.23
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.md +1 -2
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
- package/docs/API.md +51 -14
- package/docs/COMPARISON.md +2 -2
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +28 -1
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
- package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
- package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +250 -0
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
- package/docs/GUIDE/index.md +18 -9
- package/docs/QUICKSTART.md +5 -1
- package/docs/index.md +1 -1
- package/index.js +3 -2
- package/package.json +2 -2
- package/plugins/core/connectors/express-plugin.js +97 -184
- package/plugins/core/connectors/fastify-plugin.js +206 -0
- package/plugins/core/connectors/lib/connector-core.js +195 -0
- package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
- package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
- package/plugins/core/lib/dbIntrospection.js +1078 -0
- package/plugins/core/lib/dbTablesOperations.js +1104 -160
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -2
- package/plugins/core/lib/querying/knex-process-includes.js +3 -4
- package/plugins/core/lib/querying/knex-query-helpers.js +17 -71
- package/plugins/core/lib/querying/knex-relationship-includes.js +466 -206
- package/plugins/core/lib/querying/query-builder-utils.js +19 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
- package/plugins/core/lib/querying/sort-helpers.js +33 -0
- package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
- package/plugins/core/lib/querying/url-helpers.js +8 -2
- package/plugins/core/lib/querying-writing/compile-schemas.js +12 -1
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -13
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
- package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
- package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
- package/plugins/core/lib/storage/canonical-storage-mapping.js +113 -0
- package/plugins/core/lib/storage/storage-adapter.js +79 -2
- package/plugins/core/lib/storage/storage-mapping.js +204 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +355 -228
- package/plugins/core/rest-api-autofilter-plugin.js +414 -0
- package/plugins/core/rest-api-knex-plugin.js +249 -211
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
- package/plugins/core/rest-api-plugin-methods/common.js +121 -83
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
- package/plugins/core/rest-api-plugin-methods/get.js +23 -14
- package/plugins/core/rest-api-plugin-methods/patch.js +28 -12
- package/plugins/core/rest-api-plugin-methods/post.js +13 -8
- package/plugins/core/rest-api-plugin-methods/put.js +45 -21
- package/plugins/core/rest-api-plugin-methods/query.js +15 -53
- package/plugins/core/rest-api-plugin.js +2 -0
- package/plugins/core/rest-api-positioning-plugin.js +1 -1
- package/plugins/core/rest-api-query-projections-plugin.js +130 -0
- package/plugins/core/socketio-plugin.js +1 -3
- package/tests/autofilter.test.js +463 -0
- package/tests/custom-id-simplified-post.test.js +75 -0
- package/tests/custom-idproperty-relationships.test.js +33 -0
- package/tests/db-introspection.test.js +518 -0
- package/tests/db-table-operations.test.js +781 -0
- package/tests/fastify-plugin.test.js +141 -0
- package/tests/field-getters.test.js +1 -2
- package/tests/field-setters.test.js +0 -4
- package/tests/fixtures/api-configs.js +188 -219
- package/tests/helpers/fake-fastify.js +99 -0
- package/tests/http-connectors-parity.test.js +235 -0
- package/tests/projected-fields.test.js +319 -0
- package/tests/relationships.test.js +3 -3
- package/tests/searchschema-knex-behavior.test.js +187 -0
- package/tests/searchschema-merge.test.js +21 -69
- package/tests/storage-mapping.test.js +210 -0
- package/audit/00-charter.md +0 -103
- package/audit/01-scope-map.md +0 -84
- package/audit/02-coverage-matrix.csv +0 -174
- package/audit/03-invariants.md +0 -123
- package/audit/04-standards-rubric.md +0 -56
- package/audit/05-competitor-rubric.md +0 -77
- package/audit/06-findings.md +0 -160
- package/audit/07-risk-register.csv +0 -10
- package/audit/08-remediation-roadmap.md +0 -177
- package/audit/09-signoff-check.md +0 -23
- package/audit/10-final-synthesis.md +0 -29
- package/audit/evidence/code/all-files-json-rest-api.txt +0 -168
- package/audit/evidence/code/all-files-json-rest-schema.txt +0 -12
- package/audit/evidence/code/boundary-leak-checks.txt +0 -72
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__bulk-operations-plugin.js.txt +0 -480
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__express-plugin.js.txt +0 -532
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__busboy-detector.js.txt +0 -175
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__formidable-detector.js.txt +0 -138
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__request-helpers.js.txt +0 -222
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt +0 -342
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__dbTablesOperations.js.txt +0 -304
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__fractional-positioning.js.txt +0 -188
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__compile-schemas.js.txt +0 -336
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__connectors-query-parser.js.txt +0 -165
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-capabilities.js.txt +0 -265
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-value-normalizers.js.txt +0 -369
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__default-data-helpers.js.txt +0 -72
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__field-utils.js.txt +0 -139
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-constants.js.txt +0 -45
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-field-helpers.js.txt +0 -396
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-json-api-transformers.js.txt +0 -162
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__payload-validators.js.txt +0 -1314
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__schema-helpers.js.txt +0 -488
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__scope-validations.js.txt +0 -484
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__simplified-helpers.js.txt +0 -857
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-cross-table-search.js.txt +0 -565
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-json-api-transformers-querying.js.txt +0 -485
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-pagination-helpers.js.txt +0 -560
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-process-includes.js.txt +0 -167
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers-base.js.txt +0 -109
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers.js.txt +0 -1064
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-relationship-includes.js.txt +0 -1983
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-window-queries.js.txt +0 -328
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__url-helpers.js.txt +0 -119
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__storage__storage-adapter.js.txt +0 -160
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__many-to-many-manipulations.js.txt +0 -300
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__relationship-processor.js.txt +0 -245
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-access.js.txt +0 -459
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-cors-plugin.js.txt +0 -237
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-knex-plugin.js.txt +0 -1135
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-multihome-plugin.js.txt +0 -314
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__compile-resource-schemas.js.txt +0 -7
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-relationship-routes.js.txt +0 -71
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-scope-routes.js.txt +0 -102
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__turn-scope-init-into-vars.js.txt +0 -49
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__validate-include-configurations.js.txt +0 -39
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__add-route.js.txt +0 -23
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__check-permissions.js.txt +0 -13
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__common.js.txt +0 -639
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete-relationship.js.txt +0 -117
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete.js.txt +0 -104
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__enrich-attributes.js.txt +0 -199
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-related.js.txt +0 -353
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-relationship.js.txt +0 -61
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get.js.txt +0 -184
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch-relationship.js.txt +0 -55
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch.js.txt +0 -191
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post-relationship.js.txt +0 -110
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post.js.txt +0 -168
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__put.js.txt +0 -253
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__query.js.txt +0 -207
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__release.js.txt +0 -12
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin.js.txt +0 -165
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-positioning-plugin.js.txt +0 -668
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__socketio-plugin.js.txt +0 -621
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__local-storage.js.txt +0 -218
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__s3-storage.js.txt +0 -124
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__CorePlugin.js.txt +0 -269
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__Schema.js.txt +0 -242
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__index.js.txt +0 -77
- package/audit/evidence/code/critical-pass1-risk-keywords.txt +0 -238
- package/audit/evidence/code/critical-pass1-summary.csv +0 -67
- package/audit/evidence/code/critical-pass2-summary.csv +0 -67
- package/audit/evidence/code/early-silent-zones-no-test-token.txt +0 -57
- package/audit/evidence/code/error-model-signals.txt +0 -98
- package/audit/evidence/code/git-files-json-rest-api.txt +0 -171
- package/audit/evidence/code/git-files-json-rest-schema.txt +0 -27
- package/audit/evidence/code/hook-flow-signals.txt +0 -282
- package/audit/evidence/code/idproperty-signals.txt +0 -157
- package/audit/evidence/code/import-cycles-non-anyapi.txt +0 -3
- package/audit/evidence/code/import-hotspots-non-anyapi.txt +0 -96
- package/audit/evidence/code/imports-json-rest-api.txt +0 -354
- package/audit/evidence/code/imports-json-rest-schema.txt +0 -15
- package/audit/evidence/code/in-scope-files-json-rest-api.txt +0 -160
- package/audit/evidence/code/in-scope-files-json-rest-schema.txt +0 -13
- package/audit/evidence/code/non-anyapi-core-files.txt +0 -101
- package/audit/evidence/code/permission-signals.txt +0 -193
- package/audit/evidence/code/query-include-signals.txt +0 -687
- package/audit/evidence/code/transaction-signals.txt +0 -28
- package/audit/evidence/code/validation-signals.txt +0 -650
- package/audit/evidence/code/write-path-signals.txt +0 -61
- package/audit/evidence/docs/docs-claims-signals.txt +0 -1075
- package/audit/evidence/docs/schema-docs-claims-signals.txt +0 -179
- package/audit/evidence/external/corpus-freeze.md +0 -21
- package/audit/evidence/external/npm-competitor-metadata.txt +0 -70
- package/audit/evidence/external/npm-download-signals.txt +0 -23
- package/audit/evidence/external/npm-jsonapi-server.txt +0 -8
- package/audit/evidence/external/npm-modern-jsonapi-metadata.txt +0 -38
- package/audit/evidence/external/readme-fortune-signals.txt +0 -26
- package/audit/evidence/external/readme-fortune.md +0 -165
- package/audit/evidence/external/readme-json-api-serializer-signals.txt +0 -80
- package/audit/evidence/external/readme-json-api-serializer.md +0 -690
- package/audit/evidence/external/readme-jsonapi-serde-server-signals.txt +0 -0
- package/audit/evidence/external/readme-jsonapi-serde-server.md +0 -22
- package/audit/evidence/external/readme-jsonapi-serializer-signals.txt +0 -63
- package/audit/evidence/external/readme-jsonapi-serializer.md +0 -315
- package/audit/evidence/external/readme-jsonapi-server-signals.txt +0 -19
- package/audit/evidence/external/readme-jsonapi-server.md +0 -104
- package/audit/evidence/external/readme-nestjs-json-api-signals.txt +0 -4
- package/audit/evidence/external/readme-nestjs-json-api.md +0 -94
- package/audit/evidence/external/readme-yaser-json-api-nestjs-signals.txt +0 -47
- package/audit/evidence/external/readme-yaser-json-api-nestjs.md +0 -447
- package/audit/evidence/external/standard-jsonapi-snippets.txt +0 -84
- package/audit/evidence/external/standard-rfc-snippets.txt +0 -168
- package/audit/evidence/external/standards-sources.txt +0 -6
- package/audit/evidence/runtime/baseline-commits.txt +0 -2
- package/audit/evidence/runtime/db-adapter-assumptions.txt +0 -3
- package/audit/evidence/runtime/environment.txt +0 -4
- package/audit/evidence/runtime/invariant-runtime-mapping.md +0 -38
- package/audit/evidence/runtime/probe-patch-relationship-error.exitcode +0 -1
- package/audit/evidence/runtime/probe-patch-relationship-error.log +0 -76
- package/audit/evidence/runtime/probe-patch-relationship-error.mjs +0 -67
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.exitcode +0 -1
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.log +0 -62
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.mjs +0 -47
- package/audit/evidence/runtime/test-exitcodes.txt +0 -4
- package/audit/evidence/runtime/test-summary.md +0 -107
- package/audit/evidence/runtime/trace-anchors.txt +0 -61
- package/audit/evidence/tests/access-multitenancy-test-signals.txt +0 -125
- package/audit/evidence/tests/failing-tests-summary.txt +0 -25
- package/audit/evidence/tests/npm-test-json-rest-api.exitcode +0 -1
- package/audit/evidence/tests/npm-test-json-rest-api.log +0 -5885
- package/audit/evidence/tests/npm-test-json-rest-schema.exitcode +0 -1
- package/audit/evidence/tests/npm-test-json-rest-schema.log +0 -356
- package/audit/evidence/tests/query-include-test-signals.txt +0 -948
- package/audit/evidence/tests/relationship-test-signals.txt +0 -223
- package/audit/evidence/tests/skipped-tests.txt +0 -10
- package/audit/evidence/tests/test-suite-list-non-anyapi.txt +0 -30
- package/audit/evidence/tests/test-suite-list.txt +0 -32
- package/audit/subsystems/docs-drift.md +0 -49
- package/audit/subsystems/pass2-cross-review.md +0 -131
- package/audit/subsystems/plugins-access.md +0 -51
- package/audit/subsystems/plugins-bulk.md +0 -50
- package/audit/subsystems/plugins-cors.md +0 -48
- package/audit/subsystems/plugins-file.md +0 -53
- package/audit/subsystems/plugins-multihome.md +0 -50
- package/audit/subsystems/plugins-positioning.md +0 -54
- package/audit/subsystems/plugins-socketio.md +0 -50
- package/audit/subsystems/query-engine.md +0 -61
- package/audit/subsystems/relationships.md +0 -55
- package/audit/subsystems/rest-orchestration.md +0 -66
- package/audit/subsystems/schema-core.md +0 -64
- package/audit/subsystems/storage-knex.md +0 -57
- package/audit/subsystems/tests.md +0 -48
- package/audit/subsystems/transport-express.md +0 -55
- package/audit/traces/trace-read-get.md +0 -22
- package/audit/traces/trace-read-query.md +0 -25
- package/audit/traces/trace-relationship-endpoints.md +0 -31
- package/audit/traces/trace-write-delete.md +0 -17
- package/audit/traces/trace-write-patch.md +0 -26
- package/audit/traces/trace-write-post.md +0 -22
- package/audit/traces/trace-write-put.md +0 -23
- package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
- package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
- package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
- package/plugins/core/rest-api-access.js +0 -459
- package/plugins/core/rest-api-multihome-plugin.js +0 -314
- package/tests/multihome.test.js +0 -553
- package/tests/multihome.test.js.bak +0 -550
- package/tests/ownership-optout.test.js +0 -76
- package/tests/rest-api-access.test.js +0 -206
|
@@ -1,484 +0,0 @@
|
|
|
1
|
-
1 /**
|
|
2
|
-
2 * Scope configuration validators for relationship definitions
|
|
3
|
-
3 *
|
|
4
|
-
4 * @description
|
|
5
|
-
5 * These validators run at scope registration time to ensure:
|
|
6
|
-
6 * - Polymorphic relationships are properly configured
|
|
7
|
-
7 * - HasMany relationships specify required foreign keys
|
|
8
|
-
8 * - ManyToMany relationships have pivot table settings
|
|
9
|
-
9 * - Referenced scopes actually exist in the system
|
|
10
|
-
10 */
|
|
11
|
-
11
|
|
12
|
-
12 /**
|
|
13
|
-
13 * Validates all relationships in a scope configuration
|
|
14
|
-
14 *
|
|
15
|
-
15 * @param {Object} params - Validation parameters
|
|
16
|
-
16 * @param {Object} params.context - Event context with scopeName and scopeOptions
|
|
17
|
-
17 * @param {Object} params.scopes - All registered scopes for validation
|
|
18
|
-
18 * @throws {Error} If any relationship is misconfigured
|
|
19
|
-
19 *
|
|
20
|
-
20 * @example
|
|
21
|
-
21 * // Input: Valid polymorphic relationship
|
|
22
|
-
22 * validateRelationships({
|
|
23
|
-
23 * context: {
|
|
24
|
-
24 * scopeName: 'comments',
|
|
25
|
-
25 * scopeOptions: {
|
|
26
|
-
26 * schema: {
|
|
27
|
-
27 * commentable_type: { type: 'string' },
|
|
28
|
-
28 * commentable_id: { type: 'integer' }
|
|
29
|
-
29 * },
|
|
30
|
-
30 * relationships: {
|
|
31
|
-
31 * commentable: {
|
|
32
|
-
32 * belongsToPolymorphic: {
|
|
33
|
-
33 * types: ['posts', 'videos'], // Must exist
|
|
34
|
-
34 * typeField: 'commentable_type',
|
|
35
|
-
35 * idField: 'commentable_id'
|
|
36
|
-
36 * }
|
|
37
|
-
37 * }
|
|
38
|
-
38 * }
|
|
39
|
-
39 * }
|
|
40
|
-
40 * },
|
|
41
|
-
41 * scopes: { posts: {}, videos: {} } // Referenced types exist
|
|
42
|
-
42 * });
|
|
43
|
-
43 * // Output: No error (valid configuration)
|
|
44
|
-
44 *
|
|
45
|
-
45 * @example
|
|
46
|
-
46 * // Input: Missing required field
|
|
47
|
-
47 * validateRelaationships({
|
|
48
|
-
48 * context: {
|
|
49
|
-
49 * scopeName: 'attachments',
|
|
50
|
-
50 * scopeOptions: {
|
|
51
|
-
51 * relationships: {
|
|
52
|
-
52 * attachable: {
|
|
53
|
-
53 * belongsToPolymorphic: {
|
|
54
|
-
54 * types: ['documents'],
|
|
55
|
-
55 * typeField: 'attachable_type'
|
|
56
|
-
56 * // Missing: idField
|
|
57
|
-
57 * }
|
|
58
|
-
58 * }
|
|
59
|
-
59 * }
|
|
60
|
-
60 * }
|
|
61
|
-
61 * },
|
|
62
|
-
62 * scopes: { documents: {} }
|
|
63
|
-
63 * });
|
|
64
|
-
64 * // Throws: Error
|
|
65
|
-
65 * // "Invalid polymorphic relationship 'attachable' in scope 'attachments':
|
|
66
|
-
66 * // belongsToPolymorphic.idField must be specified"
|
|
67
|
-
67 *
|
|
68
|
-
68 * @example
|
|
69
|
-
69 * // Input: HasMany without foreignKey
|
|
70
|
-
70 * validateRelationships({
|
|
71
|
-
71 * context: {
|
|
72
|
-
72 * scopeName: 'users',
|
|
73
|
-
73 * scopeOptions: {
|
|
74
|
-
74 * relationships: {
|
|
75
|
-
75 * posts: {
|
|
76
|
-
76 * type: 'hasMany',
|
|
77
|
-
77 * target: 'posts'
|
|
78
|
-
78 * // Missing: foreignKey
|
|
79
|
-
79 * }
|
|
80
|
-
80 * }
|
|
81
|
-
81 * }
|
|
82
|
-
82 * },
|
|
83
|
-
83 * scopes: { posts: {} }
|
|
84
|
-
84 * });
|
|
85
|
-
85 * // Throws: Error
|
|
86
|
-
86 * // "Invalid hasMany relationship 'posts' in scope 'users':
|
|
87
|
-
87 * // hasMany relationship requires foreignKey to be specified"
|
|
88
|
-
88 *
|
|
89
|
-
89 * @example
|
|
90
|
-
90 * // Input: Complex activity feed with multiple polymorphic relationships
|
|
91
|
-
91 * validateRelationships({
|
|
92
|
-
92 * context: {
|
|
93
|
-
93 * scopeName: 'activities',
|
|
94
|
-
94 * scopeOptions: {
|
|
95
|
-
95 * schema: {
|
|
96
|
-
96 * trackable_type: { type: 'string' },
|
|
97
|
-
97 * trackable_id: { type: 'integer' },
|
|
98
|
-
98 * actor_type: { type: 'string' },
|
|
99
|
-
99 * actor_id: { type: 'integer' }
|
|
100
|
-
100 * },
|
|
101
|
-
101 * relationships: {
|
|
102
|
-
102 * trackable: { // What was changed
|
|
103
|
-
103 * belongsToPolymorphic: {
|
|
104
|
-
104 * types: ['posts', 'comments', 'users'],
|
|
105
|
-
105 * typeField: 'trackable_type',
|
|
106
|
-
106 * idField: 'trackable_id'
|
|
107
|
-
107 * }
|
|
108
|
-
108 * },
|
|
109
|
-
109 * actor: { // Who made the change
|
|
110
|
-
110 * belongsToPolymorphic: {
|
|
111
|
-
111 * types: ['users', 'api_clients'],
|
|
112
|
-
112 * typeField: 'actor_type',
|
|
113
|
-
113 * idField: 'actor_id'
|
|
114
|
-
114 * }
|
|
115
|
-
115 * }
|
|
116
|
-
116 * }
|
|
117
|
-
117 * }
|
|
118
|
-
118 * },
|
|
119
|
-
119 * scopes: { posts: {}, comments: {}, users: {}, api_clients: {} }
|
|
120
|
-
120 * });
|
|
121
|
-
121 * // Output: No error (both polymorphic relationships valid)
|
|
122
|
-
122 *
|
|
123
|
-
123 * @description
|
|
124
|
-
124 * Used by:
|
|
125
|
-
125 * - rest-api-plugin on 'scope:added' event
|
|
126
|
-
126 * - Runs automatically when scopes are registered
|
|
127
|
-
127 *
|
|
128
|
-
128 * Purpose:
|
|
129
|
-
129 * - Catches configuration errors at startup, not runtime
|
|
130
|
-
130 * - Validates polymorphic relationship structure
|
|
131
|
-
131 * - Ensures referenced scope types exist
|
|
132
|
-
132 * - Validates hasMany/manyToMany foreign keys
|
|
133
|
-
133 * - Provides clear error messages for debugging
|
|
134
|
-
134 *
|
|
135
|
-
135 * Data flow:
|
|
136
|
-
136 * 1. Extracts relationships from scope options
|
|
137
|
-
137 * 2. For each relationship, checks its type
|
|
138
|
-
138 * 3. Validates polymorphic: types, typeField, idField
|
|
139
|
-
139 * 4. Validates hasMany: foreignKey (unless via)
|
|
140
|
-
140 * 5. Validates manyToMany: through, foreignKey, otherKey
|
|
141
|
-
141 * 6. Throws descriptive error on first validation failure
|
|
142
|
-
142 */
|
|
143
|
-
143 export function validateRelationships ({ context, scopes }) {
|
|
144
|
-
144 const { scopeName, scopeOptions } = context
|
|
145
|
-
145 const relationships = scopeOptions.relationships || {}
|
|
146
|
-
146
|
|
147
|
-
147 for (const [relName, relDef] of Object.entries(relationships)) {
|
|
148
|
-
148 if (relDef.belongsToPolymorphic) {
|
|
149
|
-
149 const validation = validatePolymorphicRelationship(relDef, scopeName, scopes)
|
|
150
|
-
150 if (!validation.valid) {
|
|
151
|
-
151 throw new Error(
|
|
152
|
-
152 `Invalid polymorphic relationship '${relName}' in scope '${scopeName}': ${validation.error}`
|
|
153
|
-
153 )
|
|
154
|
-
154 }
|
|
155
|
-
155 }
|
|
156
|
-
156
|
|
157
|
-
157 // Validate based on relationship type
|
|
158
|
-
158 if (relDef.type === 'hasMany') {
|
|
159
|
-
159 const validation = validateHasManyRelationship(relDef, relName, scopeName)
|
|
160
|
-
160 if (!validation.valid) {
|
|
161
|
-
161 throw new Error(
|
|
162
|
-
162 `Invalid hasMany relationship '${relName}' in scope '${scopeName}': ${validation.error}`
|
|
163
|
-
163 )
|
|
164
|
-
164 }
|
|
165
|
-
165 }
|
|
166
|
-
166
|
|
167
|
-
167 if (relDef.type === 'hasOne') {
|
|
168
|
-
168 const validation = validateHasOneRelationship(relDef, relName, scopeName)
|
|
169
|
-
169 if (!validation.valid) {
|
|
170
|
-
170 throw new Error(
|
|
171
|
-
171 `Invalid hasOne relationship '${relName}' in scope '${scopeName}': ${validation.error}`
|
|
172
|
-
172 )
|
|
173
|
-
173 }
|
|
174
|
-
174 }
|
|
175
|
-
175
|
|
176
|
-
176 if (relDef.type === 'manyToMany') {
|
|
177
|
-
177 const validation = validateManyToManyRelationship(relDef, relName, scopeName)
|
|
178
|
-
178 if (!validation.valid) {
|
|
179
|
-
179 throw new Error(
|
|
180
|
-
180 `Invalid manyToMany relationship '${relName}' in scope '${scopeName}': ${validation.error}`
|
|
181
|
-
181 )
|
|
182
|
-
182 }
|
|
183
|
-
183 }
|
|
184
|
-
184 }
|
|
185
|
-
185 }
|
|
186
|
-
186
|
|
187
|
-
187 /**
|
|
188
|
-
188 * Validates a polymorphic relationship definition
|
|
189
|
-
189 *
|
|
190
|
-
190 * @param {Object} relDef - Relationship definition with belongsToPolymorphic
|
|
191
|
-
191 * @param {string} scopeName - Scope being registered (for error messages)
|
|
192
|
-
192 * @param {Object} scopes - All registered scopes for validation
|
|
193
|
-
193 * @returns {Object} Validation result {valid: boolean, error?: string}
|
|
194
|
-
194 *
|
|
195
|
-
195 * @example
|
|
196
|
-
196 * // Input: Valid polymorphic configuration
|
|
197
|
-
197 * const relDef = {
|
|
198
|
-
198 * belongsToPolymorphic: {
|
|
199
|
-
199 * types: ['articles', 'videos', 'products'],
|
|
200
|
-
200 * typeField: 'commentable_type',
|
|
201
|
-
201 * idField: 'commentable_id'
|
|
202
|
-
202 * }
|
|
203
|
-
203 * };
|
|
204
|
-
204 * validatePolymorphicRelationship(relDef, 'comments', scopes);
|
|
205
|
-
205 * // Output: { valid: true }
|
|
206
|
-
206 *
|
|
207
|
-
207 * @example
|
|
208
|
-
208 * // Input: Missing required types array
|
|
209
|
-
209 * const relDef = {
|
|
210
|
-
210 * belongsToPolymorphic: {
|
|
211
|
-
211 * typeField: 'commentable_type',
|
|
212
|
-
212 * idField: 'commentable_id'
|
|
213
|
-
213 * // Missing: types
|
|
214
|
-
214 * }
|
|
215
|
-
215 * };
|
|
216
|
-
216 * validatePolymorphicRelationship(relDef, 'comments', scopes);
|
|
217
|
-
217 * // Output: {
|
|
218
|
-
218 * // valid: false,
|
|
219
|
-
219 * // error: 'belongsToPolymorphic.types must be a non-empty array'
|
|
220
|
-
220 * // }
|
|
221
|
-
221 *
|
|
222
|
-
222 * @example
|
|
223
|
-
223 * // Input: References non-existent scope
|
|
224
|
-
224 * const relDef = {
|
|
225
|
-
225 * belongsToPolymorphic: {
|
|
226
|
-
226 * types: ['articles', 'unicorns'], // 'unicorns' doesn't exist
|
|
227
|
-
227 * typeField: 'attachable_type',
|
|
228
|
-
228 * idField: 'attachable_id'
|
|
229
|
-
229 * }
|
|
230
|
-
230 * };
|
|
231
|
-
231 * validatePolymorphicRelationship(relDef, 'attachments', scopes);
|
|
232
|
-
232 * // Output: {
|
|
233
|
-
233 * // valid: false,
|
|
234
|
-
234 * // error: "Polymorphic type 'unicorns' is not a registered scope"
|
|
235
|
-
235 * // }
|
|
236
|
-
236 *
|
|
237
|
-
237 * @private
|
|
238
|
-
238 */
|
|
239
|
-
239 const validatePolymorphicRelationship = (relDef, scopeName, scopes) => {
|
|
240
|
-
240 // Validation logic:
|
|
241
|
-
241 // 1. Check relDef.belongsToPolymorphic exists
|
|
242
|
-
242 // 2. Validate required properties: types, typeField, idField
|
|
243
|
-
243 // 3. Ensure types is non-empty array
|
|
244
|
-
244 // 4. Verify all types are registered scopes
|
|
245
|
-
245 // 5. Check that typeField and idField exist in the schema
|
|
246
|
-
246
|
|
247
|
-
247 const { belongsToPolymorphic } = relDef
|
|
248
|
-
248
|
|
249
|
-
249 if (!belongsToPolymorphic) {
|
|
250
|
-
250 return { valid: false, error: 'Missing belongsToPolymorphic definition' }
|
|
251
|
-
251 }
|
|
252
|
-
252
|
|
253
|
-
253 const { types, typeField, idField } = belongsToPolymorphic
|
|
254
|
-
254
|
|
255
|
-
255 if (!types || !Array.isArray(types) || types.length === 0) {
|
|
256
|
-
256 return {
|
|
257
|
-
257 valid: false,
|
|
258
|
-
258 error: 'belongsToPolymorphic.types must be a non-empty array'
|
|
259
|
-
259 }
|
|
260
|
-
260 }
|
|
261
|
-
261
|
|
262
|
-
262 if (!typeField || typeof typeField !== 'string') {
|
|
263
|
-
263 return {
|
|
264
|
-
264 valid: false,
|
|
265
|
-
265 error: 'belongsToPolymorphic.typeField must be specified'
|
|
266
|
-
266 }
|
|
267
|
-
267 }
|
|
268
|
-
268
|
|
269
|
-
269 if (!idField || typeof idField !== 'string') {
|
|
270
|
-
270 return {
|
|
271
|
-
271 valid: false,
|
|
272
|
-
272 error: 'belongsToPolymorphic.idField must be specified'
|
|
273
|
-
273 }
|
|
274
|
-
274 }
|
|
275
|
-
275
|
|
276
|
-
276 // Check that all types are valid scopes
|
|
277
|
-
277 for (const type of types) {
|
|
278
|
-
278 if (!scopes[type]) {
|
|
279
|
-
279 return {
|
|
280
|
-
280 valid: false,
|
|
281
|
-
281 error: `Polymorphic type '${type}' is not a registered scope`
|
|
282
|
-
282 }
|
|
283
|
-
283 }
|
|
284
|
-
284 }
|
|
285
|
-
285
|
|
286
|
-
286 return { valid: true }
|
|
287
|
-
287 }
|
|
288
|
-
288
|
|
289
|
-
289 /**
|
|
290
|
-
290 * Validates a hasMany relationship definition
|
|
291
|
-
291 *
|
|
292
|
-
292 * @param {Object} relDef - Relationship definition with hasMany
|
|
293
|
-
293 * @param {string} relName - Relationship name
|
|
294
|
-
294 * @param {string} scopeName - Scope being registered
|
|
295
|
-
295 * @returns {Object} Validation result {valid: boolean, error?: string}
|
|
296
|
-
296 *
|
|
297
|
-
297 * @example
|
|
298
|
-
298 * // Input: Valid hasMany configuration
|
|
299
|
-
299 * const relDef = {
|
|
300
|
-
300 * type: 'hasMany',
|
|
301
|
-
301 * target: 'posts',
|
|
302
|
-
302 * foreignKey: 'author_id' // Required!
|
|
303
|
-
303 * };
|
|
304
|
-
304 * validateHasManyRelationship(relDef, 'posts', 'users');
|
|
305
|
-
305 * // Output: { valid: true }
|
|
306
|
-
306 *
|
|
307
|
-
307 * @example
|
|
308
|
-
308 * // Input: Missing required foreignKey
|
|
309
|
-
309 * const relDef = {
|
|
310
|
-
310 * type: 'hasMany',
|
|
311
|
-
311 * target: 'posts'
|
|
312
|
-
312 * // Missing: foreignKey
|
|
313
|
-
313 * };
|
|
314
|
-
314 * validateHasManyRelationship(relDef, 'posts', 'users');
|
|
315
|
-
315 * // Output: {
|
|
316
|
-
316 * // valid: false,
|
|
317
|
-
317 * // error: 'hasMany relationship requires foreignKey to be specified...'
|
|
318
|
-
318 * // }
|
|
319
|
-
319 *
|
|
320
|
-
320 * @example
|
|
321
|
-
321 * // Input: Polymorphic hasMany with 'via' (doesn't need foreignKey)
|
|
322
|
-
322 * const relDef = {
|
|
323
|
-
323 * type: 'hasMany',
|
|
324
|
-
324 * target: 'comments',
|
|
325
|
-
325 * via: 'commentable' // Uses polymorphic relationship
|
|
326
|
-
326 * };
|
|
327
|
-
327 * validateHasManyRelationship(relDef, 'comments', 'posts');
|
|
328
|
-
328 * // Output: { valid: true }
|
|
329
|
-
329 *
|
|
330
|
-
330 * @private
|
|
331
|
-
331 */
|
|
332
|
-
332 const validateHasManyRelationship = (relDef, relName, scopeName) => {
|
|
333
|
-
333 // Validate target is specified
|
|
334
|
-
334 if (!relDef.target) {
|
|
335
|
-
335 return {
|
|
336
|
-
336 valid: false,
|
|
337
|
-
337 error: 'hasMany relationship requires \'target\' to be specified.'
|
|
338
|
-
338 }
|
|
339
|
-
339 }
|
|
340
|
-
340
|
|
341
|
-
341 // Polymorphic hasMany relationships using 'via' don't need foreignKey
|
|
342
|
-
342 // They use the polymorphic fields (typeField, idField) from the belongsToPolymorphic relationship
|
|
343
|
-
343 if (relDef.via) {
|
|
344
|
-
344 return { valid: true }
|
|
345
|
-
345 }
|
|
346
|
-
346
|
|
347
|
-
347 if (!relDef.foreignKey) {
|
|
348
|
-
348 return {
|
|
349
|
-
349 valid: false,
|
|
350
|
-
350 error: 'hasMany relationship requires foreignKey to be specified. Add foreignKey: \'<field_name>\' to the relationship definition.'
|
|
351
|
-
351 }
|
|
352
|
-
352 }
|
|
353
|
-
353
|
|
354
|
-
354 if (typeof relDef.foreignKey !== 'string') {
|
|
355
|
-
355 return {
|
|
356
|
-
356 valid: false,
|
|
357
|
-
357 error: `hasMany relationship foreignKey must be a string, got ${typeof relDef.foreignKey}`
|
|
358
|
-
358 }
|
|
359
|
-
359 }
|
|
360
|
-
360
|
|
361
|
-
361 return { valid: true }
|
|
362
|
-
362 }
|
|
363
|
-
363
|
|
364
|
-
364 /**
|
|
365
|
-
365 * Validates a hasOne relationship definition
|
|
366
|
-
366 *
|
|
367
|
-
367 * @param {Object} relDef - Relationship definition with hasOne
|
|
368
|
-
368 * @param {string} relName - Relationship name
|
|
369
|
-
369 * @param {string} scopeName - Scope being registered
|
|
370
|
-
370 * @returns {Object} Validation result {valid: boolean, error?: string}
|
|
371
|
-
371 *
|
|
372
|
-
372 * @example
|
|
373
|
-
373 * // Input: Valid hasOne configuration
|
|
374
|
-
374 * const relDef = {
|
|
375
|
-
375 * type: 'hasOne',
|
|
376
|
-
376 * target: 'profile',
|
|
377
|
-
377 * foreignKey: 'user_id' // Required!
|
|
378
|
-
378 * };
|
|
379
|
-
379 * validateHasOneRelationship(relDef, 'profile', 'users');
|
|
380
|
-
380 * // Output: { valid: true }
|
|
381
|
-
381 *
|
|
382
|
-
382 * @private
|
|
383
|
-
383 */
|
|
384
|
-
384 const validateHasOneRelationship = (relDef, relName, scopeName) => {
|
|
385
|
-
385 // Validate target is specified
|
|
386
|
-
386 if (!relDef.target) {
|
|
387
|
-
387 return {
|
|
388
|
-
388 valid: false,
|
|
389
|
-
389 error: 'hasOne relationship requires \'target\' to be specified.'
|
|
390
|
-
390 }
|
|
391
|
-
391 }
|
|
392
|
-
392
|
|
393
|
-
393 if (!relDef.foreignKey) {
|
|
394
|
-
394 return {
|
|
395
|
-
395 valid: false,
|
|
396
|
-
396 error: 'hasOne relationship requires foreignKey to be specified. Add foreignKey: \'<field_name>\' to the relationship definition.'
|
|
397
|
-
397 }
|
|
398
|
-
398 }
|
|
399
|
-
399
|
|
400
|
-
400 if (typeof relDef.foreignKey !== 'string') {
|
|
401
|
-
401 return {
|
|
402
|
-
402 valid: false,
|
|
403
|
-
403 error: `hasOne relationship foreignKey must be a string, got ${typeof relDef.foreignKey}`
|
|
404
|
-
404 }
|
|
405
|
-
405 }
|
|
406
|
-
406
|
|
407
|
-
407 return { valid: true }
|
|
408
|
-
408 }
|
|
409
|
-
409
|
|
410
|
-
410 /**
|
|
411
|
-
411 * Validates a manyToMany relationship definition
|
|
412
|
-
412 *
|
|
413
|
-
413 * @param {Object} relDef - Relationship definition with manyToMany
|
|
414
|
-
414 * @param {string} relName - Relationship name
|
|
415
|
-
415 * @param {string} scopeName - Scope being registered
|
|
416
|
-
416 * @returns {Object} Validation result {valid: boolean, error?: string}
|
|
417
|
-
417 *
|
|
418
|
-
418 * @example
|
|
419
|
-
419 * // Input: Valid manyToMany configuration
|
|
420
|
-
420 * const relDef = {
|
|
421
|
-
421 * type: 'manyToMany',
|
|
422
|
-
422 * through: 'article_tags', // Pivot table
|
|
423
|
-
423 * foreignKey: 'article_id', // This scope's FK
|
|
424
|
-
424 * otherKey: 'tag_id' // Other scope's FK
|
|
425
|
-
425 * };
|
|
426
|
-
426 * validateManyToManyRelationship(relDef, 'tags', 'articles');
|
|
427
|
-
427 * // Output: { valid: true }
|
|
428
|
-
428 *
|
|
429
|
-
429 * @example
|
|
430
|
-
430 * // Input: Missing required fields
|
|
431
|
-
431 * const relDef = {
|
|
432
|
-
432 * type: 'manyToMany',
|
|
433
|
-
433 * through: 'article_tags',
|
|
434
|
-
434 * foreignKey: 'article_id'
|
|
435
|
-
435 * // Missing: otherKey
|
|
436
|
-
436 * };
|
|
437
|
-
437 * validateManyToManyRelationship(relDef, 'tags', 'articles');
|
|
438
|
-
438 * // Output: {
|
|
439
|
-
439 * // valid: false,
|
|
440
|
-
440 * // error: 'manyToMany relationship requires otherKey to be specified...'
|
|
441
|
-
441 * // }
|
|
442
|
-
442 *
|
|
443
|
-
443 * @private
|
|
444
|
-
444 */
|
|
445
|
-
445 const validateManyToManyRelationship = (relDef, relName, scopeName) => {
|
|
446
|
-
446 const { through, foreignKey, otherKey } = relDef
|
|
447
|
-
447
|
|
448
|
-
448 if (!through) {
|
|
449
|
-
449 return {
|
|
450
|
-
450 valid: false,
|
|
451
|
-
451 error: 'manyToMany relationship requires \'through\' table to be specified'
|
|
452
|
-
452 }
|
|
453
|
-
453 }
|
|
454
|
-
454
|
|
455
|
-
455 if (!foreignKey) {
|
|
456
|
-
456 return {
|
|
457
|
-
457 valid: false,
|
|
458
|
-
458 error: 'manyToMany relationship requires foreignKey to be specified. Add foreignKey: \'<field_name>\' to the manyToMany configuration.'
|
|
459
|
-
459 }
|
|
460
|
-
460 }
|
|
461
|
-
461
|
|
462
|
-
462 if (!otherKey) {
|
|
463
|
-
463 return {
|
|
464
|
-
464 valid: false,
|
|
465
|
-
465 error: 'manyToMany relationship requires otherKey to be specified. Add otherKey: \'<field_name>\' to the manyToMany configuration.'
|
|
466
|
-
466 }
|
|
467
|
-
467 }
|
|
468
|
-
468
|
|
469
|
-
469 if (typeof foreignKey !== 'string') {
|
|
470
|
-
470 return {
|
|
471
|
-
471 valid: false,
|
|
472
|
-
472 error: `manyToMany relationship foreignKey must be a string, got ${typeof foreignKey}`
|
|
473
|
-
473 }
|
|
474
|
-
474 }
|
|
475
|
-
475
|
|
476
|
-
476 if (typeof otherKey !== 'string') {
|
|
477
|
-
477 return {
|
|
478
|
-
478 valid: false,
|
|
479
|
-
479 error: `manyToMany relationship otherKey must be a string, got ${typeof otherKey}`
|
|
480
|
-
480 }
|
|
481
|
-
481 }
|
|
482
|
-
482
|
|
483
|
-
483 return { valid: true }
|
|
484
|
-
484 }
|