better-auth 0.2.3-beta.8 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,5 +1,1012 @@
1
1
  #!/usr/bin/env node
2
- import{Command as ye}from"commander";import"dotenv/config";import{Command as le}from"commander";import{loadConfig as I}from"c12";import{createConsola as _}from"consola";var A=_({formatOptions:{date:!1,colors:!0,compact:!0},defaults:{tag:"Better Auth"}}),H=e=>({log:(...t)=>{!e?.disabled&&A.log("",...t)},error:(...t)=>{!e?.disabled&&A.error("",...t)},warn:(...t)=>{!e?.disabled&&A.warn("",...t)},info:(...t)=>{!e?.disabled&&A.info("",...t)},debug:(...t)=>{!e?.disabled&&A.debug("",...t)},box:(...t)=>{!e?.disabled&&A.box("",...t)},success:(...t)=>{!e?.disabled&&A.success("",...t)},break:(...t)=>{!e?.disabled&&console.log(`
3
- `)}}),c=H();import J from"path";import Y from"@babel/preset-typescript";import Z from"@babel/preset-react";var w=["auth.ts","auth.tsx"];w=[...w,...w.map(e=>`lib/${e}`),...w.map(e=>`utils/${e}`)];w=[...w,...w.map(e=>`src/${e}`)];var E={transformOptions:{babel:{presets:[[Y,{isTSX:!0,allExtensions:!0}],[Z,{runtime:"automatic"}]]}},extensions:[".ts",".tsx",".js",".jsx"]};async function B({cwd:e,configPath:t}){try{let a=null;if(t){let{config:r}=await I({configFile:J.join(e,t),dotenv:!0,jitiOptions:E});!r.auth&&!r.default&&(c.error("[#better-auth]: Couldn't read your auth config. Make sure to default export your auth instance or to export as a variable named auth."),process.exit(1)),a=r.auth?.options||r.default?.options||null}if(!a)for(let r of w)try{let{config:i}=await I({configFile:r,jitiOptions:E});if(Object.keys(i).length>0){a=i.auth?.options||i.default?.options||null,a||(c.error("[#better-auth]: Couldn't read your auth config."),c.break(),c.info("[#better-auth]: Make sure to default export your auth instance or to export as a variable named auth."),process.exit(1));break}}catch(i){c.error(i),process.exit(1)}return a}catch(a){c.error("Error while reading your auth config.",a),process.exit(1)}}import{z as M}from"zod";import{existsSync as de}from"fs";import ce from"path";import{Kysely as ee}from"kysely";import{MysqlDialect as $,PostgresDialect as P,SqliteDialect as L}from"kysely";var g=class extends Error{constructor(t,a,r){super(t),this.name="BetterAuthError",this.message=t,this.cause=a}};var te=async e=>{if(!e.database)return;if("createDriver"in e.database)return e.database;let t;if("provider"in e.database){let a=e.database.provider,r=e.database?.url?.trim();if(a==="postgres"){let o=(await import("pg").catch(s=>{throw new g("Please install `pg` to use postgres database")})).Pool;t=new P({pool:new o({connectionString:r})})}if(a==="mysql")try{let{createPool:i}=await import("mysql2/promise").catch(n=>{throw new g("Please install `mysql2` to use mysql database")}),o=new URL(r),s=i({host:o.hostname,user:o.username,password:o.password,database:o.pathname.split("/")[1],port:Number(o.port)});t=new $({pool:s})}catch(i){if(i instanceof TypeError)throw new g("Invalid database URL")}if(a==="sqlite")try{let i=await import("better-sqlite3").catch(n=>{throw new g("Please install `better-sqlite3` to use sqlite database")}),o=i.default||i;if(!o)throw new g("Failed to import better-sqlite3. Please ensure `better-sqlite3` is properly installed.");let s=new o(r);t=new L({database:s})}catch(i){throw console.error(i),new g("Failed to initialize SQLite. Please ensure `better-sqlite3` is properly installed.")}}return t},T=async e=>{let t=await te(e);return t&&new ee({dialect:t})},F=e=>{if("provider"in e.database)return e.database.provider;if("dialect"in e.database){if(e.database.dialect instanceof P)return"postgres";if(e.database.dialect instanceof $)return"mysql";if(e.database.dialect instanceof L)return"sqlite"}return"sqlite"};import ue from"ora";import N from"chalk";import fe from"prompts";import"kysely";var k=e=>{let t=e.plugins?.reduce((l,d)=>{let f=d.schema;if(!f)return l;for(let[u,m]of Object.entries(f))l[u]={fields:{...l[u]?.fields,...m.fields},tableName:u};return l},{}),a=e.rateLimit?.storage==="database",r={rateLimit:{tableName:e.rateLimit?.tableName||"rateLimit",fields:{key:{type:"string"},count:{type:"number"},lastRequest:{type:"number"}}}},{user:i,session:o,account:s,...n}=t||{};return{user:{tableName:e.user?.modelName||"user",fields:{name:{type:"string",required:!0},email:{type:"string",unique:!0,required:!0},emailVerified:{type:"boolean",defaultValue:()=>!1,required:!0},image:{type:"string",required:!1},createdAt:{type:"date",defaultValue:()=>new Date,required:!0},updatedAt:{type:"date",defaultValue:()=>new Date,required:!0},...i?.fields},order:0},session:{tableName:e.session?.modelName||"session",fields:{expiresAt:{type:"date",required:!0},ipAddress:{type:"string",required:!1},userAgent:{type:"string",required:!1},userId:{type:"string",references:{model:"user",field:"id",onDelete:"cascade"},required:!0},...o?.fields},order:1},account:{tableName:e.account?.modelName||"account",fields:{accountId:{type:"string",required:!0},providerId:{type:"string",required:!0},userId:{type:"string",references:{model:"user",field:"id",onDelete:"cascade"},required:!0},accessToken:{type:"string",required:!1},refreshToken:{type:"string",required:!1},idToken:{type:"string",required:!1},expiresAt:{type:"date",required:!1},password:{type:"string",required:!1},...s?.fields},order:2},...n,...a?r:{}}};function re(e){return e.plugins?.flatMap(a=>Object.keys(a.schema||{}).map(r=>{let o=(a.schema||{})[r];if(!o?.disableMigration)return{tableName:r,fields:o?.fields}}).filter(r=>r!==void 0))||[]}function v(e){let t=k(e),a=re(e);return[t.user,t.session,t.account,...a].reduce((i,o)=>(i[o.tableName]={fields:{...i[o.tableName]?.fields,...o.fields}},i),{})}var ae={string:["character varying","text"],number:["int4","integer","bigint","smallint","numeric","real","double precision"],boolean:["bool","boolean"],date:["timestamp","date"]},oe={string:["varchar","text"],number:["integer","int","bigint","smallint","decimal","float","double"],boolean:["boolean"],date:["date","datetime"]},ie={string:["TEXT"],number:["INTEGER","REAL"],boolean:["INTEGER","BOOLEAN"],date:["DATE","INTEGER"]},se={postgres:ae,mysql:oe,sqlite:ie};function ne(e,t,a){return se[a][t].map(s=>s.toLowerCase()).includes(e.toLowerCase())}async function D(e){let t=v(e),a=F(e),r=await T(e);r||(c.error("Invalid database configuration."),process.exit(1));let i=await r.introspection.getTables(),o=[],s=[];for(let[u,m]of Object.entries(t)){let h=i.find(y=>y.name===u);if(!h){let y=o.findIndex(O=>O.table===u),p={table:u,fields:m.fields,order:m.order||1/0},q=o.findIndex(O=>(O.order||1/0)>p.order);q===-1?y===-1?o.push(p):o[y].fields={...o[y].fields,...m.fields}:o.splice(q,0,p);continue}let b={};for(let[y,p]of Object.entries(m.fields)){let q=h.columns.find(O=>O.name===y);if(!q){b[y]=p;continue}ne(q.dataType,p.type,a)||c.warn(`Field ${y} in table ${u} has a different type in the database. Expected ${p.type} but got ${q.dataType}.`)}Object.keys(b).length>0&&s.push({table:u,fields:b,order:m.order||1/0})}let n=[];function l(u){let m={string:"text",boolean:"boolean",number:"integer",date:"date"};return a==="mysql"&&u==="string"?"varchar(255)":m[u]}if(s.length)for(let u of s)for(let[m,h]of Object.entries(u.fields)){let b=l(h.type),y=r.schema.alterTable(u.table).addColumn(m,b,p=>(p=h.required!==!1?p.notNull():p,h.references&&(p=p.references(`${h.references.model}.${h.references.field}`)),p));n.push(y)}if(o.length)for(let u of o){let m=r.schema.createTable(u.table).addColumn("id",l("string"),h=>h.primaryKey());for(let[h,b]of Object.entries(u.fields)){let y=l(b.type);m=m.addColumn(h,y,p=>(p=b.required!==!1?p.notNull():p,b.references&&(p=p.references(`${b.references.model}.${b.references.field}`)),b.unique&&(p=p.unique()),p))}n.push(m)}async function d(){for(let u of n)await u.execute()}async function f(){return n.map(m=>m.compile().sql).join(`;
4
2
 
5
- `)}return{toBeCreated:o,toBeAdded:s,runMigrations:d,compileMigrations:f}}var K=new le("migrate").option("-c, --cwd <cwd>","the working directory. defaults to the current directory.",process.cwd()).option("--config <config>","the path to the configuration file. defaults to the first configuration file found.").option("--y","").action(async e=>{let t=M.object({cwd:M.string(),config:M.string().optional()}).parse(e),a=ce.resolve(t.cwd);de(a)||(c.error(`The directory "${a}" does not exist.`),process.exit(1));let r=await B({cwd:a,configPath:t.config});if(!r){c.error("No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag.");return}await T(r)||(c.error("Invalid database configuration."),process.exit(1));let o=ue("preparing migration...").start(),{toBeAdded:s,toBeCreated:n,runMigrations:l}=await D(r);!s.length&&!n.length&&(o.stop(),c.success("\u{1F680} No migrations needed."),process.exit(0)),o.stop(),c.info("\u{1F511} The migration will affect the following:");for(let f of[...n,...s])c.info("->",N.magenta(Object.keys(f.fields).join(", ")),N.white("fields on"),N.yellow(`${f.table}`),N.white("table."));let{migrate:d}=await fe({type:"confirm",name:"migrate",message:"Are you sure you want to run these migrations?",initial:!1});d||(c.info("Migration cancelled."),process.exit(0)),o?.start("migrating..."),await l(),o.stop(),c.success("\u{1F680} migration was completed successfully!"),process.exit(0)});import{Command as pe}from"commander";import{z as j}from"zod";import{existsSync as W}from"fs";import x from"path";import me from"ora";import U from"prompts";function C(e){if(!e)return{and:null,or:null};let t=e?.filter(r=>r.connector==="AND"||!r.connector).reduce((r,i)=>({...r,[i.field]:i.value}),{}),a=e?.filter(r=>r.connector==="OR").reduce((r,i)=>({...r,[i.field]:i.value}),{});return{and:Object.keys(t).length?t:null,or:Object.keys(a).length?a:null}}function S(e,t,a){for(let r in e)e[r]===0&&t[r]?.type==="boolean"&&a?.boolean&&(e[r]=!1),e[r]===1&&t[r]?.type==="boolean"&&a?.boolean&&(e[r]=!0),t[r]?.type==="date"&&(e[r]instanceof Date||(e[r]=new Date(e[r])));return e}function z(e,t){for(let a in e)typeof e[a]=="boolean"&&t?.boolean&&(e[a]=e[a]?1:0),e[a]instanceof Date&&(e[a]=e[a].toISOString());return e}var V=(e,t)=>({id:"kysely",async create(a){let{model:r,data:i,select:o}=a;t?.transform&&(i=z(i,t.transform));let s=await e.insertInto(r).values(i).returningAll().executeTakeFirst();if(t?.transform){let n=t.transform.schema[r];s=n?S(i,n,t.transform):s}return o?.length&&(s=s?o.reduce((l,d)=>s?.[d]?{...l,[d]:s[d]}:l,{}):null),s},async findOne(a){let{model:r,where:i,select:o}=a,{and:s,or:n}=C(i),l=e.selectFrom(r).selectAll();n&&(l=l.where(f=>f.or(n))),s&&(l=l.where(f=>f.and(s)));let d=await l.executeTakeFirst();if(o?.length&&(d=d?o.reduce((u,m)=>d?.[m]?{...u,[m]:d[m]}:u,{}):null),t?.transform){let f=t.transform.schema[r];return d=d&&f?S(d,f,t.transform):d,d||null}return d||null},async findMany(a){let{model:r,where:i}=a,o=e.selectFrom(r),{and:s,or:n}=C(i);s&&(o=o.where(d=>d.and(s))),n&&(o=o.where(d=>d.or(n)));let l=await o.selectAll().execute();if(t?.transform){let d=t.transform.schema[r];return d?l.map(f=>S(f,d,t.transform)):l}return l},async update(a){let{model:r,where:i,update:o}=a,{and:s,or:n}=C(i);t?.transform&&(o=z(o,t.transform));let l=e.updateTable(r).set(o);s&&(l=l.where(f=>f.and(s))),n&&(l=l.where(f=>f.or(n)));let d=await l.returningAll().executeTakeFirst()||null;if(t?.transform){let f=t.transform.schema[r];return f?S(d,f,t.transform):d}return d},async delete(a){let{model:r,where:i}=a,{and:o,or:s}=C(i),n=e.deleteFrom(r);o&&(n=n.where(l=>l.and(o))),s&&(n=n.where(l=>l.or(s))),await n.execute()},async createSchema(a){let{compileMigrations:r}=await D(a);return console.log(r),{code:await r(),fileName:`./better-auth_migrations/${new Date().toISOString()}.sql`}}});async function G(e){if(!e.database)throw new g("Database configuration is required");if("create"in e.database)return e.database;let t=await T(e);if(!t)throw new g("Failed to initialize database adapter");let a=k(e),r={};for(let i of Object.values(a))r[i.tableName]=i.fields;return V(t,{transform:{schema:r,date:!0,boolean:F(e)==="sqlite"}})}import R from"fs/promises";import X from"chalk";var Q=new pe("generate").option("-c, --cwd <cwd>","the working directory. defaults to the current directory.",process.cwd()).option("--config <config>","the path to the configuration file. defaults to the first configuration file found.").option("--out <output>","the file to output to the generated schema").option("--y","").action(async e=>{let t=j.object({cwd:j.string(),config:j.string().optional(),out:j.string().optional()}).parse(e),a=x.resolve(t.cwd);W(a)||(c.error(`The directory "${a}" does not exist.`),process.exit(1));let r=await B({cwd:a,configPath:t.config});if(!r){c.error("No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag.");return}let i=await G(r);i.createSchema||(c.error("The adapter does not support schema generation."),process.exit(1));let o=me("preparing schema...").start(),{code:s,fileName:n,append:l}=await i.createSchema(r,t.out);if(o.stop(),l){let{append:u}=await U({type:"confirm",name:"append",message:`The file ${n} already exists. Do you want to ${X.yellow("append")} the schema to the file?`});u?(await R.appendFile(x.join(a,n),s),c.success("\u{1F680} schema was appended successfully!"),process.exit(0)):(c.error("Schema generation aborted."),process.exit(1))}let{confirm:d}=await U({type:"confirm",name:"confirm",message:`Do you want to generate the schema to ${X.yellow(n)}?`});d||(c.error("Schema generation aborted."),process.exit(1)),W(x.dirname(x.join(a,n)))||await R.mkdir(x.dirname(x.join(a,n)),{recursive:!0}),await R.writeFile(t.out||x.join(a,n),s),c.success("\u{1F680} schema was generated successfully!"),process.exit(0)});async function he(){let e=new ye().name("better-auth");e.addCommand(K).addCommand(Q),e.parse()}he();
3
+ // src/cli/index.ts
4
+ import { Command as Command3 } from "commander";
5
+ import "dotenv/config";
6
+
7
+ // src/cli/commands/migrate.ts
8
+ import { Command } from "commander";
9
+
10
+ // src/cli/get-config.ts
11
+ import { loadConfig } from "c12";
12
+
13
+ // src/utils/logger.ts
14
+ import { createConsola } from "consola";
15
+ var consola = createConsola({
16
+ formatOptions: {
17
+ date: false,
18
+ colors: true,
19
+ compact: true
20
+ },
21
+ defaults: {
22
+ tag: "Better Auth"
23
+ }
24
+ });
25
+ var createLogger = (options) => {
26
+ return {
27
+ log: (...args) => {
28
+ !options?.disabled && consola.log("", ...args);
29
+ },
30
+ error: (...args) => {
31
+ !options?.disabled && consola.error("", ...args);
32
+ },
33
+ warn: (...args) => {
34
+ !options?.disabled && consola.warn("", ...args);
35
+ },
36
+ info: (...args) => {
37
+ !options?.disabled && consola.info("", ...args);
38
+ },
39
+ debug: (...args) => {
40
+ !options?.disabled && consola.debug("", ...args);
41
+ },
42
+ box: (...args) => {
43
+ !options?.disabled && consola.box("", ...args);
44
+ },
45
+ success: (...args) => {
46
+ !options?.disabled && consola.success("", ...args);
47
+ },
48
+ break: (...args) => {
49
+ !options?.disabled && console.log("\n");
50
+ }
51
+ };
52
+ };
53
+ var logger = createLogger();
54
+
55
+ // src/cli/get-config.ts
56
+ import path from "path";
57
+ import babelPresetTypescript from "@babel/preset-typescript";
58
+ import babelPresetReact from "@babel/preset-react";
59
+ var possiblePaths = ["auth.ts", "auth.tsx"];
60
+ possiblePaths = [
61
+ ...possiblePaths,
62
+ ...possiblePaths.map((it) => `lib/${it}`),
63
+ ...possiblePaths.map((it) => `utils/${it}`)
64
+ ];
65
+ possiblePaths = [...possiblePaths, ...possiblePaths.map((it) => `src/${it}`)];
66
+ var jitiOptions = {
67
+ transformOptions: {
68
+ babel: {
69
+ presets: [
70
+ [babelPresetTypescript, { isTSX: true, allExtensions: true }],
71
+ [babelPresetReact, { runtime: "automatic" }]
72
+ ]
73
+ }
74
+ },
75
+ extensions: [".ts", ".tsx", ".js", ".jsx"]
76
+ };
77
+ async function getConfig({
78
+ cwd,
79
+ configPath
80
+ }) {
81
+ try {
82
+ let configFile = null;
83
+ if (configPath) {
84
+ const { config } = await loadConfig({
85
+ configFile: path.join(cwd, configPath),
86
+ dotenv: true,
87
+ jitiOptions
88
+ });
89
+ if (!config.auth && !config.default) {
90
+ logger.error(
91
+ "[#better-auth]: Couldn't read your auth config. Make sure to default export your auth instance or to export as a variable named auth."
92
+ );
93
+ process.exit(1);
94
+ }
95
+ configFile = config.auth?.options || config.default?.options || null;
96
+ }
97
+ if (!configFile) {
98
+ for (const possiblePath of possiblePaths) {
99
+ try {
100
+ const { config } = await loadConfig({
101
+ configFile: possiblePath,
102
+ jitiOptions
103
+ });
104
+ const hasConfig = Object.keys(config).length > 0;
105
+ if (hasConfig) {
106
+ configFile = config.auth?.options || config.default?.options || null;
107
+ if (!configFile) {
108
+ logger.error("[#better-auth]: Couldn't read your auth config.");
109
+ logger.break();
110
+ logger.info(
111
+ "[#better-auth]: Make sure to default export your auth instance or to export as a variable named auth."
112
+ );
113
+ process.exit(1);
114
+ }
115
+ break;
116
+ }
117
+ } catch (e) {
118
+ process.exit(1);
119
+ }
120
+ }
121
+ }
122
+ return configFile;
123
+ } catch (e) {
124
+ process.exit(1);
125
+ }
126
+ }
127
+
128
+ // src/cli/commands/migrate.ts
129
+ import { z } from "zod";
130
+ import { existsSync } from "fs";
131
+ import path2 from "path";
132
+
133
+ // src/adapters/kysely-adapter/dialect.ts
134
+ import { Kysely } from "kysely";
135
+ import {
136
+ MysqlDialect,
137
+ PostgresDialect,
138
+ SqliteDialect
139
+ } from "kysely";
140
+
141
+ // src/error/better-auth-error.ts
142
+ var BetterAuthError = class extends Error {
143
+ constructor(message, cause, docsLink) {
144
+ super(message);
145
+ this.name = "BetterAuthError";
146
+ this.message = message;
147
+ this.cause = cause;
148
+ this.stack = "";
149
+ }
150
+ };
151
+ var MissingDependencyError = class extends BetterAuthError {
152
+ constructor(pkgName) {
153
+ super(
154
+ `The package "${pkgName}" is required. Make sure it is installed.`,
155
+ pkgName
156
+ );
157
+ }
158
+ };
159
+
160
+ // src/adapters/kysely-adapter/dialect.ts
161
+ import "execa";
162
+ import "prompts";
163
+
164
+ // src/cli/utils/get-package-manager.ts
165
+ import { detect } from "@antfu/ni";
166
+
167
+ // src/adapters/kysely-adapter/dialect.ts
168
+ import "ora";
169
+ var getDialect = async (config, isCli) => {
170
+ if (!config.database) {
171
+ return void 0;
172
+ }
173
+ if ("createDriver" in config.database) {
174
+ return config.database;
175
+ }
176
+ let dialect = void 0;
177
+ if ("provider" in config.database) {
178
+ const provider = config.database.provider;
179
+ const connectionString = config.database?.url?.trim();
180
+ if (provider === "postgres") {
181
+ const pg = await import("pg").catch(async (e) => {
182
+ throw new MissingDependencyError("pg");
183
+ });
184
+ const Pool = pg.default?.Pool || pg.Pool;
185
+ const pool = new Pool({
186
+ connectionString
187
+ });
188
+ dialect = new PostgresDialect({
189
+ pool
190
+ });
191
+ }
192
+ if (provider === "mysql") {
193
+ try {
194
+ const { createPool } = await import("mysql2/promise").catch(
195
+ async (e) => {
196
+ throw new MissingDependencyError("mysql2");
197
+ }
198
+ );
199
+ const params = new URL(connectionString);
200
+ const pool = createPool({
201
+ host: params.hostname,
202
+ user: params.username,
203
+ password: params.password,
204
+ database: params.pathname.split("/")[1],
205
+ port: Number(params.port)
206
+ });
207
+ dialect = new MysqlDialect({ pool });
208
+ } catch (e) {
209
+ if (e instanceof TypeError) {
210
+ throw new BetterAuthError("Invalid database URL");
211
+ }
212
+ throw e;
213
+ }
214
+ }
215
+ if (provider === "sqlite") {
216
+ try {
217
+ const database = await import("better-sqlite3").catch(async (e) => {
218
+ throw new MissingDependencyError("better-sqlite3");
219
+ });
220
+ const Database = database.default || database;
221
+ const db = new Database(connectionString);
222
+ dialect = new SqliteDialect({
223
+ database: db
224
+ });
225
+ } catch (e) {
226
+ console.error(e);
227
+ throw new BetterAuthError(
228
+ "Failed to initialize SQLite. Make sure `better-sqlite3` is properly installed."
229
+ );
230
+ }
231
+ }
232
+ }
233
+ return dialect;
234
+ };
235
+ var createKyselyAdapter = async (config, isCli) => {
236
+ const dialect = await getDialect(config, isCli);
237
+ if (!dialect) {
238
+ return dialect;
239
+ }
240
+ const db = new Kysely({
241
+ dialect
242
+ });
243
+ return db;
244
+ };
245
+ var getDatabaseType = (config) => {
246
+ if ("provider" in config.database) {
247
+ return config.database.provider;
248
+ }
249
+ if ("dialect" in config.database) {
250
+ if (config.database.dialect instanceof PostgresDialect) {
251
+ return "postgres";
252
+ }
253
+ if (config.database.dialect instanceof MysqlDialect) {
254
+ return "mysql";
255
+ }
256
+ if (config.database.dialect instanceof SqliteDialect) {
257
+ return "sqlite";
258
+ }
259
+ }
260
+ return "sqlite";
261
+ };
262
+
263
+ // src/cli/commands/migrate.ts
264
+ import ora3 from "ora";
265
+ import chalk from "chalk";
266
+ import prompts3 from "prompts";
267
+
268
+ // src/cli/utils/get-migration.ts
269
+ import "kysely";
270
+
271
+ // src/db/get-tables.ts
272
+ var getAuthTables = (options) => {
273
+ const pluginSchema = options.plugins?.reduce(
274
+ (acc, plugin) => {
275
+ const schema = plugin.schema;
276
+ if (!schema) return acc;
277
+ for (const [key, value] of Object.entries(schema)) {
278
+ acc[key] = {
279
+ fields: {
280
+ ...acc[key]?.fields,
281
+ ...value.fields
282
+ },
283
+ tableName: key
284
+ };
285
+ }
286
+ return acc;
287
+ },
288
+ {}
289
+ );
290
+ const shouldAddRateLimitTable = options.rateLimit?.storage === "database";
291
+ const rateLimitTable = {
292
+ rateLimit: {
293
+ tableName: options.rateLimit?.tableName || "rateLimit",
294
+ fields: {
295
+ key: {
296
+ type: "string"
297
+ },
298
+ count: {
299
+ type: "number"
300
+ },
301
+ lastRequest: {
302
+ type: "number"
303
+ }
304
+ }
305
+ }
306
+ };
307
+ const { user, session, account, ...pluginTables } = pluginSchema || {};
308
+ return {
309
+ user: {
310
+ tableName: options.user?.modelName || "user",
311
+ fields: {
312
+ name: {
313
+ type: "string",
314
+ required: true
315
+ },
316
+ email: {
317
+ type: "string",
318
+ unique: true,
319
+ required: true
320
+ },
321
+ emailVerified: {
322
+ type: "boolean",
323
+ defaultValue: () => false,
324
+ required: true
325
+ },
326
+ image: {
327
+ type: "string",
328
+ required: false
329
+ },
330
+ createdAt: {
331
+ type: "date",
332
+ defaultValue: () => /* @__PURE__ */ new Date(),
333
+ required: true
334
+ },
335
+ updatedAt: {
336
+ type: "date",
337
+ defaultValue: () => /* @__PURE__ */ new Date(),
338
+ required: true
339
+ },
340
+ ...user?.fields
341
+ },
342
+ order: 0
343
+ },
344
+ session: {
345
+ tableName: options.session?.modelName || "session",
346
+ fields: {
347
+ expiresAt: {
348
+ type: "date",
349
+ required: true
350
+ },
351
+ ipAddress: {
352
+ type: "string",
353
+ required: false
354
+ },
355
+ userAgent: {
356
+ type: "string",
357
+ required: false
358
+ },
359
+ userId: {
360
+ type: "string",
361
+ references: {
362
+ model: "user",
363
+ field: "id",
364
+ onDelete: "cascade"
365
+ },
366
+ required: true
367
+ },
368
+ ...session?.fields
369
+ },
370
+ order: 1
371
+ },
372
+ account: {
373
+ tableName: options.account?.modelName || "account",
374
+ fields: {
375
+ accountId: {
376
+ type: "string",
377
+ required: true
378
+ },
379
+ providerId: {
380
+ type: "string",
381
+ required: true
382
+ },
383
+ userId: {
384
+ type: "string",
385
+ references: {
386
+ model: "user",
387
+ field: "id",
388
+ onDelete: "cascade"
389
+ },
390
+ required: true
391
+ },
392
+ accessToken: {
393
+ type: "string",
394
+ required: false
395
+ },
396
+ refreshToken: {
397
+ type: "string",
398
+ required: false
399
+ },
400
+ idToken: {
401
+ type: "string",
402
+ required: false
403
+ },
404
+ expiresAt: {
405
+ type: "date",
406
+ required: false
407
+ },
408
+ password: {
409
+ type: "string",
410
+ required: false
411
+ },
412
+ ...account?.fields
413
+ },
414
+ order: 2
415
+ },
416
+ ...pluginTables,
417
+ ...shouldAddRateLimitTable ? rateLimitTable : {}
418
+ };
419
+ };
420
+
421
+ // src/cli/utils/get-schema.ts
422
+ function getPluginTable(config) {
423
+ const pluginsMigrations = config.plugins?.flatMap(
424
+ (plugin) => Object.keys(plugin.schema || {}).map((key) => {
425
+ const schema = plugin.schema || {};
426
+ const table = schema[key];
427
+ if (table?.disableMigration) {
428
+ return;
429
+ }
430
+ return {
431
+ tableName: key,
432
+ fields: table?.fields
433
+ };
434
+ }).filter((value) => value !== void 0)
435
+ ) || [];
436
+ return pluginsMigrations;
437
+ }
438
+ function getSchema(config) {
439
+ const baseSchema = getAuthTables(config);
440
+ const pluginSchema = getPluginTable(config);
441
+ const schema = [
442
+ baseSchema.user,
443
+ baseSchema.session,
444
+ baseSchema.account,
445
+ ...pluginSchema
446
+ ].reduce((acc, curr) => {
447
+ acc[curr.tableName] = {
448
+ fields: {
449
+ ...acc[curr.tableName]?.fields,
450
+ ...curr.fields
451
+ }
452
+ };
453
+ return acc;
454
+ }, {});
455
+ return schema;
456
+ }
457
+
458
+ // src/cli/utils/get-migration.ts
459
+ var postgresMap = {
460
+ string: ["character varying", "text"],
461
+ number: [
462
+ "int4",
463
+ "integer",
464
+ "bigint",
465
+ "smallint",
466
+ "numeric",
467
+ "real",
468
+ "double precision"
469
+ ],
470
+ boolean: ["bool", "boolean"],
471
+ date: ["timestamp", "date"]
472
+ };
473
+ var mysqlMap = {
474
+ string: ["varchar", "text"],
475
+ number: [
476
+ "integer",
477
+ "int",
478
+ "bigint",
479
+ "smallint",
480
+ "decimal",
481
+ "float",
482
+ "double"
483
+ ],
484
+ boolean: ["boolean"],
485
+ date: ["date", "datetime"]
486
+ };
487
+ var sqliteMap = {
488
+ string: ["TEXT"],
489
+ number: ["INTEGER", "REAL"],
490
+ boolean: ["INTEGER", "BOOLEAN"],
491
+ // 0 or 1
492
+ date: ["DATE", "INTEGER"]
493
+ };
494
+ var map = {
495
+ postgres: postgresMap,
496
+ mysql: mysqlMap,
497
+ sqlite: sqliteMap
498
+ };
499
+ function matchType(columnDataType, fieldType, dbType) {
500
+ const types = map[dbType];
501
+ const type = types[fieldType].map((t) => t.toLowerCase());
502
+ const matches = type.includes(columnDataType.toLowerCase());
503
+ return matches;
504
+ }
505
+ async function getMigrations(config) {
506
+ const betterAuthSchema = getSchema(config);
507
+ const dbType = getDatabaseType(config);
508
+ const db = await createKyselyAdapter(config);
509
+ if (!db) {
510
+ logger.error("Invalid database configuration.");
511
+ process.exit(1);
512
+ }
513
+ const tableMetadata = await db.introspection.getTables();
514
+ const toBeCreated = [];
515
+ const toBeAdded = [];
516
+ for (const [key, value] of Object.entries(betterAuthSchema)) {
517
+ const table = tableMetadata.find((t) => t.name === key);
518
+ if (!table) {
519
+ const tIndex = toBeCreated.findIndex((t) => t.table === key);
520
+ const tableData = {
521
+ table: key,
522
+ fields: value.fields,
523
+ order: value.order || Infinity
524
+ };
525
+ const insertIndex = toBeCreated.findIndex(
526
+ (t) => (t.order || Infinity) > tableData.order
527
+ );
528
+ if (insertIndex === -1) {
529
+ if (tIndex === -1) {
530
+ toBeCreated.push(tableData);
531
+ } else {
532
+ toBeCreated[tIndex].fields = {
533
+ ...toBeCreated[tIndex].fields,
534
+ ...value.fields
535
+ };
536
+ }
537
+ } else {
538
+ toBeCreated.splice(insertIndex, 0, tableData);
539
+ }
540
+ continue;
541
+ }
542
+ let toBeAddedFields = {};
543
+ for (const [fieldName, field] of Object.entries(value.fields)) {
544
+ const column = table.columns.find((c) => c.name === fieldName);
545
+ if (!column) {
546
+ toBeAddedFields[fieldName] = field;
547
+ continue;
548
+ }
549
+ if (matchType(column.dataType, field.type, dbType)) {
550
+ continue;
551
+ } else {
552
+ logger.warn(
553
+ `Field ${fieldName} in table ${key} has a different type in the database. Expected ${field.type} but got ${column.dataType}.`
554
+ );
555
+ }
556
+ }
557
+ if (Object.keys(toBeAddedFields).length > 0) {
558
+ toBeAdded.push({
559
+ table: key,
560
+ fields: toBeAddedFields,
561
+ order: value.order || Infinity
562
+ });
563
+ }
564
+ }
565
+ const migrations = [];
566
+ function getType(type) {
567
+ const typeMap = {
568
+ string: "text",
569
+ boolean: "boolean",
570
+ number: "integer",
571
+ date: "date"
572
+ };
573
+ if (dbType === "mysql" && type === "string") {
574
+ return "varchar(255)";
575
+ }
576
+ return typeMap[type];
577
+ }
578
+ if (toBeAdded.length) {
579
+ for (const table of toBeAdded) {
580
+ for (const [fieldName, field] of Object.entries(table.fields)) {
581
+ const type = getType(field.type);
582
+ const exec = db.schema.alterTable(table.table).addColumn(fieldName, type, (col) => {
583
+ col = field.required !== false ? col.notNull() : col;
584
+ if (field.references) {
585
+ col = col.references(
586
+ `${field.references.model}.${field.references.field}`
587
+ );
588
+ }
589
+ return col;
590
+ });
591
+ migrations.push(exec);
592
+ }
593
+ }
594
+ }
595
+ if (toBeCreated.length) {
596
+ for (const table of toBeCreated) {
597
+ let dbT = db.schema.createTable(table.table).addColumn("id", getType("string"), (col) => col.primaryKey());
598
+ for (const [fieldName, field] of Object.entries(table.fields)) {
599
+ const type = getType(field.type);
600
+ dbT = dbT.addColumn(fieldName, type, (col) => {
601
+ col = field.required !== false ? col.notNull() : col;
602
+ if (field.references) {
603
+ col = col.references(
604
+ `${field.references.model}.${field.references.field}`
605
+ );
606
+ }
607
+ if (field.unique) {
608
+ col = col.unique();
609
+ }
610
+ return col;
611
+ });
612
+ }
613
+ migrations.push(dbT);
614
+ }
615
+ }
616
+ async function runMigrations() {
617
+ for (const migration of migrations) {
618
+ await migration.execute();
619
+ }
620
+ }
621
+ async function compileMigrations() {
622
+ const compiled = migrations.map((m) => m.compile().sql);
623
+ return compiled.join(";\n\n");
624
+ }
625
+ return { toBeCreated, toBeAdded, runMigrations, compileMigrations };
626
+ }
627
+
628
+ // src/cli/utils/install-dep.ts
629
+ import ora2 from "ora";
630
+ import prompts2 from "prompts";
631
+ import { execa as execa2 } from "execa";
632
+
633
+ // src/cli/commands/migrate.ts
634
+ var migrate = new Command("migrate").option(
635
+ "-c, --cwd <cwd>",
636
+ "the working directory. defaults to the current directory.",
637
+ process.cwd()
638
+ ).option(
639
+ "--config <config>",
640
+ "the path to the configuration file. defaults to the first configuration file found."
641
+ ).option("--y", "").action(async (opts) => {
642
+ const options = z.object({
643
+ cwd: z.string(),
644
+ config: z.string().optional()
645
+ }).parse(opts);
646
+ const cwd = path2.resolve(options.cwd);
647
+ if (!existsSync(cwd)) {
648
+ logger.error(`The directory "${cwd}" does not exist.`);
649
+ process.exit(1);
650
+ }
651
+ const config = await getConfig({
652
+ cwd,
653
+ configPath: options.config
654
+ });
655
+ if (!config) {
656
+ logger.error(
657
+ "No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag."
658
+ );
659
+ return;
660
+ }
661
+ const db = await createKyselyAdapter(config, true).catch((e) => {
662
+ logger.error(e.message);
663
+ process.exit(1);
664
+ });
665
+ if (!db) {
666
+ logger.error("Invalid database configuration.");
667
+ process.exit(1);
668
+ }
669
+ const spinner = ora3("preparing migration...").start();
670
+ const { toBeAdded, toBeCreated, runMigrations } = await getMigrations(config);
671
+ if (!toBeAdded.length && !toBeCreated.length) {
672
+ spinner.stop();
673
+ logger.success("\u{1F680} No migrations needed.");
674
+ process.exit(0);
675
+ }
676
+ spinner.stop();
677
+ logger.info(`\u{1F511} The migration will affect the following:`);
678
+ for (const table of [...toBeCreated, ...toBeAdded]) {
679
+ logger.info(
680
+ "->",
681
+ chalk.magenta(Object.keys(table.fields).join(", ")),
682
+ chalk.white("fields on"),
683
+ chalk.yellow(`${table.table}`),
684
+ chalk.white("table.")
685
+ );
686
+ }
687
+ const { migrate: migrate2 } = await prompts3({
688
+ type: "confirm",
689
+ name: "migrate",
690
+ message: "Are you sure you want to run these migrations?",
691
+ initial: false
692
+ });
693
+ if (!migrate2) {
694
+ logger.info("Migration cancelled.");
695
+ process.exit(0);
696
+ }
697
+ spinner?.start("migrating...");
698
+ await runMigrations();
699
+ spinner.stop();
700
+ logger.success("\u{1F680} migration was completed successfully!");
701
+ process.exit(0);
702
+ });
703
+
704
+ // src/cli/commands/generate.ts
705
+ import { Command as Command2 } from "commander";
706
+ import { z as z3 } from "zod";
707
+ import { existsSync as existsSync2 } from "fs";
708
+ import path3 from "path";
709
+ import ora4 from "ora";
710
+ import prompts4 from "prompts";
711
+
712
+ // src/utils/cookies.ts
713
+ import { TimeSpan } from "oslo";
714
+
715
+ // src/utils/id.ts
716
+ import { alphabet, generateRandomString } from "oslo/crypto";
717
+
718
+ // src/utils/state.ts
719
+ import { generateState as generateStateOAuth } from "oslo/oauth2";
720
+ import { z as z2 } from "zod";
721
+
722
+ // src/adapters/kysely-adapter/index.ts
723
+ function convertWhere(w) {
724
+ if (!w)
725
+ return {
726
+ and: null,
727
+ or: null
728
+ };
729
+ const and = w?.filter((w2) => w2.connector === "AND" || !w2.connector).reduce(
730
+ (acc, w2) => ({
731
+ ...acc,
732
+ [w2.field]: w2.value
733
+ }),
734
+ {}
735
+ );
736
+ const or = w?.filter((w2) => w2.connector === "OR").reduce(
737
+ (acc, w2) => ({
738
+ ...acc,
739
+ [w2.field]: w2.value
740
+ }),
741
+ {}
742
+ );
743
+ return {
744
+ and: Object.keys(and).length ? and : null,
745
+ or: Object.keys(or).length ? or : null
746
+ };
747
+ }
748
+ function transformTo(val, fields, transform) {
749
+ for (const key in val) {
750
+ if (val[key] === 0 && fields[key]?.type === "boolean" && transform?.boolean) {
751
+ val[key] = false;
752
+ }
753
+ if (val[key] === 1 && fields[key]?.type === "boolean" && transform?.boolean) {
754
+ val[key] = true;
755
+ }
756
+ if (fields[key]?.type === "date") {
757
+ if (!(val[key] instanceof Date)) {
758
+ val[key] = new Date(val[key]);
759
+ }
760
+ }
761
+ }
762
+ return val;
763
+ }
764
+ function transformFrom(val, transform) {
765
+ for (const key in val) {
766
+ if (typeof val[key] === "boolean" && transform?.boolean) {
767
+ val[key] = val[key] ? 1 : 0;
768
+ }
769
+ if (val[key] instanceof Date) {
770
+ val[key] = val[key].toISOString();
771
+ }
772
+ }
773
+ return val;
774
+ }
775
+ var kyselyAdapter = (db, config) => {
776
+ return {
777
+ id: "kysely",
778
+ async create(data) {
779
+ let { model, data: val, select } = data;
780
+ if (config?.transform) {
781
+ val = transformFrom(val, config.transform);
782
+ }
783
+ let res = await db.insertInto(model).values(val).returningAll().executeTakeFirst();
784
+ if (config?.transform) {
785
+ const schema = config.transform.schema[model];
786
+ res = schema ? transformTo(val, schema, config.transform) : res;
787
+ }
788
+ if (select?.length) {
789
+ const data2 = res ? select.reduce((acc, cur) => {
790
+ if (res?.[cur]) {
791
+ return {
792
+ ...acc,
793
+ [cur]: res[cur]
794
+ };
795
+ }
796
+ return acc;
797
+ }, {}) : null;
798
+ res = data2;
799
+ }
800
+ return res;
801
+ },
802
+ async findOne(data) {
803
+ const { model, where, select } = data;
804
+ const { and, or } = convertWhere(where);
805
+ let query = db.selectFrom(model).selectAll();
806
+ if (or) {
807
+ query = query.where((eb) => eb.or(or));
808
+ }
809
+ if (and) {
810
+ query = query.where((eb) => eb.and(and));
811
+ }
812
+ let res = await query.executeTakeFirst();
813
+ if (select?.length) {
814
+ const data2 = res ? select.reduce((acc, cur) => {
815
+ if (res?.[cur]) {
816
+ return {
817
+ ...acc,
818
+ [cur]: res[cur]
819
+ };
820
+ }
821
+ return acc;
822
+ }, {}) : null;
823
+ res = data2;
824
+ }
825
+ if (config?.transform) {
826
+ const schema = config.transform.schema[model];
827
+ res = res && schema ? transformTo(res, schema, config.transform) : res;
828
+ return res || null;
829
+ }
830
+ return res || null;
831
+ },
832
+ async findMany(data) {
833
+ const { model, where } = data;
834
+ let query = db.selectFrom(model);
835
+ const { and, or } = convertWhere(where);
836
+ if (and) {
837
+ query = query.where((eb) => eb.and(and));
838
+ }
839
+ if (or) {
840
+ query = query.where((eb) => eb.or(or));
841
+ }
842
+ const res = await query.selectAll().execute();
843
+ if (config?.transform) {
844
+ const schema = config.transform.schema[model];
845
+ return schema ? res.map((v) => transformTo(v, schema, config.transform)) : res;
846
+ }
847
+ return res;
848
+ },
849
+ async update(data) {
850
+ let { model, where, update: val } = data;
851
+ const { and, or } = convertWhere(where);
852
+ if (config?.transform) {
853
+ val = transformFrom(val, config.transform);
854
+ }
855
+ let query = db.updateTable(model).set(val);
856
+ if (and) {
857
+ query = query.where((eb) => eb.and(and));
858
+ }
859
+ if (or) {
860
+ query = query.where((eb) => eb.or(or));
861
+ }
862
+ const res = await query.returningAll().executeTakeFirst() || null;
863
+ if (config?.transform) {
864
+ const schema = config.transform.schema[model];
865
+ return schema ? transformTo(res, schema, config.transform) : res;
866
+ }
867
+ return res;
868
+ },
869
+ async delete(data) {
870
+ const { model, where } = data;
871
+ const { and, or } = convertWhere(where);
872
+ let query = db.deleteFrom(model);
873
+ if (and) {
874
+ query = query.where((eb) => eb.and(and));
875
+ }
876
+ if (or) {
877
+ query = query.where((eb) => eb.or(or));
878
+ }
879
+ await query.execute();
880
+ },
881
+ async createSchema(options) {
882
+ const { compileMigrations } = await getMigrations(options);
883
+ const migrations = await compileMigrations();
884
+ return {
885
+ code: migrations,
886
+ fileName: `./better-auth_migrations/${(/* @__PURE__ */ new Date()).toISOString()}.sql`
887
+ };
888
+ }
889
+ };
890
+ };
891
+
892
+ // src/db/utils.ts
893
+ async function getAdapter(options, isCli) {
894
+ if (!options.database) {
895
+ throw new BetterAuthError("Database configuration is required");
896
+ }
897
+ if ("create" in options.database) {
898
+ return options.database;
899
+ }
900
+ const db = await createKyselyAdapter(options, isCli);
901
+ if (!db) {
902
+ throw new BetterAuthError("Failed to initialize database adapter");
903
+ }
904
+ const tables = getAuthTables(options);
905
+ let schema = {};
906
+ for (const table of Object.values(tables)) {
907
+ schema[table.tableName] = table.fields;
908
+ }
909
+ return kyselyAdapter(db, {
910
+ transform: {
911
+ schema,
912
+ date: true,
913
+ boolean: getDatabaseType(options) === "sqlite"
914
+ }
915
+ });
916
+ }
917
+
918
+ // src/cli/commands/generate.ts
919
+ import fs from "fs/promises";
920
+ import chalk2 from "chalk";
921
+ var generate = new Command2("generate").option(
922
+ "-c, --cwd <cwd>",
923
+ "the working directory. defaults to the current directory.",
924
+ process.cwd()
925
+ ).option(
926
+ "--config <config>",
927
+ "the path to the configuration file. defaults to the first configuration file found."
928
+ ).option("--out <output>", "the file to output to the generated schema").option("--y", "").action(async (opts) => {
929
+ const options = z3.object({
930
+ cwd: z3.string(),
931
+ config: z3.string().optional(),
932
+ out: z3.string().optional()
933
+ }).parse(opts);
934
+ const cwd = path3.resolve(options.cwd);
935
+ if (!existsSync2(cwd)) {
936
+ logger.error(`The directory "${cwd}" does not exist.`);
937
+ process.exit(1);
938
+ }
939
+ const config = await getConfig({
940
+ cwd,
941
+ configPath: options.config
942
+ });
943
+ if (!config) {
944
+ logger.error(
945
+ "No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag."
946
+ );
947
+ return;
948
+ }
949
+ const adapter = await getAdapter(config, true).catch((e) => {
950
+ logger.error(e.message);
951
+ process.exit(1);
952
+ });
953
+ if (!adapter.createSchema) {
954
+ logger.error("The adapter does not support schema generation.");
955
+ process.exit(1);
956
+ }
957
+ const spinner = ora4("preparing schema...").start();
958
+ const { code, fileName, append } = await adapter.createSchema(
959
+ config,
960
+ options.out
961
+ );
962
+ spinner.stop();
963
+ if (!code) {
964
+ logger.success("Your schema is already up to date.");
965
+ process.exit(0);
966
+ }
967
+ if (append) {
968
+ const { append: append2 } = await prompts4({
969
+ type: "confirm",
970
+ name: "append",
971
+ message: `The file ${fileName} already exists. Do you want to ${chalk2.yellow(
972
+ "append"
973
+ )} the schema to the file?`
974
+ });
975
+ if (append2) {
976
+ await fs.appendFile(path3.join(cwd, fileName), code);
977
+ logger.success(`\u{1F680} schema was appended successfully!`);
978
+ process.exit(0);
979
+ } else {
980
+ logger.error("Schema generation aborted.");
981
+ process.exit(1);
982
+ }
983
+ }
984
+ const { confirm } = await prompts4({
985
+ type: "confirm",
986
+ name: "confirm",
987
+ message: `Do you want to generate the schema to ${chalk2.yellow(
988
+ fileName
989
+ )}?`
990
+ });
991
+ if (!confirm) {
992
+ logger.error("Schema generation aborted.");
993
+ process.exit(1);
994
+ }
995
+ const dirExist = existsSync2(path3.dirname(path3.join(cwd, fileName)));
996
+ if (!dirExist) {
997
+ await fs.mkdir(path3.dirname(path3.join(cwd, fileName)), {
998
+ recursive: true
999
+ });
1000
+ }
1001
+ await fs.writeFile(options.out || path3.join(cwd, fileName), code);
1002
+ logger.success(`\u{1F680} schema was generated successfully!`);
1003
+ process.exit(0);
1004
+ });
1005
+
1006
+ // src/cli/index.ts
1007
+ async function main() {
1008
+ const program = new Command3().name("better-auth");
1009
+ program.addCommand(migrate).addCommand(generate).version("0.0.1").description("Better Auth CLI");
1010
+ program.parse();
1011
+ }
1012
+ main();