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
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const unwrapQueryBuilderState = (value, fallback = null) => {
|
|
2
|
+
let current = value
|
|
3
|
+
const seen = new Set()
|
|
4
|
+
|
|
5
|
+
while (
|
|
6
|
+
current &&
|
|
7
|
+
typeof current === 'object' &&
|
|
8
|
+
!Array.isArray(current) &&
|
|
9
|
+
Object.hasOwn(current, 'query') &&
|
|
10
|
+
current.query &&
|
|
11
|
+
current.query !== current &&
|
|
12
|
+
!seen.has(current)
|
|
13
|
+
) {
|
|
14
|
+
seen.add(current)
|
|
15
|
+
current = current.query
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return current || fallback
|
|
19
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { normalizeStableSort, parseSortEntry } from './sort-helpers.js'
|
|
2
|
+
|
|
3
|
+
export const getEffectiveSortableFields = (vars = {}) => {
|
|
4
|
+
const baseSortableFields = Array.isArray(vars.sortableFields)
|
|
5
|
+
? vars.sortableFields
|
|
6
|
+
: []
|
|
7
|
+
const queryFieldSortableFields = Object.entries(vars.queryFields || {})
|
|
8
|
+
.filter(([, fieldDef]) => fieldDef?.sortable === true)
|
|
9
|
+
.map(([fieldName]) => fieldName)
|
|
10
|
+
|
|
11
|
+
return Array.from(new Set([...baseSortableFields, ...queryFieldSortableFields]))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const buildEffectiveSortList = (sort, { defaultSort, idField = 'id' } = {}) => {
|
|
15
|
+
let sortList = Array.isArray(sort) ? [...sort] : (sort ? [sort] : [])
|
|
16
|
+
|
|
17
|
+
if (sortList.length === 0 && defaultSort) {
|
|
18
|
+
if (Array.isArray(defaultSort)) {
|
|
19
|
+
sortList = [...defaultSort]
|
|
20
|
+
} else if (typeof defaultSort === 'string') {
|
|
21
|
+
sortList = [defaultSort]
|
|
22
|
+
} else if (typeof defaultSort === 'object') {
|
|
23
|
+
const field = defaultSort.field || defaultSort.column || idField
|
|
24
|
+
const direction = (defaultSort.direction || '').toLowerCase() === 'desc' ? '-' : ''
|
|
25
|
+
sortList = [`${direction}${field}`]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return normalizeStableSort(sortList.length > 0 ? sortList : [idField], { idField })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const applyQueryFieldOrder = (query, queryFieldRuntime, direction) => {
|
|
33
|
+
query.orderByRaw(`(${queryFieldRuntime.sql}) ${direction}`, queryFieldRuntime.bindings)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const applyQueryFieldPredicate = (builder, queryFieldRuntime, operator, value) => {
|
|
37
|
+
builder.whereRaw(`(${queryFieldRuntime.sql}) ${operator} ?`, [...queryFieldRuntime.bindings, value])
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const applySortDescriptorPredicate = (builder, descriptor, operator, value, applyPlainPredicate) => {
|
|
41
|
+
if (descriptor?.queryFieldRuntime) {
|
|
42
|
+
return applyQueryFieldPredicate(builder, descriptor.queryFieldRuntime, operator, value)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return applyPlainPredicate(builder, descriptor, operator, value)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const buildCursorPredicateChains = (
|
|
49
|
+
descriptors,
|
|
50
|
+
cursorValues,
|
|
51
|
+
operatorSelector,
|
|
52
|
+
{ onMissingValue } = {}
|
|
53
|
+
) => {
|
|
54
|
+
if (!Array.isArray(descriptors) || descriptors.length === 0) {
|
|
55
|
+
return []
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const predicateChains = []
|
|
59
|
+
|
|
60
|
+
descriptors.forEach((descriptor, index) => {
|
|
61
|
+
const cursorValue = cursorValues?.[descriptor.field]
|
|
62
|
+
|
|
63
|
+
if (cursorValue === undefined) {
|
|
64
|
+
onMissingValue?.(descriptor.field)
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const chain = []
|
|
69
|
+
for (let i = 0; i < index; i += 1) {
|
|
70
|
+
const previousDescriptor = descriptors[i]
|
|
71
|
+
const previousValue = cursorValues?.[previousDescriptor.field]
|
|
72
|
+
|
|
73
|
+
if (previousValue === undefined) {
|
|
74
|
+
onMissingValue?.(previousDescriptor.field)
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
chain.push({
|
|
79
|
+
descriptor: previousDescriptor,
|
|
80
|
+
operator: '=',
|
|
81
|
+
value: previousValue
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
chain.push({
|
|
86
|
+
descriptor,
|
|
87
|
+
operator: operatorSelector(descriptor.direction),
|
|
88
|
+
value: cursorValue
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
predicateChains.push(chain)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
return predicateChains
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const applyCursorPredicate = (
|
|
98
|
+
query,
|
|
99
|
+
descriptors,
|
|
100
|
+
cursorValues,
|
|
101
|
+
operatorSelector,
|
|
102
|
+
applyPlainPredicate,
|
|
103
|
+
{ onMissingValue } = {}
|
|
104
|
+
) => {
|
|
105
|
+
const predicateChains = buildCursorPredicateChains(
|
|
106
|
+
descriptors,
|
|
107
|
+
cursorValues,
|
|
108
|
+
operatorSelector,
|
|
109
|
+
{ onMissingValue }
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
if (predicateChains.length === 0) {
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
query.where(function () {
|
|
117
|
+
predicateChains.forEach((chain) => {
|
|
118
|
+
this.orWhere(function () {
|
|
119
|
+
chain.forEach(({ descriptor, operator, value }) => {
|
|
120
|
+
applySortDescriptorPredicate(
|
|
121
|
+
this,
|
|
122
|
+
descriptor,
|
|
123
|
+
operator,
|
|
124
|
+
value,
|
|
125
|
+
applyPlainPredicate
|
|
126
|
+
)
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
return true
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export { parseSortEntry }
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function normalizeStableSort (sort = [], { idField = 'id' } = {}) {
|
|
2
|
+
const sortList = Array.isArray(sort)
|
|
3
|
+
? sort.filter((entry) => typeof entry === 'string' && entry.trim())
|
|
4
|
+
: []
|
|
5
|
+
|
|
6
|
+
if (sortList.length === 0) {
|
|
7
|
+
return [idField]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const hasId = sortList.some((entry) => {
|
|
11
|
+
const fieldName = entry.startsWith('-') ? entry.slice(1) : entry
|
|
12
|
+
return fieldName === idField
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
if (!hasId) {
|
|
16
|
+
sortList.push(idField)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return sortList
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function parseSortEntry (sortEntry) {
|
|
23
|
+
const desc = typeof sortEntry === 'string' && sortEntry.startsWith('-')
|
|
24
|
+
const field = desc ? sortEntry.slice(1) : sortEntry
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
raw: sortEntry,
|
|
28
|
+
field,
|
|
29
|
+
desc,
|
|
30
|
+
direction: desc ? 'desc' : 'asc',
|
|
31
|
+
sqlDirection: desc ? 'DESC' : 'ASC'
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const createStorageAdapterUtilities = (hookParams, { getStorageAdapter } = {}) => {
|
|
2
|
+
const context = hookParams?.context || {}
|
|
3
|
+
const storageCache = new Map()
|
|
4
|
+
|
|
5
|
+
const fetchStorageAdapter = (scopeName) => {
|
|
6
|
+
if (!scopeName) return null
|
|
7
|
+
if (storageCache.has(scopeName)) return storageCache.get(scopeName)
|
|
8
|
+
|
|
9
|
+
let adapter = null
|
|
10
|
+
if (scopeName === context?.knexQuery?.scopeName) {
|
|
11
|
+
adapter = context.storageAdapter ||
|
|
12
|
+
context.knexQuery?.storageAdapter ||
|
|
13
|
+
(typeof getStorageAdapter === 'function' ? getStorageAdapter(scopeName) : null)
|
|
14
|
+
if (adapter && !context.storageAdapter) {
|
|
15
|
+
context.storageAdapter = adapter
|
|
16
|
+
}
|
|
17
|
+
} else {
|
|
18
|
+
adapter = typeof getStorageAdapter === 'function' ? getStorageAdapter(scopeName) : null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
storageCache.set(scopeName, adapter)
|
|
22
|
+
return adapter
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const defaultAliasForScope = (scopeName) => {
|
|
26
|
+
if (scopeName === context?.knexQuery?.scopeName) {
|
|
27
|
+
return context.knexQuery?.tableName || scopeName
|
|
28
|
+
}
|
|
29
|
+
return scopeName
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const translateColumn = (scopeName, field, alias = defaultAliasForScope(scopeName)) => {
|
|
33
|
+
const adapter = fetchStorageAdapter(scopeName)
|
|
34
|
+
const translated = adapter?.translateColumn?.(field) ?? field
|
|
35
|
+
if (!alias) return translated
|
|
36
|
+
return `${alias}.${translated}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const translateFilterValue = (scopeName, field, value) => {
|
|
40
|
+
const adapter = fetchStorageAdapter(scopeName)
|
|
41
|
+
if (!adapter?.translateFilterValue) return value
|
|
42
|
+
return adapter.translateFilterValue(field, value)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
fetchStorageAdapter,
|
|
47
|
+
defaultAliasForScope,
|
|
48
|
+
translateColumn,
|
|
49
|
+
translateFilterValue,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -41,8 +41,14 @@ export function getUrlPrefix (context, scope, req = null) {
|
|
|
41
41
|
|
|
42
42
|
// Priority 3: Calculate from request if provided
|
|
43
43
|
if (req) {
|
|
44
|
-
const protocol = req.get?.('x-forwarded-proto') ||
|
|
45
|
-
|
|
44
|
+
const protocol = req.get?.('x-forwarded-proto') ||
|
|
45
|
+
req.headers?.['x-forwarded-proto'] ||
|
|
46
|
+
req.protocol ||
|
|
47
|
+
'http'
|
|
48
|
+
const host = req.get?.('x-forwarded-host') ||
|
|
49
|
+
req.headers?.['x-forwarded-host'] ||
|
|
50
|
+
req.get?.('host') ||
|
|
51
|
+
req.headers?.host
|
|
46
52
|
const mountPath = scope?.vars?.transport?.mountPath || ''
|
|
47
53
|
|
|
48
54
|
if (host) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createSchema } from 'json-rest-schema'
|
|
2
2
|
import { ensureSearchFieldsAreIndexed, generateSearchSchemaFromSchema, sortFieldsByDependencies } from './schema-helpers.js'
|
|
3
|
+
import { buildStorageInfo, normalizeStorageConfig } from '../storage/storage-mapping.js'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Compiles and enriches schemas for a resource scope
|
|
@@ -154,7 +155,7 @@ export async function compileSchemas (scope, deps) {
|
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
// Default enrichment: Add type to belongsTo fields
|
|
157
|
-
for (const [
|
|
158
|
+
for (const [, fieldDef] of Object.entries(enrichedFields)) {
|
|
158
159
|
if (fieldDef.belongsTo && !fieldDef.type) {
|
|
159
160
|
fieldDef.type = 'id'
|
|
160
161
|
}
|
|
@@ -173,7 +174,7 @@ export async function compileSchemas (scope, deps) {
|
|
|
173
174
|
|
|
174
175
|
if (isProbablyPivot) {
|
|
175
176
|
// Make all belongsTo fields searchable for pivot operations
|
|
176
|
-
for (const [
|
|
177
|
+
for (const [, fieldDef] of belongsToFields) {
|
|
177
178
|
if (!fieldDef.search) {
|
|
178
179
|
fieldDef.search = true
|
|
179
180
|
}
|
|
@@ -205,6 +206,7 @@ export async function compileSchemas (scope, deps) {
|
|
|
205
206
|
scope.scopeOptions.searchSchema
|
|
206
207
|
)
|
|
207
208
|
|
|
209
|
+
let searchSchemaObject
|
|
208
210
|
if (rawSearchFields) {
|
|
209
211
|
// Mark all search fields as indexed for database optimization.
|
|
210
212
|
// This ensures that any field used for filtering will have a database index created,
|
|
@@ -223,9 +225,9 @@ export async function compileSchemas (scope, deps) {
|
|
|
223
225
|
await runHooks('searchSchema:enrich', searchSchemaContext)
|
|
224
226
|
|
|
225
227
|
// Create searchSchema object
|
|
226
|
-
|
|
228
|
+
searchSchemaObject = createSchema(rawSearchFields)
|
|
227
229
|
} else {
|
|
228
|
-
|
|
230
|
+
searchSchemaObject = createSchema({})
|
|
229
231
|
}
|
|
230
232
|
|
|
231
233
|
// Build schemaRelationships including polymorphic fields from schema
|
|
@@ -315,6 +317,14 @@ export async function compileSchemas (scope, deps) {
|
|
|
315
317
|
}
|
|
316
318
|
}
|
|
317
319
|
|
|
320
|
+
const idProperty = scope.scopeOptions.idProperty || scope.vars.idProperty || 'id'
|
|
321
|
+
const storage = normalizeStorageConfig(scope.scopeOptions.storage)
|
|
322
|
+
const storageInfo = buildStorageInfo({
|
|
323
|
+
schemaStructure: schemaObject.structure,
|
|
324
|
+
idProperty,
|
|
325
|
+
storage
|
|
326
|
+
})
|
|
327
|
+
|
|
318
328
|
// Cache everything
|
|
319
329
|
scope.vars.schemaInfo = {
|
|
320
330
|
|
|
@@ -327,7 +337,12 @@ export async function compileSchemas (scope, deps) {
|
|
|
327
337
|
computed: computedFields,
|
|
328
338
|
schemaRelationships,
|
|
329
339
|
tableName: scope.scopeOptions.tableName || scopeName,
|
|
330
|
-
idProperty
|
|
340
|
+
idProperty,
|
|
341
|
+
storage,
|
|
342
|
+
storageInfo,
|
|
343
|
+
indexes: Array.isArray(scope.scopeOptions.indexes) ? [...scope.scopeOptions.indexes] : [],
|
|
344
|
+
foreignKeys: Array.isArray(scope.scopeOptions.foreignKeys) ? [...scope.scopeOptions.foreignKeys] : [],
|
|
345
|
+
checkConstraints: Array.isArray(scope.scopeOptions.checkConstraints) ? [...scope.scopeOptions.checkConstraints] : [],
|
|
331
346
|
fieldGetters,
|
|
332
347
|
sortedGetterFields,
|
|
333
348
|
fieldSetters,
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { buildQuerySelection } from '../querying/knex-query-helpers-base.js'
|
|
2
|
+
import { createSelectTranslator } from '../storage/storage-adapter.js'
|
|
3
|
+
import { buildQueryFieldRuntimes } from './query-field-helpers.js'
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* Checks if a field exists only in code, not in the database
|
|
3
7
|
*
|
|
@@ -143,25 +147,21 @@ export const buildFieldSelection = async (scope, deps) => {
|
|
|
143
147
|
const computedDependencies = new Set()
|
|
144
148
|
|
|
145
149
|
// Extract values from scope
|
|
150
|
+
const scopeSchemaInfo = scope?.vars?.schemaInfo || {}
|
|
146
151
|
const {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
152
|
+
schemaInstance,
|
|
153
|
+
computed: computedFields = {}
|
|
154
|
+
} = scopeSchemaInfo
|
|
155
|
+
let schemaStructure = scopeSchemaInfo.schemaStructure
|
|
156
|
+
const queryFields = scope?.vars?.queryFields || {}
|
|
151
157
|
|
|
152
158
|
// Extract values from deps
|
|
153
159
|
const { context } = deps
|
|
154
160
|
const scopeName = context.scopeName
|
|
155
161
|
const requestedFields = context.queryParams?.fields?.[scopeName]
|
|
156
|
-
const idProperty = context.schemaInfo.idProperty
|
|
157
162
|
|
|
158
|
-
// Always include the ID field -
|
|
159
|
-
|
|
160
|
-
if (idProperty !== 'id') {
|
|
161
|
-
fieldsToSelect.add(`${idProperty} as id`)
|
|
162
|
-
} else {
|
|
163
|
-
fieldsToSelect.add('id')
|
|
164
|
-
}
|
|
163
|
+
// Always include the logical ID field - the storage adapter translates it.
|
|
164
|
+
fieldsToSelect.add('id')
|
|
165
165
|
|
|
166
166
|
// Handle both Schema objects and plain objects
|
|
167
167
|
if (!schemaStructure) {
|
|
@@ -170,6 +170,8 @@ export const buildFieldSelection = async (scope, deps) => {
|
|
|
170
170
|
|
|
171
171
|
// Get computed fields and virtual fields from schema
|
|
172
172
|
const computedFieldNames = new Set(Object.keys(computedFields))
|
|
173
|
+
const queryFieldNames = new Set(Object.keys(queryFields))
|
|
174
|
+
const queryFieldsToSelect = new Set()
|
|
173
175
|
|
|
174
176
|
// Find fields marked as virtual in the schema
|
|
175
177
|
const virtualFieldNames = new Set()
|
|
@@ -194,6 +196,12 @@ export const buildFieldSelection = async (scope, deps) => {
|
|
|
194
196
|
// Sparse fieldsets requested - only select specified fields
|
|
195
197
|
// Example: ?fields[products]=name,price,profit_margin
|
|
196
198
|
requested.forEach(field => {
|
|
199
|
+
if (queryFieldNames.has(field)) {
|
|
200
|
+
if (queryFields[field]?.hidden === true) return
|
|
201
|
+
queryFieldsToSelect.add(field)
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
197
205
|
// Skip computed and virtual fields - they don't exist in database
|
|
198
206
|
// Computed fields will be calculated later in enrichAttributes
|
|
199
207
|
// Virtual fields are handled separately (from request input)
|
|
@@ -274,7 +282,7 @@ export const buildFieldSelection = async (scope, deps) => {
|
|
|
274
282
|
// So we need to include their dependencies even if normallyHidden
|
|
275
283
|
// Example: profit_margin depends on 'cost' which is normallyHidden
|
|
276
284
|
// We fetch 'cost' for calculation but don't return it in response
|
|
277
|
-
for (const [
|
|
285
|
+
for (const [, fieldDef] of Object.entries(computedFields)) {
|
|
278
286
|
if (fieldDef.dependencies) {
|
|
279
287
|
for (const dep of fieldDef.dependencies) {
|
|
280
288
|
const depFieldDef = schemaStructure[dep]
|
|
@@ -289,6 +297,24 @@ export const buildFieldSelection = async (scope, deps) => {
|
|
|
289
297
|
}
|
|
290
298
|
}
|
|
291
299
|
}
|
|
300
|
+
|
|
301
|
+
for (const [fieldName, fieldDef] of Object.entries(queryFields)) {
|
|
302
|
+
if (fieldDef.hidden === true || fieldDef.normallyHidden === true) continue
|
|
303
|
+
queryFieldsToSelect.add(fieldName)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const sortFields = Array.isArray(context.queryParams?.sort)
|
|
308
|
+
? context.queryParams.sort
|
|
309
|
+
: []
|
|
310
|
+
|
|
311
|
+
for (const sortField of sortFields) {
|
|
312
|
+
if (typeof sortField !== 'string') continue
|
|
313
|
+
const normalizedField = sortField.startsWith('-') ? sortField.slice(1) : sortField
|
|
314
|
+
|
|
315
|
+
if (queryFieldNames.has(normalizedField)) {
|
|
316
|
+
queryFieldsToSelect.add(normalizedField)
|
|
317
|
+
}
|
|
292
318
|
}
|
|
293
319
|
|
|
294
320
|
// Always include foreign keys for relationships (unless hidden)
|
|
@@ -318,9 +344,52 @@ export const buildFieldSelection = async (scope, deps) => {
|
|
|
318
344
|
// 3. enrichAttributes to remove dependencies from final response
|
|
319
345
|
return {
|
|
320
346
|
fieldsToSelect: Array.from(fieldsToSelect), // Fields to SELECT from database
|
|
347
|
+
queryFieldsToSelect: Array.from(queryFieldsToSelect),
|
|
321
348
|
requestedFields: requested, // Fields explicitly requested by user
|
|
322
349
|
computedDependencies: Array.from(computedDependencies), // Dependencies to remove from response
|
|
323
|
-
idProperty
|
|
350
|
+
idProperty: 'id' // Logical field name reference
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export const applyFieldSelectionToQuery = async ({
|
|
355
|
+
query,
|
|
356
|
+
scope,
|
|
357
|
+
fieldSelectionInfo,
|
|
358
|
+
tableName,
|
|
359
|
+
useTablePrefix = false,
|
|
360
|
+
storageAdapter = null,
|
|
361
|
+
db = null,
|
|
362
|
+
context = null,
|
|
363
|
+
scopeName = ''
|
|
364
|
+
}) => {
|
|
365
|
+
const selectTranslator = createSelectTranslator(storageAdapter)
|
|
366
|
+
|
|
367
|
+
const selectedQuery = buildQuerySelection(
|
|
368
|
+
query,
|
|
369
|
+
tableName,
|
|
370
|
+
fieldSelectionInfo?.fieldsToSelect || [],
|
|
371
|
+
useTablePrefix,
|
|
372
|
+
selectTranslator ? { translateColumn: selectTranslator } : undefined
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
const queryFieldRuntimeByField = await buildQueryFieldRuntimes({
|
|
376
|
+
queryFieldNames: fieldSelectionInfo?.queryFieldsToSelect || [],
|
|
377
|
+
queryFields: scope?.vars?.queryFields || {},
|
|
378
|
+
schemaInfo: scope?.vars?.schemaInfo || {},
|
|
379
|
+
tableName,
|
|
380
|
+
storageAdapter,
|
|
381
|
+
db,
|
|
382
|
+
context,
|
|
383
|
+
scopeName
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
for (const [fieldName, runtime] of queryFieldRuntimeByField.entries()) {
|
|
387
|
+
selectedQuery.select({ [fieldName]: runtime.expression })
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
query: selectedQuery,
|
|
392
|
+
queryFieldRuntimeByField
|
|
324
393
|
}
|
|
325
394
|
}
|
|
326
395
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { toJsonApiRecord } from '../querying/knex-json-api-transformers-querying.js'
|
|
2
2
|
import { getSchemaStructure } from './knex-constants.js'
|
|
3
|
+
import { translateRecordFromStorage } from '../storage/storage-mapping.js'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Converts database record to JSON:API format with belongsTo relationships
|
|
@@ -92,17 +93,17 @@ import { getSchemaStructure } from './knex-constants.js'
|
|
|
92
93
|
export const toJsonApiRecordWithBelongsTo = (scope, record, scopeName) => {
|
|
93
94
|
if (!record) return null
|
|
94
95
|
|
|
96
|
+
const schemaInfo = scope.vars.schemaInfo
|
|
97
|
+
const logicalRecord = translateRecordFromStorage(record, schemaInfo)
|
|
98
|
+
|
|
95
99
|
// Get the basic JSON:API structure (without relationships)
|
|
96
|
-
const jsonApiRecord = toJsonApiRecord(scope,
|
|
100
|
+
const jsonApiRecord = toJsonApiRecord(scope, logicalRecord, scopeName)
|
|
97
101
|
|
|
98
102
|
// Extract schema info from scope
|
|
99
103
|
const {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
} = scope
|
|
104
|
-
|
|
105
|
-
const idField = idProperty || 'id'
|
|
104
|
+
schemaInstance,
|
|
105
|
+
schemaRelationships: relationships
|
|
106
|
+
} = schemaInfo
|
|
106
107
|
|
|
107
108
|
// Get schema structure
|
|
108
109
|
const schemaStructure = getSchemaStructure(schemaInstance)
|
|
@@ -113,13 +114,13 @@ export const toJsonApiRecordWithBelongsTo = (scope, record, scopeName) => {
|
|
|
113
114
|
// Process regular belongsTo relationships from schema
|
|
114
115
|
for (const [fieldName, fieldDef] of Object.entries(schemaStructure)) {
|
|
115
116
|
if (fieldDef.belongsTo && fieldDef.as) {
|
|
116
|
-
const
|
|
117
|
+
const fieldValue = logicalRecord[fieldName]
|
|
117
118
|
|
|
118
|
-
if (
|
|
119
|
+
if (fieldValue !== null && fieldValue !== undefined) {
|
|
119
120
|
jsonApiRecord.relationships[fieldDef.as] = {
|
|
120
121
|
data: {
|
|
121
122
|
type: fieldDef.belongsTo,
|
|
122
|
-
id: String(
|
|
123
|
+
id: String(fieldValue)
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
126
|
} else {
|
|
@@ -134,8 +135,8 @@ export const toJsonApiRecordWithBelongsTo = (scope, record, scopeName) => {
|
|
|
134
135
|
// Process polymorphic belongsTo relationships
|
|
135
136
|
Object.entries(relationships || {}).forEach(([relName, relDef]) => {
|
|
136
137
|
if (relDef.belongsToPolymorphic) {
|
|
137
|
-
const typeValue =
|
|
138
|
-
const idValue =
|
|
138
|
+
const typeValue = logicalRecord[relDef.typeField]
|
|
139
|
+
const idValue = logicalRecord[relDef.idField]
|
|
139
140
|
|
|
140
141
|
if (typeValue && idValue) {
|
|
141
142
|
jsonApiRecord.relationships[relName] = {
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { getStorageColumn } from '../storage/storage-mapping.js'
|
|
2
|
+
|
|
3
|
+
function qualifyColumnReference (columnName, tableName) {
|
|
4
|
+
if (!tableName || !columnName || columnName === '*' || String(columnName).includes('.')) {
|
|
5
|
+
return columnName
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return `${tableName}.${columnName}`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeQueryFieldSql (expression) {
|
|
12
|
+
if (expression && typeof expression.toSQL === 'function') {
|
|
13
|
+
const compiled = expression.toSQL()
|
|
14
|
+
return {
|
|
15
|
+
sql: compiled.sql,
|
|
16
|
+
bindings: compiled.bindings || []
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
throw new Error('Query field expressions must return knex raw, knex ref, or a knex query builder.')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function buildQueryFieldRuntimes ({
|
|
24
|
+
queryFieldNames = [],
|
|
25
|
+
queryFields = {},
|
|
26
|
+
schemaInfo = {},
|
|
27
|
+
tableName = '',
|
|
28
|
+
storageAdapter = null,
|
|
29
|
+
db = null,
|
|
30
|
+
context = null,
|
|
31
|
+
scopeName = ''
|
|
32
|
+
} = {}) {
|
|
33
|
+
const runtimes = new Map()
|
|
34
|
+
const seen = new Set()
|
|
35
|
+
|
|
36
|
+
for (const fieldName of queryFieldNames) {
|
|
37
|
+
if (!fieldName || seen.has(fieldName)) {
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
seen.add(fieldName)
|
|
41
|
+
|
|
42
|
+
const fieldDef = queryFields[fieldName]
|
|
43
|
+
if (!fieldDef) {
|
|
44
|
+
throw new Error(`Unknown query field '${fieldName}' requested for '${scopeName}'.`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const column = (logicalField) => {
|
|
48
|
+
const translated = storageAdapter?.translateColumn?.(logicalField) || getStorageColumn(schemaInfo, logicalField)
|
|
49
|
+
return qualifyColumnReference(translated, tableName)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const selectResult = fieldDef.select({
|
|
53
|
+
knex: db,
|
|
54
|
+
db,
|
|
55
|
+
context,
|
|
56
|
+
scopeName,
|
|
57
|
+
tableName,
|
|
58
|
+
fieldName,
|
|
59
|
+
schemaInfo,
|
|
60
|
+
adapter: storageAdapter,
|
|
61
|
+
column,
|
|
62
|
+
ref: (logicalField) => db?.ref ? db.ref(column(logicalField)) : column(logicalField)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const expression = (
|
|
66
|
+
selectResult &&
|
|
67
|
+
typeof selectResult === 'object' &&
|
|
68
|
+
typeof selectResult.toSQL === 'function'
|
|
69
|
+
)
|
|
70
|
+
? selectResult
|
|
71
|
+
: await selectResult
|
|
72
|
+
|
|
73
|
+
if (expression === undefined || expression === null) {
|
|
74
|
+
throw new Error(`Query field '${fieldName}' in scope '${scopeName}' returned an empty select expression.`)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const { sql, bindings } = normalizeQueryFieldSql(expression)
|
|
78
|
+
runtimes.set(fieldName, {
|
|
79
|
+
fieldName,
|
|
80
|
+
definition: fieldDef,
|
|
81
|
+
expression,
|
|
82
|
+
sql,
|
|
83
|
+
bindings
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return runtimes
|
|
88
|
+
}
|