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
@@ -0,0 +1,328 @@
1
+ import assert from "assert";
2
+ import inflection from "inflection";
3
+ import { diff, group, sort, unique } from "radashi";
4
+ import {
5
+ apiParamToTsCode,
6
+ apiParamToTsCodeAsObject,
7
+ apiParamTypeToTsType,
8
+ unwrapPromiseOnce,
9
+ } from "../../api/code-converters";
10
+ import type { ExtendedApi } from "../../api/decorators";
11
+ import { Sonamu } from "../../api/sonamu";
12
+ import type { EntityNamesRecord } from "../../entity/entity-manager";
13
+ import { Naite } from "../../naite/naite";
14
+ import type { TemplateOptions } from "../../types/types";
15
+ import { type ApiParam, ApiParamType } from "../../types/types";
16
+ import { assertDefined } from "../../utils/utils";
17
+ import { Template } from "../template";
18
+ import { zodTypeToTsTypeDef } from "../zod-converter";
19
+
20
+ export class Template__service extends Template {
21
+ constructor() {
22
+ super("service");
23
+ }
24
+
25
+ getTargetAndPath(names: EntityNamesRecord) {
26
+ return {
27
+ target: ":target/src/services",
28
+ path: `${names.fs}/${names.fs}.service.ts`,
29
+ };
30
+ }
31
+
32
+ render({ namesRecord }: TemplateOptions["service"]) {
33
+ Naite.t("render", { namesRecord });
34
+
35
+ const {
36
+ syncer: { apis },
37
+ } = Sonamu;
38
+
39
+ const apisForThisModel = apis.filter(
40
+ (api) =>
41
+ api.modelName === `${namesRecord.capital}Model` ||
42
+ api.modelName === `${namesRecord.capital}Frame`,
43
+ );
44
+
45
+ // 서비스 TypeSource
46
+ const { lines, importKeys } = this.getTypeSource(apisForThisModel);
47
+
48
+ // AxiosProgressEvent 있는지 확인
49
+ const hasAxiosProgressEvent = apis.find((api) =>
50
+ (api.options.clients ?? []).includes("axios-multipart"),
51
+ );
52
+
53
+ return {
54
+ ...this.getTargetAndPath(namesRecord),
55
+ body: lines.join("\n"),
56
+ importKeys: importKeys.filter((key) => ["ListResult"].includes(key) === false),
57
+ customHeaders: [
58
+ `import { z } from 'zod';`,
59
+ `import qs from "qs";`,
60
+ `import useSWR, { type SWRResponse } from "swr";`,
61
+ `import { fetch, ListResult, SWRError, SwrOptions, handleConditional, swrPostFetcher, EventHandlers, SSEStreamOptions, useSSEStream } from '../sonamu.shared';`,
62
+ ...(hasAxiosProgressEvent ? [`import { type AxiosProgressEvent } from 'axios';`] : []),
63
+ ],
64
+ };
65
+ }
66
+
67
+ getTypeSource(apis: ExtendedApi[]): {
68
+ lines: string[];
69
+ importKeys: string[];
70
+ } {
71
+ const importKeys: string[] = [];
72
+
73
+ // 제네릭에서 선언한 타입, importKeys에서 제외 필요
74
+ let typeParamNames: string[] = [];
75
+
76
+ const groups = group(apis, (api) => api.modelName);
77
+ const body = Object.keys(groups)
78
+ .map((modelName) => {
79
+ const methods = groups[modelName];
80
+ assert(methods);
81
+ const methodCodes = methods
82
+ .map((api) => {
83
+ // 컨텍스트 제외된 파라미터 리스트
84
+ const paramsWithoutContext = api.parameters.filter(
85
+ (param) =>
86
+ !ApiParamType.isContext(param.type) &&
87
+ !ApiParamType.isRefKnex(param.type) &&
88
+ !(param.optional === true && param.name.startsWith("_")), // _로 시작하는 파라미터는 제외
89
+ );
90
+
91
+ // 파라미터 타입 정의
92
+ const typeParametersAsTsType = api.typeParameters
93
+ .map((typeParam) => {
94
+ return apiParamTypeToTsType(typeParam, importKeys);
95
+ })
96
+ .join(", ");
97
+ const typeParamsDef = typeParametersAsTsType ? `<${typeParametersAsTsType}>` : "";
98
+ typeParamNames = typeParamNames.concat(
99
+ api.typeParameters.map((typeParam) => typeParam.id),
100
+ );
101
+
102
+ // 파라미터 정의
103
+ const paramsDef = apiParamToTsCode(paramsWithoutContext, importKeys);
104
+
105
+ // 파라미터 정의 (객체 형태)
106
+ const paramsDefAsObject = apiParamToTsCodeAsObject(paramsWithoutContext, importKeys);
107
+
108
+ // 리턴 타입 정의
109
+ const returnTypeDef = apiParamTypeToTsType(
110
+ assertDefined(unwrapPromiseOnce(api.returnType)),
111
+ importKeys,
112
+ );
113
+
114
+ // 페이로드 데이터 정의
115
+ const payloadDef = `{ ${paramsWithoutContext.map((param) => param.name).join(", ")} }`;
116
+
117
+ // 기본 URL
118
+ const apiBaseUrl = `${Sonamu.config.api.route.prefix}${api.path}`;
119
+
120
+ const clients = api.options.clients ?? [];
121
+ return [
122
+ // 클라이언트별로 생성
123
+ ...sort(clients, (client) => (client === "swr" ? 0 : 1)).map((client) => {
124
+ switch (client) {
125
+ case "axios":
126
+ return this.renderAxios(
127
+ api,
128
+ apiBaseUrl,
129
+ typeParamsDef,
130
+ paramsDef,
131
+ returnTypeDef,
132
+ payloadDef,
133
+ );
134
+ case "axios-multipart":
135
+ return this.renderAxiosMultipart(
136
+ api,
137
+ apiBaseUrl,
138
+ typeParamsDef,
139
+ paramsDef,
140
+ returnTypeDef,
141
+ paramsWithoutContext,
142
+ );
143
+ case "swr":
144
+ return this.renderSwr(
145
+ api,
146
+ apiBaseUrl,
147
+ typeParamsDef,
148
+ paramsDef,
149
+ returnTypeDef,
150
+ payloadDef,
151
+ );
152
+ case "window-fetch":
153
+ return this.renderWindowFetch(
154
+ api,
155
+ apiBaseUrl,
156
+ typeParamsDef,
157
+ paramsDef,
158
+ payloadDef,
159
+ );
160
+ default:
161
+ return `// Not supported ${inflection.camelize(client, true)} yet.`;
162
+ }
163
+ }),
164
+ // 스트리밍인 경우
165
+ ...(api.streamOptions ? [this.renderStream(api, apiBaseUrl, paramsDefAsObject)] : []),
166
+ ].join("\n");
167
+ })
168
+ .join("\n\n");
169
+
170
+ return `export namespace ${modelName.replace(/Model$/, "Service").replace(/Frame$/, "Service")} {
171
+ ${methodCodes}
172
+ }`;
173
+ })
174
+ .join("\n\n");
175
+
176
+ return {
177
+ lines: [body],
178
+ importKeys: diff(unique(importKeys), typeParamNames),
179
+ };
180
+ }
181
+
182
+ renderAxios(
183
+ api: ExtendedApi,
184
+ apiBaseUrl: string,
185
+ typeParamsDef: string,
186
+ paramsDef: string,
187
+ returnTypeDef: string,
188
+ payloadDef: string,
189
+ ) {
190
+ const methodNameAxios = api.options.resourceName
191
+ ? `get${inflection.camelize(api.options.resourceName)}`
192
+ : api.methodName;
193
+
194
+ if (api.options.httpMethod === "GET") {
195
+ return `
196
+ export async function ${methodNameAxios}${typeParamsDef}(${paramsDef}): Promise<${returnTypeDef}> {
197
+ return fetch({
198
+ method: "GET",
199
+ url: \`${apiBaseUrl}?\${qs.stringify(${payloadDef})}\`,
200
+ ${api.options.timeout ? `signal: AbortSignal.timeout(${api.options.timeout}),` : ""}
201
+ });
202
+ }
203
+ `.trim();
204
+ } else {
205
+ return `
206
+ export async function ${methodNameAxios}${typeParamsDef}(${paramsDef}): Promise<${returnTypeDef}> {
207
+ return fetch({
208
+ method: '${api.options.httpMethod}',
209
+ url: \`${apiBaseUrl}\`,
210
+ data: ${payloadDef},
211
+ ${api.options.timeout ? `signal: AbortSignal.timeout(${api.options.timeout}),` : ""}
212
+ });
213
+ }
214
+ `.trim();
215
+ }
216
+ }
217
+
218
+ renderAxiosMultipart(
219
+ api: ExtendedApi,
220
+ apiBaseUrl: string,
221
+ typeParamsDef: string,
222
+ paramsDef: string,
223
+ returnTypeDef: string,
224
+ paramsWithoutContext: ApiParam[],
225
+ ) {
226
+ const isMultiple = api.uploadOptions?.mode === "multiple";
227
+ const fileParamName = isMultiple ? "files" : "file";
228
+ const fileParamType = isMultiple ? "File[]" : "File";
229
+
230
+ const formDataDef = isMultiple
231
+ ? [
232
+ `${fileParamName}.forEach(f => { formData.append("${fileParamName}", f) } ); `,
233
+ ...paramsWithoutContext.map(
234
+ (param) => `formData.append('${param.name}', String(${param.name}));`,
235
+ ),
236
+ ].join("\n")
237
+ : [
238
+ `formData.append("${fileParamName}", ${fileParamName});`,
239
+ ...paramsWithoutContext.map(
240
+ (param) => `formData.append('${param.name}', String(${param.name}));`,
241
+ ),
242
+ ].join("\n");
243
+
244
+ const paramsDefComma = paramsDef !== "" ? ", " : "";
245
+ return `
246
+ export async function ${api.methodName}${typeParamsDef}(
247
+ ${paramsDef}${paramsDefComma}
248
+ ${fileParamName}: ${fileParamType},
249
+ onUploadProgress?: (pe:AxiosProgressEvent) => void
250
+ ): Promise<${returnTypeDef}> {
251
+ const formData = new FormData();
252
+ ${formDataDef}
253
+ return fetch({
254
+ method: 'POST',
255
+ url: \`${apiBaseUrl}\`,
256
+ headers: {
257
+ "Content-Type": "multipart/form-data",
258
+ },
259
+ onUploadProgress,
260
+ data: formData,
261
+ ${api.options.timeout ? `signal: AbortSignal.timeout(${api.options.timeout}),` : ""}
262
+ });
263
+ }
264
+ `.trim();
265
+ }
266
+
267
+ renderSwr(
268
+ api: ExtendedApi,
269
+ apiBaseUrl: string,
270
+ typeParamsDef: string,
271
+ paramsDef: string,
272
+ returnTypeDef: string,
273
+ payloadDef: string,
274
+ ) {
275
+ const methodNameSwr = api.options.resourceName
276
+ ? `use${inflection.camelize(api.options.resourceName)}`
277
+ : `use${inflection.camelize(api.methodName)}`;
278
+ return ` export function ${inflection.camelize(methodNameSwr, true)}${typeParamsDef}(${[
279
+ paramsDef,
280
+ "swrOptions?: SwrOptions",
281
+ ]
282
+ .filter((p) => p !== "")
283
+ .join(",")}, ): SWRResponse<${returnTypeDef}, SWRError> {
284
+ return useSWR(handleConditional([
285
+ \`${apiBaseUrl}\`,
286
+ ${payloadDef},
287
+ ], swrOptions?.conditional)${api.options.httpMethod === "POST" ? ", swrPostFetcher" : ""}${
288
+ api.options.timeout ? `, { loadingTimeout: ${api.options.timeout} }` : ""
289
+ });
290
+ }`;
291
+ }
292
+
293
+ renderWindowFetch(
294
+ api: ExtendedApi,
295
+ apiBaseUrl: string,
296
+ typeParamsDef: string,
297
+ paramsDef: string,
298
+ payloadDef: string,
299
+ ) {
300
+ return `
301
+ export async function ${api.methodName}${typeParamsDef}(${paramsDef}): Promise<Response> {
302
+ return window.fetch(\`${apiBaseUrl}?\${qs.stringify(${payloadDef})}\`${
303
+ api.options.timeout ? `, { signal: AbortSignal.timeout(${api.options.timeout}) }` : ""
304
+ });
305
+ }
306
+ `.trim();
307
+ }
308
+
309
+ renderStream(api: ExtendedApi, apiBaseUrl: string, paramsDefAsObject: string) {
310
+ if (!api.streamOptions) {
311
+ return "// streamOptions not found";
312
+ }
313
+
314
+ const methodNameStream = api.options.resourceName
315
+ ? `use${inflection.camelize(api.options.resourceName)}`
316
+ : `use${inflection.camelize(api.methodName)}`;
317
+ const methodNameStreamCamelized = inflection.camelize(methodNameStream, true);
318
+
319
+ const eventsTypeDef = zodTypeToTsTypeDef(api.streamOptions.events);
320
+
321
+ return ` export function ${methodNameStreamCamelized}(
322
+ params: ${paramsDefAsObject},
323
+ handlers: EventHandlers<${eventsTypeDef} & { end?: () => void }>,
324
+ options: SSEStreamOptions) {
325
+ return useSSEStream<${eventsTypeDef}>(\`${apiBaseUrl}\`, params, handlers, options);
326
+ }`;
327
+ }
328
+ }
@@ -55,7 +55,6 @@ export class Template__services extends Template {
55
55
  !(param.optional === true && param.name.startsWith("_")),
56
56
  );
57
57
 
58
- const paramsDef = apiParamToTsCode(paramsWithoutContext, importKeys);
59
58
  const apiBaseUrl = `${Sonamu.config.api.route.prefix}${api.path}`;
60
59
 
61
60
  const methodNameStream = api.options.resourceName
@@ -65,19 +64,20 @@ export class Template__services extends Template {
65
64
 
66
65
  const eventsTypeDef = zodTypeToTsTypeDef(api.streamOptions.events);
67
66
 
67
+ // 파라미터를 객체 형태로 정의 (타입과 실제 값 모두에 사용)
68
68
  const paramsDefAsObject =
69
69
  paramsWithoutContext.length > 0
70
- ? `{ ${paramsWithoutContext.map((p) => p.name).join(", ")} }`
70
+ ? `{ ${paramsWithoutContext.map((p) => `${p.name}: ${apiParamTypeToTsType(p.type, importKeys)}`).join(", ")} }`
71
71
  : "{}";
72
72
 
73
73
  functions.push(
74
74
  `
75
75
  export function ${methodNameStreamCamelized}(
76
- params: ${paramsDef ? `{ ${paramsWithoutContext.map((p) => `${p.name}: ${apiParamTypeToTsType(p.type, importKeys)}`).join(", ")} }` : "{}"},
76
+ params: ${paramsDefAsObject},
77
77
  handlers: EventHandlers<${eventsTypeDef} & { end?: () => void }>,
78
78
  options: SSEStreamOptions
79
79
  ) {
80
- return useSSEStream<${eventsTypeDef}>(\`${apiBaseUrl}\`, ${paramsDefAsObject}, handlers, options);
80
+ return useSSEStream<${eventsTypeDef}>(\`${apiBaseUrl}\`, params, handlers, options);
81
81
  }
82
82
  `.trim(),
83
83
  );
@@ -255,6 +255,9 @@ ${functions.join("\n\n")}
255
255
  body: namespaces.join("\n\n"),
256
256
  importKeys: diff(unique(importKeys), [...typeParamNames, "ListResult"]),
257
257
  customHeaders: [
258
+ "/** biome-ignore-all lint: generated는 무시 */",
259
+ "/** biome-ignore-all assist: generated는 무시 */",
260
+ "",
258
261
  `import { queryOptions, useQuery, useMutation } from '@tanstack/react-query';`,
259
262
  `import type { AxiosProgressEvent } from 'axios';`,
260
263
  `import qs from 'qs';`,
@@ -87,7 +87,7 @@ export class FixtureManagerClass {
87
87
  }
88
88
 
89
89
  this.tdb = knex(Sonamu.dbConfig.test);
90
- this.fdb = knex(Sonamu.dbConfig.fixture_remote);
90
+ this.fdb = knex(Sonamu.dbConfig.fixture);
91
91
  }
92
92
 
93
93
  async getChecksum(db: Knex, tableName: string) {
@@ -100,7 +100,7 @@ export class FixtureManagerClass {
100
100
  pg_dump로 원격 DB를 덤프하고, pg_restore로 로컬에 복원합니다.
101
101
  */
102
102
  async sync() {
103
- const fixtureConn = Sonamu.dbConfig.fixture_remote.connection as Knex.PgConnectionConfig;
103
+ const fixtureConn = Sonamu.dbConfig.fixture.connection as Knex.PgConnectionConfig;
104
104
  const testConn = Sonamu.dbConfig.test.connection as Knex.PgConnectionConfig;
105
105
 
106
106
  // 1. 로컬 test DB 연결 종료 및 재생성
@@ -182,8 +182,7 @@ export class FixtureManagerClass {
182
182
  }
183
183
 
184
184
  // 픽스쳐DB, 실DB
185
- const fixtureDatabase = (Sonamu.dbConfig.fixture_remote.connection as Knex.ConnectionConfig)
186
- .database;
185
+ const fixtureDatabase = (Sonamu.dbConfig.fixture.connection as Knex.ConnectionConfig).database;
187
186
  const realDatabase = (Sonamu.dbConfig.production_master.connection as Knex.ConnectionConfig)
188
187
  .database;
189
188
 
@@ -1,48 +0,0 @@
1
- import { S3Client, type S3ClientConfig } from "@aws-sdk/client-s3";
2
- /**
3
- * 파일 저장소의 공통 인터페이스
4
- */
5
- export interface Driver {
6
- put(key: string, contents: Buffer, options?: {
7
- contentType?: string;
8
- visibility?: "public" | "private";
9
- }): Promise<void>;
10
- del(key: string): Promise<void>;
11
- getUrl(key: string): string;
12
- getSignedUrl(key: string, expiresIn?: number): Promise<string>;
13
- destroy(): void;
14
- }
15
- export type FSDriverConfig = {
16
- location: string;
17
- urlPrefix: string;
18
- };
19
- /**
20
- * 로컬 파일시스템
21
- */
22
- export declare class FSDriver implements Driver {
23
- private config;
24
- constructor(config: FSDriverConfig);
25
- put(key: string, contents: Buffer): Promise<void>;
26
- del(key: string): Promise<void>;
27
- getUrl(key: string): string;
28
- getSignedUrl(key: string, _expiresIn?: number): Promise<string>;
29
- destroy(): void;
30
- }
31
- export type S3DriverConfig = S3ClientConfig & {
32
- bucket: string;
33
- };
34
- export declare class S3Driver implements Driver {
35
- private config;
36
- s3: S3Client;
37
- constructor(config: S3DriverConfig);
38
- put(key: string, contents: Buffer, options?: {
39
- contentType?: string;
40
- visibility?: "public" | "private";
41
- }): Promise<void>;
42
- del(key: string): Promise<void>;
43
- getUrl(key: string): string;
44
- getSignedUrl(key: string, expiresIn?: number): Promise<string>;
45
- private getAcl;
46
- destroy(): void;
47
- }
48
- //# sourceMappingURL=driver.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../src/file-storage/driver.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,QAAQ,EACR,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAC;AAK5B;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,GAAG,CACD,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;KAAE,GACpE,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAE5B,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE/D,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;GAEG;AACH,qBAAa,QAAS,YAAW,MAAM;IACzB,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,cAAc;IAEpC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASjD,GAAG,CAAC,GAAG,EAAE,MAAM;IAIrB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAKrB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIrE,OAAO;CAGR;AAED,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG;IAC5C,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,qBAAa,QAAS,YAAW,MAAM;IAGzB,OAAO,CAAC,MAAM;IAF1B,EAAE,EAAE,QAAQ,CAAC;gBAEO,MAAM,EAAE,cAAc;IAIpC,GAAG,CACP,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;KAAE,GACpE,OAAO,CAAC,IAAI,CAAC;IAYV,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASrC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAIrB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAWpE,OAAO,CAAC,MAAM;IAQd,OAAO;CAGR"}
@@ -1,79 +0,0 @@
1
- import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
2
- import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
3
- import fs from "node:fs/promises";
4
- import path from "path";
5
- /**
6
- * 로컬 파일시스템
7
- */ export class FSDriver {
8
- config;
9
- constructor(config){
10
- this.config = config;
11
- }
12
- async put(key, contents) {
13
- const filePath = path.join(this.config.location, key);
14
- const dir = path.dirname(filePath);
15
- await fs.mkdir(dir, {
16
- recursive: true
17
- });
18
- await fs.writeFile(filePath, contents);
19
- }
20
- async del(key) {
21
- await fs.rm(path.join(this.config.location, key));
22
- }
23
- getUrl(key) {
24
- return `${this.config.urlPrefix}/${key}`;
25
- }
26
- // 로컬 파일시스템은 signed URL을 지원하지 않으므로 일반 URL 반환
27
- async getSignedUrl(key, _expiresIn) {
28
- return this.getUrl(key);
29
- }
30
- destroy() {
31
- // 아무것도 하지 않음
32
- }
33
- }
34
- export class S3Driver {
35
- config;
36
- s3;
37
- constructor(config){
38
- this.config = config;
39
- this.s3 = new S3Client(config);
40
- }
41
- async put(key, contents, options) {
42
- await this.s3.send(new PutObjectCommand({
43
- Bucket: this.config.bucket,
44
- Key: key,
45
- Body: contents,
46
- ContentType: options?.contentType,
47
- ACL: this.getAcl(options?.visibility)
48
- }));
49
- }
50
- async del(key) {
51
- await this.s3.send(new DeleteObjectCommand({
52
- Bucket: this.config.bucket,
53
- Key: key
54
- }));
55
- }
56
- getUrl(key) {
57
- return `https://${this.config.bucket}.s3.${this.config.region}.amazonaws.com/${key}`;
58
- }
59
- async getSignedUrl(key, expiresIn) {
60
- const command = new GetObjectCommand({
61
- Bucket: this.config.bucket,
62
- Key: key
63
- });
64
- return getSignedUrl(this.s3, command, {
65
- expiresIn: expiresIn ?? 60 * 60 * 24 * 7
66
- });
67
- }
68
- getAcl(visibility) {
69
- if (visibility === "public") {
70
- return "public-read";
71
- }
72
- return visibility;
73
- }
74
- destroy() {
75
- this.s3.destroy();
76
- }
77
- }
78
-
79
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9maWxlLXN0b3JhZ2UvZHJpdmVyLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIERlbGV0ZU9iamVjdENvbW1hbmQsXG4gIEdldE9iamVjdENvbW1hbmQsXG4gIFB1dE9iamVjdENvbW1hbmQsXG4gIFMzQ2xpZW50LFxuICB0eXBlIFMzQ2xpZW50Q29uZmlnLFxufSBmcm9tIFwiQGF3cy1zZGsvY2xpZW50LXMzXCI7XG5pbXBvcnQgeyBnZXRTaWduZWRVcmwgfSBmcm9tIFwiQGF3cy1zZGsvczMtcmVxdWVzdC1wcmVzaWduZXJcIjtcbmltcG9ydCBmcyBmcm9tIFwiZnMvcHJvbWlzZXNcIjtcbmltcG9ydCBwYXRoIGZyb20gXCJwYXRoXCI7XG5cbi8qKlxuICog7YyM7J28IOyggOyepeyGjOydmCDqs7XthrUg7J247YSw7Y6Y7J207IqkXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgRHJpdmVyIHtcbiAgcHV0KFxuICAgIGtleTogc3RyaW5nLFxuICAgIGNvbnRlbnRzOiBCdWZmZXIsXG4gICAgb3B0aW9ucz86IHsgY29udGVudFR5cGU/OiBzdHJpbmc7IHZpc2liaWxpdHk/OiBcInB1YmxpY1wiIHwgXCJwcml2YXRlXCIgfSxcbiAgKTogUHJvbWlzZTx2b2lkPjtcblxuICBkZWwoa2V5OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+O1xuXG4gIGdldFVybChrZXk6IHN0cmluZyk6IHN0cmluZztcblxuICBnZXRTaWduZWRVcmwoa2V5OiBzdHJpbmcsIGV4cGlyZXNJbj86IG51bWJlcik6IFByb21pc2U8c3RyaW5nPjtcblxuICBkZXN0cm95KCk6IHZvaWQ7XG59XG5cbmV4cG9ydCB0eXBlIEZTRHJpdmVyQ29uZmlnID0ge1xuICBsb2NhdGlvbjogc3RyaW5nO1xuICB1cmxQcmVmaXg6IHN0cmluZztcbn07XG5cbi8qKlxuICog66Gc7LusIO2MjOydvOyLnOyKpO2FnFxuICovXG5leHBvcnQgY2xhc3MgRlNEcml2ZXIgaW1wbGVtZW50cyBEcml2ZXIge1xuICBjb25zdHJ1Y3Rvcihwcml2YXRlIGNvbmZpZzogRlNEcml2ZXJDb25maWcpIHt9XG5cbiAgYXN5bmMgcHV0KGtleTogc3RyaW5nLCBjb250ZW50czogQnVmZmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgZmlsZVBhdGggPSBwYXRoLmpvaW4odGhpcy5jb25maWcubG9jYXRpb24sIGtleSk7XG4gICAgY29uc3QgZGlyID0gcGF0aC5kaXJuYW1lKGZpbGVQYXRoKTtcblxuICAgIGF3YWl0IGZzLm1rZGlyKGRpciwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG5cbiAgICBhd2FpdCBmcy53cml0ZUZpbGUoZmlsZVBhdGgsIGNvbnRlbnRzKTtcbiAgfVxuXG4gIGFzeW5jIGRlbChrZXk6IHN0cmluZykge1xuICAgIGF3YWl0IGZzLnJtKHBhdGguam9pbih0aGlzLmNvbmZpZy5sb2NhdGlvbiwga2V5KSk7XG4gIH1cblxuICBnZXRVcmwoa2V5OiBzdHJpbmcpOiBzdHJpbmcge1xuICAgIHJldHVybiBgJHt0aGlzLmNvbmZpZy51cmxQcmVmaXh9LyR7a2V5fWA7XG4gIH1cblxuICAvLyDroZzsu6wg7YyM7J287Iuc7Iqk7YWc7J2AIHNpZ25lZCBVUkzsnYQg7KeA7JuQ7ZWY7KeAIOyViuycvOuvgOuhnCDsnbzrsJggVVJMIOuwmO2ZmFxuICBhc3luYyBnZXRTaWduZWRVcmwoa2V5OiBzdHJpbmcsIF9leHBpcmVzSW4/OiBudW1iZXIpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIHJldHVybiB0aGlzLmdldFVybChrZXkpO1xuICB9XG5cbiAgZGVzdHJveSgpIHtcbiAgICAvLyDslYTrrLTqsoPrj4Qg7ZWY7KeAIOyViuydjFxuICB9XG59XG5cbmV4cG9ydCB0eXBlIFMzRHJpdmVyQ29uZmlnID0gUzNDbGllbnRDb25maWcgJiB7XG4gIGJ1Y2tldDogc3RyaW5nO1xufTtcblxuZXhwb3J0IGNsYXNzIFMzRHJpdmVyIGltcGxlbWVudHMgRHJpdmVyIHtcbiAgczM6IFMzQ2xpZW50O1xuXG4gIGNvbnN0cnVjdG9yKHByaXZhdGUgY29uZmlnOiBTM0RyaXZlckNvbmZpZykge1xuICAgIHRoaXMuczMgPSBuZXcgUzNDbGllbnQoY29uZmlnKTtcbiAgfVxuXG4gIGFzeW5jIHB1dChcbiAgICBrZXk6IHN0cmluZyxcbiAgICBjb250ZW50czogQnVmZmVyLFxuICAgIG9wdGlvbnM/OiB7IGNvbnRlbnRUeXBlPzogc3RyaW5nOyB2aXNpYmlsaXR5PzogXCJwdWJsaWNcIiB8IFwicHJpdmF0ZVwiIH0sXG4gICk6IFByb21pc2U8dm9pZD4ge1xuICAgIGF3YWl0IHRoaXMuczMuc2VuZChcbiAgICAgIG5ldyBQdXRPYmplY3RDb21tYW5kKHtcbiAgICAgICAgQnVja2V0OiB0aGlzLmNvbmZpZy5idWNrZXQsXG4gICAgICAgIEtleToga2V5LFxuICAgICAgICBCb2R5OiBjb250ZW50cyxcbiAgICAgICAgQ29udGVudFR5cGU6IG9wdGlvbnM/LmNvbnRlbnRUeXBlLFxuICAgICAgICBBQ0w6IHRoaXMuZ2V0QWNsKG9wdGlvbnM/LnZpc2liaWxpdHkpLFxuICAgICAgfSksXG4gICAgKTtcbiAgfVxuXG4gIGFzeW5jIGRlbChrZXk6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIGF3YWl0IHRoaXMuczMuc2VuZChcbiAgICAgIG5ldyBEZWxldGVPYmplY3RDb21tYW5kKHtcbiAgICAgICAgQnVja2V0OiB0aGlzLmNvbmZpZy5idWNrZXQsXG4gICAgICAgIEtleToga2V5LFxuICAgICAgfSksXG4gICAgKTtcbiAgfVxuXG4gIGdldFVybChrZXk6IHN0cmluZyk6IHN0cmluZyB7XG4gICAgcmV0dXJuIGBodHRwczovLyR7dGhpcy5jb25maWcuYnVja2V0fS5zMy4ke3RoaXMuY29uZmlnLnJlZ2lvbn0uYW1hem9uYXdzLmNvbS8ke2tleX1gO1xuICB9XG5cbiAgYXN5bmMgZ2V0U2lnbmVkVXJsKGtleTogc3RyaW5nLCBleHBpcmVzSW4/OiBudW1iZXIpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIGNvbnN0IGNvbW1hbmQgPSBuZXcgR2V0T2JqZWN0Q29tbWFuZCh7XG4gICAgICBCdWNrZXQ6IHRoaXMuY29uZmlnLmJ1Y2tldCxcbiAgICAgIEtleToga2V5LFxuICAgIH0pO1xuXG4gICAgcmV0dXJuIGdldFNpZ25lZFVybCh0aGlzLnMzLCBjb21tYW5kLCB7XG4gICAgICBleHBpcmVzSW46IGV4cGlyZXNJbiA/PyA2MCAqIDYwICogMjQgKiA3LFxuICAgIH0pO1xuICB9XG5cbiAgcHJpdmF0ZSBnZXRBY2wodmlzaWJpbGl0eT86IFwicHVibGljXCIgfCBcInByaXZhdGVcIikge1xuICAgIGlmICh2aXNpYmlsaXR5ID09PSBcInB1YmxpY1wiKSB7XG4gICAgICByZXR1cm4gXCJwdWJsaWMtcmVhZFwiO1xuICAgIH1cblxuICAgIHJldHVybiB2aXNpYmlsaXR5O1xuICB9XG5cbiAgZGVzdHJveSgpIHtcbiAgICB0aGlzLnMzLmRlc3Ryb3koKTtcbiAgfVxufVxuIl0sIm5hbWVzIjpbIkRlbGV0ZU9iamVjdENvbW1hbmQiLCJHZXRPYmplY3RDb21tYW5kIiwiUHV0T2JqZWN0Q29tbWFuZCIsIlMzQ2xpZW50IiwiZ2V0U2lnbmVkVXJsIiwiZnMiLCJwYXRoIiwiRlNEcml2ZXIiLCJjb25maWciLCJwdXQiLCJrZXkiLCJjb250ZW50cyIsImZpbGVQYXRoIiwiam9pbiIsImxvY2F0aW9uIiwiZGlyIiwiZGlybmFtZSIsIm1rZGlyIiwicmVjdXJzaXZlIiwid3JpdGVGaWxlIiwiZGVsIiwicm0iLCJnZXRVcmwiLCJ1cmxQcmVmaXgiLCJfZXhwaXJlc0luIiwiZGVzdHJveSIsIlMzRHJpdmVyIiwiczMiLCJvcHRpb25zIiwic2VuZCIsIkJ1Y2tldCIsImJ1Y2tldCIsIktleSIsIkJvZHkiLCJDb250ZW50VHlwZSIsImNvbnRlbnRUeXBlIiwiQUNMIiwiZ2V0QWNsIiwidmlzaWJpbGl0eSIsInJlZ2lvbiIsImV4cGlyZXNJbiIsImNvbW1hbmQiXSwibWFwcGluZ3MiOiJBQUFBLFNBQ0VBLG1CQUFtQixF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
@@ -1,50 +0,0 @@
1
- import type { MultipartFile } from "@fastify/multipart";
2
- import type { Driver } from "./driver";
3
- /**
4
- * @fastify/multipart의 MultipartFile 래퍼
5
- */
6
- export declare class FileStorage {
7
- private _file;
8
- private _buffer?;
9
- private _driver;
10
- constructor(file: MultipartFile, driver: Driver);
11
- /**
12
- * 사용자 컴퓨터의 원본 파일명
13
- */
14
- get clientName(): string;
15
- /**
16
- * 파일명 (clientName의 별칭)
17
- */
18
- get filename(): string;
19
- /**
20
- * HTML input 필드명
21
- */
22
- get fieldName(): string;
23
- /**
24
- * 파일 크기 (바이트)
25
- */
26
- get size(): number;
27
- /**
28
- * 파일 확장자 (점 제외)
29
- */
30
- get extname(): string | false;
31
- get mimetype(): string;
32
- get encoding(): string;
33
- toBuffer(): Promise<Buffer>;
34
- md5(): Promise<string>;
35
- /**
36
- * 파일을 저장소에 저장
37
- *
38
- * @example
39
- * ```typescript
40
- * const { file } = Sonamu.getUploadContext();
41
- * const url = await file.saveToDisk('uploads/avatar.png');
42
- * ```
43
- */
44
- saveToDisk(key: string, options?: {
45
- contentType?: string;
46
- visibility?: "public" | "private";
47
- }): Promise<string>;
48
- get raw(): MultipartFile;
49
- }
50
- //# sourceMappingURL=file-storage.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"file-storage.d.ts","sourceRoot":"","sources":["../../src/file-storage/file-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;gBAEZ,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM;IAK/C;;OAEG;IACH,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,MAAM,GAAG,KAAK,CAE5B;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAEK,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAO3B,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC;IAK5B;;;;;;;;OAQG;IACG,UAAU,CACd,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;KAAE,GACpE,OAAO,CAAC,MAAM,CAAC;IAWlB,IAAI,GAAG,IAAI,aAAa,CAEvB;CACF"}
@@ -1,75 +0,0 @@
1
- import { createHash } from "crypto";
2
- import mime from "mime-types";
3
- /**
4
- * @fastify/multipart의 MultipartFile 래퍼
5
- */ export class FileStorage {
6
- _file;
7
- _buffer;
8
- _driver;
9
- constructor(file, driver){
10
- this._file = file;
11
- this._driver = driver;
12
- }
13
- /**
14
- * 사용자 컴퓨터의 원본 파일명
15
- */ get clientName() {
16
- return this._file.filename;
17
- }
18
- /**
19
- * 파일명 (clientName의 별칭)
20
- */ get filename() {
21
- return this._file.filename;
22
- }
23
- /**
24
- * HTML input 필드명
25
- */ get fieldName() {
26
- return this._file.fieldname;
27
- }
28
- /**
29
- * 파일 크기 (바이트)
30
- */ get size() {
31
- return this._file.file.bytesRead;
32
- }
33
- /**
34
- * 파일 확장자 (점 제외)
35
- */ get extname() {
36
- return mime.extension(this._file.mimetype);
37
- }
38
- get mimetype() {
39
- return this._file.mimetype;
40
- }
41
- get encoding() {
42
- return this._file.encoding;
43
- }
44
- async toBuffer() {
45
- if (!this._buffer) {
46
- this._buffer = await this._file.toBuffer();
47
- }
48
- return this._buffer;
49
- }
50
- async md5() {
51
- const buffer = await this.toBuffer();
52
- return createHash("md5").update(buffer).digest("hex");
53
- }
54
- /**
55
- * 파일을 저장소에 저장
56
- *
57
- * @example
58
- * ```typescript
59
- * const { file } = Sonamu.getUploadContext();
60
- * const url = await file.saveToDisk('uploads/avatar.png');
61
- * ```
62
- */ async saveToDisk(key, options) {
63
- const buffer = await this.toBuffer();
64
- await this._driver.put(key, buffer, {
65
- contentType: options?.contentType ?? this.mimetype,
66
- visibility: options?.visibility
67
- });
68
- return this._driver.getSignedUrl(key);
69
- }
70
- get raw() {
71
- return this._file;
72
- }
73
- }
74
-
75
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9maWxlLXN0b3JhZ2UvZmlsZS1zdG9yYWdlLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB0eXBlIHsgTXVsdGlwYXJ0RmlsZSB9IGZyb20gXCJAZmFzdGlmeS9tdWx0aXBhcnRcIjtcbmltcG9ydCB7IGNyZWF0ZUhhc2ggfSBmcm9tIFwiY3J5cHRvXCI7XG5pbXBvcnQgbWltZSBmcm9tIFwibWltZS10eXBlc1wiO1xuaW1wb3J0IHR5cGUgeyBEcml2ZXIgfSBmcm9tIFwiLi9kcml2ZXJcIjtcblxuLyoqXG4gKiBAZmFzdGlmeS9tdWx0aXBhcnTsnZggTXVsdGlwYXJ0RmlsZSDrnpjtjbxcbiAqL1xuZXhwb3J0IGNsYXNzIEZpbGVTdG9yYWdlIHtcbiAgcHJpdmF0ZSBfZmlsZTogTXVsdGlwYXJ0RmlsZTtcbiAgcHJpdmF0ZSBfYnVmZmVyPzogQnVmZmVyO1xuICBwcml2YXRlIF9kcml2ZXI6IERyaXZlcjtcblxuICBjb25zdHJ1Y3RvcihmaWxlOiBNdWx0aXBhcnRGaWxlLCBkcml2ZXI6IERyaXZlcikge1xuICAgIHRoaXMuX2ZpbGUgPSBmaWxlO1xuICAgIHRoaXMuX2RyaXZlciA9IGRyaXZlcjtcbiAgfVxuXG4gIC8qKlxuICAgKiDsgqzsmqnsnpAg7Lu07ZOo7YSw7J2YIOybkOuzuCDtjIzsnbzrqoVcbiAgICovXG4gIGdldCBjbGllbnROYW1lKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIHRoaXMuX2ZpbGUuZmlsZW5hbWU7XG4gIH1cblxuICAvKipcbiAgICog7YyM7J2866qFIChjbGllbnROYW1l7J2YIOuzhOy5rSlcbiAgICovXG4gIGdldCBmaWxlbmFtZSgpOiBzdHJpbmcge1xuICAgIHJldHVybiB0aGlzLl9maWxlLmZpbGVuYW1lO1xuICB9XG5cbiAgLyoqXG4gICAqIEhUTUwgaW5wdXQg7ZWE65Oc66qFXG4gICAqL1xuICBnZXQgZmllbGROYW1lKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIHRoaXMuX2ZpbGUuZmllbGRuYW1lO1xuICB9XG5cbiAgLyoqXG4gICAqIO2MjOydvCDtgazquLAgKOuwlOydtO2KuClcbiAgICovXG4gIGdldCBzaXplKCk6IG51bWJlciB7XG4gICAgcmV0dXJuIHRoaXMuX2ZpbGUuZmlsZS5ieXRlc1JlYWQ7XG4gIH1cblxuICAvKipcbiAgICog7YyM7J28IO2ZleyepeyekCAo7KCQIOygnOyZuClcbiAgICovXG4gIGdldCBleHRuYW1lKCk6IHN0cmluZyB8IGZhbHNlIHtcbiAgICByZXR1cm4gbWltZS5leHRlbnNpb24odGhpcy5fZmlsZS5taW1ldHlwZSk7XG4gIH1cblxuICBnZXQgbWltZXR5cGUoKTogc3RyaW5nIHtcbiAgICByZXR1cm4gdGhpcy5fZmlsZS5taW1ldHlwZTtcbiAgfVxuXG4gIGdldCBlbmNvZGluZygpOiBzdHJpbmcge1xuICAgIHJldHVybiB0aGlzLl9maWxlLmVuY29kaW5nO1xuICB9XG5cbiAgYXN5bmMgdG9CdWZmZXIoKTogUHJvbWlzZTxCdWZmZXI+IHtcbiAgICBpZiAoIXRoaXMuX2J1ZmZlcikge1xuICAgICAgdGhpcy5fYnVmZmVyID0gYXdhaXQgdGhpcy5fZmlsZS50b0J1ZmZlcigpO1xuICAgIH1cbiAgICByZXR1cm4gdGhpcy5fYnVmZmVyO1xuICB9XG5cbiAgYXN5bmMgbWQ1KCk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgY29uc3QgYnVmZmVyID0gYXdhaXQgdGhpcy50b0J1ZmZlcigpO1xuICAgIHJldHVybiBjcmVhdGVIYXNoKFwibWQ1XCIpLnVwZGF0ZShidWZmZXIpLmRpZ2VzdChcImhleFwiKTtcbiAgfVxuXG4gIC8qKlxuICAgKiDtjIzsnbzsnYQg7KCA7J6l7IaM7JeQIOyggOyepVxuICAgKlxuICAgKiBAZXhhbXBsZVxuICAgKiBgYGB0eXBlc2NyaXB0XG4gICAqIGNvbnN0IHsgZmlsZSB9ID0gU29uYW11LmdldFVwbG9hZENvbnRleHQoKTtcbiAgICogY29uc3QgdXJsID0gYXdhaXQgZmlsZS5zYXZlVG9EaXNrKCd1cGxvYWRzL2F2YXRhci5wbmcnKTtcbiAgICogYGBgXG4gICAqL1xuICBhc3luYyBzYXZlVG9EaXNrKFxuICAgIGtleTogc3RyaW5nLFxuICAgIG9wdGlvbnM/OiB7IGNvbnRlbnRUeXBlPzogc3RyaW5nOyB2aXNpYmlsaXR5PzogXCJwdWJsaWNcIiB8IFwicHJpdmF0ZVwiIH0sXG4gICk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgY29uc3QgYnVmZmVyID0gYXdhaXQgdGhpcy50b0J1ZmZlcigpO1xuXG4gICAgYXdhaXQgdGhpcy5fZHJpdmVyLnB1dChrZXksIGJ1ZmZlciwge1xuICAgICAgY29udGVudFR5cGU6IG9wdGlvbnM/LmNvbnRlbnRUeXBlID8/IHRoaXMubWltZXR5cGUsXG4gICAgICB2aXNpYmlsaXR5OiBvcHRpb25zPy52aXNpYmlsaXR5LFxuICAgIH0pO1xuXG4gICAgcmV0dXJuIHRoaXMuX2RyaXZlci5nZXRTaWduZWRVcmwoa2V5KTtcbiAgfVxuXG4gIGdldCByYXcoKTogTXVsdGlwYXJ0RmlsZSB7XG4gICAgcmV0dXJuIHRoaXMuX2ZpbGU7XG4gIH1cbn1cbiJdLCJuYW1lcyI6WyJjcmVhdGVIYXNoIiwibWltZSIsIkZpbGVTdG9yYWdlIiwiX2ZpbGUiLCJfYnVmZmVyIiwiX2RyaXZlciIsImZpbGUiLCJkcml2ZXIiLCJjbGllbnROYW1lIiwiZmlsZW5hbWUiLCJmaWVsZE5hbWUiLCJmaWVsZG5hbWUiLCJzaXplIiwiYnl0ZXNSZWFkIiwiZXh0bmFtZSIsImV4dGVuc2lvbiIsIm1pbWV0eXBlIiwiZW5jb2RpbmciLCJ0b0J1ZmZlciIsIm1kNSIsImJ1ZmZlciIsInVwZGF0ZSIsImRpZ2VzdCIsInNhdmVUb0Rpc2siLCJrZXkiLCJvcHRpb25zIiwicHV0IiwiY29udGVudFR5cGUiLCJ2aXNpYmlsaXR5IiwiZ2V0U2lnbmVkVXJsIiwicmF3Il0sIm1hcHBpbmdzIjoiQUFDQSxTQUFTQSxVQUFVLFFBQVEsU0FBUztBQUNwQyxPQUFPQyxVQUFVLGFBQWE7QUFHOUI7O0NBRUMsR0FDRCxPQUFPLE1BQU1DO0lBQ0hDLE1BQXFCO0lBQ3JCQyxRQUFpQjtJQUNqQkMsUUFBZ0I7SUFFeEIsWUFBWUMsSUFBbUIsRUFBRUMsTUFBYyxDQUFFO1FBQy9DLElBQUksQ0FBQ0osS0FBSyxHQUFHRztRQUNiLElBQUksQ0FBQ0QsT0FBTyxHQUFHRTtJQUNqQjtJQUVBOztHQUVDLEdBQ0QsSUFBSUMsYUFBcUI7UUFDdkIsT0FBTyxJQUFJLENBQUNMLEtBQUssQ0FBQ00sUUFBUTtJQUM1QjtJQUVBOztHQUVDLEdBQ0QsSUFBSUEsV0FBbUI7UUFDckIsT0FBTyxJQUFJLENBQUNOLEtBQUssQ0FBQ00sUUFBUTtJQUM1QjtJQUVBOztHQUVDLEdBQ0QsSUFBSUMsWUFBb0I7UUFDdEIsT0FBTyxJQUFJLENBQUNQLEtBQUssQ0FBQ1EsU0FBUztJQUM3QjtJQUVBOztHQUVDLEdBQ0QsSUFBSUMsT0FBZTtRQUNqQixPQUFPLElBQUksQ0FBQ1QsS0FBSyxDQUFDRyxJQUFJLENBQUNPLFNBQVM7SUFDbEM7SUFFQTs7R0FFQyxHQUNELElBQUlDLFVBQTBCO1FBQzVCLE9BQU9iLEtBQUtjLFNBQVMsQ0FBQyxJQUFJLENBQUNaLEtBQUssQ0FBQ2EsUUFBUTtJQUMzQztJQUVBLElBQUlBLFdBQW1CO1FBQ3JCLE9BQU8sSUFBSSxDQUFDYixLQUFLLENBQUNhLFFBQVE7SUFDNUI7SUFFQSxJQUFJQyxXQUFtQjtRQUNyQixPQUFPLElBQUksQ0FBQ2QsS0FBSyxDQUFDYyxRQUFRO0lBQzVCO0lBRUEsTUFBTUMsV0FBNEI7UUFDaEMsSUFBSSxDQUFDLElBQUksQ0FBQ2QsT0FBTyxFQUFFO1lBQ2pCLElBQUksQ0FBQ0EsT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDRCxLQUFLLENBQUNlLFFBQVE7UUFDMUM7UUFDQSxPQUFPLElBQUksQ0FBQ2QsT0FBTztJQUNyQjtJQUVBLE1BQU1lLE1BQXVCO1FBQzNCLE1BQU1DLFNBQVMsTUFBTSxJQUFJLENBQUNGLFFBQVE7UUFDbEMsT0FBT2xCLFdBQVcsT0FBT3FCLE1BQU0sQ0FBQ0QsUUFBUUUsTUFBTSxDQUFDO0lBQ2pEO0lBRUE7Ozs7Ozs7O0dBUUMsR0FDRCxNQUFNQyxXQUNKQyxHQUFXLEVBQ1hDLE9BQXFFLEVBQ3BEO1FBQ2pCLE1BQU1MLFNBQVMsTUFBTSxJQUFJLENBQUNGLFFBQVE7UUFFbEMsTUFBTSxJQUFJLENBQUNiLE9BQU8sQ0FBQ3FCLEdBQUcsQ0FBQ0YsS0FBS0osUUFBUTtZQUNsQ08sYUFBYUYsU0FBU0UsZUFBZSxJQUFJLENBQUNYLFFBQVE7WUFDbERZLFlBQVlILFNBQVNHO1FBQ3ZCO1FBRUEsT0FBTyxJQUFJLENBQUN2QixPQUFPLENBQUN3QixZQUFZLENBQUNMO0lBQ25DO0lBRUEsSUFBSU0sTUFBcUI7UUFDdkIsT0FBTyxJQUFJLENBQUMzQixLQUFLO0lBQ25CO0FBQ0YifQ==