@usefy/use-cookie 0.19.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/README.md ADDED
@@ -0,0 +1,182 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/mirunamu00/usefy/master/assets/logo.png" alt="usefy logo" width="120" />
3
+ </p>
4
+
5
+ <h1 align="center">@usefy/use-cookie</h1>
6
+
7
+ <p align="center">
8
+ <strong>Read and write a browser cookie as React state — SSR-aware, JSON-friendly, with full cookie attribute support</strong>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/@usefy/use-cookie"><img src="https://img.shields.io/npm/v/@usefy/use-cookie.svg?style=flat-square&color=007acc" alt="npm version" /></a>
13
+ <a href="https://www.npmjs.com/package/@usefy/use-cookie"><img src="https://img.shields.io/npm/dm/@usefy/use-cookie.svg?style=flat-square&color=007acc" alt="npm downloads" /></a>
14
+ <a href="https://bundlephobia.com/package/@usefy/use-cookie"><img src="https://img.shields.io/bundlephobia/minzip/@usefy/use-cookie?style=flat-square&color=007acc" alt="bundle size" /></a>
15
+ <a href="https://github.com/mirunamu00/usefy/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/@usefy/use-cookie.svg?style=flat-square&color=007acc" alt="license" /></a>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="#installation">Installation</a> •
20
+ <a href="#quick-start">Quick Start</a> •
21
+ <a href="#api">API</a> •
22
+ <a href="#testing">Testing</a> •
23
+ <a href="#license">License</a>
24
+ </p>
25
+
26
+ <p align="center">
27
+ <a href="https://mirunamu00.github.io/usefy/?path=/docs/hooks-usecookie--docs" target="_blank" rel="noopener noreferrer">
28
+ <strong>📚 View Storybook Demo</strong>
29
+ </a>
30
+ </p>
31
+
32
+ ---
33
+
34
+ ## Overview
35
+
36
+ `useCookie` is part of the [@usefy](https://www.npmjs.com/org/usefy) ecosystem — a collection of production-ready, TypeScript-first, SSR-safe React hooks. It reads and writes a browser cookie with a `useState`-like API, and completes the storage trio alongside [`@usefy/use-local-storage`](https://www.npmjs.com/package/@usefy/use-local-storage) and [`@usefy/use-session-storage`](https://www.npmjs.com/package/@usefy/use-session-storage) — the three feel like siblings.
37
+
38
+ ## Features
39
+
40
+ - **`useState`-like tuple** — `[value, setValue, remove]`, with a functional updater `(prev) => next`
41
+ - **Full cookie attributes** — `expires` (Date or days), `maxAge` (seconds), `path`, `domain`, `secure`, `sameSite`
42
+ - **Safe (de)serialization** — JSON by default, with a graceful fallback to the raw string so plain (non-JSON) cookies never throw
43
+ - **SSR-safe** — guards all `document` access and returns `initialValue` on the server (built on `useSyncExternalStore`, exactly like its storage siblings, to avoid hydration mismatches)
44
+ - **Same-document sync** — multiple `useCookie(key)` instances stay in sync when any of them writes or removes
45
+ - **Stable references** — `setValue` and `remove` are memoized
46
+ - **TypeScript-first** — full type inference and exported types
47
+ - **Tiny & tree-shakeable** — zero dependencies, published as its own package
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ # npm
53
+ npm install @usefy/use-cookie
54
+
55
+ # yarn
56
+ yarn add @usefy/use-cookie
57
+
58
+ # pnpm
59
+ pnpm add @usefy/use-cookie
60
+ ```
61
+
62
+ Requires React 18 or 19 (`peerDependencies: "react": "^18.0.0 || ^19.0.0"`).
63
+
64
+ ## Quick Start
65
+
66
+ ```tsx
67
+ import { useCookie } from "@usefy/use-cookie";
68
+
69
+ function ThemeToggle() {
70
+ const [theme, setTheme, removeTheme] = useCookie("theme", {
71
+ initialValue: "light",
72
+ });
73
+
74
+ return (
75
+ <div>
76
+ <p>Current theme: {theme}</p>
77
+ <button onClick={() => setTheme("dark")}>Dark</button>
78
+ <button onClick={removeTheme}>Reset</button>
79
+ </div>
80
+ );
81
+ }
82
+ ```
83
+
84
+ ## API
85
+
86
+ ### `useCookie<T>(key, options?)`
87
+
88
+ Reads and writes the cookie named `key`, returning a `[value, setValue, remove]` tuple.
89
+
90
+ #### Parameters
91
+
92
+ | Parameter | Type | Description |
93
+ | --------- | --------------------- | ------------------------- |
94
+ | `key` | `string` | The cookie name |
95
+ | `options` | `UseCookieOptions<T>` | Configuration (see below) |
96
+
97
+ #### Options
98
+
99
+ | Option | Type | Default | Description |
100
+ | -------------- | ------------------------ | ---------------- | ----------------------------------------------------------------------- |
101
+ | `initialValue` | `T \| (() => T)` | `undefined` | Value when the cookie is absent (and on the server); also the reset target for `remove()`. Supports a lazy initializer. |
102
+ | `serializer` | `(value: T) => string` | `JSON.stringify` | Convert the value to the stored cookie string |
103
+ | `deserializer` | `(value: string) => T` | JSON-or-raw\* | Parse the stored cookie string back to a value |
104
+ | `onError` | `(error: Error) => void` | — | Called on a serialization/deserialization error |
105
+ | `expires` | `Date \| number` | — | Absolute `Date`, or a number of **days** from now. Omit for a session cookie. |
106
+ | `maxAge` | `number` | — | `Max-Age` in **seconds**. Use `0`/negative to expire immediately. |
107
+ | `path` | `string` | `"/"` | Path the cookie is scoped to |
108
+ | `domain` | `string` | — | Domain the cookie is scoped to (e.g. `.example.com`) |
109
+ | `secure` | `boolean` | — | Send the cookie over HTTPS only |
110
+ | `sameSite` | `'strict' \| 'lax' \| 'none'` | — | `SameSite` policy. `'none'` requires `secure: true`. |
111
+
112
+ \* **Default deserialization:** the value is written with `JSON.stringify` and read with `JSON.parse`. Because cookies are very often plain, non-JSON strings (set by a server or another library), the default deserializer **falls back to the raw string** when `JSON.parse` throws — so `foo=bar` reads back as `"bar"` and never crashes, while `'{"a":1}'` reads back as `{ a: 1 }`.
113
+
114
+ #### Returns `[T | undefined, SetValue, Remove]`
115
+
116
+ | Index | Type | Description |
117
+ | ----- | ----------------------------------------------- | ------------------------------------------------------------- |
118
+ | `[0]` | `T \| undefined` | Current value (`undefined` when absent and no `initialValue`) |
119
+ | `[1]` | `Dispatch<SetStateAction<T \| undefined>>` | Set the cookie; accepts a value or `(prev) => next` |
120
+ | `[2]` | `() => void` | Delete the cookie and reset state to `initialValue` |
121
+
122
+ ### SSR / hydration
123
+
124
+ On the server there is no `document`, so `useCookie` returns `initialValue`. Like `@usefy/use-local-storage`, it reads through `useSyncExternalStore` with a server snapshot, so the first client render is consistent and there is no hydration mismatch.
125
+
126
+ ### Cross-tab limitation
127
+
128
+ Cookies have **no `storage` event**, so writes made in *other* browser tabs cannot be observed without polling — `useCookie` does not fake this. It does keep every `useCookie(key)` instance **within the same document** in sync via an internal subscription (mirroring `@usefy/use-local-storage`'s same-tab store). If you need cross-tab reactivity for a cookie, poll `document.cookie` yourself or store the value in `localStorage` instead.
129
+
130
+ ## Examples
131
+
132
+ ### Cookie attributes
133
+
134
+ ```tsx
135
+ import { useCookie } from "@usefy/use-cookie";
136
+
137
+ // A secure, same-site session token that expires in a week.
138
+ const [token, setToken, clearToken] = useCookie<string>("token", {
139
+ expires: 7, // days (a Date also works)
140
+ path: "/",
141
+ secure: true,
142
+ sameSite: "strict",
143
+ });
144
+ ```
145
+
146
+ ### Object value (JSON)
147
+
148
+ ```tsx
149
+ import { useCookie } from "@usefy/use-cookie";
150
+
151
+ interface Prefs {
152
+ lang: string;
153
+ compact: boolean;
154
+ }
155
+
156
+ const [prefs, setPrefs] = useCookie<Prefs>("prefs", {
157
+ initialValue: { lang: "en", compact: false },
158
+ maxAge: 60 * 60 * 24 * 30, // 30 days
159
+ });
160
+
161
+ setPrefs((prev) => ({ ...prev!, compact: true }));
162
+ ```
163
+
164
+ ### Reading a plain server-set cookie
165
+
166
+ ```tsx
167
+ import { useCookie } from "@usefy/use-cookie";
168
+
169
+ // Cookie set by the backend as `consent=accepted` (not JSON) — reads back as
170
+ // the raw string, no throw.
171
+ const [consent] = useCookie<string>("consent");
172
+ ```
173
+
174
+ ## Testing
175
+
176
+ 📊 <a href="https://mirunamu00.github.io/usefy/coverage/use-cookie/src/index.html" target="_blank" rel="noopener noreferrer"><strong>View Detailed Coverage Report</strong></a> (GitHub Pages) — **47 tests**, 96% statement coverage.
177
+
178
+ ## License
179
+
180
+ MIT © [mirunamu](https://github.com/mirunamu00)
181
+
182
+ This package is part of the [usefy](https://github.com/mirunamu00/usefy) monorepo.
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Pure, framework-agnostic helpers for reading and writing `document.cookie`.
3
+ * These are unit-tested in isolation and used by {@link useCookie}.
4
+ */
5
+ /**
6
+ * The `SameSite` cookie attribute values.
7
+ *
8
+ * - `"strict"` — cookie is sent only for same-site requests.
9
+ * - `"lax"` — cookie is sent on same-site requests and top-level navigations.
10
+ * - `"none"` — cookie is sent on all requests; **requires `secure: true`**.
11
+ */
12
+ type SameSite = "strict" | "lax" | "none";
13
+ /**
14
+ * Cookie write attributes, mirroring the standard `Set-Cookie` directives.
15
+ * These control how the cookie is persisted by the browser.
16
+ */
17
+ interface CookieAttributes {
18
+ /**
19
+ * Expiry of the cookie. Either an absolute `Date`, or a number of **days**
20
+ * from now (e.g. `7` = expires in a week). Omit for a session cookie.
21
+ */
22
+ expires?: Date | number;
23
+ /**
24
+ * `Max-Age` in **seconds** from now. Takes precedence over `expires` in
25
+ * browsers that support it. Use `0` or a negative number to expire immediately.
26
+ */
27
+ maxAge?: number;
28
+ /**
29
+ * The URL path the cookie is scoped to.
30
+ * @default "/"
31
+ */
32
+ path?: string;
33
+ /** The domain the cookie is scoped to (e.g. `.example.com`). */
34
+ domain?: string;
35
+ /** When `true`, the cookie is only sent over HTTPS. */
36
+ secure?: boolean;
37
+ /** The `SameSite` policy. `"none"` requires `secure: true`. */
38
+ sameSite?: SameSite;
39
+ }
40
+ /**
41
+ * Parse a raw `document.cookie` string and return the **decoded** value for
42
+ * `key`, or `undefined` when the key is absent. Pure — does not touch
43
+ * `document`, so it is trivially testable.
44
+ *
45
+ * @param cookieString - The raw `document.cookie` value (e.g. `"a=1; b=2"`).
46
+ * @param key - The (un-encoded) cookie name to look up.
47
+ */
48
+ declare function parseCookie(cookieString: string, key: string): string | undefined;
49
+ /**
50
+ * Build a `document.cookie` assignment string for a cookie, encoding the name
51
+ * and value and appending the standard attributes. Pure and testable.
52
+ *
53
+ * @example
54
+ * buildCookieString("theme", "dark", { path: "/", maxAge: 3600 })
55
+ * // => "theme=dark; Path=/; Max-Age=3600"
56
+ */
57
+ declare function buildCookieString(name: string, value: string, attributes?: CookieAttributes): string;
58
+ /**
59
+ * Build the `document.cookie` assignment string that **deletes** a cookie. The
60
+ * value is emptied and the cookie is expired in the past (`Max-Age=-1` plus an
61
+ * epoch `Expires`). The same `path`/`domain` used to write it must be provided,
62
+ * otherwise the browser treats it as a different cookie and deletion is a no-op.
63
+ */
64
+ declare function buildRemovalCookieString(name: string, attributes?: Pick<CookieAttributes, "path" | "domain">): string;
65
+
66
+ /**
67
+ * Type for the initial value that can be a value or a function returning a value
68
+ * (lazy initialization).
69
+ */
70
+ type InitialValue<T> = T | (() => T);
71
+ /**
72
+ * Options for the {@link useCookie} hook. Combines serialization/lifecycle
73
+ * options (aligned with `@usefy/use-local-storage`) with the cookie-specific
74
+ * write attributes from {@link CookieAttributes}.
75
+ */
76
+ interface UseCookieOptions<T> extends CookieAttributes {
77
+ /**
78
+ * Initial value used when the cookie is absent (or on the server). Supports a
79
+ * lazy initializer function. This is also the value the state resets to when
80
+ * {@link UseCookieReturn} `remove()` is called.
81
+ */
82
+ initialValue?: InitialValue<T>;
83
+ /**
84
+ * Custom serializer for converting the value to the stored cookie string.
85
+ * @default JSON.stringify
86
+ */
87
+ serializer?: (value: T) => string;
88
+ /**
89
+ * Custom deserializer for parsing the stored cookie string back to a value.
90
+ * The default tries `JSON.parse` and **falls back to the raw string** for
91
+ * non-JSON cookies (so a plain `foo=bar` cookie never throws).
92
+ * @default (value) => { try { return JSON.parse(value) } catch { return value } }
93
+ */
94
+ deserializer?: (value: string) => T;
95
+ /** Callback invoked when a serialization/deserialization error occurs. */
96
+ onError?: (error: Error) => void;
97
+ }
98
+ /**
99
+ * Return type for {@link useCookie} — a tuple similar to `useState`.
100
+ */
101
+ type UseCookieReturn<T> = readonly [
102
+ /** Current cookie value (`undefined` when the cookie is absent and no `initialValue` was given). */
103
+ T | undefined,
104
+ /** Update the cookie value. Accepts a value or a functional updater `(prev) => next`. */
105
+ React.Dispatch<React.SetStateAction<T | undefined>>,
106
+ /** Delete the cookie and reset state to `initialValue` (or `undefined`). */
107
+ () => void
108
+ ];
109
+ /**
110
+ * A hook for reading and writing a browser **cookie** as React state — the
111
+ * cookie sibling of `@usefy/use-local-storage` and `@usefy/use-session-storage`.
112
+ * Works like `useState` but the value is persisted in `document.cookie`.
113
+ *
114
+ * Features:
115
+ * - **`useState`-like tuple**: `[value, setValue, remove]`.
116
+ * - **Same-document sync**: multiple `useCookie(key)` instances stay in sync.
117
+ * - **SSR-safe**: guards all `document` access and returns `initialValue` on the
118
+ * server (uses `useSyncExternalStore`, identical to its storage siblings, to
119
+ * avoid hydration mismatches).
120
+ * - **Cookie attributes**: `expires`, `maxAge`, `path`, `domain`, `secure`,
121
+ * `sameSite`.
122
+ * - **Safe (de)serialization**: JSON by default, with a graceful fallback to the
123
+ * raw string for plain (non-JSON) cookies.
124
+ *
125
+ * > **Cross-tab note:** cookies have no `storage` event, so writes in *other*
126
+ * > tabs are not observed without polling. Same-document instances always sync.
127
+ *
128
+ * @template T - The type of the stored value
129
+ * @param key - The cookie name
130
+ * @param options - Serialization, initial value, and cookie write attributes
131
+ * @returns A tuple of `[value, setValue, remove]`
132
+ *
133
+ * @example
134
+ * ```tsx
135
+ * // Basic usage — a string cookie
136
+ * function ThemeToggle() {
137
+ * const [theme, setTheme, removeTheme] = useCookie("theme", {
138
+ * initialValue: "light",
139
+ * });
140
+ *
141
+ * return (
142
+ * <div>
143
+ * <p>Current theme: {theme}</p>
144
+ * <button onClick={() => setTheme("dark")}>Dark</button>
145
+ * <button onClick={removeTheme}>Reset</button>
146
+ * </div>
147
+ * );
148
+ * }
149
+ * ```
150
+ *
151
+ * @example
152
+ * ```tsx
153
+ * // With cookie attributes — persist for a week over HTTPS only
154
+ * const [token, setToken] = useCookie<string>("token", {
155
+ * expires: 7, // days (or a Date)
156
+ * path: "/",
157
+ * secure: true,
158
+ * sameSite: "strict",
159
+ * });
160
+ * ```
161
+ *
162
+ * @example
163
+ * ```tsx
164
+ * // Object value with a functional update (JSON serialized)
165
+ * interface Prefs { lang: string; compact: boolean }
166
+ * const [prefs, setPrefs] = useCookie<Prefs>("prefs", {
167
+ * initialValue: { lang: "en", compact: false },
168
+ * });
169
+ * setPrefs((prev) => ({ ...prev!, compact: true }));
170
+ * ```
171
+ */
172
+ declare function useCookie<T = string>(key: string, options?: UseCookieOptions<T>): UseCookieReturn<T>;
173
+
174
+ export { type CookieAttributes, type InitialValue, type SameSite, type UseCookieOptions, type UseCookieReturn, buildCookieString, buildRemovalCookieString, parseCookie, useCookie };
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Pure, framework-agnostic helpers for reading and writing `document.cookie`.
3
+ * These are unit-tested in isolation and used by {@link useCookie}.
4
+ */
5
+ /**
6
+ * The `SameSite` cookie attribute values.
7
+ *
8
+ * - `"strict"` — cookie is sent only for same-site requests.
9
+ * - `"lax"` — cookie is sent on same-site requests and top-level navigations.
10
+ * - `"none"` — cookie is sent on all requests; **requires `secure: true`**.
11
+ */
12
+ type SameSite = "strict" | "lax" | "none";
13
+ /**
14
+ * Cookie write attributes, mirroring the standard `Set-Cookie` directives.
15
+ * These control how the cookie is persisted by the browser.
16
+ */
17
+ interface CookieAttributes {
18
+ /**
19
+ * Expiry of the cookie. Either an absolute `Date`, or a number of **days**
20
+ * from now (e.g. `7` = expires in a week). Omit for a session cookie.
21
+ */
22
+ expires?: Date | number;
23
+ /**
24
+ * `Max-Age` in **seconds** from now. Takes precedence over `expires` in
25
+ * browsers that support it. Use `0` or a negative number to expire immediately.
26
+ */
27
+ maxAge?: number;
28
+ /**
29
+ * The URL path the cookie is scoped to.
30
+ * @default "/"
31
+ */
32
+ path?: string;
33
+ /** The domain the cookie is scoped to (e.g. `.example.com`). */
34
+ domain?: string;
35
+ /** When `true`, the cookie is only sent over HTTPS. */
36
+ secure?: boolean;
37
+ /** The `SameSite` policy. `"none"` requires `secure: true`. */
38
+ sameSite?: SameSite;
39
+ }
40
+ /**
41
+ * Parse a raw `document.cookie` string and return the **decoded** value for
42
+ * `key`, or `undefined` when the key is absent. Pure — does not touch
43
+ * `document`, so it is trivially testable.
44
+ *
45
+ * @param cookieString - The raw `document.cookie` value (e.g. `"a=1; b=2"`).
46
+ * @param key - The (un-encoded) cookie name to look up.
47
+ */
48
+ declare function parseCookie(cookieString: string, key: string): string | undefined;
49
+ /**
50
+ * Build a `document.cookie` assignment string for a cookie, encoding the name
51
+ * and value and appending the standard attributes. Pure and testable.
52
+ *
53
+ * @example
54
+ * buildCookieString("theme", "dark", { path: "/", maxAge: 3600 })
55
+ * // => "theme=dark; Path=/; Max-Age=3600"
56
+ */
57
+ declare function buildCookieString(name: string, value: string, attributes?: CookieAttributes): string;
58
+ /**
59
+ * Build the `document.cookie` assignment string that **deletes** a cookie. The
60
+ * value is emptied and the cookie is expired in the past (`Max-Age=-1` plus an
61
+ * epoch `Expires`). The same `path`/`domain` used to write it must be provided,
62
+ * otherwise the browser treats it as a different cookie and deletion is a no-op.
63
+ */
64
+ declare function buildRemovalCookieString(name: string, attributes?: Pick<CookieAttributes, "path" | "domain">): string;
65
+
66
+ /**
67
+ * Type for the initial value that can be a value or a function returning a value
68
+ * (lazy initialization).
69
+ */
70
+ type InitialValue<T> = T | (() => T);
71
+ /**
72
+ * Options for the {@link useCookie} hook. Combines serialization/lifecycle
73
+ * options (aligned with `@usefy/use-local-storage`) with the cookie-specific
74
+ * write attributes from {@link CookieAttributes}.
75
+ */
76
+ interface UseCookieOptions<T> extends CookieAttributes {
77
+ /**
78
+ * Initial value used when the cookie is absent (or on the server). Supports a
79
+ * lazy initializer function. This is also the value the state resets to when
80
+ * {@link UseCookieReturn} `remove()` is called.
81
+ */
82
+ initialValue?: InitialValue<T>;
83
+ /**
84
+ * Custom serializer for converting the value to the stored cookie string.
85
+ * @default JSON.stringify
86
+ */
87
+ serializer?: (value: T) => string;
88
+ /**
89
+ * Custom deserializer for parsing the stored cookie string back to a value.
90
+ * The default tries `JSON.parse` and **falls back to the raw string** for
91
+ * non-JSON cookies (so a plain `foo=bar` cookie never throws).
92
+ * @default (value) => { try { return JSON.parse(value) } catch { return value } }
93
+ */
94
+ deserializer?: (value: string) => T;
95
+ /** Callback invoked when a serialization/deserialization error occurs. */
96
+ onError?: (error: Error) => void;
97
+ }
98
+ /**
99
+ * Return type for {@link useCookie} — a tuple similar to `useState`.
100
+ */
101
+ type UseCookieReturn<T> = readonly [
102
+ /** Current cookie value (`undefined` when the cookie is absent and no `initialValue` was given). */
103
+ T | undefined,
104
+ /** Update the cookie value. Accepts a value or a functional updater `(prev) => next`. */
105
+ React.Dispatch<React.SetStateAction<T | undefined>>,
106
+ /** Delete the cookie and reset state to `initialValue` (or `undefined`). */
107
+ () => void
108
+ ];
109
+ /**
110
+ * A hook for reading and writing a browser **cookie** as React state — the
111
+ * cookie sibling of `@usefy/use-local-storage` and `@usefy/use-session-storage`.
112
+ * Works like `useState` but the value is persisted in `document.cookie`.
113
+ *
114
+ * Features:
115
+ * - **`useState`-like tuple**: `[value, setValue, remove]`.
116
+ * - **Same-document sync**: multiple `useCookie(key)` instances stay in sync.
117
+ * - **SSR-safe**: guards all `document` access and returns `initialValue` on the
118
+ * server (uses `useSyncExternalStore`, identical to its storage siblings, to
119
+ * avoid hydration mismatches).
120
+ * - **Cookie attributes**: `expires`, `maxAge`, `path`, `domain`, `secure`,
121
+ * `sameSite`.
122
+ * - **Safe (de)serialization**: JSON by default, with a graceful fallback to the
123
+ * raw string for plain (non-JSON) cookies.
124
+ *
125
+ * > **Cross-tab note:** cookies have no `storage` event, so writes in *other*
126
+ * > tabs are not observed without polling. Same-document instances always sync.
127
+ *
128
+ * @template T - The type of the stored value
129
+ * @param key - The cookie name
130
+ * @param options - Serialization, initial value, and cookie write attributes
131
+ * @returns A tuple of `[value, setValue, remove]`
132
+ *
133
+ * @example
134
+ * ```tsx
135
+ * // Basic usage — a string cookie
136
+ * function ThemeToggle() {
137
+ * const [theme, setTheme, removeTheme] = useCookie("theme", {
138
+ * initialValue: "light",
139
+ * });
140
+ *
141
+ * return (
142
+ * <div>
143
+ * <p>Current theme: {theme}</p>
144
+ * <button onClick={() => setTheme("dark")}>Dark</button>
145
+ * <button onClick={removeTheme}>Reset</button>
146
+ * </div>
147
+ * );
148
+ * }
149
+ * ```
150
+ *
151
+ * @example
152
+ * ```tsx
153
+ * // With cookie attributes — persist for a week over HTTPS only
154
+ * const [token, setToken] = useCookie<string>("token", {
155
+ * expires: 7, // days (or a Date)
156
+ * path: "/",
157
+ * secure: true,
158
+ * sameSite: "strict",
159
+ * });
160
+ * ```
161
+ *
162
+ * @example
163
+ * ```tsx
164
+ * // Object value with a functional update (JSON serialized)
165
+ * interface Prefs { lang: string; compact: boolean }
166
+ * const [prefs, setPrefs] = useCookie<Prefs>("prefs", {
167
+ * initialValue: { lang: "en", compact: false },
168
+ * });
169
+ * setPrefs((prev) => ({ ...prev!, compact: true }));
170
+ * ```
171
+ */
172
+ declare function useCookie<T = string>(key: string, options?: UseCookieOptions<T>): UseCookieReturn<T>;
173
+
174
+ export { type CookieAttributes, type InitialValue, type SameSite, type UseCookieOptions, type UseCookieReturn, buildCookieString, buildRemovalCookieString, parseCookie, useCookie };
package/dist/index.js ADDED
@@ -0,0 +1,252 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ buildCookieString: () => buildCookieString,
24
+ buildRemovalCookieString: () => buildRemovalCookieString,
25
+ parseCookie: () => parseCookie,
26
+ useCookie: () => useCookie
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/useCookie.ts
31
+ var import_react = require("react");
32
+
33
+ // src/store.ts
34
+ var listeners = /* @__PURE__ */ new Map();
35
+ function subscribe(key, listener) {
36
+ if (!listeners.has(key)) {
37
+ listeners.set(key, /* @__PURE__ */ new Set());
38
+ }
39
+ const keyListeners = listeners.get(key);
40
+ keyListeners.add(listener);
41
+ return () => {
42
+ keyListeners.delete(listener);
43
+ if (keyListeners.size === 0) {
44
+ listeners.delete(key);
45
+ }
46
+ };
47
+ }
48
+ function notifyListeners(key) {
49
+ const keyListeners = listeners.get(key);
50
+ if (keyListeners) {
51
+ keyListeners.forEach((listener) => listener());
52
+ }
53
+ }
54
+
55
+ // src/utils.ts
56
+ function isDocumentAvailable() {
57
+ return typeof document !== "undefined";
58
+ }
59
+ function capitalizeSameSite(value) {
60
+ return value.charAt(0).toUpperCase() + value.slice(1);
61
+ }
62
+ function defaultSerializer(value) {
63
+ return JSON.stringify(value);
64
+ }
65
+ function defaultDeserializer(value) {
66
+ try {
67
+ return JSON.parse(value);
68
+ } catch {
69
+ return value;
70
+ }
71
+ }
72
+ function parseCookie(cookieString, key) {
73
+ if (!cookieString) return void 0;
74
+ const pairs = cookieString.split("; ");
75
+ for (const pair of pairs) {
76
+ const eqIndex = pair.indexOf("=");
77
+ const rawName = eqIndex > -1 ? pair.slice(0, eqIndex) : pair;
78
+ let name;
79
+ try {
80
+ name = decodeURIComponent(rawName);
81
+ } catch {
82
+ name = rawName;
83
+ }
84
+ if (name === key) {
85
+ const rawValue = eqIndex > -1 ? pair.slice(eqIndex + 1) : "";
86
+ try {
87
+ return decodeURIComponent(rawValue);
88
+ } catch {
89
+ return rawValue;
90
+ }
91
+ }
92
+ }
93
+ return void 0;
94
+ }
95
+ function readRawCookie(key) {
96
+ if (!isDocumentAvailable()) return void 0;
97
+ return parseCookie(document.cookie, key);
98
+ }
99
+ function buildCookieString(name, value, attributes = {}) {
100
+ const { expires, maxAge, path = "/", domain, secure, sameSite } = attributes;
101
+ let str = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
102
+ if (expires !== void 0) {
103
+ const date = typeof expires === "number" ? new Date(Date.now() + expires * 864e5) : expires;
104
+ str += `; Expires=${date.toUTCString()}`;
105
+ }
106
+ if (maxAge !== void 0) {
107
+ str += `; Max-Age=${maxAge}`;
108
+ }
109
+ if (path) {
110
+ str += `; Path=${path}`;
111
+ }
112
+ if (domain) {
113
+ str += `; Domain=${domain}`;
114
+ }
115
+ if (secure) {
116
+ str += "; Secure";
117
+ }
118
+ if (sameSite) {
119
+ str += `; SameSite=${capitalizeSameSite(sameSite)}`;
120
+ }
121
+ return str;
122
+ }
123
+ function buildRemovalCookieString(name, attributes = {}) {
124
+ const { path = "/", domain } = attributes;
125
+ let str = `${encodeURIComponent(name)}=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=-1`;
126
+ if (path) {
127
+ str += `; Path=${path}`;
128
+ }
129
+ if (domain) {
130
+ str += `; Domain=${domain}`;
131
+ }
132
+ return str;
133
+ }
134
+
135
+ // src/useCookie.ts
136
+ function resolveInitialValue(initialValue) {
137
+ return typeof initialValue === "function" ? initialValue() : initialValue;
138
+ }
139
+ function useCookie(key, options = {}) {
140
+ const {
141
+ initialValue,
142
+ serializer = defaultSerializer,
143
+ deserializer = defaultDeserializer,
144
+ onError,
145
+ expires,
146
+ maxAge,
147
+ path,
148
+ domain,
149
+ secure,
150
+ sameSite
151
+ } = options;
152
+ const serializerRef = (0, import_react.useRef)(serializer);
153
+ const deserializerRef = (0, import_react.useRef)(deserializer);
154
+ const onErrorRef = (0, import_react.useRef)(onError);
155
+ const initialValueRef = (0, import_react.useRef)(initialValue);
156
+ serializerRef.current = serializer;
157
+ deserializerRef.current = deserializer;
158
+ onErrorRef.current = onError;
159
+ initialValueRef.current = initialValue;
160
+ const attributesRef = (0, import_react.useRef)({});
161
+ attributesRef.current = { expires, maxAge, path, domain, secure, sameSite };
162
+ const cacheRef = (0, import_react.useRef)(null);
163
+ const isClient = isDocumentAvailable();
164
+ const subscribeToStore = (0, import_react.useCallback)(
165
+ (onStoreChange) => {
166
+ return subscribe(key, onStoreChange);
167
+ },
168
+ [key]
169
+ );
170
+ const getSnapshot = (0, import_react.useCallback)(() => {
171
+ if (!isClient) {
172
+ return resolveInitialValue(initialValueRef.current);
173
+ }
174
+ try {
175
+ const rawValue = readRawCookie(key);
176
+ if (cacheRef.current && cacheRef.current.rawValue === rawValue) {
177
+ return cacheRef.current.parsedValue;
178
+ }
179
+ let parsedValue;
180
+ if (rawValue !== void 0) {
181
+ parsedValue = deserializerRef.current(rawValue);
182
+ } else {
183
+ parsedValue = resolveInitialValue(initialValueRef.current);
184
+ }
185
+ cacheRef.current = { rawValue, parsedValue };
186
+ return parsedValue;
187
+ } catch (error) {
188
+ onErrorRef.current?.(error);
189
+ const fallbackValue = resolveInitialValue(initialValueRef.current);
190
+ cacheRef.current = { rawValue: void 0, parsedValue: fallbackValue };
191
+ return fallbackValue;
192
+ }
193
+ }, [key, isClient]);
194
+ const getServerSnapshot = (0, import_react.useCallback)(() => {
195
+ return resolveInitialValue(initialValueRef.current);
196
+ }, []);
197
+ const storedValue = (0, import_react.useSyncExternalStore)(
198
+ subscribeToStore,
199
+ getSnapshot,
200
+ getServerSnapshot
201
+ );
202
+ const setValue = (0, import_react.useCallback)(
203
+ (value) => {
204
+ try {
205
+ if (!isDocumentAvailable()) return;
206
+ const currentValue = (() => {
207
+ try {
208
+ const raw = readRawCookie(key);
209
+ if (raw !== void 0) {
210
+ return deserializerRef.current(raw);
211
+ }
212
+ return resolveInitialValue(initialValueRef.current);
213
+ } catch {
214
+ return resolveInitialValue(initialValueRef.current);
215
+ }
216
+ })();
217
+ const valueToStore = value instanceof Function ? value(currentValue) : value;
218
+ const serialized = serializerRef.current(valueToStore);
219
+ document.cookie = buildCookieString(
220
+ key,
221
+ serialized,
222
+ attributesRef.current
223
+ );
224
+ cacheRef.current = { rawValue: serialized, parsedValue: valueToStore };
225
+ notifyListeners(key);
226
+ } catch (error) {
227
+ onErrorRef.current?.(error);
228
+ }
229
+ },
230
+ [key]
231
+ );
232
+ const removeValue = (0, import_react.useCallback)(() => {
233
+ try {
234
+ if (!isDocumentAvailable()) return;
235
+ document.cookie = buildRemovalCookieString(key, attributesRef.current);
236
+ const initialVal = resolveInitialValue(initialValueRef.current);
237
+ cacheRef.current = { rawValue: void 0, parsedValue: initialVal };
238
+ notifyListeners(key);
239
+ } catch (error) {
240
+ onErrorRef.current?.(error);
241
+ }
242
+ }, [key]);
243
+ return [storedValue, setValue, removeValue];
244
+ }
245
+ // Annotate the CommonJS export names for ESM import in node:
246
+ 0 && (module.exports = {
247
+ buildCookieString,
248
+ buildRemovalCookieString,
249
+ parseCookie,
250
+ useCookie
251
+ });
252
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/useCookie.ts","../src/store.ts","../src/utils.ts"],"sourcesContent":["export {\n useCookie,\n type UseCookieOptions,\n type UseCookieReturn,\n type InitialValue,\n type CookieAttributes,\n type SameSite,\n} from \"./useCookie\";\n\nexport {\n buildCookieString,\n buildRemovalCookieString,\n parseCookie,\n} from \"./utils\";\n","import { useCallback, useRef, useSyncExternalStore } from \"react\";\nimport { subscribe, notifyListeners } from \"./store\";\nimport {\n buildCookieString,\n buildRemovalCookieString,\n defaultDeserializer,\n defaultSerializer,\n isDocumentAvailable,\n readRawCookie,\n type CookieAttributes,\n} from \"./utils\";\n\nexport type { CookieAttributes, SameSite } from \"./utils\";\n\n/**\n * Type for the initial value that can be a value or a function returning a value\n * (lazy initialization).\n */\nexport type InitialValue<T> = T | (() => T);\n\n/**\n * Options for the {@link useCookie} hook. Combines serialization/lifecycle\n * options (aligned with `@usefy/use-local-storage`) with the cookie-specific\n * write attributes from {@link CookieAttributes}.\n */\nexport interface UseCookieOptions<T> extends CookieAttributes {\n /**\n * Initial value used when the cookie is absent (or on the server). Supports a\n * lazy initializer function. This is also the value the state resets to when\n * {@link UseCookieReturn} `remove()` is called.\n */\n initialValue?: InitialValue<T>;\n /**\n * Custom serializer for converting the value to the stored cookie string.\n * @default JSON.stringify\n */\n serializer?: (value: T) => string;\n /**\n * Custom deserializer for parsing the stored cookie string back to a value.\n * The default tries `JSON.parse` and **falls back to the raw string** for\n * non-JSON cookies (so a plain `foo=bar` cookie never throws).\n * @default (value) => { try { return JSON.parse(value) } catch { return value } }\n */\n deserializer?: (value: string) => T;\n /** Callback invoked when a serialization/deserialization error occurs. */\n onError?: (error: Error) => void;\n}\n\n/**\n * Return type for {@link useCookie} — a tuple similar to `useState`.\n */\nexport type UseCookieReturn<T> = readonly [\n /** Current cookie value (`undefined` when the cookie is absent and no `initialValue` was given). */\n T | undefined,\n /** Update the cookie value. Accepts a value or a functional updater `(prev) => next`. */\n React.Dispatch<React.SetStateAction<T | undefined>>,\n /** Delete the cookie and reset state to `initialValue` (or `undefined`). */\n () => void\n];\n\n/**\n * Resolve an initial value, supporting a lazy initializer function.\n */\nfunction resolveInitialValue<T>(\n initialValue: InitialValue<T> | undefined\n): T | undefined {\n return typeof initialValue === \"function\"\n ? (initialValue as () => T)()\n : initialValue;\n}\n\n/**\n * A hook for reading and writing a browser **cookie** as React state — the\n * cookie sibling of `@usefy/use-local-storage` and `@usefy/use-session-storage`.\n * Works like `useState` but the value is persisted in `document.cookie`.\n *\n * Features:\n * - **`useState`-like tuple**: `[value, setValue, remove]`.\n * - **Same-document sync**: multiple `useCookie(key)` instances stay in sync.\n * - **SSR-safe**: guards all `document` access and returns `initialValue` on the\n * server (uses `useSyncExternalStore`, identical to its storage siblings, to\n * avoid hydration mismatches).\n * - **Cookie attributes**: `expires`, `maxAge`, `path`, `domain`, `secure`,\n * `sameSite`.\n * - **Safe (de)serialization**: JSON by default, with a graceful fallback to the\n * raw string for plain (non-JSON) cookies.\n *\n * > **Cross-tab note:** cookies have no `storage` event, so writes in *other*\n * > tabs are not observed without polling. Same-document instances always sync.\n *\n * @template T - The type of the stored value\n * @param key - The cookie name\n * @param options - Serialization, initial value, and cookie write attributes\n * @returns A tuple of `[value, setValue, remove]`\n *\n * @example\n * ```tsx\n * // Basic usage — a string cookie\n * function ThemeToggle() {\n * const [theme, setTheme, removeTheme] = useCookie(\"theme\", {\n * initialValue: \"light\",\n * });\n *\n * return (\n * <div>\n * <p>Current theme: {theme}</p>\n * <button onClick={() => setTheme(\"dark\")}>Dark</button>\n * <button onClick={removeTheme}>Reset</button>\n * </div>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With cookie attributes — persist for a week over HTTPS only\n * const [token, setToken] = useCookie<string>(\"token\", {\n * expires: 7, // days (or a Date)\n * path: \"/\",\n * secure: true,\n * sameSite: \"strict\",\n * });\n * ```\n *\n * @example\n * ```tsx\n * // Object value with a functional update (JSON serialized)\n * interface Prefs { lang: string; compact: boolean }\n * const [prefs, setPrefs] = useCookie<Prefs>(\"prefs\", {\n * initialValue: { lang: \"en\", compact: false },\n * });\n * setPrefs((prev) => ({ ...prev!, compact: true }));\n * ```\n */\nexport function useCookie<T = string>(\n key: string,\n options: UseCookieOptions<T> = {}\n): UseCookieReturn<T> {\n const {\n initialValue,\n serializer = defaultSerializer,\n deserializer = defaultDeserializer,\n onError,\n expires,\n maxAge,\n path,\n domain,\n secure,\n sameSite,\n } = options;\n\n // Store options in refs for stable references and access to the latest values.\n const serializerRef = useRef(serializer);\n const deserializerRef = useRef(deserializer);\n const onErrorRef = useRef(onError);\n const initialValueRef = useRef(initialValue);\n\n serializerRef.current = serializer;\n deserializerRef.current = deserializer;\n onErrorRef.current = onError;\n initialValueRef.current = initialValue;\n\n // Latest cookie write attributes, so setValue/remove stay stable across renders.\n const attributesRef = useRef<CookieAttributes>({});\n attributesRef.current = { expires, maxAge, path, domain, secure, sameSite };\n\n // Cache for getSnapshot so it returns a stable reference when the raw cookie\n // value is unchanged — required by useSyncExternalStore to avoid loops.\n const cacheRef = useRef<{\n rawValue: string | undefined;\n parsedValue: T | undefined;\n } | null>(null);\n\n const isClient = isDocumentAvailable();\n\n // Subscribe to same-document changes via the internal store.\n const subscribeToStore = useCallback(\n (onStoreChange: () => void) => {\n return subscribe(key, onStoreChange);\n },\n [key]\n );\n\n // getSnapshot: read the current value from document.cookie with caching.\n const getSnapshot = useCallback((): T | undefined => {\n if (!isClient) {\n return resolveInitialValue(initialValueRef.current);\n }\n\n try {\n const rawValue = readRawCookie(key);\n\n if (cacheRef.current && cacheRef.current.rawValue === rawValue) {\n return cacheRef.current.parsedValue;\n }\n\n let parsedValue: T | undefined;\n if (rawValue !== undefined) {\n parsedValue = deserializerRef.current(rawValue);\n } else {\n parsedValue = resolveInitialValue(initialValueRef.current);\n }\n\n cacheRef.current = { rawValue, parsedValue };\n return parsedValue;\n } catch (error) {\n onErrorRef.current?.(error as Error);\n const fallbackValue = resolveInitialValue(initialValueRef.current);\n cacheRef.current = { rawValue: undefined, parsedValue: fallbackValue };\n return fallbackValue;\n }\n }, [key, isClient]);\n\n // getServerSnapshot: return the initial value for SSR.\n const getServerSnapshot = useCallback((): T | undefined => {\n return resolveInitialValue(initialValueRef.current);\n }, []);\n\n const storedValue = useSyncExternalStore(\n subscribeToStore,\n getSnapshot,\n getServerSnapshot\n );\n\n // setValue — stable reference that writes the cookie and notifies listeners.\n const setValue = useCallback<React.Dispatch<React.SetStateAction<T | undefined>>>(\n (value) => {\n try {\n if (!isDocumentAvailable()) return;\n\n // Resolve the current value for functional updates.\n const currentValue = (() => {\n try {\n const raw = readRawCookie(key);\n if (raw !== undefined) {\n return deserializerRef.current(raw);\n }\n return resolveInitialValue(initialValueRef.current);\n } catch {\n return resolveInitialValue(initialValueRef.current);\n }\n })();\n\n const valueToStore =\n value instanceof Function ? value(currentValue) : value;\n\n const serialized = serializerRef.current(valueToStore as T);\n document.cookie = buildCookieString(\n key,\n serialized,\n attributesRef.current\n );\n\n // Invalidate cache so the next getSnapshot reads the fresh value.\n cacheRef.current = { rawValue: serialized, parsedValue: valueToStore };\n\n notifyListeners(key);\n } catch (error) {\n onErrorRef.current?.(error as Error);\n }\n },\n [key]\n );\n\n // remove — stable reference that deletes the cookie and resets to initial.\n const removeValue = useCallback(() => {\n try {\n if (!isDocumentAvailable()) return;\n\n document.cookie = buildRemovalCookieString(key, attributesRef.current);\n\n const initialVal = resolveInitialValue(initialValueRef.current);\n cacheRef.current = { rawValue: undefined, parsedValue: initialVal };\n\n notifyListeners(key);\n } catch (error) {\n onErrorRef.current?.(error as Error);\n }\n }, [key]);\n\n return [storedValue, setValue, removeValue] as const;\n}\n","/**\n * Internal Store Manager for cookie synchronization.\n * This module manages listeners for same-document synchronization across\n * components using the same cookie key.\n *\n * Unlike `localStorage`, cookies have no native `storage` event, so there is no\n * way to observe cross-tab writes without polling. This store therefore keeps\n * every `useCookie(key)` instance **within the same document** in sync: when one\n * instance writes or removes a cookie it notifies the others. Cross-tab\n * synchronization is intentionally not attempted (see the README).\n *\n * @internal This module is not exported publicly\n */\n\n/** Map of key -> Set of listener callbacks */\nconst listeners = new Map<string, Set<() => void>>();\n\n/**\n * Subscribe a listener to changes for a specific key\n * @param key - The cookie key to subscribe to\n * @param listener - Callback to invoke when the key's value changes\n * @returns Unsubscribe function\n */\nexport function subscribe(key: string, listener: () => void): () => void {\n if (!listeners.has(key)) {\n listeners.set(key, new Set());\n }\n\n const keyListeners = listeners.get(key)!;\n keyListeners.add(listener);\n\n return () => {\n keyListeners.delete(listener);\n\n // Cleanup: remove the key entry if no more listeners\n if (keyListeners.size === 0) {\n listeners.delete(key);\n }\n };\n}\n\n/**\n * Notify all listeners subscribed to a specific key.\n * This is called when setValue or removeValue is invoked to synchronize all\n * components using the same key in the same document.\n *\n * @param key - The cookie key that was updated\n */\nexport function notifyListeners(key: string): void {\n const keyListeners = listeners.get(key);\n if (keyListeners) {\n keyListeners.forEach((listener) => listener());\n }\n}\n\n/**\n * Get the count of listeners for a key (for testing purposes)\n * @internal\n */\nexport function getListenerCount(key: string): number {\n return listeners.get(key)?.size ?? 0;\n}\n\n/**\n * Clear all listeners (for testing purposes)\n * @internal\n */\nexport function clearAllListeners(): void {\n listeners.clear();\n}\n","/**\n * Pure, framework-agnostic helpers for reading and writing `document.cookie`.\n * These are unit-tested in isolation and used by {@link useCookie}.\n */\n\n/**\n * The `SameSite` cookie attribute values.\n *\n * - `\"strict\"` — cookie is sent only for same-site requests.\n * - `\"lax\"` — cookie is sent on same-site requests and top-level navigations.\n * - `\"none\"` — cookie is sent on all requests; **requires `secure: true`**.\n */\nexport type SameSite = \"strict\" | \"lax\" | \"none\";\n\n/**\n * Cookie write attributes, mirroring the standard `Set-Cookie` directives.\n * These control how the cookie is persisted by the browser.\n */\nexport interface CookieAttributes {\n /**\n * Expiry of the cookie. Either an absolute `Date`, or a number of **days**\n * from now (e.g. `7` = expires in a week). Omit for a session cookie.\n */\n expires?: Date | number;\n /**\n * `Max-Age` in **seconds** from now. Takes precedence over `expires` in\n * browsers that support it. Use `0` or a negative number to expire immediately.\n */\n maxAge?: number;\n /**\n * The URL path the cookie is scoped to.\n * @default \"/\"\n */\n path?: string;\n /** The domain the cookie is scoped to (e.g. `.example.com`). */\n domain?: string;\n /** When `true`, the cookie is only sent over HTTPS. */\n secure?: boolean;\n /** The `SameSite` policy. `\"none\"` requires `secure: true`. */\n sameSite?: SameSite;\n}\n\n/** `true` when running in an environment that exposes `document`. */\nexport function isDocumentAvailable(): boolean {\n return typeof document !== \"undefined\";\n}\n\n/** Capitalize the first letter (e.g. `\"lax\"` → `\"Lax\"`) for the `SameSite` directive. */\nfunction capitalizeSameSite(value: SameSite): string {\n return value.charAt(0).toUpperCase() + value.slice(1);\n}\n\n/**\n * Default serializer — JSON-encodes any value.\n * @default JSON.stringify\n */\nexport function defaultSerializer<T>(value: T): string {\n return JSON.stringify(value);\n}\n\n/**\n * Default deserializer — tries `JSON.parse`, and **falls back to the raw\n * string** if the value is not valid JSON. Cookies are very often plain,\n * non-JSON strings (set by a server or another library), so this never throws\n * on a legacy/plain value: `\"bar\"` stays `\"bar\"`, while `'{\"a\":1}'` becomes\n * `{ a: 1 }`.\n */\nexport function defaultDeserializer<T>(value: string): T {\n try {\n return JSON.parse(value) as T;\n } catch {\n return value as unknown as T;\n }\n}\n\n/**\n * Parse a raw `document.cookie` string and return the **decoded** value for\n * `key`, or `undefined` when the key is absent. Pure — does not touch\n * `document`, so it is trivially testable.\n *\n * @param cookieString - The raw `document.cookie` value (e.g. `\"a=1; b=2\"`).\n * @param key - The (un-encoded) cookie name to look up.\n */\nexport function parseCookie(\n cookieString: string,\n key: string\n): string | undefined {\n if (!cookieString) return undefined;\n\n const pairs = cookieString.split(\"; \");\n for (const pair of pairs) {\n const eqIndex = pair.indexOf(\"=\");\n const rawName = eqIndex > -1 ? pair.slice(0, eqIndex) : pair;\n\n let name: string;\n try {\n name = decodeURIComponent(rawName);\n } catch {\n name = rawName;\n }\n\n if (name === key) {\n const rawValue = eqIndex > -1 ? pair.slice(eqIndex + 1) : \"\";\n try {\n return decodeURIComponent(rawValue);\n } catch {\n return rawValue;\n }\n }\n }\n\n return undefined;\n}\n\n/**\n * Read the decoded value of a cookie from `document.cookie`, guarding for SSR.\n * Returns `undefined` on the server or when the cookie is absent.\n */\nexport function readRawCookie(key: string): string | undefined {\n if (!isDocumentAvailable()) return undefined;\n return parseCookie(document.cookie, key);\n}\n\n/**\n * Build a `document.cookie` assignment string for a cookie, encoding the name\n * and value and appending the standard attributes. Pure and testable.\n *\n * @example\n * buildCookieString(\"theme\", \"dark\", { path: \"/\", maxAge: 3600 })\n * // => \"theme=dark; Path=/; Max-Age=3600\"\n */\nexport function buildCookieString(\n name: string,\n value: string,\n attributes: CookieAttributes = {}\n): string {\n const { expires, maxAge, path = \"/\", domain, secure, sameSite } = attributes;\n\n let str = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;\n\n if (expires !== undefined) {\n const date =\n typeof expires === \"number\"\n ? new Date(Date.now() + expires * 864e5)\n : expires;\n str += `; Expires=${date.toUTCString()}`;\n }\n\n if (maxAge !== undefined) {\n str += `; Max-Age=${maxAge}`;\n }\n\n if (path) {\n str += `; Path=${path}`;\n }\n\n if (domain) {\n str += `; Domain=${domain}`;\n }\n\n if (secure) {\n str += \"; Secure\";\n }\n\n if (sameSite) {\n str += `; SameSite=${capitalizeSameSite(sameSite)}`;\n }\n\n return str;\n}\n\n/**\n * Build the `document.cookie` assignment string that **deletes** a cookie. The\n * value is emptied and the cookie is expired in the past (`Max-Age=-1` plus an\n * epoch `Expires`). The same `path`/`domain` used to write it must be provided,\n * otherwise the browser treats it as a different cookie and deletion is a no-op.\n */\nexport function buildRemovalCookieString(\n name: string,\n attributes: Pick<CookieAttributes, \"path\" | \"domain\"> = {}\n): string {\n const { path = \"/\", domain } = attributes;\n\n let str = `${encodeURIComponent(name)}=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=-1`;\n\n if (path) {\n str += `; Path=${path}`;\n }\n\n if (domain) {\n str += `; Domain=${domain}`;\n }\n\n return str;\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,222 @@
1
+ // src/useCookie.ts
2
+ import { useCallback, useRef, useSyncExternalStore } from "react";
3
+
4
+ // src/store.ts
5
+ var listeners = /* @__PURE__ */ new Map();
6
+ function subscribe(key, listener) {
7
+ if (!listeners.has(key)) {
8
+ listeners.set(key, /* @__PURE__ */ new Set());
9
+ }
10
+ const keyListeners = listeners.get(key);
11
+ keyListeners.add(listener);
12
+ return () => {
13
+ keyListeners.delete(listener);
14
+ if (keyListeners.size === 0) {
15
+ listeners.delete(key);
16
+ }
17
+ };
18
+ }
19
+ function notifyListeners(key) {
20
+ const keyListeners = listeners.get(key);
21
+ if (keyListeners) {
22
+ keyListeners.forEach((listener) => listener());
23
+ }
24
+ }
25
+
26
+ // src/utils.ts
27
+ function isDocumentAvailable() {
28
+ return typeof document !== "undefined";
29
+ }
30
+ function capitalizeSameSite(value) {
31
+ return value.charAt(0).toUpperCase() + value.slice(1);
32
+ }
33
+ function defaultSerializer(value) {
34
+ return JSON.stringify(value);
35
+ }
36
+ function defaultDeserializer(value) {
37
+ try {
38
+ return JSON.parse(value);
39
+ } catch {
40
+ return value;
41
+ }
42
+ }
43
+ function parseCookie(cookieString, key) {
44
+ if (!cookieString) return void 0;
45
+ const pairs = cookieString.split("; ");
46
+ for (const pair of pairs) {
47
+ const eqIndex = pair.indexOf("=");
48
+ const rawName = eqIndex > -1 ? pair.slice(0, eqIndex) : pair;
49
+ let name;
50
+ try {
51
+ name = decodeURIComponent(rawName);
52
+ } catch {
53
+ name = rawName;
54
+ }
55
+ if (name === key) {
56
+ const rawValue = eqIndex > -1 ? pair.slice(eqIndex + 1) : "";
57
+ try {
58
+ return decodeURIComponent(rawValue);
59
+ } catch {
60
+ return rawValue;
61
+ }
62
+ }
63
+ }
64
+ return void 0;
65
+ }
66
+ function readRawCookie(key) {
67
+ if (!isDocumentAvailable()) return void 0;
68
+ return parseCookie(document.cookie, key);
69
+ }
70
+ function buildCookieString(name, value, attributes = {}) {
71
+ const { expires, maxAge, path = "/", domain, secure, sameSite } = attributes;
72
+ let str = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
73
+ if (expires !== void 0) {
74
+ const date = typeof expires === "number" ? new Date(Date.now() + expires * 864e5) : expires;
75
+ str += `; Expires=${date.toUTCString()}`;
76
+ }
77
+ if (maxAge !== void 0) {
78
+ str += `; Max-Age=${maxAge}`;
79
+ }
80
+ if (path) {
81
+ str += `; Path=${path}`;
82
+ }
83
+ if (domain) {
84
+ str += `; Domain=${domain}`;
85
+ }
86
+ if (secure) {
87
+ str += "; Secure";
88
+ }
89
+ if (sameSite) {
90
+ str += `; SameSite=${capitalizeSameSite(sameSite)}`;
91
+ }
92
+ return str;
93
+ }
94
+ function buildRemovalCookieString(name, attributes = {}) {
95
+ const { path = "/", domain } = attributes;
96
+ let str = `${encodeURIComponent(name)}=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=-1`;
97
+ if (path) {
98
+ str += `; Path=${path}`;
99
+ }
100
+ if (domain) {
101
+ str += `; Domain=${domain}`;
102
+ }
103
+ return str;
104
+ }
105
+
106
+ // src/useCookie.ts
107
+ function resolveInitialValue(initialValue) {
108
+ return typeof initialValue === "function" ? initialValue() : initialValue;
109
+ }
110
+ function useCookie(key, options = {}) {
111
+ const {
112
+ initialValue,
113
+ serializer = defaultSerializer,
114
+ deserializer = defaultDeserializer,
115
+ onError,
116
+ expires,
117
+ maxAge,
118
+ path,
119
+ domain,
120
+ secure,
121
+ sameSite
122
+ } = options;
123
+ const serializerRef = useRef(serializer);
124
+ const deserializerRef = useRef(deserializer);
125
+ const onErrorRef = useRef(onError);
126
+ const initialValueRef = useRef(initialValue);
127
+ serializerRef.current = serializer;
128
+ deserializerRef.current = deserializer;
129
+ onErrorRef.current = onError;
130
+ initialValueRef.current = initialValue;
131
+ const attributesRef = useRef({});
132
+ attributesRef.current = { expires, maxAge, path, domain, secure, sameSite };
133
+ const cacheRef = useRef(null);
134
+ const isClient = isDocumentAvailable();
135
+ const subscribeToStore = useCallback(
136
+ (onStoreChange) => {
137
+ return subscribe(key, onStoreChange);
138
+ },
139
+ [key]
140
+ );
141
+ const getSnapshot = useCallback(() => {
142
+ if (!isClient) {
143
+ return resolveInitialValue(initialValueRef.current);
144
+ }
145
+ try {
146
+ const rawValue = readRawCookie(key);
147
+ if (cacheRef.current && cacheRef.current.rawValue === rawValue) {
148
+ return cacheRef.current.parsedValue;
149
+ }
150
+ let parsedValue;
151
+ if (rawValue !== void 0) {
152
+ parsedValue = deserializerRef.current(rawValue);
153
+ } else {
154
+ parsedValue = resolveInitialValue(initialValueRef.current);
155
+ }
156
+ cacheRef.current = { rawValue, parsedValue };
157
+ return parsedValue;
158
+ } catch (error) {
159
+ onErrorRef.current?.(error);
160
+ const fallbackValue = resolveInitialValue(initialValueRef.current);
161
+ cacheRef.current = { rawValue: void 0, parsedValue: fallbackValue };
162
+ return fallbackValue;
163
+ }
164
+ }, [key, isClient]);
165
+ const getServerSnapshot = useCallback(() => {
166
+ return resolveInitialValue(initialValueRef.current);
167
+ }, []);
168
+ const storedValue = useSyncExternalStore(
169
+ subscribeToStore,
170
+ getSnapshot,
171
+ getServerSnapshot
172
+ );
173
+ const setValue = useCallback(
174
+ (value) => {
175
+ try {
176
+ if (!isDocumentAvailable()) return;
177
+ const currentValue = (() => {
178
+ try {
179
+ const raw = readRawCookie(key);
180
+ if (raw !== void 0) {
181
+ return deserializerRef.current(raw);
182
+ }
183
+ return resolveInitialValue(initialValueRef.current);
184
+ } catch {
185
+ return resolveInitialValue(initialValueRef.current);
186
+ }
187
+ })();
188
+ const valueToStore = value instanceof Function ? value(currentValue) : value;
189
+ const serialized = serializerRef.current(valueToStore);
190
+ document.cookie = buildCookieString(
191
+ key,
192
+ serialized,
193
+ attributesRef.current
194
+ );
195
+ cacheRef.current = { rawValue: serialized, parsedValue: valueToStore };
196
+ notifyListeners(key);
197
+ } catch (error) {
198
+ onErrorRef.current?.(error);
199
+ }
200
+ },
201
+ [key]
202
+ );
203
+ const removeValue = useCallback(() => {
204
+ try {
205
+ if (!isDocumentAvailable()) return;
206
+ document.cookie = buildRemovalCookieString(key, attributesRef.current);
207
+ const initialVal = resolveInitialValue(initialValueRef.current);
208
+ cacheRef.current = { rawValue: void 0, parsedValue: initialVal };
209
+ notifyListeners(key);
210
+ } catch (error) {
211
+ onErrorRef.current?.(error);
212
+ }
213
+ }, [key]);
214
+ return [storedValue, setValue, removeValue];
215
+ }
216
+ export {
217
+ buildCookieString,
218
+ buildRemovalCookieString,
219
+ parseCookie,
220
+ useCookie
221
+ };
222
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useCookie.ts","../src/store.ts","../src/utils.ts"],"sourcesContent":["import { useCallback, useRef, useSyncExternalStore } from \"react\";\nimport { subscribe, notifyListeners } from \"./store\";\nimport {\n buildCookieString,\n buildRemovalCookieString,\n defaultDeserializer,\n defaultSerializer,\n isDocumentAvailable,\n readRawCookie,\n type CookieAttributes,\n} from \"./utils\";\n\nexport type { CookieAttributes, SameSite } from \"./utils\";\n\n/**\n * Type for the initial value that can be a value or a function returning a value\n * (lazy initialization).\n */\nexport type InitialValue<T> = T | (() => T);\n\n/**\n * Options for the {@link useCookie} hook. Combines serialization/lifecycle\n * options (aligned with `@usefy/use-local-storage`) with the cookie-specific\n * write attributes from {@link CookieAttributes}.\n */\nexport interface UseCookieOptions<T> extends CookieAttributes {\n /**\n * Initial value used when the cookie is absent (or on the server). Supports a\n * lazy initializer function. This is also the value the state resets to when\n * {@link UseCookieReturn} `remove()` is called.\n */\n initialValue?: InitialValue<T>;\n /**\n * Custom serializer for converting the value to the stored cookie string.\n * @default JSON.stringify\n */\n serializer?: (value: T) => string;\n /**\n * Custom deserializer for parsing the stored cookie string back to a value.\n * The default tries `JSON.parse` and **falls back to the raw string** for\n * non-JSON cookies (so a plain `foo=bar` cookie never throws).\n * @default (value) => { try { return JSON.parse(value) } catch { return value } }\n */\n deserializer?: (value: string) => T;\n /** Callback invoked when a serialization/deserialization error occurs. */\n onError?: (error: Error) => void;\n}\n\n/**\n * Return type for {@link useCookie} — a tuple similar to `useState`.\n */\nexport type UseCookieReturn<T> = readonly [\n /** Current cookie value (`undefined` when the cookie is absent and no `initialValue` was given). */\n T | undefined,\n /** Update the cookie value. Accepts a value or a functional updater `(prev) => next`. */\n React.Dispatch<React.SetStateAction<T | undefined>>,\n /** Delete the cookie and reset state to `initialValue` (or `undefined`). */\n () => void\n];\n\n/**\n * Resolve an initial value, supporting a lazy initializer function.\n */\nfunction resolveInitialValue<T>(\n initialValue: InitialValue<T> | undefined\n): T | undefined {\n return typeof initialValue === \"function\"\n ? (initialValue as () => T)()\n : initialValue;\n}\n\n/**\n * A hook for reading and writing a browser **cookie** as React state — the\n * cookie sibling of `@usefy/use-local-storage` and `@usefy/use-session-storage`.\n * Works like `useState` but the value is persisted in `document.cookie`.\n *\n * Features:\n * - **`useState`-like tuple**: `[value, setValue, remove]`.\n * - **Same-document sync**: multiple `useCookie(key)` instances stay in sync.\n * - **SSR-safe**: guards all `document` access and returns `initialValue` on the\n * server (uses `useSyncExternalStore`, identical to its storage siblings, to\n * avoid hydration mismatches).\n * - **Cookie attributes**: `expires`, `maxAge`, `path`, `domain`, `secure`,\n * `sameSite`.\n * - **Safe (de)serialization**: JSON by default, with a graceful fallback to the\n * raw string for plain (non-JSON) cookies.\n *\n * > **Cross-tab note:** cookies have no `storage` event, so writes in *other*\n * > tabs are not observed without polling. Same-document instances always sync.\n *\n * @template T - The type of the stored value\n * @param key - The cookie name\n * @param options - Serialization, initial value, and cookie write attributes\n * @returns A tuple of `[value, setValue, remove]`\n *\n * @example\n * ```tsx\n * // Basic usage — a string cookie\n * function ThemeToggle() {\n * const [theme, setTheme, removeTheme] = useCookie(\"theme\", {\n * initialValue: \"light\",\n * });\n *\n * return (\n * <div>\n * <p>Current theme: {theme}</p>\n * <button onClick={() => setTheme(\"dark\")}>Dark</button>\n * <button onClick={removeTheme}>Reset</button>\n * </div>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With cookie attributes — persist for a week over HTTPS only\n * const [token, setToken] = useCookie<string>(\"token\", {\n * expires: 7, // days (or a Date)\n * path: \"/\",\n * secure: true,\n * sameSite: \"strict\",\n * });\n * ```\n *\n * @example\n * ```tsx\n * // Object value with a functional update (JSON serialized)\n * interface Prefs { lang: string; compact: boolean }\n * const [prefs, setPrefs] = useCookie<Prefs>(\"prefs\", {\n * initialValue: { lang: \"en\", compact: false },\n * });\n * setPrefs((prev) => ({ ...prev!, compact: true }));\n * ```\n */\nexport function useCookie<T = string>(\n key: string,\n options: UseCookieOptions<T> = {}\n): UseCookieReturn<T> {\n const {\n initialValue,\n serializer = defaultSerializer,\n deserializer = defaultDeserializer,\n onError,\n expires,\n maxAge,\n path,\n domain,\n secure,\n sameSite,\n } = options;\n\n // Store options in refs for stable references and access to the latest values.\n const serializerRef = useRef(serializer);\n const deserializerRef = useRef(deserializer);\n const onErrorRef = useRef(onError);\n const initialValueRef = useRef(initialValue);\n\n serializerRef.current = serializer;\n deserializerRef.current = deserializer;\n onErrorRef.current = onError;\n initialValueRef.current = initialValue;\n\n // Latest cookie write attributes, so setValue/remove stay stable across renders.\n const attributesRef = useRef<CookieAttributes>({});\n attributesRef.current = { expires, maxAge, path, domain, secure, sameSite };\n\n // Cache for getSnapshot so it returns a stable reference when the raw cookie\n // value is unchanged — required by useSyncExternalStore to avoid loops.\n const cacheRef = useRef<{\n rawValue: string | undefined;\n parsedValue: T | undefined;\n } | null>(null);\n\n const isClient = isDocumentAvailable();\n\n // Subscribe to same-document changes via the internal store.\n const subscribeToStore = useCallback(\n (onStoreChange: () => void) => {\n return subscribe(key, onStoreChange);\n },\n [key]\n );\n\n // getSnapshot: read the current value from document.cookie with caching.\n const getSnapshot = useCallback((): T | undefined => {\n if (!isClient) {\n return resolveInitialValue(initialValueRef.current);\n }\n\n try {\n const rawValue = readRawCookie(key);\n\n if (cacheRef.current && cacheRef.current.rawValue === rawValue) {\n return cacheRef.current.parsedValue;\n }\n\n let parsedValue: T | undefined;\n if (rawValue !== undefined) {\n parsedValue = deserializerRef.current(rawValue);\n } else {\n parsedValue = resolveInitialValue(initialValueRef.current);\n }\n\n cacheRef.current = { rawValue, parsedValue };\n return parsedValue;\n } catch (error) {\n onErrorRef.current?.(error as Error);\n const fallbackValue = resolveInitialValue(initialValueRef.current);\n cacheRef.current = { rawValue: undefined, parsedValue: fallbackValue };\n return fallbackValue;\n }\n }, [key, isClient]);\n\n // getServerSnapshot: return the initial value for SSR.\n const getServerSnapshot = useCallback((): T | undefined => {\n return resolveInitialValue(initialValueRef.current);\n }, []);\n\n const storedValue = useSyncExternalStore(\n subscribeToStore,\n getSnapshot,\n getServerSnapshot\n );\n\n // setValue — stable reference that writes the cookie and notifies listeners.\n const setValue = useCallback<React.Dispatch<React.SetStateAction<T | undefined>>>(\n (value) => {\n try {\n if (!isDocumentAvailable()) return;\n\n // Resolve the current value for functional updates.\n const currentValue = (() => {\n try {\n const raw = readRawCookie(key);\n if (raw !== undefined) {\n return deserializerRef.current(raw);\n }\n return resolveInitialValue(initialValueRef.current);\n } catch {\n return resolveInitialValue(initialValueRef.current);\n }\n })();\n\n const valueToStore =\n value instanceof Function ? value(currentValue) : value;\n\n const serialized = serializerRef.current(valueToStore as T);\n document.cookie = buildCookieString(\n key,\n serialized,\n attributesRef.current\n );\n\n // Invalidate cache so the next getSnapshot reads the fresh value.\n cacheRef.current = { rawValue: serialized, parsedValue: valueToStore };\n\n notifyListeners(key);\n } catch (error) {\n onErrorRef.current?.(error as Error);\n }\n },\n [key]\n );\n\n // remove — stable reference that deletes the cookie and resets to initial.\n const removeValue = useCallback(() => {\n try {\n if (!isDocumentAvailable()) return;\n\n document.cookie = buildRemovalCookieString(key, attributesRef.current);\n\n const initialVal = resolveInitialValue(initialValueRef.current);\n cacheRef.current = { rawValue: undefined, parsedValue: initialVal };\n\n notifyListeners(key);\n } catch (error) {\n onErrorRef.current?.(error as Error);\n }\n }, [key]);\n\n return [storedValue, setValue, removeValue] as const;\n}\n","/**\n * Internal Store Manager for cookie synchronization.\n * This module manages listeners for same-document synchronization across\n * components using the same cookie key.\n *\n * Unlike `localStorage`, cookies have no native `storage` event, so there is no\n * way to observe cross-tab writes without polling. This store therefore keeps\n * every `useCookie(key)` instance **within the same document** in sync: when one\n * instance writes or removes a cookie it notifies the others. Cross-tab\n * synchronization is intentionally not attempted (see the README).\n *\n * @internal This module is not exported publicly\n */\n\n/** Map of key -> Set of listener callbacks */\nconst listeners = new Map<string, Set<() => void>>();\n\n/**\n * Subscribe a listener to changes for a specific key\n * @param key - The cookie key to subscribe to\n * @param listener - Callback to invoke when the key's value changes\n * @returns Unsubscribe function\n */\nexport function subscribe(key: string, listener: () => void): () => void {\n if (!listeners.has(key)) {\n listeners.set(key, new Set());\n }\n\n const keyListeners = listeners.get(key)!;\n keyListeners.add(listener);\n\n return () => {\n keyListeners.delete(listener);\n\n // Cleanup: remove the key entry if no more listeners\n if (keyListeners.size === 0) {\n listeners.delete(key);\n }\n };\n}\n\n/**\n * Notify all listeners subscribed to a specific key.\n * This is called when setValue or removeValue is invoked to synchronize all\n * components using the same key in the same document.\n *\n * @param key - The cookie key that was updated\n */\nexport function notifyListeners(key: string): void {\n const keyListeners = listeners.get(key);\n if (keyListeners) {\n keyListeners.forEach((listener) => listener());\n }\n}\n\n/**\n * Get the count of listeners for a key (for testing purposes)\n * @internal\n */\nexport function getListenerCount(key: string): number {\n return listeners.get(key)?.size ?? 0;\n}\n\n/**\n * Clear all listeners (for testing purposes)\n * @internal\n */\nexport function clearAllListeners(): void {\n listeners.clear();\n}\n","/**\n * Pure, framework-agnostic helpers for reading and writing `document.cookie`.\n * These are unit-tested in isolation and used by {@link useCookie}.\n */\n\n/**\n * The `SameSite` cookie attribute values.\n *\n * - `\"strict\"` — cookie is sent only for same-site requests.\n * - `\"lax\"` — cookie is sent on same-site requests and top-level navigations.\n * - `\"none\"` — cookie is sent on all requests; **requires `secure: true`**.\n */\nexport type SameSite = \"strict\" | \"lax\" | \"none\";\n\n/**\n * Cookie write attributes, mirroring the standard `Set-Cookie` directives.\n * These control how the cookie is persisted by the browser.\n */\nexport interface CookieAttributes {\n /**\n * Expiry of the cookie. Either an absolute `Date`, or a number of **days**\n * from now (e.g. `7` = expires in a week). Omit for a session cookie.\n */\n expires?: Date | number;\n /**\n * `Max-Age` in **seconds** from now. Takes precedence over `expires` in\n * browsers that support it. Use `0` or a negative number to expire immediately.\n */\n maxAge?: number;\n /**\n * The URL path the cookie is scoped to.\n * @default \"/\"\n */\n path?: string;\n /** The domain the cookie is scoped to (e.g. `.example.com`). */\n domain?: string;\n /** When `true`, the cookie is only sent over HTTPS. */\n secure?: boolean;\n /** The `SameSite` policy. `\"none\"` requires `secure: true`. */\n sameSite?: SameSite;\n}\n\n/** `true` when running in an environment that exposes `document`. */\nexport function isDocumentAvailable(): boolean {\n return typeof document !== \"undefined\";\n}\n\n/** Capitalize the first letter (e.g. `\"lax\"` → `\"Lax\"`) for the `SameSite` directive. */\nfunction capitalizeSameSite(value: SameSite): string {\n return value.charAt(0).toUpperCase() + value.slice(1);\n}\n\n/**\n * Default serializer — JSON-encodes any value.\n * @default JSON.stringify\n */\nexport function defaultSerializer<T>(value: T): string {\n return JSON.stringify(value);\n}\n\n/**\n * Default deserializer — tries `JSON.parse`, and **falls back to the raw\n * string** if the value is not valid JSON. Cookies are very often plain,\n * non-JSON strings (set by a server or another library), so this never throws\n * on a legacy/plain value: `\"bar\"` stays `\"bar\"`, while `'{\"a\":1}'` becomes\n * `{ a: 1 }`.\n */\nexport function defaultDeserializer<T>(value: string): T {\n try {\n return JSON.parse(value) as T;\n } catch {\n return value as unknown as T;\n }\n}\n\n/**\n * Parse a raw `document.cookie` string and return the **decoded** value for\n * `key`, or `undefined` when the key is absent. Pure — does not touch\n * `document`, so it is trivially testable.\n *\n * @param cookieString - The raw `document.cookie` value (e.g. `\"a=1; b=2\"`).\n * @param key - The (un-encoded) cookie name to look up.\n */\nexport function parseCookie(\n cookieString: string,\n key: string\n): string | undefined {\n if (!cookieString) return undefined;\n\n const pairs = cookieString.split(\"; \");\n for (const pair of pairs) {\n const eqIndex = pair.indexOf(\"=\");\n const rawName = eqIndex > -1 ? pair.slice(0, eqIndex) : pair;\n\n let name: string;\n try {\n name = decodeURIComponent(rawName);\n } catch {\n name = rawName;\n }\n\n if (name === key) {\n const rawValue = eqIndex > -1 ? pair.slice(eqIndex + 1) : \"\";\n try {\n return decodeURIComponent(rawValue);\n } catch {\n return rawValue;\n }\n }\n }\n\n return undefined;\n}\n\n/**\n * Read the decoded value of a cookie from `document.cookie`, guarding for SSR.\n * Returns `undefined` on the server or when the cookie is absent.\n */\nexport function readRawCookie(key: string): string | undefined {\n if (!isDocumentAvailable()) return undefined;\n return parseCookie(document.cookie, key);\n}\n\n/**\n * Build a `document.cookie` assignment string for a cookie, encoding the name\n * and value and appending the standard attributes. Pure and testable.\n *\n * @example\n * buildCookieString(\"theme\", \"dark\", { path: \"/\", maxAge: 3600 })\n * // => \"theme=dark; Path=/; Max-Age=3600\"\n */\nexport function buildCookieString(\n name: string,\n value: string,\n attributes: CookieAttributes = {}\n): string {\n const { expires, maxAge, path = \"/\", domain, secure, sameSite } = attributes;\n\n let str = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;\n\n if (expires !== undefined) {\n const date =\n typeof expires === \"number\"\n ? new Date(Date.now() + expires * 864e5)\n : expires;\n str += `; Expires=${date.toUTCString()}`;\n }\n\n if (maxAge !== undefined) {\n str += `; Max-Age=${maxAge}`;\n }\n\n if (path) {\n str += `; Path=${path}`;\n }\n\n if (domain) {\n str += `; Domain=${domain}`;\n }\n\n if (secure) {\n str += \"; Secure\";\n }\n\n if (sameSite) {\n str += `; SameSite=${capitalizeSameSite(sameSite)}`;\n }\n\n return str;\n}\n\n/**\n * Build the `document.cookie` assignment string that **deletes** a cookie. The\n * value is emptied and the cookie is expired in the past (`Max-Age=-1` plus an\n * epoch `Expires`). The same `path`/`domain` used to write it must be provided,\n * otherwise the browser treats it as a different cookie and deletion is a no-op.\n */\nexport function buildRemovalCookieString(\n name: string,\n attributes: Pick<CookieAttributes, \"path\" | \"domain\"> = {}\n): string {\n const { path = \"/\", domain } = attributes;\n\n let str = `${encodeURIComponent(name)}=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=-1`;\n\n if (path) {\n str += `; Path=${path}`;\n }\n\n if (domain) {\n str += `; Domain=${domain}`;\n }\n\n return str;\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@usefy/use-cookie",
3
+ "version": "0.19.0",
4
+ "description": "A React hook for reading and writing browser cookies as React state, SSR-aware",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "sideEffects": false,
19
+ "peerDependencies": {
20
+ "react": "^18.0.0 || ^19.0.0"
21
+ },
22
+ "devDependencies": {
23
+ "@testing-library/jest-dom": "^6.9.1",
24
+ "@testing-library/react": "^16.3.1",
25
+ "@testing-library/user-event": "^14.6.1",
26
+ "@types/react": "^19.0.0",
27
+ "jsdom": "^27.3.0",
28
+ "react": "^19.0.0",
29
+ "rimraf": "^6.0.1",
30
+ "tsup": "^8.0.0",
31
+ "typescript": "^5.0.0",
32
+ "vitest": "^4.0.16"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/mirunamu00/usefy.git",
40
+ "directory": "packages/hooks/use-cookie"
41
+ },
42
+ "license": "MIT",
43
+ "keywords": [
44
+ "react",
45
+ "hooks",
46
+ "cookie",
47
+ "cookies",
48
+ "document.cookie",
49
+ "storage",
50
+ "state",
51
+ "persistence",
52
+ "ssr"
53
+ ],
54
+ "scripts": {
55
+ "build": "tsup",
56
+ "dev": "tsup --watch",
57
+ "test": "vitest run",
58
+ "test:watch": "vitest",
59
+ "typecheck": "tsc --noEmit",
60
+ "clean": "rimraf dist"
61
+ }
62
+ }