json-rest-api 1.0.22 → 1.0.23
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/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
- package/docs/API.md +51 -14
- package/docs/COMPARISON.md +2 -2
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +28 -1
- 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 +250 -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/package.json +2 -2
- package/plugins/core/connectors/express-plugin.js +97 -184
- package/plugins/core/connectors/fastify-plugin.js +206 -0
- 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/dbIntrospection.js +1078 -0
- package/plugins/core/lib/dbTablesOperations.js +1104 -160
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -2
- package/plugins/core/lib/querying/knex-process-includes.js +3 -4
- package/plugins/core/lib/querying/knex-query-helpers.js +17 -71
- package/plugins/core/lib/querying/knex-relationship-includes.js +466 -206
- 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 +12 -1
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -13
- 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/storage/canonical-storage-mapping.js +113 -0
- package/plugins/core/lib/storage/storage-adapter.js +79 -2
- package/plugins/core/lib/storage/storage-mapping.js +204 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +355 -228
- package/plugins/core/rest-api-autofilter-plugin.js +414 -0
- package/plugins/core/rest-api-knex-plugin.js +249 -211
- 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-methods/apply-query-filters.js +41 -0
- package/plugins/core/rest-api-plugin-methods/common.js +121 -83
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
- package/plugins/core/rest-api-plugin-methods/get.js +23 -14
- package/plugins/core/rest-api-plugin-methods/patch.js +28 -12
- package/plugins/core/rest-api-plugin-methods/post.js +13 -8
- package/plugins/core/rest-api-plugin-methods/put.js +45 -21
- package/plugins/core/rest-api-plugin-methods/query.js +15 -53
- package/plugins/core/rest-api-plugin.js +2 -0
- package/plugins/core/rest-api-positioning-plugin.js +1 -1
- package/plugins/core/rest-api-query-projections-plugin.js +130 -0
- package/plugins/core/socketio-plugin.js +1 -3
- package/tests/autofilter.test.js +463 -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 +518 -0
- package/tests/db-table-operations.test.js +781 -0
- package/tests/fastify-plugin.test.js +141 -0
- package/tests/field-getters.test.js +1 -2
- package/tests/field-setters.test.js +0 -4
- package/tests/fixtures/api-configs.js +188 -219
- package/tests/helpers/fake-fastify.js +99 -0
- package/tests/http-connectors-parity.test.js +235 -0
- 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 +210 -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 } from '../storage/storage-mapping.js'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Compiles and enriches schemas for a resource scope
|
|
@@ -315,6 +316,12 @@ export async function compileSchemas (scope, deps) {
|
|
|
315
316
|
}
|
|
316
317
|
}
|
|
317
318
|
|
|
319
|
+
const idProperty = scope.scopeOptions.idProperty || scope.vars.idProperty || 'id'
|
|
320
|
+
const storageInfo = buildStorageInfo({
|
|
321
|
+
schemaStructure: schemaObject.structure,
|
|
322
|
+
idProperty
|
|
323
|
+
})
|
|
324
|
+
|
|
318
325
|
// Cache everything
|
|
319
326
|
scope.vars.schemaInfo = {
|
|
320
327
|
|
|
@@ -327,7 +334,11 @@ export async function compileSchemas (scope, deps) {
|
|
|
327
334
|
computed: computedFields,
|
|
328
335
|
schemaRelationships,
|
|
329
336
|
tableName: scope.scopeOptions.tableName || scopeName,
|
|
330
|
-
idProperty
|
|
337
|
+
idProperty,
|
|
338
|
+
storageInfo,
|
|
339
|
+
indexes: Array.isArray(scope.scopeOptions.indexes) ? [...scope.scopeOptions.indexes] : [],
|
|
340
|
+
foreignKeys: Array.isArray(scope.scopeOptions.foreignKeys) ? [...scope.scopeOptions.foreignKeys] : [],
|
|
341
|
+
checkConstraints: Array.isArray(scope.scopeOptions.checkConstraints) ? [...scope.scopeOptions.checkConstraints] : [],
|
|
331
342
|
fieldGetters,
|
|
332
343
|
sortedGetterFields,
|
|
333
344
|
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)
|
|
@@ -249,6 +257,7 @@ export const buildFieldSelection = async (scope, deps) => {
|
|
|
249
257
|
}
|
|
250
258
|
})
|
|
251
259
|
}
|
|
260
|
+
|
|
252
261
|
} else {
|
|
253
262
|
// No sparse fieldsets - return all visible fields
|
|
254
263
|
// This is the default behavior when no ?fields parameter is provided
|
|
@@ -289,6 +298,24 @@ export const buildFieldSelection = async (scope, deps) => {
|
|
|
289
298
|
}
|
|
290
299
|
}
|
|
291
300
|
}
|
|
301
|
+
|
|
302
|
+
for (const [fieldName, fieldDef] of Object.entries(queryFields)) {
|
|
303
|
+
if (fieldDef.hidden === true || fieldDef.normallyHidden === true) continue
|
|
304
|
+
queryFieldsToSelect.add(fieldName)
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const sortFields = Array.isArray(context.queryParams?.sort)
|
|
309
|
+
? context.queryParams.sort
|
|
310
|
+
: []
|
|
311
|
+
|
|
312
|
+
for (const sortField of sortFields) {
|
|
313
|
+
if (typeof sortField !== 'string') continue
|
|
314
|
+
const normalizedField = sortField.startsWith('-') ? sortField.slice(1) : sortField
|
|
315
|
+
|
|
316
|
+
if (queryFieldNames.has(normalizedField)) {
|
|
317
|
+
queryFieldsToSelect.add(normalizedField)
|
|
318
|
+
}
|
|
292
319
|
}
|
|
293
320
|
|
|
294
321
|
// Always include foreign keys for relationships (unless hidden)
|
|
@@ -318,9 +345,52 @@ export const buildFieldSelection = async (scope, deps) => {
|
|
|
318
345
|
// 3. enrichAttributes to remove dependencies from final response
|
|
319
346
|
return {
|
|
320
347
|
fieldsToSelect: Array.from(fieldsToSelect), // Fields to SELECT from database
|
|
348
|
+
queryFieldsToSelect: Array.from(queryFieldsToSelect),
|
|
321
349
|
requestedFields: requested, // Fields explicitly requested by user
|
|
322
350
|
computedDependencies: Array.from(computedDependencies), // Dependencies to remove from response
|
|
323
|
-
idProperty
|
|
351
|
+
idProperty: 'id' // Logical field name reference
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export const applyFieldSelectionToQuery = async ({
|
|
356
|
+
query,
|
|
357
|
+
scope,
|
|
358
|
+
fieldSelectionInfo,
|
|
359
|
+
tableName,
|
|
360
|
+
useTablePrefix = false,
|
|
361
|
+
storageAdapter = null,
|
|
362
|
+
db = null,
|
|
363
|
+
context = null,
|
|
364
|
+
scopeName = ''
|
|
365
|
+
}) => {
|
|
366
|
+
const selectTranslator = createSelectTranslator(storageAdapter)
|
|
367
|
+
|
|
368
|
+
const selectedQuery = buildQuerySelection(
|
|
369
|
+
query,
|
|
370
|
+
tableName,
|
|
371
|
+
fieldSelectionInfo?.fieldsToSelect || [],
|
|
372
|
+
useTablePrefix,
|
|
373
|
+
selectTranslator ? { translateColumn: selectTranslator } : undefined
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
const queryFieldRuntimeByField = await buildQueryFieldRuntimes({
|
|
377
|
+
queryFieldNames: fieldSelectionInfo?.queryFieldsToSelect || [],
|
|
378
|
+
queryFields: scope?.vars?.queryFields || {},
|
|
379
|
+
schemaInfo: scope?.vars?.schemaInfo || {},
|
|
380
|
+
tableName,
|
|
381
|
+
storageAdapter,
|
|
382
|
+
db,
|
|
383
|
+
context,
|
|
384
|
+
scopeName
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
for (const [fieldName, runtime] of queryFieldRuntimeByField.entries()) {
|
|
388
|
+
selectedQuery.select({ [fieldName]: runtime.expression })
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
query: selectedQuery,
|
|
393
|
+
queryFieldRuntimeByField
|
|
324
394
|
}
|
|
325
395
|
}
|
|
326
396
|
|
|
@@ -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
|
+
}
|