@m1kapp/kit 0.0.2 → 0.0.4

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/dist/index.d.mts CHANGED
@@ -438,8 +438,14 @@ interface UseFormSubmitOptions<T> {
438
438
  onSuccess?: (data: T) => void;
439
439
  onError?: (err: Error) => void;
440
440
  }
441
+ /**
442
+ * Submit function type:
443
+ * - When V is `void` (fn takes no arguments) → `submit()` needs no args
444
+ * - Otherwise → `submit(vars: V)` requires the arg
445
+ */
446
+ type SubmitFn<V> = V extends void ? () => Promise<void> : (vars: V) => Promise<void>;
441
447
  interface UseFormSubmitResult<T, V> {
442
- submit: (vars: V) => Promise<void>;
448
+ submit: SubmitFn<V>;
443
449
  loading: boolean;
444
450
  error: Error | null;
445
451
  data: T | undefined;
@@ -450,16 +456,21 @@ interface UseFormSubmitResult<T, V> {
450
456
  * Eliminates the try/catch/finally + setState boilerplate from every form.
451
457
  *
452
458
  * @example
459
+ * // With args
453
460
  * const { submit, loading, error } = useFormSubmit(async (url: string) => {
454
461
  * return api.post<Site>("/api/sites", { url });
455
462
  * }, {
456
- * onSuccess: () => router.push("/dashboard"),
463
+ * onSuccess: (site) => router.push(`/${site.slug}`),
457
464
  * });
465
+ * <button onClick={() => submit(inputValue)}>등록</button>
458
466
  *
459
- * <form onSubmit={e => { e.preventDefault(); submit(input); }}>
460
- * <Button loading={loading}>등록</Button>
461
- * {error && <p>{error.message}</p>}
462
- * </form>
467
+ * @example
468
+ * // No args (fire-and-forget style)
469
+ * const { submit: save, loading } = useFormSubmit(
470
+ * () => api.put("/api/sites/settings", config),
471
+ * { onSuccess: () => setSaved(true) }
472
+ * );
473
+ * <button onClick={save}>저장</button>
463
474
  */
464
475
  declare function useFormSubmit<T, V = void>(fn: (vars: V) => Promise<T>, options?: UseFormSubmitOptions<T>): UseFormSubmitResult<T, V>;
465
476
 
@@ -542,178 +553,6 @@ interface DialogProps {
542
553
  */
543
554
  declare function Dialog({ open, onClose, title, size, children, persistent, className, }: DialogProps): React__default.ReactPortal | null;
544
555
 
545
- interface OGConfig {
546
- /** 앱 이름 (좌상단 로고) */
547
- appName?: string;
548
- /** 브랜드 색상 (hex) */
549
- color?: string;
550
- /** 하단 도메인 */
551
- domain?: string;
552
- /** 배경 스타일 — "dark"(기본) | "gradient"(다크 오로라) | "blend"(라이트 파스텔 블렌드) */
553
- bg?: "dark" | "gradient" | "blend";
554
- /** 로고 이미지 URL (지정 시 appName 첫 글자 대신 이미지 표시) */
555
- logoUrl?: string;
556
- }
557
- interface OGDefaultTemplate {
558
- type?: "default";
559
- title: string;
560
- sub?: string;
561
- badge?: string;
562
- }
563
- interface OGMatchTemplate {
564
- type: "match";
565
- home: string;
566
- away: string;
567
- score?: string;
568
- sub?: string;
569
- badge?: string;
570
- }
571
- interface OGSquareTemplate {
572
- type: "square";
573
- title: string;
574
- sub?: string;
575
- badge?: string;
576
- }
577
- interface OGIconTemplate {
578
- type: "icon";
579
- /** 아이콘에 표시할 글자 (기본: appName 첫 글자) */
580
- letter?: string;
581
- /** 아이콘 모서리 둥글기 (기본: 96) */
582
- radius?: number;
583
- }
584
- interface OGArticleTemplate {
585
- type: "article";
586
- title: string;
587
- /** 작성자 */
588
- author?: string;
589
- /** 날짜 또는 발행일 */
590
- date?: string;
591
- /** 카테고리 / 태그 */
592
- category?: string;
593
- sub?: string;
594
- }
595
- interface OGStatTemplate {
596
- type: "stat";
597
- /** 강조할 숫자 또는 지표 */
598
- stat: string;
599
- /** 지표 설명 */
600
- label: string;
601
- sub?: string;
602
- badge?: string;
603
- }
604
- interface OGProductTemplate {
605
- type: "product";
606
- title: string;
607
- tagline?: string;
608
- /** 핵심 특징 (최대 3개) */
609
- features?: string[];
610
- badge?: string;
611
- }
612
- type OGTemplate = OGDefaultTemplate | OGMatchTemplate | OGSquareTemplate | OGIconTemplate | OGArticleTemplate | OGStatTemplate | OGProductTemplate;
613
- type OGProps = OGTemplate & OGConfig;
614
- declare function OGImage(props: OGProps): react_jsx_runtime.JSX.Element;
615
-
616
- /** @vercel/og (Satori) fonts 옵션에 넘길 수 있는 폰트 정의 */
617
- interface OGFont {
618
- name: string;
619
- data: ArrayBuffer;
620
- weight: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
621
- style?: "normal" | "italic";
622
- }
623
- declare const PRETENDARD_WEIGHTS: {
624
- readonly 400: "Pretendard-Regular.otf";
625
- readonly 700: "Pretendard-Bold.otf";
626
- readonly 900: "Pretendard-Black.otf";
627
- };
628
- type PretendardWeight = keyof typeof PRETENDARD_WEIGHTS;
629
- /**
630
- * Pretendard 폰트를 CDN에서 로드합니다.
631
- *
632
- * @example
633
- * ```ts
634
- * import { OG, loadPretendard } from "@m1kapp/seo";
635
- * import { ImageResponse } from "next/og";
636
- *
637
- * export async function GET() {
638
- * const fonts = await loadPretendard();
639
- * return new ImageResponse(<OG type="default" title="Hello" />, {
640
- * width: 1200, height: 630, fonts,
641
- * });
642
- * }
643
- * ```
644
- *
645
- * @param weights 로드할 weight 배열 (기본: [700, 900])
646
- */
647
- declare function loadPretendard(weights?: PretendardWeight[]): Promise<OGFont[]>;
648
- /**
649
- * 임의의 URL에서 폰트를 로드합니다.
650
- *
651
- * @example
652
- * ```ts
653
- * const fonts = await loadFont({
654
- * name: "CustomFont",
655
- * url: "https://example.com/CustomFont-Bold.otf",
656
- * weight: 700,
657
- * });
658
- * ```
659
- */
660
- declare function loadFont(opts: {
661
- name: string;
662
- url: string;
663
- weight?: OGFont["weight"];
664
- style?: OGFont["style"];
665
- }): Promise<OGFont>;
666
- /**
667
- * Google Fonts에서 폰트를 로드합니다.
668
- * CSS 응답을 파싱해 TTF/OTF URL을 자동 추출합니다.
669
- *
670
- * @example
671
- * ```ts
672
- * const fonts = await loadGoogleFont("Noto Sans KR", [400, 700]);
673
- * return new ImageResponse(<OG ... />, { fonts });
674
- * ```
675
- */
676
- declare function loadGoogleFont(family: string, weights?: OGFont["weight"][]): Promise<OGFont[]>;
677
-
678
- /**
679
- * @vercel/og ImageResponse에서 지원하는 이모지 스타일.
680
- * ImageResponse 옵션의 `emoji` 필드에 넘기면 됩니다.
681
- */
682
- type EmojiStyle = "twemoji" | "openmoji" | "noto" | "fluent" | "fluentFlat" | "blobmoji";
683
- /**
684
- * raw Satori의 `loadAdditionalAsset` 콜백을 생성합니다.
685
- *
686
- * @example
687
- * ```ts
688
- * import satori from "satori";
689
- * import { createEmojiLoader } from "@m1kapp/seo";
690
- *
691
- * const svg = await satori(<OG ... />, {
692
- * width: 1200, height: 630,
693
- * fonts: [...],
694
- * loadAdditionalAsset: createEmojiLoader(),
695
- * });
696
- * ```
697
- */
698
- declare function createEmojiLoader(): (code: string, segment: string) => Promise<string | undefined>;
699
-
700
- /**
701
- * OG 이미지 URL의 캐시를 즉시 무효화할 버전 키를 반환합니다.
702
- *
703
- * Cache-Control로 CDN에 캐싱된 OG 이미지를 강제 갱신하고 싶을 때
704
- * URL의 `v` 파라미터에 이 값을 넣으세요.
705
- *
706
- * @example
707
- * ```ts
708
- * import { getOGVersion } from "@m1kapp/seo";
709
- *
710
- * // OG 갱신 버튼 핸들러
711
- * const freshUrl = `/og?title=${title}&v=${getOGVersion()}`;
712
- * // → /og?title=...&v=20260415152347
713
- * ```
714
- */
715
- declare function getOGVersion(): string;
716
-
717
556
  type PWAInstallState = "android-ready" | "ios-safari" | "installed" | "unsupported";
718
557
  interface UsePWAInstallReturn {
719
558
  state: PWAInstallState;
@@ -816,7 +655,9 @@ declare class ApiError extends Error {
816
655
  readonly status: number;
817
656
  readonly statusText: string;
818
657
  readonly body: unknown;
819
- constructor(status: number, statusText: string, body: unknown);
658
+ readonly url?: string | undefined;
659
+ readonly method?: string | undefined;
660
+ constructor(status: number, statusText: string, body: unknown, url?: string | undefined, method?: string | undefined);
820
661
  }
821
662
 
822
663
  interface ApiClientOptions {
@@ -824,7 +665,7 @@ interface ApiClientOptions {
824
665
  headers?: Record<string, string>;
825
666
  /** Called before every request — mutate or replace the Request */
826
667
  onRequest?: (req: Request) => Request | void;
827
- /** Called on every non-2xx response */
668
+ /** Called on every non-2xx response — includes url and method for debugging */
828
669
  onError?: (err: ApiError) => void;
829
670
  }
830
671
  type RequestOptions = {
@@ -844,6 +685,7 @@ type ApiClient = ReturnType<typeof createApiClient>;
844
685
 
845
686
  /** Manually invalidate cache entries. Pass a URL to clear one, or nothing to clear all. */
846
687
  declare function clearFetchCache(url?: string): void;
688
+ type FetchStatus = "idle" | "loading" | "success" | "error";
847
689
  interface UseFetchOptions<T> {
848
690
  /** Skip fetching when false (default: true) */
849
691
  enabled?: boolean;
@@ -864,6 +706,14 @@ interface UseFetchResult<T> {
864
706
  data: T | undefined;
865
707
  loading: boolean;
866
708
  error: Error | undefined;
709
+ /**
710
+ * Lifecycle status:
711
+ * - `"idle"` — url is null/undefined or `enabled: false`
712
+ * - `"loading"` — fetch in progress
713
+ * - `"success"` — data loaded (note: data may be null if the API returned null)
714
+ * - `"error"` — last fetch failed
715
+ */
716
+ status: FetchStatus;
867
717
  /** Manually trigger a refetch (ignores cache) */
868
718
  refetch: () => void;
869
719
  }
@@ -887,6 +737,8 @@ interface UsePollingResult<T> {
887
737
  isRunning: boolean;
888
738
  stop: () => void;
889
739
  start: () => void;
740
+ /** Immediately trigger a fetch without waiting for the next interval */
741
+ refetch: () => void;
890
742
  }
891
743
  declare function usePolling<T>(fetcher: () => Promise<T>, options?: UsePollingOptions<T>): UsePollingResult<T>;
892
744
 
@@ -895,4 +747,4 @@ declare function formatNumber(n: number): string;
895
747
  declare function formatPrice(amount: number, currency?: string, locale?: string): string;
896
748
  declare function cn(...classes: (string | undefined | null | false | 0)[]): string;
897
749
 
898
- export { type ApiClient, type ApiClientOptions, ApiError, AppShell, AppShellContent, type AppShellContentProps, AppShellHeader, type AppShellHeaderProps, type AppShellProps, Avatar, type AvatarProps, Badge, type BadgeProps, Button, type ButtonProps, type ColorName, Dialog, type DialogProps, Divider, EmojiButton, type EmojiButtonProps, EmojiPicker, type EmojiPickerProps, type EmojiStyle, EmptyState, type EmptyStateProps, type FontName, GrassMap, type GrassMapData, type GrassMapProps, IOSInstallSheet, type OGArticleTemplate, type OGConfig, type OGDefaultTemplate, type OGFont, type OGIconTemplate, OGImage, type OGMatchTemplate, type OGProductTemplate, type OGProps, type OGSquareTemplate, type OGStatTemplate, type OGTemplate, PWAInstallButton, type PWAInstallState, Section, SectionHeader, type SectionHeaderProps, type SectionProps, ShareButton, type ShareButtonProps, Skeleton, type SkeletonProps, StatChip, type StatChipProps, THEME_SCRIPT, Tab, TabBar, type TabBarProps, type TabProps, ThemeButton, type ThemeButtonProps, ThemeDialog, type ThemeDialogProps, type ToastOptions, ToastProvider, type ToastVariant, Tooltip, type TooltipProps, Typewriter, type TypewriterProps, type UseFetchOptions, type UseFetchResult, type UseFormSubmitOptions, type UseFormSubmitResult, type UseInViewOptions, type UseInViewResult, type UsePWAInstallReturn, type UsePollingOptions, type UsePollingResult, type UseShareOptions, type UseShareReturn, Watermark, type WatermarkProps, type WatermarkSponsor, clearFetchCache, cn, colors, createApiClient, createEmojiLoader, createManifest, fontFamily, fonts, formatNumber, formatPrice, getOGVersion, loadFont, loadGoogleFont, loadPretendard, mobileViewport, relativeTime, svgIcon, useDebounce, useFetch, useFormSubmit, useInView, useLocalStorage, usePWAInstall, usePolling, useShare, useToast };
750
+ export { type ApiClient, type ApiClientOptions, ApiError, AppShell, AppShellContent, type AppShellContentProps, AppShellHeader, type AppShellHeaderProps, type AppShellProps, Avatar, type AvatarProps, Badge, type BadgeProps, Button, type ButtonProps, type ColorName, Dialog, type DialogProps, Divider, EmojiButton, type EmojiButtonProps, EmojiPicker, type EmojiPickerProps, EmptyState, type EmptyStateProps, type FontName, GrassMap, type GrassMapData, type GrassMapProps, IOSInstallSheet, PWAInstallButton, type PWAInstallState, Section, SectionHeader, type SectionHeaderProps, type SectionProps, ShareButton, type ShareButtonProps, Skeleton, type SkeletonProps, StatChip, type StatChipProps, THEME_SCRIPT, Tab, TabBar, type TabBarProps, type TabProps, ThemeButton, type ThemeButtonProps, ThemeDialog, type ThemeDialogProps, type ToastOptions, ToastProvider, type ToastVariant, Tooltip, type TooltipProps, Typewriter, type TypewriterProps, type UseFetchOptions, type UseFetchResult, type UseFormSubmitOptions, type UseFormSubmitResult, type UseInViewOptions, type UseInViewResult, type UsePWAInstallReturn, type UsePollingOptions, type UsePollingResult, type UseShareOptions, type UseShareReturn, Watermark, type WatermarkProps, type WatermarkSponsor, clearFetchCache, cn, colors, createApiClient, createManifest, fontFamily, fonts, formatNumber, formatPrice, mobileViewport, relativeTime, svgIcon, useDebounce, useFetch, useFormSubmit, useInView, useLocalStorage, usePWAInstall, usePolling, useShare, useToast };
package/dist/index.d.ts CHANGED
@@ -438,8 +438,14 @@ interface UseFormSubmitOptions<T> {
438
438
  onSuccess?: (data: T) => void;
439
439
  onError?: (err: Error) => void;
440
440
  }
441
+ /**
442
+ * Submit function type:
443
+ * - When V is `void` (fn takes no arguments) → `submit()` needs no args
444
+ * - Otherwise → `submit(vars: V)` requires the arg
445
+ */
446
+ type SubmitFn<V> = V extends void ? () => Promise<void> : (vars: V) => Promise<void>;
441
447
  interface UseFormSubmitResult<T, V> {
442
- submit: (vars: V) => Promise<void>;
448
+ submit: SubmitFn<V>;
443
449
  loading: boolean;
444
450
  error: Error | null;
445
451
  data: T | undefined;
@@ -450,16 +456,21 @@ interface UseFormSubmitResult<T, V> {
450
456
  * Eliminates the try/catch/finally + setState boilerplate from every form.
451
457
  *
452
458
  * @example
459
+ * // With args
453
460
  * const { submit, loading, error } = useFormSubmit(async (url: string) => {
454
461
  * return api.post<Site>("/api/sites", { url });
455
462
  * }, {
456
- * onSuccess: () => router.push("/dashboard"),
463
+ * onSuccess: (site) => router.push(`/${site.slug}`),
457
464
  * });
465
+ * <button onClick={() => submit(inputValue)}>등록</button>
458
466
  *
459
- * <form onSubmit={e => { e.preventDefault(); submit(input); }}>
460
- * <Button loading={loading}>등록</Button>
461
- * {error && <p>{error.message}</p>}
462
- * </form>
467
+ * @example
468
+ * // No args (fire-and-forget style)
469
+ * const { submit: save, loading } = useFormSubmit(
470
+ * () => api.put("/api/sites/settings", config),
471
+ * { onSuccess: () => setSaved(true) }
472
+ * );
473
+ * <button onClick={save}>저장</button>
463
474
  */
464
475
  declare function useFormSubmit<T, V = void>(fn: (vars: V) => Promise<T>, options?: UseFormSubmitOptions<T>): UseFormSubmitResult<T, V>;
465
476
 
@@ -542,178 +553,6 @@ interface DialogProps {
542
553
  */
543
554
  declare function Dialog({ open, onClose, title, size, children, persistent, className, }: DialogProps): React__default.ReactPortal | null;
544
555
 
545
- interface OGConfig {
546
- /** 앱 이름 (좌상단 로고) */
547
- appName?: string;
548
- /** 브랜드 색상 (hex) */
549
- color?: string;
550
- /** 하단 도메인 */
551
- domain?: string;
552
- /** 배경 스타일 — "dark"(기본) | "gradient"(다크 오로라) | "blend"(라이트 파스텔 블렌드) */
553
- bg?: "dark" | "gradient" | "blend";
554
- /** 로고 이미지 URL (지정 시 appName 첫 글자 대신 이미지 표시) */
555
- logoUrl?: string;
556
- }
557
- interface OGDefaultTemplate {
558
- type?: "default";
559
- title: string;
560
- sub?: string;
561
- badge?: string;
562
- }
563
- interface OGMatchTemplate {
564
- type: "match";
565
- home: string;
566
- away: string;
567
- score?: string;
568
- sub?: string;
569
- badge?: string;
570
- }
571
- interface OGSquareTemplate {
572
- type: "square";
573
- title: string;
574
- sub?: string;
575
- badge?: string;
576
- }
577
- interface OGIconTemplate {
578
- type: "icon";
579
- /** 아이콘에 표시할 글자 (기본: appName 첫 글자) */
580
- letter?: string;
581
- /** 아이콘 모서리 둥글기 (기본: 96) */
582
- radius?: number;
583
- }
584
- interface OGArticleTemplate {
585
- type: "article";
586
- title: string;
587
- /** 작성자 */
588
- author?: string;
589
- /** 날짜 또는 발행일 */
590
- date?: string;
591
- /** 카테고리 / 태그 */
592
- category?: string;
593
- sub?: string;
594
- }
595
- interface OGStatTemplate {
596
- type: "stat";
597
- /** 강조할 숫자 또는 지표 */
598
- stat: string;
599
- /** 지표 설명 */
600
- label: string;
601
- sub?: string;
602
- badge?: string;
603
- }
604
- interface OGProductTemplate {
605
- type: "product";
606
- title: string;
607
- tagline?: string;
608
- /** 핵심 특징 (최대 3개) */
609
- features?: string[];
610
- badge?: string;
611
- }
612
- type OGTemplate = OGDefaultTemplate | OGMatchTemplate | OGSquareTemplate | OGIconTemplate | OGArticleTemplate | OGStatTemplate | OGProductTemplate;
613
- type OGProps = OGTemplate & OGConfig;
614
- declare function OGImage(props: OGProps): react_jsx_runtime.JSX.Element;
615
-
616
- /** @vercel/og (Satori) fonts 옵션에 넘길 수 있는 폰트 정의 */
617
- interface OGFont {
618
- name: string;
619
- data: ArrayBuffer;
620
- weight: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
621
- style?: "normal" | "italic";
622
- }
623
- declare const PRETENDARD_WEIGHTS: {
624
- readonly 400: "Pretendard-Regular.otf";
625
- readonly 700: "Pretendard-Bold.otf";
626
- readonly 900: "Pretendard-Black.otf";
627
- };
628
- type PretendardWeight = keyof typeof PRETENDARD_WEIGHTS;
629
- /**
630
- * Pretendard 폰트를 CDN에서 로드합니다.
631
- *
632
- * @example
633
- * ```ts
634
- * import { OG, loadPretendard } from "@m1kapp/seo";
635
- * import { ImageResponse } from "next/og";
636
- *
637
- * export async function GET() {
638
- * const fonts = await loadPretendard();
639
- * return new ImageResponse(<OG type="default" title="Hello" />, {
640
- * width: 1200, height: 630, fonts,
641
- * });
642
- * }
643
- * ```
644
- *
645
- * @param weights 로드할 weight 배열 (기본: [700, 900])
646
- */
647
- declare function loadPretendard(weights?: PretendardWeight[]): Promise<OGFont[]>;
648
- /**
649
- * 임의의 URL에서 폰트를 로드합니다.
650
- *
651
- * @example
652
- * ```ts
653
- * const fonts = await loadFont({
654
- * name: "CustomFont",
655
- * url: "https://example.com/CustomFont-Bold.otf",
656
- * weight: 700,
657
- * });
658
- * ```
659
- */
660
- declare function loadFont(opts: {
661
- name: string;
662
- url: string;
663
- weight?: OGFont["weight"];
664
- style?: OGFont["style"];
665
- }): Promise<OGFont>;
666
- /**
667
- * Google Fonts에서 폰트를 로드합니다.
668
- * CSS 응답을 파싱해 TTF/OTF URL을 자동 추출합니다.
669
- *
670
- * @example
671
- * ```ts
672
- * const fonts = await loadGoogleFont("Noto Sans KR", [400, 700]);
673
- * return new ImageResponse(<OG ... />, { fonts });
674
- * ```
675
- */
676
- declare function loadGoogleFont(family: string, weights?: OGFont["weight"][]): Promise<OGFont[]>;
677
-
678
- /**
679
- * @vercel/og ImageResponse에서 지원하는 이모지 스타일.
680
- * ImageResponse 옵션의 `emoji` 필드에 넘기면 됩니다.
681
- */
682
- type EmojiStyle = "twemoji" | "openmoji" | "noto" | "fluent" | "fluentFlat" | "blobmoji";
683
- /**
684
- * raw Satori의 `loadAdditionalAsset` 콜백을 생성합니다.
685
- *
686
- * @example
687
- * ```ts
688
- * import satori from "satori";
689
- * import { createEmojiLoader } from "@m1kapp/seo";
690
- *
691
- * const svg = await satori(<OG ... />, {
692
- * width: 1200, height: 630,
693
- * fonts: [...],
694
- * loadAdditionalAsset: createEmojiLoader(),
695
- * });
696
- * ```
697
- */
698
- declare function createEmojiLoader(): (code: string, segment: string) => Promise<string | undefined>;
699
-
700
- /**
701
- * OG 이미지 URL의 캐시를 즉시 무효화할 버전 키를 반환합니다.
702
- *
703
- * Cache-Control로 CDN에 캐싱된 OG 이미지를 강제 갱신하고 싶을 때
704
- * URL의 `v` 파라미터에 이 값을 넣으세요.
705
- *
706
- * @example
707
- * ```ts
708
- * import { getOGVersion } from "@m1kapp/seo";
709
- *
710
- * // OG 갱신 버튼 핸들러
711
- * const freshUrl = `/og?title=${title}&v=${getOGVersion()}`;
712
- * // → /og?title=...&v=20260415152347
713
- * ```
714
- */
715
- declare function getOGVersion(): string;
716
-
717
556
  type PWAInstallState = "android-ready" | "ios-safari" | "installed" | "unsupported";
718
557
  interface UsePWAInstallReturn {
719
558
  state: PWAInstallState;
@@ -816,7 +655,9 @@ declare class ApiError extends Error {
816
655
  readonly status: number;
817
656
  readonly statusText: string;
818
657
  readonly body: unknown;
819
- constructor(status: number, statusText: string, body: unknown);
658
+ readonly url?: string | undefined;
659
+ readonly method?: string | undefined;
660
+ constructor(status: number, statusText: string, body: unknown, url?: string | undefined, method?: string | undefined);
820
661
  }
821
662
 
822
663
  interface ApiClientOptions {
@@ -824,7 +665,7 @@ interface ApiClientOptions {
824
665
  headers?: Record<string, string>;
825
666
  /** Called before every request — mutate or replace the Request */
826
667
  onRequest?: (req: Request) => Request | void;
827
- /** Called on every non-2xx response */
668
+ /** Called on every non-2xx response — includes url and method for debugging */
828
669
  onError?: (err: ApiError) => void;
829
670
  }
830
671
  type RequestOptions = {
@@ -844,6 +685,7 @@ type ApiClient = ReturnType<typeof createApiClient>;
844
685
 
845
686
  /** Manually invalidate cache entries. Pass a URL to clear one, or nothing to clear all. */
846
687
  declare function clearFetchCache(url?: string): void;
688
+ type FetchStatus = "idle" | "loading" | "success" | "error";
847
689
  interface UseFetchOptions<T> {
848
690
  /** Skip fetching when false (default: true) */
849
691
  enabled?: boolean;
@@ -864,6 +706,14 @@ interface UseFetchResult<T> {
864
706
  data: T | undefined;
865
707
  loading: boolean;
866
708
  error: Error | undefined;
709
+ /**
710
+ * Lifecycle status:
711
+ * - `"idle"` — url is null/undefined or `enabled: false`
712
+ * - `"loading"` — fetch in progress
713
+ * - `"success"` — data loaded (note: data may be null if the API returned null)
714
+ * - `"error"` — last fetch failed
715
+ */
716
+ status: FetchStatus;
867
717
  /** Manually trigger a refetch (ignores cache) */
868
718
  refetch: () => void;
869
719
  }
@@ -887,6 +737,8 @@ interface UsePollingResult<T> {
887
737
  isRunning: boolean;
888
738
  stop: () => void;
889
739
  start: () => void;
740
+ /** Immediately trigger a fetch without waiting for the next interval */
741
+ refetch: () => void;
890
742
  }
891
743
  declare function usePolling<T>(fetcher: () => Promise<T>, options?: UsePollingOptions<T>): UsePollingResult<T>;
892
744
 
@@ -895,4 +747,4 @@ declare function formatNumber(n: number): string;
895
747
  declare function formatPrice(amount: number, currency?: string, locale?: string): string;
896
748
  declare function cn(...classes: (string | undefined | null | false | 0)[]): string;
897
749
 
898
- export { type ApiClient, type ApiClientOptions, ApiError, AppShell, AppShellContent, type AppShellContentProps, AppShellHeader, type AppShellHeaderProps, type AppShellProps, Avatar, type AvatarProps, Badge, type BadgeProps, Button, type ButtonProps, type ColorName, Dialog, type DialogProps, Divider, EmojiButton, type EmojiButtonProps, EmojiPicker, type EmojiPickerProps, type EmojiStyle, EmptyState, type EmptyStateProps, type FontName, GrassMap, type GrassMapData, type GrassMapProps, IOSInstallSheet, type OGArticleTemplate, type OGConfig, type OGDefaultTemplate, type OGFont, type OGIconTemplate, OGImage, type OGMatchTemplate, type OGProductTemplate, type OGProps, type OGSquareTemplate, type OGStatTemplate, type OGTemplate, PWAInstallButton, type PWAInstallState, Section, SectionHeader, type SectionHeaderProps, type SectionProps, ShareButton, type ShareButtonProps, Skeleton, type SkeletonProps, StatChip, type StatChipProps, THEME_SCRIPT, Tab, TabBar, type TabBarProps, type TabProps, ThemeButton, type ThemeButtonProps, ThemeDialog, type ThemeDialogProps, type ToastOptions, ToastProvider, type ToastVariant, Tooltip, type TooltipProps, Typewriter, type TypewriterProps, type UseFetchOptions, type UseFetchResult, type UseFormSubmitOptions, type UseFormSubmitResult, type UseInViewOptions, type UseInViewResult, type UsePWAInstallReturn, type UsePollingOptions, type UsePollingResult, type UseShareOptions, type UseShareReturn, Watermark, type WatermarkProps, type WatermarkSponsor, clearFetchCache, cn, colors, createApiClient, createEmojiLoader, createManifest, fontFamily, fonts, formatNumber, formatPrice, getOGVersion, loadFont, loadGoogleFont, loadPretendard, mobileViewport, relativeTime, svgIcon, useDebounce, useFetch, useFormSubmit, useInView, useLocalStorage, usePWAInstall, usePolling, useShare, useToast };
750
+ export { type ApiClient, type ApiClientOptions, ApiError, AppShell, AppShellContent, type AppShellContentProps, AppShellHeader, type AppShellHeaderProps, type AppShellProps, Avatar, type AvatarProps, Badge, type BadgeProps, Button, type ButtonProps, type ColorName, Dialog, type DialogProps, Divider, EmojiButton, type EmojiButtonProps, EmojiPicker, type EmojiPickerProps, EmptyState, type EmptyStateProps, type FontName, GrassMap, type GrassMapData, type GrassMapProps, IOSInstallSheet, PWAInstallButton, type PWAInstallState, Section, SectionHeader, type SectionHeaderProps, type SectionProps, ShareButton, type ShareButtonProps, Skeleton, type SkeletonProps, StatChip, type StatChipProps, THEME_SCRIPT, Tab, TabBar, type TabBarProps, type TabProps, ThemeButton, type ThemeButtonProps, ThemeDialog, type ThemeDialogProps, type ToastOptions, ToastProvider, type ToastVariant, Tooltip, type TooltipProps, Typewriter, type TypewriterProps, type UseFetchOptions, type UseFetchResult, type UseFormSubmitOptions, type UseFormSubmitResult, type UseInViewOptions, type UseInViewResult, type UsePWAInstallReturn, type UsePollingOptions, type UsePollingResult, type UseShareOptions, type UseShareReturn, Watermark, type WatermarkProps, type WatermarkSponsor, clearFetchCache, cn, colors, createApiClient, createManifest, fontFamily, fonts, formatNumber, formatPrice, mobileViewport, relativeTime, svgIcon, useDebounce, useFetch, useFormSubmit, useInView, useLocalStorage, usePWAInstall, usePolling, useShare, useToast };