@xcelsior/auth-adapter-knex 1.0.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.
@@ -0,0 +1,22 @@
1
+
2
+ > @xcelsior/auth-adapter-knex@1.0.0 build /home/circleci/repo/packages/services/auth-adapter-knex
3
+ > tsup && tsc --noEmit
4
+
5
+ CLI Building entry: src/index.ts
6
+ CLI Using tsconfig: tsconfig.json
7
+ CLI tsup v8.5.1
8
+ CLI Using tsup config: /home/circleci/repo/packages/services/auth-adapter-knex/tsup.config.ts
9
+ CLI Target: es2020
10
+ CLI Cleaning output folder
11
+ CJS Build start
12
+ ESM Build start
13
+ CJS dist/index.js 11.45 KB
14
+ CJS dist/index.js.map 22.55 KB
15
+ CJS ⚡️ Build success in 159ms
16
+ ESM dist/index.mjs 10.31 KB
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
package/biome.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "//"
3
+ }
@@ -0,0 +1,90 @@
1
+ import { Knex } from 'knex';
2
+ import { IStorageProvider, User, UserFilter, FindUsersOptions, FindUsersResult, Session } from '@xcelsior/auth';
3
+
4
+ interface KnexConfig {
5
+ /** Pre-configured Knex instance */
6
+ knex: Knex;
7
+ /** Table name for users */
8
+ tableName: string;
9
+ /** Table name for sessions */
10
+ sessionsTableName: string;
11
+ /** Optional schema (for PostgreSQL) */
12
+ schema?: string;
13
+ }
14
+ declare class KnexStorageProvider implements IStorageProvider {
15
+ private knex;
16
+ private tableName;
17
+ private sessionsTableName;
18
+ private schema?;
19
+ constructor(config: KnexConfig);
20
+ private get table();
21
+ private get sessionsTable();
22
+ createUser(user: User): Promise<void>;
23
+ getUserById(id: string): Promise<User | null>;
24
+ getUserByEmail(email: string): Promise<User | null>;
25
+ getUserByResetPasswordToken(resetPasswordToken: string): Promise<User | null>;
26
+ getUserByVerifyEmailToken(verifyEmailToken: string): Promise<User | null>;
27
+ updateUser(id: string, updates: Partial<User>): Promise<void>;
28
+ deleteUser(id: string): Promise<void>;
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>;
36
+ deleteExpiredSessions(): Promise<void>;
37
+ /**
38
+ * Convert User object to database row format (snake_case)
39
+ */
40
+ private toDbUser;
41
+ /**
42
+ * Convert database row to User object (camelCase)
43
+ */
44
+ private fromDbUser;
45
+ /**
46
+ * Convert partial User updates to database format
47
+ */
48
+ private toDbUpdates;
49
+ /**
50
+ * Convert Session object to database row format (snake_case)
51
+ */
52
+ private toDbSession;
53
+ /**
54
+ * Convert database row to Session object (camelCase)
55
+ */
56
+ private fromDbSession;
57
+ /**
58
+ * Convert partial Session updates to database format
59
+ */
60
+ private toDbSessionUpdates;
61
+ }
62
+
63
+ /**
64
+ * SQL migration helper to create the users table
65
+ * Can be used with Knex migrations
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * // In your migration file
70
+ * import { createUsersTableMigration, createSessionsTableMigration } from '@xcelsior/auth-adapter-knex';
71
+ *
72
+ * export async function up(knex: Knex): Promise<void> {
73
+ * await createUsersTableMigration(knex, 'users');
74
+ * await createSessionsTableMigration(knex, 'sessions');
75
+ * }
76
+ *
77
+ * export async function down(knex: Knex): Promise<void> {
78
+ * await knex.schema.dropTableIfExists('sessions');
79
+ * await knex.schema.dropTableIfExists('users');
80
+ * }
81
+ * ```
82
+ */
83
+ declare function createUsersTableMigration(knex: Knex, tableName: string, schema?: string): Promise<void>;
84
+ /**
85
+ * SQL migration helper to create the sessions table
86
+ * Can be used with Knex migrations
87
+ */
88
+ declare function createSessionsTableMigration(knex: Knex, tableName: string, schema?: string): Promise<void>;
89
+
90
+ export { type KnexConfig, KnexStorageProvider, createSessionsTableMigration, createUsersTableMigration };
@@ -0,0 +1,90 @@
1
+ import { Knex } from 'knex';
2
+ import { IStorageProvider, User, UserFilter, FindUsersOptions, FindUsersResult, Session } from '@xcelsior/auth';
3
+
4
+ interface KnexConfig {
5
+ /** Pre-configured Knex instance */
6
+ knex: Knex;
7
+ /** Table name for users */
8
+ tableName: string;
9
+ /** Table name for sessions */
10
+ sessionsTableName: string;
11
+ /** Optional schema (for PostgreSQL) */
12
+ schema?: string;
13
+ }
14
+ declare class KnexStorageProvider implements IStorageProvider {
15
+ private knex;
16
+ private tableName;
17
+ private sessionsTableName;
18
+ private schema?;
19
+ constructor(config: KnexConfig);
20
+ private get table();
21
+ private get sessionsTable();
22
+ createUser(user: User): Promise<void>;
23
+ getUserById(id: string): Promise<User | null>;
24
+ getUserByEmail(email: string): Promise<User | null>;
25
+ getUserByResetPasswordToken(resetPasswordToken: string): Promise<User | null>;
26
+ getUserByVerifyEmailToken(verifyEmailToken: string): Promise<User | null>;
27
+ updateUser(id: string, updates: Partial<User>): Promise<void>;
28
+ deleteUser(id: string): Promise<void>;
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>;
36
+ deleteExpiredSessions(): Promise<void>;
37
+ /**
38
+ * Convert User object to database row format (snake_case)
39
+ */
40
+ private toDbUser;
41
+ /**
42
+ * Convert database row to User object (camelCase)
43
+ */
44
+ private fromDbUser;
45
+ /**
46
+ * Convert partial User updates to database format
47
+ */
48
+ private toDbUpdates;
49
+ /**
50
+ * Convert Session object to database row format (snake_case)
51
+ */
52
+ private toDbSession;
53
+ /**
54
+ * Convert database row to Session object (camelCase)
55
+ */
56
+ private fromDbSession;
57
+ /**
58
+ * Convert partial Session updates to database format
59
+ */
60
+ private toDbSessionUpdates;
61
+ }
62
+
63
+ /**
64
+ * SQL migration helper to create the users table
65
+ * Can be used with Knex migrations
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * // In your migration file
70
+ * import { createUsersTableMigration, createSessionsTableMigration } from '@xcelsior/auth-adapter-knex';
71
+ *
72
+ * export async function up(knex: Knex): Promise<void> {
73
+ * await createUsersTableMigration(knex, 'users');
74
+ * await createSessionsTableMigration(knex, 'sessions');
75
+ * }
76
+ *
77
+ * export async function down(knex: Knex): Promise<void> {
78
+ * await knex.schema.dropTableIfExists('sessions');
79
+ * await knex.schema.dropTableIfExists('users');
80
+ * }
81
+ * ```
82
+ */
83
+ declare function createUsersTableMigration(knex: Knex, tableName: string, schema?: string): Promise<void>;
84
+ /**
85
+ * SQL migration helper to create the sessions table
86
+ * Can be used with Knex migrations
87
+ */
88
+ declare function createSessionsTableMigration(knex: Knex, tableName: string, schema?: string): Promise<void>;
89
+
90
+ export { type KnexConfig, KnexStorageProvider, createSessionsTableMigration, createUsersTableMigration };
package/dist/index.js ADDED
@@ -0,0 +1,336 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ KnexStorageProvider: () => KnexStorageProvider,
24
+ createSessionsTableMigration: () => createSessionsTableMigration,
25
+ createUsersTableMigration: () => createUsersTableMigration
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/knex.ts
30
+ var KnexStorageProvider = class {
31
+ constructor(config) {
32
+ this.knex = config.knex;
33
+ this.tableName = config.tableName;
34
+ this.sessionsTableName = config.sessionsTableName;
35
+ this.schema = config.schema;
36
+ }
37
+ get table() {
38
+ const query = this.knex(this.tableName);
39
+ if (this.schema) {
40
+ return query.withSchema(this.schema);
41
+ }
42
+ return query;
43
+ }
44
+ get sessionsTable() {
45
+ const query = this.knex(this.sessionsTableName);
46
+ if (this.schema) {
47
+ return query.withSchema(this.schema);
48
+ }
49
+ return query;
50
+ }
51
+ // ==================== User Methods ====================
52
+ async createUser(user) {
53
+ await this.table.insert(this.toDbUser(user));
54
+ }
55
+ async getUserById(id) {
56
+ const row = await this.table.where({ id }).first();
57
+ return row ? this.fromDbUser(row) : null;
58
+ }
59
+ async getUserByEmail(email) {
60
+ const row = await this.table.where({ email }).first();
61
+ return row ? this.fromDbUser(row) : null;
62
+ }
63
+ async getUserByResetPasswordToken(resetPasswordToken) {
64
+ const row = await this.table.where({ reset_password_token: resetPasswordToken }).first();
65
+ return row ? this.fromDbUser(row) : null;
66
+ }
67
+ async getUserByVerifyEmailToken(verifyEmailToken) {
68
+ const row = await this.table.where({ verification_token: verifyEmailToken }).first();
69
+ return row ? this.fromDbUser(row) : null;
70
+ }
71
+ async updateUser(id, updates) {
72
+ const dbUpdates = this.toDbUpdates(updates);
73
+ if (Object.keys(dbUpdates).length === 0) {
74
+ return;
75
+ }
76
+ await this.table.where({ id }).update(dbUpdates);
77
+ }
78
+ async deleteUser(id) {
79
+ await this.deleteAllUserSessions(id);
80
+ await this.table.where({ id }).delete();
81
+ }
82
+ async findUsers(filter, options = {}) {
83
+ const limit = options.limit ?? 50;
84
+ let query = this.table.clone();
85
+ if (filter.email) {
86
+ query = query.where("email", filter.email);
87
+ }
88
+ if (filter.emailContains) {
89
+ query = query.where("email", "like", `%${filter.emailContains}%`);
90
+ }
91
+ if (filter.isEmailVerified !== void 0) {
92
+ query = query.where("is_email_verified", filter.isEmailVerified);
93
+ }
94
+ if (filter.roles && filter.roles.length > 0) {
95
+ for (const role of filter.roles) {
96
+ query = query.whereRaw(
97
+ this.knex.client.config.client === "sqlite3" ? `roles LIKE ?` : `roles @> ?`,
98
+ this.knex.client.config.client === "sqlite3" ? [`%"${role}"%`] : [JSON.stringify([role])]
99
+ );
100
+ }
101
+ }
102
+ if (filter.hasAnyRole && filter.hasAnyRole.length > 0) {
103
+ query = query.where((builder) => {
104
+ for (const role of filter.hasAnyRole) {
105
+ builder.orWhereRaw(
106
+ this.knex.client.config.client === "sqlite3" ? `roles LIKE ?` : `roles @> ?`,
107
+ this.knex.client.config.client === "sqlite3" ? [`%"${role}"%`] : [JSON.stringify([role])]
108
+ );
109
+ }
110
+ });
111
+ }
112
+ if (options.cursor) {
113
+ const cursorData = JSON.parse(Buffer.from(options.cursor, "base64").toString());
114
+ query = query.where("id", ">", cursorData.lastId);
115
+ }
116
+ query = query.orderBy("id", "asc").limit(limit + 1);
117
+ const rows = await query;
118
+ const hasMore = rows.length > limit;
119
+ const resultRows = hasMore ? rows.slice(0, limit) : rows;
120
+ const users = resultRows.map((row) => this.fromDbUser(row));
121
+ let nextCursor;
122
+ if (hasMore && resultRows.length > 0) {
123
+ const lastUser = resultRows[resultRows.length - 1];
124
+ nextCursor = Buffer.from(JSON.stringify({ lastId: lastUser.id })).toString("base64");
125
+ }
126
+ return { users, nextCursor };
127
+ }
128
+ // ==================== Session Methods ====================
129
+ async createSession(session) {
130
+ await this.sessionsTable.insert(this.toDbSession(session));
131
+ }
132
+ async getSessionById(id) {
133
+ const row = await this.sessionsTable.where({ id }).first();
134
+ return row ? this.fromDbSession(row) : null;
135
+ }
136
+ async getSessionsByUserId(userId) {
137
+ const rows = await this.sessionsTable.where({ user_id: userId });
138
+ return rows.map((row) => this.fromDbSession(row));
139
+ }
140
+ async updateSession(id, updates) {
141
+ const dbUpdates = this.toDbSessionUpdates(updates);
142
+ if (Object.keys(dbUpdates).length === 0) {
143
+ return;
144
+ }
145
+ await this.sessionsTable.where({ id }).update(dbUpdates);
146
+ }
147
+ async deleteSession(id) {
148
+ await this.sessionsTable.where({ id }).delete();
149
+ }
150
+ async deleteAllUserSessions(userId) {
151
+ await this.sessionsTable.where({ user_id: userId }).delete();
152
+ }
153
+ async deleteExpiredSessions() {
154
+ const now = Date.now();
155
+ await this.sessionsTable.where("expires_at", "<", now).delete();
156
+ }
157
+ // ==================== User Mapping Helpers ====================
158
+ /**
159
+ * Convert User object to database row format (snake_case)
160
+ */
161
+ toDbUser(user) {
162
+ return {
163
+ id: user.id,
164
+ email: user.email,
165
+ first_name: user.firstName ?? null,
166
+ last_name: user.lastName ?? null,
167
+ password_hash: user.passwordHash,
168
+ roles: JSON.stringify(user.roles),
169
+ is_email_verified: user.isEmailVerified,
170
+ verification_token: user.verificationToken ?? null,
171
+ reset_password_token: user.resetPasswordToken ?? null,
172
+ reset_password_expires: user.resetPasswordExpires ?? null,
173
+ created_at: user.createdAt,
174
+ updated_at: user.updatedAt
175
+ };
176
+ }
177
+ /**
178
+ * Convert database row to User object (camelCase)
179
+ */
180
+ fromDbUser(row) {
181
+ return {
182
+ id: row.id,
183
+ email: row.email,
184
+ firstName: row.first_name,
185
+ lastName: row.last_name,
186
+ passwordHash: row.password_hash,
187
+ roles: typeof row.roles === "string" ? JSON.parse(row.roles) : row.roles,
188
+ isEmailVerified: Boolean(row.is_email_verified),
189
+ verificationToken: row.verification_token,
190
+ resetPasswordToken: row.reset_password_token,
191
+ resetPasswordExpires: row.reset_password_expires,
192
+ createdAt: row.created_at,
193
+ updatedAt: row.updated_at
194
+ };
195
+ }
196
+ /**
197
+ * Convert partial User updates to database format
198
+ */
199
+ toDbUpdates(updates) {
200
+ const dbUpdates = {};
201
+ if (updates.email !== void 0) {
202
+ dbUpdates.email = updates.email;
203
+ }
204
+ if ("firstName" in updates) {
205
+ dbUpdates.first_name = updates.firstName ?? null;
206
+ }
207
+ if ("lastName" in updates) {
208
+ dbUpdates.last_name = updates.lastName ?? null;
209
+ }
210
+ if (updates.passwordHash !== void 0) {
211
+ dbUpdates.password_hash = updates.passwordHash;
212
+ }
213
+ if (updates.roles !== void 0) {
214
+ dbUpdates.roles = JSON.stringify(updates.roles);
215
+ }
216
+ if (updates.isEmailVerified !== void 0) {
217
+ dbUpdates.is_email_verified = updates.isEmailVerified;
218
+ }
219
+ if ("verificationToken" in updates) {
220
+ dbUpdates.verification_token = updates.verificationToken ?? null;
221
+ }
222
+ if ("resetPasswordToken" in updates) {
223
+ dbUpdates.reset_password_token = updates.resetPasswordToken ?? null;
224
+ }
225
+ if ("resetPasswordExpires" in updates) {
226
+ dbUpdates.reset_password_expires = updates.resetPasswordExpires ?? null;
227
+ }
228
+ if (updates.updatedAt !== void 0) {
229
+ dbUpdates.updated_at = updates.updatedAt;
230
+ }
231
+ return dbUpdates;
232
+ }
233
+ // ==================== Session Mapping Helpers ====================
234
+ /**
235
+ * Convert Session object to database row format (snake_case)
236
+ */
237
+ toDbSession(session) {
238
+ return {
239
+ id: session.id,
240
+ user_id: session.userId,
241
+ refresh_token_hash: session.refreshTokenHash,
242
+ user_agent: session.userAgent ?? null,
243
+ ip_address: session.ipAddress ?? null,
244
+ device_name: session.deviceName ?? null,
245
+ created_at: session.createdAt,
246
+ last_used_at: session.lastUsedAt,
247
+ expires_at: session.expiresAt
248
+ };
249
+ }
250
+ /**
251
+ * Convert database row to Session object (camelCase)
252
+ */
253
+ fromDbSession(row) {
254
+ return {
255
+ id: row.id,
256
+ userId: row.user_id,
257
+ refreshTokenHash: row.refresh_token_hash,
258
+ userAgent: row.user_agent,
259
+ ipAddress: row.ip_address,
260
+ deviceName: row.device_name,
261
+ createdAt: row.created_at,
262
+ lastUsedAt: row.last_used_at,
263
+ expiresAt: row.expires_at
264
+ };
265
+ }
266
+ /**
267
+ * Convert partial Session updates to database format
268
+ */
269
+ toDbSessionUpdates(updates) {
270
+ const dbUpdates = {};
271
+ if (updates.refreshTokenHash !== void 0) {
272
+ dbUpdates.refresh_token_hash = updates.refreshTokenHash;
273
+ }
274
+ if ("userAgent" in updates) {
275
+ dbUpdates.user_agent = updates.userAgent ?? null;
276
+ }
277
+ if ("ipAddress" in updates) {
278
+ dbUpdates.ip_address = updates.ipAddress ?? null;
279
+ }
280
+ if ("deviceName" in updates) {
281
+ dbUpdates.device_name = updates.deviceName ?? null;
282
+ }
283
+ if (updates.lastUsedAt !== void 0) {
284
+ dbUpdates.last_used_at = updates.lastUsedAt;
285
+ }
286
+ if (updates.expiresAt !== void 0) {
287
+ dbUpdates.expires_at = updates.expiresAt;
288
+ }
289
+ return dbUpdates;
290
+ }
291
+ };
292
+
293
+ // src/migration.ts
294
+ async function createUsersTableMigration(knex, tableName, schema) {
295
+ const schemaBuilder = schema ? knex.schema.withSchema(schema) : knex.schema;
296
+ await schemaBuilder.createTable(tableName, (table) => {
297
+ table.string("id").primary();
298
+ table.string("email").notNullable().unique();
299
+ table.string("first_name").nullable();
300
+ table.string("last_name").nullable();
301
+ table.string("password_hash").notNullable();
302
+ table.jsonb("roles").notNullable();
303
+ table.boolean("is_email_verified").notNullable().defaultTo(false);
304
+ table.string("verification_token").nullable();
305
+ table.string("reset_password_token").nullable();
306
+ table.bigInteger("reset_password_expires").nullable();
307
+ table.bigInteger("created_at").notNullable();
308
+ table.bigInteger("updated_at").notNullable();
309
+ table.index("email");
310
+ table.index("verification_token");
311
+ table.index("reset_password_token");
312
+ });
313
+ }
314
+ async function createSessionsTableMigration(knex, tableName, schema) {
315
+ const schemaBuilder = schema ? knex.schema.withSchema(schema) : knex.schema;
316
+ await schemaBuilder.createTable(tableName, (table) => {
317
+ table.string("id").primary();
318
+ table.string("user_id").notNullable();
319
+ table.string("refresh_token_hash").notNullable();
320
+ table.string("user_agent").nullable();
321
+ table.string("ip_address").nullable();
322
+ table.string("device_name").nullable();
323
+ table.bigInteger("created_at").notNullable();
324
+ table.bigInteger("last_used_at").notNullable();
325
+ table.bigInteger("expires_at").notNullable();
326
+ table.index("user_id");
327
+ table.index("expires_at");
328
+ });
329
+ }
330
+ // Annotate the CommonJS export names for ESM import in node:
331
+ 0 && (module.exports = {
332
+ KnexStorageProvider,
333
+ createSessionsTableMigration,
334
+ createUsersTableMigration
335
+ });
336
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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":[]}