nuxt-users 1.1.0 → 1.1.2

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/README.md CHANGED
@@ -31,12 +31,14 @@ export default defineNuxtConfig({
31
31
  })
32
32
 
33
33
  # Run migrations
34
- yarn db:migrate
34
+ npx nuxt-users migrate
35
35
 
36
36
  # Create your first user
37
- yarn db:create-user user@example.com "John Doe" password123
37
+ npx nuxt-users create-user user@example.com "John Doe" password123
38
38
  ```
39
39
 
40
+ > **Note**: For SQLite (default), the module automatically creates the database file. If installation hangs with other databases, add `skipDatabaseChecks: true` to your configuration. See [Troubleshooting](/docs/troubleshooting/hanging-during-install) for more details.
41
+
40
42
  ## Documentation
41
43
 
42
44
  📚 **Full documentation is available at: [docs/](/docs/)**
Binary file
package/dist/module.d.mts CHANGED
@@ -8,6 +8,9 @@ type DatabaseConfig = {
8
8
  user?: string;
9
9
  password?: string;
10
10
  database?: string;
11
+ connectTimeout?: number;
12
+ acquireTimeout?: number;
13
+ connectionTimeoutMillis?: number;
11
14
  };
12
15
  interface ModuleOptions {
13
16
  connector?: {
@@ -29,6 +32,11 @@ interface ModuleOptions {
29
32
  * @default 'http://localhost:3000' // Example, will be set in module defaults
30
33
  */
31
34
  passwordResetBaseUrl?: string;
35
+ /**
36
+ * Skip database checks during module setup to prevent hanging
37
+ * @default false
38
+ */
39
+ skipDatabaseChecks?: boolean;
32
40
  }
33
41
  interface MailerOptions {
34
42
  host: string;
package/dist/module.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
3
  "configKey": "nuxtUsers",
4
- "version": "1.1.0",
4
+ "version": "1.1.2",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.1",
7
- "unbuild": "3.5.0"
7
+ "unbuild": "2.0.0"
8
8
  }
9
9
  }
package/dist/module.mjs CHANGED
@@ -1,7 +1,9 @@
1
1
  import { defineNuxtModule, createResolver, addServerHandler, addPlugin, addComponent } from '@nuxt/kit';
2
2
  import { defu } from 'defu';
3
- import { checkUsersTableExists, checkPersonalAccessTokensTableExists, checkPasswordResetTokensTableExists, hasAnyUsers } from '../dist/runtime/server/utils/db.js';
3
+ import { hasAnyUsers, checkUsersTableExists, checkPersonalAccessTokensTableExists, checkPasswordResetTokensTableExists } from '../dist/runtime/server/utils/db.js';
4
4
  import { getAppliedMigrations } from '../dist/runtime/server/utils/migrate.js';
5
+ import { promises } from 'node:fs';
6
+ import { dirname } from 'node:path';
5
7
 
6
8
  const defaultOptions = {
7
9
  connector: {
@@ -30,8 +32,39 @@ const defaultOptions = {
30
32
  from: '"Nuxt Users Module" <noreply@example.com>'
31
33
  }
32
34
  },
33
- passwordResetBaseUrl: "http://localhost:3000"
35
+ passwordResetBaseUrl: "http://localhost:3000",
34
36
  // Added
37
+ // Add option to disable database checks during setup
38
+ skipDatabaseChecks: false
39
+ };
40
+ const runWithTimeout = async (operation, timeoutMs = 5e3) => {
41
+ return Promise.race([
42
+ operation(),
43
+ new Promise((resolve) => {
44
+ setTimeout(() => {
45
+ console.warn("[Nuxt Users DB] \u26A0\uFE0F Database operation timed out after", timeoutMs, "ms");
46
+ resolve(null);
47
+ }, timeoutMs);
48
+ })
49
+ ]);
50
+ };
51
+ const ensureSqliteDatabaseExists = async (options) => {
52
+ if (options.connector?.name === "sqlite" && options.connector.options.path) {
53
+ const dbPath = options.connector.options.path;
54
+ try {
55
+ await promises.access(dbPath);
56
+ console.log(`[Nuxt Users DB] \u2139\uFE0F SQLite database exists: ${dbPath}`);
57
+ } catch {
58
+ try {
59
+ const dbDir = dirname(dbPath);
60
+ await promises.mkdir(dbDir, { recursive: true });
61
+ console.log(`[Nuxt Users DB] \u2139\uFE0F Created SQLite database directory: ${dbDir}`);
62
+ console.log(`[Nuxt Users DB] \u2139\uFE0F SQLite database will be created at: ${dbPath}`);
63
+ } catch (error) {
64
+ console.warn(`[Nuxt Users DB] \u26A0\uFE0F Failed to create database directory: ${error instanceof Error ? error.message : "Unknown error"}`);
65
+ }
66
+ }
67
+ }
35
68
  };
36
69
  const module = defineNuxtModule({
37
70
  meta: {
@@ -42,29 +75,20 @@ const module = defineNuxtModule({
42
75
  defaults: defaultOptions,
43
76
  async setup(options, nuxt) {
44
77
  const resolver = createResolver(import.meta.url);
45
- const appliedMigrations = await getAppliedMigrations(options);
46
- const requiredMigrations = [
47
- "create_migrations_table",
48
- "create_users_table",
49
- "create_personal_access_tokens_table",
50
- "create_password_reset_tokens_table"
51
- ];
52
- const missingMigrations = requiredMigrations.filter((migration) => !appliedMigrations.includes(migration));
53
- if (missingMigrations.length > 0) {
54
- console.warn(`[Nuxt Users DB] \u26A0\uFE0F Missing migrations: ${missingMigrations.join(", ")}`);
55
- console.warn("[Nuxt Users DB] \u26A0\uFE0F Run migrations with: yarn db:migrate");
56
- }
57
- const hasUsersTable = await checkUsersTableExists(options);
58
- if (!hasUsersTable) {
59
- console.warn("[Nuxt Users DB] \u26A0\uFE0F Users table does not exist, you should run the migration script to create it by running: yarn db:migrate");
60
- }
61
- const hasPersonalAccessTokensTable = await checkPersonalAccessTokensTableExists(options);
62
- if (!hasPersonalAccessTokensTable) {
63
- console.warn("[Nuxt Users DB] \u26A0\uFE0F Personal access tokens table does not exist, you should run the migration script to create it by running: yarn db:migrate");
64
- }
65
- const hasPasswordResetTokensTable = await checkPasswordResetTokensTableExists(options);
66
- if (!hasPasswordResetTokensTable) {
67
- console.warn("[Nuxt Users DB] \u26A0\uFE0F Password reset tokens table does not exist, you should run the migration script to create it by running: yarn db:migrate");
78
+ await ensureSqliteDatabaseExists(options);
79
+ if (!options.skipDatabaseChecks) {
80
+ const appliedMigrations = await runWithTimeout(() => getAppliedMigrations(options)) || [];
81
+ const requiredMigrations = [
82
+ "create_migrations_table",
83
+ "create_users_table",
84
+ "create_personal_access_tokens_table",
85
+ "create_password_reset_tokens_table"
86
+ ];
87
+ const missingMigrations = requiredMigrations.filter((migration) => !appliedMigrations.includes(migration));
88
+ if (missingMigrations.length > 0) {
89
+ console.warn(`[Nuxt Users DB] \u26A0\uFE0F Missing migrations: ${missingMigrations.join(", ")}`);
90
+ console.warn("[Nuxt Users DB] \u26A0\uFE0F Run migrations with: npx nuxt-users-migrate");
91
+ }
68
92
  }
69
93
  const runtimeConfigOptions = defu(options, nuxt.options.runtimeConfig.nuxtUsers || {}, defaultOptions);
70
94
  nuxt.options.runtimeConfig.nuxtUsers = {
@@ -75,19 +99,37 @@ const module = defineNuxtModule({
75
99
  passwordResetTokens: options.tables?.passwordResetTokens || "password_reset_tokens"
76
100
  }
77
101
  };
102
+ let hasUsersTable = false;
103
+ let hasPersonalAccessTokensTable = false;
104
+ let hasPasswordResetTokensTable = false;
105
+ if (!options.skipDatabaseChecks) {
106
+ hasUsersTable = await runWithTimeout(() => checkUsersTableExists(options)) || false;
107
+ if (!hasUsersTable) {
108
+ console.warn("[Nuxt Users DB] \u26A0\uFE0F Users table does not exist, you should run the migration script to create it by running: npx nuxt-users-migrate");
109
+ }
110
+ hasPersonalAccessTokensTable = await runWithTimeout(() => checkPersonalAccessTokensTableExists(options)) || false;
111
+ if (!hasPersonalAccessTokensTable) {
112
+ console.warn("[Nuxt Users DB] \u26A0\uFE0F Personal access tokens table does not exist, you should run the migration script to create it by running: npx nuxt-users-migrate");
113
+ }
114
+ hasPasswordResetTokensTable = await runWithTimeout(() => checkPasswordResetTokensTableExists(options)) || false;
115
+ if (!hasPasswordResetTokensTable) {
116
+ console.warn("[Nuxt Users DB] \u26A0\uFE0F Password reset tokens table does not exist, you should run the migration script to create it by running: npx nuxt-users-migrate");
117
+ }
118
+ const hasUsers = await runWithTimeout(() => hasAnyUsers(options)) || false;
119
+ if (!hasUsers) {
120
+ console.warn('[Nuxt Users DB] \u26A0\uFE0F No users found! Create a default user by running: npx nuxt-users-create-user rrd@example.com "John Doe" mypassword123');
121
+ }
122
+ } else {
123
+ console.log("[Nuxt Users DB] \u2139\uFE0F Skipping database checks during setup");
124
+ }
78
125
  nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
79
126
  nuxt.options.runtimeConfig.public.nuxtUsers = {
80
127
  tables: {
81
128
  users: hasUsersTable,
82
129
  personalAccessTokens: hasPersonalAccessTokensTable,
83
130
  passwordResetTokens: hasPasswordResetTokensTable
84
- // Use stored value
85
131
  }
86
132
  };
87
- const hasUsers = await hasAnyUsers(options);
88
- if (!hasUsers) {
89
- console.warn('[Nuxt Users DB] \u26A0\uFE0F No users found! Create a default user by running: yarn db:create-user rrd@example.com "John Doe" mypassword123');
90
- }
91
133
  addServerHandler({
92
134
  route: "/api/login",
93
135
  method: "post",
@@ -1,2 +1,2 @@
1
- declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
1
+ declare const _default: import("vue").DefineComponent<{}, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
2
  export default _default;
@@ -1,6 +1,6 @@
1
1
  import type { LoginFormData, LoginFormProps, User } from '../../types.js';
2
2
  declare var __VLS_9: {}, __VLS_11: {}, __VLS_18: {}, __VLS_25: {}, __VLS_32: {}, __VLS_39: {}, __VLS_41: {
3
- error: any;
3
+ error: string;
4
4
  };
5
5
  type __VLS_Slots = {} & {
6
6
  header?: (props: typeof __VLS_9) => any;
@@ -17,7 +17,7 @@ type __VLS_Slots = {} & {
17
17
  } & {
18
18
  'error-message'?: (props: typeof __VLS_41) => any;
19
19
  };
20
- declare const __VLS_component: import("vue").DefineComponent<LoginFormProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
20
+ declare const __VLS_component: import("vue").DefineComponent<LoginFormProps, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
21
21
  error: (error: string) => any;
22
22
  submit: (data: LoginFormData) => any;
23
23
  success: (user: User) => any;
@@ -25,7 +25,10 @@ declare const __VLS_component: import("vue").DefineComponent<LoginFormProps, {},
25
25
  onError?: ((error: string) => any) | undefined;
26
26
  onSubmit?: ((data: LoginFormData) => any) | undefined;
27
27
  onSuccess?: ((user: User) => any) | undefined;
28
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
28
+ }>, {
29
+ apiEndpoint: string;
30
+ redirectTo: string;
31
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
29
32
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
30
33
  export default _default;
31
34
  type __VLS_WithSlots<T, S> = T & {
@@ -1,2 +1,2 @@
1
- declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
1
+ declare const _default: import("vue").DefineComponent<{}, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
2
  export default _default;
@@ -1,6 +1,5 @@
1
1
  import { createError, defineEventHandler, readBody, setCookie } from "h3";
2
- import { getConnector } from "../utils/db.js";
3
- import { createDatabase } from "db0";
2
+ import { useDb } from "../utils/db.js";
4
3
  import bcrypt from "bcrypt";
5
4
  import crypto from "node:crypto";
6
5
  import { useRuntimeConfig } from "#imports";
@@ -15,9 +14,7 @@ export default defineEventHandler(async (event) => {
15
14
  }
16
15
  const { nuxtUsers } = useRuntimeConfig();
17
16
  const options = nuxtUsers;
18
- const connectorName = options.connector.name;
19
- const connector = await getConnector(connectorName);
20
- const db = createDatabase(connector(options.connector.options));
17
+ const db = await useDb(options);
21
18
  const usersTable = options.tables.users;
22
19
  const personalAccessTokensTable = options.tables.personalAccessTokens;
23
20
  const userResult = await db.sql`SELECT * FROM {${usersTable}} WHERE email = ${email}`;
@@ -2,8 +2,7 @@ import { createTransport } from "nodemailer";
2
2
  import crypto from "node:crypto";
3
3
  import bcrypt from "bcrypt";
4
4
  import { findUserByEmail, updateUserPassword } from "../utils/user.js";
5
- import { getConnector } from "../utils/db.js";
6
- import { createDatabase } from "db0";
5
+ import { useDb } from "../utils/db.js";
7
6
  const TOKEN_EXPIRATION_HOURS = 1;
8
7
  export const sendPasswordResetLink = async (email, options) => {
9
8
  const user = await findUserByEmail(email, options);
@@ -11,9 +10,7 @@ export const sendPasswordResetLink = async (email, options) => {
11
10
  console.log(`Password reset requested for non-existent email: ${email}`);
12
11
  return;
13
12
  }
14
- const connectorName = options.connector.name;
15
- const connector = await getConnector(connectorName);
16
- const db = createDatabase(connector(options.connector.options));
13
+ const db = await useDb(options);
17
14
  const token = crypto.randomBytes(32).toString("hex");
18
15
  const hashedToken = await bcrypt.hash(token, 10);
19
16
  const passwordResetTokensTable = options.tables.passwordResetTokens;
@@ -48,9 +45,7 @@ export const sendPasswordResetLink = async (email, options) => {
48
45
  }
49
46
  };
50
47
  export const resetPassword = async (token, email, newPassword, options) => {
51
- const connectorName = options.connector.name;
52
- const connector = await getConnector(connectorName);
53
- const db = createDatabase(connector(options.connector.options));
48
+ const db = await useDb(options);
54
49
  const passwordResetTokensTable = options.tables.passwordResetTokens;
55
50
  const tokenRecords = await db.sql`
56
51
  SELECT * FROM {${passwordResetTokensTable}}
@@ -98,9 +93,7 @@ export const resetPassword = async (token, email, newPassword, options) => {
98
93
  return true;
99
94
  };
100
95
  export const deleteExpiredPasswordResetTokens = async (options) => {
101
- const connectorName = options.connector.name;
102
- const connector = await getConnector(connectorName);
103
- const db = createDatabase(connector(options.connector.options));
96
+ const db = await useDb(options);
104
97
  const passwordResetTokensTable = options.tables.passwordResetTokens;
105
98
  const expirationDate = /* @__PURE__ */ new Date();
106
99
  expirationDate.setHours(expirationDate.getHours() - TOKEN_EXPIRATION_HOURS);
@@ -1,9 +1,7 @@
1
- import { createDatabase } from "db0";
2
- import { getConnector } from "./db.js";
1
+ import { useDb } from "./db.js";
3
2
  export const createMigrationsTable = async (options) => {
4
3
  const connectorName = options.connector.name;
5
- const connector = await getConnector(connectorName);
6
- const db = createDatabase(connector(options.connector.options));
4
+ const db = await useDb(options);
7
5
  const tableName = "migrations";
8
6
  console.log(`[DB:Create Migrations ${connectorName} Table] Creating ${tableName} table with ${connectorName} connector...`);
9
7
  if (connectorName === "sqlite") {
@@ -1,9 +1,7 @@
1
- import { createDatabase } from "db0";
2
- import { getConnector } from "./db.js";
1
+ import { useDb } from "./db.js";
3
2
  export const createPasswordResetTokensTable = async (options) => {
4
3
  const connectorName = options.connector.name;
5
- const connector = await getConnector(connectorName);
6
- const db = createDatabase(connector(options.connector.options));
4
+ const db = await useDb(options);
7
5
  const tableName = options.tables.passwordResetTokens;
8
6
  console.log(`[DB:Create Password Reset Tokens ${connectorName} Table] Creating ${tableName} table with ${connectorName} connector...`);
9
7
  if (connectorName === "sqlite") {
@@ -1,9 +1,7 @@
1
- import { createDatabase } from "db0";
2
- import { getConnector } from "./db.js";
1
+ import { useDb } from "./db.js";
3
2
  export const createPersonalAccessTokensTable = async (options) => {
4
3
  const connectorName = options.connector.name;
5
- const connector = await getConnector(connectorName);
6
- const db = createDatabase(connector(options.connector.options));
4
+ const db = await useDb(options);
7
5
  const tableName = options.tables.personalAccessTokens;
8
6
  console.log(`[DB:Create Personal Access Tokens ${connectorName} Table] Creating ${tableName} table with ${connectorName} connector...`);
9
7
  if (connectorName === "sqlite") {
@@ -1,9 +1,7 @@
1
- import { createDatabase } from "db0";
2
- import { getConnector } from "./db.js";
1
+ import { useDb } from "./db.js";
3
2
  export const createUsersTable = async (options) => {
4
3
  const connectorName = options.connector.name;
5
- const connector = await getConnector(connectorName);
6
- const db = createDatabase(connector(options.connector.options));
4
+ const db = await useDb(options);
7
5
  const tableName = options.tables.users;
8
6
  console.log(`[DB:Create ${connectorName} Users Table] Creating ${tableName}...`);
9
7
  if (connectorName === "sqlite") {
@@ -1,5 +1,6 @@
1
1
  import type { ModuleOptions } from '../../../types.js';
2
2
  export declare const getConnector: (name: string) => Promise<typeof import("db0/connectors/mysql2").default | typeof import("db0/connectors/postgresql").default | typeof import("db0/connectors/better-sqlite3").default>;
3
+ export declare const useDb: (options: ModuleOptions) => Promise<import("db0").Database<import("db0").Connector<Database.Database>>>;
3
4
  export declare const checkPasswordResetTokensTableExists: (options: ModuleOptions) => Promise<boolean>;
4
5
  export declare const checkUsersTableExists: (options: ModuleOptions) => Promise<boolean>;
5
6
  export declare const checkPersonalAccessTokensTableExists: (options: ModuleOptions) => Promise<boolean>;
@@ -11,10 +11,26 @@ export const getConnector = async (name) => {
11
11
  throw new Error(`Unsupported database connector: ${name}`);
12
12
  }
13
13
  };
14
- const useDb = async (options) => {
14
+ export const useDb = async (options) => {
15
15
  const connectorName = options.connector.name;
16
16
  const connector = await getConnector(connectorName);
17
- return createDatabase(connector(options.connector.options));
17
+ const connectorOptions = { ...options.connector.options };
18
+ if (connectorName !== "sqlite") {
19
+ delete connectorOptions.path;
20
+ }
21
+ if (connectorName === "mysql") {
22
+ connectorOptions.connectTimeout = 5e3;
23
+ connectorOptions.acquireTimeout = 5e3;
24
+ }
25
+ if (connectorName === "postgresql") {
26
+ connectorOptions.connectionTimeoutMillis = 5e3;
27
+ }
28
+ try {
29
+ return createDatabase(connector(connectorOptions));
30
+ } catch (error) {
31
+ console.warn(`[Nuxt Users DB] \u26A0\uFE0F Failed to connect to ${connectorName} database:`, error instanceof Error ? error.message : "Unknown error");
32
+ throw error;
33
+ }
18
34
  };
19
35
  export const checkPasswordResetTokensTableExists = async (options) => {
20
36
  return await checkTableExists(options, options.tables.passwordResetTokens);
@@ -26,8 +42,8 @@ export const checkPersonalAccessTokensTableExists = async (options) => {
26
42
  return await checkTableExists(options, options.tables.personalAccessTokens);
27
43
  };
28
44
  export const checkTableExists = async (options, tableName) => {
29
- const db = await useDb(options);
30
45
  try {
46
+ const db = await useDb(options);
31
47
  await db.sql`SELECT 1 FROM {${tableName}} LIMIT 1`;
32
48
  return true;
33
49
  } catch {
@@ -35,8 +51,8 @@ export const checkTableExists = async (options, tableName) => {
35
51
  }
36
52
  };
37
53
  export const hasAnyUsers = async (options) => {
38
- const db = await useDb(options);
39
54
  try {
55
+ const db = await useDb(options);
40
56
  const users = await db.sql`SELECT COUNT(*) as count FROM {${options.tables.users}}`;
41
57
  return users.rows?.[0]?.count > 0;
42
58
  } catch {
@@ -1,5 +1,4 @@
1
- import { createDatabase } from "db0";
2
- import { getConnector } from "./db.js";
1
+ import { useDb } from "./db.js";
3
2
  import { createUsersTable } from "./create-users-table.js";
4
3
  import { createPersonalAccessTokensTable } from "./create-personal-access-tokens-table.js";
5
4
  import { createPasswordResetTokensTable } from "./create-password-reset-tokens-table.js";
@@ -23,9 +22,7 @@ const migrations = [
23
22
  }
24
23
  ];
25
24
  export const getAppliedMigrations = async (options) => {
26
- const connectorName = options.connector.name;
27
- const connector = await getConnector(connectorName);
28
- const db = createDatabase(connector(options.connector.options));
25
+ const db = await useDb(options);
29
26
  try {
30
27
  const result = await db.sql`SELECT name FROM migrations ORDER BY id`;
31
28
  return result.rows.map((row) => row.name);
@@ -34,9 +31,7 @@ export const getAppliedMigrations = async (options) => {
34
31
  }
35
32
  };
36
33
  export const markMigrationAsApplied = async (options, migrationName) => {
37
- const connectorName = options.connector.name;
38
- const connector = await getConnector(connectorName);
39
- const db = createDatabase(connector(options.connector.options));
34
+ const db = await useDb(options);
40
35
  await db.sql`INSERT INTO migrations (name) VALUES (${migrationName})`;
41
36
  };
42
37
  export const runMigrations = async (options) => {
@@ -1,10 +1,7 @@
1
- import { createDatabase } from "db0";
2
- import { getConnector } from "./db.js";
1
+ import { useDb } from "./db.js";
3
2
  import bcrypt from "bcrypt";
4
3
  export const createUser = async (userData, options) => {
5
- const connectorName = options.connector.name;
6
- const connector = await getConnector(connectorName);
7
- const db = createDatabase(connector(options.connector.options));
4
+ const db = await useDb(options);
8
5
  const usersTable = options.tables.users;
9
6
  const hashedPassword = await bcrypt.hash(userData.password, 10);
10
7
  await db.sql`
@@ -25,9 +22,7 @@ export const createUser = async (userData, options) => {
25
22
  };
26
23
  };
27
24
  export const findUserByEmail = async (email, options) => {
28
- const connectorName = options.connector.name;
29
- const connector = await getConnector(connectorName);
30
- const db = createDatabase(connector(options.connector.options));
25
+ const db = await useDb(options);
31
26
  const usersTable = options.tables.users;
32
27
  const result = await db.sql`SELECT * FROM {${usersTable}} WHERE email = ${email}`;
33
28
  if (result.rows.length === 0) {
@@ -44,9 +39,7 @@ export const findUserByEmail = async (email, options) => {
44
39
  };
45
40
  };
46
41
  export const updateUserPassword = async (email, newPassword, options) => {
47
- const connectorName = options.connector.name;
48
- const connector = await getConnector(connectorName);
49
- const db = createDatabase(connector(options.connector.options));
42
+ const db = await useDb(options);
50
43
  const usersTable = options.tables.users;
51
44
  const hashedPassword = await bcrypt.hash(newPassword, 10);
52
45
  await db.sql`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Nuxt users module",
5
5
  "repository": "rrd108/nuxt-users",
6
6
  "license": "MIT",
@@ -20,10 +20,15 @@
20
20
  }
21
21
  },
22
22
  "files": [
23
- "dist"
23
+ "dist",
24
+ "src/cli",
25
+ "data"
24
26
  ],
27
+ "bin": {
28
+ "nuxt-users": "./src/cli/main.ts"
29
+ },
25
30
  "scripts": {
26
- "prepack": "nuxt-module-build build",
31
+ "prepack": "npm run db:create-empty-sqlite && nuxt-module-build build",
27
32
  "predev": "node -v | grep -q 'v22' || (echo 'Please use Node.js v22 for deployment' && exit 1)",
28
33
  "dev": "npm run dev:prepare && nuxi dev playground",
29
34
  "dev:build": "nuxi build playground",
@@ -45,9 +50,11 @@
45
50
  "db:create-password-reset-tokens-table": "tsx src/cli/create-password-reset-tokens-table.ts",
46
51
  "db:create-migrations-table": "tsx src/cli/create-migrations-table.ts",
47
52
  "db:migrate": "tsx src/cli/migrate.ts",
53
+ "db:create-empty-sqlite": "node scripts/create-empty-sqlite.js",
48
54
  "docs:dev": "vitepress dev docs",
49
55
  "docs:build": "vitepress build docs",
50
- "docs:preview": "vitepress preview docs"
56
+ "docs:preview": "vitepress preview docs",
57
+ "docs:deploy": "npm run docs:build && ./scripts/deploy-docs.sh"
51
58
  },
52
59
  "dependencies": {
53
60
  "@nuxt/kit": "^3.17.6",
@@ -55,11 +62,11 @@
55
62
  "@types/pg": "^8.15.4",
56
63
  "bcrypt": "^6.0.0",
57
64
  "better-sqlite3": "^12.2.0",
65
+ "citty": "^0.1.6",
58
66
  "defu": "^6.1.4",
59
67
  "mysql2": "^3.14.2",
60
68
  "nodemailer": "^7.0.5",
61
- "pg": "^8.16.3",
62
- "vitepress": "^1.6.3"
69
+ "pg": "^8.16.3"
63
70
  },
64
71
  "devDependencies": {
65
72
  "@formkit/nuxt": "^1.6.9",
@@ -73,9 +80,10 @@
73
80
  "changelogen": "^0.6.2",
74
81
  "eslint": "^9.30.1",
75
82
  "nuxt": "^3.17.6",
76
- "ts-node": "^10.9.2",
77
83
  "tsx": "^4.20.3",
78
84
  "typescript": "~5.8.3",
85
+ "unbuild": "^2.0.0",
86
+ "vitepress": "^1.6.3",
79
87
  "vitest": "^3.2.4",
80
88
  "vue-tsc": "^3.0.1"
81
89
  }
@@ -0,0 +1,24 @@
1
+ import { defineCommand } from 'citty'
2
+ import { createMigrationsTable } from '../runtime/server/utils/create-migrations-table'
3
+ import { getOptionsFromEnv } from './utils'
4
+
5
+ export default defineCommand({
6
+ meta: {
7
+ name: 'create-migrations-table',
8
+ description: 'Create the migrations table in the database'
9
+ },
10
+ async run() {
11
+ console.log('[Nuxt Users] Creating migrations table...')
12
+
13
+ const options = getOptionsFromEnv()
14
+
15
+ try {
16
+ await createMigrationsTable(options)
17
+ console.log('[Nuxt Users] Migrations table created successfully!')
18
+ }
19
+ catch (error) {
20
+ console.error('[DB:Create Migrations Table] Error:', error)
21
+ process.exit(1)
22
+ }
23
+ }
24
+ })
@@ -0,0 +1,24 @@
1
+ import { defineCommand } from 'citty'
2
+ import { createPasswordResetTokensTable } from '../runtime/server/utils/create-password-reset-tokens-table'
3
+ import { getOptionsFromEnv } from './utils'
4
+
5
+ export default defineCommand({
6
+ meta: {
7
+ name: 'create-password-reset-tokens-table',
8
+ description: 'Create the password reset tokens table in the database'
9
+ },
10
+ async run() {
11
+ console.log('[Nuxt Users] Creating password reset tokens table...')
12
+
13
+ const options = getOptionsFromEnv()
14
+
15
+ try {
16
+ await createPasswordResetTokensTable(options)
17
+ console.log('[Nuxt Users] Password reset tokens table created successfully!')
18
+ }
19
+ catch (error) {
20
+ console.error('[DB:Create Password Reset Tokens Table] Error:', error)
21
+ process.exit(1)
22
+ }
23
+ }
24
+ })
@@ -0,0 +1,24 @@
1
+ import { defineCommand } from 'citty'
2
+ import { createPersonalAccessTokensTable } from '../runtime/server/utils/create-personal-access-tokens-table'
3
+ import { getOptionsFromEnv } from './utils'
4
+
5
+ export default defineCommand({
6
+ meta: {
7
+ name: 'create-personal-access-tokens-table',
8
+ description: 'Create the personal access tokens table in the database'
9
+ },
10
+ async run() {
11
+ console.log('[Nuxt Users] Creating personal access tokens table...')
12
+
13
+ const options = getOptionsFromEnv()
14
+
15
+ try {
16
+ await createPersonalAccessTokensTable(options)
17
+ console.log('[Nuxt Users] Personal access tokens table created successfully!')
18
+ }
19
+ catch (error) {
20
+ console.error('[DB:Create Personal Access Tokens Table] Error:', error)
21
+ process.exit(1)
22
+ }
23
+ }
24
+ })
@@ -0,0 +1,40 @@
1
+ import { defineCommand } from 'citty'
2
+ import { createUser } from '../runtime/server/utils/user'
3
+ import { getOptionsFromEnv } from './utils'
4
+
5
+ export default defineCommand({
6
+ meta: {
7
+ name: 'create-user',
8
+ description: 'Create a new user in the database'
9
+ },
10
+ args: {
11
+ email: {
12
+ type: 'string',
13
+ description: 'User email address',
14
+ required: true
15
+ },
16
+ name: {
17
+ type: 'string',
18
+ description: 'User full name',
19
+ required: true
20
+ },
21
+ password: {
22
+ type: 'string',
23
+ description: 'User password',
24
+ required: true
25
+ }
26
+ },
27
+ async run({ args }) {
28
+ const { email, name, password } = args
29
+ const options = getOptionsFromEnv()
30
+
31
+ try {
32
+ const user = await createUser({ email, name, password }, options)
33
+ console.log(`[DB:Create User] User created successfully: ${user.email}`)
34
+ }
35
+ catch (error) {
36
+ console.error('[DB:Create User] Error:', error)
37
+ process.exit(1)
38
+ }
39
+ }
40
+ })
@@ -0,0 +1,24 @@
1
+ import { defineCommand } from 'citty'
2
+ import { createUsersTable } from '../runtime/server/utils/create-users-table'
3
+ import { getOptionsFromEnv } from './utils'
4
+
5
+ export default defineCommand({
6
+ meta: {
7
+ name: 'create-users-table',
8
+ description: 'Create the users table in the database'
9
+ },
10
+ async run() {
11
+ console.log('[Nuxt Users] Creating users table...')
12
+
13
+ const options = getOptionsFromEnv()
14
+
15
+ try {
16
+ await createUsersTable(options)
17
+ console.log('[Nuxt Users] Users table created successfully!')
18
+ }
19
+ catch (error) {
20
+ console.error('[DB:Create Users Table] Error:', error)
21
+ process.exit(1)
22
+ }
23
+ }
24
+ })
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ import { defineCommand, runMain } from 'citty'
3
+ import migrate from './migrate'
4
+ import createUser from './create-user'
5
+ import createUsersTable from './create-users-table'
6
+ import createPersonalAccessTokensTable from './create-personal-access-tokens-table'
7
+ import createPasswordResetTokensTable from './create-password-reset-tokens-table'
8
+ import createMigrationsTable from './create-migrations-table'
9
+ import projectInfo from './project-info'
10
+
11
+ const main = defineCommand({
12
+ meta: {
13
+ name: 'nuxt-users',
14
+ description: 'CLI for Nuxt Users Module - Manage users, migrations, and database operations',
15
+ version: '1.1.0'
16
+ },
17
+ subCommands: {
18
+ migrate,
19
+ 'create-user': createUser,
20
+ 'create-users-table': createUsersTable,
21
+ 'create-personal-access-tokens-table': createPersonalAccessTokensTable,
22
+ 'create-password-reset-tokens-table': createPasswordResetTokensTable,
23
+ 'create-migrations-table': createMigrationsTable,
24
+ 'project-info': projectInfo
25
+ }
26
+ })
27
+
28
+ runMain(main)
@@ -0,0 +1,24 @@
1
+ import { defineCommand } from 'citty'
2
+ import { runMigrations } from '../runtime/server/utils/migrate'
3
+ import { getOptionsFromEnv } from './utils'
4
+
5
+ export default defineCommand({
6
+ meta: {
7
+ name: 'migrate',
8
+ description: 'Run database migrations for the Nuxt Users module'
9
+ },
10
+ async run() {
11
+ console.log('[Nuxt Users] Starting migration system...')
12
+
13
+ const options = getOptionsFromEnv()
14
+
15
+ try {
16
+ await runMigrations(options)
17
+ console.log('[Nuxt Users] Migration completed successfully!')
18
+ }
19
+ catch (error) {
20
+ console.error('[Nuxt Users] Migration failed:', error)
21
+ process.exit(1)
22
+ }
23
+ }
24
+ })
@@ -0,0 +1,45 @@
1
+ import { defineCommand } from 'citty'
2
+ import { loadNuxt } from '@nuxt/kit'
3
+
4
+ export default defineCommand({
5
+ meta: {
6
+ name: 'project-info',
7
+ description: 'Get information about the Nuxt project and module configuration'
8
+ },
9
+ async run() {
10
+ try {
11
+ console.log('[Nuxt Users] Loading Nuxt project...')
12
+ const nuxt = await loadNuxt({ cwd: process.cwd() })
13
+
14
+ console.log('✅ Nuxt project loaded successfully!')
15
+ console.log('📁 Source directory:', nuxt.options.srcDir)
16
+ console.log('🌐 Base URL:', nuxt.options.app?.baseURL || '/')
17
+
18
+ // Check if nuxt-users module is configured
19
+ const nuxtUsersConfig = nuxt.options.runtimeConfig?.nuxtUsers
20
+ if (nuxtUsersConfig) {
21
+ console.log('🔧 Nuxt Users module configuration:')
22
+ console.log(' Database connector:', nuxtUsersConfig.connector?.name)
23
+ console.log(' Users table:', nuxtUsersConfig.tables?.users)
24
+ console.log(' Personal access tokens table:', nuxtUsersConfig.tables?.personalAccessTokens)
25
+ console.log(' Password reset tokens table:', nuxtUsersConfig.tables?.passwordResetTokens)
26
+ }
27
+ else {
28
+ console.log('⚠️ Nuxt Users module not configured in runtime config')
29
+ }
30
+
31
+ // Check public runtime config
32
+ const publicConfig = nuxt.options.runtimeConfig?.public?.nuxtUsers
33
+ if (publicConfig) {
34
+ console.log('🌍 Public runtime config:')
35
+ console.log(' Users table exists:', publicConfig.tables?.users)
36
+ console.log(' Personal access tokens table exists:', publicConfig.tables?.personalAccessTokens)
37
+ console.log(' Password reset tokens table exists:', publicConfig.tables?.passwordResetTokens)
38
+ }
39
+ }
40
+ catch (error) {
41
+ console.error('❌ Could not load Nuxt project:', error)
42
+ process.exit(1)
43
+ }
44
+ }
45
+ })
@@ -0,0 +1,70 @@
1
+ import type { ModuleOptions, DatabaseConfig } from '../types'
2
+
3
+ export const defaultOptions: ModuleOptions = {
4
+ connector: {
5
+ name: 'sqlite',
6
+ options: {
7
+ path: './data/default.sqlite3',
8
+ },
9
+ },
10
+ tables: {
11
+ users: 'users',
12
+ personalAccessTokens: 'personal_access_tokens',
13
+ passwordResetTokens: 'password_reset_tokens',
14
+ },
15
+ mailer: {
16
+ host: 'smtp.ethereal.email',
17
+ port: 587,
18
+ secure: false,
19
+ auth: {
20
+ user: 'user@ethereal.email',
21
+ pass: 'password',
22
+ },
23
+ defaults: {
24
+ from: '"Nuxt Users Module" <noreply@example.com>',
25
+ },
26
+ },
27
+ passwordResetBaseUrl: 'http://localhost:3000',
28
+ }
29
+
30
+ export const getOptionsFromEnv = (): ModuleOptions => {
31
+ const connectorName = process.env.DB_CONNECTOR || 'sqlite'
32
+
33
+ let connectorOptions: DatabaseConfig
34
+
35
+ switch (connectorName) {
36
+ case 'sqlite':
37
+ connectorOptions = {
38
+ path: process.env.DB_PATH || './data/default.sqlite3',
39
+ }
40
+ break
41
+ case 'mysql':
42
+ connectorOptions = {
43
+ host: process.env.DB_HOST || 'localhost',
44
+ port: Number.parseInt(process.env.DB_PORT || '3306'),
45
+ user: process.env.DB_USER || 'root',
46
+ password: process.env.DB_PASSWORD || '',
47
+ database: process.env.DB_NAME || 'nuxt_users',
48
+ }
49
+ break
50
+ case 'postgresql':
51
+ connectorOptions = {
52
+ host: process.env.DB_HOST || 'localhost',
53
+ port: Number.parseInt(process.env.DB_PORT || '5432'),
54
+ user: process.env.DB_USER || 'postgres',
55
+ password: process.env.DB_PASSWORD || '',
56
+ database: process.env.DB_NAME || 'nuxt_users',
57
+ }
58
+ break
59
+ default:
60
+ throw new Error(`Unsupported database connector: ${connectorName}`)
61
+ }
62
+
63
+ return {
64
+ ...defaultOptions,
65
+ connector: {
66
+ name: connectorName as 'sqlite' | 'mysql' | 'postgresql',
67
+ options: connectorOptions,
68
+ },
69
+ }
70
+ }