sonamu 0.7.18 → 0.7.19

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 (55) hide show
  1. package/dist/api/config.d.ts +19 -2
  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/index.d.ts +0 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1 -2
  18. package/dist/storage/drivers.d.ts +14 -0
  19. package/dist/storage/drivers.d.ts.map +1 -0
  20. package/dist/storage/drivers.js +11 -0
  21. package/dist/storage/index.d.ts +5 -0
  22. package/dist/storage/index.d.ts.map +1 -0
  23. package/dist/storage/index.js +6 -0
  24. package/dist/storage/storage-manager.d.ts +21 -0
  25. package/dist/storage/storage-manager.d.ts.map +1 -0
  26. package/dist/storage/storage-manager.js +33 -0
  27. package/dist/storage/types.d.ts +12 -0
  28. package/dist/storage/types.d.ts.map +1 -0
  29. package/dist/storage/types.js +5 -0
  30. package/dist/storage/uploaded-file.d.ts +35 -0
  31. package/dist/storage/uploaded-file.d.ts.map +1 -0
  32. package/dist/storage/uploaded-file.js +58 -0
  33. package/dist/template/implementations/services.template.js +5 -5
  34. package/package.json +7 -2
  35. package/src/api/config.ts +19 -2
  36. package/src/api/context.ts +3 -3
  37. package/src/api/decorators.ts +3 -8
  38. package/src/api/index.ts +0 -2
  39. package/src/api/sonamu.ts +12 -9
  40. package/src/index.ts +0 -1
  41. package/src/storage/drivers.ts +15 -0
  42. package/src/storage/index.ts +5 -0
  43. package/src/storage/storage-manager.ts +39 -0
  44. package/src/storage/types.ts +12 -0
  45. package/src/storage/uploaded-file.ts +81 -0
  46. package/src/template/implementations/service.template.ts.txt +328 -0
  47. package/src/template/implementations/services.template.ts +4 -4
  48. package/dist/file-storage/driver.d.ts +0 -48
  49. package/dist/file-storage/driver.d.ts.map +0 -1
  50. package/dist/file-storage/driver.js +0 -79
  51. package/dist/file-storage/file-storage.d.ts +0 -50
  52. package/dist/file-storage/file-storage.d.ts.map +0 -1
  53. package/dist/file-storage/file-storage.js +0 -75
  54. package/src/file-storage/driver.ts +0 -131
  55. 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
  );
@@ -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==