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.
- package/dist/api/config.d.ts +21 -3
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +1 -1
- package/dist/api/context.d.ts +3 -3
- 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 +4 -8
- package/dist/api/index.d.ts +0 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +1 -3
- package/dist/api/sonamu.d.ts +5 -3
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +10 -8
- package/dist/bin/cli.js +3 -3
- package/dist/database/db.d.ts +2 -2
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +23 -30
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/storage/drivers.d.ts +14 -0
- package/dist/storage/drivers.d.ts.map +1 -0
- package/dist/storage/drivers.js +11 -0
- package/dist/storage/index.d.ts +5 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +6 -0
- package/dist/storage/storage-manager.d.ts +21 -0
- package/dist/storage/storage-manager.d.ts.map +1 -0
- package/dist/storage/storage-manager.js +33 -0
- package/dist/storage/types.d.ts +12 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +5 -0
- package/dist/storage/uploaded-file.d.ts +35 -0
- package/dist/storage/uploaded-file.d.ts.map +1 -0
- package/dist/storage/uploaded-file.js +58 -0
- package/dist/template/implementations/services.template.d.ts.map +1 -1
- package/dist/template/implementations/services.template.js +8 -5
- package/dist/testing/fixture-manager.d.ts.map +1 -1
- package/dist/testing/fixture-manager.js +4 -4
- package/dist/ui-web/assets/{index-DFqVuxOB.js → index-B87IyofX.js} +1 -1
- package/dist/ui-web/index.html +1 -1
- package/package.json +6 -1
- package/src/api/config.ts +21 -3
- package/src/api/context.ts +3 -3
- package/src/api/decorators.ts +3 -8
- package/src/api/index.ts +0 -2
- package/src/api/sonamu.ts +12 -9
- package/src/bin/cli.ts +2 -2
- package/src/database/db.ts +40 -43
- package/src/index.ts +0 -1
- package/src/storage/drivers.ts +15 -0
- package/src/storage/index.ts +5 -0
- package/src/storage/storage-manager.ts +39 -0
- package/src/storage/types.ts +12 -0
- package/src/storage/uploaded-file.ts +81 -0
- package/src/template/implementations/service.template.ts.txt +328 -0
- package/src/template/implementations/services.template.ts +7 -4
- package/src/testing/fixture-manager.ts +3 -4
- package/dist/file-storage/driver.d.ts +0 -48
- package/dist/file-storage/driver.d.ts.map +0 -1
- package/dist/file-storage/driver.js +0 -79
- package/dist/file-storage/file-storage.d.ts +0 -50
- package/dist/file-storage/file-storage.d.ts.map +0 -1
- package/dist/file-storage/file-storage.js +0 -75
- package/src/file-storage/driver.ts +0 -131
- package/src/file-storage/file-storage.ts +0 -100
package/src/api/decorators.ts
CHANGED
|
@@ -294,24 +294,19 @@ export function upload(options: UploadDecoratorOptions = {}) {
|
|
|
294
294
|
files: [],
|
|
295
295
|
};
|
|
296
296
|
|
|
297
|
-
const
|
|
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
|
|
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
|
|
309
|
+
uploadContext.file = new UploadedFile(rawFile);
|
|
315
310
|
}
|
|
316
311
|
}
|
|
317
312
|
|
package/src/api/index.ts
CHANGED
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:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
get storage():
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
})(),
|
package/src/database/db.ts
CHANGED
|
@@ -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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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,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
|
+
}
|