@carvajalconsultants/headstart 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cookies.ts ADDED
@@ -0,0 +1,86 @@
1
+ import Cookies from "js-cookie";
2
+
3
+ import { getServerCookie, setServerCookie } from "./serverCookies";
4
+
5
+ import type { CookieSetOptions } from "./serverCookies";
6
+
7
+ /**
8
+ * Retrieves a cookie value by its name, working seamlessly in both client and server environments.
9
+ * This is crucial for applications that need to maintain state or user preferences across page loads
10
+ * and server-side rendering scenarios.
11
+ *
12
+ * @param name - The unique identifier of the cookie you want to retrieve (e.g., 'user_session', 'theme_preference')
13
+ * @returns The value stored in the cookie if it exists, or undefined if the cookie is not found
14
+ *
15
+ * @example
16
+ * // Get user's theme preference
17
+ * const theme = getCookie('theme_preference');
18
+ * if (theme === 'dark') {
19
+ * // Apply dark theme
20
+ * }
21
+ */
22
+ export const getCookie = async (name: string): Promise<string | undefined> => {
23
+ // Check if code is running in browser
24
+ if (typeof window !== "undefined") {
25
+ // Client-side cookie retrieval
26
+ return Cookies.get(name);
27
+ }
28
+
29
+ // Server-side cookie retrieval
30
+ return getServerCookie(name);
31
+ };
32
+
33
+ /**
34
+ * Sets a cookie with the specified name, value, and optional configuration parameters.
35
+ * Essential for storing user preferences, session tokens, or any client-side state that
36
+ * needs to persist across page reloads or browser sessions.
37
+ *
38
+ * @param name - The unique identifier for the cookie (e.g., 'auth_token', 'language_preference')
39
+ * @param value - The data to be stored in the cookie
40
+ * @param options - Configuration object for the cookie
41
+ * @param options.domain - Specifies which domains can access the cookie
42
+ * @param options.expires - When the cookie should expire, either as a Date object or days from now
43
+ * @param options.secure - If true, cookie will only be transmitted over HTTPS
44
+ * @param options.sameSite - Controls how the cookie behaves with cross-site requests
45
+ * @param options.path - The path on the server the cookie is valid for
46
+ *
47
+ * @example
48
+ * // Set a session cookie that expires in 7 days
49
+ * setCookie('session_id', 'abc123', {
50
+ * expires: 7,
51
+ * secure: true,
52
+ * sameSite: 'strict'
53
+ * });
54
+ */
55
+ export const setCookie = async (name: string, value: string, options?: CookieSetOptions) => {
56
+ if (typeof window !== "undefined") {
57
+ // Set cookie on the client side without hitting the server
58
+ Cookies.set(name, value, options);
59
+
60
+ return;
61
+ }
62
+
63
+ // Set cookie on the server side
64
+ await setServerCookie(name, value, options);
65
+ };
66
+
67
+ /**
68
+ * Removes a cookie from the browser, effectively logging out users or clearing stored preferences.
69
+ * Useful for scenarios like user logout, clearing cached data, or resetting user preferences.
70
+ *
71
+ * @param name - The name of the cookie to remove (e.g., 'auth_token', 'user_session')
72
+ *
73
+ * @example
74
+ * // Clear user session during logout
75
+ * removeCookie('auth_token');
76
+ * removeCookie('user_preferences');
77
+ */
78
+ export const removeCookie = async (name: string) => {
79
+ if (typeof window !== "undefined") {
80
+ // Remove cookie on the client side without hitting the server
81
+ return Cookies.remove(name);
82
+ }
83
+
84
+ // Remove cookie on the server side
85
+ return setCookie(name, "", { expires: new Date(0) });
86
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carvajalconsultants/headstart",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Library to assist in integrating PostGraphile with Tanstack Start and URQL.",
5
5
  "license": "MIT",
6
6
  "author": "Miguel Carvajal <omar@carvajalonline.com>",
@@ -9,9 +9,11 @@
9
9
  "main": "server.ts",
10
10
  "files": [
11
11
  "client.ts",
12
+ "cookies.ts",
12
13
  "grafastExchange.ts",
13
14
  "graphQLRouteHandler.ts",
14
- "server.ts"
15
+ "server.ts",
16
+ "serverCookies.ts"
15
17
  ],
16
18
  "scripts": {
17
19
  "lint": "biome check --write"
@@ -0,0 +1,160 @@
1
+ export interface CookieSetOptions {
2
+ /**
3
+ * Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.3|Domain Set-Cookie attribute}. By default, no
4
+ * domain is set, and most clients will consider the cookie to apply to only
5
+ * the current domain.
6
+ */
7
+ domain?: string | undefined;
8
+ /**
9
+ * Specifies a function that will be used to encode a cookie's value. Since
10
+ * value of a cookie has a limited character set (and must be a simple
11
+ * string), this function can be used to encode a value into a string suited
12
+ * for a cookie's value.
13
+ *
14
+ * The default function is the global `encodeURIComponent`, which will
15
+ * encode a JavaScript string into UTF-8 byte sequences and then URL-encode
16
+ * any that fall outside of the cookie range.
17
+ */
18
+ encode?(value: string): string;
19
+ /**
20
+ * Specifies the `Date` object to be the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.1|`Expires` `Set-Cookie` attribute}. By default,
21
+ * no expiration is set, and most clients will consider this a "non-persistent cookie" and will delete
22
+ * it on a condition like exiting a web browser application.
23
+ *
24
+ * *Note* the {@link https://tools.ietf.org/html/rfc6265#section-5.3|cookie storage model specification}
25
+ * states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is
26
+ * possible not all clients by obey this, so if both are set, they should
27
+ * point to the same date and time.
28
+ */
29
+ expires?: Date | number | undefined;
30
+ /**
31
+ * Specifies the boolean value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.6|`HttpOnly` `Set-Cookie` attribute}.
32
+ * When truthy, the `HttpOnly` attribute is set, otherwise it is not. By
33
+ * default, the `HttpOnly` attribute is not set.
34
+ *
35
+ * *Note* be careful when setting this to true, as compliant clients will
36
+ * not allow client-side JavaScript to see the cookie in `document.cookie`.
37
+ */
38
+ httpOnly?: boolean | undefined;
39
+ /**
40
+ * Specifies the number (in seconds) to be the value for the `Max-Age`
41
+ * `Set-Cookie` attribute. The given number will be converted to an integer
42
+ * by rounding down. By default, no maximum age is set.
43
+ *
44
+ * *Note* the {@link https://tools.ietf.org/html/rfc6265#section-5.3|cookie storage model specification}
45
+ * states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is
46
+ * possible not all clients by obey this, so if both are set, they should
47
+ * point to the same date and time.
48
+ */
49
+ maxAge?: number | undefined;
50
+ /**
51
+ * Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.4|`Path` `Set-Cookie` attribute}.
52
+ * By default, the path is considered the "default path".
53
+ */
54
+ path?: string | undefined;
55
+ /**
56
+ * Specifies the `string` to be the value for the [`Priority` `Set-Cookie` attribute][rfc-west-cookie-priority-00-4.1].
57
+ *
58
+ * - `'low'` will set the `Priority` attribute to `Low`.
59
+ * - `'medium'` will set the `Priority` attribute to `Medium`, the default priority when not set.
60
+ * - `'high'` will set the `Priority` attribute to `High`.
61
+ *
62
+ * More information about the different priority levels can be found in
63
+ * [the specification][rfc-west-cookie-priority-00-4.1].
64
+ *
65
+ * **note** This is an attribute that has not yet been fully standardized, and may change in the future.
66
+ * This also means many clients may ignore this attribute until they understand it.
67
+ */
68
+ priority?: "low" | "medium" | "high" | undefined;
69
+ /**
70
+ * Specifies the boolean or string to be the value for the {@link https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|`SameSite` `Set-Cookie` attribute}.
71
+ *
72
+ * - `true` will set the `SameSite` attribute to `Strict` for strict same
73
+ * site enforcement.
74
+ * - `false` will not set the `SameSite` attribute.
75
+ * - `'lax'` will set the `SameSite` attribute to Lax for lax same site
76
+ * enforcement.
77
+ * - `'strict'` will set the `SameSite` attribute to Strict for strict same
78
+ * site enforcement.
79
+ * - `'none'` will set the SameSite attribute to None for an explicit
80
+ * cross-site cookie.
81
+ *
82
+ * More information about the different enforcement levels can be found in {@link https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|the specification}.
83
+ *
84
+ * *note* This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it.
85
+ */
86
+ sameSite?: "lax" | "strict" | "none" | undefined;
87
+ /**
88
+ * Specifies the boolean value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.5|`Secure` `Set-Cookie` attribute}. When truthy, the
89
+ * `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set.
90
+ *
91
+ * *Note* be careful when setting this to `true`, as compliant clients will
92
+ * not send the cookie back to the server in the future if the browser does
93
+ * not have an HTTPS connection.
94
+ */
95
+ secure?: boolean | undefined;
96
+ /**
97
+ * Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](https://datatracker.ietf.org/doc/html/draft-cutler-httpbis-partitioned-cookies#section-2.1)
98
+ * attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the
99
+ * `Partitioned` attribute is not set.
100
+ *
101
+ * **note** This is an attribute that has not yet been fully standardized, and may change in the future.
102
+ * This also means many clients may ignore this attribute until they understand it.
103
+ *
104
+ * More information can be found in the [proposal](https://github.com/privacycg/CHIPS).
105
+ */
106
+ partitioned?: boolean;
107
+ }
108
+
109
+ /**
110
+ * Retrieves a cookie value during server-side rendering (SSR)
111
+ * This is useful when you need to access cookies that were set by the server
112
+ * before the page is sent to the client, such as authentication tokens or user preferences
113
+ *
114
+ * @param name - The name of the cookie to retrieve
115
+ * @returns The cookie value if running on the server, undefined if on the client
116
+ */
117
+ export const getServerCookie = async (name: string) => {
118
+ // Only execute this logic during server-side rendering: https://v3.vitejs.dev/guide/ssr.html#conditional-logic
119
+ if (import.meta.env.SSR) {
120
+ // Dynamically import the cookie getter to avoid loading it on the client bundle
121
+ const { getCookie } = await import("@tanstack/react-start-server");
122
+
123
+ return getCookie(name);
124
+ }
125
+
126
+ return undefined;
127
+ };
128
+
129
+ /**
130
+ * Sets a cookie during server-side rendering (SSR)
131
+ * This is essential for scenarios where you need to set cookies before the initial page load,
132
+ * such as storing session tokens, user preferences, or other server-determined values
133
+ *
134
+ * @param name - The name of the cookie to set
135
+ * @param value - The value to store in the cookie
136
+ * @param options - Cookie configuration options
137
+ * @param options.expires - Expiration date or time in days from now
138
+ * @param options.path - Path where the cookie is valid
139
+ * @param options.domain - Domain where the cookie is valid
140
+ * @param options.secure - Whether the cookie should only be transmitted over HTTPS
141
+ * @param options.httpOnly - Whether the cookie should be inaccessible to JavaScript
142
+ * @param options.sameSite - Controls how the cookie behaves with cross-site requests
143
+ */
144
+ export const setServerCookie = async (name: string, value: string, options?: CookieSetOptions) => {
145
+ // Only execute this logic during server-side rendering: https://v3.vitejs.dev/guide/ssr.html#conditional-logic
146
+ if (import.meta.env.SSR) {
147
+ // Dynamically import the cookie setter to avoid loading it on the client bundle
148
+ const { setCookie: setStartCookie } = await import("@tanstack/react-start-server");
149
+
150
+ // Set the cookie with provided options, converting numeric expiry to actual date
151
+ // This handles both relative (days from now) and absolute date expiration times
152
+ setStartCookie(name, value, {
153
+ ...options,
154
+ expires:
155
+ typeof options?.expires === "number"
156
+ ? new Date(Date.now() + options.expires * 864e5) // Convert days to milliseconds (864e5 = 24 * 60 * 60 * 1000)
157
+ : options?.expires,
158
+ });
159
+ }
160
+ };