sonamu 0.7.18 → 0.7.20

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 (67) hide show
  1. package/dist/api/config.d.ts +21 -3
  2. package/dist/api/config.d.ts.map +1 -1
  3. package/dist/api/config.js +1 -1
  4. package/dist/api/context.d.ts +3 -3
  5. package/dist/api/context.d.ts.map +1 -1
  6. package/dist/api/context.js +1 -1
  7. package/dist/api/decorators.d.ts.map +1 -1
  8. package/dist/api/decorators.js +4 -8
  9. package/dist/api/index.d.ts +0 -2
  10. package/dist/api/index.d.ts.map +1 -1
  11. package/dist/api/index.js +1 -3
  12. package/dist/api/sonamu.d.ts +5 -3
  13. package/dist/api/sonamu.d.ts.map +1 -1
  14. package/dist/api/sonamu.js +10 -8
  15. package/dist/bin/cli.js +3 -3
  16. package/dist/database/db.d.ts +2 -2
  17. package/dist/database/db.d.ts.map +1 -1
  18. package/dist/database/db.js +23 -30
  19. package/dist/index.d.ts +0 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +1 -2
  22. package/dist/storage/drivers.d.ts +14 -0
  23. package/dist/storage/drivers.d.ts.map +1 -0
  24. package/dist/storage/drivers.js +11 -0
  25. package/dist/storage/index.d.ts +5 -0
  26. package/dist/storage/index.d.ts.map +1 -0
  27. package/dist/storage/index.js +6 -0
  28. package/dist/storage/storage-manager.d.ts +21 -0
  29. package/dist/storage/storage-manager.d.ts.map +1 -0
  30. package/dist/storage/storage-manager.js +33 -0
  31. package/dist/storage/types.d.ts +12 -0
  32. package/dist/storage/types.d.ts.map +1 -0
  33. package/dist/storage/types.js +5 -0
  34. package/dist/storage/uploaded-file.d.ts +35 -0
  35. package/dist/storage/uploaded-file.d.ts.map +1 -0
  36. package/dist/storage/uploaded-file.js +58 -0
  37. package/dist/template/implementations/services.template.d.ts.map +1 -1
  38. package/dist/template/implementations/services.template.js +8 -5
  39. package/dist/testing/fixture-manager.d.ts.map +1 -1
  40. package/dist/testing/fixture-manager.js +4 -4
  41. package/dist/ui-web/assets/{index-DFqVuxOB.js → index-B87IyofX.js} +1 -1
  42. package/dist/ui-web/index.html +1 -1
  43. package/package.json +6 -1
  44. package/src/api/config.ts +21 -3
  45. package/src/api/context.ts +3 -3
  46. package/src/api/decorators.ts +3 -8
  47. package/src/api/index.ts +0 -2
  48. package/src/api/sonamu.ts +12 -9
  49. package/src/bin/cli.ts +2 -2
  50. package/src/database/db.ts +40 -43
  51. package/src/index.ts +0 -1
  52. package/src/storage/drivers.ts +15 -0
  53. package/src/storage/index.ts +5 -0
  54. package/src/storage/storage-manager.ts +39 -0
  55. package/src/storage/types.ts +12 -0
  56. package/src/storage/uploaded-file.ts +81 -0
  57. package/src/template/implementations/service.template.ts.txt +328 -0
  58. package/src/template/implementations/services.template.ts +7 -4
  59. package/src/testing/fixture-manager.ts +3 -4
  60. package/dist/file-storage/driver.d.ts +0 -48
  61. package/dist/file-storage/driver.d.ts.map +0 -1
  62. package/dist/file-storage/driver.js +0 -79
  63. package/dist/file-storage/file-storage.d.ts +0 -50
  64. package/dist/file-storage/file-storage.d.ts.map +0 -1
  65. package/dist/file-storage/file-storage.js +0 -75
  66. package/src/file-storage/driver.ts +0 -131
  67. package/src/file-storage/file-storage.ts +0 -100
@@ -294,24 +294,19 @@ export function upload(options: UploadDecoratorOptions = {}) {
294
294
  files: [],
295
295
  };
296
296
 
297
- const storage = Sonamu.storage;
298
- if (!storage) {
299
- throw new Error("Storage가 설정되지 않았습니다.");
300
- }
301
-
302
- const { FileStorage } = await import("../file-storage/file-storage");
297
+ const { UploadedFile } = await import("../storage/uploaded-file");
303
298
  if (options.mode === "multiple") {
304
299
  const rawFilesIterator = request.files();
305
300
  for await (const rawFile of rawFilesIterator) {
306
301
  if (rawFile) {
307
302
  await rawFile.toBuffer();
308
- uploadContext.files.push(new FileStorage(rawFile, storage));
303
+ uploadContext.files.push(new UploadedFile(rawFile));
309
304
  }
310
305
  }
311
306
  } else {
312
307
  const rawFile = await request.file();
313
308
  if (rawFile) {
314
- uploadContext.file = new FileStorage(rawFile, storage);
309
+ uploadContext.file = new UploadedFile(rawFile);
315
310
  }
316
311
  }
317
312
 
package/src/api/index.ts CHANGED
@@ -1,5 +1,3 @@
1
- export * from "../file-storage/driver";
2
- export * from "../file-storage/file-storage";
3
1
  export * from "./caster";
4
2
  export * from "./code-converters";
5
3
  export * from "./context";
package/src/api/sonamu.ts CHANGED
@@ -8,8 +8,8 @@ import path from "path";
8
8
  import type { ZodObject } from "zod";
9
9
  import { createMockSSEFactory, DB, isDaemonServer } from "..";
10
10
  import type { SonamuDBConfig } from "../database/db";
11
- import type { Driver } from "../file-storage/driver";
12
11
  import { Naite } from "../naite/naite";
12
+ import type { StorageManager } from "../storage/storage-manager";
13
13
  import type { Syncer } from "../syncer/syncer";
14
14
  import type { WorkflowManager } from "../tasks/workflow-manager";
15
15
  import type { SonamuFastifyConfig } from "../types/types";
@@ -117,11 +117,14 @@ class SonamuClass {
117
117
  return this._secrets;
118
118
  }
119
119
 
120
- private _storage: Driver | null = null;
121
- set storage(storage: Driver) {
122
- this._storage = storage;
123
- }
124
- get storage(): Driver | null {
120
+ private _storage: StorageManager | null = null;
121
+ /**
122
+ * StorageManager 인스턴스
123
+ */
124
+ get storage(): StorageManager {
125
+ if (!this._storage) {
126
+ throw new Error("Storage has not been initialized. Check storage config.");
127
+ }
125
128
  return this._storage;
126
129
  }
127
130
 
@@ -250,9 +253,10 @@ class SonamuClass {
250
253
  const server = fastify(options.fastify);
251
254
  this.server = server;
252
255
 
253
- // Storage 설정 저장
256
+ // Storage 설정 → StorageManager 생성
254
257
  if (options.storage) {
255
- this.storage = options.storage;
258
+ const { StorageManager } = await import("../storage/storage-manager");
259
+ this._storage = new StorageManager(options.storage);
256
260
  }
257
261
 
258
262
  // 플러그인 등록
@@ -714,7 +718,6 @@ class SonamuClass {
714
718
  await BaseModel.destroy();
715
719
  await this._workflows?.destroy();
716
720
  await this.watcher?.close();
717
- this.storage?.destroy();
718
721
  }
719
722
  }
720
723
  export const Sonamu = new SonamuClass();
package/src/bin/cli.ts CHANGED
@@ -297,13 +297,13 @@ async function fixture_init() {
297
297
  const targets = [
298
298
  {
299
299
  label: "(REMOTE) Fixture DB",
300
- config: Sonamu.dbConfig.fixture_remote,
300
+ config: Sonamu.dbConfig.fixture,
301
301
  },
302
302
  {
303
303
  label: "(LOCAL) Testing DB",
304
304
  config: Sonamu.dbConfig.test,
305
305
  toSkip: (() => {
306
- const remoteConn = Sonamu.dbConfig.fixture_remote.connection as Knex.ConnectionConfig;
306
+ const remoteConn = Sonamu.dbConfig.fixture.connection as Knex.ConnectionConfig;
307
307
  const localConn = Sonamu.dbConfig.test.connection as Knex.ConnectionConfig;
308
308
  return remoteConn.host === localConn.host && remoteConn.database === localConn.database;
309
309
  })(),
@@ -6,15 +6,23 @@ import { Sonamu } from "../api";
6
6
  import type { DatabaseConfig, SonamuConfig } from "../api/config";
7
7
  import { TransactionContext } from "./transaction-context";
8
8
 
9
+ /**
10
+ * 여러 설정 객체를 순차적으로 deep merge합니다.
11
+ * undefined/null인 인자는 무시됩니다.
12
+ */
13
+ function mergeConfigs<T extends object>(...configs: (Partial<T> | undefined | null)[]): T {
14
+ return configs.reduce<T>((acc, config) => (config ? assign(acc, config as T) : acc), {} as T);
15
+ }
16
+
9
17
  export type DBPreset = "w" | "r";
10
18
 
11
19
  export type SonamuDBConfig = {
12
20
  development_master: Knex.Config;
13
21
  development_slave: Knex.Config;
14
- test: Knex.Config;
15
- fixture_remote: Knex.Config;
16
22
  production_master: Knex.Config;
17
23
  production_slave: Knex.Config;
24
+ fixture: Knex.Config;
25
+ test: Knex.Config;
18
26
  };
19
27
 
20
28
  export class DBClass {
@@ -120,48 +128,37 @@ export class DBClass {
120
128
  config.defaultOptions,
121
129
  );
122
130
 
123
- // 로컬 환경 설정
124
- const test: DatabaseConfig = assign(defaultKnexConfig, {
125
- connection: {
126
- database: `${config.name}_test`,
127
- ...config.defaultOptions?.connection,
128
- },
129
- });
130
-
131
- // 개발 환경 설정
132
- const devMasterOptions = config.environments?.development;
133
- const devSlaveOptions = config.environments?.development_slave;
134
- const development_master = assign(defaultKnexConfig, devMasterOptions ?? {});
135
- const development_slave = assign(
136
- assign(defaultKnexConfig, devMasterOptions ?? {}),
137
- devSlaveOptions ?? {},
138
- );
139
- // NOTE: fixture remote는 default connection의 DB를 override해선 안됨.
140
- const fixture_remote = assign(
141
- assign(assign(defaultKnexConfig, devMasterOptions ?? {}), {
142
- connection: {
143
- database: `${config.name}_fixture_remote`,
144
- },
145
- }),
146
- config.environments?.remote_fixture ?? {},
147
- );
148
-
149
- // 프로덕션 환경 설정
150
- const prodMasterOptions = config.environments?.production ?? {};
151
- const prodSlaveOptions = config.environments?.production_slave ?? {};
152
- const production_master = assign(defaultKnexConfig, prodMasterOptions);
153
- const production_slave = assign(
154
- assign(defaultKnexConfig, prodMasterOptions),
155
- prodSlaveOptions ?? {},
156
- );
157
-
131
+ // biome-ignore format: 설정 구조 가독성을 위해 여러 줄로 유지
158
132
  return {
159
- test,
160
- fixture_remote,
161
- development_master,
162
- development_slave,
163
- production_master,
164
- production_slave,
133
+ // 여기에 나열한 순서대로 Sonamu UI의 DB Migration 탭에 표시됩니다.
134
+ test: mergeConfigs(
135
+ defaultKnexConfig,
136
+ { connection: { database: `${config.name}_test` } },
137
+ config.environments?.test
138
+ ),
139
+ fixture: mergeConfigs(
140
+ defaultKnexConfig,
141
+ { connection: { database: `${config.name}_fixture` } },
142
+ config.environments?.fixture,
143
+ ),
144
+ development_master: mergeConfigs(
145
+ defaultKnexConfig,
146
+ config.environments?.development
147
+ ),
148
+ development_slave: mergeConfigs(
149
+ defaultKnexConfig,
150
+ config.environments?.development,
151
+ config.environments?.development_slave,
152
+ ),
153
+ production_master: mergeConfigs(
154
+ defaultKnexConfig,
155
+ config.environments?.production
156
+ ),
157
+ production_slave: mergeConfigs(
158
+ defaultKnexConfig,
159
+ config.environments?.production,
160
+ config.environments?.production_slave,
161
+ ),
165
162
  };
166
163
  }
167
164
 
package/src/index.ts CHANGED
@@ -15,7 +15,6 @@ export * from "./entity/entity";
15
15
  export * from "./entity/entity-manager";
16
16
  export * from "./exceptions/error-handler";
17
17
  export * from "./exceptions/so-exceptions";
18
- export * from "./file-storage/driver";
19
18
  export * from "./migration/migration-set";
20
19
  export * from "./migration/migrator";
21
20
  export * from "./migration/postgresql-schema-reader";
@@ -0,0 +1,15 @@
1
+ import { FSDriver } from "flydrive/drivers/fs";
2
+ import type { FSDriverOptions } from "flydrive/drivers/fs/types";
3
+ import { S3Driver } from "flydrive/drivers/s3";
4
+ import type { S3DriverOptions } from "flydrive/drivers/s3/types";
5
+
6
+ /**
7
+ * 드라이버 팩토리 함수
8
+ * 설정 → 드라이버 인스턴스 생성 함수 변환
9
+ */
10
+ export const drivers = {
11
+ fs: (config: FSDriverOptions) => () => new FSDriver(config),
12
+ s3: (config: S3DriverOptions) => () => new S3Driver(config),
13
+ };
14
+
15
+ export type DriverKey = keyof typeof drivers;
@@ -0,0 +1,5 @@
1
+ // Storage 서브모듈 exports
2
+ export { type DriverKey, drivers } from "./drivers";
3
+ export { StorageManager } from "./storage-manager";
4
+ export type { StorageConfig } from "./types";
5
+ export { UploadedFile } from "./uploaded-file";
@@ -0,0 +1,39 @@
1
+ import { Disk } from "flydrive";
2
+ import { assertDefined } from "../utils/utils";
3
+ import type { DriverKey } from "./drivers";
4
+ import type { StorageConfig } from "./types";
5
+
6
+ /**
7
+ * 여러 디스크를 관리하는 매니저
8
+ */
9
+ export class StorageManager {
10
+ private disks: Map<DriverKey, Disk> = new Map();
11
+
12
+ constructor(private config: StorageConfig) {}
13
+
14
+ /**
15
+ * 디스크 인스턴스 반환 (lazy initialization)
16
+ * @param diskName 디스크 이름 (없으면 default)
17
+ */
18
+ use(diskName?: DriverKey): Disk {
19
+ const name = diskName ?? (this.config.default as DriverKey);
20
+
21
+ if (!this.disks.has(name)) {
22
+ const factory = this.config.drivers[name];
23
+ if (!factory) {
24
+ const available = Object.keys(this.config.drivers).join(", ");
25
+ throw new Error(`Unknown disk: "${name}". Available: ${available}`);
26
+ }
27
+ this.disks.set(name, new Disk(factory()));
28
+ }
29
+
30
+ return assertDefined(this.disks.get(name), `Disk ${name} not found`);
31
+ }
32
+
33
+ /**
34
+ * 기본 디스크 이름 반환
35
+ */
36
+ get defaultDisk(): string {
37
+ return this.config.default;
38
+ }
39
+ }
@@ -0,0 +1,12 @@
1
+ import type { DriverContract } from "flydrive/types";
2
+ import type { DriverKey } from "./drivers";
3
+
4
+ /**
5
+ * Storage 설정 타입
6
+ */
7
+ export type StorageConfig = {
8
+ /** 기본 디스크 이름 */
9
+ default: string;
10
+ /** 디스크별 드라이버 팩토리 */
11
+ drivers: Record<DriverKey, () => DriverContract>;
12
+ };
@@ -0,0 +1,81 @@
1
+ import type { MultipartFile } from "@fastify/multipart";
2
+ import { createHash } from "crypto";
3
+ import mime from "mime-types";
4
+ import type { DriverKey } from "./drivers";
5
+
6
+ /**
7
+ * 업로드된 파일 래퍼
8
+ */
9
+ export class UploadedFile {
10
+ private _file: MultipartFile;
11
+ private _buffer?: Buffer;
12
+ private _url?: string;
13
+
14
+ constructor(file: MultipartFile) {
15
+ this._file = file;
16
+ }
17
+
18
+ /** 원본 파일명 */
19
+ get filename(): string {
20
+ return this._file.filename;
21
+ }
22
+
23
+ /** MIME 타입 */
24
+ get mimetype(): string {
25
+ return this._file.mimetype;
26
+ }
27
+
28
+ /** 파일 크기 (bytes) */
29
+ get size(): number {
30
+ return this._file.file.bytesRead;
31
+ }
32
+
33
+ /** 확장자 (점 제외) */
34
+ get extname(): string | false {
35
+ return mime.extension(this._file.mimetype);
36
+ }
37
+
38
+ /** saveToDisk 후 저장된 URL */
39
+ get url(): string | undefined {
40
+ return this._url;
41
+ }
42
+
43
+ /** Buffer로 변환 (캐싱됨) */
44
+ async toBuffer(): Promise<Buffer> {
45
+ if (!this._buffer) {
46
+ this._buffer = await this._file.toBuffer();
47
+ }
48
+ return this._buffer;
49
+ }
50
+
51
+ /** MD5 해시 계산 */
52
+ async md5(): Promise<string> {
53
+ const buffer = await this.toBuffer();
54
+ return createHash("md5").update(buffer).digest("hex");
55
+ }
56
+
57
+ /**
58
+ * 파일을 디스크에 저장
59
+ * @param key 저장 경로 (예: 'uploads/avatar.png')
60
+ * @param diskName 디스크 이름 (기본: default disk)
61
+ * @returns 저장된 파일의 URL
62
+ */
63
+ async saveToDisk(key: string, diskName?: DriverKey): Promise<string> {
64
+ // 순환 의존성 방지를 위해 동적 import
65
+ const { Sonamu } = await import("../api/sonamu");
66
+ const disk = Sonamu.storage.use(diskName);
67
+ const buffer = await this.toBuffer();
68
+
69
+ await disk.put(key, new Uint8Array(buffer), {
70
+ contentType: this.mimetype,
71
+ });
72
+
73
+ this._url = await disk.getSignedUrl(key);
74
+ return this._url;
75
+ }
76
+
77
+ /** 원본 MultipartFile 접근 */
78
+ get raw(): MultipartFile {
79
+ return this._file;
80
+ }
81
+ }