sonamu 0.10.12 → 0.10.13
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/dist/ai/agents/agent.d.ts +1 -1
- package/dist/ai/agents/agent.d.ts.map +1 -1
- package/dist/ai/agents/agent.js +5 -6
- package/dist/ai/agents/types.d.ts +1 -1
- package/dist/ai/agents/types.d.ts.map +1 -1
- package/dist/ai/providers/rtzr/api.js +18 -18
- package/dist/ai/providers/rtzr/error.d.ts.map +1 -1
- package/dist/ai/providers/rtzr/error.js +17 -15
- package/dist/ai/providers/rtzr/model.d.ts.map +1 -1
- package/dist/ai/providers/rtzr/model.js +3 -2
- package/dist/ai/providers/rtzr/options.d.ts.map +1 -1
- package/dist/ai/providers/rtzr/options.js +16 -16
- package/dist/ai/providers/rtzr/provider.d.ts.map +1 -1
- package/dist/ai/providers/rtzr/provider.js +9 -9
- package/dist/ai/providers/rtzr/utils.d.ts.map +1 -1
- package/dist/ai/providers/rtzr/utils.js +1 -1
- package/dist/api/caster.d.ts.map +1 -1
- package/dist/api/caster.js +17 -17
- package/dist/api/code-converters.d.ts +4 -2
- package/dist/api/code-converters.d.ts.map +1 -1
- package/dist/api/code-converters.js +100 -19
- package/dist/api/config.d.ts +16 -1
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +28 -21
- package/dist/api/context.d.ts +2 -2
- package/dist/api/context.d.ts.map +1 -1
- package/dist/api/context.js +1 -1
- package/dist/api/decorators.d.ts.map +1 -1
- package/dist/api/decorators.js +18 -11
- package/dist/api/http-validator.d.ts +36 -0
- package/dist/api/http-validator.d.ts.map +1 -0
- package/dist/api/http-validator.js +72 -0
- package/dist/api/index.js +6 -4
- package/dist/api/sonamu.d.ts +59 -20
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +250 -106
- package/dist/api/websocket-helpers.d.ts +7 -5
- package/dist/api/websocket-helpers.d.ts.map +1 -1
- package/dist/api/websocket-helpers.js +6 -6
- package/dist/auth/audit-log/builders.d.ts.map +1 -1
- package/dist/auth/audit-log/builders.js +1 -1
- package/dist/auth/audit-log/client-ip.d.ts +1 -1
- package/dist/auth/audit-log/client-ip.d.ts.map +1 -1
- package/dist/auth/audit-log/client-ip.js +4 -2
- package/dist/auth/audit-log/events.d.ts +5 -1
- package/dist/auth/audit-log/events.d.ts.map +1 -1
- package/dist/auth/audit-log/events.js +1 -1
- package/dist/auth/audit-log/plugin.d.ts +0 -8
- package/dist/auth/audit-log/plugin.d.ts.map +1 -1
- package/dist/auth/audit-log/plugin.js +48 -52
- package/dist/auth/audit-log-ingestor.d.ts.map +1 -1
- package/dist/auth/audit-log-ingestor.js +16 -12
- package/dist/auth/auth-generator.d.ts.map +1 -1
- package/dist/auth/auth-generator.js +1 -1
- package/dist/auth/better-auth-request.d.ts.map +1 -1
- package/dist/auth/better-auth-request.js +6 -5
- package/dist/auth/knex-adapter.js +1 -1
- package/dist/auth/plugins/entity-definitions/index.d.ts +13 -1
- package/dist/auth/plugins/entity-definitions/index.d.ts.map +1 -1
- package/dist/auth/plugins/entity-definitions/index.js +1 -1
- package/dist/auth/plugins/wrappers/admin.d.ts +1 -1
- package/dist/bin/build-config.d.ts +25 -0
- package/dist/bin/build-config.d.ts.map +1 -1
- package/dist/bin/build-config.js +105 -2
- package/dist/bin/cli.js +131 -27
- package/dist/bin/compiler-policy.d.ts +3 -0
- package/dist/bin/compiler-policy.d.ts.map +1 -0
- package/dist/bin/compiler-policy.js +24 -0
- package/dist/bin/fixture.d.ts.map +1 -1
- package/dist/bin/fixture.js +5 -3
- package/dist/bin/test-command.d.ts +22 -1
- package/dist/bin/test-command.d.ts.map +1 -1
- package/dist/bin/test-command.js +18 -10
- package/dist/bin/ts-loader-registration.d.ts +7 -1
- package/dist/bin/ts-loader-registration.d.ts.map +1 -1
- package/dist/bin/ts-loader-registration.js +13 -4
- package/dist/cache/decorator.d.ts.map +1 -1
- package/dist/cache/decorator.js +11 -4
- package/dist/compress/compress.d.ts.map +1 -1
- package/dist/compress/compress.js +1 -1
- package/dist/cone/cone-generator.d.ts +14 -1
- package/dist/cone/cone-generator.d.ts.map +1 -1
- package/dist/cone/cone-generator.js +13 -9
- package/dist/database/_batch_update.js +1 -1
- package/dist/database/base-model.d.ts +22 -10
- package/dist/database/base-model.d.ts.map +1 -1
- package/dist/database/base-model.js +26 -16
- package/dist/database/base-model.types.d.ts +15 -3
- package/dist/database/base-model.types.d.ts.map +1 -1
- package/dist/database/db.d.ts +12 -2
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +10 -7
- package/dist/database/knex.d.ts.map +1 -1
- package/dist/database/knex.js +17 -14
- package/dist/database/puri.d.ts +11 -9
- package/dist/database/puri.d.ts.map +1 -1
- package/dist/database/puri.js +93 -79
- package/dist/database/puri.types.d.ts +21 -21
- package/dist/database/puri.types.d.ts.map +1 -1
- package/dist/database/puri.types.js +1 -1
- package/dist/database/upsert-builder.d.ts +12 -10
- package/dist/database/upsert-builder.d.ts.map +1 -1
- package/dist/database/upsert-builder.js +20 -12
- package/dist/dict/sd.d.ts.map +1 -1
- package/dist/dict/sd.js +1 -1
- package/dist/dict/sonamu-dictionary.d.ts.map +1 -1
- package/dist/dict/sonamu-dictionary.js +1 -1
- package/dist/dict/utils.d.ts.map +1 -1
- package/dist/dict/utils.js +7 -2
- package/dist/entity/entity-manager.d.ts +13 -4
- package/dist/entity/entity-manager.d.ts.map +1 -1
- package/dist/entity/entity-manager.js +19 -11
- package/dist/entity/entity-template-cone.d.ts +73 -5
- package/dist/entity/entity-template-cone.d.ts.map +1 -1
- package/dist/entity/entity-template-cone.js +4 -2
- package/dist/entity/entity.d.ts +6 -2
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +30 -28
- package/dist/env.d.ts +3 -1
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +1 -1
- package/dist/exceptions/error-handler.d.ts.map +1 -1
- package/dist/exceptions/error-handler.js +7 -2
- package/dist/exceptions/so-exceptions.d.ts +24 -20
- package/dist/exceptions/so-exceptions.d.ts.map +1 -1
- package/dist/exceptions/so-exceptions.js +4 -2
- package/dist/filter/utils.d.ts +7 -2
- package/dist/filter/utils.d.ts.map +1 -1
- package/dist/filter/utils.js +8 -6
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -6
- package/dist/logger/configure.d.ts.map +1 -1
- package/dist/logger/configure.js +10 -10
- package/dist/migration/code-generation.d.ts.map +1 -1
- package/dist/migration/code-generation.js +50 -46
- package/dist/migration/index-where-predicate.d.ts.map +1 -1
- package/dist/migration/index-where-predicate.js +22 -8
- package/dist/migration/migration-set.d.ts.map +1 -1
- package/dist/migration/migration-set.js +1 -1
- package/dist/migration/migrator.d.ts +3 -2
- package/dist/migration/migrator.d.ts.map +1 -1
- package/dist/migration/migrator.js +27 -21
- package/dist/migration/postgresql-schema-reader.d.ts.map +1 -1
- package/dist/migration/postgresql-schema-reader.js +51 -36
- package/dist/migration/slack-confirm.d.ts.map +1 -1
- package/dist/migration/slack-confirm.js +1 -1
- package/dist/naite/naite-reporter.js +2 -2
- package/dist/naite/naite.d.ts +74 -15
- package/dist/naite/naite.d.ts.map +1 -1
- package/dist/naite/naite.js +5 -19
- package/dist/runtime-type.d.ts +6 -0
- package/dist/runtime-type.d.ts.map +1 -0
- package/dist/runtime-type.js +26 -0
- package/dist/ssr/hmr-opt-out.d.ts +26 -0
- package/dist/ssr/hmr-opt-out.d.ts.map +1 -0
- package/dist/ssr/hmr-opt-out.js +59 -0
- package/dist/ssr/renderer.d.ts.map +1 -1
- package/dist/ssr/renderer.js +4 -1
- package/dist/storage/base-file.js +14 -14
- package/dist/storage/buffered-file.d.ts +2 -2
- package/dist/storage/buffered-file.d.ts.map +1 -1
- package/dist/storage/buffered-file.js +12 -12
- package/dist/storage/s3-driver.d.ts.map +1 -1
- package/dist/storage/s3-driver.js +1 -1
- package/dist/storage/uploaded-file.d.ts +2 -2
- package/dist/storage/uploaded-file.d.ts.map +1 -1
- package/dist/storage/uploaded-file.js +8 -8
- package/dist/stream/sse.js +11 -11
- package/dist/stream/ws-core.d.ts +1 -1
- package/dist/stream/ws-core.d.ts.map +1 -1
- package/dist/stream/ws-delivery.d.ts +1 -1
- package/dist/stream/ws-delivery.d.ts.map +1 -1
- package/dist/stream/ws-delivery.js +6 -4
- package/dist/stream/ws-local-connection-store.d.ts.map +1 -1
- package/dist/stream/ws-local-connection-store.js +4 -2
- package/dist/stream/ws-presence-store.js +1 -1
- package/dist/stream/ws-registry.d.ts +4 -4
- package/dist/stream/ws-registry.d.ts.map +1 -1
- package/dist/stream/ws-registry.js +4 -2
- package/dist/stream/ws-telemetry.d.ts +16 -12
- package/dist/stream/ws-telemetry.d.ts.map +1 -1
- package/dist/stream/ws-telemetry.js +22 -18
- package/dist/stream/ws.d.ts +18 -12
- package/dist/stream/ws.d.ts.map +1 -1
- package/dist/stream/ws.js +26 -22
- package/dist/syncer/api-parser.js +1 -1
- package/dist/syncer/checksum.d.ts.map +1 -1
- package/dist/syncer/checksum.js +7 -8
- package/dist/syncer/code-generator.d.ts.map +1 -1
- package/dist/syncer/code-generator.js +13 -14
- package/dist/syncer/entity-operations.d.ts.map +1 -1
- package/dist/syncer/entity-operations.js +5 -6
- package/dist/syncer/file-copy.d.ts +5 -0
- package/dist/syncer/file-copy.d.ts.map +1 -0
- package/dist/syncer/file-copy.js +36 -0
- package/dist/syncer/file-patterns.d.ts +1 -0
- package/dist/syncer/file-patterns.d.ts.map +1 -1
- package/dist/syncer/file-patterns.js +5 -2
- package/dist/syncer/file-tracking.d.ts.map +1 -1
- package/dist/syncer/file-tracking.js +5 -4
- package/dist/syncer/filesystem-dependencies.d.ts +15 -0
- package/dist/syncer/filesystem-dependencies.d.ts.map +1 -0
- package/dist/syncer/filesystem-dependencies.js +53 -0
- package/dist/syncer/module-loader.d.ts +1 -1
- package/dist/syncer/module-loader.d.ts.map +1 -1
- package/dist/syncer/module-loader.js +6 -4
- package/dist/syncer/syncer-actions.d.ts +4 -0
- package/dist/syncer/syncer-actions.d.ts.map +1 -1
- package/dist/syncer/syncer-actions.js +34 -17
- package/dist/syncer/syncer.d.ts +17 -6
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +71 -29
- package/dist/syncer/watcher.d.ts.map +1 -1
- package/dist/syncer/watcher.js +3 -3
- package/dist/tasks/decorator.d.ts +6 -6
- package/dist/tasks/decorator.d.ts.map +1 -1
- package/dist/tasks/decorator.js +4 -4
- package/dist/tasks/step-wrapper.d.ts +6 -4
- package/dist/tasks/step-wrapper.d.ts.map +1 -1
- package/dist/tasks/step-wrapper.js +5 -5
- package/dist/tasks/workflow-manager.d.ts.map +1 -1
- package/dist/tasks/workflow-manager.js +13 -7
- 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 +2 -2
- package/dist/template/helpers.d.ts +8 -6
- package/dist/template/helpers.d.ts.map +1 -1
- package/dist/template/helpers.js +1 -1
- package/dist/template/implementations/generated.template.d.ts.map +1 -1
- package/dist/template/implementations/generated.template.js +4 -4
- package/dist/template/implementations/generated_http.template.d.ts +7 -4
- package/dist/template/implementations/generated_http.template.d.ts.map +1 -1
- package/dist/template/implementations/generated_http.template.js +21 -21
- package/dist/template/implementations/generated_sso.template.d.ts.map +1 -1
- package/dist/template/implementations/generated_sso.template.js +6 -3
- package/dist/template/implementations/http_validators.template.d.ts +18 -0
- package/dist/template/implementations/http_validators.template.d.ts.map +1 -0
- package/dist/template/implementations/http_validators.template.js +127 -0
- package/dist/template/implementations/model.template.d.ts.map +1 -1
- package/dist/template/implementations/model.template.js +1 -1
- package/dist/template/implementations/queries.template.js +1 -1
- package/dist/template/implementations/sd.template.d.ts.map +1 -1
- package/dist/template/implementations/sd.template.js +79 -39
- package/dist/template/implementations/services.template.d.ts.map +1 -1
- package/dist/template/implementations/services.template.js +6 -4
- package/dist/template/implementations/view_form.template.d.ts +6 -1
- package/dist/template/implementations/view_form.template.d.ts.map +1 -1
- package/dist/template/implementations/view_form.template.js +23 -15
- package/dist/template/implementations/view_list.template.d.ts.map +1 -1
- package/dist/template/implementations/view_list.template.js +11 -6
- package/dist/template/template-manager.d.ts.map +1 -1
- package/dist/template/template-manager.js +9 -7
- package/dist/template/template-types.d.ts +12 -5
- package/dist/template/template-types.d.ts.map +1 -1
- package/dist/template/template-types.js +1 -1
- package/dist/template/template.d.ts +2 -2
- package/dist/template/template.d.ts.map +1 -1
- package/dist/template/template.js +6 -4
- package/dist/template/zod-converter.d.ts +0 -19
- package/dist/template/zod-converter.d.ts.map +1 -1
- package/dist/template/zod-converter.js +100 -84
- package/dist/testing/_relation-graph.d.ts.map +1 -1
- package/dist/testing/_relation-graph.js +2 -1
- package/dist/testing/bootstrap.d.ts.map +1 -1
- package/dist/testing/bootstrap.js +9 -6
- package/dist/testing/data-explorer.d.ts +32 -7
- package/dist/testing/data-explorer.d.ts.map +1 -1
- package/dist/testing/data-explorer.js +77 -31
- package/dist/testing/dev-test-routes.d.ts +8 -1
- package/dist/testing/dev-test-routes.d.ts.map +1 -1
- package/dist/testing/dev-test-routes.js +125 -108
- package/dist/testing/dev-vitest-manager.d.ts +54 -8
- package/dist/testing/dev-vitest-manager.d.ts.map +1 -1
- package/dist/testing/dev-vitest-manager.js +24 -9
- package/dist/testing/faker-mappings.d.ts +412 -1
- package/dist/testing/faker-mappings.d.ts.map +1 -1
- package/dist/testing/faker-mappings.js +1 -1
- package/dist/testing/filesystem.d.ts +2 -0
- package/dist/testing/filesystem.d.ts.map +1 -0
- package/dist/testing/filesystem.js +7 -0
- package/dist/testing/fixture-generator.d.ts +25 -8
- package/dist/testing/fixture-generator.d.ts.map +1 -1
- package/dist/testing/fixture-generator.js +132 -73
- package/dist/testing/fixture-loader.d.ts +3 -1
- package/dist/testing/fixture-loader.d.ts.map +1 -1
- package/dist/testing/fixture-loader.js +1 -20
- package/dist/testing/fixture-manager.d.ts +8 -7
- package/dist/testing/fixture-manager.d.ts.map +1 -1
- package/dist/testing/fixture-manager.js +91 -59
- package/dist/testing/global-setup.d.ts.map +1 -1
- package/dist/testing/global-setup.js +5 -2
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +6 -3
- package/dist/types/types.d.ts +101 -99
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +234 -225
- package/dist/ui/ai-api.d.ts.map +1 -1
- package/dist/ui/ai-api.js +1 -1
- package/dist/ui/ai-client.d.ts.map +1 -1
- package/dist/ui/ai-client.js +28 -18
- package/dist/ui/api.d.ts.map +1 -1
- package/dist/ui/api.js +135 -128
- package/dist/ui/async-route.d.ts +3 -0
- package/dist/ui/async-route.d.ts.map +1 -0
- package/dist/ui/async-route.js +12 -0
- package/dist/ui/cdd-service.d.ts +9 -7
- package/dist/ui/cdd-service.d.ts.map +1 -1
- package/dist/ui/cdd-service.js +7 -5
- package/dist/ui/migrations-api.d.ts.map +1 -1
- package/dist/ui/migrations-api.js +17 -15
- package/dist/ui-web/assets/index-DV1AAq6W.js +178 -0
- package/dist/ui-web/index.html +1 -1
- package/dist/utils/formatter.d.ts.map +1 -1
- package/dist/utils/formatter.js +7 -5
- package/dist/utils/fs-utils.d.ts +3 -3
- package/dist/utils/fs-utils.d.ts.map +1 -1
- package/dist/utils/fs-utils.js +6 -6
- package/dist/utils/model.d.ts +3 -5
- package/dist/utils/model.d.ts.map +1 -1
- package/dist/utils/model.js +1 -1
- package/dist/utils/object-utils.d.ts +1 -1
- package/dist/utils/object-utils.d.ts.map +1 -1
- package/dist/utils/object-utils.js +17 -16
- package/dist/utils/process-utils.d.ts.map +1 -1
- package/dist/utils/process-utils.js +4 -2
- package/dist/utils/runtime-value.d.ts +13 -0
- package/dist/utils/runtime-value.d.ts.map +1 -0
- package/dist/utils/runtime-value.js +40 -0
- package/dist/utils/sql-parser.d.ts +8 -1
- package/dist/utils/sql-parser.d.ts.map +1 -1
- package/dist/utils/sql-parser.js +6 -3
- package/dist/utils/type-utils.d.ts.map +1 -1
- package/dist/utils/type-utils.js +10 -8
- package/dist/utils/utils.d.ts +2 -2
- package/dist/utils/utils.d.ts.map +1 -1
- package/dist/utils/utils.js +11 -8
- package/dist/utils/zod-error.d.ts.map +1 -1
- package/dist/utils/zod-error.js +7 -4
- package/dist/vector/chunking.d.ts.map +1 -1
- package/dist/vector/chunking.js +1 -1
- package/dist/vector/embedding.d.ts.map +1 -1
- package/dist/vector/embedding.js +1 -1
- package/dist/vector/types.d.ts +3 -2
- package/dist/vector/types.d.ts.map +1 -1
- package/package.json +5 -3
- package/src/ai/agents/agent.ts +5 -5
- package/src/ai/agents/types.ts +1 -1
- package/src/ai/providers/rtzr/__tests__/model.test.ts +2 -2
- package/src/ai/providers/rtzr/error.ts +5 -3
- package/src/ai/providers/rtzr/model.ts +3 -2
- package/src/ai/providers/rtzr/options.ts +10 -5
- package/src/ai/providers/rtzr/provider.ts +10 -10
- package/src/ai/providers/rtzr/utils.ts +3 -1
- package/src/api/__tests__/config.test.ts +11 -21
- package/src/api/__tests__/sonamu.http-validator.test.ts +512 -0
- package/src/api/__tests__/sonamu.websocket.test.ts +51 -41
- package/src/api/__tests__/zod-compiler-policy.test.ts +95 -0
- package/src/api/caster.ts +6 -8
- package/src/api/code-converters.ts +136 -36
- package/src/api/config.ts +62 -4
- package/src/api/context.ts +3 -3
- package/src/api/decorators.ts +28 -17
- package/src/api/http-validator.ts +149 -0
- package/src/api/sonamu.ts +459 -152
- package/src/api/websocket-helpers.ts +24 -17
- package/src/auth/audit-log/builders.ts +3 -2
- package/src/auth/audit-log/client-ip.ts +4 -2
- package/src/auth/audit-log/events.ts +15 -1
- package/src/auth/audit-log/plugin.ts +129 -200
- package/src/auth/audit-log-ingestor.ts +19 -15
- package/src/auth/auth-generator.ts +10 -5
- package/src/auth/better-auth-request.ts +4 -3
- package/src/auth/knex-adapter.ts +8 -2
- package/src/auth/plugins/entity-definitions/index.ts +6 -5
- package/src/bin/__tests__/http-validator-benchmark.test.ts +94 -0
- package/src/bin/__tests__/test-command.test.ts +9 -15
- package/src/bin/__tests__/ts-loader-register.test.ts +15 -17
- package/src/bin/__tests__/zod-compiler-build.test.ts +307 -0
- package/src/bin/build-config.ts +167 -0
- package/src/bin/cli.ts +153 -36
- package/src/bin/compiler-policy.ts +35 -0
- package/src/bin/fixture.ts +14 -3
- package/src/bin/test-command.ts +74 -29
- package/src/bin/ts-loader-registration.ts +22 -4
- package/src/cache/decorator.ts +10 -3
- package/src/compress/compress.ts +17 -4
- package/src/cone/cone-generator.ts +34 -6
- package/src/database/__tests__/db.test.ts +1 -0
- package/src/database/__tests__/puri.test.ts +16 -15
- package/src/database/__tests__/transaction-scope.test.ts +5 -0
- package/src/database/_batch_update.ts +1 -0
- package/src/database/base-model.ts +81 -50
- package/src/database/base-model.types.ts +6 -6
- package/src/database/db.ts +29 -19
- package/src/database/knex.ts +30 -17
- package/src/database/puri-subset.test-d.ts +17 -0
- package/src/database/puri.ts +181 -143
- package/src/database/puri.types.test-d.ts +31 -1
- package/src/database/puri.types.ts +23 -21
- package/src/database/upsert-builder.ts +75 -53
- package/src/dict/sd.ts +15 -5
- package/src/dict/sonamu-dictionary.ts +6 -6
- package/src/dict/utils.ts +9 -1
- package/src/entity/entity-manager.ts +26 -9
- package/src/entity/entity-template-cone.ts +5 -10
- package/src/entity/entity.ts +50 -34
- package/src/env.ts +7 -3
- package/src/exceptions/error-handler.ts +17 -2
- package/src/exceptions/so-exceptions.ts +26 -20
- package/src/filter/utils.ts +21 -11
- package/src/index.ts +3 -0
- package/src/logger/configure.ts +26 -18
- package/src/migration/__tests__/migrator.test.ts +50 -57
- package/src/migration/code-generation.ts +132 -104
- package/src/migration/index-where-predicate.ts +51 -24
- package/src/migration/migration-set.ts +11 -5
- package/src/migration/migrator.ts +68 -41
- package/src/migration/postgresql-schema-reader.ts +52 -47
- package/src/migration/slack-confirm.ts +17 -15
- package/src/naite/naite-reporter.ts +1 -1
- package/src/naite/naite.ts +97 -24
- package/src/runtime-type.ts +34 -0
- package/src/ssr/hmr-opt-out.ts +78 -0
- package/src/ssr/renderer.ts +6 -0
- package/src/storage/base-file.ts +13 -13
- package/src/storage/buffered-file.ts +11 -11
- package/src/storage/s3-driver.ts +4 -1
- package/src/storage/uploaded-file.ts +8 -8
- package/src/stream/__tests__/ws-contracts.test.ts +4 -2
- package/src/stream/__tests__/ws-telemetry.test.ts +1 -1
- package/src/stream/__tests__/ws.test.ts +30 -6
- package/src/stream/sse.ts +12 -11
- package/src/stream/ws-core.ts +1 -1
- package/src/stream/ws-delivery.ts +16 -7
- package/src/stream/ws-local-connection-store.ts +2 -3
- package/src/stream/ws-presence-store.ts +6 -1
- package/src/stream/ws-registry.ts +16 -7
- package/src/stream/ws-telemetry.ts +66 -43
- package/src/stream/ws.ts +73 -39
- package/src/syncer/__tests__/api-parser.websocket-type-ref.test.ts +3 -2
- package/src/syncer/__tests__/http-validator-registry.test.ts +137 -0
- package/src/syncer/api-parser.ts +67 -27
- package/src/syncer/checksum.ts +17 -11
- package/src/syncer/code-generator.ts +25 -17
- package/src/syncer/entity-operations.ts +3 -5
- package/src/syncer/file-copy.ts +46 -0
- package/src/syncer/file-patterns.ts +11 -8
- package/src/syncer/file-tracking.ts +3 -4
- package/src/syncer/filesystem-dependencies.ts +73 -0
- package/src/syncer/module-loader.ts +8 -4
- package/src/syncer/syncer-actions.ts +56 -18
- package/src/syncer/syncer.ts +116 -43
- package/src/syncer/watcher.ts +8 -4
- package/src/tasks/decorator.ts +20 -12
- package/src/tasks/step-wrapper.ts +19 -9
- package/src/tasks/workflow-manager.ts +15 -9
- package/src/template/__tests__/http_validators.template.test.ts +160 -0
- package/src/template/__tests__/services.template.mutation.test.ts +24 -23
- package/src/template/__tests__/services.template.websocket.test.ts +21 -25
- package/src/template/entity-converter.ts +5 -2
- package/src/template/helpers.ts +8 -9
- package/src/template/implementations/generated.template.ts +5 -4
- package/src/template/implementations/generated_http.template.ts +61 -15
- package/src/template/implementations/generated_sso.template.ts +15 -4
- package/src/template/implementations/http_validators.template.ts +175 -0
- package/src/template/implementations/model.template.ts +3 -1
- package/src/template/implementations/sd.template.ts +81 -41
- package/src/template/implementations/services.template.ts +2 -2
- package/src/template/implementations/view_form.template.ts +56 -39
- package/src/template/implementations/view_list.template.ts +31 -23
- package/src/template/template-manager.ts +15 -13
- package/src/template/template-types.ts +13 -5
- package/src/template/template.ts +4 -3
- package/src/template/zod-converter.ts +206 -91
- package/src/testing/__tests__/dev-test-routes.test.ts +85 -25
- package/src/testing/__tests__/dev-vitest-manager.test.ts +15 -15
- package/src/testing/_relation-graph.ts +4 -1
- package/src/testing/bootstrap.ts +12 -5
- package/src/testing/data-explorer.ts +102 -53
- package/src/testing/dev-test-routes.ts +87 -50
- package/src/testing/dev-vitest-manager.ts +88 -32
- package/src/testing/faker-mappings.ts +2 -2
- package/src/testing/filesystem.ts +1 -0
- package/src/testing/fixture-generator.ts +191 -131
- package/src/testing/fixture-loader.ts +5 -1
- package/src/testing/fixture-manager.ts +129 -90
- package/src/testing/global-setup.ts +7 -3
- package/src/testing/index.ts +5 -0
- package/src/types/__tests__/entity-json-schema-search-text.test.ts +9 -3
- package/src/types/types.ts +184 -158
- package/src/ui/ai-api.ts +9 -7
- package/src/ui/ai-client.ts +61 -56
- package/src/ui/api.ts +727 -616
- package/src/ui/async-route.ts +4 -0
- package/src/ui/cdd-service.ts +35 -12
- package/src/ui/migrations-api.ts +63 -51
- package/src/utils/formatter.ts +7 -3
- package/src/utils/fs-utils.ts +5 -5
- package/src/utils/model.ts +4 -5
- package/src/utils/object-utils.ts +32 -22
- package/src/utils/process-utils.ts +4 -1
- package/src/utils/runtime-value.ts +43 -0
- package/src/utils/sql-parser.ts +13 -6
- package/src/utils/type-utils.ts +9 -7
- package/src/utils/utils.ts +19 -21
- package/src/utils/zod-error.ts +4 -2
- package/src/vector/chunking.ts +6 -1
- package/src/vector/embedding.ts +2 -1
- package/src/vector/types.ts +4 -2
- package/tsdown.api.config.ts +5 -2
- package/dist/ui-web/assets/index-C0m1pk6K.js +0 -177
package/src/api/sonamu.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { type IncomingMessage, type Server, type ServerResponse } from "http";
|
|
|
4
4
|
import os from "os";
|
|
5
5
|
import path from "path";
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import "@fastify/websocket";
|
|
8
8
|
import { dispose as logtapeDispose } from "@logtape/logtape";
|
|
9
9
|
import { type Auth, type BetterAuthOptions } from "better-auth";
|
|
10
10
|
import chalk from "chalk";
|
|
@@ -27,16 +27,22 @@ import { SD, setSDConfig } from "../dict/sd";
|
|
|
27
27
|
import { type LocalizedString } from "../dict/types";
|
|
28
28
|
import { getSonamuEnvironment, readAllEnvironmentSnapshots } from "../env";
|
|
29
29
|
import { NotFoundException } from "../exceptions/so-exceptions";
|
|
30
|
+
import { runtimeTypeOf } from "../runtime-type";
|
|
30
31
|
import { BufferedFile } from "../storage/buffered-file";
|
|
31
32
|
import { type StorageManager } from "../storage/storage-manager";
|
|
32
33
|
import { type KeyGenerator } from "../storage/types";
|
|
33
34
|
import { UploadedFile } from "../storage/uploaded-file";
|
|
34
35
|
import { createMockSSEFactory } from "../stream/sse";
|
|
35
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
type AnyWebSocketConnection,
|
|
38
|
+
WebSocketRuntime,
|
|
39
|
+
type WebSocketConnection,
|
|
40
|
+
} from "../stream/ws";
|
|
36
41
|
import {
|
|
37
42
|
type TelemetryContextProvider,
|
|
38
43
|
type WebSocketTelemetryConnectionContext,
|
|
39
44
|
} from "../stream/ws-telemetry";
|
|
45
|
+
import { type LoadedModels } from "../syncer/module-loader";
|
|
40
46
|
import { type Syncer } from "../syncer/syncer";
|
|
41
47
|
import { type WorkflowManager } from "../tasks/workflow-manager";
|
|
42
48
|
import { type DevVitestManager } from "../testing/dev-vitest-manager";
|
|
@@ -45,10 +51,30 @@ import { centerText } from "../utils/console-util";
|
|
|
45
51
|
import { isDaemonServer } from "../utils/controller";
|
|
46
52
|
import { exists, fileExists } from "../utils/fs-utils";
|
|
47
53
|
import { type AbsolutePath } from "../utils/path-utils";
|
|
54
|
+
import {
|
|
55
|
+
isBigIntValue,
|
|
56
|
+
isFunctionValue,
|
|
57
|
+
isNumberValue,
|
|
58
|
+
isObjectValue,
|
|
59
|
+
isStringValue,
|
|
60
|
+
isSymbolValue,
|
|
61
|
+
} from "../utils/runtime-value";
|
|
48
62
|
import { convertFastifyHeadersToStandard, merge } from "../utils/utils";
|
|
49
|
-
import {
|
|
63
|
+
import {
|
|
64
|
+
normalizeZodCompilerPolicy,
|
|
65
|
+
type SonamuConfig,
|
|
66
|
+
type SonamuServerOptions,
|
|
67
|
+
type SonamuTaskOptions,
|
|
68
|
+
} from "./config";
|
|
50
69
|
import { type Context, type RuntimeContext, type WebSocketContext } from "./context";
|
|
51
70
|
import { type ExtendedApi } from "./decorators";
|
|
71
|
+
import {
|
|
72
|
+
assertHttpValidatorRegistry,
|
|
73
|
+
createHttpValidator,
|
|
74
|
+
getHttpValidatorRouteKey,
|
|
75
|
+
type HttpValidator,
|
|
76
|
+
type HttpValidatorRegistry,
|
|
77
|
+
} from "./http-validator";
|
|
52
78
|
import { getSecrets } from "./secret";
|
|
53
79
|
import { type SonamuSecrets } from "./secret";
|
|
54
80
|
import {
|
|
@@ -64,22 +90,100 @@ export {
|
|
|
64
90
|
resolveWebSocketPluginOptions,
|
|
65
91
|
} from "./websocket-helpers";
|
|
66
92
|
|
|
93
|
+
interface UploadedRequestFiles {
|
|
94
|
+
bufferedFiles: BufferedFile[];
|
|
95
|
+
uploadedFiles: UploadedFile[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
type ApiArgument =
|
|
99
|
+
| string
|
|
100
|
+
| number
|
|
101
|
+
| boolean
|
|
102
|
+
| bigint
|
|
103
|
+
| symbol
|
|
104
|
+
| CallableFunction
|
|
105
|
+
| object
|
|
106
|
+
| null
|
|
107
|
+
| undefined;
|
|
108
|
+
type ApiMethodResult = ApiArgument | void;
|
|
109
|
+
|
|
110
|
+
function toApiArgument<Value>(value: Value): ApiArgument {
|
|
111
|
+
if (value === null) return null;
|
|
112
|
+
if (value === undefined) return undefined;
|
|
113
|
+
if (value === true) return true;
|
|
114
|
+
if (value === false) return false;
|
|
115
|
+
if (isStringValue(value)) return value;
|
|
116
|
+
if (isNumberValue(value)) return value;
|
|
117
|
+
if (isBigIntValue(value)) return value;
|
|
118
|
+
if (isSymbolValue(value)) return value;
|
|
119
|
+
if (isFunctionValue(value)) return value;
|
|
120
|
+
if (isObjectValue(value)) {
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
throw new Error("지원하지 않는 API 인수 타입입니다.");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function toApiArgumentMap<Value extends object>(value: Value): Map<string, ApiArgument> {
|
|
127
|
+
return new Map(Object.entries(value).map(([key, item]) => [key, toApiArgument(item)]));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function createUnavailableSSE<T extends ZodObject>(_events: T): never {
|
|
131
|
+
throw new Error(
|
|
132
|
+
"createSSE is not available in websocket context. Define websocketContextProvider if your context setup depends on SSE helpers.",
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function printDimSummary(message: string): void {
|
|
137
|
+
console.log(chalk.dim(`✓ ${message}`));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function printGreenSummary(message: string): void {
|
|
141
|
+
console.log(chalk.green(`✓ ${message}`));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface SonamuTestingSnapshot {
|
|
145
|
+
apiRootPath: AbsolutePath | null;
|
|
146
|
+
config: SonamuConfig | null;
|
|
147
|
+
syncer: Syncer | null;
|
|
148
|
+
httpValidators: WeakMap<ExtendedApi, HttpValidator>;
|
|
149
|
+
pendingHttpValidators: WeakMap<ExtendedApi, Promise<HttpValidator>>;
|
|
150
|
+
aotHttpValidatorRegistry: HttpValidatorRegistry | undefined;
|
|
151
|
+
preparedApis: readonly ExtendedApi[];
|
|
152
|
+
preparedModels: LoadedModels;
|
|
153
|
+
websocketRuntime: WebSocketRuntime | null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface SonamuHttpValidatorDependencies {
|
|
157
|
+
createValidator: typeof createHttpValidator;
|
|
158
|
+
assertRegistry: typeof assertHttpValidatorRegistry;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const defaultSonamuHttpValidatorDependencies: SonamuHttpValidatorDependencies = {
|
|
162
|
+
createValidator: createHttpValidator,
|
|
163
|
+
assertRegistry: assertHttpValidatorRegistry,
|
|
164
|
+
};
|
|
165
|
+
|
|
67
166
|
class SonamuClass {
|
|
68
167
|
public isInitialized: boolean = false;
|
|
69
168
|
public forTesting: boolean = false;
|
|
70
169
|
public asyncLocalStorage: AsyncLocalStorage<{
|
|
71
170
|
context: RuntimeContext;
|
|
72
171
|
}> = new AsyncLocalStorage();
|
|
172
|
+
private httpValidators: WeakMap<ExtendedApi, HttpValidator> = new WeakMap();
|
|
173
|
+
private pendingHttpValidators: WeakMap<ExtendedApi, Promise<HttpValidator>> = new WeakMap();
|
|
174
|
+
private aotHttpValidatorRegistry: HttpValidatorRegistry | undefined;
|
|
175
|
+
private preparedApis: readonly ExtendedApi[] = [];
|
|
176
|
+
private preparedModels: LoadedModels = {};
|
|
73
177
|
|
|
74
178
|
public getContext<T extends RuntimeContext = Context>(): T {
|
|
75
179
|
const store = this.asyncLocalStorage.getStore();
|
|
76
180
|
if (store?.context) {
|
|
77
|
-
return store.context as T;
|
|
181
|
+
return /* SAFETY: API 데코레이터와 Zod 검증기 등록 계약이 이 값의 타입을 보장한다. */ store.context as T;
|
|
78
182
|
}
|
|
79
183
|
|
|
80
184
|
if (process.env.NODE_ENV === "test") {
|
|
81
185
|
// 테스팅 환경에서 컨텍스트가 주입되지 않은 경우 빈 컨텍스트 리턴
|
|
82
|
-
|
|
186
|
+
const mockContext = {
|
|
83
187
|
transport: "http",
|
|
84
188
|
request: null,
|
|
85
189
|
reply: null,
|
|
@@ -89,122 +193,151 @@ class SonamuClass {
|
|
|
89
193
|
user: null,
|
|
90
194
|
session: null,
|
|
91
195
|
naiteStore: new Map<string, any>(),
|
|
92
|
-
}
|
|
196
|
+
};
|
|
197
|
+
// SAFETY: 테스트 기본 컨텍스트는 HTTP Context이며 기본 제네릭과 일치한다.
|
|
198
|
+
return /* SAFETY: 테스트 기본 컨텍스트는 요청 범위가 없는 HTTP 컨텍스트 대체값입니다. */ mockContext as T &
|
|
199
|
+
typeof mockContext;
|
|
93
200
|
} else {
|
|
94
201
|
throw new Error("Sonamu cannot find context");
|
|
95
202
|
}
|
|
96
203
|
}
|
|
97
204
|
|
|
98
|
-
private
|
|
205
|
+
private apiRootPathValue: AbsolutePath | null = null;
|
|
99
206
|
set apiRootPath(apiRootPath: AbsolutePath) {
|
|
100
|
-
this.
|
|
207
|
+
this.apiRootPathValue = apiRootPath;
|
|
101
208
|
}
|
|
102
209
|
get apiRootPath(): AbsolutePath {
|
|
103
|
-
if (this.
|
|
210
|
+
if (this.apiRootPathValue === null) {
|
|
104
211
|
throw new Error("Sonamu has not been initialized");
|
|
105
212
|
}
|
|
106
|
-
return this.
|
|
213
|
+
return this.apiRootPathValue;
|
|
107
214
|
}
|
|
108
215
|
get appRootPath(): string {
|
|
109
216
|
return this.apiRootPath.split(path.sep).slice(0, -1).join(path.sep);
|
|
110
217
|
}
|
|
111
218
|
|
|
112
|
-
private
|
|
219
|
+
private dbConfigValue: SonamuDBConfig | null = null;
|
|
113
220
|
set dbConfig(dbConfig: SonamuDBConfig) {
|
|
114
|
-
this.
|
|
221
|
+
this.dbConfigValue = dbConfig;
|
|
115
222
|
}
|
|
116
223
|
get dbConfig(): SonamuDBConfig {
|
|
117
|
-
if (this.
|
|
224
|
+
if (this.dbConfigValue === null) {
|
|
118
225
|
throw new Error("Sonamu has not been initialized");
|
|
119
226
|
}
|
|
120
|
-
return this.
|
|
227
|
+
return this.dbConfigValue;
|
|
121
228
|
}
|
|
122
229
|
|
|
123
|
-
private
|
|
230
|
+
private syncerValue: Syncer | null = null;
|
|
124
231
|
set syncer(syncer: Syncer) {
|
|
125
|
-
this.
|
|
232
|
+
this.syncerValue = syncer;
|
|
126
233
|
}
|
|
127
234
|
get syncer(): Syncer {
|
|
128
|
-
if (this.
|
|
235
|
+
if (this.syncerValue === null) {
|
|
129
236
|
throw new Error("Sonamu has not been initialized");
|
|
130
237
|
}
|
|
131
|
-
return this.
|
|
238
|
+
return this.syncerValue;
|
|
132
239
|
}
|
|
133
240
|
|
|
134
|
-
private
|
|
241
|
+
private configValue: SonamuConfig | null = null;
|
|
135
242
|
set config(config: SonamuConfig) {
|
|
136
|
-
this.
|
|
243
|
+
this.configValue = config;
|
|
137
244
|
}
|
|
138
245
|
get config(): SonamuConfig {
|
|
139
|
-
if (this.
|
|
246
|
+
if (this.configValue === null) {
|
|
140
247
|
throw new Error("Sonamu has not been initialized");
|
|
141
248
|
}
|
|
142
|
-
return this.
|
|
249
|
+
return this.configValue;
|
|
143
250
|
}
|
|
144
251
|
|
|
145
252
|
public readonly secrets: SonamuSecrets = getSecrets();
|
|
146
253
|
|
|
147
|
-
private
|
|
254
|
+
private storageValue: StorageManager | null = null;
|
|
148
255
|
/**
|
|
149
256
|
* StorageManager 인스턴스
|
|
150
257
|
*/
|
|
151
258
|
get storage(): StorageManager {
|
|
152
|
-
if (!this.
|
|
259
|
+
if (!this.storageValue) {
|
|
153
260
|
throw new Error("Storage has not been initialized. Check storage config.");
|
|
154
261
|
}
|
|
155
|
-
return this.
|
|
262
|
+
return this.storageValue;
|
|
156
263
|
}
|
|
157
264
|
|
|
158
|
-
private
|
|
265
|
+
private cacheValue: CacheManager | null = null;
|
|
159
266
|
/**
|
|
160
267
|
* CacheManager 인스턴스 (BentoCache)
|
|
161
268
|
*/
|
|
162
269
|
get cache(): CacheManager {
|
|
163
|
-
if (!this.
|
|
270
|
+
if (!this.cacheValue) {
|
|
164
271
|
throw new Error("Cache has not been initialized. Check cache config in sonamu.config.ts.");
|
|
165
272
|
}
|
|
166
|
-
return this.
|
|
273
|
+
return this.cacheValue;
|
|
167
274
|
}
|
|
168
275
|
|
|
169
|
-
private
|
|
276
|
+
private workflowsValue: WorkflowManager | null = null;
|
|
170
277
|
get workflows(): WorkflowManager {
|
|
171
|
-
if (this.
|
|
278
|
+
if (this.workflowsValue === null) {
|
|
172
279
|
throw new Error("Sonamu has not been initialized");
|
|
173
280
|
}
|
|
174
281
|
|
|
175
|
-
return this.
|
|
282
|
+
return this.workflowsValue;
|
|
176
283
|
}
|
|
177
284
|
|
|
178
|
-
private
|
|
285
|
+
private authValue: Auth<BetterAuthOptions> | null = null;
|
|
179
286
|
get auth(): Auth<BetterAuthOptions> {
|
|
180
|
-
if (!this.
|
|
287
|
+
if (!this.authValue) {
|
|
181
288
|
throw new Error("Auth has not been initialized. Check auth config in sonamu.config.ts.");
|
|
182
289
|
}
|
|
183
|
-
return this.
|
|
290
|
+
return this.authValue;
|
|
184
291
|
}
|
|
185
292
|
|
|
186
|
-
private
|
|
293
|
+
private devVitestManagerValue: DevVitestManager | null = null;
|
|
187
294
|
get devVitestManager(): DevVitestManager | null {
|
|
188
|
-
return this.
|
|
295
|
+
return this.devVitestManagerValue;
|
|
189
296
|
}
|
|
190
297
|
set devVitestManager(manager: DevVitestManager | null) {
|
|
191
|
-
this.
|
|
298
|
+
this.devVitestManagerValue = manager;
|
|
192
299
|
}
|
|
193
300
|
|
|
194
301
|
// Sonamu가 runtime을 직접 소유해 registry/connection lifecycle을 애플리케이션 수명주기와 동기화함
|
|
195
|
-
private
|
|
302
|
+
private websocketRuntimeValue: WebSocketRuntime | null = null;
|
|
196
303
|
// 같은 Fastify 인스턴스에 @fastify/websocket을 중복 등록하는 것을 WeakSet으로 차단함
|
|
197
304
|
private readonly websocketPluginServers = new WeakSet<
|
|
198
305
|
FastifyInstance<Server, IncomingMessage, ServerResponse>
|
|
199
306
|
>();
|
|
200
307
|
get websocketRuntime(): WebSocketRuntime {
|
|
201
|
-
if (!this.
|
|
308
|
+
if (!this.websocketRuntimeValue) {
|
|
202
309
|
throw new Error("WebSocket runtime has not been initialized.");
|
|
203
310
|
}
|
|
204
|
-
return this.
|
|
311
|
+
return this.websocketRuntimeValue;
|
|
205
312
|
}
|
|
206
313
|
set websocketRuntime(runtime: WebSocketRuntime | null) {
|
|
207
|
-
this.
|
|
314
|
+
this.websocketRuntimeValue = runtime;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
captureTestingSnapshot(): SonamuTestingSnapshot {
|
|
318
|
+
return {
|
|
319
|
+
apiRootPath: this.apiRootPathValue,
|
|
320
|
+
config: this.configValue,
|
|
321
|
+
syncer: this.syncerValue,
|
|
322
|
+
httpValidators: this.httpValidators,
|
|
323
|
+
pendingHttpValidators: this.pendingHttpValidators,
|
|
324
|
+
aotHttpValidatorRegistry: this.aotHttpValidatorRegistry,
|
|
325
|
+
preparedApis: this.preparedApis,
|
|
326
|
+
preparedModels: this.preparedModels,
|
|
327
|
+
websocketRuntime: this.websocketRuntimeValue,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
restoreTestingSnapshot(snapshot: SonamuTestingSnapshot): void {
|
|
332
|
+
this.apiRootPathValue = snapshot.apiRootPath;
|
|
333
|
+
this.configValue = snapshot.config;
|
|
334
|
+
this.syncerValue = snapshot.syncer;
|
|
335
|
+
this.httpValidators = snapshot.httpValidators;
|
|
336
|
+
this.pendingHttpValidators = snapshot.pendingHttpValidators;
|
|
337
|
+
this.aotHttpValidatorRegistry = snapshot.aotHttpValidatorRegistry;
|
|
338
|
+
this.preparedApis = snapshot.preparedApis;
|
|
339
|
+
this.preparedModels = snapshot.preparedModels;
|
|
340
|
+
this.websocketRuntimeValue = snapshot.websocketRuntime;
|
|
208
341
|
}
|
|
209
342
|
|
|
210
343
|
// HMR 처리: 파일 시스템 감시 + HMR/sync 사이클 실행은 watcher 모듈로 위임합니다.
|
|
@@ -255,17 +388,17 @@ class SonamuClass {
|
|
|
255
388
|
}
|
|
256
389
|
|
|
257
390
|
// DB 로드
|
|
258
|
-
const { DB } = await import("../database/db");
|
|
391
|
+
const { DB: RuntimeDB } = await import("../database/db");
|
|
259
392
|
const { isLocal: isLocalEnvironment } = await import("../utils/controller");
|
|
260
393
|
const environmentSnapshots = isLocalEnvironment()
|
|
261
394
|
? readAllEnvironmentSnapshots(this.apiRootPath, baseEnvBeforeConfigLoad)
|
|
262
395
|
: undefined;
|
|
263
|
-
this.dbConfig =
|
|
396
|
+
this.dbConfig = RuntimeDB.generateDBConfig(
|
|
264
397
|
this.config.database,
|
|
265
398
|
this.config.projectName,
|
|
266
399
|
environmentSnapshots,
|
|
267
400
|
);
|
|
268
|
-
|
|
401
|
+
RuntimeDB.setConfig(this.dbConfig);
|
|
269
402
|
|
|
270
403
|
// Entity 로드
|
|
271
404
|
// 테스트에서도 Entity 정보는 필요합니다.
|
|
@@ -290,7 +423,7 @@ class SonamuClass {
|
|
|
290
423
|
database: sonamuKnexAdapter(),
|
|
291
424
|
...mergedFieldMappings,
|
|
292
425
|
};
|
|
293
|
-
this.
|
|
426
|
+
this.authValue = betterAuth(authOptions);
|
|
294
427
|
}
|
|
295
428
|
|
|
296
429
|
// 테스팅인 경우 싱크 없이 중단
|
|
@@ -322,12 +455,12 @@ class SonamuClass {
|
|
|
322
455
|
}
|
|
323
456
|
|
|
324
457
|
this.isInitialized = true;
|
|
325
|
-
this.
|
|
326
|
-
this.
|
|
458
|
+
this.initElapsed = performance.now() - initStart;
|
|
459
|
+
this.configElapsed = configTime;
|
|
327
460
|
}
|
|
328
461
|
|
|
329
|
-
private
|
|
330
|
-
private
|
|
462
|
+
private initElapsed = 0;
|
|
463
|
+
private configElapsed = 0;
|
|
331
464
|
|
|
332
465
|
async createServer(initOptions?: { enableSync?: boolean; doSilent?: boolean }) {
|
|
333
466
|
if (!this.isInitialized) {
|
|
@@ -352,7 +485,7 @@ class SonamuClass {
|
|
|
352
485
|
// Storage 설정 → StorageManager 생성
|
|
353
486
|
if (options.storage) {
|
|
354
487
|
const { StorageManager } = await import("../storage/storage-manager");
|
|
355
|
-
this.
|
|
488
|
+
this.storageValue = new StorageManager(options.storage);
|
|
356
489
|
}
|
|
357
490
|
|
|
358
491
|
// 플러그인 등록
|
|
@@ -394,6 +527,7 @@ class SonamuClass {
|
|
|
394
527
|
|
|
395
528
|
this.server = server;
|
|
396
529
|
this.websocketRuntime ??= new WebSocketRuntime(this.config.server.websocket);
|
|
530
|
+
await this.refreshHttpValidators();
|
|
397
531
|
|
|
398
532
|
// timezone 설정
|
|
399
533
|
const timezone = this.config.api.timezone;
|
|
@@ -413,10 +547,10 @@ class SonamuClass {
|
|
|
413
547
|
|
|
414
548
|
server.setReplySerializer((payload) => {
|
|
415
549
|
return JSON.stringify(payload, (_key, value) => {
|
|
416
|
-
if (
|
|
550
|
+
if (isStringValue(value) && ISO_DATE_REGEX.test(value)) {
|
|
417
551
|
return formatInTimeZone(
|
|
418
552
|
new Date(value),
|
|
419
|
-
timezone as `${string}/${string}`,
|
|
553
|
+
/* SAFETY: API 데코레이터와 Zod 검증기 등록 계약이 이 값의 타입을 보장한다. */ timezone as `${string}/${string}`,
|
|
420
554
|
DATE_FORMAT,
|
|
421
555
|
);
|
|
422
556
|
}
|
|
@@ -500,7 +634,7 @@ class SonamuClass {
|
|
|
500
634
|
server.route({
|
|
501
635
|
method: api.options.httpMethod ?? "GET",
|
|
502
636
|
url: this.config.api.route.prefix + api.path,
|
|
503
|
-
handler: this.createApiHandler(api, config),
|
|
637
|
+
handler: await this.createApiHandler(api, config),
|
|
504
638
|
compress: toFastifyCompressOption(api.options.compress, globalCompressOptions),
|
|
505
639
|
});
|
|
506
640
|
}
|
|
@@ -512,17 +646,17 @@ class SonamuClass {
|
|
|
512
646
|
}
|
|
513
647
|
|
|
514
648
|
/**
|
|
515
|
-
* dev 모드 공통: catch-all에서
|
|
649
|
+
* dev 모드 공통: catch-all에서 마지막 준비 완료 API snapshot을 탐색하여 요청을 처리합니다.
|
|
516
650
|
* server.route()로 개별 등록하면 handler가 고정되어 HMR이 동작하지 않으므로,
|
|
517
|
-
*
|
|
651
|
+
* refresh 성공 시 교체되는 snapshot을 매 요청 조회합니다.
|
|
518
652
|
*
|
|
519
653
|
* 요청이 /api(정확히는 this.config.api.route.prefix)로 시작하지 않는 경우라면 null을 반환하며 끝냅니다.
|
|
520
654
|
*/
|
|
521
|
-
private handleDevApiRequest(
|
|
655
|
+
private async handleDevApiRequest(
|
|
522
656
|
request: FastifyRequest,
|
|
523
657
|
config: SonamuFastifyConfig,
|
|
524
|
-
): ((request: FastifyRequest, reply: FastifyReply) => Promise<
|
|
525
|
-
const matchedApi = this.findMatchedApi(request);
|
|
658
|
+
): Promise<((request: FastifyRequest, reply: FastifyReply) => Promise<ApiMethodResult>) | null> {
|
|
659
|
+
const matchedApi = this.findMatchedApi(request, this.preparedApis, this.preparedModels);
|
|
526
660
|
|
|
527
661
|
if (!matchedApi) {
|
|
528
662
|
throw new NotFoundException(SD("error.api.notFound"));
|
|
@@ -533,10 +667,14 @@ class SonamuClass {
|
|
|
533
667
|
return this.createWebSocketUpgradeRequiredHandler();
|
|
534
668
|
}
|
|
535
669
|
|
|
536
|
-
return this.createApiHandler(matchedApi, config);
|
|
670
|
+
return await this.createApiHandler(matchedApi, config, this.preparedModels);
|
|
537
671
|
}
|
|
538
672
|
|
|
539
|
-
private findMatchedApi(
|
|
673
|
+
private findMatchedApi(
|
|
674
|
+
request: FastifyRequest,
|
|
675
|
+
apis: readonly ExtendedApi[],
|
|
676
|
+
models: LoadedModels,
|
|
677
|
+
): ExtendedApi | undefined {
|
|
540
678
|
const url = this.getPathnameFromUrl(request.url);
|
|
541
679
|
const method = request.method;
|
|
542
680
|
|
|
@@ -544,8 +682,8 @@ class SonamuClass {
|
|
|
544
682
|
return undefined;
|
|
545
683
|
}
|
|
546
684
|
|
|
547
|
-
return
|
|
548
|
-
if (
|
|
685
|
+
return apis.find((api) => {
|
|
686
|
+
if (models[api.modelName] === undefined) {
|
|
549
687
|
return false;
|
|
550
688
|
}
|
|
551
689
|
const apiMethod = api.options.httpMethod ?? "GET";
|
|
@@ -558,7 +696,7 @@ class SonamuClass {
|
|
|
558
696
|
|
|
559
697
|
/**
|
|
560
698
|
* dev api 모드: Vite 없이 API 동적 라우팅만 제공합니다.
|
|
561
|
-
* HMR을 위해 catch-all에서 매 요청마다
|
|
699
|
+
* HMR을 위해 catch-all에서 매 요청마다 준비 완료 API snapshot을 조회합니다.
|
|
562
700
|
*/
|
|
563
701
|
private setupDevServer(
|
|
564
702
|
server: FastifyInstance<Server, IncomingMessage, ServerResponse>,
|
|
@@ -569,7 +707,7 @@ class SonamuClass {
|
|
|
569
707
|
method: "GET",
|
|
570
708
|
url: `${this.config.api.route.prefix}/*`,
|
|
571
709
|
handler: async (request, reply) => {
|
|
572
|
-
const handler = this.handleDevApiRequest(request, config);
|
|
710
|
+
const handler = await this.handleDevApiRequest(request, config);
|
|
573
711
|
if (handler) {
|
|
574
712
|
return handler(request, reply);
|
|
575
713
|
}
|
|
@@ -585,7 +723,7 @@ class SonamuClass {
|
|
|
585
723
|
method: ["HEAD", "POST", "PUT", "DELETE", "PATCH"],
|
|
586
724
|
url: `${this.config.api.route.prefix}/*`,
|
|
587
725
|
handler: async (request, reply) => {
|
|
588
|
-
const handler = this.handleDevApiRequest(request, config);
|
|
726
|
+
const handler = await this.handleDevApiRequest(request, config);
|
|
589
727
|
if (handler) {
|
|
590
728
|
return handler(request, reply);
|
|
591
729
|
}
|
|
@@ -640,7 +778,7 @@ class SonamuClass {
|
|
|
640
778
|
method: "GET",
|
|
641
779
|
url: `${this.config.api.route.prefix}/*`,
|
|
642
780
|
handler: async (request, reply) => {
|
|
643
|
-
const result = this.handleDevApiRequest(request, config);
|
|
781
|
+
const result = await this.handleDevApiRequest(request, config);
|
|
644
782
|
if (result) {
|
|
645
783
|
return result(request, reply);
|
|
646
784
|
}
|
|
@@ -655,7 +793,7 @@ class SonamuClass {
|
|
|
655
793
|
method: ["HEAD", "POST", "PUT", "DELETE", "PATCH"],
|
|
656
794
|
url: `${this.config.api.route.prefix}/*`,
|
|
657
795
|
handler: async (request, reply) => {
|
|
658
|
-
const result = this.handleDevApiRequest(request, config);
|
|
796
|
+
const result = await this.handleDevApiRequest(request, config);
|
|
659
797
|
if (result) {
|
|
660
798
|
return result(request, reply);
|
|
661
799
|
}
|
|
@@ -691,20 +829,29 @@ class SonamuClass {
|
|
|
691
829
|
|
|
692
830
|
// 2. CSR fallback
|
|
693
831
|
try {
|
|
694
|
-
const fs = await import("node:fs/promises");
|
|
695
832
|
let template = await fs.readFile(
|
|
696
833
|
path.join(this.viteServer.config.root, "index.html"),
|
|
697
834
|
"utf-8",
|
|
698
835
|
);
|
|
699
836
|
template = await this.viteServer.transformIndexHtml(url, template);
|
|
700
837
|
|
|
838
|
+
// ?hmr=false로 연 페이지는 HMR 웹소켓만 끊어 새로고침되지 않게 한다.
|
|
839
|
+
const { HMR_OPT_OUT_SCRIPT, isHmrDisabledByQuery } = await import("../ssr/hmr-opt-out");
|
|
840
|
+
if (isHmrDisabledByQuery(url)) {
|
|
841
|
+
template = template.replace("<head>", `<head>\n${HMR_OPT_OUT_SCRIPT}`);
|
|
842
|
+
}
|
|
843
|
+
|
|
701
844
|
reply.type("text/html");
|
|
702
845
|
return template;
|
|
703
846
|
} catch (e) {
|
|
704
|
-
this.viteServer.ssrFixStacktrace(
|
|
847
|
+
this.viteServer.ssrFixStacktrace(
|
|
848
|
+
/* SAFETY: API 데코레이터와 Zod 검증기 등록 계약이 이 값의 타입을 보장한다. */ e as Error,
|
|
849
|
+
);
|
|
705
850
|
console.error(e);
|
|
706
851
|
reply.status(500);
|
|
707
|
-
return
|
|
852
|
+
return /* SAFETY: API 데코레이터와 Zod 검증기 등록 계약이 이 값의 타입을 보장한다. */ (
|
|
853
|
+
e as Error
|
|
854
|
+
).message;
|
|
708
855
|
}
|
|
709
856
|
},
|
|
710
857
|
});
|
|
@@ -714,7 +861,6 @@ class SonamuClass {
|
|
|
714
861
|
await this.viteServer.close();
|
|
715
862
|
});
|
|
716
863
|
|
|
717
|
-
const chalk = (await import("chalk")).default;
|
|
718
864
|
if ("port" in hmr) {
|
|
719
865
|
console.log(
|
|
720
866
|
chalk.dim(
|
|
@@ -764,7 +910,10 @@ class SonamuClass {
|
|
|
764
910
|
|
|
765
911
|
// 롤링 업데이트 대응: asset hash 불일치 시 현재 버전 직접 서빙
|
|
766
912
|
server.get("/assets/:filename", async (request, reply) => {
|
|
767
|
-
const requestedFile =
|
|
913
|
+
const requestedFile =
|
|
914
|
+
/* SAFETY: API 데코레이터와 Zod 검증기 등록 계약이 이 값의 타입을 보장한다. */ (
|
|
915
|
+
request.params as { filename: string }
|
|
916
|
+
).filename;
|
|
768
917
|
const assetsDir = path.join(webDistPath, "assets");
|
|
769
918
|
const safeFilePath = this.resolvePathWithinBaseDir(assetsDir, requestedFile);
|
|
770
919
|
if (safeFilePath === null) {
|
|
@@ -899,8 +1048,14 @@ class SonamuClass {
|
|
|
899
1048
|
createApiHandler(
|
|
900
1049
|
api: ExtendedApi,
|
|
901
1050
|
config: SonamuFastifyConfig,
|
|
902
|
-
|
|
903
|
-
|
|
1051
|
+
models: LoadedModels = this.syncer.models,
|
|
1052
|
+
dependencies: SonamuHttpValidatorDependencies = defaultSonamuHttpValidatorDependencies,
|
|
1053
|
+
): (request: FastifyRequest, reply: FastifyReply) => Promise<ApiMethodResult> {
|
|
1054
|
+
const preparedValidator = this.getOrCreateHttpValidator(api, dependencies);
|
|
1055
|
+
|
|
1056
|
+
return async (request: FastifyRequest, reply: FastifyReply): Promise<ApiMethodResult> => {
|
|
1057
|
+
const validator =
|
|
1058
|
+
preparedValidator instanceof Promise ? await preparedValidator : preparedValidator;
|
|
904
1059
|
// Context 생성
|
|
905
1060
|
const context: Context = await this.createContext(config, request, reply);
|
|
906
1061
|
|
|
@@ -913,26 +1068,17 @@ class SonamuClass {
|
|
|
913
1068
|
api,
|
|
914
1069
|
});
|
|
915
1070
|
|
|
916
|
-
// 파라미터 정보로 zod 스키마 빌드
|
|
917
|
-
const { getZodObjectFromApi } = await import("./code-converters");
|
|
918
|
-
const ReqType = getZodObjectFromApi(api, this.syncer.types);
|
|
919
|
-
|
|
920
1071
|
// request 파싱
|
|
921
1072
|
const which = api.options.httpMethod === "GET" ? "query" : "body";
|
|
922
|
-
let reqBody
|
|
923
|
-
[key: string]: unknown;
|
|
924
|
-
};
|
|
1073
|
+
let reqBody = new Map<string, ApiArgument>();
|
|
925
1074
|
// 파일 업로드 있는 경우 임시 데이터
|
|
926
|
-
const files: {
|
|
927
|
-
bufferedFiles: BufferedFile[];
|
|
928
|
-
uploadedFiles: UploadedFile[];
|
|
929
|
-
} = {
|
|
1075
|
+
const files: UploadedRequestFiles = {
|
|
930
1076
|
bufferedFiles: [],
|
|
931
1077
|
uploadedFiles: [],
|
|
932
1078
|
};
|
|
933
1079
|
|
|
934
1080
|
try {
|
|
935
|
-
const body =
|
|
1081
|
+
const body = request[which] ?? {};
|
|
936
1082
|
if (api.uploadOptions) {
|
|
937
1083
|
const parts = request.parts({
|
|
938
1084
|
limits: api.uploadOptions.limits,
|
|
@@ -1001,8 +1147,7 @@ class SonamuClass {
|
|
|
1001
1147
|
Object.assign(body, parsed);
|
|
1002
1148
|
}
|
|
1003
1149
|
|
|
1004
|
-
|
|
1005
|
-
reqBody = fastifyCaster(ReqType).parse(body);
|
|
1150
|
+
reqBody = toApiArgumentMap(validator.parse(body));
|
|
1006
1151
|
} catch (e) {
|
|
1007
1152
|
const { ZodError } = await import("zod");
|
|
1008
1153
|
if (e instanceof ZodError) {
|
|
@@ -1011,9 +1156,12 @@ class SonamuClass {
|
|
|
1011
1156
|
.map((issue) => issue.message)
|
|
1012
1157
|
.join(" ");
|
|
1013
1158
|
const { BadRequestException } = await import("../exceptions/so-exceptions");
|
|
1014
|
-
throw new BadRequestException(
|
|
1015
|
-
|
|
1016
|
-
|
|
1159
|
+
throw new BadRequestException(
|
|
1160
|
+
/* SAFETY: API 데코레이터와 Zod 검증기 등록 계약이 이 값의 타입을 보장한다. */ messages as LocalizedString,
|
|
1161
|
+
{
|
|
1162
|
+
zodError: e,
|
|
1163
|
+
},
|
|
1164
|
+
);
|
|
1017
1165
|
} else {
|
|
1018
1166
|
throw e;
|
|
1019
1167
|
}
|
|
@@ -1045,15 +1193,146 @@ class SonamuClass {
|
|
|
1045
1193
|
if (ApiParamType.isContext(param.type)) {
|
|
1046
1194
|
return context;
|
|
1047
1195
|
} else {
|
|
1048
|
-
return reqBody
|
|
1196
|
+
return reqBody.get(param.name);
|
|
1049
1197
|
}
|
|
1050
1198
|
});
|
|
1051
1199
|
|
|
1052
|
-
return this.invokeModelMethod(api, args, reply);
|
|
1200
|
+
return this.invokeModelMethod(api, args, reply, models);
|
|
1053
1201
|
});
|
|
1054
1202
|
};
|
|
1055
1203
|
}
|
|
1056
1204
|
|
|
1205
|
+
private getOrCreateHttpValidator(
|
|
1206
|
+
api: ExtendedApi,
|
|
1207
|
+
dependencies: SonamuHttpValidatorDependencies,
|
|
1208
|
+
): HttpValidator | Promise<HttpValidator> {
|
|
1209
|
+
const cached = this.httpValidators.get(api);
|
|
1210
|
+
if (cached !== undefined) {
|
|
1211
|
+
return cached;
|
|
1212
|
+
}
|
|
1213
|
+
const existing = this.pendingHttpValidators.get(api);
|
|
1214
|
+
if (existing !== undefined) {
|
|
1215
|
+
return existing;
|
|
1216
|
+
}
|
|
1217
|
+
const pending = this.buildHttpValidator(api, dependencies);
|
|
1218
|
+
this.pendingHttpValidators.set(api, pending);
|
|
1219
|
+
void pending.then(
|
|
1220
|
+
(validator) => {
|
|
1221
|
+
this.httpValidators.set(api, validator);
|
|
1222
|
+
this.pendingHttpValidators.delete(api);
|
|
1223
|
+
},
|
|
1224
|
+
() => {
|
|
1225
|
+
this.pendingHttpValidators.delete(api);
|
|
1226
|
+
},
|
|
1227
|
+
);
|
|
1228
|
+
return pending;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
private async buildHttpValidator(
|
|
1232
|
+
api: ExtendedApi,
|
|
1233
|
+
dependencies: SonamuHttpValidatorDependencies,
|
|
1234
|
+
): Promise<HttpValidator> {
|
|
1235
|
+
const policy = normalizeZodCompilerPolicy(this.config.validation?.zodCompiler);
|
|
1236
|
+
const sourceAot = process.env.HOT === "yes" || process.env.VITEST === "true";
|
|
1237
|
+
if (policy.api === "aot" && !sourceAot) {
|
|
1238
|
+
const registry = this.aotHttpValidatorRegistry ?? (await this.loadAotHttpValidatorRegistry());
|
|
1239
|
+
return this.getAotHttpValidator(api, registry);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
return await dependencies.createValidator({
|
|
1243
|
+
api,
|
|
1244
|
+
policy,
|
|
1245
|
+
types: this.syncer.types,
|
|
1246
|
+
allowAotSourceFallback: sourceAot,
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
private getAotHttpValidator(api: ExtendedApi, registry: HttpValidatorRegistry): HttpValidator {
|
|
1251
|
+
const routeKey = getHttpValidatorRouteKey(api);
|
|
1252
|
+
const validator = registry.validators.get(routeKey);
|
|
1253
|
+
if (validator === undefined) {
|
|
1254
|
+
throw new Error(`HTTP validator registry is missing ${routeKey}`);
|
|
1255
|
+
}
|
|
1256
|
+
return validator;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
private async loadAotHttpValidatorRegistry(
|
|
1260
|
+
apis: readonly ExtendedApi[] = this.syncer.apis,
|
|
1261
|
+
dependencies: SonamuHttpValidatorDependencies = defaultSonamuHttpValidatorDependencies,
|
|
1262
|
+
): Promise<HttpValidatorRegistry> {
|
|
1263
|
+
const { pathToFileURL } = await import("node:url");
|
|
1264
|
+
const registryPath = path.join(
|
|
1265
|
+
this.apiRootPath,
|
|
1266
|
+
"dist/application/sonamu.validators.generated.js",
|
|
1267
|
+
);
|
|
1268
|
+
if (!(await exists(registryPath))) {
|
|
1269
|
+
throw new Error(`Generated HTTP validator registry not found: ${registryPath}`);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
const imported =
|
|
1273
|
+
/* SAFETY: API 데코레이터와 Zod 검증기 등록 계약이 이 값의 타입을 보장한다. */ (await import(
|
|
1274
|
+
pathToFileURL(registryPath).href
|
|
1275
|
+
)) as {
|
|
1276
|
+
fingerprint?: unknown;
|
|
1277
|
+
validators?: unknown;
|
|
1278
|
+
};
|
|
1279
|
+
if (!isStringValue(imported.fingerprint) || !(imported.validators instanceof Map)) {
|
|
1280
|
+
throw new Error(`Invalid generated HTTP validator registry: ${registryPath}`);
|
|
1281
|
+
}
|
|
1282
|
+
for (const [key, validator] of imported.validators) {
|
|
1283
|
+
if (
|
|
1284
|
+
!isStringValue(key) ||
|
|
1285
|
+
!isObjectValue(validator) ||
|
|
1286
|
+
validator === null ||
|
|
1287
|
+
!("parse" in validator) ||
|
|
1288
|
+
!isFunctionValue(validator.parse)
|
|
1289
|
+
) {
|
|
1290
|
+
throw new Error(`Invalid HTTP validator registry entry: ${String(key)}`);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
const registry: HttpValidatorRegistry = {
|
|
1295
|
+
fingerprint: imported.fingerprint,
|
|
1296
|
+
validators: imported.validators,
|
|
1297
|
+
};
|
|
1298
|
+
dependencies.assertRegistry(apis, registry);
|
|
1299
|
+
return registry;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
async refreshHttpValidators(
|
|
1303
|
+
dependencies: SonamuHttpValidatorDependencies = defaultSonamuHttpValidatorDependencies,
|
|
1304
|
+
): Promise<void> {
|
|
1305
|
+
const policy = normalizeZodCompilerPolicy(this.config.validation?.zodCompiler);
|
|
1306
|
+
const sourceAot = process.env.HOT === "yes" || process.env.VITEST === "true";
|
|
1307
|
+
const nextApis = [...this.syncer.apis];
|
|
1308
|
+
const nextModels = { ...this.syncer.models };
|
|
1309
|
+
const restApis = nextApis.filter((api) => api.websocketOptions === undefined);
|
|
1310
|
+
const next = new WeakMap<ExtendedApi, HttpValidator>();
|
|
1311
|
+
let nextAotRegistry: HttpValidatorRegistry | undefined;
|
|
1312
|
+
|
|
1313
|
+
if (policy.api === "aot" && !sourceAot) {
|
|
1314
|
+
const registry = await this.loadAotHttpValidatorRegistry(nextApis, dependencies);
|
|
1315
|
+
for (const api of restApis) {
|
|
1316
|
+
next.set(api, this.getAotHttpValidator(api, registry));
|
|
1317
|
+
}
|
|
1318
|
+
nextAotRegistry = registry;
|
|
1319
|
+
} else {
|
|
1320
|
+
await Promise.all(
|
|
1321
|
+
restApis.map(async (api) => {
|
|
1322
|
+
const validator = await this.buildHttpValidator(api, dependencies);
|
|
1323
|
+
next.set(api, validator);
|
|
1324
|
+
}),
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// validator와 같은 revision의 API/model snapshot을 함께 공개해 HMR 중간 상태를 숨깁니다.
|
|
1329
|
+
this.httpValidators = next;
|
|
1330
|
+
this.pendingHttpValidators = new WeakMap();
|
|
1331
|
+
this.aotHttpValidatorRegistry = nextAotRegistry;
|
|
1332
|
+
this.preparedApis = nextApis;
|
|
1333
|
+
this.preparedModels = nextModels;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1057
1336
|
// WS path를 일반 HTTP GET으로 호출한 경우 426 + Upgrade 헤더로 명시적으로 websocket 접속을 유도함
|
|
1058
1337
|
private createWebSocketUpgradeRequiredHandler() {
|
|
1059
1338
|
return async (_request: FastifyRequest, reply: FastifyReply): Promise<void> => {
|
|
@@ -1069,7 +1348,7 @@ class SonamuClass {
|
|
|
1069
1348
|
request: FastifyRequest,
|
|
1070
1349
|
config: SonamuFastifyConfig,
|
|
1071
1350
|
): Promise<void> {
|
|
1072
|
-
const matchedApi = this.findMatchedApi(request);
|
|
1351
|
+
const matchedApi = this.findMatchedApi(request, this.syncer.apis, this.syncer.models);
|
|
1073
1352
|
if (!matchedApi?.websocketOptions) {
|
|
1074
1353
|
const traceContext = this.websocketRuntime.telemetryController.createConnectionContext({
|
|
1075
1354
|
headers: request.headers,
|
|
@@ -1099,6 +1378,10 @@ class SonamuClass {
|
|
|
1099
1378
|
// 2) `active: false`로 먼저 등록한 뒤 context 안에서 guard를 실행해 인증 정보를 참조할 수 있게 함
|
|
1100
1379
|
// 3) context/guard 준비가 끝난 뒤 `activate()`해 브로드캐스트가 초기화 중간 상태를 보지 못하게 함
|
|
1101
1380
|
// 에러 발생 시에는 resolveWebSocketCloseDescriptor 정책에 따라 close code를 매핑함
|
|
1381
|
+
createWebSocketHandlerForTesting(api: ExtendedApi, config: SonamuFastifyConfig) {
|
|
1382
|
+
return this.createWebSocketHandler(api, config);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1102
1385
|
private createWebSocketHandler(api: ExtendedApi, config: SonamuFastifyConfig) {
|
|
1103
1386
|
return async (
|
|
1104
1387
|
connection: {
|
|
@@ -1107,7 +1390,7 @@ class SonamuClass {
|
|
|
1107
1390
|
request: FastifyRequest,
|
|
1108
1391
|
): Promise<void> => {
|
|
1109
1392
|
const socket = connection.socket;
|
|
1110
|
-
let wsContext: WebSocketContext | null = null;
|
|
1393
|
+
let wsContext: WebSocketContext<object, object> | null = null;
|
|
1111
1394
|
let rawWs: ReturnType<WebSocketRuntime["registerConnection"]> | null = null;
|
|
1112
1395
|
let preRegisterPhase: "guard" | "query" | "register" | "handler" = "guard";
|
|
1113
1396
|
let traceContext: WebSocketTelemetryConnectionContext = {};
|
|
@@ -1153,7 +1436,7 @@ class SonamuClass {
|
|
|
1153
1436
|
return wsContext;
|
|
1154
1437
|
}
|
|
1155
1438
|
|
|
1156
|
-
return reqBody
|
|
1439
|
+
return reqBody.get(param.name);
|
|
1157
1440
|
});
|
|
1158
1441
|
|
|
1159
1442
|
await this.asyncLocalStorage.run({ context: wsContext }, async () => {
|
|
@@ -1208,14 +1491,11 @@ class SonamuClass {
|
|
|
1208
1491
|
|
|
1209
1492
|
// onMessage/onClose처럼 다른 tick에서 실행되는 callback은 ALS context가 끊기므로 wrapper에서 `asyncLocalStorage.run`으로 다시 감싸 복원함
|
|
1210
1493
|
// publish/join/leave/setUserId 같은 즉시 실행 API는 단순 위임만 하고, deferred callback에만 context 복원을 적용함
|
|
1211
|
-
private createScopedWebSocketConnection<
|
|
1212
|
-
TOut extends WebSocketEventMap,
|
|
1213
|
-
TIn extends WebSocketEventMap,
|
|
1214
|
-
>(
|
|
1494
|
+
private createScopedWebSocketConnection<TOut extends object, TIn extends object>(
|
|
1215
1495
|
ws: WebSocketConnection<TOut, TIn>,
|
|
1216
1496
|
getContext: () => WebSocketContext | null,
|
|
1217
1497
|
): WebSocketConnection<TOut, TIn> {
|
|
1218
|
-
const telemetryController = this.
|
|
1498
|
+
const telemetryController = this.websocketRuntimeValue?.telemetryController;
|
|
1219
1499
|
const runInContext = <T>(callback: () => T): T => {
|
|
1220
1500
|
const context = getContext();
|
|
1221
1501
|
if (!context) {
|
|
@@ -1309,7 +1589,7 @@ class SonamuClass {
|
|
|
1309
1589
|
namespace: ws.namespace,
|
|
1310
1590
|
userId: ws.userId,
|
|
1311
1591
|
attributes: { event: eventName },
|
|
1312
|
-
errorType: error instanceof Error ? error.name :
|
|
1592
|
+
errorType: error instanceof Error ? error.name : runtimeTypeOf(error),
|
|
1313
1593
|
...traceContext,
|
|
1314
1594
|
});
|
|
1315
1595
|
throw error;
|
|
@@ -1338,16 +1618,23 @@ class SonamuClass {
|
|
|
1338
1618
|
};
|
|
1339
1619
|
}
|
|
1340
1620
|
|
|
1621
|
+
createScopedWebSocketConnectionForTesting<TOut extends object, TIn extends object>(
|
|
1622
|
+
ws: WebSocketConnection<TOut, TIn>,
|
|
1623
|
+
getContext: () => WebSocketContext | null,
|
|
1624
|
+
): WebSocketConnection<TOut, TIn> {
|
|
1625
|
+
return this.createScopedWebSocketConnection(ws, getContext);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1341
1628
|
private async parseWebSocketRequestParams(
|
|
1342
1629
|
api: ExtendedApi,
|
|
1343
1630
|
request: FastifyRequest,
|
|
1344
|
-
): Promise<
|
|
1631
|
+
): Promise<Map<string, ApiArgument>> {
|
|
1345
1632
|
const { getZodObjectFromApi } = await import("./code-converters");
|
|
1346
1633
|
const ReqType = getZodObjectFromApi(api, this.syncer.types);
|
|
1347
1634
|
|
|
1348
1635
|
try {
|
|
1349
1636
|
const { fastifyCaster } = await import("./caster");
|
|
1350
|
-
return fastifyCaster(ReqType).parse(
|
|
1637
|
+
return toApiArgumentMap(fastifyCaster(ReqType).parse(request.query ?? {}));
|
|
1351
1638
|
} catch (e) {
|
|
1352
1639
|
const { ZodError } = await import("zod");
|
|
1353
1640
|
if (e instanceof ZodError) {
|
|
@@ -1356,9 +1643,12 @@ class SonamuClass {
|
|
|
1356
1643
|
.map((issue) => issue.message)
|
|
1357
1644
|
.join(" ");
|
|
1358
1645
|
const { BadRequestException } = await import("../exceptions/so-exceptions");
|
|
1359
|
-
throw new BadRequestException(
|
|
1360
|
-
|
|
1361
|
-
|
|
1646
|
+
throw new BadRequestException(
|
|
1647
|
+
/* SAFETY: API 데코레이터와 Zod 검증기 등록 계약이 이 값의 타입을 보장한다. */ messages as LocalizedString,
|
|
1648
|
+
{
|
|
1649
|
+
zodError: e,
|
|
1650
|
+
},
|
|
1651
|
+
);
|
|
1362
1652
|
}
|
|
1363
1653
|
|
|
1364
1654
|
throw e;
|
|
@@ -1369,7 +1659,7 @@ class SonamuClass {
|
|
|
1369
1659
|
* URL에서 path params를 추출합니다.
|
|
1370
1660
|
* 예: pattern="/admin/companies/:companyId", url="/admin/companies/123" → { companyId: "123" }
|
|
1371
1661
|
*/
|
|
1372
|
-
private extractPathParams(pattern: string, url: string)
|
|
1662
|
+
private extractPathParams(pattern: string, url: string) {
|
|
1373
1663
|
const patternParts = pattern.split("/").filter(Boolean);
|
|
1374
1664
|
const urlParts = this.getPathnameFromUrl(url).split("/").filter(Boolean);
|
|
1375
1665
|
const params: Record<string, string> = {};
|
|
@@ -1466,7 +1756,7 @@ class SonamuClass {
|
|
|
1466
1756
|
config: SonamuFastifyConfig,
|
|
1467
1757
|
request: FastifyRequest,
|
|
1468
1758
|
reply: FastifyReply,
|
|
1469
|
-
): Promise<
|
|
1759
|
+
): Promise<ApiMethodResult> {
|
|
1470
1760
|
// Context 생성 (기존 메소드 재사용)
|
|
1471
1761
|
const context = await this.createContext(config, request, reply);
|
|
1472
1762
|
|
|
@@ -1486,14 +1776,29 @@ class SonamuClass {
|
|
|
1486
1776
|
});
|
|
1487
1777
|
}
|
|
1488
1778
|
|
|
1779
|
+
async handleDevApiRequestForTesting(request: FastifyRequest, config: SonamuFastifyConfig) {
|
|
1780
|
+
return await this.handleDevApiRequest(request, config);
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1489
1783
|
// WS 경로에서는 HTTP reply가 없으므로 reply를 optional로 받아 공통 호출 경로를 유지함
|
|
1490
1784
|
async invokeModelMethod(
|
|
1491
1785
|
api: ExtendedApi,
|
|
1492
1786
|
args: unknown[],
|
|
1493
1787
|
reply?: FastifyReply,
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
const
|
|
1788
|
+
models: LoadedModels = this.syncer.models,
|
|
1789
|
+
): Promise<ApiMethodResult> {
|
|
1790
|
+
const model = models[api.modelName];
|
|
1791
|
+
if (model === undefined) {
|
|
1792
|
+
throw new Error(`Model not found: ${api.modelName}`);
|
|
1793
|
+
}
|
|
1794
|
+
const method =
|
|
1795
|
+
model[
|
|
1796
|
+
/* SAFETY: 동기화기가 모델 클래스에서 수집한 API 메서드 이름만 전달한다. */ api.methodName as keyof typeof model
|
|
1797
|
+
];
|
|
1798
|
+
if (!isFunctionValue(method)) {
|
|
1799
|
+
throw new Error(`Model method not found: ${api.modelName}.${api.methodName}`);
|
|
1800
|
+
}
|
|
1801
|
+
const result = await Function.prototype.apply.call(method, model, args);
|
|
1497
1802
|
reply?.type(api.options.contentType ?? "application/json");
|
|
1498
1803
|
|
|
1499
1804
|
return result;
|
|
@@ -1519,7 +1824,7 @@ class SonamuClass {
|
|
|
1519
1824
|
|
|
1520
1825
|
// auth context 추가
|
|
1521
1826
|
const headers = convertFastifyHeadersToStandard(request.headers);
|
|
1522
|
-
const session = (await this.
|
|
1827
|
+
const session = (await this.authValue?.api.getSession({ headers })) ?? null;
|
|
1523
1828
|
|
|
1524
1829
|
const context: Context = await Promise.resolve(
|
|
1525
1830
|
config.contextProvider(
|
|
@@ -1547,14 +1852,14 @@ class SonamuClass {
|
|
|
1547
1852
|
async createWebSocketContext(
|
|
1548
1853
|
config: SonamuFastifyConfig,
|
|
1549
1854
|
request: FastifyRequest,
|
|
1550
|
-
ws:
|
|
1855
|
+
ws: AnyWebSocketConnection,
|
|
1551
1856
|
): Promise<WebSocketContext> {
|
|
1552
1857
|
const locale =
|
|
1553
1858
|
this.detectLocale(request.headers["accept-language"], this.config.i18n.supportedLocales) ??
|
|
1554
1859
|
this.config.i18n.defaultLocale;
|
|
1555
1860
|
|
|
1556
1861
|
const headers = convertFastifyHeadersToStandard(request.headers);
|
|
1557
|
-
const session = (await this.
|
|
1862
|
+
const session = (await this.authValue?.api.getSession({ headers })) ?? null;
|
|
1558
1863
|
|
|
1559
1864
|
const defaultContext = {
|
|
1560
1865
|
transport: "ws" as const,
|
|
@@ -1575,11 +1880,6 @@ class SonamuClass {
|
|
|
1575
1880
|
|
|
1576
1881
|
// reply/createSSE에 의존하는 contextProvider가 있으면 즉시 에러를 던져 transport misuse를 빨리 드러냄
|
|
1577
1882
|
const replyStub = createWebSocketReplyStub();
|
|
1578
|
-
const createSSE = <T extends ZodObject>(_events: T) => {
|
|
1579
|
-
throw new Error(
|
|
1580
|
-
"createSSE is not available in websocket context. Define websocketContextProvider if your context setup depends on SSE helpers.",
|
|
1581
|
-
);
|
|
1582
|
-
};
|
|
1583
1883
|
const httpLikeContext = await Promise.resolve(
|
|
1584
1884
|
config.contextProvider(
|
|
1585
1885
|
{
|
|
@@ -1587,7 +1887,7 @@ class SonamuClass {
|
|
|
1587
1887
|
request,
|
|
1588
1888
|
reply: replyStub,
|
|
1589
1889
|
headers: request.headers,
|
|
1590
|
-
createSSE,
|
|
1890
|
+
createSSE: createUnavailableSSE,
|
|
1591
1891
|
naiteStore: defaultContext.naiteStore,
|
|
1592
1892
|
locale,
|
|
1593
1893
|
user: defaultContext.user,
|
|
@@ -1684,7 +1984,11 @@ class SonamuClass {
|
|
|
1684
1984
|
const compressPlugin = (await import("@fastify/compress")).default;
|
|
1685
1985
|
const defaultOptions = {
|
|
1686
1986
|
threshold: 1024,
|
|
1687
|
-
encodings:
|
|
1987
|
+
encodings: /* SAFETY: API 데코레이터와 Zod 검증기 등록 계약이 이 값의 타입을 보장한다. */ [
|
|
1988
|
+
"br",
|
|
1989
|
+
"gzip",
|
|
1990
|
+
"deflate",
|
|
1991
|
+
] as ("br" | "gzip" | "deflate")[],
|
|
1688
1992
|
};
|
|
1689
1993
|
|
|
1690
1994
|
if (plugins.compress === true) {
|
|
@@ -1721,7 +2025,10 @@ class SonamuClass {
|
|
|
1721
2025
|
};
|
|
1722
2026
|
|
|
1723
2027
|
for (const [key, pluginName] of Object.entries(pluginsModules)) {
|
|
1724
|
-
await registerPlugin(
|
|
2028
|
+
await registerPlugin(
|
|
2029
|
+
/* SAFETY: API 데코레이터와 Zod 검증기 등록 계약이 이 값의 타입을 보장한다. */ key as keyof typeof plugins,
|
|
2030
|
+
pluginName,
|
|
2031
|
+
);
|
|
1725
2032
|
}
|
|
1726
2033
|
|
|
1727
2034
|
if (plugins.ws) {
|
|
@@ -1820,23 +2127,24 @@ class SonamuClass {
|
|
|
1820
2127
|
}
|
|
1821
2128
|
|
|
1822
2129
|
private async printStartupSummary() {
|
|
1823
|
-
const chalk = (await import("chalk")).default;
|
|
1824
2130
|
const activePreset = getSonamuEnvironment();
|
|
1825
2131
|
|
|
1826
|
-
|
|
1827
|
-
const green = (msg: string) => console.log(chalk.green(`✓ ${msg}`));
|
|
1828
|
-
|
|
1829
|
-
dim(`Config loaded${formatTime(this._configElapsed)}`);
|
|
2132
|
+
printDimSummary(`Config loaded${formatTime(this.configElapsed)}`);
|
|
1830
2133
|
|
|
1831
2134
|
// DB preset 목록
|
|
1832
|
-
|
|
2135
|
+
printGreenSummary("DB");
|
|
1833
2136
|
const { isLocal } = await import("../utils/controller");
|
|
1834
|
-
const presetNames =
|
|
2137
|
+
const presetNames =
|
|
2138
|
+
/* SAFETY: API 데코레이터와 Zod 검증기 등록 계약이 이 값의 타입을 보장한다. */ Object.keys(
|
|
2139
|
+
this.dbConfig,
|
|
2140
|
+
) as (keyof SonamuDBConfig)[];
|
|
1835
2141
|
const maxLen = Math.max(...presetNames.map((n) => n.length));
|
|
1836
2142
|
for (const name of presetNames) {
|
|
1837
|
-
const conn =
|
|
1838
|
-
|
|
1839
|
-
|
|
2143
|
+
const conn =
|
|
2144
|
+
/* SAFETY: API 데코레이터와 Zod 검증기 등록 계약이 이 값의 타입을 보장한다. */ this
|
|
2145
|
+
.dbConfig[name].connection as
|
|
2146
|
+
| { host?: string; port?: number; database?: string }
|
|
2147
|
+
| undefined;
|
|
1840
2148
|
const host = conn?.host ?? "localhost";
|
|
1841
2149
|
const addr = `@ ${host}:${conn?.port ?? 5432}/${conn?.database ?? "(unknown)"}`;
|
|
1842
2150
|
const padded = name.padEnd(maxLen);
|
|
@@ -1851,12 +2159,12 @@ class SonamuClass {
|
|
|
1851
2159
|
|
|
1852
2160
|
if (this.config.server.auth) {
|
|
1853
2161
|
const basePath = this.config.server.auth.basePath ?? "/api/auth";
|
|
1854
|
-
|
|
2162
|
+
printDimSummary(`Auth: better-auth at ${basePath}/*`);
|
|
1855
2163
|
}
|
|
1856
2164
|
if (this.config.api.timezone) {
|
|
1857
|
-
|
|
2165
|
+
printDimSummary(`Timezone: ${this.config.api.timezone}`);
|
|
1858
2166
|
}
|
|
1859
|
-
|
|
2167
|
+
printGreenSummary(`Sonamu ready${formatTime(this.initElapsed)}`);
|
|
1860
2168
|
}
|
|
1861
2169
|
|
|
1862
2170
|
private async initializeCache(config: CacheConfig | undefined, forTesting: boolean) {
|
|
@@ -1865,8 +2173,8 @@ class SonamuClass {
|
|
|
1865
2173
|
// 테스트 환경에서 메모리 드라이버 자동 사용
|
|
1866
2174
|
if (forTesting) {
|
|
1867
2175
|
const { createTestCacheManager } = await import("../cache/cache-manager");
|
|
1868
|
-
this.
|
|
1869
|
-
setCacheManagerRef(this.
|
|
2176
|
+
this.cacheValue = createTestCacheManager();
|
|
2177
|
+
setCacheManagerRef(this.cacheValue);
|
|
1870
2178
|
return;
|
|
1871
2179
|
}
|
|
1872
2180
|
|
|
@@ -1878,14 +2186,14 @@ class SonamuClass {
|
|
|
1878
2186
|
|
|
1879
2187
|
// 설정에 따라 CacheManager 생성
|
|
1880
2188
|
const { createCacheManager } = await import("../cache/cache-manager");
|
|
1881
|
-
this.
|
|
1882
|
-
setCacheManagerRef(this.
|
|
2189
|
+
this.cacheValue = createCacheManager(config);
|
|
2190
|
+
setCacheManagerRef(this.cacheValue);
|
|
1883
2191
|
}
|
|
1884
2192
|
|
|
1885
2193
|
private async initializeWorkflows(options: SonamuTaskOptions | undefined) {
|
|
1886
2194
|
const { WorkflowManager } = await import("../tasks/workflow-manager");
|
|
1887
2195
|
// NOTE: @sonamu-kit/tasks 안에선 knex config를 수정하기 때문에 connection이 아닌 config 째로 보냅니다.
|
|
1888
|
-
this.
|
|
2196
|
+
this.workflowsValue = new WorkflowManager(DB.getDBConfig("w"));
|
|
1889
2197
|
if (!options) {
|
|
1890
2198
|
return;
|
|
1891
2199
|
}
|
|
@@ -1939,7 +2247,6 @@ class SonamuClass {
|
|
|
1939
2247
|
await options.lifecycle?.onStart?.(server);
|
|
1940
2248
|
})
|
|
1941
2249
|
.catch(async (err) => {
|
|
1942
|
-
const chalk = (await import("chalk")).default;
|
|
1943
2250
|
console.error(chalk.red("Failed to start server:", err));
|
|
1944
2251
|
await shutdown();
|
|
1945
2252
|
});
|
|
@@ -1950,15 +2257,15 @@ class SonamuClass {
|
|
|
1950
2257
|
// 먼저 처리해야함.
|
|
1951
2258
|
await BaseModel.destroy();
|
|
1952
2259
|
// WebSocket shutdown first to flush telemetry
|
|
1953
|
-
if (this.
|
|
1954
|
-
await this.
|
|
2260
|
+
if (this.websocketRuntimeValue) {
|
|
2261
|
+
await this.websocketRuntimeValue.shutdown();
|
|
1955
2262
|
}
|
|
1956
|
-
this.
|
|
2263
|
+
this.websocketRuntimeValue = null;
|
|
1957
2264
|
// Then shut down remaining resources in parallel
|
|
1958
2265
|
await Promise.allSettled([
|
|
1959
|
-
this.
|
|
1960
|
-
this.
|
|
1961
|
-
this.
|
|
2266
|
+
this.workflowsValue?.destroy() ?? Promise.resolve(),
|
|
2267
|
+
this.cacheValue?.disconnect() ?? Promise.resolve(),
|
|
2268
|
+
this.devVitestManagerValue?.shutdown() ?? Promise.resolve(),
|
|
1962
2269
|
this.watcher?.close() ?? Promise.resolve(),
|
|
1963
2270
|
]);
|
|
1964
2271
|
// LogTape dispose after WS shutdown so telemetry records are flushed first
|
|
@@ -1967,7 +2274,7 @@ class SonamuClass {
|
|
|
1967
2274
|
}
|
|
1968
2275
|
|
|
1969
2276
|
function getWebSocketTelemetryContext(
|
|
1970
|
-
ws:
|
|
2277
|
+
ws: AnyWebSocketConnection,
|
|
1971
2278
|
): WebSocketTelemetryConnectionContext {
|
|
1972
2279
|
if (!isTelemetryContextProvider(ws)) {
|
|
1973
2280
|
return {};
|
|
@@ -1977,9 +2284,9 @@ function getWebSocketTelemetryContext(
|
|
|
1977
2284
|
}
|
|
1978
2285
|
|
|
1979
2286
|
function isTelemetryContextProvider(
|
|
1980
|
-
ws:
|
|
1981
|
-
): ws is
|
|
1982
|
-
return "getTelemetryContext" in ws &&
|
|
2287
|
+
ws: AnyWebSocketConnection,
|
|
2288
|
+
): ws is AnyWebSocketConnection & TelemetryContextProvider {
|
|
2289
|
+
return "getTelemetryContext" in ws && isFunctionValue(ws.getTelemetryContext);
|
|
1983
2290
|
}
|
|
1984
2291
|
|
|
1985
2292
|
export const Sonamu = new SonamuClass();
|