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,72 +0,0 @@
|
|
|
1
|
-
1 /**
|
|
2
|
-
2 * Default data helper implementations that instruct users to install a storage plugin
|
|
3
|
-
3 *
|
|
4
|
-
4 * @description
|
|
5
|
-
5 * These default implementations ensure the REST API plugin fails gracefully
|
|
6
|
-
6 * when no storage plugin is installed. Each method throws a descriptive error
|
|
7
|
-
7 * guiding users to install a storage implementation.
|
|
8
|
-
8 *
|
|
9
|
-
9 * @example
|
|
10
|
-
10 * // When no storage plugin is installed:
|
|
11
|
-
11 * try {
|
|
12
|
-
12 * await api.resources.articles.get({ id: 1 });
|
|
13
|
-
13 * } catch (error) {
|
|
14
|
-
14 * console.log(error.message);
|
|
15
|
-
15 * // "No storage implementation for get. Install a storage plugin."
|
|
16
|
-
16 * }
|
|
17
|
-
17 *
|
|
18
|
-
18 * @example
|
|
19
|
-
19 * // After installing rest-api-knex-plugin:
|
|
20
|
-
20 * api.use(RestApiKnexPlugin, { knex });
|
|
21
|
-
21 * // Now data helpers are replaced with actual implementations
|
|
22
|
-
22 * const article = await api.resources.articles.get({ id: 1 });
|
|
23
|
-
23 * // Works! Returns article from database
|
|
24
|
-
24 *
|
|
25
|
-
25 * Used by:
|
|
26
|
-
26 * - rest-api-plugin sets these as initial data helpers
|
|
27
|
-
27 * - Storage plugins replace these with actual implementations
|
|
28
|
-
28 *
|
|
29
|
-
29 * Purpose:
|
|
30
|
-
30 * - Provides clear error messages when storage is missing
|
|
31
|
-
31 * - Ensures REST API plugin can be installed independently
|
|
32
|
-
32 * - Allows storage plugins to be swapped without changing API
|
|
33
|
-
33 *
|
|
34
|
-
34 * Data flow:
|
|
35
|
-
35 * 1. REST API plugin initializes with these defaults
|
|
36
|
-
36 * 2. User tries to use API without storage plugin
|
|
37
|
-
37 * 3. Clear error message guides them to install storage
|
|
38
|
-
38 * 4. Storage plugin replaces these with real implementations
|
|
39
|
-
39 */
|
|
40
|
-
40 export const defaultDataHelpers = {
|
|
41
|
-
41 dataExists: async function (scope, deps) {
|
|
42
|
-
42 throw new Error('No storage implementation for exists. Install a storage plugin.')
|
|
43
|
-
43 },
|
|
44
|
-
44
|
|
45
|
-
45 dataGet: async function (scope, deps) {
|
|
46
|
-
46 throw new Error('No storage implementation for get. Install a storage plugin.')
|
|
47
|
-
47 },
|
|
48
|
-
48
|
|
49
|
-
49 dataGetMinimal: async function (scope, deps) {
|
|
50
|
-
50 throw new Error('No storage implementation for getMinimal. Install a storage plugin.')
|
|
51
|
-
51 },
|
|
52
|
-
52
|
|
53
|
-
53 dataQuery: async function (scope, deps) {
|
|
54
|
-
54 throw new Error('No storage implementation for query. Install a storage plugin.')
|
|
55
|
-
55 },
|
|
56
|
-
56
|
|
57
|
-
57 dataPost: async function (scope, deps) {
|
|
58
|
-
58 throw new Error('No storage implementation for post. Install a storage plugin.')
|
|
59
|
-
59 },
|
|
60
|
-
60
|
|
61
|
-
61 dataPatch: async function (scope, deps) {
|
|
62
|
-
62 throw new Error('No storage implementation for patch. Install a storage plugin.')
|
|
63
|
-
63 },
|
|
64
|
-
64
|
|
65
|
-
65 dataPut: async function (scope, deps) {
|
|
66
|
-
66 throw new Error('No storage implementation for put. Install a storage plugin.')
|
|
67
|
-
67 },
|
|
68
|
-
68
|
|
69
|
-
69 dataDelete: async function (scope, deps) {
|
|
70
|
-
70 throw new Error('No storage implementation for delete. Install a storage plugin.')
|
|
71
|
-
71 }
|
|
72
|
-
72 }
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
1 /**
|
|
2
|
-
2 * Extracts all foreign key fields from a schema definition
|
|
3
|
-
3 *
|
|
4
|
-
4 * @param {Object} schema - The schema definition (Schema object or plain object)
|
|
5
|
-
5 * @returns {Set<string>} Set of foreign key field names
|
|
6
|
-
6 *
|
|
7
|
-
7 * @example
|
|
8
|
-
8 * // Input: Schema with belongsTo relationships
|
|
9
|
-
9 * const schema = {
|
|
10
|
-
10 * title: { type: 'string' },
|
|
11
|
-
11 * content: { type: 'string' },
|
|
12
|
-
12 * author_id: { type: 'number', belongsTo: 'users', as: 'author' },
|
|
13
|
-
13 * category_id: { type: 'number', belongsTo: 'categories', as: 'category' },
|
|
14
|
-
14 * status: { type: 'string' }
|
|
15
|
-
15 * };
|
|
16
|
-
16 *
|
|
17
|
-
17 * const foreignKeys = getForeignKeyFields(schema);
|
|
18
|
-
18 *
|
|
19
|
-
19 * // Output: Set containing only foreign key fields
|
|
20
|
-
20 * // Set(['author_id', 'category_id'])
|
|
21
|
-
21 *
|
|
22
|
-
22 * @example
|
|
23
|
-
23 * // Input: Works with compiled Schema objects too
|
|
24
|
-
24 * const schemaObject = {
|
|
25
|
-
25 * structure: {
|
|
26
|
-
26 * post_id: { type: 'id', belongsTo: 'posts', as: 'post' },
|
|
27
|
-
27 * user_id: { type: 'id', belongsTo: 'users', as: 'user' }
|
|
28
|
-
28 * }
|
|
29
|
-
29 * };
|
|
30
|
-
30 *
|
|
31
|
-
31 * const foreignKeys = getForeignKeyFields(schemaObject);
|
|
32
|
-
32 *
|
|
33
|
-
33 * // Output: Handles both formats
|
|
34
|
-
34 * // Set(['post_id', 'user_id'])
|
|
35
|
-
35 *
|
|
36
|
-
36 * @description
|
|
37
|
-
37 * Used by:
|
|
38
|
-
38 * - toJsonApi to filter foreign keys from attributes
|
|
39
|
-
39 * - buildFieldSelection to ensure foreign keys are always selected
|
|
40
|
-
40 * - knex-json-api-helpers for field filtering
|
|
41
|
-
41 *
|
|
42
|
-
42 * Purpose:
|
|
43
|
-
43 * - Identifies fields that should be relationships, not attributes
|
|
44
|
-
44 * - Foreign keys are stored in DB but not exposed as JSON:API attributes
|
|
45
|
-
45 * - Ensures these fields are included in SELECT for relationship building
|
|
46
|
-
46 * - Helps transform database structure to JSON:API format
|
|
47
|
-
47 *
|
|
48
|
-
48 * Data flow:
|
|
49
|
-
49 * 1. Accepts schema in either format (plain or with structure property)
|
|
50
|
-
50 * 2. Iterates through all field definitions
|
|
51
|
-
51 * 3. Collects fields with belongsTo property
|
|
52
|
-
52 * 4. Returns Set for efficient lookups
|
|
53
|
-
53 */
|
|
54
|
-
54 export const getForeignKeyFields = (schema) => {
|
|
55
|
-
55 const foreignKeys = new Set()
|
|
56
|
-
56 if (!schema) return foreignKeys
|
|
57
|
-
57
|
|
58
|
-
58 // Handle both Schema objects and plain objects
|
|
59
|
-
59 const schemaStructure = schema.structure || schema
|
|
60
|
-
60
|
|
61
|
-
61 Object.entries(schemaStructure).forEach(([field, def]) => {
|
|
62
|
-
62 if (def.belongsTo) {
|
|
63
|
-
63 foreignKeys.add(field)
|
|
64
|
-
64 }
|
|
65
|
-
65 })
|
|
66
|
-
66 return foreignKeys
|
|
67
|
-
67 }
|
|
68
|
-
68
|
|
69
|
-
69 /**
|
|
70
|
-
70 * Filters hidden fields from attributes based on schema rules.
|
|
71
|
-
71 *
|
|
72
|
-
72 * This pure function removes fields marked as hidden or normallyHidden
|
|
73
|
-
73 * based on the schema definition and requested fields. It ensures that
|
|
74
|
-
74 * sensitive data is never exposed in API responses.
|
|
75
|
-
75 *
|
|
76
|
-
76 * @param {Object} attributes - The attributes object to filter
|
|
77
|
-
77 * @param {Object} schema - The schema object with structure property
|
|
78
|
-
78 * @param {Array<string>|string} requestedFields - Fields explicitly requested (for normallyHidden)
|
|
79
|
-
79 * @returns {Object} Filtered attributes object
|
|
80
|
-
80 *
|
|
81
|
-
81 * @example <caption>Filtering hidden fields</caption>
|
|
82
|
-
82 * const attributes = {
|
|
83
|
-
83 * name: 'John',
|
|
84
|
-
84 * email: 'john@example.com',
|
|
85
|
-
85 * password_hash: 'xxx',
|
|
86
|
-
86 * internal_id: '123'
|
|
87
|
-
87 * };
|
|
88
|
-
88 * const schema = {
|
|
89
|
-
89 * structure: {
|
|
90
|
-
90 * name: { type: 'string' },
|
|
91
|
-
91 * email: { type: 'string' },
|
|
92
|
-
92 * password_hash: { type: 'string', hidden: true },
|
|
93
|
-
93 * internal_id: { type: 'string', normallyHidden: true }
|
|
94
|
-
94 * }
|
|
95
|
-
95 * };
|
|
96
|
-
96 * const filtered = filterHiddenFields(attributes, schema, null);
|
|
97
|
-
97 * // Returns: { name: 'John', email: 'john@example.com' }
|
|
98
|
-
98 * // password_hash (hidden) and internal_id (normallyHidden) are removed
|
|
99
|
-
99 *
|
|
100
|
-
100 * @example <caption>Including normallyHidden fields when requested</caption>
|
|
101
|
-
101 * const filtered = filterHiddenFields(attributes, schema, 'name,internal_id');
|
|
102
|
-
102 * // Returns: { name: 'John', internal_id: '123' }
|
|
103
|
-
103 * // internal_id is included because explicitly requested
|
|
104
|
-
104 * // password_hash is still filtered (always hidden)
|
|
105
|
-
105 */
|
|
106
|
-
106 export const filterHiddenFields = (attributes, schema, requestedFields) => {
|
|
107
|
-
107 const filtered = {}
|
|
108
|
-
108
|
|
109
|
-
109 // Parse requested fields if it's a string (from query params)
|
|
110
|
-
110 // Example: "name,price,cost" -> ['name', 'price', 'cost']
|
|
111
|
-
111 const requested = requestedFields
|
|
112
|
-
112 ? (
|
|
113
|
-
113 typeof requestedFields === 'string'
|
|
114
|
-
114 ? requestedFields.split(',').map(f => f.trim()).filter(f => f)
|
|
115
|
-
115 : requestedFields
|
|
116
|
-
116 )
|
|
117
|
-
117 : null
|
|
118
|
-
118
|
|
119
|
-
119 Object.entries(attributes).forEach(([field, value]) => {
|
|
120
|
-
120 const fieldDef = schema.structure?.[field]
|
|
121
|
-
121
|
|
122
|
-
122 // Never include hidden fields - these are completely invisible
|
|
123
|
-
123 // Example: password_hash with hidden:true is always filtered out
|
|
124
|
-
124 if (fieldDef?.hidden === true) return
|
|
125
|
-
125
|
|
126
|
-
126 // Include normallyHidden fields only if explicitly requested
|
|
127
|
-
127 // Example: 'cost' with normallyHidden:true is only included if user requests it
|
|
128
|
-
128 // via sparse fieldsets like ?fields[products]=name,cost
|
|
129
|
-
129 if (fieldDef?.normallyHidden === true) {
|
|
130
|
-
130 if (!requested || !requested.includes(field)) {
|
|
131
|
-
131 return // Filter out normallyHidden field
|
|
132
|
-
132 }
|
|
133
|
-
133 }
|
|
134
|
-
134
|
|
135
|
-
135 filtered[field] = value
|
|
136
|
-
136 })
|
|
137
|
-
137
|
|
138
|
-
138 return filtered
|
|
139
|
-
139 }
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
1 /**
|
|
2
|
-
2 * @module knex-constants
|
|
3
|
-
3 * @description Shared constants for the REST API Knex plugin
|
|
4
|
-
4 *
|
|
5
|
-
5 * This module contains all the magic strings and shared constants used
|
|
6
|
-
6 * throughout the plugin to avoid repetition and improve maintainability.
|
|
7
|
-
7 */
|
|
8
|
-
8
|
|
9
|
-
9 // Internal property names - using unique names to avoid collisions with user data
|
|
10
|
-
10 export const RELATIONSHIPS_KEY = '__$jsonrestapi_relationships$__'
|
|
11
|
-
11 export const RELATIONSHIP_METADATA_KEY = '__$jsonrestapi_metadata$__'
|
|
12
|
-
12 export const ROW_NUMBER_KEY = '__$jsonrestapi_rn$__'
|
|
13
|
-
13 export const COMPUTED_DEPENDENCIES_KEY = '__$jsonrestapi_computed_deps$__'
|
|
14
|
-
14
|
|
15
|
-
15 // Default query limits
|
|
16
|
-
16 export const DEFAULT_QUERY_LIMIT = 20
|
|
17
|
-
17 export const DEFAULT_MAX_QUERY_LIMIT = 100
|
|
18
|
-
18 export const DEFAULT_MAX_INCLUDE_LIMIT = 1000
|
|
19
|
-
19 export const DEFAULT_INCLUDE_DEPTH_LIMIT = 3
|
|
20
|
-
20
|
|
21
|
-
21 // Schema handling helpers
|
|
22
|
-
22 export const getSchemaStructure = (schema) => schema?.structure || schema || {}
|
|
23
|
-
23 export const getIdProperty = (schemaInfo) => schemaInfo?.idProperty || 'id'
|
|
24
|
-
24
|
|
25
|
-
25 // Default values
|
|
26
|
-
26 export const DEFAULT_PAGE_SIZE = 20
|
|
27
|
-
27 export const DEFAULT_MAX_PAGE_SIZE = 100
|
|
28
|
-
28 export const DEFAULT_PAGE_NUMBER = 1
|
|
29
|
-
29
|
|
30
|
-
30 // Database clients
|
|
31
|
-
31 export const DB_CLIENTS = {
|
|
32
|
-
32 POSTGRES: 'pg',
|
|
33
|
-
33 MYSQL: 'mysql',
|
|
34
|
-
34 MYSQL2: 'mysql2',
|
|
35
|
-
35 SQLITE: 'sqlite3',
|
|
36
|
-
36 MSSQL: 'mssql'
|
|
37
|
-
37 }
|
|
38
|
-
38
|
|
39
|
-
39 // Error subtypes
|
|
40
|
-
40 export const ERROR_SUBTYPES = {
|
|
41
|
-
41 NOT_FOUND: 'not_found',
|
|
42
|
-
42 VALIDATION: 'validation',
|
|
43
|
-
43 PERMISSION: 'permission',
|
|
44
|
-
44 CONFLICT: 'conflict'
|
|
45
|
-
45 }
|
|
@@ -1,396 +0,0 @@
|
|
|
1
|
-
1 /**
|
|
2
|
-
2 * Checks if a field exists only in code, not in the database
|
|
3
|
-
3 *
|
|
4
|
-
4 * @param {string} fieldName - The field name to check
|
|
5
|
-
5 * @param {Object} schemaInfo - Schema info with computed fields and structure
|
|
6
|
-
6 * @returns {boolean} True if field is computed or virtual
|
|
7
|
-
7 *
|
|
8
|
-
8 * @example
|
|
9
|
-
9 * // Input: Check computed field
|
|
10
|
-
10 * const schemaInfo = {
|
|
11
|
-
11 * computed: { profit_margin: { (definition) } },
|
|
12
|
-
12 * schemaStructure: { price: { type: 'number' } }
|
|
13
|
-
13 * };
|
|
14
|
-
14 * isNonDatabaseField('profit_margin', schemaInfo);
|
|
15
|
-
15 * // Output: true (it's computed, not stored)
|
|
16
|
-
16 *
|
|
17
|
-
17 * @example
|
|
18
|
-
18 * // Input: Check virtual field
|
|
19
|
-
19 * const schemaInfo = {
|
|
20
|
-
20 * computed: {},
|
|
21
|
-
21 * schemaStructure: {
|
|
22
|
-
22 * temp_password: { type: 'string', virtual: true }
|
|
23
|
-
23 * }
|
|
24
|
-
24 * };
|
|
25
|
-
25 * isNonDatabaseField('temp_password', schemaInfo);
|
|
26
|
-
26 * // Output: true (it's virtual, not stored)
|
|
27
|
-
27 *
|
|
28
|
-
28 * @description
|
|
29
|
-
29 * Used by:
|
|
30
|
-
30 * - buildFieldSelection to exclude from SELECT queries
|
|
31
|
-
31 * - dataPost/dataPatch to skip non-database fields
|
|
32
|
-
32 * - enrichAttributes to identify computed fields
|
|
33
|
-
33 *
|
|
34
|
-
34 * Purpose:
|
|
35
|
-
35 * - Prevents SQL errors by excluding non-existent columns
|
|
36
|
-
36 * - Identifies fields that need computation or special handling
|
|
37
|
-
37 * - Supports virtual fields for temporary/input-only data
|
|
38
|
-
38 */
|
|
39
|
-
39 export const isNonDatabaseField = (fieldName, schemaInfo) => {
|
|
40
|
-
40 const { computed = {}, schemaStructure = {} } = schemaInfo
|
|
41
|
-
41 if (fieldName in computed) return true
|
|
42
|
-
42 const fieldDef = schemaStructure[fieldName]
|
|
43
|
-
43 return fieldDef && fieldDef.virtual === true
|
|
44
|
-
44 }
|
|
45
|
-
45
|
|
46
|
-
46 /**
|
|
47
|
-
47 * Builds the field selection list for database queries with sparse fieldset support
|
|
48
|
-
48 *
|
|
49
|
-
49 * @param {Object} scope - Resource scope with schema information
|
|
50
|
-
50 * @param {Object} deps - Dependencies object
|
|
51
|
-
51 * @param {Object} deps.context - Request context with queryParams and schemaInfo
|
|
52
|
-
52 * @returns {Promise<Object>} Field selection information
|
|
53
|
-
53 *
|
|
54
|
-
54 * @example
|
|
55
|
-
55 * // Input: Basic sparse fieldset request
|
|
56
|
-
56 * const deps = {
|
|
57
|
-
57 * context: {
|
|
58
|
-
58 * scopeName: 'articles',
|
|
59
|
-
59 * queryParams: { fields: { articles: 'title,body' } },
|
|
60
|
-
60 * schemaInfo: { idProperty: 'id', ... }
|
|
61
|
-
61 * }
|
|
62
|
-
62 * };
|
|
63
|
-
63 * const result = await buildFieldSelection(scope, deps);
|
|
64
|
-
64 *
|
|
65
|
-
65 * // Output: Includes requested fields plus required fields
|
|
66
|
-
66 * // {
|
|
67
|
-
67 * // fieldsToSelect: ['id', 'title', 'body', 'author_id', 'category_id'],
|
|
68
|
-
68 * // requestedFields: ['title', 'body'],
|
|
69
|
-
69 * // computedDependencies: [],
|
|
70
|
-
70 * // idProperty: 'id'
|
|
71
|
-
71 * // }
|
|
72
|
-
72 * // Note: Foreign keys (author_id, category_id) always included for relationships
|
|
73
|
-
73 *
|
|
74
|
-
74 * @example
|
|
75
|
-
75 * // Input: Computed field with hidden dependencies
|
|
76
|
-
76 * const schemaInfo = {
|
|
77
|
-
77 * computed: {
|
|
78
|
-
78 * profit_margin: {
|
|
79
|
-
79 * compute: (record) => (record.price - record.cost) / record.price,
|
|
80
|
-
80 * dependencies: ['price', 'cost']
|
|
81
|
-
81 * }
|
|
82
|
-
82 * },
|
|
83
|
-
83 * schemaStructure: {
|
|
84
|
-
84 * name: { type: 'string' },
|
|
85
|
-
85 * price: { type: 'number' },
|
|
86
|
-
86 * cost: { type: 'number', normallyHidden: true }
|
|
87
|
-
87 * }
|
|
88
|
-
88 * };
|
|
89
|
-
89 * const deps = {
|
|
90
|
-
90 * context: {
|
|
91
|
-
91 * queryParams: { fields: { products: 'name,profit_margin' } }
|
|
92
|
-
92 * }
|
|
93
|
-
93 * };
|
|
94
|
-
94 *
|
|
95
|
-
95 * // Output: Fetches hidden dependency for computation
|
|
96
|
-
96 * // {
|
|
97
|
-
97 * // fieldsToSelect: ['id', 'name', 'price', 'cost'],
|
|
98
|
-
98 * // requestedFields: ['name', 'profit_margin'],
|
|
99
|
-
99 * // computedDependencies: ['cost'], // Will be removed after computation
|
|
100
|
-
100 * // idProperty: 'id'
|
|
101
|
-
101 * // }
|
|
102
|
-
102 *
|
|
103
|
-
103 * @example
|
|
104
|
-
104 * // Input: No sparse fieldsets (returns all visible fields)
|
|
105
|
-
105 * const deps = {
|
|
106
|
-
106 * context: {
|
|
107
|
-
107 * queryParams: {}, // No fields parameter
|
|
108
|
-
108 * schemaInfo: { ... }
|
|
109
|
-
109 * }
|
|
110
|
-
110 * };
|
|
111
|
-
111 *
|
|
112
|
-
112 * // Output: All non-hidden, non-virtual fields
|
|
113
|
-
113 * // {
|
|
114
|
-
114 * // fieldsToSelect: ['id', 'title', 'body', 'published_at', 'author_id'],
|
|
115
|
-
115 * // requestedFields: null,
|
|
116
|
-
116 * // computedDependencies: [],
|
|
117
|
-
117 * // idProperty: 'id'
|
|
118
|
-
118 * // }
|
|
119
|
-
119 *
|
|
120
|
-
120 * @description
|
|
121
|
-
121 * Used by:
|
|
122
|
-
122 * - dataGet to build SELECT query
|
|
123
|
-
123 * - dataQuery to build SELECT query
|
|
124
|
-
124 * - buildQuerySelection as the main field resolver
|
|
125
|
-
125 *
|
|
126
|
-
126 * Purpose:
|
|
127
|
-
127 * - Implements JSON:API sparse fieldsets specification
|
|
128
|
-
128 * - Ensures relationships work by including foreign keys
|
|
129
|
-
129 * - Handles computed field dependencies intelligently
|
|
130
|
-
130 * - Respects field visibility rules (hidden/normallyHidden)
|
|
131
|
-
131 * - Supports custom ID column names
|
|
132
|
-
132 *
|
|
133
|
-
133 * Data flow:
|
|
134
|
-
134 * 1. Always includes ID (with aliasing if needed)
|
|
135
|
-
135 * 2. Parses requested fields from query string
|
|
136
|
-
136 * 3. Validates requested fields exist in schema
|
|
137
|
-
137 * 4. Adds dependencies for computed fields
|
|
138
|
-
138 * 5. Includes all foreign keys for relationships
|
|
139
|
-
139 * 6. Returns complete field list for SQL SELECT
|
|
140
|
-
140 */
|
|
141
|
-
141 export const buildFieldSelection = async (scope, deps) => {
|
|
142
|
-
142 const fieldsToSelect = new Set()
|
|
143
|
-
143 const computedDependencies = new Set()
|
|
144
|
-
144
|
|
145
|
-
145 // Extract values from scope
|
|
146
|
-
146 const {
|
|
147
|
-
147 vars: {
|
|
148
|
-
148 schemaInfo: { schemaInstance, computed: computedFields = {}, schemaStructure }
|
|
149
|
-
149 }
|
|
150
|
-
150 } = scope
|
|
151
|
-
151
|
|
152
|
-
152 // Extract values from deps
|
|
153
|
-
153 const { context } = deps
|
|
154
|
-
154 const scopeName = context.scopeName
|
|
155
|
-
155 const requestedFields = context.queryParams?.fields?.[scopeName]
|
|
156
|
-
156 const idProperty = context.schemaInfo.idProperty
|
|
157
|
-
157
|
|
158
|
-
158 // Always include the ID field - required for JSON:API
|
|
159
|
-
159 // Handle aliasing if idProperty is not 'id'
|
|
160
|
-
160 if (idProperty !== 'id') {
|
|
161
|
-
161 fieldsToSelect.add(`${idProperty} as id`)
|
|
162
|
-
162 } else {
|
|
163
|
-
163 fieldsToSelect.add('id')
|
|
164
|
-
164 }
|
|
165
|
-
165
|
|
166
|
-
166 // Handle both Schema objects and plain objects
|
|
167
|
-
167 if (!schemaStructure) {
|
|
168
|
-
168 schemaStructure = schemaInstance?.structure || schemaInstance || {}
|
|
169
|
-
169 }
|
|
170
|
-
170
|
|
171
|
-
171 // Get computed fields and virtual fields from schema
|
|
172
|
-
172 const computedFieldNames = new Set(Object.keys(computedFields))
|
|
173
|
-
173
|
|
174
|
-
174 // Find fields marked as virtual in the schema
|
|
175
|
-
175 const virtualFieldNames = new Set()
|
|
176
|
-
176 Object.entries(schemaStructure).forEach(([fieldName, fieldDef]) => {
|
|
177
|
-
177 if (fieldDef.virtual === true) {
|
|
178
|
-
178 virtualFieldNames.add(fieldName)
|
|
179
|
-
179 }
|
|
180
|
-
180 })
|
|
181
|
-
181
|
|
182
|
-
182 const nonDatabaseFields = new Set([...computedFieldNames, ...virtualFieldNames])
|
|
183
|
-
183
|
|
184
|
-
184 // Parse requested fields
|
|
185
|
-
185 const requested = requestedFields
|
|
186
|
-
186 ? (
|
|
187
|
-
187 typeof requestedFields === 'string'
|
|
188
|
-
188 ? requestedFields.split(',').map(f => f.trim()).filter(f => f)
|
|
189
|
-
189 : requestedFields
|
|
190
|
-
190 )
|
|
191
|
-
191 : null
|
|
192
|
-
192
|
|
193
|
-
193 if (requested && requested.length > 0) {
|
|
194
|
-
194 // Sparse fieldsets requested - only select specified fields
|
|
195
|
-
195 // Example: ?fields[products]=name,price,profit_margin
|
|
196
|
-
196 requested.forEach(field => {
|
|
197
|
-
197 // Skip computed and virtual fields - they don't exist in database
|
|
198
|
-
198 // Computed fields will be calculated later in enrichAttributes
|
|
199
|
-
199 // Virtual fields are handled separately (from request input)
|
|
200
|
-
200 if (nonDatabaseFields.has(field)) return
|
|
201
|
-
201
|
|
202
|
-
202 const fieldDef = schemaStructure[field]
|
|
203
|
-
203 if (!fieldDef) throw new Error(`Unknown sparse field '${field}' requested for '${scopeName}'`)
|
|
204
|
-
204
|
|
205
|
-
205 if (fieldDef.belongsToPolymorphic) {
|
|
206
|
-
206 return
|
|
207
|
-
207 }
|
|
208
|
-
208
|
|
209
|
-
209 // NEVER include hidden fields, even if explicitly requested
|
|
210
|
-
210 // Example: password_hash with hidden:true is never returned
|
|
211
|
-
211 if (fieldDef.hidden === true) return
|
|
212
|
-
212
|
|
213
|
-
213 fieldsToSelect.add(field)
|
|
214
|
-
214 })
|
|
215
|
-
215
|
|
216
|
-
216 // Handle computed field dependencies - fetch fields needed for calculations
|
|
217
|
-
217 // Example: User requests 'profit_margin' which depends on 'price' and 'cost'
|
|
218
|
-
218 // We need to fetch price and cost from DB even if not explicitly requested
|
|
219
|
-
219 const requestedComputedFields = requested.filter(f => computedFieldNames.has(f))
|
|
220
|
-
220 for (const computedField of requestedComputedFields) {
|
|
221
|
-
221 const fieldDef = computedFields[computedField]
|
|
222
|
-
222 if (fieldDef.dependencies) {
|
|
223
|
-
223 for (const dep of fieldDef.dependencies) {
|
|
224
|
-
224 const depFieldDef = schemaStructure[dep]
|
|
225
|
-
225 // Only add dependency if it exists and isn't hidden
|
|
226
|
-
226 if (depFieldDef && depFieldDef.hidden !== true) {
|
|
227
|
-
227 fieldsToSelect.add(dep)
|
|
228
|
-
228 // Track dependencies that weren't explicitly requested
|
|
229
|
-
229 // These will be removed from the final response
|
|
230
|
-
230 if (!requested.includes(dep)) {
|
|
231
|
-
231 computedDependencies.add(dep)
|
|
232
|
-
232 }
|
|
233
|
-
233 }
|
|
234
|
-
234 }
|
|
235
|
-
235 }
|
|
236
|
-
236 }
|
|
237
|
-
237
|
|
238
|
-
238 // Still handle normallyHidden fields for backward compatibility
|
|
239
|
-
239 if (requestedComputedFields.length > 0) {
|
|
240
|
-
240 Object.entries(schemaStructure).forEach(([field, fieldDef]) => {
|
|
241
|
-
241 if (fieldDef.normallyHidden === true && fieldDef.hidden !== true) {
|
|
242
|
-
242 // Only add if not already handled by dependencies
|
|
243
|
-
243 if (!fieldsToSelect.has(field)) {
|
|
244
|
-
244 fieldsToSelect.add(field)
|
|
245
|
-
245 if (!requested.includes(field)) {
|
|
246
|
-
246 computedDependencies.add(field)
|
|
247
|
-
247 }
|
|
248
|
-
248 }
|
|
249
|
-
249 }
|
|
250
|
-
250 })
|
|
251
|
-
251 }
|
|
252
|
-
252 } else {
|
|
253
|
-
253 // No sparse fieldsets - return all visible fields
|
|
254
|
-
254 // This is the default behavior when no ?fields parameter is provided
|
|
255
|
-
255 Object.entries(schemaStructure).forEach(([field, fieldDef]) => {
|
|
256
|
-
256 // Skip virtual fields - they don't exist in database
|
|
257
|
-
257 if (fieldDef.virtual === true) return
|
|
258
|
-
258
|
|
259
|
-
259 // Skip hidden fields - these are NEVER returned
|
|
260
|
-
260 // Example: password_hash with hidden:true
|
|
261
|
-
261 if (fieldDef.hidden === true) return
|
|
262
|
-
262
|
|
263
|
-
263 // Skip polymorphic placeholder fields - handled via type/id columns
|
|
264
|
-
264 if (fieldDef.belongsToPolymorphic) return
|
|
265
|
-
265
|
|
266
|
-
266 // Skip normallyHidden fields - these are hidden by default
|
|
267
|
-
267 // Example: cost with normallyHidden:true (only returned when explicitly requested)
|
|
268
|
-
268 if (fieldDef.normallyHidden === true) return
|
|
269
|
-
269
|
|
270
|
-
270 fieldsToSelect.add(field)
|
|
271
|
-
271 })
|
|
272
|
-
272
|
|
273
|
-
273 // When no sparse fieldsets, we compute all computed fields
|
|
274
|
-
274 // So we need to include their dependencies even if normallyHidden
|
|
275
|
-
275 // Example: profit_margin depends on 'cost' which is normallyHidden
|
|
276
|
-
276 // We fetch 'cost' for calculation but don't return it in response
|
|
277
|
-
277 for (const [fieldName, fieldDef] of Object.entries(computedFields)) {
|
|
278
|
-
278 if (fieldDef.dependencies) {
|
|
279
|
-
279 for (const dep of fieldDef.dependencies) {
|
|
280
|
-
280 const depFieldDef = schemaStructure[dep]
|
|
281
|
-
281 if (depFieldDef && depFieldDef.hidden !== true) {
|
|
282
|
-
282 fieldsToSelect.add(dep)
|
|
283
|
-
283 // Track normallyHidden dependencies for later removal
|
|
284
|
-
284 // These are fetched for computation but not returned
|
|
285
|
-
285 if (depFieldDef.normallyHidden === true) {
|
|
286
|
-
286 computedDependencies.add(dep)
|
|
287
|
-
287 }
|
|
288
|
-
288 }
|
|
289
|
-
289 }
|
|
290
|
-
290 }
|
|
291
|
-
291 }
|
|
292
|
-
292 }
|
|
293
|
-
293
|
|
294
|
-
294 // Always include foreign keys for relationships (unless hidden)
|
|
295
|
-
295 Object.entries(schemaStructure).forEach(([field, fieldDef]) => {
|
|
296
|
-
296 if (fieldDef.belongsTo && fieldDef.hidden !== true) {
|
|
297
|
-
297 fieldsToSelect.add(field)
|
|
298
|
-
298 }
|
|
299
|
-
299 })
|
|
300
|
-
300
|
|
301
|
-
301 // Always include polymorphic type and id fields from relationships
|
|
302
|
-
302 try {
|
|
303
|
-
303 const relationships = scope.vars.schemaInfo.schemaRelationships
|
|
304
|
-
304 Object.entries(relationships || {}).forEach(([relName, relDef]) => {
|
|
305
|
-
305 if (relDef.belongsToPolymorphic) {
|
|
306
|
-
306 if (relDef.typeField) fieldsToSelect.add(relDef.typeField)
|
|
307
|
-
307 if (relDef.idField) fieldsToSelect.add(relDef.idField)
|
|
308
|
-
308 }
|
|
309
|
-
309 })
|
|
310
|
-
310 } catch (e) {
|
|
311
|
-
311 // Scope might not have relationships
|
|
312
|
-
312 }
|
|
313
|
-
313
|
|
314
|
-
314 // Return detailed information about field selection
|
|
315
|
-
315 // This info is used by:
|
|
316
|
-
316 // 1. SQL query builder to SELECT the right columns
|
|
317
|
-
317 // 2. enrichAttributes to know which computed fields to calculate
|
|
318
|
-
318 // 3. enrichAttributes to remove dependencies from final response
|
|
319
|
-
319 return {
|
|
320
|
-
320 fieldsToSelect: Array.from(fieldsToSelect), // Fields to SELECT from database
|
|
321
|
-
321 requestedFields: requested, // Fields explicitly requested by user
|
|
322
|
-
322 computedDependencies: Array.from(computedDependencies), // Dependencies to remove from response
|
|
323
|
-
323 idProperty // Pass idProperty for reference
|
|
324
|
-
324 }
|
|
325
|
-
325 }
|
|
326
|
-
326
|
|
327
|
-
327 /**
|
|
328
|
-
328 * Determines which computed fields to calculate based on sparse fieldsets
|
|
329
|
-
329 *
|
|
330
|
-
330 * @param {string} scopeName - Resource name (e.g., 'products')
|
|
331
|
-
331 * @param {Array<string>|string} requestedFields - Requested fields from query
|
|
332
|
-
332 * @param {Object} computedFields - Computed field definitions from schema
|
|
333
|
-
333 * @returns {Array<string>} Names of computed fields to calculate
|
|
334
|
-
334 *
|
|
335
|
-
335 * @example
|
|
336
|
-
336 * // Input: No sparse fieldsets (calculate all computed fields)
|
|
337
|
-
337 * const computed = {
|
|
338
|
-
338 * full_name: { compute: (r) => `${r.first} ${r.last}` },
|
|
339
|
-
339 * age: { compute: (r) => new Date().getFullYear() - r.birth_year }
|
|
340
|
-
340 * };
|
|
341
|
-
341 * getRequestedComputedFields('users', null, computed);
|
|
342
|
-
342 * // Output: ['full_name', 'age'] (all computed fields)
|
|
343
|
-
343 *
|
|
344
|
-
344 * @example
|
|
345
|
-
345 * // Input: Sparse fieldsets with some computed fields
|
|
346
|
-
346 * const requestedFields = 'first_name,full_name,email';
|
|
347
|
-
347 * const computed = {
|
|
348
|
-
348 * full_name: { compute: (r) => `${r.first} ${r.last}` },
|
|
349
|
-
349 * age: { compute: (r) => new Date().getFullYear() - r.birth_year }
|
|
350
|
-
350 * };
|
|
351
|
-
351 * getRequestedComputedFields('users', requestedFields, computed);
|
|
352
|
-
352 * // Output: ['full_name'] (only requested computed field)
|
|
353
|
-
353 *
|
|
354
|
-
354 * @example
|
|
355
|
-
355 * // Input: Sparse fieldsets with no computed fields
|
|
356
|
-
356 * const requestedFields = ['first_name', 'email'];
|
|
357
|
-
357 * const computed = {
|
|
358
|
-
358 * full_name: { compute: (r) => `${r.first} ${r.last}` }
|
|
359
|
-
359 * };
|
|
360
|
-
360 * getRequestedComputedFields('users', requestedFields, computed);
|
|
361
|
-
361 * // Output: [] (no computed fields requested)
|
|
362
|
-
362 *
|
|
363
|
-
363 * @description
|
|
364
|
-
364 * Used by:
|
|
365
|
-
365 * - enrichAttributes to determine which computations to run
|
|
366
|
-
366 * - Applied after fetching data from database
|
|
367
|
-
367 *
|
|
368
|
-
368 * Purpose:
|
|
369
|
-
369 * - Optimizes performance by only computing requested fields
|
|
370
|
-
370 * - Supports JSON:API sparse fieldsets for computed fields
|
|
371
|
-
371 * - Handles both string and array input formats
|
|
372
|
-
372 *
|
|
373
|
-
373 * Data flow:
|
|
374
|
-
374 * 1. Checks if sparse fieldsets are specified
|
|
375
|
-
375 * 2. If not, returns all computed field names
|
|
376
|
-
376 * 3. If yes, filters to only requested computed fields
|
|
377
|
-
377 * 4. Normalizes string input to array format
|
|
378
|
-
378 */
|
|
379
|
-
379 export const getRequestedComputedFields = (scopeName, requestedFields, computedFields) => {
|
|
380
|
-
380 if (!computedFields) return []
|
|
381
|
-
381
|
|
382
|
-
382 const allComputedFields = Object.keys(computedFields)
|
|
383
|
-
383
|
|
384
|
-
384 if (!requestedFields || requestedFields.length === 0) {
|
|
385
|
-
385 // No sparse fieldsets - return all computed fields
|
|
386
|
-
386 return allComputedFields
|
|
387
|
-
387 }
|
|
388
|
-
388
|
|
389
|
-
389 // Parse requested fields if it's a string
|
|
390
|
-
390 const requested = typeof requestedFields === 'string'
|
|
391
|
-
391 ? requestedFields.split(',').map(f => f.trim()).filter(f => f)
|
|
392
|
-
392 : requestedFields
|
|
393
|
-
393
|
|
394
|
-
394 // Return only requested computed fields that exist
|
|
395
|
-
395 return requested.filter(field => allComputedFields.includes(field))
|
|
396
|
-
396 }
|