@connextable/popbill-juso 1.0.0
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/LICENSE.md +19 -0
- package/dist/index.d.mts +386 -0
- package/dist/index.mjs +311 -0
- package/package.json +46 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2014 Linkhub
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { LinkhubRequestClient } from "@connextable/popbill-runtime";
|
|
2
|
+
|
|
3
|
+
//#region src/errors/index.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* 주소링크 요청 단계 상수입니다.
|
|
6
|
+
*/
|
|
7
|
+
declare const JusoLinkRequestStages: {
|
|
8
|
+
/**
|
|
9
|
+
* 토큰 발급 단계입니다.
|
|
10
|
+
*/
|
|
11
|
+
readonly IssueToken: "issue_token";
|
|
12
|
+
/**
|
|
13
|
+
* API 요청 단계입니다.
|
|
14
|
+
*/
|
|
15
|
+
readonly RequestApi: "request_api";
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* 주소링크 요청 단계 타입입니다.
|
|
19
|
+
*/
|
|
20
|
+
type JusoLinkRequestStage = (typeof JusoLinkRequestStages)[keyof typeof JusoLinkRequestStages];
|
|
21
|
+
/**
|
|
22
|
+
* 주소링크 공통 예외 코드입니다.
|
|
23
|
+
*/
|
|
24
|
+
declare const JusoLinkErrorCodes: {
|
|
25
|
+
/**
|
|
26
|
+
* 원격 API 코드가 없는 경우 사용하는 내부 코드입니다.
|
|
27
|
+
*/
|
|
28
|
+
readonly Unexpected: -99999999;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* 주소링크 SDK 표준 에러 모델입니다.
|
|
32
|
+
*/
|
|
33
|
+
interface JusoApiErrorDetails {
|
|
34
|
+
/**
|
|
35
|
+
* 서비스가 반환한 에러 코드입니다.
|
|
36
|
+
*/
|
|
37
|
+
code: number;
|
|
38
|
+
/**
|
|
39
|
+
* 에러 메시지입니다.
|
|
40
|
+
*/
|
|
41
|
+
message: string;
|
|
42
|
+
/**
|
|
43
|
+
* HTTP 상태 코드입니다.
|
|
44
|
+
*/
|
|
45
|
+
httpStatusCode?: number;
|
|
46
|
+
/**
|
|
47
|
+
* 실패한 공개 메서드 이름입니다.
|
|
48
|
+
*/
|
|
49
|
+
operationName: string;
|
|
50
|
+
/**
|
|
51
|
+
* 실패 단계입니다.
|
|
52
|
+
*/
|
|
53
|
+
requestStage: JusoLinkRequestStage;
|
|
54
|
+
/**
|
|
55
|
+
* 원본 예외 객체입니다.
|
|
56
|
+
*/
|
|
57
|
+
cause?: unknown;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 주소링크 SDK 예외 클래스입니다.
|
|
61
|
+
*/
|
|
62
|
+
declare class JusoApiError extends Error implements JusoApiErrorDetails {
|
|
63
|
+
readonly code: number;
|
|
64
|
+
readonly httpStatusCode?: number;
|
|
65
|
+
readonly operationName: string;
|
|
66
|
+
readonly requestStage: JusoLinkRequestStage;
|
|
67
|
+
readonly cause?: unknown;
|
|
68
|
+
constructor(details: JusoApiErrorDetails);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 에러 정규화 입력 컨텍스트입니다.
|
|
72
|
+
*/
|
|
73
|
+
interface NormalizeJusoApiErrorContext {
|
|
74
|
+
/**
|
|
75
|
+
* 실패한 공개 메서드 이름입니다.
|
|
76
|
+
*/
|
|
77
|
+
operationName: string;
|
|
78
|
+
/**
|
|
79
|
+
* 실패 단계입니다.
|
|
80
|
+
*/
|
|
81
|
+
requestStage?: JusoLinkRequestStage;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 임의 예외를 `JusoApiError`로 정규화합니다.
|
|
85
|
+
*/
|
|
86
|
+
declare function normalizeJusoApiError(error: unknown, context: NormalizeJusoApiErrorContext): JusoApiError;
|
|
87
|
+
/**
|
|
88
|
+
* 에러 핸들러를 안전하게 호출합니다.
|
|
89
|
+
*/
|
|
90
|
+
declare function dispatchJusoApiErrorSafely(handler: ((error: JusoApiError) => void) | undefined, error: JusoApiError): void;
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/services/juso/types.d.ts
|
|
93
|
+
/**
|
|
94
|
+
* 주소검색 시도 코드 상수입니다.
|
|
95
|
+
*/
|
|
96
|
+
declare const JusoLinkProvinceCodes: {
|
|
97
|
+
readonly Gangwon: "GANGWON";
|
|
98
|
+
readonly Gyeonggi: "GYEONGGI";
|
|
99
|
+
readonly Gyeongsangnam: "GYEONGSANGNAM";
|
|
100
|
+
readonly Gyeongsangbuk: "GYEONGSANGBUK";
|
|
101
|
+
readonly Gwangju: "GWANGJU";
|
|
102
|
+
readonly Daegu: "DAEGU";
|
|
103
|
+
readonly Daejeon: "DAEJEON";
|
|
104
|
+
readonly Busan: "BUSAN";
|
|
105
|
+
readonly Seoul: "SEOUL";
|
|
106
|
+
readonly Sejong: "SEJONG";
|
|
107
|
+
readonly Ulsan: "ULSAN";
|
|
108
|
+
readonly Incheon: "INCHEON";
|
|
109
|
+
readonly Jeollanam: "JEOLLANAM";
|
|
110
|
+
readonly Jeollabuk: "JEOLLABUK";
|
|
111
|
+
readonly Jeju: "JEJU";
|
|
112
|
+
readonly Chungcheongnam: "CHUNGCHEONGNAM";
|
|
113
|
+
readonly Chungcheongbuk: "CHUNGCHEONGBUK";
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* 주소검색 시도 코드 타입입니다.
|
|
117
|
+
*/
|
|
118
|
+
type JusoLinkProvinceCode = (typeof JusoLinkProvinceCodes)[keyof typeof JusoLinkProvinceCodes];
|
|
119
|
+
/**
|
|
120
|
+
* 주소검색 입력 모델입니다.
|
|
121
|
+
*/
|
|
122
|
+
interface SearchAddressesInput {
|
|
123
|
+
/**
|
|
124
|
+
* 검색어입니다.
|
|
125
|
+
*/
|
|
126
|
+
searchKeyword: string;
|
|
127
|
+
/**
|
|
128
|
+
* 페이지 번호입니다.
|
|
129
|
+
*
|
|
130
|
+
* @default 1
|
|
131
|
+
*/
|
|
132
|
+
pageNumber?: number;
|
|
133
|
+
/**
|
|
134
|
+
* 페이지 크기입니다.
|
|
135
|
+
*
|
|
136
|
+
* 허용 범위는 1 이상 100 이하입니다.
|
|
137
|
+
* @default 20
|
|
138
|
+
*/
|
|
139
|
+
pageSize?: number;
|
|
140
|
+
/**
|
|
141
|
+
* 차등검색 비활성화 여부입니다.
|
|
142
|
+
*
|
|
143
|
+
* `true` 이면 차등검색을 비활성화합니다.
|
|
144
|
+
* @default false
|
|
145
|
+
*/
|
|
146
|
+
disableDifferentialSearch?: boolean;
|
|
147
|
+
/**
|
|
148
|
+
* 수정 제시어 비활성화 여부입니다.
|
|
149
|
+
*
|
|
150
|
+
* `true` 이면 수정 제시어를 비활성화합니다.
|
|
151
|
+
* @default false
|
|
152
|
+
*/
|
|
153
|
+
disableSuggestion?: boolean;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* 주소 항목 모델입니다.
|
|
157
|
+
*/
|
|
158
|
+
interface AddressSearchItem {
|
|
159
|
+
/**
|
|
160
|
+
* 도로명 기본주소입니다.
|
|
161
|
+
*/
|
|
162
|
+
roadNameAddress: string;
|
|
163
|
+
/**
|
|
164
|
+
* 도로명 부가정보입니다.
|
|
165
|
+
*/
|
|
166
|
+
roadNameAddressAdditionalInfo?: string;
|
|
167
|
+
/**
|
|
168
|
+
* 지번주소입니다.
|
|
169
|
+
*/
|
|
170
|
+
lotNumberAddress: string;
|
|
171
|
+
/**
|
|
172
|
+
* 구우편번호입니다.
|
|
173
|
+
*/
|
|
174
|
+
oldPostalCode: string;
|
|
175
|
+
/**
|
|
176
|
+
* 신우편번호(기초구역번호)입니다.
|
|
177
|
+
*/
|
|
178
|
+
newPostalCode: string;
|
|
179
|
+
/**
|
|
180
|
+
* 상세건물명 목록입니다.
|
|
181
|
+
*/
|
|
182
|
+
detailedBuildingNames: readonly string[];
|
|
183
|
+
/**
|
|
184
|
+
* 관련지번 목록입니다.
|
|
185
|
+
*/
|
|
186
|
+
relatedLotNumbers: readonly string[];
|
|
187
|
+
/**
|
|
188
|
+
* 법정동 코드입니다.
|
|
189
|
+
*/
|
|
190
|
+
legalDistrictCode: string;
|
|
191
|
+
/**
|
|
192
|
+
* 도로명 코드입니다.
|
|
193
|
+
*/
|
|
194
|
+
roadNameCode: string;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* 주소검색 결과 모델입니다.
|
|
198
|
+
*/
|
|
199
|
+
interface SearchAddressesResult {
|
|
200
|
+
/**
|
|
201
|
+
* 적용된 검색어입니다.
|
|
202
|
+
*/
|
|
203
|
+
searchKeyword: string;
|
|
204
|
+
/**
|
|
205
|
+
* 차등검색에서 제외된 단어 목록입니다.
|
|
206
|
+
*/
|
|
207
|
+
excludedKeywords: readonly string[];
|
|
208
|
+
/**
|
|
209
|
+
* 수정 제시어입니다.
|
|
210
|
+
*/
|
|
211
|
+
suggestedKeyword?: string;
|
|
212
|
+
/**
|
|
213
|
+
* 시도별 검색 결과 수입니다.
|
|
214
|
+
*/
|
|
215
|
+
provinceResultCountMap: Readonly<Partial<Record<JusoLinkProvinceCode, number>>>;
|
|
216
|
+
/**
|
|
217
|
+
* 총 검색 건수입니다.
|
|
218
|
+
*/
|
|
219
|
+
totalResultCount: number;
|
|
220
|
+
/**
|
|
221
|
+
* 현재 페이지 주소 건수입니다.
|
|
222
|
+
*/
|
|
223
|
+
pageResultCount: number;
|
|
224
|
+
/**
|
|
225
|
+
* 총 페이지 수입니다.
|
|
226
|
+
*/
|
|
227
|
+
totalPageCount: number;
|
|
228
|
+
/**
|
|
229
|
+
* 현재 페이지 번호입니다.
|
|
230
|
+
*/
|
|
231
|
+
pageNumber: number;
|
|
232
|
+
/**
|
|
233
|
+
* 현재 페이지 주소 목록입니다.
|
|
234
|
+
*/
|
|
235
|
+
addresses: readonly AddressSearchItem[];
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* 주소링크 주소검색 서비스 인터페이스입니다.
|
|
239
|
+
*/
|
|
240
|
+
interface JusoService {
|
|
241
|
+
/**
|
|
242
|
+
* 주소를 검색합니다.
|
|
243
|
+
*/
|
|
244
|
+
searchAddresses(input: SearchAddressesInput): Promise<SearchAddressesResult>;
|
|
245
|
+
}
|
|
246
|
+
//#endregion
|
|
247
|
+
//#region src/services/juso/service.d.ts
|
|
248
|
+
interface CreateJusoServiceInput {
|
|
249
|
+
/**
|
|
250
|
+
* 링크허브 공용 요청 클라이언트입니다.
|
|
251
|
+
*/
|
|
252
|
+
requestClient: LinkhubRequestClient;
|
|
253
|
+
/**
|
|
254
|
+
* SDK 에러 콜백입니다.
|
|
255
|
+
*/
|
|
256
|
+
onError?: (error: JusoApiError) => void;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* 주소검색 서비스를 생성합니다.
|
|
260
|
+
*/
|
|
261
|
+
declare function createJusoService(input: CreateJusoServiceInput): JusoService;
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/client/types.d.ts
|
|
264
|
+
/**
|
|
265
|
+
* 주소링크 클라이언트 설정입니다.
|
|
266
|
+
*/
|
|
267
|
+
interface JusoLinkClientConfig {
|
|
268
|
+
/**
|
|
269
|
+
* 링크허브 링크아이디입니다.
|
|
270
|
+
*/
|
|
271
|
+
linkId: string;
|
|
272
|
+
/**
|
|
273
|
+
* 링크허브 비밀키(base64)입니다.
|
|
274
|
+
*/
|
|
275
|
+
secretKey: string;
|
|
276
|
+
/**
|
|
277
|
+
* 연동회원 식별자입니다.
|
|
278
|
+
*/
|
|
279
|
+
accessId: string;
|
|
280
|
+
/**
|
|
281
|
+
* 주소링크 API 기본 URL 입니다.
|
|
282
|
+
*
|
|
283
|
+
* @default https://jusolink.linkhub.co.kr
|
|
284
|
+
*/
|
|
285
|
+
apiBaseUrl?: string;
|
|
286
|
+
/**
|
|
287
|
+
* 링크허브 인증 API 기본 URL 입니다.
|
|
288
|
+
*
|
|
289
|
+
* @default https://auth.linkhub.co.kr
|
|
290
|
+
*/
|
|
291
|
+
authBaseUrl?: string;
|
|
292
|
+
/**
|
|
293
|
+
* 토큰 사용 제한 아이피입니다.
|
|
294
|
+
*/
|
|
295
|
+
forwardedIpAddress?: string;
|
|
296
|
+
/**
|
|
297
|
+
* 로컬 UTC 시간 사용 여부입니다.
|
|
298
|
+
*
|
|
299
|
+
* @default true
|
|
300
|
+
*/
|
|
301
|
+
useLocalTime?: boolean;
|
|
302
|
+
/**
|
|
303
|
+
* 요청 제한시간(밀리초)입니다.
|
|
304
|
+
*
|
|
305
|
+
* @default 180000
|
|
306
|
+
*/
|
|
307
|
+
requestTimeoutMilliseconds?: number;
|
|
308
|
+
/**
|
|
309
|
+
* Accept-Language 헤더 값입니다.
|
|
310
|
+
*/
|
|
311
|
+
acceptLanguage?: string;
|
|
312
|
+
/**
|
|
313
|
+
* 에러 콜백입니다.
|
|
314
|
+
*/
|
|
315
|
+
onError?: (error: JusoApiError) => void;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* 주소링크 클라이언트입니다.
|
|
319
|
+
*/
|
|
320
|
+
interface JusoLinkClient {
|
|
321
|
+
/**
|
|
322
|
+
* 서비스 그룹입니다.
|
|
323
|
+
*/
|
|
324
|
+
services: {
|
|
325
|
+
/**
|
|
326
|
+
* 주소검색 서비스입니다.
|
|
327
|
+
*/
|
|
328
|
+
juso: JusoService;
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
//#endregion
|
|
332
|
+
//#region src/client/index.d.ts
|
|
333
|
+
/**
|
|
334
|
+
* 주소링크 클라이언트를 생성합니다.
|
|
335
|
+
*/
|
|
336
|
+
declare function createJusoLinkClient(configuration: JusoLinkClientConfig): JusoLinkClient;
|
|
337
|
+
//#endregion
|
|
338
|
+
//#region src/constants.d.ts
|
|
339
|
+
/**
|
|
340
|
+
* 주소링크 기본 URL 상수입니다.
|
|
341
|
+
*/
|
|
342
|
+
declare const JusoLinkBaseUrls: {
|
|
343
|
+
/**
|
|
344
|
+
* 주소링크 API 기본 URL 입니다.
|
|
345
|
+
*/
|
|
346
|
+
readonly Api: "https://jusolink.linkhub.co.kr";
|
|
347
|
+
/**
|
|
348
|
+
* 링크허브 인증 API 기본 URL 입니다.
|
|
349
|
+
*/
|
|
350
|
+
readonly Auth: "https://auth.linkhub.co.kr";
|
|
351
|
+
};
|
|
352
|
+
/**
|
|
353
|
+
* 주소링크 검색 기본값 상수입니다.
|
|
354
|
+
*/
|
|
355
|
+
declare const JusoLinkSearchDefaults: {
|
|
356
|
+
/**
|
|
357
|
+
* 기본 페이지 번호입니다.
|
|
358
|
+
*/
|
|
359
|
+
readonly PageNumber: 1;
|
|
360
|
+
/**
|
|
361
|
+
* 기본 페이지 크기입니다.
|
|
362
|
+
*/
|
|
363
|
+
readonly PageSize: 20;
|
|
364
|
+
/**
|
|
365
|
+
* 허용되는 최대 페이지 크기입니다.
|
|
366
|
+
*/
|
|
367
|
+
readonly MaximumPageSize: 100;
|
|
368
|
+
/**
|
|
369
|
+
* 기본 차등검색 사용 여부입니다.
|
|
370
|
+
*/
|
|
371
|
+
readonly DisableDifferentialSearch: false;
|
|
372
|
+
/**
|
|
373
|
+
* 기본 수정 제시어 사용 여부입니다.
|
|
374
|
+
*/
|
|
375
|
+
readonly DisableSuggestion: false;
|
|
376
|
+
};
|
|
377
|
+
/**
|
|
378
|
+
* 주소링크 SDK 기본 요청 제한시간(밀리초)입니다.
|
|
379
|
+
*/
|
|
380
|
+
declare const JusoLinkDefaultRequestTimeoutMilliseconds: 180000;
|
|
381
|
+
/**
|
|
382
|
+
* 주소링크 SDK 기본 사용자 에이전트입니다.
|
|
383
|
+
*/
|
|
384
|
+
declare const JusoLinkUserAgent: "NODEJS JUSOLINK SDK";
|
|
385
|
+
//#endregion
|
|
386
|
+
export { type AddressSearchItem, JusoApiError, JusoApiErrorDetails, JusoLinkBaseUrls, type JusoLinkClient, type JusoLinkClientConfig, JusoLinkDefaultRequestTimeoutMilliseconds, JusoLinkErrorCodes, type JusoLinkProvinceCode, JusoLinkProvinceCodes, JusoLinkRequestStage, JusoLinkRequestStages, JusoLinkSearchDefaults, JusoLinkUserAgent, type JusoService, NormalizeJusoApiErrorContext, type SearchAddressesInput, type SearchAddressesResult, createJusoLinkClient, createJusoService, dispatchJusoApiErrorSafely, normalizeJusoApiError };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { LinkhubAuthScope, LinkhubServiceIds, createLinkhubAuthClient, createLinkhubRequestClient, createTokenProvider } from "@connextable/popbill-runtime";
|
|
2
|
+
import { normalizeErrorMessage, normalizeOptionalString, normalizeRequiredString, trimTrailingSlash } from "@connextable/popbill-utils";
|
|
3
|
+
//#region src/constants.ts
|
|
4
|
+
/**
|
|
5
|
+
* 주소링크 기본 URL 상수입니다.
|
|
6
|
+
*/
|
|
7
|
+
const JusoLinkBaseUrls = {
|
|
8
|
+
/**
|
|
9
|
+
* 주소링크 API 기본 URL 입니다.
|
|
10
|
+
*/
|
|
11
|
+
Api: "https://jusolink.linkhub.co.kr",
|
|
12
|
+
/**
|
|
13
|
+
* 링크허브 인증 API 기본 URL 입니다.
|
|
14
|
+
*/
|
|
15
|
+
Auth: "https://auth.linkhub.co.kr"
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* 주소링크 검색 기본값 상수입니다.
|
|
19
|
+
*/
|
|
20
|
+
const JusoLinkSearchDefaults = {
|
|
21
|
+
/**
|
|
22
|
+
* 기본 페이지 번호입니다.
|
|
23
|
+
*/
|
|
24
|
+
PageNumber: 1,
|
|
25
|
+
/**
|
|
26
|
+
* 기본 페이지 크기입니다.
|
|
27
|
+
*/
|
|
28
|
+
PageSize: 20,
|
|
29
|
+
/**
|
|
30
|
+
* 허용되는 최대 페이지 크기입니다.
|
|
31
|
+
*/
|
|
32
|
+
MaximumPageSize: 100,
|
|
33
|
+
/**
|
|
34
|
+
* 기본 차등검색 사용 여부입니다.
|
|
35
|
+
*/
|
|
36
|
+
DisableDifferentialSearch: false,
|
|
37
|
+
/**
|
|
38
|
+
* 기본 수정 제시어 사용 여부입니다.
|
|
39
|
+
*/
|
|
40
|
+
DisableSuggestion: false
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* 주소링크 SDK 기본 요청 제한시간(밀리초)입니다.
|
|
44
|
+
*/
|
|
45
|
+
const JusoLinkDefaultRequestTimeoutMilliseconds = 18e4;
|
|
46
|
+
/**
|
|
47
|
+
* 주소링크 SDK 기본 사용자 에이전트입니다.
|
|
48
|
+
*/
|
|
49
|
+
const JusoLinkUserAgent = "NODEJS JUSOLINK SDK";
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/services/juso/types.ts
|
|
52
|
+
/**
|
|
53
|
+
* 주소검색 시도 코드 상수입니다.
|
|
54
|
+
*/
|
|
55
|
+
const JusoLinkProvinceCodes = {
|
|
56
|
+
Gangwon: "GANGWON",
|
|
57
|
+
Gyeonggi: "GYEONGGI",
|
|
58
|
+
Gyeongsangnam: "GYEONGSANGNAM",
|
|
59
|
+
Gyeongsangbuk: "GYEONGSANGBUK",
|
|
60
|
+
Gwangju: "GWANGJU",
|
|
61
|
+
Daegu: "DAEGU",
|
|
62
|
+
Daejeon: "DAEJEON",
|
|
63
|
+
Busan: "BUSAN",
|
|
64
|
+
Seoul: "SEOUL",
|
|
65
|
+
Sejong: "SEJONG",
|
|
66
|
+
Ulsan: "ULSAN",
|
|
67
|
+
Incheon: "INCHEON",
|
|
68
|
+
Jeollanam: "JEOLLANAM",
|
|
69
|
+
Jeollabuk: "JEOLLABUK",
|
|
70
|
+
Jeju: "JEJU",
|
|
71
|
+
Chungcheongnam: "CHUNGCHEONGNAM",
|
|
72
|
+
Chungcheongbuk: "CHUNGCHEONGBUK"
|
|
73
|
+
};
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/errors/index.ts
|
|
76
|
+
/**
|
|
77
|
+
* 주소링크 요청 단계 상수입니다.
|
|
78
|
+
*/
|
|
79
|
+
const JusoLinkRequestStages = {
|
|
80
|
+
/**
|
|
81
|
+
* 토큰 발급 단계입니다.
|
|
82
|
+
*/
|
|
83
|
+
IssueToken: "issue_token",
|
|
84
|
+
/**
|
|
85
|
+
* API 요청 단계입니다.
|
|
86
|
+
*/
|
|
87
|
+
RequestApi: "request_api"
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* 주소링크 공통 예외 코드입니다.
|
|
91
|
+
*/
|
|
92
|
+
const JusoLinkErrorCodes = {
|
|
93
|
+
/**
|
|
94
|
+
* 원격 API 코드가 없는 경우 사용하는 내부 코드입니다.
|
|
95
|
+
*/
|
|
96
|
+
Unexpected: -99999999 };
|
|
97
|
+
/**
|
|
98
|
+
* 주소링크 SDK 예외 클래스입니다.
|
|
99
|
+
*/
|
|
100
|
+
var JusoApiError = class extends Error {
|
|
101
|
+
code;
|
|
102
|
+
httpStatusCode;
|
|
103
|
+
operationName;
|
|
104
|
+
requestStage;
|
|
105
|
+
cause;
|
|
106
|
+
constructor(details) {
|
|
107
|
+
super(details.message);
|
|
108
|
+
this.name = "JusoApiError";
|
|
109
|
+
this.code = details.code;
|
|
110
|
+
this.httpStatusCode = details.httpStatusCode;
|
|
111
|
+
this.operationName = details.operationName;
|
|
112
|
+
this.requestStage = details.requestStage;
|
|
113
|
+
this.cause = details.cause;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* 임의 예외를 `JusoApiError`로 정규화합니다.
|
|
118
|
+
*/
|
|
119
|
+
function normalizeJusoApiError(error, context) {
|
|
120
|
+
if (error instanceof JusoApiError) return error;
|
|
121
|
+
if (isJusoLinkRequestStageError(error)) return normalizeJusoApiError(error.cause, {
|
|
122
|
+
operationName: context.operationName,
|
|
123
|
+
requestStage: error.requestStage
|
|
124
|
+
});
|
|
125
|
+
if (isHttpResponseErrorShape(error)) {
|
|
126
|
+
const remoteError = extractRemoteError(error.body);
|
|
127
|
+
return new JusoApiError({
|
|
128
|
+
code: remoteError?.code ?? JusoLinkErrorCodes.Unexpected,
|
|
129
|
+
message: remoteError?.message ?? createFallbackMessage(error.body),
|
|
130
|
+
httpStatusCode: error.status,
|
|
131
|
+
operationName: context.operationName,
|
|
132
|
+
requestStage: context.requestStage ?? JusoLinkRequestStages.RequestApi,
|
|
133
|
+
cause: error
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return new JusoApiError({
|
|
137
|
+
code: JusoLinkErrorCodes.Unexpected,
|
|
138
|
+
message: normalizeErrorMessage(error),
|
|
139
|
+
operationName: context.operationName,
|
|
140
|
+
requestStage: context.requestStage ?? JusoLinkRequestStages.RequestApi,
|
|
141
|
+
cause: error
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 에러 핸들러를 안전하게 호출합니다.
|
|
146
|
+
*/
|
|
147
|
+
function dispatchJusoApiErrorSafely(handler, error) {
|
|
148
|
+
if (!handler) return;
|
|
149
|
+
try {
|
|
150
|
+
handler(error);
|
|
151
|
+
} catch {}
|
|
152
|
+
}
|
|
153
|
+
function extractRemoteError(value) {
|
|
154
|
+
if (typeof value !== "object" || value === null) return;
|
|
155
|
+
const candidate = value;
|
|
156
|
+
if (typeof candidate.code !== "number" || typeof candidate.message !== "string") return;
|
|
157
|
+
return {
|
|
158
|
+
code: candidate.code,
|
|
159
|
+
message: candidate.message
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function createFallbackMessage(value) {
|
|
163
|
+
if (typeof value === "string") return value;
|
|
164
|
+
return normalizeErrorMessage(value);
|
|
165
|
+
}
|
|
166
|
+
function isJusoLinkRequestStageError(error) {
|
|
167
|
+
if (typeof error !== "object" || error === null) return false;
|
|
168
|
+
if (!("requestStage" in error) || !("cause" in error)) return false;
|
|
169
|
+
const requestStage = error.requestStage;
|
|
170
|
+
return requestStage === JusoLinkRequestStages.IssueToken || requestStage === JusoLinkRequestStages.RequestApi;
|
|
171
|
+
}
|
|
172
|
+
function isHttpResponseErrorShape(error) {
|
|
173
|
+
if (typeof error !== "object" || error === null) return false;
|
|
174
|
+
if (!("status" in error) || !("body" in error)) return false;
|
|
175
|
+
return typeof error.status === "number";
|
|
176
|
+
}
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/services/juso/service.ts
|
|
179
|
+
const SEARCH_ADDRESSES_OPERATION = "juso.searchAddresses";
|
|
180
|
+
/**
|
|
181
|
+
* 주소검색 서비스를 생성합니다.
|
|
182
|
+
*/
|
|
183
|
+
function createJusoService(input) {
|
|
184
|
+
return { async searchAddresses(searchInput) {
|
|
185
|
+
try {
|
|
186
|
+
const normalizedSearchInput = normalizeSearchInput(searchInput);
|
|
187
|
+
const queryParameters = new URLSearchParams({
|
|
188
|
+
Searches: normalizedSearchInput.searchKeyword,
|
|
189
|
+
PageNum: String(normalizedSearchInput.pageNumber),
|
|
190
|
+
PerPage: String(normalizedSearchInput.pageSize),
|
|
191
|
+
noDifferential: String(normalizedSearchInput.disableDifferentialSearch),
|
|
192
|
+
noSuggest: String(normalizedSearchInput.disableSuggestion)
|
|
193
|
+
});
|
|
194
|
+
return mapSearchResult(await input.requestClient.requestJson({
|
|
195
|
+
uri: `/Search?${queryParameters.toString()}`,
|
|
196
|
+
method: "GET"
|
|
197
|
+
}));
|
|
198
|
+
} catch (error) {
|
|
199
|
+
const normalizedError = normalizeJusoApiError(error, { operationName: SEARCH_ADDRESSES_OPERATION });
|
|
200
|
+
dispatchJusoApiErrorSafely(input.onError, normalizedError);
|
|
201
|
+
throw normalizedError;
|
|
202
|
+
}
|
|
203
|
+
} };
|
|
204
|
+
}
|
|
205
|
+
function normalizeSearchInput(input) {
|
|
206
|
+
return {
|
|
207
|
+
searchKeyword: normalizeRequiredString(input.searchKeyword, "searchKeyword는 필수입니다."),
|
|
208
|
+
pageNumber: normalizePositiveInteger(input.pageNumber, JusoLinkSearchDefaults.PageNumber, "pageNumber"),
|
|
209
|
+
pageSize: normalizePageSize(input.pageSize),
|
|
210
|
+
disableDifferentialSearch: input.disableDifferentialSearch ?? JusoLinkSearchDefaults.DisableDifferentialSearch,
|
|
211
|
+
disableSuggestion: input.disableSuggestion ?? JusoLinkSearchDefaults.DisableSuggestion
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function normalizePositiveInteger(value, defaultValue, fieldName) {
|
|
215
|
+
if (value === void 0) return defaultValue;
|
|
216
|
+
if (!Number.isInteger(value) || value < 1) throw new Error(`${fieldName}는 1 이상의 정수여야 합니다.`);
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
219
|
+
function normalizePageSize(value) {
|
|
220
|
+
const pageSize = normalizePositiveInteger(value, JusoLinkSearchDefaults.PageSize, "pageSize");
|
|
221
|
+
if (pageSize > JusoLinkSearchDefaults.MaximumPageSize) throw new Error(`pageSize는 ${String(JusoLinkSearchDefaults.MaximumPageSize)} 이하여야 합니다.`);
|
|
222
|
+
return pageSize;
|
|
223
|
+
}
|
|
224
|
+
function mapSearchResult(apiResponse) {
|
|
225
|
+
return {
|
|
226
|
+
searchKeyword: apiResponse.searches,
|
|
227
|
+
excludedKeywords: apiResponse.deletedWord ?? [],
|
|
228
|
+
suggestedKeyword: normalizeOptionalString(apiResponse.suggest),
|
|
229
|
+
provinceResultCountMap: mapProvinceResultCountMap(apiResponse.sidoCount),
|
|
230
|
+
totalResultCount: apiResponse.numFound,
|
|
231
|
+
pageResultCount: apiResponse.listSize,
|
|
232
|
+
totalPageCount: apiResponse.totalPage,
|
|
233
|
+
pageNumber: apiResponse.page,
|
|
234
|
+
addresses: (apiResponse.juso ?? []).map(mapAddressItem)
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function mapAddressItem(item) {
|
|
238
|
+
return {
|
|
239
|
+
roadNameAddress: item.roadAddr1,
|
|
240
|
+
roadNameAddressAdditionalInfo: normalizeOptionalString(item.roadAddr2),
|
|
241
|
+
lotNumberAddress: item.jibunAddr,
|
|
242
|
+
oldPostalCode: item.zipcode,
|
|
243
|
+
newPostalCode: item.sectionNum,
|
|
244
|
+
detailedBuildingNames: item.detailBuildingName ?? [],
|
|
245
|
+
relatedLotNumbers: item.relatedJibun ?? [],
|
|
246
|
+
legalDistrictCode: item.dongCode,
|
|
247
|
+
roadNameCode: item.streetCode
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function mapProvinceResultCountMap(source) {
|
|
251
|
+
if (!source) return {};
|
|
252
|
+
const result = {};
|
|
253
|
+
const validProvinceCodeSet = new Set(Object.values(JusoLinkProvinceCodes));
|
|
254
|
+
for (const [provinceCode, count] of Object.entries(source)) {
|
|
255
|
+
if (!validProvinceCodeSet.has(provinceCode)) continue;
|
|
256
|
+
result[provinceCode] = count;
|
|
257
|
+
}
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
//#endregion
|
|
261
|
+
//#region src/client/index.ts
|
|
262
|
+
/**
|
|
263
|
+
* 주소링크 클라이언트를 생성합니다.
|
|
264
|
+
*/
|
|
265
|
+
function createJusoLinkClient(configuration) {
|
|
266
|
+
const resolvedConfiguration = resolveConfiguration(configuration);
|
|
267
|
+
const tokenProvider = createTokenProvider({
|
|
268
|
+
authClient: createLinkhubAuthClient({
|
|
269
|
+
linkId: resolvedConfiguration.linkId,
|
|
270
|
+
secretKey: resolvedConfiguration.secretKey,
|
|
271
|
+
authBaseUrl: resolvedConfiguration.authBaseUrl,
|
|
272
|
+
useLocalTime: resolvedConfiguration.useLocalTime,
|
|
273
|
+
timeoutMs: resolvedConfiguration.requestTimeoutMilliseconds
|
|
274
|
+
}),
|
|
275
|
+
serviceId: LinkhubServiceIds.JusoLink,
|
|
276
|
+
scopes: [LinkhubAuthScope.Member],
|
|
277
|
+
forwardedIp: resolvedConfiguration.forwardedIpAddress
|
|
278
|
+
});
|
|
279
|
+
return { services: { juso: createJusoService({
|
|
280
|
+
requestClient: createLinkhubRequestClient({
|
|
281
|
+
apiBaseUrl: resolvedConfiguration.apiBaseUrl,
|
|
282
|
+
timeoutMs: resolvedConfiguration.requestTimeoutMilliseconds,
|
|
283
|
+
tokenProvider,
|
|
284
|
+
tokenCacheKey: resolvedConfiguration.accessId,
|
|
285
|
+
acceptLanguage: resolvedConfiguration.acceptLanguage,
|
|
286
|
+
userAgent: JusoLinkUserAgent
|
|
287
|
+
}),
|
|
288
|
+
onError: resolvedConfiguration.onError
|
|
289
|
+
}) } };
|
|
290
|
+
}
|
|
291
|
+
function resolveConfiguration(configuration) {
|
|
292
|
+
return {
|
|
293
|
+
linkId: normalizeRequiredString(configuration.linkId, "linkId는 필수입니다."),
|
|
294
|
+
secretKey: normalizeRequiredString(configuration.secretKey, "secretKey는 필수입니다."),
|
|
295
|
+
accessId: normalizeRequiredString(configuration.accessId, "accessId는 필수입니다."),
|
|
296
|
+
apiBaseUrl: trimTrailingSlash(configuration.apiBaseUrl ?? JusoLinkBaseUrls.Api),
|
|
297
|
+
authBaseUrl: trimTrailingSlash(configuration.authBaseUrl ?? JusoLinkBaseUrls.Auth),
|
|
298
|
+
forwardedIpAddress: normalizeOptionalString(configuration.forwardedIpAddress),
|
|
299
|
+
useLocalTime: configuration.useLocalTime ?? true,
|
|
300
|
+
requestTimeoutMilliseconds: normalizeRequestTimeoutMilliseconds(configuration.requestTimeoutMilliseconds),
|
|
301
|
+
acceptLanguage: normalizeOptionalString(configuration.acceptLanguage),
|
|
302
|
+
onError: configuration.onError
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function normalizeRequestTimeoutMilliseconds(value) {
|
|
306
|
+
if (value === void 0) return JusoLinkDefaultRequestTimeoutMilliseconds;
|
|
307
|
+
if (!Number.isInteger(value) || value <= 0) throw new Error("requestTimeoutMilliseconds는 1 이상의 정수여야 합니다.");
|
|
308
|
+
return value;
|
|
309
|
+
}
|
|
310
|
+
//#endregion
|
|
311
|
+
export { JusoApiError, JusoLinkBaseUrls, JusoLinkDefaultRequestTimeoutMilliseconds, JusoLinkErrorCodes, JusoLinkProvinceCodes, JusoLinkRequestStages, JusoLinkSearchDefaults, JusoLinkUserAgent, createJusoLinkClient, createJusoService, dispatchJusoApiErrorSafely, normalizeJusoApiError };
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@connextable/popbill-juso",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "JusoLink API SDK for TypeScript",
|
|
5
|
+
"bugs": {
|
|
6
|
+
"url": "https://github.com/connextable/popbill/issues"
|
|
7
|
+
},
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/connextable/popbill.git"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"types": "./dist/index.d.mts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": "./dist/index.mjs",
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@connextable/popbill-runtime": "1.0.0",
|
|
28
|
+
"@connextable/popbill-utils": "1.0.0"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsdown",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"test:coverage": "vitest run --coverage",
|
|
37
|
+
"lint": "oxlint --type-aware",
|
|
38
|
+
"lint:fix": "oxlint --fix",
|
|
39
|
+
"fmt": "oxfmt",
|
|
40
|
+
"fmt:check": "oxfmt --check",
|
|
41
|
+
"typecheck": "tsgo -b --noEmit",
|
|
42
|
+
"typecheck:lib": "tsgo -p tsconfig.lib.json --noEmit",
|
|
43
|
+
"typecheck:test": "tsgo -p tsconfig.test.json --noEmit",
|
|
44
|
+
"clean": "del node_modules dist"
|
|
45
|
+
}
|
|
46
|
+
}
|