json-rest-api 1.0.22 → 1.0.24
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/README.md +22 -1
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
- package/docs/API.md +196 -38
- package/docs/COMPARISON.md +2 -2
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +111 -5
- 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 +370 -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/lib/enhanced-logger.js +1 -1
- package/package.json +6 -2
- package/plugins/core/bulk-operations-plugin.js +26 -34
- package/plugins/core/connectors/express-plugin.js +97 -190
- package/plugins/core/connectors/fastify-plugin.js +206 -0
- package/plugins/core/connectors/lib/busboy-detector.js +4 -4
- 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/anyapi/schema-utils.js +43 -4
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +4 -1
- package/plugins/core/lib/dbIntrospection.js +1078 -0
- package/plugins/core/lib/dbTablesOperations.js +1111 -160
- package/plugins/core/lib/querying/knex-cross-table-search.js +0 -7
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -3
- package/plugins/core/lib/querying/knex-process-includes.js +3 -4
- package/plugins/core/lib/querying/knex-query-helpers.js +21 -76
- package/plugins/core/lib/querying/knex-relationship-includes.js +468 -215
- 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 +20 -5
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -14
- 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/querying-writing/resource-id-normalization.js +131 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +0 -2
- package/plugins/core/lib/querying-writing/simplified-helpers.js +4 -11
- package/plugins/core/lib/storage/canonical-storage-mapping.js +133 -0
- package/plugins/core/lib/storage/storage-adapter.js +82 -4
- package/plugins/core/lib/storage/storage-mapping.js +274 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +4 -2
- package/plugins/core/rest-api-anyapi-knex-plugin.js +531 -312
- package/plugins/core/rest-api-autofilter-plugin.js +414 -0
- package/plugins/core/rest-api-knex-plugin.js +271 -226
- 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-hooks/turn-scope-init-into-vars.js +1 -0
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
- package/plugins/core/rest-api-plugin-methods/common.js +148 -92
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +11 -2
- package/plugins/core/rest-api-plugin-methods/delete.js +6 -1
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
- package/plugins/core/rest-api-plugin-methods/get-related.js +7 -2
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +7 -2
- package/plugins/core/rest-api-plugin-methods/get.js +29 -14
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +10 -3
- package/plugins/core/rest-api-plugin-methods/patch.js +56 -12
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +11 -2
- package/plugins/core/rest-api-plugin-methods/post.js +21 -8
- package/plugins/core/rest-api-plugin-methods/put.js +73 -21
- package/plugins/core/rest-api-plugin-methods/query.js +15 -53
- package/plugins/core/rest-api-plugin-methods/release.js +0 -2
- package/plugins/core/rest-api-plugin.js +6 -0
- package/plugins/core/rest-api-positioning-plugin.js +2 -5
- package/plugins/core/rest-api-query-projections-plugin.js +130 -0
- package/plugins/core/socketio-plugin.js +1 -4
- package/plugins/storage/local-storage.js +1 -1
- package/plugins/storage/s3-storage.js +5 -2
- package/quickTest.js +1 -0
- package/scripts/run-docs.js +80 -84
- package/test-debug.js +1 -1
- package/tests/README.md +7 -0
- package/tests/anyapi-basic.test.js +3 -0
- package/tests/autofilter.test.js +463 -0
- package/tests/custom-id-mysql-returning-fallback.test.js +128 -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 +521 -0
- package/tests/db-table-operations.test.js +786 -0
- package/tests/fastify-plugin.test.js +156 -0
- package/tests/field-getters.test.js +1 -2
- package/tests/field-setters.test.js +0 -4
- package/tests/fixtures/api-configs.js +278 -176
- package/tests/helpers/fake-fastify.js +99 -0
- package/tests/http-connectors-parity.test.js +235 -0
- package/tests/id-normalization.test.js +444 -0
- package/tests/logical-id-write-validation.test.js +151 -0
- package/tests/pagination-cursor-multifield.test.js +11 -11
- 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 +259 -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
|
@@ -36,35 +36,40 @@ export default async function registerRelationshipRoutes ({ context, api, log })
|
|
|
36
36
|
await api.addRoute({
|
|
37
37
|
method: 'GET',
|
|
38
38
|
path: `${scopePath}/:id/relationships/:relationshipName`,
|
|
39
|
-
handler: createRouteHandler('getRelationship')
|
|
39
|
+
handler: createRouteHandler('getRelationship'),
|
|
40
|
+
routeMeta: { kind: 'relationship', scopeName, operation: 'getRelationship' }
|
|
40
41
|
})
|
|
41
42
|
|
|
42
43
|
// GET /api/{scope}/{id}/{relationshipName}
|
|
43
44
|
await api.addRoute({
|
|
44
45
|
method: 'GET',
|
|
45
46
|
path: `${scopePath}/:id/:relationshipName`,
|
|
46
|
-
handler: createRouteHandler('getRelated')
|
|
47
|
+
handler: createRouteHandler('getRelated'),
|
|
48
|
+
routeMeta: { kind: 'relationship', scopeName, operation: 'getRelated' }
|
|
47
49
|
})
|
|
48
50
|
|
|
49
51
|
// POST /api/{scope}/{id}/relationships/{relationshipName}
|
|
50
52
|
await api.addRoute({
|
|
51
53
|
method: 'POST',
|
|
52
54
|
path: `${scopePath}/:id/relationships/:relationshipName`,
|
|
53
|
-
handler: createRouteHandler('postRelationship')
|
|
55
|
+
handler: createRouteHandler('postRelationship'),
|
|
56
|
+
routeMeta: { kind: 'relationship', scopeName, operation: 'postRelationship' }
|
|
54
57
|
})
|
|
55
58
|
|
|
56
59
|
// PATCH /api/{scope}/{id}/relationships/{relationshipName}
|
|
57
60
|
await api.addRoute({
|
|
58
61
|
method: 'PATCH',
|
|
59
62
|
path: `${scopePath}/:id/relationships/:relationshipName`,
|
|
60
|
-
handler: createRouteHandler('patchRelationship')
|
|
63
|
+
handler: createRouteHandler('patchRelationship'),
|
|
64
|
+
routeMeta: { kind: 'relationship', scopeName, operation: 'patchRelationship' }
|
|
61
65
|
})
|
|
62
66
|
|
|
63
67
|
// DELETE /api/{scope}/{id}/relationships/{relationshipName}
|
|
64
68
|
await api.addRoute({
|
|
65
69
|
method: 'DELETE',
|
|
66
70
|
path: `${scopePath}/:id/relationships/:relationshipName`,
|
|
67
|
-
handler: createRouteHandler('deleteRelationship')
|
|
71
|
+
handler: createRouteHandler('deleteRelationship'),
|
|
72
|
+
routeMeta: { kind: 'relationship', scopeName, operation: 'deleteRelationship' }
|
|
68
73
|
})
|
|
69
74
|
|
|
70
75
|
log.trace(`Registered relationship routes for scope: ${scopeName}`)
|
|
@@ -60,42 +60,48 @@ export default async function registerScopeRoutes ({ context, api, vars, log })
|
|
|
60
60
|
await api.addRoute({
|
|
61
61
|
method: 'GET',
|
|
62
62
|
path: scopePath,
|
|
63
|
-
handler: createRouteHandler(scopeName, 'query')
|
|
63
|
+
handler: createRouteHandler(scopeName, 'query'),
|
|
64
|
+
routeMeta: { kind: 'resource', scopeName, operation: 'query' }
|
|
64
65
|
})
|
|
65
66
|
|
|
66
67
|
// GET /api/{scope}/{id} - Get single resource
|
|
67
68
|
await api.addRoute({
|
|
68
69
|
method: 'GET',
|
|
69
70
|
path: `${scopePath}/:id`,
|
|
70
|
-
handler: createRouteHandler(scopeName, 'get')
|
|
71
|
+
handler: createRouteHandler(scopeName, 'get'),
|
|
72
|
+
routeMeta: { kind: 'resource', scopeName, operation: 'get' }
|
|
71
73
|
})
|
|
72
74
|
|
|
73
75
|
// POST /api/{scope} - Create resource
|
|
74
76
|
await api.addRoute({
|
|
75
77
|
method: 'POST',
|
|
76
78
|
path: scopePath,
|
|
77
|
-
handler: createRouteHandler(scopeName, 'post')
|
|
79
|
+
handler: createRouteHandler(scopeName, 'post'),
|
|
80
|
+
routeMeta: { kind: 'resource', scopeName, operation: 'post' }
|
|
78
81
|
})
|
|
79
82
|
|
|
80
83
|
// PUT /api/{scope}/{id} - Replace resource
|
|
81
84
|
await api.addRoute({
|
|
82
85
|
method: 'PUT',
|
|
83
86
|
path: `${scopePath}/:id`,
|
|
84
|
-
handler: createRouteHandler(scopeName, 'put')
|
|
87
|
+
handler: createRouteHandler(scopeName, 'put'),
|
|
88
|
+
routeMeta: { kind: 'resource', scopeName, operation: 'put' }
|
|
85
89
|
})
|
|
86
90
|
|
|
87
91
|
// PATCH /api/{scope}/{id} - Update resource
|
|
88
92
|
await api.addRoute({
|
|
89
93
|
method: 'PATCH',
|
|
90
94
|
path: `${scopePath}/:id`,
|
|
91
|
-
handler: createRouteHandler(scopeName, 'patch')
|
|
95
|
+
handler: createRouteHandler(scopeName, 'patch'),
|
|
96
|
+
routeMeta: { kind: 'resource', scopeName, operation: 'patch' }
|
|
92
97
|
})
|
|
93
98
|
|
|
94
99
|
// DELETE /api/{scope}/{id} - Delete resource
|
|
95
100
|
await api.addRoute({
|
|
96
101
|
method: 'DELETE',
|
|
97
102
|
path: `${scopePath}/:id`,
|
|
98
|
-
handler: createRouteHandler(scopeName, 'delete')
|
|
103
|
+
handler: createRouteHandler(scopeName, 'delete'),
|
|
104
|
+
routeMeta: { kind: 'resource', scopeName, operation: 'delete' }
|
|
99
105
|
})
|
|
100
106
|
|
|
101
107
|
log.info(`Routes registered for scope '${scopeName}'`)
|
|
@@ -24,6 +24,7 @@ export default async function turnScopeInitIntoVars ({ context, scopes, vars: ap
|
|
|
24
24
|
|
|
25
25
|
// Set idProperty as scope var
|
|
26
26
|
if (typeof scopeOptions.idProperty !== 'undefined') vars.idProperty = scopeOptions.idProperty
|
|
27
|
+
if (typeof scopeOptions.normalizeId === 'function') vars.normalizeId = scopeOptions.normalizeId
|
|
27
28
|
|
|
28
29
|
// Add validation for query limits
|
|
29
30
|
if (vars.queryDefaultLimit && vars.queryMaxLimit) {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export default async function applyQueryFiltersMethod ({ context, params = {}, runHooks }) {
|
|
2
|
+
const { query, filters, storageAdapter, ...knexQuery } = params
|
|
3
|
+
|
|
4
|
+
if (!query) {
|
|
5
|
+
return query
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const previousKnexQuery = context.knexQuery
|
|
9
|
+
const previousStorageAdapter = context.storageAdapter
|
|
10
|
+
|
|
11
|
+
context.knexQuery = {
|
|
12
|
+
query,
|
|
13
|
+
filters,
|
|
14
|
+
storageAdapter,
|
|
15
|
+
adapter: storageAdapter,
|
|
16
|
+
...knexQuery
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (storageAdapter) {
|
|
20
|
+
context.storageAdapter = storageAdapter
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
await runHooks('knexQueryFiltering')
|
|
25
|
+
return {
|
|
26
|
+
query: context.knexQuery?.query || query
|
|
27
|
+
}
|
|
28
|
+
} finally {
|
|
29
|
+
if (previousKnexQuery === undefined) {
|
|
30
|
+
delete context.knexQuery
|
|
31
|
+
} else {
|
|
32
|
+
context.knexQuery = previousKnexQuery
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (previousStorageAdapter === undefined) {
|
|
36
|
+
delete context.storageAdapter
|
|
37
|
+
} else {
|
|
38
|
+
context.storageAdapter = previousStorageAdapter
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
RestApiResourceError,
|
|
3
|
+
RestApiValidationError
|
|
4
4
|
} from '../../../lib/rest-api-errors.js'
|
|
5
5
|
import { transformSimplifiedToJsonApi } from '../lib/querying-writing/simplified-helpers.js'
|
|
6
|
+
import { normalizeRelationshipIdentifiers } from '../lib/querying-writing/resource-id-normalization.js'
|
|
6
7
|
import { createEnhancedLogger } from '../../../lib/enhanced-logger.js'
|
|
8
|
+
import { unwrapQueryBuilderState } from '../lib/querying/query-builder-utils.js'
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Gets an enhanced logger instance with full error details and stack traces
|
|
@@ -140,56 +142,6 @@ export async function setupCommonRequest ({ params, context, vars, scopes, scope
|
|
|
140
142
|
{ inputRecord: context.inputRecord },
|
|
141
143
|
{ context: { scopeName, schemaStructure, schemaRelationships } }
|
|
142
144
|
)
|
|
143
|
-
} else {
|
|
144
|
-
// Strict mode: validate no belongsTo fields in attributes
|
|
145
|
-
if (context.inputRecord?.data?.attributes) {
|
|
146
|
-
for (const [key, fieldDef] of Object.entries(schemaStructure)) {
|
|
147
|
-
if (fieldDef.belongsTo && key in context.inputRecord.data.attributes) {
|
|
148
|
-
throw new RestApiValidationError(
|
|
149
|
-
`Field '${key}' is a foreign key and must be set via relationships, not attributes`,
|
|
150
|
-
{ fields: [`data.attributes.${key}`] }
|
|
151
|
-
)
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (context.inputRecord.data.type !== scopeName) {
|
|
158
|
-
throw new RestApiValidationError(
|
|
159
|
-
`Resource type mismatch. Expected '${scopeName}' but got '${context.inputRecord.data.type}'`,
|
|
160
|
-
{
|
|
161
|
-
fields: ['data.type'],
|
|
162
|
-
violations: [{
|
|
163
|
-
field: 'data.type',
|
|
164
|
-
rule: 'resource_type_match',
|
|
165
|
-
message: `Resource type must be '${scopeName}'`
|
|
166
|
-
}]
|
|
167
|
-
}
|
|
168
|
-
)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Remove included validation since JSON:API doesn't support it
|
|
172
|
-
if (context.inputRecord.included) {
|
|
173
|
-
throw new RestApiPayloadError(
|
|
174
|
-
context.method + ' requests cannot include an "included" array. JSON:API does not support creating multiple resources in a single request.',
|
|
175
|
-
{ path: 'included', expected: 'undefined', received: 'array' }
|
|
176
|
-
)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// If both URL path ID and request body ID are provided, they must match
|
|
180
|
-
// Convert both to strings for comparison since databases may return numeric IDs
|
|
181
|
-
if (context.id && context.inputRecord.data.id && String(context.id) !== String(context.inputRecord.data.id)) {
|
|
182
|
-
throw new RestApiValidationError(
|
|
183
|
-
`ID mismatch. URL path ID '${context.id}' does not match request body ID '${context.inputRecord.data.id}'`,
|
|
184
|
-
{
|
|
185
|
-
fields: ['data.id'],
|
|
186
|
-
violations: [{
|
|
187
|
-
field: 'data.id',
|
|
188
|
-
rule: 'id_consistency',
|
|
189
|
-
message: 'Request body ID must match URL path ID when both are provided'
|
|
190
|
-
}]
|
|
191
|
-
}
|
|
192
|
-
)
|
|
193
145
|
}
|
|
194
146
|
|
|
195
147
|
// Return key schema-related objects for direct use in the main methods
|
|
@@ -261,6 +213,61 @@ export const getMethodHookSuffix = (method) => {
|
|
|
261
213
|
return method.charAt(0).toUpperCase() + method.slice(1)
|
|
262
214
|
}
|
|
263
215
|
|
|
216
|
+
export const validateCompleteReplacePayload = ({
|
|
217
|
+
context,
|
|
218
|
+
belongsToUpdates
|
|
219
|
+
}) => {
|
|
220
|
+
const schemaInfo = context.schemaInfo || {}
|
|
221
|
+
const schemaStructure = schemaInfo.schemaStructure || {}
|
|
222
|
+
const idProperty = schemaInfo.idProperty || 'id'
|
|
223
|
+
const originalInputAttributes = context.originalInputAttributes || context.inputRecord?.data?.attributes || {}
|
|
224
|
+
const missingFields = []
|
|
225
|
+
|
|
226
|
+
for (const [fieldName, fieldDef] of Object.entries(schemaStructure)) {
|
|
227
|
+
if (!fieldDef) continue
|
|
228
|
+
if (fieldName === idProperty) continue
|
|
229
|
+
if (fieldDef.computed === true || fieldDef.virtual === true) continue
|
|
230
|
+
if (fieldDef.type === undefined) continue
|
|
231
|
+
|
|
232
|
+
if (fieldDef.belongsTo && fieldDef.as) {
|
|
233
|
+
if (Object.hasOwn(belongsToUpdates, fieldName)) continue
|
|
234
|
+
|
|
235
|
+
const existingRelationshipData = context.minimalRecord?.relationships?.[fieldDef.as]?.data
|
|
236
|
+
if (existingRelationshipData?.id !== undefined && existingRelationshipData?.id !== null) {
|
|
237
|
+
missingFields.push({
|
|
238
|
+
field: `data.relationships.${fieldDef.as}.data.id`,
|
|
239
|
+
message: `PUT requests must explicitly include relationship '${fieldDef.as}' when it already has a value`
|
|
240
|
+
})
|
|
241
|
+
}
|
|
242
|
+
continue
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (Object.hasOwn(originalInputAttributes, fieldName)) continue
|
|
246
|
+
|
|
247
|
+
const existingValue = context.minimalRecord?.attributes?.[fieldName]
|
|
248
|
+
if (existingValue !== undefined && existingValue !== null) {
|
|
249
|
+
missingFields.push({
|
|
250
|
+
field: `data.attributes.${fieldName}`,
|
|
251
|
+
message: `PUT requests must explicitly include attribute '${fieldName}' when it already has a value`
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (missingFields.length > 0) {
|
|
257
|
+
throw new RestApiValidationError(
|
|
258
|
+
'PUT request omits persisted fields that already have stored values',
|
|
259
|
+
{
|
|
260
|
+
fields: missingFields.map(({ field }) => field),
|
|
261
|
+
violations: missingFields.map(({ field, message }) => ({
|
|
262
|
+
field,
|
|
263
|
+
rule: 'complete_replacement',
|
|
264
|
+
message
|
|
265
|
+
}))
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
264
271
|
/**
|
|
265
272
|
* Validates resource attributes before write operations
|
|
266
273
|
*
|
|
@@ -285,22 +292,25 @@ export const validateResourceAttributesBeforeWrite = async ({
|
|
|
285
292
|
await runHooks('beforeSchemaValidate')
|
|
286
293
|
await runHooks(`beforeSchemaValidate${methodSpecificHookSuffix}`)
|
|
287
294
|
|
|
288
|
-
// Store original input attributes before validation adds defaults
|
|
289
|
-
|
|
290
|
-
if (!context.originalInputAttributes && context.method === 'post') {
|
|
295
|
+
// Store original input attributes before validation adds defaults or casts.
|
|
296
|
+
if (!context.originalInputAttributes) {
|
|
291
297
|
context.originalInputAttributes = { ...(context.inputRecord.data.attributes || {}) }
|
|
292
298
|
}
|
|
293
299
|
|
|
300
|
+
const schemaStructure = context.schemaInfo.schemaStructure || {}
|
|
301
|
+
const hasLogicalIdField = Object.hasOwn(schemaStructure, 'id')
|
|
302
|
+
const resourceId = context.inputRecord?.data?.id
|
|
303
|
+
|
|
294
304
|
// Merge belongsTo updates with attributes for validation
|
|
295
305
|
const attributesToValidate = {
|
|
296
306
|
...context.inputRecord.data.attributes,
|
|
297
|
-
...belongsToUpdates
|
|
307
|
+
...belongsToUpdates,
|
|
308
|
+
...(hasLogicalIdField && resourceId !== undefined ? { id: resourceId } : {})
|
|
298
309
|
}
|
|
299
310
|
|
|
300
311
|
// Extract computed fields and foreign key fields before validation
|
|
301
312
|
const computedFields = {}
|
|
302
313
|
const foreignKeyFields = {}
|
|
303
|
-
const schemaStructure = context.schemaInfo.schemaStructure || {}
|
|
304
314
|
Object.entries(attributesToValidate).forEach(([key, value]) => {
|
|
305
315
|
const fieldDef = schemaStructure[key]
|
|
306
316
|
// Computed fields should be stripped from input
|
|
@@ -361,17 +371,22 @@ export const validateResourceAttributesBeforeWrite = async ({
|
|
|
361
371
|
return acc
|
|
362
372
|
}, {})
|
|
363
373
|
|
|
364
|
-
const
|
|
374
|
+
const validationMethod = isPartialValidation
|
|
375
|
+
? 'patch'
|
|
376
|
+
: context.method === 'put'
|
|
377
|
+
? 'replace'
|
|
378
|
+
: 'create'
|
|
365
379
|
|
|
366
|
-
const { validatedObject, errors } = await schema
|
|
380
|
+
const { validatedObject, errors } = await schema[validationMethod](attributesForValidation)
|
|
367
381
|
|
|
368
382
|
if (Object.keys(errors).length > 0) {
|
|
369
|
-
// --- START OF MODIFICATION ---
|
|
370
|
-
const schemaStructure = context.schemaInfo.schemaInstance.structure // Get the schema structure for lookup
|
|
371
|
-
|
|
372
383
|
const violations = Object.entries(errors).map(([field, error]) => {
|
|
373
384
|
let fieldPath = `data.attributes.${field}` // Default path for attributes
|
|
374
385
|
|
|
386
|
+
if (field === 'id' && hasLogicalIdField) {
|
|
387
|
+
fieldPath = 'data.id'
|
|
388
|
+
}
|
|
389
|
+
|
|
375
390
|
// Check if this field is a foreign key that has an 'as' alias
|
|
376
391
|
const fieldDef = schemaStructure[field]
|
|
377
392
|
if (fieldDef && fieldDef.belongsTo && fieldDef.as) {
|
|
@@ -391,7 +406,6 @@ export const validateResourceAttributesBeforeWrite = async ({
|
|
|
391
406
|
message: error.message
|
|
392
407
|
}
|
|
393
408
|
})
|
|
394
|
-
// --- END OF MODIFICATION ---
|
|
395
409
|
|
|
396
410
|
throw new RestApiValidationError(
|
|
397
411
|
'Schema validation failed for resource attributes',
|
|
@@ -402,9 +416,14 @@ export const validateResourceAttributesBeforeWrite = async ({
|
|
|
402
416
|
)
|
|
403
417
|
}
|
|
404
418
|
|
|
419
|
+
if (Object.hasOwn(validatedObject, 'id')) {
|
|
420
|
+
context.inputRecord.data.id = validatedObject.id
|
|
421
|
+
}
|
|
422
|
+
const { id: _validatedId, ...validatedAttributes } = validatedObject
|
|
423
|
+
|
|
405
424
|
// Update attributes with validated values
|
|
406
425
|
// Virtual fields have now been validated and cast properly
|
|
407
|
-
context.inputRecord.data.attributes =
|
|
426
|
+
context.inputRecord.data.attributes = validatedAttributes
|
|
408
427
|
|
|
409
428
|
await runHooks(`afterSchemaValidate${methodSpecificHookSuffix}`)
|
|
410
429
|
await runHooks('afterSchemaValidate')
|
|
@@ -416,18 +435,25 @@ export const validateResourceAttributesBeforeWrite = async ({
|
|
|
416
435
|
* @param {object} context - The context object containing authentication info
|
|
417
436
|
* @param {object} inputRecord - The input record containing relationships to validate
|
|
418
437
|
* @param {object} helpers - Data helpers including dataGetMinimal
|
|
419
|
-
* @param {function} runHooks - Function to run hooks
|
|
420
438
|
* @param {object} api - API instance to access resources
|
|
421
439
|
* @throws {Error} If user doesn't have access to any related resource
|
|
422
440
|
*/
|
|
423
|
-
export const validateRelationshipAccess = async (context, inputRecord, helpers,
|
|
441
|
+
export const validateRelationshipAccess = async (context, inputRecord, helpers, api) => {
|
|
424
442
|
if (!inputRecord?.data?.relationships) return
|
|
425
443
|
|
|
426
444
|
for (const [relName, relData] of Object.entries(inputRecord.data.relationships)) {
|
|
427
445
|
if (!relData?.data) continue
|
|
428
446
|
|
|
447
|
+
const normalizedRelationshipData = normalizeRelationshipIdentifiers(relData.data, { api })
|
|
448
|
+
inputRecord.data.relationships[relName] = {
|
|
449
|
+
...relData,
|
|
450
|
+
data: normalizedRelationshipData
|
|
451
|
+
}
|
|
452
|
+
|
|
429
453
|
// Handle both single and array relationships
|
|
430
|
-
const relatedItems = Array.isArray(
|
|
454
|
+
const relatedItems = Array.isArray(normalizedRelationshipData)
|
|
455
|
+
? normalizedRelationshipData
|
|
456
|
+
: [normalizedRelationshipData]
|
|
431
457
|
|
|
432
458
|
for (const item of relatedItems) {
|
|
433
459
|
// Get the scope for the related resource
|
|
@@ -442,19 +468,47 @@ export const validateRelationshipAccess = async (context, inputRecord, helpers,
|
|
|
442
468
|
id: item.id,
|
|
443
469
|
schemaInfo: relatedScope.vars.schemaInfo,
|
|
444
470
|
scopeName: item.type,
|
|
471
|
+
queryParams: {},
|
|
445
472
|
method: 'get', // We're checking read permission
|
|
446
473
|
isUpdate: false
|
|
447
474
|
}
|
|
448
475
|
|
|
476
|
+
const targetScope = api.resources?.[item.type]
|
|
477
|
+
const applyQueryFilters = targetScope?.applyQueryFilters
|
|
478
|
+
? async ({ query, filters, tableName, db, scopeName, storageAdapter, isAnyApi }) => {
|
|
479
|
+
const queryState = await targetScope.applyQueryFilters({
|
|
480
|
+
query,
|
|
481
|
+
filters,
|
|
482
|
+
schemaInfo: getContext.schemaInfo,
|
|
483
|
+
scopeName,
|
|
484
|
+
tableName,
|
|
485
|
+
db,
|
|
486
|
+
isAnyApi,
|
|
487
|
+
storageAdapter
|
|
488
|
+
}, getContext)
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
query: unwrapQueryBuilderState(queryState, query)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
: undefined
|
|
495
|
+
|
|
449
496
|
// Get the minimal record
|
|
450
497
|
const record = await helpers.dataGetMinimal({
|
|
451
498
|
scopeName: item.type,
|
|
452
499
|
context: getContext,
|
|
453
|
-
|
|
500
|
+
applyQueryFilters
|
|
454
501
|
})
|
|
455
502
|
|
|
456
503
|
if (!record) {
|
|
457
|
-
throw new
|
|
504
|
+
throw new RestApiResourceError(
|
|
505
|
+
`Cannot create relationship to non-existent ${item.type} with id ${item.id}`,
|
|
506
|
+
{
|
|
507
|
+
subtype: 'not_found',
|
|
508
|
+
resourceType: item.type,
|
|
509
|
+
resourceId: item.id
|
|
510
|
+
}
|
|
511
|
+
)
|
|
458
512
|
}
|
|
459
513
|
|
|
460
514
|
// Check permissions using the related scope's checkPermissions
|
|
@@ -493,29 +547,31 @@ export const applyFieldSetters = async (attributes, schemaInfo, context, api, he
|
|
|
493
547
|
const transformedAttributes = { ...attributes }
|
|
494
548
|
|
|
495
549
|
for (const fieldName of sortedSetterFields) {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
setterContext
|
|
514
|
-
)
|
|
515
|
-
} catch (error) {
|
|
516
|
-
console.warn(`Setter for field '${fieldName}' failed:`, error)
|
|
517
|
-
// Keep original value if setter fails
|
|
550
|
+
const setterInfo = fieldSetters[fieldName]
|
|
551
|
+
const fieldPresent = fieldName in transformedAttributes
|
|
552
|
+
const hasDependencies = Array.isArray(setterInfo.runSetterAfter) && setterInfo.runSetterAfter.length > 0
|
|
553
|
+
|
|
554
|
+
if (!fieldPresent && !hasDependencies) continue
|
|
555
|
+
|
|
556
|
+
try {
|
|
557
|
+
const setterContext = {
|
|
558
|
+
attributes: transformedAttributes, // Current state with previous setters applied
|
|
559
|
+
fieldName,
|
|
560
|
+
originalValue: attributes[fieldName],
|
|
561
|
+
originalAttributes: attributes,
|
|
562
|
+
scopeName: context.scopeName,
|
|
563
|
+
method: context.method,
|
|
564
|
+
api,
|
|
565
|
+
helpers,
|
|
566
|
+
auth: context.auth
|
|
518
567
|
}
|
|
568
|
+
transformedAttributes[fieldName] = await setterInfo.setter(
|
|
569
|
+
transformedAttributes[fieldName],
|
|
570
|
+
setterContext
|
|
571
|
+
)
|
|
572
|
+
} catch (error) {
|
|
573
|
+
console.warn(`Setter for field '${fieldName}' failed:`, error)
|
|
574
|
+
// Keep original value if setter fails
|
|
519
575
|
}
|
|
520
576
|
}
|
|
521
577
|
|
|
@@ -629,7 +685,7 @@ export const findRelationshipDefinition = (schemaInfo, relationshipName) => {
|
|
|
629
685
|
}
|
|
630
686
|
|
|
631
687
|
// Then check schema fields for belongsTo relationships with matching 'as' property
|
|
632
|
-
for (const [
|
|
688
|
+
for (const [, fieldDef] of Object.entries(schemaInfo.schemaStructure || {})) {
|
|
633
689
|
if (fieldDef.as === relationshipName && fieldDef.belongsTo) {
|
|
634
690
|
return fieldDef
|
|
635
691
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { RestApiResourceError, RestApiValidationError, RestApiPayloadError } from '../../../lib/rest-api-errors.js'
|
|
2
2
|
import { findRelationshipDefinition, handleWriteMethodError } from './common.js'
|
|
3
|
+
import {
|
|
4
|
+
normalizeRelationshipIdentifiers,
|
|
5
|
+
requireExistingResourceId
|
|
6
|
+
} from '../lib/querying-writing/resource-id-normalization.js'
|
|
3
7
|
|
|
4
8
|
/**
|
|
5
9
|
* DELETE RELATIONSHIP
|
|
@@ -11,9 +15,13 @@ import { findRelationshipDefinition, handleWriteMethodError } from './common.js'
|
|
|
11
15
|
* @param {array} relationshipData - Array of resource identifiers to remove
|
|
12
16
|
* @returns {Promise<void>} 204 No Content
|
|
13
17
|
*/
|
|
14
|
-
export default async function deleteRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api, log }) {
|
|
18
|
+
export default async function deleteRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeOptions, scopeName, api, log }) {
|
|
15
19
|
context.method = 'deleteRelationship'
|
|
16
|
-
context.id = params.id
|
|
20
|
+
context.id = requireExistingResourceId(params.id, {
|
|
21
|
+
scopeOptions,
|
|
22
|
+
vars,
|
|
23
|
+
scopeName
|
|
24
|
+
})
|
|
17
25
|
context.relationshipName = params.relationshipName
|
|
18
26
|
context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
19
27
|
|
|
@@ -43,6 +51,7 @@ export default async function deleteRelationshipMethod ({ params, context, vars,
|
|
|
43
51
|
if (!Array.isArray(params.relationshipData)) {
|
|
44
52
|
throw new RestApiPayloadError('DELETE from relationship requires array of resource identifiers')
|
|
45
53
|
}
|
|
54
|
+
params.relationshipData = normalizeRelationshipIdentifiers(params.relationshipData, { api })
|
|
46
55
|
|
|
47
56
|
// Check permissions
|
|
48
57
|
await runHooks('checkPermissions')
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
|
|
2
2
|
import { handleWriteMethodError } from './common.js'
|
|
3
|
+
import { requireExistingResourceId } from '../lib/querying-writing/resource-id-normalization.js'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* DELETE
|
|
@@ -26,7 +27,11 @@ export default async function deleteMethod ({
|
|
|
26
27
|
context.method = 'delete'
|
|
27
28
|
|
|
28
29
|
// Set the ID in context
|
|
29
|
-
context.id = params.id
|
|
30
|
+
context.id = requireExistingResourceId(params.id, {
|
|
31
|
+
scopeOptions,
|
|
32
|
+
vars,
|
|
33
|
+
scopeName
|
|
34
|
+
})
|
|
30
35
|
|
|
31
36
|
// Set scopeName in context (needed for broadcasting)
|
|
32
37
|
context.scopeName = scopeName
|
|
@@ -33,6 +33,7 @@ export default async function enrichAttributesMethod ({ context, params, runHook
|
|
|
33
33
|
const computedFields = scopes[scopeName]?.vars?.schemaInfo?.computed || {}
|
|
34
34
|
const fieldGetters = scopes[scopeName]?.vars?.schemaInfo?.fieldGetters || {}
|
|
35
35
|
const sortedGetterFields = scopes[scopeName]?.vars?.schemaInfo?.sortedGetterFields || []
|
|
36
|
+
const queryFields = scopes[scopeName]?.vars?.queryFields || {}
|
|
36
37
|
|
|
37
38
|
// Make a copy of attributes for transformation
|
|
38
39
|
const transformedAttributes = { ...attributes }
|
|
@@ -72,7 +73,11 @@ export default async function enrichAttributesMethod ({ context, params, runHook
|
|
|
72
73
|
// Filter hidden fields from attributes based on visibility rules
|
|
73
74
|
// This removes hidden:true fields and normallyHidden:true fields (unless requested)
|
|
74
75
|
const requestedFields = parentContext?.queryParams?.fields?.[scopeName]
|
|
75
|
-
const filteredAttributes = filterHiddenFields(
|
|
76
|
+
const filteredAttributes = filterHiddenFields(
|
|
77
|
+
transformedAttributes,
|
|
78
|
+
{ structure: { ...schemaStructure, ...queryFields } },
|
|
79
|
+
requestedFields
|
|
80
|
+
)
|
|
76
81
|
|
|
77
82
|
// Handle virtual fields - they need to be added from parent context if available
|
|
78
83
|
// Virtual fields come from input and need to be preserved in responses
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
|
|
2
2
|
import { findRelationshipDefinition } from './common.js'
|
|
3
3
|
import { buildRelationshipUrl } from '../lib/querying/url-helpers.js'
|
|
4
|
+
import { requireExistingResourceId } from '../lib/querying-writing/resource-id-normalization.js'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* GET RELATED
|
|
@@ -12,9 +13,13 @@ import { buildRelationshipUrl } from '../lib/querying/url-helpers.js'
|
|
|
12
13
|
* @param {object} queryParams - Standard query parameters
|
|
13
14
|
* @returns {Promise<object>} Related resources with full data
|
|
14
15
|
*/
|
|
15
|
-
export default async function getRelatedMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api }) {
|
|
16
|
+
export default async function getRelatedMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeOptions, scopeName, api }) {
|
|
16
17
|
context.method = 'getRelated'
|
|
17
|
-
context.id = params.id
|
|
18
|
+
context.id = requireExistingResourceId(params.id, {
|
|
19
|
+
scopeOptions,
|
|
20
|
+
vars,
|
|
21
|
+
scopeName
|
|
22
|
+
})
|
|
18
23
|
context.relationshipName = params.relationshipName
|
|
19
24
|
context.queryParams = params.queryParams || {}
|
|
20
25
|
context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
|
|
2
2
|
import { findRelationshipDefinition } from './common.js'
|
|
3
3
|
import { buildRelationshipUrl } from '../lib/querying/url-helpers.js'
|
|
4
|
+
import { requireExistingResourceId } from '../lib/querying-writing/resource-id-normalization.js'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* GET RELATIONSHIP
|
|
@@ -11,9 +12,13 @@ import { buildRelationshipUrl } from '../lib/querying/url-helpers.js'
|
|
|
11
12
|
* @param {string} relationshipName - The name of the relationship
|
|
12
13
|
* @returns {Promise<object>} Relationship linkage with links
|
|
13
14
|
*/
|
|
14
|
-
export default async function getRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api }) {
|
|
15
|
+
export default async function getRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeOptions, scopeName, api }) {
|
|
15
16
|
context.method = 'getRelationship'
|
|
16
|
-
context.id = params.id
|
|
17
|
+
context.id = requireExistingResourceId(params.id, {
|
|
18
|
+
scopeOptions,
|
|
19
|
+
vars,
|
|
20
|
+
scopeName
|
|
21
|
+
})
|
|
17
22
|
context.relationshipName = params.relationshipName
|
|
18
23
|
context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
19
24
|
|