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
|
@@ -1,353 +0,0 @@
|
|
|
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
|
-
4
|
|
5
|
-
5 /**
|
|
6
|
-
6 * GET RELATED
|
|
7
|
-
7 * Retrieves the actual related resources (full data)
|
|
8
|
-
8 * GET /api/articles/1/comments
|
|
9
|
-
9 *
|
|
10
|
-
10 * @param {string} id - The ID of the parent resource
|
|
11
|
-
11 * @param {string} relationshipName - The name of the relationship
|
|
12
|
-
12 * @param {object} queryParams - Standard query parameters
|
|
13
|
-
13 * @returns {Promise<object>} Related resources with full data
|
|
14
|
-
14 */
|
|
15
|
-
15 export default async function getRelatedMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api }) {
|
|
16
|
-
16 context.method = 'getRelated'
|
|
17
|
-
17 context.id = params.id
|
|
18
|
-
18 context.relationshipName = params.relationshipName
|
|
19
|
-
19 context.queryParams = params.queryParams || {}
|
|
20
|
-
20 context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
21
|
-
21 context.transaction = params.transaction
|
|
22
|
-
22 context.db = context.transaction || api.knex.instance
|
|
23
|
-
23
|
|
24
|
-
24 // Validate the relationship exists
|
|
25
|
-
25 const relDef = findRelationshipDefinition(context.schemaInfo, context.relationshipName)
|
|
26
|
-
26
|
|
27
|
-
27 if (!relDef) {
|
|
28
|
-
28 throw new RestApiResourceError(
|
|
29
|
-
29 `Relationship '${context.relationshipName}' not found on resource '${scopeName}'`,
|
|
30
|
-
30 { subtype: 'relationship_not_found' }
|
|
31
|
-
31 )
|
|
32
|
-
32 }
|
|
33
|
-
33
|
|
34
|
-
34 // Determine target type based on relationship type
|
|
35
|
-
35 let targetType
|
|
36
|
-
36 if (relDef.type === 'hasMany' || relDef.type === 'hasOne') {
|
|
37
|
-
37 targetType = relDef.target
|
|
38
|
-
38 } else if (relDef.type === 'manyToMany') {
|
|
39
|
-
39 targetType = context.relationshipName // For manyToMany, use the relationship name
|
|
40
|
-
40 } else if (relDef.belongsTo) {
|
|
41
|
-
41 targetType = relDef.belongsTo // belongsTo still in schema
|
|
42
|
-
42 }
|
|
43
|
-
43
|
|
44
|
-
44 if (!targetType || !scopes[targetType]) {
|
|
45
|
-
45 throw new RestApiResourceError(
|
|
46
|
-
46 `Related resource type '${targetType}' not found`,
|
|
47
|
-
47 { subtype: 'related_type_not_found' }
|
|
48
|
-
48 )
|
|
49
|
-
49 }
|
|
50
|
-
50
|
|
51
|
-
51 // Check permissions
|
|
52
|
-
52 await runHooks('checkPermissions')
|
|
53
|
-
53 await runHooks('checkPermissionsGetRelated')
|
|
54
|
-
54
|
|
55
|
-
55 // Verify parent exists
|
|
56
|
-
56 const exists = await helpers.dataExists({
|
|
57
|
-
57 scopeName,
|
|
58
|
-
58 context: { db: context.db, id: context.id, schemaInfo: context.schemaInfo }
|
|
59
|
-
59 })
|
|
60
|
-
60
|
|
61
|
-
61 if (!exists) {
|
|
62
|
-
62 throw new RestApiResourceError('Resource not found', { subtype: 'not_found' })
|
|
63
|
-
63 }
|
|
64
|
-
64
|
|
65
|
-
65 // Handle to-one relationships (belongsTo and hasOne)
|
|
66
|
-
66 // For example: GET /api/books/1/country or GET /api/books/1/publisher
|
|
67
|
-
67 if (relDef.belongsTo || relDef.type === 'hasOne') {
|
|
68
|
-
68 // OPTIMIZATION: Detect if we actually need to make two API calls
|
|
69
|
-
69 //
|
|
70
|
-
70 // The naive approach always makes 2 calls:
|
|
71
|
-
71 // 1. Get parent with relationship included (fetches FULL related record)
|
|
72
|
-
72 // 2. Extract just the ID and fetch the same record again with queryParams
|
|
73
|
-
73 //
|
|
74
|
-
74 // This optimization checks if there are queryParams that would affect
|
|
75
|
-
75 // the related resource. If not, we can use the data from the first call.
|
|
76
|
-
76 const hasRelevantQueryParams = context.queryParams && (
|
|
77
|
-
77 // Check for includes on the related resource (e.g., ?include=some.nested.relation)
|
|
78
|
-
78 context.queryParams.include?.length > 0 ||
|
|
79
|
-
79 // Check for field selection on the related resource (e.g., ?fields[countries]=name,code)
|
|
80
|
-
80 context.queryParams.fields?.[targetType] ||
|
|
81
|
-
81 // Note: Filters and sorting don't make sense for a single to-one relationship
|
|
82
|
-
82 // so we don't check for them
|
|
83
|
-
83 false
|
|
84
|
-
84 )
|
|
85
|
-
85
|
|
86
|
-
86 if (hasRelevantQueryParams) {
|
|
87
|
-
87 // CASE 1: Has queryParams that affect the related resource
|
|
88
|
-
88 // We need to make two calls to properly apply the queryParams
|
|
89
|
-
89
|
|
90
|
-
90 // First call: Get parent with minimal data (just need the related ID)
|
|
91
|
-
91 const parent = await scope.get({
|
|
92
|
-
92 id: context.id,
|
|
93
|
-
93 queryParams: {
|
|
94
|
-
94 include: [context.relationshipName],
|
|
95
|
-
95 fields: { [scopeName]: vars.idProperty || 'id' } // Only fetch parent ID to minimize data
|
|
96
|
-
96 },
|
|
97
|
-
97 transaction: context.transaction,
|
|
98
|
-
98 simplified: false,
|
|
99
|
-
99 isTransport: params.isTransport
|
|
100
|
-
100 })
|
|
101
|
-
101
|
|
102
|
-
102 const relatedId = parent.data.relationships?.[context.relationshipName]?.data?.id
|
|
103
|
-
103 if (!relatedId) {
|
|
104
|
-
104 return {
|
|
105
|
-
105 links: { self: buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false) },
|
|
106
|
-
106 data: null
|
|
107
|
-
107 }
|
|
108
|
-
108 }
|
|
109
|
-
109
|
|
110
|
-
110 // Second call: Get the related resource with all queryParams applied
|
|
111
|
-
111 const related = await api.resources[targetType].get({
|
|
112
|
-
112 id: relatedId,
|
|
113
|
-
113 queryParams: context.queryParams,
|
|
114
|
-
114 transaction: context.transaction,
|
|
115
|
-
115 simplified: false,
|
|
116
|
-
116 isTransport: params.isTransport
|
|
117
|
-
117 })
|
|
118
|
-
118
|
|
119
|
-
119 return {
|
|
120
|
-
120 links: { self: buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false) },
|
|
121
|
-
121 data: related.data,
|
|
122
|
-
122 included: related.included
|
|
123
|
-
123 }
|
|
124
|
-
124 } else {
|
|
125
|
-
125 // CASE 2: No queryParams that affect the related resource
|
|
126
|
-
126 // We can get everything in one call and extract from included
|
|
127
|
-
127
|
|
128
|
-
128 // Single call: Get parent with full related resource included
|
|
129
|
-
129 const parent = await scope.get({
|
|
130
|
-
130 id: context.id,
|
|
131
|
-
131 queryParams: {
|
|
132
|
-
132 include: [context.relationshipName],
|
|
133
|
-
133 fields: context.queryParams.fields // Respect any field selections for the parent
|
|
134
|
-
134 },
|
|
135
|
-
135 transaction: context.transaction,
|
|
136
|
-
136 simplified: false,
|
|
137
|
-
137 isTransport: params.isTransport
|
|
138
|
-
138 })
|
|
139
|
-
139
|
|
140
|
-
140 // Extract the related resource from the parent's relationships
|
|
141
|
-
141 const relatedId = parent.data.relationships?.[context.relationshipName]?.data?.id
|
|
142
|
-
142 if (!relatedId) {
|
|
143
|
-
143 return {
|
|
144
|
-
144 links: { self: buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false) },
|
|
145
|
-
145 data: null
|
|
146
|
-
146 }
|
|
147
|
-
147 }
|
|
148
|
-
148
|
|
149
|
-
149 // Find the full related resource in the included array
|
|
150
|
-
150 // The include system already fetched it for us!
|
|
151
|
-
151 const relatedResource = parent.included?.find(
|
|
152
|
-
152 r => r.type === targetType && r.id === relatedId
|
|
153
|
-
153 )
|
|
154
|
-
154
|
|
155
|
-
155 return {
|
|
156
|
-
156 links: { self: buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false) },
|
|
157
|
-
157 data: relatedResource || null
|
|
158
|
-
158 }
|
|
159
|
-
159 }
|
|
160
|
-
160 }
|
|
161
|
-
161
|
|
162
|
-
162 // Handle simple hasMany (one-to-many, NOT many-to-many)
|
|
163
|
-
163 if (relDef.type === 'hasMany') {
|
|
164
|
-
164 // Check if this is a polymorphic relationship using 'via'
|
|
165
|
-
165 if (relDef.via) {
|
|
166
|
-
166 // Polymorphic hasMany relationship
|
|
167
|
-
167 // Example: publishers hasMany reviews via reviewable
|
|
168
|
-
168 const targetRelationships = scopes[targetType].vars.schemaInfo.schemaRelationships
|
|
169
|
-
169 const viaRel = targetRelationships?.[relDef.via]
|
|
170
|
-
170
|
|
171
|
-
171 if (!viaRel?.belongsToPolymorphic) {
|
|
172
|
-
172 throw new RestApiResourceError(
|
|
173
|
-
173 `Via relationship '${relDef.via}' not found or not polymorphic in '${targetType}'`,
|
|
174
|
-
174 { subtype: 'invalid_via_relationship' }
|
|
175
|
-
175 )
|
|
176
|
-
176 }
|
|
177
|
-
177
|
|
178
|
-
178 const { typeField, idField } = viaRel.belongsToPolymorphic
|
|
179
|
-
179
|
|
180
|
-
180 // Add polymorphic filters
|
|
181
|
-
181 const filters = {
|
|
182
|
-
182 ...context.queryParams.filters,
|
|
183
|
-
183 [typeField]: scopeName,
|
|
184
|
-
184 [idField]: context.id
|
|
185
|
-
185 }
|
|
186
|
-
186
|
|
187
|
-
187 const result = await api.resources[targetType].query({
|
|
188
|
-
188 queryParams: { ...context.queryParams, filters },
|
|
189
|
-
189 transaction: context.transaction,
|
|
190
|
-
190 simplified: false,
|
|
191
|
-
191 isTransport: params.isTransport
|
|
192
|
-
192 })
|
|
193
|
-
193
|
|
194
|
-
194 if (!result.links) {
|
|
195
|
-
195 result.links = {}
|
|
196
|
-
196 }
|
|
197
|
-
197 result.links.self = buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false)
|
|
198
|
-
198 return result
|
|
199
|
-
199 } else {
|
|
200
|
-
200 // Regular hasMany with foreignKey
|
|
201
|
-
201 // Need to find the relationship name in the target resource that points back to this resource
|
|
202
|
-
202 const targetSchema = scopes[targetType].vars.schemaInfo.schemaStructure
|
|
203
|
-
203 let relationshipFilterName = null
|
|
204
|
-
204
|
|
205
|
-
205 // Find the field in target schema that has the foreign key and get its relationship name
|
|
206
|
-
206 for (const [fieldName, fieldDef] of Object.entries(targetSchema)) {
|
|
207
|
-
207 if (fieldName === relDef.foreignKey && fieldDef.belongsTo === scopeName && fieldDef.as) {
|
|
208
|
-
208 relationshipFilterName = fieldDef.as
|
|
209
|
-
209 break
|
|
210
|
-
210 }
|
|
211
|
-
211 }
|
|
212
|
-
212
|
|
213
|
-
213 // Fall back to foreign key if no relationship name found (shouldn't happen with proper schema)
|
|
214
|
-
214 const filterKey = relationshipFilterName || relDef.foreignKey
|
|
215
|
-
215
|
|
216
|
-
216 const filters = {
|
|
217
|
-
217 ...context.queryParams.filters,
|
|
218
|
-
218 [filterKey]: context.id
|
|
219
|
-
219 }
|
|
220
|
-
220
|
|
221
|
-
221 const result = await api.resources[targetType].query({
|
|
222
|
-
222 queryParams: { ...context.queryParams, filters },
|
|
223
|
-
223 transaction: context.transaction,
|
|
224
|
-
224 simplified: false,
|
|
225
|
-
225 isTransport: params.isTransport
|
|
226
|
-
226 })
|
|
227
|
-
227
|
|
228
|
-
228 if (!result.links) {
|
|
229
|
-
229 result.links = {}
|
|
230
|
-
230 }
|
|
231
|
-
231 result.links.self = buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false)
|
|
232
|
-
232 return result
|
|
233
|
-
233 }
|
|
234
|
-
234 }
|
|
235
|
-
235
|
|
236
|
-
236 // Handle many-to-many relationships
|
|
237
|
-
237 // For example: GET /api/authors/1/books (where authors and books are linked via book_authors)
|
|
238
|
-
238 if (relDef?.through) {
|
|
239
|
-
239 if (api.anyapi?.links?.listMany) {
|
|
240
|
-
240 const identifiers = await api.anyapi.links.listMany({
|
|
241
|
-
241 context,
|
|
242
|
-
242 scopeName,
|
|
243
|
-
243 relName: context.relationshipName,
|
|
244
|
-
244 })
|
|
245
|
-
245
|
|
246
|
-
246 const results = []
|
|
247
|
-
247 for (const identifier of identifiers) {
|
|
248
|
-
248 const related = await api.resources[targetType].get({
|
|
249
|
-
249 id: identifier.id,
|
|
250
|
-
250 queryParams: context.queryParams,
|
|
251
|
-
251 transaction: context.transaction,
|
|
252
|
-
252 simplified: false,
|
|
253
|
-
253 isTransport: params.isTransport,
|
|
254
|
-
254 })
|
|
255
|
-
255 if (related?.data) {
|
|
256
|
-
256 results.push(related.data)
|
|
257
|
-
257 }
|
|
258
|
-
258 }
|
|
259
|
-
259
|
|
260
|
-
260 return {
|
|
261
|
-
261 links: {
|
|
262
|
-
262 self: buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false)
|
|
263
|
-
263 },
|
|
264
|
-
264 data: results,
|
|
265
|
-
265 }
|
|
266
|
-
266 }
|
|
267
|
-
267
|
|
268
|
-
268 // Legacy fallback
|
|
269
|
-
269 const pivotResource = relDef.through
|
|
270
|
-
270 const foreignKey = relDef.foreignKey
|
|
271
|
-
271 const otherKey = relDef.otherKey
|
|
272
|
-
272 if (!foreignKey || !otherKey) {
|
|
273
|
-
273 throw new Error('Missing foreignKey or otherKey in many-to-many relationship')
|
|
274
|
-
274 }
|
|
275
|
-
275 const pivotScope = scopes[pivotResource]
|
|
276
|
-
276 if (!pivotScope) {
|
|
277
|
-
277 throw new RestApiResourceError(
|
|
278
|
-
278 `Pivot table resource '${pivotResource}' not found`,
|
|
279
|
-
279 { subtype: 'pivot_table_not_found' }
|
|
280
|
-
280 )
|
|
281
|
-
281 }
|
|
282
|
-
282 const pivotSchema = pivotScope.vars.schemaInfo.schemaStructure
|
|
283
|
-
283 let parentRelationshipName = null
|
|
284
|
-
284 for (const [fieldName, fieldDef] of Object.entries(pivotSchema)) {
|
|
285
|
-
285 if (fieldName === foreignKey && fieldDef.belongsTo === scopeName && fieldDef.as) {
|
|
286
|
-
286 parentRelationshipName = fieldDef.as
|
|
287
|
-
287 break
|
|
288
|
-
288 }
|
|
289
|
-
289 }
|
|
290
|
-
290 const filterKey = parentRelationshipName || foreignKey
|
|
291
|
-
291 const pivotFilters = {
|
|
292
|
-
292 [filterKey]: context.id
|
|
293
|
-
293 }
|
|
294
|
-
294 const pivotResult = await api.resources[pivotResource].query({
|
|
295
|
-
295 queryParams: {
|
|
296
|
-
296 filters: pivotFilters,
|
|
297
|
-
297 include: [context.relationshipName],
|
|
298
|
-
298 fields: context.queryParams.fields,
|
|
299
|
-
299 sort: context.queryParams.sort,
|
|
300
|
-
300 page: context.queryParams.page
|
|
301
|
-
301 },
|
|
302
|
-
302 transaction: context.transaction,
|
|
303
|
-
303 simplified: false,
|
|
304
|
-
304 isTransport: params.isTransport
|
|
305
|
-
305 })
|
|
306
|
-
306 let includedResources = pivotResult.included?.filter((r) => r.type === targetType) || []
|
|
307
|
-
307
|
|
308
|
-
308 if (includedResources.length === 0) {
|
|
309
|
-
309 const pivotData = pivotResult.data || []
|
|
310
|
-
310 const relatedIds = [...new Set(pivotData
|
|
311
|
-
311 .map((item) => {
|
|
312
|
-
312 const attrId = item?.attributes?.[otherKey]
|
|
313
|
-
313 if (attrId !== null && attrId !== undefined) {
|
|
314
|
-
314 return String(attrId)
|
|
315
|
-
315 }
|
|
316
|
-
316 const relationships = item?.relationships || {}
|
|
317
|
-
317 for (const rel of Object.values(relationships)) {
|
|
318
|
-
318 const data = rel?.data
|
|
319
|
-
319 if (data?.type === targetType && data?.id != null) {
|
|
320
|
-
320 return String(data.id)
|
|
321
|
-
321 }
|
|
322
|
-
322 }
|
|
323
|
-
323 return null
|
|
324
|
-
324 })
|
|
325
|
-
325 .filter((id) => id !== null)
|
|
326
|
-
326 )]
|
|
327
|
-
327
|
|
328
|
-
328 if (relatedIds.length > 0) {
|
|
329
|
-
329 includedResources = []
|
|
330
|
-
330 for (const relatedId of relatedIds) {
|
|
331
|
-
331 const related = await api.resources[targetType].get({
|
|
332
|
-
332 id: relatedId,
|
|
333
|
-
333 queryParams: context.queryParams,
|
|
334
|
-
334 transaction: context.transaction,
|
|
335
|
-
335 simplified: false,
|
|
336
|
-
336 isTransport: params.isTransport,
|
|
337
|
-
337 })
|
|
338
|
-
338 if (related?.data) {
|
|
339
|
-
339 includedResources.push(related.data)
|
|
340
|
-
340 }
|
|
341
|
-
341 }
|
|
342
|
-
342 }
|
|
343
|
-
343 }
|
|
344
|
-
344
|
|
345
|
-
345 return {
|
|
346
|
-
346 links: {
|
|
347
|
-
347 self: buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false)
|
|
348
|
-
348 },
|
|
349
|
-
349 data: includedResources,
|
|
350
|
-
350 meta: pivotResult.meta
|
|
351
|
-
351 }
|
|
352
|
-
352 }
|
|
353
|
-
353 }
|
|
@@ -1,61 +0,0 @@
|
|
|
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
|
-
4
|
|
5
|
-
5 /**
|
|
6
|
-
6 * GET RELATIONSHIP
|
|
7
|
-
7 * Retrieves relationship linkage data (just resource identifiers)
|
|
8
|
-
8 * GET /api/articles/1/relationships/author
|
|
9
|
-
9 *
|
|
10
|
-
10 * @param {string} id - The ID of the resource
|
|
11
|
-
11 * @param {string} relationshipName - The name of the relationship
|
|
12
|
-
12 * @returns {Promise<object>} Relationship linkage with links
|
|
13
|
-
13 */
|
|
14
|
-
14 export default async function getRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api }) {
|
|
15
|
-
15 context.method = 'getRelationship'
|
|
16
|
-
16 context.id = params.id
|
|
17
|
-
17 context.relationshipName = params.relationshipName
|
|
18
|
-
18 context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
19
|
-
19
|
|
20
|
-
20 // Validate the relationship exists
|
|
21
|
-
21 const relDef = findRelationshipDefinition(context.schemaInfo, context.relationshipName)
|
|
22
|
-
22
|
|
23
|
-
23 if (!relDef) {
|
|
24
|
-
24 throw new RestApiResourceError(
|
|
25
|
-
25 `Relationship '${context.relationshipName}' not found on resource '${scopeName}'`,
|
|
26
|
-
26 { subtype: 'relationship_not_found' }
|
|
27
|
-
27 )
|
|
28
|
-
28 }
|
|
29
|
-
29
|
|
30
|
-
30 // Check permissions
|
|
31
|
-
31 await runHooks('checkPermissions')
|
|
32
|
-
32 await runHooks('checkPermissionsGetRelationship')
|
|
33
|
-
33
|
|
34
|
-
34 // Reuse existing get method with minimal fields
|
|
35
|
-
35 const fullRecord = await scope.get({
|
|
36
|
-
36 id: context.id,
|
|
37
|
-
37 queryParams: {
|
|
38
|
-
38 include: [context.relationshipName],
|
|
39
|
-
39 fields: { [scopeName]: vars.idProperty || 'id' }
|
|
40
|
-
40 },
|
|
41
|
-
41 transaction: context.transaction,
|
|
42
|
-
42 simplified: false,
|
|
43
|
-
43 isTransport: params.isTransport
|
|
44
|
-
44 })
|
|
45
|
-
45
|
|
46
|
-
46 if (!fullRecord || !fullRecord.data) {
|
|
47
|
-
47 throw new RestApiResourceError('Resource not found', { subtype: 'not_found' })
|
|
48
|
-
48 }
|
|
49
|
-
49
|
|
50
|
-
50 // Extract just the relationship data
|
|
51
|
-
51 const relationshipData = fullRecord.data.relationships?.[context.relationshipName]
|
|
52
|
-
52
|
|
53
|
-
53 // Build response with links
|
|
54
|
-
54 return {
|
|
55
|
-
55 links: {
|
|
56
|
-
56 self: buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, true),
|
|
57
|
-
57 related: buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false)
|
|
58
|
-
58 },
|
|
59
|
-
59 data: relationshipData?.data || (relDef.type === 'hasMany' || relDef.type === 'manyToMany' ? [] : null)
|
|
60
|
-
60 }
|
|
61
|
-
61 };
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
1 import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
|
|
2
|
-
2 import { validateGetPayload } from '../lib/querying-writing/payload-validators.js'
|
|
3
|
-
3 import { normalizeRecordAttributes } from '../lib/querying-writing/database-value-normalizers.js'
|
|
4
|
-
4 import { getRequestedComputedFields } from '../lib/querying-writing/knex-field-helpers.js'
|
|
5
|
-
5 import { transformJsonApiToSimplified } from '../lib/querying-writing/simplified-helpers.js'
|
|
6
|
-
6 import { cascadeConfig } from './common.js'
|
|
7
|
-
7
|
|
8
|
-
8 /**
|
|
9
|
-
9 * GET
|
|
10
|
-
10 * Retrieves a single resource by its type and ID.
|
|
11
|
-
11 * @param {(string|number)} id - The unique ID of the resource to fetch.
|
|
12
|
-
12 * @param {object} [queryParams={}] - Optional. An object to customize the query.
|
|
13
|
-
13 * @param {string} [queryParams.include] - A comma-separated string of relationship paths to sideload (e.g., "authors,publisher").
|
|
14
|
-
14 * @param {object} [queryParams.fields] - An object to request only specific fields (sparse fieldsets). Keys are resource types, values are comma-separated field names.
|
|
15
|
-
15 * @returns {Promise<object>} A Promise that resolves to the JSON:API response document.
|
|
16
|
-
16 */
|
|
17
|
-
17 export default async function getMethod ({
|
|
18
|
-
18 params,
|
|
19
|
-
19 context,
|
|
20
|
-
20 vars,
|
|
21
|
-
21 helpers,
|
|
22
|
-
22 scope,
|
|
23
|
-
23 scopes,
|
|
24
|
-
24 runHooks,
|
|
25
|
-
25 apiOptions,
|
|
26
|
-
26 pluginOptions,
|
|
27
|
-
27 scopeOptions,
|
|
28
|
-
28 scopeName,
|
|
29
|
-
29 api
|
|
30
|
-
30 }) {
|
|
31
|
-
31 context.method = 'get'
|
|
32
|
-
32
|
|
33
|
-
33 // Determine which simplified setting to use based on transport
|
|
34
|
-
34 const isTransport = params.isTransport === true
|
|
35
|
-
35
|
|
36
|
-
36 // Use vars which automatically cascade from scope to global
|
|
37
|
-
37 const defaultSimplified = isTransport ? vars.simplifiedTransport : vars.simplifiedApi
|
|
38
|
-
38
|
|
39
|
-
39 // Get simplified setting - from params only (per-call override) or use default
|
|
40
|
-
40 context.simplified = params.simplified !== undefined ? params.simplified : defaultSimplified
|
|
41
|
-
41
|
|
42
|
-
42 // Assign common context properties
|
|
43
|
-
43 context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
44
|
-
44 context.queryParams = params.queryParams || {}
|
|
45
|
-
45
|
|
46
|
-
46 // These only make sense as parameter per query
|
|
47
|
-
47 context.queryParams.fields = cascadeConfig('fields', [context.queryParams], {})
|
|
48
|
-
48 context.queryParams.include = cascadeConfig('include', [context.queryParams], [])
|
|
49
|
-
49
|
|
50
|
-
50 context.transaction = params.transaction
|
|
51
|
-
51 context.db = context.transaction || api.knex.instance
|
|
52
|
-
52
|
|
53
|
-
53 context.scopeName = scopeName
|
|
54
|
-
54
|
|
55
|
-
55 // These are just shortcuts used in this function and will be returned
|
|
56
|
-
56 const schemaStructure = context.schemaInfo.schemaInstance.structure
|
|
57
|
-
57 const schemaRelationships = context.schemaInfo.schemaRelationships
|
|
58
|
-
58
|
|
59
|
-
59 context.id = params.id
|
|
60
|
-
60
|
|
61
|
-
61 // Validate GET request to ensure required parameters are present and properly formatted.
|
|
62
|
-
62 // This checks that 'id' parameter exists and is not empty (you can't GET without an ID),
|
|
63
|
-
63 // validates 'include' contains valid relationship names (not arbitrary fields), and ensures
|
|
64
|
-
64 // 'fields' for sparse fieldsets follow the format fields[type]=comma,separated,list.
|
|
65
|
-
65 // Example: validates id: '123' exists, include: ['author', 'tags'] are real relationships.
|
|
66
|
-
66 validateGetPayload({ id: context.id, queryParams: context.queryParams }, vars.includeDepthLimit)
|
|
67
|
-
67
|
|
68
|
-
68 // Fetch minimal record for authorization checks
|
|
69
|
-
69 const minimalRecord = await helpers.dataGetMinimal({
|
|
70
|
-
70 scopeName,
|
|
71
|
-
71 context,
|
|
72
|
-
72 runHooks
|
|
73
|
-
73 })
|
|
74
|
-
74
|
|
75
|
-
75 if (!minimalRecord) {
|
|
76
|
-
76 throw new RestApiResourceError(
|
|
77
|
-
77 'Resource not found',
|
|
78
|
-
78 {
|
|
79
|
-
79 subtype: 'not_found',
|
|
80
|
-
80 resourceType: scopeName,
|
|
81
|
-
81 resourceId: context.id
|
|
82
|
-
82 }
|
|
83
|
-
83 )
|
|
84
|
-
84 }
|
|
85
|
-
85
|
|
86
|
-
86 context.minimalRecord = minimalRecord
|
|
87
|
-
87
|
|
88
|
-
88 // Centralised checkPermissions function
|
|
89
|
-
89 await scope.checkPermissions({
|
|
90
|
-
90 method: 'get',
|
|
91
|
-
91 originalContext: context,
|
|
92
|
-
92 })
|
|
93
|
-
93
|
|
94
|
-
94 await runHooks('beforeData')
|
|
95
|
-
95 await runHooks('beforeDataGet')
|
|
96
|
-
96
|
|
97
|
-
97 context.record = await helpers.dataGet({
|
|
98
|
-
98 scopeName,
|
|
99
|
-
99 context,
|
|
100
|
-
100 runHooks
|
|
101
|
-
101 })
|
|
102
|
-
102
|
|
103
|
-
103 // Check if record was found - storage layer returns null/undefined for non-existent records.
|
|
104
|
-
104 // This generates a proper 404 error with JSON:API error format instead of returning empty data.
|
|
105
|
-
105 if (!context.record || !context.record.data) {
|
|
106
|
-
106 throw new RestApiResourceError(
|
|
107
|
-
107 'Resource not found',
|
|
108
|
-
108 {
|
|
109
|
-
109 subtype: 'not_found',
|
|
110
|
-
110 resourceType: scopeName,
|
|
111
|
-
111 resourceId: context.id
|
|
112
|
-
112 }
|
|
113
|
-
113 )
|
|
114
|
-
114 }
|
|
115
|
-
115
|
|
116
|
-
116 // Normalize database values (e.g., convert 1/0 to true/false for booleans)
|
|
117
|
-
117 context.record = normalizeRecordAttributes(context.record, scopes)
|
|
118
|
-
118
|
|
119
|
-
119 await runHooks('checkDataPermissions')
|
|
120
|
-
120 await runHooks('checkDataPermissionsGet')
|
|
121
|
-
121
|
|
122
|
-
122 // Make a backup
|
|
123
|
-
123 context.originalRecord = structuredClone(context.record)
|
|
124
|
-
124
|
|
125
|
-
125 // This will enhance record, which is the WHOLE JSON:API record
|
|
126
|
-
126 await runHooks('enrichRecord')
|
|
127
|
-
127
|
|
128
|
-
128 // Get computed field information for main resource
|
|
129
|
-
129 const computedFields = scope.vars.schemaInfo?.computed || {}
|
|
130
|
-
130 const requestedFields = context.queryParams.fields?.[scopeName]
|
|
131
|
-
131 const requestedComputedFields = getRequestedComputedFields(scopeName, requestedFields, computedFields)
|
|
132
|
-
132
|
|
133
|
-
133 // Enrich attributes for the main resource
|
|
134
|
-
134 // Pass computedDependencies from context (set by dataGet in Knex plugin)
|
|
135
|
-
135 // This tells enrichAttributes which fields to remove after computation
|
|
136
|
-
136 context.record.data.attributes = await scope.enrichAttributes({
|
|
137
|
-
137 attributes: context.record.data.attributes,
|
|
138
|
-
138 parentContext: context,
|
|
139
|
-
139 requestedComputedFields,
|
|
140
|
-
140 isMainResource: true,
|
|
141
|
-
141 computedDependencies: context.computedDependencies // Fields to remove if not requested
|
|
142
|
-
142 })
|
|
143
|
-
143
|
|
144
|
-
144 for (const entry of (context.record.included || [])) {
|
|
145
|
-
145 const entryScope = scopes[entry.type]
|
|
146
|
-
146 const entryComputed = entryScope.vars.schemaInfo?.computed || {}
|
|
147
|
-
147 const entryRequestedFields = context.queryParams.fields?.[entry.type]
|
|
148
|
-
148 const entryRequestedComputed = getRequestedComputedFields(
|
|
149
|
-
149 entry.type,
|
|
150
|
-
150 entryRequestedFields,
|
|
151
|
-
151 entryComputed
|
|
152
|
-
152 )
|
|
153
|
-
153
|
|
154
|
-
154 entry.attributes = await entryScope.enrichAttributes({
|
|
155
|
-
155 attributes: entry.attributes,
|
|
156
|
-
156 parentContext: context,
|
|
157
|
-
157 requestedComputedFields: entryRequestedComputed,
|
|
158
|
-
158 isMainResource: false,
|
|
159
|
-
159 computedDependencies: entry.__$jsonrestapi_computed_deps$__
|
|
160
|
-
160 })
|
|
161
|
-
161 }
|
|
162
|
-
162
|
|
163
|
-
163 await runHooks('enrichRecordWithRelationships')
|
|
164
|
-
164
|
|
165
|
-
165 // The called hooks should NOT change context.record
|
|
166
|
-
166 await runHooks('finish')
|
|
167
|
-
167 await runHooks('finishGet')
|
|
168
|
-
168
|
|
169
|
-
169 // Get schema info for transformation
|
|
170
|
-
170 context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
171
|
-
171
|
|
172
|
-
172 // Transform output if in simplified mode
|
|
173
|
-
173 if (context.simplified) {
|
|
174
|
-
174 // Convert JSON:API response back to simplified format
|
|
175
|
-
175 // Example: {data: {type: 'posts', id: '1', attributes: {title: 'My Post'}, relationships: {author: {data: {type: 'users', id: '123'}}}}}
|
|
176
|
-
176 // becomes: {id: '1', title: 'My Post', author_id: '123'} - flattens structure and restores foreign keys
|
|
177
|
-
177 return transformJsonApiToSimplified(
|
|
178
|
-
178 { record: context.record },
|
|
179
|
-
179 { context: { schemaStructure, schemaRelationships, scopes } }
|
|
180
|
-
180 )
|
|
181
|
-
181 }
|
|
182
|
-
182
|
|
183
|
-
183 return context.record
|
|
184
|
-
184 }
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
1 /**
|
|
2
|
-
2 * PATCH RELATIONSHIP
|
|
3
|
-
3 * Completely replaces a relationship
|
|
4
|
-
4 * PATCH /api/articles/1/relationships/author
|
|
5
|
-
5 *
|
|
6
|
-
6 * @param {string} id - The ID of the resource
|
|
7
|
-
7 * @param {string} relationshipName - The name of the relationship
|
|
8
|
-
8 * @param {object|array} relationshipData - New relationship data
|
|
9
|
-
9 * @returns {Promise<void>} 204 No Content
|
|
10
|
-
10 */
|
|
11
|
-
11 export default async function patchRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api }) {
|
|
12
|
-
12 context.method = 'patchRelationship'
|
|
13
|
-
13 context.id = params.id
|
|
14
|
-
14 context.relationshipName = params.relationshipName
|
|
15
|
-
15
|
|
16
|
-
16 // Transaction handling
|
|
17
|
-
17 context.transaction = params.transaction ||
|
|
18
|
-
18 (helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null)
|
|
19
|
-
19 context.shouldCommit = !params.transaction && !!context.transaction
|
|
20
|
-
20 context.db = context.transaction || api.knex.instance
|
|
21
|
-
21
|
|
22
|
-
22 try {
|
|
23
|
-
23 // Check permissions
|
|
24
|
-
24 await runHooks('checkPermissions')
|
|
25
|
-
25 await runHooks('checkPermissionsPatchRelationship')
|
|
26
|
-
26
|
|
27
|
-
27 // Reuse existing patch with relationship data
|
|
28
|
-
28 await scope.patch({
|
|
29
|
-
29 id: context.id,
|
|
30
|
-
30 inputRecord: {
|
|
31
|
-
31 data: {
|
|
32
|
-
32 type: scopeName,
|
|
33
|
-
33 id: context.id,
|
|
34
|
-
34 relationships: {
|
|
35
|
-
35 [params.relationshipName]: { data: params.relationshipData }
|
|
36
|
-
36 }
|
|
37
|
-
37 }
|
|
38
|
-
38 },
|
|
39
|
-
39 transaction: context.transaction,
|
|
40
|
-
40 simplified: false,
|
|
41
|
-
41 isTransport: params.isTransport
|
|
42
|
-
42 })
|
|
43
|
-
43
|
|
44
|
-
44 await runHooks('finish')
|
|
45
|
-
45 await runHooks('finishPatchRelationship')
|
|
46
|
-
46
|
|
47
|
-
47 if (context.shouldCommit) {
|
|
48
|
-
48 await context.transaction.commit()
|
|
49
|
-
49 }
|
|
50
|
-
50
|
|
51
|
-
51 // 204 No Content
|
|
52
|
-
52 } catch (error) {
|
|
53
|
-
53 await handleWriteMethodError(error, context, 'PATCH_RELATIONSHIP', scopeName, log)
|
|
54
|
-
54 }
|
|
55
|
-
55 }
|