better-auth-cookie-consent 0.1.1 → 0.3.0-dev.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marc Stammerjohann, Gary Großgarten
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -7,7 +7,7 @@ GDPR-compliant cookie consent management plugin for [Better Auth](https://better
7
7
  - Automatic merge of anonymous consent on sign-in / sign-up
8
8
  - Consent versioning with automatic invalidation
9
9
  - Validation schema support (e.g. Zod) to enforce consent shape
10
- - Generic client plugin — `cookieConsentClient<z.infer<typeof schema>>()` types your consent model end-to-end
10
+ - Generic client plugin — `cookieConsentClient<DefaultConsentModel>()` types your consent model end-to-end
11
11
 
12
12
  ## How It Works
13
13
 
@@ -113,7 +113,7 @@ export const auth = betterAuth({
113
113
  The plugin validates consent JSON against a `StandardSchemaV1` (e.g. Zod schema) on every write. A preset `defaultConsentSchema` is exported for the standard categories:
114
114
 
115
115
  ```ts
116
- import { defaultConsentSchema } from 'better-auth-cookie-consent';
116
+ import { defaultConsentSchema } from 'better-auth-cookie-consent/client';
117
117
 
118
118
  // Equivalent to:
119
119
  // z.object({
@@ -126,19 +126,14 @@ import { defaultConsentSchema } from 'better-auth-cookie-consent';
126
126
 
127
127
  ## Client Setup
128
128
 
129
- The client plugin accepts a generic type parameter for the consent shape. Use `z.infer<typeof schema>` to get full end-to-end typing:
129
+ The client plugin accepts a generic type parameter for the consent shape. Use `DefaultConsentModel` to get full end-to-end typing:
130
130
 
131
131
  ```ts
132
132
  import { createAuthClient } from 'better-auth/client';
133
- import { defaultConsentSchema } from 'better-auth-cookie-consent';
134
- import { cookieConsentClient } from 'better-auth-cookie-consent/client';
135
- import type { z } from 'zod';
136
-
137
- type ConsentModel = z.infer<typeof defaultConsentSchema>;
138
- // { necessary: boolean; analytics: boolean; marketing: boolean; functional: boolean }
133
+ import { cookieConsentClient, type DefaultConsentModel } from 'better-auth-cookie-consent/client';
139
134
 
140
135
  export const authClient = createAuthClient({
141
- plugins: [cookieConsentClient<ConsentModel>()],
136
+ plugins: [cookieConsentClient<DefaultConsentModel>()],
142
137
  });
143
138
  ```
144
139
 
@@ -250,11 +245,9 @@ The consent object is validated against the server's `validationSchema` on every
250
245
  Use `keyof` on the inferred consent model to type your banner categories:
251
246
 
252
247
  ```ts
253
- import type { z } from 'zod';
254
- import { defaultConsentSchema } from 'better-auth-cookie-consent';
248
+ import { type DefaultConsentModel } from 'better-auth-cookie-consent/client';
255
249
 
256
- type ConsentModel = z.infer<typeof defaultConsentSchema>;
257
- type CategoryId = keyof ConsentModel;
250
+ type CategoryId = keyof DefaultConsentModel;
258
251
 
259
252
  const CATEGORIES: { id: CategoryId; label: string }[] = [
260
253
  { id: 'necessary', label: 'Necessary' },
@@ -296,30 +289,36 @@ The plugin registers a server-side `after` hook that runs on every `sign-in/*` a
296
289
 
297
290
  When the `consentVersion` option changes, the `getConsent` endpoint returns `versionMatch: false` so the client knows to prompt for re-consent.
298
291
 
299
- ## Database Schema
292
+ ## Schema
293
+
294
+ ### CookieConsent
300
295
 
301
- The plugin creates a `cookieConsent` table:
296
+ Table name: `cookieConsent`
302
297
 
303
- | Column | Type | Description |
304
- | -------------- | ------- | -------------------------------- |
305
- | id | string | Primary key |
306
- | userId | string? | References user table |
307
- | anonymousId | string | Anonymous client identifier |
308
- | consent | string | JSON-encoded consent preferences |
309
- | consentVersion | string | Consent policy version |
310
- | timestamp | date | When consent was recorded |
298
+ | Field | Type | Key | Description |
299
+ | -------------- | ------- | ------ | ----------------------------------------- |
300
+ | id | string | pk | Unique identifier for each consent record |
301
+ | userId | string? | unique | ID of an associated better-auth user |
302
+ | anonymousId | string | unique | Anonymous client identifier |
303
+ | consent | string | | JSON-encoded consent preferences |
304
+ | consentVersion | string | | Consent policy version |
305
+ | timestamp | date | | When consent was recorded |
311
306
 
312
- ### Prisma Schema
307
+ #### Prisma
313
308
 
314
309
  ```prisma
315
310
  model CookieConsent {
316
- id String @id @default(cuid())
311
+ id String @id
317
312
  userId String?
313
+ user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
318
314
  anonymousId String
319
315
  consent String
320
316
  consentVersion String
321
- timestamp DateTime @default(now())
322
- user User? @relation(fields: [userId], references: [id])
317
+ timestamp DateTime
318
+
319
+ @@unique([userId])
320
+ @@unique([anonymousId])
321
+ @@map("cookieConsent")
323
322
  }
324
323
  ```
325
324
 
package/dist/client.d.mts CHANGED
@@ -1,8 +1,6 @@
1
- import { n as cookieConsentPlugin, o as Consent } from "./index-B8ovmV25.mjs";
2
- import * as better_auth0 from "better-auth";
3
- import * as nanostores from "nanostores";
4
- import * as better_auth_client0 from "better-auth/client";
5
-
1
+ import { n as cookieConsentPlugin, o as Consent, r as defaultConsentSchema } from "./index-B6ZS0bF2.mjs";
2
+ import z from "zod";
3
+ import { BetterFetch } from "better-auth/client";
6
4
  //#region src/client.d.ts
7
5
  /**
8
6
  * Client-side consent state, kept in sync with the server.
@@ -12,29 +10,31 @@ interface ConsentState<TConsent extends Consent = Consent> {
12
10
  consentVersion: string | null;
13
11
  versionMatch: boolean;
14
12
  }
13
+ /**
14
+ * Inferred type from the default consent schema.
15
+ */
16
+ type DefaultConsentModel = z.infer<typeof defaultConsentSchema>;
15
17
  /**
16
18
  * Client plugin for cookie consent management.
17
19
  *
18
- * @typeParam TConsent - The consent shape, typically `z.infer<typeof yourConsentSchema>`.
20
+ * @typeParam TConsent - The consent shape, defaults to `DefaultConsentModel`.
19
21
  *
20
22
  * @example
21
23
  * ```ts
22
- * import { defaultConsentSchema } from 'better-auth-cookie-consent';
23
- * import { cookieConsentClient } from 'better-auth-cookie-consent/client';
24
- * import type { z } from 'zod';
24
+ * import { cookieConsentClient, type DefaultConsentModel } from 'better-auth-cookie-consent/client';
25
25
  *
26
26
  * const authClient = createAuthClient({
27
- * plugins: [cookieConsentClient<z.infer<typeof defaultConsentSchema>>()],
27
+ * plugins: [cookieConsentClient<DefaultConsentModel>()],
28
28
  * });
29
29
  * ```
30
30
  */
31
- declare const cookieConsentClient: <TConsent extends Consent = Consent>() => {
31
+ declare const cookieConsentClient: <TConsent extends Consent = Record<string, boolean>>() => {
32
32
  id: "cookie-consent";
33
33
  $InferServerPlugin: ReturnType<typeof cookieConsentPlugin>;
34
- getAtoms($fetch: better_auth_client0.BetterFetch): {
35
- $consent: nanostores.PreinitializedWritableAtom<ConsentState<TConsent>> & object;
34
+ getAtoms(): {
35
+ $consent: import("nanostores").PreinitializedWritableAtom<ConsentState<TConsent>> & object;
36
36
  };
37
- getActions($fetch: better_auth_client0.BetterFetch, $store: better_auth0.ClientStore): {
37
+ getActions($fetch: BetterFetch, $store: import("better-auth").ClientStore): {
38
38
  cookieConsent: {
39
39
  /**
40
40
  * Set consent preferences on the server.
@@ -53,7 +53,7 @@ declare const cookieConsentClient: <TConsent extends Consent = Consent>() => {
53
53
  } | {
54
54
  data: null;
55
55
  error: {
56
- message?: string | undefined;
56
+ message?: string;
57
57
  status: number;
58
58
  statusText: string;
59
59
  };
@@ -62,13 +62,6 @@ declare const cookieConsentClient: <TConsent extends Consent = Consent>() => {
62
62
  * Retrieve consent from the server.
63
63
  */
64
64
  getConsent: (anonymousId?: string) => Promise<{
65
- data: null;
66
- error: {
67
- message?: string | undefined;
68
- status: number;
69
- statusText: string;
70
- };
71
- } | {
72
65
  data: {
73
66
  consent: {
74
67
  id: string;
@@ -81,27 +74,34 @@ declare const cookieConsentClient: <TConsent extends Consent = Consent>() => {
81
74
  versionMatch: boolean;
82
75
  };
83
76
  error: null;
84
- }>;
85
- /**
86
- * Merge anonymous consent into the authenticated user's record.
87
- */
88
- mergeConsent: (anonymousId: string) => Promise<{
77
+ } | {
89
78
  data: null;
90
79
  error: {
91
- message?: string | undefined;
80
+ message?: string;
92
81
  status: number;
93
82
  statusText: string;
94
83
  };
95
- } | {
84
+ }>;
85
+ /**
86
+ * Merge anonymous consent into the authenticated user's record.
87
+ */
88
+ mergeConsent: (anonymousId: string) => Promise<{
96
89
  data: {
97
90
  status: boolean;
98
91
  merged: boolean;
99
92
  };
100
93
  error: null;
94
+ } | {
95
+ data: null;
96
+ error: {
97
+ message?: string;
98
+ status: number;
99
+ statusText: string;
100
+ };
101
101
  }>;
102
102
  };
103
103
  };
104
104
  };
105
105
  //#endregion
106
- export { ConsentState, cookieConsentClient };
106
+ export { ConsentState, DefaultConsentModel, cookieConsentClient };
107
107
  //# sourceMappingURL=client.d.mts.map
package/dist/client.mjs CHANGED
@@ -3,16 +3,14 @@ import { atom } from "nanostores";
3
3
  /**
4
4
  * Client plugin for cookie consent management.
5
5
  *
6
- * @typeParam TConsent - The consent shape, typically `z.infer<typeof yourConsentSchema>`.
6
+ * @typeParam TConsent - The consent shape, defaults to `DefaultConsentModel`.
7
7
  *
8
8
  * @example
9
9
  * ```ts
10
- * import { defaultConsentSchema } from 'better-auth-cookie-consent';
11
- * import { cookieConsentClient } from 'better-auth-cookie-consent/client';
12
- * import type { z } from 'zod';
10
+ * import { cookieConsentClient, type DefaultConsentModel } from 'better-auth-cookie-consent/client';
13
11
  *
14
12
  * const authClient = createAuthClient({
15
- * plugins: [cookieConsentClient<z.infer<typeof defaultConsentSchema>>()],
13
+ * plugins: [cookieConsentClient<DefaultConsentModel>()],
16
14
  * });
17
15
  * ```
18
16
  */
@@ -20,7 +18,7 @@ const cookieConsentClient = () => {
20
18
  return {
21
19
  id: "cookie-consent",
22
20
  $InferServerPlugin: {},
23
- getAtoms($fetch) {
21
+ getAtoms() {
24
22
  return { $consent: atom({
25
23
  consent: null,
26
24
  consentVersion: null,
@@ -44,6 +42,11 @@ const cookieConsentClient = () => {
44
42
  return res;
45
43
  }
46
44
  return { cookieConsent: {
45
+ /**
46
+ * Set consent preferences on the server.
47
+ * Also used for accept-all / reject-all by passing the
48
+ * appropriate consent object (all `true` or all `false`).
49
+ */
47
50
  setConsent: async (data) => {
48
51
  const res = await $fetch("/cookie-consent/set", {
49
52
  method: "POST",
@@ -56,9 +59,15 @@ const cookieConsentClient = () => {
56
59
  });
57
60
  return res;
58
61
  },
62
+ /**
63
+ * Retrieve consent from the server.
64
+ */
59
65
  getConsent: async (anonymousId) => {
60
66
  return syncFromServer(anonymousId);
61
67
  },
68
+ /**
69
+ * Merge anonymous consent into the authenticated user's record.
70
+ */
62
71
  mergeConsent: async (anonymousId) => {
63
72
  const res = await $fetch("/cookie-consent/merge", {
64
73
  method: "POST",
@@ -1 +1 @@
1
- {"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import type { BetterAuthClientPlugin } from 'better-auth/client';\nimport { atom } from 'nanostores';\n\n// import { COOKIE_CONSENT_ERROR_CODES } from './error-codes';\nimport type { Consent, cookieConsentPlugin } from './index';\n\n/**\n * Client-side consent state, kept in sync with the server.\n */\nexport interface ConsentState<TConsent extends Consent = Consent> {\n consent: TConsent | null;\n consentVersion: string | null;\n versionMatch: boolean;\n}\n\n/**\n * Client plugin for cookie consent management.\n *\n * @typeParam TConsent - The consent shape, typically `z.infer<typeof yourConsentSchema>`.\n *\n * @example\n * ```ts\n * import { defaultConsentSchema } from 'better-auth-cookie-consent';\n * import { cookieConsentClient } from 'better-auth-cookie-consent/client';\n * import type { z } from 'zod';\n *\n * const authClient = createAuthClient({\n * plugins: [cookieConsentClient<z.infer<typeof defaultConsentSchema>>()],\n * });\n * ```\n */\nexport const cookieConsentClient = <TConsent extends Consent = Consent>() => {\n return {\n id: 'cookie-consent',\n $InferServerPlugin: {} as ReturnType<typeof cookieConsentPlugin>,\n\n getAtoms($fetch) {\n const $consent = atom<ConsentState<TConsent>>({\n consent: null,\n consentVersion: null,\n versionMatch: false,\n });\n return { $consent };\n },\n\n getActions($fetch, $store) {\n const consentAtom = $store.atoms.$consent as ReturnType<typeof atom<ConsentState<TConsent>>>;\n\n async function syncFromServer(anonymousId?: string) {\n let path = '/cookie-consent/get';\n if (anonymousId) {\n const params = new URLSearchParams({ anonymousId });\n path = `${path}?${params.toString()}`;\n }\n const res = await $fetch<{\n consent: {\n id: string;\n userId?: string | null;\n anonymousId: string;\n consent: TConsent;\n consentVersion: string;\n timestamp: string;\n } | null;\n versionMatch: boolean;\n }>(`${path}`, { method: 'GET' });\n if (res.data) {\n consentAtom.set({\n consent: res.data.consent?.consent ?? null,\n consentVersion: res.data.consent?.consentVersion ?? null,\n versionMatch: res.data.versionMatch,\n });\n }\n return res;\n }\n\n return {\n cookieConsent: {\n /**\n * Set consent preferences on the server.\n * Also used for accept-all / reject-all by passing the\n * appropriate consent object (all `true` or all `false`).\n */\n setConsent: async (data: {\n anonymousId: string;\n consent: TConsent;\n consentVersion: string;\n }) => {\n const res = await $fetch<{ status: boolean }>('/cookie-consent/set', {\n method: 'POST',\n body: data,\n });\n if (res.data?.status) {\n consentAtom.set({\n consent: data.consent,\n consentVersion: data.consentVersion,\n versionMatch: true,\n });\n }\n return res;\n },\n\n /**\n * Retrieve consent from the server.\n */\n getConsent: async (anonymousId?: string) => {\n return syncFromServer(anonymousId);\n },\n\n /**\n * Merge anonymous consent into the authenticated user's record.\n */\n mergeConsent: async (anonymousId: string) => {\n const res = await $fetch<{ status: boolean; merged: boolean }>(\n '/cookie-consent/merge',\n {\n method: 'POST',\n body: { anonymousId },\n },\n );\n if (res.data?.merged) {\n await syncFromServer(anonymousId);\n }\n return res;\n },\n },\n };\n },\n\n // $ERROR_CODES: COOKIE_CONSENT_ERROR_CODES,\n } satisfies BetterAuthClientPlugin;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AA+BA,MAAa,4BAAgE;AAC3E,QAAO;EACL,IAAI;EACJ,oBAAoB,EAAE;EAEtB,SAAS,QAAQ;AAMf,UAAO,EAAE,UALQ,KAA6B;IAC5C,SAAS;IACT,gBAAgB;IAChB,cAAc;IACf,CAAC,EACiB;;EAGrB,WAAW,QAAQ,QAAQ;GACzB,MAAM,cAAc,OAAO,MAAM;GAEjC,eAAe,eAAe,aAAsB;IAClD,IAAI,OAAO;AACX,QAAI,aAAa;KACf,MAAM,SAAS,IAAI,gBAAgB,EAAE,aAAa,CAAC;AACnD,YAAO,GAAG,KAAK,GAAG,OAAO,UAAU;;IAErC,MAAM,MAAM,MAAM,OAUf,GAAG,QAAQ,EAAE,QAAQ,OAAO,CAAC;AAChC,QAAI,IAAI,KACN,aAAY,IAAI;KACd,SAAS,IAAI,KAAK,SAAS,WAAW;KACtC,gBAAgB,IAAI,KAAK,SAAS,kBAAkB;KACpD,cAAc,IAAI,KAAK;KACxB,CAAC;AAEJ,WAAO;;AAGT,UAAO,EACL,eAAe;IAMb,YAAY,OAAO,SAIb;KACJ,MAAM,MAAM,MAAM,OAA4B,uBAAuB;MACnE,QAAQ;MACR,MAAM;MACP,CAAC;AACF,SAAI,IAAI,MAAM,OACZ,aAAY,IAAI;MACd,SAAS,KAAK;MACd,gBAAgB,KAAK;MACrB,cAAc;MACf,CAAC;AAEJ,YAAO;;IAMT,YAAY,OAAO,gBAAyB;AAC1C,YAAO,eAAe,YAAY;;IAMpC,cAAc,OAAO,gBAAwB;KAC3C,MAAM,MAAM,MAAM,OAChB,yBACA;MACE,QAAQ;MACR,MAAM,EAAE,aAAa;MACtB,CACF;AACD,SAAI,IAAI,MAAM,OACZ,OAAM,eAAe,YAAY;AAEnC,YAAO;;IAEV,EACF;;EAIJ"}
1
+ {"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import type { BetterAuthClientPlugin, BetterFetch } from 'better-auth/client';\nimport { atom } from 'nanostores';\nimport type z from 'zod';\n\nimport type { Consent, cookieConsentPlugin, defaultConsentSchema } from './index';\n\n/**\n * Client-side consent state, kept in sync with the server.\n */\nexport interface ConsentState<TConsent extends Consent = Consent> {\n consent: TConsent | null;\n consentVersion: string | null;\n versionMatch: boolean;\n}\n\n/**\n * Inferred type from the default consent schema.\n */\nexport type DefaultConsentModel = z.infer<typeof defaultConsentSchema>;\n\n/**\n * Client plugin for cookie consent management.\n *\n * @typeParam TConsent - The consent shape, defaults to `DefaultConsentModel`.\n *\n * @example\n * ```ts\n * import { cookieConsentClient, type DefaultConsentModel } from 'better-auth-cookie-consent/client';\n *\n * const authClient = createAuthClient({\n * plugins: [cookieConsentClient<DefaultConsentModel>()],\n * });\n * ```\n */\nexport const cookieConsentClient = <TConsent extends Consent = Consent>() => {\n return {\n id: 'cookie-consent',\n $InferServerPlugin: {} as ReturnType<typeof cookieConsentPlugin>,\n\n getAtoms() {\n const $consent = atom<ConsentState<TConsent>>({\n consent: null,\n consentVersion: null,\n versionMatch: false,\n });\n return { $consent };\n },\n\n getActions($fetch: BetterFetch, $store) {\n const consentAtom = $store.atoms.$consent as ReturnType<typeof atom<ConsentState<TConsent>>>;\n\n async function syncFromServer(anonymousId?: string) {\n let path = '/cookie-consent/get';\n if (anonymousId) {\n const params = new URLSearchParams({ anonymousId });\n path = `${path}?${params.toString()}`;\n }\n const res = await $fetch<{\n consent: {\n id: string;\n userId?: string | null;\n anonymousId: string;\n consent: TConsent;\n consentVersion: string;\n timestamp: string;\n } | null;\n versionMatch: boolean;\n }>(`${path}`, { method: 'GET' });\n if (res.data) {\n consentAtom.set({\n consent: res.data.consent?.consent ?? null,\n consentVersion: res.data.consent?.consentVersion ?? null,\n versionMatch: res.data.versionMatch,\n });\n }\n return res;\n }\n\n return {\n cookieConsent: {\n /**\n * Set consent preferences on the server.\n * Also used for accept-all / reject-all by passing the\n * appropriate consent object (all `true` or all `false`).\n */\n setConsent: async (data: {\n anonymousId: string;\n consent: TConsent;\n consentVersion: string;\n }) => {\n const res = await $fetch<{ status: boolean }>('/cookie-consent/set', {\n method: 'POST',\n body: data,\n });\n if (res.data?.status) {\n consentAtom.set({\n consent: data.consent,\n consentVersion: data.consentVersion,\n versionMatch: true,\n });\n }\n return res;\n },\n\n /**\n * Retrieve consent from the server.\n */\n getConsent: async (anonymousId?: string) => {\n return syncFromServer(anonymousId);\n },\n\n /**\n * Merge anonymous consent into the authenticated user's record.\n */\n mergeConsent: async (anonymousId: string) => {\n const res = await $fetch<{ status: boolean; merged: boolean }>(\n '/cookie-consent/merge',\n {\n method: 'POST',\n body: { anonymousId },\n },\n );\n if (res.data?.merged) {\n await syncFromServer(anonymousId);\n }\n return res;\n },\n },\n };\n },\n\n // $ERROR_CODES: COOKIE_CONSENT_ERROR_CODES,\n } satisfies BetterAuthClientPlugin;\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAkCA,MAAa,4BAAgE;CAC3E,OAAO;EACL,IAAI;EACJ,oBAAoB,CAAC;EAErB,WAAW;GAMT,OAAO,EAAE,UALQ,KAA6B;IAC5C,SAAS;IACT,gBAAgB;IAChB,cAAc;GAChB,CACgB,EAAE;EACpB;EAEA,WAAW,QAAqB,QAAQ;GACtC,MAAM,cAAc,OAAO,MAAM;GAEjC,eAAe,eAAe,aAAsB;IAClD,IAAI,OAAO;IACX,IAAI,aAAa;KACf,MAAM,SAAS,IAAI,gBAAgB,EAAE,YAAY,CAAC;KAClD,OAAO,GAAG,KAAK,GAAG,OAAO,SAAS;IACpC;IACA,MAAM,MAAM,MAAM,OAUf,GAAG,QAAQ,EAAE,QAAQ,MAAM,CAAC;IAC/B,IAAI,IAAI,MACN,YAAY,IAAI;KACd,SAAS,IAAI,KAAK,SAAS,WAAW;KACtC,gBAAgB,IAAI,KAAK,SAAS,kBAAkB;KACpD,cAAc,IAAI,KAAK;IACzB,CAAC;IAEH,OAAO;GACT;GAEA,OAAO,EACL,eAAe;;;;;;IAMb,YAAY,OAAO,SAIb;KACJ,MAAM,MAAM,MAAM,OAA4B,uBAAuB;MACnE,QAAQ;MACR,MAAM;KACR,CAAC;KACD,IAAI,IAAI,MAAM,QACZ,YAAY,IAAI;MACd,SAAS,KAAK;MACd,gBAAgB,KAAK;MACrB,cAAc;KAChB,CAAC;KAEH,OAAO;IACT;;;;IAKA,YAAY,OAAO,gBAAyB;KAC1C,OAAO,eAAe,WAAW;IACnC;;;;IAKA,cAAc,OAAO,gBAAwB;KAC3C,MAAM,MAAM,MAAM,OAChB,yBACA;MACE,QAAQ;MACR,MAAM,EAAE,YAAY;KACtB,CACF;KACA,IAAI,IAAI,MAAM,QACZ,MAAM,eAAe,WAAW;KAElC,OAAO;IACT;GACF,EACF;EACF;CAGF;AACF"}
@@ -1,7 +1,5 @@
1
- import * as z from "zod";
2
- import * as better_auth0 from "better-auth";
1
+ import * as z$1 from "zod";
3
2
  import { BetterAuthOptions, DBAdapter, InferOptionSchema, StandardSchemaV1 } from "better-auth";
4
-
5
3
  //#region src/schema.d.ts
6
4
  declare const cookieConsent: {
7
5
  cookieConsent: {
@@ -9,6 +7,7 @@ declare const cookieConsent: {
9
7
  userId: {
10
8
  type: "string";
11
9
  required: false;
10
+ unique: true;
12
11
  references: {
13
12
  model: string;
14
13
  field: string;
@@ -17,6 +16,7 @@ declare const cookieConsent: {
17
16
  anonymousId: {
18
17
  type: "string";
19
18
  required: true;
19
+ unique: true;
20
20
  };
21
21
  consent: {
22
22
  type: "string";
@@ -30,6 +30,7 @@ declare const cookieConsent: {
30
30
  type: "date";
31
31
  defaultValue: () => Date;
32
32
  required: true;
33
+ input: false;
33
34
  };
34
35
  };
35
36
  };
@@ -40,8 +41,8 @@ declare const cookieConsent: {
40
41
  * Consent categories mapped to boolean values.
41
42
  * Each key represents a cookie category (e.g., "analytics", "marketing").
42
43
  */
43
- type ConsentSchemaModel = z.ZodObject<Record<string, z.ZodBoolean>>;
44
- type Consent = z.infer<ConsentSchemaModel>;
44
+ type ConsentSchemaModel = z$1.ZodObject<Record<string, z$1.ZodBoolean>>;
45
+ type Consent = z$1.infer<ConsentSchemaModel>;
45
46
  /**
46
47
  * Options for the cookie consent plugin.
47
48
  */
@@ -145,12 +146,12 @@ declare const ANONYMOUS_ID_COOKIE = "cookie-consent-anon-id";
145
146
  * })
146
147
  * ```
147
148
  */
148
- declare const defaultConsentSchema: z.ZodObject<{
149
- necessary: z.ZodBoolean;
150
- analytics: z.ZodBoolean;
151
- marketing: z.ZodBoolean;
152
- functional: z.ZodBoolean;
153
- }, z.core.$strip>;
149
+ declare const defaultConsentSchema: z$1.ZodObject<{
150
+ necessary: z$1.ZodBoolean;
151
+ analytics: z$1.ZodBoolean;
152
+ marketing: z$1.ZodBoolean;
153
+ functional: z$1.ZodBoolean;
154
+ }, z$1.core.$strip>;
154
155
  declare const cookieConsentPlugin: <O extends CookieConsentOptions>(options?: O) => {
155
156
  id: "cookie-consent";
156
157
  schema: {
@@ -159,6 +160,7 @@ declare const cookieConsentPlugin: <O extends CookieConsentOptions>(options?: O)
159
160
  userId: {
160
161
  type: "string";
161
162
  required: false;
163
+ unique: true;
162
164
  references: {
163
165
  model: string;
164
166
  field: string;
@@ -167,6 +169,7 @@ declare const cookieConsentPlugin: <O extends CookieConsentOptions>(options?: O)
167
169
  anonymousId: {
168
170
  type: "string";
169
171
  required: true;
172
+ unique: true;
170
173
  };
171
174
  consent: {
172
175
  type: "string";
@@ -180,26 +183,27 @@ declare const cookieConsentPlugin: <O extends CookieConsentOptions>(options?: O)
180
183
  type: "date";
181
184
  defaultValue: () => Date;
182
185
  required: true;
186
+ input: false;
183
187
  };
184
188
  };
185
189
  };
186
190
  };
187
191
  endpoints: {
188
- setConsent: better_auth0.StrictEndpoint<"/cookie-consent/set", {
192
+ setConsent: import("better-auth").StrictEndpoint<"/cookie-consent/set", {
189
193
  method: "POST";
190
- body: z.ZodObject<{
191
- anonymousId: z.ZodString;
192
- consent: z.ZodRecord<z.ZodString, z.ZodBoolean>;
193
- consentVersion: z.ZodString;
194
- }, z.core.$strip>;
194
+ body: z$1.ZodObject<{
195
+ anonymousId: z$1.ZodString;
196
+ consent: z$1.ZodRecord<z$1.ZodString, z$1.ZodBoolean>;
197
+ consentVersion: z$1.ZodString;
198
+ }, z$1.core.$strip>;
195
199
  }, {
196
200
  status: boolean;
197
201
  }>;
198
- getConsent: better_auth0.StrictEndpoint<"/cookie-consent/get", {
202
+ getConsent: import("better-auth").StrictEndpoint<"/cookie-consent/get", {
199
203
  method: "GET";
200
- query: z.ZodObject<{
201
- anonymousId: z.ZodOptional<z.ZodString>;
202
- }, z.core.$strip>;
204
+ query: z$1.ZodObject<{
205
+ anonymousId: z$1.ZodOptional<z$1.ZodString>;
206
+ }, z$1.core.$strip>;
203
207
  }, {
204
208
  consent: null;
205
209
  versionMatch: boolean;
@@ -214,11 +218,11 @@ declare const cookieConsentPlugin: <O extends CookieConsentOptions>(options?: O)
214
218
  };
215
219
  versionMatch: boolean;
216
220
  }>;
217
- mergeConsent: better_auth0.StrictEndpoint<"/cookie-consent/merge", {
221
+ mergeConsent: import("better-auth").StrictEndpoint<"/cookie-consent/merge", {
218
222
  method: "POST";
219
- body: z.ZodObject<{
220
- anonymousId: z.ZodString;
221
- }, z.core.$strip>;
223
+ body: z$1.ZodObject<{
224
+ anonymousId: z$1.ZodString;
225
+ }, z$1.core.$strip>;
222
226
  }, {
223
227
  status: boolean;
224
228
  merged: boolean;
@@ -226,8 +230,8 @@ declare const cookieConsentPlugin: <O extends CookieConsentOptions>(options?: O)
226
230
  };
227
231
  hooks: {
228
232
  after: {
229
- matcher: (context: better_auth0.HookEndpointContext) => boolean;
230
- handler: (inputContext: better_auth0.MiddlewareInputContext<better_auth0.MiddlewareOptions>) => Promise<void>;
233
+ matcher: (context: import("better-auth").HookEndpointContext) => boolean;
234
+ handler: import("better-auth").Middleware<import("better-auth").MiddlewareOptions, (inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<void>>;
231
235
  }[];
232
236
  };
233
237
  options: NoInfer<O>;
@@ -237,11 +241,11 @@ declare const cookieConsentPlugin: <O extends CookieConsentOptions>(options?: O)
237
241
  max: number;
238
242
  }[];
239
243
  $ERROR_CODES: {
240
- MISSING_ANONYMOUS_ID: better_auth0.RawError<"MISSING_ANONYMOUS_ID">;
241
- CONSENT_NOT_FOUND: better_auth0.RawError<"CONSENT_NOT_FOUND">;
242
- INVALID_CONSENT: better_auth0.RawError<"INVALID_CONSENT">;
243
- VERSION_MISMATCH: better_auth0.RawError<"VERSION_MISMATCH">;
244
- AUTHENTICATION_REQUIRED: better_auth0.RawError<"AUTHENTICATION_REQUIRED">;
244
+ AUTHENTICATION_REQUIRED: import("better-auth").RawError<"AUTHENTICATION_REQUIRED">;
245
+ CONSENT_NOT_FOUND: import("better-auth").RawError<"CONSENT_NOT_FOUND">;
246
+ INVALID_CONSENT: import("better-auth").RawError<"INVALID_CONSENT">;
247
+ MISSING_ANONYMOUS_ID: import("better-auth").RawError<"MISSING_ANONYMOUS_ID">;
248
+ VERSION_MISMATCH: import("better-auth").RawError<"VERSION_MISMATCH">;
245
249
  };
246
250
  };
247
251
  /**
@@ -267,4 +271,4 @@ declare function getConsentFromCtx(ctx: {
267
271
  declare function hasConsent(ctx: Parameters<typeof getConsentFromCtx>[0], category: string): Promise<boolean>;
268
272
  //#endregion
269
273
  export { hasConsent as a, CookieConsentOptions as c, getConsentFromCtx as i, CookieConsentPayload as l, cookieConsentPlugin as n, Consent as o, defaultConsentSchema as r, ConsentSchemaModel as s, ANONYMOUS_ID_COOKIE as t, CookieConsentRecord as u };
270
- //# sourceMappingURL=index-B8ovmV25.d.mts.map
274
+ //# sourceMappingURL=index-B6ZS0bF2.d.mts.map
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as hasConsent, c as CookieConsentOptions, i as getConsentFromCtx, l as CookieConsentPayload, n as cookieConsentPlugin, o as Consent, r as defaultConsentSchema, s as ConsentSchemaModel, t as ANONYMOUS_ID_COOKIE, u as CookieConsentRecord } from "./index-B8ovmV25.mjs";
2
- export { ANONYMOUS_ID_COOKIE, Consent, ConsentSchemaModel, CookieConsentOptions, CookieConsentPayload, CookieConsentRecord, cookieConsentPlugin, defaultConsentSchema, getConsentFromCtx, hasConsent };
1
+ import { a as hasConsent, c as CookieConsentOptions, i as getConsentFromCtx, l as CookieConsentPayload, n as cookieConsentPlugin, o as Consent, r as defaultConsentSchema, s as ConsentSchemaModel, t as ANONYMOUS_ID_COOKIE, u as CookieConsentRecord } from "./index-B6ZS0bF2.mjs";
2
+ export { ANONYMOUS_ID_COOKIE, type Consent, type ConsentSchemaModel, type CookieConsentOptions, type CookieConsentPayload, type CookieConsentRecord, cookieConsentPlugin, defaultConsentSchema, getConsentFromCtx, hasConsent };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { APIError, createAuthEndpoint, createAuthMiddleware, getSessionFromCtx } from "better-auth/api";
2
- import * as z from "zod";
2
+ import * as z$1 from "zod";
3
3
  import { BASE_ERROR_CODES, defineErrorCodes } from "better-auth";
4
4
  import { mergeSchema } from "better-auth/db";
5
5
  //#region src/error-codes.ts
@@ -12,13 +12,13 @@ const COOKIE_CONSENT_ERROR_CODES = defineErrorCodes({
12
12
  });
13
13
  //#endregion
14
14
  //#region src/routes.ts
15
- const setConsentSchema = z.object({
16
- anonymousId: z.string().meta({ description: "Anonymous identifier for unauthenticated users" }),
17
- consent: z.record(z.string(), z.boolean()).meta({ description: "Consent preferences as category-boolean pairs" }),
18
- consentVersion: z.string().meta({ description: "Version of the consent policy" })
15
+ const setConsentSchema = z$1.object({
16
+ anonymousId: z$1.string().meta({ description: "Anonymous identifier for unauthenticated users" }),
17
+ consent: z$1.record(z$1.string(), z$1.boolean()).meta({ description: "Consent preferences as category-boolean pairs" }),
18
+ consentVersion: z$1.string().meta({ description: "Version of the consent policy" })
19
19
  });
20
- const getConsentSchema = z.object({ anonymousId: z.string().optional().meta({ description: "Anonymous identifier fallback when no session exists" }) });
21
- const mergeConsentSchema = z.object({ anonymousId: z.string().meta({ description: "Anonymous identifier to merge consent from" }) });
20
+ const getConsentSchema = z$1.object({ anonymousId: z$1.string().optional().meta({ description: "Anonymous identifier fallback when no session exists" }) });
21
+ const mergeConsentSchema = z$1.object({ anonymousId: z$1.string().meta({ description: "Anonymous identifier to merge consent from" }) });
22
22
  const setConsent = (options) => createAuthEndpoint("/cookie-consent/set", {
23
23
  method: "POST",
24
24
  body: setConsentSchema
@@ -112,51 +112,53 @@ const mergeConsent = (_options) => createAuthEndpoint("/cookie-consent/merge", {
112
112
  * @returns `true` if consent was merged, `false` if no anonymous record was found.
113
113
  */
114
114
  async function mergeAnonymousConsentToUser(adapter, userId, anonymousId) {
115
- const anonymousRecord = await adapter.findOne({
116
- model: "cookieConsent",
117
- where: [{
118
- field: "anonymousId",
119
- value: anonymousId
120
- }]
121
- });
122
- if (!anonymousRecord || anonymousRecord.userId) return false;
123
- const userRecord = await adapter.findOne({
124
- model: "cookieConsent",
125
- where: [{
126
- field: "userId",
127
- value: userId
128
- }]
129
- });
130
- if (userRecord) {
131
- await adapter.update({
115
+ return await adapter.transaction(async (tx) => {
116
+ const anonymousRecord = await tx.findOne({
132
117
  model: "cookieConsent",
133
118
  where: [{
134
- field: "id",
135
- value: userRecord.id
136
- }],
137
- update: {
138
- consent: anonymousRecord.consent,
139
- consentVersion: anonymousRecord.consentVersion,
140
- anonymousId,
141
- timestamp: /* @__PURE__ */ new Date()
142
- }
119
+ field: "anonymousId",
120
+ value: anonymousId
121
+ }]
143
122
  });
144
- await adapter.delete({
123
+ if (!anonymousRecord || anonymousRecord.userId) return false;
124
+ const userRecord = await tx.findOne({
125
+ model: "cookieConsent",
126
+ where: [{
127
+ field: "userId",
128
+ value: userId
129
+ }]
130
+ });
131
+ if (userRecord) {
132
+ await tx.delete({
133
+ model: "cookieConsent",
134
+ where: [{
135
+ field: "id",
136
+ value: anonymousRecord.id
137
+ }]
138
+ });
139
+ await tx.update({
140
+ model: "cookieConsent",
141
+ where: [{
142
+ field: "id",
143
+ value: userRecord.id
144
+ }],
145
+ update: {
146
+ consent: anonymousRecord.consent,
147
+ consentVersion: anonymousRecord.consentVersion,
148
+ anonymousId: anonymousRecord.anonymousId,
149
+ timestamp: /* @__PURE__ */ new Date()
150
+ }
151
+ });
152
+ } else await tx.update({
145
153
  model: "cookieConsent",
146
154
  where: [{
147
155
  field: "id",
148
156
  value: anonymousRecord.id
149
- }]
157
+ }],
158
+ update: { userId }
150
159
  });
151
- } else await adapter.update({
152
- model: "cookieConsent",
153
- where: [{
154
- field: "id",
155
- value: anonymousRecord.id
156
- }],
157
- update: { userId }
160
+ return true;
158
161
  });
159
- return true;
160
162
  }
161
163
  /**
162
164
  * Find a consent record by userId (preferred) or anonymousId fallback.
@@ -201,6 +203,7 @@ const cookieConsent = { cookieConsent: { fields: {
201
203
  userId: {
202
204
  type: "string",
203
205
  required: false,
206
+ unique: true,
204
207
  references: {
205
208
  model: "user",
206
209
  field: "id"
@@ -208,7 +211,8 @@ const cookieConsent = { cookieConsent: { fields: {
208
211
  },
209
212
  anonymousId: {
210
213
  type: "string",
211
- required: true
214
+ required: true,
215
+ unique: true
212
216
  },
213
217
  consent: {
214
218
  type: "string",
@@ -221,7 +225,8 @@ const cookieConsent = { cookieConsent: { fields: {
221
225
  timestamp: {
222
226
  type: "date",
223
227
  defaultValue: () => /* @__PURE__ */ new Date(),
224
- required: true
228
+ required: true,
229
+ input: false
225
230
  }
226
231
  } } };
227
232
  const getSchema = (options) => {
@@ -265,11 +270,11 @@ function parseCookieValue(cookieHeader, name) {
265
270
  * })
266
271
  * ```
267
272
  */
268
- const defaultConsentSchema = z.object({
269
- necessary: z.boolean(),
270
- analytics: z.boolean(),
271
- marketing: z.boolean(),
272
- functional: z.boolean()
273
+ const defaultConsentSchema = z$1.object({
274
+ necessary: z$1.boolean(),
275
+ analytics: z$1.boolean(),
276
+ marketing: z$1.boolean(),
277
+ functional: z$1.boolean()
273
278
  });
274
279
  const cookieConsentPlugin = (options = {}) => {
275
280
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/error-codes.ts","../src/routes.ts","../src/schema.ts","../src/index.ts"],"sourcesContent":["import { defineErrorCodes } from 'better-auth';\n\nexport const COOKIE_CONSENT_ERROR_CODES = defineErrorCodes({\n MISSING_ANONYMOUS_ID: 'Anonymous ID is required',\n CONSENT_NOT_FOUND: 'Cookie consent record not found',\n INVALID_CONSENT: 'Consent must be a non-empty object with boolean values',\n VERSION_MISMATCH: 'Consent version is outdated and must be renewed',\n AUTHENTICATION_REQUIRED: 'Authentication is required for this operation',\n});\n","import {\n BASE_ERROR_CODES,\n type BetterAuthOptions,\n type DBAdapter,\n type InternalLogger,\n} from 'better-auth';\nimport { APIError, createAuthEndpoint, getSessionFromCtx } from 'better-auth/api';\nimport * as z from 'zod';\n\nimport { COOKIE_CONSENT_ERROR_CODES } from './error-codes';\nimport type {\n Consent,\n CookieConsentOptions,\n CookieConsentPayload,\n CookieConsentRecord,\n} from './type';\n\nconst setConsentSchema = z.object({\n anonymousId: z.string().meta({\n description: 'Anonymous identifier for unauthenticated users',\n }),\n consent: z.record(z.string(), z.boolean()).meta({\n description: 'Consent preferences as category-boolean pairs',\n }),\n consentVersion: z.string().meta({\n description: 'Version of the consent policy',\n }),\n});\n\nconst getConsentSchema = z.object({\n anonymousId: z.string().optional().meta({\n description: 'Anonymous identifier fallback when no session exists',\n }),\n});\n\nconst mergeConsentSchema = z.object({\n anonymousId: z.string().meta({\n description: 'Anonymous identifier to merge consent from',\n }),\n});\n\nexport const setConsent = <O extends CookieConsentOptions>(options: O) =>\n createAuthEndpoint(\n '/cookie-consent/set',\n {\n method: 'POST',\n body: setConsentSchema,\n },\n async (ctx) => {\n const { anonymousId, consent, consentVersion } = ctx.body;\n\n if (!anonymousId) {\n throw APIError.from('BAD_REQUEST', COOKIE_CONSENT_ERROR_CODES.MISSING_ANONYMOUS_ID);\n }\n\n if (!consent || Object.keys(consent).length === 0) {\n throw APIError.from('BAD_REQUEST', COOKIE_CONSENT_ERROR_CODES.INVALID_CONSENT);\n }\n\n const validatedConsent = validateConsent(options, consent, ctx.context.logger);\n\n const session = await getSessionFromCtx(ctx);\n const userId = session?.user?.id ?? null;\n\n // Look for existing record by userId or anonymousId\n const existing = await findConsentRecord(ctx, userId, anonymousId);\n\n const consentJson = JSON.stringify(validatedConsent);\n const now = new Date();\n\n if (existing) {\n await ctx.context.adapter.update<CookieConsentRecord>({\n model: 'cookieConsent',\n where: [{ field: 'id', value: existing.id }],\n update: {\n userId,\n consent: consentJson,\n consentVersion,\n timestamp: now,\n },\n });\n } else {\n await ctx.context.adapter.create<CookieConsentPayload, CookieConsentRecord>({\n model: 'cookieConsent',\n data: {\n userId,\n anonymousId,\n consent: consentJson,\n consentVersion,\n timestamp: now,\n },\n });\n }\n\n if (options.onConsentChange) {\n const record =\n (await findConsentRecord(ctx, userId, anonymousId)) ??\n ({\n id: '',\n userId,\n anonymousId,\n consent: consentJson,\n consentVersion,\n timestamp: now,\n } as CookieConsentRecord);\n\n await ctx.context.runInBackgroundOrAwait(\n options.onConsentChange({ consent: record }, ctx.request),\n );\n }\n\n return ctx.json({ status: true });\n },\n );\n\nexport const getConsent = <O extends CookieConsentOptions>(options: O) =>\n createAuthEndpoint(\n '/cookie-consent/get',\n {\n method: 'GET',\n query: getConsentSchema,\n },\n async (ctx) => {\n const session = await getSessionFromCtx(ctx);\n const userId = session?.user?.id ?? null;\n const anonymousId = ctx.query?.anonymousId;\n\n if (!userId && !anonymousId) {\n throw APIError.from('BAD_REQUEST', COOKIE_CONSENT_ERROR_CODES.MISSING_ANONYMOUS_ID);\n }\n\n const record = await findConsentRecord(ctx, userId, anonymousId);\n\n if (!record) {\n return ctx.json({ consent: null, versionMatch: false });\n }\n\n const currentVersion = options.consentVersion ?? 'v1';\n const versionMatch = record.consentVersion === currentVersion;\n\n return ctx.json({\n consent: {\n id: record.id,\n userId: record.userId,\n anonymousId: record.anonymousId,\n consent: JSON.parse(record.consent) as Consent,\n consentVersion: record.consentVersion,\n timestamp: record.timestamp,\n },\n versionMatch,\n });\n },\n );\n\nexport const mergeConsent = <O extends CookieConsentOptions>(_options: O) =>\n createAuthEndpoint(\n '/cookie-consent/merge',\n {\n method: 'POST',\n body: mergeConsentSchema,\n },\n async (ctx) => {\n const session = await getSessionFromCtx(ctx);\n const userId = session?.user?.id;\n\n if (!userId) {\n throw APIError.from('UNAUTHORIZED', COOKIE_CONSENT_ERROR_CODES.AUTHENTICATION_REQUIRED);\n }\n\n const merged = await mergeAnonymousConsentToUser(\n ctx.context.adapter,\n userId,\n ctx.body.anonymousId,\n );\n return ctx.json({ status: true, merged });\n },\n );\n\n/**\n * Merge anonymous consent into a user's record.\n * Shared between the merge endpoint and the sign-in/sign-up hook.\n *\n * @returns `true` if consent was merged, `false` if no anonymous record was found.\n */\nexport async function mergeAnonymousConsentToUser(\n adapter: DBAdapter<BetterAuthOptions>,\n userId: string,\n anonymousId: string,\n): Promise<boolean> {\n const anonymousRecord = await adapter.findOne<CookieConsentRecord>({\n model: 'cookieConsent',\n where: [{ field: 'anonymousId', value: anonymousId }],\n });\n\n if (!anonymousRecord || anonymousRecord.userId) return false;\n // ^ Skip if record doesn't exist or is already linked to a user (already merged)\n\n const userRecord = await adapter.findOne<CookieConsentRecord>({\n model: 'cookieConsent',\n where: [{ field: 'userId', value: userId }],\n });\n\n if (userRecord) {\n // User already has consent; update with anonymous data\n await adapter.update<CookieConsentRecord>({\n model: 'cookieConsent',\n where: [{ field: 'id', value: userRecord.id }],\n update: {\n consent: anonymousRecord.consent,\n consentVersion: anonymousRecord.consentVersion,\n anonymousId,\n timestamp: new Date(),\n },\n });\n await adapter.delete({\n model: 'cookieConsent',\n where: [{ field: 'id', value: anonymousRecord.id }],\n });\n } else {\n // Attach anonymous consent to user\n await adapter.update<CookieConsentRecord>({\n model: 'cookieConsent',\n where: [{ field: 'id', value: anonymousRecord.id }],\n update: { userId },\n });\n }\n\n return true;\n}\n\n/**\n * Find a consent record by userId (preferred) or anonymousId fallback.\n */\nasync function findConsentRecord(\n ctx: {\n context: {\n adapter: DBAdapter<BetterAuthOptions>;\n };\n },\n userId: string | null | undefined,\n anonymousId: string | undefined,\n): Promise<CookieConsentRecord | null> {\n if (userId) {\n const byUser = await ctx.context.adapter.findOne<CookieConsentRecord>({\n model: 'cookieConsent',\n where: [{ field: 'userId', value: userId }],\n });\n if (byUser) return byUser;\n }\n\n if (anonymousId) {\n return ctx.context.adapter.findOne<CookieConsentRecord>({\n model: 'cookieConsent',\n where: [{ field: 'anonymousId', value: anonymousId }],\n });\n }\n\n return null;\n}\n\n/**\n * Validate the consent object against the configured validation schema.\n * Returns the validated consent or the original consent when no schema is set.\n */\nfunction validateConsent(\n options: CookieConsentOptions,\n consent: Consent,\n logger: InternalLogger,\n): Consent {\n if (!options.consent?.validationSchema) {\n return consent;\n }\n const validationResult = options.consent.validationSchema['~standard'].validate(consent);\n\n if (validationResult instanceof Promise) {\n throw APIError.from('INTERNAL_SERVER_ERROR', BASE_ERROR_CODES.ASYNC_VALIDATION_NOT_SUPPORTED);\n }\n\n if (validationResult.issues) {\n logger.error('Invalid consent', validationResult.issues);\n throw APIError.from('BAD_REQUEST', COOKIE_CONSENT_ERROR_CODES.INVALID_CONSENT);\n }\n\n return validationResult.value as Consent;\n}\n","import { type BetterAuthPluginDBSchema } from 'better-auth';\nimport { mergeSchema } from 'better-auth/db';\n\nimport type { CookieConsentOptions } from './type';\n\nexport const cookieConsent = {\n cookieConsent: {\n fields: {\n userId: {\n type: 'string',\n required: false,\n references: {\n model: 'user',\n field: 'id',\n },\n },\n anonymousId: {\n type: 'string',\n required: true,\n },\n consent: {\n type: 'string',\n required: true,\n },\n consentVersion: {\n type: 'string',\n required: true,\n },\n timestamp: {\n type: 'date',\n defaultValue: () => new Date(),\n required: true,\n },\n },\n },\n} satisfies BetterAuthPluginDBSchema;\n\nexport const getSchema = <O extends CookieConsentOptions>(options: O) => {\n return mergeSchema(cookieConsent, options.schema);\n};\n","import type { BetterAuthOptions, BetterAuthPlugin, DBAdapter } from 'better-auth';\nimport { createAuthMiddleware } from 'better-auth/api';\nimport * as z from 'zod';\n\nimport { COOKIE_CONSENT_ERROR_CODES } from './error-codes';\nimport { getConsent, mergeAnonymousConsentToUser, mergeConsent, setConsent } from './routes';\nimport { getSchema } from './schema';\nimport type {\n Consent,\n ConsentSchemaModel,\n CookieConsentOptions,\n CookieConsentRecord,\n} from './type';\n\n/**\n * Cookie name used to store the anonymous consent ID.\n * The client sets this cookie so the server-side hook can\n * read it during sign-in to auto-merge anonymous consent.\n */\nexport const ANONYMOUS_ID_COOKIE = 'cookie-consent-anon-id';\n\n/**\n * Parse a single cookie value from a raw `Cookie` header string.\n */\nfunction parseCookieValue(\n cookieHeader: string | null | undefined,\n name: string,\n): string | undefined {\n if (!cookieHeader) return undefined;\n for (const pair of cookieHeader.split('; ')) {\n const [key, ...valueParts] = pair.split('=');\n if (key === name && valueParts.length > 0) {\n const raw = valueParts.join('=');\n try {\n return decodeURIComponent(raw);\n } catch {\n return raw;\n }\n }\n }\n return undefined;\n}\n\n/**\n * Preset validation schema for cookie consent.\n * Validates the standard consent categories: necessary, analytics, marketing, functional.\n * Use this with `consent.validationSchema` in the plugin options.\n *\n * @example\n * ```ts\n * cookieConsentPlugin({\n * consentVersion: 'v1',\n * consent: { validationSchema: defaultConsentSchema },\n * })\n * ```\n */\nexport const defaultConsentSchema = z.object({\n necessary: z.boolean(),\n analytics: z.boolean(),\n marketing: z.boolean(),\n functional: z.boolean(),\n}) satisfies ConsentSchemaModel;\n\nexport const cookieConsentPlugin = <O extends CookieConsentOptions>(options: O = {} as O) => {\n return {\n id: 'cookie-consent',\n schema: getSchema(options),\n endpoints: {\n setConsent: setConsent(options),\n getConsent: getConsent(options),\n mergeConsent: mergeConsent(options),\n },\n hooks: {\n after: [\n {\n // After sign-in or sign-up (any method), merge anonymous consent to user.\n // Uses prefix matching to cover all auth methods: email, social,\n // biometrics, passkey, phone, etc.\n matcher: (context) => {\n return (\n !!context.path &&\n (context.path.startsWith('/sign-in/') || context.path.startsWith('/sign-up/'))\n );\n },\n handler: createAuthMiddleware(async (ctx) => {\n const userId = ctx.context.newSession?.user?.id;\n if (!userId) return;\n\n // Read anonymousId from the cookie set by the client\n const anonymousId = parseCookieValue(ctx.headers?.get('cookie'), ANONYMOUS_ID_COOKIE);\n if (!anonymousId) return;\n\n await mergeAnonymousConsentToUser(ctx.context.adapter, userId, anonymousId);\n }),\n },\n ],\n },\n options: options as NoInfer<O>,\n rateLimit: [\n {\n pathMatcher: (path) => ['/cookie-consent/set', '/cookie-consent/merge'].includes(path),\n window: options.rateLimit?.window ?? 10,\n max: options.rateLimit?.max ?? 10,\n },\n ],\n $ERROR_CODES: COOKIE_CONSENT_ERROR_CODES,\n } satisfies BetterAuthPlugin;\n};\n\n// ─── Helper utilities ──────────────────────────────────────────────────\n\n/**\n * Extract the parsed consent object from the endpoint context.\n * Returns `null` if no consent is available.\n */\nexport async function getConsentFromCtx(ctx: {\n context: {\n adapter: DBAdapter<BetterAuthOptions>;\n session?: { user?: { id?: string } } | null;\n };\n query?: { anonymousId?: string };\n}): Promise<CookieConsentRecord | null> {\n const userId = ctx.context.session?.user?.id ?? null;\n const anonymousId = ctx.query?.anonymousId;\n\n if (userId) {\n const byUser = await ctx.context.adapter.findOne<CookieConsentRecord>({\n model: 'cookieConsent',\n where: [{ field: 'userId', value: userId }],\n });\n if (byUser) return byUser;\n }\n\n if (anonymousId) {\n return ctx.context.adapter.findOne<CookieConsentRecord>({\n model: 'cookieConsent',\n where: [{ field: 'anonymousId', value: anonymousId }],\n });\n }\n\n return null;\n}\n\n/**\n * Check if a specific consent category is granted.\n */\nexport async function hasConsent(\n ctx: Parameters<typeof getConsentFromCtx>[0],\n category: string,\n): Promise<boolean> {\n const record = await getConsentFromCtx(ctx);\n if (!record) return false;\n const parsed = JSON.parse(record.consent) as Consent;\n return parsed[category] === true;\n}\n\nexport type * from './type';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["z","z"],"sources":["../src/error-codes.ts","../src/routes.ts","../src/schema.ts","../src/index.ts"],"sourcesContent":["import { defineErrorCodes } from 'better-auth';\n\nexport const COOKIE_CONSENT_ERROR_CODES = defineErrorCodes({\n MISSING_ANONYMOUS_ID: 'Anonymous ID is required',\n CONSENT_NOT_FOUND: 'Cookie consent record not found',\n INVALID_CONSENT: 'Consent must be a non-empty object with boolean values',\n VERSION_MISMATCH: 'Consent version is outdated and must be renewed',\n AUTHENTICATION_REQUIRED: 'Authentication is required for this operation',\n});\n","import {\n BASE_ERROR_CODES,\n type BetterAuthOptions,\n type DBAdapter,\n type InternalLogger,\n} from 'better-auth';\nimport { APIError, createAuthEndpoint, getSessionFromCtx } from 'better-auth/api';\nimport * as z from 'zod';\n\nimport { COOKIE_CONSENT_ERROR_CODES } from './error-codes';\nimport type {\n Consent,\n CookieConsentOptions,\n CookieConsentPayload,\n CookieConsentRecord,\n} from './type';\n\nconst setConsentSchema = z.object({\n anonymousId: z.string().meta({\n description: 'Anonymous identifier for unauthenticated users',\n }),\n consent: z.record(z.string(), z.boolean()).meta({\n description: 'Consent preferences as category-boolean pairs',\n }),\n consentVersion: z.string().meta({\n description: 'Version of the consent policy',\n }),\n});\n\nconst getConsentSchema = z.object({\n anonymousId: z.string().optional().meta({\n description: 'Anonymous identifier fallback when no session exists',\n }),\n});\n\nconst mergeConsentSchema = z.object({\n anonymousId: z.string().meta({\n description: 'Anonymous identifier to merge consent from',\n }),\n});\n\nexport const setConsent = <O extends CookieConsentOptions>(options: O) =>\n createAuthEndpoint(\n '/cookie-consent/set',\n {\n method: 'POST',\n body: setConsentSchema,\n },\n async (ctx) => {\n const { anonymousId, consent, consentVersion } = ctx.body;\n\n if (!anonymousId) {\n throw APIError.from('BAD_REQUEST', COOKIE_CONSENT_ERROR_CODES.MISSING_ANONYMOUS_ID);\n }\n\n if (!consent || Object.keys(consent).length === 0) {\n throw APIError.from('BAD_REQUEST', COOKIE_CONSENT_ERROR_CODES.INVALID_CONSENT);\n }\n\n const validatedConsent = validateConsent(options, consent, ctx.context.logger);\n\n const session = await getSessionFromCtx(ctx);\n const userId = session?.user?.id ?? null;\n\n // Look for existing record by userId or anonymousId\n const existing = await findConsentRecord(ctx, userId, anonymousId);\n\n const consentJson = JSON.stringify(validatedConsent);\n const now = new Date();\n\n if (existing) {\n await ctx.context.adapter.update<CookieConsentRecord>({\n model: 'cookieConsent',\n where: [{ field: 'id', value: existing.id }],\n update: {\n userId,\n consent: consentJson,\n consentVersion,\n timestamp: now,\n },\n });\n } else {\n await ctx.context.adapter.create<CookieConsentPayload, CookieConsentRecord>({\n model: 'cookieConsent',\n data: {\n userId,\n anonymousId,\n consent: consentJson,\n consentVersion,\n timestamp: now,\n },\n });\n }\n\n if (options.onConsentChange) {\n const record =\n (await findConsentRecord(ctx, userId, anonymousId)) ??\n ({\n id: '',\n userId,\n anonymousId,\n consent: consentJson,\n consentVersion,\n timestamp: now,\n } as CookieConsentRecord);\n\n await ctx.context.runInBackgroundOrAwait(\n options.onConsentChange({ consent: record }, ctx.request),\n );\n }\n\n return ctx.json({ status: true });\n },\n );\n\nexport const getConsent = <O extends CookieConsentOptions>(options: O) =>\n createAuthEndpoint(\n '/cookie-consent/get',\n {\n method: 'GET',\n query: getConsentSchema,\n },\n async (ctx) => {\n const session = await getSessionFromCtx(ctx);\n const userId = session?.user?.id ?? null;\n const anonymousId = ctx.query?.anonymousId;\n\n if (!userId && !anonymousId) {\n throw APIError.from('BAD_REQUEST', COOKIE_CONSENT_ERROR_CODES.MISSING_ANONYMOUS_ID);\n }\n\n const record = await findConsentRecord(ctx, userId, anonymousId);\n\n if (!record) {\n return ctx.json({ consent: null, versionMatch: false });\n }\n\n const currentVersion = options.consentVersion ?? 'v1';\n const versionMatch = record.consentVersion === currentVersion;\n\n return ctx.json({\n consent: {\n id: record.id,\n userId: record.userId,\n anonymousId: record.anonymousId,\n consent: JSON.parse(record.consent) as Consent,\n consentVersion: record.consentVersion,\n timestamp: record.timestamp,\n },\n versionMatch,\n });\n },\n );\n\nexport const mergeConsent = <O extends CookieConsentOptions>(_options: O) =>\n createAuthEndpoint(\n '/cookie-consent/merge',\n {\n method: 'POST',\n body: mergeConsentSchema,\n },\n async (ctx) => {\n const session = await getSessionFromCtx(ctx);\n const userId = session?.user?.id;\n\n if (!userId) {\n throw APIError.from('UNAUTHORIZED', COOKIE_CONSENT_ERROR_CODES.AUTHENTICATION_REQUIRED);\n }\n\n const merged = await mergeAnonymousConsentToUser(\n ctx.context.adapter,\n userId,\n ctx.body.anonymousId,\n );\n return ctx.json({ status: true, merged });\n },\n );\n\n/**\n * Merge anonymous consent into a user's record.\n * Shared between the merge endpoint and the sign-in/sign-up hook.\n *\n * @returns `true` if consent was merged, `false` if no anonymous record was found.\n */\nexport async function mergeAnonymousConsentToUser(\n adapter: DBAdapter<BetterAuthOptions>,\n userId: string,\n anonymousId: string,\n): Promise<boolean> {\n return await adapter.transaction(async (tx) => {\n const anonymousRecord = await tx.findOne<CookieConsentRecord>({\n model: 'cookieConsent',\n where: [{ field: 'anonymousId', value: anonymousId }],\n });\n\n // Skip if record doesn't exist or is already linked to a user (already merged)\n if (!anonymousRecord || anonymousRecord.userId) return false;\n\n const userRecord = await tx.findOne<CookieConsentRecord>({\n model: 'cookieConsent',\n where: [{ field: 'userId', value: userId }],\n });\n\n if (userRecord) {\n // Delete the anonymous record first so the new anonymousId can\n // be carried over without a unique-constraint conflict.\n await tx.delete({\n model: 'cookieConsent',\n where: [{ field: 'id', value: anonymousRecord.id }],\n });\n await tx.update<CookieConsentRecord>({\n model: 'cookieConsent',\n where: [{ field: 'id', value: userRecord.id }],\n update: {\n consent: anonymousRecord.consent,\n consentVersion: anonymousRecord.consentVersion,\n anonymousId: anonymousRecord.anonymousId,\n timestamp: new Date(),\n },\n });\n } else {\n await tx.update<CookieConsentRecord>({\n model: 'cookieConsent',\n where: [{ field: 'id', value: anonymousRecord.id }],\n update: { userId },\n });\n }\n\n return true;\n });\n}\n\n/**\n * Find a consent record by userId (preferred) or anonymousId fallback.\n */\nasync function findConsentRecord(\n ctx: {\n context: {\n adapter: DBAdapter<BetterAuthOptions>;\n };\n },\n userId: string | null | undefined,\n anonymousId: string | undefined,\n): Promise<CookieConsentRecord | null> {\n if (userId) {\n const byUser = await ctx.context.adapter.findOne<CookieConsentRecord>({\n model: 'cookieConsent',\n where: [{ field: 'userId', value: userId }],\n });\n if (byUser) return byUser;\n }\n\n if (anonymousId) {\n return ctx.context.adapter.findOne<CookieConsentRecord>({\n model: 'cookieConsent',\n where: [{ field: 'anonymousId', value: anonymousId }],\n });\n }\n\n return null;\n}\n\n/**\n * Validate the consent object against the configured validation schema.\n * Returns the validated consent or the original consent when no schema is set.\n */\nfunction validateConsent(\n options: CookieConsentOptions,\n consent: Consent,\n logger: InternalLogger,\n): Consent {\n if (!options.consent?.validationSchema) {\n return consent;\n }\n const validationResult = options.consent.validationSchema['~standard'].validate(consent);\n\n if (validationResult instanceof Promise) {\n throw APIError.from('INTERNAL_SERVER_ERROR', BASE_ERROR_CODES.ASYNC_VALIDATION_NOT_SUPPORTED);\n }\n\n if (validationResult.issues) {\n logger.error('Invalid consent', validationResult.issues);\n throw APIError.from('BAD_REQUEST', COOKIE_CONSENT_ERROR_CODES.INVALID_CONSENT);\n }\n\n return validationResult.value as Consent;\n}\n","import { type BetterAuthPluginDBSchema } from 'better-auth';\nimport { mergeSchema } from 'better-auth/db';\n\nimport type { CookieConsentOptions } from './type';\n\nexport const cookieConsent = {\n cookieConsent: {\n fields: {\n userId: {\n type: 'string',\n required: false,\n unique: true,\n references: {\n model: 'user',\n field: 'id',\n },\n },\n anonymousId: {\n type: 'string',\n required: true,\n unique: true,\n },\n consent: {\n type: 'string',\n required: true,\n },\n consentVersion: {\n type: 'string',\n required: true,\n },\n timestamp: {\n type: 'date',\n defaultValue: () => new Date(),\n required: true,\n input: false,\n },\n },\n },\n} satisfies BetterAuthPluginDBSchema;\n\nexport const getSchema = <O extends CookieConsentOptions>(options: O) => {\n return mergeSchema(cookieConsent, options.schema);\n};\n","import type { BetterAuthOptions, BetterAuthPlugin, DBAdapter } from 'better-auth';\nimport { createAuthMiddleware } from 'better-auth/api';\nimport * as z from 'zod';\n\nimport { COOKIE_CONSENT_ERROR_CODES } from './error-codes';\nimport { getConsent, mergeAnonymousConsentToUser, mergeConsent, setConsent } from './routes';\nimport { getSchema } from './schema';\nimport type {\n Consent,\n ConsentSchemaModel,\n CookieConsentOptions,\n CookieConsentRecord,\n} from './type';\n\n/**\n * Cookie name used to store the anonymous consent ID.\n * The client sets this cookie so the server-side hook can\n * read it during sign-in to auto-merge anonymous consent.\n */\nexport const ANONYMOUS_ID_COOKIE = 'cookie-consent-anon-id';\n\n/**\n * Parse a single cookie value from a raw `Cookie` header string.\n */\nfunction parseCookieValue(\n cookieHeader: string | null | undefined,\n name: string,\n): string | undefined {\n if (!cookieHeader) return undefined;\n for (const pair of cookieHeader.split('; ')) {\n const [key, ...valueParts] = pair.split('=');\n if (key === name && valueParts.length > 0) {\n const raw = valueParts.join('=');\n try {\n return decodeURIComponent(raw);\n } catch {\n return raw;\n }\n }\n }\n return undefined;\n}\n\n/**\n * Preset validation schema for cookie consent.\n * Validates the standard consent categories: necessary, analytics, marketing, functional.\n * Use this with `consent.validationSchema` in the plugin options.\n *\n * @example\n * ```ts\n * cookieConsentPlugin({\n * consentVersion: 'v1',\n * consent: { validationSchema: defaultConsentSchema },\n * })\n * ```\n */\nexport const defaultConsentSchema = z.object({\n necessary: z.boolean(),\n analytics: z.boolean(),\n marketing: z.boolean(),\n functional: z.boolean(),\n}) satisfies ConsentSchemaModel;\n\nexport const cookieConsentPlugin = <O extends CookieConsentOptions>(options: O = {} as O) => {\n return {\n id: 'cookie-consent',\n schema: getSchema(options),\n endpoints: {\n setConsent: setConsent(options),\n getConsent: getConsent(options),\n mergeConsent: mergeConsent(options),\n },\n hooks: {\n after: [\n {\n // After sign-in or sign-up (any method), merge anonymous consent to user.\n // Uses prefix matching to cover all auth methods: email, social,\n // biometrics, passkey, phone, etc.\n matcher: (context) => {\n return (\n !!context.path &&\n (context.path.startsWith('/sign-in/') || context.path.startsWith('/sign-up/'))\n );\n },\n handler: createAuthMiddleware(async (ctx) => {\n const userId = ctx.context.newSession?.user?.id;\n if (!userId) return;\n\n // Read anonymousId from the cookie set by the client\n const anonymousId = parseCookieValue(ctx.headers?.get('cookie'), ANONYMOUS_ID_COOKIE);\n if (!anonymousId) return;\n\n await mergeAnonymousConsentToUser(ctx.context.adapter, userId, anonymousId);\n }),\n },\n ],\n },\n options: options as NoInfer<O>,\n rateLimit: [\n {\n pathMatcher: (path) => ['/cookie-consent/set', '/cookie-consent/merge'].includes(path),\n window: options.rateLimit?.window ?? 10,\n max: options.rateLimit?.max ?? 10,\n },\n ],\n $ERROR_CODES: COOKIE_CONSENT_ERROR_CODES,\n } satisfies BetterAuthPlugin;\n};\n\n// ─── Helper utilities ──────────────────────────────────────────────────\n\n/**\n * Extract the parsed consent object from the endpoint context.\n * Returns `null` if no consent is available.\n */\nexport async function getConsentFromCtx(ctx: {\n context: {\n adapter: DBAdapter<BetterAuthOptions>;\n session?: { user?: { id?: string } } | null;\n };\n query?: { anonymousId?: string };\n}): Promise<CookieConsentRecord | null> {\n const userId = ctx.context.session?.user?.id ?? null;\n const anonymousId = ctx.query?.anonymousId;\n\n if (userId) {\n const byUser = await ctx.context.adapter.findOne<CookieConsentRecord>({\n model: 'cookieConsent',\n where: [{ field: 'userId', value: userId }],\n });\n if (byUser) return byUser;\n }\n\n if (anonymousId) {\n return ctx.context.adapter.findOne<CookieConsentRecord>({\n model: 'cookieConsent',\n where: [{ field: 'anonymousId', value: anonymousId }],\n });\n }\n\n return null;\n}\n\n/**\n * Check if a specific consent category is granted.\n */\nexport async function hasConsent(\n ctx: Parameters<typeof getConsentFromCtx>[0],\n category: string,\n): Promise<boolean> {\n const record = await getConsentFromCtx(ctx);\n if (!record) return false;\n const parsed = JSON.parse(record.consent) as Consent;\n return parsed[category] === true;\n}\n\nexport type * from './type';\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "better-auth-cookie-consent",
3
- "version": "0.1.1",
3
+ "version": "0.3.0-dev.0",
4
4
  "description": "Better Auth Cookie Consent plugin for GDPR-compliant consent management",
5
5
  "homepage": "https://github.com/marcjulian/better-auth-plugins#readme",
6
6
  "bugs": {
7
7
  "url": "https://github.com/marcjulian/better-auth-plugins/issues"
8
8
  },
9
9
  "license": "MIT",
10
- "author": "Marc Stammerjohann <me@marcjulian.de>",
10
+ "author": "Gary Großgarten <info@gary.dev>",
11
11
  "repository": {
12
12
  "type": "git",
13
13
  "url": "git+https://github.com/marcjulian/better-auth-plugins.git"
@@ -25,23 +25,26 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "nanostores": "^1.1.1",
29
- "zod": "^4.3.6"
28
+ "zod": "^4.4.3"
30
29
  },
31
30
  "devDependencies": {
32
- "@types/node": "^25.3.5",
33
- "bumpp": "^10.4.1",
34
- "tsdown": "^0.21.1",
35
- "typescript": "^5.9.3",
36
- "vitest": "^4.0.18"
31
+ "@types/node": "^25.6.2",
32
+ "better-auth": "1.7.5",
33
+ "bumpp": "^11.1.0",
34
+ "nanostores": "^1.5.1",
35
+ "tsdown": "^0.22.14",
36
+ "typescript": "^7.0.2",
37
+ "vitest": "^4.1.11"
37
38
  },
38
39
  "peerDependencies": {
39
- "better-auth": "^1.5.0"
40
+ "better-auth": "^1.7.5",
41
+ "nanostores": "^1.5.0"
40
42
  },
41
43
  "scripts": {
42
44
  "build": "tsdown",
43
45
  "dev": "tsdown --watch",
44
46
  "test": "vitest",
45
- "typecheck": "tsc --noEmit"
47
+ "typecheck": "tsc --noEmit",
48
+ "release": "bumpp"
46
49
  }
47
50
  }