@kitr/storage 0.0.0-stage → 0.3.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/dist/index.js ADDED
@@ -0,0 +1,367 @@
1
+ import { createDefineConfig, getDevtools, validateSync } from "@kitr/core";
2
+ // src/index.ts
3
+ /** Type-checks `config/storage.ts`. Returns the object unchanged. */
4
+ const defineConfig = createDefineConfig();
5
+ /** The default codec for objects: `JSON.stringify` and `JSON.parse`. */
6
+ const jsonCodec = () => ({
7
+ encode: (v) => JSON.stringify(v),
8
+ decode: (raw) => JSON.parse(raw)
9
+ });
10
+ /** Stores strings as they are, with no quotes. The default for cookies whose default is a string. */
11
+ const stringCodec = {
12
+ encode: (v) => v,
13
+ decode: (raw) => raw
14
+ };
15
+ const listenersByKey = /* @__PURE__ */ new Map();
16
+ let crossTabInstalled = false;
17
+ function webStorage(name) {
18
+ try {
19
+ if (typeof window === "undefined") return null;
20
+ const s = name === "local" ? window.localStorage : window.sessionStorage;
21
+ s.getItem("__kitr__");
22
+ return s;
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+ /** Web Storage as a driver. Null outside a browser, so items fall back to their defaults. */
28
+ function createWebStorageDriver(name) {
29
+ const s = webStorage(name);
30
+ if (!s) return null;
31
+ return {
32
+ name: `web:${name}`,
33
+ get: (key) => s.getItem(key),
34
+ set: (key, raw) => s.setItem(key, raw),
35
+ remove: (key) => s.removeItem(key),
36
+ keys: () => {
37
+ const out = [];
38
+ for (let i = 0; i < s.length; i++) {
39
+ const key = s.key(i);
40
+ if (key !== null) out.push(key);
41
+ }
42
+ return out;
43
+ },
44
+ subscribe: (listener) => {
45
+ if (typeof window === "undefined") return () => {};
46
+ const handler = (e) => {
47
+ if (e.storageArea === s) listener(e.key);
48
+ };
49
+ window.addEventListener("storage", handler);
50
+ return () => window.removeEventListener("storage", handler);
51
+ }
52
+ };
53
+ }
54
+ /** In-memory driver: tests, the server, and `session` items on native platforms. */
55
+ function createMemoryStorageDriver(name = "memory") {
56
+ const map = /* @__PURE__ */ new Map();
57
+ return {
58
+ name,
59
+ get: (key) => map.get(key) ?? null,
60
+ set: (key, raw) => void map.set(key, raw),
61
+ remove: (key) => void map.delete(key),
62
+ keys: () => [...map.keys()]
63
+ };
64
+ }
65
+ /**
66
+ * Build a driver over an async native store. `load` runs once at boot and fills the cache;
67
+ * `write`/`erase` run in the background after each change, so reads stay synchronous.
68
+ */
69
+ function createAsyncStorageDriver(options) {
70
+ const cache = /* @__PURE__ */ new Map();
71
+ const fail = options.onError ?? ((e) => console.error(`[@kitr/storage] ${options.name}:`, e));
72
+ return {
73
+ name: options.name,
74
+ async hydrate() {
75
+ try {
76
+ for (const [k, v] of Object.entries(await options.load())) cache.set(k, v);
77
+ } catch (e) {
78
+ fail(e);
79
+ }
80
+ },
81
+ get: (key) => cache.get(key) ?? null,
82
+ set: (key, raw) => {
83
+ cache.set(key, raw);
84
+ options.write(key, raw).catch(fail);
85
+ },
86
+ remove: (key) => {
87
+ cache.delete(key);
88
+ options.erase(key).catch(fail);
89
+ },
90
+ keys: () => [...cache.keys()]
91
+ };
92
+ }
93
+ const drivers = {};
94
+ const driverUnsubscribers = [];
95
+ /**
96
+ * Replace the Web Storage backing with a platform driver. Called by the storage provider when a
97
+ * platform registered one (`app.drivers.register('storage', driver)`); `session` items keep an
98
+ * in-memory driver unless `areas` says otherwise.
99
+ */
100
+ function setStorageDriver(driver, areas = {}) {
101
+ drivers.local = areas.local ?? driver;
102
+ drivers.session = areas.session ?? createMemoryStorageDriver("memory:session");
103
+ for (const stop of driverUnsubscribers.splice(0)) stop();
104
+ for (const d of [drivers.local, drivers.session]) {
105
+ const stop = d.subscribe?.((key) => {
106
+ if (key === null) for (const set of listenersByKey.values()) for (const l of set) l();
107
+ else emit(key);
108
+ });
109
+ if (stop) driverUnsubscribers.push(stop);
110
+ }
111
+ }
112
+ /** The driver behind an area: the platform driver, Web Storage, or null on the server. */
113
+ function getStorageDriver(name) {
114
+ if (drivers[name] === void 0) drivers[name] = createWebStorageDriver(name);
115
+ return drivers[name] ?? null;
116
+ }
117
+ function area(name) {
118
+ return getStorageDriver(name);
119
+ }
120
+ function installCrossTab() {
121
+ if (crossTabInstalled || typeof window === "undefined") return;
122
+ crossTabInstalled = true;
123
+ window.addEventListener("storage", (e) => {
124
+ if (drivers.local && drivers.local.name !== "web:local") return;
125
+ if (!e.key) return;
126
+ for (const l of listenersByKey.get(e.key) ?? []) l();
127
+ });
128
+ }
129
+ function emit(key) {
130
+ for (const l of listenersByKey.get(key) ?? []) l();
131
+ }
132
+ let keyPrefix = "";
133
+ /** Set by the provider from `config/storage.ts`; items created later pick it up. */
134
+ function setStoragePrefix(prefix) {
135
+ keyPrefix = prefix;
136
+ }
137
+ /**
138
+ * A typed item in localStorage or sessionStorage.
139
+ *
140
+ * const sidebar = defineStorage('sidebar', { default: { open: true } })
141
+ * sidebar.get().open; sidebar.set((s) => ({ ...s, open: false }))
142
+ *
143
+ * Safe everywhere: on the server (or when storage is blocked) `get()` returns the default
144
+ * and `set()` is a no-op, so the same module renders on both sides.
145
+ */
146
+ function defineStorage(name, options) {
147
+ const areaName = options.area ?? "local";
148
+ const codec = options.codec ?? jsonCodec();
149
+ const version = options.version ?? 1;
150
+ const fullKey = () => `${keyPrefix}${name}`;
151
+ let cache;
152
+ const parse = (raw) => {
153
+ if (raw === null) return options.default;
154
+ try {
155
+ const env = codec.decode(raw);
156
+ if (!env || typeof env !== "object" || !("d" in env)) return options.default;
157
+ if (env.e !== void 0 && env.e < Date.now()) return options.default;
158
+ let data = env.d;
159
+ if (env.v !== version) {
160
+ if (!options.migrate) return options.default;
161
+ data = options.migrate(data, env.v);
162
+ }
163
+ if (options.schema) {
164
+ const result = validateSync(options.schema, data);
165
+ return result.success ? result.data : options.default;
166
+ }
167
+ return data;
168
+ } catch {
169
+ return options.default;
170
+ }
171
+ };
172
+ const item = {
173
+ key: name,
174
+ area: areaName,
175
+ default: options.default,
176
+ get() {
177
+ const s = area(areaName);
178
+ if (!s) return options.default;
179
+ const raw = s.get(fullKey());
180
+ if (cache && cache.raw === raw) return cache.value;
181
+ const value = parse(raw);
182
+ cache = {
183
+ raw,
184
+ value
185
+ };
186
+ return value;
187
+ },
188
+ set(next) {
189
+ const s = area(areaName);
190
+ if (!s) return;
191
+ const value = typeof next === "function" ? next(item.get()) : next;
192
+ const env = {
193
+ v: version,
194
+ d: value
195
+ };
196
+ if (options.ttl) env.e = Date.now() + options.ttl;
197
+ try {
198
+ s.set(fullKey(), codec.encode(env));
199
+ } catch {
200
+ cache = {
201
+ raw: null,
202
+ value
203
+ };
204
+ }
205
+ emit(fullKey());
206
+ },
207
+ remove() {
208
+ area(areaName)?.remove(fullKey());
209
+ cache = void 0;
210
+ emit(fullKey());
211
+ },
212
+ subscribe(listener) {
213
+ installCrossTab();
214
+ const key = fullKey();
215
+ const wrapped = () => listener(item.get());
216
+ let set = listenersByKey.get(key);
217
+ if (!set) {
218
+ set = /* @__PURE__ */ new Set();
219
+ listenersByKey.set(key, set);
220
+ }
221
+ set.add(wrapped);
222
+ return () => {
223
+ set.delete(wrapped);
224
+ };
225
+ }
226
+ };
227
+ return item;
228
+ }
229
+ const cookieListeners = /* @__PURE__ */ new Set();
230
+ let cookieDefaults = {};
231
+ /** Attributes merged into every cookie this app writes. The provider sets them from `cookie` in config. */
232
+ function setCookieDefaults(defaults) {
233
+ cookieDefaults = defaults;
234
+ }
235
+ /** Parse a `Cookie` header into a map. Values are decoded with decodeURIComponent. */
236
+ function parseCookies(header) {
237
+ const out = {};
238
+ if (!header) return out;
239
+ for (const part of header.split(";")) {
240
+ const i = part.indexOf("=");
241
+ if (i < 0) continue;
242
+ const k = part.slice(0, i).trim();
243
+ if (!k) continue;
244
+ const v = part.slice(i + 1).trim();
245
+ try {
246
+ out[k] = decodeURIComponent(v);
247
+ } catch {
248
+ out[k] = v;
249
+ }
250
+ }
251
+ return out;
252
+ }
253
+ /** Build a `Set-Cookie` value. `value === null` expires the cookie. */
254
+ function serializeCookie(name, value, options = {}) {
255
+ const parts = [`${name}=${value === null ? "" : encodeURIComponent(value)}`];
256
+ const o = {
257
+ path: "/",
258
+ sameSite: "lax",
259
+ ...cookieDefaults,
260
+ ...options
261
+ };
262
+ if (value === null) parts.push("Max-Age=0", "Expires=Thu, 01 Jan 1970 00:00:00 GMT");
263
+ else {
264
+ if (o.maxAge !== void 0) parts.push(`Max-Age=${Math.floor(o.maxAge)}`);
265
+ if (o.expires) parts.push(`Expires=${o.expires.toUTCString()}`);
266
+ }
267
+ if (o.domain) parts.push(`Domain=${o.domain}`);
268
+ if (o.path) parts.push(`Path=${o.path}`);
269
+ if (o.secure || o.sameSite === "none") parts.push("Secure");
270
+ if (o.httpOnly) parts.push("HttpOnly");
271
+ if (o.sameSite) parts.push(`SameSite=${o.sameSite[0].toUpperCase()}${o.sameSite.slice(1)}`);
272
+ return parts.join("; ");
273
+ }
274
+ function headerFrom(source) {
275
+ if (source === void 0) return typeof document === "undefined" ? void 0 : document.cookie;
276
+ if (source === null || typeof source === "string") return source;
277
+ if (typeof Headers !== "undefined" && source instanceof Headers) return source.get("cookie");
278
+ return source.cookie;
279
+ }
280
+ /**
281
+ * A typed cookie, readable on both sides and writable from the browser or the server.
282
+ *
283
+ * const locale = defineCookie('locale', { default: 'en', maxAge: 60 * 60 * 24 * 365 })
284
+ * locale.get() // client: document.cookie
285
+ * locale.get(request.headers) // server: any Headers / Cookie header string
286
+ * locale.set('de') // client
287
+ * cookies(event).set(locale, 'de') // server (@kitr/storage/server)
288
+ */
289
+ function defineCookie(name, options) {
290
+ const { default: fallback, schema, codec: givenCodec, ...cookieOptions } = options;
291
+ const codec = givenCodec ?? (typeof fallback === "string" ? stringCodec : jsonCodec());
292
+ const fullName = () => `${keyPrefix}${name}`;
293
+ const decode = (raw) => {
294
+ if (raw === void 0) return fallback;
295
+ try {
296
+ const data = codec.decode(raw);
297
+ if (schema) {
298
+ const result = validateSync(schema, data);
299
+ return result.success ? result.data : fallback;
300
+ }
301
+ return data;
302
+ } catch {
303
+ return fallback;
304
+ }
305
+ };
306
+ const item = {
307
+ name,
308
+ default: fallback,
309
+ options: cookieOptions,
310
+ decode,
311
+ get(source) {
312
+ const header = headerFrom(source);
313
+ if (header === void 0) return fallback;
314
+ return decode(parseCookies(header)[fullName()]);
315
+ },
316
+ serialize(value, overrides) {
317
+ return serializeCookie(fullName(), value === null ? null : codec.encode(value), {
318
+ ...cookieOptions,
319
+ ...overrides
320
+ });
321
+ },
322
+ set(value, overrides) {
323
+ if (typeof document === "undefined") return;
324
+ document.cookie = item.serialize(value, {
325
+ ...overrides,
326
+ httpOnly: false
327
+ });
328
+ for (const l of cookieListeners) l();
329
+ },
330
+ remove() {
331
+ if (typeof document === "undefined") return;
332
+ document.cookie = item.serialize(null);
333
+ for (const l of cookieListeners) l();
334
+ },
335
+ subscribe(listener) {
336
+ const wrapped = () => listener(item.get());
337
+ cookieListeners.add(wrapped);
338
+ return () => {
339
+ cookieListeners.delete(wrapped);
340
+ };
341
+ }
342
+ };
343
+ return item;
344
+ }
345
+ /** Call after something else wrote `document.cookie` so `useCookie()` re-reads. */
346
+ function notifyCookiesChanged() {
347
+ for (const l of cookieListeners) l();
348
+ }
349
+ /** Every key in an area, for the devtools panel. */
350
+ function listStorage(name) {
351
+ const s = area(name);
352
+ if (!s) return [];
353
+ return s.keys().map((key) => ({
354
+ key,
355
+ raw: s.get(key)
356
+ }));
357
+ }
358
+ /** Delete one raw key from an area and notify subscribers. Used by the devtools panel. */
359
+ function removeStorageKey(name, key) {
360
+ area(name)?.remove(key);
361
+ emit(key);
362
+ }
363
+ /** Write a line to the devtools `storage` channel. Does nothing when devtools are off. */
364
+ function logStorage(app, label, data) {
365
+ getDevtools(app)?.log("storage", label, data);
366
+ }
367
+ export { createAsyncStorageDriver, createMemoryStorageDriver, createWebStorageDriver, defineConfig, defineCookie, defineStorage, getStorageDriver, jsonCodec, listStorage, logStorage, notifyCookiesChanged, parseCookies, removeStorageKey, serializeCookie, setCookieDefaults, setStorageDriver, setStoragePrefix, stringCodec };
package/dist/kitr.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /** `kitr add storage`: typed localStorage/sessionStorage items and cookies with one API on the client and the server. */
2
+ declare const _default: {
3
+ readonly name: "storage";
4
+ readonly add: (ctx: import("@kitr/core/package").AddContext) => void;
5
+ readonly remove: (ctx: import("@kitr/core/package").AddContext) => void;
6
+ readonly eject: {
7
+ readonly '@kitr/storage': "localStorage / document.cookie (+ a cookie parser)";
8
+ };
9
+ };
10
+ export { _default as default };
package/dist/kitr.js ADDED
@@ -0,0 +1,46 @@
1
+ import { definePackage } from "@kitr/core/package";
2
+ // src/kitr.ts
3
+ /** `kitr add storage`: typed localStorage/sessionStorage items and cookies with one API on the client and the server. */
4
+ var kitr_default = definePackage({
5
+ name: "storage",
6
+ add(ctx) {
7
+ const serverOnly = ctx.isServerOnly;
8
+ ctx.provider.add("@kitr/storage/provider");
9
+ ctx.config("storage", `import { defineConfig } from '@kitr/storage'
10
+
11
+ export default defineConfig({
12
+ // Prefixes every storage key and cookie name so several apps on one origin never collide.
13
+ prefix: '${(ctx.pkg.name ?? "app").replace(/^@[^/]+\//, "").replace(/[^a-z0-9]+/gi, "-")}:',
14
+ cookie: { path: '/', sameSite: 'lax', secure: import.meta.env?.PROD ?? false },
15
+ })
16
+ `);
17
+ ctx.write("app/storage.ts", serverOnly ? `import { defineCookie } from '@kitr/storage'
18
+
19
+ /** Cookies are typed once and read with \`cookies(event).get(item)\` from @kitr/storage/server. */
20
+ export const localeCookie = defineCookie('locale', { default: 'en', maxAge: 60 * 60 * 24 * 365 })
21
+ ` : `import { defineCookie, defineStorage } from '@kitr/storage'
22
+
23
+ /**
24
+ * Every persisted value, in one file, typed. Components read them with
25
+ * \`useStorage(item)\` / \`useCookie(item)\` from @kitr/storage/react; plain code with \`item.get()\`.
26
+ */
27
+ export const sidebar = defineStorage('sidebar', { default: { collapsed: false } })
28
+
29
+ export const recentSearches = defineStorage<string[]>('recent-searches', {
30
+ default: [],
31
+ area: 'session',
32
+ version: 1,
33
+ })
34
+
35
+ /** Readable on the server too (\`localeCookie.get(request.headers)\`), so SSR picks the right locale. */
36
+ export const localeCookie = defineCookie('locale', { default: 'en', maxAge: 60 * 60 * 24 * 365 })
37
+ `);
38
+ ctx.agents.add("storage", serverOnly ? `- Cookies: declare with \`defineCookie\` in \`app/storage.ts\`; in controllers use \`const jar = cookies(ctx.event)\` from \`@kitr/storage/server\`, then \`jar.get(item)\` / \`jar.set(item, value, { httpOnly: true })\`.` : `- Persisted values are declared once in \`app/storage.ts\` (\`defineStorage\` for local/session storage, \`defineCookie\` for cookies) and never read through \`localStorage\`/\`document.cookie\` directly.\n- Components: \`const [value, setValue] = useStorage(item)\` / \`useCookie(item)\` from \`@kitr/storage/react\`. Both are hydration-safe: the server and first render see the default.\n- Server side (actions, controllers): \`cookies(event).get(item)\` / \`.set(item, value)\` from \`@kitr/storage/server\`; wrap the root route in \`<CookieHeader value={request.headers.get('cookie')}>\` so \`useCookie\` renders the right value during SSR.\n- Add \`schema\` (zod/valibot) to validate what comes back from storage, \`version\` + \`migrate\` when a shape changes, \`ttl\` to expire.`);
39
+ },
40
+ remove(ctx) {
41
+ ctx.provider.remove("@kitr/storage/provider");
42
+ ctx.agents.remove("storage");
43
+ },
44
+ eject: { "@kitr/storage": "localStorage / document.cookie (+ a cookie parser)" }
45
+ });
46
+ export { kitr_default as default };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Applies `config/storage.ts` (key prefix, cookie defaults), switches `defineStorage`
3
+ * items to the platform's native driver when one is registered, hydrates it before the first
4
+ * render, and contributes the Storage panel to the devtools.
5
+ */
6
+ export declare const storageProvider: import("@kitr/core").KitrProvider;
7
+ export { storageProvider as default };
@@ -0,0 +1,29 @@
1
+ import { getStorageDriver, setCookieDefaults, setStorageDriver, setStoragePrefix } from "./index.js";
2
+ import { defineProvider, getDevtools } from "@kitr/core";
3
+ // src/provider.ts
4
+ /**
5
+ * Applies `config/storage.ts` (key prefix, cookie defaults), switches `defineStorage`
6
+ * items to the platform's native driver when one is registered, hydrates it before the first
7
+ * render, and contributes the Storage panel to the devtools.
8
+ */
9
+ const storageProvider = defineProvider({
10
+ name: "storage",
11
+ register(app) {
12
+ const config = app.config.get("storage", {});
13
+ if (config.prefix) setStoragePrefix(config.prefix);
14
+ if (config.cookie) setCookieDefaults(config.cookie);
15
+ const driver = app.drivers.resolve("storage");
16
+ if (driver) setStorageDriver(driver);
17
+ },
18
+ async boot(app) {
19
+ const driver = app.drivers.resolve("storage");
20
+ if (driver && getStorageDriver("local") !== driver) setStorageDriver(driver);
21
+ await driver?.hydrate?.();
22
+ const hub = getDevtools(app);
23
+ if (!hub) return;
24
+ hub.log("storage", `driver: ${getStorageDriver("local")?.name ?? "none (server)"}`);
25
+ for (const area of ["local", "session"]) getStorageDriver(area)?.subscribe?.((key) => hub.log("storage", `${key ?? "storage"} changed outside this context`));
26
+ },
27
+ devtools: typeof __KITR_DEVTOOLS__ === "boolean" && __KITR_DEVTOOLS__ === false ? void 0 : (_app, ui) => import("./devtools-BTqcJ52N.js").then((m) => m.panels(ui))
28
+ });
29
+ export { storageProvider as default, storageProvider };
@@ -0,0 +1,20 @@
1
+ import { CookieItem, StorageItem } from "./index.js";
2
+ import { ReactNode } from "react";
3
+ /**
4
+ * Gives `useCookie()` the request's `Cookie` header during server rendering, so the first
5
+ * HTML shows the stored value instead of the default. Wrap the tree once, in the root route.
6
+ */
7
+ export declare function CookieHeader({ value, children }: {
8
+ value: string | null | undefined;
9
+ children: ReactNode;
10
+ }): import("react").FunctionComponentElement<import("react").ProviderProps<string | null | undefined>>;
11
+ /**
12
+ * Read and write a storage item from a component. Hydration-safe: the server (and the first
13
+ * client render) see the default, then the stored value applies, so markup always matches.
14
+ */
15
+ export declare function useStorage<T>(item: StorageItem<T>): [T, (value: T | ((current: T) => T)) => void, () => void];
16
+ /**
17
+ * Read and write a cookie from a component. On the server the value comes from
18
+ * `<CookieHeader>` when present, otherwise the default.
19
+ */
20
+ export declare function useCookie<T>(item: CookieItem<T>): [T, (value: T) => void, () => void];
package/dist/react.js ADDED
@@ -0,0 +1,40 @@
1
+ import { createContext, createElement, useCallback, useContext, useSyncExternalStore } from "react";
2
+ // src/react.ts
3
+ /**
4
+ * The request's `Cookie` header during server rendering, so `useCookie()` renders the
5
+ * same value the browser will hydrate with. Wrap the tree once, in the root route:
6
+ *
7
+ * <CookieHeader value={request.headers.get('cookie')}>...</CookieHeader>
8
+ */
9
+ const CookieHeaderContext = createContext(void 0);
10
+ /**
11
+ * Gives `useCookie()` the request's `Cookie` header during server rendering, so the first
12
+ * HTML shows the stored value instead of the default. Wrap the tree once, in the root route.
13
+ */
14
+ function CookieHeader({ value, children }) {
15
+ return createElement(CookieHeaderContext.Provider, { value }, children);
16
+ }
17
+ /**
18
+ * Read and write a storage item from a component. Hydration-safe: the server (and the first
19
+ * client render) see the default, then the stored value applies, so markup always matches.
20
+ */
21
+ function useStorage(item) {
22
+ return [
23
+ useSyncExternalStore(useCallback((onChange) => item.subscribe(onChange), [item]), () => item.get(), () => item.default),
24
+ item.set,
25
+ item.remove
26
+ ];
27
+ }
28
+ /**
29
+ * Read and write a cookie from a component. On the server the value comes from
30
+ * `<CookieHeader>` when present, otherwise the default.
31
+ */
32
+ function useCookie(item) {
33
+ const header = useContext(CookieHeaderContext);
34
+ return [
35
+ useSyncExternalStore(useCallback((onChange) => item.subscribe(onChange), [item]), () => item.get(), () => item.get(header ?? null)),
36
+ item.set,
37
+ item.remove
38
+ ];
39
+ }
40
+ export { CookieHeader, useCookie, useStorage };
@@ -0,0 +1,29 @@
1
+ import { CookieItem, CookieOptions } from "./index.js";
2
+ /** The parts of an h3 v2 event (or anything request/response shaped) that cookies need. */
3
+ export interface CookieEventLike {
4
+ /** The request; only its headers are read. */
5
+ req: {
6
+ headers: Headers;
7
+ };
8
+ /** The response; `Set-Cookie` headers are appended here. */
9
+ res: {
10
+ headers: Headers;
11
+ };
12
+ }
13
+ /** Typed cookie access for one request, returned by `cookies()`. */
14
+ export interface ServerCookies {
15
+ /** The item's value from the request's `Cookie` header, or its default. */
16
+ get<T>(item: CookieItem<T>): T;
17
+ /** Appends a `Set-Cookie` header; `httpOnly` and the other item options apply. */
18
+ set<T>(item: CookieItem<T>, value: T, options?: CookieOptions): void;
19
+ /** Appends a `Set-Cookie` header that expires the cookie. */
20
+ remove(item: CookieItem<unknown>): void;
21
+ /** The raw `Cookie` header, for `<CookieHeader>` during SSR. */
22
+ readonly header: string | null;
23
+ }
24
+ /**
25
+ * Typed cookies on the server: `cookies(event)` in a controller or middleware,
26
+ * `cookies(request, responseHeaders)` anywhere you only have the Fetch primitives.
27
+ */
28
+ export declare function cookies(event: CookieEventLike): ServerCookies;
29
+ export declare function cookies(request: Request, responseHeaders: Headers): ServerCookies;
package/dist/server.js ADDED
@@ -0,0 +1,14 @@
1
+ // src/server.ts
2
+ function cookies(source, responseHeaders) {
3
+ const reqHeaders = "req" in source ? source.req.headers : source.headers;
4
+ const resHeaders = "res" in source ? source.res.headers : responseHeaders;
5
+ const header = reqHeaders.get("cookie");
6
+ const headerSource = header;
7
+ return {
8
+ header,
9
+ get: (item) => item.get(headerSource),
10
+ set: (item, value, options) => resHeaders.append("set-cookie", item.serialize(value, options)),
11
+ remove: (item) => resHeaders.append("set-cookie", item.serialize(null))
12
+ };
13
+ }
14
+ export { cookies };