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,19 +1,475 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @file Creates Knex table definitions from json-rest-schema definitions
|
|
2
|
+
* @file Creates Knex table definitions and migration plans from json-rest-schema definitions
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
import { buildStorageInfo } from './storage/storage-mapping.js'
|
|
6
|
+
|
|
7
|
+
const MYSQL_DIALECT_PATTERN = /mysql/i
|
|
8
|
+
|
|
9
|
+
function normalizeText (value) {
|
|
10
|
+
if (value == null) return ''
|
|
11
|
+
return String(value).trim()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function quoteJsString (value) {
|
|
15
|
+
const escaped = String(value)
|
|
16
|
+
.replace(/\\/g, '\\\\')
|
|
17
|
+
.replace(/'/g, "\\'")
|
|
18
|
+
.replace(/\r/g, '\\r')
|
|
19
|
+
.replace(/\n/g, '\\n')
|
|
20
|
+
|
|
21
|
+
return `'${escaped}'`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function formatArrayLiteral (values = []) {
|
|
25
|
+
return `[${values.map((value) => quoteJsString(value)).join(', ')}]`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function formatPlainObjectLiteral (value) {
|
|
29
|
+
const entries = Object.entries(value).map(([key, entryValue]) => {
|
|
30
|
+
return `${JSON.stringify(key)}: ${formatCodeLiteral(entryValue)}`
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
return `{ ${entries.join(', ')} }`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function formatCodeLiteral (value) {
|
|
37
|
+
if (value === undefined) {
|
|
38
|
+
throw new Error('Cannot generate a migration literal from undefined.')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeof value === 'function') {
|
|
42
|
+
throw new Error('Cannot generate deterministic migrations for function defaults.')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (typeof value === 'string') {
|
|
46
|
+
return quoteJsString(value)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (typeof value === 'number' || typeof value === 'bigint') {
|
|
50
|
+
return String(value)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (typeof value === 'boolean') {
|
|
54
|
+
return value ? 'true' : 'false'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (value === null) {
|
|
58
|
+
return 'null'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (Array.isArray(value)) {
|
|
62
|
+
return `[${value.map((entry) => formatCodeLiteral(entry)).join(', ')}]`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (typeof value === 'object') {
|
|
66
|
+
return formatPlainObjectLiteral(value)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return JSON.stringify(value)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function detectKnexDialect (knex) {
|
|
73
|
+
return normalizeText(
|
|
74
|
+
knex?.client?.config?.client ||
|
|
75
|
+
knex?.client?.dialect ||
|
|
76
|
+
knex?.client?.driverName
|
|
77
|
+
).toLowerCase()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isMysqlDialect (dialect = '') {
|
|
81
|
+
return MYSQL_DIALECT_PATTERN.test(String(dialect || ''))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function ensureStringArray (value, label) {
|
|
85
|
+
const values = Array.isArray(value) ? value : [value]
|
|
86
|
+
const normalized = values
|
|
87
|
+
.map((entry) => normalizeText(entry))
|
|
88
|
+
.filter(Boolean)
|
|
89
|
+
|
|
90
|
+
if (normalized.length < 1) {
|
|
91
|
+
throw new Error(`${label} must define at least one column.`)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return normalized
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function buildSetColumnType (values = []) {
|
|
98
|
+
const normalized = values.map((value) => normalizeText(value)).filter(Boolean)
|
|
99
|
+
if (normalized.length < 1) {
|
|
100
|
+
throw new Error('setValues must contain at least one value.')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return `set(${normalized.map((value) => quoteJsString(value)).join(', ')})`
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function createTableBuilderContext (schemaStructure = {}, idColumn = 'id', storage = undefined) {
|
|
107
|
+
return {
|
|
108
|
+
schemaStructure,
|
|
109
|
+
idProperty: idColumn,
|
|
110
|
+
storageInfo: buildStorageInfo({
|
|
111
|
+
schemaStructure,
|
|
112
|
+
idProperty: idColumn,
|
|
113
|
+
storage
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function resolveFieldOrColumnName (tableContext, fieldOrColumn) {
|
|
119
|
+
const normalized = normalizeText(fieldOrColumn)
|
|
120
|
+
if (!normalized) return ''
|
|
121
|
+
|
|
122
|
+
if (normalized === 'id') {
|
|
123
|
+
return tableContext.storageInfo.idColumn
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return tableContext.storageInfo.fields[normalized]?.column || normalized
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getColumnNameForDefinition (tableContext, fieldName) {
|
|
130
|
+
return resolveFieldOrColumnName(tableContext, fieldName)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function normalizeTopLevelIndexes (schemaLike, tableName, tableContext) {
|
|
134
|
+
const indexes = Array.isArray(schemaLike?.indexes) ? schemaLike.indexes : []
|
|
14
135
|
|
|
15
|
-
|
|
136
|
+
return indexes.map((indexDef, indexPosition) => {
|
|
137
|
+
if (!indexDef || typeof indexDef !== 'object' || Array.isArray(indexDef)) {
|
|
138
|
+
throw new Error(`Index metadata for table '${tableName}' must be an object.`)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const columns = ensureStringArray(indexDef.columns, `Index ${indexPosition + 1}`)
|
|
142
|
+
.map((column) => resolveFieldOrColumnName(tableContext, column))
|
|
143
|
+
const unique = indexDef.unique === true
|
|
144
|
+
const indexType = normalizeText(indexDef.indexType).toUpperCase()
|
|
145
|
+
const name = normalizeText(indexDef.name) || `${unique ? 'uq' : 'idx'}_${tableName}_${columns.join('_')}`
|
|
146
|
+
|
|
147
|
+
if (unique && indexType) {
|
|
148
|
+
throw new Error(`Unique index '${name}' on table '${tableName}' cannot define indexType.`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
name,
|
|
153
|
+
unique,
|
|
154
|
+
columns,
|
|
155
|
+
indexType
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function normalizeFieldIndexes (schemaStructure, tableName, tableContext) {
|
|
161
|
+
const indexes = []
|
|
162
|
+
|
|
163
|
+
for (const [fieldName, definition] of Object.entries(schemaStructure)) {
|
|
164
|
+
const columnName = getColumnNameForDefinition(tableContext, fieldName)
|
|
165
|
+
|
|
166
|
+
if (definition.unique === true) {
|
|
167
|
+
indexes.push({
|
|
168
|
+
name: `uq_${tableName}_${columnName}`,
|
|
169
|
+
unique: true,
|
|
170
|
+
columns: [columnName],
|
|
171
|
+
indexType: ''
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (definition.index === true) {
|
|
176
|
+
indexes.push({
|
|
177
|
+
name: `idx_${tableName}_${columnName}`,
|
|
178
|
+
unique: false,
|
|
179
|
+
columns: [columnName],
|
|
180
|
+
indexType: ''
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return indexes
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function normalizeTopLevelForeignKeys (schemaLike, tableName, tableContext) {
|
|
189
|
+
const foreignKeys = Array.isArray(schemaLike?.foreignKeys) ? schemaLike.foreignKeys : []
|
|
190
|
+
|
|
191
|
+
return foreignKeys.map((foreignKeyDef, foreignKeyPosition) => {
|
|
192
|
+
if (!foreignKeyDef || typeof foreignKeyDef !== 'object' || Array.isArray(foreignKeyDef)) {
|
|
193
|
+
throw new Error(`Foreign key metadata for table '${tableName}' must be an object.`)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const columns = ensureStringArray(foreignKeyDef.columns, `Foreign key ${foreignKeyPosition + 1}`)
|
|
197
|
+
.map((column) => resolveFieldOrColumnName(tableContext, column))
|
|
198
|
+
const referencedTableName = normalizeText(
|
|
199
|
+
foreignKeyDef.referencedTableName ||
|
|
200
|
+
foreignKeyDef.table ||
|
|
201
|
+
foreignKeyDef.references?.table
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if (!referencedTableName) {
|
|
205
|
+
throw new Error(`Foreign key ${foreignKeyPosition + 1} on table '${tableName}' requires referencedTableName.`)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const referencedColumns = ensureStringArray(
|
|
209
|
+
foreignKeyDef.referencedColumns ||
|
|
210
|
+
foreignKeyDef.references?.columns ||
|
|
211
|
+
foreignKeyDef.references?.column ||
|
|
212
|
+
'id',
|
|
213
|
+
`Foreign key ${foreignKeyPosition + 1}`
|
|
214
|
+
)
|
|
215
|
+
const name = normalizeText(foreignKeyDef.name) || `${tableName}_${columns.join('_')}_foreign`
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
name,
|
|
219
|
+
columns,
|
|
220
|
+
referencedTableName,
|
|
221
|
+
referencedColumns,
|
|
222
|
+
deleteRule: normalizeText(foreignKeyDef.deleteRule || foreignKeyDef.onDelete).toUpperCase(),
|
|
223
|
+
updateRule: normalizeText(foreignKeyDef.updateRule || foreignKeyDef.onUpdate).toUpperCase()
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function normalizeFieldForeignKeys (schemaStructure, tableName, tableContext) {
|
|
229
|
+
const foreignKeys = []
|
|
230
|
+
|
|
231
|
+
for (const [fieldName, definition] of Object.entries(schemaStructure)) {
|
|
232
|
+
if (!definition.references) continue
|
|
233
|
+
|
|
234
|
+
const columnName = getColumnNameForDefinition(tableContext, fieldName)
|
|
235
|
+
foreignKeys.push({
|
|
236
|
+
name: normalizeText(definition.references.name) || `${tableName}_${columnName}_foreign`,
|
|
237
|
+
columns: [columnName],
|
|
238
|
+
referencedTableName: normalizeText(definition.references.table),
|
|
239
|
+
referencedColumns: [normalizeText(definition.references.column || 'id')],
|
|
240
|
+
deleteRule: normalizeText(definition.references.onDelete).toUpperCase(),
|
|
241
|
+
updateRule: normalizeText(definition.references.onUpdate).toUpperCase()
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return foreignKeys
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function normalizeCheckConstraints (schemaLike, tableName) {
|
|
249
|
+
const constraints = Array.isArray(schemaLike?.checkConstraints) ? schemaLike.checkConstraints : []
|
|
250
|
+
|
|
251
|
+
return constraints.map((constraintDef, constraintIndex) => {
|
|
252
|
+
if (!constraintDef || typeof constraintDef !== 'object' || Array.isArray(constraintDef)) {
|
|
253
|
+
throw new Error(`Check constraint metadata for table '${tableName}' must be an object.`)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const clause = normalizeText(constraintDef.clause)
|
|
257
|
+
if (!clause) {
|
|
258
|
+
throw new Error(`Check constraint ${constraintIndex + 1} on table '${tableName}' requires a clause.`)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
name: normalizeText(constraintDef.name) || `${tableName}_check_${constraintIndex + 1}`,
|
|
263
|
+
clause
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function dedupeBySignature (entries, signatureFn, sortFn) {
|
|
269
|
+
const deduped = new Map()
|
|
270
|
+
|
|
271
|
+
for (const entry of entries) {
|
|
272
|
+
deduped.set(signatureFn(entry), entry)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return [...deduped.values()].sort(sortFn)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function compareByName (left, right) {
|
|
279
|
+
return left.name.localeCompare(right.name)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function normalizeColumnShape (definition = {}) {
|
|
283
|
+
if (Array.isArray(definition.enum) && definition.enum.length > 0) {
|
|
284
|
+
return 'enum'
|
|
285
|
+
}
|
|
286
|
+
if (Array.isArray(definition.setValues) && definition.setValues.length > 0) {
|
|
287
|
+
return 'set'
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
switch (definition.type) {
|
|
291
|
+
case 'id':
|
|
292
|
+
return 'integer'
|
|
293
|
+
case 'number':
|
|
294
|
+
return definition.precision !== undefined && definition.scale !== undefined ? 'decimal' : 'float'
|
|
295
|
+
case 'boolean':
|
|
296
|
+
return 'boolean'
|
|
297
|
+
case 'date':
|
|
298
|
+
return 'date'
|
|
299
|
+
case 'dateTime':
|
|
300
|
+
return 'datetime'
|
|
301
|
+
case 'time':
|
|
302
|
+
return 'time'
|
|
303
|
+
case 'timestamp':
|
|
304
|
+
return 'integer'
|
|
305
|
+
case 'array':
|
|
306
|
+
case 'object':
|
|
307
|
+
case 'serialize':
|
|
308
|
+
return 'json'
|
|
309
|
+
case 'blob':
|
|
310
|
+
case 'file':
|
|
311
|
+
return 'binary'
|
|
312
|
+
case 'string':
|
|
313
|
+
case 'none':
|
|
314
|
+
default:
|
|
315
|
+
return 'string'
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function normalizeDesiredTypeKind (definition = {}) {
|
|
320
|
+
if (Array.isArray(definition.enum) && definition.enum.length > 0) {
|
|
321
|
+
return 'string'
|
|
322
|
+
}
|
|
323
|
+
if (Array.isArray(definition.setValues) && definition.setValues.length > 0) {
|
|
324
|
+
return 'string'
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
switch (definition.type) {
|
|
328
|
+
case 'id':
|
|
329
|
+
return 'integer'
|
|
330
|
+
case 'number':
|
|
331
|
+
return 'number'
|
|
332
|
+
case 'boolean':
|
|
333
|
+
return 'boolean'
|
|
334
|
+
case 'date':
|
|
335
|
+
return 'date'
|
|
336
|
+
case 'dateTime':
|
|
337
|
+
return 'datetime'
|
|
338
|
+
case 'time':
|
|
339
|
+
return 'time'
|
|
340
|
+
case 'array':
|
|
341
|
+
case 'object':
|
|
342
|
+
case 'serialize':
|
|
343
|
+
return 'json'
|
|
344
|
+
case 'blob':
|
|
345
|
+
case 'file':
|
|
346
|
+
return 'binary'
|
|
347
|
+
case 'timestamp':
|
|
348
|
+
return 'integer'
|
|
16
349
|
case 'string':
|
|
350
|
+
case 'none':
|
|
351
|
+
default:
|
|
352
|
+
return 'string'
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function normalizeComparableDefault (value) {
|
|
357
|
+
if (value === undefined) return undefined
|
|
358
|
+
if (typeof value === 'boolean') return value ? '1' : '0'
|
|
359
|
+
if (value === null) return null
|
|
360
|
+
if (typeof value === 'object') return JSON.stringify(value)
|
|
361
|
+
return String(value)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function normalizeDesiredColumn (tableContext, fieldName, definition, options = {}) {
|
|
365
|
+
const nullable = definition.nullable === true
|
|
366
|
+
? true
|
|
367
|
+
: !(definition.required === true || definition.nullable === false)
|
|
368
|
+
const shape = normalizeColumnShape(definition)
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
fieldName,
|
|
372
|
+
name: getColumnNameForDefinition(tableContext, fieldName),
|
|
373
|
+
definition,
|
|
374
|
+
shape,
|
|
375
|
+
typeKind: normalizeDesiredTypeKind(definition),
|
|
376
|
+
nullable,
|
|
377
|
+
required: definition.required === true,
|
|
378
|
+
hasDefault: definition.defaultTo !== undefined,
|
|
379
|
+
defaultValue: normalizeComparableDefault(definition.defaultTo),
|
|
380
|
+
unsigned: definition.type === 'id' ? definition.unsigned !== false : definition.unsigned === true,
|
|
381
|
+
maxLength: definition.maxLength ?? null,
|
|
382
|
+
numericPrecision: definition.precision ?? null,
|
|
383
|
+
numericScale: definition.scale ?? null,
|
|
384
|
+
datetimePrecision: definition.temporalPrecision ?? null,
|
|
385
|
+
enumValues: Array.isArray(definition.enum) ? definition.enum.map((value) => String(value)) : [],
|
|
386
|
+
setValues: Array.isArray(definition.setValues) ? definition.setValues.map((value) => String(value)) : [],
|
|
387
|
+
autoIncrement: options.autoIncrement === true
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function buildImplicitIdDefinition (idColumn) {
|
|
392
|
+
return {
|
|
393
|
+
type: 'id',
|
|
394
|
+
primary: true,
|
|
395
|
+
storage: {
|
|
396
|
+
column: idColumn
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function resolveTableSchemaContext (schemaLike, options = {}) {
|
|
402
|
+
const schemaStructure = schemaLike?.structure || schemaLike || {}
|
|
403
|
+
const configuredIdColumn = options.idProperty || schemaStructure.id?.storage?.column || 'id'
|
|
404
|
+
const storage = options.storage ?? schemaLike?.storage
|
|
405
|
+
const tableContext = createTableBuilderContext(schemaStructure, configuredIdColumn, storage)
|
|
406
|
+
|
|
407
|
+
let resourceIdField = null
|
|
408
|
+
for (const [fieldName, definition] of Object.entries(schemaStructure)) {
|
|
409
|
+
if (definition?.type !== 'id') continue
|
|
410
|
+
const columnName = getColumnNameForDefinition(tableContext, fieldName)
|
|
411
|
+
if (columnName === configuredIdColumn) {
|
|
412
|
+
resourceIdField = fieldName
|
|
413
|
+
break
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const implicitIndexes = normalizeFieldIndexes(schemaStructure, options.tableName || 'table', tableContext)
|
|
418
|
+
const explicitIndexes = normalizeTopLevelIndexes(schemaLike, options.tableName || 'table', tableContext)
|
|
419
|
+
const implicitForeignKeys = normalizeFieldForeignKeys(schemaStructure, options.tableName || 'table', tableContext)
|
|
420
|
+
const explicitForeignKeys = normalizeTopLevelForeignKeys(schemaLike, options.tableName || 'table', tableContext)
|
|
421
|
+
const checkConstraints = normalizeCheckConstraints(schemaLike, options.tableName || 'table')
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
schemaStructure,
|
|
425
|
+
idColumn: configuredIdColumn,
|
|
426
|
+
resourceIdField,
|
|
427
|
+
hasPrimaryIdField: resourceIdField
|
|
428
|
+
? schemaStructure[resourceIdField]?.primary === true
|
|
429
|
+
: false,
|
|
430
|
+
tableContext,
|
|
431
|
+
indexes: dedupeBySignature(
|
|
432
|
+
[...implicitIndexes, ...explicitIndexes],
|
|
433
|
+
(entry) => `${entry.name}|${entry.unique ? 'unique' : 'index'}|${entry.columns.join(',')}`,
|
|
434
|
+
compareByName
|
|
435
|
+
),
|
|
436
|
+
foreignKeys: dedupeBySignature(
|
|
437
|
+
[...implicitForeignKeys, ...explicitForeignKeys],
|
|
438
|
+
(entry) => `${entry.name}|${entry.columns.join(',')}|${entry.referencedTableName}|${entry.referencedColumns.join(',')}`,
|
|
439
|
+
compareByName
|
|
440
|
+
),
|
|
441
|
+
checkConstraints: dedupeBySignature(
|
|
442
|
+
checkConstraints,
|
|
443
|
+
(entry) => `${entry.name}|${entry.clause}`,
|
|
444
|
+
compareByName
|
|
445
|
+
)
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function mapTypeToKnex (table, columnName, definition, options = {}) {
|
|
450
|
+
const {
|
|
451
|
+
precision,
|
|
452
|
+
scale,
|
|
453
|
+
maxLength,
|
|
454
|
+
unsigned,
|
|
455
|
+
temporalPrecision
|
|
456
|
+
} = definition
|
|
457
|
+
const enumValues = Array.isArray(definition.enum) ? definition.enum : []
|
|
458
|
+
const setValues = Array.isArray(definition.setValues) ? definition.setValues : []
|
|
459
|
+
const dialect = normalizeText(options.dialect).toLowerCase()
|
|
460
|
+
|
|
461
|
+
if (setValues.length > 0) {
|
|
462
|
+
if (!isMysqlDialect(dialect)) {
|
|
463
|
+
throw new Error(`Field '${columnName}' uses setValues, which requires a MySQL-compatible knex client.`)
|
|
464
|
+
}
|
|
465
|
+
return table.specificType(columnName, buildSetColumnType(setValues))
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
switch (definition.type) {
|
|
469
|
+
case 'string':
|
|
470
|
+
if (enumValues.length > 0) {
|
|
471
|
+
return table.enu(columnName, enumValues)
|
|
472
|
+
}
|
|
17
473
|
return maxLength ? table.string(columnName, maxLength) : table.string(columnName)
|
|
18
474
|
|
|
19
475
|
case 'number':
|
|
@@ -22,9 +478,10 @@ function mapTypeToKnex (table, columnName, definition) {
|
|
|
22
478
|
}
|
|
23
479
|
return table.float(columnName)
|
|
24
480
|
|
|
25
|
-
case 'id':
|
|
481
|
+
case 'id': {
|
|
26
482
|
const col = table.integer(columnName)
|
|
27
483
|
return unsigned !== false ? col.unsigned() : col
|
|
484
|
+
}
|
|
28
485
|
|
|
29
486
|
case 'boolean':
|
|
30
487
|
return table.boolean(columnName)
|
|
@@ -33,10 +490,14 @@ function mapTypeToKnex (table, columnName, definition) {
|
|
|
33
490
|
return table.date(columnName)
|
|
34
491
|
|
|
35
492
|
case 'dateTime':
|
|
36
|
-
return
|
|
493
|
+
return temporalPrecision != null
|
|
494
|
+
? table.datetime(columnName, { precision: temporalPrecision })
|
|
495
|
+
: table.datetime(columnName)
|
|
37
496
|
|
|
38
497
|
case 'time':
|
|
39
|
-
return
|
|
498
|
+
return temporalPrecision != null
|
|
499
|
+
? table.time(columnName, { precision: temporalPrecision })
|
|
500
|
+
: table.time(columnName)
|
|
40
501
|
|
|
41
502
|
case 'timestamp':
|
|
42
503
|
return table.integer(columnName)
|
|
@@ -56,61 +517,368 @@ function mapTypeToKnex (table, columnName, definition) {
|
|
|
56
517
|
}
|
|
57
518
|
}
|
|
58
519
|
|
|
59
|
-
|
|
60
|
-
* Applies schema constraints to a Knex column
|
|
61
|
-
* @param {object} column - The Knex column builder instance
|
|
62
|
-
* @param {object} definition - The schema definition for the field
|
|
63
|
-
*/
|
|
64
|
-
function applyConstraints (column, definition) {
|
|
65
|
-
// Nullability
|
|
520
|
+
function applyColumnConstraints (column, definition) {
|
|
66
521
|
if (definition.nullable === true) {
|
|
67
522
|
column.nullable()
|
|
68
523
|
} else if (definition.required === true || definition.nullable === false) {
|
|
69
524
|
column.notNullable()
|
|
70
525
|
}
|
|
71
526
|
|
|
72
|
-
// Default value
|
|
73
527
|
if (definition.defaultTo !== undefined) {
|
|
74
528
|
column.defaultTo(definition.defaultTo)
|
|
75
529
|
}
|
|
76
530
|
|
|
77
|
-
// Database-specific constraints
|
|
78
|
-
if (definition.unique === true) {
|
|
79
|
-
column.unique()
|
|
80
|
-
}
|
|
81
|
-
|
|
82
531
|
if (definition.primary === true) {
|
|
83
532
|
column.primary()
|
|
84
533
|
}
|
|
85
534
|
|
|
86
|
-
if (definition.
|
|
87
|
-
column.
|
|
535
|
+
if (definition.comment) {
|
|
536
|
+
column.comment(definition.comment)
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function applyTableMetadata (table, tableSchemaContext) {
|
|
541
|
+
for (const index of tableSchemaContext.indexes) {
|
|
542
|
+
if (index.unique) {
|
|
543
|
+
table.unique(index.columns, index.name)
|
|
544
|
+
continue
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const indexOptions = index.indexType
|
|
548
|
+
? { indexType: index.indexType }
|
|
549
|
+
: undefined
|
|
550
|
+
table.index(index.columns, index.name, indexOptions)
|
|
88
551
|
}
|
|
89
552
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
.
|
|
553
|
+
for (const foreignKey of tableSchemaContext.foreignKeys) {
|
|
554
|
+
const reference = table.foreign(foreignKey.columns, foreignKey.name)
|
|
555
|
+
.references(foreignKey.referencedColumns)
|
|
556
|
+
.inTable(foreignKey.referencedTableName)
|
|
93
557
|
|
|
94
|
-
if (
|
|
95
|
-
|
|
558
|
+
if (foreignKey.deleteRule) {
|
|
559
|
+
reference.onDelete(foreignKey.deleteRule)
|
|
96
560
|
}
|
|
97
561
|
|
|
98
|
-
if (
|
|
99
|
-
|
|
562
|
+
if (foreignKey.updateRule) {
|
|
563
|
+
reference.onUpdate(foreignKey.updateRule)
|
|
100
564
|
}
|
|
101
565
|
}
|
|
102
566
|
|
|
567
|
+
for (const constraint of tableSchemaContext.checkConstraints) {
|
|
568
|
+
table.check(constraint.clause, [], constraint.name)
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function buildColumnBuilderCode (columnName, definition, options = {}) {
|
|
573
|
+
const dialect = normalizeText(options.dialect).toLowerCase()
|
|
574
|
+
const enumValues = Array.isArray(definition.enum) ? definition.enum : []
|
|
575
|
+
const setValues = Array.isArray(definition.setValues) ? definition.setValues : []
|
|
576
|
+
let line = ''
|
|
577
|
+
|
|
578
|
+
if (setValues.length > 0) {
|
|
579
|
+
if (dialect && !isMysqlDialect(dialect)) {
|
|
580
|
+
throw new Error(`Field '${columnName}' uses setValues, which requires a MySQL-compatible migration target.`)
|
|
581
|
+
}
|
|
582
|
+
line = `table.specificType(${quoteJsString(columnName)}, ${quoteJsString(buildSetColumnType(setValues))})`
|
|
583
|
+
} else {
|
|
584
|
+
switch (definition.type) {
|
|
585
|
+
case 'string':
|
|
586
|
+
if (enumValues.length > 0) {
|
|
587
|
+
line = `table.enu(${quoteJsString(columnName)}, ${formatArrayLiteral(enumValues)})`
|
|
588
|
+
} else {
|
|
589
|
+
line = definition.maxLength
|
|
590
|
+
? `table.string(${quoteJsString(columnName)}, ${definition.maxLength})`
|
|
591
|
+
: `table.string(${quoteJsString(columnName)})`
|
|
592
|
+
}
|
|
593
|
+
break
|
|
594
|
+
|
|
595
|
+
case 'number':
|
|
596
|
+
if (definition.precision !== undefined && definition.scale !== undefined) {
|
|
597
|
+
line = `table.decimal(${quoteJsString(columnName)}, ${definition.precision}, ${definition.scale})`
|
|
598
|
+
} else {
|
|
599
|
+
line = `table.float(${quoteJsString(columnName)})`
|
|
600
|
+
}
|
|
601
|
+
break
|
|
602
|
+
|
|
603
|
+
case 'id':
|
|
604
|
+
line = `table.integer(${quoteJsString(columnName)})`
|
|
605
|
+
if (definition.unsigned !== false) {
|
|
606
|
+
line += '.unsigned()'
|
|
607
|
+
}
|
|
608
|
+
break
|
|
609
|
+
|
|
610
|
+
case 'boolean':
|
|
611
|
+
line = `table.boolean(${quoteJsString(columnName)})`
|
|
612
|
+
break
|
|
613
|
+
|
|
614
|
+
case 'date':
|
|
615
|
+
line = `table.date(${quoteJsString(columnName)})`
|
|
616
|
+
break
|
|
617
|
+
|
|
618
|
+
case 'dateTime':
|
|
619
|
+
if (definition.temporalPrecision != null) {
|
|
620
|
+
line = `table.datetime(${quoteJsString(columnName)}, { precision: ${definition.temporalPrecision} })`
|
|
621
|
+
} else {
|
|
622
|
+
line = `table.datetime(${quoteJsString(columnName)})`
|
|
623
|
+
}
|
|
624
|
+
break
|
|
625
|
+
|
|
626
|
+
case 'time':
|
|
627
|
+
if (definition.temporalPrecision != null) {
|
|
628
|
+
line = `table.time(${quoteJsString(columnName)}, { precision: ${definition.temporalPrecision} })`
|
|
629
|
+
} else {
|
|
630
|
+
line = `table.time(${quoteJsString(columnName)})`
|
|
631
|
+
}
|
|
632
|
+
break
|
|
633
|
+
|
|
634
|
+
case 'timestamp':
|
|
635
|
+
line = `table.integer(${quoteJsString(columnName)})`
|
|
636
|
+
break
|
|
637
|
+
|
|
638
|
+
case 'array':
|
|
639
|
+
case 'object':
|
|
640
|
+
case 'serialize':
|
|
641
|
+
line = `table.json(${quoteJsString(columnName)})`
|
|
642
|
+
break
|
|
643
|
+
|
|
644
|
+
case 'blob':
|
|
645
|
+
case 'file':
|
|
646
|
+
line = `table.binary(${quoteJsString(columnName)})`
|
|
647
|
+
break
|
|
648
|
+
|
|
649
|
+
case 'none':
|
|
650
|
+
default:
|
|
651
|
+
line = `table.string(${quoteJsString(columnName)})`
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (definition.nullable === true) {
|
|
656
|
+
line += '.nullable()'
|
|
657
|
+
} else if (definition.required === true || definition.nullable === false) {
|
|
658
|
+
line += '.notNullable()'
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (definition.defaultTo !== undefined) {
|
|
662
|
+
line += `.defaultTo(${formatCodeLiteral(definition.defaultTo)})`
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (definition.primary === true && options.includePrimary !== false) {
|
|
666
|
+
line += '.primary()'
|
|
667
|
+
}
|
|
668
|
+
|
|
103
669
|
if (definition.comment) {
|
|
104
|
-
|
|
670
|
+
line += `.comment(${quoteJsString(definition.comment)})`
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (options.alter === true) {
|
|
674
|
+
line += '.alter()'
|
|
105
675
|
}
|
|
676
|
+
|
|
677
|
+
return line
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function buildIndexLine (index) {
|
|
681
|
+
if (index.unique) {
|
|
682
|
+
return `table.unique(${formatCodeLiteral(index.columns)}, ${quoteJsString(index.name)})`
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (index.indexType) {
|
|
686
|
+
return `table.index(${formatCodeLiteral(index.columns)}, ${quoteJsString(index.name)}, ${formatCodeLiteral({ indexType: index.indexType })})`
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
return `table.index(${formatCodeLiteral(index.columns)}, ${quoteJsString(index.name)})`
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function buildForeignKeyLine (foreignKey) {
|
|
693
|
+
let line = `table.foreign(${formatCodeLiteral(foreignKey.columns)}, ${quoteJsString(foreignKey.name)})`
|
|
694
|
+
line += `.references(${formatCodeLiteral(foreignKey.referencedColumns)})`
|
|
695
|
+
line += `.inTable(${quoteJsString(foreignKey.referencedTableName)})`
|
|
696
|
+
|
|
697
|
+
if (foreignKey.deleteRule) {
|
|
698
|
+
line += `.onDelete(${quoteJsString(foreignKey.deleteRule)})`
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (foreignKey.updateRule) {
|
|
702
|
+
line += `.onUpdate(${quoteJsString(foreignKey.updateRule)})`
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return line
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function buildCheckConstraintLine (constraint) {
|
|
709
|
+
return `table.check(${quoteJsString(constraint.clause)}, [], ${quoteJsString(constraint.name)})`
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function buildDesiredColumnsMap (tableSchemaContext, options = {}) {
|
|
713
|
+
const desiredColumns = new Map()
|
|
714
|
+
|
|
715
|
+
if (!tableSchemaContext.hasPrimaryIdField && options.autoIncrement === true) {
|
|
716
|
+
const implicitIdDefinition = buildImplicitIdDefinition(tableSchemaContext.idColumn)
|
|
717
|
+
desiredColumns.set(
|
|
718
|
+
tableSchemaContext.idColumn,
|
|
719
|
+
normalizeDesiredColumn(
|
|
720
|
+
createTableBuilderContext({ id: implicitIdDefinition }, tableSchemaContext.idColumn),
|
|
721
|
+
'id',
|
|
722
|
+
implicitIdDefinition,
|
|
723
|
+
{ autoIncrement: true }
|
|
724
|
+
)
|
|
725
|
+
)
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
for (const [fieldName, definition] of Object.entries(tableSchemaContext.schemaStructure)) {
|
|
729
|
+
if (fieldName === tableSchemaContext.resourceIdField && !tableSchemaContext.hasPrimaryIdField && options.autoIncrement === true) {
|
|
730
|
+
continue
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
desiredColumns.set(
|
|
734
|
+
getColumnNameForDefinition(tableSchemaContext.tableContext, fieldName),
|
|
735
|
+
normalizeDesiredColumn(tableSchemaContext.tableContext, fieldName, definition)
|
|
736
|
+
)
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
return desiredColumns
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function valuesEqual (left, right) {
|
|
743
|
+
return JSON.stringify(left) === JSON.stringify(right)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function normalizeSnapshotDefaultValue (column = {}) {
|
|
747
|
+
if (!column.hasDefault) return undefined
|
|
748
|
+
return normalizeComparableDefault(column.defaultValue)
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function collectDestructiveColumnWarnings (currentColumn, desiredColumn) {
|
|
752
|
+
const warnings = []
|
|
753
|
+
const columnName = desiredColumn.name
|
|
754
|
+
|
|
755
|
+
if (currentColumn.nullable === true && desiredColumn.nullable === false) {
|
|
756
|
+
warnings.push(`Column '${columnName}' changes from nullable to not-null and may fail on existing rows.`)
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (
|
|
760
|
+
currentColumn.maxLength != null &&
|
|
761
|
+
desiredColumn.maxLength != null &&
|
|
762
|
+
desiredColumn.maxLength < currentColumn.maxLength
|
|
763
|
+
) {
|
|
764
|
+
warnings.push(`Column '${columnName}' reduces maxLength from ${currentColumn.maxLength} to ${desiredColumn.maxLength}.`)
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if (
|
|
768
|
+
currentColumn.numericPrecision != null &&
|
|
769
|
+
desiredColumn.numericPrecision != null &&
|
|
770
|
+
desiredColumn.numericPrecision < currentColumn.numericPrecision
|
|
771
|
+
) {
|
|
772
|
+
warnings.push(`Column '${columnName}' reduces numeric precision from ${currentColumn.numericPrecision} to ${desiredColumn.numericPrecision}.`)
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (
|
|
776
|
+
currentColumn.numericScale != null &&
|
|
777
|
+
desiredColumn.numericScale != null &&
|
|
778
|
+
desiredColumn.numericScale < currentColumn.numericScale
|
|
779
|
+
) {
|
|
780
|
+
warnings.push(`Column '${columnName}' reduces numeric scale from ${currentColumn.numericScale} to ${desiredColumn.numericScale}.`)
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (!valuesEqual(currentColumn.enumValues || [], desiredColumn.enumValues || [])) {
|
|
784
|
+
warnings.push(`Column '${columnName}' changes enum values and may invalidate existing data.`)
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (!valuesEqual(currentColumn.setValues || [], desiredColumn.setValues || [])) {
|
|
788
|
+
warnings.push(`Column '${columnName}' changes setValues and may invalidate existing data.`)
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (normalizeText(currentColumn.typeKind) !== normalizeText(desiredColumn.typeKind)) {
|
|
792
|
+
warnings.push(`Column '${columnName}' changes type semantics from '${currentColumn.typeKind}' to '${desiredColumn.typeKind}'.`)
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
return warnings
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function columnNeedsAlter (currentColumn, desiredColumn, options = {}) {
|
|
799
|
+
const currentNumericShape = (() => {
|
|
800
|
+
const dataType = normalizeText(currentColumn.dataType)
|
|
801
|
+
if (dataType === 'decimal' || dataType === 'numeric') return 'decimal'
|
|
802
|
+
if (dataType === 'float' || dataType === 'double' || dataType === 'real') return 'float'
|
|
803
|
+
return ''
|
|
804
|
+
})()
|
|
805
|
+
const compareUnsigned = isMysqlDialect(normalizeText(options.dialect).toLowerCase())
|
|
806
|
+
|
|
807
|
+
return (
|
|
808
|
+
normalizeText(currentColumn.typeKind) !== normalizeText(desiredColumn.typeKind) ||
|
|
809
|
+
(desiredColumn.typeKind === 'number' && currentNumericShape && currentNumericShape !== normalizeText(desiredColumn.shape)) ||
|
|
810
|
+
Boolean(currentColumn.nullable) !== Boolean(desiredColumn.nullable) ||
|
|
811
|
+
(compareUnsigned && Boolean(currentColumn.unsigned) !== Boolean(desiredColumn.unsigned)) ||
|
|
812
|
+
(currentColumn.maxLength ?? null) !== (desiredColumn.maxLength ?? null) ||
|
|
813
|
+
(currentColumn.numericPrecision ?? null) !== (desiredColumn.numericPrecision ?? null) ||
|
|
814
|
+
(currentColumn.numericScale ?? null) !== (desiredColumn.numericScale ?? null) ||
|
|
815
|
+
normalizeSnapshotDefaultValue(currentColumn) !== desiredColumn.defaultValue ||
|
|
816
|
+
Boolean(currentColumn.hasDefault) !== Boolean(desiredColumn.hasDefault) ||
|
|
817
|
+
!valuesEqual(currentColumn.enumValues || [], desiredColumn.enumValues || []) ||
|
|
818
|
+
!valuesEqual(currentColumn.setValues || [], desiredColumn.setValues || [])
|
|
819
|
+
)
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function mapByName (entries = []) {
|
|
823
|
+
return new Map((entries || []).map((entry) => [entry.name, entry]))
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function createEmptyDiffPlan () {
|
|
827
|
+
return {
|
|
828
|
+
addColumns: [],
|
|
829
|
+
alterColumns: [],
|
|
830
|
+
dropColumns: [],
|
|
831
|
+
addIndexes: [],
|
|
832
|
+
dropIndexes: [],
|
|
833
|
+
addForeignKeys: [],
|
|
834
|
+
dropForeignKeys: [],
|
|
835
|
+
addCheckConstraints: [],
|
|
836
|
+
warnings: []
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function assertNoTopLevelTableMetadata (schemaLike, helperName) {
|
|
841
|
+
const metadataKeys = ['indexes', 'foreignKeys', 'checkConstraints']
|
|
842
|
+
|
|
843
|
+
for (const key of metadataKeys) {
|
|
844
|
+
if (Array.isArray(schemaLike?.[key]) && schemaLike[key].length > 0) {
|
|
845
|
+
throw new Error(`${helperName} does not accept top-level ${key}. Use createKnexTable(), generateKnexMigration(), or generateKnexMigrationDiff() for table metadata.`)
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function buildUnsupportedConstraintWarning (constraintType, name, dialect) {
|
|
851
|
+
return `Skipping ${constraintType} '${name}' because automatic ALTER support is not available for dialect '${dialect || 'unknown'}'.`
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function isSqliteDialectName (dialect = '') {
|
|
855
|
+
return String(dialect || '').toLowerCase().includes('sqlite')
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function normalizeConstraintClause (clause = '') {
|
|
859
|
+
return normalizeText(clause)
|
|
860
|
+
.replace(/[`"]/g, '')
|
|
861
|
+
.replace(/\s*\(\s*/g, '(')
|
|
862
|
+
.replace(/\s*\)\s*/g, ')')
|
|
863
|
+
.replace(/\s*,\s*/g, ',')
|
|
864
|
+
.replace(/\s+/g, ' ')
|
|
865
|
+
.toLowerCase()
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function isInlineEnumCheckConstraint (currentCheck = {}, desiredColumn = {}) {
|
|
869
|
+
if (!desiredColumn.enumValues || desiredColumn.enumValues.length < 1) {
|
|
870
|
+
return false
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const expectedClause = `${desiredColumn.name} in (${desiredColumn.enumValues.map((value) => quoteJsString(value)).join(', ')})`
|
|
874
|
+
return normalizeConstraintClause(currentCheck.clause) === normalizeConstraintClause(expectedClause)
|
|
106
875
|
}
|
|
107
876
|
|
|
108
877
|
/**
|
|
109
878
|
* Creates a Knex table from a json-rest-schema definition
|
|
110
879
|
* @param {object} knex - The Knex instance
|
|
111
|
-
* @param {
|
|
112
|
-
* @param {object}
|
|
113
|
-
* @param {string} [idProperty='id'] - The name of the ID column
|
|
880
|
+
* @param {object} schemaInfo - Resource schema metadata
|
|
881
|
+
* @param {object} tableSchemaInstance - The json-rest-schema instance or schema-like table metadata object
|
|
114
882
|
* @param {object} [options={}] - Additional options
|
|
115
883
|
* @param {boolean} [options.autoIncrement=true] - Whether to use auto-incrementing IDs
|
|
116
884
|
* @param {boolean} [options.timestamps=false] - Whether to add created_at/updated_at columns
|
|
@@ -118,62 +886,71 @@ function applyConstraints (column, definition) {
|
|
|
118
886
|
*/
|
|
119
887
|
export async function createKnexTable (knex, schemaInfo, tableSchemaInstance, options = {}) {
|
|
120
888
|
const { autoIncrement = true, timestamps = false } = options
|
|
121
|
-
|
|
122
889
|
const tableName = schemaInfo.tableName
|
|
123
|
-
const
|
|
124
|
-
const
|
|
890
|
+
const dialect = detectKnexDialect(knex)
|
|
891
|
+
const tableSchemaContext = resolveTableSchemaContext(tableSchemaInstance, {
|
|
892
|
+
idProperty: schemaInfo.idProperty,
|
|
893
|
+
storage: schemaInfo.storage,
|
|
894
|
+
tableName
|
|
895
|
+
})
|
|
125
896
|
|
|
126
897
|
return knex.schema.createTable(tableName, (table) => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// Add auto-incrementing ID if no primary key is defined and autoIncrement is true
|
|
131
|
-
if (!hasIdField && autoIncrement) {
|
|
132
|
-
table.increments(idProperty).primary()
|
|
898
|
+
if (!tableSchemaContext.hasPrimaryIdField && autoIncrement) {
|
|
899
|
+
table.increments(tableSchemaContext.idColumn).primary()
|
|
133
900
|
}
|
|
134
901
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// Skip if this is the ID field and we already handled it
|
|
138
|
-
if (fieldName === idProperty && !hasIdField && autoIncrement) {
|
|
902
|
+
for (const [fieldName, definition] of Object.entries(tableSchemaContext.schemaStructure)) {
|
|
903
|
+
if (fieldName === tableSchemaContext.resourceIdField && !tableSchemaContext.hasPrimaryIdField && autoIncrement) {
|
|
139
904
|
continue
|
|
140
905
|
}
|
|
141
906
|
|
|
142
|
-
|
|
143
|
-
const column = mapTypeToKnex(table,
|
|
144
|
-
|
|
145
|
-
// Apply constraints
|
|
146
|
-
applyConstraints(column, definition)
|
|
907
|
+
const columnName = getColumnNameForDefinition(tableSchemaContext.tableContext, fieldName)
|
|
908
|
+
const column = mapTypeToKnex(table, columnName, definition, { dialect })
|
|
909
|
+
applyColumnConstraints(column, definition)
|
|
147
910
|
}
|
|
148
911
|
|
|
149
|
-
|
|
912
|
+
applyTableMetadata(table, tableSchemaContext)
|
|
913
|
+
|
|
150
914
|
if (timestamps) {
|
|
151
915
|
table.timestamps(true, true)
|
|
152
916
|
}
|
|
153
917
|
})
|
|
154
918
|
}
|
|
155
919
|
|
|
156
|
-
export async function addKnexFields (knex, tableName, schema) {
|
|
920
|
+
export async function addKnexFields (knex, tableName, schema, options = {}) {
|
|
921
|
+
assertNoTopLevelTableMetadata(schema, 'addKnexFields')
|
|
922
|
+
const dialect = detectKnexDialect(knex)
|
|
923
|
+
const tableSchemaContext = resolveTableSchemaContext(schema, {
|
|
924
|
+
storage: options.storage,
|
|
925
|
+
...options,
|
|
926
|
+
tableName
|
|
927
|
+
})
|
|
928
|
+
|
|
157
929
|
return knex.schema.alterTable(tableName, (table) => {
|
|
158
|
-
for (const [fieldName, definition] of Object.entries(
|
|
159
|
-
const
|
|
160
|
-
|
|
930
|
+
for (const [fieldName, definition] of Object.entries(tableSchemaContext.schemaStructure)) {
|
|
931
|
+
const columnName = getColumnNameForDefinition(tableSchemaContext.tableContext, fieldName)
|
|
932
|
+
const column = mapTypeToKnex(table, columnName, definition, { dialect })
|
|
933
|
+
applyColumnConstraints(column, definition)
|
|
161
934
|
}
|
|
162
935
|
})
|
|
163
936
|
}
|
|
164
937
|
|
|
165
938
|
// Helper function to alter multiple fields in an existing table
|
|
166
939
|
export async function alterKnexFields (knex, tableName, fields, options = {}) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
940
|
+
assertNoTopLevelTableMetadata(fields, 'alterKnexFields')
|
|
941
|
+
const dialect = detectKnexDialect(knex)
|
|
942
|
+
const tableSchemaContext = resolveTableSchemaContext(fields, {
|
|
943
|
+
storage: options.storage,
|
|
944
|
+
...options,
|
|
945
|
+
tableName
|
|
946
|
+
})
|
|
171
947
|
|
|
172
|
-
|
|
948
|
+
return knex.schema.alterTable(tableName, (table) => {
|
|
949
|
+
for (const [fieldName, definition] of Object.entries(tableSchemaContext.schemaStructure)) {
|
|
950
|
+
const columnName = getColumnNameForDefinition(tableSchemaContext.tableContext, fieldName)
|
|
951
|
+
const column = mapTypeToKnex(table, columnName, definition, { dialect })
|
|
173
952
|
column.alter()
|
|
174
|
-
|
|
175
|
-
// Apply constraints (these will be reapplied with the alter)
|
|
176
|
-
applyConstraints(column, definition)
|
|
953
|
+
applyColumnConstraints(column, definition)
|
|
177
954
|
}
|
|
178
955
|
})
|
|
179
956
|
}
|
|
@@ -181,124 +958,298 @@ export async function alterKnexFields (knex, tableName, fields, options = {}) {
|
|
|
181
958
|
/**
|
|
182
959
|
* Generates a Knex migration string from a json-rest-schema definition
|
|
183
960
|
* @param {string} tableName - The name of the table
|
|
184
|
-
* @param {object} schema - The json-rest-schema instance
|
|
961
|
+
* @param {object} schema - The json-rest-schema instance or schema-like table metadata object
|
|
185
962
|
* @param {object} [options={}] - Additional options
|
|
186
963
|
* @returns {string} The migration code as a string
|
|
187
964
|
*/
|
|
188
965
|
export function generateKnexMigration (tableName, schema, options = {}) {
|
|
189
|
-
const { autoIncrement = true, timestamps = false } = options
|
|
966
|
+
const { autoIncrement = true, timestamps = false, dialect = '' } = options
|
|
967
|
+
const tableSchemaContext = resolveTableSchemaContext(schema, {
|
|
968
|
+
...options,
|
|
969
|
+
storage: options.storage,
|
|
970
|
+
tableName
|
|
971
|
+
})
|
|
190
972
|
|
|
191
|
-
|
|
192
|
-
return knex.schema.createTable('${tableName}', (table) => {\n`
|
|
973
|
+
const lines = []
|
|
193
974
|
|
|
194
|
-
|
|
195
|
-
|
|
975
|
+
if (!tableSchemaContext.hasPrimaryIdField && autoIncrement) {
|
|
976
|
+
lines.push(`table.increments(${quoteJsString(tableSchemaContext.idColumn)}).primary()`)
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
for (const [fieldName, definition] of Object.entries(tableSchemaContext.schemaStructure)) {
|
|
980
|
+
if (fieldName === tableSchemaContext.resourceIdField && !tableSchemaContext.hasPrimaryIdField && autoIncrement) {
|
|
981
|
+
continue
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
const columnName = getColumnNameForDefinition(tableSchemaContext.tableContext, fieldName)
|
|
985
|
+
lines.push(buildColumnBuilderCode(columnName, definition, {
|
|
986
|
+
dialect
|
|
987
|
+
}))
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
for (const index of tableSchemaContext.indexes) {
|
|
991
|
+
lines.push(buildIndexLine(index))
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
for (const foreignKey of tableSchemaContext.foreignKeys) {
|
|
995
|
+
lines.push(buildForeignKeyLine(foreignKey))
|
|
996
|
+
}
|
|
196
997
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
migration += ' table.increments(\'id\').primary();\n'
|
|
998
|
+
for (const checkConstraint of tableSchemaContext.checkConstraints) {
|
|
999
|
+
lines.push(buildCheckConstraintLine(checkConstraint))
|
|
200
1000
|
}
|
|
201
1001
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
1002
|
+
if (timestamps) {
|
|
1003
|
+
lines.push('table.timestamps(true, true)')
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
return `exports.up = function(knex) {
|
|
1007
|
+
return knex.schema.createTable(${quoteJsString(tableName)}, (table) => {
|
|
1008
|
+
${lines.map((line) => ` ${line};`).join('\n')}
|
|
1009
|
+
});
|
|
1010
|
+
};
|
|
1011
|
+
|
|
1012
|
+
exports.down = function(knex) {
|
|
1013
|
+
return knex.schema.dropTable(${quoteJsString(tableName)});
|
|
1014
|
+
};`
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Generates a deterministic Knex alter migration by diffing a live table snapshot against a desired schema.
|
|
1019
|
+
* @param {string} tableName - The table name being diffed
|
|
1020
|
+
* @param {object} currentSnapshot - The normalized table snapshot returned by introspection
|
|
1021
|
+
* @param {object} schema - The desired schema-like table metadata object
|
|
1022
|
+
* @param {object} [options={}] - Diff options
|
|
1023
|
+
* @param {boolean} [options.autoIncrement=true] - Whether the resource expects an implicit auto-increment id column
|
|
1024
|
+
* @param {boolean} [options.allowDropColumns=false] - Whether removed columns should be dropped automatically
|
|
1025
|
+
* @param {string} [options.dialect=''] - Optional target dialect override
|
|
1026
|
+
* @returns {{ migration: string, warnings: string[], plan: object }} Diff result
|
|
1027
|
+
*/
|
|
1028
|
+
export function generateKnexMigrationDiff (tableName, currentSnapshot, schema, options = {}) {
|
|
1029
|
+
const {
|
|
1030
|
+
autoIncrement = true,
|
|
1031
|
+
allowDropColumns = false,
|
|
1032
|
+
dialect = ''
|
|
1033
|
+
} = options
|
|
1034
|
+
const resolvedDialect = normalizeText(dialect || currentSnapshot?.dialect).toLowerCase()
|
|
1035
|
+
const tableSchemaContext = resolveTableSchemaContext(schema, {
|
|
1036
|
+
...options,
|
|
1037
|
+
storage: options.storage,
|
|
1038
|
+
tableName
|
|
1039
|
+
})
|
|
1040
|
+
const desiredColumns = buildDesiredColumnsMap(tableSchemaContext, { autoIncrement })
|
|
1041
|
+
const currentColumns = mapByName(currentSnapshot?.columns || [])
|
|
1042
|
+
const desiredIndexes = mapByName(tableSchemaContext.indexes)
|
|
1043
|
+
const currentIndexes = mapByName(currentSnapshot?.indexes || [])
|
|
1044
|
+
const desiredForeignKeys = mapByName(tableSchemaContext.foreignKeys)
|
|
1045
|
+
const currentForeignKeys = mapByName(currentSnapshot?.foreignKeys || [])
|
|
1046
|
+
const desiredChecks = mapByName(tableSchemaContext.checkConstraints)
|
|
1047
|
+
const currentChecks = mapByName(currentSnapshot?.checkConstraints || [])
|
|
1048
|
+
const plan = createEmptyDiffPlan()
|
|
1049
|
+
|
|
1050
|
+
for (const [columnName, desiredColumn] of desiredColumns.entries()) {
|
|
1051
|
+
const currentColumn = currentColumns.get(columnName)
|
|
1052
|
+
|
|
1053
|
+
if (!currentColumn) {
|
|
1054
|
+
if (desiredColumn.autoIncrement) {
|
|
1055
|
+
plan.warnings.push(`Skipping implicit auto-increment id column '${columnName}' because it cannot be added safely to an existing table.`)
|
|
1056
|
+
continue
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (desiredColumn.setValues.length > 0 && !isMysqlDialect(resolvedDialect)) {
|
|
1060
|
+
plan.warnings.push(`Skipping setValues column '${columnName}' because it requires a MySQL-compatible ALTER target.`)
|
|
1061
|
+
continue
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
plan.addColumns.push(desiredColumn)
|
|
205
1065
|
continue
|
|
206
1066
|
}
|
|
207
1067
|
|
|
208
|
-
|
|
1068
|
+
if (desiredColumn.autoIncrement) {
|
|
1069
|
+
continue
|
|
1070
|
+
}
|
|
209
1071
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
case 'number':
|
|
218
|
-
if (definition.precision !== undefined && definition.scale !== undefined) {
|
|
219
|
-
line += `table.decimal('${fieldName}', ${definition.precision}, ${definition.scale})`
|
|
220
|
-
} else {
|
|
221
|
-
line += `table.float('${fieldName}')`
|
|
222
|
-
}
|
|
223
|
-
break
|
|
224
|
-
case 'id':
|
|
225
|
-
line += `table.integer('${fieldName}')`
|
|
226
|
-
if (definition.unsigned !== false) line += '.unsigned()'
|
|
227
|
-
break
|
|
228
|
-
case 'boolean':
|
|
229
|
-
line += `table.boolean('${fieldName}')`
|
|
230
|
-
break
|
|
231
|
-
case 'date':
|
|
232
|
-
line += `table.date('${fieldName}')`
|
|
233
|
-
break
|
|
234
|
-
case 'dateTime':
|
|
235
|
-
line += `table.datetime('${fieldName}')`
|
|
236
|
-
break
|
|
237
|
-
case 'time':
|
|
238
|
-
line += `table.time('${fieldName}')`
|
|
239
|
-
break
|
|
240
|
-
case 'timestamp':
|
|
241
|
-
line += `table.integer('${fieldName}')`
|
|
242
|
-
break
|
|
243
|
-
case 'array':
|
|
244
|
-
case 'object':
|
|
245
|
-
case 'serialize':
|
|
246
|
-
line += `table.json('${fieldName}')`
|
|
247
|
-
break
|
|
248
|
-
case 'blob':
|
|
249
|
-
case 'file':
|
|
250
|
-
line += `table.binary('${fieldName}')`
|
|
251
|
-
break
|
|
252
|
-
default:
|
|
253
|
-
line += `table.string('${fieldName}')`
|
|
1072
|
+
if (!columnNeedsAlter(currentColumn, desiredColumn, { dialect: resolvedDialect })) {
|
|
1073
|
+
continue
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
if (desiredColumn.setValues.length > 0 && !isMysqlDialect(resolvedDialect)) {
|
|
1077
|
+
plan.warnings.push(`Skipping setValues alteration for column '${columnName}' because it requires a MySQL-compatible ALTER target.`)
|
|
1078
|
+
continue
|
|
254
1079
|
}
|
|
255
1080
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
1081
|
+
plan.alterColumns.push(desiredColumn)
|
|
1082
|
+
plan.warnings.push(...collectDestructiveColumnWarnings(currentColumn, desiredColumn))
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
for (const currentColumn of currentColumns.values()) {
|
|
1086
|
+
if (desiredColumns.has(currentColumn.name)) {
|
|
1087
|
+
continue
|
|
261
1088
|
}
|
|
262
1089
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
1090
|
+
const warning = `Column '${currentColumn.name}' exists in the live table but not in the desired schema.`
|
|
1091
|
+
if (allowDropColumns) {
|
|
1092
|
+
plan.dropColumns.push(currentColumn)
|
|
1093
|
+
plan.warnings.push(`${warning} It will be dropped because allowDropColumns=true.`)
|
|
1094
|
+
} else {
|
|
1095
|
+
plan.warnings.push(`${warning} Skipping automatic drop.`)
|
|
268
1096
|
}
|
|
1097
|
+
}
|
|
269
1098
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
1099
|
+
for (const desiredIndex of desiredIndexes.values()) {
|
|
1100
|
+
const currentIndex = currentIndexes.get(desiredIndex.name)
|
|
1101
|
+
const sameIndexType = normalizeText(currentIndex?.indexType).toUpperCase() === desiredIndex.indexType
|
|
1102
|
+
const indexChanged = Boolean(currentIndex) && (
|
|
1103
|
+
!valuesEqual(currentIndex.columns, desiredIndex.columns) ||
|
|
1104
|
+
Boolean(currentIndex.unique) !== Boolean(desiredIndex.unique) ||
|
|
1105
|
+
!sameIndexType
|
|
1106
|
+
)
|
|
273
1107
|
|
|
274
|
-
if (
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
line += `.onDelete('${definition.references.onDelete}')`
|
|
279
|
-
}
|
|
280
|
-
if (definition.references.onUpdate) {
|
|
281
|
-
line += `.onUpdate('${definition.references.onUpdate}')`
|
|
1108
|
+
if (!currentIndex || indexChanged) {
|
|
1109
|
+
plan.addIndexes.push(desiredIndex)
|
|
1110
|
+
if (indexChanged) {
|
|
1111
|
+
plan.dropIndexes.push(currentIndex)
|
|
282
1112
|
}
|
|
283
1113
|
}
|
|
1114
|
+
}
|
|
284
1115
|
|
|
285
|
-
|
|
286
|
-
|
|
1116
|
+
for (const currentIndex of currentIndexes.values()) {
|
|
1117
|
+
if (!desiredIndexes.has(currentIndex.name)) {
|
|
1118
|
+
plan.dropIndexes.push(currentIndex)
|
|
287
1119
|
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
for (const desiredForeignKey of desiredForeignKeys.values()) {
|
|
1123
|
+
const currentForeignKey = currentForeignKeys.get(desiredForeignKey.name)
|
|
1124
|
+
const sameColumns = valuesEqual(
|
|
1125
|
+
(currentForeignKey?.columns || []).map((entry) => entry.name),
|
|
1126
|
+
desiredForeignKey.columns
|
|
1127
|
+
)
|
|
1128
|
+
const sameReferencedColumns = valuesEqual(
|
|
1129
|
+
(currentForeignKey?.columns || []).map((entry) => entry.referencedName),
|
|
1130
|
+
desiredForeignKey.referencedColumns
|
|
1131
|
+
)
|
|
1132
|
+
const sameTable = normalizeText(currentForeignKey?.referencedTableName) === desiredForeignKey.referencedTableName
|
|
1133
|
+
const sameDeleteRule = normalizeText(currentForeignKey?.deleteRule).toUpperCase() === desiredForeignKey.deleteRule
|
|
1134
|
+
const sameUpdateRule = normalizeText(currentForeignKey?.updateRule).toUpperCase() === desiredForeignKey.updateRule
|
|
288
1135
|
|
|
289
|
-
|
|
1136
|
+
if (!currentForeignKey || !sameColumns || !sameReferencedColumns || !sameTable || !sameDeleteRule || !sameUpdateRule) {
|
|
1137
|
+
plan.addForeignKeys.push(desiredForeignKey)
|
|
1138
|
+
}
|
|
290
1139
|
}
|
|
291
1140
|
|
|
292
|
-
|
|
293
|
-
|
|
1141
|
+
for (const currentForeignKey of currentForeignKeys.values()) {
|
|
1142
|
+
if (!desiredForeignKeys.has(currentForeignKey.name)) {
|
|
1143
|
+
plan.dropForeignKeys.push(currentForeignKey)
|
|
1144
|
+
}
|
|
294
1145
|
}
|
|
295
1146
|
|
|
296
|
-
|
|
297
|
-
|
|
1147
|
+
for (const desiredCheck of desiredChecks.values()) {
|
|
1148
|
+
const currentCheck = currentChecks.get(desiredCheck.name)
|
|
1149
|
+
if (currentCheck?.clause === desiredCheck.clause) {
|
|
1150
|
+
continue
|
|
1151
|
+
}
|
|
298
1152
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
1153
|
+
if (isSqliteDialectName(resolvedDialect)) {
|
|
1154
|
+
plan.warnings.push(buildUnsupportedConstraintWarning('check constraint', desiredCheck.name, resolvedDialect))
|
|
1155
|
+
continue
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
plan.addCheckConstraints.push(desiredCheck)
|
|
1159
|
+
}
|
|
302
1160
|
|
|
303
|
-
|
|
1161
|
+
for (const currentCheck of currentChecks.values()) {
|
|
1162
|
+
const matchesDesiredEnum = [...desiredColumns.values()].some((desiredColumn) => {
|
|
1163
|
+
return isInlineEnumCheckConstraint(currentCheck, desiredColumn)
|
|
1164
|
+
})
|
|
1165
|
+
if (matchesDesiredEnum) {
|
|
1166
|
+
continue
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
if (!desiredChecks.has(currentCheck.name)) {
|
|
1170
|
+
plan.warnings.push(buildUnsupportedConstraintWarning('check constraint removal', currentCheck.name, resolvedDialect))
|
|
1171
|
+
continue
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
if (desiredChecks.get(currentCheck.name)?.clause !== currentCheck.clause) {
|
|
1175
|
+
plan.warnings.push(buildUnsupportedConstraintWarning('check constraint alteration', currentCheck.name, resolvedDialect))
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
plan.addColumns.sort(compareByName)
|
|
1180
|
+
plan.alterColumns.sort(compareByName)
|
|
1181
|
+
plan.dropColumns.sort(compareByName)
|
|
1182
|
+
plan.addIndexes.sort(compareByName)
|
|
1183
|
+
plan.dropIndexes.sort(compareByName)
|
|
1184
|
+
plan.addForeignKeys.sort(compareByName)
|
|
1185
|
+
plan.dropForeignKeys.sort(compareByName)
|
|
1186
|
+
plan.addCheckConstraints.sort(compareByName)
|
|
1187
|
+
plan.warnings = [...new Set(plan.warnings)]
|
|
1188
|
+
|
|
1189
|
+
const blocks = []
|
|
1190
|
+
|
|
1191
|
+
const addOrAlterColumnLines = [
|
|
1192
|
+
...plan.addColumns.map((column) => buildColumnBuilderCode(column.name, column.definition, { dialect: resolvedDialect })),
|
|
1193
|
+
...plan.alterColumns.map((column) => buildColumnBuilderCode(column.name, column.definition, {
|
|
1194
|
+
dialect: resolvedDialect,
|
|
1195
|
+
alter: true,
|
|
1196
|
+
includePrimary: false
|
|
1197
|
+
}))
|
|
1198
|
+
]
|
|
1199
|
+
|
|
1200
|
+
if (plan.dropForeignKeys.length > 0) {
|
|
1201
|
+
blocks.push(` await knex.schema.alterTable(${quoteJsString(tableName)}, (table) => {
|
|
1202
|
+
${plan.dropForeignKeys.map((foreignKey) => ` table.dropForeign(${formatCodeLiteral(foreignKey.columns.map((entry) => entry.name))}, ${quoteJsString(foreignKey.name)});`).join('\n')}
|
|
1203
|
+
});`)
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
if (plan.dropIndexes.length > 0) {
|
|
1207
|
+
blocks.push(` await knex.schema.alterTable(${quoteJsString(tableName)}, (table) => {
|
|
1208
|
+
${plan.dropIndexes.map((index) => {
|
|
1209
|
+
if (index.unique) {
|
|
1210
|
+
return ` table.dropUnique(${formatCodeLiteral(index.columns)}, ${quoteJsString(index.name)});`
|
|
1211
|
+
}
|
|
1212
|
+
return ` table.dropIndex(${formatCodeLiteral(index.columns)}, ${quoteJsString(index.name)});`
|
|
1213
|
+
}).join('\n')}
|
|
1214
|
+
});`)
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
if (plan.dropColumns.length > 0) {
|
|
1218
|
+
blocks.push(` await knex.schema.alterTable(${quoteJsString(tableName)}, (table) => {
|
|
1219
|
+
${plan.dropColumns.map((column) => ` table.dropColumn(${quoteJsString(column.name)});`).join('\n')}
|
|
1220
|
+
});`)
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
if (addOrAlterColumnLines.length > 0) {
|
|
1224
|
+
blocks.push(` await knex.schema.alterTable(${quoteJsString(tableName)}, (table) => {
|
|
1225
|
+
${addOrAlterColumnLines.map((line) => ` ${line};`).join('\n')}
|
|
1226
|
+
});`)
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
const addConstraintLines = [
|
|
1230
|
+
...plan.addIndexes.map((index) => buildIndexLine(index)),
|
|
1231
|
+
...plan.addForeignKeys.map((foreignKey) => buildForeignKeyLine(foreignKey)),
|
|
1232
|
+
...plan.addCheckConstraints.map((constraint) => buildCheckConstraintLine(constraint))
|
|
1233
|
+
]
|
|
1234
|
+
|
|
1235
|
+
if (addConstraintLines.length > 0) {
|
|
1236
|
+
blocks.push(` await knex.schema.alterTable(${quoteJsString(tableName)}, (table) => {
|
|
1237
|
+
${addConstraintLines.map((line) => ` ${line};`).join('\n')}
|
|
1238
|
+
});`)
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
const warningComment = plan.warnings.length > 0
|
|
1242
|
+
? ` // Warnings:\n${plan.warnings.map((warning) => ` // - ${warning}`).join('\n')}\n`
|
|
1243
|
+
: ''
|
|
1244
|
+
|
|
1245
|
+
return {
|
|
1246
|
+
migration: `exports.up = async function(knex) {
|
|
1247
|
+
${warningComment}${blocks.length > 0 ? `${blocks.join('\n\n')}\n` : ' return Promise.resolve();\n'}};
|
|
1248
|
+
|
|
1249
|
+
exports.down = async function() {
|
|
1250
|
+
throw new Error(${quoteJsString(`Down migration was not generated automatically for table '${tableName}'.`)});
|
|
1251
|
+
};`,
|
|
1252
|
+
warnings: plan.warnings,
|
|
1253
|
+
plan
|
|
1254
|
+
}
|
|
304
1255
|
}
|