@cp949/japanpost-react 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 +15 -0
- package/README.md +500 -0
- package/dist/components/AddressSearchInput.d.ts +7 -0
- package/dist/components/AddressSearchInput.d.ts.map +1 -0
- package/dist/components/PostalCodeInput.d.ts +7 -0
- package/dist/components/PostalCodeInput.d.ts.map +1 -0
- package/dist/core/errors.d.ts +10 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/formatters.d.ts +12 -0
- package/dist/core/formatters.d.ts.map +1 -0
- package/dist/core/normalizers.d.ts +7 -0
- package/dist/core/normalizers.d.ts.map +1 -0
- package/dist/core/types.d.ts +182 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/validators.d.ts +6 -0
- package/dist/core/validators.d.ts.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.es.js +431 -0
- package/dist/index.umd.cjs +6 -0
- package/dist/react/toJapanAddressError.d.ts +7 -0
- package/dist/react/toJapanAddressError.d.ts.map +1 -0
- package/dist/react/useJapanAddress.d.ts +7 -0
- package/dist/react/useJapanAddress.d.ts.map +1 -0
- package/dist/react/useJapanAddressSearch.d.ts +7 -0
- package/dist/react/useJapanAddressSearch.d.ts.map +1 -0
- package/dist/react/useJapanPostalCode.d.ts +7 -0
- package/dist/react/useJapanPostalCode.d.ts.map +1 -0
- package/dist/react/useLatestRequestState.d.ts +17 -0
- package/dist/react/useLatestRequestState.d.ts.map +1 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 cp949
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
<!-- This file is generated by `pnpm readme:package`. Edit docs/README.en.md and docs/README.ko.md instead. -->
|
|
2
|
+
|
|
3
|
+
# @cp949/japanpost-react
|
|
4
|
+
|
|
5
|
+
[English](#english) | [한국어](#한국어)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## English
|
|
10
|
+
|
|
11
|
+
React + TypeScript hooks and headless inputs for Japan postal-code and address
|
|
12
|
+
lookup.
|
|
13
|
+
|
|
14
|
+
This package guide covers the published library. Repository-level demo scripts
|
|
15
|
+
such as `pnpm demo:full` currently target Linux/WSL-style shell environments
|
|
16
|
+
and are documented in the root README.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add @cp949/japanpost-react
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- Supported React versions: React 18 and React 19
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { useJapanPostalCode } from "@cp949/japanpost-react";
|
|
30
|
+
import type { JapanAddressDataSource } from "@cp949/japanpost-react";
|
|
31
|
+
import { createJapanAddressError } from "@cp949/japanpost-react";
|
|
32
|
+
|
|
33
|
+
// The only supported integration model is a real server-backed flow.
|
|
34
|
+
// Point the data source at your own backend API.
|
|
35
|
+
const dataSource: JapanAddressDataSource = {
|
|
36
|
+
async lookupPostalCode(postalCode) {
|
|
37
|
+
const res = await fetch(`/searchcode/${postalCode}`);
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
const message = `Postal code lookup failed with status ${res.status}`;
|
|
40
|
+
|
|
41
|
+
if (res.status === 400) {
|
|
42
|
+
throw createJapanAddressError("invalid_postal_code", message, {
|
|
43
|
+
status: res.status,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (res.status === 404) {
|
|
48
|
+
throw createJapanAddressError("not_found", message, {
|
|
49
|
+
status: res.status,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (res.status === 504) {
|
|
54
|
+
throw createJapanAddressError("timeout", message, {
|
|
55
|
+
status: res.status,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
throw createJapanAddressError("data_source_error", message, {
|
|
60
|
+
status: res.status,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const payload = await res.json();
|
|
64
|
+
if (!Array.isArray(payload.addresses)) {
|
|
65
|
+
throw createJapanAddressError(
|
|
66
|
+
"bad_response",
|
|
67
|
+
"Postal code lookup returned an invalid payload",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return payload.addresses;
|
|
71
|
+
},
|
|
72
|
+
async searchAddress(query) {
|
|
73
|
+
const res = await fetch(`/addresszip?q=${encodeURIComponent(query)}`);
|
|
74
|
+
if (!res.ok) {
|
|
75
|
+
const message = `Address search failed with status ${res.status}`;
|
|
76
|
+
|
|
77
|
+
if (res.status === 400) {
|
|
78
|
+
throw createJapanAddressError("invalid_query", message, {
|
|
79
|
+
status: res.status,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (res.status === 404) {
|
|
84
|
+
throw createJapanAddressError("not_found", message, {
|
|
85
|
+
status: res.status,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (res.status === 504) {
|
|
90
|
+
throw createJapanAddressError("timeout", message, {
|
|
91
|
+
status: res.status,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
throw createJapanAddressError("data_source_error", message, {
|
|
96
|
+
status: res.status,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const payload = await res.json();
|
|
100
|
+
if (!Array.isArray(payload.addresses)) {
|
|
101
|
+
throw createJapanAddressError(
|
|
102
|
+
"bad_response",
|
|
103
|
+
"Address search returned an invalid payload",
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return payload.addresses;
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export function PostalForm() {
|
|
111
|
+
const { loading, data, error, search } = useJapanPostalCode({ dataSource });
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<div>
|
|
115
|
+
<button onClick={() => void search("100-0001")}>Search</button>
|
|
116
|
+
{loading && <p>Loading...</p>}
|
|
117
|
+
{error && (
|
|
118
|
+
<p>
|
|
119
|
+
{error.code}: {error.message}
|
|
120
|
+
</p>
|
|
121
|
+
)}
|
|
122
|
+
{data?.addresses.map((addr) => (
|
|
123
|
+
<p key={addr.postalCode + addr.address}>{addr.address}</p>
|
|
124
|
+
))}
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Exports
|
|
131
|
+
|
|
132
|
+
- `normalizeJapanPostalCode`
|
|
133
|
+
- `formatJapanPostalCode`
|
|
134
|
+
- `normalizeJapanPostAddressRecord`
|
|
135
|
+
- `isValidJapanPostalCode`
|
|
136
|
+
- `createJapanAddressError`
|
|
137
|
+
- `useJapanPostalCode`
|
|
138
|
+
- `useJapanAddressSearch`
|
|
139
|
+
- `useJapanAddress`
|
|
140
|
+
- `PostalCodeInput`
|
|
141
|
+
- `AddressSearchInput`
|
|
142
|
+
- Public types including `JapanAddress` and `JapanAddressDataSource`
|
|
143
|
+
- Request options type: `JapanAddressRequestOptions`
|
|
144
|
+
|
|
145
|
+
## Utility Notes
|
|
146
|
+
|
|
147
|
+
`formatJapanPostalCode()` inserts a hyphen only when the normalized value is
|
|
148
|
+
exactly 7 digits. For any other length, it returns the normalized digits
|
|
149
|
+
without inserting a hyphen.
|
|
150
|
+
|
|
151
|
+
## Hooks
|
|
152
|
+
|
|
153
|
+
### useJapanPostalCode
|
|
154
|
+
|
|
155
|
+
Looks up addresses by postal code.
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
const { loading, data, error, search, reset } = useJapanPostalCode({
|
|
159
|
+
dataSource,
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### useJapanAddressSearch
|
|
164
|
+
|
|
165
|
+
Searches addresses by free-form keyword and supports debouncing.
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
const { loading, data, error, search, reset } = useJapanAddressSearch({
|
|
169
|
+
dataSource,
|
|
170
|
+
debounceMs: 300,
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### useJapanAddress
|
|
175
|
+
|
|
176
|
+
Combines postal-code lookup and keyword search into one hook.
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
const { loading, data, error, searchByPostalCode, searchByKeyword, reset } =
|
|
180
|
+
useJapanAddress({ dataSource, debounceMs: 300 });
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
All hooks require `dataSource` at runtime.
|
|
184
|
+
|
|
185
|
+
## Error Handling Notes
|
|
186
|
+
|
|
187
|
+
`JapanAddressDataSource` should return `JapanAddress[]` directly from both
|
|
188
|
+
methods. The hooks wrap those arrays into lookup/search result objects.
|
|
189
|
+
|
|
190
|
+
Both methods may also receive an optional second argument:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
type JapanAddressRequestOptions = {
|
|
194
|
+
signal?: AbortSignal;
|
|
195
|
+
};
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Hooks pass `signal` so your data source can cancel superseded requests,
|
|
199
|
+
`reset()` calls, and unmount cleanup when your backend layer supports aborts.
|
|
200
|
+
|
|
201
|
+
Recommended error-code mapping:
|
|
202
|
+
|
|
203
|
+
| Situation | Recommended code |
|
|
204
|
+
| --- | --- |
|
|
205
|
+
| Invalid postal code input | `invalid_postal_code` |
|
|
206
|
+
| Blank keyword input | `invalid_query` |
|
|
207
|
+
| Network failure | `network_error` |
|
|
208
|
+
| Request aborted / timeout | `timeout` |
|
|
209
|
+
| No matching addresses | `not_found` |
|
|
210
|
+
| Malformed success payload | `bad_response` |
|
|
211
|
+
| Other backend failures | `data_source_error` |
|
|
212
|
+
|
|
213
|
+
In this repository's reference demo flow, the sample `dataSource` maps `400
|
|
214
|
+
/searchcode/...` to `invalid_postal_code`, `400 /addresszip?...` to
|
|
215
|
+
`invalid_query`, `404` to `not_found`, and `504` to `timeout`.
|
|
216
|
+
|
|
217
|
+
## Headless Components
|
|
218
|
+
|
|
219
|
+
`PostalCodeInput` and `AddressSearchInput` provide behavior and DOM structure
|
|
220
|
+
without bundled styles, so you can plug them into your own design system.
|
|
221
|
+
|
|
222
|
+
Both components also support native prop passthrough:
|
|
223
|
+
|
|
224
|
+
- `inputProps`: forwarded to the rendered `<input />`
|
|
225
|
+
- `buttonProps`: forwarded to the rendered `<button />`
|
|
226
|
+
|
|
227
|
+
Use these for `id`, `name`, `placeholder`, `aria-*`, `autoComplete`,
|
|
228
|
+
`className`, and form integration. `PostalCodeInput` defaults to
|
|
229
|
+
`inputMode="numeric"` unless you override it through `inputProps`.
|
|
230
|
+
|
|
231
|
+
## Data Source and Server Integration
|
|
232
|
+
|
|
233
|
+
Use this package with your own backend server. The official Japan Post flow
|
|
234
|
+
uses token-based authentication, so browser apps should not hold upstream
|
|
235
|
+
credentials directly. The supported integration model is a real server-backed
|
|
236
|
+
flow.
|
|
237
|
+
|
|
238
|
+
This repository includes `apps/minimal-api` as the reference local server. It
|
|
239
|
+
wraps Japan Post API ver 2.0 and is intended for local development and
|
|
240
|
+
integration testing. The demo's `/minimal-api` path is only a development-time
|
|
241
|
+
route to that local server. When the upstream payload includes both structured
|
|
242
|
+
address parts and a free-form `address` string, the reference server keeps the
|
|
243
|
+
display address non-duplicated instead of concatenating both blindly.
|
|
244
|
+
|
|
245
|
+
Timeout messages can differ depending on whether the token exchange timed out or
|
|
246
|
+
the upstream lookup request timed out. Both cases still map cleanly to the
|
|
247
|
+
`timeout` error code.
|
|
248
|
+
|
|
249
|
+
## SSR
|
|
250
|
+
|
|
251
|
+
Use your server-side API from the `dataSource` implementation, and keep token
|
|
252
|
+
exchange plus upstream signing on the server. React hooks and UI components
|
|
253
|
+
should stay in client components.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 한국어
|
|
258
|
+
|
|
259
|
+
React + TypeScript 기반의 일본 우편번호/주소 검색 훅과 headless 입력
|
|
260
|
+
컴포넌트 라이브러리입니다.
|
|
261
|
+
|
|
262
|
+
이 문서는 배포 패키지 사용 가이드입니다. `pnpm demo:full` 같은 저장소 수준 보조
|
|
263
|
+
스크립트는 현재 Linux/WSL 계열 셸 환경을 전제로 하며, 자세한 내용은 루트 README에서 다룹니다.
|
|
264
|
+
|
|
265
|
+
## 설치
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
pnpm add @cp949/japanpost-react
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
- 지원 React 버전: React 18, React 19
|
|
272
|
+
|
|
273
|
+
## 빠른 시작
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
import { useJapanPostalCode } from "@cp949/japanpost-react";
|
|
277
|
+
import type { JapanAddressDataSource } from "@cp949/japanpost-react";
|
|
278
|
+
import { createJapanAddressError } from "@cp949/japanpost-react";
|
|
279
|
+
|
|
280
|
+
// 현재 지원 방식은 실제 서버 연동뿐입니다.
|
|
281
|
+
// 앱의 백엔드 API 경로에 맞게 dataSource를 연결하세요.
|
|
282
|
+
const dataSource: JapanAddressDataSource = {
|
|
283
|
+
async lookupPostalCode(postalCode) {
|
|
284
|
+
const res = await fetch(`/searchcode/${postalCode}`);
|
|
285
|
+
if (!res.ok) {
|
|
286
|
+
const message = `Postal code lookup failed with status ${res.status}`;
|
|
287
|
+
|
|
288
|
+
if (res.status === 400) {
|
|
289
|
+
throw createJapanAddressError("invalid_postal_code", message, {
|
|
290
|
+
status: res.status,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (res.status === 404) {
|
|
295
|
+
throw createJapanAddressError("not_found", message, {
|
|
296
|
+
status: res.status,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (res.status === 504) {
|
|
301
|
+
throw createJapanAddressError("timeout", message, {
|
|
302
|
+
status: res.status,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
throw createJapanAddressError("data_source_error", message, {
|
|
307
|
+
status: res.status,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
const payload = await res.json();
|
|
311
|
+
if (!Array.isArray(payload.addresses)) {
|
|
312
|
+
throw createJapanAddressError(
|
|
313
|
+
"bad_response",
|
|
314
|
+
"Postal code lookup returned an invalid payload",
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
return payload.addresses;
|
|
318
|
+
},
|
|
319
|
+
async searchAddress(query) {
|
|
320
|
+
const res = await fetch(`/addresszip?q=${encodeURIComponent(query)}`);
|
|
321
|
+
if (!res.ok) {
|
|
322
|
+
const message = `Address search failed with status ${res.status}`;
|
|
323
|
+
|
|
324
|
+
if (res.status === 400) {
|
|
325
|
+
throw createJapanAddressError("invalid_query", message, {
|
|
326
|
+
status: res.status,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (res.status === 404) {
|
|
331
|
+
throw createJapanAddressError("not_found", message, {
|
|
332
|
+
status: res.status,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (res.status === 504) {
|
|
337
|
+
throw createJapanAddressError("timeout", message, {
|
|
338
|
+
status: res.status,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
throw createJapanAddressError("data_source_error", message, {
|
|
343
|
+
status: res.status,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
const payload = await res.json();
|
|
347
|
+
if (!Array.isArray(payload.addresses)) {
|
|
348
|
+
throw createJapanAddressError(
|
|
349
|
+
"bad_response",
|
|
350
|
+
"Address search returned an invalid payload",
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
return payload.addresses;
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
export function PostalForm() {
|
|
358
|
+
const { loading, data, error, search } = useJapanPostalCode({ dataSource });
|
|
359
|
+
|
|
360
|
+
return (
|
|
361
|
+
<div>
|
|
362
|
+
<button onClick={() => void search("100-0001")}>조회</button>
|
|
363
|
+
{loading && <p>조회 중...</p>}
|
|
364
|
+
{error && (
|
|
365
|
+
<p>
|
|
366
|
+
{error.code}: {error.message}
|
|
367
|
+
</p>
|
|
368
|
+
)}
|
|
369
|
+
{data?.addresses.map((addr) => (
|
|
370
|
+
<p key={addr.postalCode + addr.address}>{addr.address}</p>
|
|
371
|
+
))}
|
|
372
|
+
</div>
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Exports
|
|
378
|
+
|
|
379
|
+
- `normalizeJapanPostalCode`
|
|
380
|
+
- `formatJapanPostalCode`
|
|
381
|
+
- `normalizeJapanPostAddressRecord`
|
|
382
|
+
- `isValidJapanPostalCode`
|
|
383
|
+
- `createJapanAddressError`
|
|
384
|
+
- `useJapanPostalCode`
|
|
385
|
+
- `useJapanAddressSearch`
|
|
386
|
+
- `useJapanAddress`
|
|
387
|
+
- `PostalCodeInput`
|
|
388
|
+
- `AddressSearchInput`
|
|
389
|
+
- `JapanAddress`, `JapanAddressDataSource`를 포함한 공개 타입
|
|
390
|
+
- 요청 옵션 타입: `JapanAddressRequestOptions`
|
|
391
|
+
|
|
392
|
+
## 유틸리티 메모
|
|
393
|
+
|
|
394
|
+
`formatJapanPostalCode()`는 정규화된 값이 정확히 7자리일 때만 하이픈을 넣습니다.
|
|
395
|
+
그 외 길이에서는 하이픈을 추가하지 않고 숫자만 남긴 값을 그대로 반환합니다.
|
|
396
|
+
|
|
397
|
+
## Hooks
|
|
398
|
+
|
|
399
|
+
### useJapanPostalCode
|
|
400
|
+
|
|
401
|
+
우편번호로 주소를 조회합니다.
|
|
402
|
+
|
|
403
|
+
```tsx
|
|
404
|
+
const { loading, data, error, search, reset } = useJapanPostalCode({
|
|
405
|
+
dataSource,
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### useJapanAddressSearch
|
|
410
|
+
|
|
411
|
+
자유 형식 키워드로 주소를 검색하며 `debounceMs`를 지원합니다.
|
|
412
|
+
|
|
413
|
+
```tsx
|
|
414
|
+
const { loading, data, error, search, reset } = useJapanAddressSearch({
|
|
415
|
+
dataSource,
|
|
416
|
+
debounceMs: 300,
|
|
417
|
+
});
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### useJapanAddress
|
|
421
|
+
|
|
422
|
+
우편번호 조회와 키워드 검색을 하나의 훅으로 합칩니다.
|
|
423
|
+
|
|
424
|
+
```tsx
|
|
425
|
+
const { loading, data, error, searchByPostalCode, searchByKeyword, reset } =
|
|
426
|
+
useJapanAddress({ dataSource, debounceMs: 300 });
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
모든 훅은 런타임에서 `dataSource`가 필요합니다.
|
|
430
|
+
|
|
431
|
+
## 에러 처리 메모
|
|
432
|
+
|
|
433
|
+
`JapanAddressDataSource`의 두 메서드는 모두 `JapanAddress[]`를 직접
|
|
434
|
+
반환해야 합니다. 각 훅은 그 배열을 받아 `{ postalCode, addresses }`,
|
|
435
|
+
`{ query, addresses }` 형태의 결과 객체를 조합합니다.
|
|
436
|
+
|
|
437
|
+
두 메서드는 선택적인 두 번째 인자도 받을 수 있습니다.
|
|
438
|
+
|
|
439
|
+
```ts
|
|
440
|
+
type JapanAddressRequestOptions = {
|
|
441
|
+
signal?: AbortSignal;
|
|
442
|
+
};
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
훅은 superseded 요청, `reset()`, unmount 정리 상황에서 이전 요청을 취소할 수
|
|
446
|
+
있도록 `signal`을 전달합니다. 백엔드 레이어가 abort를 지원하면 그대로 활용할 수
|
|
447
|
+
있습니다.
|
|
448
|
+
|
|
449
|
+
권장 에러 코드 매핑:
|
|
450
|
+
|
|
451
|
+
| 상황 | 권장 코드 |
|
|
452
|
+
| --- | --- |
|
|
453
|
+
| 잘못된 우편번호 입력 | `invalid_postal_code` |
|
|
454
|
+
| 빈 주소 검색어 | `invalid_query` |
|
|
455
|
+
| 네트워크 실패 | `network_error` |
|
|
456
|
+
| 요청 중단 / 타임아웃 | `timeout` |
|
|
457
|
+
| 검색 결과 없음 | `not_found` |
|
|
458
|
+
| 성공 응답 shape 이상 | `bad_response` |
|
|
459
|
+
| 그 외 백엔드 오류 | `data_source_error` |
|
|
460
|
+
|
|
461
|
+
이 저장소의 참고 demo 흐름에서는 예시 `dataSource`가 `400 /searchcode/...`를
|
|
462
|
+
`invalid_postal_code`, `400 /addresszip?...`를 `invalid_query`, `404`를
|
|
463
|
+
`not_found`, `504`를 `timeout`으로 매핑합니다.
|
|
464
|
+
|
|
465
|
+
## Headless 컴포넌트
|
|
466
|
+
|
|
467
|
+
`PostalCodeInput`, `AddressSearchInput`은 스타일 없이 동작과 DOM 구조만
|
|
468
|
+
제공하므로, 앱의 디자인 시스템에 맞게 직접 꾸밀 수 있습니다.
|
|
469
|
+
|
|
470
|
+
두 컴포넌트는 네이티브 props 전달도 지원합니다.
|
|
471
|
+
|
|
472
|
+
- `inputProps`: 실제 `<input />`에 전달
|
|
473
|
+
- `buttonProps`: 실제 `<button />`에 전달
|
|
474
|
+
|
|
475
|
+
따라서 `id`, `name`, `placeholder`, `aria-*`, `autoComplete`, `className`,
|
|
476
|
+
폼 연동용 속성을 직접 넘길 수 있습니다. `PostalCodeInput`은 별도 override가
|
|
477
|
+
없으면 기본적으로 `inputMode="numeric"`를 사용합니다.
|
|
478
|
+
|
|
479
|
+
## Data Source와 서버 연동
|
|
480
|
+
|
|
481
|
+
이 패키지는 자체 백엔드 서버와 함께 사용하는 것을 권장합니다. Japan Post
|
|
482
|
+
공식 연동은 토큰 기반 인증을 사용하므로, 브라우저에서 업스트림 자격증명을
|
|
483
|
+
직접 보관하면 안 됩니다. 현재 지원 방식은 실제 서버 연동뿐입니다.
|
|
484
|
+
|
|
485
|
+
이 저장소의 `apps/minimal-api`는 로컬 기준 참고 서버 구현입니다. Japan Post
|
|
486
|
+
API ver 2.0을 감싸며, 로컬 개발과 통합 확인 용도로 쓰는 구성을 목표로
|
|
487
|
+
합니다. demo의 `/minimal-api` 경로는 개발 편의를 위한 로컬 경로 연결입니다.
|
|
488
|
+
업스트림 payload에 구조화된 주소 필드와 원본 전체 주소 문자열인 `address`가 함께
|
|
489
|
+
있더라도, 참고 서버는 둘을 그대로 이어붙이지 않고 중복 없는 표시 주소를
|
|
490
|
+
우선 사용합니다.
|
|
491
|
+
|
|
492
|
+
timeout 메시지는 토큰 발급 단계와 실제 조회 단계 중 어느 쪽에서 timeout이
|
|
493
|
+
발생했는지에 따라 달라질 수 있지만, 두 경우 모두 `timeout` 코드로 다루면
|
|
494
|
+
됩니다.
|
|
495
|
+
|
|
496
|
+
## SSR
|
|
497
|
+
|
|
498
|
+
`dataSource` 구현에서는 서버 측 API를 사용하고, 토큰 교환과 업스트림 서명은
|
|
499
|
+
서버에서만 처리하세요. React 훅과 UI 컴포넌트는 클라이언트 컴포넌트에서
|
|
500
|
+
사용하는 것이 안전합니다.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { AddressSearchInputProps } from '../core/types';
|
|
2
|
+
/**
|
|
3
|
+
* 스타일 의존성이 없는 최소한의 주소 키워드 검색 입력 컴포넌트.
|
|
4
|
+
* value를 전달하면 제어 모드, 전달하지 않으면 비제어 모드로 동작한다.
|
|
5
|
+
*/
|
|
6
|
+
export declare function AddressSearchInput({ defaultValue, value, disabled, label, buttonLabel, inputProps, buttonProps, onChange, onSearch, }: AddressSearchInputProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=AddressSearchInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AddressSearchInput.d.ts","sourceRoot":"","sources":["../../src/components/AddressSearchInput.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAE7D;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,YAAiB,EACjB,KAAK,EACL,QAAQ,EACR,KAAyB,EACzB,WAAsB,EACtB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,QAAQ,GACT,EAAE,uBAAuB,2CA4CzB"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { PostalCodeInputProps } from '../core/types';
|
|
2
|
+
/**
|
|
3
|
+
* 스타일 의존성이 없는 최소한의 우편번호 입력 컴포넌트.
|
|
4
|
+
* value를 전달하면 제어 모드, 전달하지 않으면 비제어 모드로 동작한다.
|
|
5
|
+
*/
|
|
6
|
+
export declare function PostalCodeInput({ defaultValue, value, disabled, label, buttonLabel, inputProps, buttonProps, onChange, onSearch, }: PostalCodeInputProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=PostalCodeInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostalCodeInput.d.ts","sourceRoot":"","sources":["../../src/components/PostalCodeInput.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAE1D;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC9B,YAAiB,EACjB,KAAK,EACL,QAAQ,EACR,KAAqB,EACrB,WAAsB,EACtB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,QAAQ,GACT,EAAE,oBAAoB,2CA6CtB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { JapanAddressError, JapanAddressErrorCode } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* 라이브러리 전용 에러 객체를 생성한다.
|
|
4
|
+
* name과 code를 일관되게 설정해 catch 블록에서 타입 좁히기가 쉽도록 한다.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createJapanAddressError(code: JapanAddressErrorCode, message: string, options?: {
|
|
7
|
+
cause?: unknown;
|
|
8
|
+
status?: number;
|
|
9
|
+
}): JapanAddressError;
|
|
10
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAExE;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,qBAAqB,EAC3B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7C,iBAAiB,CAQnB"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 우편번호에서 숫자가 아닌 문자(하이픈 등)를 제거해 순수 숫자 문자열로 만든다.
|
|
3
|
+
* 예: "123-4567" → "1234567"
|
|
4
|
+
*/
|
|
5
|
+
export declare function normalizeJapanPostalCode(value: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* 우편번호를 "NNN-NNNN" 형식으로 포맷한다.
|
|
8
|
+
* 정확히 7자리가 아닐 때는 하이픈 없이 숫자만 반환한다.
|
|
9
|
+
* 예: "1234567" → "123-4567"
|
|
10
|
+
*/
|
|
11
|
+
export declare function formatJapanPostalCode(value: string): string;
|
|
12
|
+
//# sourceMappingURL=formatters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatters.d.ts","sourceRoot":"","sources":["../../src/core/formatters.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAW3D"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { JapanAddress, NormalizedJapanAddressRecord } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* 정규화된 내부 주소 레코드를 라이브러리 공개 JapanAddress 형태로 변환한다.
|
|
4
|
+
* address는 도도부현·시구정촌·동·상세주소를 순서대로 이어붙인 문자열이다.
|
|
5
|
+
*/
|
|
6
|
+
export declare function normalizeJapanPostAddressRecord(record: NormalizedJapanAddressRecord): JapanAddress;
|
|
7
|
+
//# sourceMappingURL=normalizers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizers.d.ts","sourceRoot":"","sources":["../../src/core/normalizers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAC;AAU1E;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,4BAA4B,GACnC,YAAY,CAoBd"}
|