sonamu 0.6.0 → 0.7.1
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/.swcrc.project-default +18 -0
- package/bin/cli.js +24 -0
- package/dist/ai/agents/agent.d.ts +11 -0
- package/dist/ai/agents/agent.d.ts.map +1 -0
- package/dist/ai/agents/agent.js +65 -0
- package/dist/ai/agents/index.d.ts +3 -0
- package/dist/ai/agents/index.d.ts.map +1 -0
- package/dist/ai/agents/index.js +4 -0
- package/dist/ai/agents/types.d.ts +43 -0
- package/dist/ai/agents/types.d.ts.map +1 -0
- package/dist/ai/agents/types.js +3 -0
- package/dist/ai/index.d.ts +2 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +3 -0
- package/dist/ai/providers/rtzr/api.d.ts +22 -0
- package/dist/ai/providers/rtzr/api.d.ts.map +1 -0
- package/dist/ai/providers/rtzr/api.js +28 -0
- package/dist/ai/providers/rtzr/error.d.ts +18 -0
- package/dist/ai/providers/rtzr/error.d.ts.map +1 -0
- package/dist/ai/providers/rtzr/error.js +29 -0
- package/dist/ai/providers/rtzr/index.d.ts +5 -0
- package/dist/ai/providers/rtzr/index.d.ts.map +1 -0
- package/dist/ai/providers/rtzr/index.js +6 -0
- package/dist/ai/providers/rtzr/model.d.ts +52 -0
- package/dist/ai/providers/rtzr/model.d.ts.map +1 -0
- package/dist/ai/providers/rtzr/model.js +137 -0
- package/dist/ai/providers/rtzr/options.d.ts +7 -0
- package/dist/ai/providers/rtzr/options.d.ts.map +1 -0
- package/dist/ai/providers/rtzr/options.js +47 -0
- package/dist/ai/providers/rtzr/provider.d.ts +18 -0
- package/dist/ai/providers/rtzr/provider.d.ts.map +1 -0
- package/dist/ai/providers/rtzr/provider.js +54 -0
- package/dist/ai/providers/rtzr/utils.d.ts +19 -0
- package/dist/ai/providers/rtzr/utils.d.ts.map +1 -0
- package/dist/ai/providers/rtzr/utils.js +88 -0
- package/dist/api/base-frame.d.ts +2 -2
- package/dist/api/base-frame.d.ts.map +1 -1
- package/dist/api/base-frame.js +2 -1
- package/dist/api/caster.d.ts.map +1 -1
- package/dist/api/caster.js +6 -1
- package/dist/api/code-converters.d.ts +58 -14
- package/dist/api/code-converters.d.ts.map +1 -1
- package/dist/api/code-converters.js +178 -409
- package/dist/api/config.d.ts +27 -13
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +19 -26
- package/dist/api/context.d.ts +4 -3
- package/dist/api/context.d.ts.map +1 -1
- package/dist/api/context.js +1 -1
- package/dist/api/decorators.d.ts +20 -6
- package/dist/api/decorators.d.ts.map +1 -1
- package/dist/api/decorators.js +111 -18
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +3 -3
- package/dist/api/sonamu.d.ts +7 -7
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +83 -51
- package/dist/api/validator.d.ts +6 -0
- package/dist/api/validator.d.ts.map +1 -0
- package/dist/api/validator.js +81 -0
- package/dist/bin/build-config.d.ts +5 -1
- package/dist/bin/build-config.d.ts.map +1 -1
- package/dist/bin/build-config.js +5 -2
- package/dist/bin/cli.js +165 -64
- package/dist/bin/loader-register.d.ts +2 -0
- package/dist/bin/loader-register.d.ts.map +1 -0
- package/dist/bin/loader-register.js +34 -0
- package/dist/database/_batch_update.d.ts +5 -3
- package/dist/database/_batch_update.d.ts.map +1 -1
- package/dist/database/_batch_update.js +30 -13
- package/dist/database/base-model.d.ts +96 -10
- package/dist/database/base-model.d.ts.map +1 -1
- package/dist/database/base-model.js +232 -89
- package/dist/database/base-model.types.d.ts +93 -0
- package/dist/database/base-model.types.d.ts.map +1 -0
- package/dist/database/base-model.types.js +10 -0
- package/dist/database/code-generator.d.ts +1 -1
- package/dist/database/code-generator.d.ts.map +1 -1
- package/dist/database/code-generator.js +11 -10
- package/dist/database/db.d.ts +5 -6
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +22 -25
- package/dist/database/puri-subset.test-d.js +81 -0
- package/dist/database/puri-subset.types.d.ts +123 -0
- package/dist/database/puri-subset.types.d.ts.map +1 -0
- package/dist/database/puri-subset.types.js +16 -0
- package/dist/database/puri-wrapper.d.ts +13 -11
- package/dist/database/puri-wrapper.d.ts.map +1 -1
- package/dist/database/puri-wrapper.js +2 -2
- package/dist/database/puri.d.ts +25 -14
- package/dist/database/puri.d.ts.map +1 -1
- package/dist/database/puri.js +83 -21
- package/dist/database/puri.types.d.ts +21 -7
- package/dist/database/puri.types.d.ts.map +1 -1
- package/dist/database/puri.types.js +4 -1
- package/dist/database/transaction-context.d.ts +1 -1
- package/dist/database/transaction-context.d.ts.map +1 -1
- package/dist/database/transaction-context.js +1 -1
- package/dist/database/upsert-builder.d.ts +9 -3
- package/dist/database/upsert-builder.d.ts.map +1 -1
- package/dist/database/upsert-builder.js +227 -78
- package/dist/entity/entity-manager.d.ts +165 -2
- package/dist/entity/entity-manager.d.ts.map +1 -1
- package/dist/entity/entity-manager.js +26 -10
- package/dist/entity/entity.d.ts +5 -3
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +153 -54
- package/dist/exceptions/error-handler.d.ts +1 -1
- package/dist/exceptions/error-handler.d.ts.map +1 -1
- package/dist/exceptions/error-handler.js +1 -1
- package/dist/exceptions/so-exceptions.d.ts +1 -1
- package/dist/exceptions/so-exceptions.d.ts.map +1 -1
- package/dist/exceptions/so-exceptions.js +1 -1
- package/dist/file-storage/driver.d.ts +1 -1
- package/dist/file-storage/driver.d.ts.map +1 -1
- package/dist/file-storage/driver.js +1 -1
- package/dist/file-storage/file-storage.js +2 -2
- package/dist/index.d.ts +18 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -13
- package/dist/migration/code-generation.d.ts +1 -1
- package/dist/migration/code-generation.d.ts.map +1 -1
- package/dist/migration/code-generation.js +123 -67
- package/dist/migration/migration-set.d.ts +2 -10
- package/dist/migration/migration-set.d.ts.map +1 -1
- package/dist/migration/migration-set.js +67 -218
- package/dist/migration/migrator.d.ts +24 -73
- package/dist/migration/migrator.d.ts.map +1 -1
- package/dist/migration/migrator.js +121 -301
- package/dist/migration/postgresql-schema-reader.d.ts +51 -0
- package/dist/migration/postgresql-schema-reader.d.ts.map +1 -0
- package/dist/migration/postgresql-schema-reader.js +245 -0
- package/dist/migration/types.d.ts +6 -38
- package/dist/migration/types.d.ts.map +1 -1
- package/dist/migration/types.js +1 -1
- package/dist/naite/messaging-types.d.ts +43 -0
- package/dist/naite/messaging-types.d.ts.map +1 -0
- package/dist/naite/messaging-types.js +7 -0
- package/dist/naite/naite-reporter.d.ts +41 -0
- package/dist/naite/naite-reporter.d.ts.map +1 -0
- package/dist/naite/naite-reporter.js +102 -0
- package/dist/naite/naite.d.ts +91 -8
- package/dist/naite/naite.d.ts.map +1 -1
- package/dist/naite/naite.js +285 -41
- package/dist/stream/sse.d.ts +2 -2
- package/dist/stream/sse.d.ts.map +1 -1
- package/dist/stream/sse.js +1 -1
- package/dist/syncer/api-parser.d.ts +3 -13
- package/dist/syncer/api-parser.d.ts.map +1 -1
- package/dist/syncer/api-parser.js +67 -56
- package/dist/syncer/checksum.d.ts +2 -2
- package/dist/syncer/checksum.d.ts.map +1 -1
- package/dist/syncer/checksum.js +11 -11
- package/dist/syncer/code-generator.d.ts +3 -3
- package/dist/syncer/code-generator.d.ts.map +1 -1
- package/dist/syncer/code-generator.js +37 -17
- package/dist/syncer/entity-operations.d.ts +2 -2
- package/dist/syncer/entity-operations.d.ts.map +1 -1
- package/dist/syncer/entity-operations.js +9 -8
- package/dist/syncer/file-patterns.d.ts +1 -1
- package/dist/syncer/file-patterns.d.ts.map +1 -1
- package/dist/syncer/file-patterns.js +1 -1
- package/dist/syncer/index.d.ts +4 -4
- package/dist/syncer/index.d.ts.map +1 -1
- package/dist/syncer/index.js +5 -5
- package/dist/syncer/module-loader.d.ts +4 -4
- package/dist/syncer/module-loader.d.ts.map +1 -1
- package/dist/syncer/module-loader.js +17 -12
- package/dist/syncer/syncer.d.ts +31 -24
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +92 -45
- package/dist/template/entity-converter.d.ts +1 -1
- package/dist/template/entity-converter.d.ts.map +1 -1
- package/dist/template/entity-converter.js +15 -8
- package/dist/template/helpers.d.ts +2 -2
- package/dist/template/helpers.d.ts.map +1 -1
- package/dist/template/helpers.js +3 -3
- package/dist/template/implementations/entity.template.d.ts +2 -2
- package/dist/template/implementations/entity.template.d.ts.map +1 -1
- package/dist/template/implementations/entity.template.js +4 -5
- package/dist/template/implementations/generated.template.d.ts +2 -3
- package/dist/template/implementations/generated.template.d.ts.map +1 -1
- package/dist/template/implementations/generated.template.js +46 -29
- package/dist/template/implementations/generated_http.template.d.ts +2 -3
- package/dist/template/implementations/generated_http.template.d.ts.map +1 -1
- package/dist/template/implementations/generated_http.template.js +9 -9
- package/dist/template/implementations/generated_sso.template.d.ts +3 -4
- package/dist/template/implementations/generated_sso.template.d.ts.map +1 -1
- package/dist/template/implementations/generated_sso.template.js +54 -25
- package/dist/template/implementations/init_types.template.d.ts +2 -2
- package/dist/template/implementations/init_types.template.d.ts.map +1 -1
- package/dist/template/implementations/init_types.template.js +2 -2
- package/dist/template/implementations/model.template.d.ts +2 -2
- package/dist/template/implementations/model.template.d.ts.map +1 -1
- package/dist/template/implementations/model.template.js +47 -37
- package/dist/template/implementations/model_test.template.d.ts +2 -2
- package/dist/template/implementations/model_test.template.d.ts.map +1 -1
- package/dist/template/implementations/model_test.template.js +2 -2
- package/dist/template/implementations/service.template.d.ts +4 -4
- package/dist/template/implementations/service.template.d.ts.map +1 -1
- package/dist/template/implementations/service.template.js +24 -16
- package/dist/template/implementations/view_enums_buttonset.template.d.ts +2 -2
- package/dist/template/implementations/view_enums_buttonset.template.d.ts.map +1 -1
- package/dist/template/implementations/view_enums_buttonset.template.js +1 -1
- package/dist/template/implementations/view_enums_dropdown.template.d.ts +2 -2
- package/dist/template/implementations/view_enums_dropdown.template.d.ts.map +1 -1
- package/dist/template/implementations/view_enums_dropdown.template.js +2 -2
- package/dist/template/implementations/view_enums_select.template.d.ts +2 -2
- package/dist/template/implementations/view_enums_select.template.d.ts.map +1 -1
- package/dist/template/implementations/view_enums_select.template.js +2 -2
- package/dist/template/implementations/view_form.template.d.ts +2 -2
- package/dist/template/implementations/view_form.template.d.ts.map +1 -1
- package/dist/template/implementations/view_form.template.js +4 -4
- package/dist/template/implementations/view_id_all_select.template.d.ts +2 -2
- package/dist/template/implementations/view_id_all_select.template.d.ts.map +1 -1
- package/dist/template/implementations/view_id_all_select.template.js +1 -1
- package/dist/template/implementations/view_id_async_select.template.d.ts +2 -2
- package/dist/template/implementations/view_id_async_select.template.d.ts.map +1 -1
- package/dist/template/implementations/view_id_async_select.template.js +1 -1
- package/dist/template/implementations/view_list.template.d.ts +2 -2
- package/dist/template/implementations/view_list.template.d.ts.map +1 -1
- package/dist/template/implementations/view_list.template.js +29 -19
- package/dist/template/implementations/view_list_columns.template.d.ts +3 -3
- package/dist/template/implementations/view_list_columns.template.d.ts.map +1 -1
- package/dist/template/implementations/view_list_columns.template.js +1 -1
- package/dist/template/implementations/view_search_input.template.d.ts +2 -2
- package/dist/template/implementations/view_search_input.template.d.ts.map +1 -1
- package/dist/template/implementations/view_search_input.template.js +1 -1
- package/dist/template/index.d.ts +4 -2
- package/dist/template/index.d.ts.map +1 -1
- package/dist/template/index.js +5 -3
- package/dist/template/template-manager.d.ts +56 -0
- package/dist/template/template-manager.d.ts.map +1 -0
- package/dist/template/template-manager.js +125 -0
- package/dist/template/template-types.d.ts +16 -0
- package/dist/template/template-types.d.ts.map +1 -0
- package/dist/template/template-types.js +7 -0
- package/dist/template/template.d.ts +12 -2
- package/dist/template/template.d.ts.map +1 -1
- package/dist/template/template.js +19 -6
- package/dist/template/zod-converter.d.ts +40 -7
- package/dist/template/zod-converter.d.ts.map +1 -1
- package/dist/template/zod-converter.js +386 -58
- package/dist/testing/_relation-graph.d.ts +1 -1
- package/dist/testing/_relation-graph.d.ts.map +1 -1
- package/dist/testing/_relation-graph.js +12 -3
- package/dist/testing/fixture-manager.d.ts +42 -11
- package/dist/testing/fixture-manager.d.ts.map +1 -1
- package/dist/testing/fixture-manager.js +338 -236
- package/dist/types/types.d.ts +709 -104
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +309 -52
- package/dist/typings/knex.d.js +2 -2
- package/dist/utils/async-utils.d.ts.map +1 -1
- package/dist/utils/async-utils.js +3 -3
- package/dist/utils/console-util.js +1 -1
- package/dist/utils/controller.d.ts +1 -0
- package/dist/utils/controller.d.ts.map +1 -1
- package/dist/utils/controller.js +4 -1
- package/dist/utils/esm-utils.d.ts +0 -6
- package/dist/utils/esm-utils.d.ts.map +1 -1
- package/dist/utils/esm-utils.js +2 -9
- package/dist/utils/formatter.d.ts +3 -0
- package/dist/utils/formatter.d.ts.map +1 -0
- package/dist/utils/formatter.js +110 -0
- package/dist/utils/fs-utils.d.ts +1 -1
- package/dist/utils/fs-utils.d.ts.map +1 -1
- package/dist/utils/fs-utils.js +1 -1
- package/dist/utils/lodash-able.d.ts.map +1 -1
- package/dist/utils/lodash-able.js +1 -1
- package/dist/utils/object-utils.d.ts +44 -0
- package/dist/utils/object-utils.d.ts.map +1 -0
- package/dist/utils/object-utils.js +191 -0
- package/dist/utils/path-utils.d.ts +1 -1
- package/dist/utils/path-utils.d.ts.map +1 -1
- package/dist/utils/path-utils.js +3 -3
- package/dist/utils/process-utils.js +1 -1
- package/dist/utils/sql-parser.d.ts +5 -1
- package/dist/utils/sql-parser.d.ts.map +1 -1
- package/dist/utils/sql-parser.js +14 -3
- package/dist/utils/type-utils.d.ts +23 -0
- package/dist/utils/type-utils.d.ts.map +1 -0
- package/dist/utils/type-utils.js +45 -0
- package/dist/utils/utils.d.ts +7 -1
- package/dist/utils/utils.d.ts.map +1 -1
- package/dist/utils/utils.js +44 -5
- package/dist/utils/zod-error.d.ts +1 -1
- package/dist/utils/zod-error.d.ts.map +1 -1
- package/dist/utils/zod-error.js +1 -1
- package/package.json +55 -30
- package/src/ai/agents/agent.ts +87 -0
- package/src/ai/agents/index.ts +2 -0
- package/src/ai/agents/types.ts +47 -0
- package/src/ai/index.ts +1 -0
- package/src/ai/providers/rtzr/api.ts +37 -0
- package/src/ai/providers/rtzr/error.ts +34 -0
- package/src/ai/providers/rtzr/index.ts +4 -0
- package/src/ai/providers/rtzr/model.ts +201 -0
- package/src/ai/providers/rtzr/options.ts +49 -0
- package/src/ai/providers/rtzr/provider.ts +91 -0
- package/src/ai/providers/rtzr/utils.ts +127 -0
- package/src/api/base-frame.ts +4 -2
- package/src/api/caster.ts +17 -23
- package/src/api/code-converters.ts +176 -533
- package/src/api/config.ts +39 -56
- package/src/api/context.ts +7 -18
- package/src/api/decorators.ts +175 -46
- package/src/api/index.ts +2 -2
- package/src/api/sonamu.ts +133 -124
- package/src/api/validator.ts +83 -0
- package/src/bin/build-config.ts +7 -1
- package/src/bin/cli.ts +192 -110
- package/src/bin/loader-register.ts +38 -0
- package/src/database/_batch_update.ts +46 -31
- package/src/database/base-model.ts +390 -182
- package/src/database/base-model.types.ts +155 -0
- package/src/database/code-generator.ts +13 -32
- package/src/database/db.ts +36 -50
- package/src/database/puri-subset.test-d.ts +471 -0
- package/src/database/puri-subset.types.ts +195 -0
- package/src/database/puri-wrapper.ts +58 -67
- package/src/database/puri.ts +182 -126
- package/src/database/puri.types.ts +64 -31
- package/src/database/transaction-context.ts +1 -1
- package/src/database/upsert-builder.ts +261 -132
- package/src/entity/entity-manager.ts +36 -28
- package/src/entity/entity.ts +330 -249
- package/src/exceptions/error-handler.ts +3 -3
- package/src/exceptions/so-exceptions.ts +11 -11
- package/src/file-storage/driver.ts +5 -5
- package/src/file-storage/file-storage.ts +2 -2
- package/src/index.ts +18 -12
- package/src/migration/code-generation.ts +185 -172
- package/src/migration/migration-set.ts +80 -293
- package/src/migration/migrator.ts +182 -425
- package/src/migration/mysql-schema-reader.ts.txt +272 -0
- package/src/migration/postgresql-schema-reader.ts +310 -0
- package/src/migration/types.ts +6 -39
- package/src/naite/messaging-types.ts +51 -0
- package/src/naite/naite-reporter.ts +128 -0
- package/src/naite/naite.ts +378 -33
- package/src/shared/web.shared.ts.txt +20 -24
- package/src/stream/sse.ts +5 -5
- package/src/syncer/api-parser.ts +52 -69
- package/src/syncer/checksum.ts +25 -37
- package/src/syncer/code-generator.ts +58 -62
- package/src/syncer/entity-operations.ts +12 -15
- package/src/syncer/file-patterns.ts +2 -2
- package/src/syncer/index.ts +4 -4
- package/src/syncer/module-loader.ts +28 -25
- package/src/syncer/syncer.ts +155 -162
- package/src/template/entity-converter.ts +18 -27
- package/src/template/helpers.ts +8 -11
- package/src/template/implementations/entity.template.ts +6 -6
- package/src/template/implementations/generated.template.ts +99 -99
- package/src/template/implementations/generated_http.template.ts +21 -54
- package/src/template/implementations/generated_sso.template.ts +78 -65
- package/src/template/implementations/init_types.template.ts +4 -6
- package/src/template/implementations/model.template.ts +47 -38
- package/src/template/implementations/model_test.template.ts +3 -3
- package/src/template/implementations/service.template.ts +56 -80
- package/src/template/implementations/view_enums_buttonset.template.ts +2 -2
- package/src/template/implementations/view_enums_dropdown.template.ts +4 -4
- package/src/template/implementations/view_enums_select.template.ts +3 -3
- package/src/template/implementations/view_form.template.ts +34 -75
- package/src/template/implementations/view_id_all_select.template.ts +2 -2
- package/src/template/implementations/view_id_async_select.template.ts +9 -23
- package/src/template/implementations/view_list.template.ts +54 -95
- package/src/template/implementations/view_list_columns.template.ts +4 -10
- package/src/template/implementations/view_search_input.template.ts +2 -2
- package/src/template/index.ts +4 -2
- package/src/template/template-manager.ts +166 -0
- package/src/template/template-types.ts +16 -0
- package/src/template/template.ts +29 -10
- package/src/template/zod-converter.ts +459 -101
- package/src/testing/_relation-graph.ts +18 -11
- package/src/testing/fixture-manager.ts +468 -362
- package/src/types/types.ts +516 -248
- package/src/typings/knex.d.ts +7 -9
- package/src/utils/async-utils.ts +8 -12
- package/src/utils/console-util.ts +1 -1
- package/src/utils/controller.ts +3 -0
- package/src/utils/esm-utils.ts +8 -18
- package/src/utils/formatter.ts +109 -0
- package/src/utils/fs-utils.ts +1 -1
- package/src/utils/lodash-able.ts +1 -4
- package/src/utils/object-utils.ts +217 -0
- package/src/utils/path-utils.ts +3 -6
- package/src/utils/process-utils.ts +1 -1
- package/src/utils/sql-parser.ts +23 -5
- package/src/utils/type-utils.ts +83 -0
- package/src/utils/utils.ts +58 -9
- package/src/utils/zod-error.ts +3 -3
- package/dist/bin/cli-wrapper.d.ts +0 -3
- package/dist/bin/cli-wrapper.d.ts.map +0 -1
- package/dist/bin/cli-wrapper.js +0 -72
- package/dist/database/knex-plugins/knex-on-duplicate-update.d.ts +0 -2
- package/dist/database/knex-plugins/knex-on-duplicate-update.d.ts.map +0 -1
- package/dist/database/knex-plugins/knex-on-duplicate-update.js +0 -39
- package/dist/entity/entity-utils.d.ts +0 -61
- package/dist/entity/entity-utils.d.ts.map +0 -1
- package/dist/entity/entity-utils.js +0 -210
- package/src/bin/cli-wrapper.ts +0 -82
- package/src/database/knex-plugins/knex-on-duplicate-update.ts +0 -45
- package/src/entity/entity-utils.ts +0 -291
|
@@ -1,27 +1,38 @@
|
|
|
1
|
+
import assert from "assert";
|
|
1
2
|
import chalk from "chalk";
|
|
2
|
-
import
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
5
|
+
import inflection from "inflection";
|
|
6
|
+
import knex, { type Knex } from "knex";
|
|
7
|
+
import { unique } from "radashi";
|
|
8
|
+
import { inspect } from "util";
|
|
3
9
|
import { Sonamu } from "../api";
|
|
10
|
+
import { BaseModel } from "../database/base-model";
|
|
11
|
+
import type { SonamuDBConfig } from "../database/db";
|
|
12
|
+
import { type UBRef, UpsertBuilder } from "../database/upsert-builder";
|
|
13
|
+
import type { Entity } from "../entity/entity";
|
|
4
14
|
import { EntityManager } from "../entity/entity-manager";
|
|
5
15
|
import {
|
|
6
|
-
EntityProp,
|
|
7
|
-
FixtureImportResult,
|
|
8
|
-
FixtureRecord,
|
|
9
|
-
FixtureSearchOptions,
|
|
10
|
-
ManyToManyRelationProp,
|
|
16
|
+
type EntityProp,
|
|
17
|
+
type FixtureImportResult,
|
|
18
|
+
type FixtureRecord,
|
|
19
|
+
type FixtureSearchOptions,
|
|
11
20
|
isBelongsToOneRelationProp,
|
|
12
21
|
isHasManyRelationProp,
|
|
13
22
|
isManyToManyRelationProp,
|
|
14
23
|
isOneToOneRelationProp,
|
|
15
24
|
isRelationProp,
|
|
16
25
|
isVirtualProp,
|
|
26
|
+
type ManyToManyRelationProp,
|
|
17
27
|
} from "../types/types";
|
|
18
|
-
import { Entity } from "../entity/entity";
|
|
19
|
-
import inflection from "inflection";
|
|
20
|
-
import { readFileSync, writeFileSync } from "fs";
|
|
21
28
|
import { RelationGraph } from "./_relation-graph";
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
|
|
30
|
+
/** 사용자 지정 중복 확인 컬럼 (entityId별로 지정) */
|
|
31
|
+
export interface DuplicateCheckOptions {
|
|
32
|
+
columns?: {
|
|
33
|
+
[entityId: string]: string[];
|
|
34
|
+
};
|
|
35
|
+
}
|
|
25
36
|
|
|
26
37
|
export class FixtureManagerClass {
|
|
27
38
|
private _tdb: Knex | null = null;
|
|
@@ -49,6 +60,12 @@ export class FixtureManagerClass {
|
|
|
49
60
|
|
|
50
61
|
private relationGraph = new RelationGraph();
|
|
51
62
|
|
|
63
|
+
// UpsertBuilder 기반 import를 위한 상태
|
|
64
|
+
private builder: UpsertBuilder = new UpsertBuilder();
|
|
65
|
+
private fixtureRefMap: Map<string, UBRef> = new Map();
|
|
66
|
+
private uuidToFixtureId: Map<string, string> = new Map();
|
|
67
|
+
private skippedFixtures: Map<string, { entityId: string; existingId: number }> = new Map();
|
|
68
|
+
|
|
52
69
|
init() {
|
|
53
70
|
if (this._tdb !== null) {
|
|
54
71
|
return;
|
|
@@ -57,89 +74,19 @@ export class FixtureManagerClass {
|
|
|
57
74
|
const tConn = Sonamu.dbConfig.test.connection as Knex.ConnectionConfig & {
|
|
58
75
|
port?: number;
|
|
59
76
|
};
|
|
60
|
-
const pConn = Sonamu.dbConfig.production_master
|
|
61
|
-
|
|
77
|
+
const pConn = Sonamu.dbConfig.production_master.connection as Knex.ConnectionConfig & {
|
|
78
|
+
port?: number;
|
|
79
|
+
};
|
|
62
80
|
if (
|
|
63
|
-
`${tConn.host ?? "localhost"}:${tConn.port ??
|
|
64
|
-
|
|
65
|
-
}` ===
|
|
66
|
-
`${pConn.host ?? "localhost"}:${pConn.port ?? 3306}/${pConn.database}`
|
|
81
|
+
`${tConn.host ?? "localhost"}:${tConn.port ?? 5432}/${tConn.database}` ===
|
|
82
|
+
`${pConn.host ?? "localhost"}:${pConn.port ?? 5432}/${pConn.database}`
|
|
67
83
|
) {
|
|
68
|
-
throw new Error(
|
|
69
|
-
`테스트DB와 프로덕션DB에 동일한 데이터베이스가 사용되었습니다.`
|
|
70
|
-
);
|
|
84
|
+
throw new Error(`테스트DB와 프로덕션DB에 동일한 데이터베이스가 사용되었습니다.`);
|
|
71
85
|
}
|
|
72
86
|
}
|
|
73
87
|
|
|
74
88
|
this.tdb = knex(Sonamu.dbConfig.test);
|
|
75
|
-
this.fdb = knex(Sonamu.dbConfig.
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async cleanAndSeed(usingTables?: string[]) {
|
|
79
|
-
const tableNames: string[] = await (async () => {
|
|
80
|
-
if (usingTables) {
|
|
81
|
-
return usingTables;
|
|
82
|
-
}
|
|
83
|
-
if (this.cachedTableNames) {
|
|
84
|
-
return this.cachedTableNames;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const [tables] = await this.tdb.raw(
|
|
88
|
-
`SHOW TABLE STATUS WHERE Engine IS NOT NULL AND Name != 'migrations'`
|
|
89
|
-
);
|
|
90
|
-
const tableNames = tables.map(
|
|
91
|
-
(tableInfo: { Name: string }) => tableInfo["Name"]
|
|
92
|
-
);
|
|
93
|
-
this.cachedTableNames = tableNames;
|
|
94
|
-
return tableNames;
|
|
95
|
-
})();
|
|
96
|
-
|
|
97
|
-
// migrations 제외한 테이블 목록
|
|
98
|
-
const tableListStr = tableNames.join(", ");
|
|
99
|
-
|
|
100
|
-
// 한 번에 모든 테이블 체크섬 확인
|
|
101
|
-
const [fdbChecksumRows] = await this.fdb.raw<
|
|
102
|
-
[{ Table: string; Checksum: number }[]]
|
|
103
|
-
>(`CHECKSUM TABLE ${tableListStr}`);
|
|
104
|
-
const [tdbChecksumRows] = await this.tdb.raw<
|
|
105
|
-
[{ Table: string; Checksum: number }[]]
|
|
106
|
-
>(`CHECKSUM TABLE ${tableListStr}`);
|
|
107
|
-
|
|
108
|
-
// 체크섬 맵 생성
|
|
109
|
-
const fdbChecksums = new Map(
|
|
110
|
-
fdbChecksumRows.map((row) => [row.Table.split(".").pop()!, row.Checksum])
|
|
111
|
-
);
|
|
112
|
-
const tdbChecksums = new Map(
|
|
113
|
-
tdbChecksumRows.map((row) => [row.Table.split(".").pop()!, row.Checksum])
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
// 변경된 테이블들만 처리
|
|
117
|
-
const changedTables = tableNames.filter(
|
|
118
|
-
(tableName) => fdbChecksums.get(tableName) !== tdbChecksums.get(tableName)
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
// 병렬로 truncate + insert 실행
|
|
122
|
-
await this.tdb.transaction(async (trx) => {
|
|
123
|
-
await trx.raw(`SET FOREIGN_KEY_CHECKS = 0`);
|
|
124
|
-
|
|
125
|
-
await Promise.all(
|
|
126
|
-
changedTables.map(async (tableName) => {
|
|
127
|
-
await trx.raw(`SET FOREIGN_KEY_CHECKS = 0`);
|
|
128
|
-
await trx(tableName).truncate();
|
|
129
|
-
const rawQuery = `INSERT INTO ${
|
|
130
|
-
(Sonamu.dbConfig.test.connection as Knex.ConnectionConfig).database
|
|
131
|
-
}.${tableName}
|
|
132
|
-
SELECT * FROM ${
|
|
133
|
-
(Sonamu.dbConfig.fixture_local.connection as Knex.ConnectionConfig)
|
|
134
|
-
.database
|
|
135
|
-
}.${tableName}`;
|
|
136
|
-
await trx.raw(rawQuery);
|
|
137
|
-
})
|
|
138
|
-
);
|
|
139
|
-
await trx.raw(`SET FOREIGN_KEY_CHECKS = 1`);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// console.timeEnd("FIXTURE-CleanAndSeed");
|
|
89
|
+
this.fdb = knex(Sonamu.dbConfig.fixture_remote);
|
|
143
90
|
}
|
|
144
91
|
|
|
145
92
|
async getChecksum(db: Knex, tableName: string) {
|
|
@@ -147,68 +94,48 @@ export class FixtureManagerClass {
|
|
|
147
94
|
return checksumRow.Checksum;
|
|
148
95
|
}
|
|
149
96
|
|
|
97
|
+
/**
|
|
98
|
+
이제 FixtureManager.sync() 는 checksum 비교 없이 create database template 으로 수행합니다.
|
|
99
|
+
*/
|
|
150
100
|
async sync() {
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
101
|
+
const fixtureConn = Sonamu.dbConfig.fixture_remote.connection as Knex.PgConnectionConfig;
|
|
102
|
+
const testConn = Sonamu.dbConfig.test.connection as Knex.PgConnectionConfig;
|
|
103
|
+
|
|
104
|
+
// PostgreSQL 패스워드 환경변수 설정
|
|
105
|
+
const pgEnv = { PGPASSWORD: testConn.password || "" };
|
|
106
|
+
|
|
107
|
+
// 1. 연결 강제 종료
|
|
108
|
+
execSync(
|
|
109
|
+
`psql -h ${testConn.host} -p ${testConn.port ?? 5432} -U ${testConn.user} -d postgres -c "
|
|
110
|
+
SELECT pg_terminate_backend(pg_stat_activity.pid)
|
|
111
|
+
FROM pg_stat_activity
|
|
112
|
+
WHERE datname = '${testConn.database}'
|
|
113
|
+
AND pid <> pg_backend_pid();
|
|
114
|
+
"`,
|
|
115
|
+
{ stdio: "inherit", env: { ...process.env, ...pgEnv } as NodeJS.ProcessEnv },
|
|
158
116
|
);
|
|
159
117
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (remoteChecksum !== localChecksum) {
|
|
171
|
-
await this.fdb.transaction(async (transaction) => {
|
|
172
|
-
await transaction.raw(`SET FOREIGN_KEY_CHECKS = 0`);
|
|
173
|
-
await transaction(tableName).truncate();
|
|
174
|
-
|
|
175
|
-
const rows = await frdb(tableName);
|
|
176
|
-
if (rows.length === 0) {
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
118
|
+
execSync(
|
|
119
|
+
`psql -h ${fixtureConn.host} -p ${fixtureConn.port ?? 5432} -U ${fixtureConn.user} -d postgres -c "
|
|
120
|
+
SELECT pg_terminate_backend(pg_stat_activity.pid)
|
|
121
|
+
FROM pg_stat_activity
|
|
122
|
+
WHERE datname = '${fixtureConn.database}'
|
|
123
|
+
AND pid <> pg_backend_pid();
|
|
124
|
+
"`,
|
|
125
|
+
{ stdio: "inherit", env: { ...process.env, ...pgEnv } as NodeJS.ProcessEnv },
|
|
126
|
+
);
|
|
179
127
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return Object.fromEntries(
|
|
185
|
-
Object.entries(row).map(([key, value]) => {
|
|
186
|
-
if (value === null) {
|
|
187
|
-
return [key, null];
|
|
188
|
-
} else if (typeof value === "boolean") {
|
|
189
|
-
return [key, value ? 1 : 0];
|
|
190
|
-
} else if (
|
|
191
|
-
typeof value === "object" &&
|
|
192
|
-
!(value instanceof Date)
|
|
193
|
-
) {
|
|
194
|
-
return [key, JSON.stringify(value)];
|
|
195
|
-
} else {
|
|
196
|
-
return [key, value];
|
|
197
|
-
}
|
|
198
|
-
})
|
|
199
|
-
);
|
|
200
|
-
})
|
|
201
|
-
)
|
|
202
|
-
.into(tableName);
|
|
203
|
-
console.log("OK");
|
|
204
|
-
await transaction.raw(`SET FOREIGN_KEY_CHECKS = 1`);
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
})
|
|
128
|
+
// 2. DROP DATABASE (별도 실행!)
|
|
129
|
+
execSync(
|
|
130
|
+
`psql -h ${testConn.host} -p ${testConn.port ?? 5432} -U ${testConn.user} -d postgres -c "DROP DATABASE IF EXISTS \\"${testConn.database}\\""`,
|
|
131
|
+
{ stdio: "inherit", env: { ...process.env, ...pgEnv } as NodeJS.ProcessEnv },
|
|
208
132
|
);
|
|
209
|
-
console.log(chalk.magenta("DONE!"));
|
|
210
133
|
|
|
211
|
-
|
|
134
|
+
// 3. CREATE DATABASE
|
|
135
|
+
execSync(
|
|
136
|
+
`psql -h ${testConn.host} -p ${testConn.port ?? 5432} -U ${testConn.user} -d postgres -c "CREATE DATABASE \\"${testConn.database}\\" TEMPLATE \\"${fixtureConn.database}\\""`,
|
|
137
|
+
{ stdio: "inherit", env: { ...process.env, ...pgEnv } as NodeJS.ProcessEnv },
|
|
138
|
+
);
|
|
212
139
|
}
|
|
213
140
|
|
|
214
141
|
private visitedRecords = new Set<string>();
|
|
@@ -216,18 +143,18 @@ export class FixtureManagerClass {
|
|
|
216
143
|
// 방문 기록 초기화 (새로운 import 작업 시작)
|
|
217
144
|
this.visitedRecords.clear();
|
|
218
145
|
|
|
219
|
-
const queries =
|
|
146
|
+
const queries = unique(
|
|
220
147
|
(
|
|
221
148
|
await Promise.all(
|
|
222
149
|
ids.map(async (id) => {
|
|
223
150
|
return await this.getImportQueries(entityId, "id", id);
|
|
224
|
-
})
|
|
151
|
+
}),
|
|
225
152
|
)
|
|
226
|
-
).flat()
|
|
153
|
+
).flat(),
|
|
227
154
|
);
|
|
228
155
|
|
|
229
156
|
const wdb = BaseModel.getDB("w");
|
|
230
|
-
for (
|
|
157
|
+
for (const query of queries) {
|
|
231
158
|
const [rsh] = await wdb.raw(query);
|
|
232
159
|
console.log({
|
|
233
160
|
query,
|
|
@@ -236,11 +163,7 @@ export class FixtureManagerClass {
|
|
|
236
163
|
}
|
|
237
164
|
}
|
|
238
165
|
|
|
239
|
-
async getImportQueries(
|
|
240
|
-
entityId: string,
|
|
241
|
-
field: string,
|
|
242
|
-
id: number
|
|
243
|
-
): Promise<string[]> {
|
|
166
|
+
async getImportQueries(entityId: string, field: string, id: number): Promise<string[]> {
|
|
244
167
|
const recordKey = `${entityId}#${field}#${id}`;
|
|
245
168
|
|
|
246
169
|
// 순환 참조 방지: 이미 방문한 레코드는 스킵
|
|
@@ -260,9 +183,9 @@ export class FixtureManagerClass {
|
|
|
260
183
|
}
|
|
261
184
|
|
|
262
185
|
// 픽스쳐DB, 실DB
|
|
263
|
-
const fixtureDatabase = (Sonamu.dbConfig.fixture_remote.connection as
|
|
186
|
+
const fixtureDatabase = (Sonamu.dbConfig.fixture_remote.connection as Knex.ConnectionConfig)
|
|
264
187
|
.database;
|
|
265
|
-
const realDatabase = (Sonamu.dbConfig.production_master.connection as
|
|
188
|
+
const realDatabase = (Sonamu.dbConfig.production_master.connection as Knex.ConnectionConfig)
|
|
266
189
|
.database;
|
|
267
190
|
|
|
268
191
|
const selfQuery = `INSERT IGNORE INTO \`${fixtureDatabase}\`.\`${entity.table}\` (SELECT * FROM \`${realDatabase}\`.\`${entity.table}\` WHERE \`id\` = ${id})`;
|
|
@@ -271,8 +194,7 @@ export class FixtureManagerClass {
|
|
|
271
194
|
.filter(
|
|
272
195
|
([, relation]) =>
|
|
273
196
|
isBelongsToOneRelationProp(relation) ||
|
|
274
|
-
(isOneToOneRelationProp(relation) &&
|
|
275
|
-
relation.customJoinClause === undefined)
|
|
197
|
+
(isOneToOneRelationProp(relation) && relation.customJoinClause === undefined),
|
|
276
198
|
)
|
|
277
199
|
.map(([, relation]) => {
|
|
278
200
|
/*
|
|
@@ -288,15 +210,13 @@ export class FixtureManagerClass {
|
|
|
288
210
|
if (isOneToOneRelationProp(relation) && !relation.hasJoinColumn) {
|
|
289
211
|
const relatedEntity = EntityManager.get(relation.with);
|
|
290
212
|
const relatedIdColumnName = relatedEntity.props.find(
|
|
291
|
-
(p) => isRelationProp(p) && p.with === entity.id
|
|
213
|
+
(p) => isRelationProp(p) && p.with === entity.id,
|
|
292
214
|
)?.name;
|
|
293
215
|
if (!relatedIdColumnName) {
|
|
294
|
-
throw new Error(
|
|
295
|
-
`${relatedEntity.id}의 ${entity.id} 관계 프롭을 찾을 수 없습니다.`
|
|
296
|
-
);
|
|
216
|
+
throw new Error(`${relatedEntity.id}의 ${entity.id} 관계 프롭을 찾을 수 없습니다.`);
|
|
297
217
|
}
|
|
298
218
|
field = `${relatedIdColumnName}_id`;
|
|
299
|
-
id = row
|
|
219
|
+
id = row.id;
|
|
300
220
|
} else {
|
|
301
221
|
field = "id";
|
|
302
222
|
id = row[`${relation.name}_id`];
|
|
@@ -312,10 +232,10 @@ export class FixtureManagerClass {
|
|
|
312
232
|
const relQueries = await Promise.all(
|
|
313
233
|
args.map(async (args) => {
|
|
314
234
|
return this.getImportQueries(args.entityId, args.field, args.id);
|
|
315
|
-
})
|
|
235
|
+
}),
|
|
316
236
|
);
|
|
317
237
|
|
|
318
|
-
return [...
|
|
238
|
+
return [...unique(relQueries.reverse().flat()), selfQuery];
|
|
319
239
|
}
|
|
320
240
|
|
|
321
241
|
async destroy() {
|
|
@@ -333,17 +253,17 @@ export class FixtureManagerClass {
|
|
|
333
253
|
async getFixtures(
|
|
334
254
|
sourceDBName: keyof SonamuDBConfig,
|
|
335
255
|
targetDBName: keyof SonamuDBConfig,
|
|
336
|
-
searchOptions: FixtureSearchOptions
|
|
256
|
+
searchOptions: FixtureSearchOptions,
|
|
257
|
+
duplicateCheck?: DuplicateCheckOptions,
|
|
337
258
|
) {
|
|
338
259
|
const sourceDB = knex(Sonamu.dbConfig[sourceDBName]);
|
|
339
260
|
const targetDB = knex(Sonamu.dbConfig[targetDBName]);
|
|
261
|
+
|
|
340
262
|
const { entityId, field, value, searchType } = searchOptions;
|
|
341
263
|
|
|
342
264
|
const entity = EntityManager.get(entityId);
|
|
343
265
|
const column =
|
|
344
|
-
entity.props.find((prop) => prop.name === field)?.type === "relation"
|
|
345
|
-
? `${field}_id`
|
|
346
|
-
: field;
|
|
266
|
+
entity.props.find((prop) => prop.name === field)?.type === "relation" ? `${field}_id` : field;
|
|
347
267
|
|
|
348
268
|
let query = sourceDB(entity.table);
|
|
349
269
|
if (searchType === "equals") {
|
|
@@ -360,11 +280,11 @@ export class FixtureManagerClass {
|
|
|
360
280
|
const fixtures: FixtureRecord[] = [];
|
|
361
281
|
for (const row of rows) {
|
|
362
282
|
const initialRecordsLength = fixtures.length;
|
|
363
|
-
const newRecords = await this.createFixtureRecord(entity, row
|
|
283
|
+
const newRecords = await this.createFixtureRecord(entity, row, {
|
|
284
|
+
_db: sourceDB,
|
|
285
|
+
});
|
|
364
286
|
fixtures.push(...newRecords);
|
|
365
|
-
const currentFixtureRecord = fixtures.find(
|
|
366
|
-
(r) => r.fixtureId === `${entityId}#${row.id}`
|
|
367
|
-
);
|
|
287
|
+
const currentFixtureRecord = fixtures.find((r) => r.fixtureId === `${entityId}#${row.id}`);
|
|
368
288
|
|
|
369
289
|
if (currentFixtureRecord) {
|
|
370
290
|
// 현재 fixture로부터 생성된 fetchedRecords 설정
|
|
@@ -378,23 +298,26 @@ export class FixtureManagerClass {
|
|
|
378
298
|
for await (const fixture of fixtures) {
|
|
379
299
|
const entity = EntityManager.get(fixture.entityId);
|
|
380
300
|
|
|
381
|
-
//
|
|
382
|
-
const
|
|
383
|
-
if (
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
301
|
+
// 사용자 지정 컬럼 기준 중복 확인 → target
|
|
302
|
+
const customColumns = duplicateCheck?.columns?.[fixture.entityId];
|
|
303
|
+
if (customColumns && customColumns.length > 0) {
|
|
304
|
+
const customDuplicateRow = await this.checkDuplicateByColumns(
|
|
305
|
+
targetDB,
|
|
306
|
+
entity,
|
|
307
|
+
fixture,
|
|
308
|
+
customColumns,
|
|
309
|
+
);
|
|
310
|
+
if (customDuplicateRow) {
|
|
311
|
+
const [record] = await this.createFixtureRecord(entity, customDuplicateRow, {
|
|
312
|
+
singleRecord: true,
|
|
313
|
+
_db: targetDB,
|
|
314
|
+
});
|
|
315
|
+
fixture.target = record;
|
|
316
|
+
}
|
|
390
317
|
}
|
|
391
318
|
|
|
392
|
-
//
|
|
393
|
-
const uniqueRow = await this.checkUniqueViolation(
|
|
394
|
-
targetDB,
|
|
395
|
-
entity,
|
|
396
|
-
fixture
|
|
397
|
-
);
|
|
319
|
+
// Unique index 기준 중복 확인 → fixture.unique
|
|
320
|
+
const uniqueRow = await this.checkUniqueViolation(targetDB, entity, fixture);
|
|
398
321
|
if (uniqueRow) {
|
|
399
322
|
const [record] = await this.createFixtureRecord(entity, uniqueRow, {
|
|
400
323
|
singleRecord: true,
|
|
@@ -407,21 +330,30 @@ export class FixtureManagerClass {
|
|
|
407
330
|
await targetDB.destroy();
|
|
408
331
|
await sourceDB.destroy();
|
|
409
332
|
|
|
410
|
-
return
|
|
333
|
+
return unique(fixtures, (f) => f.fixtureId);
|
|
411
334
|
}
|
|
412
335
|
|
|
413
336
|
async createFixtureRecord(
|
|
414
337
|
entity: Entity,
|
|
415
|
-
row:
|
|
338
|
+
row: {
|
|
339
|
+
id: number;
|
|
340
|
+
[key: string]: string | number | boolean | null;
|
|
341
|
+
},
|
|
416
342
|
options?: {
|
|
417
343
|
singleRecord?: boolean;
|
|
418
344
|
_db?: Knex;
|
|
419
|
-
}
|
|
345
|
+
},
|
|
420
346
|
): Promise<FixtureRecord[]> {
|
|
421
347
|
const records: FixtureRecord[] = [];
|
|
422
348
|
const visitedEntities = new Set<string>();
|
|
423
349
|
|
|
424
|
-
const create = async (
|
|
350
|
+
const create = async (
|
|
351
|
+
entity: Entity,
|
|
352
|
+
row: {
|
|
353
|
+
id: number;
|
|
354
|
+
[key: string]: string | number | boolean | null;
|
|
355
|
+
},
|
|
356
|
+
) => {
|
|
425
357
|
const fixtureId = `${entity.id}#${row.id}`;
|
|
426
358
|
if (visitedEntities.has(fixtureId)) {
|
|
427
359
|
return;
|
|
@@ -454,9 +386,7 @@ export class FixtureManagerClass {
|
|
|
454
386
|
const fromColumn = `${inflection.singularize(entity.table)}_id`;
|
|
455
387
|
const toColumn = `${inflection.singularize(relatedEntity.table)}_id`;
|
|
456
388
|
|
|
457
|
-
const relatedIds = await db(throughTable)
|
|
458
|
-
.where(fromColumn, row.id)
|
|
459
|
-
.pluck(toColumn);
|
|
389
|
+
const relatedIds = await db(throughTable).where(fromColumn, row.id).pluck(toColumn);
|
|
460
390
|
record.columns[prop.name].value = relatedIds;
|
|
461
391
|
} else if (isHasManyRelationProp(prop)) {
|
|
462
392
|
const relatedEntity = EntityManager.get(prop.with);
|
|
@@ -467,12 +397,10 @@ export class FixtureManagerClass {
|
|
|
467
397
|
} else if (isOneToOneRelationProp(prop) && !prop.hasJoinColumn) {
|
|
468
398
|
const relatedEntity = EntityManager.get(prop.with);
|
|
469
399
|
const relatedProp = relatedEntity.props.find(
|
|
470
|
-
(p) => isRelationProp(p) && p.with === entity.id
|
|
400
|
+
(p) => isRelationProp(p) && p.with === entity.id,
|
|
471
401
|
);
|
|
472
402
|
if (relatedProp) {
|
|
473
|
-
const relatedRow = await db(relatedEntity.table)
|
|
474
|
-
.where("id", row.id)
|
|
475
|
-
.first();
|
|
403
|
+
const relatedRow = await db(relatedEntity.table).where("id", row.id).first();
|
|
476
404
|
record.columns[prop.name].value = relatedRow?.id;
|
|
477
405
|
}
|
|
478
406
|
} else if (isRelationProp(prop)) {
|
|
@@ -483,9 +411,7 @@ export class FixtureManagerClass {
|
|
|
483
411
|
}
|
|
484
412
|
if (!options?.singleRecord && relatedId) {
|
|
485
413
|
const relatedEntity = EntityManager.get(prop.with);
|
|
486
|
-
const relatedRow = await db(relatedEntity.table)
|
|
487
|
-
.where("id", relatedId)
|
|
488
|
-
.first();
|
|
414
|
+
const relatedRow = await db(relatedEntity.table).where("id", relatedId).first();
|
|
489
415
|
if (relatedRow) {
|
|
490
416
|
await create(relatedEntity, relatedRow);
|
|
491
417
|
}
|
|
@@ -501,221 +427,348 @@ export class FixtureManagerClass {
|
|
|
501
427
|
return records;
|
|
502
428
|
}
|
|
503
429
|
|
|
430
|
+
/**
|
|
431
|
+
* 1. RelationGraph로 fixture 단위 삽입 순서 계산 (self-reference 포함)
|
|
432
|
+
* 2. 순서대로 UpsertBuilder에 등록 (UBRef로 참조 관계 표현)
|
|
433
|
+
* 3. 테이블별 upsert 실행 (ID는 DB가 자동 할당)
|
|
434
|
+
*/
|
|
504
435
|
async insertFixtures(
|
|
505
436
|
dbName: keyof SonamuDBConfig,
|
|
506
|
-
_fixtures: FixtureRecord[]
|
|
507
|
-
) {
|
|
508
|
-
const fixtures =
|
|
437
|
+
_fixtures: FixtureRecord[],
|
|
438
|
+
): Promise<FixtureImportResult[]> {
|
|
439
|
+
const fixtures = unique(_fixtures, (f) => f.fixtureId);
|
|
440
|
+
|
|
441
|
+
// 초기화
|
|
442
|
+
this.builder = new UpsertBuilder();
|
|
443
|
+
this.fixtureRefMap = new Map();
|
|
444
|
+
this.uuidToFixtureId = new Map();
|
|
445
|
+
this.skippedFixtures = new Map();
|
|
509
446
|
|
|
510
|
-
this.relationGraph.buildGraph(fixtures);
|
|
511
|
-
const insertionOrder = this.relationGraph.getInsertionOrder();
|
|
512
447
|
const db = knex(Sonamu.dbConfig[dbName]);
|
|
448
|
+
const results: FixtureImportResult[] = [];
|
|
513
449
|
|
|
514
|
-
|
|
515
|
-
|
|
450
|
+
try {
|
|
451
|
+
// 1. RelationGraph로 fixture 단위 삽입 순서 계산
|
|
452
|
+
this.relationGraph.buildGraph(fixtures);
|
|
453
|
+
const insertionOrder = this.relationGraph.getInsertionOrder();
|
|
516
454
|
|
|
455
|
+
// 2. 순서대로 UpsertBuilder에 등록 (override 체크)
|
|
517
456
|
for (const fixtureId of insertionOrder) {
|
|
518
|
-
const fixture = fixtures.find((f) => f.fixtureId === fixtureId)
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
457
|
+
const fixture = fixtures.find((f) => f.fixtureId === fixtureId);
|
|
458
|
+
if (!fixture) continue;
|
|
459
|
+
|
|
460
|
+
const hasTarget = !!fixture.target;
|
|
461
|
+
const hasUnique = !!fixture.unique;
|
|
462
|
+
const hasDuplicate = hasTarget || hasUnique;
|
|
463
|
+
|
|
464
|
+
// 중복이 있고 override=false인 경우: 스킵
|
|
465
|
+
if (hasDuplicate && !fixture.override) {
|
|
466
|
+
// 기존 레코드 ID 저장 (unique 우선, 없으면 target)
|
|
467
|
+
const existingId = fixture.unique?.id ?? fixture.target?.id;
|
|
468
|
+
assert(existingId);
|
|
469
|
+
this.skippedFixtures.set(fixtureId, {
|
|
470
|
+
entityId: fixture.entityId,
|
|
471
|
+
existingId,
|
|
472
|
+
});
|
|
473
|
+
|
|
522
474
|
console.log(
|
|
523
475
|
chalk.yellow(
|
|
524
|
-
`
|
|
525
|
-
)
|
|
476
|
+
`Skipped ${fixture.entityId}#${fixture.id} (existing: #${existingId}, override: false)`,
|
|
477
|
+
),
|
|
526
478
|
);
|
|
527
|
-
|
|
528
|
-
Object.values(f.columns).forEach((column) => {
|
|
529
|
-
if (
|
|
530
|
-
column.prop.type === "relation" &&
|
|
531
|
-
column.prop.with === result.entityId &&
|
|
532
|
-
column.value === fixture.id
|
|
533
|
-
) {
|
|
534
|
-
column.value = result.id;
|
|
535
|
-
}
|
|
536
|
-
});
|
|
537
|
-
});
|
|
538
|
-
fixture.id = result.id;
|
|
479
|
+
continue;
|
|
539
480
|
}
|
|
540
|
-
}
|
|
541
481
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
482
|
+
this.registerFixture(fixture);
|
|
483
|
+
console.log(
|
|
484
|
+
chalk.blue(
|
|
485
|
+
`Registered ${fixture.entityId}#${fixture.id}${fixture.override ? ` (override existing: #${fixture.target?.id})` : ""}`,
|
|
486
|
+
),
|
|
487
|
+
);
|
|
545
488
|
}
|
|
546
|
-
await trx.raw(`SET FOREIGN_KEY_CHECKS = 1`);
|
|
547
|
-
});
|
|
548
489
|
|
|
549
|
-
|
|
490
|
+
// 3. 테이블별 upsert 실행
|
|
491
|
+
const tableOrder = this.getTableOrder(fixtures);
|
|
492
|
+
|
|
493
|
+
await db.transaction(async (trx) => {
|
|
494
|
+
const insertedIdsByTable = new Map<string, Map<string, number>>();
|
|
495
|
+
|
|
496
|
+
for (const tableName of tableOrder) {
|
|
497
|
+
if (!this.builder.hasTable(tableName)) continue;
|
|
550
498
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
499
|
+
// upsert 실행 전 uuid 목록 저장
|
|
500
|
+
const table = this.builder.getTable(tableName);
|
|
501
|
+
const uuids = table.rows.map((row) => row.uuid as string);
|
|
502
|
+
|
|
503
|
+
console.log(chalk.blue(`Upserting ${tableName} with ${uuids.length} rows`));
|
|
504
|
+
await this.builder.upsert(trx, tableName);
|
|
505
|
+
|
|
506
|
+
// upsert된 row들의 uuid -> id 매핑 구축
|
|
507
|
+
if (uuids.length > 0) {
|
|
508
|
+
const uuidToId = new Map<string, number>();
|
|
509
|
+
const rows = await trx(tableName).select("uuid", "id").whereIn("uuid", uuids);
|
|
510
|
+
|
|
511
|
+
for (const row of rows) {
|
|
512
|
+
uuidToId.set(row.uuid, row.id);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
insertedIdsByTable.set(tableName, uuidToId);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// 4. ManyToMany 관계 처리
|
|
520
|
+
await this.processManyToManyRelations(trx, fixtures, insertedIdsByTable);
|
|
521
|
+
|
|
522
|
+
// 5. 결과 수집
|
|
523
|
+
for (const fixture of fixtures) {
|
|
524
|
+
const entity = EntityManager.get(fixture.entityId);
|
|
525
|
+
|
|
526
|
+
// 스킵된 fixture는 기존 레코드 정보로 결과 추가
|
|
527
|
+
const skipped = this.skippedFixtures.get(fixture.fixtureId);
|
|
528
|
+
if (skipped) {
|
|
529
|
+
results.push({
|
|
530
|
+
entityId: fixture.entityId,
|
|
531
|
+
data: await trx(entity.table).where("id", skipped.existingId).first(),
|
|
532
|
+
});
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const ref = this.fixtureRefMap.get(fixture.fixtureId);
|
|
537
|
+
if (ref) {
|
|
538
|
+
const uuidToId = insertedIdsByTable.get(entity.table);
|
|
539
|
+
const insertedId = uuidToId?.get(ref.uuid);
|
|
540
|
+
|
|
541
|
+
if (insertedId !== undefined) {
|
|
542
|
+
results.push({
|
|
543
|
+
entityId: fixture.entityId,
|
|
544
|
+
data: await trx(entity.table).where("id", insertedId).first(),
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
console.log(
|
|
548
|
+
chalk.green(`Inserted into ${entity.table}: #${fixture.id} -> #${insertedId}`),
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
557
553
|
});
|
|
554
|
+
} finally {
|
|
555
|
+
await db.destroy();
|
|
558
556
|
}
|
|
559
557
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
return _.uniqBy(records, (r) => `${r.entityId}#${r.data.id}`);
|
|
558
|
+
return unique(results, (r) => `${r.entityId}#${r.data.id}`);
|
|
563
559
|
}
|
|
564
560
|
|
|
565
|
-
|
|
566
|
-
|
|
561
|
+
/**
|
|
562
|
+
* FixtureRecord를 UpsertBuilder에 등록
|
|
563
|
+
*/
|
|
564
|
+
private registerFixture(fixture: FixtureRecord): UBRef {
|
|
565
|
+
const entity = EntityManager.get(fixture.entityId);
|
|
566
|
+
const row: Record<string, unknown> = {};
|
|
567
|
+
|
|
568
|
+
// Override 모드 판단: target 또는 unique가 있고 override=true인 경우
|
|
569
|
+
const existingRecord = fixture.target ?? fixture.unique;
|
|
570
|
+
const isOverrideMode = fixture.override && existingRecord;
|
|
571
|
+
|
|
567
572
|
for (const [propName, column] of Object.entries(fixture.columns)) {
|
|
568
|
-
|
|
573
|
+
const prop = column.prop;
|
|
574
|
+
|
|
575
|
+
if (isVirtualProp(prop)) {
|
|
569
576
|
continue;
|
|
570
577
|
}
|
|
571
578
|
|
|
572
|
-
|
|
573
|
-
if (
|
|
574
|
-
if (
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
insertData[propName] = new Date(column.value);
|
|
578
|
-
} else {
|
|
579
|
-
insertData[propName] = column.value;
|
|
579
|
+
// id/uuid 처리: Override 모드일 때만 기존 값 사용
|
|
580
|
+
if (propName === "id" || propName === "uuid") {
|
|
581
|
+
if (isOverrideMode && existingRecord) {
|
|
582
|
+
// Override: 기존 레코드의 값 사용 → UPDATE
|
|
583
|
+
row[propName] = existingRecord.columns[propName]?.value;
|
|
580
584
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
585
|
+
// 새 레코드: 제외 → INSERT (DB/UpsertBuilder가 생성)
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (isRelationProp(prop)) {
|
|
590
|
+
if (
|
|
591
|
+
isBelongsToOneRelationProp(prop) ||
|
|
592
|
+
(isOneToOneRelationProp(prop) && prop.hasJoinColumn)
|
|
593
|
+
) {
|
|
594
|
+
const relatedId = column.value as number | null;
|
|
595
|
+
if (relatedId !== null && relatedId !== undefined) {
|
|
596
|
+
const relatedFixtureId = `${prop.with}#${relatedId}`;
|
|
597
|
+
|
|
598
|
+
// 먼저 skip된 fixture인지 확인
|
|
599
|
+
const skippedExistingId = this.skippedFixtures.get(relatedFixtureId)?.existingId;
|
|
600
|
+
if (skippedExistingId !== undefined) {
|
|
601
|
+
// skip된 fixture → target DB의 기존 레코드 id 사용
|
|
602
|
+
row[`${propName}_id`] = skippedExistingId;
|
|
603
|
+
} else {
|
|
604
|
+
const relatedRef = this.fixtureRefMap.get(relatedFixtureId);
|
|
605
|
+
if (relatedRef) {
|
|
606
|
+
// 이미 등록된 fixture 참조 → UBRef 사용
|
|
607
|
+
row[`${propName}_id`] = relatedRef;
|
|
608
|
+
} else {
|
|
609
|
+
// fixtures에 포함되지 않은 레코드 → ID 그대로 사용
|
|
610
|
+
row[`${propName}_id`] = relatedId;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
} else {
|
|
614
|
+
row[`${propName}_id`] = null;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
// HasMany, ManyToMany는 별도 처리
|
|
618
|
+
} else {
|
|
619
|
+
// 일반 컬럼
|
|
620
|
+
row[propName] = this.convertColumnValue(prop as EntityProp, column.value);
|
|
586
621
|
}
|
|
587
622
|
}
|
|
588
|
-
|
|
623
|
+
|
|
624
|
+
console.log(chalk.blue(`Registering ${entity.table} - ${inspect(row, false, null, true)}`));
|
|
625
|
+
const ref = this.builder.register(entity.table, row);
|
|
626
|
+
this.fixtureRefMap.set(fixture.fixtureId, ref);
|
|
627
|
+
this.uuidToFixtureId.set(ref.uuid, fixture.fixtureId);
|
|
628
|
+
|
|
629
|
+
return ref;
|
|
589
630
|
}
|
|
590
631
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
632
|
+
/**
|
|
633
|
+
* 컬럼 값 변환
|
|
634
|
+
*/
|
|
635
|
+
private convertColumnValue(prop: EntityProp, value: unknown): unknown {
|
|
636
|
+
if (value === null || value === undefined) {
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
594
639
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
return
|
|
599
|
-
entityId: fixture.entityId,
|
|
600
|
-
id: uniqueFound.id,
|
|
601
|
-
};
|
|
602
|
-
}
|
|
640
|
+
switch (prop.type) {
|
|
641
|
+
case "json":
|
|
642
|
+
// UpsertBuilder.register에서 JSON.stringify 처리하므로 object 그대로 전달
|
|
643
|
+
return value;
|
|
603
644
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
645
|
+
case "date":
|
|
646
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
647
|
+
return new Date(value);
|
|
648
|
+
}
|
|
649
|
+
return value;
|
|
650
|
+
|
|
651
|
+
default:
|
|
652
|
+
return value;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
611
655
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
656
|
+
/**
|
|
657
|
+
* 테이블 순서 추출 (fixtures에 포함된 테이블만)
|
|
658
|
+
*/
|
|
659
|
+
private getTableOrder(fixtures: FixtureRecord[]): string[] {
|
|
660
|
+
const tables: string[] = [];
|
|
661
|
+
const seen = new Set<string>();
|
|
615
662
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
throw err;
|
|
663
|
+
for (const fixture of fixtures) {
|
|
664
|
+
const entity = EntityManager.get(fixture.entityId);
|
|
665
|
+
if (!seen.has(entity.table)) {
|
|
666
|
+
seen.add(entity.table);
|
|
667
|
+
tables.push(entity.table);
|
|
668
|
+
}
|
|
623
669
|
}
|
|
670
|
+
|
|
671
|
+
return tables;
|
|
624
672
|
}
|
|
625
673
|
|
|
626
|
-
private async
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
) {
|
|
631
|
-
for (const
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
const joinTable = (prop as ManyToManyRelationProp).joinTable;
|
|
635
|
-
const relatedIds = column.value as number[];
|
|
636
|
-
|
|
637
|
-
for (const relatedId of relatedIds) {
|
|
638
|
-
if (
|
|
639
|
-
!fixtures.find((f) => f.fixtureId === `${prop.with}#${relatedId}`)
|
|
640
|
-
) {
|
|
641
|
-
continue;
|
|
642
|
-
}
|
|
674
|
+
private async processManyToManyRelations(
|
|
675
|
+
trx: Knex.Transaction,
|
|
676
|
+
fixtures: FixtureRecord[],
|
|
677
|
+
insertedIdsByTable: Map<string, Map<string, number>>,
|
|
678
|
+
): Promise<void> {
|
|
679
|
+
for (const fixture of fixtures) {
|
|
680
|
+
const entity = EntityManager.get(fixture.entityId);
|
|
681
|
+
const sourceRef = this.fixtureRefMap.get(fixture.fixtureId);
|
|
643
682
|
|
|
644
|
-
|
|
683
|
+
if (!sourceRef) continue;
|
|
684
|
+
|
|
685
|
+
const sourceUuidToId = insertedIdsByTable.get(entity.table);
|
|
686
|
+
const sourceId = sourceUuidToId?.get(sourceRef.uuid);
|
|
687
|
+
|
|
688
|
+
if (sourceId === undefined) continue;
|
|
689
|
+
|
|
690
|
+
for (const [, column] of Object.entries(fixture.columns)) {
|
|
691
|
+
const prop = column.prop;
|
|
692
|
+
|
|
693
|
+
if (isManyToManyRelationProp(prop) && Array.isArray(column.value)) {
|
|
694
|
+
// 선택되지 않은 ManyToMany 관계는 저장하지 않음
|
|
695
|
+
const targetTable = EntityManager.get(prop.with);
|
|
696
|
+
if (this.builder.hasTable(targetTable.table) === false) continue;
|
|
697
|
+
|
|
698
|
+
const relatedIds = column.value as number[];
|
|
699
|
+
if (relatedIds.length === 0) continue;
|
|
700
|
+
|
|
701
|
+
const joinTable = (prop as ManyToManyRelationProp).joinTable;
|
|
645
702
|
const relatedEntity = EntityManager.get(prop.with);
|
|
646
|
-
if (!entity || !relatedEntity) {
|
|
647
|
-
throw new Error(
|
|
648
|
-
`Entity not found: ${fixture.entityId}, ${prop.with}`
|
|
649
|
-
);
|
|
650
|
-
}
|
|
651
703
|
|
|
652
|
-
const
|
|
653
|
-
|
|
654
|
-
[`${inflection.singularize(entity.table)}_id`]: fixture.id,
|
|
655
|
-
[`${inflection.singularize(relatedEntity.table)}_id`]: relatedId,
|
|
656
|
-
})
|
|
657
|
-
.limit(1);
|
|
658
|
-
if (found) {
|
|
659
|
-
continue;
|
|
660
|
-
}
|
|
704
|
+
const sourceColumn = `${inflection.singularize(entity.table)}_id`;
|
|
705
|
+
const targetColumn = `${inflection.singularize(relatedEntity.table)}_id`;
|
|
661
706
|
|
|
662
|
-
const
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
});
|
|
666
|
-
console.log(
|
|
667
|
-
chalk.green(
|
|
668
|
-
`Inserted into ${joinTable}: ${entity.table}(${fixture.id}) - ${relatedEntity.table}(${relatedId}) ID: ${newIds}`
|
|
669
|
-
)
|
|
670
|
-
);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
}
|
|
707
|
+
for (const relatedId of relatedIds) {
|
|
708
|
+
const relatedFixtureId = `${prop.with}#${relatedId}`;
|
|
709
|
+
const relatedRef = this.fixtureRefMap.get(relatedFixtureId);
|
|
675
710
|
|
|
676
|
-
|
|
677
|
-
const path = Sonamu.apiRootPath + "/src/testing/fixture.ts";
|
|
678
|
-
let content = readFileSync(path).toString();
|
|
711
|
+
let targetId: number;
|
|
679
712
|
|
|
680
|
-
|
|
681
|
-
|
|
713
|
+
if (relatedRef) {
|
|
714
|
+
const relatedUuidToId = insertedIdsByTable.get(relatedEntity.table);
|
|
715
|
+
const resolvedId = relatedUuidToId?.get(relatedRef.uuid);
|
|
682
716
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
717
|
+
if (resolvedId === undefined) {
|
|
718
|
+
console.warn(
|
|
719
|
+
`Related fixture ${relatedFixtureId} not found in insertedIds, skipping`,
|
|
720
|
+
);
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
targetId = resolvedId;
|
|
724
|
+
} else {
|
|
725
|
+
targetId = relatedId;
|
|
726
|
+
}
|
|
690
727
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
728
|
+
// JoinTable에 삽입
|
|
729
|
+
const [found] = await trx(joinTable)
|
|
730
|
+
.where({
|
|
731
|
+
[sourceColumn]: sourceId,
|
|
732
|
+
[targetColumn]: targetId,
|
|
733
|
+
})
|
|
734
|
+
.limit(1);
|
|
735
|
+
|
|
736
|
+
if (!found) {
|
|
737
|
+
await trx(joinTable).insert({
|
|
738
|
+
[sourceColumn]: sourceId,
|
|
739
|
+
[targetColumn]: targetId,
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
console.log(
|
|
743
|
+
chalk.green(
|
|
744
|
+
`Inserted into ${joinTable}: ${entity.table}(${sourceId}) - ${relatedEntity.table}(${targetId})`,
|
|
745
|
+
),
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
694
751
|
}
|
|
695
752
|
}
|
|
696
753
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
db: Knex,
|
|
700
|
-
entity: Entity,
|
|
701
|
-
fixture: FixtureRecord
|
|
702
|
-
) {
|
|
703
|
-
const _uniqueIndexes = entity.indexes.filter((i) => i.type === "unique");
|
|
754
|
+
private async checkUniqueViolation(db: Knex, entity: Entity, fixture: FixtureRecord) {
|
|
755
|
+
const _uniqueIndexes = entity.indexes?.filter((i) => i.type === "unique") ?? [];
|
|
704
756
|
|
|
705
|
-
// ManyToMany 관계 테이블의 유니크 제약은 제외
|
|
706
757
|
const uniqueIndexes = _uniqueIndexes.filter((index) =>
|
|
707
|
-
index.columns.every((column) => !column.startsWith(`${entity.table}__`))
|
|
758
|
+
index.columns.every((column) => !column.startsWith(`${entity.table}__`)),
|
|
708
759
|
);
|
|
709
760
|
if (uniqueIndexes.length === 0) {
|
|
710
761
|
return null;
|
|
711
762
|
}
|
|
712
763
|
|
|
713
764
|
let uniqueQuery = db(entity.table);
|
|
765
|
+
let hasCondition = false;
|
|
766
|
+
|
|
714
767
|
for (const index of uniqueIndexes) {
|
|
715
768
|
// 컬럼 중 하나라도 null이면 유니크 제약을 위반하지 않기 때문에 해당 인덱스는 무시
|
|
716
769
|
const containsNull = index.columns.some((column) => {
|
|
717
|
-
const field = column.
|
|
718
|
-
return fixture.columns[field]
|
|
770
|
+
const field = column.replace(/_id$/, "");
|
|
771
|
+
return fixture.columns[field]?.value === null;
|
|
719
772
|
});
|
|
720
773
|
if (containsNull) {
|
|
721
774
|
continue;
|
|
@@ -723,18 +776,71 @@ export class FixtureManagerClass {
|
|
|
723
776
|
|
|
724
777
|
uniqueQuery = uniqueQuery.orWhere((qb) => {
|
|
725
778
|
for (const column of index.columns) {
|
|
726
|
-
const field = column.
|
|
779
|
+
const field = column.replace(/_id$/, "");
|
|
727
780
|
|
|
728
|
-
if (Array.isArray(fixture.columns[field]
|
|
781
|
+
if (Array.isArray(fixture.columns[field]?.value)) {
|
|
729
782
|
qb.whereIn(column, fixture.columns[field].value);
|
|
730
783
|
} else {
|
|
731
|
-
qb.andWhere(column, fixture.columns[field]
|
|
784
|
+
qb.andWhere(column, fixture.columns[field]?.value);
|
|
732
785
|
}
|
|
733
786
|
}
|
|
734
787
|
});
|
|
788
|
+
hasCondition = true;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (!hasCondition) {
|
|
792
|
+
return null;
|
|
735
793
|
}
|
|
794
|
+
|
|
736
795
|
const [uniqueFound] = await uniqueQuery;
|
|
737
796
|
return uniqueFound;
|
|
738
797
|
}
|
|
798
|
+
|
|
799
|
+
private async checkDuplicateByColumns(
|
|
800
|
+
db: Knex,
|
|
801
|
+
entity: Entity,
|
|
802
|
+
fixture: FixtureRecord,
|
|
803
|
+
columns: string[],
|
|
804
|
+
) {
|
|
805
|
+
if (columns.length === 0) {
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
const whereClause: Record<string, unknown> = {};
|
|
810
|
+
|
|
811
|
+
for (const column of columns) {
|
|
812
|
+
// relation 필드인 경우 _id 붙이기
|
|
813
|
+
const prop = entity.props.find((p) => p.name === column);
|
|
814
|
+
const dbColumn = prop && isRelationProp(prop) ? `${column}_id` : column;
|
|
815
|
+
const value = fixture.columns[column]?.value;
|
|
816
|
+
|
|
817
|
+
// null 값이 포함된 경우 중복 확인 스킵
|
|
818
|
+
if (value === null || value === undefined) {
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
whereClause[dbColumn] = value;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const [found] = await db(entity.table).where(whereClause).limit(1);
|
|
826
|
+
return found;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
async addFixtureLoader(code: string) {
|
|
830
|
+
const path = `${Sonamu.apiRootPath}/src/testing/fixture.ts`;
|
|
831
|
+
const content = readFileSync(path).toString();
|
|
832
|
+
|
|
833
|
+
const fixtureLoaderStart = content.indexOf("const fixtureLoader = {");
|
|
834
|
+
const fixtureLoaderEnd = content.indexOf("};", fixtureLoaderStart);
|
|
835
|
+
|
|
836
|
+
if (fixtureLoaderStart !== -1 && fixtureLoaderEnd !== -1) {
|
|
837
|
+
const newContent = `${content.slice(0, fixtureLoaderEnd)} ${code}\n${content.slice(fixtureLoaderEnd)}`;
|
|
838
|
+
|
|
839
|
+
writeFileSync(path, newContent);
|
|
840
|
+
} else {
|
|
841
|
+
throw new Error("Failed to find fixtureLoader in fixture.ts");
|
|
842
|
+
}
|
|
843
|
+
}
|
|
739
844
|
}
|
|
845
|
+
|
|
740
846
|
export const FixtureManager = new FixtureManagerClass();
|