@lunora/auth 1.0.0-alpha.2 → 1.0.0-alpha.200

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.
Files changed (86) hide show
  1. package/LICENSE.md +38 -0
  2. package/README.md +99 -10
  3. package/__assets__/package-og.svg +1 -1
  4. package/dist/adapter.d.mts +4 -43
  5. package/dist/adapter.d.ts +4 -43
  6. package/dist/adapter.mjs +1 -47
  7. package/dist/audit.d.mts +142 -0
  8. package/dist/audit.d.ts +142 -0
  9. package/dist/audit.mjs +12 -0
  10. package/dist/cimd-node.d.mts +1 -0
  11. package/dist/cimd-node.d.ts +1 -0
  12. package/dist/cimd-node.mjs +1 -0
  13. package/dist/cimd-workers.d.mts +32 -0
  14. package/dist/cimd-workers.d.ts +32 -0
  15. package/dist/cimd-workers.mjs +1 -0
  16. package/dist/email-guard.d.mts +127 -0
  17. package/dist/email-guard.d.ts +127 -0
  18. package/dist/email-guard.mjs +1 -0
  19. package/dist/index.d.mts +935 -147
  20. package/dist/index.d.ts +935 -147
  21. package/dist/index.mjs +1 -12
  22. package/dist/middleware.d.mts +167 -156
  23. package/dist/middleware.d.ts +167 -156
  24. package/dist/middleware.mjs +1 -53
  25. package/dist/packem_shared/AUTH_DO_AUDIT_PATH-CptaQ6ZX.mjs +1 -0
  26. package/dist/packem_shared/DEFAULT_AUTH_BASE_PATH-DneiLGpv.mjs +1 -0
  27. package/dist/packem_shared/LunoraAuthAdminError-uhKG1MgQ.mjs +1 -0
  28. package/dist/packem_shared/adapter-CG0Yv85f.mjs +2 -0
  29. package/dist/packem_shared/adapter.d-CElAPJ1U.d.ts +88 -0
  30. package/dist/packem_shared/adapter.d-CaPwF2v3.d.mts +88 -0
  31. package/dist/packem_shared/auth-do-B2X2lboC.mjs +1 -0
  32. package/dist/packem_shared/authAuditHook-DPTKdjBe.mjs +1 -0
  33. package/dist/packem_shared/authDiscoveryPaths-B14lM14u.mjs +1 -0
  34. package/dist/packem_shared/authDoColumnAdditions-UwF3Rl41.mjs +1 -0
  35. package/dist/packem_shared/compileMigrationsSql-BiYsLz91.mjs +1 -0
  36. package/dist/packem_shared/create-auth.d-D1CKOFWc.d.mts +142 -0
  37. package/dist/packem_shared/create-auth.d-D1CKOFWc.d.ts +142 -0
  38. package/dist/packem_shared/createAuth-BdVltKyf.mjs +1 -0
  39. package/dist/packem_shared/createDoAuthWiring-cqHnqjyh.mjs +1 -0
  40. package/dist/packem_shared/createLunoraAuthClient-DRcFNwp9.mjs +1 -0
  41. package/dist/packem_shared/createSignUpInvitation-DHZL9fNl.mjs +1 -0
  42. package/dist/packem_shared/emailGateDatabaseHooks-CAygR3iq.mjs +1 -0
  43. package/dist/packem_shared/invite-only.d-CsPJWAwM.d.ts +126 -0
  44. package/dist/packem_shared/invite-only.d-DKyHHkkQ.d.mts +126 -0
  45. package/dist/packem_shared/legacyIssuerCleanupStatements-D9Kf8OD1.mjs +1 -0
  46. package/dist/packem_shared/mcp-CgX1hTOv.mjs +1 -0
  47. package/dist/packem_shared/on-cloudflare-edge-Dx6gFjBO.mjs +1 -0
  48. package/dist/packem_shared/quote-identifier-CGiYFBvY.mjs +1 -0
  49. package/dist/packem_shared/sessionPresets-C867Mlo4.mjs +1 -0
  50. package/dist/packem_shared/sql-store-CYa4HJsO.mjs +1 -0
  51. package/dist/packem_shared/uiConfig-B8pN6pks.mjs +1 -0
  52. package/dist/plugins-client.d.mts +250 -1
  53. package/dist/plugins-client.d.ts +250 -1
  54. package/dist/plugins-client.mjs +1 -2
  55. package/dist/plugins-enterprise-client.d.mts +13 -0
  56. package/dist/plugins-enterprise-client.d.ts +13 -0
  57. package/dist/plugins-enterprise-client.mjs +1 -0
  58. package/dist/plugins-enterprise.d.mts +1668 -0
  59. package/dist/plugins-enterprise.d.ts +1668 -0
  60. package/dist/plugins-enterprise.mjs +1 -0
  61. package/dist/plugins.d.mts +167 -2
  62. package/dist/plugins.d.ts +167 -2
  63. package/dist/plugins.mjs +1 -22
  64. package/dist/schema.d.mts +39 -39
  65. package/dist/schema.d.ts +39 -39
  66. package/dist/schema.mjs +1 -62
  67. package/dist/sql-store.d.mts +28 -28
  68. package/dist/sql-store.d.ts +28 -28
  69. package/dist/sql-store.mjs +1 -162
  70. package/dist/store.d.mts +67 -31
  71. package/dist/store.d.ts +67 -31
  72. package/dist/store.mjs +1 -170
  73. package/dist/turnstile-middleware.d.mts +73 -55
  74. package/dist/turnstile-middleware.d.ts +73 -55
  75. package/dist/turnstile-middleware.mjs +1 -45
  76. package/dist/turnstile.d.mts +42 -59
  77. package/dist/turnstile.d.ts +42 -59
  78. package/dist/turnstile.mjs +1 -61
  79. package/package.json +49 -6
  80. package/dist/packem_shared/DEFAULT_AUTH_BASE_PATH-DjcUWEQl.mjs +0 -11
  81. package/dist/packem_shared/LunoraAuthAdminError-BxrfEeA_.mjs +0 -249
  82. package/dist/packem_shared/compileMigrationsSql-wZH3oXDu.mjs +0 -28
  83. package/dist/packem_shared/create-auth.d-M36jwG_Y.d.mts +0 -58
  84. package/dist/packem_shared/create-auth.d-M36jwG_Y.d.ts +0 -58
  85. package/dist/packem_shared/createAuth-B-tvsvQU.mjs +0 -56
  86. package/dist/packem_shared/sessionPresets-B95rXrd8.mjs +0 -35
@@ -0,0 +1,126 @@
1
+ import { BetterAuthPlugin } from 'better-auth';
2
+ import { L as LunoraAuth } from "./create-auth.d-D1CKOFWc.mjs";
3
+ /** One pending or spent sign-up invitation. */
4
+ interface SignUpInvitation {
5
+ /** When an account was created for this address; `null` while the invitation is unspent. */
6
+ acceptedAt: Date | null;
7
+ createdAt: Date;
8
+ /** The invited address, lowercased — the only thing an invitation is matched on. */
9
+ email: string;
10
+ expiresAt: Date;
11
+ id: string;
12
+ /** Free-form attribution (a user id, an operator name); never read by the gate. */
13
+ invitedBy: null | string;
14
+ }
15
+ /**
16
+ * What {@link createSignUpInvitation} hands back: the stored row plus the one
17
+ * and only sight of the plaintext `token`. Nothing reads it back afterwards —
18
+ * the database holds a SHA-256 of it — so an invitation link that is lost is
19
+ * reissued, not recovered.
20
+ */
21
+ interface IssuedSignUpInvitation extends SignUpInvitation {
22
+ /** Put this in the sign-up link as `?invite=…`. Never stored, never logged, never listed. */
23
+ token: string;
24
+ }
25
+ /** Options for {@link inviteOnly}. */
26
+ interface InviteOnlyOptions {
27
+ /**
28
+ * Let the very first account through uninvited, so a fresh deployment can be
29
+ * bootstrapped without seeding a row.
30
+ *
31
+ * **Off by default.** The check is "the `user` table is empty", which two
32
+ * concurrent sign-ups can both observe, and the window it opens is the gap
33
+ * between deploying and the owner signing up — whoever finds the URL first
34
+ * gets an account on a deployment whose whole point is that nobody does. Seed
35
+ * the first invitation with {@link createSignUpInvitation} instead (a one-off
36
+ * call at worker init, or an internal mutation you run once).
37
+ * @default false
38
+ */
39
+ allowFirstUser?: boolean;
40
+ }
41
+ /**
42
+ * A better-auth server plugin that refuses to create an account for an address
43
+ * with no unspent invitation, and the `signUpInvitation` table those live in.
44
+ *
45
+ * Issue invitations with {@link createSignUpInvitation}; there is no HTTP
46
+ * endpoint for it on purpose. Who counts as an administrator is your
47
+ * application's question — call it from a mutation you already authorize, the
48
+ * same trust model `createAuthAdmin` documents.
49
+ *
50
+ * The return type is better-auth's own `BetterAuthPlugin` rather than the
51
+ * precise shape of the schema map, for the reason `./ui-config.ts` spells out:
52
+ * an anonymous inferred type is the difference between a build that emits
53
+ * declarations and one that fails in the bundler alone.
54
+ */
55
+ declare const inviteOnly: (options?: InviteOnlyOptions) => BetterAuthPlugin;
56
+ /**
57
+ * Invite `email` to sign up, or refresh an existing invitation for it.
58
+ *
59
+ * Re-inviting an address updates the row in place — a new expiry, and `acceptedAt`
60
+ * cleared — because `email` is unique. That is also how you re-open a seat after
61
+ * deleting the account that took it.
62
+ *
63
+ * This is a trusted server-side call with no authorization of its own; gate it
64
+ * the way you gate any other administrative action. Delivering the invitation is
65
+ * yours too: nothing here sends mail, so the returned row is the whole handoff.
66
+ *
67
+ * Nothing prunes the table — a spent or expired row stays until you delete it with
68
+ * {@link revokeSignUpInvitation}, which is also what keeps it a record of who was
69
+ * let in.
70
+ *
71
+ * Each call mints a **new token** and returns it in the clear, once. Only its
72
+ * SHA-256 is stored, so re-inviting an address invalidates the previous link, and
73
+ * a link that was never delivered is reissued rather than looked up.
74
+ */
75
+ declare const createSignUpInvitation: (auth: LunoraAuth, input: {
76
+ email: string;
77
+ expiresInSeconds?: number;
78
+ invitedBy?: string;
79
+ }) => Promise<IssuedSignUpInvitation>;
80
+ /**
81
+ * The most recent invitations, newest first, up to a fixed ceiling of
82
+ * {@link MAX_LISTED}. `pendingOnly` drops the spent and the expired, which is the
83
+ * list an operator usually wants; the unfiltered form doubles as the record of who
84
+ * was let in.
85
+ *
86
+ * Deliberately not paged. "Pending" is two conditions, one of them a comparison
87
+ * against `now`, and filtering those after a page would let page 1 come back empty
88
+ * while pending invitations sat on page 2. An operator list that outgrows the
89
+ * ceiling wants a query against the `signUpInvitation` table, not an offset.
90
+ */
91
+ declare const listSignUpInvitations: (auth: LunoraAuth, options?: {
92
+ pendingOnly?: boolean;
93
+ }) => Promise<SignUpInvitation[]>;
94
+ /**
95
+ * Withdraw the invitation for `email`. Deletes the row, so it also forgets a spent
96
+ * one — the account it created is untouched, and removing that is
97
+ * `AuthAdmin.removeUser`'s job.
98
+ *
99
+ * Not retroactive, and not atomic against a sign-up already in flight: better-auth
100
+ * creates the user without wrapping the `before` hook and the insert in one
101
+ * transaction, so a revoke landing between the two lets that one account through.
102
+ * There is no conditional consume in the adapter contract to close it with. Treat
103
+ * revocation as "no further sign-ups", and `AuthAdmin.removeUser` as the way to
104
+ * undo one that already happened.
105
+ */
106
+ declare const revokeSignUpInvitation: (auth: LunoraAuth, input: {
107
+ email: string;
108
+ }) => Promise<void>;
109
+ /**
110
+ * Delete invitations that expired without being used, and report how many went.
111
+ *
112
+ * Only the dead ones: a spent invitation is the record of who was let in, and an
113
+ * unexpired one is still live, so both stay. Nothing calls this for you — an app
114
+ * that invites at any volume should put it on a cron; one that doesn't can leave
115
+ * the rows.
116
+ *
117
+ * Bounded by `limit` and therefore incremental: a backlog larger than one pass
118
+ * takes several. It reads a page and deletes row by row rather than issuing one
119
+ * ranged `deleteMany`, because a `lt` comparison against a `date` column is the
120
+ * kind of thing that behaves differently on each of the three adapters this
121
+ * package ships, and a prune job is not where that should be discovered.
122
+ */
123
+ declare const pruneSignUpInvitations: (auth: LunoraAuth, options?: {
124
+ limit?: number;
125
+ }) => Promise<number>;
126
+ export { InviteOnlyOptions as I, SignUpInvitation as S, IssuedSignUpInvitation as a, createSignUpInvitation as c, inviteOnly as i, listSignUpInvitations as l, pruneSignUpInvitations as p, revokeSignUpInvitation as r };
@@ -0,0 +1 @@
1
+ import{q as r}from"./quote-identifier-CGiYFBvY.mjs";const i="issuer",c=new RegExp(String.raw`\b${i}\b`,"iu"),u=e=>{const s=e.indexOf("(");if(s===-1)return"";let n=0;for(let t=s;t<e.length;t+=1){const o=e[t];if(o==="(")n+=1;else if(o===")"&&(n-=1,n===0))return e.slice(s+1,t)}return""},f=e=>[...e].includes(i),p=e=>[...e].filter(s=>c.test(u(s.sql??""))).map(s=>s.name),l=(e="account",s=[])=>[...[...s].map(n=>`DROP INDEX IF EXISTS ${r(n)}`),`ALTER TABLE ${r(e)} DROP COLUMN ${r(i)}`];export{p as indexesReferencingIssuer,l as legacyIssuerCleanupStatements,f as schemaDeclaresIssuer};
@@ -0,0 +1 @@
1
+ import{mcp as o,requireMcpAuth as s}from"@better-auth/mcp";import{LunoraError as c}from"@lunora/errors";import{MCP_RESOURCE_KEY as u,protectedResourcePath as i,authorizationServerPath as n}from"./authDiscoveryPaths-B14lM14u.mjs";import{DEFAULT_AUTH_BASE_PATH as p}from"./DEFAULT_AUTH_BASE_PATH-DneiLGpv.mjs";const a=e=>{if(!(typeof e=="string"&&URL.canParse(e)))throw new c("AUTH_MCP_RESOURCE_INVALID",`requireMcpAuth needs \`resource\` set to the absolute URL passed to mcp({ resource }), got ${JSON.stringify(e)}. Without it no issued token's audience matches and every request is refused.`)},d=e=>{const r=o(e);return Object.defineProperty(r,u,{value:e.resource}),r},E=(e,r,t)=>(a(t.resource),s(e,r,t)),P=(e,r=p)=>[i(e),n(r)];export{d as mcp,P as mcpDiscoveryPaths,E as requireMcpAuth};
@@ -0,0 +1 @@
1
+ const o=()=>globalThis.navigator?.userAgent==="Cloudflare-Workers";export{o};
@@ -0,0 +1 @@
1
+ const t=e=>`"${e.replaceAll('"','""')}"`;export{t as q};
@@ -0,0 +1 @@
1
+ const t=n=>{const o=["expiresIn","updateAge","freshAge"];for(const s of o){const e=n[s];if(e!==void 0&&(typeof e!="number"||!Number.isFinite(e)||e<0))throw new TypeError(`@lunora/auth: \`session.${s}\` must be a non-negative, finite number of seconds`)}return n},i={longLived:{cookieCache:{enabled:!0,maxAge:60},expiresIn:2592e3,freshAge:86400,updateAge:86400},rolling:{cookieCache:{enabled:!0,maxAge:60},expiresIn:604800,freshAge:86400,updateAge:86400},strict:{cookieCache:{enabled:!1},expiresIn:3600,freshAge:300,updateAge:900}};export{i as sessionPresets,t as validateSessionPolicy};
@@ -0,0 +1 @@
1
+ const T="_creationTime",l=s=>`"${s.replaceAll('"','""')}"`,N=/no such column|column .{0,64}? does not exist/iu,g={params:[],sql:"0"},R=(s,r)=>r?{column:`LOWER(${s})`,placeholder:"LOWER(?)"}:{column:s,placeholder:"?"},M=(s,r,a,c)=>{if(typeof r!="string")return g;const t=R(s,c);return a==="contains"?{params:[r],sql:`instr(${t.column}, ${t.placeholder}) > 0`}:a==="starts_with"?{params:[r],sql:`instr(${t.column}, ${t.placeholder}) = 1`}:{params:[r,r],sql:`substr(${t.column}, -length(?)) = ${t.placeholder}`}},h=(s,r,a,c)=>{if(r===null)return{params:[],sql:`${s} IS ${c?"NOT ":""}NULL`};const t=R(s,a),n=c?"<>":"=";return{params:[r],sql:`${t.column} ${n} ${t.placeholder}`}},O=(s,r,a,c)=>{const t=Array.isArray(r)?r:[];if(t.length===0)return{params:[],sql:a?"1":"0"};const n=R(s,c),e=t.map(()=>n.placeholder).join(", ");return{params:[...t],sql:`${n.column} ${a?"NOT IN":"IN"} (${e})`}},L=s=>{const r=l(s.field),{mode:a,operator:c,value:t}=s,n=a==="insensitive";switch(c){case"contains":case"ends_with":case"starts_with":return M(r,t,c,n);case"gt":return{params:[t],sql:`${r} > ?`};case"gte":return{params:[t],sql:`${r} >= ?`};case"in":return O(r,t,!1,n);case"lt":return{params:[t],sql:`${r} < ?`};case"lte":return{params:[t],sql:`${r} <= ?`};case"ne":return h(r,t,n,!0);case"not_in":return O(r,t,!0,n);default:return h(r,t,n,!1)}},d=(s,r)=>{if(s.length===0)return;const a=s.map(c=>L(c));return{params:a.flatMap(c=>c.params),sql:a.map(c=>`(${c.sql})`).join(` ${r} `)}},E=s=>{const r=[d(s.filter(a=>a.connector!=="OR"),"AND"),d(s.filter(a=>a.connector==="OR"),"OR")].filter(a=>a!==void 0);return r.length===0?{params:[],sql:""}:{params:r.flatMap(a=>a.params),sql:r.map(a=>`(${a.sql})`).join(" AND ")}},$=s=>s.sql?` WHERE ${s.sql}`:"",S=s=>{const r=(t,n)=>{const e=E(n);return s.all(`SELECT * FROM ${l(t)}${$(e)}`,e.params)},a=new Map,c=async t=>{const n=a.get(t);if(n!==void 0)return n;try{await s.all(`SELECT ${l(t)}.${l(T)} FROM ${l(t)} LIMIT 0`,[])}catch(e){return N.test(e instanceof Error?e.message:String(e))&&a.set(t,!1),!1}return a.set(t,!0),!0};return{consumeOne:async(t,n)=>{const e=E(n),o=l(t),[i]=await s.all(`DELETE FROM ${o} WHERE rowid IN (SELECT rowid FROM ${o}${$(e)} LIMIT 1) RETURNING *`,e.params);return i},count:async(t,n)=>{const e=E(n),[o]=await s.all(`SELECT COUNT(*) AS __count FROM ${l(t)}${$(e)}`,e.params);return Number(o?.__count??0)},create:async(t,n)=>{const e=T in n||!await c(t)?n:{[T]:Date.now(),...n},o=Object.keys(e),i=o.map(()=>"?").join(", "),u=`INSERT INTO ${l(t)} (${o.map(m=>l(m)).join(", ")}) VALUES (${i})`;return await s.run(u,o.map(m=>e[m])),{...n}},incrementOne:async(t,n,e,o)=>{const i=l(t),u=Object.keys(e),m=o?Object.keys(o):[],f=E(n);if(u.length===0&&m.length===0){const[p]=await s.all(`SELECT * FROM ${i}${$(f)} LIMIT 1`,f.params);return p}const I=[...u.map(p=>`${l(p)} = COALESCE(${l(p)}, 0) + ?`),...m.map(p=>`${l(p)} = ?`)].join(", "),[w]=await s.all(`UPDATE ${i} SET ${I} WHERE rowid IN (SELECT rowid FROM ${i}${$(f)} LIMIT 1) RETURNING *`,[...u.map(p=>e[p]),...m.map(p=>o[p]),...f.params]);return w},read:async(t,n)=>{const e=E(n.where),o=[...e.params];let i=`SELECT * FROM ${l(t)}${$(e)}`;return n.sortBy&&(i+=` ORDER BY ${l(n.sortBy.field)} ${n.sortBy.direction==="asc"?"ASC":"DESC"}`),n.limit!==void 0&&(i+=" LIMIT ?",o.push(Math.trunc(n.limit))),n.offset&&(i+=`${n.limit===void 0?" LIMIT -1":""} OFFSET ?`,o.push(Math.trunc(n.offset))),s.all(i,o)},remove:async(t,n)=>{const e=E(n);return(await s.all(`DELETE FROM ${l(t)}${$(e)} RETURNING *`,e.params)).length},update:async(t,n,e)=>{const o=Object.keys(e);if(o.length===0)return r(t,n);const i=o.map(m=>`${l(m)} = ?`).join(", "),u=E(n);return s.all(`UPDATE ${l(t)} SET ${i}${$(u)} RETURNING *`,[...o.map(m=>e[m]),...u.params])}}},C=s=>({all:async(r,a)=>(await s.prepare(r).bind(...a).all()).results??[],run:async(r,a)=>{await s.prepare(r).bind(...a).run()}});export{T as C,S as c,C as d};
@@ -0,0 +1 @@
1
+ import{createAuthEndpoint as c}from"better-auth/api";import{getAuthTables as g}from"better-auth/db";const l=e=>[...e].toSorted((o,i)=>o.localeCompare(i)),m=(e,o={})=>{const i=o.expose??{},a=new Set((e.plugins??[]).map(t=>t.id)),r=e.emailAndPassword?.enabled??!1,s=g(e),n=(e.plugins??[]).find(t=>t.id==="organization")?.options??{},d={allowUserToCreate:typeof n.allowUserToCreateOrganization=="function"?!0:n.allowUserToCreateOrganization!==!1,enabled:a.has("organization"),invitationLimit:n.invitationLimit,limit:n.organizationLimit,membershipLimit:n.membershipLimit,roles:!!s.organizationRole,teams:!!s.team};return{emailAndPassword:r,signUp:r&&e.emailAndPassword?.disableSignUp!==!0,...i.organization===!1?{}:{organization:d},...i.plugins===!1?{}:{plugins:l(a)},...i.socialProviders===!1?{}:{socialProviders:l(new Set([...Object.keys(e.socialProviders??{}),...o.extraProviders??[]]))}}},p=e=>c(e.path??"/ui-config",{metadata:{openapi:{description:"Public description of the enabled auth plugins and social providers.",responses:{200:{description:"The UI configuration."}}}},method:"GET"},o=>{const i=o;return Promise.resolve(i.json(m(i.context.options,e)))}),h=(e={})=>({endpoints:{getUiConfig:p(e)},id:"lunora-ui-config"});export{m as deriveUiConfig,h as uiConfig};
@@ -1,2 +1,251 @@
1
+ import { organizationClient } from 'better-auth/client/plugins';
2
+ export { adminClient, anonymousClient, customSessionClient, deviceAuthorizationClient, emailOTPClient, inferAdditionalFields, inferOrgAdditionalFields, jwtClient, lastLoginMethodClient, magicLinkClient, multiSessionClient, oneTapClient, oneTimeTokenClient, organizationClient, phoneNumberClient, siweClient, twoFactorClient, usernameClient } from 'better-auth/client/plugins';
3
+ export { apiKeyClient } from '@better-auth/api-key/client';
4
+ export { oauthDeviceAuthorizationClient, oauthProviderClient } from '@better-auth/oauth-provider/client';
1
5
  export { passkeyClient } from '@better-auth/passkey/client';
2
- export { adminClient, anonymousClient, customSessionClient, deviceAuthorizationClient, emailOTPClient, genericOAuthClient, inferAdditionalFields, inferOrgAdditionalFields, jwtClient, lastLoginMethodClient, magicLinkClient, multiSessionClient, oidcClient, oneTimeTokenClient, organizationClient, phoneNumberClient, siweClient, twoFactorClient, usernameClient } from 'better-auth/client/plugins';
6
+ /**
7
+ * Which client plugins to include. Each defaults to `false`.
8
+ *
9
+ * These are the same names `@lunora/auth-ui` gates its cards on, so one object
10
+ * can drive both `createLunoraAuthClient` and `registerAuthClientPlugins` — a
11
+ * flow the UI can show but this cannot install would be a card that renders and
12
+ * then fails at call time.
13
+ *
14
+ * `oneTap` is absent on purpose: Google One Tap needs a client id, not a
15
+ * boolean, so it comes in through {@link CreateLunoraAuthClientOptions.oneTapClientId}.
16
+ */
17
+ interface LunoraAuthPluginToggles {
18
+ admin?: boolean;
19
+ anonymous?: boolean;
20
+ deviceAuthorization?: boolean;
21
+ emailOtp?: boolean;
22
+ lastLoginMethod?: boolean;
23
+ magicLink?: boolean;
24
+ multiSession?: boolean;
25
+ oauthProvider?: boolean;
26
+ organization?: boolean;
27
+ passkey?: boolean;
28
+ phoneNumber?: boolean;
29
+ twoFactor?: boolean;
30
+ username?: boolean;
31
+ }
32
+ type LunoraAuthClientPlugin = ReturnType<typeof organizationClient>;
33
+ declare const lunoraAuthPlugins: (toggles?: LunoraAuthPluginToggles) => LunoraAuthClientPlugin[];
34
+ /** Options for {@link createLunoraAuthClient}; anything else is forwarded to `createAuthClient`. */
35
+ interface CreateLunoraAuthClientOptions {
36
+ [option: string]: unknown;
37
+ /** Defaults to the current origin, which is right for a same-origin app. */
38
+ baseURL?: string;
39
+ /** Your own client plugins, appended after the standard set. */
40
+ extraPlugins?: LunoraAuthClientPlugin[];
41
+ /**
42
+ * Google OAuth client id. Setting it installs the One Tap client plugin —
43
+ * a boolean toggle can't, because the prompt is Google's and needs the id.
44
+ */
45
+ oneTapClientId?: string;
46
+ /** Which standard client plugins to include. */
47
+ plugins?: LunoraAuthPluginToggles;
48
+ }
49
+ /**
50
+ * What {@link lunoraSessionSync} returns: a better-auth client plugin, typed
51
+ * narrowly rather than as `BetterAuthClientPlugin` — widening a plugin to that
52
+ * type collapses better-auth's session inference on the client it joins.
53
+ */
54
+ interface LunoraSessionSyncPlugin {
55
+ fetchPlugins: {
56
+ hooks: {
57
+ onSuccess: (context: {
58
+ request: {
59
+ method?: string;
60
+ };
61
+ }) => void;
62
+ };
63
+ id: string;
64
+ name: string;
65
+ }[];
66
+ id: "lunora-session-sync";
67
+ }
68
+ /**
69
+ * Tell every `LunoraClient` in the page, and in the browser's other tabs on the
70
+ * same origin, that the auth session may have changed, so each asks
71
+ * `/get-session` who is signed in now. A tab on another origin that shares the
72
+ * cookie is not reached: it switches on its next socket reconnect, and the
73
+ * worker's replay check keeps its writes safe meanwhile.
74
+ *
75
+ * Needed for a **cookie** session: a sign-in or sign-out sets or clears an
76
+ * `HttpOnly` cookie in a request the Lunora client never sees, and nothing it
77
+ * can observe changes. Until it re-resolves, its live queries keep showing the
78
+ * previous user's rows over a socket still authenticated as them.
79
+ * {@link lunoraSessionSync} (installed by {@link createLunoraAuthClient}) and
80
+ * `@lunora/auth-ui` call it for you; call it yourself after a session change
81
+ * made any other way.
82
+ *
83
+ * Resolves once every client in this tab has re-resolved (or failed to), so
84
+ * code that reads `client.currentIdentity()` next can `await` it; the other
85
+ * tabs re-resolve on their own. Never rejects.
86
+ */
87
+ declare const notifyLunoraSessionChange: () => Promise<void>;
88
+ /**
89
+ * A better-auth client plugin that, after every successful auth request that
90
+ * can change the session (anything but a `GET` — sign-in, sign-up, sign-out,
91
+ * 2FA, account switch, session revocation), calls
92
+ * {@link notifyLunoraSessionChange}.
93
+ *
94
+ * {@link createLunoraAuthClient} always installs it. Add it yourself when you
95
+ * call `createAuthClient` directly:
96
+ *
97
+ * ```ts
98
+ * export const authClient = createAuthClient({ plugins: [lunoraSessionSync()] });
99
+ * ```
100
+ */
101
+ declare const lunoraSessionSync: () => LunoraSessionSyncPlugin;
102
+ /**
103
+ * Build a better-auth client with Lunora's standard plugin set from toggles.
104
+ *
105
+ * You pass your framework's `createAuthClient` in — `better-auth/react`,
106
+ * `/vue`, `/svelte`, or `/solid` — because the variant has to match the UI
107
+ * framework, and a helper that picked for you would either guess wrong or drag
108
+ * every variant into your bundle.
109
+ *
110
+ * ```ts
111
+ * import { createAuthClient } from "better-auth/react";
112
+ * import { createLunoraAuthClient } from "@lunora/auth/plugins/client";
113
+ *
114
+ * export const authClient = createLunoraAuthClient(createAuthClient, {
115
+ * plugins: { organization: true, passkey: true, twoFactor: true },
116
+ * });
117
+ * ```
118
+ *
119
+ * Reach for {@link lunoraAuthPlugins} instead when you want to own the
120
+ * `createAuthClient` call and only borrow the plugin array.
121
+ */
122
+ declare const createLunoraAuthClient: <TClient>(createAuthClient: (options: Record<string, unknown>) => TClient, options?: CreateLunoraAuthClientOptions) => TClient;
123
+ export {
124
+ /**
125
+ * Client-side counterparts to the server plugins re-exported from
126
+ * `@lunora/auth/plugins`, surfaced under `@lunora/auth/plugins/client`.
127
+ *
128
+ * better-auth plugins come in two halves: a **server** plugin you pass to
129
+ * `betterAuth()` (re-exported from `@lunora/auth/plugins`) and a **client**
130
+ * plugin you pass to `createAuthClient()`. Both halves must be registered for a
131
+ * plugin's endpoints, inferred types, and client actions (e.g.
132
+ * `authClient.twoFactor.*`, `authClient.organization.*`, the `twoFactorRedirect`
133
+ * sign-in flow) to exist. This module is the client half so consumers don't have
134
+ * to reach into better-auth's `better-auth/client/plugins` subpath directly.
135
+ *
136
+ * # Wiring
137
+ *
138
+ * ```ts
139
+ * import { createAuthClient } from "better-auth/client"; // or a framework adapter
140
+ * import { organizationClient, twoFactorClient } from "@lunora/auth/plugins/client";
141
+ *
142
+ * export const authClient = createAuthClient({
143
+ * baseURL: import.meta.env.VITE_AUTH_URL,
144
+ * plugins: [
145
+ * organizationClient(),
146
+ * twoFactorClient({ onTwoFactorRedirect: () => location.assign("/2fa") }),
147
+ * ],
148
+ * });
149
+ * ```
150
+ *
151
+ * Keep the client plugin list in parity with the server `plugins` array passed
152
+ * to `createAuth`: a server plugin without its client counterpart leaves
153
+ * those actions untyped/unavailable, and vice-versa.
154
+ */
155
+ type CreateLunoraAuthClientOptions,
156
+ /**
157
+ * Client-side counterparts to the server plugins re-exported from
158
+ * `@lunora/auth/plugins`, surfaced under `@lunora/auth/plugins/client`.
159
+ *
160
+ * better-auth plugins come in two halves: a **server** plugin you pass to
161
+ * `betterAuth()` (re-exported from `@lunora/auth/plugins`) and a **client**
162
+ * plugin you pass to `createAuthClient()`. Both halves must be registered for a
163
+ * plugin's endpoints, inferred types, and client actions (e.g.
164
+ * `authClient.twoFactor.*`, `authClient.organization.*`, the `twoFactorRedirect`
165
+ * sign-in flow) to exist. This module is the client half so consumers don't have
166
+ * to reach into better-auth's `better-auth/client/plugins` subpath directly.
167
+ *
168
+ * # Wiring
169
+ *
170
+ * ```ts
171
+ * import { createAuthClient } from "better-auth/client"; // or a framework adapter
172
+ * import { organizationClient, twoFactorClient } from "@lunora/auth/plugins/client";
173
+ *
174
+ * export const authClient = createAuthClient({
175
+ * baseURL: import.meta.env.VITE_AUTH_URL,
176
+ * plugins: [
177
+ * organizationClient(),
178
+ * twoFactorClient({ onTwoFactorRedirect: () => location.assign("/2fa") }),
179
+ * ],
180
+ * });
181
+ * ```
182
+ *
183
+ * Keep the client plugin list in parity with the server `plugins` array passed
184
+ * to `createAuth`: a server plugin without its client counterpart leaves
185
+ * those actions untyped/unavailable, and vice-versa.
186
+ */
187
+ type LunoraAuthClientPlugin,
188
+ /**
189
+ * Client-side counterparts to the server plugins re-exported from
190
+ * `@lunora/auth/plugins`, surfaced under `@lunora/auth/plugins/client`.
191
+ *
192
+ * better-auth plugins come in two halves: a **server** plugin you pass to
193
+ * `betterAuth()` (re-exported from `@lunora/auth/plugins`) and a **client**
194
+ * plugin you pass to `createAuthClient()`. Both halves must be registered for a
195
+ * plugin's endpoints, inferred types, and client actions (e.g.
196
+ * `authClient.twoFactor.*`, `authClient.organization.*`, the `twoFactorRedirect`
197
+ * sign-in flow) to exist. This module is the client half so consumers don't have
198
+ * to reach into better-auth's `better-auth/client/plugins` subpath directly.
199
+ *
200
+ * # Wiring
201
+ *
202
+ * ```ts
203
+ * import { createAuthClient } from "better-auth/client"; // or a framework adapter
204
+ * import { organizationClient, twoFactorClient } from "@lunora/auth/plugins/client";
205
+ *
206
+ * export const authClient = createAuthClient({
207
+ * baseURL: import.meta.env.VITE_AUTH_URL,
208
+ * plugins: [
209
+ * organizationClient(),
210
+ * twoFactorClient({ onTwoFactorRedirect: () => location.assign("/2fa") }),
211
+ * ],
212
+ * });
213
+ * ```
214
+ *
215
+ * Keep the client plugin list in parity with the server `plugins` array passed
216
+ * to `createAuth`: a server plugin without its client counterpart leaves
217
+ * those actions untyped/unavailable, and vice-versa.
218
+ */
219
+ type LunoraAuthPluginToggles,
220
+ /**
221
+ * Client-side counterparts to the server plugins re-exported from
222
+ * `@lunora/auth/plugins`, surfaced under `@lunora/auth/plugins/client`.
223
+ *
224
+ * better-auth plugins come in two halves: a **server** plugin you pass to
225
+ * `betterAuth()` (re-exported from `@lunora/auth/plugins`) and a **client**
226
+ * plugin you pass to `createAuthClient()`. Both halves must be registered for a
227
+ * plugin's endpoints, inferred types, and client actions (e.g.
228
+ * `authClient.twoFactor.*`, `authClient.organization.*`, the `twoFactorRedirect`
229
+ * sign-in flow) to exist. This module is the client half so consumers don't have
230
+ * to reach into better-auth's `better-auth/client/plugins` subpath directly.
231
+ *
232
+ * # Wiring
233
+ *
234
+ * ```ts
235
+ * import { createAuthClient } from "better-auth/client"; // or a framework adapter
236
+ * import { organizationClient, twoFactorClient } from "@lunora/auth/plugins/client";
237
+ *
238
+ * export const authClient = createAuthClient({
239
+ * baseURL: import.meta.env.VITE_AUTH_URL,
240
+ * plugins: [
241
+ * organizationClient(),
242
+ * twoFactorClient({ onTwoFactorRedirect: () => location.assign("/2fa") }),
243
+ * ],
244
+ * });
245
+ * ```
246
+ *
247
+ * Keep the client plugin list in parity with the server `plugins` array passed
248
+ * to `createAuth`: a server plugin without its client counterpart leaves
249
+ * those actions untyped/unavailable, and vice-versa.
250
+ */
251
+ type LunoraSessionSyncPlugin, createLunoraAuthClient, lunoraAuthPlugins, lunoraSessionSync, notifyLunoraSessionChange };
@@ -1,2 +1,251 @@
1
+ import { organizationClient } from 'better-auth/client/plugins';
2
+ export { adminClient, anonymousClient, customSessionClient, deviceAuthorizationClient, emailOTPClient, inferAdditionalFields, inferOrgAdditionalFields, jwtClient, lastLoginMethodClient, magicLinkClient, multiSessionClient, oneTapClient, oneTimeTokenClient, organizationClient, phoneNumberClient, siweClient, twoFactorClient, usernameClient } from 'better-auth/client/plugins';
3
+ export { apiKeyClient } from '@better-auth/api-key/client';
4
+ export { oauthDeviceAuthorizationClient, oauthProviderClient } from '@better-auth/oauth-provider/client';
1
5
  export { passkeyClient } from '@better-auth/passkey/client';
2
- export { adminClient, anonymousClient, customSessionClient, deviceAuthorizationClient, emailOTPClient, genericOAuthClient, inferAdditionalFields, inferOrgAdditionalFields, jwtClient, lastLoginMethodClient, magicLinkClient, multiSessionClient, oidcClient, oneTimeTokenClient, organizationClient, phoneNumberClient, siweClient, twoFactorClient, usernameClient } from 'better-auth/client/plugins';
6
+ /**
7
+ * Which client plugins to include. Each defaults to `false`.
8
+ *
9
+ * These are the same names `@lunora/auth-ui` gates its cards on, so one object
10
+ * can drive both `createLunoraAuthClient` and `registerAuthClientPlugins` — a
11
+ * flow the UI can show but this cannot install would be a card that renders and
12
+ * then fails at call time.
13
+ *
14
+ * `oneTap` is absent on purpose: Google One Tap needs a client id, not a
15
+ * boolean, so it comes in through {@link CreateLunoraAuthClientOptions.oneTapClientId}.
16
+ */
17
+ interface LunoraAuthPluginToggles {
18
+ admin?: boolean;
19
+ anonymous?: boolean;
20
+ deviceAuthorization?: boolean;
21
+ emailOtp?: boolean;
22
+ lastLoginMethod?: boolean;
23
+ magicLink?: boolean;
24
+ multiSession?: boolean;
25
+ oauthProvider?: boolean;
26
+ organization?: boolean;
27
+ passkey?: boolean;
28
+ phoneNumber?: boolean;
29
+ twoFactor?: boolean;
30
+ username?: boolean;
31
+ }
32
+ type LunoraAuthClientPlugin = ReturnType<typeof organizationClient>;
33
+ declare const lunoraAuthPlugins: (toggles?: LunoraAuthPluginToggles) => LunoraAuthClientPlugin[];
34
+ /** Options for {@link createLunoraAuthClient}; anything else is forwarded to `createAuthClient`. */
35
+ interface CreateLunoraAuthClientOptions {
36
+ [option: string]: unknown;
37
+ /** Defaults to the current origin, which is right for a same-origin app. */
38
+ baseURL?: string;
39
+ /** Your own client plugins, appended after the standard set. */
40
+ extraPlugins?: LunoraAuthClientPlugin[];
41
+ /**
42
+ * Google OAuth client id. Setting it installs the One Tap client plugin —
43
+ * a boolean toggle can't, because the prompt is Google's and needs the id.
44
+ */
45
+ oneTapClientId?: string;
46
+ /** Which standard client plugins to include. */
47
+ plugins?: LunoraAuthPluginToggles;
48
+ }
49
+ /**
50
+ * What {@link lunoraSessionSync} returns: a better-auth client plugin, typed
51
+ * narrowly rather than as `BetterAuthClientPlugin` — widening a plugin to that
52
+ * type collapses better-auth's session inference on the client it joins.
53
+ */
54
+ interface LunoraSessionSyncPlugin {
55
+ fetchPlugins: {
56
+ hooks: {
57
+ onSuccess: (context: {
58
+ request: {
59
+ method?: string;
60
+ };
61
+ }) => void;
62
+ };
63
+ id: string;
64
+ name: string;
65
+ }[];
66
+ id: "lunora-session-sync";
67
+ }
68
+ /**
69
+ * Tell every `LunoraClient` in the page, and in the browser's other tabs on the
70
+ * same origin, that the auth session may have changed, so each asks
71
+ * `/get-session` who is signed in now. A tab on another origin that shares the
72
+ * cookie is not reached: it switches on its next socket reconnect, and the
73
+ * worker's replay check keeps its writes safe meanwhile.
74
+ *
75
+ * Needed for a **cookie** session: a sign-in or sign-out sets or clears an
76
+ * `HttpOnly` cookie in a request the Lunora client never sees, and nothing it
77
+ * can observe changes. Until it re-resolves, its live queries keep showing the
78
+ * previous user's rows over a socket still authenticated as them.
79
+ * {@link lunoraSessionSync} (installed by {@link createLunoraAuthClient}) and
80
+ * `@lunora/auth-ui` call it for you; call it yourself after a session change
81
+ * made any other way.
82
+ *
83
+ * Resolves once every client in this tab has re-resolved (or failed to), so
84
+ * code that reads `client.currentIdentity()` next can `await` it; the other
85
+ * tabs re-resolve on their own. Never rejects.
86
+ */
87
+ declare const notifyLunoraSessionChange: () => Promise<void>;
88
+ /**
89
+ * A better-auth client plugin that, after every successful auth request that
90
+ * can change the session (anything but a `GET` — sign-in, sign-up, sign-out,
91
+ * 2FA, account switch, session revocation), calls
92
+ * {@link notifyLunoraSessionChange}.
93
+ *
94
+ * {@link createLunoraAuthClient} always installs it. Add it yourself when you
95
+ * call `createAuthClient` directly:
96
+ *
97
+ * ```ts
98
+ * export const authClient = createAuthClient({ plugins: [lunoraSessionSync()] });
99
+ * ```
100
+ */
101
+ declare const lunoraSessionSync: () => LunoraSessionSyncPlugin;
102
+ /**
103
+ * Build a better-auth client with Lunora's standard plugin set from toggles.
104
+ *
105
+ * You pass your framework's `createAuthClient` in — `better-auth/react`,
106
+ * `/vue`, `/svelte`, or `/solid` — because the variant has to match the UI
107
+ * framework, and a helper that picked for you would either guess wrong or drag
108
+ * every variant into your bundle.
109
+ *
110
+ * ```ts
111
+ * import { createAuthClient } from "better-auth/react";
112
+ * import { createLunoraAuthClient } from "@lunora/auth/plugins/client";
113
+ *
114
+ * export const authClient = createLunoraAuthClient(createAuthClient, {
115
+ * plugins: { organization: true, passkey: true, twoFactor: true },
116
+ * });
117
+ * ```
118
+ *
119
+ * Reach for {@link lunoraAuthPlugins} instead when you want to own the
120
+ * `createAuthClient` call and only borrow the plugin array.
121
+ */
122
+ declare const createLunoraAuthClient: <TClient>(createAuthClient: (options: Record<string, unknown>) => TClient, options?: CreateLunoraAuthClientOptions) => TClient;
123
+ export {
124
+ /**
125
+ * Client-side counterparts to the server plugins re-exported from
126
+ * `@lunora/auth/plugins`, surfaced under `@lunora/auth/plugins/client`.
127
+ *
128
+ * better-auth plugins come in two halves: a **server** plugin you pass to
129
+ * `betterAuth()` (re-exported from `@lunora/auth/plugins`) and a **client**
130
+ * plugin you pass to `createAuthClient()`. Both halves must be registered for a
131
+ * plugin's endpoints, inferred types, and client actions (e.g.
132
+ * `authClient.twoFactor.*`, `authClient.organization.*`, the `twoFactorRedirect`
133
+ * sign-in flow) to exist. This module is the client half so consumers don't have
134
+ * to reach into better-auth's `better-auth/client/plugins` subpath directly.
135
+ *
136
+ * # Wiring
137
+ *
138
+ * ```ts
139
+ * import { createAuthClient } from "better-auth/client"; // or a framework adapter
140
+ * import { organizationClient, twoFactorClient } from "@lunora/auth/plugins/client";
141
+ *
142
+ * export const authClient = createAuthClient({
143
+ * baseURL: import.meta.env.VITE_AUTH_URL,
144
+ * plugins: [
145
+ * organizationClient(),
146
+ * twoFactorClient({ onTwoFactorRedirect: () => location.assign("/2fa") }),
147
+ * ],
148
+ * });
149
+ * ```
150
+ *
151
+ * Keep the client plugin list in parity with the server `plugins` array passed
152
+ * to `createAuth`: a server plugin without its client counterpart leaves
153
+ * those actions untyped/unavailable, and vice-versa.
154
+ */
155
+ type CreateLunoraAuthClientOptions,
156
+ /**
157
+ * Client-side counterparts to the server plugins re-exported from
158
+ * `@lunora/auth/plugins`, surfaced under `@lunora/auth/plugins/client`.
159
+ *
160
+ * better-auth plugins come in two halves: a **server** plugin you pass to
161
+ * `betterAuth()` (re-exported from `@lunora/auth/plugins`) and a **client**
162
+ * plugin you pass to `createAuthClient()`. Both halves must be registered for a
163
+ * plugin's endpoints, inferred types, and client actions (e.g.
164
+ * `authClient.twoFactor.*`, `authClient.organization.*`, the `twoFactorRedirect`
165
+ * sign-in flow) to exist. This module is the client half so consumers don't have
166
+ * to reach into better-auth's `better-auth/client/plugins` subpath directly.
167
+ *
168
+ * # Wiring
169
+ *
170
+ * ```ts
171
+ * import { createAuthClient } from "better-auth/client"; // or a framework adapter
172
+ * import { organizationClient, twoFactorClient } from "@lunora/auth/plugins/client";
173
+ *
174
+ * export const authClient = createAuthClient({
175
+ * baseURL: import.meta.env.VITE_AUTH_URL,
176
+ * plugins: [
177
+ * organizationClient(),
178
+ * twoFactorClient({ onTwoFactorRedirect: () => location.assign("/2fa") }),
179
+ * ],
180
+ * });
181
+ * ```
182
+ *
183
+ * Keep the client plugin list in parity with the server `plugins` array passed
184
+ * to `createAuth`: a server plugin without its client counterpart leaves
185
+ * those actions untyped/unavailable, and vice-versa.
186
+ */
187
+ type LunoraAuthClientPlugin,
188
+ /**
189
+ * Client-side counterparts to the server plugins re-exported from
190
+ * `@lunora/auth/plugins`, surfaced under `@lunora/auth/plugins/client`.
191
+ *
192
+ * better-auth plugins come in two halves: a **server** plugin you pass to
193
+ * `betterAuth()` (re-exported from `@lunora/auth/plugins`) and a **client**
194
+ * plugin you pass to `createAuthClient()`. Both halves must be registered for a
195
+ * plugin's endpoints, inferred types, and client actions (e.g.
196
+ * `authClient.twoFactor.*`, `authClient.organization.*`, the `twoFactorRedirect`
197
+ * sign-in flow) to exist. This module is the client half so consumers don't have
198
+ * to reach into better-auth's `better-auth/client/plugins` subpath directly.
199
+ *
200
+ * # Wiring
201
+ *
202
+ * ```ts
203
+ * import { createAuthClient } from "better-auth/client"; // or a framework adapter
204
+ * import { organizationClient, twoFactorClient } from "@lunora/auth/plugins/client";
205
+ *
206
+ * export const authClient = createAuthClient({
207
+ * baseURL: import.meta.env.VITE_AUTH_URL,
208
+ * plugins: [
209
+ * organizationClient(),
210
+ * twoFactorClient({ onTwoFactorRedirect: () => location.assign("/2fa") }),
211
+ * ],
212
+ * });
213
+ * ```
214
+ *
215
+ * Keep the client plugin list in parity with the server `plugins` array passed
216
+ * to `createAuth`: a server plugin without its client counterpart leaves
217
+ * those actions untyped/unavailable, and vice-versa.
218
+ */
219
+ type LunoraAuthPluginToggles,
220
+ /**
221
+ * Client-side counterparts to the server plugins re-exported from
222
+ * `@lunora/auth/plugins`, surfaced under `@lunora/auth/plugins/client`.
223
+ *
224
+ * better-auth plugins come in two halves: a **server** plugin you pass to
225
+ * `betterAuth()` (re-exported from `@lunora/auth/plugins`) and a **client**
226
+ * plugin you pass to `createAuthClient()`. Both halves must be registered for a
227
+ * plugin's endpoints, inferred types, and client actions (e.g.
228
+ * `authClient.twoFactor.*`, `authClient.organization.*`, the `twoFactorRedirect`
229
+ * sign-in flow) to exist. This module is the client half so consumers don't have
230
+ * to reach into better-auth's `better-auth/client/plugins` subpath directly.
231
+ *
232
+ * # Wiring
233
+ *
234
+ * ```ts
235
+ * import { createAuthClient } from "better-auth/client"; // or a framework adapter
236
+ * import { organizationClient, twoFactorClient } from "@lunora/auth/plugins/client";
237
+ *
238
+ * export const authClient = createAuthClient({
239
+ * baseURL: import.meta.env.VITE_AUTH_URL,
240
+ * plugins: [
241
+ * organizationClient(),
242
+ * twoFactorClient({ onTwoFactorRedirect: () => location.assign("/2fa") }),
243
+ * ],
244
+ * });
245
+ * ```
246
+ *
247
+ * Keep the client plugin list in parity with the server `plugins` array passed
248
+ * to `createAuth`: a server plugin without its client counterpart leaves
249
+ * those actions untyped/unavailable, and vice-versa.
250
+ */
251
+ type LunoraSessionSyncPlugin, createLunoraAuthClient, lunoraAuthPlugins, lunoraSessionSync, notifyLunoraSessionChange };