@xcelsior/auth-adapter-knex 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,22 +1,22 @@
1
1
 
2
- > @xcelsior/auth-adapter-knex@1.0.0 build /home/circleci/repo/packages/services/auth-adapter-knex
2
+ > @xcelsior/auth-adapter-knex@1.0.0 build /Users/tuannguyen/Work/xcelsior-packages/packages/services/auth-adapter-knex
3
3
  > tsup && tsc --noEmit
4
4
 
5
5
  CLI Building entry: src/index.ts
6
6
  CLI Using tsconfig: tsconfig.json
7
7
  CLI tsup v8.5.1
8
- CLI Using tsup config: /home/circleci/repo/packages/services/auth-adapter-knex/tsup.config.ts
8
+ CLI Using tsup config: /Users/tuannguyen/Work/xcelsior-packages/packages/services/auth-adapter-knex/tsup.config.ts
9
9
  CLI Target: es2020
10
10
  CLI Cleaning output folder
11
11
  CJS Build start
12
12
  ESM Build start
13
13
  CJS dist/index.js 11.45 KB
14
14
  CJS dist/index.js.map 22.55 KB
15
- CJS ⚡️ Build success in 159ms
15
+ CJS ⚡️ Build success in 119ms
16
16
  ESM dist/index.mjs 10.31 KB
17
17
  ESM dist/index.mjs.map 22.33 KB
18
- ESM ⚡️ Build success in 161ms
19
- DTS Build start
20
- DTS ⚡️ Build success in 20081ms
21
- DTS dist/index.d.ts 3.13 KB
22
- DTS dist/index.d.mts 3.13 KB
18
+ ESM ⚡️ Build success in 134ms
19
+ DTS Build start
20
+ DTS ⚡️ Build success in 2709ms
21
+ DTS dist/index.d.ts 3.13 KB
22
+ DTS dist/index.d.mts 3.13 KB
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Knex } from 'knex';
2
- import { IStorageProvider, User, UserFilter, FindUsersOptions, FindUsersResult, Session } from '@xcelsior/auth';
2
+ import { IStorageProvider, CreateUserInput, User, UserId, UserFilter, FindUsersOptions, FindUsersResult, CreateSessionInput, Session, SessionId } from '@xcelsior/auth';
3
3
 
4
4
  interface KnexConfig {
5
5
  /** Pre-configured Knex instance */
@@ -19,20 +19,20 @@ declare class KnexStorageProvider implements IStorageProvider {
19
19
  constructor(config: KnexConfig);
20
20
  private get table();
21
21
  private get sessionsTable();
22
- createUser(user: User): Promise<void>;
23
- getUserById(id: string): Promise<User | null>;
22
+ createUser(user: CreateUserInput): Promise<User>;
23
+ getUserById(id: UserId): Promise<User | null>;
24
24
  getUserByEmail(email: string): Promise<User | null>;
25
25
  getUserByResetPasswordToken(resetPasswordToken: string): Promise<User | null>;
26
26
  getUserByVerifyEmailToken(verifyEmailToken: string): Promise<User | null>;
27
- updateUser(id: string, updates: Partial<User>): Promise<void>;
28
- deleteUser(id: string): Promise<void>;
27
+ updateUser(id: UserId, updates: Partial<User>): Promise<void>;
28
+ deleteUser(id: UserId): Promise<void>;
29
29
  findUsers(filter: UserFilter, options?: FindUsersOptions): Promise<FindUsersResult>;
30
- createSession(session: Session): Promise<void>;
31
- getSessionById(id: string): Promise<Session | null>;
32
- getSessionsByUserId(userId: string): Promise<Session[]>;
33
- updateSession(id: string, updates: Partial<Session>): Promise<void>;
34
- deleteSession(id: string): Promise<void>;
35
- deleteAllUserSessions(userId: string): Promise<void>;
30
+ createSession(session: CreateSessionInput): Promise<Session>;
31
+ getSessionById(id: SessionId): Promise<Session | null>;
32
+ getSessionsByUserId(userId: UserId): Promise<Session[]>;
33
+ updateSession(id: SessionId, updates: Partial<Session>): Promise<void>;
34
+ deleteSession(id: SessionId): Promise<void>;
35
+ deleteAllUserSessions(userId: UserId): Promise<void>;
36
36
  deleteExpiredSessions(): Promise<void>;
37
37
  /**
38
38
  * Convert User object to database row format (snake_case)
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Knex } from 'knex';
2
- import { IStorageProvider, User, UserFilter, FindUsersOptions, FindUsersResult, Session } from '@xcelsior/auth';
2
+ import { IStorageProvider, CreateUserInput, User, UserId, UserFilter, FindUsersOptions, FindUsersResult, CreateSessionInput, Session, SessionId } from '@xcelsior/auth';
3
3
 
4
4
  interface KnexConfig {
5
5
  /** Pre-configured Knex instance */
@@ -19,20 +19,20 @@ declare class KnexStorageProvider implements IStorageProvider {
19
19
  constructor(config: KnexConfig);
20
20
  private get table();
21
21
  private get sessionsTable();
22
- createUser(user: User): Promise<void>;
23
- getUserById(id: string): Promise<User | null>;
22
+ createUser(user: CreateUserInput): Promise<User>;
23
+ getUserById(id: UserId): Promise<User | null>;
24
24
  getUserByEmail(email: string): Promise<User | null>;
25
25
  getUserByResetPasswordToken(resetPasswordToken: string): Promise<User | null>;
26
26
  getUserByVerifyEmailToken(verifyEmailToken: string): Promise<User | null>;
27
- updateUser(id: string, updates: Partial<User>): Promise<void>;
28
- deleteUser(id: string): Promise<void>;
27
+ updateUser(id: UserId, updates: Partial<User>): Promise<void>;
28
+ deleteUser(id: UserId): Promise<void>;
29
29
  findUsers(filter: UserFilter, options?: FindUsersOptions): Promise<FindUsersResult>;
30
- createSession(session: Session): Promise<void>;
31
- getSessionById(id: string): Promise<Session | null>;
32
- getSessionsByUserId(userId: string): Promise<Session[]>;
33
- updateSession(id: string, updates: Partial<Session>): Promise<void>;
34
- deleteSession(id: string): Promise<void>;
35
- deleteAllUserSessions(userId: string): Promise<void>;
30
+ createSession(session: CreateSessionInput): Promise<Session>;
31
+ getSessionById(id: SessionId): Promise<Session | null>;
32
+ getSessionsByUserId(userId: UserId): Promise<Session[]>;
33
+ updateSession(id: SessionId, updates: Partial<Session>): Promise<void>;
34
+ deleteSession(id: SessionId): Promise<void>;
35
+ deleteAllUserSessions(userId: UserId): Promise<void>;
36
36
  deleteExpiredSessions(): Promise<void>;
37
37
  /**
38
38
  * Convert User object to database row format (snake_case)
package/dist/index.js CHANGED
@@ -50,7 +50,10 @@ var KnexStorageProvider = class {
50
50
  }
51
51
  // ==================== User Methods ====================
52
52
  async createUser(user) {
53
- await this.table.insert(this.toDbUser(user));
53
+ const dbUser = this.toDbUser(user);
54
+ const [insertedId] = await this.table.insert(dbUser);
55
+ const finalId = user.id ?? insertedId;
56
+ return { ...user, id: finalId };
54
57
  }
55
58
  async getUserById(id) {
56
59
  const row = await this.table.where({ id }).first();
@@ -127,7 +130,10 @@ var KnexStorageProvider = class {
127
130
  }
128
131
  // ==================== Session Methods ====================
129
132
  async createSession(session) {
130
- await this.sessionsTable.insert(this.toDbSession(session));
133
+ const dbSession = this.toDbSession(session);
134
+ const [insertedId] = await this.sessionsTable.insert(dbSession);
135
+ const finalId = session.id ?? insertedId;
136
+ return { ...session, id: finalId };
131
137
  }
132
138
  async getSessionById(id) {
133
139
  const row = await this.sessionsTable.where({ id }).first();
@@ -159,20 +165,23 @@ var KnexStorageProvider = class {
159
165
  * Convert User object to database row format (snake_case)
160
166
  */
161
167
  toDbUser(user) {
162
- return {
163
- id: user.id,
168
+ const row = {
164
169
  email: user.email,
165
170
  first_name: user.firstName ?? null,
166
171
  last_name: user.lastName ?? null,
167
172
  password_hash: user.passwordHash,
168
173
  roles: JSON.stringify(user.roles),
169
- is_email_verified: user.isEmailVerified,
174
+ is_email_verified: user.isEmailVerified ? 1 : 0,
170
175
  verification_token: user.verificationToken ?? null,
171
176
  reset_password_token: user.resetPasswordToken ?? null,
172
177
  reset_password_expires: user.resetPasswordExpires ?? null,
173
- created_at: user.createdAt,
174
- updated_at: user.updatedAt
178
+ created_at: user.createdAt ?? Date.now(),
179
+ updated_at: user.updatedAt ?? Date.now()
175
180
  };
181
+ if (user.id !== void 0) {
182
+ row.id = user.id;
183
+ }
184
+ return row;
176
185
  }
177
186
  /**
178
187
  * Convert database row to User object (camelCase)
@@ -214,7 +223,7 @@ var KnexStorageProvider = class {
214
223
  dbUpdates.roles = JSON.stringify(updates.roles);
215
224
  }
216
225
  if (updates.isEmailVerified !== void 0) {
217
- dbUpdates.is_email_verified = updates.isEmailVerified;
226
+ dbUpdates.is_email_verified = updates.isEmailVerified ? 1 : 0;
218
227
  }
219
228
  if ("verificationToken" in updates) {
220
229
  dbUpdates.verification_token = updates.verificationToken ?? null;
@@ -235,8 +244,7 @@ var KnexStorageProvider = class {
235
244
  * Convert Session object to database row format (snake_case)
236
245
  */
237
246
  toDbSession(session) {
238
- return {
239
- id: session.id,
247
+ const row = {
240
248
  user_id: session.userId,
241
249
  refresh_token_hash: session.refreshTokenHash,
242
250
  user_agent: session.userAgent ?? null,
@@ -246,6 +254,10 @@ var KnexStorageProvider = class {
246
254
  last_used_at: session.lastUsedAt,
247
255
  expires_at: session.expiresAt
248
256
  };
257
+ if (session.id !== void 0) {
258
+ row.id = session.id;
259
+ }
260
+ return row;
249
261
  }
250
262
  /**
251
263
  * Convert database row to Session object (camelCase)
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/knex.ts","../src/migration.ts"],"sourcesContent":["export { KnexStorageProvider, type KnexConfig } from './knex';\nexport { createUsersTableMigration, createSessionsTableMigration } from './migration';\n","import type { Knex } from 'knex';\nimport type {\n IStorageProvider,\n User,\n Session,\n UserFilter,\n FindUsersOptions,\n FindUsersResult,\n} from '@xcelsior/auth';\n\nexport interface KnexConfig {\n /** Pre-configured Knex instance */\n knex: Knex;\n /** Table name for users */\n tableName: string;\n /** Table name for sessions */\n sessionsTableName: string;\n /** Optional schema (for PostgreSQL) */\n schema?: string;\n}\n\nexport class KnexStorageProvider implements IStorageProvider {\n private knex: Knex;\n private tableName: string;\n private sessionsTableName: string;\n private schema?: string;\n\n constructor(config: KnexConfig) {\n this.knex = config.knex;\n this.tableName = config.tableName;\n this.sessionsTableName = config.sessionsTableName;\n this.schema = config.schema;\n }\n\n private get table() {\n const query = this.knex(this.tableName);\n if (this.schema) {\n return query.withSchema(this.schema);\n }\n return query;\n }\n\n private get sessionsTable() {\n const query = this.knex(this.sessionsTableName);\n if (this.schema) {\n return query.withSchema(this.schema);\n }\n return query;\n }\n\n // ==================== User Methods ====================\n\n async createUser(user: User): Promise<void> {\n await this.table.insert(this.toDbUser(user));\n }\n\n async getUserById(id: string): Promise<User | null> {\n const row = await this.table.where({ id }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async getUserByEmail(email: string): Promise<User | null> {\n const row = await this.table.where({ email }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async getUserByResetPasswordToken(resetPasswordToken: string): Promise<User | null> {\n const row = await this.table.where({ reset_password_token: resetPasswordToken }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async getUserByVerifyEmailToken(verifyEmailToken: string): Promise<User | null> {\n const row = await this.table.where({ verification_token: verifyEmailToken }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async updateUser(id: string, updates: Partial<User>): Promise<void> {\n const dbUpdates = this.toDbUpdates(updates);\n\n if (Object.keys(dbUpdates).length === 0) {\n return;\n }\n\n await this.table.where({ id }).update(dbUpdates);\n }\n\n async deleteUser(id: string): Promise<void> {\n // Delete all user sessions first\n await this.deleteAllUserSessions(id);\n // Then delete the user\n await this.table.where({ id }).delete();\n }\n\n async findUsers(filter: UserFilter, options: FindUsersOptions = {}): Promise<FindUsersResult> {\n const limit = options.limit ?? 50;\n\n let query = this.table.clone();\n\n // Email exact match\n if (filter.email) {\n query = query.where('email', filter.email);\n }\n\n // Email contains (partial match)\n if (filter.emailContains) {\n query = query.where('email', 'like', `%${filter.emailContains}%`);\n }\n\n // Email verification status\n if (filter.isEmailVerified !== undefined) {\n query = query.where('is_email_verified', filter.isEmailVerified);\n }\n\n // Roles filtering - user must have ALL specified roles\n if (filter.roles && filter.roles.length > 0) {\n for (const role of filter.roles) {\n // Use JSON contains - works for PostgreSQL (jsonb) and MySQL (json)\n // For SQLite, we fall back to LIKE on the JSON string\n query = query.whereRaw(\n this.knex.client.config.client === 'sqlite3' ? `roles LIKE ?` : `roles @> ?`,\n this.knex.client.config.client === 'sqlite3'\n ? [`%\"${role}\"%`]\n : [JSON.stringify([role])]\n );\n }\n }\n\n // HasAnyRole - user must have at least ONE of specified roles\n if (filter.hasAnyRole && filter.hasAnyRole.length > 0) {\n query = query.where((builder: Knex.QueryBuilder) => {\n for (const role of filter.hasAnyRole!) {\n builder.orWhereRaw(\n this.knex.client.config.client === 'sqlite3'\n ? `roles LIKE ?`\n : `roles @> ?`,\n this.knex.client.config.client === 'sqlite3'\n ? [`%\"${role}\"%`]\n : [JSON.stringify([role])]\n );\n }\n });\n }\n\n // Apply pagination\n if (options.cursor) {\n const cursorData = JSON.parse(Buffer.from(options.cursor, 'base64').toString());\n query = query.where('id', '>', cursorData.lastId);\n }\n\n // Order by id for consistent pagination\n query = query.orderBy('id', 'asc').limit(limit + 1);\n\n const rows = await query;\n const hasMore = rows.length > limit;\n const resultRows = hasMore ? rows.slice(0, limit) : rows;\n const users = resultRows.map((row: Record<string, unknown>) => this.fromDbUser(row));\n\n let nextCursor: string | undefined;\n if (hasMore && resultRows.length > 0) {\n const lastUser = resultRows[resultRows.length - 1];\n nextCursor = Buffer.from(JSON.stringify({ lastId: lastUser.id })).toString('base64');\n }\n\n return { users, nextCursor };\n }\n\n // ==================== Session Methods ====================\n\n async createSession(session: Session): Promise<void> {\n await this.sessionsTable.insert(this.toDbSession(session));\n }\n\n async getSessionById(id: string): Promise<Session | null> {\n const row = await this.sessionsTable.where({ id }).first();\n return row ? this.fromDbSession(row) : null;\n }\n\n async getSessionsByUserId(userId: string): Promise<Session[]> {\n const rows = await this.sessionsTable.where({ user_id: userId });\n return rows.map((row: Record<string, unknown>) => this.fromDbSession(row));\n }\n\n async updateSession(id: string, updates: Partial<Session>): Promise<void> {\n const dbUpdates = this.toDbSessionUpdates(updates);\n\n if (Object.keys(dbUpdates).length === 0) {\n return;\n }\n\n await this.sessionsTable.where({ id }).update(dbUpdates);\n }\n\n async deleteSession(id: string): Promise<void> {\n await this.sessionsTable.where({ id }).delete();\n }\n\n async deleteAllUserSessions(userId: string): Promise<void> {\n await this.sessionsTable.where({ user_id: userId }).delete();\n }\n\n async deleteExpiredSessions(): Promise<void> {\n const now = Date.now();\n await this.sessionsTable.where('expires_at', '<', now).delete();\n }\n\n // ==================== User Mapping Helpers ====================\n\n /**\n * Convert User object to database row format (snake_case)\n */\n private toDbUser(user: User): Record<string, unknown> {\n return {\n id: user.id,\n email: user.email,\n first_name: user.firstName ?? null,\n last_name: user.lastName ?? null,\n password_hash: user.passwordHash,\n roles: JSON.stringify(user.roles),\n is_email_verified: user.isEmailVerified,\n verification_token: user.verificationToken ?? null,\n reset_password_token: user.resetPasswordToken ?? null,\n reset_password_expires: user.resetPasswordExpires ?? null,\n created_at: user.createdAt,\n updated_at: user.updatedAt,\n };\n }\n\n /**\n * Convert database row to User object (camelCase)\n */\n private fromDbUser(row: Record<string, unknown>): User {\n return {\n id: row.id as string,\n email: row.email as string,\n firstName: row.first_name as string | undefined,\n lastName: row.last_name as string | undefined,\n passwordHash: row.password_hash as string,\n roles: typeof row.roles === 'string' ? JSON.parse(row.roles) : row.roles,\n isEmailVerified: Boolean(row.is_email_verified),\n verificationToken: row.verification_token as string | undefined,\n resetPasswordToken: row.reset_password_token as string | undefined,\n resetPasswordExpires: row.reset_password_expires as number | undefined,\n createdAt: row.created_at as number,\n updatedAt: row.updated_at as number,\n };\n }\n\n /**\n * Convert partial User updates to database format\n */\n private toDbUpdates(updates: Partial<User>): Record<string, unknown> {\n const dbUpdates: Record<string, unknown> = {};\n\n if (updates.email !== undefined) {\n dbUpdates.email = updates.email;\n }\n if ('firstName' in updates) {\n dbUpdates.first_name = updates.firstName ?? null;\n }\n if ('lastName' in updates) {\n dbUpdates.last_name = updates.lastName ?? null;\n }\n if (updates.passwordHash !== undefined) {\n dbUpdates.password_hash = updates.passwordHash;\n }\n if (updates.roles !== undefined) {\n dbUpdates.roles = JSON.stringify(updates.roles);\n }\n if (updates.isEmailVerified !== undefined) {\n dbUpdates.is_email_verified = updates.isEmailVerified;\n }\n if ('verificationToken' in updates) {\n dbUpdates.verification_token = updates.verificationToken ?? null;\n }\n if ('resetPasswordToken' in updates) {\n dbUpdates.reset_password_token = updates.resetPasswordToken ?? null;\n }\n if ('resetPasswordExpires' in updates) {\n dbUpdates.reset_password_expires = updates.resetPasswordExpires ?? null;\n }\n if (updates.updatedAt !== undefined) {\n dbUpdates.updated_at = updates.updatedAt;\n }\n\n return dbUpdates;\n }\n\n // ==================== Session Mapping Helpers ====================\n\n /**\n * Convert Session object to database row format (snake_case)\n */\n private toDbSession(session: Session): Record<string, unknown> {\n return {\n id: session.id,\n user_id: session.userId,\n refresh_token_hash: session.refreshTokenHash,\n user_agent: session.userAgent ?? null,\n ip_address: session.ipAddress ?? null,\n device_name: session.deviceName ?? null,\n created_at: session.createdAt,\n last_used_at: session.lastUsedAt,\n expires_at: session.expiresAt,\n };\n }\n\n /**\n * Convert database row to Session object (camelCase)\n */\n private fromDbSession(row: Record<string, unknown>): Session {\n return {\n id: row.id as string,\n userId: row.user_id as string,\n refreshTokenHash: row.refresh_token_hash as string,\n userAgent: row.user_agent as string | undefined,\n ipAddress: row.ip_address as string | undefined,\n deviceName: row.device_name as string | undefined,\n createdAt: row.created_at as number,\n lastUsedAt: row.last_used_at as number,\n expiresAt: row.expires_at as number,\n };\n }\n\n /**\n * Convert partial Session updates to database format\n */\n private toDbSessionUpdates(updates: Partial<Session>): Record<string, unknown> {\n const dbUpdates: Record<string, unknown> = {};\n\n if (updates.refreshTokenHash !== undefined) {\n dbUpdates.refresh_token_hash = updates.refreshTokenHash;\n }\n if ('userAgent' in updates) {\n dbUpdates.user_agent = updates.userAgent ?? null;\n }\n if ('ipAddress' in updates) {\n dbUpdates.ip_address = updates.ipAddress ?? null;\n }\n if ('deviceName' in updates) {\n dbUpdates.device_name = updates.deviceName ?? null;\n }\n if (updates.lastUsedAt !== undefined) {\n dbUpdates.last_used_at = updates.lastUsedAt;\n }\n if (updates.expiresAt !== undefined) {\n dbUpdates.expires_at = updates.expiresAt;\n }\n\n return dbUpdates;\n }\n}\n","import type { Knex } from 'knex';\n\n/**\n * SQL migration helper to create the users table\n * Can be used with Knex migrations\n *\n * @example\n * ```ts\n * // In your migration file\n * import { createUsersTableMigration, createSessionsTableMigration } from '@xcelsior/auth-adapter-knex';\n *\n * export async function up(knex: Knex): Promise<void> {\n * await createUsersTableMigration(knex, 'users');\n * await createSessionsTableMigration(knex, 'sessions');\n * }\n *\n * export async function down(knex: Knex): Promise<void> {\n * await knex.schema.dropTableIfExists('sessions');\n * await knex.schema.dropTableIfExists('users');\n * }\n * ```\n */\nexport async function createUsersTableMigration(\n knex: Knex,\n tableName: string,\n schema?: string\n): Promise<void> {\n const schemaBuilder = schema ? knex.schema.withSchema(schema) : knex.schema;\n\n await schemaBuilder.createTable(tableName, (table) => {\n table.string('id').primary();\n table.string('email').notNullable().unique();\n table.string('first_name').nullable();\n table.string('last_name').nullable();\n table.string('password_hash').notNullable();\n table.jsonb('roles').notNullable();\n table.boolean('is_email_verified').notNullable().defaultTo(false);\n table.string('verification_token').nullable();\n table.string('reset_password_token').nullable();\n table.bigInteger('reset_password_expires').nullable();\n table.bigInteger('created_at').notNullable();\n table.bigInteger('updated_at').notNullable();\n\n // Indexes for common lookups\n table.index('email');\n table.index('verification_token');\n table.index('reset_password_token');\n });\n}\n\n/**\n * SQL migration helper to create the sessions table\n * Can be used with Knex migrations\n */\nexport async function createSessionsTableMigration(\n knex: Knex,\n tableName: string,\n schema?: string\n): Promise<void> {\n const schemaBuilder = schema ? knex.schema.withSchema(schema) : knex.schema;\n\n await schemaBuilder.createTable(tableName, (table) => {\n table.string('id').primary();\n table.string('user_id').notNullable();\n table.string('refresh_token_hash').notNullable();\n table.string('user_agent').nullable();\n table.string('ip_address').nullable();\n table.string('device_name').nullable();\n table.bigInteger('created_at').notNullable();\n table.bigInteger('last_used_at').notNullable();\n table.bigInteger('expires_at').notNullable();\n\n // Index for querying sessions by user\n table.index('user_id');\n // Index for cleaning up expired sessions\n table.index('expires_at');\n });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/knex.ts","../src/migration.ts"],"sourcesContent":["export { KnexStorageProvider, type KnexConfig } from './knex';\nexport { createUsersTableMigration, createSessionsTableMigration } from './migration';\n","import type { Knex } from 'knex';\nimport type {\n CreateSessionInput,\n CreateUserInput,\n FindUsersOptions,\n FindUsersResult,\n IStorageProvider,\n Session,\n SessionId,\n User,\n UserFilter,\n UserId,\n} from '@xcelsior/auth';\n\nexport interface KnexConfig {\n /** Pre-configured Knex instance */\n knex: Knex;\n /** Table name for users */\n tableName: string;\n /** Table name for sessions */\n sessionsTableName: string;\n /** Optional schema (for PostgreSQL) */\n schema?: string;\n}\n\nexport class KnexStorageProvider implements IStorageProvider {\n private knex: Knex;\n private tableName: string;\n private sessionsTableName: string;\n private schema?: string;\n\n constructor(config: KnexConfig) {\n this.knex = config.knex;\n this.tableName = config.tableName;\n this.sessionsTableName = config.sessionsTableName;\n this.schema = config.schema;\n }\n\n private get table() {\n const query = this.knex(this.tableName);\n if (this.schema) {\n return query.withSchema(this.schema);\n }\n return query;\n }\n\n private get sessionsTable() {\n const query = this.knex(this.sessionsTableName);\n if (this.schema) {\n return query.withSchema(this.schema);\n }\n return query;\n }\n\n // ==================== User Methods ====================\n\n async createUser(user: CreateUserInput): Promise<User> {\n const dbUser = this.toDbUser(user);\n const [insertedId] = await this.table.insert(dbUser);\n // For auto-increment DBs, insertedId is the new ID\n // For string PKs, it returns 0, so use the original id\n const finalId = user.id ?? insertedId;\n return { ...user, id: finalId } as User;\n }\n\n async getUserById(id: UserId): Promise<User | null> {\n const row = await this.table.where({ id }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async getUserByEmail(email: string): Promise<User | null> {\n const row = await this.table.where({ email }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async getUserByResetPasswordToken(resetPasswordToken: string): Promise<User | null> {\n const row = await this.table.where({ reset_password_token: resetPasswordToken }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async getUserByVerifyEmailToken(verifyEmailToken: string): Promise<User | null> {\n const row = await this.table.where({ verification_token: verifyEmailToken }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async updateUser(id: UserId, updates: Partial<User>): Promise<void> {\n const dbUpdates = this.toDbUpdates(updates);\n\n if (Object.keys(dbUpdates).length === 0) {\n return;\n }\n\n await this.table.where({ id }).update(dbUpdates);\n }\n\n async deleteUser(id: UserId): Promise<void> {\n // Delete all user sessions first\n await this.deleteAllUserSessions(id);\n // Then delete the user\n await this.table.where({ id }).delete();\n }\n\n async findUsers(filter: UserFilter, options: FindUsersOptions = {}): Promise<FindUsersResult> {\n const limit = options.limit ?? 50;\n\n let query = this.table.clone();\n\n // Email exact match\n if (filter.email) {\n query = query.where('email', filter.email);\n }\n\n // Email contains (partial match)\n if (filter.emailContains) {\n query = query.where('email', 'like', `%${filter.emailContains}%`);\n }\n\n // Email verification status\n if (filter.isEmailVerified !== undefined) {\n query = query.where('is_email_verified', filter.isEmailVerified);\n }\n\n // Roles filtering - user must have ALL specified roles\n if (filter.roles && filter.roles.length > 0) {\n for (const role of filter.roles) {\n // Use JSON contains - works for PostgreSQL (jsonb) and MySQL (json)\n // For SQLite, we fall back to LIKE on the JSON string\n query = query.whereRaw(\n this.knex.client.config.client === 'sqlite3' ? `roles LIKE ?` : `roles @> ?`,\n this.knex.client.config.client === 'sqlite3'\n ? [`%\"${role}\"%`]\n : [JSON.stringify([role])]\n );\n }\n }\n\n // HasAnyRole - user must have at least ONE of specified roles\n if (filter.hasAnyRole && filter.hasAnyRole.length > 0) {\n query = query.where((builder: Knex.QueryBuilder) => {\n for (const role of filter.hasAnyRole!) {\n builder.orWhereRaw(\n this.knex.client.config.client === 'sqlite3'\n ? `roles LIKE ?`\n : `roles @> ?`,\n this.knex.client.config.client === 'sqlite3'\n ? [`%\"${role}\"%`]\n : [JSON.stringify([role])]\n );\n }\n });\n }\n\n // Apply pagination\n if (options.cursor) {\n const cursorData = JSON.parse(Buffer.from(options.cursor, 'base64').toString());\n query = query.where('id', '>', cursorData.lastId);\n }\n\n // Order by id for consistent pagination\n query = query.orderBy('id', 'asc').limit(limit + 1);\n\n const rows = await query;\n const hasMore = rows.length > limit;\n const resultRows = hasMore ? rows.slice(0, limit) : rows;\n const users = resultRows.map((row: Record<string, unknown>) => this.fromDbUser(row));\n\n let nextCursor: string | undefined;\n if (hasMore && resultRows.length > 0) {\n const lastUser = resultRows[resultRows.length - 1];\n nextCursor = Buffer.from(JSON.stringify({ lastId: lastUser.id })).toString('base64');\n }\n\n return { users, nextCursor };\n }\n\n // ==================== Session Methods ====================\n\n async createSession(session: CreateSessionInput): Promise<Session> {\n const dbSession = this.toDbSession(session);\n const [insertedId] = await this.sessionsTable.insert(dbSession);\n const finalId = session.id ?? insertedId;\n return { ...session, id: finalId } as Session;\n }\n\n async getSessionById(id: SessionId): Promise<Session | null> {\n const row = await this.sessionsTable.where({ id }).first();\n return row ? this.fromDbSession(row) : null;\n }\n\n async getSessionsByUserId(userId: UserId): Promise<Session[]> {\n const rows = await this.sessionsTable.where({ user_id: userId });\n return rows.map((row: Record<string, unknown>) => this.fromDbSession(row));\n }\n\n async updateSession(id: SessionId, updates: Partial<Session>): Promise<void> {\n const dbUpdates = this.toDbSessionUpdates(updates);\n\n if (Object.keys(dbUpdates).length === 0) {\n return;\n }\n\n await this.sessionsTable.where({ id }).update(dbUpdates);\n }\n\n async deleteSession(id: SessionId): Promise<void> {\n await this.sessionsTable.where({ id }).delete();\n }\n\n async deleteAllUserSessions(userId: UserId): Promise<void> {\n await this.sessionsTable.where({ user_id: userId }).delete();\n }\n\n async deleteExpiredSessions(): Promise<void> {\n const now = Date.now();\n await this.sessionsTable.where('expires_at', '<', now).delete();\n }\n\n // ==================== User Mapping Helpers ====================\n\n /**\n * Convert User object to database row format (snake_case)\n */\n private toDbUser(user: CreateUserInput): Record<string, unknown> {\n const row: Record<string, unknown> = {\n email: user.email,\n first_name: user.firstName ?? null,\n last_name: user.lastName ?? null,\n password_hash: user.passwordHash,\n roles: JSON.stringify(user.roles),\n is_email_verified: user.isEmailVerified ? 1 : 0,\n verification_token: user.verificationToken ?? null,\n reset_password_token: user.resetPasswordToken ?? null,\n reset_password_expires: user.resetPasswordExpires ?? null,\n created_at: user.createdAt ?? Date.now(),\n updated_at: user.updatedAt ?? Date.now(),\n };\n // Only include id if provided (for string UUIDs)\n // Omit for auto-increment DBs\n if (user.id !== undefined) {\n row.id = user.id;\n }\n return row;\n }\n\n /**\n * Convert database row to User object (camelCase)\n */\n private fromDbUser(row: Record<string, unknown>): User {\n return {\n id: row.id as string | number,\n email: row.email as string,\n firstName: row.first_name as string | undefined,\n lastName: row.last_name as string | undefined,\n passwordHash: row.password_hash as string,\n roles: typeof row.roles === 'string' ? JSON.parse(row.roles) : row.roles,\n isEmailVerified: Boolean(row.is_email_verified),\n verificationToken: row.verification_token as string | undefined,\n resetPasswordToken: row.reset_password_token as string | undefined,\n resetPasswordExpires: row.reset_password_expires as number | undefined,\n createdAt: row.created_at as number,\n updatedAt: row.updated_at as number,\n };\n }\n\n /**\n * Convert partial User updates to database format\n */\n private toDbUpdates(updates: Partial<User>): Record<string, unknown> {\n const dbUpdates: Record<string, unknown> = {};\n\n if (updates.email !== undefined) {\n dbUpdates.email = updates.email;\n }\n if ('firstName' in updates) {\n dbUpdates.first_name = updates.firstName ?? null;\n }\n if ('lastName' in updates) {\n dbUpdates.last_name = updates.lastName ?? null;\n }\n if (updates.passwordHash !== undefined) {\n dbUpdates.password_hash = updates.passwordHash;\n }\n if (updates.roles !== undefined) {\n dbUpdates.roles = JSON.stringify(updates.roles);\n }\n if (updates.isEmailVerified !== undefined) {\n dbUpdates.is_email_verified = updates.isEmailVerified ? 1 : 0;\n }\n if ('verificationToken' in updates) {\n dbUpdates.verification_token = updates.verificationToken ?? null;\n }\n if ('resetPasswordToken' in updates) {\n dbUpdates.reset_password_token = updates.resetPasswordToken ?? null;\n }\n if ('resetPasswordExpires' in updates) {\n dbUpdates.reset_password_expires = updates.resetPasswordExpires ?? null;\n }\n if (updates.updatedAt !== undefined) {\n dbUpdates.updated_at = updates.updatedAt;\n }\n\n return dbUpdates;\n }\n\n // ==================== Session Mapping Helpers ====================\n\n /**\n * Convert Session object to database row format (snake_case)\n */\n private toDbSession(session: CreateSessionInput): Record<string, unknown> {\n const row: Record<string, unknown> = {\n user_id: session.userId,\n refresh_token_hash: session.refreshTokenHash,\n user_agent: session.userAgent ?? null,\n ip_address: session.ipAddress ?? null,\n device_name: session.deviceName ?? null,\n created_at: session.createdAt,\n last_used_at: session.lastUsedAt,\n expires_at: session.expiresAt,\n };\n // Only include id if provided (for string UUIDs)\n // Omit for auto-increment DBs\n if (session.id !== undefined) {\n row.id = session.id;\n }\n return row;\n }\n\n /**\n * Convert database row to Session object (camelCase)\n */\n private fromDbSession(row: Record<string, unknown>): Session {\n return {\n id: row.id as string | number,\n userId: row.user_id as string | number,\n refreshTokenHash: row.refresh_token_hash as string,\n userAgent: row.user_agent as string | undefined,\n ipAddress: row.ip_address as string | undefined,\n deviceName: row.device_name as string | undefined,\n createdAt: row.created_at as number,\n lastUsedAt: row.last_used_at as number,\n expiresAt: row.expires_at as number,\n };\n }\n\n /**\n * Convert partial Session updates to database format\n */\n private toDbSessionUpdates(updates: Partial<Session>): Record<string, unknown> {\n const dbUpdates: Record<string, unknown> = {};\n\n if (updates.refreshTokenHash !== undefined) {\n dbUpdates.refresh_token_hash = updates.refreshTokenHash;\n }\n if ('userAgent' in updates) {\n dbUpdates.user_agent = updates.userAgent ?? null;\n }\n if ('ipAddress' in updates) {\n dbUpdates.ip_address = updates.ipAddress ?? null;\n }\n if ('deviceName' in updates) {\n dbUpdates.device_name = updates.deviceName ?? null;\n }\n if (updates.lastUsedAt !== undefined) {\n dbUpdates.last_used_at = updates.lastUsedAt;\n }\n if (updates.expiresAt !== undefined) {\n dbUpdates.expires_at = updates.expiresAt;\n }\n\n return dbUpdates;\n }\n}\n","import type { Knex } from 'knex';\n\n/**\n * SQL migration helper to create the users table\n * Can be used with Knex migrations\n *\n * @example\n * ```ts\n * // In your migration file\n * import { createUsersTableMigration, createSessionsTableMigration } from '@xcelsior/auth-adapter-knex';\n *\n * export async function up(knex: Knex): Promise<void> {\n * await createUsersTableMigration(knex, 'users');\n * await createSessionsTableMigration(knex, 'sessions');\n * }\n *\n * export async function down(knex: Knex): Promise<void> {\n * await knex.schema.dropTableIfExists('sessions');\n * await knex.schema.dropTableIfExists('users');\n * }\n * ```\n */\nexport async function createUsersTableMigration(\n knex: Knex,\n tableName: string,\n schema?: string\n): Promise<void> {\n const schemaBuilder = schema ? knex.schema.withSchema(schema) : knex.schema;\n\n await schemaBuilder.createTable(tableName, (table) => {\n table.string('id').primary();\n table.string('email').notNullable().unique();\n table.string('first_name').nullable();\n table.string('last_name').nullable();\n table.string('password_hash').notNullable();\n table.jsonb('roles').notNullable();\n table.boolean('is_email_verified').notNullable().defaultTo(false);\n table.string('verification_token').nullable();\n table.string('reset_password_token').nullable();\n table.bigInteger('reset_password_expires').nullable();\n table.bigInteger('created_at').notNullable();\n table.bigInteger('updated_at').notNullable();\n\n // Indexes for common lookups\n table.index('email');\n table.index('verification_token');\n table.index('reset_password_token');\n });\n}\n\n/**\n * SQL migration helper to create the sessions table\n * Can be used with Knex migrations\n */\nexport async function createSessionsTableMigration(\n knex: Knex,\n tableName: string,\n schema?: string\n): Promise<void> {\n const schemaBuilder = schema ? knex.schema.withSchema(schema) : knex.schema;\n\n await schemaBuilder.createTable(tableName, (table) => {\n table.string('id').primary();\n table.string('user_id').notNullable();\n table.string('refresh_token_hash').notNullable();\n table.string('user_agent').nullable();\n table.string('ip_address').nullable();\n table.string('device_name').nullable();\n table.bigInteger('created_at').notNullable();\n table.bigInteger('last_used_at').notNullable();\n table.bigInteger('expires_at').notNullable();\n\n // Index for querying sessions by user\n table.index('user_id');\n // Index for cleaning up expired sessions\n table.index('expires_at');\n });\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -22,7 +22,10 @@ var KnexStorageProvider = class {
22
22
  }
23
23
  // ==================== User Methods ====================
24
24
  async createUser(user) {
25
- await this.table.insert(this.toDbUser(user));
25
+ const dbUser = this.toDbUser(user);
26
+ const [insertedId] = await this.table.insert(dbUser);
27
+ const finalId = user.id ?? insertedId;
28
+ return { ...user, id: finalId };
26
29
  }
27
30
  async getUserById(id) {
28
31
  const row = await this.table.where({ id }).first();
@@ -99,7 +102,10 @@ var KnexStorageProvider = class {
99
102
  }
100
103
  // ==================== Session Methods ====================
101
104
  async createSession(session) {
102
- await this.sessionsTable.insert(this.toDbSession(session));
105
+ const dbSession = this.toDbSession(session);
106
+ const [insertedId] = await this.sessionsTable.insert(dbSession);
107
+ const finalId = session.id ?? insertedId;
108
+ return { ...session, id: finalId };
103
109
  }
104
110
  async getSessionById(id) {
105
111
  const row = await this.sessionsTable.where({ id }).first();
@@ -131,20 +137,23 @@ var KnexStorageProvider = class {
131
137
  * Convert User object to database row format (snake_case)
132
138
  */
133
139
  toDbUser(user) {
134
- return {
135
- id: user.id,
140
+ const row = {
136
141
  email: user.email,
137
142
  first_name: user.firstName ?? null,
138
143
  last_name: user.lastName ?? null,
139
144
  password_hash: user.passwordHash,
140
145
  roles: JSON.stringify(user.roles),
141
- is_email_verified: user.isEmailVerified,
146
+ is_email_verified: user.isEmailVerified ? 1 : 0,
142
147
  verification_token: user.verificationToken ?? null,
143
148
  reset_password_token: user.resetPasswordToken ?? null,
144
149
  reset_password_expires: user.resetPasswordExpires ?? null,
145
- created_at: user.createdAt,
146
- updated_at: user.updatedAt
150
+ created_at: user.createdAt ?? Date.now(),
151
+ updated_at: user.updatedAt ?? Date.now()
147
152
  };
153
+ if (user.id !== void 0) {
154
+ row.id = user.id;
155
+ }
156
+ return row;
148
157
  }
149
158
  /**
150
159
  * Convert database row to User object (camelCase)
@@ -186,7 +195,7 @@ var KnexStorageProvider = class {
186
195
  dbUpdates.roles = JSON.stringify(updates.roles);
187
196
  }
188
197
  if (updates.isEmailVerified !== void 0) {
189
- dbUpdates.is_email_verified = updates.isEmailVerified;
198
+ dbUpdates.is_email_verified = updates.isEmailVerified ? 1 : 0;
190
199
  }
191
200
  if ("verificationToken" in updates) {
192
201
  dbUpdates.verification_token = updates.verificationToken ?? null;
@@ -207,8 +216,7 @@ var KnexStorageProvider = class {
207
216
  * Convert Session object to database row format (snake_case)
208
217
  */
209
218
  toDbSession(session) {
210
- return {
211
- id: session.id,
219
+ const row = {
212
220
  user_id: session.userId,
213
221
  refresh_token_hash: session.refreshTokenHash,
214
222
  user_agent: session.userAgent ?? null,
@@ -218,6 +226,10 @@ var KnexStorageProvider = class {
218
226
  last_used_at: session.lastUsedAt,
219
227
  expires_at: session.expiresAt
220
228
  };
229
+ if (session.id !== void 0) {
230
+ row.id = session.id;
231
+ }
232
+ return row;
221
233
  }
222
234
  /**
223
235
  * Convert database row to Session object (camelCase)
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/knex.ts","../src/migration.ts"],"sourcesContent":["import type { Knex } from 'knex';\nimport type {\n IStorageProvider,\n User,\n Session,\n UserFilter,\n FindUsersOptions,\n FindUsersResult,\n} from '@xcelsior/auth';\n\nexport interface KnexConfig {\n /** Pre-configured Knex instance */\n knex: Knex;\n /** Table name for users */\n tableName: string;\n /** Table name for sessions */\n sessionsTableName: string;\n /** Optional schema (for PostgreSQL) */\n schema?: string;\n}\n\nexport class KnexStorageProvider implements IStorageProvider {\n private knex: Knex;\n private tableName: string;\n private sessionsTableName: string;\n private schema?: string;\n\n constructor(config: KnexConfig) {\n this.knex = config.knex;\n this.tableName = config.tableName;\n this.sessionsTableName = config.sessionsTableName;\n this.schema = config.schema;\n }\n\n private get table() {\n const query = this.knex(this.tableName);\n if (this.schema) {\n return query.withSchema(this.schema);\n }\n return query;\n }\n\n private get sessionsTable() {\n const query = this.knex(this.sessionsTableName);\n if (this.schema) {\n return query.withSchema(this.schema);\n }\n return query;\n }\n\n // ==================== User Methods ====================\n\n async createUser(user: User): Promise<void> {\n await this.table.insert(this.toDbUser(user));\n }\n\n async getUserById(id: string): Promise<User | null> {\n const row = await this.table.where({ id }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async getUserByEmail(email: string): Promise<User | null> {\n const row = await this.table.where({ email }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async getUserByResetPasswordToken(resetPasswordToken: string): Promise<User | null> {\n const row = await this.table.where({ reset_password_token: resetPasswordToken }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async getUserByVerifyEmailToken(verifyEmailToken: string): Promise<User | null> {\n const row = await this.table.where({ verification_token: verifyEmailToken }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async updateUser(id: string, updates: Partial<User>): Promise<void> {\n const dbUpdates = this.toDbUpdates(updates);\n\n if (Object.keys(dbUpdates).length === 0) {\n return;\n }\n\n await this.table.where({ id }).update(dbUpdates);\n }\n\n async deleteUser(id: string): Promise<void> {\n // Delete all user sessions first\n await this.deleteAllUserSessions(id);\n // Then delete the user\n await this.table.where({ id }).delete();\n }\n\n async findUsers(filter: UserFilter, options: FindUsersOptions = {}): Promise<FindUsersResult> {\n const limit = options.limit ?? 50;\n\n let query = this.table.clone();\n\n // Email exact match\n if (filter.email) {\n query = query.where('email', filter.email);\n }\n\n // Email contains (partial match)\n if (filter.emailContains) {\n query = query.where('email', 'like', `%${filter.emailContains}%`);\n }\n\n // Email verification status\n if (filter.isEmailVerified !== undefined) {\n query = query.where('is_email_verified', filter.isEmailVerified);\n }\n\n // Roles filtering - user must have ALL specified roles\n if (filter.roles && filter.roles.length > 0) {\n for (const role of filter.roles) {\n // Use JSON contains - works for PostgreSQL (jsonb) and MySQL (json)\n // For SQLite, we fall back to LIKE on the JSON string\n query = query.whereRaw(\n this.knex.client.config.client === 'sqlite3' ? `roles LIKE ?` : `roles @> ?`,\n this.knex.client.config.client === 'sqlite3'\n ? [`%\"${role}\"%`]\n : [JSON.stringify([role])]\n );\n }\n }\n\n // HasAnyRole - user must have at least ONE of specified roles\n if (filter.hasAnyRole && filter.hasAnyRole.length > 0) {\n query = query.where((builder: Knex.QueryBuilder) => {\n for (const role of filter.hasAnyRole!) {\n builder.orWhereRaw(\n this.knex.client.config.client === 'sqlite3'\n ? `roles LIKE ?`\n : `roles @> ?`,\n this.knex.client.config.client === 'sqlite3'\n ? [`%\"${role}\"%`]\n : [JSON.stringify([role])]\n );\n }\n });\n }\n\n // Apply pagination\n if (options.cursor) {\n const cursorData = JSON.parse(Buffer.from(options.cursor, 'base64').toString());\n query = query.where('id', '>', cursorData.lastId);\n }\n\n // Order by id for consistent pagination\n query = query.orderBy('id', 'asc').limit(limit + 1);\n\n const rows = await query;\n const hasMore = rows.length > limit;\n const resultRows = hasMore ? rows.slice(0, limit) : rows;\n const users = resultRows.map((row: Record<string, unknown>) => this.fromDbUser(row));\n\n let nextCursor: string | undefined;\n if (hasMore && resultRows.length > 0) {\n const lastUser = resultRows[resultRows.length - 1];\n nextCursor = Buffer.from(JSON.stringify({ lastId: lastUser.id })).toString('base64');\n }\n\n return { users, nextCursor };\n }\n\n // ==================== Session Methods ====================\n\n async createSession(session: Session): Promise<void> {\n await this.sessionsTable.insert(this.toDbSession(session));\n }\n\n async getSessionById(id: string): Promise<Session | null> {\n const row = await this.sessionsTable.where({ id }).first();\n return row ? this.fromDbSession(row) : null;\n }\n\n async getSessionsByUserId(userId: string): Promise<Session[]> {\n const rows = await this.sessionsTable.where({ user_id: userId });\n return rows.map((row: Record<string, unknown>) => this.fromDbSession(row));\n }\n\n async updateSession(id: string, updates: Partial<Session>): Promise<void> {\n const dbUpdates = this.toDbSessionUpdates(updates);\n\n if (Object.keys(dbUpdates).length === 0) {\n return;\n }\n\n await this.sessionsTable.where({ id }).update(dbUpdates);\n }\n\n async deleteSession(id: string): Promise<void> {\n await this.sessionsTable.where({ id }).delete();\n }\n\n async deleteAllUserSessions(userId: string): Promise<void> {\n await this.sessionsTable.where({ user_id: userId }).delete();\n }\n\n async deleteExpiredSessions(): Promise<void> {\n const now = Date.now();\n await this.sessionsTable.where('expires_at', '<', now).delete();\n }\n\n // ==================== User Mapping Helpers ====================\n\n /**\n * Convert User object to database row format (snake_case)\n */\n private toDbUser(user: User): Record<string, unknown> {\n return {\n id: user.id,\n email: user.email,\n first_name: user.firstName ?? null,\n last_name: user.lastName ?? null,\n password_hash: user.passwordHash,\n roles: JSON.stringify(user.roles),\n is_email_verified: user.isEmailVerified,\n verification_token: user.verificationToken ?? null,\n reset_password_token: user.resetPasswordToken ?? null,\n reset_password_expires: user.resetPasswordExpires ?? null,\n created_at: user.createdAt,\n updated_at: user.updatedAt,\n };\n }\n\n /**\n * Convert database row to User object (camelCase)\n */\n private fromDbUser(row: Record<string, unknown>): User {\n return {\n id: row.id as string,\n email: row.email as string,\n firstName: row.first_name as string | undefined,\n lastName: row.last_name as string | undefined,\n passwordHash: row.password_hash as string,\n roles: typeof row.roles === 'string' ? JSON.parse(row.roles) : row.roles,\n isEmailVerified: Boolean(row.is_email_verified),\n verificationToken: row.verification_token as string | undefined,\n resetPasswordToken: row.reset_password_token as string | undefined,\n resetPasswordExpires: row.reset_password_expires as number | undefined,\n createdAt: row.created_at as number,\n updatedAt: row.updated_at as number,\n };\n }\n\n /**\n * Convert partial User updates to database format\n */\n private toDbUpdates(updates: Partial<User>): Record<string, unknown> {\n const dbUpdates: Record<string, unknown> = {};\n\n if (updates.email !== undefined) {\n dbUpdates.email = updates.email;\n }\n if ('firstName' in updates) {\n dbUpdates.first_name = updates.firstName ?? null;\n }\n if ('lastName' in updates) {\n dbUpdates.last_name = updates.lastName ?? null;\n }\n if (updates.passwordHash !== undefined) {\n dbUpdates.password_hash = updates.passwordHash;\n }\n if (updates.roles !== undefined) {\n dbUpdates.roles = JSON.stringify(updates.roles);\n }\n if (updates.isEmailVerified !== undefined) {\n dbUpdates.is_email_verified = updates.isEmailVerified;\n }\n if ('verificationToken' in updates) {\n dbUpdates.verification_token = updates.verificationToken ?? null;\n }\n if ('resetPasswordToken' in updates) {\n dbUpdates.reset_password_token = updates.resetPasswordToken ?? null;\n }\n if ('resetPasswordExpires' in updates) {\n dbUpdates.reset_password_expires = updates.resetPasswordExpires ?? null;\n }\n if (updates.updatedAt !== undefined) {\n dbUpdates.updated_at = updates.updatedAt;\n }\n\n return dbUpdates;\n }\n\n // ==================== Session Mapping Helpers ====================\n\n /**\n * Convert Session object to database row format (snake_case)\n */\n private toDbSession(session: Session): Record<string, unknown> {\n return {\n id: session.id,\n user_id: session.userId,\n refresh_token_hash: session.refreshTokenHash,\n user_agent: session.userAgent ?? null,\n ip_address: session.ipAddress ?? null,\n device_name: session.deviceName ?? null,\n created_at: session.createdAt,\n last_used_at: session.lastUsedAt,\n expires_at: session.expiresAt,\n };\n }\n\n /**\n * Convert database row to Session object (camelCase)\n */\n private fromDbSession(row: Record<string, unknown>): Session {\n return {\n id: row.id as string,\n userId: row.user_id as string,\n refreshTokenHash: row.refresh_token_hash as string,\n userAgent: row.user_agent as string | undefined,\n ipAddress: row.ip_address as string | undefined,\n deviceName: row.device_name as string | undefined,\n createdAt: row.created_at as number,\n lastUsedAt: row.last_used_at as number,\n expiresAt: row.expires_at as number,\n };\n }\n\n /**\n * Convert partial Session updates to database format\n */\n private toDbSessionUpdates(updates: Partial<Session>): Record<string, unknown> {\n const dbUpdates: Record<string, unknown> = {};\n\n if (updates.refreshTokenHash !== undefined) {\n dbUpdates.refresh_token_hash = updates.refreshTokenHash;\n }\n if ('userAgent' in updates) {\n dbUpdates.user_agent = updates.userAgent ?? null;\n }\n if ('ipAddress' in updates) {\n dbUpdates.ip_address = updates.ipAddress ?? null;\n }\n if ('deviceName' in updates) {\n dbUpdates.device_name = updates.deviceName ?? null;\n }\n if (updates.lastUsedAt !== undefined) {\n dbUpdates.last_used_at = updates.lastUsedAt;\n }\n if (updates.expiresAt !== undefined) {\n dbUpdates.expires_at = updates.expiresAt;\n }\n\n return dbUpdates;\n }\n}\n","import type { Knex } from 'knex';\n\n/**\n * SQL migration helper to create the users table\n * Can be used with Knex migrations\n *\n * @example\n * ```ts\n * // In your migration file\n * import { createUsersTableMigration, createSessionsTableMigration } from '@xcelsior/auth-adapter-knex';\n *\n * export async function up(knex: Knex): Promise<void> {\n * await createUsersTableMigration(knex, 'users');\n * await createSessionsTableMigration(knex, 'sessions');\n * }\n *\n * export async function down(knex: Knex): Promise<void> {\n * await knex.schema.dropTableIfExists('sessions');\n * await knex.schema.dropTableIfExists('users');\n * }\n * ```\n */\nexport async function createUsersTableMigration(\n knex: Knex,\n tableName: string,\n schema?: string\n): Promise<void> {\n const schemaBuilder = schema ? knex.schema.withSchema(schema) : knex.schema;\n\n await schemaBuilder.createTable(tableName, (table) => {\n table.string('id').primary();\n table.string('email').notNullable().unique();\n table.string('first_name').nullable();\n table.string('last_name').nullable();\n table.string('password_hash').notNullable();\n table.jsonb('roles').notNullable();\n table.boolean('is_email_verified').notNullable().defaultTo(false);\n table.string('verification_token').nullable();\n table.string('reset_password_token').nullable();\n table.bigInteger('reset_password_expires').nullable();\n table.bigInteger('created_at').notNullable();\n table.bigInteger('updated_at').notNullable();\n\n // Indexes for common lookups\n table.index('email');\n table.index('verification_token');\n table.index('reset_password_token');\n });\n}\n\n/**\n * SQL migration helper to create the sessions table\n * Can be used with Knex migrations\n */\nexport async function createSessionsTableMigration(\n knex: Knex,\n tableName: string,\n schema?: string\n): Promise<void> {\n const schemaBuilder = schema ? knex.schema.withSchema(schema) : knex.schema;\n\n await schemaBuilder.createTable(tableName, (table) => {\n table.string('id').primary();\n table.string('user_id').notNullable();\n table.string('refresh_token_hash').notNullable();\n table.string('user_agent').nullable();\n table.string('ip_address').nullable();\n table.string('device_name').nullable();\n table.bigInteger('created_at').notNullable();\n table.bigInteger('last_used_at').notNullable();\n table.bigInteger('expires_at').notNullable();\n\n // Index for querying sessions by user\n table.index('user_id');\n // Index for cleaning up expired sessions\n table.index('expires_at');\n });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/knex.ts","../src/migration.ts"],"sourcesContent":["import type { Knex } from 'knex';\nimport type {\n CreateSessionInput,\n CreateUserInput,\n FindUsersOptions,\n FindUsersResult,\n IStorageProvider,\n Session,\n SessionId,\n User,\n UserFilter,\n UserId,\n} from '@xcelsior/auth';\n\nexport interface KnexConfig {\n /** Pre-configured Knex instance */\n knex: Knex;\n /** Table name for users */\n tableName: string;\n /** Table name for sessions */\n sessionsTableName: string;\n /** Optional schema (for PostgreSQL) */\n schema?: string;\n}\n\nexport class KnexStorageProvider implements IStorageProvider {\n private knex: Knex;\n private tableName: string;\n private sessionsTableName: string;\n private schema?: string;\n\n constructor(config: KnexConfig) {\n this.knex = config.knex;\n this.tableName = config.tableName;\n this.sessionsTableName = config.sessionsTableName;\n this.schema = config.schema;\n }\n\n private get table() {\n const query = this.knex(this.tableName);\n if (this.schema) {\n return query.withSchema(this.schema);\n }\n return query;\n }\n\n private get sessionsTable() {\n const query = this.knex(this.sessionsTableName);\n if (this.schema) {\n return query.withSchema(this.schema);\n }\n return query;\n }\n\n // ==================== User Methods ====================\n\n async createUser(user: CreateUserInput): Promise<User> {\n const dbUser = this.toDbUser(user);\n const [insertedId] = await this.table.insert(dbUser);\n // For auto-increment DBs, insertedId is the new ID\n // For string PKs, it returns 0, so use the original id\n const finalId = user.id ?? insertedId;\n return { ...user, id: finalId } as User;\n }\n\n async getUserById(id: UserId): Promise<User | null> {\n const row = await this.table.where({ id }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async getUserByEmail(email: string): Promise<User | null> {\n const row = await this.table.where({ email }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async getUserByResetPasswordToken(resetPasswordToken: string): Promise<User | null> {\n const row = await this.table.where({ reset_password_token: resetPasswordToken }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async getUserByVerifyEmailToken(verifyEmailToken: string): Promise<User | null> {\n const row = await this.table.where({ verification_token: verifyEmailToken }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async updateUser(id: UserId, updates: Partial<User>): Promise<void> {\n const dbUpdates = this.toDbUpdates(updates);\n\n if (Object.keys(dbUpdates).length === 0) {\n return;\n }\n\n await this.table.where({ id }).update(dbUpdates);\n }\n\n async deleteUser(id: UserId): Promise<void> {\n // Delete all user sessions first\n await this.deleteAllUserSessions(id);\n // Then delete the user\n await this.table.where({ id }).delete();\n }\n\n async findUsers(filter: UserFilter, options: FindUsersOptions = {}): Promise<FindUsersResult> {\n const limit = options.limit ?? 50;\n\n let query = this.table.clone();\n\n // Email exact match\n if (filter.email) {\n query = query.where('email', filter.email);\n }\n\n // Email contains (partial match)\n if (filter.emailContains) {\n query = query.where('email', 'like', `%${filter.emailContains}%`);\n }\n\n // Email verification status\n if (filter.isEmailVerified !== undefined) {\n query = query.where('is_email_verified', filter.isEmailVerified);\n }\n\n // Roles filtering - user must have ALL specified roles\n if (filter.roles && filter.roles.length > 0) {\n for (const role of filter.roles) {\n // Use JSON contains - works for PostgreSQL (jsonb) and MySQL (json)\n // For SQLite, we fall back to LIKE on the JSON string\n query = query.whereRaw(\n this.knex.client.config.client === 'sqlite3' ? `roles LIKE ?` : `roles @> ?`,\n this.knex.client.config.client === 'sqlite3'\n ? [`%\"${role}\"%`]\n : [JSON.stringify([role])]\n );\n }\n }\n\n // HasAnyRole - user must have at least ONE of specified roles\n if (filter.hasAnyRole && filter.hasAnyRole.length > 0) {\n query = query.where((builder: Knex.QueryBuilder) => {\n for (const role of filter.hasAnyRole!) {\n builder.orWhereRaw(\n this.knex.client.config.client === 'sqlite3'\n ? `roles LIKE ?`\n : `roles @> ?`,\n this.knex.client.config.client === 'sqlite3'\n ? [`%\"${role}\"%`]\n : [JSON.stringify([role])]\n );\n }\n });\n }\n\n // Apply pagination\n if (options.cursor) {\n const cursorData = JSON.parse(Buffer.from(options.cursor, 'base64').toString());\n query = query.where('id', '>', cursorData.lastId);\n }\n\n // Order by id for consistent pagination\n query = query.orderBy('id', 'asc').limit(limit + 1);\n\n const rows = await query;\n const hasMore = rows.length > limit;\n const resultRows = hasMore ? rows.slice(0, limit) : rows;\n const users = resultRows.map((row: Record<string, unknown>) => this.fromDbUser(row));\n\n let nextCursor: string | undefined;\n if (hasMore && resultRows.length > 0) {\n const lastUser = resultRows[resultRows.length - 1];\n nextCursor = Buffer.from(JSON.stringify({ lastId: lastUser.id })).toString('base64');\n }\n\n return { users, nextCursor };\n }\n\n // ==================== Session Methods ====================\n\n async createSession(session: CreateSessionInput): Promise<Session> {\n const dbSession = this.toDbSession(session);\n const [insertedId] = await this.sessionsTable.insert(dbSession);\n const finalId = session.id ?? insertedId;\n return { ...session, id: finalId } as Session;\n }\n\n async getSessionById(id: SessionId): Promise<Session | null> {\n const row = await this.sessionsTable.where({ id }).first();\n return row ? this.fromDbSession(row) : null;\n }\n\n async getSessionsByUserId(userId: UserId): Promise<Session[]> {\n const rows = await this.sessionsTable.where({ user_id: userId });\n return rows.map((row: Record<string, unknown>) => this.fromDbSession(row));\n }\n\n async updateSession(id: SessionId, updates: Partial<Session>): Promise<void> {\n const dbUpdates = this.toDbSessionUpdates(updates);\n\n if (Object.keys(dbUpdates).length === 0) {\n return;\n }\n\n await this.sessionsTable.where({ id }).update(dbUpdates);\n }\n\n async deleteSession(id: SessionId): Promise<void> {\n await this.sessionsTable.where({ id }).delete();\n }\n\n async deleteAllUserSessions(userId: UserId): Promise<void> {\n await this.sessionsTable.where({ user_id: userId }).delete();\n }\n\n async deleteExpiredSessions(): Promise<void> {\n const now = Date.now();\n await this.sessionsTable.where('expires_at', '<', now).delete();\n }\n\n // ==================== User Mapping Helpers ====================\n\n /**\n * Convert User object to database row format (snake_case)\n */\n private toDbUser(user: CreateUserInput): Record<string, unknown> {\n const row: Record<string, unknown> = {\n email: user.email,\n first_name: user.firstName ?? null,\n last_name: user.lastName ?? null,\n password_hash: user.passwordHash,\n roles: JSON.stringify(user.roles),\n is_email_verified: user.isEmailVerified ? 1 : 0,\n verification_token: user.verificationToken ?? null,\n reset_password_token: user.resetPasswordToken ?? null,\n reset_password_expires: user.resetPasswordExpires ?? null,\n created_at: user.createdAt ?? Date.now(),\n updated_at: user.updatedAt ?? Date.now(),\n };\n // Only include id if provided (for string UUIDs)\n // Omit for auto-increment DBs\n if (user.id !== undefined) {\n row.id = user.id;\n }\n return row;\n }\n\n /**\n * Convert database row to User object (camelCase)\n */\n private fromDbUser(row: Record<string, unknown>): User {\n return {\n id: row.id as string | number,\n email: row.email as string,\n firstName: row.first_name as string | undefined,\n lastName: row.last_name as string | undefined,\n passwordHash: row.password_hash as string,\n roles: typeof row.roles === 'string' ? JSON.parse(row.roles) : row.roles,\n isEmailVerified: Boolean(row.is_email_verified),\n verificationToken: row.verification_token as string | undefined,\n resetPasswordToken: row.reset_password_token as string | undefined,\n resetPasswordExpires: row.reset_password_expires as number | undefined,\n createdAt: row.created_at as number,\n updatedAt: row.updated_at as number,\n };\n }\n\n /**\n * Convert partial User updates to database format\n */\n private toDbUpdates(updates: Partial<User>): Record<string, unknown> {\n const dbUpdates: Record<string, unknown> = {};\n\n if (updates.email !== undefined) {\n dbUpdates.email = updates.email;\n }\n if ('firstName' in updates) {\n dbUpdates.first_name = updates.firstName ?? null;\n }\n if ('lastName' in updates) {\n dbUpdates.last_name = updates.lastName ?? null;\n }\n if (updates.passwordHash !== undefined) {\n dbUpdates.password_hash = updates.passwordHash;\n }\n if (updates.roles !== undefined) {\n dbUpdates.roles = JSON.stringify(updates.roles);\n }\n if (updates.isEmailVerified !== undefined) {\n dbUpdates.is_email_verified = updates.isEmailVerified ? 1 : 0;\n }\n if ('verificationToken' in updates) {\n dbUpdates.verification_token = updates.verificationToken ?? null;\n }\n if ('resetPasswordToken' in updates) {\n dbUpdates.reset_password_token = updates.resetPasswordToken ?? null;\n }\n if ('resetPasswordExpires' in updates) {\n dbUpdates.reset_password_expires = updates.resetPasswordExpires ?? null;\n }\n if (updates.updatedAt !== undefined) {\n dbUpdates.updated_at = updates.updatedAt;\n }\n\n return dbUpdates;\n }\n\n // ==================== Session Mapping Helpers ====================\n\n /**\n * Convert Session object to database row format (snake_case)\n */\n private toDbSession(session: CreateSessionInput): Record<string, unknown> {\n const row: Record<string, unknown> = {\n user_id: session.userId,\n refresh_token_hash: session.refreshTokenHash,\n user_agent: session.userAgent ?? null,\n ip_address: session.ipAddress ?? null,\n device_name: session.deviceName ?? null,\n created_at: session.createdAt,\n last_used_at: session.lastUsedAt,\n expires_at: session.expiresAt,\n };\n // Only include id if provided (for string UUIDs)\n // Omit for auto-increment DBs\n if (session.id !== undefined) {\n row.id = session.id;\n }\n return row;\n }\n\n /**\n * Convert database row to Session object (camelCase)\n */\n private fromDbSession(row: Record<string, unknown>): Session {\n return {\n id: row.id as string | number,\n userId: row.user_id as string | number,\n refreshTokenHash: row.refresh_token_hash as string,\n userAgent: row.user_agent as string | undefined,\n ipAddress: row.ip_address as string | undefined,\n deviceName: row.device_name as string | undefined,\n createdAt: row.created_at as number,\n lastUsedAt: row.last_used_at as number,\n expiresAt: row.expires_at as number,\n };\n }\n\n /**\n * Convert partial Session updates to database format\n */\n private toDbSessionUpdates(updates: Partial<Session>): Record<string, unknown> {\n const dbUpdates: Record<string, unknown> = {};\n\n if (updates.refreshTokenHash !== undefined) {\n dbUpdates.refresh_token_hash = updates.refreshTokenHash;\n }\n if ('userAgent' in updates) {\n dbUpdates.user_agent = updates.userAgent ?? null;\n }\n if ('ipAddress' in updates) {\n dbUpdates.ip_address = updates.ipAddress ?? null;\n }\n if ('deviceName' in updates) {\n dbUpdates.device_name = updates.deviceName ?? null;\n }\n if (updates.lastUsedAt !== undefined) {\n dbUpdates.last_used_at = updates.lastUsedAt;\n }\n if (updates.expiresAt !== undefined) {\n dbUpdates.expires_at = updates.expiresAt;\n }\n\n return dbUpdates;\n }\n}\n","import type { Knex } from 'knex';\n\n/**\n * SQL migration helper to create the users table\n * Can be used with Knex migrations\n *\n * @example\n * ```ts\n * // In your migration file\n * import { createUsersTableMigration, createSessionsTableMigration } from '@xcelsior/auth-adapter-knex';\n *\n * export async function up(knex: Knex): Promise<void> {\n * await createUsersTableMigration(knex, 'users');\n * await createSessionsTableMigration(knex, 'sessions');\n * }\n *\n * export async function down(knex: Knex): Promise<void> {\n * await knex.schema.dropTableIfExists('sessions');\n * await knex.schema.dropTableIfExists('users');\n * }\n * ```\n */\nexport async function createUsersTableMigration(\n knex: Knex,\n tableName: string,\n schema?: string\n): Promise<void> {\n const schemaBuilder = schema ? knex.schema.withSchema(schema) : knex.schema;\n\n await schemaBuilder.createTable(tableName, (table) => {\n table.string('id').primary();\n table.string('email').notNullable().unique();\n table.string('first_name').nullable();\n table.string('last_name').nullable();\n table.string('password_hash').notNullable();\n table.jsonb('roles').notNullable();\n table.boolean('is_email_verified').notNullable().defaultTo(false);\n table.string('verification_token').nullable();\n table.string('reset_password_token').nullable();\n table.bigInteger('reset_password_expires').nullable();\n table.bigInteger('created_at').notNullable();\n table.bigInteger('updated_at').notNullable();\n\n // Indexes for common lookups\n table.index('email');\n table.index('verification_token');\n table.index('reset_password_token');\n });\n}\n\n/**\n * SQL migration helper to create the sessions table\n * Can be used with Knex migrations\n */\nexport async function createSessionsTableMigration(\n knex: Knex,\n tableName: string,\n schema?: string\n): Promise<void> {\n const schemaBuilder = schema ? knex.schema.withSchema(schema) : knex.schema;\n\n await schemaBuilder.createTable(tableName, (table) => {\n table.string('id').primary();\n table.string('user_id').notNullable();\n table.string('refresh_token_hash').notNullable();\n table.string('user_agent').nullable();\n table.string('ip_address').nullable();\n table.string('device_name').nullable();\n table.bigInteger('created_at').notNullable();\n table.bigInteger('last_used_at').notNullable();\n table.bigInteger('expires_at').notNullable();\n\n // Index for querying sessions by user\n table.index('user_id');\n // Index for cleaning up expired sessions\n table.index('expires_at');\n });\n}\n"],"mappings":";AAyBO,IAAM,sBAAN,MAAsD;AAAA,EAMzD,YAAY,QAAoB;AAC5B,SAAK,OAAO,OAAO;AACnB,SAAK,YAAY,OAAO;AACxB,SAAK,oBAAoB,OAAO;AAChC,SAAK,SAAS,OAAO;AAAA,EACzB;AAAA,EAEA,IAAY,QAAQ;AAChB,UAAM,QAAQ,KAAK,KAAK,KAAK,SAAS;AACtC,QAAI,KAAK,QAAQ;AACb,aAAO,MAAM,WAAW,KAAK,MAAM;AAAA,IACvC;AACA,WAAO;AAAA,EACX;AAAA,EAEA,IAAY,gBAAgB;AACxB,UAAM,QAAQ,KAAK,KAAK,KAAK,iBAAiB;AAC9C,QAAI,KAAK,QAAQ;AACb,aAAO,MAAM,WAAW,KAAK,MAAM;AAAA,IACvC;AACA,WAAO;AAAA,EACX;AAAA;AAAA,EAIA,MAAM,WAAW,MAAsC;AACnD,UAAM,SAAS,KAAK,SAAS,IAAI;AACjC,UAAM,CAAC,UAAU,IAAI,MAAM,KAAK,MAAM,OAAO,MAAM;AAGnD,UAAM,UAAU,KAAK,MAAM;AAC3B,WAAO,EAAE,GAAG,MAAM,IAAI,QAAQ;AAAA,EAClC;AAAA,EAEA,MAAM,YAAY,IAAkC;AAChD,UAAM,MAAM,MAAM,KAAK,MAAM,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM;AACjD,WAAO,MAAM,KAAK,WAAW,GAAG,IAAI;AAAA,EACxC;AAAA,EAEA,MAAM,eAAe,OAAqC;AACtD,UAAM,MAAM,MAAM,KAAK,MAAM,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;AACpD,WAAO,MAAM,KAAK,WAAW,GAAG,IAAI;AAAA,EACxC;AAAA,EAEA,MAAM,4BAA4B,oBAAkD;AAChF,UAAM,MAAM,MAAM,KAAK,MAAM,MAAM,EAAE,sBAAsB,mBAAmB,CAAC,EAAE,MAAM;AACvF,WAAO,MAAM,KAAK,WAAW,GAAG,IAAI;AAAA,EACxC;AAAA,EAEA,MAAM,0BAA0B,kBAAgD;AAC5E,UAAM,MAAM,MAAM,KAAK,MAAM,MAAM,EAAE,oBAAoB,iBAAiB,CAAC,EAAE,MAAM;AACnF,WAAO,MAAM,KAAK,WAAW,GAAG,IAAI;AAAA,EACxC;AAAA,EAEA,MAAM,WAAW,IAAY,SAAuC;AAChE,UAAM,YAAY,KAAK,YAAY,OAAO;AAE1C,QAAI,OAAO,KAAK,SAAS,EAAE,WAAW,GAAG;AACrC;AAAA,IACJ;AAEA,UAAM,KAAK,MAAM,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,SAAS;AAAA,EACnD;AAAA,EAEA,MAAM,WAAW,IAA2B;AAExC,UAAM,KAAK,sBAAsB,EAAE;AAEnC,UAAM,KAAK,MAAM,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO;AAAA,EAC1C;AAAA,EAEA,MAAM,UAAU,QAAoB,UAA4B,CAAC,GAA6B;AAC1F,UAAM,QAAQ,QAAQ,SAAS;AAE/B,QAAI,QAAQ,KAAK,MAAM,MAAM;AAG7B,QAAI,OAAO,OAAO;AACd,cAAQ,MAAM,MAAM,SAAS,OAAO,KAAK;AAAA,IAC7C;AAGA,QAAI,OAAO,eAAe;AACtB,cAAQ,MAAM,MAAM,SAAS,QAAQ,IAAI,OAAO,aAAa,GAAG;AAAA,IACpE;AAGA,QAAI,OAAO,oBAAoB,QAAW;AACtC,cAAQ,MAAM,MAAM,qBAAqB,OAAO,eAAe;AAAA,IACnE;AAGA,QAAI,OAAO,SAAS,OAAO,MAAM,SAAS,GAAG;AACzC,iBAAW,QAAQ,OAAO,OAAO;AAG7B,gBAAQ,MAAM;AAAA,UACV,KAAK,KAAK,OAAO,OAAO,WAAW,YAAY,iBAAiB;AAAA,UAChE,KAAK,KAAK,OAAO,OAAO,WAAW,YAC7B,CAAC,KAAK,IAAI,IAAI,IACd,CAAC,KAAK,UAAU,CAAC,IAAI,CAAC,CAAC;AAAA,QACjC;AAAA,MACJ;AAAA,IACJ;AAGA,QAAI,OAAO,cAAc,OAAO,WAAW,SAAS,GAAG;AACnD,cAAQ,MAAM,MAAM,CAAC,YAA+B;AAChD,mBAAW,QAAQ,OAAO,YAAa;AACnC,kBAAQ;AAAA,YACJ,KAAK,KAAK,OAAO,OAAO,WAAW,YAC7B,iBACA;AAAA,YACN,KAAK,KAAK,OAAO,OAAO,WAAW,YAC7B,CAAC,KAAK,IAAI,IAAI,IACd,CAAC,KAAK,UAAU,CAAC,IAAI,CAAC,CAAC;AAAA,UACjC;AAAA,QACJ;AAAA,MACJ,CAAC;AAAA,IACL;AAGA,QAAI,QAAQ,QAAQ;AAChB,YAAM,aAAa,KAAK,MAAM,OAAO,KAAK,QAAQ,QAAQ,QAAQ,EAAE,SAAS,CAAC;AAC9E,cAAQ,MAAM,MAAM,MAAM,KAAK,WAAW,MAAM;AAAA,IACpD;AAGA,YAAQ,MAAM,QAAQ,MAAM,KAAK,EAAE,MAAM,QAAQ,CAAC;AAElD,UAAM,OAAO,MAAM;AACnB,UAAM,UAAU,KAAK,SAAS;AAC9B,UAAM,aAAa,UAAU,KAAK,MAAM,GAAG,KAAK,IAAI;AACpD,UAAM,QAAQ,WAAW,IAAI,CAAC,QAAiC,KAAK,WAAW,GAAG,CAAC;AAEnF,QAAI;AACJ,QAAI,WAAW,WAAW,SAAS,GAAG;AAClC,YAAM,WAAW,WAAW,WAAW,SAAS,CAAC;AACjD,mBAAa,OAAO,KAAK,KAAK,UAAU,EAAE,QAAQ,SAAS,GAAG,CAAC,CAAC,EAAE,SAAS,QAAQ;AAAA,IACvF;AAEA,WAAO,EAAE,OAAO,WAAW;AAAA,EAC/B;AAAA;AAAA,EAIA,MAAM,cAAc,SAA+C;AAC/D,UAAM,YAAY,KAAK,YAAY,OAAO;AAC1C,UAAM,CAAC,UAAU,IAAI,MAAM,KAAK,cAAc,OAAO,SAAS;AAC9D,UAAM,UAAU,QAAQ,MAAM;AAC9B,WAAO,EAAE,GAAG,SAAS,IAAI,QAAQ;AAAA,EACrC;AAAA,EAEA,MAAM,eAAe,IAAwC;AACzD,UAAM,MAAM,MAAM,KAAK,cAAc,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM;AACzD,WAAO,MAAM,KAAK,cAAc,GAAG,IAAI;AAAA,EAC3C;AAAA,EAEA,MAAM,oBAAoB,QAAoC;AAC1D,UAAM,OAAO,MAAM,KAAK,cAAc,MAAM,EAAE,SAAS,OAAO,CAAC;AAC/D,WAAO,KAAK,IAAI,CAAC,QAAiC,KAAK,cAAc,GAAG,CAAC;AAAA,EAC7E;AAAA,EAEA,MAAM,cAAc,IAAe,SAA0C;AACzE,UAAM,YAAY,KAAK,mBAAmB,OAAO;AAEjD,QAAI,OAAO,KAAK,SAAS,EAAE,WAAW,GAAG;AACrC;AAAA,IACJ;AAEA,UAAM,KAAK,cAAc,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,SAAS;AAAA,EAC3D;AAAA,EAEA,MAAM,cAAc,IAA8B;AAC9C,UAAM,KAAK,cAAc,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO;AAAA,EAClD;AAAA,EAEA,MAAM,sBAAsB,QAA+B;AACvD,UAAM,KAAK,cAAc,MAAM,EAAE,SAAS,OAAO,CAAC,EAAE,OAAO;AAAA,EAC/D;AAAA,EAEA,MAAM,wBAAuC;AACzC,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,KAAK,cAAc,MAAM,cAAc,KAAK,GAAG,EAAE,OAAO;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,SAAS,MAAgD;AAC7D,UAAM,MAA+B;AAAA,MACjC,OAAO,KAAK;AAAA,MACZ,YAAY,KAAK,aAAa;AAAA,MAC9B,WAAW,KAAK,YAAY;AAAA,MAC5B,eAAe,KAAK;AAAA,MACpB,OAAO,KAAK,UAAU,KAAK,KAAK;AAAA,MAChC,mBAAmB,KAAK,kBAAkB,IAAI;AAAA,MAC9C,oBAAoB,KAAK,qBAAqB;AAAA,MAC9C,sBAAsB,KAAK,sBAAsB;AAAA,MACjD,wBAAwB,KAAK,wBAAwB;AAAA,MACrD,YAAY,KAAK,aAAa,KAAK,IAAI;AAAA,MACvC,YAAY,KAAK,aAAa,KAAK,IAAI;AAAA,IAC3C;AAGA,QAAI,KAAK,OAAO,QAAW;AACvB,UAAI,KAAK,KAAK;AAAA,IAClB;AACA,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA,EAKQ,WAAW,KAAoC;AACnD,WAAO;AAAA,MACH,IAAI,IAAI;AAAA,MACR,OAAO,IAAI;AAAA,MACX,WAAW,IAAI;AAAA,MACf,UAAU,IAAI;AAAA,MACd,cAAc,IAAI;AAAA,MAClB,OAAO,OAAO,IAAI,UAAU,WAAW,KAAK,MAAM,IAAI,KAAK,IAAI,IAAI;AAAA,MACnE,iBAAiB,QAAQ,IAAI,iBAAiB;AAAA,MAC9C,mBAAmB,IAAI;AAAA,MACvB,oBAAoB,IAAI;AAAA,MACxB,sBAAsB,IAAI;AAAA,MAC1B,WAAW,IAAI;AAAA,MACf,WAAW,IAAI;AAAA,IACnB;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAY,SAAiD;AACjE,UAAM,YAAqC,CAAC;AAE5C,QAAI,QAAQ,UAAU,QAAW;AAC7B,gBAAU,QAAQ,QAAQ;AAAA,IAC9B;AACA,QAAI,eAAe,SAAS;AACxB,gBAAU,aAAa,QAAQ,aAAa;AAAA,IAChD;AACA,QAAI,cAAc,SAAS;AACvB,gBAAU,YAAY,QAAQ,YAAY;AAAA,IAC9C;AACA,QAAI,QAAQ,iBAAiB,QAAW;AACpC,gBAAU,gBAAgB,QAAQ;AAAA,IACtC;AACA,QAAI,QAAQ,UAAU,QAAW;AAC7B,gBAAU,QAAQ,KAAK,UAAU,QAAQ,KAAK;AAAA,IAClD;AACA,QAAI,QAAQ,oBAAoB,QAAW;AACvC,gBAAU,oBAAoB,QAAQ,kBAAkB,IAAI;AAAA,IAChE;AACA,QAAI,uBAAuB,SAAS;AAChC,gBAAU,qBAAqB,QAAQ,qBAAqB;AAAA,IAChE;AACA,QAAI,wBAAwB,SAAS;AACjC,gBAAU,uBAAuB,QAAQ,sBAAsB;AAAA,IACnE;AACA,QAAI,0BAA0B,SAAS;AACnC,gBAAU,yBAAyB,QAAQ,wBAAwB;AAAA,IACvE;AACA,QAAI,QAAQ,cAAc,QAAW;AACjC,gBAAU,aAAa,QAAQ;AAAA,IACnC;AAEA,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,YAAY,SAAsD;AACtE,UAAM,MAA+B;AAAA,MACjC,SAAS,QAAQ;AAAA,MACjB,oBAAoB,QAAQ;AAAA,MAC5B,YAAY,QAAQ,aAAa;AAAA,MACjC,YAAY,QAAQ,aAAa;AAAA,MACjC,aAAa,QAAQ,cAAc;AAAA,MACnC,YAAY,QAAQ;AAAA,MACpB,cAAc,QAAQ;AAAA,MACtB,YAAY,QAAQ;AAAA,IACxB;AAGA,QAAI,QAAQ,OAAO,QAAW;AAC1B,UAAI,KAAK,QAAQ;AAAA,IACrB;AACA,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,KAAuC;AACzD,WAAO;AAAA,MACH,IAAI,IAAI;AAAA,MACR,QAAQ,IAAI;AAAA,MACZ,kBAAkB,IAAI;AAAA,MACtB,WAAW,IAAI;AAAA,MACf,WAAW,IAAI;AAAA,MACf,YAAY,IAAI;AAAA,MAChB,WAAW,IAAI;AAAA,MACf,YAAY,IAAI;AAAA,MAChB,WAAW,IAAI;AAAA,IACnB;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAmB,SAAoD;AAC3E,UAAM,YAAqC,CAAC;AAE5C,QAAI,QAAQ,qBAAqB,QAAW;AACxC,gBAAU,qBAAqB,QAAQ;AAAA,IAC3C;AACA,QAAI,eAAe,SAAS;AACxB,gBAAU,aAAa,QAAQ,aAAa;AAAA,IAChD;AACA,QAAI,eAAe,SAAS;AACxB,gBAAU,aAAa,QAAQ,aAAa;AAAA,IAChD;AACA,QAAI,gBAAgB,SAAS;AACzB,gBAAU,cAAc,QAAQ,cAAc;AAAA,IAClD;AACA,QAAI,QAAQ,eAAe,QAAW;AAClC,gBAAU,eAAe,QAAQ;AAAA,IACrC;AACA,QAAI,QAAQ,cAAc,QAAW;AACjC,gBAAU,aAAa,QAAQ;AAAA,IACnC;AAEA,WAAO;AAAA,EACX;AACJ;;;AC9VA,eAAsB,0BAClB,MACA,WACA,QACa;AACb,QAAM,gBAAgB,SAAS,KAAK,OAAO,WAAW,MAAM,IAAI,KAAK;AAErE,QAAM,cAAc,YAAY,WAAW,CAAC,UAAU;AAClD,UAAM,OAAO,IAAI,EAAE,QAAQ;AAC3B,UAAM,OAAO,OAAO,EAAE,YAAY,EAAE,OAAO;AAC3C,UAAM,OAAO,YAAY,EAAE,SAAS;AACpC,UAAM,OAAO,WAAW,EAAE,SAAS;AACnC,UAAM,OAAO,eAAe,EAAE,YAAY;AAC1C,UAAM,MAAM,OAAO,EAAE,YAAY;AACjC,UAAM,QAAQ,mBAAmB,EAAE,YAAY,EAAE,UAAU,KAAK;AAChE,UAAM,OAAO,oBAAoB,EAAE,SAAS;AAC5C,UAAM,OAAO,sBAAsB,EAAE,SAAS;AAC9C,UAAM,WAAW,wBAAwB,EAAE,SAAS;AACpD,UAAM,WAAW,YAAY,EAAE,YAAY;AAC3C,UAAM,WAAW,YAAY,EAAE,YAAY;AAG3C,UAAM,MAAM,OAAO;AACnB,UAAM,MAAM,oBAAoB;AAChC,UAAM,MAAM,sBAAsB;AAAA,EACtC,CAAC;AACL;AAMA,eAAsB,6BAClB,MACA,WACA,QACa;AACb,QAAM,gBAAgB,SAAS,KAAK,OAAO,WAAW,MAAM,IAAI,KAAK;AAErE,QAAM,cAAc,YAAY,WAAW,CAAC,UAAU;AAClD,UAAM,OAAO,IAAI,EAAE,QAAQ;AAC3B,UAAM,OAAO,SAAS,EAAE,YAAY;AACpC,UAAM,OAAO,oBAAoB,EAAE,YAAY;AAC/C,UAAM,OAAO,YAAY,EAAE,SAAS;AACpC,UAAM,OAAO,YAAY,EAAE,SAAS;AACpC,UAAM,OAAO,aAAa,EAAE,SAAS;AACrC,UAAM,WAAW,YAAY,EAAE,YAAY;AAC3C,UAAM,WAAW,cAAc,EAAE,YAAY;AAC7C,UAAM,WAAW,YAAY,EAAE,YAAY;AAG3C,UAAM,MAAM,SAAS;AAErB,UAAM,MAAM,YAAY;AAAA,EAC5B,CAAC;AACL;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcelsior/auth-adapter-knex",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Knex storage adapter for @xcelsior/auth (PostgreSQL, MySQL, SQLite, etc.)",
5
5
  "exports": {
6
6
  ".": {
@@ -15,7 +15,7 @@
15
15
  },
16
16
  "peerDependencies": {
17
17
  "knex": "^3.1.0",
18
- "@xcelsior/auth": "1.0.0"
18
+ "@xcelsior/auth": "1.1.0"
19
19
  },
20
20
  "scripts": {
21
21
  "build": "tsup && tsc --noEmit",
package/src/knex.ts CHANGED
@@ -1,11 +1,15 @@
1
1
  import type { Knex } from 'knex';
2
2
  import type {
3
+ CreateSessionInput,
4
+ CreateUserInput,
5
+ FindUsersOptions,
6
+ FindUsersResult,
3
7
  IStorageProvider,
4
- User,
5
8
  Session,
9
+ SessionId,
10
+ User,
6
11
  UserFilter,
7
- FindUsersOptions,
8
- FindUsersResult,
12
+ UserId,
9
13
  } from '@xcelsior/auth';
10
14
 
11
15
  export interface KnexConfig {
@@ -50,11 +54,16 @@ export class KnexStorageProvider implements IStorageProvider {
50
54
 
51
55
  // ==================== User Methods ====================
52
56
 
53
- async createUser(user: User): Promise<void> {
54
- await this.table.insert(this.toDbUser(user));
57
+ async createUser(user: CreateUserInput): Promise<User> {
58
+ const dbUser = this.toDbUser(user);
59
+ const [insertedId] = await this.table.insert(dbUser);
60
+ // For auto-increment DBs, insertedId is the new ID
61
+ // For string PKs, it returns 0, so use the original id
62
+ const finalId = user.id ?? insertedId;
63
+ return { ...user, id: finalId } as User;
55
64
  }
56
65
 
57
- async getUserById(id: string): Promise<User | null> {
66
+ async getUserById(id: UserId): Promise<User | null> {
58
67
  const row = await this.table.where({ id }).first();
59
68
  return row ? this.fromDbUser(row) : null;
60
69
  }
@@ -74,7 +83,7 @@ export class KnexStorageProvider implements IStorageProvider {
74
83
  return row ? this.fromDbUser(row) : null;
75
84
  }
76
85
 
77
- async updateUser(id: string, updates: Partial<User>): Promise<void> {
86
+ async updateUser(id: UserId, updates: Partial<User>): Promise<void> {
78
87
  const dbUpdates = this.toDbUpdates(updates);
79
88
 
80
89
  if (Object.keys(dbUpdates).length === 0) {
@@ -84,7 +93,7 @@ export class KnexStorageProvider implements IStorageProvider {
84
93
  await this.table.where({ id }).update(dbUpdates);
85
94
  }
86
95
 
87
- async deleteUser(id: string): Promise<void> {
96
+ async deleteUser(id: UserId): Promise<void> {
88
97
  // Delete all user sessions first
89
98
  await this.deleteAllUserSessions(id);
90
99
  // Then delete the user
@@ -166,21 +175,24 @@ export class KnexStorageProvider implements IStorageProvider {
166
175
 
167
176
  // ==================== Session Methods ====================
168
177
 
169
- async createSession(session: Session): Promise<void> {
170
- await this.sessionsTable.insert(this.toDbSession(session));
178
+ async createSession(session: CreateSessionInput): Promise<Session> {
179
+ const dbSession = this.toDbSession(session);
180
+ const [insertedId] = await this.sessionsTable.insert(dbSession);
181
+ const finalId = session.id ?? insertedId;
182
+ return { ...session, id: finalId } as Session;
171
183
  }
172
184
 
173
- async getSessionById(id: string): Promise<Session | null> {
185
+ async getSessionById(id: SessionId): Promise<Session | null> {
174
186
  const row = await this.sessionsTable.where({ id }).first();
175
187
  return row ? this.fromDbSession(row) : null;
176
188
  }
177
189
 
178
- async getSessionsByUserId(userId: string): Promise<Session[]> {
190
+ async getSessionsByUserId(userId: UserId): Promise<Session[]> {
179
191
  const rows = await this.sessionsTable.where({ user_id: userId });
180
192
  return rows.map((row: Record<string, unknown>) => this.fromDbSession(row));
181
193
  }
182
194
 
183
- async updateSession(id: string, updates: Partial<Session>): Promise<void> {
195
+ async updateSession(id: SessionId, updates: Partial<Session>): Promise<void> {
184
196
  const dbUpdates = this.toDbSessionUpdates(updates);
185
197
 
186
198
  if (Object.keys(dbUpdates).length === 0) {
@@ -190,11 +202,11 @@ export class KnexStorageProvider implements IStorageProvider {
190
202
  await this.sessionsTable.where({ id }).update(dbUpdates);
191
203
  }
192
204
 
193
- async deleteSession(id: string): Promise<void> {
205
+ async deleteSession(id: SessionId): Promise<void> {
194
206
  await this.sessionsTable.where({ id }).delete();
195
207
  }
196
208
 
197
- async deleteAllUserSessions(userId: string): Promise<void> {
209
+ async deleteAllUserSessions(userId: UserId): Promise<void> {
198
210
  await this.sessionsTable.where({ user_id: userId }).delete();
199
211
  }
200
212
 
@@ -208,21 +220,26 @@ export class KnexStorageProvider implements IStorageProvider {
208
220
  /**
209
221
  * Convert User object to database row format (snake_case)
210
222
  */
211
- private toDbUser(user: User): Record<string, unknown> {
212
- return {
213
- id: user.id,
223
+ private toDbUser(user: CreateUserInput): Record<string, unknown> {
224
+ const row: Record<string, unknown> = {
214
225
  email: user.email,
215
226
  first_name: user.firstName ?? null,
216
227
  last_name: user.lastName ?? null,
217
228
  password_hash: user.passwordHash,
218
229
  roles: JSON.stringify(user.roles),
219
- is_email_verified: user.isEmailVerified,
230
+ is_email_verified: user.isEmailVerified ? 1 : 0,
220
231
  verification_token: user.verificationToken ?? null,
221
232
  reset_password_token: user.resetPasswordToken ?? null,
222
233
  reset_password_expires: user.resetPasswordExpires ?? null,
223
- created_at: user.createdAt,
224
- updated_at: user.updatedAt,
234
+ created_at: user.createdAt ?? Date.now(),
235
+ updated_at: user.updatedAt ?? Date.now(),
225
236
  };
237
+ // Only include id if provided (for string UUIDs)
238
+ // Omit for auto-increment DBs
239
+ if (user.id !== undefined) {
240
+ row.id = user.id;
241
+ }
242
+ return row;
226
243
  }
227
244
 
228
245
  /**
@@ -230,7 +247,7 @@ export class KnexStorageProvider implements IStorageProvider {
230
247
  */
231
248
  private fromDbUser(row: Record<string, unknown>): User {
232
249
  return {
233
- id: row.id as string,
250
+ id: row.id as string | number,
234
251
  email: row.email as string,
235
252
  firstName: row.first_name as string | undefined,
236
253
  lastName: row.last_name as string | undefined,
@@ -267,7 +284,7 @@ export class KnexStorageProvider implements IStorageProvider {
267
284
  dbUpdates.roles = JSON.stringify(updates.roles);
268
285
  }
269
286
  if (updates.isEmailVerified !== undefined) {
270
- dbUpdates.is_email_verified = updates.isEmailVerified;
287
+ dbUpdates.is_email_verified = updates.isEmailVerified ? 1 : 0;
271
288
  }
272
289
  if ('verificationToken' in updates) {
273
290
  dbUpdates.verification_token = updates.verificationToken ?? null;
@@ -290,9 +307,8 @@ export class KnexStorageProvider implements IStorageProvider {
290
307
  /**
291
308
  * Convert Session object to database row format (snake_case)
292
309
  */
293
- private toDbSession(session: Session): Record<string, unknown> {
294
- return {
295
- id: session.id,
310
+ private toDbSession(session: CreateSessionInput): Record<string, unknown> {
311
+ const row: Record<string, unknown> = {
296
312
  user_id: session.userId,
297
313
  refresh_token_hash: session.refreshTokenHash,
298
314
  user_agent: session.userAgent ?? null,
@@ -302,6 +318,12 @@ export class KnexStorageProvider implements IStorageProvider {
302
318
  last_used_at: session.lastUsedAt,
303
319
  expires_at: session.expiresAt,
304
320
  };
321
+ // Only include id if provided (for string UUIDs)
322
+ // Omit for auto-increment DBs
323
+ if (session.id !== undefined) {
324
+ row.id = session.id;
325
+ }
326
+ return row;
305
327
  }
306
328
 
307
329
  /**
@@ -309,8 +331,8 @@ export class KnexStorageProvider implements IStorageProvider {
309
331
  */
310
332
  private fromDbSession(row: Record<string, unknown>): Session {
311
333
  return {
312
- id: row.id as string,
313
- userId: row.user_id as string,
334
+ id: row.id as string | number,
335
+ userId: row.user_id as string | number,
314
336
  refreshTokenHash: row.refresh_token_hash as string,
315
337
  userAgent: row.user_agent as string | undefined,
316
338
  ipAddress: row.ip_address as string | undefined,