sonamu 0.4.13 → 0.5.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 +15 -0
- package/dist/api/base-frame.d.ts +8 -0
- package/dist/api/base-frame.d.ts.map +1 -0
- package/dist/api/base-frame.js +2 -0
- package/dist/api/base-frame.js.map +1 -0
- package/dist/api/caster.d.ts +5 -0
- package/dist/api/caster.d.ts.map +1 -0
- package/dist/api/caster.js +2 -0
- package/dist/api/caster.js.map +1 -0
- package/dist/api/code-converters.d.ts +23 -0
- package/dist/api/code-converters.d.ts.map +1 -0
- package/dist/api/code-converters.js +2 -0
- package/dist/api/code-converters.js.map +1 -0
- package/dist/api/context.d.ts +16 -0
- package/dist/api/context.d.ts.map +1 -0
- package/dist/api/context.js +2 -0
- package/dist/api/context.js.map +1 -0
- package/dist/api/decorators.d.ts +50 -0
- package/dist/api/decorators.d.ts.map +1 -0
- package/dist/api/decorators.js +2 -0
- package/dist/api/decorators.js.map +1 -0
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +2 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/sonamu.d.ts +83 -0
- package/dist/api/sonamu.d.ts.map +1 -0
- package/dist/api/sonamu.js +2 -0
- package/dist/api/sonamu.js.map +1 -0
- package/dist/api/sonamu.types.d.ts +30 -0
- package/dist/api/sonamu.types.d.ts.map +1 -0
- package/dist/api/sonamu.types.js +2 -0
- package/dist/api/sonamu.types.js.map +1 -0
- package/dist/bin/build-config.d.ts +5 -0
- package/dist/bin/build-config.d.ts.map +1 -0
- package/dist/bin/build-config.js +2 -0
- package/dist/bin/build-config.js.map +1 -0
- package/dist/bin/cli-wrapper.d.ts +2 -0
- package/dist/bin/cli-wrapper.d.ts.map +1 -0
- package/dist/bin/cli-wrapper.js +1 -38
- package/dist/bin/cli-wrapper.js.map +1 -1
- package/dist/bin/cli.d.ts +2 -2
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +1 -903
- package/dist/bin/cli.js.map +1 -1
- package/dist/bin/cli.mjs +2 -2
- package/dist/{chunk-DMJSNO2L.js → chunk-2WAC2GER.js} +44 -44
- package/dist/{chunk-DMJSNO2L.js.map → chunk-2WAC2GER.js.map} +1 -1
- package/dist/{chunk-NI37CY4T.mjs → chunk-C3IPIF6O.mjs} +2 -2
- package/dist/{chunk-DYFCACHD.js → chunk-EXHKSVTE.js} +7 -7
- package/dist/{chunk-QJFHDCBN.mjs → chunk-FCERKIIF.mjs} +2 -2
- package/dist/chunk-FCERKIIF.mjs.map +1 -0
- package/dist/{chunk-DDJ7T4MA.mjs → chunk-HGIBJYOU.mjs} +2 -2
- package/dist/{chunk-NIFOTHBW.mjs → chunk-JKSOJRQA.mjs} +2 -2
- package/dist/{chunk-CXAVBVKC.js → chunk-OTKKFP3Y.js} +100 -100
- package/dist/{chunk-J6S43O7G.js → chunk-UZ2IY5VE.js} +4 -4
- package/dist/database/_batch_update.d.ts +15 -0
- package/dist/database/_batch_update.d.ts.map +1 -0
- package/dist/database/_batch_update.js +2 -0
- package/dist/database/_batch_update.js.map +1 -0
- package/dist/database/base-model.d.ts +41 -0
- package/dist/database/base-model.d.ts.map +1 -0
- package/dist/database/base-model.js +2 -0
- package/dist/database/base-model.js.map +1 -0
- package/dist/database/code-generator.d.ts +13 -0
- package/dist/database/code-generator.d.ts.map +1 -0
- package/dist/database/code-generator.js +2 -0
- package/dist/database/code-generator.js.map +1 -0
- package/dist/database/db.d.ts +40 -0
- package/dist/database/db.d.ts.map +1 -0
- package/dist/database/db.js +2 -0
- package/dist/database/db.js.map +1 -0
- package/dist/database/drivers/knex/base-model.js +8 -8
- package/dist/database/drivers/knex/base-model.mjs +3 -3
- package/dist/database/drivers/kysely/base-model.js +9 -9
- package/dist/database/drivers/kysely/base-model.mjs +3 -3
- package/dist/database/knex-plugins/knex-on-duplicate-update.d.ts +2 -0
- package/dist/database/knex-plugins/knex-on-duplicate-update.d.ts.map +1 -0
- package/dist/database/knex-plugins/knex-on-duplicate-update.js +2 -0
- package/dist/database/knex-plugins/knex-on-duplicate-update.js.map +1 -0
- package/dist/database/puri-wrapper.d.ts +34 -0
- package/dist/database/puri-wrapper.d.ts.map +1 -0
- package/dist/database/puri-wrapper.js +2 -0
- package/dist/database/puri-wrapper.js.map +1 -0
- package/dist/database/puri.d.ts +83 -0
- package/dist/database/puri.d.ts.map +1 -0
- package/dist/database/puri.js +2 -0
- package/dist/database/puri.js.map +1 -0
- package/dist/database/puri.types.d.ts +60 -0
- package/dist/database/puri.types.d.ts.map +1 -0
- package/dist/database/puri.types.js +2 -0
- package/dist/database/puri.types.js.map +1 -0
- package/dist/database/transaction-context.d.ts +9 -0
- package/dist/database/transaction-context.d.ts.map +1 -0
- package/dist/database/transaction-context.js +2 -0
- package/dist/database/transaction-context.js.map +1 -0
- package/dist/database/types.d.ts +39 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/database/types.js +2 -0
- package/dist/database/types.js.map +1 -0
- package/dist/database/upsert-builder.d.ts +34 -0
- package/dist/database/upsert-builder.d.ts.map +1 -0
- package/dist/database/upsert-builder.js +2 -0
- package/dist/database/upsert-builder.js.map +1 -0
- package/dist/entity/entity-manager.d.ts +32 -0
- package/dist/entity/entity-manager.d.ts.map +1 -0
- package/dist/entity/entity-manager.js +2 -0
- package/dist/entity/entity-manager.js.map +1 -0
- package/dist/entity/entity-utils.d.ts +61 -0
- package/dist/entity/entity-utils.d.ts.map +1 -0
- package/dist/entity/entity-utils.js +2 -0
- package/dist/entity/entity-utils.js.map +1 -0
- package/dist/entity/entity.d.ts +62 -0
- package/dist/entity/entity.d.ts.map +1 -0
- package/dist/entity/entity.js +2 -0
- package/dist/entity/entity.js.map +1 -0
- package/dist/entity/migrator.d.ts +135 -0
- package/dist/entity/migrator.d.ts.map +1 -0
- package/dist/entity/migrator.js +2 -0
- package/dist/entity/migrator.js.map +1 -0
- package/dist/exceptions/error-handler.d.ts +3 -0
- package/dist/exceptions/error-handler.d.ts.map +1 -0
- package/dist/exceptions/error-handler.js +2 -0
- package/dist/exceptions/error-handler.js.map +1 -0
- package/dist/exceptions/so-exceptions.d.ts +48 -0
- package/dist/exceptions/so-exceptions.d.ts.map +1 -0
- package/dist/exceptions/so-exceptions.js +2 -0
- package/dist/exceptions/so-exceptions.js.map +1 -0
- package/dist/file-storage/driver.d.ts +45 -0
- package/dist/file-storage/driver.d.ts.map +1 -0
- package/dist/file-storage/driver.js +2 -0
- package/dist/file-storage/driver.js.map +1 -0
- package/dist/file-storage/file-storage.d.ts +50 -0
- package/dist/file-storage/file-storage.d.ts.map +1 -0
- package/dist/file-storage/file-storage.js +2 -0
- package/dist/file-storage/file-storage.js.map +1 -0
- package/dist/index.d.ts +22 -813
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -433
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/migration/code-generation.d.ts +15 -0
- package/dist/migration/code-generation.d.ts.map +1 -0
- package/dist/migration/code-generation.js +2 -0
- package/dist/migration/code-generation.js.map +1 -0
- package/dist/migration/migration-set.d.ts +17 -0
- package/dist/migration/migration-set.d.ts.map +1 -0
- package/dist/migration/migration-set.js +2 -0
- package/dist/migration/migration-set.js.map +1 -0
- package/dist/migration/migrator.d.ts +130 -0
- package/dist/migration/migrator.d.ts.map +1 -0
- package/dist/migration/migrator.js +2 -0
- package/dist/migration/migrator.js.map +1 -0
- package/dist/migration/types.d.ts +52 -0
- package/dist/migration/types.d.ts.map +1 -0
- package/dist/migration/types.js +2 -0
- package/dist/migration/types.js.map +1 -0
- package/dist/smd/smd-manager.d.ts +28 -0
- package/dist/smd/smd-manager.d.ts.map +1 -0
- package/dist/smd/smd-manager.js +2 -0
- package/dist/smd/smd-manager.js.map +1 -0
- package/dist/smd/smd.d.ts +40 -0
- package/dist/smd/smd.d.ts.map +1 -0
- package/dist/smd/smd.js +2 -0
- package/dist/smd/smd.js.map +1 -0
- package/dist/syncer/index.d.ts +2 -0
- package/dist/syncer/index.d.ts.map +1 -0
- package/dist/syncer/index.js +2 -0
- package/dist/syncer/index.js.map +1 -0
- package/dist/syncer/syncer.d.ts +127 -0
- package/dist/syncer/syncer.d.ts.map +1 -0
- package/dist/syncer/syncer.js +2 -0
- package/dist/syncer/syncer.js.map +1 -0
- package/dist/templates/base-template.d.ts +13 -0
- package/dist/templates/base-template.d.ts.map +1 -0
- package/dist/templates/base-template.js +2 -0
- package/dist/templates/base-template.js.map +1 -0
- package/dist/templates/entity.template.d.ts +17 -0
- package/dist/templates/entity.template.d.ts.map +1 -0
- package/dist/templates/entity.template.js +2 -0
- package/dist/templates/entity.template.js.map +1 -0
- package/dist/templates/generated.template.d.ts +27 -0
- package/dist/templates/generated.template.d.ts.map +1 -0
- package/dist/templates/generated.template.js +2 -0
- package/dist/templates/generated.template.js.map +1 -0
- package/dist/templates/generated_http.template.d.ts +24 -0
- package/dist/templates/generated_http.template.d.ts.map +1 -0
- package/dist/templates/generated_http.template.js +2 -0
- package/dist/templates/generated_http.template.js.map +1 -0
- package/dist/templates/generated_sso.template.d.ts +20 -0
- package/dist/templates/generated_sso.template.d.ts.map +1 -0
- package/dist/templates/generated_sso.template.js +2 -0
- package/dist/templates/generated_sso.template.js.map +1 -0
- package/dist/templates/index.d.ts +2 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +2 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/templates/init_types.template.d.ts +17 -0
- package/dist/templates/init_types.template.d.ts.map +1 -0
- package/dist/templates/init_types.template.js +2 -0
- package/dist/templates/init_types.template.js.map +1 -0
- package/dist/templates/model.template.d.ts +17 -0
- package/dist/templates/model.template.d.ts.map +1 -0
- package/dist/templates/model.template.js +2 -0
- package/dist/templates/model.template.js.map +1 -0
- package/dist/templates/model_test.template.d.ts +17 -0
- package/dist/templates/model_test.template.d.ts.map +1 -0
- package/dist/templates/model_test.template.js +2 -0
- package/dist/templates/model_test.template.js.map +1 -0
- package/dist/templates/service.template.d.ts +29 -0
- package/dist/templates/service.template.d.ts.map +1 -0
- package/dist/templates/service.template.js +2 -0
- package/dist/templates/service.template.js.map +1 -0
- package/dist/templates/view_enums_buttonset.template.d.ts +17 -0
- package/dist/templates/view_enums_buttonset.template.d.ts.map +1 -0
- package/dist/templates/view_enums_buttonset.template.js +2 -0
- package/dist/templates/view_enums_buttonset.template.js.map +1 -0
- package/dist/templates/view_enums_dropdown.template.d.ts +18 -0
- package/dist/templates/view_enums_dropdown.template.d.ts.map +1 -0
- package/dist/templates/view_enums_dropdown.template.js +2 -0
- package/dist/templates/view_enums_dropdown.template.js.map +1 -0
- package/dist/templates/view_enums_select.template.d.ts +17 -0
- package/dist/templates/view_enums_select.template.d.ts.map +1 -0
- package/dist/templates/view_enums_select.template.js +2 -0
- package/dist/templates/view_enums_select.template.js.map +1 -0
- package/dist/templates/view_form.template.d.ts +26 -0
- package/dist/templates/view_form.template.d.ts.map +1 -0
- package/dist/templates/view_form.template.js +2 -0
- package/dist/templates/view_form.template.js.map +1 -0
- package/dist/templates/view_id_all_select.template.d.ts +17 -0
- package/dist/templates/view_id_all_select.template.d.ts.map +1 -0
- package/dist/templates/view_id_all_select.template.js +2 -0
- package/dist/templates/view_id_all_select.template.js.map +1 -0
- package/dist/templates/view_id_async_select.template.d.ts +17 -0
- package/dist/templates/view_id_async_select.template.d.ts.map +1 -0
- package/dist/templates/view_id_async_select.template.js +2 -0
- package/dist/templates/view_id_async_select.template.js.map +1 -0
- package/dist/templates/view_list.template.d.ts +38 -0
- package/dist/templates/view_list.template.d.ts.map +1 -0
- package/dist/templates/view_list.template.js +2 -0
- package/dist/templates/view_list.template.js.map +1 -0
- package/dist/templates/view_list_columns.template.d.ts +17 -0
- package/dist/templates/view_list_columns.template.d.ts.map +1 -0
- package/dist/templates/view_list_columns.template.js +2 -0
- package/dist/templates/view_list_columns.template.js.map +1 -0
- package/dist/templates/view_search_input.template.d.ts +17 -0
- package/dist/templates/view_search_input.template.d.ts.map +1 -0
- package/dist/templates/view_search_input.template.js +2 -0
- package/dist/templates/view_search_input.template.js.map +1 -0
- package/dist/testing/_relation-graph.d.ts +7 -0
- package/dist/testing/_relation-graph.d.ts.map +1 -0
- package/dist/testing/_relation-graph.js +2 -0
- package/dist/testing/_relation-graph.js.map +1 -0
- package/dist/testing/fixture-manager.d.ts +35 -0
- package/dist/testing/fixture-manager.d.ts.map +1 -0
- package/dist/testing/fixture-manager.js +2 -0
- package/dist/testing/fixture-manager.js.map +1 -0
- package/dist/types/types.d.ts +609 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/types.js +2 -0
- package/dist/types/types.js.map +1 -0
- package/dist/typings/knex.d.js +2 -0
- package/dist/typings/knex.d.js.map +1 -0
- package/dist/utils/async-utils.d.ts +25 -0
- package/dist/utils/async-utils.d.ts.map +1 -0
- package/dist/utils/async-utils.js +2 -0
- package/dist/utils/async-utils.js.map +1 -0
- package/dist/utils/controller.d.ts +9 -0
- package/dist/utils/controller.d.ts.map +1 -0
- package/dist/utils/controller.js +2 -0
- package/dist/utils/controller.js.map +1 -0
- package/dist/utils/fs-utils.d.ts +9 -0
- package/dist/utils/fs-utils.d.ts.map +1 -0
- package/dist/utils/fs-utils.js +2 -0
- package/dist/utils/fs-utils.js.map +1 -0
- package/dist/utils/lodash-able.d.ts +2 -0
- package/dist/utils/lodash-able.d.ts.map +1 -0
- package/dist/utils/lodash-able.js +2 -0
- package/dist/utils/lodash-able.js.map +1 -0
- package/dist/utils/model.d.ts +17 -0
- package/dist/utils/model.d.ts.map +1 -0
- package/dist/utils/model.js +2 -0
- package/dist/utils/model.js.map +1 -0
- package/dist/utils/sql-parser.d.ts +4 -0
- package/dist/utils/sql-parser.d.ts.map +1 -0
- package/dist/utils/sql-parser.js +2 -0
- package/dist/utils/sql-parser.js.map +1 -0
- package/dist/utils/utils.d.ts +9 -0
- package/dist/utils/utils.d.ts.map +1 -0
- package/dist/utils/utils.js +2 -0
- package/dist/utils/utils.js.map +1 -0
- package/dist/utils/zod-error.d.ts +8 -0
- package/dist/utils/zod-error.d.ts.map +1 -0
- package/dist/utils/zod-error.js +2 -0
- package/dist/utils/zod-error.js.map +1 -0
- package/nodemon.json +6 -0
- package/package.json +29 -44
- package/src/api/base-frame.ts +3 -4
- package/src/api/caster.ts +22 -23
- package/src/api/code-converters.ts +170 -134
- package/src/api/context.ts +13 -6
- package/src/api/decorators.ts +146 -20
- package/src/api/index.ts +2 -0
- package/src/api/sonamu.ts +374 -165
- package/src/bin/build-config.ts +5 -0
- package/src/bin/cli-wrapper.ts +29 -40
- package/src/bin/cli.ts +132 -190
- package/src/database/_batch_update.ts +10 -15
- package/src/database/base-model.ts +300 -216
- package/src/database/db.ts +191 -21
- package/src/database/{drivers/knex/plugins → knex-plugins}/knex-on-duplicate-update.ts +1 -1
- package/src/database/puri-wrapper.ts +129 -0
- package/src/database/puri.ts +808 -0
- package/src/database/puri.types.ts +222 -0
- package/src/database/transaction-context.ts +18 -0
- package/src/database/upsert-builder.ts +32 -35
- package/src/entity/entity-manager.ts +7 -15
- package/src/entity/entity.ts +9 -31
- package/src/entity/migrator-/354/235/264/354/202/254/352/260/224/354/226/264/354/232/224.md +1 -0
- package/src/file-storage/driver.ts +121 -0
- package/src/file-storage/file-storage.ts +100 -0
- package/src/index.ts +14 -11
- package/src/migration/code-generation.ts +777 -0
- package/src/migration/migration-set.ts +453 -0
- package/src/migration/migrator.ts +823 -0
- package/src/migration/types.ts +53 -0
- package/src/shared/web.shared.ts.txt +33 -2
- package/src/syncer/syncer.ts +294 -127
- package/src/templates/generated.template.ts +13 -1
- package/src/templates/generated_http.template.ts +15 -12
- package/src/templates/generated_sso.template.ts +50 -2
- package/src/templates/model.template.ts +138 -2
- package/src/templates/service.template.ts +0 -1
- package/src/templates/view_form.template.ts +11 -7
- package/src/templates/view_list.template.ts +12 -4
- package/src/testing/fixture-manager.ts +229 -174
- package/src/types/types.ts +102 -14
- package/src/utils/async-utils.ts +64 -0
- package/src/utils/fs-utils.ts +17 -0
- package/src/utils/model.ts +0 -2
- package/src/utils/utils.ts +14 -58
- package/src/utils/zod-error.ts +12 -176
- package/tsconfig.json +2 -0
- package/tsup.config.js +4 -2
- package/.pnp.cjs +0 -14363
- package/.pnp.loader.mjs +0 -2047
- package/.vscode/extensions.json +0 -6
- package/.vscode/settings.json +0 -9
- package/.yarnrc.yml +0 -5
- package/dist/chunk-QJFHDCBN.mjs.map +0 -1
- package/src/database/base-model.abstract.ts +0 -97
- package/src/database/db.abstract.ts +0 -75
- package/src/database/drivers/knex/base-model.ts +0 -55
- package/src/database/drivers/knex/client.ts +0 -209
- package/src/database/drivers/knex/db.ts +0 -232
- package/src/database/drivers/knex/generator.ts +0 -659
- package/src/database/drivers/kysely/base-model.ts +0 -89
- package/src/database/drivers/kysely/client.ts +0 -309
- package/src/database/drivers/kysely/db.ts +0 -238
- package/src/database/drivers/kysely/generator.ts +0 -714
- package/src/database/types.ts +0 -118
- package/src/entity/migrator.ts +0 -1400
- package/src/smd/smd-manager.ts +0 -139
- package/src/smd/smd.ts +0 -571
- package/src/templates/kysely_types.template.ts +0 -205
- /package/dist/{chunk-NI37CY4T.mjs.map → chunk-C3IPIF6O.mjs.map} +0 -0
- /package/dist/{chunk-DYFCACHD.js.map → chunk-EXHKSVTE.js.map} +0 -0
- /package/dist/{chunk-DDJ7T4MA.mjs.map → chunk-HGIBJYOU.mjs.map} +0 -0
- /package/dist/{chunk-NIFOTHBW.mjs.map → chunk-JKSOJRQA.mjs.map} +0 -0
- /package/dist/{chunk-CXAVBVKC.js.map → chunk-OTKKFP3Y.js.map} +0 -0
- /package/dist/{chunk-J6S43O7G.js.map → chunk-UZ2IY5VE.js.map} +0 -0
package/src/api/sonamu.ts
CHANGED
|
@@ -1,23 +1,35 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
3
|
+
import { exists } from "../utils/fs-utils";
|
|
4
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
1
5
|
import chalk from "chalk";
|
|
2
|
-
import
|
|
3
|
-
|
|
6
|
+
import fastify from "fastify";
|
|
7
|
+
|
|
4
8
|
import { ZodError } from "zod";
|
|
5
|
-
import path from "path";
|
|
6
|
-
import fs from "fs-extra";
|
|
7
9
|
import { getZodObjectFromApi } from "./code-converters";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
import {
|
|
11
|
+
BadRequestException,
|
|
12
|
+
NotFoundException,
|
|
13
|
+
} from "../exceptions/so-exceptions";
|
|
14
|
+
import { humanizeZodError } from "../utils/zod-error";
|
|
11
15
|
import { fastifyCaster } from "./caster";
|
|
12
|
-
import {
|
|
13
|
-
|
|
16
|
+
import {
|
|
17
|
+
ApiParamType,
|
|
18
|
+
SonamuFastifyConfig,
|
|
19
|
+
SonamuServerOptions,
|
|
20
|
+
} from "../types/types";
|
|
14
21
|
import { isLocal, isTest } from "../utils/controller";
|
|
15
22
|
import { findApiRootPath } from "../utils/utils";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
23
|
+
import { DB, SonamuDBConfig } from "../database/db";
|
|
24
|
+
import { attachOnDuplicateUpdate } from "../database/knex-plugins/knex-on-duplicate-update";
|
|
25
|
+
import type { ExtendedApi } from "./decorators";
|
|
26
|
+
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
|
27
|
+
import type { IncomingMessage, Server, ServerResponse } from "http";
|
|
28
|
+
import type { Context, UploadContext } from "./context";
|
|
29
|
+
import type { Syncer } from "../syncer/syncer";
|
|
30
|
+
import type { FSWatcher } from "chokidar";
|
|
31
|
+
import { formatInTimeZone } from "date-fns-tz";
|
|
32
|
+
import type { Driver } from "../file-storage/driver";
|
|
21
33
|
|
|
22
34
|
export type SonamuConfig = {
|
|
23
35
|
projectName?: string;
|
|
@@ -30,54 +42,24 @@ export type SonamuConfig = {
|
|
|
30
42
|
route: {
|
|
31
43
|
prefix: string;
|
|
32
44
|
};
|
|
45
|
+
timezone?: string;
|
|
46
|
+
ui?: {
|
|
47
|
+
port: number;
|
|
48
|
+
};
|
|
33
49
|
};
|
|
34
50
|
export type SonamuSecrets = {
|
|
35
51
|
[key: string]: string;
|
|
36
52
|
};
|
|
37
|
-
type SonamuFastifyConfig = {
|
|
38
|
-
contextProvider: (
|
|
39
|
-
defaultContext: Pick<Context, "headers" | "reply">,
|
|
40
|
-
request: FastifyRequest,
|
|
41
|
-
reply: FastifyReply
|
|
42
|
-
) => Context;
|
|
43
|
-
guardHandler: (
|
|
44
|
-
guard: string,
|
|
45
|
-
request: FastifyRequest,
|
|
46
|
-
api: {
|
|
47
|
-
typeParameters: ApiParamType.TypeParam[];
|
|
48
|
-
parameters: ApiParam[];
|
|
49
|
-
returnType: ApiParamType;
|
|
50
|
-
modelName: string;
|
|
51
|
-
methodName: string;
|
|
52
|
-
path: string;
|
|
53
|
-
options: ApiDecoratorOptions;
|
|
54
|
-
}
|
|
55
|
-
) => void;
|
|
56
|
-
cache?: {
|
|
57
|
-
get: (key: string) => Promise<unknown | null>;
|
|
58
|
-
put: (key: string, value: unknown, ttl?: number) => Promise<void>;
|
|
59
|
-
resolveKey: (
|
|
60
|
-
path: string,
|
|
61
|
-
reqBody: {
|
|
62
|
-
[key: string]: unknown;
|
|
63
|
-
}
|
|
64
|
-
) =>
|
|
65
|
-
| {
|
|
66
|
-
cache: false;
|
|
67
|
-
}
|
|
68
|
-
| {
|
|
69
|
-
cache: true;
|
|
70
|
-
key: string;
|
|
71
|
-
ttl?: number;
|
|
72
|
-
};
|
|
73
|
-
};
|
|
74
|
-
};
|
|
75
53
|
class SonamuClass {
|
|
76
54
|
public isInitialized: boolean = false;
|
|
77
55
|
public asyncLocalStorage: AsyncLocalStorage<{
|
|
78
56
|
context: Context;
|
|
79
57
|
}> = new AsyncLocalStorage();
|
|
80
58
|
|
|
59
|
+
public uploadStorage: AsyncLocalStorage<{
|
|
60
|
+
uploadContext: UploadContext;
|
|
61
|
+
}> = new AsyncLocalStorage();
|
|
62
|
+
|
|
81
63
|
public getContext(): Context {
|
|
82
64
|
const store = this.asyncLocalStorage.getStore();
|
|
83
65
|
if (store?.context) {
|
|
@@ -86,6 +68,16 @@ class SonamuClass {
|
|
|
86
68
|
throw new Error("Sonamu cannot find context");
|
|
87
69
|
}
|
|
88
70
|
|
|
71
|
+
public getUploadContext(): UploadContext {
|
|
72
|
+
const store = this.uploadStorage.getStore();
|
|
73
|
+
if (store?.uploadContext) {
|
|
74
|
+
return store.uploadContext;
|
|
75
|
+
}
|
|
76
|
+
throw new Error(
|
|
77
|
+
"Sonamu cannot find upload context. Did you use @upload decorator?"
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
89
81
|
private _apiRootPath: string | null = null;
|
|
90
82
|
set apiRootPath(apiRootPath: string) {
|
|
91
83
|
this._apiRootPath = apiRootPath;
|
|
@@ -104,24 +96,13 @@ class SonamuClass {
|
|
|
104
96
|
set dbConfig(dbConfig: SonamuDBConfig) {
|
|
105
97
|
this._dbConfig = dbConfig;
|
|
106
98
|
}
|
|
107
|
-
get dbConfig() {
|
|
99
|
+
get dbConfig(): SonamuDBConfig {
|
|
108
100
|
if (this._dbConfig === null) {
|
|
109
101
|
throw new Error("Sonamu has not been initialized");
|
|
110
102
|
}
|
|
111
103
|
return this._dbConfig!;
|
|
112
104
|
}
|
|
113
105
|
|
|
114
|
-
private _dbClient: DatabaseDriver | null = null;
|
|
115
|
-
set dbClient(_dbClient: DatabaseDriver) {
|
|
116
|
-
this._dbClient = _dbClient;
|
|
117
|
-
}
|
|
118
|
-
get dbClient() {
|
|
119
|
-
if (this._dbClient === null) {
|
|
120
|
-
throw new Error("Sonamu has not been initialized");
|
|
121
|
-
}
|
|
122
|
-
return this._dbClient!;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
106
|
private _syncer: Syncer | null = null;
|
|
126
107
|
set syncer(syncer: Syncer) {
|
|
127
108
|
this._syncer = syncer;
|
|
@@ -152,6 +133,21 @@ class SonamuClass {
|
|
|
152
133
|
return this._secrets;
|
|
153
134
|
}
|
|
154
135
|
|
|
136
|
+
private _storage: Driver | null = null;
|
|
137
|
+
set storage(storage: Driver) {
|
|
138
|
+
this._storage = storage;
|
|
139
|
+
}
|
|
140
|
+
get storage(): Driver | null {
|
|
141
|
+
return this._storage;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// HMR 처리
|
|
145
|
+
public watcher: FSWatcher | null = null;
|
|
146
|
+
private pendingFiles: string[] = [];
|
|
147
|
+
private hmrStartTime: number = 0;
|
|
148
|
+
|
|
149
|
+
public server: FastifyInstance | null = null;
|
|
150
|
+
|
|
155
151
|
async initForTesting() {
|
|
156
152
|
await this.init(true, false, undefined, true);
|
|
157
153
|
}
|
|
@@ -171,26 +167,25 @@ class SonamuClass {
|
|
|
171
167
|
);
|
|
172
168
|
|
|
173
169
|
// API 루트 패스
|
|
174
|
-
this.apiRootPath = apiRootPath ??
|
|
170
|
+
this.apiRootPath = apiRootPath ?? findApiRootPath();
|
|
175
171
|
const configPath = path.join(this.apiRootPath, "sonamu.config.json");
|
|
176
172
|
const secretsPath = path.join(this.apiRootPath, "sonamu.secrets.json");
|
|
177
|
-
if (
|
|
173
|
+
if (!(await exists(configPath))) {
|
|
178
174
|
throw new Error(`Cannot find sonamu.config.json in ${configPath}`);
|
|
179
175
|
}
|
|
180
176
|
this.config = JSON.parse(
|
|
181
|
-
|
|
177
|
+
(await readFile(configPath)).toString()
|
|
182
178
|
) as SonamuConfig;
|
|
183
|
-
if (
|
|
179
|
+
if (await exists(secretsPath)) {
|
|
184
180
|
this.secrets = JSON.parse(
|
|
185
|
-
|
|
181
|
+
(await readFile(secretsPath)).toString()
|
|
186
182
|
) as SonamuSecrets;
|
|
187
183
|
}
|
|
188
184
|
|
|
189
185
|
// DB 로드
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
this.dbConfig = DB.fullConfig;
|
|
186
|
+
this.dbConfig = await DB.readKnexfile();
|
|
187
|
+
!doSilent && console.log(chalk.green("DB Config Loaded!"));
|
|
188
|
+
attachOnDuplicateUpdate();
|
|
194
189
|
|
|
195
190
|
// 테스팅인 경우 엔티티 로드 & 싱크 없이 중단
|
|
196
191
|
if (forTesting) {
|
|
@@ -199,9 +194,11 @@ class SonamuClass {
|
|
|
199
194
|
}
|
|
200
195
|
|
|
201
196
|
// Entity 로드
|
|
197
|
+
const { EntityManager } = await import("../entity/entity-manager");
|
|
202
198
|
await EntityManager.autoload(doSilent);
|
|
203
199
|
|
|
204
200
|
// Syncer
|
|
201
|
+
const { Syncer } = await import("../syncer/syncer");
|
|
205
202
|
this.syncer = new Syncer();
|
|
206
203
|
|
|
207
204
|
// Autoload: Models / Types / APIs
|
|
@@ -212,17 +209,48 @@ class SonamuClass {
|
|
|
212
209
|
if (isLocal() && !isTest() && enableSync) {
|
|
213
210
|
await this.syncer.sync();
|
|
214
211
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
);
|
|
212
|
+
// FIXME: hmr 설정된 경우만 워처 시작
|
|
213
|
+
this.startWatcher();
|
|
214
|
+
|
|
215
|
+
this.syncer.syncUI();
|
|
220
216
|
}
|
|
221
217
|
|
|
222
218
|
this.isInitialized = true;
|
|
223
219
|
!doSilent && console.timeEnd(chalk.cyan("Sonamu.init"));
|
|
224
220
|
}
|
|
225
221
|
|
|
222
|
+
async createServer(
|
|
223
|
+
options: SonamuServerOptions,
|
|
224
|
+
initOptions?: {
|
|
225
|
+
enableSync?: boolean;
|
|
226
|
+
doSilent?: boolean;
|
|
227
|
+
}
|
|
228
|
+
) {
|
|
229
|
+
const server = fastify(options.fastify);
|
|
230
|
+
this.server = server;
|
|
231
|
+
|
|
232
|
+
// Storage 설정 저장
|
|
233
|
+
if (options.storage) {
|
|
234
|
+
this.storage = options.storage;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 플러그인 등록
|
|
238
|
+
if (options.plugins) {
|
|
239
|
+
this.registerPlugins(server, options.plugins);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// API 라우팅 설정
|
|
243
|
+
await this.withFastify(server, options.apiConfig, {
|
|
244
|
+
enableSync: initOptions?.enableSync,
|
|
245
|
+
doSilent: initOptions?.doSilent,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// 서버 시작
|
|
249
|
+
await this.boot(server, options);
|
|
250
|
+
|
|
251
|
+
return server;
|
|
252
|
+
}
|
|
253
|
+
|
|
226
254
|
async withFastify(
|
|
227
255
|
server: FastifyInstance<Server, IncomingMessage, ServerResponse>,
|
|
228
256
|
config: SonamuFastifyConfig,
|
|
@@ -235,6 +263,27 @@ class SonamuClass {
|
|
|
235
263
|
await this.init(options?.doSilent, options?.enableSync);
|
|
236
264
|
}
|
|
237
265
|
|
|
266
|
+
this.server = server;
|
|
267
|
+
|
|
268
|
+
// timezone 설정
|
|
269
|
+
const timezone = this.config.timezone;
|
|
270
|
+
if (timezone) {
|
|
271
|
+
const DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssXXX";
|
|
272
|
+
// ISO 8601 날짜 형식 정규식 (예: 2024-01-15T09:30:00.000Z)
|
|
273
|
+
const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/;
|
|
274
|
+
|
|
275
|
+
server.setReplySerializer((payload) => {
|
|
276
|
+
return JSON.stringify(payload, (_key, value) => {
|
|
277
|
+
if (typeof value === "string" && ISO_DATE_REGEX.test(value)) {
|
|
278
|
+
return formatInTimeZone(new Date(value), timezone, DATE_FORMAT);
|
|
279
|
+
}
|
|
280
|
+
return value;
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
!options?.doSilent &&
|
|
284
|
+
console.log(chalk.green(`Timezone set to ${timezone}`));
|
|
285
|
+
}
|
|
286
|
+
|
|
238
287
|
// 전체 라우팅 리스트
|
|
239
288
|
server.get(
|
|
240
289
|
`${this.config.route.prefix}/routes`,
|
|
@@ -251,107 +300,267 @@ class SonamuClass {
|
|
|
251
300
|
}
|
|
252
301
|
);
|
|
253
302
|
|
|
254
|
-
// API 라우팅
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
303
|
+
// API 라우팅 (로컬HMR 상태와 구분)
|
|
304
|
+
if (isLocal()) {
|
|
305
|
+
server.all("*", (request, reply) => {
|
|
306
|
+
const found = this.syncer.apis.find(
|
|
307
|
+
(api) =>
|
|
308
|
+
this.config.route.prefix + api.path === request.url.split("?")[0] &&
|
|
309
|
+
(api.options.httpMethod ?? "GET") === request.method.toUpperCase()
|
|
310
|
+
);
|
|
311
|
+
if (found) {
|
|
312
|
+
return this.getApiHandler(found, config)(request, reply);
|
|
313
|
+
}
|
|
314
|
+
throw new NotFoundException("존재하지 않는 API 접근입니다.");
|
|
315
|
+
});
|
|
316
|
+
} else {
|
|
317
|
+
this.syncer.apis.map((api) => {
|
|
318
|
+
// model
|
|
319
|
+
if (this.syncer.models[api.modelName] === undefined) {
|
|
320
|
+
throw new Error(`정의되지 않은 모델에 접근 ${api.modelName}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// route
|
|
324
|
+
server.route({
|
|
325
|
+
method: api.options.httpMethod!,
|
|
326
|
+
url: this.config.route.prefix + api.path,
|
|
327
|
+
handler: this.getApiHandler(api, config),
|
|
328
|
+
}); // END server.route
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
getApiHandler(api: ExtendedApi, config: SonamuFastifyConfig) {
|
|
334
|
+
return async (
|
|
335
|
+
request: FastifyRequest,
|
|
336
|
+
reply: FastifyReply
|
|
337
|
+
): Promise<unknown> => {
|
|
338
|
+
(api.options.guards ?? []).every((guard) =>
|
|
339
|
+
config.guardHandler(guard, request, api)
|
|
340
|
+
);
|
|
261
341
|
|
|
262
342
|
// 파라미터 정보로 zod 스키마 빌드
|
|
263
343
|
const ReqType = getZodObjectFromApi(api, this.syncer.types);
|
|
264
344
|
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
} else {
|
|
288
|
-
throw e;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
345
|
+
// request 파싱
|
|
346
|
+
const which = api.options.httpMethod === "GET" ? "query" : "body";
|
|
347
|
+
let reqBody: {
|
|
348
|
+
[key: string]: unknown;
|
|
349
|
+
};
|
|
350
|
+
try {
|
|
351
|
+
reqBody = fastifyCaster(ReqType).parse(request[which] ?? {});
|
|
352
|
+
} catch (e) {
|
|
353
|
+
if (e instanceof ZodError) {
|
|
354
|
+
const messages = humanizeZodError(e)
|
|
355
|
+
.map((issue) => issue.message)
|
|
356
|
+
.join(" ");
|
|
357
|
+
throw new BadRequestException(messages, {
|
|
358
|
+
zodError: e,
|
|
359
|
+
});
|
|
360
|
+
} else {
|
|
361
|
+
throw e;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Content-Type
|
|
366
|
+
reply.type(api.options.contentType ?? "application/json");
|
|
291
367
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (
|
|
298
|
-
try {
|
|
299
|
-
const cacheKeyRes = config.cache.resolveKey(api.path, reqBody);
|
|
300
|
-
if (cacheKeyRes.cache === false) {
|
|
301
|
-
return { cacheKey: null, cachedData: null };
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const cacheKey = cacheKeyRes.key;
|
|
305
|
-
const cacheTtl = cacheKeyRes.ttl;
|
|
306
|
-
const cachedData = await config.cache.get(cacheKey);
|
|
307
|
-
return { cacheKey, cacheTtl, cachedData };
|
|
308
|
-
} catch (e) {
|
|
309
|
-
console.error(e);
|
|
310
|
-
}
|
|
368
|
+
// 캐시
|
|
369
|
+
const { cacheKey, cacheTtl, cachedData } = await (async () => {
|
|
370
|
+
if (config.cache) {
|
|
371
|
+
try {
|
|
372
|
+
const cacheKeyRes = config.cache.resolveKey(api.path, reqBody);
|
|
373
|
+
if (cacheKeyRes.cache === false) {
|
|
311
374
|
return { cacheKey: null, cachedData: null };
|
|
312
375
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
376
|
+
|
|
377
|
+
const cacheKey = cacheKeyRes.key;
|
|
378
|
+
const cacheTtl = cacheKeyRes.ttl;
|
|
379
|
+
const cachedData = await config.cache.get(cacheKey);
|
|
380
|
+
return { cacheKey, cacheTtl, cachedData };
|
|
381
|
+
} catch (e) {
|
|
382
|
+
console.error(e);
|
|
317
383
|
}
|
|
384
|
+
return { cacheKey: null, cachedData: null };
|
|
385
|
+
}
|
|
386
|
+
return { cacheKey: null, cachedData: null };
|
|
387
|
+
})();
|
|
388
|
+
if (cachedData !== null) {
|
|
389
|
+
return cachedData;
|
|
390
|
+
}
|
|
318
391
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
request,
|
|
326
|
-
reply
|
|
327
|
-
);
|
|
328
|
-
return this.asyncLocalStorage.run({ context }, async () => {
|
|
329
|
-
const result = await (model as any)[api.methodName].apply(
|
|
330
|
-
model,
|
|
331
|
-
api.parameters.map((param) => {
|
|
332
|
-
// Context 인젝션
|
|
333
|
-
if (ApiParamType.isContext(param.type)) {
|
|
334
|
-
return context;
|
|
335
|
-
} else {
|
|
336
|
-
return reqBody[param.name];
|
|
337
|
-
}
|
|
338
|
-
})
|
|
339
|
-
);
|
|
340
|
-
reply.type(api.options.contentType ?? "application/json");
|
|
341
|
-
|
|
342
|
-
// 캐시 키 있는 경우 갱신 후 저장
|
|
343
|
-
if (config.cache && cacheKey) {
|
|
344
|
-
await config.cache.put(cacheKey, result, cacheTtl);
|
|
345
|
-
}
|
|
346
|
-
return result;
|
|
347
|
-
});
|
|
392
|
+
// 결과 (AsyncLocalStorage 적용)
|
|
393
|
+
const context = config.contextProvider(
|
|
394
|
+
{
|
|
395
|
+
request,
|
|
396
|
+
reply,
|
|
397
|
+
headers: request.headers,
|
|
348
398
|
},
|
|
349
|
-
|
|
399
|
+
request,
|
|
400
|
+
reply
|
|
401
|
+
);
|
|
402
|
+
const model = this.syncer.models[api.modelName];
|
|
403
|
+
return this.asyncLocalStorage.run({ context }, async () => {
|
|
404
|
+
const result = await (model as any)[api.methodName].apply(
|
|
405
|
+
model,
|
|
406
|
+
api.parameters.map((param) => {
|
|
407
|
+
// Context 인젝션
|
|
408
|
+
if (ApiParamType.isContext(param.type)) {
|
|
409
|
+
return context;
|
|
410
|
+
} else {
|
|
411
|
+
return reqBody[param.name];
|
|
412
|
+
}
|
|
413
|
+
})
|
|
414
|
+
);
|
|
415
|
+
reply.type(api.options.contentType ?? "application/json");
|
|
416
|
+
|
|
417
|
+
// 캐시 키 있는 경우 갱신 후 저장
|
|
418
|
+
if (config.cache && cacheKey) {
|
|
419
|
+
await config.cache.put(cacheKey, result, cacheTtl);
|
|
420
|
+
}
|
|
421
|
+
return result;
|
|
422
|
+
});
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
startWatcher(): void {
|
|
427
|
+
const watchPath = path.join(this.apiRootPath, "src");
|
|
428
|
+
const chokidar = require("chokidar") as typeof import("chokidar");
|
|
429
|
+
this.watcher = chokidar.watch(watchPath, {
|
|
430
|
+
ignored: (path, stats) =>
|
|
431
|
+
(!!stats?.isFile() &&
|
|
432
|
+
!path.endsWith(".ts") &&
|
|
433
|
+
!path.endsWith(".json")) ||
|
|
434
|
+
path.endsWith("src/index.ts"),
|
|
435
|
+
persistent: true,
|
|
436
|
+
ignoreInitial: true,
|
|
350
437
|
});
|
|
438
|
+
this.watcher.on("all", async (event: string, filePath: string) => {
|
|
439
|
+
if (event !== "change" && event !== "add") {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
await this.handleFileChange(event, filePath);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private registerPlugins(
|
|
448
|
+
server: FastifyInstance,
|
|
449
|
+
plugins: SonamuServerOptions["plugins"]
|
|
450
|
+
) {
|
|
451
|
+
if (!plugins) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const pluginsModules = {
|
|
456
|
+
formbody: "@fastify/formbody",
|
|
457
|
+
qs: "fastify-qs",
|
|
458
|
+
cors: "@fastify/cors",
|
|
459
|
+
multipart: "@fastify/multipart",
|
|
460
|
+
} as const;
|
|
461
|
+
|
|
462
|
+
const registerPlugin = <K extends keyof NonNullable<typeof plugins>>(
|
|
463
|
+
key: K,
|
|
464
|
+
pluginName: string
|
|
465
|
+
) => {
|
|
466
|
+
const option = plugins[key];
|
|
467
|
+
if (!option) return;
|
|
468
|
+
|
|
469
|
+
if (option === true) {
|
|
470
|
+
server.register(import(pluginName));
|
|
471
|
+
} else {
|
|
472
|
+
server.register(import(pluginName), option);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
Object.entries(pluginsModules).forEach(([key, pluginName]) => {
|
|
477
|
+
registerPlugin(key as keyof typeof plugins, pluginName);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
if (plugins.custom) {
|
|
481
|
+
plugins.custom(server);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
private async boot(server: FastifyInstance, options: SonamuServerOptions) {
|
|
486
|
+
const port = options.listen?.port ?? 3000;
|
|
487
|
+
const host = options.listen?.host ?? "localhost";
|
|
488
|
+
|
|
489
|
+
server.addHook("onClose", async () => {
|
|
490
|
+
await options.lifecycle?.onShutdown?.(server);
|
|
491
|
+
await this.destroy();
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
const shutdown = async () => {
|
|
495
|
+
try {
|
|
496
|
+
await server.close();
|
|
497
|
+
process.exit(0);
|
|
498
|
+
} catch (err) {
|
|
499
|
+
console.error("Error during shutdown:", err);
|
|
500
|
+
process.exit(1);
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
process.on("SIGINT", shutdown);
|
|
505
|
+
process.on("SIGTERM", shutdown);
|
|
506
|
+
|
|
507
|
+
if (options.lifecycle?.onError) {
|
|
508
|
+
server.setErrorHandler(options.lifecycle?.onError);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
server
|
|
512
|
+
.listen({ port, host })
|
|
513
|
+
.then(async () => {
|
|
514
|
+
await options.lifecycle?.onStart?.(server);
|
|
515
|
+
})
|
|
516
|
+
.catch(async (err) => {
|
|
517
|
+
console.error(chalk.red("Failed to start server:", err));
|
|
518
|
+
await shutdown();
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
private async handleFileChange(
|
|
523
|
+
event: string,
|
|
524
|
+
filePath: string
|
|
525
|
+
): Promise<void> {
|
|
526
|
+
// 첫 번째 파일이면 HMR 시작 시간 기록
|
|
527
|
+
if (this.pendingFiles.length === 0) {
|
|
528
|
+
this.hmrStartTime = Date.now();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
this.pendingFiles.push(filePath);
|
|
532
|
+
|
|
533
|
+
const relativePath = filePath.replace(this.apiRootPath, "api");
|
|
534
|
+
console.log(chalk.bold(`Detected(${event}): ${chalk.blue(relativePath)}`));
|
|
535
|
+
|
|
536
|
+
await this.syncer.syncFromWatcher([filePath]);
|
|
537
|
+
|
|
538
|
+
// 처리 완료된 파일을 대기 목록에서 제거
|
|
539
|
+
this.pendingFiles = this.pendingFiles.slice(1);
|
|
540
|
+
|
|
541
|
+
// 모든 파일 처리가 완료되면 최종 메시지 출력
|
|
542
|
+
if (this.pendingFiles.length === 0) {
|
|
543
|
+
await this.finishHMR();
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
private async finishHMR(): Promise<void> {
|
|
548
|
+
await this.syncer.saveChecksums(await this.syncer.getCurrentChecksums());
|
|
549
|
+
|
|
550
|
+
const endTime = Date.now();
|
|
551
|
+
const totalTime = endTime - this.hmrStartTime;
|
|
552
|
+
const msg = `HMR Done! ${chalk.bold.white(`${totalTime}ms`)}`;
|
|
553
|
+
const margin = Math.max(0, (process.stdout.columns - msg.length) / 2);
|
|
554
|
+
|
|
555
|
+
console.log(
|
|
556
|
+
chalk.black.bgGreen(" ".repeat(margin) + msg + " ".repeat(margin))
|
|
557
|
+
);
|
|
351
558
|
}
|
|
352
559
|
|
|
353
560
|
async destroy(): Promise<void> {
|
|
354
|
-
|
|
561
|
+
const { BaseModel } = require("../database/base-model");
|
|
562
|
+
await BaseModel.destroy();
|
|
563
|
+
await this.watcher?.close();
|
|
355
564
|
}
|
|
356
565
|
}
|
|
357
566
|
export const Sonamu = new SonamuClass();
|