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