@cp949/japanpost-react 1.0.3 → 1.0.5

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 (33) hide show
  1. package/dist/client.d.ts +318 -1
  2. package/dist/client.es.js +513 -1
  3. package/dist/index.d.ts +313 -1
  4. package/dist/index.es.js +533 -282
  5. package/package.json +5 -8
  6. package/dist/src/client.d.ts +0 -7
  7. package/dist/src/client.d.ts.map +0 -1
  8. package/dist/src/components/AddressSearchInput.d.ts +0 -8
  9. package/dist/src/components/AddressSearchInput.d.ts.map +0 -1
  10. package/dist/src/components/PostalCodeInput.d.ts +0 -8
  11. package/dist/src/components/PostalCodeInput.d.ts.map +0 -1
  12. package/dist/src/core/errors.d.ts +0 -11
  13. package/dist/src/core/errors.d.ts.map +0 -1
  14. package/dist/src/core/formatters.d.ts +0 -12
  15. package/dist/src/core/formatters.d.ts.map +0 -1
  16. package/dist/src/core/normalizers.d.ts +0 -7
  17. package/dist/src/core/normalizers.d.ts.map +0 -1
  18. package/dist/src/core/types.d.ts +0 -261
  19. package/dist/src/core/types.d.ts.map +0 -1
  20. package/dist/src/core/validators.d.ts +0 -6
  21. package/dist/src/core/validators.d.ts.map +0 -1
  22. package/dist/src/index.d.ts +0 -11
  23. package/dist/src/index.d.ts.map +0 -1
  24. package/dist/src/react/toJapanAddressError.d.ts +0 -8
  25. package/dist/src/react/toJapanAddressError.d.ts.map +0 -1
  26. package/dist/src/react/useJapanAddress.d.ts +0 -8
  27. package/dist/src/react/useJapanAddress.d.ts.map +0 -1
  28. package/dist/src/react/useJapanAddressSearch.d.ts +0 -7
  29. package/dist/src/react/useJapanAddressSearch.d.ts.map +0 -1
  30. package/dist/src/react/useJapanPostalCode.d.ts +0 -7
  31. package/dist/src/react/useJapanPostalCode.d.ts.map +0 -1
  32. package/dist/src/react/useLatestRequestState.d.ts +0 -23
  33. package/dist/src/react/useLatestRequestState.d.ts.map +0 -1
package/dist/index.es.js CHANGED
@@ -1,299 +1,550 @@
1
- import { useCallback as e, useEffect as t, useMemo as n, useRef as r, useState as i } from "react";
2
- import { jsx as a, jsxs as o } from "react/jsx-runtime";
3
- //#region src/components/AddressSearchInput.tsx
4
- function s({ defaultValue: e = "", value: t, disabled: n, label: r = "Address keyword", buttonLabel: s = "Search", inputProps: c, buttonProps: l, onChange: u, onSearch: d }) {
5
- let [f, p] = i(e), m = t ?? f;
6
- function h(e) {
7
- e.preventDefault(), d(m.trim());
8
- }
9
- function g(e) {
10
- t === void 0 && p(e), u?.(e);
11
- }
12
- return /* @__PURE__ */ o("form", {
13
- onSubmit: h,
14
- children: [/* @__PURE__ */ o("label", { children: [r, /* @__PURE__ */ a("input", {
15
- ...c,
16
- disabled: n,
17
- value: m,
18
- onChange: (e) => g(e.target.value)
19
- })] }), /* @__PURE__ */ a("button", {
20
- ...l,
21
- disabled: n,
22
- type: "submit",
23
- children: s
24
- })]
25
- });
1
+ import { forwardRef, useState, useMemo, useRef, useCallback, useEffect } from 'react';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+
4
+ // src/components/AddressSearchInput.tsx
5
+ var AddressSearchInput = forwardRef(function AddressSearchInput2({
6
+ defaultValue = "",
7
+ value,
8
+ disabled,
9
+ label = "Address keyword",
10
+ buttonLabel = "Search",
11
+ inputProps,
12
+ buttonProps,
13
+ onChange,
14
+ onSearch
15
+ }, ref) {
16
+ const [internalValue, setInternalValue] = useState(defaultValue);
17
+ const currentValue = value ?? internalValue;
18
+ function handleSubmit(event) {
19
+ event.preventDefault();
20
+ onSearch(currentValue.trim());
21
+ }
22
+ function handleChange(nextValue) {
23
+ if (value === void 0) {
24
+ setInternalValue(nextValue);
25
+ }
26
+ onChange?.(nextValue);
27
+ }
28
+ return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, children: [
29
+ /* @__PURE__ */ jsxs("label", { children: [
30
+ label,
31
+ /* @__PURE__ */ jsx(
32
+ "input",
33
+ {
34
+ ...inputProps,
35
+ ref,
36
+ disabled,
37
+ value: currentValue,
38
+ onChange: (event) => handleChange(event.target.value)
39
+ }
40
+ )
41
+ ] }),
42
+ /* @__PURE__ */ jsx("button", { ...buttonProps, disabled, type: "submit", children: buttonLabel })
43
+ ] });
44
+ });
45
+
46
+ // src/core/formatters.ts
47
+ function normalizeJapanPostalCode(value) {
48
+ return value.replace(/[^\d]/g, "");
26
49
  }
27
- //#endregion
28
- //#region src/core/formatters.ts
29
- function c(e) {
30
- return e.replace(/[^\d]/g, "");
50
+ function formatJapanPostalCode(value) {
51
+ const normalized = normalizeJapanPostalCode(value);
52
+ if (normalized.length !== 7) {
53
+ return normalized;
54
+ }
55
+ return `${normalized.slice(0, 3)}-${normalized.slice(3)}`;
31
56
  }
32
- function l(e) {
33
- let t = c(e);
34
- return t.length === 7 ? `${t.slice(0, 3)}-${t.slice(3)}` : t;
57
+ var PostalCodeInput = forwardRef(
58
+ function PostalCodeInput2({
59
+ defaultValue = "",
60
+ value,
61
+ disabled,
62
+ label = "Postal code",
63
+ buttonLabel = "Search",
64
+ inputProps,
65
+ buttonProps,
66
+ onChange,
67
+ onSearch
68
+ }, ref) {
69
+ const [internalValue, setInternalValue] = useState(defaultValue);
70
+ const currentValue = value ?? internalValue;
71
+ function handleSubmit(event) {
72
+ event.preventDefault();
73
+ onSearch(normalizeJapanPostalCode(currentValue));
74
+ }
75
+ function handleChange(nextValue) {
76
+ if (value === void 0) {
77
+ setInternalValue(nextValue);
78
+ }
79
+ onChange?.(nextValue);
80
+ }
81
+ return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, children: [
82
+ /* @__PURE__ */ jsxs("label", { children: [
83
+ label,
84
+ /* @__PURE__ */ jsx(
85
+ "input",
86
+ {
87
+ ...inputProps,
88
+ ref,
89
+ disabled,
90
+ inputMode: inputProps?.inputMode ?? "numeric",
91
+ value: currentValue,
92
+ onChange: (event) => handleChange(event.target.value)
93
+ }
94
+ )
95
+ ] }),
96
+ /* @__PURE__ */ jsx("button", { ...buttonProps, disabled, type: "submit", children: buttonLabel })
97
+ ] });
98
+ }
99
+ );
100
+
101
+ // src/core/errors.ts
102
+ function createJapanAddressError(code, message, options) {
103
+ const error = new Error(message);
104
+ error.name = "JapanAddressError";
105
+ error.code = code;
106
+ error.cause = options?.cause;
107
+ error.status = options?.status;
108
+ return error;
35
109
  }
36
- //#endregion
37
- //#region src/components/PostalCodeInput.tsx
38
- function u({ defaultValue: e = "", value: t, disabled: n, label: r = "Postal code", buttonLabel: s = "Search", inputProps: l, buttonProps: u, onChange: d, onSearch: f }) {
39
- let [p, m] = i(e), h = t ?? p;
40
- function g(e) {
41
- e.preventDefault(), f(c(h));
42
- }
43
- function _(e) {
44
- t === void 0 && m(e), d?.(e);
45
- }
46
- return /* @__PURE__ */ o("form", {
47
- onSubmit: g,
48
- children: [/* @__PURE__ */ o("label", { children: [r, /* @__PURE__ */ a("input", {
49
- ...l,
50
- disabled: n,
51
- inputMode: l?.inputMode ?? "numeric",
52
- value: h,
53
- onChange: (e) => _(e.target.value)
54
- })] }), /* @__PURE__ */ a("button", {
55
- ...u,
56
- disabled: n,
57
- type: "submit",
58
- children: s
59
- })]
60
- });
110
+
111
+ // src/core/normalizers.ts
112
+ function joinAddressParts(parts) {
113
+ return parts.filter(Boolean).join(" ").trim();
61
114
  }
62
- //#endregion
63
- //#region src/core/errors.ts
64
- function d(e, t, n) {
65
- let r = Error(t);
66
- return r.name = "JapanAddressError", r.code = e, r.cause = n?.cause, r.status = n?.status, r;
115
+ function normalizeJapanPostAddressRecord(record) {
116
+ const address = joinAddressParts([
117
+ record.prefecture,
118
+ record.city,
119
+ record.town,
120
+ record.detail ?? ""
121
+ ]);
122
+ return {
123
+ postalCode: record.postalCode,
124
+ prefecture: record.prefecture,
125
+ prefectureKana: record.prefectureKana,
126
+ city: record.city,
127
+ cityKana: record.cityKana,
128
+ town: record.town,
129
+ townKana: record.townKana,
130
+ address,
131
+ provider: "japan-post"
132
+ };
67
133
  }
68
- //#endregion
69
- //#region src/core/normalizers.ts
70
- function f(e) {
71
- return e.filter(Boolean).join(" ").trim();
134
+
135
+ // src/core/validators.ts
136
+ function isValidJapanPostalCode(value) {
137
+ return /^\d{7}$/.test(normalizeJapanPostalCode(value));
72
138
  }
73
- function p(e) {
74
- let t = f([
75
- e.prefecture,
76
- e.city,
77
- e.town,
78
- e.detail ?? ""
79
- ]);
80
- return {
81
- postalCode: e.postalCode,
82
- prefecture: e.prefecture,
83
- prefectureKana: e.prefectureKana,
84
- city: e.city,
85
- cityKana: e.cityKana,
86
- town: e.town,
87
- townKana: e.townKana,
88
- address: t,
89
- provider: "japan-post"
90
- };
139
+
140
+ // src/react/toJapanAddressError.ts
141
+ var JAPAN_ADDRESS_ERROR_CODES = /* @__PURE__ */ new Set([
142
+ "invalid_postal_code",
143
+ "invalid_query",
144
+ "network_error",
145
+ "timeout",
146
+ "not_found",
147
+ "bad_response",
148
+ "data_source_error"
149
+ ]);
150
+ function isJapanAddressError(error) {
151
+ if (typeof error !== "object" || error === null) {
152
+ return false;
153
+ }
154
+ const maybeError = error;
155
+ return maybeError.name === "JapanAddressError" && typeof maybeError.code === "string" && JAPAN_ADDRESS_ERROR_CODES.has(maybeError.code);
91
156
  }
92
- //#endregion
93
- //#region src/core/validators.ts
94
- function m(e) {
95
- return /^\d{7}$/.test(c(e));
157
+ function toJapanAddressError(error) {
158
+ if (isJapanAddressError(error)) {
159
+ return error;
160
+ }
161
+ return createJapanAddressError(
162
+ "data_source_error",
163
+ error instanceof Error ? error.message : "Unknown error",
164
+ {
165
+ cause: error
166
+ }
167
+ );
96
168
  }
97
- //#endregion
98
- //#region src/react/toJapanAddressError.ts
99
- function h(e) {
100
- return typeof e == "object" && e && "code" in e && typeof e.code == "string" ? e : d("data_source_error", e instanceof Error ? e.message : "Unknown error", { cause: e });
169
+ function useLatestRequestState() {
170
+ const requestIdRef = useRef(0);
171
+ const isMountedRef = useRef(true);
172
+ const abortControllerRef = useRef(null);
173
+ const [loading, setLoading] = useState(false);
174
+ const [data, setData] = useState(null);
175
+ const [error, setError] = useState(null);
176
+ const isCurrentRequest = useCallback((requestId) => {
177
+ return isMountedRef.current && requestId === requestIdRef.current;
178
+ }, []);
179
+ const invalidateCurrentRequest = useCallback(() => {
180
+ requestIdRef.current += 1;
181
+ abortControllerRef.current?.abort();
182
+ abortControllerRef.current = null;
183
+ }, []);
184
+ useEffect(() => {
185
+ isMountedRef.current = true;
186
+ return () => {
187
+ isMountedRef.current = false;
188
+ invalidateCurrentRequest();
189
+ };
190
+ }, [invalidateCurrentRequest]);
191
+ const beginRequest = useCallback(() => {
192
+ const requestId = requestIdRef.current + 1;
193
+ requestIdRef.current = requestId;
194
+ abortControllerRef.current?.abort();
195
+ const abortController = new AbortController();
196
+ abortControllerRef.current = abortController;
197
+ setLoading(true);
198
+ setError(null);
199
+ return {
200
+ requestId,
201
+ signal: abortController.signal
202
+ };
203
+ }, []);
204
+ const setSuccess = useCallback((requestId, result) => {
205
+ if (isCurrentRequest(requestId)) {
206
+ setData(result);
207
+ }
208
+ }, [isCurrentRequest]);
209
+ const setFailure = useCallback(
210
+ (requestId, nextError) => {
211
+ if (isCurrentRequest(requestId)) {
212
+ setError(nextError);
213
+ setData(null);
214
+ }
215
+ return null;
216
+ },
217
+ [isCurrentRequest]
218
+ );
219
+ const cancel = useCallback(() => {
220
+ invalidateCurrentRequest();
221
+ setLoading(false);
222
+ }, [invalidateCurrentRequest]);
223
+ const finishRequest = useCallback((requestId) => {
224
+ if (isCurrentRequest(requestId)) {
225
+ setLoading(false);
226
+ abortControllerRef.current = null;
227
+ }
228
+ }, [isCurrentRequest]);
229
+ const reset = useCallback(() => {
230
+ cancel();
231
+ setData(null);
232
+ setError(null);
233
+ }, [cancel]);
234
+ return {
235
+ loading,
236
+ data,
237
+ error,
238
+ beginRequest,
239
+ setSuccess,
240
+ setFailure,
241
+ finishRequest,
242
+ cancel,
243
+ reset
244
+ };
101
245
  }
102
- //#endregion
103
- //#region src/react/useLatestRequestState.ts
104
- function g() {
105
- let n = r(0), a = r(!0), o = r(null), [s, c] = i(!1), [l, u] = i(null), [d, f] = i(null), p = e((e) => a.current && e === n.current, []), m = e(() => {
106
- n.current += 1, o.current?.abort(), o.current = null;
107
- }, []);
108
- t(() => (a.current = !0, () => {
109
- a.current = !1, m();
110
- }), [m]);
111
- let h = e(() => {
112
- let e = n.current + 1;
113
- n.current = e, o.current?.abort();
114
- let t = new AbortController();
115
- return o.current = t, c(!0), f(null), {
116
- requestId: e,
117
- signal: t.signal
118
- };
119
- }, []), g = e((e, t) => {
120
- p(e) && u(t);
121
- }, [p]), _ = e((e, t) => (p(e) && (f(t), u(null)), null), [p]), v = e(() => {
122
- m(), c(!1);
123
- }, [m]);
124
- return {
125
- loading: s,
126
- data: l,
127
- error: d,
128
- beginRequest: h,
129
- setSuccess: g,
130
- setFailure: _,
131
- finishRequest: e((e) => {
132
- p(e) && (c(!1), o.current = null);
133
- }, [p]),
134
- cancel: v,
135
- reset: e(() => {
136
- v(), u(null), f(null);
137
- }, [v])
138
- };
246
+
247
+ // src/react/useJapanAddressSearch.ts
248
+ function resolveAddressSearchDataSource(dataSource) {
249
+ if (dataSource) {
250
+ return dataSource;
251
+ }
252
+ throw new Error("useJapanAddressSearch requires options.dataSource");
139
253
  }
140
- //#endregion
141
- //#region src/react/useJapanAddressSearch.ts
142
- function _(e) {
143
- if (e) return e;
144
- throw Error("useJapanAddressSearch requires options.dataSource");
254
+ function normalizeSearchText(value) {
255
+ const normalized = value?.trim();
256
+ return normalized ? normalized : void 0;
145
257
  }
146
- function v(e) {
147
- return e?.trim() || void 0;
258
+ function normalizeAddressSearchInput(input) {
259
+ const request = typeof input === "string" ? { addressQuery: input } : input;
260
+ const addressQuery = normalizeSearchText(request.addressQuery);
261
+ const prefCode = normalizeSearchText(request.prefCode);
262
+ const prefName = normalizeSearchText(request.prefName);
263
+ const prefKana = normalizeSearchText(request.prefKana);
264
+ const prefRoma = normalizeSearchText(request.prefRoma);
265
+ const cityCode = normalizeSearchText(request.cityCode);
266
+ const cityName = normalizeSearchText(request.cityName);
267
+ const cityKana = normalizeSearchText(request.cityKana);
268
+ const cityRoma = normalizeSearchText(request.cityRoma);
269
+ const townName = normalizeSearchText(request.townName);
270
+ const townKana = normalizeSearchText(request.townKana);
271
+ const townRoma = normalizeSearchText(request.townRoma);
272
+ if (addressQuery === void 0 && prefCode === void 0 && prefName === void 0 && prefKana === void 0 && prefRoma === void 0 && cityCode === void 0 && cityName === void 0 && cityKana === void 0 && cityRoma === void 0 && townName === void 0 && townKana === void 0 && townRoma === void 0) {
273
+ return null;
274
+ }
275
+ return {
276
+ ...addressQuery === void 0 ? {} : { addressQuery },
277
+ ...prefCode === void 0 ? {} : { prefCode },
278
+ ...prefName === void 0 ? {} : { prefName },
279
+ ...prefKana === void 0 ? {} : { prefKana },
280
+ ...prefRoma === void 0 ? {} : { prefRoma },
281
+ ...cityCode === void 0 ? {} : { cityCode },
282
+ ...cityName === void 0 ? {} : { cityName },
283
+ ...cityKana === void 0 ? {} : { cityKana },
284
+ ...cityRoma === void 0 ? {} : { cityRoma },
285
+ ...townName === void 0 ? {} : { townName },
286
+ ...townKana === void 0 ? {} : { townKana },
287
+ ...townRoma === void 0 ? {} : { townRoma },
288
+ pageNumber: request.pageNumber ?? 0,
289
+ rowsPerPage: request.rowsPerPage ?? 100,
290
+ ...request.includeCityDetails === void 0 ? {} : {
291
+ includeCityDetails: request.includeCityDetails
292
+ },
293
+ ...request.includePrefectureDetails === void 0 ? {} : {
294
+ includePrefectureDetails: request.includePrefectureDetails
295
+ }
296
+ };
148
297
  }
149
- function y(e) {
150
- let t = typeof e == "string" ? { addressQuery: e } : e, n = v(t.addressQuery), r = v(t.prefCode), i = v(t.prefName), a = v(t.prefKana), o = v(t.prefRoma), s = v(t.cityCode), c = v(t.cityName), l = v(t.cityKana), u = v(t.cityRoma), d = v(t.townName), f = v(t.townKana), p = v(t.townRoma);
151
- return n === void 0 && r === void 0 && i === void 0 && a === void 0 && o === void 0 && s === void 0 && c === void 0 && l === void 0 && u === void 0 && d === void 0 && f === void 0 && p === void 0 ? null : {
152
- ...n === void 0 ? {} : { addressQuery: n },
153
- ...r === void 0 ? {} : { prefCode: r },
154
- ...i === void 0 ? {} : { prefName: i },
155
- ...a === void 0 ? {} : { prefKana: a },
156
- ...o === void 0 ? {} : { prefRoma: o },
157
- ...s === void 0 ? {} : { cityCode: s },
158
- ...c === void 0 ? {} : { cityName: c },
159
- ...l === void 0 ? {} : { cityKana: l },
160
- ...u === void 0 ? {} : { cityRoma: u },
161
- ...d === void 0 ? {} : { townName: d },
162
- ...f === void 0 ? {} : { townKana: f },
163
- ...p === void 0 ? {} : { townRoma: p },
164
- pageNumber: t.pageNumber ?? 0,
165
- rowsPerPage: t.rowsPerPage ?? 100,
166
- ...t.includeCityDetails === void 0 ? {} : { includeCityDetails: t.includeCityDetails },
167
- ...t.includePrefectureDetails === void 0 ? {} : { includePrefectureDetails: t.includePrefectureDetails }
168
- };
298
+ function waitForAbort(signal) {
299
+ if (signal.aborted) {
300
+ return Promise.resolve(null);
301
+ }
302
+ return new Promise((resolve) => {
303
+ const onAbort = () => {
304
+ signal.removeEventListener("abort", onAbort);
305
+ resolve(null);
306
+ };
307
+ signal.addEventListener("abort", onAbort, { once: true });
308
+ });
169
309
  }
170
- function b(e) {
171
- return e.aborted ? Promise.resolve(null) : new Promise((t) => {
172
- let n = () => {
173
- e.removeEventListener("abort", n), t(null);
174
- };
175
- e.addEventListener("abort", n, { once: !0 });
176
- });
310
+ function useJapanAddressSearch(options) {
311
+ const dataSource = useMemo(
312
+ () => resolveAddressSearchDataSource(options.dataSource),
313
+ [options.dataSource]
314
+ );
315
+ const debounceMs = options.debounceMs ?? 0;
316
+ const timeoutRef = useRef(
317
+ null
318
+ );
319
+ const pendingResolveRef = useRef(null);
320
+ const {
321
+ loading,
322
+ data,
323
+ error,
324
+ beginRequest,
325
+ setSuccess,
326
+ setFailure,
327
+ finishRequest,
328
+ cancel: cancelRequestState,
329
+ reset: resetRequestState
330
+ } = useLatestRequestState();
331
+ const clearPendingDebounce = useCallback(
332
+ (result) => {
333
+ if (timeoutRef.current !== null) {
334
+ globalThis.clearTimeout(timeoutRef.current);
335
+ timeoutRef.current = null;
336
+ }
337
+ pendingResolveRef.current?.(result);
338
+ pendingResolveRef.current = null;
339
+ },
340
+ []
341
+ );
342
+ useEffect(() => {
343
+ return () => {
344
+ clearPendingDebounce(null);
345
+ };
346
+ }, [clearPendingDebounce]);
347
+ const reset = useCallback(() => {
348
+ clearPendingDebounce(null);
349
+ resetRequestState();
350
+ }, [clearPendingDebounce, resetRequestState]);
351
+ const cancel = useCallback(() => {
352
+ clearPendingDebounce(null);
353
+ cancelRequestState();
354
+ }, [clearPendingDebounce, cancelRequestState]);
355
+ const runSearch = useCallback(async (requestId, signal, request) => {
356
+ try {
357
+ const requestOptions = {
358
+ signal
359
+ };
360
+ const searchPromise = dataSource.searchAddress(
361
+ request,
362
+ requestOptions
363
+ );
364
+ const result = await Promise.race([
365
+ searchPromise,
366
+ waitForAbort(signal)
367
+ ]);
368
+ if (signal.aborted || result === null) {
369
+ return null;
370
+ }
371
+ setSuccess(requestId, result);
372
+ return result;
373
+ } catch (caughtError) {
374
+ if (signal.aborted) {
375
+ return null;
376
+ }
377
+ return setFailure(requestId, toJapanAddressError(caughtError));
378
+ } finally {
379
+ finishRequest(requestId);
380
+ }
381
+ }, [dataSource, finishRequest, setFailure, setSuccess]);
382
+ const search = useCallback((input) => {
383
+ const request = normalizeAddressSearchInput(input);
384
+ const { requestId, signal } = beginRequest();
385
+ clearPendingDebounce(null);
386
+ if (request === null) {
387
+ const result = setFailure(
388
+ requestId,
389
+ createJapanAddressError(
390
+ "invalid_query",
391
+ "Address query is required"
392
+ )
393
+ );
394
+ finishRequest(requestId);
395
+ return Promise.resolve(result);
396
+ }
397
+ if (debounceMs <= 0) {
398
+ return new Promise((resolve) => {
399
+ pendingResolveRef.current = resolve;
400
+ void runSearch(requestId, signal, request).then((result) => {
401
+ resolve(result);
402
+ if (pendingResolveRef.current === resolve) {
403
+ pendingResolveRef.current = null;
404
+ }
405
+ });
406
+ });
407
+ }
408
+ return new Promise((resolve) => {
409
+ pendingResolveRef.current = resolve;
410
+ timeoutRef.current = globalThis.setTimeout(() => {
411
+ timeoutRef.current = null;
412
+ const pendingResolve = pendingResolveRef.current;
413
+ pendingResolveRef.current = null;
414
+ void runSearch(requestId, signal, request).then((result) => {
415
+ pendingResolve?.(result);
416
+ });
417
+ }, debounceMs);
418
+ });
419
+ }, [beginRequest, clearPendingDebounce, debounceMs, runSearch]);
420
+ return {
421
+ loading,
422
+ data,
423
+ error,
424
+ cancel,
425
+ reset,
426
+ search
427
+ };
177
428
  }
178
- function x(i) {
179
- let a = n(() => _(i.dataSource), [i.dataSource]), o = i.debounceMs ?? 0, s = r(null), c = r(null), { loading: l, data: u, error: f, beginRequest: p, setSuccess: m, setFailure: v, finishRequest: x, cancel: S, reset: C } = g(), w = e((e) => {
180
- s.current !== null && (globalThis.clearTimeout(s.current), s.current = null), c.current?.(e), c.current = null;
181
- }, []);
182
- t(() => () => {
183
- w(null);
184
- }, [w]);
185
- let T = e(() => {
186
- w(null), C();
187
- }, [w, C]), E = e(() => {
188
- w(null), S();
189
- }, [w, S]), D = e(async (e, t, n) => {
190
- try {
191
- let r = { signal: t }, i = a.searchAddress(n, r), o = await Promise.race([i, b(t)]);
192
- return t.aborted || o === null ? null : (m(e, o), o);
193
- } catch (n) {
194
- return t.aborted ? null : v(e, h(n));
195
- } finally {
196
- x(e);
197
- }
198
- }, [
199
- a,
200
- x,
201
- v,
202
- m
203
- ]);
204
- return {
205
- loading: l,
206
- data: u,
207
- error: f,
208
- cancel: E,
209
- reset: T,
210
- search: e((e) => {
211
- let t = y(e), { requestId: n, signal: r } = p();
212
- if (w(null), t === null) {
213
- let e = v(n, d("invalid_query", "Address query is required"));
214
- return x(n), Promise.resolve(e);
215
- }
216
- return o <= 0 ? new Promise((e) => {
217
- c.current = e, D(n, r, t).then((t) => {
218
- e(t), c.current === e && (c.current = null);
219
- });
220
- }) : new Promise((e) => {
221
- c.current = e, s.current = globalThis.setTimeout(() => {
222
- s.current = null;
223
- let e = c.current;
224
- c.current = null, D(n, r, t).then((t) => {
225
- e?.(t);
226
- });
227
- }, o);
228
- });
229
- }, [
230
- p,
231
- w,
232
- o,
233
- D
234
- ])
235
- };
429
+ function resolvePostalCodeDataSource(dataSource) {
430
+ if (dataSource) {
431
+ return dataSource;
432
+ }
433
+ throw new Error("useJapanPostalCode requires options.dataSource");
236
434
  }
237
- //#endregion
238
- //#region src/react/useJapanPostalCode.ts
239
- function S(e) {
240
- if (e) return e;
241
- throw Error("useJapanPostalCode requires options.dataSource");
435
+ function useJapanPostalCode(options) {
436
+ const dataSource = useMemo(
437
+ () => resolvePostalCodeDataSource(options.dataSource),
438
+ [options.dataSource]
439
+ );
440
+ const {
441
+ loading,
442
+ data,
443
+ error,
444
+ beginRequest,
445
+ setSuccess,
446
+ setFailure,
447
+ finishRequest,
448
+ cancel,
449
+ reset
450
+ } = useLatestRequestState();
451
+ const search = useCallback(
452
+ async (input) => {
453
+ const { requestId, signal } = beginRequest();
454
+ try {
455
+ const requestInput = typeof input === "string" ? { postalCode: input } : input;
456
+ const postalCode = normalizeJapanPostalCode(requestInput.postalCode);
457
+ if (!/^\d{3,7}$/.test(postalCode)) {
458
+ throw createJapanAddressError(
459
+ "invalid_postal_code",
460
+ "Postal code must contain between 3 and 7 digits"
461
+ );
462
+ }
463
+ const requestOptions = {
464
+ signal
465
+ };
466
+ const request = {
467
+ postalCode,
468
+ pageNumber: requestInput.pageNumber ?? 0,
469
+ rowsPerPage: requestInput.rowsPerPage ?? 100,
470
+ ...requestInput.includeParenthesesTown === void 0 ? {} : {
471
+ includeParenthesesTown: requestInput.includeParenthesesTown
472
+ }
473
+ };
474
+ const result = await dataSource.lookupPostalCode(
475
+ request,
476
+ requestOptions
477
+ );
478
+ if (signal.aborted) {
479
+ return null;
480
+ }
481
+ setSuccess(requestId, result);
482
+ return result;
483
+ } catch (caughtError) {
484
+ if (signal.aborted) {
485
+ return null;
486
+ }
487
+ return setFailure(requestId, toJapanAddressError(caughtError));
488
+ } finally {
489
+ finishRequest(requestId);
490
+ }
491
+ },
492
+ [beginRequest, dataSource, finishRequest, setFailure, setSuccess]
493
+ );
494
+ return {
495
+ loading,
496
+ data,
497
+ error,
498
+ cancel,
499
+ reset,
500
+ search
501
+ };
242
502
  }
243
- function C(t) {
244
- let r = n(() => S(t.dataSource), [t.dataSource]), { loading: i, data: a, error: o, beginRequest: s, setSuccess: l, setFailure: u, finishRequest: f, cancel: p, reset: m } = g();
245
- return {
246
- loading: i,
247
- data: a,
248
- error: o,
249
- cancel: p,
250
- reset: m,
251
- search: e(async (e) => {
252
- let { requestId: t, signal: n } = s();
253
- try {
254
- let i = typeof e == "string" ? { postalCode: e } : e, a = c(i.postalCode);
255
- if (!/^\d{3,7}$/.test(a)) throw d("invalid_postal_code", "Postal code must contain between 3 and 7 digits");
256
- let o = { signal: n }, s = {
257
- postalCode: a,
258
- pageNumber: i.pageNumber ?? 0,
259
- rowsPerPage: i.rowsPerPage ?? 100,
260
- ...i.includeParenthesesTown === void 0 ? {} : { includeParenthesesTown: i.includeParenthesesTown }
261
- }, u = await r.lookupPostalCode(s, o);
262
- return n.aborted ? null : (l(t, u), u);
263
- } catch (e) {
264
- return n.aborted ? null : u(t, h(e));
265
- } finally {
266
- f(t);
267
- }
268
- }, [
269
- s,
270
- r,
271
- f,
272
- u,
273
- l
274
- ])
275
- };
503
+
504
+ // src/react/useJapanAddress.ts
505
+ function useJapanAddress(options) {
506
+ const dataSource = useMemo(() => {
507
+ if (options.dataSource) {
508
+ return options.dataSource;
509
+ }
510
+ throw new Error("useJapanAddress requires options.dataSource");
511
+ }, [options.dataSource]);
512
+ const postalCode = useJapanPostalCode({ dataSource });
513
+ const addressSearch = useJapanAddressSearch({
514
+ dataSource,
515
+ debounceMs: options.debounceMs
516
+ });
517
+ const resetPostalCode = postalCode.reset;
518
+ const searchPostalCode = postalCode.search;
519
+ const resetAddressSearch = addressSearch.reset;
520
+ const searchAddressKeyword = addressSearch.search;
521
+ const [activeSearch, setActiveSearch] = useState(null);
522
+ const searchByPostalCode = useCallback(async (input) => {
523
+ resetAddressSearch();
524
+ setActiveSearch("postalCode");
525
+ return searchPostalCode(input);
526
+ }, [resetAddressSearch, searchPostalCode]);
527
+ const searchByAddressQuery = useCallback(async (input) => {
528
+ resetPostalCode();
529
+ setActiveSearch("addressQuery");
530
+ return searchAddressKeyword(input);
531
+ }, [resetPostalCode, searchAddressKeyword]);
532
+ const reset = useCallback(() => {
533
+ resetPostalCode();
534
+ resetAddressSearch();
535
+ setActiveSearch(null);
536
+ }, [resetAddressSearch, resetPostalCode]);
537
+ const data = activeSearch === "postalCode" ? postalCode.data : activeSearch === "addressQuery" ? addressSearch.data : null;
538
+ const error = activeSearch === "postalCode" ? postalCode.error : activeSearch === "addressQuery" ? addressSearch.error : null;
539
+ return {
540
+ // 사용자는 현재 활성 모드만 보더라도, 내부에서는 두 훅 중 하나라도 정리 중이면 로딩으로 본다.
541
+ loading: postalCode.loading || addressSearch.loading,
542
+ data,
543
+ error,
544
+ reset,
545
+ searchByPostalCode,
546
+ searchByAddressQuery
547
+ };
276
548
  }
277
- //#endregion
278
- //#region src/react/useJapanAddress.ts
279
- function w(t) {
280
- let r = n(() => {
281
- if (t.dataSource) return t.dataSource;
282
- throw Error("useJapanAddress requires options.dataSource");
283
- }, [t.dataSource]), a = C({ dataSource: r }), o = x({
284
- dataSource: r,
285
- debounceMs: t.debounceMs
286
- }), s = a.reset, c = a.search, l = o.reset, u = o.search, [d, f] = i(null), p = e(async (e) => (l(), f("postalCode"), c(e)), [l, c]), m = e(async (e) => (s(), f("addressQuery"), u(e)), [s, u]), h = e(() => {
287
- s(), l(), f(null);
288
- }, [l, s]), g = d === "postalCode" ? a.data : d === "addressQuery" ? o.data : null, _ = d === "postalCode" ? a.error : d === "addressQuery" ? o.error : null;
289
- return {
290
- loading: a.loading || o.loading,
291
- data: g,
292
- error: _,
293
- reset: h,
294
- searchByPostalCode: p,
295
- searchByAddressQuery: m
296
- };
297
- }
298
- //#endregion
299
- export { s as AddressSearchInput, u as PostalCodeInput, d as createJapanAddressError, l as formatJapanPostalCode, m as isValidJapanPostalCode, p as normalizeJapanPostAddressRecord, c as normalizeJapanPostalCode, w as useJapanAddress, x as useJapanAddressSearch, C as useJapanPostalCode };
549
+
550
+ export { AddressSearchInput, PostalCodeInput, createJapanAddressError, formatJapanPostalCode, isValidJapanPostalCode, normalizeJapanPostAddressRecord, normalizeJapanPostalCode, useJapanAddress, useJapanAddressSearch, useJapanPostalCode };