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
|
@@ -5,6 +5,7 @@ import { createSchema } from 'json-rest-schema'
|
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_QUERY_LIMIT,
|
|
7
7
|
DEFAULT_MAX_QUERY_LIMIT,
|
|
8
|
+
COMPUTED_DEPENDENCIES_KEY,
|
|
8
9
|
} from './lib/querying-writing/knex-constants.js'
|
|
9
10
|
import {
|
|
10
11
|
calculatePaginationMeta,
|
|
@@ -13,7 +14,7 @@ import {
|
|
|
13
14
|
buildCursorMeta,
|
|
14
15
|
parseCursor,
|
|
15
16
|
} from './lib/querying/knex-pagination-helpers.js'
|
|
16
|
-
import { getUrlPrefix, buildResourceUrl } from './lib/querying/url-helpers.js'
|
|
17
|
+
import { getUrlPrefix, buildResourceUrl, buildRelationshipUrl } from './lib/querying/url-helpers.js'
|
|
17
18
|
import {
|
|
18
19
|
normalizeId,
|
|
19
20
|
resolveFieldInfo,
|
|
@@ -29,13 +30,30 @@ import {
|
|
|
29
30
|
ensureSearchFieldsAreIndexed,
|
|
30
31
|
generateSearchSchemaFromSchema,
|
|
31
32
|
} from './lib/querying-writing/schema-helpers.js'
|
|
32
|
-
import { buildFieldSelection } from './lib/querying-writing/knex-field-helpers.js'
|
|
33
|
+
import { applyFieldSelectionToQuery, buildFieldSelection } from './lib/querying-writing/knex-field-helpers.js'
|
|
33
34
|
import {
|
|
34
35
|
polymorphicFiltersHook,
|
|
35
36
|
crossTableFiltersHook,
|
|
36
37
|
basicFiltersHook,
|
|
38
|
+
resolveSearchOperator,
|
|
39
|
+
applyWhereForOperator,
|
|
37
40
|
} from './lib/querying/knex-query-helpers.js'
|
|
38
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
createStorageAdapter,
|
|
43
|
+
} from './lib/storage/storage-adapter.js'
|
|
44
|
+
import {
|
|
45
|
+
translateCanonicalAttributesForStorage,
|
|
46
|
+
getCanonicalResourceId,
|
|
47
|
+
getCanonicalResourceIdColumn,
|
|
48
|
+
translateCanonicalRecordFromStorage,
|
|
49
|
+
} from './lib/storage/canonical-storage-mapping.js'
|
|
50
|
+
import {
|
|
51
|
+
buildCursorPredicateChains,
|
|
52
|
+
applyQueryFieldOrder,
|
|
53
|
+
buildEffectiveSortList,
|
|
54
|
+
parseSortEntry
|
|
55
|
+
} from './lib/querying/query-field-sort-helpers.js'
|
|
56
|
+
import { unwrapQueryBuilderState } from './lib/querying/query-builder-utils.js'
|
|
39
57
|
|
|
40
58
|
const DEFAULT_TENANT = 'default'
|
|
41
59
|
const LINKS_TABLE = 'any_links'
|
|
@@ -107,76 +125,138 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
107
125
|
return descriptor
|
|
108
126
|
}
|
|
109
127
|
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
128
|
+
const decorateResourceLinks = ({ resource, scope, scopeName, context }) => {
|
|
129
|
+
if (!resource || !scope || !scopeName || !context) return
|
|
130
|
+
|
|
131
|
+
resource.links = resource.links || {}
|
|
132
|
+
resource.links.self = buildResourceUrl(context, scope, scopeName, resource.id)
|
|
133
|
+
|
|
134
|
+
if (!resource.relationships || typeof resource.relationships !== 'object') {
|
|
115
135
|
return
|
|
116
136
|
}
|
|
117
137
|
|
|
118
|
-
|
|
138
|
+
for (const [relationshipName, relationshipObject] of Object.entries(resource.relationships)) {
|
|
139
|
+
if (!relationshipObject || typeof relationshipObject !== 'object') {
|
|
140
|
+
continue
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
relationshipObject.links = {
|
|
144
|
+
self: buildRelationshipUrl(context, scope, scopeName, resource.id, relationshipName, true),
|
|
145
|
+
related: buildRelationshipUrl(context, scope, scopeName, resource.id, relationshipName, false),
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const getAllowedQueryFieldNames = (scopeName) => (
|
|
151
|
+
Object.keys(api.resources?.[scopeName]?.vars?.queryFields || {})
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
const getLogicalResourceIdColumn = (descriptor) => getCanonicalResourceIdColumn(descriptor)
|
|
155
|
+
|
|
156
|
+
const getLogicalResourceId = (row, descriptor) => getCanonicalResourceId(row, descriptor)
|
|
157
|
+
|
|
158
|
+
const applyFiltersToQuery = ({ query, filters, descriptor, searchSchema, adapter }) => {
|
|
119
159
|
if (!filters || Object.keys(filters).length === 0) return
|
|
120
160
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
161
|
+
const normalizeValue = (field, value) => {
|
|
162
|
+
if (!adapter?.translateFilterValue) {
|
|
163
|
+
return value
|
|
164
|
+
}
|
|
165
|
+
if (Array.isArray(value)) {
|
|
166
|
+
return value.map((entry) => adapter.translateFilterValue(field, entry))
|
|
167
|
+
}
|
|
168
|
+
return adapter.translateFilterValue(field, value)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (const [filterKey, rawValue] of Object.entries(filters)) {
|
|
172
|
+
const fieldDef = searchSchema?.[filterKey]
|
|
173
|
+
|
|
174
|
+
if (fieldDef?.polymorphicField) {
|
|
175
|
+
continue
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (fieldDef?.actualField?.includes('.')) {
|
|
179
|
+
continue
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (fieldDef?.oneOf?.some((field) => field.includes('.'))) {
|
|
183
|
+
continue
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (fieldDef?.oneOf && Array.isArray(fieldDef.oneOf)) {
|
|
187
|
+
continue
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (fieldDef?.applyFilter && typeof fieldDef.applyFilter === 'function') {
|
|
191
|
+
continue
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const actualField = fieldDef?.actualField || filterKey
|
|
195
|
+
const fieldInfo = ensureFilterableField(descriptor, filterKey)
|
|
196
|
+
const columnRef = adapter ? adapter.translateColumn(actualField) : fieldInfo.column
|
|
197
|
+
const operator = resolveSearchOperator(fieldDef || fieldInfo.definition)
|
|
124
198
|
const values = normalizeFilterValues(rawValue, fieldInfo.definition, {
|
|
125
199
|
isRelationship: fieldInfo.isRelationship,
|
|
126
200
|
})
|
|
127
|
-
|
|
128
|
-
const nonNullValues =
|
|
129
|
-
const hasNull =
|
|
201
|
+
const normalizedValues = values.map((value) => normalizeValue(actualField, value))
|
|
202
|
+
const nonNullValues = normalizedValues.filter((value) => value !== null)
|
|
203
|
+
const hasNull = normalizedValues.length !== nonNullValues.length
|
|
130
204
|
|
|
131
205
|
if (nonNullValues.length === 0 && hasNull) {
|
|
132
|
-
query.whereNull(
|
|
206
|
+
query.whereNull(columnRef)
|
|
133
207
|
continue
|
|
134
208
|
}
|
|
135
209
|
if (nonNullValues.length === 1 && !hasNull) {
|
|
136
|
-
|
|
210
|
+
applyWhereForOperator({
|
|
211
|
+
builder: query,
|
|
212
|
+
columnRef,
|
|
213
|
+
operator,
|
|
214
|
+
value: nonNullValues[0],
|
|
215
|
+
knex,
|
|
216
|
+
})
|
|
137
217
|
continue
|
|
138
218
|
}
|
|
139
219
|
if (nonNullValues.length > 1 && !hasNull) {
|
|
140
|
-
query.whereIn(
|
|
220
|
+
query.whereIn(columnRef, nonNullValues)
|
|
141
221
|
continue
|
|
142
222
|
}
|
|
143
223
|
if (nonNullValues.length > 0 && hasNull) {
|
|
144
|
-
query.where(function
|
|
145
|
-
this.whereIn(
|
|
224
|
+
query.where(function anyApiNullFilterGroup () {
|
|
225
|
+
this.whereIn(columnRef, nonNullValues).orWhereNull(columnRef)
|
|
146
226
|
})
|
|
147
227
|
}
|
|
148
228
|
}
|
|
149
229
|
}
|
|
150
230
|
|
|
151
|
-
const applySortingToQuery = ({ query, sort, descriptor, scope }) => {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (Array.isArray(defaultSort)) {
|
|
157
|
-
sortList = defaultSort
|
|
158
|
-
} else if (typeof defaultSort === 'string') {
|
|
159
|
-
sortList = [defaultSort]
|
|
160
|
-
} else if (defaultSort && typeof defaultSort === 'object') {
|
|
161
|
-
const field = defaultSort.field || defaultSort.column || 'id'
|
|
162
|
-
const direction = (defaultSort.direction || '').toLowerCase() === 'desc' ? '-' : ''
|
|
163
|
-
sortList = [`${direction}${field}`]
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const effectiveSort = sortList.length > 0 ? sortList : ['id']
|
|
231
|
+
const applySortingToQuery = ({ query, sort, descriptor, scope, queryFieldRuntimeByField = new Map() }) => {
|
|
232
|
+
const effectiveSort = buildEffectiveSortList(sort, {
|
|
233
|
+
defaultSort: scope?.vars?.defaultSort,
|
|
234
|
+
idField: 'id'
|
|
235
|
+
})
|
|
168
236
|
const descriptors = []
|
|
169
237
|
|
|
170
238
|
for (const entry of effectiveSort) {
|
|
171
|
-
const
|
|
172
|
-
const
|
|
239
|
+
const { field, direction, sqlDirection } = parseSortEntry(entry)
|
|
240
|
+
const queryFieldRuntime = queryFieldRuntimeByField.get(field)
|
|
241
|
+
if (queryFieldRuntime) {
|
|
242
|
+
applyQueryFieldOrder(query, queryFieldRuntime, sqlDirection)
|
|
243
|
+
descriptors.push({
|
|
244
|
+
field,
|
|
245
|
+
direction,
|
|
246
|
+
definition: queryFieldRuntime.definition,
|
|
247
|
+
isRelationship: false,
|
|
248
|
+
queryFieldRuntime,
|
|
249
|
+
})
|
|
250
|
+
continue
|
|
251
|
+
}
|
|
252
|
+
|
|
173
253
|
const fieldInfo = resolveFieldInfo(descriptor, field)
|
|
174
254
|
if (!fieldInfo?.column) continue
|
|
175
|
-
query.orderBy(fieldInfo.column,
|
|
255
|
+
query.orderBy(fieldInfo.column, direction)
|
|
176
256
|
descriptors.push({
|
|
177
257
|
field,
|
|
178
258
|
column: fieldInfo.column,
|
|
179
|
-
direction
|
|
259
|
+
direction,
|
|
180
260
|
definition: fieldInfo.definition || null,
|
|
181
261
|
isRelationship: fieldInfo.isRelationship || false,
|
|
182
262
|
})
|
|
@@ -282,64 +362,37 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
282
362
|
return typed
|
|
283
363
|
}
|
|
284
364
|
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const value = cursorValues[descriptor.field]
|
|
293
|
-
if (value === undefined) return
|
|
294
|
-
const columnRef = adapter ? adapter.translateColumn(descriptor.column) : descriptor.column
|
|
295
|
-
const operator = operatorSelector(descriptor.direction)
|
|
296
|
-
if (adapter) {
|
|
297
|
-
query.whereRaw(`${columnRef} ${operator} ?`, [value])
|
|
298
|
-
} else {
|
|
299
|
-
query.where(columnRef, operator, value)
|
|
300
|
-
}
|
|
301
|
-
return
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const translatedConditions = []
|
|
305
|
-
|
|
306
|
-
descriptors.forEach((descriptor, index) => {
|
|
307
|
-
const value = cursorValues[descriptor.field]
|
|
308
|
-
if (value === undefined) return
|
|
309
|
-
|
|
310
|
-
const chain = []
|
|
311
|
-
for (let i = 0; i < index; i += 1) {
|
|
312
|
-
const prev = descriptors[i]
|
|
313
|
-
const prevValue = cursorValues[prev.field]
|
|
314
|
-
if (prevValue === undefined) return
|
|
315
|
-
const prevColumn = adapter ? adapter.translateColumn(prev.column) : prev.column
|
|
316
|
-
chain.push({ column: prevColumn, operator: '=', value: prevValue })
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const operator = operatorSelector(descriptor.direction)
|
|
320
|
-
const columnRef = adapter ? adapter.translateColumn(descriptor.column) : descriptor.column
|
|
321
|
-
chain.push({ column: columnRef, operator, value })
|
|
322
|
-
translatedConditions.push(chain)
|
|
323
|
-
})
|
|
365
|
+
const applyRawCursorPredicate = (cursorValues, operatorSelector) => {
|
|
366
|
+
const predicateChains = buildCursorPredicateChains(
|
|
367
|
+
descriptors,
|
|
368
|
+
cursorValues,
|
|
369
|
+
operatorSelector,
|
|
370
|
+
{ onMissingValue: () => {} }
|
|
371
|
+
)
|
|
324
372
|
|
|
325
|
-
if (
|
|
373
|
+
if (predicateChains.length === 0) {
|
|
326
374
|
return
|
|
327
375
|
}
|
|
328
376
|
|
|
329
377
|
const predicateParts = []
|
|
330
378
|
const predicateBindings = []
|
|
331
|
-
|
|
332
|
-
|
|
379
|
+
|
|
380
|
+
predicateChains.forEach((chain) => {
|
|
381
|
+
const chainParts = chain.map(({ descriptor, operator, value }) => {
|
|
382
|
+
if (descriptor.queryFieldRuntime) {
|
|
383
|
+
predicateBindings.push(...(descriptor.queryFieldRuntime.bindings || []), value)
|
|
384
|
+
return `(${descriptor.queryFieldRuntime.sql}) ${operator} ?`
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const columnRef = adapter ? adapter.translateColumn(descriptor.column) : descriptor.column
|
|
333
388
|
predicateBindings.push(value)
|
|
334
|
-
return `${
|
|
389
|
+
return `${columnRef} ${operator} ?`
|
|
335
390
|
})
|
|
391
|
+
|
|
336
392
|
predicateParts.push(`(${chainParts.join(' AND ')})`)
|
|
337
393
|
})
|
|
338
394
|
|
|
339
|
-
|
|
340
|
-
const combined = predicateParts.join(' OR ')
|
|
341
|
-
query.whereRaw(`(${combined})`, predicateBindings)
|
|
342
|
-
}
|
|
395
|
+
query.whereRaw(`(${predicateParts.join(' OR ')})`, predicateBindings)
|
|
343
396
|
}
|
|
344
397
|
|
|
345
398
|
const ensurePageSize = () => {
|
|
@@ -378,11 +431,11 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
378
431
|
if (pageParams.after) {
|
|
379
432
|
const cursorMap = parseCursorOrThrow(pageParams.after, 'after')
|
|
380
433
|
const cursorValues = coerceCursorValues(cursorMap)
|
|
381
|
-
|
|
434
|
+
applyRawCursorPredicate(cursorValues, (direction) => (direction === 'desc' ? '<' : '>'))
|
|
382
435
|
} else if (pageParams.before) {
|
|
383
436
|
const cursorMap = parseCursorOrThrow(pageParams.before, 'before')
|
|
384
437
|
const cursorValues = coerceCursorValues(cursorMap)
|
|
385
|
-
|
|
438
|
+
applyRawCursorPredicate(cursorValues, (direction) => (direction === 'desc' ? '>' : '<'))
|
|
386
439
|
}
|
|
387
440
|
|
|
388
441
|
return {
|
|
@@ -900,12 +953,13 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
900
953
|
const descriptor = await getDescriptor(scopeName)
|
|
901
954
|
const { canonical } = descriptor
|
|
902
955
|
const id = context.id
|
|
956
|
+
const logicalIdColumn = getLogicalResourceIdColumn(descriptor)
|
|
903
957
|
|
|
904
958
|
const row = await context.db(canonical.tableName)
|
|
905
959
|
.select('id')
|
|
906
960
|
.where(canonical.tenantColumn, descriptor.tenant)
|
|
907
961
|
.where(canonical.resourceColumn, descriptor.resource)
|
|
908
|
-
.where(
|
|
962
|
+
.where(logicalIdColumn, id)
|
|
909
963
|
.first()
|
|
910
964
|
|
|
911
965
|
return !!row
|
|
@@ -918,8 +972,17 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
918
972
|
}
|
|
919
973
|
const descriptor = await getDescriptor(scopeName)
|
|
920
974
|
const { canonical } = descriptor
|
|
975
|
+
const logicalIdColumn = getLogicalResourceIdColumn(descriptor)
|
|
976
|
+
const explicitId = context.inputRecord?.data?.id
|
|
921
977
|
const attributes = context.inputRecord?.data?.attributes || {}
|
|
922
|
-
const row =
|
|
978
|
+
const row = storageAdapter?.toStorageRow
|
|
979
|
+
? storageAdapter.toStorageRow(attributes)
|
|
980
|
+
: translateCanonicalAttributesForStorage(attributes, descriptor)
|
|
981
|
+
|
|
982
|
+
if (explicitId !== undefined && explicitId !== null) {
|
|
983
|
+
row[logicalIdColumn] = explicitId
|
|
984
|
+
}
|
|
985
|
+
|
|
923
986
|
row[canonical.tenantColumn] = descriptor.tenant
|
|
924
987
|
row[canonical.resourceColumn] = descriptor.resource
|
|
925
988
|
|
|
@@ -928,10 +991,25 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
928
991
|
.returning('id')
|
|
929
992
|
|
|
930
993
|
const inserted = Array.isArray(result) ? result[0] : result
|
|
931
|
-
|
|
932
|
-
|
|
994
|
+
const insertedRowId = inserted && typeof inserted === 'object' && 'id' in inserted
|
|
995
|
+
? inserted.id
|
|
996
|
+
: inserted
|
|
997
|
+
|
|
998
|
+
if (explicitId !== undefined && explicitId !== null) {
|
|
999
|
+
return explicitId
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
if (insertedRowId === undefined || insertedRowId === null) {
|
|
1003
|
+
return insertedRowId
|
|
933
1004
|
}
|
|
934
|
-
|
|
1005
|
+
|
|
1006
|
+
const generatedLogicalId = String(insertedRowId)
|
|
1007
|
+
|
|
1008
|
+
await context.db(canonical.tableName)
|
|
1009
|
+
.where({ id: insertedRowId })
|
|
1010
|
+
.update({ [logicalIdColumn]: generatedLogicalId })
|
|
1011
|
+
|
|
1012
|
+
return generatedLogicalId
|
|
935
1013
|
}
|
|
936
1014
|
|
|
937
1015
|
helpers.dataPut = async ({ scopeName, context }) => {
|
|
@@ -942,8 +1020,11 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
942
1020
|
const descriptor = await getDescriptor(scopeName)
|
|
943
1021
|
const { canonical } = descriptor
|
|
944
1022
|
const id = context.id
|
|
1023
|
+
const logicalIdColumn = getLogicalResourceIdColumn(descriptor)
|
|
945
1024
|
const attributes = context.inputRecord?.data?.attributes || {}
|
|
946
|
-
const row =
|
|
1025
|
+
const row = storageAdapter?.toStorageRow
|
|
1026
|
+
? storageAdapter.toStorageRow(attributes)
|
|
1027
|
+
: translateCanonicalAttributesForStorage(attributes, descriptor)
|
|
947
1028
|
|
|
948
1029
|
row[canonical.resourceColumn] = descriptor.resource
|
|
949
1030
|
row[canonical.tenantColumn] = descriptor.tenant
|
|
@@ -957,7 +1038,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
957
1038
|
}
|
|
958
1039
|
|
|
959
1040
|
const result = await context.db(canonical.tableName)
|
|
960
|
-
.where(
|
|
1041
|
+
.where(logicalIdColumn, id)
|
|
961
1042
|
.where(canonical.resourceColumn, descriptor.resource)
|
|
962
1043
|
.where(canonical.tenantColumn, descriptor.tenant)
|
|
963
1044
|
.update(updateRow)
|
|
@@ -973,8 +1054,11 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
973
1054
|
const descriptor = await getDescriptor(scopeName)
|
|
974
1055
|
const { canonical } = descriptor
|
|
975
1056
|
const id = context.id
|
|
1057
|
+
const logicalIdColumn = getLogicalResourceIdColumn(descriptor)
|
|
976
1058
|
const attributes = context.inputRecord?.data?.attributes || {}
|
|
977
|
-
const row =
|
|
1059
|
+
const row = storageAdapter?.toStorageRow
|
|
1060
|
+
? storageAdapter.toStorageRow(attributes)
|
|
1061
|
+
: translateCanonicalAttributesForStorage(attributes, descriptor)
|
|
978
1062
|
|
|
979
1063
|
const updateRow = Object.fromEntries(
|
|
980
1064
|
Object.entries(row).filter(([, value]) => value !== undefined)
|
|
@@ -985,7 +1069,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
985
1069
|
}
|
|
986
1070
|
|
|
987
1071
|
const result = await context.db(canonical.tableName)
|
|
988
|
-
.where(
|
|
1072
|
+
.where(logicalIdColumn, id)
|
|
989
1073
|
.where(canonical.resourceColumn, descriptor.resource)
|
|
990
1074
|
.where(canonical.tenantColumn, descriptor.tenant)
|
|
991
1075
|
.update(updateRow)
|
|
@@ -1001,9 +1085,10 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1001
1085
|
const descriptor = await getDescriptor(scopeName)
|
|
1002
1086
|
const { canonical } = descriptor
|
|
1003
1087
|
const id = context.id
|
|
1088
|
+
const logicalIdColumn = getLogicalResourceIdColumn(descriptor)
|
|
1004
1089
|
|
|
1005
1090
|
const result = await context.db(canonical.tableName)
|
|
1006
|
-
.where(
|
|
1091
|
+
.where(logicalIdColumn, id)
|
|
1007
1092
|
.where(canonical.resourceColumn, descriptor.resource)
|
|
1008
1093
|
.where(canonical.tenantColumn, descriptor.tenant)
|
|
1009
1094
|
.delete()
|
|
@@ -1011,7 +1096,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1011
1096
|
return result
|
|
1012
1097
|
}
|
|
1013
1098
|
|
|
1014
|
-
helpers.dataGetMinimal = async ({ scopeName, context }) => {
|
|
1099
|
+
helpers.dataGetMinimal = async ({ scopeName, context, runHooks, applyQueryFilters }) => {
|
|
1015
1100
|
const storageAdapter = getScopeStorageAdapter(scopeName)
|
|
1016
1101
|
if (storageAdapter) {
|
|
1017
1102
|
context.storageAdapter = storageAdapter
|
|
@@ -1019,26 +1104,64 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1019
1104
|
const descriptor = await getDescriptor(scopeName)
|
|
1020
1105
|
const { canonical } = descriptor
|
|
1021
1106
|
const id = context.id
|
|
1107
|
+
const logicalIdColumn = getLogicalResourceIdColumn(descriptor)
|
|
1022
1108
|
const scope = api.resources?.[scopeName]
|
|
1023
1109
|
|
|
1024
|
-
|
|
1025
|
-
.where(
|
|
1110
|
+
let query = context.db(canonical.tableName)
|
|
1111
|
+
.where(logicalIdColumn, id)
|
|
1026
1112
|
.where(canonical.resourceColumn, descriptor.resource)
|
|
1027
1113
|
.where(canonical.tenantColumn, descriptor.tenant)
|
|
1028
|
-
|
|
1114
|
+
|
|
1115
|
+
if (typeof applyQueryFilters === 'function') {
|
|
1116
|
+
const scopedQueryState = await applyQueryFilters({
|
|
1117
|
+
query,
|
|
1118
|
+
filters: context.queryParams?.filters,
|
|
1119
|
+
tableName: canonical.tableName,
|
|
1120
|
+
db: context.db,
|
|
1121
|
+
scopeName,
|
|
1122
|
+
storageAdapter,
|
|
1123
|
+
isAnyApi: true
|
|
1124
|
+
})
|
|
1125
|
+
query = unwrapQueryBuilderState(scopedQueryState, query)
|
|
1126
|
+
} else if (runHooks) {
|
|
1127
|
+
context.knexQuery = {
|
|
1128
|
+
query,
|
|
1129
|
+
filters: context.queryParams?.filters,
|
|
1130
|
+
schemaInfo: context.schemaInfo,
|
|
1131
|
+
scopeName,
|
|
1132
|
+
tableName: canonical.tableName,
|
|
1133
|
+
db: context.db,
|
|
1134
|
+
isAnyApi: true,
|
|
1135
|
+
adapter: storageAdapter,
|
|
1136
|
+
storageAdapter,
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
await applyBuiltInAnyApiQueryFilters(context)
|
|
1140
|
+
|
|
1141
|
+
await runHooks('knexQueryFiltering')
|
|
1142
|
+
|
|
1143
|
+
if (context.knexQuery) {
|
|
1144
|
+
delete context.knexQuery
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
const row = await query.first()
|
|
1029
1149
|
|
|
1030
1150
|
if (!row) return null
|
|
1031
|
-
const translated =
|
|
1151
|
+
const translated = translateCanonicalRecordFromStorage(
|
|
1152
|
+
row,
|
|
1153
|
+
descriptor,
|
|
1154
|
+
{ allowedExtraFields: getAllowedQueryFieldNames(scopeName) }
|
|
1155
|
+
)
|
|
1156
|
+
const resourceId = getLogicalResourceId(row, descriptor)
|
|
1032
1157
|
const minimal = {
|
|
1033
1158
|
type: descriptor.resource,
|
|
1034
|
-
id:
|
|
1159
|
+
id: resourceId,
|
|
1035
1160
|
attributes: translated.attributes,
|
|
1036
1161
|
relationships: translated.relationships,
|
|
1037
1162
|
}
|
|
1038
1163
|
if (scope) {
|
|
1039
|
-
minimal
|
|
1040
|
-
self: buildResourceUrl(context, scope, scopeName, minimal.id),
|
|
1041
|
-
}
|
|
1164
|
+
decorateResourceLinks({ resource: minimal, scope, scopeName, context })
|
|
1042
1165
|
}
|
|
1043
1166
|
return minimal
|
|
1044
1167
|
}
|
|
@@ -1069,17 +1192,107 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1069
1192
|
return tree
|
|
1070
1193
|
}
|
|
1071
1194
|
|
|
1195
|
+
const applyIncludeFieldSelection = async ({ query, resourceName, tableName, context }) => {
|
|
1196
|
+
const targetScope = api.resources?.[resourceName]
|
|
1197
|
+
if (!targetScope) {
|
|
1198
|
+
return { query, fieldSelectionInfo: null }
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
const storageAdapter = getScopeStorageAdapter(resourceName)
|
|
1202
|
+
const fieldSelectionInfo = await buildFieldSelection(targetScope, {
|
|
1203
|
+
context: {
|
|
1204
|
+
...context,
|
|
1205
|
+
scopeName: resourceName,
|
|
1206
|
+
schemaInfo: targetScope.vars.schemaInfo,
|
|
1207
|
+
storageAdapter,
|
|
1208
|
+
}
|
|
1209
|
+
})
|
|
1210
|
+
|
|
1211
|
+
const selectionState = await applyFieldSelectionToQuery({
|
|
1212
|
+
query,
|
|
1213
|
+
scope: targetScope,
|
|
1214
|
+
fieldSelectionInfo,
|
|
1215
|
+
tableName,
|
|
1216
|
+
useTablePrefix: false,
|
|
1217
|
+
storageAdapter,
|
|
1218
|
+
db: context.db || context.transaction || api.knex.instance,
|
|
1219
|
+
context,
|
|
1220
|
+
scopeName: resourceName
|
|
1221
|
+
})
|
|
1222
|
+
|
|
1223
|
+
return {
|
|
1224
|
+
query: selectionState.query,
|
|
1225
|
+
fieldSelectionInfo
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
const applyTargetScopeFilters = async ({ query, resourceName, tableName, context, filters }) => {
|
|
1230
|
+
const targetScope = api.resources?.[resourceName]
|
|
1231
|
+
if (!targetScope?.applyQueryFilters) {
|
|
1232
|
+
return { query }
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
const storageAdapter = getScopeStorageAdapter(resourceName)
|
|
1236
|
+
const queryState = await targetScope.applyQueryFilters({
|
|
1237
|
+
query,
|
|
1238
|
+
filters,
|
|
1239
|
+
schemaInfo: targetScope.vars?.schemaInfo,
|
|
1240
|
+
scopeName: resourceName,
|
|
1241
|
+
tableName,
|
|
1242
|
+
db: context.db || context.transaction || api.knex.instance,
|
|
1243
|
+
isAnyApi: true,
|
|
1244
|
+
storageAdapter
|
|
1245
|
+
}, {
|
|
1246
|
+
...context,
|
|
1247
|
+
scopeName: resourceName,
|
|
1248
|
+
schemaInfo: targetScope.vars?.schemaInfo,
|
|
1249
|
+
storageAdapter
|
|
1250
|
+
})
|
|
1251
|
+
|
|
1252
|
+
return {
|
|
1253
|
+
query: unwrapQueryBuilderState(queryState, query)
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
const buildIncludedResource = ({ row, descriptor, context, fieldSelectionInfo = null }) => {
|
|
1258
|
+
const translated = translateCanonicalRecordFromStorage(
|
|
1259
|
+
row,
|
|
1260
|
+
descriptor,
|
|
1261
|
+
{ allowedExtraFields: getAllowedQueryFieldNames(descriptor.resource) }
|
|
1262
|
+
)
|
|
1263
|
+
const resourceId = getLogicalResourceId(row, descriptor)
|
|
1264
|
+
const includeResource = {
|
|
1265
|
+
type: descriptor.resource,
|
|
1266
|
+
id: resourceId,
|
|
1267
|
+
attributes: translated.attributes,
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
if (translated.relationships && Object.keys(translated.relationships).length > 0) {
|
|
1271
|
+
includeResource.relationships = translated.relationships
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
if (fieldSelectionInfo?.computedDependencies?.length) {
|
|
1275
|
+
includeResource[COMPUTED_DEPENDENCIES_KEY] = fieldSelectionInfo.computedDependencies
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
const targetScope = api.resources?.[descriptor.resource]
|
|
1279
|
+
if (targetScope) {
|
|
1280
|
+
decorateResourceLinks({
|
|
1281
|
+
resource: includeResource,
|
|
1282
|
+
scope: targetScope,
|
|
1283
|
+
scopeName: descriptor.resource,
|
|
1284
|
+
context,
|
|
1285
|
+
})
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
return includeResource
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1072
1291
|
const collectIncludes = async ({ descriptor, resources, includeTree, context, includes, seen }) => {
|
|
1073
1292
|
const entries = Object.entries(includeTree || {})
|
|
1074
1293
|
if (entries.length === 0 || !resources || resources.length === 0) return
|
|
1075
1294
|
|
|
1076
1295
|
const db = context.db || context.transaction || api.knex.instance
|
|
1077
|
-
const addSelfLink = (resource, resourceType) => {
|
|
1078
|
-
const targetScope = api.resources?.[resourceType]
|
|
1079
|
-
if (!targetScope) return
|
|
1080
|
-
resource.links = resource.links || {}
|
|
1081
|
-
resource.links.self = buildResourceUrl(context, targetScope, resourceType, resource.id)
|
|
1082
|
-
}
|
|
1083
1296
|
|
|
1084
1297
|
for (const [relName, childTree] of entries) {
|
|
1085
1298
|
const childKeys = Object.keys(childTree || {})
|
|
@@ -1095,26 +1308,31 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1095
1308
|
const targetDescriptor = await registry.getDescriptor(tenantId, belongsToInfo.target)
|
|
1096
1309
|
if (!targetDescriptor) continue
|
|
1097
1310
|
|
|
1098
|
-
|
|
1311
|
+
let query = db(targetDescriptor.canonical.tableName)
|
|
1099
1312
|
.where(targetDescriptor.canonical.tenantColumn, targetDescriptor.tenant)
|
|
1100
1313
|
.where(targetDescriptor.canonical.resourceColumn, targetDescriptor.resource)
|
|
1101
|
-
.whereIn(
|
|
1314
|
+
.whereIn(getLogicalResourceIdColumn(targetDescriptor), ids)
|
|
1315
|
+
const selectionState = await applyIncludeFieldSelection({
|
|
1316
|
+
query,
|
|
1317
|
+
resourceName: targetDescriptor.resource,
|
|
1318
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1319
|
+
context
|
|
1320
|
+
})
|
|
1321
|
+
query = selectionState.query
|
|
1322
|
+
const rows = await query
|
|
1102
1323
|
|
|
1103
1324
|
const newResources = []
|
|
1104
1325
|
for (const row of rows) {
|
|
1105
|
-
const
|
|
1326
|
+
const includeId = getLogicalResourceId(row, targetDescriptor)
|
|
1327
|
+
const includeKey = `${targetDescriptor.resource}:${includeId}`
|
|
1106
1328
|
if (seen.has(includeKey)) continue
|
|
1107
1329
|
seen.add(includeKey)
|
|
1108
|
-
const
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
}
|
|
1114
|
-
if (translated.relationships && Object.keys(translated.relationships).length > 0) {
|
|
1115
|
-
includeResource.relationships = translated.relationships
|
|
1116
|
-
}
|
|
1117
|
-
addSelfLink(includeResource, targetDescriptor.resource)
|
|
1330
|
+
const includeResource = buildIncludedResource({
|
|
1331
|
+
row,
|
|
1332
|
+
descriptor: targetDescriptor,
|
|
1333
|
+
context,
|
|
1334
|
+
fieldSelectionInfo: selectionState.fieldSelectionInfo
|
|
1335
|
+
})
|
|
1118
1336
|
includes.push(includeResource)
|
|
1119
1337
|
newResources.push(includeResource)
|
|
1120
1338
|
}
|
|
@@ -1159,25 +1377,30 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1159
1377
|
continue
|
|
1160
1378
|
}
|
|
1161
1379
|
|
|
1162
|
-
|
|
1380
|
+
let query = db(targetDescriptor.canonical.tableName)
|
|
1163
1381
|
.where(targetDescriptor.canonical.tenantColumn, targetDescriptor.tenant)
|
|
1164
1382
|
.where(targetDescriptor.canonical.resourceColumn, targetDescriptor.resource)
|
|
1165
|
-
.whereIn(
|
|
1383
|
+
.whereIn(getLogicalResourceIdColumn(targetDescriptor), [...idSet])
|
|
1384
|
+
const selectionState = await applyIncludeFieldSelection({
|
|
1385
|
+
query,
|
|
1386
|
+
resourceName: targetDescriptor.resource,
|
|
1387
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1388
|
+
context
|
|
1389
|
+
})
|
|
1390
|
+
query = selectionState.query
|
|
1391
|
+
const rows = await query
|
|
1166
1392
|
|
|
1167
1393
|
for (const row of rows) {
|
|
1168
|
-
const
|
|
1394
|
+
const includeId = getLogicalResourceId(row, targetDescriptor)
|
|
1395
|
+
const includeKey = `${targetDescriptor.resource}:${includeId}`
|
|
1169
1396
|
if (seen.has(includeKey)) continue
|
|
1170
1397
|
seen.add(includeKey)
|
|
1171
|
-
const
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
}
|
|
1177
|
-
if (translated.relationships && Object.keys(translated.relationships).length > 0) {
|
|
1178
|
-
includeResource.relationships = translated.relationships
|
|
1179
|
-
}
|
|
1180
|
-
addSelfLink(includeResource, targetDescriptor.resource)
|
|
1398
|
+
const includeResource = buildIncludedResource({
|
|
1399
|
+
row,
|
|
1400
|
+
descriptor: targetDescriptor,
|
|
1401
|
+
context,
|
|
1402
|
+
fieldSelectionInfo: selectionState.fieldSelectionInfo
|
|
1403
|
+
})
|
|
1181
1404
|
includes.push(includeResource)
|
|
1182
1405
|
newResources.push(includeResource)
|
|
1183
1406
|
}
|
|
@@ -1243,6 +1466,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1243
1466
|
|
|
1244
1467
|
let rows = []
|
|
1245
1468
|
let groupingColumn = null
|
|
1469
|
+
let fieldSelectionInfo = null
|
|
1246
1470
|
|
|
1247
1471
|
if (hasManyInfo.foreignKey) {
|
|
1248
1472
|
const foreignField = targetDescriptor.fields?.[hasManyInfo.foreignKey]
|
|
@@ -1250,21 +1474,53 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1250
1474
|
continue
|
|
1251
1475
|
}
|
|
1252
1476
|
groupingColumn = foreignField.slot
|
|
1253
|
-
|
|
1477
|
+
let query = db(targetDescriptor.canonical.tableName)
|
|
1254
1478
|
.where(targetDescriptor.canonical.tenantColumn, targetDescriptor.tenant)
|
|
1255
1479
|
.where(targetDescriptor.canonical.resourceColumn, targetDescriptor.resource)
|
|
1256
1480
|
.whereIn(groupingColumn, parentIds)
|
|
1481
|
+
const selectionState = await applyIncludeFieldSelection({
|
|
1482
|
+
query,
|
|
1483
|
+
resourceName: targetDescriptor.resource,
|
|
1484
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1485
|
+
context
|
|
1486
|
+
})
|
|
1487
|
+
query = selectionState.query
|
|
1488
|
+
const scopedQueryState = await applyTargetScopeFilters({
|
|
1489
|
+
query,
|
|
1490
|
+
resourceName: targetDescriptor.resource,
|
|
1491
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1492
|
+
context
|
|
1493
|
+
})
|
|
1494
|
+
query = scopedQueryState.query
|
|
1495
|
+
fieldSelectionInfo = selectionState.fieldSelectionInfo
|
|
1496
|
+
rows = await query
|
|
1257
1497
|
} else if (hasManyInfo.via) {
|
|
1258
1498
|
const polyInfo = targetDescriptor.polymorphicBelongsTo?.[hasManyInfo.via]
|
|
1259
1499
|
if (!polyInfo?.idColumn || !polyInfo?.typeColumn) {
|
|
1260
1500
|
continue
|
|
1261
1501
|
}
|
|
1262
1502
|
groupingColumn = polyInfo.idColumn
|
|
1263
|
-
|
|
1503
|
+
let query = db(targetDescriptor.canonical.tableName)
|
|
1264
1504
|
.where(targetDescriptor.canonical.tenantColumn, targetDescriptor.tenant)
|
|
1265
1505
|
.where(targetDescriptor.canonical.resourceColumn, targetDescriptor.resource)
|
|
1266
1506
|
.where(polyInfo.typeColumn, descriptor.resource)
|
|
1267
1507
|
.whereIn(groupingColumn, queryIds)
|
|
1508
|
+
const selectionState = await applyIncludeFieldSelection({
|
|
1509
|
+
query,
|
|
1510
|
+
resourceName: targetDescriptor.resource,
|
|
1511
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1512
|
+
context
|
|
1513
|
+
})
|
|
1514
|
+
query = selectionState.query
|
|
1515
|
+
const scopedQueryState = await applyTargetScopeFilters({
|
|
1516
|
+
query,
|
|
1517
|
+
resourceName: targetDescriptor.resource,
|
|
1518
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1519
|
+
context
|
|
1520
|
+
})
|
|
1521
|
+
query = scopedQueryState.query
|
|
1522
|
+
fieldSelectionInfo = selectionState.fieldSelectionInfo
|
|
1523
|
+
rows = await query
|
|
1268
1524
|
}
|
|
1269
1525
|
|
|
1270
1526
|
if (!groupingColumn || rows.length === 0) {
|
|
@@ -1273,19 +1529,16 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1273
1529
|
|
|
1274
1530
|
const newResources = []
|
|
1275
1531
|
for (const row of rows) {
|
|
1276
|
-
const
|
|
1532
|
+
const includeId = getLogicalResourceId(row, targetDescriptor)
|
|
1533
|
+
const includeKey = `${targetDescriptor.resource}:${includeId}`
|
|
1277
1534
|
if (seen.has(includeKey)) continue
|
|
1278
1535
|
seen.add(includeKey)
|
|
1279
|
-
const
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
}
|
|
1285
|
-
if (translated.relationships && Object.keys(translated.relationships).length > 0) {
|
|
1286
|
-
includeResource.relationships = translated.relationships
|
|
1287
|
-
}
|
|
1288
|
-
addSelfLink(includeResource, targetDescriptor.resource)
|
|
1536
|
+
const includeResource = buildIncludedResource({
|
|
1537
|
+
row,
|
|
1538
|
+
descriptor: targetDescriptor,
|
|
1539
|
+
context,
|
|
1540
|
+
fieldSelectionInfo
|
|
1541
|
+
})
|
|
1289
1542
|
includes.push(includeResource)
|
|
1290
1543
|
newResources.push(includeResource)
|
|
1291
1544
|
}
|
|
@@ -1323,26 +1576,31 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1323
1576
|
const targetDescriptor = await registry.getDescriptor(tenantId, info.relInfo.target)
|
|
1324
1577
|
if (!targetDescriptor) continue
|
|
1325
1578
|
|
|
1326
|
-
|
|
1579
|
+
let query = db(targetDescriptor.canonical.tableName)
|
|
1327
1580
|
.where(targetDescriptor.canonical.tenantColumn, targetDescriptor.tenant)
|
|
1328
1581
|
.where(targetDescriptor.canonical.resourceColumn, targetDescriptor.resource)
|
|
1329
|
-
.whereIn(
|
|
1582
|
+
.whereIn(getLogicalResourceIdColumn(targetDescriptor), childIds)
|
|
1583
|
+
const selectionState = await applyIncludeFieldSelection({
|
|
1584
|
+
query,
|
|
1585
|
+
resourceName: targetDescriptor.resource,
|
|
1586
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1587
|
+
context
|
|
1588
|
+
})
|
|
1589
|
+
query = selectionState.query
|
|
1590
|
+
const rows = await query
|
|
1330
1591
|
|
|
1331
1592
|
const newResources = []
|
|
1332
1593
|
for (const row of rows) {
|
|
1333
|
-
const
|
|
1594
|
+
const includeId = getLogicalResourceId(row, targetDescriptor)
|
|
1595
|
+
const includeKey = `${targetDescriptor.resource}:${includeId}`
|
|
1334
1596
|
if (seen.has(includeKey)) continue
|
|
1335
1597
|
seen.add(includeKey)
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
}
|
|
1342
|
-
if (translated.relationships && Object.keys(translated.relationships).length > 0) {
|
|
1343
|
-
includeResource.relationships = translated.relationships
|
|
1344
|
-
}
|
|
1345
|
-
addSelfLink(includeResource, targetDescriptor.resource)
|
|
1598
|
+
const includeResource = buildIncludedResource({
|
|
1599
|
+
row,
|
|
1600
|
+
descriptor: targetDescriptor,
|
|
1601
|
+
context,
|
|
1602
|
+
fieldSelectionInfo: selectionState.fieldSelectionInfo
|
|
1603
|
+
})
|
|
1346
1604
|
includes.push(includeResource)
|
|
1347
1605
|
newResources.push(includeResource)
|
|
1348
1606
|
}
|
|
@@ -1410,17 +1668,25 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1410
1668
|
if (!foreignField?.slot) continue
|
|
1411
1669
|
const column = foreignField.slot
|
|
1412
1670
|
|
|
1413
|
-
|
|
1671
|
+
let query = db(targetDescriptor.canonical.tableName)
|
|
1414
1672
|
.where(targetDescriptor.canonical.tenantColumn, targetDescriptor.tenant)
|
|
1415
1673
|
.where(targetDescriptor.canonical.resourceColumn, targetDescriptor.resource)
|
|
1416
1674
|
.whereIn(column, normalizedParentIds)
|
|
1675
|
+
const scopedQueryState = await applyTargetScopeFilters({
|
|
1676
|
+
query,
|
|
1677
|
+
resourceName: targetDescriptor.resource,
|
|
1678
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1679
|
+
context
|
|
1680
|
+
})
|
|
1681
|
+
query = scopedQueryState.query
|
|
1682
|
+
const rows = await query
|
|
1417
1683
|
|
|
1418
1684
|
const grouped = rows.reduce((acc, row) => {
|
|
1419
1685
|
const parentId = row[column]
|
|
1420
1686
|
if (parentId == null) return acc
|
|
1421
1687
|
const key = String(parentId)
|
|
1422
1688
|
acc[key] = acc[key] || []
|
|
1423
|
-
acc[key].push({ type: targetDescriptor.resource, id:
|
|
1689
|
+
acc[key].push({ type: targetDescriptor.resource, id: getLogicalResourceId(row, targetDescriptor) })
|
|
1424
1690
|
return acc
|
|
1425
1691
|
}, {})
|
|
1426
1692
|
|
|
@@ -1447,18 +1713,26 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1447
1713
|
.filter((value) => value !== null),
|
|
1448
1714
|
]))
|
|
1449
1715
|
|
|
1450
|
-
|
|
1716
|
+
let query = db(targetDescriptor.canonical.tableName)
|
|
1451
1717
|
.where(targetDescriptor.canonical.tenantColumn, targetDescriptor.tenant)
|
|
1452
1718
|
.where(targetDescriptor.canonical.resourceColumn, targetDescriptor.resource)
|
|
1453
1719
|
.where(polyInfo.typeColumn, descriptor.resource)
|
|
1454
1720
|
.whereIn(polyInfo.idColumn, queryIds)
|
|
1721
|
+
const scopedQueryState = await applyTargetScopeFilters({
|
|
1722
|
+
query,
|
|
1723
|
+
resourceName: targetDescriptor.resource,
|
|
1724
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1725
|
+
context
|
|
1726
|
+
})
|
|
1727
|
+
query = scopedQueryState.query
|
|
1728
|
+
const rows = await query
|
|
1455
1729
|
|
|
1456
1730
|
const grouped = rows.reduce((acc, row) => {
|
|
1457
1731
|
const parentId = row[polyInfo.idColumn]
|
|
1458
1732
|
if (parentId == null) return acc
|
|
1459
1733
|
const key = String(parentId)
|
|
1460
1734
|
acc[key] = acc[key] || []
|
|
1461
|
-
acc[key].push({ type: targetDescriptor.resource, id:
|
|
1735
|
+
acc[key].push({ type: targetDescriptor.resource, id: getLogicalResourceId(row, targetDescriptor) })
|
|
1462
1736
|
return acc
|
|
1463
1737
|
}, {})
|
|
1464
1738
|
|
|
@@ -1479,7 +1753,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1479
1753
|
const db = context.db || context.transaction || api.knex.instance
|
|
1480
1754
|
const parentIds = resources.map((resource) => resource.id)
|
|
1481
1755
|
|
|
1482
|
-
for (const [relName
|
|
1756
|
+
for (const [relName] of manyEntries) {
|
|
1483
1757
|
const info = await getManyToManyInfo(descriptor.resource, relName)
|
|
1484
1758
|
if (!info) continue
|
|
1485
1759
|
const rows = await fetchLinksForParents({
|
|
@@ -1512,6 +1786,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1512
1786
|
const descriptor = await getDescriptor(scopeName)
|
|
1513
1787
|
const { canonical } = descriptor
|
|
1514
1788
|
const id = context.id
|
|
1789
|
+
const logicalIdColumn = getLogicalResourceIdColumn(descriptor)
|
|
1515
1790
|
const scope = api.resources?.[scopeName]
|
|
1516
1791
|
|
|
1517
1792
|
let fieldSelectionInfo = null
|
|
@@ -1522,18 +1797,39 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1522
1797
|
context.computedDependencies = []
|
|
1523
1798
|
}
|
|
1524
1799
|
|
|
1525
|
-
|
|
1526
|
-
.where(
|
|
1800
|
+
let query = context.db(canonical.tableName)
|
|
1801
|
+
.where(logicalIdColumn, id)
|
|
1527
1802
|
.where(canonical.resourceColumn, descriptor.resource)
|
|
1528
1803
|
.where(canonical.tenantColumn, descriptor.tenant)
|
|
1529
|
-
|
|
1804
|
+
|
|
1805
|
+
if (scope && fieldSelectionInfo) {
|
|
1806
|
+
const selectionState = await applyFieldSelectionToQuery({
|
|
1807
|
+
query,
|
|
1808
|
+
scope,
|
|
1809
|
+
fieldSelectionInfo,
|
|
1810
|
+
tableName: canonical.tableName,
|
|
1811
|
+
useTablePrefix: false,
|
|
1812
|
+
storageAdapter,
|
|
1813
|
+
db: context.db,
|
|
1814
|
+
context,
|
|
1815
|
+
scopeName
|
|
1816
|
+
})
|
|
1817
|
+
query = selectionState.query
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
const row = await query.first()
|
|
1530
1821
|
|
|
1531
1822
|
if (!row) return null
|
|
1532
1823
|
|
|
1533
|
-
const record =
|
|
1824
|
+
const record = translateCanonicalRecordFromStorage(
|
|
1825
|
+
row,
|
|
1826
|
+
descriptor,
|
|
1827
|
+
{ allowedExtraFields: getAllowedQueryFieldNames(scopeName) }
|
|
1828
|
+
)
|
|
1829
|
+
const resourceId = getLogicalResourceId(row, descriptor)
|
|
1534
1830
|
const data = {
|
|
1535
1831
|
type: descriptor.resource,
|
|
1536
|
-
id:
|
|
1832
|
+
id: resourceId,
|
|
1537
1833
|
attributes: record.attributes,
|
|
1538
1834
|
}
|
|
1539
1835
|
|
|
@@ -1565,24 +1861,20 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1565
1861
|
}
|
|
1566
1862
|
|
|
1567
1863
|
if (scope) {
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
response.links = {
|
|
1571
|
-
self: data.links.self,
|
|
1572
|
-
}
|
|
1864
|
+
decorateResourceLinks({ resource: data, scope, scopeName, context })
|
|
1865
|
+
response.links = { self: data.links.self }
|
|
1573
1866
|
}
|
|
1574
1867
|
|
|
1575
1868
|
if (included.length > 0) {
|
|
1576
1869
|
for (const includeResource of included) {
|
|
1577
1870
|
const targetScope = api.resources?.[includeResource.type]
|
|
1578
1871
|
if (!targetScope) continue
|
|
1579
|
-
|
|
1580
|
-
|
|
1872
|
+
decorateResourceLinks({
|
|
1873
|
+
resource: includeResource,
|
|
1874
|
+
scope: targetScope,
|
|
1875
|
+
scopeName: includeResource.type,
|
|
1581
1876
|
context,
|
|
1582
|
-
|
|
1583
|
-
includeResource.type,
|
|
1584
|
-
includeResource.id
|
|
1585
|
-
)
|
|
1877
|
+
})
|
|
1586
1878
|
}
|
|
1587
1879
|
}
|
|
1588
1880
|
|
|
@@ -1592,10 +1884,11 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1592
1884
|
const rehydrateSchemaInfo = async (scopeName) => {
|
|
1593
1885
|
const scope = api.resources?.[scopeName] || scopes?.[scopeName] || {}
|
|
1594
1886
|
const storedOptions = scopeOptionsRegistry.get(scopeName) || {}
|
|
1887
|
+
const rawSchema = scope.scopeOptions?.schema || storedOptions.schema || {}
|
|
1595
1888
|
|
|
1596
1889
|
if (!scope.scopeOptions) {
|
|
1597
1890
|
scope.scopeOptions = {
|
|
1598
|
-
schema:
|
|
1891
|
+
schema: rawSchema,
|
|
1599
1892
|
relationships: storedOptions.relationships || {},
|
|
1600
1893
|
searchSchema: storedOptions.searchSchema || null,
|
|
1601
1894
|
tableName: storedOptions.tableName || scopeName,
|
|
@@ -1611,7 +1904,6 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1611
1904
|
|
|
1612
1905
|
const mergedSchema = { ...(existingInfo.schemaInstance?.structure || {}) }
|
|
1613
1906
|
const computed = { ...(existingInfo.computed || {}) }
|
|
1614
|
-
|
|
1615
1907
|
for (const [fieldName, definition] of Object.entries(descriptor.schema || {})) {
|
|
1616
1908
|
if (definition?.computed) {
|
|
1617
1909
|
if (!computed[fieldName]) {
|
|
@@ -1699,53 +1991,15 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1699
1991
|
return { resourceToTableName, tableNameToResource }
|
|
1700
1992
|
}
|
|
1701
1993
|
|
|
1702
|
-
const
|
|
1703
|
-
if (!adapter || !fields) return fields
|
|
1704
|
-
|
|
1705
|
-
return fields.map((field) => {
|
|
1706
|
-
if (typeof field !== 'string') return field
|
|
1707
|
-
|
|
1708
|
-
if (field === '*') return '*'
|
|
1994
|
+
const queryHookDependencies = { log, scopes: api.resources, knex, getStorageAdapter: getScopeStorageAdapter }
|
|
1709
1995
|
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
const translated = adapter.translateColumn(field)
|
|
1718
|
-
return translated || field
|
|
1719
|
-
})
|
|
1996
|
+
const applyBuiltInAnyApiQueryFilters = async (context) => {
|
|
1997
|
+
const hookParams = { context }
|
|
1998
|
+
await polymorphicFiltersHook(hookParams, queryHookDependencies)
|
|
1999
|
+
await crossTableFiltersHook(hookParams, queryHookDependencies)
|
|
2000
|
+
await basicFiltersHook(hookParams, queryHookDependencies)
|
|
1720
2001
|
}
|
|
1721
2002
|
|
|
1722
|
-
const scopesProxy = new Proxy({}, {
|
|
1723
|
-
get (_target, prop) {
|
|
1724
|
-
if (typeof prop !== 'string') {
|
|
1725
|
-
return undefined
|
|
1726
|
-
}
|
|
1727
|
-
const resource = api.resources?.[prop]
|
|
1728
|
-
if (resource?.vars?.schemaInfo) {
|
|
1729
|
-
return resource
|
|
1730
|
-
}
|
|
1731
|
-
return scopes?.[prop]
|
|
1732
|
-
},
|
|
1733
|
-
})
|
|
1734
|
-
|
|
1735
|
-
const queryHookDependencies = { log, scopes: scopesProxy, knex, getStorageAdapter: getScopeStorageAdapter }
|
|
1736
|
-
|
|
1737
|
-
addHook('knexQueryFiltering', 'polymorphicFiltersHook', {}, async (hookParams) =>
|
|
1738
|
-
polymorphicFiltersHook(hookParams, queryHookDependencies)
|
|
1739
|
-
)
|
|
1740
|
-
|
|
1741
|
-
addHook('knexQueryFiltering', 'crossTableFiltersHook', {}, async (hookParams) =>
|
|
1742
|
-
crossTableFiltersHook(hookParams, queryHookDependencies)
|
|
1743
|
-
)
|
|
1744
|
-
|
|
1745
|
-
addHook('knexQueryFiltering', 'basicFiltersHook', {}, async (hookParams) =>
|
|
1746
|
-
basicFiltersHook(hookParams, queryHookDependencies)
|
|
1747
|
-
)
|
|
1748
|
-
|
|
1749
2003
|
helpers.dataQuery = async ({ scopeName, context, runHooks }) => {
|
|
1750
2004
|
const storageAdapter = getScopeStorageAdapter(scopeName)
|
|
1751
2005
|
if (storageAdapter) {
|
|
@@ -1801,6 +2055,8 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1801
2055
|
storageAdapter,
|
|
1802
2056
|
}
|
|
1803
2057
|
|
|
2058
|
+
await applyBuiltInAnyApiQueryFilters(context)
|
|
2059
|
+
|
|
1804
2060
|
if (runHooks) {
|
|
1805
2061
|
await runHooks('knexQueryFiltering')
|
|
1806
2062
|
}
|
|
@@ -1809,16 +2065,29 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1809
2065
|
|
|
1810
2066
|
const countQuery = queryBuilder.clone()
|
|
1811
2067
|
|
|
2068
|
+
let selectionState = { query: queryBuilder, queryFieldRuntimeByField: new Map() }
|
|
2069
|
+
if (scope && fieldSelectionInfo) {
|
|
2070
|
+
selectionState = await applyFieldSelectionToQuery({
|
|
2071
|
+
query: queryBuilder,
|
|
2072
|
+
scope,
|
|
2073
|
+
fieldSelectionInfo,
|
|
2074
|
+
tableName: adapter.tableAlias,
|
|
2075
|
+
useTablePrefix: false,
|
|
2076
|
+
storageAdapter,
|
|
2077
|
+
db,
|
|
2078
|
+
context,
|
|
2079
|
+
scopeName
|
|
2080
|
+
})
|
|
2081
|
+
}
|
|
2082
|
+
|
|
1812
2083
|
const sortDescriptors = applySortingToQuery({
|
|
1813
|
-
query:
|
|
2084
|
+
query: selectionState.query,
|
|
1814
2085
|
sort: queryParams.sort,
|
|
1815
2086
|
descriptor,
|
|
1816
2087
|
scope,
|
|
2088
|
+
queryFieldRuntimeByField: selectionState.queryFieldRuntimeByField
|
|
1817
2089
|
})
|
|
1818
2090
|
|
|
1819
|
-
if (queryParams?.page?.after) {
|
|
1820
|
-
}
|
|
1821
|
-
|
|
1822
2091
|
const paginationInfo = applyPaginationToQuery({
|
|
1823
2092
|
query: queryBuilder,
|
|
1824
2093
|
scope,
|
|
@@ -1827,14 +2096,6 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1827
2096
|
adapter,
|
|
1828
2097
|
})
|
|
1829
2098
|
|
|
1830
|
-
if (queryParams?.page?.after) {
|
|
1831
|
-
}
|
|
1832
|
-
|
|
1833
|
-
if (fieldSelectionInfo && fieldSelectionInfo.fieldsToSelect && fieldSelectionInfo.fieldsToSelect.length > 0) {
|
|
1834
|
-
const translatedSelect = translateSelectFieldsForAdapter(fieldSelectionInfo.fieldsToSelect, adapter)
|
|
1835
|
-
queryBuilder.select(translatedSelect)
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1838
2099
|
let rows = await queryBuilder
|
|
1839
2100
|
let cursorRecords = null
|
|
1840
2101
|
let hasMore = false
|
|
@@ -1845,7 +2106,9 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1845
2106
|
const record = {}
|
|
1846
2107
|
for (const descriptorEntry of cursorDescriptors) {
|
|
1847
2108
|
if (descriptorEntry.field === 'id') {
|
|
1848
|
-
record.id = row
|
|
2109
|
+
record.id = getLogicalResourceId(row, descriptor)
|
|
2110
|
+
} else if (descriptorEntry.queryFieldRuntime) {
|
|
2111
|
+
record[descriptorEntry.field] = row[descriptorEntry.field]
|
|
1849
2112
|
} else if (descriptorEntry.column) {
|
|
1850
2113
|
record[descriptorEntry.field] = row[descriptorEntry.column]
|
|
1851
2114
|
}
|
|
@@ -1861,10 +2124,15 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1861
2124
|
}
|
|
1862
2125
|
|
|
1863
2126
|
const data = rows.map((row) => {
|
|
1864
|
-
const translated =
|
|
2127
|
+
const translated = translateCanonicalRecordFromStorage(
|
|
2128
|
+
row,
|
|
2129
|
+
descriptor,
|
|
2130
|
+
{ allowedExtraFields: getAllowedQueryFieldNames(scopeName) }
|
|
2131
|
+
)
|
|
2132
|
+
const resourceId = getLogicalResourceId(row, descriptor)
|
|
1865
2133
|
const resource = {
|
|
1866
2134
|
type: descriptor.resource,
|
|
1867
|
-
id:
|
|
2135
|
+
id: resourceId,
|
|
1868
2136
|
attributes: translated.attributes,
|
|
1869
2137
|
}
|
|
1870
2138
|
if (translated.relationships && Object.keys(translated.relationships).length > 0) {
|
|
@@ -1872,8 +2140,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1872
2140
|
}
|
|
1873
2141
|
const resourceScope = scope
|
|
1874
2142
|
if (resourceScope) {
|
|
1875
|
-
resource
|
|
1876
|
-
resource.links.self = buildResourceUrl(context, resourceScope, scopeName, resource.id)
|
|
2143
|
+
decorateResourceLinks({ resource, scope: resourceScope, scopeName, context })
|
|
1877
2144
|
}
|
|
1878
2145
|
if (fieldSelectionInfo?.computedDependencies?.length) {
|
|
1879
2146
|
resource.__$jsonrestapi_computed_deps$__ = fieldSelectionInfo.computedDependencies
|
|
@@ -1904,6 +2171,19 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1904
2171
|
response.included = included
|
|
1905
2172
|
}
|
|
1906
2173
|
|
|
2174
|
+
if (included.length > 0) {
|
|
2175
|
+
for (const includeResource of included) {
|
|
2176
|
+
const targetScope = api.resources?.[includeResource.type]
|
|
2177
|
+
if (!targetScope) continue
|
|
2178
|
+
decorateResourceLinks({
|
|
2179
|
+
resource: includeResource,
|
|
2180
|
+
scope: targetScope,
|
|
2181
|
+
scopeName: includeResource.type,
|
|
2182
|
+
context,
|
|
2183
|
+
})
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
|
|
1907
2187
|
context.returnMeta = context.returnMeta || {}
|
|
1908
2188
|
context.returnMeta.queryString = buildQueryString(queryParams)
|
|
1909
2189
|
delete context.returnMeta.paginationMeta
|
|
@@ -2194,64 +2474,3 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
2194
2474
|
})
|
|
2195
2475
|
},
|
|
2196
2476
|
}
|
|
2197
|
-
|
|
2198
|
-
const translateAttributesForStorage = (attributes, descriptor) => {
|
|
2199
|
-
const row = {}
|
|
2200
|
-
for (const [fieldName, value] of Object.entries(attributes)) {
|
|
2201
|
-
const slot = descriptor.fields[fieldName]
|
|
2202
|
-
if (!slot) continue
|
|
2203
|
-
|
|
2204
|
-
if (slot.slotType === 'belongsTo') {
|
|
2205
|
-
row[slot.slot] = value == null ? null : String(value)
|
|
2206
|
-
} else {
|
|
2207
|
-
row[slot.slot] = value
|
|
2208
|
-
}
|
|
2209
|
-
|
|
2210
|
-
if (slot.slotType === 'belongsTo') {
|
|
2211
|
-
const alias = slot.alias || descriptor.fields[fieldName]?.alias
|
|
2212
|
-
const belongsToInfo = alias ? descriptor.belongsTo?.[alias] : null
|
|
2213
|
-
if (belongsToInfo) {
|
|
2214
|
-
row[belongsToInfo.typeColumn] = value == null ? null : belongsToInfo.target
|
|
2215
|
-
}
|
|
2216
|
-
}
|
|
2217
|
-
}
|
|
2218
|
-
return row
|
|
2219
|
-
}
|
|
2220
|
-
|
|
2221
|
-
const translateRecordFromStorage = (row, descriptor) => {
|
|
2222
|
-
const attributes = {}
|
|
2223
|
-
for (const [slot, logical] of Object.entries(descriptor.reverseAttributes)) {
|
|
2224
|
-
if (slot in row) {
|
|
2225
|
-
attributes[logical] = row[slot]
|
|
2226
|
-
}
|
|
2227
|
-
}
|
|
2228
|
-
|
|
2229
|
-
const relationships = {}
|
|
2230
|
-
for (const [alias, info] of Object.entries(descriptor.belongsTo || {})) {
|
|
2231
|
-
const idValue = row[info.idColumn]
|
|
2232
|
-
const relationshipData = idValue == null
|
|
2233
|
-
? null
|
|
2234
|
-
: { type: info.target, id: String(idValue) }
|
|
2235
|
-
|
|
2236
|
-
relationships[alias] = { data: relationshipData }
|
|
2237
|
-
}
|
|
2238
|
-
|
|
2239
|
-
for (const [alias, info] of Object.entries(descriptor.polymorphicBelongsTo || {})) {
|
|
2240
|
-
const typeValue = info.typeColumn ? row[info.typeColumn] : null
|
|
2241
|
-
const idValue = info.idColumn ? row[info.idColumn] : null
|
|
2242
|
-
|
|
2243
|
-
let relationshipData = null
|
|
2244
|
-
if (typeValue != null && idValue != null) {
|
|
2245
|
-
relationshipData = {
|
|
2246
|
-
type: String(typeValue),
|
|
2247
|
-
id: String(idValue),
|
|
2248
|
-
}
|
|
2249
|
-
} else if (typeValue == null && idValue == null) {
|
|
2250
|
-
relationshipData = null
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
|
-
relationships[alias] = { data: relationshipData }
|
|
2254
|
-
}
|
|
2255
|
-
|
|
2256
|
-
return { attributes, relationships }
|
|
2257
|
-
}
|