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
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { requirePackage } from 'hooked-api'
|
|
2
2
|
import { createSchema } from 'json-rest-schema'
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
createKnexTable,
|
|
5
|
+
addKnexFields,
|
|
6
|
+
alterKnexFields,
|
|
7
|
+
generateKnexMigration,
|
|
8
|
+
generateKnexMigrationDiff
|
|
9
|
+
} from './lib/dbTablesOperations.js'
|
|
10
|
+
import { introspectKnexTableSnapshot } from './lib/dbIntrospection.js'
|
|
11
|
+
import { applyFieldSelectionToQuery, buildFieldSelection, isNonDatabaseField } from './lib/querying-writing/knex-field-helpers.js'
|
|
5
12
|
import { getForeignKeyFields } from './lib/querying-writing/field-utils.js'
|
|
6
|
-
import { buildQuerySelection } from './lib/querying/knex-query-helpers-base.js'
|
|
7
13
|
import { toJsonApiRecord, buildJsonApiResponse } from './lib/querying/knex-json-api-transformers-querying.js'
|
|
8
14
|
import { processBelongsToRelationships } from './lib/writing/knex-json-api-transformers-writing.js'
|
|
9
15
|
import { toJsonApiRecordWithBelongsTo } from './lib/querying-writing/knex-json-api-transformers.js'
|
|
@@ -26,30 +32,13 @@ import {
|
|
|
26
32
|
} from './lib/querying/knex-pagination-helpers.js'
|
|
27
33
|
import { getUrlPrefix } from './lib/querying/url-helpers.js'
|
|
28
34
|
import { createStorageAdapter } from './lib/storage/storage-adapter.js'
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const stripNonDatabaseFields = (attributes, schemaInfo) => {
|
|
37
|
-
if (!attributes || !schemaInfo) return attributes || {}
|
|
38
|
-
|
|
39
|
-
const { computed = {}, schemaStructure = {} } = schemaInfo
|
|
40
|
-
return Object.entries(attributes)
|
|
41
|
-
.filter(([key]) => {
|
|
42
|
-
// Remove computed fields
|
|
43
|
-
if (key in computed) return false
|
|
44
|
-
// Remove fields marked as virtual in the schema
|
|
45
|
-
const fieldDef = schemaStructure[key]
|
|
46
|
-
if (fieldDef && fieldDef.virtual === true) {
|
|
47
|
-
return false
|
|
48
|
-
}
|
|
49
|
-
return true
|
|
50
|
-
})
|
|
51
|
-
.reduce((acc, [key, val]) => ({ ...acc, [key]: val }), {})
|
|
52
|
-
}
|
|
35
|
+
import {
|
|
36
|
+
applyCursorPredicate,
|
|
37
|
+
applyQueryFieldOrder,
|
|
38
|
+
buildEffectiveSortList,
|
|
39
|
+
parseSortEntry
|
|
40
|
+
} from './lib/querying/query-field-sort-helpers.js'
|
|
41
|
+
import { unwrapQueryBuilderState } from './lib/querying/query-builder-utils.js'
|
|
53
42
|
|
|
54
43
|
export const RestApiKnexPlugin = {
|
|
55
44
|
name: 'rest-api-knex',
|
|
@@ -99,22 +88,89 @@ export const RestApiKnexPlugin = {
|
|
|
99
88
|
api.knex.helpers.getStorageAdapter = getScopeStorageAdapter
|
|
100
89
|
helpers.getStorageAdapter = getScopeStorageAdapter
|
|
101
90
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
91
|
+
const buildScopeTableSchema = (vars = {}) => {
|
|
92
|
+
const schemaStructure = vars.schemaInfo?.schemaStructure || {}
|
|
93
|
+
const filteredSchema = {}
|
|
94
|
+
|
|
95
|
+
Object.entries(schemaStructure).forEach(([fieldName, fieldDef]) => {
|
|
96
|
+
if (!fieldDef.virtual) {
|
|
97
|
+
filteredSchema[fieldName] = fieldDef
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
structure: filteredSchema,
|
|
103
|
+
indexes: vars.schemaInfo?.indexes || [],
|
|
104
|
+
foreignKeys: vars.schemaInfo?.foreignKeys || [],
|
|
105
|
+
checkConstraints: vars.schemaInfo?.checkConstraints || []
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const buildLegacySortDescriptors = ({
|
|
110
|
+
query,
|
|
111
|
+
sort,
|
|
112
|
+
schemaInfo,
|
|
113
|
+
sortableFields,
|
|
114
|
+
storageAdapter,
|
|
115
|
+
defaultSort,
|
|
116
|
+
queryFieldRuntimeByField = new Map()
|
|
117
|
+
}) => {
|
|
118
|
+
const effectiveSort = buildEffectiveSortList(sort, { defaultSort, idField: 'id' })
|
|
119
|
+
const descriptors = []
|
|
120
|
+
|
|
121
|
+
for (const sortEntry of effectiveSort) {
|
|
122
|
+
const { field, direction, sqlDirection } = parseSortEntry(sortEntry)
|
|
123
|
+
|
|
124
|
+
if (field !== 'id' && sortableFields?.length > 0 && !sortableFields.includes(field)) {
|
|
125
|
+
log.warn(`Ignoring non-sortable field: ${field}`)
|
|
126
|
+
continue
|
|
107
127
|
}
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
if (
|
|
111
|
-
|
|
128
|
+
|
|
129
|
+
const queryFieldRuntime = queryFieldRuntimeByField.get(field)
|
|
130
|
+
if (queryFieldRuntime) {
|
|
131
|
+
applyQueryFieldOrder(query, queryFieldRuntime, sqlDirection)
|
|
132
|
+
descriptors.push({
|
|
133
|
+
field,
|
|
134
|
+
direction: sqlDirection,
|
|
135
|
+
queryFieldRuntime,
|
|
136
|
+
definition: queryFieldRuntime.definition,
|
|
137
|
+
isRelationship: false
|
|
138
|
+
})
|
|
139
|
+
continue
|
|
112
140
|
}
|
|
113
|
-
|
|
114
|
-
|
|
141
|
+
|
|
142
|
+
let dbField = field
|
|
143
|
+
const searchField = schemaInfo.searchSchemaStructure?.[field]
|
|
144
|
+
if (searchField?.actualField) {
|
|
145
|
+
dbField = searchField.actualField
|
|
115
146
|
}
|
|
116
|
-
|
|
147
|
+
|
|
148
|
+
const translatedField = storageAdapter?.translateColumn
|
|
149
|
+
? storageAdapter.translateColumn(dbField)
|
|
150
|
+
: dbField
|
|
151
|
+
|
|
152
|
+
query.orderBy(translatedField, direction)
|
|
153
|
+
descriptors.push({
|
|
154
|
+
field,
|
|
155
|
+
direction: sqlDirection,
|
|
156
|
+
column: translatedField,
|
|
157
|
+
definition: schemaInfo.schemaStructure?.[field] || { type: field === 'id' ? 'id' : undefined },
|
|
158
|
+
isRelationship: Boolean(searchField?.isRelationship)
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (descriptors.length === 0) {
|
|
163
|
+
query.orderBy('id', 'asc')
|
|
164
|
+
descriptors.push({
|
|
165
|
+
field: 'id',
|
|
166
|
+
direction: 'ASC',
|
|
167
|
+
column: 'id',
|
|
168
|
+
definition: { type: 'id' },
|
|
169
|
+
isRelationship: false
|
|
170
|
+
})
|
|
117
171
|
}
|
|
172
|
+
|
|
173
|
+
return descriptors
|
|
118
174
|
}
|
|
119
175
|
|
|
120
176
|
// Check database capabilities
|
|
@@ -170,21 +226,45 @@ export const RestApiKnexPlugin = {
|
|
|
170
226
|
|
|
171
227
|
// Helper scope method to get all schema-related information
|
|
172
228
|
addScopeMethod('createKnexTable', async ({ vars, scope, scopeName, scopeOptions, runHooks }) => {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
229
|
+
const tableSchemaInstance = buildScopeTableSchema(vars)
|
|
230
|
+
await createKnexTable(api.knex.instance, vars.schemaInfo, tableSchemaInstance, scopeOptions)
|
|
231
|
+
})
|
|
176
232
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
233
|
+
addScopeMethod('introspectKnexTableSnapshot', async ({ vars }) => {
|
|
234
|
+
return introspectKnexTableSnapshot(api.knex.instance, {
|
|
235
|
+
tableName: vars.schemaInfo.tableName,
|
|
236
|
+
idColumn: vars.schemaInfo.idProperty
|
|
182
237
|
})
|
|
238
|
+
})
|
|
183
239
|
|
|
184
|
-
|
|
185
|
-
|
|
240
|
+
addScopeMethod('generateKnexMigration', async ({ vars, scopeOptions }) => {
|
|
241
|
+
return generateKnexMigration(
|
|
242
|
+
vars.schemaInfo.tableName,
|
|
243
|
+
buildScopeTableSchema(vars),
|
|
244
|
+
{
|
|
245
|
+
...scopeOptions,
|
|
246
|
+
idProperty: vars.schemaInfo.idProperty,
|
|
247
|
+
dialect: api.knex.instance?.client?.config?.client
|
|
248
|
+
}
|
|
249
|
+
)
|
|
250
|
+
})
|
|
186
251
|
|
|
187
|
-
|
|
252
|
+
addScopeMethod('generateKnexMigrationDiff', async ({ vars, params }) => {
|
|
253
|
+
const snapshot = await introspectKnexTableSnapshot(api.knex.instance, {
|
|
254
|
+
tableName: vars.schemaInfo.tableName,
|
|
255
|
+
idColumn: vars.schemaInfo.idProperty
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
return generateKnexMigrationDiff(
|
|
259
|
+
vars.schemaInfo.tableName,
|
|
260
|
+
snapshot,
|
|
261
|
+
buildScopeTableSchema(vars),
|
|
262
|
+
{
|
|
263
|
+
...(params?.options || {}),
|
|
264
|
+
idProperty: vars.schemaInfo.idProperty,
|
|
265
|
+
dialect: snapshot.dialect || api.knex.instance?.client?.config?.client
|
|
266
|
+
}
|
|
267
|
+
)
|
|
188
268
|
})
|
|
189
269
|
|
|
190
270
|
// Helper scope method to alter existing fields in a table
|
|
@@ -198,7 +278,7 @@ export const RestApiKnexPlugin = {
|
|
|
198
278
|
api.knex.instance,
|
|
199
279
|
vars.schemaInfo.tableName,
|
|
200
280
|
params.fields,
|
|
201
|
-
params.options
|
|
281
|
+
{ ...params.options, idProperty: vars.schemaInfo.idProperty }
|
|
202
282
|
)
|
|
203
283
|
})
|
|
204
284
|
|
|
@@ -207,7 +287,12 @@ export const RestApiKnexPlugin = {
|
|
|
207
287
|
// Create schema object from filtered fields
|
|
208
288
|
const partialTableSchema = createSchema(params.fields)
|
|
209
289
|
|
|
210
|
-
await addKnexFields(
|
|
290
|
+
await addKnexFields(
|
|
291
|
+
api.knex.instance,
|
|
292
|
+
vars.schemaInfo.tableName,
|
|
293
|
+
partialTableSchema,
|
|
294
|
+
{ idProperty: vars.schemaInfo.idProperty }
|
|
295
|
+
)
|
|
211
296
|
})
|
|
212
297
|
|
|
213
298
|
helpers.newTransaction = async () => {
|
|
@@ -317,15 +402,18 @@ export const RestApiKnexPlugin = {
|
|
|
317
402
|
// Permission checks will handle access control
|
|
318
403
|
let query = db(tableName).where(idProperty, id)
|
|
319
404
|
|
|
320
|
-
|
|
321
|
-
const selectTranslator = createSelectTranslator(storageAdapter)
|
|
322
|
-
query = buildQuerySelection(
|
|
405
|
+
const selectionState = await applyFieldSelectionToQuery({
|
|
323
406
|
query,
|
|
407
|
+
scope,
|
|
408
|
+
fieldSelectionInfo,
|
|
324
409
|
tableName,
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
410
|
+
useTablePrefix: false,
|
|
411
|
+
storageAdapter,
|
|
412
|
+
db,
|
|
413
|
+
context,
|
|
414
|
+
scopeName
|
|
415
|
+
})
|
|
416
|
+
query = selectionState.query
|
|
329
417
|
|
|
330
418
|
const record = await query.first()
|
|
331
419
|
|
|
@@ -342,7 +430,7 @@ export const RestApiKnexPlugin = {
|
|
|
342
430
|
|
|
343
431
|
// Load relationship identifiers for all hasMany relationships
|
|
344
432
|
const records = [record] // Wrap in array for processing
|
|
345
|
-
await loadRelationshipIdentifiers(records, scopeName, scopes, db)
|
|
433
|
+
await loadRelationshipIdentifiers(records, scopeName, scopes, db, context)
|
|
346
434
|
|
|
347
435
|
// Process includes
|
|
348
436
|
const included = await processIncludes(scope, records, {
|
|
@@ -369,7 +457,7 @@ export const RestApiKnexPlugin = {
|
|
|
369
457
|
* @param {Object} params.context.db - Database connection (knex instance or transaction)
|
|
370
458
|
* @returns {Promise<Object|null>} JSON:API formatted resource with belongsTo relationships, or null if not found
|
|
371
459
|
*/
|
|
372
|
-
helpers.dataGetMinimal = async ({ scopeName, context }) => {
|
|
460
|
+
helpers.dataGetMinimal = async ({ scopeName, context, runHooks, applyQueryFilters }) => {
|
|
373
461
|
const storageAdapter = getScopeStorageAdapter(scopeName)
|
|
374
462
|
if (storageAdapter) {
|
|
375
463
|
context.storageAdapter = storageAdapter
|
|
@@ -382,8 +470,9 @@ export const RestApiKnexPlugin = {
|
|
|
382
470
|
|
|
383
471
|
log.debug(`[Knex] GET_MINIMAL ${tableName}/${id}`)
|
|
384
472
|
|
|
385
|
-
// Build query
|
|
386
|
-
//
|
|
473
|
+
// Build query.
|
|
474
|
+
// Single-record lookups still go through knexQueryFiltering when hooks are available,
|
|
475
|
+
// so resource scoping plugins can treat GET/PUT/PATCH/DELETE the same way as collections.
|
|
387
476
|
let query = db(tableName).where(idProperty, id)
|
|
388
477
|
|
|
389
478
|
// Add alias if idProperty is not 'id'
|
|
@@ -391,6 +480,35 @@ export const RestApiKnexPlugin = {
|
|
|
391
480
|
query = query.select('*', `${idProperty} as id`)
|
|
392
481
|
}
|
|
393
482
|
|
|
483
|
+
if (typeof applyQueryFilters === 'function') {
|
|
484
|
+
const scopedQueryState = await applyQueryFilters({
|
|
485
|
+
query,
|
|
486
|
+
filters: context.queryParams?.filters,
|
|
487
|
+
tableName,
|
|
488
|
+
db,
|
|
489
|
+
scopeName,
|
|
490
|
+
storageAdapter
|
|
491
|
+
})
|
|
492
|
+
query = unwrapQueryBuilderState(scopedQueryState, query)
|
|
493
|
+
} else if (runHooks) {
|
|
494
|
+
context.knexQuery = {
|
|
495
|
+
query,
|
|
496
|
+
filters: context.queryParams?.filters,
|
|
497
|
+
schemaInfo: context.schemaInfo,
|
|
498
|
+
scopeName,
|
|
499
|
+
tableName,
|
|
500
|
+
db,
|
|
501
|
+
adapter: storageAdapter,
|
|
502
|
+
storageAdapter,
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
await runHooks('knexQueryFiltering')
|
|
506
|
+
|
|
507
|
+
if (context.knexQuery) {
|
|
508
|
+
delete context.knexQuery
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
394
512
|
// Execute query
|
|
395
513
|
const record = await query.first()
|
|
396
514
|
|
|
@@ -461,14 +579,19 @@ export const RestApiKnexPlugin = {
|
|
|
461
579
|
// Start building query with table prefix (for JOIN support)
|
|
462
580
|
let query = db(tableName)
|
|
463
581
|
|
|
464
|
-
const
|
|
465
|
-
query = buildQuerySelection(
|
|
582
|
+
const selectionState = await applyFieldSelectionToQuery({
|
|
466
583
|
query,
|
|
584
|
+
scope,
|
|
585
|
+
fieldSelectionInfo,
|
|
467
586
|
tableName,
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
587
|
+
useTablePrefix: true,
|
|
588
|
+
storageAdapter,
|
|
589
|
+
db,
|
|
590
|
+
context,
|
|
591
|
+
scopeName
|
|
592
|
+
})
|
|
593
|
+
query = selectionState.query
|
|
594
|
+
const queryFieldRuntimeByField = selectionState.queryFieldRuntimeByField
|
|
472
595
|
|
|
473
596
|
/* ═══════════════════════════════════════════════════════════════════
|
|
474
597
|
* FILTERING HOOKS
|
|
@@ -513,33 +636,15 @@ export const RestApiKnexPlugin = {
|
|
|
513
636
|
|
|
514
637
|
log.trace('[DATA-QUERY] Finished knexQueryFiltering hook')
|
|
515
638
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
queryParams.sort
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
return // Skip non-sortable fields
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// Map relationship names to actual database columns for sorting
|
|
529
|
-
// Check if this is a relationship field that needs to be mapped
|
|
530
|
-
let dbField = field
|
|
531
|
-
const searchField = schemaInfo.searchSchemaStructure?.[field]
|
|
532
|
-
if (searchField?.actualField) {
|
|
533
|
-
// This is a relationship field, use the actual database column
|
|
534
|
-
dbField = searchField.actualField
|
|
535
|
-
}
|
|
536
|
-
const translatedField = storageAdapter?.translateColumn
|
|
537
|
-
? storageAdapter.translateColumn(dbField)
|
|
538
|
-
: dbField
|
|
539
|
-
|
|
540
|
-
query.orderBy(translatedField, desc ? 'desc' : 'asc')
|
|
541
|
-
})
|
|
542
|
-
}
|
|
639
|
+
const sortDescriptors = buildLegacySortDescriptors({
|
|
640
|
+
query,
|
|
641
|
+
sort: queryParams.sort,
|
|
642
|
+
schemaInfo,
|
|
643
|
+
sortableFields,
|
|
644
|
+
defaultSort: scope.vars.defaultSort,
|
|
645
|
+
storageAdapter,
|
|
646
|
+
queryFieldRuntimeByField
|
|
647
|
+
})
|
|
543
648
|
|
|
544
649
|
// Apply pagination
|
|
545
650
|
// Check if page object has any actual pagination parameters
|
|
@@ -583,9 +688,6 @@ export const RestApiKnexPlugin = {
|
|
|
583
688
|
// Fetch one extra record to determine if there are more
|
|
584
689
|
query.limit(pageSize + 1)
|
|
585
690
|
|
|
586
|
-
// Get the configured ID property
|
|
587
|
-
const idProperty = context.schemaInfo.idProperty || 'id'
|
|
588
|
-
|
|
589
691
|
if (queryParams.page.after) {
|
|
590
692
|
let cursorData
|
|
591
693
|
try {
|
|
@@ -604,56 +706,16 @@ export const RestApiKnexPlugin = {
|
|
|
604
706
|
)
|
|
605
707
|
}
|
|
606
708
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
field: scope.vars.defaultSort.field || idProperty,
|
|
618
|
-
direction: scope.vars.defaultSort.direction || 'ASC'
|
|
619
|
-
})
|
|
620
|
-
} else {
|
|
621
|
-
sortFields.push({ field: idProperty, direction: 'ASC' })
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
// Build compound WHERE clause for multi-field cursor
|
|
625
|
-
query.where(function () {
|
|
626
|
-
sortFields.forEach((sortInfo, index) => {
|
|
627
|
-
const { field, direction } = sortInfo
|
|
628
|
-
const qualifiedField = `${tableName}.${field}`
|
|
629
|
-
const cursorValue = cursorData[field]
|
|
630
|
-
|
|
631
|
-
if (cursorValue === undefined) {
|
|
632
|
-
log.warn(`Cursor missing value for sort field: ${field}`)
|
|
633
|
-
return
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// Build condition for this level and all previous levels
|
|
637
|
-
this.orWhere(function () {
|
|
638
|
-
// All previous fields must be equal
|
|
639
|
-
for (let i = 0; i < index; i++) {
|
|
640
|
-
const prevField = sortFields[i].field
|
|
641
|
-
const prevQualifiedField = `${tableName}.${prevField}`
|
|
642
|
-
const prevValue = cursorData[prevField]
|
|
643
|
-
if (prevValue !== undefined) {
|
|
644
|
-
this.where(prevQualifiedField, '=', prevValue)
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
// Current field must be greater/less than cursor value
|
|
649
|
-
if (direction === 'DESC') {
|
|
650
|
-
this.where(qualifiedField, '<', cursorValue)
|
|
651
|
-
} else {
|
|
652
|
-
this.where(qualifiedField, '>', cursorValue)
|
|
653
|
-
}
|
|
654
|
-
})
|
|
655
|
-
})
|
|
656
|
-
})
|
|
709
|
+
applyCursorPredicate(
|
|
710
|
+
query,
|
|
711
|
+
sortDescriptors,
|
|
712
|
+
cursorData,
|
|
713
|
+
(direction) => (direction === 'DESC' ? '<' : '>'),
|
|
714
|
+
(builder, descriptor, operator, value) => builder.where(descriptor.column, operator, value),
|
|
715
|
+
{
|
|
716
|
+
onMissingValue: (fieldName) => log.warn(`Cursor missing value for sort field: ${fieldName}`)
|
|
717
|
+
}
|
|
718
|
+
)
|
|
657
719
|
} else if (queryParams.page.before) {
|
|
658
720
|
let cursorData
|
|
659
721
|
try {
|
|
@@ -672,56 +734,16 @@ export const RestApiKnexPlugin = {
|
|
|
672
734
|
)
|
|
673
735
|
}
|
|
674
736
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
field: scope.vars.defaultSort.field || idProperty,
|
|
686
|
-
direction: scope.vars.defaultSort.direction || 'ASC'
|
|
687
|
-
})
|
|
688
|
-
} else {
|
|
689
|
-
sortFields.push({ field: idProperty, direction: 'ASC' })
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
// Build compound WHERE clause for multi-field cursor (reversed for before)
|
|
693
|
-
query.where(function () {
|
|
694
|
-
sortFields.forEach((sortInfo, index) => {
|
|
695
|
-
const { field, direction } = sortInfo
|
|
696
|
-
const qualifiedField = `${tableName}.${field}`
|
|
697
|
-
const cursorValue = cursorData[field]
|
|
698
|
-
|
|
699
|
-
if (cursorValue === undefined) {
|
|
700
|
-
log.warn(`Cursor missing value for sort field: ${field}`)
|
|
701
|
-
return
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
// Build condition for this level and all previous levels
|
|
705
|
-
this.orWhere(function () {
|
|
706
|
-
// All previous fields must be equal
|
|
707
|
-
for (let i = 0; i < index; i++) {
|
|
708
|
-
const prevField = sortFields[i].field
|
|
709
|
-
const prevQualifiedField = `${tableName}.${prevField}`
|
|
710
|
-
const prevValue = cursorData[prevField]
|
|
711
|
-
if (prevValue !== undefined) {
|
|
712
|
-
this.where(prevQualifiedField, '=', prevValue)
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
// Current field must be less/greater than cursor value (reversed)
|
|
717
|
-
if (direction === 'DESC') {
|
|
718
|
-
this.where(qualifiedField, '>', cursorValue)
|
|
719
|
-
} else {
|
|
720
|
-
this.where(qualifiedField, '<', cursorValue)
|
|
721
|
-
}
|
|
722
|
-
})
|
|
723
|
-
})
|
|
724
|
-
})
|
|
737
|
+
applyCursorPredicate(
|
|
738
|
+
query,
|
|
739
|
+
sortDescriptors,
|
|
740
|
+
cursorData,
|
|
741
|
+
(direction) => (direction === 'DESC' ? '>' : '<'),
|
|
742
|
+
(builder, descriptor, operator, value) => builder.where(descriptor.column, operator, value),
|
|
743
|
+
{
|
|
744
|
+
onMissingValue: (fieldName) => log.warn(`Cursor missing value for sort field: ${fieldName}`)
|
|
745
|
+
}
|
|
746
|
+
)
|
|
725
747
|
}
|
|
726
748
|
}
|
|
727
749
|
// Default pagination if only size is specified - treat as cursor-based for "load more"
|
|
@@ -844,12 +866,7 @@ export const RestApiKnexPlugin = {
|
|
|
844
866
|
records.pop()
|
|
845
867
|
}
|
|
846
868
|
|
|
847
|
-
|
|
848
|
-
const idProperty = context.schemaInfo.idProperty || 'id'
|
|
849
|
-
let sortFields = [idProperty]
|
|
850
|
-
if (queryParams.sort && queryParams.sort.length > 0) {
|
|
851
|
-
sortFields = queryParams.sort.map(s => s.startsWith('-') ? s.substring(1) : s)
|
|
852
|
-
}
|
|
869
|
+
const sortFields = sortDescriptors.map((descriptor) => descriptor.field)
|
|
853
870
|
|
|
854
871
|
context.returnMeta.paginationMeta = buildCursorMeta(records, pageSize, hasMore, sortFields)
|
|
855
872
|
const urlPrefix = getUrlPrefix(context, scope)
|
|
@@ -865,7 +882,7 @@ export const RestApiKnexPlugin = {
|
|
|
865
882
|
}
|
|
866
883
|
|
|
867
884
|
// Load relationship identifiers for all hasMany relationships
|
|
868
|
-
await loadRelationshipIdentifiers(records, scopeName, scopes, db)
|
|
885
|
+
await loadRelationshipIdentifiers(records, scopeName, scopes, db, context)
|
|
869
886
|
|
|
870
887
|
// Process includes
|
|
871
888
|
const included = await processIncludes(scope, records, {
|
|
@@ -909,11 +926,22 @@ export const RestApiKnexPlugin = {
|
|
|
909
926
|
|
|
910
927
|
log.debug(`[Knex] POST ${tableName}`, inputRecord)
|
|
911
928
|
|
|
912
|
-
// Extract attributes from JSON:API format
|
|
913
|
-
|
|
929
|
+
// Extract attributes from JSON:API format.
|
|
930
|
+
// POST may legitimately provide a resource id when the table uses a custom primary key
|
|
931
|
+
// (for example, one-to-one rows keyed by user_id/workspace_id). Keep that id in the
|
|
932
|
+
// attribute bag so storage translation can write it to the configured id column.
|
|
933
|
+
const attributes = {
|
|
934
|
+
...(inputRecord.data.attributes || {}),
|
|
935
|
+
...(inputRecord.data.id !== undefined ? { id: inputRecord.data.id } : {})
|
|
936
|
+
}
|
|
914
937
|
|
|
915
938
|
// Strip non-database fields (computed and virtual) before insert
|
|
916
|
-
const dbAttributes =
|
|
939
|
+
const dbAttributes = storageAdapter?.toStorageRow
|
|
940
|
+
? storageAdapter.toStorageRow(attributes, {
|
|
941
|
+
context,
|
|
942
|
+
operation: 'post'
|
|
943
|
+
})
|
|
944
|
+
: attributes
|
|
917
945
|
|
|
918
946
|
// Insert and get the new ID
|
|
919
947
|
const result = await db(tableName).insert(dbAttributes).returning(idProperty)
|
|
@@ -964,7 +992,12 @@ export const RestApiKnexPlugin = {
|
|
|
964
992
|
const mergedAttributes = { ...attributes, ...foreignKeyUpdates }
|
|
965
993
|
|
|
966
994
|
// Strip non-database fields (computed and virtual) before database operation
|
|
967
|
-
const finalAttributes =
|
|
995
|
+
const finalAttributes = storageAdapter?.toStorageRow
|
|
996
|
+
? storageAdapter.toStorageRow(mergedAttributes, {
|
|
997
|
+
context,
|
|
998
|
+
operation: 'put'
|
|
999
|
+
})
|
|
1000
|
+
: mergedAttributes
|
|
968
1001
|
|
|
969
1002
|
// Map 'id' to actual idProperty if needed (for PUT with specific ID)
|
|
970
1003
|
if (idProperty !== 'id' && inputRecord.data.id) {
|
|
@@ -1063,7 +1096,12 @@ export const RestApiKnexPlugin = {
|
|
|
1063
1096
|
const mergedAttributes = { ...attributes, ...foreignKeyUpdates }
|
|
1064
1097
|
|
|
1065
1098
|
// Strip non-database fields (computed and virtual) before database operation
|
|
1066
|
-
const finalAttributes =
|
|
1099
|
+
const finalAttributes = storageAdapter?.toStorageRow
|
|
1100
|
+
? storageAdapter.toStorageRow(mergedAttributes, {
|
|
1101
|
+
context,
|
|
1102
|
+
operation: 'patch'
|
|
1103
|
+
})
|
|
1104
|
+
: mergedAttributes
|
|
1067
1105
|
|
|
1068
1106
|
log.debug('[Knex] PATCH finalAttributes:', finalAttributes)
|
|
1069
1107
|
|
|
@@ -36,35 +36,40 @@ export default async function registerRelationshipRoutes ({ context, api, log })
|
|
|
36
36
|
await api.addRoute({
|
|
37
37
|
method: 'GET',
|
|
38
38
|
path: `${scopePath}/:id/relationships/:relationshipName`,
|
|
39
|
-
handler: createRouteHandler('getRelationship')
|
|
39
|
+
handler: createRouteHandler('getRelationship'),
|
|
40
|
+
routeMeta: { kind: 'relationship', scopeName, operation: 'getRelationship' }
|
|
40
41
|
})
|
|
41
42
|
|
|
42
43
|
// GET /api/{scope}/{id}/{relationshipName}
|
|
43
44
|
await api.addRoute({
|
|
44
45
|
method: 'GET',
|
|
45
46
|
path: `${scopePath}/:id/:relationshipName`,
|
|
46
|
-
handler: createRouteHandler('getRelated')
|
|
47
|
+
handler: createRouteHandler('getRelated'),
|
|
48
|
+
routeMeta: { kind: 'relationship', scopeName, operation: 'getRelated' }
|
|
47
49
|
})
|
|
48
50
|
|
|
49
51
|
// POST /api/{scope}/{id}/relationships/{relationshipName}
|
|
50
52
|
await api.addRoute({
|
|
51
53
|
method: 'POST',
|
|
52
54
|
path: `${scopePath}/:id/relationships/:relationshipName`,
|
|
53
|
-
handler: createRouteHandler('postRelationship')
|
|
55
|
+
handler: createRouteHandler('postRelationship'),
|
|
56
|
+
routeMeta: { kind: 'relationship', scopeName, operation: 'postRelationship' }
|
|
54
57
|
})
|
|
55
58
|
|
|
56
59
|
// PATCH /api/{scope}/{id}/relationships/{relationshipName}
|
|
57
60
|
await api.addRoute({
|
|
58
61
|
method: 'PATCH',
|
|
59
62
|
path: `${scopePath}/:id/relationships/:relationshipName`,
|
|
60
|
-
handler: createRouteHandler('patchRelationship')
|
|
63
|
+
handler: createRouteHandler('patchRelationship'),
|
|
64
|
+
routeMeta: { kind: 'relationship', scopeName, operation: 'patchRelationship' }
|
|
61
65
|
})
|
|
62
66
|
|
|
63
67
|
// DELETE /api/{scope}/{id}/relationships/{relationshipName}
|
|
64
68
|
await api.addRoute({
|
|
65
69
|
method: 'DELETE',
|
|
66
70
|
path: `${scopePath}/:id/relationships/:relationshipName`,
|
|
67
|
-
handler: createRouteHandler('deleteRelationship')
|
|
71
|
+
handler: createRouteHandler('deleteRelationship'),
|
|
72
|
+
routeMeta: { kind: 'relationship', scopeName, operation: 'deleteRelationship' }
|
|
68
73
|
})
|
|
69
74
|
|
|
70
75
|
log.trace(`Registered relationship routes for scope: ${scopeName}`)
|