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
|
@@ -280,22 +280,15 @@ export const transformSimplifiedToJsonApi = (scope, deps) => {
|
|
|
280
280
|
relationshipsData[relName] = {
|
|
281
281
|
data: value.map(relId => ({ type: targetType, id: String(relId) }))
|
|
282
282
|
}
|
|
283
|
-
}
|
|
284
|
-
// Handle hasOne relationships
|
|
285
|
-
else if (relConfig.type === 'hasOne' && value !== undefined) {
|
|
283
|
+
} else if (relConfig.type === 'hasOne' && value !== undefined) {
|
|
286
284
|
relationshipsData[relName] = {
|
|
287
285
|
data: value ? { type: relConfig.target, id: String(value) } : null
|
|
288
286
|
}
|
|
289
|
-
}
|
|
290
|
-
// Handle single belongsTo relationships if they were also defined in 'relationships'
|
|
291
|
-
// (though typically they are defined in schema.belongsTo)
|
|
292
|
-
else if (relConfig.belongsTo && value !== undefined) {
|
|
287
|
+
} else if (relConfig.belongsTo && value !== undefined) {
|
|
293
288
|
relationshipsData[relName] = {
|
|
294
289
|
data: value ? { type: relConfig.belongsTo, id: String(value) } : null
|
|
295
290
|
}
|
|
296
|
-
}
|
|
297
|
-
// Handle polymorphic belongsTo relationships with minimal object format
|
|
298
|
-
else if (relConfig.belongsToPolymorphic && value !== undefined) {
|
|
291
|
+
} else if (relConfig.belongsToPolymorphic && value !== undefined) {
|
|
299
292
|
if (value === null) {
|
|
300
293
|
relationshipsData[relName] = { data: null }
|
|
301
294
|
} else if (typeof value === 'object' && value !== null && value.id && value._type) {
|
|
@@ -780,7 +773,7 @@ export const transformSingleJsonApiToSimplified = (scope, deps) => {
|
|
|
780
773
|
const schemaEntry = Object.entries(schema).find(([_, def]) => def.as === relName)
|
|
781
774
|
|
|
782
775
|
if (schemaEntry) {
|
|
783
|
-
const [
|
|
776
|
+
const [, fieldDef] = schemaEntry
|
|
784
777
|
if (fieldDef.belongsTo && relData.data) {
|
|
785
778
|
// Create minimal relationship object (no more foreign key field)
|
|
786
779
|
simplified[relName] = { id: relData.data.id }
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
export const translateCanonicalAttributesForStorage = (attributes = {}, descriptor = {}) => {
|
|
2
|
+
const row = {}
|
|
3
|
+
|
|
4
|
+
for (const [fieldName, value] of Object.entries(attributes)) {
|
|
5
|
+
const slot = descriptor.fields?.[fieldName]
|
|
6
|
+
if (!slot) continue
|
|
7
|
+
|
|
8
|
+
if (slot.slotType === 'belongsTo') {
|
|
9
|
+
row[slot.slot] = value == null ? null : String(value)
|
|
10
|
+
} else {
|
|
11
|
+
row[slot.slot] = value
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (slot.slotType === 'belongsTo') {
|
|
15
|
+
const alias = slot.alias || descriptor.fields?.[fieldName]?.alias
|
|
16
|
+
const belongsToInfo = alias ? descriptor.belongsTo?.[alias] : null
|
|
17
|
+
if (belongsToInfo) {
|
|
18
|
+
row[belongsToInfo.typeColumn] = value == null ? null : belongsToInfo.target
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return row
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const getCanonicalResourceIdColumn = (descriptor = {}) => (
|
|
27
|
+
descriptor?.canonical?.logicalIdColumn || 'logical_id'
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
export const getCanonicalResourceId = (row = {}, descriptor = {}) => {
|
|
31
|
+
const logicalIdColumn = getCanonicalResourceIdColumn(descriptor)
|
|
32
|
+
const logicalId = row?.[logicalIdColumn]
|
|
33
|
+
|
|
34
|
+
if (logicalId !== undefined && logicalId !== null) {
|
|
35
|
+
return String(logicalId)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (row?.id !== undefined && row?.id !== null) {
|
|
39
|
+
return String(row.id)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const translateCanonicalRecordFromStorage = (row = {}, descriptor = {}, options = {}) => {
|
|
46
|
+
const attributes = {}
|
|
47
|
+
const logicalIdColumn = getCanonicalResourceIdColumn(descriptor)
|
|
48
|
+
const consumedColumns = new Set(['id', logicalIdColumn])
|
|
49
|
+
const allowedExtraFields = new Set(options.allowedExtraFields || [])
|
|
50
|
+
|
|
51
|
+
for (const [slot, logical] of Object.entries(descriptor.reverseAttributes || {})) {
|
|
52
|
+
if (slot in row) {
|
|
53
|
+
attributes[logical] = row[slot]
|
|
54
|
+
consumedColumns.add(slot)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const relationships = {}
|
|
59
|
+
|
|
60
|
+
for (const [alias, info] of Object.entries(descriptor.belongsTo || {})) {
|
|
61
|
+
const idValue = row[info.idColumn]
|
|
62
|
+
consumedColumns.add(info.idColumn)
|
|
63
|
+
if (info.typeColumn) {
|
|
64
|
+
consumedColumns.add(info.typeColumn)
|
|
65
|
+
}
|
|
66
|
+
const relationshipData = idValue == null
|
|
67
|
+
? null
|
|
68
|
+
: { type: info.target, id: String(idValue) }
|
|
69
|
+
|
|
70
|
+
relationships[alias] = { data: relationshipData }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (const [alias, info] of Object.entries(descriptor.polymorphicBelongsTo || {})) {
|
|
74
|
+
const typeValue = info.typeColumn ? row[info.typeColumn] : null
|
|
75
|
+
const idValue = info.idColumn ? row[info.idColumn] : null
|
|
76
|
+
if (info.typeColumn) {
|
|
77
|
+
consumedColumns.add(info.typeColumn)
|
|
78
|
+
}
|
|
79
|
+
if (info.idColumn) {
|
|
80
|
+
consumedColumns.add(info.idColumn)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let relationshipData = null
|
|
84
|
+
if (typeValue != null && idValue != null) {
|
|
85
|
+
relationshipData = {
|
|
86
|
+
type: String(typeValue),
|
|
87
|
+
id: String(idValue),
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
relationships[alias] = { data: relationshipData }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const canonical = descriptor.canonical || {}
|
|
95
|
+
const internalColumns = new Set([
|
|
96
|
+
canonical.tenantColumn,
|
|
97
|
+
canonical.resourceColumn,
|
|
98
|
+
'created_at',
|
|
99
|
+
'updated_at',
|
|
100
|
+
'deleted_at'
|
|
101
|
+
].filter(Boolean))
|
|
102
|
+
|
|
103
|
+
for (const [columnName, value] of Object.entries(row)) {
|
|
104
|
+
if (consumedColumns.has(columnName) || internalColumns.has(columnName)) {
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
if (!allowedExtraFields.has(columnName)) {
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
attributes[columnName] = value
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { attributes, relationships }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const getCanonicalFieldValue = (row, descriptor = {}, fieldName) => {
|
|
117
|
+
if (!row || !fieldName) return undefined
|
|
118
|
+
|
|
119
|
+
if (fieldName === 'id') {
|
|
120
|
+
return getCanonicalResourceId(row, descriptor)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const fieldInfo = descriptor.fields?.[fieldName]
|
|
124
|
+
if (fieldInfo?.slot && Object.hasOwn(row, fieldInfo.slot)) {
|
|
125
|
+
return row[fieldInfo.slot]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (Object.hasOwn(row, fieldName)) {
|
|
129
|
+
return row[fieldName]
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return undefined
|
|
133
|
+
}
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getFieldValue as getLegacyFieldValue,
|
|
3
|
+
getIdColumn,
|
|
4
|
+
getStorageColumn,
|
|
5
|
+
translateAttributesForStorage,
|
|
6
|
+
} from './storage-mapping.js'
|
|
7
|
+
import {
|
|
8
|
+
getCanonicalFieldValue,
|
|
9
|
+
getCanonicalResourceIdColumn,
|
|
10
|
+
translateCanonicalAttributesForStorage,
|
|
11
|
+
} from './canonical-storage-mapping.js'
|
|
12
|
+
|
|
1
13
|
const passthrough = (value) => value
|
|
2
14
|
const identityTranslate = (_field, value) => value
|
|
3
15
|
const identityScope = (query) => query
|
|
@@ -22,6 +34,59 @@ const normalizeBelongsToValue = (value) => {
|
|
|
22
34
|
return String(value)
|
|
23
35
|
}
|
|
24
36
|
|
|
37
|
+
const translateSourceColumn = (source, adapter) => {
|
|
38
|
+
if (!adapter || source === '*') return source
|
|
39
|
+
return adapter.translateColumn(source) || source
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const createSelectTranslator = (adapter) => {
|
|
43
|
+
if (!adapter) return null
|
|
44
|
+
|
|
45
|
+
return (field, alias) => {
|
|
46
|
+
if (field === '*') {
|
|
47
|
+
return alias ? `${alias}.*` : '*'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const translated = adapter.translateColumn(field)
|
|
51
|
+
const result = translated || field
|
|
52
|
+
|
|
53
|
+
if (field === 'id' && result !== 'id') {
|
|
54
|
+
const qualified = alias ? `${alias}.${result}` : result
|
|
55
|
+
return `${qualified} as id`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!alias) {
|
|
59
|
+
return result
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (result.includes('.')) {
|
|
63
|
+
return result
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return `${alias}.${result}`
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const translateSelectFieldsForAdapter = (fields, adapter) => {
|
|
71
|
+
if (!adapter || !fields) return fields
|
|
72
|
+
|
|
73
|
+
const translateField = createSelectTranslator(adapter)
|
|
74
|
+
|
|
75
|
+
return fields.map((field) => {
|
|
76
|
+
if (typeof field !== 'string') return field
|
|
77
|
+
if (field === '*') return '*'
|
|
78
|
+
|
|
79
|
+
const aliasMatch = field.match(/\s+as\s+/i)
|
|
80
|
+
if (aliasMatch) {
|
|
81
|
+
const [source, alias] = field.split(/\s+as\s+/i)
|
|
82
|
+
const translatedSource = translateSourceColumn(source.trim(), adapter)
|
|
83
|
+
return `${translatedSource} as ${alias.trim()}`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return translateField(field)
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
25
90
|
const baseAdapter = ({
|
|
26
91
|
knex,
|
|
27
92
|
tableName,
|
|
@@ -29,6 +94,8 @@ const baseAdapter = ({
|
|
|
29
94
|
translateColumn,
|
|
30
95
|
translateFilterValue = identityTranslate,
|
|
31
96
|
applyResourceScope = identityScope,
|
|
97
|
+
toStorageRow = passthrough,
|
|
98
|
+
getFieldValue = (record, fieldName) => record?.[fieldName],
|
|
32
99
|
isCanonical = false,
|
|
33
100
|
}) => {
|
|
34
101
|
const buildBaseQuery = ({ transaction } = {}) => {
|
|
@@ -43,6 +110,8 @@ const baseAdapter = ({
|
|
|
43
110
|
translateColumn,
|
|
44
111
|
translateFilterValue,
|
|
45
112
|
applyResourceScope,
|
|
113
|
+
toStorageRow,
|
|
114
|
+
getFieldValue,
|
|
46
115
|
buildBaseQuery,
|
|
47
116
|
selectColumns: (builder, columns) => selectColumnsOnBuilder(builder, columns),
|
|
48
117
|
}
|
|
@@ -50,9 +119,11 @@ const baseAdapter = ({
|
|
|
50
119
|
|
|
51
120
|
const createLegacyAdapter = ({ knex, schemaInfo }) => {
|
|
52
121
|
const tableName = schemaInfo.tableName
|
|
53
|
-
const idColumn = schemaInfo
|
|
122
|
+
const idColumn = getIdColumn(schemaInfo)
|
|
54
123
|
|
|
55
|
-
const translateColumn = (
|
|
124
|
+
const translateColumn = (field) => getStorageColumn(schemaInfo, field)
|
|
125
|
+
const toStorageRow = (attributes, options = {}) => translateAttributesForStorage(attributes, schemaInfo, options)
|
|
126
|
+
const getFieldValue = (record, fieldName) => getLegacyFieldValue(record, schemaInfo, fieldName)
|
|
56
127
|
|
|
57
128
|
return baseAdapter({
|
|
58
129
|
knex,
|
|
@@ -61,6 +132,8 @@ const createLegacyAdapter = ({ knex, schemaInfo }) => {
|
|
|
61
132
|
translateColumn,
|
|
62
133
|
translateFilterValue: identityTranslate,
|
|
63
134
|
applyResourceScope: identityScope,
|
|
135
|
+
toStorageRow,
|
|
136
|
+
getFieldValue,
|
|
64
137
|
isCanonical: false,
|
|
65
138
|
})
|
|
66
139
|
}
|
|
@@ -74,7 +147,7 @@ const createCanonicalAdapter = ({ knex, schemaInfo }) => {
|
|
|
74
147
|
|
|
75
148
|
const translateColumn = (field) => {
|
|
76
149
|
if (!field) return field
|
|
77
|
-
if (field === 'id') return
|
|
150
|
+
if (field === 'id') return getCanonicalResourceIdColumn(descriptor)
|
|
78
151
|
|
|
79
152
|
const canonicalEntry = canonicalFieldMap[field]
|
|
80
153
|
if (typeof canonicalEntry === 'string') {
|
|
@@ -136,13 +209,18 @@ const createCanonicalAdapter = ({ knex, schemaInfo }) => {
|
|
|
136
209
|
.where(canonical.resourceColumn, descriptor.resource)
|
|
137
210
|
}
|
|
138
211
|
|
|
212
|
+
const toStorageRow = (attributes) => translateCanonicalAttributesForStorage(attributes, descriptor)
|
|
213
|
+
const getFieldValue = (record, fieldName) => getCanonicalFieldValue(record, descriptor, fieldName)
|
|
214
|
+
|
|
139
215
|
return baseAdapter({
|
|
140
216
|
knex,
|
|
141
217
|
tableName: canonical.tableName,
|
|
142
|
-
idColumn:
|
|
218
|
+
idColumn: getCanonicalResourceIdColumn(descriptor),
|
|
143
219
|
translateColumn,
|
|
144
220
|
translateFilterValue,
|
|
145
221
|
applyResourceScope,
|
|
222
|
+
toStorageRow,
|
|
223
|
+
getFieldValue,
|
|
146
224
|
isCanonical: true,
|
|
147
225
|
})
|
|
148
226
|
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
const EMPTY_STORAGE_INFO = Object.freeze({
|
|
2
|
+
idColumn: 'id',
|
|
3
|
+
storage: Object.freeze({
|
|
4
|
+
naming: 'snake_case'
|
|
5
|
+
}),
|
|
6
|
+
fields: Object.freeze({}),
|
|
7
|
+
columns: Object.freeze({})
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const isPlainObject = (value) => (
|
|
11
|
+
value !== null &&
|
|
12
|
+
typeof value === 'object' &&
|
|
13
|
+
!Array.isArray(value)
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
const SNAKE_CASE_STORAGE_NAMING = 'snake_case'
|
|
17
|
+
const EXACT_STORAGE_NAMING = 'exact'
|
|
18
|
+
|
|
19
|
+
const STORAGE_NAMING_ALIASES = Object.freeze({
|
|
20
|
+
snake: SNAKE_CASE_STORAGE_NAMING,
|
|
21
|
+
snake_case: SNAKE_CASE_STORAGE_NAMING,
|
|
22
|
+
snakeCase: SNAKE_CASE_STORAGE_NAMING,
|
|
23
|
+
exact: EXACT_STORAGE_NAMING,
|
|
24
|
+
field: EXACT_STORAGE_NAMING,
|
|
25
|
+
verbatim: EXACT_STORAGE_NAMING
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const toSnakeCase = (value) => String(value)
|
|
29
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
30
|
+
.replace(/([A-Z]+)([A-Z][a-z0-9])/g, '$1_$2')
|
|
31
|
+
.toLowerCase()
|
|
32
|
+
|
|
33
|
+
export const normalizeStorageConfig = (storage = undefined) => {
|
|
34
|
+
if (storage === undefined || storage === null) {
|
|
35
|
+
return {
|
|
36
|
+
naming: SNAKE_CASE_STORAGE_NAMING
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!isPlainObject(storage)) {
|
|
41
|
+
throw new Error('Resource storage metadata must be an object.')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const rawNaming = storage.naming
|
|
45
|
+
const naming = rawNaming === undefined
|
|
46
|
+
? SNAKE_CASE_STORAGE_NAMING
|
|
47
|
+
: STORAGE_NAMING_ALIASES[rawNaming]
|
|
48
|
+
|
|
49
|
+
if (!naming) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Invalid resource storage.naming value '${String(rawNaming)}'. Expected 'snake_case' or 'exact'.`
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
...storage,
|
|
57
|
+
naming
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const resolveDefaultStorageColumn = (fieldName, { idColumn = 'id', storageConfig = undefined } = {}) => {
|
|
62
|
+
if (fieldName === 'id') {
|
|
63
|
+
return idColumn
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const normalizedStorage = normalizeStorageConfig(storageConfig)
|
|
67
|
+
if (normalizedStorage.naming === EXACT_STORAGE_NAMING) {
|
|
68
|
+
return fieldName
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return toSnakeCase(fieldName)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const normalizeFieldStorage = (fieldName, definition = {}, { idColumn = 'id', storageConfig = undefined } = {}) => {
|
|
75
|
+
const fieldStorage = definition.storage
|
|
76
|
+
|
|
77
|
+
if (fieldStorage !== undefined && !isPlainObject(fieldStorage)) {
|
|
78
|
+
throw new Error(`Field '${fieldName}' has invalid storage metadata. Expected an object.`)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const column = fieldStorage?.column ?? resolveDefaultStorageColumn(fieldName, {
|
|
82
|
+
idColumn,
|
|
83
|
+
storageConfig
|
|
84
|
+
})
|
|
85
|
+
if (typeof column !== 'string' || column.trim() === '') {
|
|
86
|
+
throw new Error(`Field '${fieldName}' has an invalid storage.column value.`)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (fieldName === 'id' && column !== idColumn) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Field 'id' must use storage.column '${idColumn}' to match the resource id column.`
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (fieldStorage?.serialize !== undefined && typeof fieldStorage.serialize !== 'function') {
|
|
96
|
+
throw new Error(`Field '${fieldName}' has an invalid storage.serialize value. Expected a function.`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
column,
|
|
101
|
+
serialize: fieldStorage?.serialize || null,
|
|
102
|
+
persisted: definition.virtual !== true && definition.computed !== true
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const buildStorageInfo = ({ schemaStructure = {}, idProperty = 'id', storage = undefined } = {}) => {
|
|
107
|
+
const idColumn = idProperty || 'id'
|
|
108
|
+
const storageConfig = normalizeStorageConfig(storage)
|
|
109
|
+
const fields = {}
|
|
110
|
+
const columns = {}
|
|
111
|
+
const usedColumns = new Map([[idColumn, 'id']])
|
|
112
|
+
|
|
113
|
+
for (const [fieldName, definition] of Object.entries(schemaStructure)) {
|
|
114
|
+
const normalized = normalizeFieldStorage(fieldName, definition, {
|
|
115
|
+
idColumn,
|
|
116
|
+
storageConfig
|
|
117
|
+
})
|
|
118
|
+
const isIdSurrogateField = fieldName === idColumn && definition.type === 'id'
|
|
119
|
+
|
|
120
|
+
if (fieldName !== 'id' && normalized.column === idColumn && !isIdSurrogateField) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Field '${fieldName}' cannot use storage.column '${idColumn}' because it is reserved for the resource id.`
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const existingField = usedColumns.get(normalized.column)
|
|
127
|
+
if (existingField && existingField !== fieldName && !(isIdSurrogateField && existingField === 'id')) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Fields '${existingField}' and '${fieldName}' both map to storage column '${normalized.column}'.`
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
usedColumns.set(normalized.column, fieldName)
|
|
134
|
+
fields[fieldName] = {
|
|
135
|
+
...normalized,
|
|
136
|
+
definition
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (normalized.persisted && !isIdSurrogateField) {
|
|
140
|
+
columns[normalized.column] = fieldName
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
idColumn,
|
|
146
|
+
storage: storageConfig,
|
|
147
|
+
fields,
|
|
148
|
+
columns
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export const getStorageInfo = (schemaInfo = {}) => {
|
|
153
|
+
if (schemaInfo.storageInfo) {
|
|
154
|
+
return schemaInfo.storageInfo
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!schemaInfo.schemaStructure) {
|
|
158
|
+
return EMPTY_STORAGE_INFO
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return buildStorageInfo({
|
|
162
|
+
schemaStructure: schemaInfo.schemaStructure,
|
|
163
|
+
idProperty: schemaInfo.idProperty || 'id',
|
|
164
|
+
storage: schemaInfo.storage
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const getIdColumn = (schemaInfo = {}) => {
|
|
169
|
+
return getStorageInfo(schemaInfo).idColumn || schemaInfo.idProperty || 'id'
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export const getStorageColumn = (schemaInfo = {}, fieldName) => {
|
|
173
|
+
if (!fieldName) return fieldName
|
|
174
|
+
if (fieldName === 'id') return getIdColumn(schemaInfo)
|
|
175
|
+
|
|
176
|
+
const fieldStorage = getStorageInfo(schemaInfo).fields[fieldName]
|
|
177
|
+
return fieldStorage?.column || fieldName
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export const getLogicalFieldName = (schemaInfo = {}, columnName) => {
|
|
181
|
+
if (!columnName) return columnName
|
|
182
|
+
if (columnName === getIdColumn(schemaInfo)) return 'id'
|
|
183
|
+
|
|
184
|
+
const logicalField = getStorageInfo(schemaInfo).columns[columnName]
|
|
185
|
+
return logicalField || columnName
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export const getFieldValue = (record, schemaInfo = {}, fieldName) => {
|
|
189
|
+
if (!record || !fieldName) return undefined
|
|
190
|
+
|
|
191
|
+
if (fieldName === 'id') {
|
|
192
|
+
if (Object.hasOwn(record, 'id')) {
|
|
193
|
+
return record.id
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const idColumn = getIdColumn(schemaInfo)
|
|
197
|
+
if (Object.hasOwn(record, idColumn)) {
|
|
198
|
+
return record[idColumn]
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return translateRecordFromStorage(record, schemaInfo).id
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (Object.hasOwn(record, fieldName)) {
|
|
205
|
+
return record[fieldName]
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const columnName = getStorageColumn(schemaInfo, fieldName)
|
|
209
|
+
if (Object.hasOwn(record, columnName)) {
|
|
210
|
+
return record[columnName]
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return translateRecordFromStorage(record, schemaInfo)[fieldName]
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export const translateAttributesForStorage = (attributes, schemaInfo = {}, options = {}) => {
|
|
217
|
+
if (!attributes) return attributes || {}
|
|
218
|
+
|
|
219
|
+
const { context = null, operation = null } = options
|
|
220
|
+
const storageInfo = getStorageInfo(schemaInfo)
|
|
221
|
+
const computed = schemaInfo.computed || {}
|
|
222
|
+
|
|
223
|
+
return Object.entries(attributes).reduce((translated, [fieldName, value]) => {
|
|
224
|
+
if (fieldName in computed) {
|
|
225
|
+
return translated
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const fieldStorage = storageInfo.fields[fieldName]
|
|
229
|
+
if (fieldStorage && !fieldStorage.persisted) {
|
|
230
|
+
return translated
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const columnName = fieldName === 'id'
|
|
234
|
+
? storageInfo.idColumn
|
|
235
|
+
: fieldStorage?.column || fieldName
|
|
236
|
+
const serializedValue = fieldStorage?.serialize
|
|
237
|
+
? fieldStorage.serialize(value, {
|
|
238
|
+
fieldName,
|
|
239
|
+
columnName,
|
|
240
|
+
definition: fieldStorage.definition,
|
|
241
|
+
schemaInfo,
|
|
242
|
+
context,
|
|
243
|
+
operation
|
|
244
|
+
})
|
|
245
|
+
: value
|
|
246
|
+
|
|
247
|
+
translated[columnName] = serializedValue
|
|
248
|
+
return translated
|
|
249
|
+
}, {})
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export const translateRecordFromStorage = (record, schemaInfo = {}) => {
|
|
253
|
+
if (!record || !schemaInfo) return record
|
|
254
|
+
|
|
255
|
+
const storageInfo = getStorageInfo(schemaInfo)
|
|
256
|
+
const translated = {}
|
|
257
|
+
|
|
258
|
+
for (const [columnName, value] of Object.entries(record)) {
|
|
259
|
+
if (columnName === storageInfo.idColumn) {
|
|
260
|
+
translated.id = value
|
|
261
|
+
continue
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const logicalField = storageInfo.columns[columnName]
|
|
265
|
+
if (logicalField) {
|
|
266
|
+
translated[logicalField] = value
|
|
267
|
+
continue
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
translated[columnName] = value
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return translated
|
|
274
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RestApiResourceError } from '../../../../lib/rest-api-errors.js'
|
|
2
|
-
import {
|
|
2
|
+
import { normalizeRelationshipIdentifiers } from '../querying-writing/resource-id-normalization.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Updates many-to-many relationships intelligently by synchronizing pivot table records
|
|
@@ -103,7 +103,8 @@ import { transformSimplifiedToJsonApi } from '../querying-writing/simplified-hel
|
|
|
103
103
|
export const updateManyToManyRelationship = async (scope, deps) => {
|
|
104
104
|
// Extract values from deps
|
|
105
105
|
const { api, context } = deps
|
|
106
|
-
const { resourceId, relDef,
|
|
106
|
+
const { resourceId, relDef, transaction: trx } = context
|
|
107
|
+
const relData = normalizeRelationshipIdentifiers(context.relData || [], { api })
|
|
107
108
|
|
|
108
109
|
// Get the knex instance from the pivot scope
|
|
109
110
|
const pivotScope = api.resources[relDef.through]
|
|
@@ -257,6 +258,7 @@ export const updateManyToManyRelationship = async (scope, deps) => {
|
|
|
257
258
|
* 6. Returns (no data returned, throws on error)
|
|
258
259
|
*/
|
|
259
260
|
export const createPivotRecords = async (api, resourceId, relDef, relData, trx) => {
|
|
261
|
+
relData = normalizeRelationshipIdentifiers(relData, { api })
|
|
260
262
|
if (relData.length === 0) return // Early exit if nothing to create
|
|
261
263
|
|
|
262
264
|
// Get pivot table info
|