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,857 +0,0 @@
|
|
|
1
|
-
1 /**
|
|
2
|
-
2 * Transformation functions for simplified API mode
|
|
3
|
-
3 *
|
|
4
|
-
4 * @description
|
|
5
|
-
5 * This module enables "simplified mode" - a developer-friendly alternative
|
|
6
|
-
6 * to verbose JSON:API format. It allows APIs to:
|
|
7
|
-
7 * - Accept plain JavaScript objects as input
|
|
8
|
-
8 * - Return flat response objects instead of nested JSON:API
|
|
9
|
-
9 * - Automatically convert between formats transparently
|
|
10
|
-
10 * - Maintain JSON:API compliance internally
|
|
11
|
-
11 */
|
|
12
|
-
12
|
|
13
|
-
13 import { RestApiValidationError } from '../../../../lib/rest-api-errors.js'
|
|
14
|
-
14
|
|
15
|
-
15 /**
|
|
16
|
-
16 * Transforms simplified plain object into JSON:API format
|
|
17
|
-
17 *
|
|
18
|
-
18 * @param {Object} scope - Scope containing input data
|
|
19
|
-
19 * @param {Object} scope.inputRecord - Plain object or JSON:API document
|
|
20
|
-
20 * @param {Object} deps - Dependencies with context
|
|
21
|
-
21 * @param {string} deps.context.scopeName - Resource type name
|
|
22
|
-
22 * @param {Object} deps.context.schemaStructure - Schema with field definitions
|
|
23
|
-
23 * @param {Object} deps.context.schemaRelationships - Relationship configurations
|
|
24
|
-
24 * @returns {Object} JSON:API formatted document
|
|
25
|
-
25 *
|
|
26
|
-
26 * @example
|
|
27
|
-
27 * // Input: Simple object with only attributes
|
|
28
|
-
28 * const input = {
|
|
29
|
-
29 * id: '123',
|
|
30
|
-
30 * title: 'My Article',
|
|
31
|
-
31 * content: 'Article content here'
|
|
32
|
-
32 * };
|
|
33
|
-
33 * const result = transformSimplifiedToJsonApi(
|
|
34
|
-
34 * { inputRecord: input },
|
|
35
|
-
35 * { context: { scopeName: 'articles', schemaStructure: schema } }
|
|
36
|
-
36 * );
|
|
37
|
-
37 *
|
|
38
|
-
38 * // Output: Proper JSON:API structure
|
|
39
|
-
39 * // {
|
|
40
|
-
40 * // data: {
|
|
41
|
-
41 * // type: 'articles',
|
|
42
|
-
42 * // id: '123',
|
|
43
|
-
43 * // attributes: {
|
|
44
|
-
44 * // title: 'My Article',
|
|
45
|
-
45 * // content: 'Article content here'
|
|
46
|
-
46 * // }
|
|
47
|
-
47 * // }
|
|
48
|
-
48 * // }
|
|
49
|
-
49 *
|
|
50
|
-
50 * @example
|
|
51
|
-
51 * // Input: Object with relationship name (preferred way)
|
|
52
|
-
52 * const schema = {
|
|
53
|
-
53 * title: { type: 'string' },
|
|
54
|
-
54 * author_id: {
|
|
55
|
-
55 * type: 'number',
|
|
56
|
-
56 * belongsTo: 'users',
|
|
57
|
-
57 * as: 'author'
|
|
58
|
-
58 * }
|
|
59
|
-
59 * };
|
|
60
|
-
60 * const input = {
|
|
61
|
-
61 * id: '456',
|
|
62
|
-
62 * title: 'Another Article',
|
|
63
|
-
63 * author: 789 // Using relationship name
|
|
64
|
-
64 * };
|
|
65
|
-
65 *
|
|
66
|
-
66 * // Output: Relationship becomes JSON:API relationship
|
|
67
|
-
67 * // {
|
|
68
|
-
68 * // data: {
|
|
69
|
-
69 * // type: 'articles',
|
|
70
|
-
70 * // id: '456',
|
|
71
|
-
71 * // attributes: {
|
|
72
|
-
72 * // title: 'Another Article' // author removed from attributes
|
|
73
|
-
73 * // },
|
|
74
|
-
74 * // relationships: {
|
|
75
|
-
75 * // author: {
|
|
76
|
-
76 * // data: { type: 'users', id: '789' }
|
|
77
|
-
77 * // }
|
|
78
|
-
78 * // }
|
|
79
|
-
79 * // }
|
|
80
|
-
80 * // }
|
|
81
|
-
81 *
|
|
82
|
-
82 * @example
|
|
83
|
-
83 * // Input: Object with relationship name (only supported way)
|
|
84
|
-
84 * const input = {
|
|
85
|
-
85 * id: '456',
|
|
86
|
-
86 * title: 'Another Article',
|
|
87
|
-
87 * author: 789 // Using relationship name
|
|
88
|
-
88 * };
|
|
89
|
-
89 * // Output: Same JSON:API structure as above
|
|
90
|
-
90 *
|
|
91
|
-
91 * @example
|
|
92
|
-
92 * // Input: Many-to-many relationship
|
|
93
|
-
93 * const relationships = {
|
|
94
|
-
94 * tags: {
|
|
95
|
-
95 * type: 'manyToMany',
|
|
96
|
-
96 * through: 'article_tags',
|
|
97
|
-
97 * foreignKey: 'article_id',
|
|
98
|
-
98 * otherKey: 'tag_id',
|
|
99
|
-
99 * target: 'tags'
|
|
100
|
-
100 * }
|
|
101
|
-
101 * };
|
|
102
|
-
102 * const input = {
|
|
103
|
-
103 * title: 'Tagged Article',
|
|
104
|
-
104 * tags: ['10', '20', '30'] // Array of IDs
|
|
105
|
-
105 * };
|
|
106
|
-
106 *
|
|
107
|
-
107 * // Output: Array becomes relationship data array
|
|
108
|
-
108 * // {
|
|
109
|
-
109 * // data: {
|
|
110
|
-
110 * // type: 'articles',
|
|
111
|
-
111 * // attributes: {
|
|
112
|
-
112 * // title: 'Tagged Article'
|
|
113
|
-
113 * // },
|
|
114
|
-
114 * // relationships: {
|
|
115
|
-
115 * // tags: {
|
|
116
|
-
116 * // data: [
|
|
117
|
-
117 * // { type: 'tags', id: '10' },
|
|
118
|
-
118 * // { type: 'tags', id: '20' },
|
|
119
|
-
119 * // { type: 'tags', id: '30' }
|
|
120
|
-
120 * // ]
|
|
121
|
-
121 * // }
|
|
122
|
-
122 * // }
|
|
123
|
-
123 * // }
|
|
124
|
-
124 * // }
|
|
125
|
-
125 *
|
|
126
|
-
126 * @example
|
|
127
|
-
127 * // Input: Already JSON:API formatted
|
|
128
|
-
128 * const input = {
|
|
129
|
-
129 * data: {
|
|
130
|
-
130 * type: 'articles',
|
|
131
|
-
131 * id: '1',
|
|
132
|
-
132 * attributes: { title: 'Already formatted' }
|
|
133
|
-
133 * }
|
|
134
|
-
134 * };
|
|
135
|
-
135 * // Output: Returned unchanged (detected by data.type)
|
|
136
|
-
136 *
|
|
137
|
-
137 * @example
|
|
138
|
-
138 * // Input: Polymorphic relationship with minimal object format
|
|
139
|
-
139 * const relationships = {
|
|
140
|
-
140 * commentable: {
|
|
141
|
-
141 * belongsToPolymorphic: {
|
|
142
|
-
142 * types: ['posts', 'videos'],
|
|
143
|
-
143 * typeField: 'commentable_type',
|
|
144
|
-
144 * idField: 'commentable_id'
|
|
145
|
-
145 * }
|
|
146
|
-
146 * }
|
|
147
|
-
147 * };
|
|
148
|
-
148 * const input = {
|
|
149
|
-
149 * text: 'Great content!',
|
|
150
|
-
150 * commentable: { id: '456', _type: 'posts' } // Minimal object format
|
|
151
|
-
151 * };
|
|
152
|
-
152 *
|
|
153
|
-
153 * // Output: Proper JSON:API structure for polymorphic
|
|
154
|
-
154 * // {
|
|
155
|
-
155 * // data: {
|
|
156
|
-
156 * // type: 'comments',
|
|
157
|
-
157 * // attributes: {
|
|
158
|
-
158 * // text: 'Great content!'
|
|
159
|
-
159 * // },
|
|
160
|
-
160 * // relationships: {
|
|
161
|
-
161 * // commentable: {
|
|
162
|
-
162 * // data: { type: 'posts', id: '456' }
|
|
163
|
-
163 * // }
|
|
164
|
-
164 * // }
|
|
165
|
-
165 * // }
|
|
166
|
-
166 * // }
|
|
167
|
-
167 *
|
|
168
|
-
168 * @example
|
|
169
|
-
169 * // Input: Clearing polymorphic relationship
|
|
170
|
-
170 * const input = {
|
|
171
|
-
171 * text: 'Updated comment',
|
|
172
|
-
172 * commentable: null // Clear the relationship
|
|
173
|
-
173 * };
|
|
174
|
-
174 *
|
|
175
|
-
175 * // Output: Relationship set to null
|
|
176
|
-
176 * // {
|
|
177
|
-
177 * // data: {
|
|
178
|
-
178 * // type: 'comments',
|
|
179
|
-
179 * // attributes: {
|
|
180
|
-
180 * // text: 'Updated comment'
|
|
181
|
-
181 * // },
|
|
182
|
-
182 * // relationships: {
|
|
183
|
-
183 * // commentable: { data: null }
|
|
184
|
-
184 * // }
|
|
185
|
-
185 * // }
|
|
186
|
-
186 * // }
|
|
187
|
-
187 *
|
|
188
|
-
188 * @description
|
|
189
|
-
189 * Used by:
|
|
190
|
-
190 * - rest-api-plugin for POST/PUT/PATCH when simplified: true
|
|
191
|
-
191 * - Processes input before validation and storage
|
|
192
|
-
192 *
|
|
193
|
-
193 * Purpose:
|
|
194
|
-
194 * - Provides developer-friendly plain object API
|
|
195
|
-
195 * - Automatically detects and converts relationships
|
|
196
|
-
196 * - Supports relationship alias notation
|
|
197
|
-
197 * - Supports polymorphic relationships with minimal object format
|
|
198
|
-
198 * - Maintains JSON:API compliance internally
|
|
199
|
-
199 * - Allows gradual migration from simple to JSON:API
|
|
200
|
-
200 *
|
|
201
|
-
201 * Data flow:
|
|
202
|
-
202 * 1. Checks if already JSON:API (has data.type)
|
|
203
|
-
203 * 2. Extracts ID from input object
|
|
204
|
-
204 * 3. Processes belongsTo relationships from schema
|
|
205
|
-
205 * 4. Processes hasMany/manyToMany from relationships
|
|
206
|
-
206 * 5. Processes polymorphic belongsTo from relationships
|
|
207
|
-
207 * 6. Remaining fields become attributes
|
|
208
|
-
208 * 6. Builds proper JSON:API document structure
|
|
209
|
-
209 */
|
|
210
|
-
210 export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
211
|
-
211 // Extract needed values from scope and deps
|
|
212
|
-
212 const input = scope.inputRecord
|
|
213
|
-
213 const scopeName = deps.context.scopeName
|
|
214
|
-
214 const schema = deps.context.schemaStructure
|
|
215
|
-
215 const relationships = deps.context.schemaRelationships
|
|
216
|
-
216
|
|
217
|
-
217 // If already JSON:API format, return as-is
|
|
218
|
-
218 if (input?.data?.type) {
|
|
219
|
-
219 return input
|
|
220
|
-
220 }
|
|
221
|
-
221
|
|
222
|
-
222 const attributes = {}
|
|
223
|
-
223 const relationshipsData = {}
|
|
224
|
-
224 const tempInput = { ...input } // Create a mutable copy of the input
|
|
225
|
-
225
|
|
226
|
-
226 const id = tempInput.id
|
|
227
|
-
227 delete tempInput.id // Extract ID from tempInput
|
|
228
|
-
228
|
|
229
|
-
229 // 1. Process belongsTo relationships (from schema fields)
|
|
230
|
-
230 // Iterate through the schema structure to find belongsTo fields
|
|
231
|
-
231 for (const [fieldName, fieldDef] of Object.entries(schema)) {
|
|
232
|
-
232 if (fieldDef.belongsTo) {
|
|
233
|
-
233 const relAlias = fieldDef.as || fieldName // Use 'as' alias or foreign key name as default alias
|
|
234
|
-
234 let valueToProcess
|
|
235
|
-
235
|
|
236
|
-
236 // Check if the user provided the foreign key name (e.g., country_id) - NO LONGER ALLOWED
|
|
237
|
-
237 if (tempInput[fieldName] !== undefined) {
|
|
238
|
-
238 throw new RestApiValidationError(
|
|
239
|
-
239 `Foreign key field '${fieldName}' is no longer supported for relationship input. ` +
|
|
240
|
-
240 `Use the relationship name '${fieldDef.as || fieldName}' instead.`,
|
|
241
|
-
241 {
|
|
242
|
-
242 fields: [fieldName],
|
|
243
|
-
243 violations: [{
|
|
244
|
-
244 field: fieldName,
|
|
245
|
-
245 rule: 'deprecated_foreign_key',
|
|
246
|
-
246 message: 'Use relationship name instead of foreign key field'
|
|
247
|
-
247 }]
|
|
248
|
-
248 }
|
|
249
|
-
249 )
|
|
250
|
-
250 }
|
|
251
|
-
251
|
|
252
|
-
252 // Check if the user provided the relationship name (e.g., country)
|
|
253
|
-
253 const relName = fieldDef.as || fieldName
|
|
254
|
-
254 if (tempInput[relName] !== undefined) {
|
|
255
|
-
255 valueToProcess = tempInput[relName]
|
|
256
|
-
256 delete tempInput[relName] // Remove from tempInput once processed
|
|
257
|
-
257 }
|
|
258
|
-
258
|
|
259
|
-
259 if (valueToProcess !== undefined) {
|
|
260
|
-
260 relationshipsData[relAlias] = {
|
|
261
|
-
261 data: valueToProcess ? { type: fieldDef.belongsTo, id: String(valueToProcess) } : null
|
|
262
|
-
262 }
|
|
263
|
-
263 }
|
|
264
|
-
264 }
|
|
265
|
-
265 }
|
|
266
|
-
266
|
|
267
|
-
267 // 2. Process hasMany/manyToMany relationships (from relationships object)
|
|
268
|
-
268 // Iterate through the relationships config to find hasMany/manyToMany
|
|
269
|
-
269 for (const [relName, relConfig] of Object.entries(relationships || {})) {
|
|
270
|
-
270 // Check if the user provided data for this relationship name
|
|
271
|
-
271 if (tempInput[relName] !== undefined) {
|
|
272
|
-
272 const value = tempInput[relName]
|
|
273
|
-
273 delete tempInput[relName] // Remove from tempInput once processed
|
|
274
|
-
274
|
|
275
|
-
275 if ((relConfig.type === 'hasMany' || relConfig.type === 'manyToMany') && Array.isArray(value)) {
|
|
276
|
-
276 // Determine the target type based on relationship type:
|
|
277
|
-
277 // - For manyToMany: the targetType is the relationship name itself
|
|
278
|
-
278 // - For hasMany/hasOne: the targetType is specified in the target property
|
|
279
|
-
279 const targetType = relConfig.type === 'manyToMany' ? relName : relConfig.target
|
|
280
|
-
280 relationshipsData[relName] = {
|
|
281
|
-
281 data: value.map(relId => ({ type: targetType, id: String(relId) }))
|
|
282
|
-
282 }
|
|
283
|
-
283 }
|
|
284
|
-
284 // Handle hasOne relationships
|
|
285
|
-
285 else if (relConfig.type === 'hasOne' && value !== undefined) {
|
|
286
|
-
286 relationshipsData[relName] = {
|
|
287
|
-
287 data: value ? { type: relConfig.target, id: String(value) } : null
|
|
288
|
-
288 }
|
|
289
|
-
289 }
|
|
290
|
-
290 // Handle single belongsTo relationships if they were also defined in 'relationships'
|
|
291
|
-
291 // (though typically they are defined in schema.belongsTo)
|
|
292
|
-
292 else if (relConfig.belongsTo && value !== undefined) {
|
|
293
|
-
293 relationshipsData[relName] = {
|
|
294
|
-
294 data: value ? { type: relConfig.belongsTo, id: String(value) } : null
|
|
295
|
-
295 }
|
|
296
|
-
296 }
|
|
297
|
-
297 // Handle polymorphic belongsTo relationships with minimal object format
|
|
298
|
-
298 else if (relConfig.belongsToPolymorphic && value !== undefined) {
|
|
299
|
-
299 if (value === null) {
|
|
300
|
-
300 relationshipsData[relName] = { data: null }
|
|
301
|
-
301 } else if (typeof value === 'object' && value !== null && value.id && value._type) {
|
|
302
|
-
302 relationshipsData[relName] = {
|
|
303
|
-
303 data: { type: value._type, id: String(value.id) }
|
|
304
|
-
304 }
|
|
305
|
-
305 } else {
|
|
306
|
-
306 throw new RestApiValidationError(
|
|
307
|
-
307 `Invalid format for polymorphic relationship '${relName}'. Expected { id: 'value', _type: 'type' } or null`,
|
|
308
|
-
308 {
|
|
309
|
-
309 fields: [relName],
|
|
310
|
-
310 violations: [{
|
|
311
|
-
311 field: relName,
|
|
312
|
-
312 rule: 'polymorphic_format',
|
|
313
|
-
313 message: 'Must be an object with id and _type properties, or null'
|
|
314
|
-
314 }]
|
|
315
|
-
315 }
|
|
316
|
-
316 )
|
|
317
|
-
317 }
|
|
318
|
-
318 }
|
|
319
|
-
319 }
|
|
320
|
-
320 }
|
|
321
|
-
321
|
|
322
|
-
322 // 3. Any remaining fields in tempInput are attributes
|
|
323
|
-
323 Object.assign(attributes, tempInput)
|
|
324
|
-
324
|
|
325
|
-
325 return {
|
|
326
|
-
326 data: {
|
|
327
|
-
327 type: scopeName,
|
|
328
|
-
328 ...(id && { id: String(id) }),
|
|
329
|
-
329 ...(Object.keys(attributes).length > 0 && { attributes }),
|
|
330
|
-
330 ...(Object.keys(relationshipsData).length > 0 && { relationships: relationshipsData })
|
|
331
|
-
331 }
|
|
332
|
-
332 }
|
|
333
|
-
333 }
|
|
334
|
-
334
|
|
335
|
-
335 /**
|
|
336
|
-
336 * Transforms JSON:API response into simplified plain object
|
|
337
|
-
337 *
|
|
338
|
-
338 * @param {Object} scope - Scope containing JSON:API response
|
|
339
|
-
339 * @param {Object} scope.record - JSON:API document to transform
|
|
340
|
-
340 * @param {Object} deps - Dependencies with context
|
|
341
|
-
341 * @param {Object} deps.context.schemaStructure - Schema for mapping relationships
|
|
342
|
-
342 * @param {Object} deps.context.schemaRelationships - Relationship configurations
|
|
343
|
-
343 * @param {Object} deps.context.scopes - All scopes for nested transformations
|
|
344
|
-
344 * @returns {Object|Object[]} Simplified object or array of objects
|
|
345
|
-
345 *
|
|
346
|
-
346 * @example
|
|
347
|
-
347 * // Input: Single resource JSON:API
|
|
348
|
-
348 * const jsonApi = {
|
|
349
|
-
349 * data: {
|
|
350
|
-
350 * type: 'articles',
|
|
351
|
-
351 * id: '123',
|
|
352
|
-
352 * attributes: {
|
|
353
|
-
353 * title: 'My Article',
|
|
354
|
-
354 * content: 'Content here'
|
|
355
|
-
355 * }
|
|
356
|
-
356 * }
|
|
357
|
-
357 * };
|
|
358
|
-
358 * const result = transformJsonApiToSimplified(
|
|
359
|
-
359 * { record: jsonApi },
|
|
360
|
-
360 * { context: { schemaStructure: schema } }
|
|
361
|
-
361 * );
|
|
362
|
-
362 *
|
|
363
|
-
363 * // Output: Flattened object
|
|
364
|
-
364 * // {
|
|
365
|
-
365 * // id: '123',
|
|
366
|
-
366 * // title: 'My Article', // Attributes flattened
|
|
367
|
-
367 * // content: 'Content here'
|
|
368
|
-
368 * // }
|
|
369
|
-
369 *
|
|
370
|
-
370 * @example
|
|
371
|
-
371 * // Input: Resource with belongsTo relationship
|
|
372
|
-
372 * const schema = {
|
|
373
|
-
373 * title: { type: 'string' },
|
|
374
|
-
374 * author_id: { type: 'number', belongsTo: 'users', as: 'author' }
|
|
375
|
-
375 * };
|
|
376
|
-
376 * const jsonApi = {
|
|
377
|
-
377 * data: {
|
|
378
|
-
378 * type: 'articles',
|
|
379
|
-
379 * id: '456',
|
|
380
|
-
380 * attributes: { title: 'Article with Author' },
|
|
381
|
-
381 * relationships: {
|
|
382
|
-
382 * author: {
|
|
383
|
-
383 * data: { type: 'users', id: '789' }
|
|
384
|
-
384 * }
|
|
385
|
-
385 * }
|
|
386
|
-
386 * }
|
|
387
|
-
387 * };
|
|
388
|
-
388 *
|
|
389
|
-
389 * // Output: Relationship becomes foreign key
|
|
390
|
-
390 * // {
|
|
391
|
-
391 * // id: '456',
|
|
392
|
-
392 * // title: 'Article with Author',
|
|
393
|
-
393 * // author_id: '789' // Restored from relationship
|
|
394
|
-
394 * // }
|
|
395
|
-
395 *
|
|
396
|
-
396 * @example
|
|
397
|
-
397 * // Input: Collection response
|
|
398
|
-
398 * const jsonApi = {
|
|
399
|
-
399 * data: [
|
|
400
|
-
400 * {
|
|
401
|
-
401 * type: 'articles',
|
|
402
|
-
402 * id: '1',
|
|
403
|
-
403 * attributes: { title: 'First Article' }
|
|
404
|
-
404 * },
|
|
405
|
-
405 * {
|
|
406
|
-
406 * type: 'articles',
|
|
407
|
-
407 * id: '2',
|
|
408
|
-
408 * attributes: { title: 'Second Article' }
|
|
409
|
-
409 * }
|
|
410
|
-
410 * ],
|
|
411
|
-
411 * meta: { total: 2 },
|
|
412
|
-
412 * links: { next: '/articles?page=2' }
|
|
413
|
-
413 * };
|
|
414
|
-
414 *
|
|
415
|
-
415 * // Output: Preserves meta and links
|
|
416
|
-
416 * // {
|
|
417
|
-
417 * // data: [
|
|
418
|
-
418 * // { id: '1', title: 'First Article' },
|
|
419
|
-
419 * // { id: '2', title: 'Second Article' }
|
|
420
|
-
420 * // ],
|
|
421
|
-
421 * // meta: { total: 2 },
|
|
422
|
-
422 * // links: { next: '/articles?page=2' }
|
|
423
|
-
423 * // }
|
|
424
|
-
424 *
|
|
425
|
-
425 * @example
|
|
426
|
-
426 * // Input: With included resources
|
|
427
|
-
427 * const jsonApi = {
|
|
428
|
-
428 * data: {
|
|
429
|
-
429 * type: 'articles',
|
|
430
|
-
430 * id: '400',
|
|
431
|
-
431 * attributes: { title: 'JavaScript Guide' },
|
|
432
|
-
432 * relationships: {
|
|
433
|
-
433 * author: {
|
|
434
|
-
434 * data: { type: 'authors', id: '75' }
|
|
435
|
-
435 * },
|
|
436
|
-
436 * chapters: {
|
|
437
|
-
437 * data: [
|
|
438
|
-
438 * { type: 'chapters', id: '1001' },
|
|
439
|
-
439 * { type: 'chapters', id: '1002' }
|
|
440
|
-
440 * ]
|
|
441
|
-
441 * }
|
|
442
|
-
442 * }
|
|
443
|
-
443 * },
|
|
444
|
-
444 * included: [
|
|
445
|
-
445 * {
|
|
446
|
-
446 * type: 'authors',
|
|
447
|
-
447 * id: '75',
|
|
448
|
-
448 * attributes: { name: 'Jane Smith' }
|
|
449
|
-
449 * },
|
|
450
|
-
450 * {
|
|
451
|
-
451 * type: 'chapters',
|
|
452
|
-
452 * id: '1001',
|
|
453
|
-
453 * attributes: { number: 1, title: 'Introduction' }
|
|
454
|
-
454 * },
|
|
455
|
-
455 * {
|
|
456
|
-
456 * type: 'chapters',
|
|
457
|
-
457 * id: '1002',
|
|
458
|
-
458 * attributes: { number: 2, title: 'Basics' }
|
|
459
|
-
459 * }
|
|
460
|
-
460 * ]
|
|
461
|
-
461 * };
|
|
462
|
-
462 *
|
|
463
|
-
463 * // Output: Expands included resources
|
|
464
|
-
464 * // {
|
|
465
|
-
465 * // id: '400',
|
|
466
|
-
466 * // title: 'JavaScript Guide',
|
|
467
|
-
467 * // author_id: '75',
|
|
468
|
-
468 * // author: { // Expanded from included
|
|
469
|
-
469 * // id: '75',
|
|
470
|
-
470 * // name: 'Jane Smith'
|
|
471
|
-
471 * // },
|
|
472
|
-
472 * // chapters: [{ id: '1001' }, { id: '1002' }],
|
|
473
|
-
473 * // chapters: [ // Expanded array
|
|
474
|
-
474 * // { id: '1001', number: 1, title: 'Introduction' },
|
|
475
|
-
475 * // { id: '1002', number: 2, title: 'Basics' }
|
|
476
|
-
476 * // ]
|
|
477
|
-
477 * // }
|
|
478
|
-
478 *
|
|
479
|
-
479 * @description
|
|
480
|
-
480 * Used by:
|
|
481
|
-
481 * - rest-api-plugin for GET/QUERY when simplified: true
|
|
482
|
-
482 * - Transforms responses before sending to client
|
|
483
|
-
483 *
|
|
484
|
-
484 * Purpose:
|
|
485
|
-
485 * - Provides cleaner response format for clients
|
|
486
|
-
486 * - Creates minimal relationship objects from relationships
|
|
487
|
-
487 * - Expands included resources for convenience
|
|
488
|
-
488 * - Maintains compatibility with collection metadata
|
|
489
|
-
489 * - Handles both single and array responses
|
|
490
|
-
490 *
|
|
491
|
-
491 * Data flow:
|
|
492
|
-
492 * 1. Detects array vs single resource
|
|
493
|
-
493 * 2. For arrays, preserves meta/links at root
|
|
494
|
-
494 * 3. Calls transformSingleJsonApiToSimplified for each
|
|
495
|
-
495 * 4. Returns simplified structure
|
|
496
|
-
496 */
|
|
497
|
-
497 export const transformJsonApiToSimplified = (scope, deps) => {
|
|
498
|
-
498 // Extract needed values from scope and deps
|
|
499
|
-
499 const jsonApi = scope.record
|
|
500
|
-
500 const schema = deps.context.schemaStructure
|
|
501
|
-
501 const relationships = deps.context.schemaRelationships
|
|
502
|
-
502 const scopes = deps.context.scopes
|
|
503
|
-
503
|
|
504
|
-
504 if (!jsonApi?.data) return jsonApi
|
|
505
|
-
505
|
|
506
|
-
506 // Handle array response (QUERY)
|
|
507
|
-
507 if (Array.isArray(jsonApi.data)) {
|
|
508
|
-
508 const simplifiedData = jsonApi.data.map(item =>
|
|
509
|
-
509 transformSingleJsonApiToSimplified(
|
|
510
|
-
510 { data: item, included: jsonApi.included },
|
|
511
|
-
511 { context: { schemaStructure: schema, schemaRelationships: relationships, scopes } }
|
|
512
|
-
512 )
|
|
513
|
-
513 )
|
|
514
|
-
514
|
|
515
|
-
515 // For query results, return object with data, meta, and links
|
|
516
|
-
516 const result = { data: simplifiedData }
|
|
517
|
-
517
|
|
518
|
-
518 // Add meta if present
|
|
519
|
-
519 if (jsonApi.meta) {
|
|
520
|
-
520 result.meta = jsonApi.meta
|
|
521
|
-
521 }
|
|
522
|
-
522
|
|
523
|
-
523 // Add links if present
|
|
524
|
-
524 if (jsonApi.links) {
|
|
525
|
-
525 result.links = jsonApi.links
|
|
526
|
-
526 }
|
|
527
|
-
527
|
|
528
|
-
528 return result
|
|
529
|
-
529 }
|
|
530
|
-
530
|
|
531
|
-
531 // Handle single response (no change for single resources)
|
|
532
|
-
532 return transformSingleJsonApiToSimplified(
|
|
533
|
-
533 { data: jsonApi.data, included: jsonApi.included },
|
|
534
|
-
534 { context: { schemaStructure: schema, schemaRelationships: relationships, scopes } }
|
|
535
|
-
535 )
|
|
536
|
-
536 }
|
|
537
|
-
537
|
|
538
|
-
538 /**
|
|
539
|
-
539 * Transforms single JSON:API resource into simplified format
|
|
540
|
-
540 *
|
|
541
|
-
541 * @param {Object} scope - Scope with data and included resources
|
|
542
|
-
542 * @param {Object} scope.data - Single JSON:API resource object
|
|
543
|
-
543 * @param {Array} scope.included - Array of included resources
|
|
544
|
-
544 * @param {Object} deps - Dependencies with context
|
|
545
|
-
545 * @param {Object} deps.context.schemaStructure - Resource schema
|
|
546
|
-
546 * @param {Object} deps.context.schemaRelationships - Relationship configs
|
|
547
|
-
547 * @param {Object} deps.context.scopes - All scopes for recursion
|
|
548
|
-
548 * @returns {Object} Simplified plain object
|
|
549
|
-
549 *
|
|
550
|
-
550 * @example
|
|
551
|
-
551 * // Input: Basic resource with relationships
|
|
552
|
-
552 * const data = {
|
|
553
|
-
553 * type: 'comments',
|
|
554
|
-
554 * id: '100',
|
|
555
|
-
555 * attributes: {
|
|
556
|
-
556 * text: 'Great article!',
|
|
557
|
-
557 * created_at: '2024-01-15'
|
|
558
|
-
558 * },
|
|
559
|
-
559 * relationships: {
|
|
560
|
-
560 * author: {
|
|
561
|
-
561 * data: { type: 'users', id: '50' }
|
|
562
|
-
562 * },
|
|
563
|
-
563 * article: {
|
|
564
|
-
564 * data: { type: 'articles', id: '200' }
|
|
565
|
-
565 * }
|
|
566
|
-
566 * }
|
|
567
|
-
567 * };
|
|
568
|
-
568 * const schema = {
|
|
569
|
-
569 * text: { type: 'string' },
|
|
570
|
-
570 * created_at: { type: 'datetime' },
|
|
571
|
-
571 * author_id: { type: 'number', belongsTo: 'users', as: 'author' },
|
|
572
|
-
572 * article_id: { type: 'number', belongsTo: 'articles', as: 'article' }
|
|
573
|
-
573 * };
|
|
574
|
-
574 * const result = transformSingleJsonApiToSimplified(
|
|
575
|
-
575 * { data: data, included: null },
|
|
576
|
-
576 * { context: { schemaStructure: schema } }
|
|
577
|
-
577 * );
|
|
578
|
-
578 *
|
|
579
|
-
579 * // Output: Minimal relationship objects
|
|
580
|
-
580 * // {
|
|
581
|
-
581 * // id: '100',
|
|
582
|
-
582 * // text: 'Great article!',
|
|
583
|
-
583 * // created_at: '2024-01-15',
|
|
584
|
-
584 * // author: { id: '50' }, // Minimal object from relationships.author
|
|
585
|
-
585 * // article: { id: '200' } // Minimal object from relationships.article
|
|
586
|
-
586 * // }
|
|
587
|
-
587 *
|
|
588
|
-
588 * @example
|
|
589
|
-
589 * // Input: Many-to-many relationships
|
|
590
|
-
590 * const data = {
|
|
591
|
-
591 * type: 'articles',
|
|
592
|
-
592 * id: '300',
|
|
593
|
-
593 * attributes: { title: 'My Article' },
|
|
594
|
-
594 * relationships: {
|
|
595
|
-
595 * tags: {
|
|
596
|
-
596 * data: [
|
|
597
|
-
597 * { type: 'tags', id: '1' },
|
|
598
|
-
598 * { type: 'tags', id: '2' },
|
|
599
|
-
599 * { type: 'tags', id: '3' }
|
|
600
|
-
600 * ]
|
|
601
|
-
601 * }
|
|
602
|
-
602 * }
|
|
603
|
-
603 * };
|
|
604
|
-
604 * const relationships = {
|
|
605
|
-
605 * tags: { manyToMany: { via: 'article_tags' } }
|
|
606
|
-
606 * };
|
|
607
|
-
607 *
|
|
608
|
-
608 * // Output: Minimal relationship objects
|
|
609
|
-
609 * // {
|
|
610
|
-
610 * // id: '300',
|
|
611
|
-
611 * // title: 'My Article',
|
|
612
|
-
612 * // tags: [{ id: '1' }, { id: '2' }, { id: '3' }] // Minimal objects
|
|
613
|
-
613 * // }
|
|
614
|
-
614 *
|
|
615
|
-
615 * @example
|
|
616
|
-
616 * // Input: Polymorphic relationship
|
|
617
|
-
617 * const data = {
|
|
618
|
-
618 * type: 'reviews',
|
|
619
|
-
619 * id: '500',
|
|
620
|
-
620 * attributes: { rating: 5, comment: 'Great!' },
|
|
621
|
-
621 * relationships: {
|
|
622
|
-
622 * reviewable: {
|
|
623
|
-
623 * data: { type: 'authors', id: '75' }
|
|
624
|
-
624 * }
|
|
625
|
-
625 * }
|
|
626
|
-
626 * };
|
|
627
|
-
627 * const relationships = {
|
|
628
|
-
628 * reviewable: {
|
|
629
|
-
629 * belongsToPolymorphic: {
|
|
630
|
-
630 * types: ['authors', 'publishers'],
|
|
631
|
-
631 * typeField: 'reviewable_type',
|
|
632
|
-
632 * idField: 'reviewable_id'
|
|
633
|
-
633 * }
|
|
634
|
-
634 * }
|
|
635
|
-
635 * };
|
|
636
|
-
636 *
|
|
637
|
-
637 * // Output: Minimal polymorphic object with type
|
|
638
|
-
638 * // {
|
|
639
|
-
639 * // id: '500',
|
|
640
|
-
640 * // rating: 5,
|
|
641
|
-
641 * // comment: 'Great!',
|
|
642
|
-
642 * // reviewable: { id: '75', type: 'authors' } // Minimal object with type
|
|
643
|
-
643 * // }
|
|
644
|
-
644 *
|
|
645
|
-
645 * @example
|
|
646
|
-
646 * // Input: Polymorphic relationship with included resource
|
|
647
|
-
647 * const dataWithInclude = {
|
|
648
|
-
648 * type: 'reviews',
|
|
649
|
-
649 * id: '500',
|
|
650
|
-
650 * attributes: { rating: 5, comment: 'Great!' },
|
|
651
|
-
651 * relationships: {
|
|
652
|
-
652 * reviewable: {
|
|
653
|
-
653 * data: { type: 'authors', id: '75' }
|
|
654
|
-
654 * }
|
|
655
|
-
655 * }
|
|
656
|
-
656 * };
|
|
657
|
-
657 * const included = [
|
|
658
|
-
658 * {
|
|
659
|
-
659 * type: 'authors',
|
|
660
|
-
660 * id: '75',
|
|
661
|
-
661 * attributes: { name: 'Jane Smith', bio: 'Fantastic author' }
|
|
662
|
-
662 * }
|
|
663
|
-
663 * ];
|
|
664
|
-
664 *
|
|
665
|
-
665 * // Output: Full polymorphic object (type field removed as redundant)
|
|
666
|
-
666 * // {
|
|
667
|
-
667 * // id: '500',
|
|
668
|
-
668 * // rating: 5,
|
|
669
|
-
669 * // comment: 'Great!',
|
|
670
|
-
670 * // reviewable: { // Full object when included
|
|
671
|
-
671 * // id: '75',
|
|
672
|
-
672 * // name: 'Jane Smith',
|
|
673
|
-
673 * // bio: 'Fantastic author'
|
|
674
|
-
674 * // }
|
|
675
|
-
675 * // }
|
|
676
|
-
676 *
|
|
677
|
-
677 * @example
|
|
678
|
-
678 * // Input: With included resources (recursive expansion)
|
|
679
|
-
679 * const data = {
|
|
680
|
-
680 * type: 'books',
|
|
681
|
-
681 * id: '400',
|
|
682
|
-
682 * attributes: { title: 'JavaScript Guide' },
|
|
683
|
-
683 * relationships: {
|
|
684
|
-
684 * author: {
|
|
685
|
-
685 * data: { type: 'authors', id: '75' }
|
|
686
|
-
686 * },
|
|
687
|
-
687 * chapters: {
|
|
688
|
-
688 * data: [
|
|
689
|
-
689 * { type: 'chapters', id: '1001' },
|
|
690
|
-
690 * { type: 'chapters', id: '1002' }
|
|
691
|
-
691 * ]
|
|
692
|
-
692 * }
|
|
693
|
-
693 * }
|
|
694
|
-
694 * };
|
|
695
|
-
695 * const included = [
|
|
696
|
-
696 * {
|
|
697
|
-
697 * type: 'authors',
|
|
698
|
-
698 * id: '75',
|
|
699
|
-
699 * attributes: { name: 'Jane Smith' },
|
|
700
|
-
700 * relationships: {
|
|
701
|
-
701 * company: {
|
|
702
|
-
702 * data: { type: 'companies', id: '5' }
|
|
703
|
-
703 * }
|
|
704
|
-
704 * }
|
|
705
|
-
705 * },
|
|
706
|
-
706 * {
|
|
707
|
-
707 * type: 'chapters',
|
|
708
|
-
708 * id: '1001',
|
|
709
|
-
709 * attributes: { number: 1, title: 'Introduction' }
|
|
710
|
-
710 * },
|
|
711
|
-
711 * {
|
|
712
|
-
712 * type: 'chapters',
|
|
713
|
-
713 * id: '1002',
|
|
714
|
-
714 * attributes: { number: 2, title: 'Basics' }
|
|
715
|
-
715 * }
|
|
716
|
-
716 * ];
|
|
717
|
-
717 *
|
|
718
|
-
718 * // Output: Recursively expanded
|
|
719
|
-
719 * // {
|
|
720
|
-
720 * // id: '400',
|
|
721
|
-
721 * // title: 'JavaScript Guide',
|
|
722
|
-
722 * // author: { // Expanded and transformed
|
|
723
|
-
723 * // id: '75',
|
|
724
|
-
724 * // name: 'Jane Smith',
|
|
725
|
-
725 * // company: { id: '5' } // Author's relationships processed as minimal objects!
|
|
726
|
-
726 * // },
|
|
727
|
-
727 * // chapters: [
|
|
728
|
-
728 * // { id: '1001', number: 1, title: 'Introduction' },
|
|
729
|
-
729 * // { id: '1002', number: 2, title: 'Basics' }
|
|
730
|
-
730 * // ]
|
|
731
|
-
731 * // }
|
|
732
|
-
732 *
|
|
733
|
-
733 * @description
|
|
734
|
-
734 * Used by:
|
|
735
|
-
735 * - transformJsonApiToSimplified for each resource
|
|
736
|
-
736 * - Calls itself recursively for included resources
|
|
737
|
-
737 *
|
|
738
|
-
738 * Purpose:
|
|
739
|
-
739 * - Core transformation logic for single resources
|
|
740
|
-
740 * - Creates minimal relationship objects from relationships
|
|
741
|
-
741 * - Creates arrays of minimal relationship objects for to-many relationships
|
|
742
|
-
742 * - Recursively expands and transforms included data
|
|
743
|
-
743 * - Handles polymorphic relationships
|
|
744
|
-
744 *
|
|
745
|
-
745 * Data flow:
|
|
746
|
-
746 * 1. Copies ID and attributes to result
|
|
747
|
-
747 * 2. Processes each relationship:
|
|
748
|
-
748 * - BelongsTo: creates minimal relationship object
|
|
749
|
-
749 * - Polymorphic: creates minimal relationship object with type
|
|
750
|
-
750 * - HasMany/ManyToMany: creates array of minimal relationship objects
|
|
751
|
-
751 * 3. If included data exists:
|
|
752
|
-
752 * - Finds matching included resources
|
|
753
|
-
753 * - Recursively transforms them (preserving their relationships!)
|
|
754
|
-
754 * - Adds transformed objects/arrays to result
|
|
755
|
-
755 *
|
|
756
|
-
756 * @private
|
|
757
|
-
757 */
|
|
758
|
-
758 export const transformSingleJsonApiToSimplified = (scope, deps) => {
|
|
759
|
-
759 // Extract needed values from scope and deps
|
|
760
|
-
760 const data = scope.data
|
|
761
|
-
761 const included = scope.included
|
|
762
|
-
762 const schema = deps.context.schemaStructure
|
|
763
|
-
763 const relationships = deps.context.schemaRelationships
|
|
764
|
-
764 const scopes = deps.context.scopes
|
|
765
|
-
765
|
|
766
|
-
766 const simplified = {}
|
|
767
|
-
767
|
|
768
|
-
768 // Add ID
|
|
769
|
-
769 if (data.id) {
|
|
770
|
-
770 simplified.id = data.id
|
|
771
|
-
771 }
|
|
772
|
-
772
|
|
773
|
-
773 // Add attributes
|
|
774
|
-
774 Object.assign(simplified, data.attributes || {})
|
|
775
|
-
775
|
|
776
|
-
776 // Process relationships to create minimal objects
|
|
777
|
-
777 if (data.relationships) {
|
|
778
|
-
778 for (const [relName, relData] of Object.entries(data.relationships)) {
|
|
779
|
-
779 // Find schema field for this belongsTo relationship
|
|
780
|
-
780 const schemaEntry = Object.entries(schema).find(([_, def]) => def.as === relName)
|
|
781
|
-
781
|
|
782
|
-
782 if (schemaEntry) {
|
|
783
|
-
783 const [fieldName, fieldDef] = schemaEntry
|
|
784
|
-
784 if (fieldDef.belongsTo && relData.data) {
|
|
785
|
-
785 // Create minimal relationship object (no more foreign key field)
|
|
786
|
-
786 simplified[relName] = { id: relData.data.id }
|
|
787
|
-
787 }
|
|
788
|
-
788 }
|
|
789
|
-
789
|
|
790
|
-
790 // Check if this is a polymorphic relationship from the relationships config
|
|
791
|
-
791 const rel = relationships?.[relName]
|
|
792
|
-
792 if (rel?.belongsToPolymorphic && relData.data) {
|
|
793
|
-
793 // Create minimal relationship object for polymorphic (no more type/id fields)
|
|
794
|
-
794 simplified[relName] = { id: relData.data.id, _type: relData.data.type }
|
|
795
|
-
795 }
|
|
796
|
-
796
|
|
797
|
-
797 // Handle to-many relationships (create minimal objects with just IDs)
|
|
798
|
-
798 if (rel?.type === 'hasMany' || rel?.type === 'manyToMany') {
|
|
799
|
-
799 if (relData.data && Array.isArray(relData.data)) {
|
|
800
|
-
800 simplified[relName] = relData.data.map(item => ({ id: item.id }))
|
|
801
|
-
801 }
|
|
802
|
-
802 }
|
|
803
|
-
803
|
|
804
|
-
804 // Handle includes (nested objects)
|
|
805
|
-
805 if (included && relData.data) {
|
|
806
|
-
806 const findIncluded = (ref) =>
|
|
807
|
-
807 included.find(inc => inc.type === ref.type && inc.id === ref.id)
|
|
808
|
-
808
|
|
809
|
-
809 if (Array.isArray(relData.data)) {
|
|
810
|
-
810 const nestedData = relData.data.map(findIncluded).filter(Boolean)
|
|
811
|
-
811 if (nestedData.length > 0) {
|
|
812
|
-
812 simplified[relName] = nestedData.map(item => {
|
|
813
|
-
813 // For each included record, transform it recursively to restore its relationship fields
|
|
814
|
-
814 const itemSimplified = transformSingleJsonApiToSimplified(
|
|
815
|
-
815 { data: item, included: null },
|
|
816
|
-
816 {
|
|
817
|
-
817 context: {
|
|
818
|
-
818 schemaStructure: scopes?.[item.type]?.vars?.schemaInfo?.schema || {},
|
|
819
|
-
819 schemaRelationships: scopes?.[item.type]?.vars?.schemaInfo?.schemaRelationships || {},
|
|
820
|
-
820 scopes
|
|
821
|
-
821 }
|
|
822
|
-
822 }
|
|
823
|
-
823 )
|
|
824
|
-
824 // For polymorphic relationships, add _type field to identify the resource type
|
|
825
|
-
825 if (rel?.belongsToPolymorphic) {
|
|
826
|
-
826 itemSimplified._type = item.type
|
|
827
|
-
827 }
|
|
828
|
-
828 return itemSimplified
|
|
829
|
-
829 })
|
|
830
|
-
830 }
|
|
831
|
-
831 } else {
|
|
832
|
-
832 const nestedData = findIncluded(relData.data)
|
|
833
|
-
833 if (nestedData) {
|
|
834
|
-
834 // Transform the single included record to restore its relationship fields
|
|
835
|
-
835 const itemSimplified = transformSingleJsonApiToSimplified(
|
|
836
|
-
836 { data: nestedData, included: null },
|
|
837
|
-
837 {
|
|
838
|
-
838 context: {
|
|
839
|
-
839 schemaStructure: scopes?.[nestedData.type]?.vars?.schemaInfo?.schema || {},
|
|
840
|
-
840 schemaRelationships: scopes?.[nestedData.type]?.vars?.schemaInfo?.schemaRelationships || {},
|
|
841
|
-
841 scopes
|
|
842
|
-
842 }
|
|
843
|
-
843 }
|
|
844
|
-
844 )
|
|
845
|
-
845 // For polymorphic relationships, add _type field to identify the resource type
|
|
846
|
-
846 if (rel?.belongsToPolymorphic) {
|
|
847
|
-
847 itemSimplified._type = nestedData.type
|
|
848
|
-
848 }
|
|
849
|
-
849 simplified[relName] = itemSimplified
|
|
850
|
-
850 }
|
|
851
|
-
851 }
|
|
852
|
-
852 }
|
|
853
|
-
853 }
|
|
854
|
-
854 }
|
|
855
|
-
855
|
|
856
|
-
856 return simplified
|
|
857
|
-
857 }
|