sonamu 0.6.0 → 0.7.0
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 +228 -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 +341 -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 +54 -29
- 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 +262 -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 +407 -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,20 +1,48 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Knex } from "knex";
|
|
3
|
-
import { chunk, groupBy, isObject, omit, set, uniq } from "lodash-es";
|
|
4
|
-
import { DBPreset, DB } from "./db";
|
|
5
|
-
import { isCustomJoinClause, type SubsetQuery } from "../types/types";
|
|
6
|
-
import type { BaseListParams } from "../utils/model";
|
|
1
|
+
import assert from "assert";
|
|
7
2
|
import inflection from "inflection";
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
3
|
+
import type { Knex } from "knex";
|
|
4
|
+
import { group, isObject, omit, set, unique } from "radashi";
|
|
5
|
+
import { Sonamu } from "../api";
|
|
6
|
+
import { type DatabaseSchemaExtend, isCustomJoinClause, type SubsetQuery } from "../types/types";
|
|
7
|
+
import type { BaseListParams } from "../utils/model";
|
|
8
|
+
import { getJoinTables, getTableNamesFromWhere } from "../utils/sql-parser";
|
|
9
|
+
import { chunk } from "../utils/utils";
|
|
10
|
+
import type {
|
|
11
|
+
EnhancerMap,
|
|
12
|
+
ExecuteSubsetQueryResult,
|
|
13
|
+
ResolveSubsetIntersection,
|
|
14
|
+
UnionExtractedTTables,
|
|
15
|
+
} from "./base-model.types";
|
|
16
|
+
import type { DBPreset } from "./db";
|
|
17
|
+
import { DB } from "./db";
|
|
18
|
+
import { Puri } from "./puri";
|
|
19
|
+
import type { InferAllSubsets, PuriLoaderQueries, PuriSubsetFn } from "./puri-subset.types";
|
|
12
20
|
import { PuriWrapper } from "./puri-wrapper";
|
|
21
|
+
import { UpsertBuilder } from "./upsert-builder";
|
|
13
22
|
|
|
14
|
-
|
|
23
|
+
type UnknownDBRecord = Record<string, unknown>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 모든 Model 클래스의 기본 클래스
|
|
27
|
+
*
|
|
28
|
+
* @template TSubsetKey - 서브셋 키 유니온 (예: "A" | "P" | "SS")
|
|
29
|
+
* @template TSubsetMapping - 서브셋별 최종 결과 타입 매핑
|
|
30
|
+
* @template TSubsetQueries - 서브셋 쿼리 함수 객체
|
|
31
|
+
* @template TLoaderQueries - 서브셋별 로더 쿼리 배열 객체
|
|
32
|
+
*/
|
|
33
|
+
export class BaseModelClass<
|
|
34
|
+
TSubsetKey extends string = never,
|
|
35
|
+
TSubsetMapping extends Record<string, any> = never,
|
|
36
|
+
TSubsetQueries extends Record<TSubsetKey, PuriSubsetFn> = never,
|
|
37
|
+
TLoaderQueries extends PuriLoaderQueries<TSubsetKey> = never,
|
|
38
|
+
> {
|
|
15
39
|
public modelName: string = "Unknown";
|
|
16
40
|
|
|
17
|
-
|
|
41
|
+
constructor(
|
|
42
|
+
protected subsetQueries?: TSubsetQueries,
|
|
43
|
+
protected loaderQueries?: TLoaderQueries,
|
|
44
|
+
) {}
|
|
45
|
+
|
|
18
46
|
getDB(which: DBPreset): Knex {
|
|
19
47
|
return DB.getDB(which);
|
|
20
48
|
}
|
|
@@ -35,160 +63,281 @@ export class BaseModelClass {
|
|
|
35
63
|
return DB.destroy();
|
|
36
64
|
}
|
|
37
65
|
|
|
38
|
-
myNow(timestamp?: number): string {
|
|
39
|
-
const dt: DateTime =
|
|
40
|
-
timestamp === undefined
|
|
41
|
-
? DateTime.local()
|
|
42
|
-
: DateTime.fromSeconds(timestamp);
|
|
43
|
-
return dt.toFormat("yyyy-MM-dd HH:mm:ss");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
66
|
async getInsertedIds(
|
|
47
67
|
wdb: Knex,
|
|
48
|
-
rows:
|
|
68
|
+
rows: UnknownDBRecord[],
|
|
49
69
|
tableName: string,
|
|
50
70
|
unqKeyFields: string[],
|
|
51
|
-
chunkSize: number = 500
|
|
71
|
+
chunkSize: number = 500,
|
|
52
72
|
) {
|
|
53
73
|
if (!wdb) {
|
|
54
74
|
wdb = this.getDB("w");
|
|
55
75
|
}
|
|
56
76
|
|
|
57
77
|
let unqKeys: string[];
|
|
58
|
-
let whereInField:
|
|
78
|
+
let whereInField: string | Knex.Raw;
|
|
79
|
+
let selectField: string;
|
|
80
|
+
|
|
59
81
|
if (unqKeyFields.length > 1) {
|
|
60
82
|
whereInField = wdb.raw(`CONCAT_WS('_', '${unqKeyFields.join(",")}')`);
|
|
61
83
|
selectField = `${whereInField} as tmpUid`;
|
|
62
|
-
unqKeys = rows.map((row) =>
|
|
63
|
-
unqKeyFields.map((field) => row[field]).join("_")
|
|
64
|
-
);
|
|
84
|
+
unqKeys = rows.map((row) => unqKeyFields.map((field) => row[field]).join("_"));
|
|
65
85
|
} else {
|
|
66
86
|
whereInField = unqKeyFields[0];
|
|
67
87
|
selectField = unqKeyFields[0];
|
|
68
|
-
unqKeys = rows.map((row) => row[unqKeyFields[0]]);
|
|
88
|
+
unqKeys = rows.map((row) => row[unqKeyFields[0]] as string);
|
|
69
89
|
}
|
|
70
|
-
const chunks = chunk(unqKeys, chunkSize);
|
|
71
90
|
|
|
72
91
|
let resultIds: number[] = [];
|
|
73
|
-
for (
|
|
92
|
+
for (const items of chunk(unqKeys, chunkSize)) {
|
|
74
93
|
const dbRows = await wdb(tableName)
|
|
75
94
|
.select("id", wdb.raw(selectField))
|
|
76
|
-
.whereIn(whereInField,
|
|
95
|
+
.whereIn(whereInField as string, items);
|
|
77
96
|
resultIds = resultIds.concat(
|
|
78
|
-
dbRows.map((dbRow:
|
|
97
|
+
dbRows.map((dbRow: UnknownDBRecord) => parseInt(String(dbRow.id))),
|
|
79
98
|
);
|
|
80
99
|
}
|
|
81
100
|
|
|
82
101
|
return resultIds;
|
|
83
102
|
}
|
|
84
103
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
104
|
+
/**
|
|
105
|
+
* 특정 서브셋에 대한 쿼리 빌더 획득
|
|
106
|
+
*
|
|
107
|
+
* @returns qb - 쿼리 빌더 (조건 추가용)
|
|
108
|
+
* @returns onSubset - 특정 서브셋 전용 타입이 필요할 때 사용
|
|
109
|
+
*/
|
|
110
|
+
getSubsetQueries<T extends TSubsetKey>(subset: T) {
|
|
111
|
+
if (!this.subsetQueries) {
|
|
112
|
+
throw new Error("subsetQueries is not defined");
|
|
88
113
|
}
|
|
89
114
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
let subRows: any[];
|
|
93
|
-
let toCol: string;
|
|
115
|
+
const puriWrapper = new PuriWrapper(this.getDB("r"), new UpsertBuilder());
|
|
116
|
+
const qb = this.subsetQueries[subset]?.(puriWrapper);
|
|
94
117
|
|
|
95
|
-
|
|
118
|
+
// NonAllowedAsSingleTable: 단일 테이블 컬럼 접근 방지용 마커
|
|
119
|
+
type QBTables = UnionExtractedTTables<TSubsetKey, TSubsetQueries> & {
|
|
120
|
+
NonAllowedAsSingleTable: { __fulltext__: true };
|
|
121
|
+
};
|
|
96
122
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
123
|
+
return {
|
|
124
|
+
qb: qb as unknown as Puri<DatabaseSchemaExtend, QBTables, {}>,
|
|
125
|
+
onSubset: ((_subset: TSubsetKey | readonly TSubsetKey[]) => qb) as {
|
|
126
|
+
// 단일 키
|
|
127
|
+
<S extends TSubsetKey>(subset: S): ReturnType<TSubsetQueries[S]>;
|
|
128
|
+
// 키 배열 -> 교집합 반환
|
|
129
|
+
<Arr extends readonly TSubsetKey[]>(
|
|
130
|
+
subsets: [...Arr],
|
|
131
|
+
): ResolveSubsetIntersection<Arr, TSubsetQueries>;
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
103
135
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
136
|
+
/**
|
|
137
|
+
* Enhancer 객체 생성 헬퍼
|
|
138
|
+
* 타입 검증 및 추론을 도와줌
|
|
139
|
+
*/
|
|
140
|
+
createEnhancers<T extends TSubsetKey>(
|
|
141
|
+
enhancers: EnhancerMap<T, InferAllSubsets<TSubsetQueries, TLoaderQueries>, TSubsetMapping>,
|
|
142
|
+
) {
|
|
143
|
+
return enhancers;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 서브셋 쿼리 실행
|
|
148
|
+
*
|
|
149
|
+
* 1. 쿼리 실행 (pagination 적용)
|
|
150
|
+
* 2. 로더 실행 (1:N, N:M 관계 데이터 로딩)
|
|
151
|
+
* 3. Hydrate (flat → 중첩 객체)
|
|
152
|
+
* 4. Enhancer 적용 (virtual 필드 계산)
|
|
153
|
+
*/
|
|
154
|
+
async executeSubsetQuery<
|
|
155
|
+
T extends TSubsetKey,
|
|
156
|
+
TComputedResults extends InferAllSubsets<TSubsetQueries, TLoaderQueries>,
|
|
157
|
+
>(
|
|
158
|
+
params: {
|
|
159
|
+
subset: T;
|
|
160
|
+
qb: Puri<any, any, any>;
|
|
161
|
+
params: {
|
|
162
|
+
num?: number;
|
|
163
|
+
page?: number;
|
|
164
|
+
queryMode?: "list" | "count" | "both";
|
|
165
|
+
};
|
|
166
|
+
debug?: boolean;
|
|
167
|
+
optimizeCountQuery?: boolean;
|
|
168
|
+
} & EnhancerParam<TSubsetKey, TComputedResults, TSubsetMapping>,
|
|
169
|
+
): Promise<ExecuteSubsetQueryResult<TSubsetMapping, T>> {
|
|
170
|
+
const { subset, qb, params: queryParams, debug = false, optimizeCountQuery = false } = params;
|
|
171
|
+
|
|
172
|
+
if (!this.loaderQueries) {
|
|
173
|
+
throw new Error("loaderQueries is not defined");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!queryParams.num || !queryParams.page) {
|
|
177
|
+
throw new Error("num and page are required");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const { num, page } = queryParams;
|
|
181
|
+
|
|
182
|
+
// COUNT 쿼리 실행
|
|
183
|
+
const total = await this.executeCountQuery(qb, queryParams, debug, optimizeCountQuery);
|
|
184
|
+
|
|
185
|
+
// LIST 쿼리 실행
|
|
186
|
+
const computedRows = await this.executeListQuery(subset, qb, queryParams, num, page, debug);
|
|
187
|
+
|
|
188
|
+
// Enhancer 적용
|
|
189
|
+
const enhancer = (params as any).enhancers?.[subset];
|
|
190
|
+
const rows = (await Promise.all(
|
|
191
|
+
computedRows.map((row) => enhancer?.(row) ?? row),
|
|
192
|
+
)) as TSubsetMapping[T][];
|
|
193
|
+
|
|
194
|
+
return { rows, total };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* COUNT 쿼리 실행 (내부 메서드)
|
|
199
|
+
*/
|
|
200
|
+
private async executeCountQuery(
|
|
201
|
+
qb: Puri<any, any, any>,
|
|
202
|
+
params: { queryMode?: "list" | "count" | "both" },
|
|
203
|
+
debug: boolean,
|
|
204
|
+
optimizeCountQuery: boolean,
|
|
205
|
+
): Promise<number> {
|
|
206
|
+
if (params.queryMode === "list") {
|
|
207
|
+
return 0;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const countPuri = qb.clone().clear("order").clear("limit").clear("offset");
|
|
211
|
+
|
|
212
|
+
if (optimizeCountQuery) {
|
|
213
|
+
const { default: SqlParser } = await import("node-sql-parser");
|
|
214
|
+
const parser = new SqlParser.Parser();
|
|
215
|
+
const parsedQuery = parser.astify(countPuri.toQuery(), {
|
|
216
|
+
database: Sonamu.config.database.database,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const leftJoinTables = getJoinTables(parsedQuery, ["LEFT JOIN"]);
|
|
220
|
+
const whereTables = getTableNamesFromWhere(parsedQuery);
|
|
221
|
+
|
|
222
|
+
const tablesToRemove = leftJoinTables.filter((j) => !whereTables.includes(j));
|
|
223
|
+
tablesToRemove.forEach((table) => {
|
|
224
|
+
countPuri.clearJoin(table);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// COUNT(*)로 전체 레코드 수를 계산
|
|
229
|
+
// TODO: qb의 DISTINCT가 있는 경우 처리해야 함
|
|
230
|
+
const countResult: { total?: number } = await countPuri
|
|
231
|
+
.clear("select")
|
|
232
|
+
.select({ total: Puri.rawNumber(`COUNT(*)`) })
|
|
233
|
+
.first();
|
|
234
|
+
|
|
235
|
+
if (debug) {
|
|
236
|
+
countPuri.debug();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return countResult?.total ?? 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* LIST 쿼리 실행 (내부 메서드)
|
|
244
|
+
*/
|
|
245
|
+
private async executeListQuery<T extends TSubsetKey>(
|
|
246
|
+
subset: T,
|
|
247
|
+
qb: Puri<any, any, any>,
|
|
248
|
+
params: { queryMode?: "list" | "count" | "both" },
|
|
249
|
+
num: number,
|
|
250
|
+
page: number,
|
|
251
|
+
debug: boolean,
|
|
252
|
+
): Promise<any[]> {
|
|
253
|
+
if (params.queryMode === "count") {
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let unloadedRows = (await qb.limit(num).offset(num * (page - 1))) as any[];
|
|
258
|
+
|
|
259
|
+
if (debug) {
|
|
260
|
+
qb.debug();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 로더 처리
|
|
264
|
+
const loaders = (this.loaderQueries as any)[subset];
|
|
265
|
+
if (loaders && Array.isArray(loaders)) {
|
|
266
|
+
unloadedRows = await this.processLoaders(unloadedRows, loaders, debug);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return this.hydrate(unloadedRows);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* 재귀적 로더 처리
|
|
274
|
+
*/
|
|
275
|
+
private async processLoaders(rows: any[], loaders: any[], debug: boolean): Promise<any[]> {
|
|
276
|
+
for (const resolveLoader of loaders) {
|
|
277
|
+
const { as, refId, qb: resolveLoaderQbFn, loaders: nestedLoaders } = resolveLoader;
|
|
278
|
+
|
|
279
|
+
const resolveLoaderQb = resolveLoaderQbFn(
|
|
280
|
+
new PuriWrapper(this.getDB("r"), new UpsertBuilder()),
|
|
281
|
+
rows.map((row) => row[refId]),
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
if (debug) {
|
|
285
|
+
resolveLoaderQb.debug();
|
|
146
286
|
}
|
|
147
|
-
subRows = await subQ;
|
|
148
287
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
288
|
+
let loadedRows = (await resolveLoaderQb) as any[];
|
|
289
|
+
|
|
290
|
+
// 중첩 loaders가 있으면 재귀 처리
|
|
291
|
+
if (nestedLoaders && nestedLoaders.length > 0) {
|
|
292
|
+
loadedRows = await this.processLoaders(loadedRows, nestedLoaders, debug);
|
|
152
293
|
}
|
|
153
294
|
|
|
154
|
-
|
|
155
|
-
|
|
295
|
+
const subRowGroups = group(loadedRows, (row) => row.refId);
|
|
296
|
+
|
|
156
297
|
rows = rows.map((row) => {
|
|
157
|
-
row[
|
|
158
|
-
(r) => omit(r, toCol)
|
|
159
|
-
);
|
|
298
|
+
row[as] = (subRowGroups[row[refId]] ?? []).map((r) => omit(r, ["refId"]));
|
|
160
299
|
return row;
|
|
161
300
|
});
|
|
162
301
|
}
|
|
302
|
+
|
|
163
303
|
return rows;
|
|
164
304
|
}
|
|
165
305
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Flat 레코드를 중첩 객체로 변환
|
|
308
|
+
*
|
|
309
|
+
* - `user__name` → `{ user: { name } }`
|
|
310
|
+
* - nullable relation의 경우 모든 필드가 null이면 객체 자체를 null로
|
|
311
|
+
*/
|
|
312
|
+
hydrate<T extends UnknownDBRecord>(rows: T[]): T[] {
|
|
313
|
+
return rows.map((row: T) => {
|
|
314
|
+
// nullable relation 처리: 관련 필드가 전부 null인 경우 방지
|
|
169
315
|
const nestedKeys = Object.keys(row).filter((key) => key.includes("__"));
|
|
170
|
-
const groups = groupBy(nestedKeys, (key) => key.split("__")[0]);
|
|
171
|
-
const nullKeys = Object.
|
|
172
|
-
(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
(
|
|
178
|
-
|
|
179
|
-
|
|
316
|
+
const groups = Object.groupBy(nestedKeys, (key) => key.split("__")[0]);
|
|
317
|
+
const nullKeys = Object.entries(groups)
|
|
318
|
+
.filter(
|
|
319
|
+
([_, data]) =>
|
|
320
|
+
data &&
|
|
321
|
+
data.length > 1 &&
|
|
322
|
+
data.every(
|
|
323
|
+
(field) =>
|
|
324
|
+
row[field] === null || (Array.isArray(row[field]) && row[field].length === 0),
|
|
325
|
+
),
|
|
326
|
+
)
|
|
327
|
+
.map(([key]) => key);
|
|
180
328
|
|
|
181
329
|
const hydrated = Object.keys(row).reduce((r, field) => {
|
|
182
330
|
if (!field.includes("__")) {
|
|
331
|
+
// 일반 필드: 배열 내 객체면 재귀 hydrate
|
|
183
332
|
if (Array.isArray(row[field]) && isObject(row[field][0])) {
|
|
184
333
|
r[field] = this.hydrate(row[field]);
|
|
185
|
-
return r;
|
|
186
334
|
} else {
|
|
187
335
|
r[field] = row[field];
|
|
188
|
-
return r;
|
|
189
336
|
}
|
|
337
|
+
return r;
|
|
190
338
|
}
|
|
191
339
|
|
|
340
|
+
// 중첩 필드 처리: user__name → user[name]
|
|
192
341
|
const parts = field.split("__");
|
|
193
342
|
const objPath =
|
|
194
343
|
parts[0] +
|
|
@@ -196,22 +345,28 @@ export class BaseModelClass {
|
|
|
196
345
|
.slice(1)
|
|
197
346
|
.map((part) => `[${part}]`)
|
|
198
347
|
.join("");
|
|
199
|
-
|
|
348
|
+
|
|
349
|
+
r = set(
|
|
200
350
|
r,
|
|
201
351
|
objPath,
|
|
202
352
|
row[field] && Array.isArray(row[field]) && isObject(row[field][0])
|
|
203
353
|
? this.hydrate(row[field])
|
|
204
|
-
: row[field]
|
|
354
|
+
: row[field],
|
|
205
355
|
);
|
|
206
356
|
|
|
207
357
|
return r;
|
|
208
|
-
}, {} as
|
|
209
|
-
|
|
358
|
+
}, {} as UnknownDBRecord);
|
|
359
|
+
|
|
360
|
+
// null relation 처리
|
|
361
|
+
nullKeys.forEach((nullKey) => {
|
|
362
|
+
hydrated[nullKey] = null;
|
|
363
|
+
});
|
|
210
364
|
|
|
211
365
|
return hydrated;
|
|
212
|
-
});
|
|
366
|
+
}) as T[];
|
|
213
367
|
}
|
|
214
368
|
|
|
369
|
+
// Legacy SubsetQuery 실행 (Puri 도입 전 호환용)
|
|
215
370
|
async runSubsetQuery<T extends BaseListParams, U extends string>({
|
|
216
371
|
params,
|
|
217
372
|
baseTable,
|
|
@@ -245,16 +400,19 @@ export class BaseModelClass {
|
|
|
245
400
|
db?: Knex;
|
|
246
401
|
optimizeCountQuery?: boolean;
|
|
247
402
|
}): Promise<{
|
|
403
|
+
// biome-ignore lint/suspicious/noExplicitAny: Puri 도입 전까지 any로 유지
|
|
248
404
|
rows: any[];
|
|
249
405
|
total?: number | undefined;
|
|
250
406
|
subsetQuery: SubsetQuery;
|
|
251
407
|
qb: Knex.QueryBuilder;
|
|
252
408
|
}> {
|
|
409
|
+
const chalk = (await import("chalk")).default;
|
|
410
|
+
const SqlParser = (await import("node-sql-parser")).default;
|
|
411
|
+
const { getTableName, getTableNamesFromWhere } = await import("../utils/sql-parser");
|
|
412
|
+
|
|
253
413
|
const db = _db ?? this.getDB(subset.startsWith("A") ? "w" : "r");
|
|
254
|
-
baseTable =
|
|
255
|
-
|
|
256
|
-
const queryMode =
|
|
257
|
-
params.queryMode ?? (params.id !== undefined ? "list" : "both");
|
|
414
|
+
baseTable = baseTable ?? inflection.pluralize(inflection.underscore(this.modelName));
|
|
415
|
+
const queryMode = params.queryMode ?? (params.id !== undefined ? "list" : "both");
|
|
258
416
|
|
|
259
417
|
const { select, virtual, joins, loaders } = subsetQuery;
|
|
260
418
|
const qb = build({
|
|
@@ -265,21 +423,12 @@ export class BaseModelClass {
|
|
|
265
423
|
virtual,
|
|
266
424
|
});
|
|
267
425
|
|
|
268
|
-
const applyJoinClause = (
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
qb.innerJoin(
|
|
275
|
-
`${join.table} as ${join.as}`,
|
|
276
|
-
this.getJoinClause(db, join)
|
|
277
|
-
);
|
|
278
|
-
} else if (join.join == "outer") {
|
|
279
|
-
qb.leftOuterJoin(
|
|
280
|
-
`${join.table} as ${join.as}`,
|
|
281
|
-
this.getJoinClause(db, join)
|
|
282
|
-
);
|
|
426
|
+
const applyJoinClause = (qb: Knex.QueryBuilder, joins: SubsetQuery["joins"]) => {
|
|
427
|
+
joins.forEach((join) => {
|
|
428
|
+
if (join.join === "inner") {
|
|
429
|
+
qb.innerJoin(`${join.table} as ${join.as}`, this.getJoinClause(db, join));
|
|
430
|
+
} else if (join.join === "outer") {
|
|
431
|
+
qb.leftOuterJoin(`${join.table} as ${join.as}`, this.getJoinClause(db, join));
|
|
283
432
|
}
|
|
284
433
|
});
|
|
285
434
|
};
|
|
@@ -293,34 +442,27 @@ export class BaseModelClass {
|
|
|
293
442
|
const clonedQb = qb.clone().clear("order").clear("offset").clear("limit");
|
|
294
443
|
const parser = new SqlParser.Parser();
|
|
295
444
|
|
|
296
|
-
// optmizeCountQuery가 true인 경우 다른 clause에 영향을 주지 않는 모든 join을 제외함
|
|
297
445
|
if (optimizeCountQuery) {
|
|
298
|
-
const parsedQuery = parser.astify(clonedQb.toQuery()
|
|
446
|
+
const parsedQuery = parser.astify(clonedQb.toQuery(), {
|
|
447
|
+
database: Sonamu.config.database.database,
|
|
448
|
+
});
|
|
299
449
|
const tables = getTableNamesFromWhere(parsedQuery);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
tables.flatMap((table) =>
|
|
303
|
-
table.split("__").map((t) => inflection.pluralize(t))
|
|
304
|
-
)
|
|
450
|
+
const needToJoin = unique(
|
|
451
|
+
tables.flatMap((table) => table.split("__").map((t) => inflection.pluralize(t))),
|
|
305
452
|
);
|
|
306
453
|
applyJoinClause(
|
|
307
454
|
clonedQb,
|
|
308
|
-
joins.filter((j) => needToJoin.includes(j.table))
|
|
455
|
+
joins.filter((j) => needToJoin.includes(j.table)),
|
|
309
456
|
);
|
|
310
457
|
} else {
|
|
311
458
|
applyJoinClause(clonedQb, joins);
|
|
312
459
|
}
|
|
313
460
|
|
|
314
|
-
const processedQb =
|
|
315
|
-
afterBuild?.({
|
|
316
|
-
qb: clonedQb,
|
|
317
|
-
db,
|
|
318
|
-
select,
|
|
319
|
-
joins,
|
|
320
|
-
virtual,
|
|
321
|
-
}) ?? clonedQb;
|
|
461
|
+
const processedQb = afterBuild?.({ qb: clonedQb, db, select, joins, virtual }) ?? clonedQb;
|
|
322
462
|
|
|
323
|
-
const parsedQuery = parser.astify(processedQb.toQuery()
|
|
463
|
+
const parsedQuery = parser.astify(processedQb.toQuery(), {
|
|
464
|
+
database: Sonamu.config.database.database,
|
|
465
|
+
});
|
|
324
466
|
const q = Array.isArray(parsedQuery) ? parsedQuery[0] : parsedQuery;
|
|
325
467
|
if (q.type !== "select") {
|
|
326
468
|
throw new Error("Invalid query");
|
|
@@ -332,19 +474,15 @@ export class BaseModelClass {
|
|
|
332
474
|
.clear("select")
|
|
333
475
|
.select(
|
|
334
476
|
db.raw(
|
|
335
|
-
`COUNT(DISTINCT \`${getTableName(q.columns[0].expr)}\`.\`${q.columns[0].expr.column}\`) as total
|
|
336
|
-
)
|
|
477
|
+
`COUNT(DISTINCT \`${getTableName(q.columns[0].expr)}\`.\`${q.columns[0].expr.column}\`) as total`,
|
|
478
|
+
),
|
|
337
479
|
)
|
|
338
480
|
.first()
|
|
339
481
|
: clonedQb.clear("select").count("*", { as: "total" }).first();
|
|
340
482
|
const countRow: { total?: number } = await countQuery;
|
|
341
483
|
|
|
342
|
-
// debug: countQuery
|
|
343
484
|
if (debug === true || debug === "count") {
|
|
344
|
-
console.debug(
|
|
345
|
-
"DEBUG: count query",
|
|
346
|
-
chalk.blue(countQuery.toQuery().toString())
|
|
347
|
-
);
|
|
485
|
+
console.debug("DEBUG: count query", chalk.blue(countQuery.toQuery().toString()));
|
|
348
486
|
}
|
|
349
487
|
|
|
350
488
|
return countRow?.total ?? 0;
|
|
@@ -356,34 +494,20 @@ export class BaseModelClass {
|
|
|
356
494
|
return [];
|
|
357
495
|
}
|
|
358
496
|
|
|
359
|
-
// limit, offset
|
|
360
497
|
if (params.num !== 0) {
|
|
361
|
-
|
|
362
|
-
qb.
|
|
498
|
+
assert(params.num);
|
|
499
|
+
qb.limit(params.num);
|
|
500
|
+
qb.offset(params.num * ((params.page ?? 1) - 1));
|
|
363
501
|
}
|
|
364
502
|
|
|
365
|
-
// select, rows
|
|
366
503
|
const clonedQb = qb.clone().select(select);
|
|
367
|
-
|
|
368
|
-
// join
|
|
369
504
|
applyJoinClause(clonedQb, joins);
|
|
370
505
|
|
|
371
|
-
const listQuery =
|
|
372
|
-
afterBuild?.({
|
|
373
|
-
qb: clonedQb,
|
|
374
|
-
db,
|
|
375
|
-
select,
|
|
376
|
-
joins,
|
|
377
|
-
virtual,
|
|
378
|
-
}) ?? clonedQb;
|
|
506
|
+
const listQuery = afterBuild?.({ qb: clonedQb, db, select, joins, virtual }) ?? clonedQb;
|
|
379
507
|
|
|
380
508
|
let rows = await listQuery;
|
|
381
|
-
// debug: listQuery
|
|
382
509
|
if (debug === true || debug === "list") {
|
|
383
|
-
console.debug(
|
|
384
|
-
"DEBUG: list query",
|
|
385
|
-
chalk.blue(listQuery.toQuery().toString())
|
|
386
|
-
);
|
|
510
|
+
console.debug("DEBUG: list query", chalk.blue(listQuery.toQuery().toString()));
|
|
387
511
|
}
|
|
388
512
|
|
|
389
513
|
rows = await this.useLoaders(db, rows, loaders);
|
|
@@ -394,10 +518,73 @@ export class BaseModelClass {
|
|
|
394
518
|
return { rows, total, subsetQuery, qb };
|
|
395
519
|
}
|
|
396
520
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
521
|
+
// Legacy Loader 처리 (Puri 도입 전 호환용)
|
|
522
|
+
async useLoaders(db: Knex, rows: UnknownDBRecord[], loaders: SubsetQuery["loaders"]) {
|
|
523
|
+
if (loaders.length === 0) {
|
|
524
|
+
return rows;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
for (const loader of loaders) {
|
|
528
|
+
let subQ: Knex.QueryBuilder;
|
|
529
|
+
let subRows: UnknownDBRecord[];
|
|
530
|
+
let toCol: string;
|
|
531
|
+
|
|
532
|
+
const fromIds = rows.map((row) => row[loader.manyJoin.idField]);
|
|
533
|
+
|
|
534
|
+
if (loader.manyJoin.through === undefined) {
|
|
535
|
+
// HasMany
|
|
536
|
+
const idColumn = `${loader.manyJoin.toTable}.${loader.manyJoin.toCol}`;
|
|
537
|
+
subQ = db(loader.manyJoin.toTable)
|
|
538
|
+
.whereIn(idColumn as string, fromIds as string[])
|
|
539
|
+
.select([...loader.select, idColumn]);
|
|
540
|
+
|
|
541
|
+
loader.oneJoins.forEach((join) => {
|
|
542
|
+
if (join.join === "inner") {
|
|
543
|
+
subQ.innerJoin(`${join.table} as ${join.as}`, this.getJoinClause(db, join));
|
|
544
|
+
} else if (join.join === "outer") {
|
|
545
|
+
subQ.leftOuterJoin(`${join.table} as ${join.as}`, this.getJoinClause(db, join));
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
toCol = loader.manyJoin.toCol;
|
|
549
|
+
} else {
|
|
550
|
+
// ManyToMany
|
|
551
|
+
const idColumn = `${loader.manyJoin.through.table}.${loader.manyJoin.through.fromCol}`;
|
|
552
|
+
subQ = db(loader.manyJoin.through.table)
|
|
553
|
+
.join(
|
|
554
|
+
loader.manyJoin.toTable,
|
|
555
|
+
`${loader.manyJoin.through.table}.${loader.manyJoin.through.toCol}`,
|
|
556
|
+
`${loader.manyJoin.toTable}.${loader.manyJoin.toCol}`,
|
|
557
|
+
)
|
|
558
|
+
.whereIn(idColumn as string, fromIds as string[])
|
|
559
|
+
.select(unique([...loader.select, idColumn]));
|
|
560
|
+
|
|
561
|
+
loader.oneJoins.forEach((join) => {
|
|
562
|
+
if (join.join === "inner") {
|
|
563
|
+
subQ.innerJoin(`${join.table} as ${join.as}`, this.getJoinClause(db, join));
|
|
564
|
+
} else if (join.join === "outer") {
|
|
565
|
+
subQ.leftOuterJoin(`${join.table} as ${join.as}`, this.getJoinClause(db, join));
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
toCol = loader.manyJoin.through.fromCol;
|
|
569
|
+
}
|
|
570
|
+
subRows = await subQ;
|
|
571
|
+
|
|
572
|
+
if (loader.loaders) {
|
|
573
|
+
subRows = await this.useLoaders(db, subRows, loader.loaders);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const subRowGroups = group(subRows, (row) => row[toCol] as string);
|
|
577
|
+
rows = rows.map((row) => {
|
|
578
|
+
row[loader.as] = (subRowGroups[row[loader.manyJoin.idField] as string] ?? []).map((r) =>
|
|
579
|
+
omit(r, [toCol]),
|
|
580
|
+
);
|
|
581
|
+
return row;
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
return rows;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
getJoinClause(db: Knex<any, unknown>, join: SubsetQuery["joins"][number]): Knex.Raw<any> {
|
|
401
588
|
if (!isCustomJoinClause(join)) {
|
|
402
589
|
return db.raw(`${join.from} = ${join.to}`);
|
|
403
590
|
} else {
|
|
@@ -409,4 +596,25 @@ export class BaseModelClass {
|
|
|
409
596
|
return new UpsertBuilder();
|
|
410
597
|
}
|
|
411
598
|
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Enhancer 파라미터 조건부 타입
|
|
602
|
+
* RequiredEnhancerKeys가 없으면 enhancers 선택적, 있으면 필수
|
|
603
|
+
*/
|
|
604
|
+
type EnhancerParam<
|
|
605
|
+
TSubsetKey extends string,
|
|
606
|
+
TComputedResults extends Record<TSubsetKey, any>,
|
|
607
|
+
TSubsetMapping extends Record<TSubsetKey, any>,
|
|
608
|
+
> = [RequiredEnhancerKeys<TSubsetKey, TComputedResults, TSubsetMapping>] extends [never]
|
|
609
|
+
? { enhancers?: EnhancerMap<TSubsetKey, TComputedResults, TSubsetMapping> }
|
|
610
|
+
: { enhancers: EnhancerMap<TSubsetKey, TComputedResults, TSubsetMapping> };
|
|
611
|
+
|
|
612
|
+
type RequiredEnhancerKeys<
|
|
613
|
+
TSubsetKey extends string,
|
|
614
|
+
TComputedResults extends Record<TSubsetKey, any>,
|
|
615
|
+
TSubsetMapping extends Record<TSubsetKey, any>,
|
|
616
|
+
> = {
|
|
617
|
+
[K in TSubsetKey]: TComputedResults[K] extends TSubsetMapping[K] ? never : K;
|
|
618
|
+
}[TSubsetKey];
|
|
619
|
+
|
|
412
620
|
export const BaseModel = new BaseModelClass();
|