@cp949/japanpost-react 1.0.1 → 1.0.2

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 (45) hide show
  1. package/README.ko.md +62 -22
  2. package/README.md +207 -191
  3. package/dist/index.d.ts +1 -11
  4. package/dist/index.es.js +106 -62
  5. package/dist/index.umd.cjs +1 -1
  6. package/dist/src/components/AddressSearchInput.d.ts.map +1 -0
  7. package/dist/src/components/PostalCodeInput.d.ts.map +1 -0
  8. package/dist/src/core/errors.d.ts.map +1 -0
  9. package/dist/src/core/formatters.d.ts.map +1 -0
  10. package/dist/src/core/normalizers.d.ts.map +1 -0
  11. package/dist/{core → src/core}/types.d.ts +27 -7
  12. package/dist/src/core/types.d.ts.map +1 -0
  13. package/dist/src/core/validators.d.ts.map +1 -0
  14. package/dist/src/index.d.ts +11 -0
  15. package/dist/src/index.d.ts.map +1 -0
  16. package/dist/src/react/toJapanAddressError.d.ts.map +1 -0
  17. package/dist/src/react/useJapanAddress.d.ts.map +1 -0
  18. package/dist/src/react/useJapanAddressSearch.d.ts.map +1 -0
  19. package/dist/src/react/useJapanPostalCode.d.ts.map +1 -0
  20. package/dist/{react → src/react}/useLatestRequestState.d.ts +1 -0
  21. package/dist/src/react/useLatestRequestState.d.ts.map +1 -0
  22. package/package.json +4 -4
  23. package/dist/components/AddressSearchInput.d.ts.map +0 -1
  24. package/dist/components/PostalCodeInput.d.ts.map +0 -1
  25. package/dist/core/errors.d.ts.map +0 -1
  26. package/dist/core/formatters.d.ts.map +0 -1
  27. package/dist/core/normalizers.d.ts.map +0 -1
  28. package/dist/core/types.d.ts.map +0 -1
  29. package/dist/core/validators.d.ts.map +0 -1
  30. package/dist/index.d.ts.map +0 -1
  31. package/dist/react/toJapanAddressError.d.ts.map +0 -1
  32. package/dist/react/useJapanAddress.d.ts.map +0 -1
  33. package/dist/react/useJapanAddressSearch.d.ts.map +0 -1
  34. package/dist/react/useJapanPostalCode.d.ts.map +0 -1
  35. package/dist/react/useLatestRequestState.d.ts.map +0 -1
  36. /package/dist/{components → src/components}/AddressSearchInput.d.ts +0 -0
  37. /package/dist/{components → src/components}/PostalCodeInput.d.ts +0 -0
  38. /package/dist/{core → src/core}/errors.d.ts +0 -0
  39. /package/dist/{core → src/core}/formatters.d.ts +0 -0
  40. /package/dist/{core → src/core}/normalizers.d.ts +0 -0
  41. /package/dist/{core → src/core}/validators.d.ts +0 -0
  42. /package/dist/{react → src/react}/toJapanAddressError.d.ts +0 -0
  43. /package/dist/{react → src/react}/useJapanAddress.d.ts +0 -0
  44. /package/dist/{react → src/react}/useJapanAddressSearch.d.ts +0 -0
  45. /package/dist/{react → src/react}/useJapanPostalCode.d.ts +0 -0
package/README.ko.md CHANGED
@@ -169,8 +169,10 @@ export function PostalForm() {
169
169
  - `useJapanAddress`
170
170
  - `PostalCodeInput`
171
171
  - `AddressSearchInput`
172
- - `JapanAddress`, `JapanAddressDataSource`, `JapanPostSearchcodeRequest`,
173
- `JapanPostAddresszipRequest`, `Page`를 포함한 공개 타입
172
+ - `JapanAddress`, `JapanAddressDataSource`, `JapanPostalCodeSearchInput`,
173
+ `JapanAddressSearchInput`, `JapanPostSearchcodeRequest`,
174
+ `JapanPostAddresszipRequest`, `Page`를 포함한
175
+ 공개 타입
174
176
  - 요청 옵션 타입: `JapanAddressRequestOptions`
175
177
 
176
178
  ## 유틸리티 메모
@@ -182,56 +184,94 @@ export function PostalForm() {
182
184
 
183
185
  ### useJapanPostalCode
184
186
 
185
- 우편번호로 주소를 조회합니다. `3~7자리` 숫자 입력을 받아 `3~6자리`일 때는
186
- prefix 검색으로 동작합니다.
187
+ 우편번호로 주소를 조회합니다. 문자열 입력과 구조화된 요청 입력을 모두
188
+ 받을 수 있고, `3~6자리` 입력일 때는 prefix 검색으로 동작합니다.
187
189
 
188
190
  ```tsx
189
- const { loading, data, error, search, reset } = useJapanPostalCode({
191
+ const { loading, data, error, search, cancel, reset } = useJapanPostalCode({
190
192
  dataSource,
191
193
  });
192
194
  ```
193
195
 
196
+ ```tsx
197
+ void search("1000001");
198
+ void search({
199
+ postalCode: "1000001",
200
+ pageNumber: 1,
201
+ rowsPerPage: 10,
202
+ includeParenthesesTown: true,
203
+ });
204
+ ```
205
+
206
+ `cancel()`은 진행 중인 조회를 중단하고 현재 data/error 상태는 그대로 유지합니다.
207
+ `reset()`은 `cancel()`을 호출한 뒤 data/error까지 비워 훅을 idle 상태로
208
+ 되돌립니다.
209
+
194
210
  ### useJapanAddressSearch
195
211
 
196
- 자유 형식 키워드로 주소를 검색하며 `debounceMs`를 지원합니다.
212
+ 자유 형식 키워드 또는 구조화된 필드로 주소를 검색하며 `debounceMs`를 지원합니다.
197
213
 
198
214
  ```tsx
199
- const { loading, data, error, search, reset } = useJapanAddressSearch({
215
+ const { loading, data, error, search, cancel, reset } = useJapanAddressSearch({
200
216
  dataSource,
201
217
  debounceMs: 300,
202
218
  });
203
219
  ```
204
220
 
221
+ ```tsx
222
+ void search("Tokyo");
223
+ void search({
224
+ addressQuery: "Tokyo",
225
+ pageNumber: 1,
226
+ rowsPerPage: 10,
227
+ includeCityDetails: true,
228
+ });
229
+ void search({
230
+ prefName: "東京都",
231
+ cityName: "千代田区",
232
+ });
233
+ ```
234
+
235
+ `search("Tokyo")`는 `addressQuery` 검색의 축약형입니다. 구조화된 필드는
236
+ `addressQuery` 없이도 사용할 수 있어서, 도도부현/시/동 단위로만 검색하고 싶을 때
237
+ 유용합니다.
238
+
205
239
  이 훅은 빈 검색어에 대해 여전히 클라이언트 선제 검증을 수행하고, 요청을 보내기
206
240
  전에 `invalid_query`를 반환합니다. 이 검증은 UX 보조 장치이며 서버 검증이나
207
241
  서버 계약 처리를 대체하지 않습니다.
208
242
 
243
+ `cancel()`은 진행 중인 요청이나 대기 중인 debounce 타이머를 중단하고,
244
+ 현재 data/error 상태는 그대로 유지합니다. `reset()`은 `cancel()`을 호출한 뒤
245
+ data와 error까지 비워 훅을 idle 상태로 되돌립니다.
246
+
209
247
  ### useJapanAddress
210
248
 
211
- 우편번호 조회와 키워드 검색을 하나의 훅으로 합칩니다.
249
+ 우편번호 조회와 주소 질의 검색을 하나의 훅으로 합칩니다.
212
250
 
213
251
  ```tsx
214
- const { loading, data, error, searchByPostalCode, searchByKeyword, reset } =
252
+ const { loading, data, error, searchByPostalCode, searchByAddressQuery, reset } =
215
253
  useJapanAddress({ dataSource, debounceMs: 300 });
216
254
  ```
217
255
 
218
256
  모든 훅은 런타임에서 `dataSource`가 필요합니다.
219
257
 
220
- 훅의 public API는 계속 문자열 기반입니다.
258
+ `useJapanPostalCode().search`, `useJapanAddressSearch().search`,
259
+ `useJapanAddress`의 편의 메서드는 모두 같은 공개 검색 입력 타입을 받습니다.
221
260
 
222
- - `useJapanPostalCode().search(value: string)`
223
- - `useJapanAddressSearch().search(query: string)`
224
- - `useJapanAddress().searchByPostalCode(value: string)`
225
- - `useJapanAddress().searchByKeyword(query: string)`
261
+ - `useJapanPostalCode().search(input: JapanPostalCodeSearchInput)`
262
+ - `useJapanAddressSearch().search(input: JapanAddressSearchInput)`
263
+ - `useJapanAddress().searchByPostalCode(input: JapanPostalCodeSearchInput)`
264
+ - `useJapanAddress().searchByAddressQuery(input: JapanAddressSearchInput)`
226
265
 
227
266
  대신 훅 내부에서 `dataSource` 호출 전에 request object를 조립합니다.
228
267
 
229
- - 우편번호 조회: `{ value, pageNumber: 0, rowsPerPage: 100 }`
230
- - 주소 검색: `{ freeword, pageNumber: 0, rowsPerPage: 100 }`
268
+ - 우편번호 조회: `{ postalCode, pageNumber: 0, rowsPerPage: 100 }`
269
+ - 주소 검색: `{ addressQuery, pageNumber: 0, rowsPerPage: 100 }`
231
270
 
232
- `includeCityDetails`, `includePrefectureDetails` 같은 optional flag는
233
- 기본적으로 넣지 않으며, 필요하면 사용자 data source 구현에서 직접
234
- 지정하면 됩니다.
271
+ `useJapanAddressSearch`에서는 `addressQuery`를 생략한 구조화 요청도 가능하므로
272
+ 도도부현, 시, 필드만으로 검색할 있습니다. `includeCityDetails`,
273
+ `includePrefectureDetails` 같은 optional flag는 기본적으로 넣지 않으며,
274
+ 필요하면 사용자 data source 구현에서 직접 지정하면 됩니다.
235
275
 
236
276
  ## 에러 처리 메모
237
277
 
@@ -248,9 +288,9 @@ type JapanAddressRequestOptions = {
248
288
  };
249
289
  ```
250
290
 
251
- 훅은 superseded 요청, `reset()`, unmount 정리 상황에서 이전 요청을 취소할 수
252
- 있도록 `signal`을 전달합니다. 백엔드 레이어가 abort를 지원하면 그대로 활용할 수
253
- 있습니다.
291
+ 훅은 superseded 요청, `cancel()`, `reset()`, unmount 정리 상황에서 이전
292
+ 요청을 취소할 수 있도록 `signal`을 전달합니다. 백엔드 레이어가 abort를 지원하면
293
+ 그대로 활용할 수 있습니다.
254
294
 
255
295
  권장 에러 코드 매핑:
256
296
 
package/README.md CHANGED
@@ -4,12 +4,11 @@
4
4
 
5
5
  <!-- This file is generated by `pnpm readme:package`. Edit docs/README.en.md and docs/README.ko.md instead. -->
6
6
 
7
- React + TypeScript hooks and headless inputs for Japan postal-code and address
8
- lookup.
7
+ React hooks, headless input components, and utilities for Japan postal-code
8
+ and address lookup.
9
9
 
10
- This package guide covers the published library. Repository-level demo scripts
11
- such as `pnpm demo:full` currently target Linux/WSL-style shell environments
12
- and are documented in the root README.
10
+ This package does not call Japan Post directly. You provide a
11
+ `JapanAddressDataSource` that talks to your own backend API.
13
12
 
14
13
  ## Install
15
14
 
@@ -17,45 +16,56 @@ and are documented in the root README.
17
16
  pnpm add @cp949/japanpost-react
18
17
  ```
19
18
 
20
- - Supported React versions: React 18 and React 19
19
+ - Peer dependencies: React 18 or React 19
20
+ - Package source in this repository:
21
+ `packages/japanpost-react`
22
+ - Demo app in this repository: `apps/demo`
23
+
24
+ ## What The Package Provides
25
+
26
+ - Hooks for postal-code lookup and address search:
27
+ `useJapanPostalCode`, `useJapanAddressSearch`, `useJapanAddress`
28
+ - Headless form components:
29
+ `PostalCodeInput`, `AddressSearchInput`
30
+ - Utilities:
31
+ `normalizeJapanPostalCode`, `formatJapanPostalCode`,
32
+ `isValidJapanPostalCode`, `normalizeJapanPostAddressRecord`,
33
+ `createJapanAddressError`
34
+ - Public types for the request, response, error, and data-source contracts
21
35
 
22
36
  ## Quick Start
23
37
 
38
+ The package expects a `JapanAddressDataSource` with two methods:
39
+
40
+ - `lookupPostalCode(request, options?)`
41
+ - `searchAddress(request, options?)`
42
+
43
+ Both methods return `Promise<Page<JapanAddress>>`.
44
+
24
45
  ```tsx
25
- import { useJapanPostalCode } from "@cp949/japanpost-react";
26
- import type {
27
- JapanAddressDataSource,
28
- JapanAddressRequestOptions,
29
- JapanAddress,
30
- Page,
46
+ import {
47
+ PostalCodeInput,
48
+ createJapanAddressError,
49
+ useJapanPostalCode,
50
+ type JapanAddress,
51
+ type JapanAddressDataSource,
52
+ type JapanAddressRequestOptions,
53
+ type Page,
31
54
  } from "@cp949/japanpost-react";
32
- import { createJapanAddressError } from "@cp949/japanpost-react";
33
55
 
34
- // The only supported integration model is a real server-backed flow.
35
- // Point the data source at your own backend API.
36
- // On beta-compatible backends, blank addresszip searches and postal-code misses
37
- // may return HTTP 200 with an empty page. Keep those as successful Page results.
38
- // The status mapping below applies only to non-OK responses.
39
56
  function isAbortError(error: unknown): boolean {
40
57
  return error instanceof DOMException && error.name === "AbortError";
41
58
  }
42
59
 
43
- function resolveErrorCode(path: string, status: number) {
44
- if (status === 404) {
45
- return "not_found";
46
- }
47
-
48
- if (status === 504) {
49
- return "timeout";
50
- }
51
-
52
- if (status === 400) {
53
- return path === "/q/japanpost/searchcode"
54
- ? "invalid_postal_code"
55
- : "invalid_query";
56
- }
57
-
58
- return "data_source_error";
60
+ function isPagePayload(payload: unknown): payload is Page<JapanAddress> {
61
+ return (
62
+ typeof payload === "object" &&
63
+ payload !== null &&
64
+ Array.isArray((payload as { elements?: unknown }).elements) &&
65
+ typeof (payload as { totalElements?: unknown }).totalElements === "number" &&
66
+ typeof (payload as { pageNumber?: unknown }).pageNumber === "number" &&
67
+ typeof (payload as { rowsPerPage?: unknown }).rowsPerPage === "number"
68
+ );
59
69
  }
60
70
 
61
71
  async function readPage(
@@ -63,10 +73,10 @@ async function readPage(
63
73
  request: unknown,
64
74
  options?: JapanAddressRequestOptions,
65
75
  ): Promise<Page<JapanAddress>> {
66
- let res: Response;
76
+ let response: Response;
67
77
 
68
78
  try {
69
- res = await fetch(path, {
79
+ response = await fetch(path, {
70
80
  method: "POST",
71
81
  headers: {
72
82
  "content-type": "application/json",
@@ -78,173 +88,213 @@ async function readPage(
78
88
  throw createJapanAddressError(
79
89
  isAbortError(error) ? "timeout" : "network_error",
80
90
  isAbortError(error) ? "Request timed out" : "Network request failed",
81
- {
82
- cause: error,
83
- },
91
+ { cause: error },
84
92
  );
85
93
  }
86
94
 
87
- if (!res.ok) {
88
- const message = `Request failed with status ${res.status}`;
89
-
90
- throw createJapanAddressError(resolveErrorCode(path, res.status), message, {
91
- status: res.status,
92
- });
95
+ if (!response.ok) {
96
+ throw createJapanAddressError(
97
+ "data_source_error",
98
+ `Request failed with status ${response.status}`,
99
+ { status: response.status },
100
+ );
93
101
  }
94
102
 
95
103
  let payload: unknown;
96
104
 
97
105
  try {
98
- payload = await res.json();
106
+ payload = await response.json();
99
107
  } catch (error) {
100
108
  throw createJapanAddressError(
101
109
  "bad_response",
102
110
  "Response payload was not valid JSON",
103
- {
104
- cause: error,
105
- },
111
+ { cause: error },
106
112
  );
107
113
  }
108
114
 
109
- if (
110
- typeof payload !== "object" ||
111
- payload === null ||
112
- !Array.isArray((payload as { elements?: unknown }).elements) ||
113
- typeof (payload as { totalElements?: unknown }).totalElements !== "number" ||
114
- typeof (payload as { pageNumber?: unknown }).pageNumber !== "number" ||
115
- typeof (payload as { rowsPerPage?: unknown }).rowsPerPage !== "number"
116
- ) {
115
+ if (!isPagePayload(payload)) {
117
116
  throw createJapanAddressError(
118
117
  "bad_response",
119
118
  "Response payload must include a valid page payload",
120
119
  );
121
120
  }
122
121
 
123
- return payload as Page<JapanAddress>;
122
+ return payload;
124
123
  }
125
124
 
126
125
  const dataSource: JapanAddressDataSource = {
127
- async lookupPostalCode(request, options) {
128
- return readPage(`/q/japanpost/searchcode`, request, options);
126
+ lookupPostalCode(request, options) {
127
+ return readPage("/q/japanpost/searchcode", request, options);
129
128
  },
130
- async searchAddress(request, options) {
131
- return readPage(`/q/japanpost/addresszip`, request, options);
129
+ searchAddress(request, options) {
130
+ return readPage("/q/japanpost/addresszip", request, options);
132
131
  },
133
132
  };
134
133
 
135
- export function PostalForm() {
134
+ export function PostalCodeLookupExample() {
136
135
  const { loading, data, error, search } = useJapanPostalCode({ dataSource });
137
136
 
138
137
  return (
139
138
  <div>
140
- <button onClick={() => void search("100-0001")}>Search</button>
141
- {loading && <p>Loading...</p>}
142
- {error && (
143
- <p>
144
- {error.code}: {error.message}
145
- </p>
146
- )}
147
- <p>Total results: {data?.totalElements ?? 0}</p>
148
- {data?.elements.map((addr) => (
149
- <p key={addr.postalCode + addr.address}>{addr.address}</p>
150
- ))}
139
+ <PostalCodeInput
140
+ buttonLabel="Search"
141
+ label="Postal code"
142
+ onSearch={(postalCode) => {
143
+ void search({ postalCode });
144
+ }}
145
+ />
146
+
147
+ {loading ? <p>Loading...</p> : null}
148
+ {error ? <p>{error.message}</p> : null}
149
+
150
+ <ul>
151
+ {(data?.elements ?? []).map((address) => (
152
+ <li key={`${address.postalCode}-${address.address}`}>
153
+ {address.address}
154
+ </li>
155
+ ))}
156
+ </ul>
151
157
  </div>
152
158
  );
153
159
  }
154
160
  ```
155
161
 
156
- The sample `resolveErrorCode()` helper only classifies non-OK responses. In the
157
- current beta-compatible contract, blank address-search requests and postal-code
158
- misses may still succeed with `200` plus an empty page, while `404 -> not_found`
159
- remains a backend-specific choice for servers that intentionally surface misses
160
- as errors.
161
-
162
- ## Exports
163
-
164
- - `normalizeJapanPostalCode`
165
- - `formatJapanPostalCode`
166
- - `normalizeJapanPostAddressRecord`
167
- - `isValidJapanPostalCode`
168
- - `createJapanAddressError`
169
- - `useJapanPostalCode`
170
- - `useJapanAddressSearch`
171
- - `useJapanAddress`
172
- - `PostalCodeInput`
173
- - `AddressSearchInput`
174
- - Public types including `JapanAddress`, `JapanAddressDataSource`,
175
- `JapanPostSearchcodeRequest`, `JapanPostAddresszipRequest`, and `Page`
176
- - Request options type: `JapanAddressRequestOptions`
177
-
178
- ## Utility Notes
179
-
180
- `formatJapanPostalCode()` inserts a hyphen only when the normalized value is
181
- exactly 7 digits. For any other length, it returns the normalized digits
182
- without inserting a hyphen.
162
+ The example paths above match this repository's reference backend. In your own
163
+ app, the backend routes can be different as long as your `dataSource`
164
+ implementation returns the same public types.
165
+
166
+ ## Core Contract
167
+
168
+ `Page<T>` is the result shape shared by the hooks and the reference backend:
169
+
170
+ ```ts
171
+ type Page<T> = {
172
+ elements: T[];
173
+ totalElements: number;
174
+ pageNumber: number;
175
+ rowsPerPage: number;
176
+ };
177
+ ```
178
+
179
+ `JapanAddress` is the normalized address shape returned by the package:
180
+
181
+ ```ts
182
+ type JapanAddress = {
183
+ postalCode: string;
184
+ prefecture: string;
185
+ prefectureKana?: string;
186
+ city: string;
187
+ cityKana?: string;
188
+ town: string;
189
+ townKana?: string;
190
+ address: string;
191
+ provider: "japan-post";
192
+ };
193
+ ```
194
+
195
+ The hooks keep this page payload as-is, so consumers read
196
+ `data?.elements`, `data?.totalElements`, `data?.pageNumber`, and
197
+ `data?.rowsPerPage` directly.
183
198
 
184
199
  ## Hooks
185
200
 
186
- ### useJapanPostalCode
201
+ ### `useJapanPostalCode`
187
202
 
188
- Looks up addresses by postal code. The hook accepts `3-7` digits and uses
189
- prefix search when the input has `3-6` digits.
203
+ - Accepts `string` or `JapanPostalCodeSearchInput`
204
+ - Normalizes the input to digits before calling the data source
205
+ - Allows `3-7` digits, so prefix lookup is possible
206
+ - Builds `{ postalCode, pageNumber: 0, rowsPerPage: 100 }` by default
207
+ - Exposes `loading`, `data`, `error`, `search`, `cancel`, and `reset`
190
208
 
191
209
  ```tsx
192
- const { loading, data, error, search, reset } = useJapanPostalCode({
193
- dataSource,
210
+ const postalCode = useJapanPostalCode({ dataSource });
211
+
212
+ void postalCode.search("100-0001");
213
+ void postalCode.search({
214
+ postalCode: "1000001",
215
+ pageNumber: 1,
216
+ rowsPerPage: 10,
217
+ includeParenthesesTown: true,
194
218
  });
195
219
  ```
196
220
 
197
- ### useJapanAddressSearch
221
+ ### `useJapanAddressSearch`
198
222
 
199
- Searches addresses by free-form keyword and supports debouncing.
223
+ - Accepts `string` or `JapanAddressSearchInput`
224
+ - Supports free-form search and structured fields in the same request type
225
+ - Rejects a fully blank query before calling the data source
226
+ - Omits `includeCityDetails` and `includePrefectureDetails` unless you set them
227
+ - Supports `debounceMs`
228
+ - Exposes `loading`, `data`, `error`, `search`, `cancel`, and `reset`
200
229
 
201
230
  ```tsx
202
- const { loading, data, error, search, reset } = useJapanAddressSearch({
231
+ const addressSearch = useJapanAddressSearch({
203
232
  dataSource,
204
233
  debounceMs: 300,
205
234
  });
206
- ```
207
235
 
208
- The hook still performs client-side pre-validation for blank queries and
209
- returns `invalid_query` before sending a request. That validation is a UX
210
- helper only and does not replace server-side validation or server-side
211
- contract handling.
236
+ void addressSearch.search("千代田");
237
+ void addressSearch.search({
238
+ prefName: "東京都",
239
+ cityName: "千代田区",
240
+ pageNumber: 0,
241
+ rowsPerPage: 10,
242
+ });
243
+ ```
212
244
 
213
- ### useJapanAddress
245
+ ### `useJapanAddress`
214
246
 
215
- Combines postal-code lookup and keyword search into one hook.
247
+ - Combines postal-code lookup and address search in one hook
248
+ - Reuses the same `dataSource`
249
+ - Exposes `searchByPostalCode`, `searchByAddressQuery`, and `reset`
250
+ - Returns `data` and `error` for the currently active search mode only
216
251
 
217
252
  ```tsx
218
- const { loading, data, error, searchByPostalCode, searchByKeyword, reset } =
219
- useJapanAddress({ dataSource, debounceMs: 300 });
253
+ const address = useJapanAddress({
254
+ dataSource,
255
+ debounceMs: 300,
256
+ });
257
+
258
+ void address.searchByPostalCode("1000001");
259
+ void address.searchByAddressQuery({
260
+ addressQuery: "千代田",
261
+ pageNumber: 0,
262
+ rowsPerPage: 10,
263
+ });
220
264
  ```
221
265
 
222
- All hooks require `dataSource` at runtime.
266
+ ## Headless Components
267
+
268
+ ### `PostalCodeInput`
269
+
270
+ - Renders a `<form>` with `<label>`, `<input>`, and `<button>`
271
+ - Supports controlled and uncontrolled usage
272
+ - Calls `onSearch` with a normalized digits-only postal code
273
+ - Defaults `inputMode="numeric"` unless overridden with `inputProps`
223
274
 
224
- The hook public APIs stay string-based:
275
+ ### `AddressSearchInput`
225
276
 
226
- - `useJapanPostalCode().search(value: string)`
227
- - `useJapanAddressSearch().search(query: string)`
228
- - `useJapanAddress().searchByPostalCode(value: string)`
229
- - `useJapanAddress().searchByKeyword(query: string)`
277
+ - Renders the same minimal form structure
278
+ - Supports controlled and uncontrolled usage
279
+ - Calls `onSearch` with a trimmed query string
230
280
 
231
- Internally, the hooks build request objects before calling the data source:
281
+ Both components accept:
232
282
 
233
- - postal-code lookup: `{ value, pageNumber: 0, rowsPerPage: 100 }`
234
- - address search: `{ freeword, pageNumber: 0, rowsPerPage: 100 }`
283
+ - `inputProps` for the rendered `<input>`
284
+ - `buttonProps` for the rendered `<button>`
235
285
 
236
- Optional request flags such as `includeCityDetails` and
237
- `includePrefectureDetails` are omitted unless your own data source
238
- implementation sets them explicitly.
286
+ ## Data Source Integration
239
287
 
240
- ## Error Handling Notes
288
+ The package exports types for both sides of the integration:
241
289
 
242
- `JapanAddressDataSource` should return `Page<JapanAddress>` directly from both
243
- methods. Hooks preserve that page payload as-is, so consumers can read
244
- `data.elements`, `data.totalElements`, `data.pageNumber`, and
245
- `data.rowsPerPage` directly.
290
+ - `JapanAddressDataSource`
291
+ - `JapanPostSearchcodeRequest`
292
+ - `JapanPostAddresszipRequest`
293
+ - `JapanPostalCodeSearchInput`
294
+ - `JapanAddressSearchInput`
295
+ - `JapanAddressRequestOptions`
246
296
 
247
- Both methods may also receive an optional second argument:
297
+ The optional second argument to each data-source method is:
248
298
 
249
299
  ```ts
250
300
  type JapanAddressRequestOptions = {
@@ -252,63 +302,29 @@ type JapanAddressRequestOptions = {
252
302
  };
253
303
  ```
254
304
 
255
- Hooks pass `signal` so your data source can cancel superseded requests,
256
- `reset()` calls, and unmount cleanup when your backend layer supports aborts.
257
-
258
- Recommended error-code mapping:
259
-
260
- | Situation | Recommended code |
261
- | --- | --- |
262
- | Invalid postal code input | `invalid_postal_code` |
263
- | Blank keyword input in hook-side pre-validation | `invalid_query` |
264
- | Network failure | `network_error` |
265
- | Request aborted / timeout | `timeout` |
266
- | No matching addresses on backends that surface misses as errors | `not_found` |
267
- | Malformed success payload | `bad_response` |
268
- | Other backend failures | `data_source_error` |
269
-
270
- In this repository's reference demo flow, the sample `dataSource` classifies
271
- failed requests by HTTP status code only. Current beta-compatible flows may
272
- return `200` with an empty page for both blank `addresszip` requests and
273
- postal-code misses, and those should stay successful page results. Other
274
- `400` responses can still map to `invalid_query`, `404` remains useful for
275
- backends that intentionally surface misses as errors, and `504` maps to
276
- `timeout`.
277
-
278
- ## Headless Components
279
-
280
- `PostalCodeInput` and `AddressSearchInput` provide behavior and DOM structure
281
- without bundled styles, so you can plug them into your own design system.
282
-
283
- Both components also support native prop passthrough:
284
-
285
- - `inputProps`: forwarded to the rendered `<input />`
286
- - `buttonProps`: forwarded to the rendered `<button />`
287
-
288
- Use these for `id`, `name`, `placeholder`, `aria-*`, `autoComplete`,
289
- `className`, and form integration. `PostalCodeInput` defaults to
290
- `inputMode="numeric"` unless you override it through `inputProps`.
291
-
292
- ## Data Source and Server Integration
305
+ The hooks pass `signal` so your data source can cancel superseded requests,
306
+ `cancel()` calls, `reset()` calls, and unmount cleanup.
293
307
 
294
- Use this package with your own backend server. The official Japan Post flow
295
- uses token-based authentication, so browser apps should not hold upstream
296
- credentials directly. The supported integration model is a real server-backed
297
- flow.
308
+ This repository's reference backend uses these routes:
298
309
 
299
- This repository includes `apps/minimal-api` as the reference local server. It
300
- wraps Japan Post API ver 2.0 and is intended for local development and
301
- integration testing. The demo's `/minimal-api` path is only a development-time
302
- route to that local server. When the upstream payload includes both structured
303
- address parts and a free-form `address` string, the reference server keeps the
304
- display address non-duplicated instead of concatenating both blindly.
310
+ - `POST /q/japanpost/searchcode`
311
+ - `POST /q/japanpost/addresszip`
305
312
 
306
- Timeout messages can differ depending on whether the token exchange timed out or
307
- the upstream lookup request timed out. Both cases still map cleanly to the
308
- `timeout` error code.
313
+ But those route names are not part of the package API. They are just the
314
+ example used by `apps/demo` and `apps/minimal-api`.
309
315
 
310
- ## SSR
316
+ ## Constraints And Notes
311
317
 
312
- Use your server-side API from the `dataSource` implementation, and keep token
313
- exchange plus upstream signing on the server. React hooks and UI components
314
- should stay in client components.
318
+ - `dataSource` is required at runtime for all hooks.
319
+ - `isValidJapanPostalCode()` checks for an exact 7-digit postal code after
320
+ normalization. `useJapanPostalCode()` is less strict and accepts `3-7`
321
+ digits for prefix lookup.
322
+ - `formatJapanPostalCode()` inserts a hyphen only when the normalized value is
323
+ exactly 7 digits.
324
+ - `cancel()` on `useJapanPostalCode()` and `useJapanAddressSearch()` aborts the
325
+ in-flight request but keeps the latest settled `data` and `error`.
326
+ - `reset()` clears both `data` and `error`.
327
+ - The package does not require a backend to return `404` for misses. Returning
328
+ `200` with an empty page is also compatible with the hook contract.
329
+ - Use your own server-side API in the `dataSource` implementation. Keep Japan
330
+ Post credentials and token exchange on the server side.
package/dist/index.d.ts CHANGED
@@ -1,11 +1 @@
1
- export { AddressSearchInput } from './components/AddressSearchInput';
2
- export { PostalCodeInput } from './components/PostalCodeInput';
3
- export { createJapanAddressError } from './core/errors';
4
- export { formatJapanPostalCode, normalizeJapanPostalCode, } from './core/formatters';
5
- export { normalizeJapanPostAddressRecord } from './core/normalizers';
6
- export type { AddressSearchInputProps, JapanAddress, JapanAddressDataSource, JapanAddressError, JapanAddressErrorCode, JapanPostAddresszipRequest, JapanPostSearchcodeRequest, JapanAddressRequestOptions, JapanAddressSearchResult, JapanPostalCodeLookupResult, NormalizedJapanAddressRecord, Page, PostalCodeInputProps, UseJapanAddressOptions, UseJapanAddressResult, UseJapanAddressSearchOptions, UseJapanAddressSearchResult, UseJapanPostalCodeOptions, UseJapanPostalCodeResult, } from './core/types';
7
- export { isValidJapanPostalCode } from './core/validators';
8
- export { useJapanAddress } from './react/useJapanAddress';
9
- export { useJapanAddressSearch } from './react/useJapanAddressSearch';
10
- export { useJapanPostalCode } from './react/useJapanPostalCode';
11
- //# sourceMappingURL=index.d.ts.map
1
+ export {}