json-rest-api 1.0.22 → 1.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1 -2
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
- package/README.md +22 -1
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
- package/docs/API.md +196 -38
- package/docs/COMPARISON.md +2 -2
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +111 -5
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
- package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
- package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +370 -0
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
- package/docs/GUIDE/index.md +18 -9
- package/docs/QUICKSTART.md +5 -1
- package/docs/index.md +1 -1
- package/index.js +3 -2
- package/lib/enhanced-logger.js +1 -1
- package/package.json +6 -2
- package/plugins/core/bulk-operations-plugin.js +26 -34
- package/plugins/core/connectors/express-plugin.js +97 -190
- package/plugins/core/connectors/fastify-plugin.js +206 -0
- package/plugins/core/connectors/lib/busboy-detector.js +4 -4
- package/plugins/core/connectors/lib/connector-core.js +195 -0
- package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
- package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
- package/plugins/core/lib/anyapi/schema-utils.js +43 -4
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +4 -1
- package/plugins/core/lib/dbIntrospection.js +1078 -0
- package/plugins/core/lib/dbTablesOperations.js +1111 -160
- package/plugins/core/lib/querying/knex-cross-table-search.js +0 -7
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -3
- package/plugins/core/lib/querying/knex-process-includes.js +3 -4
- package/plugins/core/lib/querying/knex-query-helpers.js +21 -76
- package/plugins/core/lib/querying/knex-relationship-includes.js +468 -215
- package/plugins/core/lib/querying/query-builder-utils.js +19 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
- package/plugins/core/lib/querying/sort-helpers.js +33 -0
- package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
- package/plugins/core/lib/querying/url-helpers.js +8 -2
- package/plugins/core/lib/querying-writing/compile-schemas.js +20 -5
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -14
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
- package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
- package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +131 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +0 -2
- package/plugins/core/lib/querying-writing/simplified-helpers.js +4 -11
- package/plugins/core/lib/storage/canonical-storage-mapping.js +133 -0
- package/plugins/core/lib/storage/storage-adapter.js +82 -4
- package/plugins/core/lib/storage/storage-mapping.js +274 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +4 -2
- package/plugins/core/rest-api-anyapi-knex-plugin.js +531 -312
- package/plugins/core/rest-api-autofilter-plugin.js +414 -0
- package/plugins/core/rest-api-knex-plugin.js +271 -226
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +1 -0
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
- package/plugins/core/rest-api-plugin-methods/common.js +148 -92
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +11 -2
- package/plugins/core/rest-api-plugin-methods/delete.js +6 -1
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
- package/plugins/core/rest-api-plugin-methods/get-related.js +7 -2
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +7 -2
- package/plugins/core/rest-api-plugin-methods/get.js +29 -14
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +10 -3
- package/plugins/core/rest-api-plugin-methods/patch.js +56 -12
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +11 -2
- package/plugins/core/rest-api-plugin-methods/post.js +21 -8
- package/plugins/core/rest-api-plugin-methods/put.js +73 -21
- package/plugins/core/rest-api-plugin-methods/query.js +15 -53
- package/plugins/core/rest-api-plugin-methods/release.js +0 -2
- package/plugins/core/rest-api-plugin.js +6 -0
- package/plugins/core/rest-api-positioning-plugin.js +2 -5
- package/plugins/core/rest-api-query-projections-plugin.js +130 -0
- package/plugins/core/socketio-plugin.js +1 -4
- package/plugins/storage/local-storage.js +1 -1
- package/plugins/storage/s3-storage.js +5 -2
- package/quickTest.js +1 -0
- package/scripts/run-docs.js +80 -84
- package/test-debug.js +1 -1
- package/tests/README.md +7 -0
- package/tests/anyapi-basic.test.js +3 -0
- package/tests/autofilter.test.js +463 -0
- package/tests/custom-id-mysql-returning-fallback.test.js +128 -0
- package/tests/custom-id-simplified-post.test.js +75 -0
- package/tests/custom-idproperty-relationships.test.js +33 -0
- package/tests/db-introspection.test.js +521 -0
- package/tests/db-table-operations.test.js +786 -0
- package/tests/fastify-plugin.test.js +156 -0
- package/tests/field-getters.test.js +1 -2
- package/tests/field-setters.test.js +0 -4
- package/tests/fixtures/api-configs.js +278 -176
- package/tests/helpers/fake-fastify.js +99 -0
- package/tests/http-connectors-parity.test.js +235 -0
- package/tests/id-normalization.test.js +444 -0
- package/tests/logical-id-write-validation.test.js +151 -0
- package/tests/pagination-cursor-multifield.test.js +11 -11
- package/tests/projected-fields.test.js +319 -0
- package/tests/relationships.test.js +3 -3
- package/tests/searchschema-knex-behavior.test.js +187 -0
- package/tests/searchschema-merge.test.js +21 -69
- package/tests/storage-mapping.test.js +259 -0
- package/audit/00-charter.md +0 -103
- package/audit/01-scope-map.md +0 -84
- package/audit/02-coverage-matrix.csv +0 -174
- package/audit/03-invariants.md +0 -123
- package/audit/04-standards-rubric.md +0 -56
- package/audit/05-competitor-rubric.md +0 -77
- package/audit/06-findings.md +0 -160
- package/audit/07-risk-register.csv +0 -10
- package/audit/08-remediation-roadmap.md +0 -177
- package/audit/09-signoff-check.md +0 -23
- package/audit/10-final-synthesis.md +0 -29
- package/audit/evidence/code/all-files-json-rest-api.txt +0 -168
- package/audit/evidence/code/all-files-json-rest-schema.txt +0 -12
- package/audit/evidence/code/boundary-leak-checks.txt +0 -72
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__bulk-operations-plugin.js.txt +0 -480
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__express-plugin.js.txt +0 -532
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__busboy-detector.js.txt +0 -175
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__formidable-detector.js.txt +0 -138
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__request-helpers.js.txt +0 -222
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt +0 -342
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__dbTablesOperations.js.txt +0 -304
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__fractional-positioning.js.txt +0 -188
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__compile-schemas.js.txt +0 -336
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__connectors-query-parser.js.txt +0 -165
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-capabilities.js.txt +0 -265
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-value-normalizers.js.txt +0 -369
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__default-data-helpers.js.txt +0 -72
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__field-utils.js.txt +0 -139
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-constants.js.txt +0 -45
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-field-helpers.js.txt +0 -396
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-json-api-transformers.js.txt +0 -162
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__payload-validators.js.txt +0 -1314
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__schema-helpers.js.txt +0 -488
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__scope-validations.js.txt +0 -484
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__simplified-helpers.js.txt +0 -857
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-cross-table-search.js.txt +0 -565
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-json-api-transformers-querying.js.txt +0 -485
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-pagination-helpers.js.txt +0 -560
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-process-includes.js.txt +0 -167
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers-base.js.txt +0 -109
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers.js.txt +0 -1064
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-relationship-includes.js.txt +0 -1983
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-window-queries.js.txt +0 -328
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__url-helpers.js.txt +0 -119
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__storage__storage-adapter.js.txt +0 -160
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__many-to-many-manipulations.js.txt +0 -300
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__relationship-processor.js.txt +0 -245
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-access.js.txt +0 -459
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-cors-plugin.js.txt +0 -237
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-knex-plugin.js.txt +0 -1135
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-multihome-plugin.js.txt +0 -314
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__compile-resource-schemas.js.txt +0 -7
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-relationship-routes.js.txt +0 -71
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-scope-routes.js.txt +0 -102
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__turn-scope-init-into-vars.js.txt +0 -49
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__validate-include-configurations.js.txt +0 -39
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__add-route.js.txt +0 -23
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__check-permissions.js.txt +0 -13
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__common.js.txt +0 -639
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete-relationship.js.txt +0 -117
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete.js.txt +0 -104
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__enrich-attributes.js.txt +0 -199
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-related.js.txt +0 -353
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-relationship.js.txt +0 -61
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get.js.txt +0 -184
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch-relationship.js.txt +0 -55
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch.js.txt +0 -191
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post-relationship.js.txt +0 -110
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post.js.txt +0 -168
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__put.js.txt +0 -253
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__query.js.txt +0 -207
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__release.js.txt +0 -12
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin.js.txt +0 -165
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-positioning-plugin.js.txt +0 -668
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__socketio-plugin.js.txt +0 -621
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__local-storage.js.txt +0 -218
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__s3-storage.js.txt +0 -124
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__CorePlugin.js.txt +0 -269
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__Schema.js.txt +0 -242
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__index.js.txt +0 -77
- package/audit/evidence/code/critical-pass1-risk-keywords.txt +0 -238
- package/audit/evidence/code/critical-pass1-summary.csv +0 -67
- package/audit/evidence/code/critical-pass2-summary.csv +0 -67
- package/audit/evidence/code/early-silent-zones-no-test-token.txt +0 -57
- package/audit/evidence/code/error-model-signals.txt +0 -98
- package/audit/evidence/code/git-files-json-rest-api.txt +0 -171
- package/audit/evidence/code/git-files-json-rest-schema.txt +0 -27
- package/audit/evidence/code/hook-flow-signals.txt +0 -282
- package/audit/evidence/code/idproperty-signals.txt +0 -157
- package/audit/evidence/code/import-cycles-non-anyapi.txt +0 -3
- package/audit/evidence/code/import-hotspots-non-anyapi.txt +0 -96
- package/audit/evidence/code/imports-json-rest-api.txt +0 -354
- package/audit/evidence/code/imports-json-rest-schema.txt +0 -15
- package/audit/evidence/code/in-scope-files-json-rest-api.txt +0 -160
- package/audit/evidence/code/in-scope-files-json-rest-schema.txt +0 -13
- package/audit/evidence/code/non-anyapi-core-files.txt +0 -101
- package/audit/evidence/code/permission-signals.txt +0 -193
- package/audit/evidence/code/query-include-signals.txt +0 -687
- package/audit/evidence/code/transaction-signals.txt +0 -28
- package/audit/evidence/code/validation-signals.txt +0 -650
- package/audit/evidence/code/write-path-signals.txt +0 -61
- package/audit/evidence/docs/docs-claims-signals.txt +0 -1075
- package/audit/evidence/docs/schema-docs-claims-signals.txt +0 -179
- package/audit/evidence/external/corpus-freeze.md +0 -21
- package/audit/evidence/external/npm-competitor-metadata.txt +0 -70
- package/audit/evidence/external/npm-download-signals.txt +0 -23
- package/audit/evidence/external/npm-jsonapi-server.txt +0 -8
- package/audit/evidence/external/npm-modern-jsonapi-metadata.txt +0 -38
- package/audit/evidence/external/readme-fortune-signals.txt +0 -26
- package/audit/evidence/external/readme-fortune.md +0 -165
- package/audit/evidence/external/readme-json-api-serializer-signals.txt +0 -80
- package/audit/evidence/external/readme-json-api-serializer.md +0 -690
- package/audit/evidence/external/readme-jsonapi-serde-server-signals.txt +0 -0
- package/audit/evidence/external/readme-jsonapi-serde-server.md +0 -22
- package/audit/evidence/external/readme-jsonapi-serializer-signals.txt +0 -63
- package/audit/evidence/external/readme-jsonapi-serializer.md +0 -315
- package/audit/evidence/external/readme-jsonapi-server-signals.txt +0 -19
- package/audit/evidence/external/readme-jsonapi-server.md +0 -104
- package/audit/evidence/external/readme-nestjs-json-api-signals.txt +0 -4
- package/audit/evidence/external/readme-nestjs-json-api.md +0 -94
- package/audit/evidence/external/readme-yaser-json-api-nestjs-signals.txt +0 -47
- package/audit/evidence/external/readme-yaser-json-api-nestjs.md +0 -447
- package/audit/evidence/external/standard-jsonapi-snippets.txt +0 -84
- package/audit/evidence/external/standard-rfc-snippets.txt +0 -168
- package/audit/evidence/external/standards-sources.txt +0 -6
- package/audit/evidence/runtime/baseline-commits.txt +0 -2
- package/audit/evidence/runtime/db-adapter-assumptions.txt +0 -3
- package/audit/evidence/runtime/environment.txt +0 -4
- package/audit/evidence/runtime/invariant-runtime-mapping.md +0 -38
- package/audit/evidence/runtime/probe-patch-relationship-error.exitcode +0 -1
- package/audit/evidence/runtime/probe-patch-relationship-error.log +0 -76
- package/audit/evidence/runtime/probe-patch-relationship-error.mjs +0 -67
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.exitcode +0 -1
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.log +0 -62
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.mjs +0 -47
- package/audit/evidence/runtime/test-exitcodes.txt +0 -4
- package/audit/evidence/runtime/test-summary.md +0 -107
- package/audit/evidence/runtime/trace-anchors.txt +0 -61
- package/audit/evidence/tests/access-multitenancy-test-signals.txt +0 -125
- package/audit/evidence/tests/failing-tests-summary.txt +0 -25
- package/audit/evidence/tests/npm-test-json-rest-api.exitcode +0 -1
- package/audit/evidence/tests/npm-test-json-rest-api.log +0 -5885
- package/audit/evidence/tests/npm-test-json-rest-schema.exitcode +0 -1
- package/audit/evidence/tests/npm-test-json-rest-schema.log +0 -356
- package/audit/evidence/tests/query-include-test-signals.txt +0 -948
- package/audit/evidence/tests/relationship-test-signals.txt +0 -223
- package/audit/evidence/tests/skipped-tests.txt +0 -10
- package/audit/evidence/tests/test-suite-list-non-anyapi.txt +0 -30
- package/audit/evidence/tests/test-suite-list.txt +0 -32
- package/audit/subsystems/docs-drift.md +0 -49
- package/audit/subsystems/pass2-cross-review.md +0 -131
- package/audit/subsystems/plugins-access.md +0 -51
- package/audit/subsystems/plugins-bulk.md +0 -50
- package/audit/subsystems/plugins-cors.md +0 -48
- package/audit/subsystems/plugins-file.md +0 -53
- package/audit/subsystems/plugins-multihome.md +0 -50
- package/audit/subsystems/plugins-positioning.md +0 -54
- package/audit/subsystems/plugins-socketio.md +0 -50
- package/audit/subsystems/query-engine.md +0 -61
- package/audit/subsystems/relationships.md +0 -55
- package/audit/subsystems/rest-orchestration.md +0 -66
- package/audit/subsystems/schema-core.md +0 -64
- package/audit/subsystems/storage-knex.md +0 -57
- package/audit/subsystems/tests.md +0 -48
- package/audit/subsystems/transport-express.md +0 -55
- package/audit/traces/trace-read-get.md +0 -22
- package/audit/traces/trace-read-query.md +0 -25
- package/audit/traces/trace-relationship-endpoints.md +0 -31
- package/audit/traces/trace-write-delete.md +0 -17
- package/audit/traces/trace-write-patch.md +0 -26
- package/audit/traces/trace-write-post.md +0 -22
- package/audit/traces/trace-write-put.md +0 -23
- package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
- package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
- package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
- package/plugins/core/rest-api-access.js +0 -459
- package/plugins/core/rest-api-multihome-plugin.js +0 -314
- package/tests/multihome.test.js +0 -553
- package/tests/multihome.test.js.bak +0 -550
- package/tests/ownership-optout.test.js +0 -76
- package/tests/rest-api-access.test.js +0 -206
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { requirePackage } from 'hooked-api'
|
|
2
2
|
import { createSchema } from 'json-rest-schema'
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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 } from './lib/querying-writing/knex-field-helpers.js'
|
|
12
|
+
import { buildJsonApiResponse } from './lib/querying/knex-json-api-transformers-querying.js'
|
|
8
13
|
import { processBelongsToRelationships } from './lib/writing/knex-json-api-transformers-writing.js'
|
|
9
14
|
import { toJsonApiRecordWithBelongsTo } from './lib/querying-writing/knex-json-api-transformers.js'
|
|
10
15
|
import { processIncludes } from './lib/querying/knex-process-includes.js'
|
|
@@ -26,30 +31,13 @@ import {
|
|
|
26
31
|
} from './lib/querying/knex-pagination-helpers.js'
|
|
27
32
|
import { getUrlPrefix } from './lib/querying/url-helpers.js'
|
|
28
33
|
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
|
-
}
|
|
34
|
+
import {
|
|
35
|
+
applyCursorPredicate,
|
|
36
|
+
applyQueryFieldOrder,
|
|
37
|
+
buildEffectiveSortList,
|
|
38
|
+
parseSortEntry
|
|
39
|
+
} from './lib/querying/query-field-sort-helpers.js'
|
|
40
|
+
import { unwrapQueryBuilderState } from './lib/querying/query-builder-utils.js'
|
|
53
41
|
|
|
54
42
|
export const RestApiKnexPlugin = {
|
|
55
43
|
name: 'rest-api-knex',
|
|
@@ -57,9 +45,8 @@ export const RestApiKnexPlugin = {
|
|
|
57
45
|
|
|
58
46
|
async install ({ helpers, vars, pluginOptions, api, log, scopes, addHook, addScopeMethod }) {
|
|
59
47
|
// Try to import knex dynamically
|
|
60
|
-
let knexLib
|
|
61
48
|
try {
|
|
62
|
-
|
|
49
|
+
await import('knex')
|
|
63
50
|
} catch (e) {
|
|
64
51
|
requirePackage('knex', 'rest-api-knex',
|
|
65
52
|
'Knex.js is required for database operations. This is a peer dependency that allows you to control the version.')
|
|
@@ -99,22 +86,90 @@ export const RestApiKnexPlugin = {
|
|
|
99
86
|
api.knex.helpers.getStorageAdapter = getScopeStorageAdapter
|
|
100
87
|
helpers.getStorageAdapter = getScopeStorageAdapter
|
|
101
88
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
89
|
+
const buildScopeTableSchema = (vars = {}) => {
|
|
90
|
+
const schemaStructure = vars.schemaInfo?.schemaStructure || {}
|
|
91
|
+
const filteredSchema = {}
|
|
92
|
+
|
|
93
|
+
Object.entries(schemaStructure).forEach(([fieldName, fieldDef]) => {
|
|
94
|
+
if (!fieldDef.virtual) {
|
|
95
|
+
filteredSchema[fieldName] = fieldDef
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
structure: filteredSchema,
|
|
101
|
+
storage: vars.schemaInfo?.storage,
|
|
102
|
+
indexes: vars.schemaInfo?.indexes || [],
|
|
103
|
+
foreignKeys: vars.schemaInfo?.foreignKeys || [],
|
|
104
|
+
checkConstraints: vars.schemaInfo?.checkConstraints || []
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const buildLegacySortDescriptors = ({
|
|
109
|
+
query,
|
|
110
|
+
sort,
|
|
111
|
+
schemaInfo,
|
|
112
|
+
sortableFields,
|
|
113
|
+
storageAdapter,
|
|
114
|
+
defaultSort,
|
|
115
|
+
queryFieldRuntimeByField = new Map()
|
|
116
|
+
}) => {
|
|
117
|
+
const effectiveSort = buildEffectiveSortList(sort, { defaultSort, idField: 'id' })
|
|
118
|
+
const descriptors = []
|
|
119
|
+
|
|
120
|
+
for (const sortEntry of effectiveSort) {
|
|
121
|
+
const { field, direction, sqlDirection } = parseSortEntry(sortEntry)
|
|
122
|
+
|
|
123
|
+
if (field !== 'id' && sortableFields?.length > 0 && !sortableFields.includes(field)) {
|
|
124
|
+
log.warn(`Ignoring non-sortable field: ${field}`)
|
|
125
|
+
continue
|
|
107
126
|
}
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
if (
|
|
111
|
-
|
|
127
|
+
|
|
128
|
+
const queryFieldRuntime = queryFieldRuntimeByField.get(field)
|
|
129
|
+
if (queryFieldRuntime) {
|
|
130
|
+
applyQueryFieldOrder(query, queryFieldRuntime, sqlDirection)
|
|
131
|
+
descriptors.push({
|
|
132
|
+
field,
|
|
133
|
+
direction: sqlDirection,
|
|
134
|
+
queryFieldRuntime,
|
|
135
|
+
definition: queryFieldRuntime.definition,
|
|
136
|
+
isRelationship: false
|
|
137
|
+
})
|
|
138
|
+
continue
|
|
112
139
|
}
|
|
113
|
-
|
|
114
|
-
|
|
140
|
+
|
|
141
|
+
let dbField = field
|
|
142
|
+
const searchField = schemaInfo.searchSchemaStructure?.[field]
|
|
143
|
+
if (searchField?.actualField) {
|
|
144
|
+
dbField = searchField.actualField
|
|
115
145
|
}
|
|
116
|
-
|
|
146
|
+
|
|
147
|
+
const translatedField = storageAdapter?.translateColumn
|
|
148
|
+
? storageAdapter.translateColumn(dbField)
|
|
149
|
+
: dbField
|
|
150
|
+
|
|
151
|
+
query.orderBy(translatedField, direction)
|
|
152
|
+
descriptors.push({
|
|
153
|
+
field,
|
|
154
|
+
direction: sqlDirection,
|
|
155
|
+
column: translatedField,
|
|
156
|
+
definition: schemaInfo.schemaStructure?.[field] || { type: field === 'id' ? 'id' : undefined },
|
|
157
|
+
isRelationship: Boolean(searchField?.isRelationship)
|
|
158
|
+
})
|
|
117
159
|
}
|
|
160
|
+
|
|
161
|
+
if (descriptors.length === 0) {
|
|
162
|
+
query.orderBy('id', 'asc')
|
|
163
|
+
descriptors.push({
|
|
164
|
+
field: 'id',
|
|
165
|
+
direction: 'ASC',
|
|
166
|
+
column: 'id',
|
|
167
|
+
definition: { type: 'id' },
|
|
168
|
+
isRelationship: false
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return descriptors
|
|
118
173
|
}
|
|
119
174
|
|
|
120
175
|
// Check database capabilities
|
|
@@ -170,21 +225,45 @@ export const RestApiKnexPlugin = {
|
|
|
170
225
|
|
|
171
226
|
// Helper scope method to get all schema-related information
|
|
172
227
|
addScopeMethod('createKnexTable', async ({ vars, scope, scopeName, scopeOptions, runHooks }) => {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
228
|
+
const tableSchemaInstance = buildScopeTableSchema(vars)
|
|
229
|
+
await createKnexTable(api.knex.instance, vars.schemaInfo, tableSchemaInstance, scopeOptions)
|
|
230
|
+
})
|
|
176
231
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
232
|
+
addScopeMethod('introspectKnexTableSnapshot', async ({ vars }) => {
|
|
233
|
+
return introspectKnexTableSnapshot(api.knex.instance, {
|
|
234
|
+
tableName: vars.schemaInfo.tableName,
|
|
235
|
+
idColumn: vars.schemaInfo.idProperty
|
|
182
236
|
})
|
|
237
|
+
})
|
|
183
238
|
|
|
184
|
-
|
|
185
|
-
|
|
239
|
+
addScopeMethod('generateKnexMigration', async ({ vars, scopeOptions }) => {
|
|
240
|
+
return generateKnexMigration(
|
|
241
|
+
vars.schemaInfo.tableName,
|
|
242
|
+
buildScopeTableSchema(vars),
|
|
243
|
+
{
|
|
244
|
+
...scopeOptions,
|
|
245
|
+
idProperty: vars.schemaInfo.idProperty,
|
|
246
|
+
dialect: api.knex.instance?.client?.config?.client
|
|
247
|
+
}
|
|
248
|
+
)
|
|
249
|
+
})
|
|
186
250
|
|
|
187
|
-
|
|
251
|
+
addScopeMethod('generateKnexMigrationDiff', async ({ vars, params }) => {
|
|
252
|
+
const snapshot = await introspectKnexTableSnapshot(api.knex.instance, {
|
|
253
|
+
tableName: vars.schemaInfo.tableName,
|
|
254
|
+
idColumn: vars.schemaInfo.idProperty
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
return generateKnexMigrationDiff(
|
|
258
|
+
vars.schemaInfo.tableName,
|
|
259
|
+
snapshot,
|
|
260
|
+
buildScopeTableSchema(vars),
|
|
261
|
+
{
|
|
262
|
+
...(params?.options || {}),
|
|
263
|
+
idProperty: vars.schemaInfo.idProperty,
|
|
264
|
+
dialect: snapshot.dialect || api.knex.instance?.client?.config?.client
|
|
265
|
+
}
|
|
266
|
+
)
|
|
188
267
|
})
|
|
189
268
|
|
|
190
269
|
// Helper scope method to alter existing fields in a table
|
|
@@ -198,7 +277,7 @@ export const RestApiKnexPlugin = {
|
|
|
198
277
|
api.knex.instance,
|
|
199
278
|
vars.schemaInfo.tableName,
|
|
200
279
|
params.fields,
|
|
201
|
-
params.options
|
|
280
|
+
{ ...params.options, idProperty: vars.schemaInfo.idProperty }
|
|
202
281
|
)
|
|
203
282
|
})
|
|
204
283
|
|
|
@@ -207,7 +286,12 @@ export const RestApiKnexPlugin = {
|
|
|
207
286
|
// Create schema object from filtered fields
|
|
208
287
|
const partialTableSchema = createSchema(params.fields)
|
|
209
288
|
|
|
210
|
-
await addKnexFields(
|
|
289
|
+
await addKnexFields(
|
|
290
|
+
api.knex.instance,
|
|
291
|
+
vars.schemaInfo.tableName,
|
|
292
|
+
partialTableSchema,
|
|
293
|
+
{ idProperty: vars.schemaInfo.idProperty }
|
|
294
|
+
)
|
|
211
295
|
})
|
|
212
296
|
|
|
213
297
|
helpers.newTransaction = async () => {
|
|
@@ -317,15 +401,18 @@ export const RestApiKnexPlugin = {
|
|
|
317
401
|
// Permission checks will handle access control
|
|
318
402
|
let query = db(tableName).where(idProperty, id)
|
|
319
403
|
|
|
320
|
-
|
|
321
|
-
const selectTranslator = createSelectTranslator(storageAdapter)
|
|
322
|
-
query = buildQuerySelection(
|
|
404
|
+
const selectionState = await applyFieldSelectionToQuery({
|
|
323
405
|
query,
|
|
406
|
+
scope,
|
|
407
|
+
fieldSelectionInfo,
|
|
324
408
|
tableName,
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
409
|
+
useTablePrefix: false,
|
|
410
|
+
storageAdapter,
|
|
411
|
+
db,
|
|
412
|
+
context,
|
|
413
|
+
scopeName
|
|
414
|
+
})
|
|
415
|
+
query = selectionState.query
|
|
329
416
|
|
|
330
417
|
const record = await query.first()
|
|
331
418
|
|
|
@@ -342,7 +429,7 @@ export const RestApiKnexPlugin = {
|
|
|
342
429
|
|
|
343
430
|
// Load relationship identifiers for all hasMany relationships
|
|
344
431
|
const records = [record] // Wrap in array for processing
|
|
345
|
-
await loadRelationshipIdentifiers(records, scopeName, scopes, db)
|
|
432
|
+
await loadRelationshipIdentifiers(records, scopeName, scopes, db, context)
|
|
346
433
|
|
|
347
434
|
// Process includes
|
|
348
435
|
const included = await processIncludes(scope, records, {
|
|
@@ -369,7 +456,7 @@ export const RestApiKnexPlugin = {
|
|
|
369
456
|
* @param {Object} params.context.db - Database connection (knex instance or transaction)
|
|
370
457
|
* @returns {Promise<Object|null>} JSON:API formatted resource with belongsTo relationships, or null if not found
|
|
371
458
|
*/
|
|
372
|
-
helpers.dataGetMinimal = async ({ scopeName, context }) => {
|
|
459
|
+
helpers.dataGetMinimal = async ({ scopeName, context, runHooks, applyQueryFilters }) => {
|
|
373
460
|
const storageAdapter = getScopeStorageAdapter(scopeName)
|
|
374
461
|
if (storageAdapter) {
|
|
375
462
|
context.storageAdapter = storageAdapter
|
|
@@ -382,8 +469,9 @@ export const RestApiKnexPlugin = {
|
|
|
382
469
|
|
|
383
470
|
log.debug(`[Knex] GET_MINIMAL ${tableName}/${id}`)
|
|
384
471
|
|
|
385
|
-
// Build query
|
|
386
|
-
//
|
|
472
|
+
// Build query.
|
|
473
|
+
// Single-record lookups still go through knexQueryFiltering when hooks are available,
|
|
474
|
+
// so resource scoping plugins can treat GET/PUT/PATCH/DELETE the same way as collections.
|
|
387
475
|
let query = db(tableName).where(idProperty, id)
|
|
388
476
|
|
|
389
477
|
// Add alias if idProperty is not 'id'
|
|
@@ -391,6 +479,35 @@ export const RestApiKnexPlugin = {
|
|
|
391
479
|
query = query.select('*', `${idProperty} as id`)
|
|
392
480
|
}
|
|
393
481
|
|
|
482
|
+
if (typeof applyQueryFilters === 'function') {
|
|
483
|
+
const scopedQueryState = await applyQueryFilters({
|
|
484
|
+
query,
|
|
485
|
+
filters: context.queryParams?.filters,
|
|
486
|
+
tableName,
|
|
487
|
+
db,
|
|
488
|
+
scopeName,
|
|
489
|
+
storageAdapter
|
|
490
|
+
})
|
|
491
|
+
query = unwrapQueryBuilderState(scopedQueryState, query)
|
|
492
|
+
} else if (runHooks) {
|
|
493
|
+
context.knexQuery = {
|
|
494
|
+
query,
|
|
495
|
+
filters: context.queryParams?.filters,
|
|
496
|
+
schemaInfo: context.schemaInfo,
|
|
497
|
+
scopeName,
|
|
498
|
+
tableName,
|
|
499
|
+
db,
|
|
500
|
+
adapter: storageAdapter,
|
|
501
|
+
storageAdapter,
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
await runHooks('knexQueryFiltering')
|
|
505
|
+
|
|
506
|
+
if (context.knexQuery) {
|
|
507
|
+
delete context.knexQuery
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
394
511
|
// Execute query
|
|
395
512
|
const record = await query.first()
|
|
396
513
|
|
|
@@ -461,14 +578,19 @@ export const RestApiKnexPlugin = {
|
|
|
461
578
|
// Start building query with table prefix (for JOIN support)
|
|
462
579
|
let query = db(tableName)
|
|
463
580
|
|
|
464
|
-
const
|
|
465
|
-
query = buildQuerySelection(
|
|
581
|
+
const selectionState = await applyFieldSelectionToQuery({
|
|
466
582
|
query,
|
|
583
|
+
scope,
|
|
584
|
+
fieldSelectionInfo,
|
|
467
585
|
tableName,
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
586
|
+
useTablePrefix: true,
|
|
587
|
+
storageAdapter,
|
|
588
|
+
db,
|
|
589
|
+
context,
|
|
590
|
+
scopeName
|
|
591
|
+
})
|
|
592
|
+
query = selectionState.query
|
|
593
|
+
const queryFieldRuntimeByField = selectionState.queryFieldRuntimeByField
|
|
472
594
|
|
|
473
595
|
/* ═══════════════════════════════════════════════════════════════════
|
|
474
596
|
* FILTERING HOOKS
|
|
@@ -513,33 +635,15 @@ export const RestApiKnexPlugin = {
|
|
|
513
635
|
|
|
514
636
|
log.trace('[DATA-QUERY] Finished knexQueryFiltering hook')
|
|
515
637
|
|
|
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
|
-
}
|
|
638
|
+
const sortDescriptors = buildLegacySortDescriptors({
|
|
639
|
+
query,
|
|
640
|
+
sort: queryParams.sort,
|
|
641
|
+
schemaInfo,
|
|
642
|
+
sortableFields,
|
|
643
|
+
defaultSort: scope.vars.defaultSort,
|
|
644
|
+
storageAdapter,
|
|
645
|
+
queryFieldRuntimeByField
|
|
646
|
+
})
|
|
543
647
|
|
|
544
648
|
// Apply pagination
|
|
545
649
|
// Check if page object has any actual pagination parameters
|
|
@@ -577,15 +681,10 @@ export const RestApiKnexPlugin = {
|
|
|
577
681
|
query
|
|
578
682
|
.limit(pageSize)
|
|
579
683
|
.offset((pageNumber - 1) * pageSize)
|
|
580
|
-
}
|
|
581
|
-
// Cursor-based pagination
|
|
582
|
-
else if (queryParams.page.after || queryParams.page.before) {
|
|
684
|
+
} else if (queryParams.page.after || queryParams.page.before) {
|
|
583
685
|
// Fetch one extra record to determine if there are more
|
|
584
686
|
query.limit(pageSize + 1)
|
|
585
687
|
|
|
586
|
-
// Get the configured ID property
|
|
587
|
-
const idProperty = context.schemaInfo.idProperty || 'id'
|
|
588
|
-
|
|
589
688
|
if (queryParams.page.after) {
|
|
590
689
|
let cursorData
|
|
591
690
|
try {
|
|
@@ -604,56 +703,16 @@ export const RestApiKnexPlugin = {
|
|
|
604
703
|
)
|
|
605
704
|
}
|
|
606
705
|
|
|
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
|
-
})
|
|
706
|
+
applyCursorPredicate(
|
|
707
|
+
query,
|
|
708
|
+
sortDescriptors,
|
|
709
|
+
cursorData,
|
|
710
|
+
(direction) => (direction === 'DESC' ? '<' : '>'),
|
|
711
|
+
(builder, descriptor, operator, value) => builder.where(descriptor.column, operator, value),
|
|
712
|
+
{
|
|
713
|
+
onMissingValue: (fieldName) => log.warn(`Cursor missing value for sort field: ${fieldName}`)
|
|
714
|
+
}
|
|
715
|
+
)
|
|
657
716
|
} else if (queryParams.page.before) {
|
|
658
717
|
let cursorData
|
|
659
718
|
try {
|
|
@@ -672,60 +731,18 @@ export const RestApiKnexPlugin = {
|
|
|
672
731
|
)
|
|
673
732
|
}
|
|
674
733
|
|
|
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
|
-
})
|
|
734
|
+
applyCursorPredicate(
|
|
735
|
+
query,
|
|
736
|
+
sortDescriptors,
|
|
737
|
+
cursorData,
|
|
738
|
+
(direction) => (direction === 'DESC' ? '>' : '<'),
|
|
739
|
+
(builder, descriptor, operator, value) => builder.where(descriptor.column, operator, value),
|
|
740
|
+
{
|
|
741
|
+
onMissingValue: (fieldName) => log.warn(`Cursor missing value for sort field: ${fieldName}`)
|
|
742
|
+
}
|
|
743
|
+
)
|
|
725
744
|
}
|
|
726
|
-
}
|
|
727
|
-
// Default pagination if only size is specified - treat as cursor-based for "load more"
|
|
728
|
-
else {
|
|
745
|
+
} else {
|
|
729
746
|
// Fetch one extra to detect hasMore
|
|
730
747
|
query.limit(pageSize + 1)
|
|
731
748
|
}
|
|
@@ -844,12 +861,7 @@ export const RestApiKnexPlugin = {
|
|
|
844
861
|
records.pop()
|
|
845
862
|
}
|
|
846
863
|
|
|
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
|
-
}
|
|
864
|
+
const sortFields = sortDescriptors.map((descriptor) => descriptor.field)
|
|
853
865
|
|
|
854
866
|
context.returnMeta.paginationMeta = buildCursorMeta(records, pageSize, hasMore, sortFields)
|
|
855
867
|
const urlPrefix = getUrlPrefix(context, scope)
|
|
@@ -865,7 +877,7 @@ export const RestApiKnexPlugin = {
|
|
|
865
877
|
}
|
|
866
878
|
|
|
867
879
|
// Load relationship identifiers for all hasMany relationships
|
|
868
|
-
await loadRelationshipIdentifiers(records, scopeName, scopes, db)
|
|
880
|
+
await loadRelationshipIdentifiers(records, scopeName, scopes, db, context)
|
|
869
881
|
|
|
870
882
|
// Process includes
|
|
871
883
|
const included = await processIncludes(scope, records, {
|
|
@@ -901,7 +913,6 @@ export const RestApiKnexPlugin = {
|
|
|
901
913
|
if (storageAdapter) {
|
|
902
914
|
context.storageAdapter = storageAdapter
|
|
903
915
|
}
|
|
904
|
-
const scope = api.resources[scopeName]
|
|
905
916
|
const tableName = storageAdapter?.getTableName?.() || context.schemaInfo.tableName
|
|
906
917
|
const idProperty = storageAdapter?.getIdColumn?.() || context.schemaInfo.idProperty
|
|
907
918
|
const db = context.db || api.knex.instance
|
|
@@ -909,18 +920,43 @@ export const RestApiKnexPlugin = {
|
|
|
909
920
|
|
|
910
921
|
log.debug(`[Knex] POST ${tableName}`, inputRecord)
|
|
911
922
|
|
|
912
|
-
// Extract attributes from JSON:API format
|
|
913
|
-
|
|
923
|
+
// Extract attributes from JSON:API format.
|
|
924
|
+
// POST may legitimately provide a resource id when the table uses a custom primary key
|
|
925
|
+
// (for example, one-to-one rows keyed by user_id/workspace_id). Keep that id in the
|
|
926
|
+
// attribute bag so storage translation can write it to the configured id column.
|
|
927
|
+
const attributes = {
|
|
928
|
+
...(inputRecord.data.attributes || {}),
|
|
929
|
+
...(inputRecord.data.id !== undefined ? { id: inputRecord.data.id } : {})
|
|
930
|
+
}
|
|
914
931
|
|
|
915
932
|
// Strip non-database fields (computed and virtual) before insert
|
|
916
|
-
const dbAttributes =
|
|
933
|
+
const dbAttributes = storageAdapter?.toStorageRow
|
|
934
|
+
? storageAdapter.toStorageRow(attributes, {
|
|
935
|
+
context,
|
|
936
|
+
operation: 'post'
|
|
937
|
+
})
|
|
938
|
+
: attributes
|
|
939
|
+
|
|
940
|
+
const explicitId = dbAttributes[idProperty] ?? inputRecord.data.id
|
|
917
941
|
|
|
918
942
|
// Insert and get the new ID
|
|
919
943
|
const result = await db(tableName).insert(dbAttributes).returning(idProperty)
|
|
920
944
|
|
|
921
|
-
// Extract the ID value (
|
|
922
|
-
|
|
923
|
-
|
|
945
|
+
// Extract the ID value. Some dialects ignore .returning() for inserts and can
|
|
946
|
+
// yield 0 even when the caller explicitly supplied the primary key.
|
|
947
|
+
const returnedId = Array.isArray(result)
|
|
948
|
+
? (result[0]?.[idProperty] ?? result[0])
|
|
949
|
+
: (result?.[idProperty] ?? result)
|
|
950
|
+
|
|
951
|
+
if (
|
|
952
|
+
explicitId !== undefined &&
|
|
953
|
+
explicitId !== null &&
|
|
954
|
+
(returnedId === undefined || returnedId === null || returnedId === 0 || returnedId === '0')
|
|
955
|
+
) {
|
|
956
|
+
return explicitId
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
return returnedId
|
|
924
960
|
}
|
|
925
961
|
|
|
926
962
|
/**
|
|
@@ -964,7 +1000,12 @@ export const RestApiKnexPlugin = {
|
|
|
964
1000
|
const mergedAttributes = { ...attributes, ...foreignKeyUpdates }
|
|
965
1001
|
|
|
966
1002
|
// Strip non-database fields (computed and virtual) before database operation
|
|
967
|
-
const finalAttributes =
|
|
1003
|
+
const finalAttributes = storageAdapter?.toStorageRow
|
|
1004
|
+
? storageAdapter.toStorageRow(mergedAttributes, {
|
|
1005
|
+
context,
|
|
1006
|
+
operation: 'put'
|
|
1007
|
+
})
|
|
1008
|
+
: mergedAttributes
|
|
968
1009
|
|
|
969
1010
|
// Map 'id' to actual idProperty if needed (for PUT with specific ID)
|
|
970
1011
|
if (idProperty !== 'id' && inputRecord.data.id) {
|
|
@@ -1063,7 +1104,12 @@ export const RestApiKnexPlugin = {
|
|
|
1063
1104
|
const mergedAttributes = { ...attributes, ...foreignKeyUpdates }
|
|
1064
1105
|
|
|
1065
1106
|
// Strip non-database fields (computed and virtual) before database operation
|
|
1066
|
-
const finalAttributes =
|
|
1107
|
+
const finalAttributes = storageAdapter?.toStorageRow
|
|
1108
|
+
? storageAdapter.toStorageRow(mergedAttributes, {
|
|
1109
|
+
context,
|
|
1110
|
+
operation: 'patch'
|
|
1111
|
+
})
|
|
1112
|
+
: mergedAttributes
|
|
1067
1113
|
|
|
1068
1114
|
log.debug('[Knex] PATCH finalAttributes:', finalAttributes)
|
|
1069
1115
|
|
|
@@ -1097,7 +1143,6 @@ export const RestApiKnexPlugin = {
|
|
|
1097
1143
|
if (storageAdapter) {
|
|
1098
1144
|
context.storageAdapter = storageAdapter
|
|
1099
1145
|
}
|
|
1100
|
-
const scope = api.resources[scopeName]
|
|
1101
1146
|
const id = context.id
|
|
1102
1147
|
|
|
1103
1148
|
const tableName = storageAdapter?.getTableName?.() || context.schemaInfo.tableName
|