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,265 +0,0 @@
|
|
|
1
|
-
1 /**
|
|
2
|
-
2 * Parses database version string to comparable format
|
|
3
|
-
3 *
|
|
4
|
-
4 * @param {string} versionString - Raw version string from database
|
|
5
|
-
5 * @returns {Object|null} Parsed version with major, minor, patch
|
|
6
|
-
6 *
|
|
7
|
-
7 * @example
|
|
8
|
-
8 * // Input: MySQL version
|
|
9
|
-
9 * parseVersion("8.0.33");
|
|
10
|
-
10 * // Output: { major: 8, minor: 0, patch: 33 }
|
|
11
|
-
11 *
|
|
12
|
-
12 * @example
|
|
13
|
-
13 * // Input: PostgreSQL version with extra info
|
|
14
|
-
14 * parseVersion("14.5 (Ubuntu 14.5-1.pgdg20.04+1)");
|
|
15
|
-
15 * // Output: { major: 14, minor: 5, patch: 0 }
|
|
16
|
-
16 *
|
|
17
|
-
17 * @example
|
|
18
|
-
18 * // Input: MariaDB version
|
|
19
|
-
19 * parseVersion("10.6.12-MariaDB");
|
|
20
|
-
20 * // Output: { major: 10, minor: 6, patch: 12 }
|
|
21
|
-
21 */
|
|
22
|
-
22 const parseVersion = (versionString) => {
|
|
23
|
-
23 // MySQL: "8.0.33"
|
|
24
|
-
24 // PostgreSQL: "14.5 (Ubuntu 14.5-1.pgdg20.04+1)"
|
|
25
|
-
25 // MariaDB: "10.6.12-MariaDB"
|
|
26
|
-
26 // SQLite: "3.35.5"
|
|
27
|
-
27
|
|
28
|
-
28 const match = versionString.match(/(\d+)\.(\d+)(?:\.(\d+))?/)
|
|
29
|
-
29 if (!match) return null
|
|
30
|
-
30
|
|
31
|
-
31 return {
|
|
32
|
-
32 major: parseInt(match[1]),
|
|
33
|
-
33 minor: parseInt(match[2]),
|
|
34
|
-
34 patch: match[3] ? parseInt(match[3]) : 0
|
|
35
|
-
35 }
|
|
36
|
-
36 }
|
|
37
|
-
37
|
|
38
|
-
38 /**
|
|
39
|
-
39 * Checks if the database supports window functions based on version
|
|
40
|
-
40 *
|
|
41
|
-
41 * @param {Object} knex - Knex instance
|
|
42
|
-
42 * @returns {Promise<boolean>} True if window functions are supported
|
|
43
|
-
43 *
|
|
44
|
-
44 * @example
|
|
45
|
-
45 * // Input: MySQL 8.0.33
|
|
46
|
-
46 * const supports = await supportsWindowFunctions(knex);
|
|
47
|
-
47 * // Output: true (MySQL 8.0+ supports window functions)
|
|
48
|
-
48 *
|
|
49
|
-
49 * @example
|
|
50
|
-
50 * // Input: MySQL 5.7.42
|
|
51
|
-
51 * const supports = await supportsWindowFunctions(knex);
|
|
52
|
-
52 * // Output: false (MySQL < 8.0 doesn't support window functions)
|
|
53
|
-
53 *
|
|
54
|
-
54 * @example
|
|
55
|
-
55 * // Input: PostgreSQL (any modern version)
|
|
56
|
-
56 * const supports = await supportsWindowFunctions(knex);
|
|
57
|
-
57 * // Output: true (PostgreSQL 8.4+ supports window functions)
|
|
58
|
-
58 *
|
|
59
|
-
59 * @example
|
|
60
|
-
60 * // Input: SQLite 3.24.0
|
|
61
|
-
61 * const supports = await supportsWindowFunctions(knex);
|
|
62
|
-
62 * // Output: false (SQLite needs 3.25.0+)
|
|
63
|
-
63 *
|
|
64
|
-
64 * @description
|
|
65
|
-
65 * Used by:
|
|
66
|
-
66 * - buildWindowedIncludeQuery to validate before using window functions
|
|
67
|
-
67 * - rest-api-knex-plugin to determine query strategies
|
|
68
|
-
68 *
|
|
69
|
-
69 * Purpose:
|
|
70
|
-
70 * - Prevents runtime errors by checking feature support
|
|
71
|
-
71 * - Enables fallback strategies for older databases
|
|
72
|
-
72 * - Provides clear error messages about version requirements
|
|
73
|
-
73 *
|
|
74
|
-
74 * Data flow:
|
|
75
|
-
75 * 1. Identifies database client type from Knex config
|
|
76
|
-
76 * 2. For version-dependent databases, queries version string
|
|
77
|
-
77 * 3. Parses version and compares against known thresholds
|
|
78
|
-
78 * 4. Returns boolean indicating support
|
|
79
|
-
79 * 5. Falls back to false if version can't be determined
|
|
80
|
-
80 *
|
|
81
|
-
81 * Version requirements:
|
|
82
|
-
82 * - PostgreSQL: 8.4+ (all modern versions)
|
|
83
|
-
83 * - MySQL: 8.0+
|
|
84
|
-
84 * - MariaDB: 10.2+
|
|
85
|
-
85 * - SQLite: 3.25.0+
|
|
86
|
-
86 * - SQL Server: 2005+ (all versions)
|
|
87
|
-
87 * - Oracle: Supported (long-standing feature)
|
|
88
|
-
88 */
|
|
89
|
-
89 export const supportsWindowFunctions = async (knex) => {
|
|
90
|
-
90 try {
|
|
91
|
-
91 const client = knex.client.config.client
|
|
92
|
-
92
|
|
93
|
-
93 let versionString
|
|
94
|
-
94
|
|
95
|
-
95 switch (client) {
|
|
96
|
-
96 case 'pg':
|
|
97
|
-
97 case 'postgresql':
|
|
98
|
-
98 // PostgreSQL 8.4+ supports window functions (all modern versions)
|
|
99
|
-
99 return true
|
|
100
|
-
100
|
|
101
|
-
101 case 'mysql':
|
|
102
|
-
102 case 'mysql2':
|
|
103
|
-
103 try {
|
|
104
|
-
104 const row = await knex.first(knex.raw('VERSION() as version'))
|
|
105
|
-
105 const mysqlVersion = parseVersion(row.version)
|
|
106
|
-
106
|
|
107
|
-
107 return mysqlVersion && mysqlVersion.major >= 8
|
|
108
|
-
108 } catch (dbError) {
|
|
109
|
-
109 console.warn(`[supportsWindowFunctions] Failed to get MySQL version: ${dbError.message}`)
|
|
110
|
-
110 return false
|
|
111
|
-
111 }
|
|
112
|
-
112
|
|
113
|
-
113 case 'sqlite3':
|
|
114
|
-
114 case 'better-sqlite3':
|
|
115
|
-
115 try {
|
|
116
|
-
116 versionString = await knex.raw('SELECT sqlite_version() as version')
|
|
117
|
-
117 const sqliteVersion = versionString[0].version
|
|
118
|
-
118 // SQLite 3.25.0+ supports window functions
|
|
119
|
-
119 const parsed = parseVersion(sqliteVersion)
|
|
120
|
-
120 return parsed && (parsed.major > 3 || (parsed.major === 3 && parsed.minor >= 25))
|
|
121
|
-
121 } catch (dbError) {
|
|
122
|
-
122 console.warn(`[supportsWindowFunctions] Failed to get SQLite version: ${dbError.message}`)
|
|
123
|
-
123 return false
|
|
124
|
-
124 }
|
|
125
|
-
125
|
|
126
|
-
126 case 'mssql':
|
|
127
|
-
127 // SQL Server 2005+ supports window functions
|
|
128
|
-
128 return true
|
|
129
|
-
129
|
|
130
|
-
130 case 'oracledb':
|
|
131
|
-
131 // Oracle has supported window functions for a long time
|
|
132
|
-
132 return true
|
|
133
|
-
133
|
|
134
|
-
134 default:
|
|
135
|
-
135 // For MariaDB, we need to check if it's actually MariaDB or MySQL
|
|
136
|
-
136 if (client.includes('maria')) {
|
|
137
|
-
137 try {
|
|
138
|
-
138 versionString = await knex.raw('SELECT VERSION() as version')
|
|
139
|
-
139 const version = versionString[0].version.toLowerCase()
|
|
140
|
-
140 if (version.includes('mariadb')) {
|
|
141
|
-
141 const mariaVersion = parseVersion(version)
|
|
142
|
-
142 // MariaDB 10.2+ supports window functions
|
|
143
|
-
143 return mariaVersion && (mariaVersion.major > 10 ||
|
|
144
|
-
144 (mariaVersion.major === 10 && mariaVersion.minor >= 2))
|
|
145
|
-
145 }
|
|
146
|
-
146 } catch (dbError) {
|
|
147
|
-
147 console.warn(`[supportsWindowFunctions] Failed to get MariaDB version: ${dbError.message}`)
|
|
148
|
-
148 return false
|
|
149
|
-
149 }
|
|
150
|
-
150 }
|
|
151
|
-
151 return false
|
|
152
|
-
152 }
|
|
153
|
-
153 } catch (error) {
|
|
154
|
-
154 // Log error with context
|
|
155
|
-
155 console.error('[supportsWindowFunctions] Unexpected error checking database capabilities:', {
|
|
156
|
-
156 error: error.message,
|
|
157
|
-
157 stack: error.stack,
|
|
158
|
-
158 client: knex?.client?.config?.client || 'unknown'
|
|
159
|
-
159 })
|
|
160
|
-
160 // If we can't determine, assume no support for safety
|
|
161
|
-
161 return false
|
|
162
|
-
162 }
|
|
163
|
-
163 }
|
|
164
|
-
164
|
|
165
|
-
165 /**
|
|
166
|
-
166 * Gets database info for error messages and capability checks
|
|
167
|
-
167 *
|
|
168
|
-
168 * @param {Object} knex - Knex instance
|
|
169
|
-
169 * @returns {Promise<Object>} Database info with client and version
|
|
170
|
-
170 *
|
|
171
|
-
171 * @example
|
|
172
|
-
172 * // Input: MySQL connection
|
|
173
|
-
173 * const info = await getDatabaseInfo(knex);
|
|
174
|
-
174 * // Output: { client: 'MySQL', version: '8.0.33' }
|
|
175
|
-
175 *
|
|
176
|
-
176 * @example
|
|
177
|
-
177 * // Input: PostgreSQL connection
|
|
178
|
-
178 * const info = await getDatabaseInfo(knex);
|
|
179
|
-
179 * // Output: {
|
|
180
|
-
180 * // client: 'PostgreSQL',
|
|
181
|
-
181 * // version: 'PostgreSQL 14.5 (Ubuntu 14.5-1.pgdg20.04+1)'
|
|
182
|
-
182 * // }
|
|
183
|
-
183 *
|
|
184
|
-
184 * @example
|
|
185
|
-
185 * // Input: Connection error case
|
|
186
|
-
186 * const info = await getDatabaseInfo(knexWithError);
|
|
187
|
-
187 * // Output: {
|
|
188
|
-
188 * // client: 'MySQL',
|
|
189
|
-
189 * // version: 'unknown',
|
|
190
|
-
190 * // error: 'Connection refused'
|
|
191
|
-
191 * // }
|
|
192
|
-
192 *
|
|
193
|
-
193 * @description
|
|
194
|
-
194 * Used by:
|
|
195
|
-
195 * - Error messages to show which database/version lacks a feature
|
|
196
|
-
196 * - Capability detection for feature flags
|
|
197
|
-
197 * - Debugging connection issues
|
|
198
|
-
198 *
|
|
199
|
-
199 * Purpose:
|
|
200
|
-
200 * - Provides human-readable database identification
|
|
201
|
-
201 * - Helps users understand version requirements
|
|
202
|
-
202 * - Enables detailed error messages
|
|
203
|
-
203 * - Gracefully handles query failures
|
|
204
|
-
204 *
|
|
205
|
-
205 * Data flow:
|
|
206
|
-
206 * 1. Identifies client type from Knex config
|
|
207
|
-
207 * 2. Runs version query specific to each database
|
|
208
|
-
208 * 3. Returns formatted info object
|
|
209
|
-
209 * 4. Includes error details if version query fails
|
|
210
|
-
210 * 5. Falls back to 'unknown' for unsupported databases
|
|
211
|
-
211 */
|
|
212
|
-
212 export const getDatabaseInfo = async (knex) => {
|
|
213
|
-
213 try {
|
|
214
|
-
214 const client = knex.client.config.client
|
|
215
|
-
215
|
|
216
|
-
216 let versionString
|
|
217
|
-
217 switch (client) {
|
|
218
|
-
218 case 'mysql':
|
|
219
|
-
219 case 'mysql2':
|
|
220
|
-
220 try {
|
|
221
|
-
221 const row = await knex.first(knex.raw('VERSION() as version'))
|
|
222
|
-
222 return { client: 'MySQL', version: row.version }
|
|
223
|
-
223 } catch (dbError) {
|
|
224
|
-
224 console.warn(`[getDatabaseInfo] Failed to get MySQL version: ${dbError.message}`)
|
|
225
|
-
225 return { client: 'MySQL', version: 'unknown', error: dbError.message }
|
|
226
|
-
226 }
|
|
227
|
-
227
|
|
228
|
-
228 case 'sqlite3':
|
|
229
|
-
229 case 'better-sqlite3':
|
|
230
|
-
230 try {
|
|
231
|
-
231 versionString = await knex.raw('SELECT sqlite_version() as version')
|
|
232
|
-
232 return { client: 'SQLite', version: versionString[0].version }
|
|
233
|
-
233 } catch (dbError) {
|
|
234
|
-
234 console.warn(`[getDatabaseInfo] Failed to get SQLite version: ${dbError.message}`)
|
|
235
|
-
235 return { client: 'SQLite', version: 'unknown', error: dbError.message }
|
|
236
|
-
236 }
|
|
237
|
-
237
|
|
238
|
-
238 case 'pg':
|
|
239
|
-
239 case 'postgresql':
|
|
240
|
-
240 try {
|
|
241
|
-
241 versionString = await knex.raw('SELECT version() as version')
|
|
242
|
-
242 return { client: 'PostgreSQL', version: versionString.rows[0].version }
|
|
243
|
-
243 } catch (dbError) {
|
|
244
|
-
244 console.warn(`[getDatabaseInfo] Failed to get PostgreSQL version: ${dbError.message}`)
|
|
245
|
-
245 return { client: 'PostgreSQL', version: 'unknown', error: dbError.message }
|
|
246
|
-
246 }
|
|
247
|
-
247
|
|
248
|
-
248 default:
|
|
249
|
-
249 return { client, version: 'unknown' }
|
|
250
|
-
250 }
|
|
251
|
-
251 } catch (error) {
|
|
252
|
-
252 // Log unexpected errors
|
|
253
|
-
253 console.error('[getDatabaseInfo] Unexpected error getting database info:', {
|
|
254
|
-
254 error: error.message,
|
|
255
|
-
255 stack: error.stack,
|
|
256
|
-
256 client: knex?.client?.config?.client || 'unknown'
|
|
257
|
-
257 })
|
|
258
|
-
258
|
|
259
|
-
259 return {
|
|
260
|
-
260 client: knex?.client?.config?.client || 'unknown',
|
|
261
|
-
261 version: 'unknown',
|
|
262
|
-
262 error: error.message
|
|
263
|
-
263 }
|
|
264
|
-
264 }
|
|
265
|
-
265 }
|
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
1 /**
|
|
2
|
-
2 * Normalizes a date value to appropriate format based on field type
|
|
3
|
-
3 *
|
|
4
|
-
4 * @param {*} value - The date value from the database
|
|
5
|
-
5 * @param {string} type - The field type ('date', 'dateTime', or 'time')
|
|
6
|
-
6 * @returns {Date|string|null} Date object for date/dateTime, string for time, or null
|
|
7
|
-
7 *
|
|
8
|
-
8 * @example
|
|
9
|
-
9 * // Input: Already a Date object
|
|
10
|
-
10 * normalizeDateValue(new Date('2024-01-15T10:30:00Z'), 'dateTime');
|
|
11
|
-
11 * // Output: Date object (unchanged)
|
|
12
|
-
12 *
|
|
13
|
-
13 * @example
|
|
14
|
-
14 * // Input: MySQL datetime string without timezone
|
|
15
|
-
15 * normalizeDateValue('2024-01-15 10:30:00', 'dateTime');
|
|
16
|
-
16 * // Output: Date object parsed as UTC (2024-01-15T10:30:00Z)
|
|
17
|
-
17 *
|
|
18
|
-
18 * @example
|
|
19
|
-
19 * // Input: Date-only string
|
|
20
|
-
20 * normalizeDateValue('2024-01-15', 'date');
|
|
21
|
-
21 * // Output: Date object at UTC midnight (2024-01-15T00:00:00Z)
|
|
22
|
-
22 *
|
|
23
|
-
23 * @example
|
|
24
|
-
24 * // Input: Time field (always returns string)
|
|
25
|
-
25 * normalizeDateValue('14:30:45', 'time');
|
|
26
|
-
26 * // Output: "14:30:45" (string)
|
|
27
|
-
27 *
|
|
28
|
-
28 * @example
|
|
29
|
-
29 * // Input: Date object for time field
|
|
30
|
-
30 * normalizeDateValue(new Date('2024-01-15T14:30:45Z'), 'time');
|
|
31
|
-
31 * // Output: "14:30:45" (extracts time portion)
|
|
32
|
-
32 *
|
|
33
|
-
33 * @description
|
|
34
|
-
34 * Used by:
|
|
35
|
-
35 * - normalizeAttributes to process individual field values
|
|
36
|
-
36 * - Applied to all date/time fields fetched from database
|
|
37
|
-
37 *
|
|
38
|
-
38 * Purpose:
|
|
39
|
-
39 * - Handles database-specific date formats (especially MySQL)
|
|
40
|
-
40 * - Prevents timezone shifts for date-only fields
|
|
41
|
-
41 * - Ensures consistent Date objects across databases
|
|
42
|
-
42 * - Keeps time fields as HH:MM:SS strings
|
|
43
|
-
43 *
|
|
44
|
-
44 * Data flow:
|
|
45
|
-
45 * 1. Checks for null/undefined (returns null)
|
|
46
|
-
46 * 2. For time fields: ensures HH:MM:SS string format
|
|
47
|
-
47 * 3. For date/dateTime: converts to Date objects
|
|
48
|
-
48 * 4. Handles MySQL datetime format (no T separator)
|
|
49
|
-
49 * 5. Forces UTC interpretation to prevent timezone issues
|
|
50
|
-
50 */
|
|
51
|
-
51 function normalizeDateValue (value, type) {
|
|
52
|
-
52 // Handle null/undefined
|
|
53
|
-
53 if (value === null || value === undefined) {
|
|
54
|
-
54 return null
|
|
55
|
-
55 }
|
|
56
|
-
56
|
|
57
|
-
57 // Handle time type specially - always return as string
|
|
58
|
-
58 if (type === 'time') {
|
|
59
|
-
59 // If it's already a properly formatted time string, return as-is
|
|
60
|
-
60 if (typeof value === 'string' && /^\d{2}:\d{2}:\d{2}/.test(value)) {
|
|
61
|
-
61 return value
|
|
62
|
-
62 }
|
|
63
|
-
63 // If we got a Date object for a time field, extract the time portion
|
|
64
|
-
64 if (value instanceof Date) {
|
|
65
|
-
65 return value.toISOString().slice(11, 19) // Extract HH:MM:SS
|
|
66
|
-
66 }
|
|
67
|
-
67 // Try to parse and extract time
|
|
68
|
-
68 if (typeof value === 'string' || typeof value === 'number') {
|
|
69
|
-
69 const d = new Date(value)
|
|
70
|
-
70 if (!isNaN(d.getTime())) {
|
|
71
|
-
71 return d.toISOString().slice(11, 19)
|
|
72
|
-
72 }
|
|
73
|
-
73 }
|
|
74
|
-
74 return null
|
|
75
|
-
75 }
|
|
76
|
-
76
|
|
77
|
-
77 // For date and dateTime: Already a Date object? Return as-is
|
|
78
|
-
78 if (value instanceof Date) {
|
|
79
|
-
79 return value
|
|
80
|
-
80 }
|
|
81
|
-
81
|
|
82
|
-
82 // Handle string values
|
|
83
|
-
83 if (typeof value === 'string') {
|
|
84
|
-
84 // Detect MySQL datetime format: 'YYYY-MM-DD HH:MM:SS'
|
|
85
|
-
85 // These have no T separator and no timezone indicator
|
|
86
|
-
86 const isMySQLDateTime = type === 'dateTime' &&
|
|
87
|
-
87 /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(value) &&
|
|
88
|
-
88 !value.includes('T') &&
|
|
89
|
-
89 !value.includes('Z') &&
|
|
90
|
-
90 !value.includes('+') &&
|
|
91
|
-
91 !value.includes('-', 10) // Don't match date separators
|
|
92
|
-
92
|
|
93
|
-
93 if (isMySQLDateTime) {
|
|
94
|
-
94 // Convert to ISO format and force UTC interpretation
|
|
95
|
-
95 // '2024-01-15 10:30:00' becomes '2024-01-15T10:30:00Z'
|
|
96
|
-
96 return new Date(value.replace(' ', 'T') + 'Z')
|
|
97
|
-
97 }
|
|
98
|
-
98
|
|
99
|
-
99 // For date-only fields, ensure parsing at UTC midnight
|
|
100
|
-
100 // This prevents timezone shifts when parsing dates
|
|
101
|
-
101 if (type === 'date' && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
102
|
-
102 return new Date(value + 'T00:00:00Z')
|
|
103
|
-
103 }
|
|
104
|
-
104 }
|
|
105
|
-
105
|
|
106
|
-
106 // Handle numeric values (Unix timestamps)
|
|
107
|
-
107 if (typeof value === 'number') {
|
|
108
|
-
108 return new Date(value)
|
|
109
|
-
109 }
|
|
110
|
-
110
|
|
111
|
-
111 // Try to parse any other format
|
|
112
|
-
112 const dateObj = new Date(value)
|
|
113
|
-
113
|
|
114
|
-
114 // Check if date is valid
|
|
115
|
-
115 if (isNaN(dateObj.getTime())) {
|
|
116
|
-
116 console.warn(`Invalid date value for field type '${type}': ${value}`)
|
|
117
|
-
117 return null // Return null for invalid dates
|
|
118
|
-
118 }
|
|
119
|
-
119
|
|
120
|
-
120 return dateObj
|
|
121
|
-
121 }
|
|
122
|
-
122
|
|
123
|
-
123 /**
|
|
124
|
-
124 * Normalizes database values in an attributes object
|
|
125
|
-
125 *
|
|
126
|
-
126 * @param {Object} attributes - The attributes object to normalize
|
|
127
|
-
127 * @param {Object} schemaStructure - The schema structure defining field types
|
|
128
|
-
128 * @returns {Object} The normalized attributes object
|
|
129
|
-
129 *
|
|
130
|
-
130 * @example
|
|
131
|
-
131 * // Input: Raw database values
|
|
132
|
-
132 * const attributes = {
|
|
133
|
-
133 * id: 1,
|
|
134
|
-
134 * is_active: 1, // Boolean as 0/1
|
|
135
|
-
135 * created_at: '2024-01-15 10:30:00', // MySQL datetime
|
|
136
|
-
136 * birth_date: '1990-05-20', // Date string
|
|
137
|
-
137 * shift_time: '09:00:00' // Time string
|
|
138
|
-
138 * };
|
|
139
|
-
139 *
|
|
140
|
-
140 * const schema = {
|
|
141
|
-
141 * is_active: { type: 'boolean' },
|
|
142
|
-
142 * created_at: { type: 'dateTime' },
|
|
143
|
-
143 * birth_date: { type: 'date' },
|
|
144
|
-
144 * shift_time: { type: 'time' }
|
|
145
|
-
145 * };
|
|
146
|
-
146 *
|
|
147
|
-
147 * const normalized = normalizeAttributes(attributes, schema);
|
|
148
|
-
148 *
|
|
149
|
-
149 * // Output: Properly typed values
|
|
150
|
-
150 * // {
|
|
151
|
-
151 * // id: 1,
|
|
152
|
-
152 * // is_active: true, // 1 → true
|
|
153
|
-
153 * // created_at: Date('2024-01-15T10:30:00Z'), // Date object
|
|
154
|
-
154 * // birth_date: Date('1990-05-20T00:00:00Z'), // Date at midnight UTC
|
|
155
|
-
155 * // shift_time: '09:00:00' // String unchanged
|
|
156
|
-
156 * // }
|
|
157
|
-
157 *
|
|
158
|
-
158 * @example
|
|
159
|
-
159 * // Input: Boolean edge cases
|
|
160
|
-
160 * const attributes = {
|
|
161
|
-
161 * flag1: 0, // Number 0
|
|
162
|
-
162 * flag2: '1', // String '1'
|
|
163
|
-
163 * flag3: true, // Already boolean
|
|
164
|
-
164 * flag4: null // Null value
|
|
165
|
-
165 * };
|
|
166
|
-
166 *
|
|
167
|
-
167 * const normalized = normalizeAttributes(attributes, {
|
|
168
|
-
168 * flag1: { type: 'boolean' },
|
|
169
|
-
169 * flag2: { type: 'boolean' },
|
|
170
|
-
170 * flag3: { type: 'boolean' },
|
|
171
|
-
171 * flag4: { type: 'boolean' }
|
|
172
|
-
172 * });
|
|
173
|
-
173 *
|
|
174
|
-
174 * // Output:
|
|
175
|
-
175 * // {
|
|
176
|
-
176 * // flag1: false, // 0 → false
|
|
177
|
-
177 * // flag2: true, // '1' → true
|
|
178
|
-
178 * // flag3: true, // unchanged
|
|
179
|
-
179 * // flag4: null // null preserved
|
|
180
|
-
180 * // }
|
|
181
|
-
181 *
|
|
182
|
-
182 * @description
|
|
183
|
-
183 * Used by:
|
|
184
|
-
184 * - normalizeRecordAttributes for each record's attributes
|
|
185
|
-
185 * - Applied after fetching data from database
|
|
186
|
-
186 *
|
|
187
|
-
187 * Purpose:
|
|
188
|
-
188 * - Handles databases without native boolean support (SQLite, older MySQL)
|
|
189
|
-
189 * - Normalizes date formats from different databases
|
|
190
|
-
190 * - Ensures consistent data types in API responses
|
|
191
|
-
191 * - Preserves null values appropriately
|
|
192
|
-
192 *
|
|
193
|
-
193 * Data flow:
|
|
194
|
-
194 * 1. Creates copy of attributes to avoid mutation
|
|
195
|
-
195 * 2. Iterates through each field with schema definition
|
|
196
|
-
196 * 3. Normalizes booleans: 1/0 or '1'/'0' to true/false
|
|
197
|
-
197 * 4. Normalizes dates using normalizeDateValue
|
|
198
|
-
198 * 5. Returns new object with normalized values
|
|
199
|
-
199 */
|
|
200
|
-
200 export function normalizeAttributes (attributes, schemaStructure) {
|
|
201
|
-
201 if (!attributes || !schemaStructure) {
|
|
202
|
-
202 return attributes
|
|
203
|
-
203 }
|
|
204
|
-
204
|
|
205
|
-
205 const normalized = { ...attributes }
|
|
206
|
-
206
|
|
207
|
-
207 // Iterate through each attribute
|
|
208
|
-
208 for (const [fieldName, value] of Object.entries(attributes)) {
|
|
209
|
-
209 const fieldDef = schemaStructure[fieldName]
|
|
210
|
-
210
|
|
211
|
-
211 // Skip if no field definition found
|
|
212
|
-
212 if (!fieldDef) continue
|
|
213
|
-
213
|
|
214
|
-
214 // Normalize boolean values
|
|
215
|
-
215 if (fieldDef.type === 'boolean') {
|
|
216
|
-
216 if (value === 1 || value === '1') {
|
|
217
|
-
217 normalized[fieldName] = true
|
|
218
|
-
218 } else if (value === 0 || value === '0') {
|
|
219
|
-
219 normalized[fieldName] = false
|
|
220
|
-
220 }
|
|
221
|
-
221 // null/undefined remain as-is
|
|
222
|
-
222 }
|
|
223
|
-
223
|
|
224
|
-
224 // Normalize date/dateTime/time values
|
|
225
|
-
225 if (fieldDef.type === 'date' || fieldDef.type === 'dateTime' || fieldDef.type === 'time') {
|
|
226
|
-
226 normalized[fieldName] = normalizeDateValue(value, fieldDef.type)
|
|
227
|
-
227 }
|
|
228
|
-
228 }
|
|
229
|
-
229
|
|
230
|
-
230 return normalized
|
|
231
|
-
231 }
|
|
232
|
-
232
|
|
233
|
-
233 /**
|
|
234
|
-
234 * Normalizes all records in a JSON:API response
|
|
235
|
-
235 *
|
|
236
|
-
236 * @param {Object} record - The JSON:API response object
|
|
237
|
-
237 * @param {Object} scopes - All available scopes for schema lookup
|
|
238
|
-
238 * @returns {Object} The response with normalized values
|
|
239
|
-
239 *
|
|
240
|
-
240 * @example
|
|
241
|
-
241 * // Input: JSON:API response with various data types
|
|
242
|
-
242 * const response = {
|
|
243
|
-
243 * data: {
|
|
244
|
-
244 * type: 'articles',
|
|
245
|
-
245 * id: '1',
|
|
246
|
-
246 * attributes: {
|
|
247
|
-
247 * title: 'My Article',
|
|
248
|
-
248 * is_published: 1, // Boolean as number
|
|
249
|
-
249 * published_at: '2024-01-15 10:00:00' // MySQL datetime
|
|
250
|
-
250 * }
|
|
251
|
-
251 * },
|
|
252
|
-
252 * included: [{
|
|
253
|
-
253 * type: 'users',
|
|
254
|
-
254 * id: '10',
|
|
255
|
-
255 * attributes: {
|
|
256
|
-
256 * name: 'John',
|
|
257
|
-
257 * is_admin: 0, // Boolean as number
|
|
258
|
-
258 * last_login: '2024-01-14 15:30:00'
|
|
259
|
-
259 * }
|
|
260
|
-
260 * }]
|
|
261
|
-
261 * };
|
|
262
|
-
262 *
|
|
263
|
-
263 * const normalized = normalizeRecordAttributes(response, scopes);
|
|
264
|
-
264 *
|
|
265
|
-
265 * // Output: All booleans and dates normalized
|
|
266
|
-
266 * // {
|
|
267
|
-
267 * // data: {
|
|
268
|
-
268 * // type: 'articles',
|
|
269
|
-
269 * // id: '1',
|
|
270
|
-
270 * // attributes: {
|
|
271
|
-
271 * // title: 'My Article',
|
|
272
|
-
272 * // is_published: true, // 1 → true
|
|
273
|
-
273 * // published_at: Date('2024-01-15T10:00:00Z') // Date object
|
|
274
|
-
274 * // }
|
|
275
|
-
275 * // },
|
|
276
|
-
276 * // included: [{
|
|
277
|
-
277 * // type: 'users',
|
|
278
|
-
278 * // id: '10',
|
|
279
|
-
279 * // attributes: {
|
|
280
|
-
280 * // name: 'John',
|
|
281
|
-
281 * // is_admin: false, // 0 → false
|
|
282
|
-
282 * // last_login: Date('2024-01-14T15:30:00Z') // Date object
|
|
283
|
-
283 * // }
|
|
284
|
-
284 * // }]
|
|
285
|
-
285 * // }
|
|
286
|
-
286 *
|
|
287
|
-
287 * @example
|
|
288
|
-
288 * // Input: Collection response (array of records)
|
|
289
|
-
289 * const response = {
|
|
290
|
-
290 * data: [
|
|
291
|
-
291 * {
|
|
292
|
-
292 * type: 'comments',
|
|
293
|
-
293 * id: '1',
|
|
294
|
-
294 * attributes: { approved: 1, created_at: '2024-01-01 09:00:00' }
|
|
295
|
-
295 * },
|
|
296
|
-
296 * {
|
|
297
|
-
297 * type: 'comments',
|
|
298
|
-
298 * id: '2',
|
|
299
|
-
299 * attributes: { approved: 0, created_at: '2024-01-02 10:00:00' }
|
|
300
|
-
300 * }
|
|
301
|
-
301 * ]
|
|
302
|
-
302 * };
|
|
303
|
-
303 *
|
|
304
|
-
304 * // Each record in the array is normalized
|
|
305
|
-
305 *
|
|
306
|
-
306 * @description
|
|
307
|
-
307 * Used by:
|
|
308
|
-
308 * - rest-api-knex-plugin after fetching data, before sending response
|
|
309
|
-
309 * - Applied to both GET (single) and QUERY (collection) responses
|
|
310
|
-
310 *
|
|
311
|
-
311 * Purpose:
|
|
312
|
-
312 * - Ensures consistent data types in API responses
|
|
313
|
-
313 * - Handles both primary data and included resources
|
|
314
|
-
314 * - Works with single records and collections
|
|
315
|
-
315 * - Uses schema definitions to determine field types
|
|
316
|
-
316 *
|
|
317
|
-
317 * Data flow:
|
|
318
|
-
318 * 1. Checks if data is array (collection) or object (single)
|
|
319
|
-
319 * 2. For each record, looks up its schema by type
|
|
320
|
-
320 * 3. Calls normalizeAttributes with schema structure
|
|
321
|
-
321 * 4. Processes included array the same way
|
|
322
|
-
322 * 5. Returns complete response with normalized values
|
|
323
|
-
323 */
|
|
324
|
-
324 export function normalizeRecordAttributes (record, scopes) {
|
|
325
|
-
325 if (!record || !scopes) {
|
|
326
|
-
326 return record
|
|
327
|
-
327 }
|
|
328
|
-
328
|
|
329
|
-
329 // Normalize main data records
|
|
330
|
-
330 if (record.data) {
|
|
331
|
-
331 if (Array.isArray(record.data)) {
|
|
332
|
-
332 // Handle array of records (query result)
|
|
333
|
-
333 for (const entry of record.data) {
|
|
334
|
-
334 const scope = scopes[entry.type]
|
|
335
|
-
335 if (scope?.vars?.schemaInfo?.schemaStructure && entry.attributes) {
|
|
336
|
-
336 entry.attributes = normalizeAttributes(
|
|
337
|
-
337 entry.attributes,
|
|
338
|
-
338 scope.vars.schemaInfo.schemaStructure
|
|
339
|
-
339 )
|
|
340
|
-
340 }
|
|
341
|
-
341 }
|
|
342
|
-
342 } else {
|
|
343
|
-
343 // Handle single record (get result)
|
|
344
|
-
344 const entry = record.data
|
|
345
|
-
345 const scope = scopes[entry.type]
|
|
346
|
-
346 if (scope?.vars?.schemaInfo?.schemaStructure && entry.attributes) {
|
|
347
|
-
347 entry.attributes = normalizeAttributes(
|
|
348
|
-
348 entry.attributes,
|
|
349
|
-
349 scope.vars.schemaInfo.schemaStructure
|
|
350
|
-
350 )
|
|
351
|
-
351 }
|
|
352
|
-
352 }
|
|
353
|
-
353 }
|
|
354
|
-
354
|
|
355
|
-
355 // Normalize included records
|
|
356
|
-
356 if (record.included && Array.isArray(record.included)) {
|
|
357
|
-
357 for (const entry of record.included) {
|
|
358
|
-
358 const scope = scopes[entry.type]
|
|
359
|
-
359 if (scope?.vars?.schemaInfo?.schemaStructure && entry.attributes) {
|
|
360
|
-
360 entry.attributes = normalizeAttributes(
|
|
361
|
-
361 entry.attributes,
|
|
362
|
-
362 scope.vars.schemaInfo.schemaStructure
|
|
363
|
-
363 )
|
|
364
|
-
364 }
|
|
365
|
-
365 }
|
|
366
|
-
366 }
|
|
367
|
-
367
|
|
368
|
-
368 return record
|
|
369
|
-
369 }
|