json-rest-api 1.0.22 → 1.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1 -2
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
- package/README.md +22 -1
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
- package/docs/API.md +196 -38
- package/docs/COMPARISON.md +2 -2
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +111 -5
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
- package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
- package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +370 -0
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
- package/docs/GUIDE/index.md +18 -9
- package/docs/QUICKSTART.md +5 -1
- package/docs/index.md +1 -1
- package/index.js +3 -2
- package/lib/enhanced-logger.js +1 -1
- package/package.json +6 -2
- package/plugins/core/bulk-operations-plugin.js +26 -34
- package/plugins/core/connectors/express-plugin.js +97 -190
- package/plugins/core/connectors/fastify-plugin.js +206 -0
- package/plugins/core/connectors/lib/busboy-detector.js +4 -4
- package/plugins/core/connectors/lib/connector-core.js +195 -0
- package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
- package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
- package/plugins/core/lib/anyapi/schema-utils.js +43 -4
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +4 -1
- package/plugins/core/lib/dbIntrospection.js +1078 -0
- package/plugins/core/lib/dbTablesOperations.js +1111 -160
- package/plugins/core/lib/querying/knex-cross-table-search.js +0 -7
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -3
- package/plugins/core/lib/querying/knex-process-includes.js +3 -4
- package/plugins/core/lib/querying/knex-query-helpers.js +21 -76
- package/plugins/core/lib/querying/knex-relationship-includes.js +468 -215
- package/plugins/core/lib/querying/query-builder-utils.js +19 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
- package/plugins/core/lib/querying/sort-helpers.js +33 -0
- package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
- package/plugins/core/lib/querying/url-helpers.js +8 -2
- package/plugins/core/lib/querying-writing/compile-schemas.js +20 -5
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -14
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
- package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
- package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +131 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +0 -2
- package/plugins/core/lib/querying-writing/simplified-helpers.js +4 -11
- package/plugins/core/lib/storage/canonical-storage-mapping.js +133 -0
- package/plugins/core/lib/storage/storage-adapter.js +82 -4
- package/plugins/core/lib/storage/storage-mapping.js +274 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +4 -2
- package/plugins/core/rest-api-anyapi-knex-plugin.js +531 -312
- package/plugins/core/rest-api-autofilter-plugin.js +414 -0
- package/plugins/core/rest-api-knex-plugin.js +271 -226
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +1 -0
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
- package/plugins/core/rest-api-plugin-methods/common.js +148 -92
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +11 -2
- package/plugins/core/rest-api-plugin-methods/delete.js +6 -1
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
- package/plugins/core/rest-api-plugin-methods/get-related.js +7 -2
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +7 -2
- package/plugins/core/rest-api-plugin-methods/get.js +29 -14
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +10 -3
- package/plugins/core/rest-api-plugin-methods/patch.js +56 -12
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +11 -2
- package/plugins/core/rest-api-plugin-methods/post.js +21 -8
- package/plugins/core/rest-api-plugin-methods/put.js +73 -21
- package/plugins/core/rest-api-plugin-methods/query.js +15 -53
- package/plugins/core/rest-api-plugin-methods/release.js +0 -2
- package/plugins/core/rest-api-plugin.js +6 -0
- package/plugins/core/rest-api-positioning-plugin.js +2 -5
- package/plugins/core/rest-api-query-projections-plugin.js +130 -0
- package/plugins/core/socketio-plugin.js +1 -4
- package/plugins/storage/local-storage.js +1 -1
- package/plugins/storage/s3-storage.js +5 -2
- package/quickTest.js +1 -0
- package/scripts/run-docs.js +80 -84
- package/test-debug.js +1 -1
- package/tests/README.md +7 -0
- package/tests/anyapi-basic.test.js +3 -0
- package/tests/autofilter.test.js +463 -0
- package/tests/custom-id-mysql-returning-fallback.test.js +128 -0
- package/tests/custom-id-simplified-post.test.js +75 -0
- package/tests/custom-idproperty-relationships.test.js +33 -0
- package/tests/db-introspection.test.js +521 -0
- package/tests/db-table-operations.test.js +786 -0
- package/tests/fastify-plugin.test.js +156 -0
- package/tests/field-getters.test.js +1 -2
- package/tests/field-setters.test.js +0 -4
- package/tests/fixtures/api-configs.js +278 -176
- package/tests/helpers/fake-fastify.js +99 -0
- package/tests/http-connectors-parity.test.js +235 -0
- package/tests/id-normalization.test.js +444 -0
- package/tests/logical-id-write-validation.test.js +151 -0
- package/tests/pagination-cursor-multifield.test.js +11 -11
- package/tests/projected-fields.test.js +319 -0
- package/tests/relationships.test.js +3 -3
- package/tests/searchschema-knex-behavior.test.js +187 -0
- package/tests/searchschema-merge.test.js +21 -69
- package/tests/storage-mapping.test.js +259 -0
- package/audit/00-charter.md +0 -103
- package/audit/01-scope-map.md +0 -84
- package/audit/02-coverage-matrix.csv +0 -174
- package/audit/03-invariants.md +0 -123
- package/audit/04-standards-rubric.md +0 -56
- package/audit/05-competitor-rubric.md +0 -77
- package/audit/06-findings.md +0 -160
- package/audit/07-risk-register.csv +0 -10
- package/audit/08-remediation-roadmap.md +0 -177
- package/audit/09-signoff-check.md +0 -23
- package/audit/10-final-synthesis.md +0 -29
- package/audit/evidence/code/all-files-json-rest-api.txt +0 -168
- package/audit/evidence/code/all-files-json-rest-schema.txt +0 -12
- package/audit/evidence/code/boundary-leak-checks.txt +0 -72
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__bulk-operations-plugin.js.txt +0 -480
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__express-plugin.js.txt +0 -532
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__busboy-detector.js.txt +0 -175
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__formidable-detector.js.txt +0 -138
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__request-helpers.js.txt +0 -222
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt +0 -342
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__dbTablesOperations.js.txt +0 -304
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__fractional-positioning.js.txt +0 -188
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__compile-schemas.js.txt +0 -336
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__connectors-query-parser.js.txt +0 -165
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-capabilities.js.txt +0 -265
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-value-normalizers.js.txt +0 -369
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__default-data-helpers.js.txt +0 -72
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__field-utils.js.txt +0 -139
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-constants.js.txt +0 -45
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-field-helpers.js.txt +0 -396
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-json-api-transformers.js.txt +0 -162
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__payload-validators.js.txt +0 -1314
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__schema-helpers.js.txt +0 -488
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__scope-validations.js.txt +0 -484
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__simplified-helpers.js.txt +0 -857
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-cross-table-search.js.txt +0 -565
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-json-api-transformers-querying.js.txt +0 -485
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-pagination-helpers.js.txt +0 -560
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-process-includes.js.txt +0 -167
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers-base.js.txt +0 -109
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers.js.txt +0 -1064
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-relationship-includes.js.txt +0 -1983
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-window-queries.js.txt +0 -328
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__url-helpers.js.txt +0 -119
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__storage__storage-adapter.js.txt +0 -160
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__many-to-many-manipulations.js.txt +0 -300
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__relationship-processor.js.txt +0 -245
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-access.js.txt +0 -459
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-cors-plugin.js.txt +0 -237
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-knex-plugin.js.txt +0 -1135
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-multihome-plugin.js.txt +0 -314
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__compile-resource-schemas.js.txt +0 -7
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-relationship-routes.js.txt +0 -71
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-scope-routes.js.txt +0 -102
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__turn-scope-init-into-vars.js.txt +0 -49
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__validate-include-configurations.js.txt +0 -39
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__add-route.js.txt +0 -23
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__check-permissions.js.txt +0 -13
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__common.js.txt +0 -639
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete-relationship.js.txt +0 -117
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete.js.txt +0 -104
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__enrich-attributes.js.txt +0 -199
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-related.js.txt +0 -353
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-relationship.js.txt +0 -61
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get.js.txt +0 -184
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch-relationship.js.txt +0 -55
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch.js.txt +0 -191
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post-relationship.js.txt +0 -110
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post.js.txt +0 -168
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__put.js.txt +0 -253
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__query.js.txt +0 -207
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__release.js.txt +0 -12
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin.js.txt +0 -165
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-positioning-plugin.js.txt +0 -668
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__socketio-plugin.js.txt +0 -621
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__local-storage.js.txt +0 -218
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__s3-storage.js.txt +0 -124
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__CorePlugin.js.txt +0 -269
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__Schema.js.txt +0 -242
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__index.js.txt +0 -77
- package/audit/evidence/code/critical-pass1-risk-keywords.txt +0 -238
- package/audit/evidence/code/critical-pass1-summary.csv +0 -67
- package/audit/evidence/code/critical-pass2-summary.csv +0 -67
- package/audit/evidence/code/early-silent-zones-no-test-token.txt +0 -57
- package/audit/evidence/code/error-model-signals.txt +0 -98
- package/audit/evidence/code/git-files-json-rest-api.txt +0 -171
- package/audit/evidence/code/git-files-json-rest-schema.txt +0 -27
- package/audit/evidence/code/hook-flow-signals.txt +0 -282
- package/audit/evidence/code/idproperty-signals.txt +0 -157
- package/audit/evidence/code/import-cycles-non-anyapi.txt +0 -3
- package/audit/evidence/code/import-hotspots-non-anyapi.txt +0 -96
- package/audit/evidence/code/imports-json-rest-api.txt +0 -354
- package/audit/evidence/code/imports-json-rest-schema.txt +0 -15
- package/audit/evidence/code/in-scope-files-json-rest-api.txt +0 -160
- package/audit/evidence/code/in-scope-files-json-rest-schema.txt +0 -13
- package/audit/evidence/code/non-anyapi-core-files.txt +0 -101
- package/audit/evidence/code/permission-signals.txt +0 -193
- package/audit/evidence/code/query-include-signals.txt +0 -687
- package/audit/evidence/code/transaction-signals.txt +0 -28
- package/audit/evidence/code/validation-signals.txt +0 -650
- package/audit/evidence/code/write-path-signals.txt +0 -61
- package/audit/evidence/docs/docs-claims-signals.txt +0 -1075
- package/audit/evidence/docs/schema-docs-claims-signals.txt +0 -179
- package/audit/evidence/external/corpus-freeze.md +0 -21
- package/audit/evidence/external/npm-competitor-metadata.txt +0 -70
- package/audit/evidence/external/npm-download-signals.txt +0 -23
- package/audit/evidence/external/npm-jsonapi-server.txt +0 -8
- package/audit/evidence/external/npm-modern-jsonapi-metadata.txt +0 -38
- package/audit/evidence/external/readme-fortune-signals.txt +0 -26
- package/audit/evidence/external/readme-fortune.md +0 -165
- package/audit/evidence/external/readme-json-api-serializer-signals.txt +0 -80
- package/audit/evidence/external/readme-json-api-serializer.md +0 -690
- package/audit/evidence/external/readme-jsonapi-serde-server-signals.txt +0 -0
- package/audit/evidence/external/readme-jsonapi-serde-server.md +0 -22
- package/audit/evidence/external/readme-jsonapi-serializer-signals.txt +0 -63
- package/audit/evidence/external/readme-jsonapi-serializer.md +0 -315
- package/audit/evidence/external/readme-jsonapi-server-signals.txt +0 -19
- package/audit/evidence/external/readme-jsonapi-server.md +0 -104
- package/audit/evidence/external/readme-nestjs-json-api-signals.txt +0 -4
- package/audit/evidence/external/readme-nestjs-json-api.md +0 -94
- package/audit/evidence/external/readme-yaser-json-api-nestjs-signals.txt +0 -47
- package/audit/evidence/external/readme-yaser-json-api-nestjs.md +0 -447
- package/audit/evidence/external/standard-jsonapi-snippets.txt +0 -84
- package/audit/evidence/external/standard-rfc-snippets.txt +0 -168
- package/audit/evidence/external/standards-sources.txt +0 -6
- package/audit/evidence/runtime/baseline-commits.txt +0 -2
- package/audit/evidence/runtime/db-adapter-assumptions.txt +0 -3
- package/audit/evidence/runtime/environment.txt +0 -4
- package/audit/evidence/runtime/invariant-runtime-mapping.md +0 -38
- package/audit/evidence/runtime/probe-patch-relationship-error.exitcode +0 -1
- package/audit/evidence/runtime/probe-patch-relationship-error.log +0 -76
- package/audit/evidence/runtime/probe-patch-relationship-error.mjs +0 -67
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.exitcode +0 -1
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.log +0 -62
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.mjs +0 -47
- package/audit/evidence/runtime/test-exitcodes.txt +0 -4
- package/audit/evidence/runtime/test-summary.md +0 -107
- package/audit/evidence/runtime/trace-anchors.txt +0 -61
- package/audit/evidence/tests/access-multitenancy-test-signals.txt +0 -125
- package/audit/evidence/tests/failing-tests-summary.txt +0 -25
- package/audit/evidence/tests/npm-test-json-rest-api.exitcode +0 -1
- package/audit/evidence/tests/npm-test-json-rest-api.log +0 -5885
- package/audit/evidence/tests/npm-test-json-rest-schema.exitcode +0 -1
- package/audit/evidence/tests/npm-test-json-rest-schema.log +0 -356
- package/audit/evidence/tests/query-include-test-signals.txt +0 -948
- package/audit/evidence/tests/relationship-test-signals.txt +0 -223
- package/audit/evidence/tests/skipped-tests.txt +0 -10
- package/audit/evidence/tests/test-suite-list-non-anyapi.txt +0 -30
- package/audit/evidence/tests/test-suite-list.txt +0 -32
- package/audit/subsystems/docs-drift.md +0 -49
- package/audit/subsystems/pass2-cross-review.md +0 -131
- package/audit/subsystems/plugins-access.md +0 -51
- package/audit/subsystems/plugins-bulk.md +0 -50
- package/audit/subsystems/plugins-cors.md +0 -48
- package/audit/subsystems/plugins-file.md +0 -53
- package/audit/subsystems/plugins-multihome.md +0 -50
- package/audit/subsystems/plugins-positioning.md +0 -54
- package/audit/subsystems/plugins-socketio.md +0 -50
- package/audit/subsystems/query-engine.md +0 -61
- package/audit/subsystems/relationships.md +0 -55
- package/audit/subsystems/rest-orchestration.md +0 -66
- package/audit/subsystems/schema-core.md +0 -64
- package/audit/subsystems/storage-knex.md +0 -57
- package/audit/subsystems/tests.md +0 -48
- package/audit/subsystems/transport-express.md +0 -55
- package/audit/traces/trace-read-get.md +0 -22
- package/audit/traces/trace-read-query.md +0 -25
- package/audit/traces/trace-relationship-endpoints.md +0 -31
- package/audit/traces/trace-write-delete.md +0 -17
- package/audit/traces/trace-write-patch.md +0 -26
- package/audit/traces/trace-write-post.md +0 -22
- package/audit/traces/trace-write-put.md +0 -23
- package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
- package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
- package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
- package/plugins/core/rest-api-access.js +0 -459
- package/plugins/core/rest-api-multihome-plugin.js +0 -314
- package/tests/multihome.test.js +0 -553
- package/tests/multihome.test.js.bak +0 -550
- package/tests/ownership-optout.test.js +0 -76
- package/tests/rest-api-access.test.js +0 -206
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { describe, it, before, after, beforeEach } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import knexLib from 'knex'
|
|
4
|
+
import { Api } from 'hooked-api'
|
|
5
|
+
import { RestApiPlugin } from '../plugins/core/rest-api-plugin.js'
|
|
6
|
+
import { RestApiKnexPlugin } from '../plugins/core/rest-api-knex-plugin.js'
|
|
7
|
+
import {
|
|
8
|
+
cleanTables,
|
|
9
|
+
validateJsonApiStructure,
|
|
10
|
+
} from './helpers/test-utils.js'
|
|
11
|
+
|
|
12
|
+
const knex = knexLib({
|
|
13
|
+
client: 'better-sqlite3',
|
|
14
|
+
connection: {
|
|
15
|
+
filename: ':memory:'
|
|
16
|
+
},
|
|
17
|
+
useNullAsDefault: true
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
let api
|
|
21
|
+
|
|
22
|
+
describe('Logical resource ids in write validation', () => {
|
|
23
|
+
before(async () => {
|
|
24
|
+
api = new Api({
|
|
25
|
+
name: 'logical-id-write-validation-test'
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
await api.use(RestApiPlugin, {
|
|
29
|
+
simplifiedApi: false,
|
|
30
|
+
simplifiedTransport: false,
|
|
31
|
+
returnRecordApi: {
|
|
32
|
+
post: 'full',
|
|
33
|
+
put: 'full',
|
|
34
|
+
patch: 'full'
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
await api.use(RestApiKnexPlugin, { knex })
|
|
38
|
+
|
|
39
|
+
await api.addResource('profiles', {
|
|
40
|
+
tableName: 'logical_id_profiles',
|
|
41
|
+
idProperty: 'user_id',
|
|
42
|
+
schema: {
|
|
43
|
+
id: { type: 'id', required: true, storage: { column: 'user_id' } },
|
|
44
|
+
name: { type: 'string', required: true, max: 64 }
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
await api.resources.profiles.createKnexTable()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
after(async () => {
|
|
51
|
+
await knex.destroy()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
beforeEach(async () => {
|
|
55
|
+
await cleanTables(knex, ['logical_id_profiles'])
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('accepts explicit resource ids in non-simplified POST without exposing id as an attribute', async () => {
|
|
59
|
+
const created = await api.resources.profiles.post({
|
|
60
|
+
inputRecord: {
|
|
61
|
+
data: {
|
|
62
|
+
type: 'profiles',
|
|
63
|
+
id: '101',
|
|
64
|
+
attributes: {
|
|
65
|
+
name: 'Alice'
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
validateJsonApiStructure(created)
|
|
72
|
+
assert.equal(created.data.id, '101')
|
|
73
|
+
assert.equal(created.data.attributes.name, 'Alice')
|
|
74
|
+
assert.ok(!('id' in created.data.attributes))
|
|
75
|
+
|
|
76
|
+
const dbRow = await knex('logical_id_profiles').where('user_id', 101).first()
|
|
77
|
+
assert.ok(dbRow)
|
|
78
|
+
assert.equal(String(dbRow.user_id), '101')
|
|
79
|
+
assert.equal(dbRow.name, 'Alice')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('reports missing logical ids against data.id during POST validation', async () => {
|
|
83
|
+
await assert.rejects(
|
|
84
|
+
() => api.resources.profiles.post({
|
|
85
|
+
inputRecord: {
|
|
86
|
+
data: {
|
|
87
|
+
type: 'profiles',
|
|
88
|
+
attributes: {
|
|
89
|
+
name: 'Missing Id'
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}),
|
|
94
|
+
(error) => {
|
|
95
|
+
assert.equal(error.code, 'REST_API_VALIDATION')
|
|
96
|
+
assert.deepEqual(error.details?.fields, ['data.id'])
|
|
97
|
+
assert.equal(error.details?.violations?.[0]?.field, 'data.id')
|
|
98
|
+
return true
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('accepts explicit resource ids in simplified POST and still keeps id out of attributes', async () => {
|
|
104
|
+
const created = await api.resources.profiles.post({
|
|
105
|
+
id: '202',
|
|
106
|
+
name: 'Bob'
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
assert.equal(created.id, '202')
|
|
110
|
+
assert.equal(created.name, 'Bob')
|
|
111
|
+
|
|
112
|
+
const fetched = await api.resources.profiles.get({
|
|
113
|
+
id: '202',
|
|
114
|
+
simplified: false
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
validateJsonApiStructure(fetched)
|
|
118
|
+
assert.equal(fetched.data.id, '202')
|
|
119
|
+
assert.equal(fetched.data.attributes.name, 'Bob')
|
|
120
|
+
assert.ok(!('id' in fetched.data.attributes))
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('accepts logical id validation through PUT full replacement', async () => {
|
|
124
|
+
await knex('logical_id_profiles').insert({
|
|
125
|
+
user_id: 303,
|
|
126
|
+
name: 'Before'
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const updated = await api.resources.profiles.put({
|
|
130
|
+
id: '303',
|
|
131
|
+
inputRecord: {
|
|
132
|
+
data: {
|
|
133
|
+
type: 'profiles',
|
|
134
|
+
id: '303',
|
|
135
|
+
attributes: {
|
|
136
|
+
name: 'After'
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
validateJsonApiStructure(updated)
|
|
143
|
+
assert.equal(updated.data.id, '303')
|
|
144
|
+
assert.equal(updated.data.attributes.name, 'After')
|
|
145
|
+
assert.ok(!('id' in updated.data.attributes))
|
|
146
|
+
|
|
147
|
+
const dbRow = await knex('logical_id_profiles').where('user_id', 303).first()
|
|
148
|
+
assert.ok(dbRow)
|
|
149
|
+
assert.equal(dbRow.name, 'After')
|
|
150
|
+
})
|
|
151
|
+
})
|
|
@@ -55,7 +55,7 @@ describe('Multi-field Cursor Pagination', () => {
|
|
|
55
55
|
}
|
|
56
56
|
})
|
|
57
57
|
|
|
58
|
-
it
|
|
58
|
+
it('should handle cursor pagination with multi-field sorting without skipping records', async () => {
|
|
59
59
|
// Sort by category first (which has duplicates), then by name
|
|
60
60
|
const pageSize = 3
|
|
61
61
|
const allProducts = []
|
|
@@ -102,7 +102,7 @@ describe('Multi-field Cursor Pagination', () => {
|
|
|
102
102
|
assert.equal(ids.length, uniqueIds.length, 'Should have no duplicate products')
|
|
103
103
|
})
|
|
104
104
|
|
|
105
|
-
it
|
|
105
|
+
it('should handle DESC sorting with duplicates correctly', async () => {
|
|
106
106
|
// Sort by category DESC, then name ASC
|
|
107
107
|
const firstPage = await api.resources.products.query({
|
|
108
108
|
queryParams: {
|
|
@@ -189,7 +189,7 @@ describe('Multi-field Cursor Pagination', () => {
|
|
|
189
189
|
'Green Gardens vegetables should be in alphabetical order by name')
|
|
190
190
|
})
|
|
191
191
|
|
|
192
|
-
it
|
|
192
|
+
it('should handle cursor pagination with numeric and text fields', async () => {
|
|
193
193
|
// Sort by price (numeric), then category (text), then name (text)
|
|
194
194
|
const firstPage = await api.resources.products.query({
|
|
195
195
|
queryParams: {
|
|
@@ -222,7 +222,7 @@ describe('Multi-field Cursor Pagination', () => {
|
|
|
222
222
|
'Prices should be non-decreasing across pages')
|
|
223
223
|
})
|
|
224
224
|
|
|
225
|
-
it
|
|
225
|
+
it('should generate correct cursor values for multi-field sorting', async () => {
|
|
226
226
|
const result = await api.resources.products.query({
|
|
227
227
|
queryParams: {
|
|
228
228
|
page: { size: 3 },
|
|
@@ -244,7 +244,7 @@ describe('Multi-field Cursor Pagination', () => {
|
|
|
244
244
|
assert(cursor.includes('name:'), 'Cursor should include name field')
|
|
245
245
|
})
|
|
246
246
|
|
|
247
|
-
it
|
|
247
|
+
it('should handle page[before] with multi-field sorting', async () => {
|
|
248
248
|
// First, get to the middle of the dataset
|
|
249
249
|
const firstPage = await api.resources.products.query({
|
|
250
250
|
queryParams: {
|
|
@@ -272,7 +272,7 @@ describe('Multi-field Cursor Pagination', () => {
|
|
|
272
272
|
size: 4,
|
|
273
273
|
before: secondPage.meta.pagination.cursor?.next ||
|
|
274
274
|
// If no next cursor, create one from the last record
|
|
275
|
-
`category:${encodeURIComponent(secondPage.data[0].attributes.category)},name:${encodeURIComponent(secondPage.data[0].attributes.name)}`
|
|
275
|
+
`category:${encodeURIComponent(secondPage.data[0].attributes.category)},name:${encodeURIComponent(secondPage.data[0].attributes.name)},id:${encodeURIComponent(secondPage.data[0].id)}`
|
|
276
276
|
},
|
|
277
277
|
sort: ['category', 'name']
|
|
278
278
|
},
|
|
@@ -292,7 +292,7 @@ describe('Multi-field Cursor Pagination', () => {
|
|
|
292
292
|
await cleanTables(knex, ['cursor_products', 'cursor_items'])
|
|
293
293
|
})
|
|
294
294
|
|
|
295
|
-
it
|
|
295
|
+
it('should handle all records having the same value in first sort field', async () => {
|
|
296
296
|
// Create products all in the same category
|
|
297
297
|
const products = [
|
|
298
298
|
{ name: 'Alpha', category: 'Electronics', brand: 'TechCo', price: 100, sku: 'TC-001' },
|
|
@@ -348,7 +348,7 @@ describe('Multi-field Cursor Pagination', () => {
|
|
|
348
348
|
assert(gadgetIncIndex < firstTechCoIndex, 'GadgetInc products should come before TechCo')
|
|
349
349
|
})
|
|
350
350
|
|
|
351
|
-
it
|
|
351
|
+
it('should handle empty results with multi-field cursor', async () => {
|
|
352
352
|
// Create one product
|
|
353
353
|
await api.resources.products.post({
|
|
354
354
|
inputRecord: createJsonApiDocument('products', {
|
|
@@ -375,7 +375,7 @@ describe('Multi-field Cursor Pagination', () => {
|
|
|
375
375
|
assert(!firstPage.meta.pagination.cursor?.next, 'Should not have next cursor on last page')
|
|
376
376
|
})
|
|
377
377
|
|
|
378
|
-
it
|
|
378
|
+
it('should handle special characters in multi-field cursors', async () => {
|
|
379
379
|
// Create products with special characters
|
|
380
380
|
const products = [
|
|
381
381
|
{ name: 'Product: Special', category: 'Cat:1', brand: 'Brand,A', price: 10, sku: 'SP-001' },
|
|
@@ -418,7 +418,7 @@ describe('Multi-field Cursor Pagination', () => {
|
|
|
418
418
|
'Should get different record on second page')
|
|
419
419
|
})
|
|
420
420
|
|
|
421
|
-
it
|
|
421
|
+
it('should maintain stable sorting with custom id property', async () => {
|
|
422
422
|
// Create items using the resource with custom ID property
|
|
423
423
|
const items = [
|
|
424
424
|
{ code: 'A001', name: 'Apple', category: 'Fruit', type: 'Fresh' },
|
|
@@ -464,7 +464,7 @@ describe('Multi-field Cursor Pagination', () => {
|
|
|
464
464
|
}
|
|
465
465
|
})
|
|
466
466
|
|
|
467
|
-
it
|
|
467
|
+
it('should handle the exact bug scenario - multiple fruits with same category', async () => {
|
|
468
468
|
// This is the exact scenario from the bug report
|
|
469
469
|
await cleanTables(knex, ['cursor_products'])
|
|
470
470
|
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { describe, it, before, beforeEach, after } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { Api } from 'hooked-api'
|
|
4
|
+
import knexLib from 'knex'
|
|
5
|
+
import { LabelPlugin, QueryProjectionsPlugin, RestApiKnexPlugin, RestApiPlugin } from '../index.js'
|
|
6
|
+
import {
|
|
7
|
+
cleanTables,
|
|
8
|
+
createJsonApiDocument,
|
|
9
|
+
createRelationship,
|
|
10
|
+
resourceIdentifier,
|
|
11
|
+
validateJsonApiStructure,
|
|
12
|
+
} from './helpers/test-utils.js'
|
|
13
|
+
import { createProjectedFieldsApi } from './fixtures/api-configs.js'
|
|
14
|
+
|
|
15
|
+
const knex = knexLib({
|
|
16
|
+
client: 'better-sqlite3',
|
|
17
|
+
connection: {
|
|
18
|
+
filename: ':memory:'
|
|
19
|
+
},
|
|
20
|
+
useNullAsDefault: true
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe('Query Projections', () => {
|
|
24
|
+
let api
|
|
25
|
+
const testData = {}
|
|
26
|
+
|
|
27
|
+
before(async () => {
|
|
28
|
+
api = await createProjectedFieldsApi(knex)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
after(async () => {
|
|
32
|
+
await knex.destroy()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
beforeEach(async () => {
|
|
36
|
+
await cleanTables(knex, ['projected_books', 'projected_authors'])
|
|
37
|
+
|
|
38
|
+
const authorPayloads = [
|
|
39
|
+
{ first_name: 'Alex', last_name: 'Carter' },
|
|
40
|
+
{ first_name: 'Alex', last_name: 'Carter' },
|
|
41
|
+
{ first_name: 'Bella', last_name: 'Stone' },
|
|
42
|
+
{ first_name: 'Aaron', last_name: 'Zephyr' }
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
testData.authors = []
|
|
46
|
+
for (const payload of authorPayloads) {
|
|
47
|
+
const result = await api.resources.authors.post({
|
|
48
|
+
inputRecord: createJsonApiDocument('authors', payload),
|
|
49
|
+
simplified: false
|
|
50
|
+
})
|
|
51
|
+
testData.authors.push(result.data)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
testData.expectedAuthorOrder = [
|
|
55
|
+
testData.authors[3].id,
|
|
56
|
+
testData.authors[0].id,
|
|
57
|
+
testData.authors[1].id,
|
|
58
|
+
testData.authors[2].id
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
const bookOne = await api.resources.books.post({
|
|
62
|
+
inputRecord: createJsonApiDocument(
|
|
63
|
+
'books',
|
|
64
|
+
{ title: 'Derived Fields Handbook' },
|
|
65
|
+
{ author: createRelationship(resourceIdentifier('authors', testData.authors[0].id)) }
|
|
66
|
+
),
|
|
67
|
+
simplified: false
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const bookTwo = await api.resources.books.post({
|
|
71
|
+
inputRecord: createJsonApiDocument(
|
|
72
|
+
'books',
|
|
73
|
+
{ title: 'Cursor Patterns' },
|
|
74
|
+
{ author: createRelationship(resourceIdentifier('authors', testData.authors[2].id)) }
|
|
75
|
+
),
|
|
76
|
+
simplified: false
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
testData.books = [bookOne.data, bookTwo.data]
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
async function createEphemeralProjectionApi (resourceName, resourceOptions) {
|
|
83
|
+
const api = new Api({
|
|
84
|
+
name: `${resourceName}-projection-test-api`,
|
|
85
|
+
log: { level: process.env.LOG_LEVEL || 'info' }
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
await api.use(RestApiPlugin, {
|
|
89
|
+
simplifiedApi: true,
|
|
90
|
+
simplifiedTransport: true,
|
|
91
|
+
returnRecordApi: {
|
|
92
|
+
post: true,
|
|
93
|
+
put: true,
|
|
94
|
+
patch: true
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
await api.use(QueryProjectionsPlugin)
|
|
98
|
+
await api.use(RestApiKnexPlugin, { knex })
|
|
99
|
+
await api.addResource(resourceName, resourceOptions)
|
|
100
|
+
|
|
101
|
+
return api
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
it('should return query projection fields by default and ignore query projection input on writes', async () => {
|
|
105
|
+
const created = await api.resources.authors.post({
|
|
106
|
+
inputRecord: createJsonApiDocument('authors', {
|
|
107
|
+
first_name: 'Nina',
|
|
108
|
+
last_name: 'Simone',
|
|
109
|
+
full_name: 'Injected Value'
|
|
110
|
+
}),
|
|
111
|
+
simplified: false
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
validateJsonApiStructure(created)
|
|
115
|
+
assert.equal(created.data.attributes.first_name, 'Nina')
|
|
116
|
+
assert.equal(created.data.attributes.last_name, 'Simone')
|
|
117
|
+
assert.equal(created.data.attributes.full_name, 'Nina Simone')
|
|
118
|
+
|
|
119
|
+
const fetched = await api.resources.authors.get({
|
|
120
|
+
id: created.data.id,
|
|
121
|
+
simplified: false
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
validateJsonApiStructure(fetched)
|
|
125
|
+
assert.equal(fetched.data.attributes.full_name, 'Nina Simone')
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('should allow query projection fields in sparse fieldsets', async () => {
|
|
129
|
+
const result = await api.resources.authors.get({
|
|
130
|
+
id: testData.authors[0].id,
|
|
131
|
+
queryParams: {
|
|
132
|
+
fields: {
|
|
133
|
+
authors: 'first_name,full_name'
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
simplified: false
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
validateJsonApiStructure(result)
|
|
140
|
+
assert.equal(result.data.attributes.first_name, 'Alex')
|
|
141
|
+
assert.equal(result.data.attributes.full_name, 'Alex Carter')
|
|
142
|
+
assert.equal(result.data.attributes.last_name, undefined)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('should expose query projection fields on included resources', async () => {
|
|
146
|
+
const result = await api.resources.books.get({
|
|
147
|
+
id: testData.books[0].id,
|
|
148
|
+
queryParams: {
|
|
149
|
+
include: ['author'],
|
|
150
|
+
fields: {
|
|
151
|
+
books: 'title',
|
|
152
|
+
authors: 'full_name'
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
simplified: false
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
validateJsonApiStructure(result)
|
|
159
|
+
assert.ok(Array.isArray(result.included))
|
|
160
|
+
|
|
161
|
+
const includedAuthor = result.included.find((resource) => resource.type === 'authors')
|
|
162
|
+
assert.ok(includedAuthor, 'expected included author resource')
|
|
163
|
+
assert.equal(includedAuthor.attributes.full_name, 'Alex Carter')
|
|
164
|
+
assert.equal(includedAuthor.attributes.first_name, undefined)
|
|
165
|
+
assert.equal(includedAuthor.attributes.last_name, undefined)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('should support stable cursor pagination when sorting by a query projection field', async () => {
|
|
169
|
+
const seenIds = []
|
|
170
|
+
const seenFirstNames = []
|
|
171
|
+
let nextCursor = null
|
|
172
|
+
let iterations = 0
|
|
173
|
+
let firstCursor = null
|
|
174
|
+
|
|
175
|
+
while (iterations < 10) {
|
|
176
|
+
const result = await api.resources.authors.query({
|
|
177
|
+
queryParams: {
|
|
178
|
+
sort: ['full_name'],
|
|
179
|
+
fields: {
|
|
180
|
+
authors: 'first_name'
|
|
181
|
+
},
|
|
182
|
+
page: {
|
|
183
|
+
size: 1,
|
|
184
|
+
...(nextCursor ? { after: nextCursor } : {})
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
simplified: false
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
validateJsonApiStructure(result, true)
|
|
191
|
+
assert.equal(result.data.length, 1)
|
|
192
|
+
|
|
193
|
+
const resource = result.data[0]
|
|
194
|
+
seenIds.push(resource.id)
|
|
195
|
+
seenFirstNames.push(resource.attributes.first_name)
|
|
196
|
+
assert.equal(resource.attributes.full_name, undefined)
|
|
197
|
+
|
|
198
|
+
if (!firstCursor && result.meta?.pagination?.cursor?.next) {
|
|
199
|
+
firstCursor = result.meta.pagination.cursor.next
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!result.meta?.pagination?.hasMore) {
|
|
203
|
+
break
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
nextCursor = result.meta.pagination.cursor.next
|
|
207
|
+
iterations += 1
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
assert.deepEqual(seenIds, testData.expectedAuthorOrder)
|
|
211
|
+
assert.equal(new Set(seenIds).size, seenIds.length, 'cursor pagination should not duplicate records')
|
|
212
|
+
assert.ok(firstCursor?.includes('full_name:'), 'cursor should encode query projection sort field')
|
|
213
|
+
assert.ok(firstCursor?.includes('id:'), 'cursor should append id as deterministic tie-breaker')
|
|
214
|
+
assert.deepEqual(seenFirstNames, ['Aaron', 'Alex', 'Alex', 'Bella'])
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('should allow sorting by a normallyHidden query projection field without returning it by default', async () => {
|
|
218
|
+
const result = await api.resources.authors.query({
|
|
219
|
+
queryParams: {
|
|
220
|
+
sort: ['sort_name']
|
|
221
|
+
},
|
|
222
|
+
simplified: false
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
validateJsonApiStructure(result, true)
|
|
226
|
+
assert.deepEqual(
|
|
227
|
+
result.data.map((resource) => resource.id),
|
|
228
|
+
testData.expectedAuthorOrder
|
|
229
|
+
)
|
|
230
|
+
result.data.forEach((resource) => {
|
|
231
|
+
assert.equal(resource.attributes.sort_name, undefined)
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('should reject query projection expressions that return plain SQL strings', async () => {
|
|
236
|
+
const stringApi = await createEphemeralProjectionApi('string_projection_authors', {
|
|
237
|
+
schema: {
|
|
238
|
+
id: { type: 'id' },
|
|
239
|
+
first_name: { type: 'string', required: true },
|
|
240
|
+
last_name: { type: 'string', required: true }
|
|
241
|
+
},
|
|
242
|
+
queryFields: {
|
|
243
|
+
full_name: {
|
|
244
|
+
type: 'string',
|
|
245
|
+
select: () => "trim(coalesce(first_name, '') || ' ' || coalesce(last_name, ''))"
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
tableName: 'projected_authors_string_expr'
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
await stringApi.resources.string_projection_authors.createKnexTable()
|
|
252
|
+
await knex('projected_authors_string_expr').insert({
|
|
253
|
+
first_name: 'Nina',
|
|
254
|
+
last_name: 'Simone'
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
await assert.rejects(
|
|
258
|
+
() => stringApi.resources.string_projection_authors.query({
|
|
259
|
+
queryParams: {},
|
|
260
|
+
simplified: false
|
|
261
|
+
}),
|
|
262
|
+
/Query field expressions must return knex raw, knex ref, or a knex query builder/
|
|
263
|
+
)
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('should reject query projection names that conflict with relationship names', async () => {
|
|
267
|
+
await assert.rejects(
|
|
268
|
+
() => createEphemeralProjectionApi('conflicting_projection_authors', {
|
|
269
|
+
schema: {
|
|
270
|
+
id: { type: 'id' },
|
|
271
|
+
name: { type: 'string', required: true }
|
|
272
|
+
},
|
|
273
|
+
queryFields: {
|
|
274
|
+
books: {
|
|
275
|
+
type: 'string',
|
|
276
|
+
select: ({ knex }) => knex.raw("'X'")
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
relationships: {
|
|
280
|
+
books: { type: 'hasMany', target: 'books', foreignKey: 'author_id' }
|
|
281
|
+
},
|
|
282
|
+
tableName: 'projected_authors_conflict'
|
|
283
|
+
}),
|
|
284
|
+
/conflicts with an existing schema/
|
|
285
|
+
)
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
it('should reject query projection names that conflict with computed fields injected by plugins', async () => {
|
|
289
|
+
const labeledApi = new Api({
|
|
290
|
+
name: 'label-projection-conflict-api',
|
|
291
|
+
log: { level: process.env.LOG_LEVEL || 'info' }
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
await labeledApi.use(RestApiPlugin, {
|
|
295
|
+
simplifiedApi: true,
|
|
296
|
+
simplifiedTransport: true
|
|
297
|
+
})
|
|
298
|
+
await labeledApi.use(LabelPlugin)
|
|
299
|
+
await labeledApi.use(QueryProjectionsPlugin)
|
|
300
|
+
await labeledApi.use(RestApiKnexPlugin, { knex })
|
|
301
|
+
|
|
302
|
+
await assert.rejects(
|
|
303
|
+
() => labeledApi.addResource('labeled_projection_authors', {
|
|
304
|
+
schema: {
|
|
305
|
+
id: { type: 'id' },
|
|
306
|
+
name: { type: 'string', required: true }
|
|
307
|
+
},
|
|
308
|
+
queryFields: {
|
|
309
|
+
label: {
|
|
310
|
+
type: 'string',
|
|
311
|
+
select: ({ knex }) => knex.raw("'X'")
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
tableName: 'projected_authors_label_conflict'
|
|
315
|
+
}),
|
|
316
|
+
/conflicts with an existing schema, computed, or relationship name/
|
|
317
|
+
)
|
|
318
|
+
})
|
|
319
|
+
})
|
|
@@ -330,7 +330,6 @@ describe('Relationship Operations', () => {
|
|
|
330
330
|
}
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
|
-
console.log('THE NEXT LINE WILL BLOCK THE TEST')
|
|
334
333
|
await basicApi.resources.books.put({
|
|
335
334
|
id: bookResult.data.id,
|
|
336
335
|
inputRecord: putDoc,
|
|
@@ -590,7 +589,7 @@ describe('Relationship Operations', () => {
|
|
|
590
589
|
assert(publisher.relationships.country)
|
|
591
590
|
})
|
|
592
591
|
|
|
593
|
-
it('should
|
|
592
|
+
it('should clear omitted relationships when PUT provides a relationships object', async () => {
|
|
594
593
|
// Create resources
|
|
595
594
|
const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
596
595
|
const countryResult = await basicApi.resources.countries.post({
|
|
@@ -640,7 +639,8 @@ describe('Relationship Operations', () => {
|
|
|
640
639
|
simplified: false
|
|
641
640
|
})
|
|
642
641
|
|
|
643
|
-
// PUT with only publisher relationship
|
|
642
|
+
// PUT with only publisher relationship. Omitted relationships are cleared
|
|
643
|
+
// once a relationships object is provided for a complete replacement.
|
|
644
644
|
const putDoc = {
|
|
645
645
|
data: {
|
|
646
646
|
type: 'books',
|