@fuzionx/core 0.1.42 → 0.1.44

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/lib/i18n.js CHANGED
@@ -1,37 +1,37 @@
1
- /**
2
- * I18n — fuzionx-bridge i18n N-API를 래핑하는 헬퍼.
3
- * app.i18n으로 접근, req.t()는 context.js에서 구현.
4
- */
5
-
6
- export function createI18n(bridge) {
7
- return {
8
- /** 번역 조회 */
9
- translate: (locale, key) => bridge.i18NTranslate(locale, key),
10
-
11
- /** 로케일 목록 */
12
- get locales() {
13
- return bridge.i18NGetLocales();
14
- },
15
-
16
- /** 현재 로케일 변경 */
17
- setLocale: (locale) => bridge.i18NSetLocale(locale),
18
-
19
- /** 누락 키 업데이트 (auto_complete) */
20
- updateMissing: (key, value) => bridge.i18NUpdateMissingKey(key, value),
21
-
22
- /** auto_complete 활성화 여부 */
23
- get autoComplete() {
24
- return bridge.i18NIsAutoComplete();
25
- },
26
-
27
- /**
28
- * SSR 템플릿 렌더링
29
- * @param {string} template - Tera 템플릿 문자열
30
- * @param {object} context - 템플릿 변수
31
- * @param {string} [locale='en'] - 로케일
32
- */
33
- render: (template, context = {}, locale = 'en') => {
34
- return bridge.ssrRenderString(template, context, locale);
35
- },
36
- };
37
- }
1
+ /**
2
+ * I18n — fuzionx-bridge i18n N-API를 래핑하는 헬퍼.
3
+ * app.i18n으로 접근, req.t()는 context.js에서 구현.
4
+ */
5
+
6
+ export function createI18n(bridge) {
7
+ return {
8
+ /** 번역 조회 */
9
+ translate: (locale, key) => bridge.i18NTranslate(locale, key),
10
+
11
+ /** 로케일 목록 */
12
+ get locales() {
13
+ return bridge.i18NGetLocales();
14
+ },
15
+
16
+ /** 현재 로케일 변경 */
17
+ setLocale: (locale) => bridge.i18NSetLocale(locale),
18
+
19
+ /** 누락 키 업데이트 (auto_complete) */
20
+ updateMissing: (key, value) => bridge.i18NUpdateMissingKey(key, value),
21
+
22
+ /** auto_complete 활성화 여부 */
23
+ get autoComplete() {
24
+ return bridge.i18NIsAutoComplete();
25
+ },
26
+
27
+ /**
28
+ * SSR 템플릿 렌더링
29
+ * @param {string} template - Tera 템플릿 문자열
30
+ * @param {object} context - 템플릿 변수
31
+ * @param {string} [locale='en'] - 로케일
32
+ */
33
+ render: (template, context = {}, locale = 'en') => {
34
+ return bridge.ssrRenderString(template, context, locale);
35
+ },
36
+ };
37
+ }
package/lib/logger.js CHANGED
@@ -1,146 +1,146 @@
1
- /**
2
- * @fileoverview FuzionX Bridge 통합 로거.
3
- *
4
- * Rust tracing과 Node.js console을 일원화한다.
5
- * - createLogger(bridge): app.logger 객체 생성
6
- * - interceptConsole(bridge): console.* 가로채기
7
- *
8
- * @module logger
9
- */
10
-
11
- import { format } from 'node:util';
12
-
13
- /**
14
- * 스택 프레임 문자열에서 파일:라인을 추출한다.
15
- * @param {string} frameLine - 스택 프레임 한 줄
16
- * @returns {string} 파일:라인 또는 빈 문자열
17
- */
18
- function parseFrame(frameLine) {
19
- const matchParen = frameLine.match(/\((.+):(\d+):\d+\)/);
20
- if (matchParen) return `${matchParen[1]}:${matchParen[2]}`;
21
- const matchDirect = frameLine.match(/at (.+):(\d+):\d+/);
22
- if (matchDirect) return `${matchDirect[1]}:${matchDirect[2]}`;
23
- return '';
24
- }
25
-
26
- /** 로거/프레임워크 내부 파일 — 호출 위치 추출 시 건너뜀 */
27
- const SKIP_PATTERNS = ['/logger.js:', '/Logger.js:', '/ErrorHandler.js:'];
28
-
29
- /**
30
- * 인자 목록에서 Error 객체를 찾아 throw 위치를 반환한다.
31
- * @param {any[]} args
32
- * @returns {string} 파일:라인 또는 빈 문자열
33
- */
34
- function findErrorOrigin(args) {
35
- for (const arg of args) {
36
- if (arg instanceof Error && arg.stack) {
37
- return parseFrame(arg.stack.split('\n')[1] || '');
38
- }
39
- }
40
- return '';
41
- }
42
-
43
- /**
44
- * 호출 스택에서 로거/프레임워크 내부 파일을 건너뛴 첫 외부 호출 위치를 반환한다.
45
- * depth 기반이 아니라 파일명 패턴 기반이므로 호출 체인 변경에 안전하다.
46
- *
47
- * @returns {string} 파일:라인 또는 빈 문자열
48
- */
49
- function getExternalCallSite() {
50
- const stack = new Error().stack;
51
- if (!stack) return '';
52
- const lines = stack.split('\n');
53
- for (let i = 1; i < lines.length; i++) {
54
- const frame = lines[i];
55
- // 로거/프레임워크 내부 프레임 스킵
56
- if (SKIP_PATTERNS.some(p => frame.includes(p))) continue;
57
- return parseFrame(frame);
58
- }
59
- return '';
60
- }
61
-
62
- /**
63
- * 에러/경고 로그의 위치를 결정한다.
64
- * 1. 인자에 Error 객체가 있으면 → err.stack에서 throw 위치
65
- * 2. 없으면 → 호출 스택에서 로거 파일을 건너뛴 외부 호출 위치
66
- *
67
- * @param {any[]} args
68
- * @returns {string}
69
- */
70
- function resolveLocation(args) {
71
- return findErrorOrigin(args) || getExternalCallSite();
72
- }
73
-
74
- /**
75
- * Rust N-API bridge를 통한 로거 객체를 생성한다.
76
- *
77
- * @param {object} bridge - fuzionx-bridge N-API 모듈
78
- * @returns {{ info: Function, warn: Function, error: Function, debug: Function }}
79
- */
80
- export function createLogger(bridge) {
81
- return {
82
- info(...args) {
83
- bridge.logInfo('app', format(...args));
84
- },
85
-
86
- warn(...args) {
87
- const location = resolveLocation(args);
88
- bridge.logWarn('app', format(...args), location || undefined);
89
- },
90
-
91
- error(...args) {
92
- const location = resolveLocation(args);
93
- bridge.logError('app', format(...args), location || undefined);
94
- },
95
-
96
- debug(...args) {
97
- bridge.logDebug('app', format(...args));
98
- },
99
- };
100
- }
101
-
102
- /**
103
- * console.log/warn/error/debug를 가로채서 Rust tracing으로 전달한다.
104
- *
105
- * @param {object} bridge - fuzionx-bridge N-API 모듈
106
- * @returns {Function} 원래 console을 복원하는 함수
107
- */
108
- export function interceptConsole(bridge) {
109
- const orig = {
110
- log: console.log,
111
- info: console.info,
112
- warn: console.warn,
113
- error: console.error,
114
- debug: console.debug,
115
- };
116
-
117
- console.log = (...args) => {
118
- bridge.logInfo('console', format(...args));
119
- };
120
-
121
- console.info = (...args) => {
122
- bridge.logInfo('console', format(...args));
123
- };
124
-
125
- console.warn = (...args) => {
126
- const location = resolveLocation(args);
127
- bridge.logWarn('console', format(...args), location || undefined);
128
- };
129
-
130
- console.error = (...args) => {
131
- const location = resolveLocation(args);
132
- bridge.logError('console', format(...args), location || undefined);
133
- };
134
-
135
- console.debug = (...args) => {
136
- bridge.logDebug('console', format(...args));
137
- };
138
-
139
- return function restoreConsole() {
140
- console.log = orig.log;
141
- console.info = orig.info;
142
- console.warn = orig.warn;
143
- console.error = orig.error;
144
- console.debug = orig.debug;
145
- };
146
- }
1
+ /**
2
+ * @fileoverview FuzionX Bridge 통합 로거.
3
+ *
4
+ * Rust tracing과 Node.js console을 일원화한다.
5
+ * - createLogger(bridge): app.logger 객체 생성
6
+ * - interceptConsole(bridge): console.* 가로채기
7
+ *
8
+ * @module logger
9
+ */
10
+
11
+ import { format } from 'node:util';
12
+
13
+ /**
14
+ * 스택 프레임 문자열에서 파일:라인을 추출한다.
15
+ * @param {string} frameLine - 스택 프레임 한 줄
16
+ * @returns {string} 파일:라인 또는 빈 문자열
17
+ */
18
+ function parseFrame(frameLine) {
19
+ const matchParen = frameLine.match(/\((.+):(\d+):\d+\)/);
20
+ if (matchParen) return `${matchParen[1]}:${matchParen[2]}`;
21
+ const matchDirect = frameLine.match(/at (.+):(\d+):\d+/);
22
+ if (matchDirect) return `${matchDirect[1]}:${matchDirect[2]}`;
23
+ return '';
24
+ }
25
+
26
+ /** 로거/프레임워크 내부 파일 — 호출 위치 추출 시 건너뜀 */
27
+ const SKIP_PATTERNS = ['/logger.js:', '/Logger.js:', '/ErrorHandler.js:'];
28
+
29
+ /**
30
+ * 인자 목록에서 Error 객체를 찾아 throw 위치를 반환한다.
31
+ * @param {any[]} args
32
+ * @returns {string} 파일:라인 또는 빈 문자열
33
+ */
34
+ function findErrorOrigin(args) {
35
+ for (const arg of args) {
36
+ if (arg instanceof Error && arg.stack) {
37
+ return parseFrame(arg.stack.split('\n')[1] || '');
38
+ }
39
+ }
40
+ return '';
41
+ }
42
+
43
+ /**
44
+ * 호출 스택에서 로거/프레임워크 내부 파일을 건너뛴 첫 외부 호출 위치를 반환한다.
45
+ * depth 기반이 아니라 파일명 패턴 기반이므로 호출 체인 변경에 안전하다.
46
+ *
47
+ * @returns {string} 파일:라인 또는 빈 문자열
48
+ */
49
+ function getExternalCallSite() {
50
+ const stack = new Error().stack;
51
+ if (!stack) return '';
52
+ const lines = stack.split('\n');
53
+ for (let i = 1; i < lines.length; i++) {
54
+ const frame = lines[i];
55
+ // 로거/프레임워크 내부 프레임 스킵
56
+ if (SKIP_PATTERNS.some(p => frame.includes(p))) continue;
57
+ return parseFrame(frame);
58
+ }
59
+ return '';
60
+ }
61
+
62
+ /**
63
+ * 에러/경고 로그의 위치를 결정한다.
64
+ * 1. 인자에 Error 객체가 있으면 → err.stack에서 throw 위치
65
+ * 2. 없으면 → 호출 스택에서 로거 파일을 건너뛴 외부 호출 위치
66
+ *
67
+ * @param {any[]} args
68
+ * @returns {string}
69
+ */
70
+ function resolveLocation(args) {
71
+ return findErrorOrigin(args) || getExternalCallSite();
72
+ }
73
+
74
+ /**
75
+ * Rust N-API bridge를 통한 로거 객체를 생성한다.
76
+ *
77
+ * @param {object} bridge - fuzionx-bridge N-API 모듈
78
+ * @returns {{ info: Function, warn: Function, error: Function, debug: Function }}
79
+ */
80
+ export function createLogger(bridge) {
81
+ return {
82
+ info(...args) {
83
+ bridge.logInfo('app', format(...args));
84
+ },
85
+
86
+ warn(...args) {
87
+ const location = resolveLocation(args);
88
+ bridge.logWarn('app', format(...args), location || undefined);
89
+ },
90
+
91
+ error(...args) {
92
+ const location = resolveLocation(args);
93
+ bridge.logError('app', format(...args), location || undefined);
94
+ },
95
+
96
+ debug(...args) {
97
+ bridge.logDebug('app', format(...args));
98
+ },
99
+ };
100
+ }
101
+
102
+ /**
103
+ * console.log/warn/error/debug를 가로채서 Rust tracing으로 전달한다.
104
+ *
105
+ * @param {object} bridge - fuzionx-bridge N-API 모듈
106
+ * @returns {Function} 원래 console을 복원하는 함수
107
+ */
108
+ export function interceptConsole(bridge) {
109
+ const orig = {
110
+ log: console.log,
111
+ info: console.info,
112
+ warn: console.warn,
113
+ error: console.error,
114
+ debug: console.debug,
115
+ };
116
+
117
+ console.log = (...args) => {
118
+ bridge.logInfo('console', format(...args));
119
+ };
120
+
121
+ console.info = (...args) => {
122
+ bridge.logInfo('console', format(...args));
123
+ };
124
+
125
+ console.warn = (...args) => {
126
+ const location = resolveLocation(args);
127
+ bridge.logWarn('console', format(...args), location || undefined);
128
+ };
129
+
130
+ console.error = (...args) => {
131
+ const location = resolveLocation(args);
132
+ bridge.logError('console', format(...args), location || undefined);
133
+ };
134
+
135
+ console.debug = (...args) => {
136
+ bridge.logDebug('console', format(...args));
137
+ };
138
+
139
+ return function restoreConsole() {
140
+ console.log = orig.log;
141
+ console.info = orig.info;
142
+ console.warn = orig.warn;
143
+ console.error = orig.error;
144
+ console.debug = orig.debug;
145
+ };
146
+ }
package/lib/media.js CHANGED
@@ -1,75 +1,75 @@
1
- /**
2
- * MediaHelper — fuzionx-bridge 미디어(이미지+비디오) N-API 래핑.
3
- * app.media로 접근.
4
- *
5
- * 최적화:
6
- * - 이미지 처리: Rust image+webp crate 직통 (Lanczos3 리사이즈)
7
- * - 비디오: ffmpeg CLI 호출 (Rust std::process::Command)
8
- * - JS 힙 메모리 무할당 — 파일-to-파일 처리
9
- */
10
-
11
- export function createMedia(bridge) {
12
- return {
13
- // ── 이미지 ──
14
-
15
- /**
16
- * 이미지 리사이즈 (종횡비 유지).
17
- * @param {string} input - 입력 파일 경로
18
- * @param {string} output - 출력 파일 경로
19
- * @param {number} width - 최대 가로
20
- * @param {number} height - 최대 세로
21
- * @param {string} [format='webp'] - 출력 포맷
22
- * @param {number} [quality=80] - 품질 (1-100)
23
- * @returns {string} 출력 경로
24
- */
25
- resize: (input, output, width, height, format, quality) =>
26
- bridge.mediaResize(input, output, width, height, format, quality),
27
-
28
- /**
29
- * 다중 사이즈 리사이즈 (한 번의 디코드로 여러 출력).
30
- * @param {string} input - 입력 파일 경로
31
- * @param {string} outputDir - 출력 디렉토리
32
- * @param {string} baseName - 기본 파일명
33
- * @param {Array<{width:number,height:number,format?:string,quality?:number,suffix:string}>} specs
34
- * @returns {string[]} 출력 경로 배열
35
- */
36
- resizeMultiple: (input, outputDir, baseName, specs) =>
37
- bridge.mediaResizeMultiple(input, outputDir, baseName, specs),
38
-
39
- /**
40
- * 이미지 정보 조회.
41
- * @returns {{width:number, height:number, format:string}}
42
- */
43
- imageInfo: (filePath) => bridge.mediaGetImageInfo(filePath),
44
-
45
- /**
46
- * WebP 변환.
47
- * @param {string} input - 입력 경로
48
- * @param {string} output - 출력 경로
49
- * @param {number} [quality=80] - 품질
50
- * @returns {string} 출력 경로
51
- */
52
- toWebp: (input, output, quality) =>
53
- bridge.mediaConvertToWebp(input, output, quality),
54
-
55
- // ── 비디오 ──
56
-
57
- /**
58
- * 비디오 썸네일 추출 (ffmpeg).
59
- * @param {string} input - 비디오 경로
60
- * @param {string} output - 출력 이미지 경로
61
- * @param {number} [atSeconds=3] - 캡처 시점 (초)
62
- * @param {number} [width=0] - 출력 가로 (0=원본)
63
- * @param {string} [format='jpeg'] - 출력 포맷
64
- * @returns {string} 출력 경로
65
- */
66
- videoThumbnail: (input, output, atSeconds, width, format) =>
67
- bridge.mediaVideoThumbnail(input, output, atSeconds, width, format),
68
-
69
- /**
70
- * 비디오 정보 조회 (ffprobe).
71
- * @returns {{duration:number, width:number, height:number, codec:string, fps:number}}
72
- */
73
- videoInfo: (filePath) => bridge.mediaVideoInfo(filePath),
74
- };
75
- }
1
+ /**
2
+ * MediaHelper — fuzionx-bridge 미디어(이미지+비디오) N-API 래핑.
3
+ * app.media로 접근.
4
+ *
5
+ * 최적화:
6
+ * - 이미지 처리: Rust image+webp crate 직통 (Lanczos3 리사이즈)
7
+ * - 비디오: ffmpeg CLI 호출 (Rust std::process::Command)
8
+ * - JS 힙 메모리 무할당 — 파일-to-파일 처리
9
+ */
10
+
11
+ export function createMedia(bridge) {
12
+ return {
13
+ // ── 이미지 ──
14
+
15
+ /**
16
+ * 이미지 리사이즈 (종횡비 유지).
17
+ * @param {string} input - 입력 파일 경로
18
+ * @param {string} output - 출력 파일 경로
19
+ * @param {number} width - 최대 가로
20
+ * @param {number} height - 최대 세로
21
+ * @param {string} [format='webp'] - 출력 포맷
22
+ * @param {number} [quality=80] - 품질 (1-100)
23
+ * @returns {string} 출력 경로
24
+ */
25
+ resize: (input, output, width, height, format, quality) =>
26
+ bridge.mediaResize(input, output, width, height, format, quality),
27
+
28
+ /**
29
+ * 다중 사이즈 리사이즈 (한 번의 디코드로 여러 출력).
30
+ * @param {string} input - 입력 파일 경로
31
+ * @param {string} outputDir - 출력 디렉토리
32
+ * @param {string} baseName - 기본 파일명
33
+ * @param {Array<{width:number,height:number,format?:string,quality?:number,suffix:string}>} specs
34
+ * @returns {string[]} 출력 경로 배열
35
+ */
36
+ resizeMultiple: (input, outputDir, baseName, specs) =>
37
+ bridge.mediaResizeMultiple(input, outputDir, baseName, specs),
38
+
39
+ /**
40
+ * 이미지 정보 조회.
41
+ * @returns {{width:number, height:number, format:string}}
42
+ */
43
+ imageInfo: (filePath) => bridge.mediaGetImageInfo(filePath),
44
+
45
+ /**
46
+ * WebP 변환.
47
+ * @param {string} input - 입력 경로
48
+ * @param {string} output - 출력 경로
49
+ * @param {number} [quality=80] - 품질
50
+ * @returns {string} 출력 경로
51
+ */
52
+ toWebp: (input, output, quality) =>
53
+ bridge.mediaConvertToWebp(input, output, quality),
54
+
55
+ // ── 비디오 ──
56
+
57
+ /**
58
+ * 비디오 썸네일 추출 (ffmpeg).
59
+ * @param {string} input - 비디오 경로
60
+ * @param {string} output - 출력 이미지 경로
61
+ * @param {number} [atSeconds=3] - 캡처 시점 (초)
62
+ * @param {number} [width=0] - 출력 가로 (0=원본)
63
+ * @param {string} [format='jpeg'] - 출력 포맷
64
+ * @returns {string} 출력 경로
65
+ */
66
+ videoThumbnail: (input, output, atSeconds, width, format) =>
67
+ bridge.mediaVideoThumbnail(input, output, atSeconds, width, format),
68
+
69
+ /**
70
+ * 비디오 정보 조회 (ffprobe).
71
+ * @returns {{duration:number, width:number, height:number, codec:string, fps:number}}
72
+ */
73
+ videoInfo: (filePath) => bridge.mediaVideoInfo(filePath),
74
+ };
75
+ }
package/lib/middleware.js CHANGED
@@ -1,65 +1,65 @@
1
- /**
2
- * Middleware — 동기 + 비동기 미들웨어 체인 엔진.
3
- *
4
- * Express 호환: next()가 Promise를 반환하면 체인 전체가 async로 전파.
5
- * Fusion 콜백에서 Promise가 감지되면 bridge의 sendAsyncResponse로 처리.
6
- */
7
-
8
- /**
9
- * 미들웨어 체인 실행
10
- * @param {Function[]} handlers - (req, res, next) 핸들러 배열
11
- * @param {object} req
12
- * @param {object} res
13
- * @returns {undefined|Promise} 비동기 핸들러가 있으면 Promise 리턴
14
- */
15
- export function runMiddlewareChain(handlers, req, res) {
16
- let index = 0;
17
- const len = handlers.length;
18
-
19
- function next(err) {
20
- if (err) throw err;
21
- if (res._sent || index >= len) return;
22
-
23
- const handler = handlers[index++];
24
- const result = handler(req, res, next);
25
-
26
- // 핸들러가 Promise를 리턴하면 (async 함수) → 체인 완료까지 전파
27
- // async 핸들러 내부에서 await next()를 호출하므로 후속 체인은 Promise 안에서 실행됨
28
- if (result && typeof result.then === 'function') {
29
- return result.catch((e) => {
30
- // async 핸들러의 unhandled rejection 방지
31
- if (!res._sent) throw e;
32
- });
33
- }
34
- return result;
35
- }
36
-
37
- return next();
38
- }
39
-
40
- /**
41
- * 에러 핸들러 체인 실행
42
- * @param {Function[]} handlers - (err, req, res, next) 핸들러 배열
43
- * @param {Error} err
44
- * @param {object} req
45
- * @param {object} res
46
- */
47
- export function runErrorChain(handlers, err, req, res) {
48
- let index = 0;
49
- const len = handlers.length;
50
-
51
- function next(nextErr) {
52
- if (res._sent || index >= len) return;
53
- const handler = handlers[index++];
54
- try {
55
- handler(nextErr || err, req, res, next);
56
- } catch (e) {
57
- // 에러 핸들러가 또 throw하면 다음 핸들러로
58
- if (index < len) {
59
- next(e);
60
- }
61
- }
62
- }
63
-
64
- next(err);
65
- }
1
+ /**
2
+ * Middleware — 동기 + 비동기 미들웨어 체인 엔진.
3
+ *
4
+ * Express 호환: next()가 Promise를 반환하면 체인 전체가 async로 전파.
5
+ * Fusion 콜백에서 Promise가 감지되면 bridge의 sendAsyncResponse로 처리.
6
+ */
7
+
8
+ /**
9
+ * 미들웨어 체인 실행
10
+ * @param {Function[]} handlers - (req, res, next) 핸들러 배열
11
+ * @param {object} req
12
+ * @param {object} res
13
+ * @returns {undefined|Promise} 비동기 핸들러가 있으면 Promise 리턴
14
+ */
15
+ export function runMiddlewareChain(handlers, req, res) {
16
+ let index = 0;
17
+ const len = handlers.length;
18
+
19
+ function next(err) {
20
+ if (err) throw err;
21
+ if (res._sent || index >= len) return;
22
+
23
+ const handler = handlers[index++];
24
+ const result = handler(req, res, next);
25
+
26
+ // 핸들러가 Promise를 리턴하면 (async 함수) → 체인 완료까지 전파
27
+ // async 핸들러 내부에서 await next()를 호출하므로 후속 체인은 Promise 안에서 실행됨
28
+ if (result && typeof result.then === 'function') {
29
+ return result.catch((e) => {
30
+ // async 핸들러의 unhandled rejection 방지
31
+ if (!res._sent) throw e;
32
+ });
33
+ }
34
+ return result;
35
+ }
36
+
37
+ return next();
38
+ }
39
+
40
+ /**
41
+ * 에러 핸들러 체인 실행
42
+ * @param {Function[]} handlers - (err, req, res, next) 핸들러 배열
43
+ * @param {Error} err
44
+ * @param {object} req
45
+ * @param {object} res
46
+ */
47
+ export function runErrorChain(handlers, err, req, res) {
48
+ let index = 0;
49
+ const len = handlers.length;
50
+
51
+ function next(nextErr) {
52
+ if (res._sent || index >= len) return;
53
+ const handler = handlers[index++];
54
+ try {
55
+ handler(nextErr || err, req, res, next);
56
+ } catch (e) {
57
+ // 에러 핸들러가 또 throw하면 다음 핸들러로
58
+ if (index < len) {
59
+ next(e);
60
+ }
61
+ }
62
+ }
63
+
64
+ next(err);
65
+ }