sonamu 0.7.2 → 0.7.4

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.
Files changed (57) hide show
  1. package/dist/api/code-converters.js +2 -2
  2. package/dist/api/config.js +2 -2
  3. package/dist/api/decorators.d.ts.map +1 -1
  4. package/dist/api/decorators.js +2 -2
  5. package/dist/api/sonamu.d.ts.map +1 -1
  6. package/dist/api/sonamu.js +3 -4
  7. package/dist/bin/cli.js +1 -17
  8. package/dist/database/base-model.types.d.ts +1 -0
  9. package/dist/database/base-model.types.d.ts.map +1 -1
  10. package/dist/database/base-model.types.js +2 -2
  11. package/dist/database/puri-wrapper.js +7 -3
  12. package/dist/database/upsert-builder.d.ts +7 -3
  13. package/dist/database/upsert-builder.d.ts.map +1 -1
  14. package/dist/database/upsert-builder.js +63 -25
  15. package/dist/entity/entity-manager.d.ts +1 -1
  16. package/dist/entity/entity.js +3 -3
  17. package/dist/migration/code-generation.d.ts.map +1 -1
  18. package/dist/migration/code-generation.js +8 -7
  19. package/dist/migration/migration-set.d.ts.map +1 -1
  20. package/dist/migration/migration-set.js +2 -25
  21. package/dist/migration/migrator.js +2 -2
  22. package/dist/migration/postgresql-schema-reader.d.ts.map +1 -1
  23. package/dist/migration/postgresql-schema-reader.js +2 -1
  24. package/dist/syncer/file-patterns.js +2 -2
  25. package/dist/syncer/syncer.js +2 -2
  26. package/dist/template/implementations/service.template.d.ts.map +1 -1
  27. package/dist/template/implementations/service.template.js +3 -2
  28. package/dist/types/types.d.ts +4 -3
  29. package/dist/types/types.d.ts.map +1 -1
  30. package/dist/types/types.js +2 -2
  31. package/dist/utils/model.d.ts +9 -2
  32. package/dist/utils/model.d.ts.map +1 -1
  33. package/dist/utils/model.js +1 -1
  34. package/dist/utils/path-utils.d.ts +1 -1
  35. package/dist/utils/path-utils.d.ts.map +1 -1
  36. package/dist/utils/path-utils.js +1 -1
  37. package/package.json +9 -10
  38. package/src/api/code-converters.ts +2 -2
  39. package/src/api/config.ts +1 -1
  40. package/src/api/decorators.ts +1 -1
  41. package/src/api/sonamu.ts +2 -5
  42. package/src/bin/cli.ts +0 -17
  43. package/src/database/base-model.types.ts +2 -0
  44. package/src/database/puri-wrapper.ts +2 -2
  45. package/src/database/upsert-builder.ts +88 -29
  46. package/src/entity/entity.ts +2 -2
  47. package/src/migration/code-generation.ts +8 -6
  48. package/src/migration/migration-set.ts +0 -20
  49. package/src/migration/migrator.ts +1 -1
  50. package/src/migration/postgresql-schema-reader.ts +1 -0
  51. package/src/shared/web.shared.ts.txt +6 -4
  52. package/src/syncer/file-patterns.ts +1 -1
  53. package/src/syncer/syncer.ts +1 -1
  54. package/src/template/implementations/service.template.ts +2 -1
  55. package/src/types/types.ts +3 -2
  56. package/src/utils/model.ts +10 -4
  57. package/src/utils/path-utils.ts +5 -2
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * **기준점**: `Sonamu.apiRootPath` (일반적으로 프로젝트의 `/api` 디렉토리)
10
10
  */
11
- export type ApiRelativePath = `${"src" | "dist"}/${string}` | "sonamu.config.ts";
11
+ export type ApiRelativePath = `${"src" | "dist"}/${string}`;
12
12
  /**
13
13
  * 앱 루트 기준 상대 경로 (api/, web/ 등 타겟 디렉토리로 시작)
14
14
  *
@@ -1 +1 @@
1
- {"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../../src/utils/path-utils.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GAAG,GAAG,KAAK,GAAG,MAAM,IAAI,MAAM,EAAE,GAAG,kBAAkB,CAAC;AAEjF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,eAAe,GAAG,GAAG,MAAM,IAAI,KAAK,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;AAEtE;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,YAAY,GAAG,IAAI,MAAM,EAAE,CAAC;AAExC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,OAAyC,GAAG,MAAM,CAMrG"}
1
+ {"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../../src/utils/path-utils.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GAAG,GAAG,KAAK,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,MAAM,MAAM,eAAe,GAAG,GAAG,MAAM,IAAI,KAAK,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;AAEtE;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,YAAY,GAAG,IAAI,MAAM,EAAE,CAAC;AAExC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,OAAyC,GAC/C,MAAM,CAMR"}
@@ -57,4 +57,4 @@ import { isHotReloadServer, isTest } from "./controller.js";
57
57
  }
58
58
  }
59
59
 
60
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91dGlscy9wYXRoLXV0aWxzLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGlzSG90UmVsb2FkU2VydmVyLCBpc1Rlc3QgfSBmcm9tIFwiLi9jb250cm9sbGVyLmpzXCI7XG5cbi8qKlxuICogQVBJIO2MqO2CpOyngCDrgrTrtoAg7IOB64yAIOqyveuhnCAoc3JjLyDrmJDripQgZGlzdC/roZwg7Iuc7J6RKVxuICpcbiAqICoq7IKs7JqpIOychOy5mCoqOiBBUEkg7Yyo7YKk7KeAIOuCtOu2gCDtjIzsnbwg7LC47KGwXG4gKiAqKuyYiOyLnCoqOlxuICogLSBgXCJzcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzXCJgXG4gKiAtIGBcImRpc3QvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLmpzXCJgXG4gKlxuICogKirquLDspIDsoJAqKjogYFNvbmFtdS5hcGlSb290UGF0aGAgKOydvOuwmOyggeycvOuhnCDtlITroZzsoJ3tirjsnZggYC9hcGlgIOuUlOugie2GoOumrClcbiAqL1xuZXhwb3J0IHR5cGUgQXBpUmVsYXRpdmVQYXRoID0gYCR7XCJzcmNcIiB8IFwiZGlzdFwifS8ke3N0cmluZ31gIHwgXCJzb25hbXUuY29uZmlnLnRzXCI7XG5cbi8qKlxuICog7JWxIOujqO2KuCDquLDspIAg7IOB64yAIOqyveuhnCAoYXBpLywgd2ViLyDrk7Eg7YOA6rKfIOuUlOugie2GoOumrOuhnCDsi5zsnpEpXG4gKlxuICogKirsgqzsmqkg7JyE7LmYKio6IOuLpOuluCDtg4Dqsp8oYXBpLCB3ZWIg65OxKeydmCDtjIzsnbwg7LC47KGwXG4gKiAqKuyYiOyLnCoqOlxuICogLSBgXCJhcGkvc3JjL2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC50c1wiYFxuICogLSBgXCJ3ZWIvc3JjL3BhZ2VzL2FkbWluL3VzZXJzL2luZGV4LnRzeFwiYFxuICogLSBgXCJhcHAvZGlzdC9pbmRleC5qc1wiYFxuICpcbiAqICoq6riw7KSA7KCQKio6IGBTb25hbXUuYXBwUm9vdFBhdGhgICjsnbzrsJjsoIHsnLzroZwg66qo64W466CI7Y+sIOujqO2KuClcbiAqL1xuZXhwb3J0IHR5cGUgQXBwUmVsYXRpdmVQYXRoID0gYCR7c3RyaW5nfS8ke1wic3JjXCIgfCBcImRpc3RcIn0vJHtzdHJpbmd9YDtcblxuLyoqXG4gKiDsi5zsiqTthZwg7KCI64yAIOqyveuhnCAo66Oo7Yq4IC8g67aA7YSwIOyLnOyekSlcbiAqXG4gKiAqKuyCrOyaqSDsnITsuZgqKjog7YyM7J287Iuc7Iqk7YWcIOyngeygkSDsoJHqt7wsIGdsb2Ig7Yyo7YS0XG4gKiAqKuyYiOyLnCoqOiBgXCIvVXNlcnMvcG90YWRvcy9Qcm9qZWN0cy9zb25hbXUvYXBpL3NyYy9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwudHNcImBcbiAqXG4gKiAqKuykkeyalCoqOlxuICogLSBpbXBvcnQg7Iuc7JeQ64qUIOuhnOuNlOqwgCDslYzslYTshJwgc3JjL2Rpc3Qg67OA7ZmY7ZW07KO866+A66GcIOyWtOuKkCDqsr3roZzrk6Ag6rCA64qlXG4gKiAtIGZzIOyngeygkSDsoJHqt7wg7Iuc7JeQ64qUIOyLpOygnCDsobTsnqztlZjripQg6rK966Gc66W8IOyCrOyaqe2VtOyVvCDtlahcbiAqICAgLSBEZXY6IHNyYy8qLnRzIOqyveuhnCDsgqzsmqlcbiAqICAgLSBQcm9kOiBkaXN0LyouanMg6rK966GcIOyCrOyaqVxuICovXG5leHBvcnQgdHlwZSBBYnNvbHV0ZVBhdGggPSBgLyR7c3RyaW5nfWA7XG5cbi8qKlxuICog7Ja065akIOqyveuhnOqwgCDrk6TslrTsmKTrk6AsIO2YhOyerCDsi6Ttlokg7ZmY6rK97JeQIOunnuuKlCDqsr3roZzroZwg67CU6r+U7KSN64uI64ukLlxuICpcbiAqIFwic3JjL2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC50c1wi6rCAIOuTpOyWtOyZlOydhCDrlYwg6rCc67CcIOuqqOuTnOudvOuptD9cbiAqIC0+IFwic3JjL2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC50c1wiXG4gKiBcInNyYy9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwudHNcIuqwgCDrk6TslrTsmZTsnYQg65WMIOuwsO2PrCDrqqjrk5zrnbzrqbQ/XG4gKiAtPiBcImRpc3QvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLmpzXCJcbiAqIFwiZGlzdC9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwuanNcIuqwgCDrk6TslrTsmZTsnYQg65WMIOqwnOuwnCDrqqjrk5zrnbzrqbQ/XG4gKiAtPiBcInNyYy9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwudHNcIlxuICogXCJkaXN0L2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC5qc1wi6rCAIOuTpOyWtOyZlOydhCDrlYwg67Cw7Y+sIOuqqOuTnOudvOuptD9cbiAqIC0+IFwiZGlzdC9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwuanNcIlxuICpcbiAqIFwiL3NyYy9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwudHNcIuqwgCDrk6TslrTsmZTsnYQg65WMIOqwnOuwnCDrqqjrk5zrnbzrqbQ/XG4gKiAtPiBcIi9zcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzXCJcbiAqIFwiL3NyYy9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwudHNcIuqwgCDrk6TslrTsmZTsnYQg65WMIOuwsO2PrCDrqqjrk5zrnbzrqbQ/XG4gKiAtPiBcIi9kaXN0L2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC5qc1wiXG4gKiBcIi9kaXN0L2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC5qc1wi6rCAIOuTpOyWtOyZlOydhCDrlYwg6rCc67CcIOuqqOuTnOudvOuptD9cbiAqIC0+IFwiL2Rpc3QvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLmpzXCJcbiAqIFwiL2Rpc3QvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLmpzXCLqsIAg65Ok7Ja07JmU7J2EIOuVjCDrsLDtj6wg66qo65Oc652866m0P1xuICogLT4gXCIvZGlzdC9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwuanNcIlxuICpcbiAqIFwiL1VzZXJzL3BvdGFkb3MvUHJvamVjdHMvc29uYW11L2FwaS9zcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzXCLqsIAg65Ok7Ja07JmU7J2EIOuVjCDqsJzrsJwg66qo65Oc652866m0P1xuICogLT4gXCIvVXNlcnMvcG90YWRvcy9Qcm9qZWN0cy9zb25hbXUvYXBpL3NyYy9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwudHNcIlxuICogXCIvVXNlcnMvcG90YWRvcy9Qcm9qZWN0cy9zb25hbXUvYXBpL3NyYy9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwudHNcIuqwgCDrk6TslrTsmZTsnYQg65WMIOuwsO2PrCDrqqjrk5zrnbzrqbQ/XG4gKiAtPiBcIi9Vc2Vycy9wb3RhZG9zL1Byb2plY3RzL3NvbmFtdS9hcGkvZGlzdC9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwuanNcIlxuICogXCIvVXNlcnMvcG90YWRvcy9Qcm9qZWN0cy9zb25hbXUvYXBpL2Rpc3QvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLmpzXCLqsIAg65Ok7Ja07JmU7J2EIOuVjCDqsJzrsJwg66qo65Oc652866m0P1xuICogLT4gXCIvVXNlcnMvcG90YWRvcy9Qcm9qZWN0cy9zb25hbXUvYXBpL3NyYy9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwudHNcIlxuICogXCIvVXNlcnMvcG90YWRvcy9Qcm9qZWN0cy9zb25hbXUvYXBpL2Rpc3QvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLmpzXCLqsIAg65Ok7Ja07JmU7J2EIOuVjCDrsLDtj6wg66qo65Oc652866m0P1xuICogLT4gXCIvVXNlcnMvcG90YWRvcy9Qcm9qZWN0cy9zb25hbXUvYXBpL2Rpc3QvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLmpzXCJcbiAqXG4gKiBcInNyYy9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwudHM/aG90PTEyMzQ1Njc4OTBcIuqwgCDrk6TslrTsmZTsnYQg65WMIOqwnOuwnCDrqqjrk5zrnbzrqbQ/XG4gKiAtPiBcInNyYy9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwudHM/aG90PTEyMzQ1Njc4OTBcIlxuICogXCJzcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzP2hvdD0xMjM0NTY3ODkwXCLqsIAg65Ok7Ja07JmU7J2EIOuVjCDrsLDtj6wg66qo65Oc652866m0P1xuICogLT4gXCJkaXN0L2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC5qcz9ob3Q9MTIzNDU2Nzg5MFwiXG4gKiBcImRpc3QvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLmpzP2hvdD0xMjM0NTY3ODkwXCLqsIAg65Ok7Ja07JmU7J2EIOuVjCDqsJzrsJwg66qo65Oc652866m0P1xuICogLT4gXCJzcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzP2hvdD0xMjM0NTY3ODkwXCJcbiAqIFwiZGlzdC9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwuanM/aG90PTEyMzQ1Njc4OTBcIuqwgCDrk6TslrTsmZTsnYQg65WMIOuwsO2PrCDrqqjrk5zrnbzrqbQ/XG4gKiAtPiBcImRpc3QvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLmpzP2hvdD0xMjM0NTY3ODkwXCJcbiAqXG4gKiBcIi9Vc2Vycy9wb3RhZG9zL1Byb2plY3RzL3NvbmFtdS9hcGkvc3JjL2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC50cz9ob3Q9MTIzNDU2Nzg5MFwi6rCAIOuTpOyWtOyZlOydhCDrlYwg6rCc67CcIOuqqOuTnOudvOuptD9cbiAqIC0+IFwiL1VzZXJzL3BvdGFkb3MvUHJvamVjdHMvc29uYW11L2FwaS9zcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzP2hvdD0xMjM0NTY3ODkwXCJcbiAqIFwiL1VzZXJzL3BvdGFkb3MvUHJvamVjdHMvc29uYW11L2FwaS9zcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzP2hvdD0xMjM0NTY3ODkwXCLqsIAg65Ok7Ja07JmU7J2EIOuVjCDrsLDtj6wg66qo65Oc652866m0P1xuICogLT4gXCIvVXNlcnMvcG90YWRvcy9Qcm9qZWN0cy9zb25hbXUvYXBpL2Rpc3QvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLmpzP2hvdD0xMjM0NTY3ODkwXCJcbiAqIFwiL1VzZXJzL3BvdGFkb3MvUHJvamVjdHMvc29uYW11L2FwaS9kaXN0L2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC5qcz9ob3Q9MTIzNDU2Nzg5MFwi6rCAIOuTpOyWtOyZlOydhCDrlYwg6rCc67CcIOuqqOuTnOudvOuptD9cbiAqIC0+IFwiL1VzZXJzL3BvdGFkb3MvUHJvamVjdHMvc29uYW11L2FwaS9zcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzP2hvdD0xMjM0NTY3ODkwXCJcbiAqIFwiL1VzZXJzL3BvdGFkb3MvUHJvamVjdHMvc29uYW11L2FwaS9kaXN0L2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC5qcz9ob3Q9MTIzNDU2Nzg5MFwi6rCAIOuTpOyWtOyZlOydhCDrlYwg67Cw7Y+sIOuqqOuTnOudvOuptD9cbiAqIC0+IFwiL1VzZXJzL3BvdGFkb3MvUHJvamVjdHMvc29uYW11L2FwaS9kaXN0L2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC5qcz9ob3Q9MTIzNDU2Nzg5MFwiXG4gKlxuICogQHBhcmFtIGFueVBhdGhcbiAqIEByZXR1cm5zXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBydW50aW1lUGF0aChhbnlQYXRoOiBzdHJpbmcsIGlzRGV2OiBib29sZWFuID0gaXNIb3RSZWxvYWRTZXJ2ZXIoKSB8fCBpc1Rlc3QoKSk6IHN0cmluZyB7XG4gIGlmIChpc0Rldikge1xuICAgIHJldHVybiBhbnlQYXRoLnJlcGxhY2UoL2Rpc3RcXC8vLCBcInNyYy9cIikucmVwbGFjZSgvXFwuanMvLCBcIi50c1wiKTtcbiAgfSBlbHNlIHtcbiAgICByZXR1cm4gYW55UGF0aC5yZXBsYWNlKC9zcmNcXC8vLCBcImRpc3QvXCIpLnJlcGxhY2UoL1xcLnRzLywgXCIuanNcIik7XG4gIH1cbn1cbiJdLCJuYW1lcyI6WyJpc0hvdFJlbG9hZFNlcnZlciIsImlzVGVzdCIsInJ1bnRpbWVQYXRoIiwiYW55UGF0aCIsImlzRGV2IiwicmVwbGFjZSJdLCJtYXBwaW5ncyI6IkFBQUEsU0FBU0EsaUJBQWlCLEVBQUVDLE1BQU0sUUFBUSxrQkFBa0I7QUF5QzVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztDQWtEQyxHQUNELE9BQU8sU0FBU0MsWUFBWUMsT0FBZSxFQUFFQyxRQUFpQkosdUJBQXVCQyxRQUFRO0lBQzNGLElBQUlHLE9BQU87UUFDVCxPQUFPRCxRQUFRRSxPQUFPLENBQUMsVUFBVSxRQUFRQSxPQUFPLENBQUMsUUFBUTtJQUMzRCxPQUFPO1FBQ0wsT0FBT0YsUUFBUUUsT0FBTyxDQUFDLFNBQVMsU0FBU0EsT0FBTyxDQUFDLFFBQVE7SUFDM0Q7QUFDRiJ9
60
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91dGlscy9wYXRoLXV0aWxzLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGlzSG90UmVsb2FkU2VydmVyLCBpc1Rlc3QgfSBmcm9tIFwiLi9jb250cm9sbGVyLmpzXCI7XG5cbi8qKlxuICogQVBJIO2MqO2CpOyngCDrgrTrtoAg7IOB64yAIOqyveuhnCAoc3JjLyDrmJDripQgZGlzdC/roZwg7Iuc7J6RKVxuICpcbiAqICoq7IKs7JqpIOychOy5mCoqOiBBUEkg7Yyo7YKk7KeAIOuCtOu2gCDtjIzsnbwg7LC47KGwXG4gKiAqKuyYiOyLnCoqOlxuICogLSBgXCJzcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzXCJgXG4gKiAtIGBcImRpc3QvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLmpzXCJgXG4gKlxuICogKirquLDspIDsoJAqKjogYFNvbmFtdS5hcGlSb290UGF0aGAgKOydvOuwmOyggeycvOuhnCDtlITroZzsoJ3tirjsnZggYC9hcGlgIOuUlOugie2GoOumrClcbiAqL1xuZXhwb3J0IHR5cGUgQXBpUmVsYXRpdmVQYXRoID0gYCR7XCJzcmNcIiB8IFwiZGlzdFwifS8ke3N0cmluZ31gO1xuXG4vKipcbiAqIOyVsSDro6jtirgg6riw7KSAIOyDgeuMgCDqsr3roZwgKGFwaS8sIHdlYi8g65OxIO2DgOqynyDrlJTroInthqDrpqzroZwg7Iuc7J6RKVxuICpcbiAqICoq7IKs7JqpIOychOy5mCoqOiDri6Trpbgg7YOA6rKfKGFwaSwgd2ViIOuTsSnsnZgg7YyM7J28IOywuOyhsFxuICogKirsmIjsi5wqKjpcbiAqIC0gYFwiYXBpL3NyYy9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwudHNcImBcbiAqIC0gYFwid2ViL3NyYy9wYWdlcy9hZG1pbi91c2Vycy9pbmRleC50c3hcImBcbiAqIC0gYFwiYXBwL2Rpc3QvaW5kZXguanNcImBcbiAqXG4gKiAqKuq4sOykgOygkCoqOiBgU29uYW11LmFwcFJvb3RQYXRoYCAo7J2867CY7KCB7Jy866GcIOuqqOuFuOugiO2PrCDro6jtirgpXG4gKi9cbmV4cG9ydCB0eXBlIEFwcFJlbGF0aXZlUGF0aCA9IGAke3N0cmluZ30vJHtcInNyY1wiIHwgXCJkaXN0XCJ9LyR7c3RyaW5nfWA7XG5cbi8qKlxuICog7Iuc7Iqk7YWcIOygiOuMgCDqsr3roZwgKOujqO2KuCAvIOu2gO2EsCDsi5zsnpEpXG4gKlxuICogKirsgqzsmqkg7JyE7LmYKio6IO2MjOydvOyLnOyKpO2FnCDsp4HsoJEg7KCR6re8LCBnbG9iIO2MqO2EtFxuICogKirsmIjsi5wqKjogYFwiL1VzZXJzL3BvdGFkb3MvUHJvamVjdHMvc29uYW11L2FwaS9zcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzXCJgXG4gKlxuICogKirspJHsmpQqKjpcbiAqIC0gaW1wb3J0IOyLnOyXkOuKlCDroZzrjZTqsIAg7JWM7JWE7IScIHNyYy9kaXN0IOuzgO2ZmO2VtOyjvOuvgOuhnCDslrTripAg6rK966Gc65OgIOqwgOuKpVxuICogLSBmcyDsp4HsoJEg7KCR6re8IOyLnOyXkOuKlCDsi6TsoJwg7KG07J6s7ZWY64qUIOqyveuhnOulvCDsgqzsmqntlbTslbwg7ZWoXG4gKiAgIC0gRGV2OiBzcmMvKi50cyDqsr3roZwg7IKs7JqpXG4gKiAgIC0gUHJvZDogZGlzdC8qLmpzIOqyveuhnCDsgqzsmqlcbiAqL1xuZXhwb3J0IHR5cGUgQWJzb2x1dGVQYXRoID0gYC8ke3N0cmluZ31gO1xuXG4vKipcbiAqIOyWtOuWpCDqsr3roZzqsIAg65Ok7Ja07Jik65OgLCDtmITsnqwg7Iuk7ZaJIO2ZmOqyveyXkCDrp57ripQg6rK966Gc66GcIOuwlOq/lOykjeuLiOuLpC5cbiAqXG4gKiBcInNyYy9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwudHNcIuqwgCDrk6TslrTsmZTsnYQg65WMIOqwnOuwnCDrqqjrk5zrnbzrqbQ/XG4gKiAtPiBcInNyYy9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwudHNcIlxuICogXCJzcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzXCLqsIAg65Ok7Ja07JmU7J2EIOuVjCDrsLDtj6wg66qo65Oc652866m0P1xuICogLT4gXCJkaXN0L2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC5qc1wiXG4gKiBcImRpc3QvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLmpzXCLqsIAg65Ok7Ja07JmU7J2EIOuVjCDqsJzrsJwg66qo65Oc652866m0P1xuICogLT4gXCJzcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzXCJcbiAqIFwiZGlzdC9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwuanNcIuqwgCDrk6TslrTsmZTsnYQg65WMIOuwsO2PrCDrqqjrk5zrnbzrqbQ/XG4gKiAtPiBcImRpc3QvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLmpzXCJcbiAqXG4gKiBcIi9zcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzXCLqsIAg65Ok7Ja07JmU7J2EIOuVjCDqsJzrsJwg66qo65Oc652866m0P1xuICogLT4gXCIvc3JjL2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC50c1wiXG4gKiBcIi9zcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzXCLqsIAg65Ok7Ja07JmU7J2EIOuVjCDrsLDtj6wg66qo65Oc652866m0P1xuICogLT4gXCIvZGlzdC9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwuanNcIlxuICogXCIvZGlzdC9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwuanNcIuqwgCDrk6TslrTsmZTsnYQg65WMIOqwnOuwnCDrqqjrk5zrnbzrqbQ/XG4gKiAtPiBcIi9kaXN0L2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC5qc1wiXG4gKiBcIi9kaXN0L2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC5qc1wi6rCAIOuTpOyWtOyZlOydhCDrlYwg67Cw7Y+sIOuqqOuTnOudvOuptD9cbiAqIC0+IFwiL2Rpc3QvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLmpzXCJcbiAqXG4gKiBcIi9Vc2Vycy9wb3RhZG9zL1Byb2plY3RzL3NvbmFtdS9hcGkvc3JjL2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC50c1wi6rCAIOuTpOyWtOyZlOydhCDrlYwg6rCc67CcIOuqqOuTnOudvOuptD9cbiAqIC0+IFwiL1VzZXJzL3BvdGFkb3MvUHJvamVjdHMvc29uYW11L2FwaS9zcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzXCJcbiAqIFwiL1VzZXJzL3BvdGFkb3MvUHJvamVjdHMvc29uYW11L2FwaS9zcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzXCLqsIAg65Ok7Ja07JmU7J2EIOuVjCDrsLDtj6wg66qo65Oc652866m0P1xuICogLT4gXCIvVXNlcnMvcG90YWRvcy9Qcm9qZWN0cy9zb25hbXUvYXBpL2Rpc3QvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLmpzXCJcbiAqIFwiL1VzZXJzL3BvdGFkb3MvUHJvamVjdHMvc29uYW11L2FwaS9kaXN0L2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC5qc1wi6rCAIOuTpOyWtOyZlOydhCDrlYwg6rCc67CcIOuqqOuTnOudvOuptD9cbiAqIC0+IFwiL1VzZXJzL3BvdGFkb3MvUHJvamVjdHMvc29uYW11L2FwaS9zcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzXCJcbiAqIFwiL1VzZXJzL3BvdGFkb3MvUHJvamVjdHMvc29uYW11L2FwaS9kaXN0L2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC5qc1wi6rCAIOuTpOyWtOyZlOydhCDrlYwg67Cw7Y+sIOuqqOuTnOudvOuptD9cbiAqIC0+IFwiL1VzZXJzL3BvdGFkb3MvUHJvamVjdHMvc29uYW11L2FwaS9kaXN0L2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC5qc1wiXG4gKlxuICogXCJzcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzP2hvdD0xMjM0NTY3ODkwXCLqsIAg65Ok7Ja07JmU7J2EIOuVjCDqsJzrsJwg66qo65Oc652866m0P1xuICogLT4gXCJzcmMvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLnRzP2hvdD0xMjM0NTY3ODkwXCJcbiAqIFwic3JjL2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC50cz9ob3Q9MTIzNDU2Nzg5MFwi6rCAIOuTpOyWtOyZlOydhCDrlYwg67Cw7Y+sIOuqqOuTnOudvOuptD9cbiAqIC0+IFwiZGlzdC9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwuanM/aG90PTEyMzQ1Njc4OTBcIlxuICogXCJkaXN0L2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC5qcz9ob3Q9MTIzNDU2Nzg5MFwi6rCAIOuTpOyWtOyZlOydhCDrlYwg6rCc67CcIOuqqOuTnOudvOuptD9cbiAqIC0+IFwic3JjL2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC50cz9ob3Q9MTIzNDU2Nzg5MFwiXG4gKiBcImRpc3QvYXBwbGljYXRpb24vdXNlci91c2VyLm1vZGVsLmpzP2hvdD0xMjM0NTY3ODkwXCLqsIAg65Ok7Ja07JmU7J2EIOuVjCDrsLDtj6wg66qo65Oc652866m0P1xuICogLT4gXCJkaXN0L2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC5qcz9ob3Q9MTIzNDU2Nzg5MFwiXG4gKlxuICogXCIvVXNlcnMvcG90YWRvcy9Qcm9qZWN0cy9zb25hbXUvYXBpL3NyYy9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwudHM/aG90PTEyMzQ1Njc4OTBcIuqwgCDrk6TslrTsmZTsnYQg65WMIOqwnOuwnCDrqqjrk5zrnbzrqbQ/XG4gKiAtPiBcIi9Vc2Vycy9wb3RhZG9zL1Byb2plY3RzL3NvbmFtdS9hcGkvc3JjL2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC50cz9ob3Q9MTIzNDU2Nzg5MFwiXG4gKiBcIi9Vc2Vycy9wb3RhZG9zL1Byb2plY3RzL3NvbmFtdS9hcGkvc3JjL2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC50cz9ob3Q9MTIzNDU2Nzg5MFwi6rCAIOuTpOyWtOyZlOydhCDrlYwg67Cw7Y+sIOuqqOuTnOudvOuptD9cbiAqIC0+IFwiL1VzZXJzL3BvdGFkb3MvUHJvamVjdHMvc29uYW11L2FwaS9kaXN0L2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC5qcz9ob3Q9MTIzNDU2Nzg5MFwiXG4gKiBcIi9Vc2Vycy9wb3RhZG9zL1Byb2plY3RzL3NvbmFtdS9hcGkvZGlzdC9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwuanM/aG90PTEyMzQ1Njc4OTBcIuqwgCDrk6TslrTsmZTsnYQg65WMIOqwnOuwnCDrqqjrk5zrnbzrqbQ/XG4gKiAtPiBcIi9Vc2Vycy9wb3RhZG9zL1Byb2plY3RzL3NvbmFtdS9hcGkvc3JjL2FwcGxpY2F0aW9uL3VzZXIvdXNlci5tb2RlbC50cz9ob3Q9MTIzNDU2Nzg5MFwiXG4gKiBcIi9Vc2Vycy9wb3RhZG9zL1Byb2plY3RzL3NvbmFtdS9hcGkvZGlzdC9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwuanM/aG90PTEyMzQ1Njc4OTBcIuqwgCDrk6TslrTsmZTsnYQg65WMIOuwsO2PrCDrqqjrk5zrnbzrqbQ/XG4gKiAtPiBcIi9Vc2Vycy9wb3RhZG9zL1Byb2plY3RzL3NvbmFtdS9hcGkvZGlzdC9hcHBsaWNhdGlvbi91c2VyL3VzZXIubW9kZWwuanM/aG90PTEyMzQ1Njc4OTBcIlxuICpcbiAqIEBwYXJhbSBhbnlQYXRoXG4gKiBAcmV0dXJuc1xuICovXG5leHBvcnQgZnVuY3Rpb24gcnVudGltZVBhdGgoXG4gIGFueVBhdGg6IHN0cmluZyxcbiAgaXNEZXY6IGJvb2xlYW4gPSBpc0hvdFJlbG9hZFNlcnZlcigpIHx8IGlzVGVzdCgpLFxuKTogc3RyaW5nIHtcbiAgaWYgKGlzRGV2KSB7XG4gICAgcmV0dXJuIGFueVBhdGgucmVwbGFjZSgvZGlzdFxcLy8sIFwic3JjL1wiKS5yZXBsYWNlKC9cXC5qcy8sIFwiLnRzXCIpO1xuICB9IGVsc2Uge1xuICAgIHJldHVybiBhbnlQYXRoLnJlcGxhY2UoL3NyY1xcLy8sIFwiZGlzdC9cIikucmVwbGFjZSgvXFwudHMvLCBcIi5qc1wiKTtcbiAgfVxufVxuIl0sIm5hbWVzIjpbImlzSG90UmVsb2FkU2VydmVyIiwiaXNUZXN0IiwicnVudGltZVBhdGgiLCJhbnlQYXRoIiwiaXNEZXYiLCJyZXBsYWNlIl0sIm1hcHBpbmdzIjoiQUFBQSxTQUFTQSxpQkFBaUIsRUFBRUMsTUFBTSxRQUFRLGtCQUFrQjtBQXlDNUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0NBa0RDLEdBQ0QsT0FBTyxTQUFTQyxZQUNkQyxPQUFlLEVBQ2ZDLFFBQWlCSix1QkFBdUJDLFFBQVE7SUFFaEQsSUFBSUcsT0FBTztRQUNULE9BQU9ELFFBQVFFLE9BQU8sQ0FBQyxVQUFVLFFBQVFBLE9BQU8sQ0FBQyxRQUFRO0lBQzNELE9BQU87UUFDTCxPQUFPRixRQUFRRSxPQUFPLENBQUMsU0FBUyxTQUFTQSxPQUFPLENBQUMsUUFBUTtJQUMzRDtBQUNGIn0=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonamu",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "description": "Sonamu — TypeScript Fullstack API Framework",
5
5
  "keywords": [
6
6
  "typescript",
@@ -30,9 +30,8 @@
30
30
  },
31
31
  "license": "MIT",
32
32
  "author": {
33
- "name": "Minsang Kim",
34
- "email": "minsangk@me.com",
35
- "url": "https://twitter.com/minsangk"
33
+ "name": "CartaNova <dev@cartanova.ai>",
34
+ "url": "https://cartanova.ai"
36
35
  },
37
36
  "repository": {
38
37
  "type": "git",
@@ -77,9 +76,9 @@
77
76
  "tsicli": "^1.0.5",
78
77
  "vitest": "^4.0.10",
79
78
  "zod": "^4.1.12",
79
+ "@sonamu-kit/ts-loader": "^2.1.3",
80
80
  "@sonamu-kit/hmr-hook": "^0.4.1",
81
- "@sonamu-kit/hmr-runner": "^0.1.1",
82
- "@sonamu-kit/ts-loader": "^2.1.3"
81
+ "@sonamu-kit/hmr-runner": "^0.1.1"
83
82
  },
84
83
  "devDependencies": {
85
84
  "@biomejs/biome": "^2.3.7",
@@ -100,10 +99,10 @@
100
99
  "fastify": "^4.23.2",
101
100
  "knex": "^3.1.0",
102
101
  "typescript": "^5.9.3",
103
- "@ai-sdk/openai": "3.0.0-beta.75",
104
- "@ai-sdk/provider": "3.0.0-beta.22",
105
- "@ai-sdk/provider-utils": "4.0.0-beta.40",
106
- "ai": "^6.0.0-beta.99"
102
+ "@ai-sdk/openai": "^3.0.0-beta.75",
103
+ "@ai-sdk/provider": "^3.0.0-beta.22",
104
+ "@ai-sdk/provider-utils": "^4.0.0-beta.40",
105
+ "ai": "^6.0.0-beta.138"
107
106
  },
108
107
  "peerDependenciesMeta": {
109
108
  "@ai-sdk/openai": {
@@ -294,11 +294,11 @@ export function apiParamTypeToTsType(paramType: ApiParamType, injectImportKeys:
294
294
  } else if (ApiParamType.isTupleType(paramType)) {
295
295
  return `[ ${paramType.elements.map((elem) => apiParamTypeToTsType(elem, injectImportKeys))} ]`;
296
296
  } else if (ApiParamType.isTypeParam(paramType)) {
297
- return `<${paramType.id}${
297
+ return `${paramType.id}${
298
298
  paramType.constraint
299
299
  ? ` extends ${apiParamTypeToTsType(paramType.constraint, injectImportKeys)}`
300
300
  : ""
301
- }>`;
301
+ }`;
302
302
  } else {
303
303
  throw new Error(`resolve 불가 ApiParamType ${paramType}`);
304
304
  }
package/src/api/config.ts CHANGED
@@ -126,7 +126,7 @@ export async function loadConfig(rootPath: string): Promise<SonamuConfig> {
126
126
  const start = performance.now();
127
127
  const configPath =
128
128
  process.env.HOT === "yes" || process.env.VITEST === "true"
129
- ? `${rootPath}/sonamu.config.ts`
129
+ ? `${rootPath}/src/sonamu.config.ts`
130
130
  : `${rootPath}/dist/sonamu.config.js`;
131
131
  const { default: config } = await import(`file://${configPath}`);
132
132
  const importTime = performance.now() - start;
@@ -10,10 +10,10 @@ import {
10
10
  type PuriWrapper,
11
11
  type TransactionalOptions,
12
12
  } from "../database/puri-wrapper";
13
+ import { UpsertBuilder } from "../database/upsert-builder";
13
14
  import type { ApiParam, ApiParamType } from "../types/types";
14
15
  import type { UploadContext } from "./context";
15
16
  import { Sonamu } from "./sonamu";
16
- import { UpsertBuilder } from "../database/upsert-builder";
17
17
 
18
18
  export interface GuardKeys {
19
19
  query: true;
package/src/api/sonamu.ts CHANGED
@@ -468,10 +468,7 @@ class SonamuClass {
468
468
  }
469
469
 
470
470
  async startWatcher(): Promise<void> {
471
- const watchPath = [
472
- path.join(this.apiRootPath, "src"),
473
- path.join(this.apiRootPath, "sonamu.config.ts"),
474
- ];
471
+ const watchPath = [path.join(this.apiRootPath, "src")];
475
472
 
476
473
  const chokidar = (await import("chokidar")).default;
477
474
  this.watcher = chokidar.watch(watchPath, {
@@ -494,7 +491,7 @@ class SonamuClass {
494
491
 
495
492
  try {
496
493
  // sonamu.config.ts 변경 시 재시작
497
- const isConfigTs = filePath === path.join(this.apiRootPath, "sonamu.config.ts");
494
+ const isConfigTs = filePath === path.join(this.apiRootPath, "src", "sonamu.config.ts");
498
495
 
499
496
  if (isConfigTs) {
500
497
  const relativePath = filePath.replace(this.apiRootPath, "api");
package/src/bin/cli.ts CHANGED
@@ -216,23 +216,6 @@ async function build() {
216
216
  process.exit(1);
217
217
  }
218
218
 
219
- // sonamu.config.ts만 따로 빌드합니다.
220
- // 이 친구는 src에 들어있지 않기 때문에 SWC_BUILD_COMMAND로 빌드되지 않습니다.
221
- // 따라서 따로 빌드해줍니다.
222
- try {
223
- const configPath = path.join(apiRoot, "sonamu.config.ts");
224
- if (await exists(configPath)) {
225
- console.log(chalk.blue("Building sonamu.config.ts..."));
226
- execSync(`swc ${configPath} -o ${BUILD_DIR}/sonamu.config.js`, {
227
- cwd: apiRoot,
228
- stdio: "inherit",
229
- });
230
- }
231
- } catch (error) {
232
- console.error(chalk.red("Building sonamu.config.ts failed."), error);
233
- process.exit(1);
234
- }
235
-
236
219
  // 마지막에는 타입 체크를 해요.
237
220
  try {
238
221
  console.log(chalk.blue("Checking types with tsc..."));
@@ -1,3 +1,5 @@
1
+ /** biome-ignore-all lint/suspicious/noExplicitAny: Puri의 타입은 개별 모델에서 확정되므로 BaseModel에서는 any를 허용함 */
2
+
1
3
  /**
2
4
  * BaseModel 타입 시스템
3
5
  *
@@ -148,7 +148,7 @@ export class PuriWrapper<TSchema extends DatabaseSchemaExtend = DatabaseSchemaEx
148
148
  }
149
149
 
150
150
  ubUpsert(tableName: TableName<TSchema>, chunkSize?: number): Promise<number[]> {
151
- return this.upsertBuilder.upsert(this.knex, tableName, chunkSize);
151
+ return this.upsertBuilder.upsert(this.knex, tableName, { chunkSize });
152
152
  }
153
153
 
154
154
  ubInsertOnly(tableName: TableName<TSchema>, chunkSize?: number): Promise<number[]> {
@@ -160,7 +160,7 @@ export class PuriWrapper<TSchema extends DatabaseSchemaExtend = DatabaseSchemaEx
160
160
  mode: "upsert" | "insert",
161
161
  chunkSize?: number,
162
162
  ): Promise<number[]> {
163
- return this.upsertBuilder.upsertOrInsert(this.knex, tableName, mode, chunkSize);
163
+ return this.upsertBuilder.upsertOrInsert(this.knex, tableName, mode, { chunkSize });
164
164
  }
165
165
 
166
166
  ubUpdateBatch(
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from "crypto";
2
2
  import type { Knex } from "knex";
3
- import { unique } from "radashi";
3
+ import { isArray, unique } from "radashi";
4
4
  import { EntityManager } from "../entity/entity-manager";
5
5
  import { Naite } from "../naite/naite";
6
6
  import { assertDefined, chunk, nonNullable } from "../utils/utils";
@@ -17,6 +17,10 @@ export type UBRef = {
17
17
  of: string;
18
18
  use?: string;
19
19
  };
20
+ type UpsertOptions = {
21
+ chunkSize?: number;
22
+ cleanOrphans?: string | string[]; // FK 컬럼명(들)
23
+ };
20
24
  export function isRefField(field: unknown): field is UBRef {
21
25
  return (
22
26
  field !== undefined &&
@@ -150,18 +154,37 @@ export class UpsertBuilder {
150
154
  return result;
151
155
  }
152
156
 
153
- async upsert(wdb: Knex, tableName: string, chunkSize?: number): Promise<number[]> {
154
- return this.upsertOrInsert(wdb, tableName, "upsert", chunkSize);
157
+ async upsert(
158
+ wdb: Knex,
159
+ tableName: string,
160
+ optionsOrChunkSize?: UpsertOptions,
161
+ ): Promise<number[]> {
162
+ // 숫자면 { chunkSize: n } 으로 변환
163
+ const options =
164
+ typeof optionsOrChunkSize === "number"
165
+ ? { chunkSize: optionsOrChunkSize }
166
+ : optionsOrChunkSize;
167
+
168
+ return this.upsertOrInsert(wdb, tableName, "upsert", options);
155
169
  }
156
- async insertOnly(wdb: Knex, tableName: string, chunkSize?: number): Promise<number[]> {
157
- return this.upsertOrInsert(wdb, tableName, "insert", chunkSize);
170
+ async insertOnly(
171
+ wdb: Knex,
172
+ tableName: string,
173
+ optionsOrChunkSize?: UpsertOptions | number,
174
+ ): Promise<number[]> {
175
+ const options =
176
+ typeof optionsOrChunkSize === "number"
177
+ ? { chunkSize: optionsOrChunkSize }
178
+ : optionsOrChunkSize;
179
+
180
+ return this.upsertOrInsert(wdb, tableName, "insert", options);
158
181
  }
159
182
 
160
183
  async upsertOrInsert(
161
184
  wdb: Knex,
162
185
  tableName: string,
163
186
  mode: "upsert" | "insert",
164
- chunkSize?: number,
187
+ options?: UpsertOptions,
165
188
  ): Promise<number[]> {
166
189
  if (this.hasTable(tableName) === false) {
167
190
  return [];
@@ -244,43 +267,47 @@ export class UpsertBuilder {
244
267
  });
245
268
 
246
269
  // 현재 레벨 upsert
270
+ const chunkSize = options?.chunkSize;
247
271
  const levelChunks = chunkSize ? chunk(resolvedRows, chunkSize) : [resolvedRows];
248
- const selectFields = unique(["uuid", "id", ...extractFields]);
272
+ const selectFields = unique(["id", ...extractFields]);
249
273
 
250
274
  for (const dataChunk of levelChunks) {
251
275
  if (dataChunk.length === 0) continue;
252
276
 
253
- let resultRows: { uuid: string; id: number; [key: string]: unknown }[];
277
+ // uuid 별도로 보관하고, DB에 저장할 데이터에서 제거
278
+ const originalUuids = dataChunk.map((r) => r.uuid as string);
279
+ const dataForDb = dataChunk.map(({ uuid, ...rest }) => rest);
254
280
 
255
- if (mode === "insert") {
256
- // INSERT 모드
257
- await wdb.insert(dataChunk).into(tableName);
281
+ let resultRows: { id: number; [key: string]: unknown }[];
258
282
 
259
- const uuids = dataChunk.map((r) => r.uuid);
260
- resultRows = await wdb(tableName)
261
- .select(selectFields)
262
- .whereIn("uuid", uuids as readonly string[]);
283
+ if (mode === "insert") {
284
+ // INSERT 모드 - RETURNING 사용
285
+ resultRows = await wdb.insert(dataForDb).into(tableName).returning(selectFields);
263
286
  } else {
264
- // UPSERT 모드: onConflict 중복 처리
287
+ // UPSERT 모드 - onConflict 사용
265
288
  const conflictColumns = table.uniqueIndexes[0].columns;
266
- const updateColumns = Object.keys(dataChunk[0]).filter(
267
- (col) => col !== "uuid" && !conflictColumns.includes(col),
289
+ const updateColumns = Object.keys(dataForDb[0]).filter(
290
+ (col) => !conflictColumns.includes(col),
268
291
  );
269
292
 
270
- const query = wdb.insert(dataChunk).into(tableName).onConflict(conflictColumns);
293
+ // updateColumns가 비어있어도 merge()를 사용하여 모든 행이 RETURNING되도록 보장
294
+ const mergeColumns = updateColumns.length > 0 ? updateColumns : conflictColumns;
271
295
 
272
- // updateColumns 유무에 따라 ignore/merge 선택하고 RETURNING으로 결과 받기
273
- if (updateColumns.length === 0) {
274
- resultRows = await query.ignore().returning(selectFields);
275
- } else {
276
- resultRows = await query.merge(updateColumns).returning(selectFields);
277
- }
296
+ resultRows = await wdb
297
+ .insert(dataForDb)
298
+ .into(tableName)
299
+ .onConflict(conflictColumns)
300
+ .merge(mergeColumns)
301
+ .returning(selectFields);
278
302
  }
279
303
 
280
- // 양쪽 모드 공통 처리
281
- for (const row of resultRows) {
282
- uuidMap.set(row.uuid, row);
283
- allIds.push(row.id);
304
+ if (originalUuids.length !== resultRows.length) {
305
+ throw new Error(`${tableName}: register/returning 불일치`);
306
+ }
307
+
308
+ for (let i = 0; i < resultRows.length; i++) {
309
+ uuidMap.set(originalUuids[i], resultRows[i]);
310
+ allIds.push(resultRows[i].id);
284
311
  }
285
312
  }
286
313
  }
@@ -311,6 +338,38 @@ export class UpsertBuilder {
311
338
  });
312
339
  }
313
340
 
341
+ if (options?.cleanOrphans) {
342
+ const cleanOrphans = options.cleanOrphans;
343
+ const fkColumns = isArray(cleanOrphans) ? cleanOrphans : [cleanOrphans];
344
+
345
+ // 현재 register된 레코드들의 FK 값들 추출
346
+ const fkConditions = fkColumns.map((fkCol) => {
347
+ const fkValues = [...new Set(table.rows.map((row) => row[fkCol]).filter((v) => v != null))];
348
+ return { column: fkCol, values: fkValues };
349
+ });
350
+
351
+ // 모든 FK 컬럼에 값이 있는 경우에만 삭제 실행
352
+ if (fkConditions.every((fc) => fc.values.length > 0)) {
353
+ let deleteQuery = wdb(tableName);
354
+
355
+ // 각 FK 컬럼에 대한 WHERE IN 조건 추가
356
+ for (const { column, values } of fkConditions) {
357
+ deleteQuery = deleteQuery.whereIn(column, values);
358
+ }
359
+
360
+ // 방금 upsert한 ID는 제외
361
+ deleteQuery = deleteQuery.whereNotIn("id", allIds);
362
+
363
+ const deletedCount = await deleteQuery.delete();
364
+
365
+ Naite.t("puri:ub-clean-orphans", {
366
+ tableName,
367
+ cleanOrphans: fkColumns,
368
+ deletedCount,
369
+ });
370
+ }
371
+ }
372
+
314
373
  // 해당 테이블의 데이터 초기화
315
374
  table.rows = [];
316
375
  table.references.clear();
@@ -524,8 +524,8 @@ export class Entity {
524
524
  // 일반 prop 처리
525
525
  if (key === "") {
526
526
  return group.map((propName) => {
527
- // uuid 개별 처리
528
- if (propName === "uuid") {
527
+ // FIXME: 이거 나중에 없애야함
528
+ if (propName === "말도안되는프롭명__이거왜타입처리가꼬여서이러지?") {
529
529
  return {
530
530
  nodeType: "plain" as const,
531
531
  prop: {
@@ -146,15 +146,15 @@ function genIndexDefinition(index: MigrationIndex, table: string) {
146
146
  };
147
147
 
148
148
  if (index.type === "fulltext" && index.parser === "ngram") {
149
- const indexName = `${table}_${index.columns.join("_")}_index`;
150
- return `await knex.raw(\`ALTER TABLE ${table} ADD FULLTEXT INDEX ${indexName} (${index.columns.join(
149
+ return `await knex.raw(\`ALTER TABLE ${table} ADD FULLTEXT INDEX ${index.name} (${index.columns.join(
151
150
  ", ",
152
151
  )}) WITH PARSER ngram\`);`;
153
152
  }
154
153
 
155
154
  return `table.${methodMap[index.type]}([${index.columns
156
155
  .map((col) => `'${col}'`)
157
- .join(",")}]${index.type === "fulltext" ? ", undefined, 'FULLTEXT'" : ""})`;
156
+ .join(",")}], '${index.name}'${index.type === "fulltext" ? ", 'FULLTEXT'" : ""}
157
+ );`;
158
158
  }
159
159
 
160
160
  /**
@@ -309,6 +309,8 @@ async function generateAlterCode_ColumnAndIndexes(
309
309
  });
310
310
  // Naite.t("migrator:generateAlterCode_ColumnAndIndexes:alterColumnsTo", alterColumnsTo);
311
311
 
312
+ // TODO: 인덱스명 변경된 경우 처리
313
+
312
314
  const lines: string[] = [
313
315
  'import { Knex } from "knex";',
314
316
  "",
@@ -536,7 +538,7 @@ function genIndexDropDefinition(index: MigrationIndex) {
536
538
 
537
539
  return `table.drop${methodMap[index.type]}([${index.columns
538
540
  .map((columnName) => `'${columnName}'`)
539
- .join(",")}])`;
541
+ .join(",")}], '${index.name}')`;
540
542
  }
541
543
 
542
544
  /**
@@ -723,10 +725,10 @@ export async function generateAlterCode(
723
725
  */
724
726
 
725
727
  const entityIndexes = alphabetical(entitySet.indexes, (a) =>
726
- [a.type, ...a.columns.sort((c1, c2) => (c1 > c2 ? 1 : -1))].join("-"),
728
+ [a.type, ...a.columns].join("-"),
727
729
  );
728
730
  const dbIndexes = alphabetical(dbSet.indexes, (a) =>
729
- [a.type, ...a.columns.sort((c1, c2) => (c1 > c2 ? 1 : -1))].join("-"),
731
+ [a.type, ...a.columns].join("-"),
730
732
  );
731
733
 
732
734
  const replaceNoActionOnMySQL = (f: MigrationForeign) => {
@@ -88,10 +88,6 @@ export function getMigrationSetFromEntity(entity: Entity): MigrationSetAndJoinTa
88
88
  r.joinTables.push({
89
89
  table: through.from.split(".")[0],
90
90
  indexes: [
91
- {
92
- type: "unique",
93
- columns: ["uuid"],
94
- },
95
91
  // 조인 테이블에 걸린 인덱스 찾아와서 연결
96
92
  ...entity.indexes
97
93
  .filter((index) => index.columns.find((col) => col.includes(`${prop.joinTable}.`)))
@@ -113,11 +109,6 @@ export function getMigrationSetFromEntity(entity: Entity): MigrationSetAndJoinTa
113
109
  nullable: false,
114
110
  } as MigrationColumn;
115
111
  }),
116
- {
117
- name: "uuid",
118
- nullable: true,
119
- type: "uuid",
120
- },
121
112
  ],
122
113
  foreigns: fields.map((field) => {
123
114
  // 현재 필드가 어떤 테이블에 속하는지 판단
@@ -175,17 +166,6 @@ export function getMigrationSetFromEntity(entity: Entity): MigrationSetAndJoinTa
175
166
  index.columns.find((col) => col.includes(".") === false),
176
167
  );
177
168
 
178
- // uuid
179
- migrationSet.columns = migrationSet.columns.concat({
180
- name: "uuid",
181
- nullable: true,
182
- type: "uuid",
183
- } as MigrationColumn);
184
- migrationSet.indexes = migrationSet.indexes.concat({
185
- type: "unique",
186
- columns: ["uuid"],
187
- } as MigrationIndex);
188
-
189
169
  return migrationSet;
190
170
  }
191
171
 
@@ -341,7 +341,7 @@ export class Migrator {
341
341
  ...tables[0],
342
342
  indexes: unique(
343
343
  tables.flatMap((t) => t.indexes),
344
- (index) => [index.type, ...index.columns.sort()].join("-"),
344
+ (index) => [index.type, ...index.columns].join("-"),
345
345
  ),
346
346
  };
347
347
  });
@@ -109,6 +109,7 @@ class PostgreSQLSchemaReaderClass {
109
109
 
110
110
  return {
111
111
  type,
112
+ name: indexName,
112
113
  columns: currentIndexes.map((idx) => idx.column_name),
113
114
  };
114
115
  });
@@ -86,10 +86,12 @@ export function defaultCatch(e: any) {
86
86
  /*
87
87
  Isomorphic Types
88
88
  */
89
- export type ListResult<T> = {
90
- rows: T[];
91
- total?: number;
92
- };
89
+ export type ListResult<LP extends { queryMode?: SonamuQueryMode }, T> = LP["queryMode"] extends "list"
90
+ ? { rows: T[] }
91
+ : LP["queryMode"] extends "count"
92
+ ? { total: number }
93
+ : { rows: T[]; total: number };
94
+
93
95
  export const SonamuQueryMode = z.enum(["both", "list", "count"]);
94
96
  export type SonamuQueryMode = z.infer<typeof SonamuQueryMode>;
95
97
 
@@ -30,7 +30,7 @@ export const checksumPatternGroup: GlobPattern<ApiRelativePath> = {
30
30
  model: "src/application/**/*.model.ts",
31
31
  frame: "src/application/**/*.frame.ts",
32
32
  functions: "src/application/**/*.functions.ts",
33
- config: "sonamu.config.ts",
33
+ config: "src/sonamu.config.ts",
34
34
  };
35
35
 
36
36
  /**
@@ -165,7 +165,7 @@ export class Syncer {
165
165
  }
166
166
 
167
167
  // 이건 프로젝트에 .ts 소스 코드 파일을 생성하는 것이므로 src의 .ts 경로로 갑니다.
168
- const destPath = path.join(Sonamu.appRootPath, target, "./sonamu.shared.ts");
168
+ const destPath = path.join(Sonamu.appRootPath, target, "./src/services/sonamu.shared.ts");
169
169
 
170
170
  // 정말 혹시나지만 target 디렉토리는 있어도 src/services 디렉토리는 없을 수 있으므로 미리 생성해줍니다.
171
171
  if (!(await exists(path.dirname(destPath)))) {
@@ -85,11 +85,12 @@ export class Template__service extends Template {
85
85
  );
86
86
 
87
87
  // 파라미터 타입 정의
88
- const typeParamsDef = api.typeParameters
88
+ const typeParametersAsTsType = api.typeParameters
89
89
  .map((typeParam) => {
90
90
  return apiParamTypeToTsType(typeParam, importKeys);
91
91
  })
92
92
  .join(", ");
93
+ const typeParamsDef = typeParametersAsTsType ? `<${typeParametersAsTsType}>` : "";
93
94
  typeParamNames = typeParamNames.concat(
94
95
  api.typeParameters.map((typeParam) => typeParam.id),
95
96
  );
@@ -175,7 +175,7 @@ export type EntityProp =
175
175
  export type EntityIndex = {
176
176
  type: "index" | "unique" | "fulltext";
177
177
  columns: string[];
178
- name?: string;
178
+ name: string;
179
179
  parser?: "built-in" | "ngram";
180
180
  };
181
181
  export type EntityJson = {
@@ -451,6 +451,7 @@ export type MigrationColumn = {
451
451
  scale?: number;
452
452
  };
453
453
  export type MigrationIndex = {
454
+ name: string;
454
455
  columns: string[];
455
456
  type: "unique" | "index" | "fulltext";
456
457
  parser?: "built-in" | "ngram";
@@ -942,7 +943,7 @@ const EntityIndexSchema = z
942
943
  .object({
943
944
  type: z.enum(["index", "unique", "fulltext"]),
944
945
  columns: z.array(z.string()),
945
- name: z.string().optional(),
946
+ name: z.string().min(1).max(63),
946
947
  parser: z.enum(["built-in", "ngram"]).optional(),
947
948
  })
948
949
  .strict();
@@ -1,7 +1,13 @@
1
- export type ListResult<T> = {
2
- rows: T[];
3
- total?: number;
4
- };
1
+ import type { SonamuQueryMode } from "..";
2
+
3
+ export type ListResult<
4
+ LP extends { queryMode?: SonamuQueryMode },
5
+ T,
6
+ > = LP["queryMode"] extends "list"
7
+ ? { rows: T[] }
8
+ : LP["queryMode"] extends "count"
9
+ ? { total: number }
10
+ : { rows: T[]; total: number };
5
11
 
6
12
  export type ArrayOr<T> = T | T[];
7
13
 
@@ -10,7 +10,7 @@ import { isHotReloadServer, isTest } from "./controller.js";
10
10
  *
11
11
  * **기준점**: `Sonamu.apiRootPath` (일반적으로 프로젝트의 `/api` 디렉토리)
12
12
  */
13
- export type ApiRelativePath = `${"src" | "dist"}/${string}` | "sonamu.config.ts";
13
+ export type ApiRelativePath = `${"src" | "dist"}/${string}`;
14
14
 
15
15
  /**
16
16
  * 앱 루트 기준 상대 경로 (api/, web/ 등 타겟 디렉토리로 시작)
@@ -90,7 +90,10 @@ export type AbsolutePath = `/${string}`;
90
90
  * @param anyPath
91
91
  * @returns
92
92
  */
93
- export function runtimePath(anyPath: string, isDev: boolean = isHotReloadServer() || isTest()): string {
93
+ export function runtimePath(
94
+ anyPath: string,
95
+ isDev: boolean = isHotReloadServer() || isTest(),
96
+ ): string {
94
97
  if (isDev) {
95
98
  return anyPath.replace(/dist\//, "src/").replace(/\.js/, ".ts");
96
99
  } else {