@mertdogar/better-auth-do-sqlite
Advanced tools
+265
-43
@@ -8,7 +8,153 @@ 'use strict'; | ||
| var kyselyPluginSerialize = require('kysely-plugin-serialize'); | ||
| var factory = require('hono/factory'); | ||
| var hono = require('hono'); | ||
| var cors = require('hono/cors'); | ||
| var zod = require('zod'); | ||
| // lib/auth-do.ts | ||
| // lib/logger.ts | ||
| var LogLevelSeverity = { | ||
| debug: 0, | ||
| info: 1, | ||
| success: 2, | ||
| warn: 3, | ||
| error: 4 | ||
| }; | ||
| var colors = { | ||
| reset: "\x1B[0m", | ||
| bright: "\x1B[1m", | ||
| dim: "\x1B[2m", | ||
| red: "\x1B[31m", | ||
| green: "\x1B[32m", | ||
| yellow: "\x1B[33m", | ||
| blue: "\x1B[34m", | ||
| cyan: "\x1B[36m", | ||
| gray: "\x1B[90m" | ||
| }; | ||
| var levelColors = { | ||
| debug: colors.gray, | ||
| info: colors.blue, | ||
| success: colors.green, | ||
| warn: colors.yellow, | ||
| error: colors.red | ||
| }; | ||
| var levelEmojis = { | ||
| debug: "\u{1F50D}", | ||
| info: "\u2139\uFE0F ", | ||
| success: "\u2705", | ||
| warn: "\u26A0\uFE0F ", | ||
| error: "\u274C" | ||
| }; | ||
| var Logger = class _Logger { | ||
| name; | ||
| format; | ||
| level; | ||
| severity; | ||
| enabled; | ||
| constructor({ name, format = "text", level = "info", enabled = true }) { | ||
| this.name = name; | ||
| this.format = format; | ||
| this.level = level; | ||
| this.severity = LogLevelSeverity[level]; | ||
| this.enabled = enabled; | ||
| } | ||
| shouldLog(level) { | ||
| return this.enabled && LogLevelSeverity[level] >= this.severity; | ||
| } | ||
| formatTimestamp() { | ||
| return (/* @__PURE__ */ new Date()).toISOString(); | ||
| } | ||
| formatError(error) { | ||
| if (error instanceof Error) { | ||
| return { | ||
| name: error.name, | ||
| message: error.message, | ||
| stack: error.stack | ||
| }; | ||
| } | ||
| return { message: String(error) }; | ||
| } | ||
| log(level, message, data) { | ||
| if (!this.shouldLog(level)) return; | ||
| if (this.format === "json") { | ||
| const log = { | ||
| timestamp: this.formatTimestamp(), | ||
| name: this.name, | ||
| level, | ||
| message, | ||
| data | ||
| }; | ||
| console.log(JSON.stringify(log)); | ||
| } else if (this.format === "pretty") { | ||
| const color = levelColors[level]; | ||
| const emoji = levelEmojis[level]; | ||
| const timestamp = colors.gray + this.formatTimestamp() + colors.reset; | ||
| const nameStr = colors.cyan + `[${this.name}]` + colors.reset; | ||
| const levelStr = color + colors.bright + level.toUpperCase() + colors.reset; | ||
| const dataStr = data ? colors.dim + ` ${JSON.stringify(data)}` + colors.reset : ""; | ||
| console.log(`${timestamp} ${emoji} ${nameStr} ${levelStr} ${message}${dataStr}`); | ||
| } else { | ||
| const timestamp = this.formatTimestamp(); | ||
| const dataStr = data ? ` ${JSON.stringify(data)}` : ""; | ||
| console.log(`[${timestamp}] [${this.name}] ${level.toUpperCase()}: ${message}${dataStr}`); | ||
| } | ||
| } | ||
| debug(message, data) { | ||
| this.log("debug", message, data); | ||
| } | ||
| info(message, data) { | ||
| this.log("info", message, data); | ||
| } | ||
| success(message, data) { | ||
| this.log("success", message, data); | ||
| } | ||
| warn(message, data) { | ||
| this.log("warn", message, data); | ||
| } | ||
| error(message, error, data) { | ||
| const logData = { ...data }; | ||
| if (error) { | ||
| if (this.format === "json") { | ||
| const errorLog = { | ||
| timestamp: this.formatTimestamp(), | ||
| name: this.name, | ||
| level: "error", | ||
| message, | ||
| data: logData, | ||
| error: this.formatError(error) | ||
| }; | ||
| if (this.shouldLog("error")) { | ||
| console.error(JSON.stringify(errorLog)); | ||
| } | ||
| return; | ||
| } else { | ||
| if (error instanceof Error) { | ||
| logData.error = error.message; | ||
| logData.stack = error.stack; | ||
| } else { | ||
| logData.error = String(error); | ||
| } | ||
| } | ||
| } | ||
| this.log("error", message, logData); | ||
| } | ||
| child(name) { | ||
| return new _Logger({ | ||
| name: `${this.name}:${name}`, | ||
| format: this.format, | ||
| level: this.level, | ||
| enabled: this.enabled | ||
| }); | ||
| } | ||
| setLevel(level) { | ||
| this.level = level; | ||
| this.severity = LogLevelSeverity[level]; | ||
| } | ||
| setEnabled(enabled) { | ||
| this.enabled = enabled; | ||
| } | ||
| }; | ||
| var createLogger = (options) => new Logger(options); | ||
| // lib/libsql-http-server.ts | ||
@@ -23,4 +169,12 @@ var LibSQLHttpServer = class { | ||
| // Track protocol version for current request | ||
| constructor(sql) { | ||
| logger; | ||
| constructor(sql, loggerOptions) { | ||
| this.sql = sql; | ||
| this.logger = new Logger({ | ||
| name: "LibSQLHttpServer", | ||
| format: loggerOptions?.format || "text", | ||
| level: loggerOptions?.level || "info", | ||
| enabled: loggerOptions?.enabled ?? true, | ||
| ...loggerOptions | ||
| }); | ||
| } | ||
@@ -108,3 +262,3 @@ /** | ||
| } catch (error) { | ||
| console.error("LibSQL HTTP Server error:", error); | ||
| this.logger.error("LibSQL HTTP Server error", error); | ||
| return new Response(JSON.stringify({ error: error.message || "Internal server error" }), { | ||
@@ -165,4 +319,4 @@ status: 500, | ||
| } catch (parseError) { | ||
| console.error("[LibSQL HTTP] Failed to parse request body:", parseError.message); | ||
| return new Response(JSON.stringify({ error: "Invalid JSON in request body" }), { | ||
| this.logger.error("Failed to parse request body", parseError); | ||
| return new Response(JSON.stringify({ error: "Invalid JSON request body" }), { | ||
| status: 400, | ||
@@ -209,6 +363,8 @@ headers: { "Content-Type": "application/json" } | ||
| } catch (error) { | ||
| console.error(`[LibSQL HTTP] Request ${i + 1} failed:`, error.message); | ||
| this.logger.error(`Request ${i + 1} failed`, error); | ||
| results.push({ | ||
| type: "error", | ||
| error: { message: error.message || "Request failed" } | ||
| error: { | ||
| message: error.message || "Query execution failed" | ||
| } | ||
| }); | ||
@@ -228,10 +384,8 @@ } | ||
| } catch (error) { | ||
| console.error("[LibSQL HTTP] V2 Pipeline error:", error); | ||
| console.error("[LibSQL HTTP] Error details:", { | ||
| this.logger.error("V2 Pipeline error", error, { | ||
| message: error.message, | ||
| stack: error.stack, | ||
| name: error.name | ||
| stack: error.stack | ||
| }); | ||
| return new Response(JSON.stringify({ error: error.message || "Pipeline execution failed" }), { | ||
| status: 400, | ||
| status: 500, | ||
| headers: { "Content-Type": "application/json" } | ||
@@ -654,4 +808,12 @@ }); | ||
| libsqlServer; | ||
| logger; | ||
| constructor(ctx, env) { | ||
| super(ctx, env); | ||
| this.logger = new Logger({ | ||
| name: "AuthDO", | ||
| format: env.loggerOptions?.format || "text", | ||
| level: env.loggerOptions?.level || "info", | ||
| enabled: env.loggerOptions?.enabled ?? true, | ||
| ...env.loggerOptions | ||
| }); | ||
| this.db = new kysely.Kysely({ | ||
@@ -664,5 +826,8 @@ dialect: new DODialect({ ctx }), | ||
| this.ctx.blockConcurrencyWhile(async () => { | ||
| this.logger.debug("Initializing Better Auth tables"); | ||
| await this.initBetterAuthTables(); | ||
| this.logger.success("Better Auth tables initialized"); | ||
| }); | ||
| this.auth = betterAuth.betterAuth({ | ||
| logger: env.logger ? env.logger : void 0, | ||
| database: { | ||
@@ -696,3 +861,3 @@ db: this.db, | ||
| after: plugins.createAuthMiddleware(async (ctx2) => { | ||
| if (ctx2.path.includes("/sign-up")) { | ||
| if (ctx2.path.includes("/sign-up") || ctx2.path.includes("/callback/:id")) { | ||
| const newSession = ctx2.context.newSession; | ||
@@ -710,3 +875,3 @@ if (newSession) { | ||
| if (!apiKeyResponse) { | ||
| console.error("Failed to create API key:", apiKeyResponse); | ||
| this.logger.error("Failed to create API key", null, { response: apiKeyResponse }); | ||
| return; | ||
@@ -725,3 +890,3 @@ } | ||
| }); | ||
| this.libsqlServer = new LibSQLHttpServer(this.ctx.storage.sql); | ||
| this.libsqlServer = new LibSQLHttpServer(this.ctx.storage.sql, env.loggerOptions); | ||
| } | ||
@@ -804,2 +969,28 @@ getDB() { | ||
| sqlUrl.pathname = sqlPath; | ||
| if (!this.env.libsqlToken) { | ||
| return new Response(JSON.stringify({ error: "Unauthorized" }), { | ||
| status: 401, | ||
| headers: { "Content-Type": "application/json" } | ||
| }); | ||
| } | ||
| const authorizationHeader = request.headers.get("Authorization"); | ||
| if (!authorizationHeader || !authorizationHeader.startsWith("Bearer ")) { | ||
| return new Response(JSON.stringify({ error: "Unauthorized" }), { | ||
| status: 401, | ||
| headers: { "Content-Type": "application/json" } | ||
| }); | ||
| } | ||
| const token = authorizationHeader.split(" ")[1]; | ||
| if (!token) { | ||
| return new Response(JSON.stringify({ error: "Unauthorized" }), { | ||
| status: 401, | ||
| headers: { "Content-Type": "application/json" } | ||
| }); | ||
| } | ||
| if (token !== this.env.libsqlToken) { | ||
| return new Response(JSON.stringify({ error: "Unauthorized" }), { | ||
| status: 401, | ||
| headers: { "Content-Type": "application/json" } | ||
| }); | ||
| } | ||
| return await this.libsqlServer.handleRequest(new Request(sqlUrl, request)); | ||
@@ -810,3 +1001,3 @@ } | ||
| } catch (error) { | ||
| console.error("Auth error:", error); | ||
| this.logger.error("Authentication error occurred", error); | ||
| return new Response(JSON.stringify({ error: "Authentication error" }), { | ||
@@ -860,3 +1051,8 @@ status: 500, | ||
| } | ||
| var SqlServerRouterEnvSchema = zod.z.object({ | ||
| APP_DO_ID: zod.z.string().optional().default("app"), | ||
| prefixRegex: zod.z.string().optional().default(/^\/api\/sqld/.source) | ||
| }); | ||
| function sqlServerRouter() { | ||
| const factory$1 = factory.createFactory(); | ||
| const router = new hono.Hono(); | ||
@@ -893,5 +1089,5 @@ router.use( | ||
| ); | ||
| router.get("/v2", async (c) => { | ||
| console.log("v2", c.req.url); | ||
| const appDO = getDO(c, c.env.APP_DO_ID || "app"); | ||
| const v2Handler = factory$1.createHandlers(async (c) => { | ||
| const env = SqlServerRouterEnvSchema.parse(c.env); | ||
| const appDO = getDO(c, env.APP_DO_ID); | ||
| const url = new URL(c.req.url); | ||
@@ -902,5 +1098,5 @@ url.pathname = "/sql/v2"; | ||
| }); | ||
| router.get("/v3", async (c) => { | ||
| console.log("v3", c.req.url); | ||
| const appDO = getDO(c, c.env.APP_DO_ID || "app"); | ||
| const v3Handler = factory$1.createHandlers(async (c) => { | ||
| const env = SqlServerRouterEnvSchema.parse(c.env); | ||
| const appDO = getDO(c, env.APP_DO_ID); | ||
| const url = new URL(c.req.url); | ||
@@ -911,5 +1107,5 @@ url.pathname = "/sql/v3"; | ||
| }); | ||
| router.get("/v3-protobuf", async (c) => { | ||
| console.log("v3-protobuf", c.req.url); | ||
| const appDO = getDO(c, c.env.APP_DO_ID || "app"); | ||
| const v3ProtobufHandler = factory$1.createHandlers(async (c) => { | ||
| const env = SqlServerRouterEnvSchema.parse(c.env); | ||
| const appDO = getDO(c, env.APP_DO_ID); | ||
| const url = new URL(c.req.url); | ||
@@ -920,43 +1116,67 @@ url.pathname = "/sql/v3-protobuf"; | ||
| }); | ||
| router.all("/v2/*", async (c) => { | ||
| console.log("v2/*", c.req.url); | ||
| const appDO = getDO(c, c.env.APP_DO_ID || "app"); | ||
| const v2AllHandler = factory$1.createHandlers(async (c) => { | ||
| const env = SqlServerRouterEnvSchema.parse(c.env); | ||
| const appDO = getDO(c, env.APP_DO_ID); | ||
| const url = new URL(c.req.url); | ||
| url.pathname = url.pathname.replace(c.env.prefixRegex || /^\/api\/sqld/, "/sql"); | ||
| url.pathname = url.pathname.replace( | ||
| env.prefixRegex ? new RegExp(env.prefixRegex) : /^\/api\/sqld/, | ||
| "/sql" | ||
| ); | ||
| const modifiedRequest = new Request(url, c.req.raw); | ||
| return appDO.fetch(modifiedRequest); | ||
| }); | ||
| router.all("/v3/*", async (c) => { | ||
| console.log("v3/*", c.req.url); | ||
| const appDO = getDO(c, c.env.APP_DO_ID || "app"); | ||
| const v3AllHandler = factory$1.createHandlers(async (c) => { | ||
| const env = SqlServerRouterEnvSchema.parse(c.env); | ||
| const appDO = getDO(c, env.APP_DO_ID); | ||
| const url = new URL(c.req.url); | ||
| url.pathname = url.pathname.replace(c.env.prefixRegex || /^\/api\/sqld/, "/sql"); | ||
| console.log("modified url", url.pathname); | ||
| url.pathname = url.pathname.replace( | ||
| env.prefixRegex ? new RegExp(env.prefixRegex) : /^\/api\/sqld/, | ||
| "/sql" | ||
| ); | ||
| const modifiedRequest = new Request(url, c.req.raw); | ||
| return appDO.fetch(modifiedRequest); | ||
| }); | ||
| router.all("/sql/*", async (c) => { | ||
| console.log("sql/*", c.req.url); | ||
| const appDO = getDO(c, c.env.APP_DO_ID || "app"); | ||
| const sqlAllHandler = factory$1.createHandlers(async (c) => { | ||
| const env = SqlServerRouterEnvSchema.parse(c.env); | ||
| const appDO = getDO(c, env.APP_DO_ID); | ||
| const url = new URL(c.req.url); | ||
| url.pathname = url.pathname.replace(c.env.prefixRegex || /^\/api\/sqld/, ""); | ||
| url.pathname = url.pathname.replace( | ||
| env.prefixRegex ? new RegExp(env.prefixRegex) : /^\/api\/sqld/, | ||
| "" | ||
| ); | ||
| const modifiedRequest = new Request(url, c.req.raw); | ||
| return appDO.fetch(modifiedRequest); | ||
| }); | ||
| router.all("/sql", async (c) => { | ||
| console.log("sql", c.req.url); | ||
| const appDO = getDO(c, c.env.APP_DO_ID || "app"); | ||
| const sqlHandler = factory$1.createHandlers(async (c) => { | ||
| const env = SqlServerRouterEnvSchema.parse(c.env); | ||
| const appDO = getDO(c, env.APP_DO_ID); | ||
| const url = new URL(c.req.url); | ||
| url.pathname = url.pathname.replace(c.env.prefixRegex || /^\/api\/sqld/, ""); | ||
| url.pathname = url.pathname.replace( | ||
| env.prefixRegex ? new RegExp(env.prefixRegex) : /^\/api\/sqld/, | ||
| "" | ||
| ); | ||
| const modifiedRequest = new Request(url, c.req.raw); | ||
| return appDO.fetch(modifiedRequest); | ||
| }); | ||
| router.get("/v2", ...v2Handler); | ||
| router.get("/v3", ...v3Handler); | ||
| router.get("/v3-protobuf", ...v3ProtobufHandler); | ||
| router.all("/v2/*", ...v2AllHandler); | ||
| router.all("/v3/*", ...v3AllHandler); | ||
| router.all("/sql/*", ...sqlAllHandler); | ||
| router.all("/sql", ...sqlHandler); | ||
| return router; | ||
| } | ||
| var BetterAuthRouterEnvSchema = zod.z.object({ | ||
| APP_DO_ID: zod.z.string().optional().default("app") | ||
| }); | ||
| function betterAuthRouter() { | ||
| const factory$1 = factory.createFactory(); | ||
| const router = new hono.Hono(); | ||
| router.all("/*", (c) => { | ||
| const authDO = getDO(c, c.env.APP_DO_ID || "app"); | ||
| const handler = factory$1.createHandlers((c) => { | ||
| const env = BetterAuthRouterEnvSchema.parse(c.env); | ||
| const authDO = getDO(c, env.APP_DO_ID); | ||
| return authDO.fetch(c.req.raw); | ||
| }); | ||
| router.all("/*", ...handler); | ||
| return router; | ||
@@ -967,4 +1187,6 @@ } | ||
| exports.LibSQLHttpServer = LibSQLHttpServer; | ||
| exports.Logger = Logger; | ||
| exports.authMiddleware = authMiddleware; | ||
| exports.betterAuthRouter = betterAuthRouter; | ||
| exports.createLogger = createLogger; | ||
| exports.getBearerToken = getBearerToken; | ||
@@ -971,0 +1193,0 @@ exports.getDO = getDO; |
+264
-44
@@ -6,7 +6,153 @@ import { DurableObject } from 'cloudflare:workers'; | ||
| import { SerializePlugin } from 'kysely-plugin-serialize'; | ||
| import { createFactory } from 'hono/factory'; | ||
| import { Hono } from 'hono'; | ||
| import { cors } from 'hono/cors'; | ||
| import { z } from 'zod'; | ||
| // lib/auth-do.ts | ||
| // lib/logger.ts | ||
| var LogLevelSeverity = { | ||
| debug: 0, | ||
| info: 1, | ||
| success: 2, | ||
| warn: 3, | ||
| error: 4 | ||
| }; | ||
| var colors = { | ||
| reset: "\x1B[0m", | ||
| bright: "\x1B[1m", | ||
| dim: "\x1B[2m", | ||
| red: "\x1B[31m", | ||
| green: "\x1B[32m", | ||
| yellow: "\x1B[33m", | ||
| blue: "\x1B[34m", | ||
| cyan: "\x1B[36m", | ||
| gray: "\x1B[90m" | ||
| }; | ||
| var levelColors = { | ||
| debug: colors.gray, | ||
| info: colors.blue, | ||
| success: colors.green, | ||
| warn: colors.yellow, | ||
| error: colors.red | ||
| }; | ||
| var levelEmojis = { | ||
| debug: "\u{1F50D}", | ||
| info: "\u2139\uFE0F ", | ||
| success: "\u2705", | ||
| warn: "\u26A0\uFE0F ", | ||
| error: "\u274C" | ||
| }; | ||
| var Logger = class _Logger { | ||
| name; | ||
| format; | ||
| level; | ||
| severity; | ||
| enabled; | ||
| constructor({ name, format = "text", level = "info", enabled = true }) { | ||
| this.name = name; | ||
| this.format = format; | ||
| this.level = level; | ||
| this.severity = LogLevelSeverity[level]; | ||
| this.enabled = enabled; | ||
| } | ||
| shouldLog(level) { | ||
| return this.enabled && LogLevelSeverity[level] >= this.severity; | ||
| } | ||
| formatTimestamp() { | ||
| return (/* @__PURE__ */ new Date()).toISOString(); | ||
| } | ||
| formatError(error) { | ||
| if (error instanceof Error) { | ||
| return { | ||
| name: error.name, | ||
| message: error.message, | ||
| stack: error.stack | ||
| }; | ||
| } | ||
| return { message: String(error) }; | ||
| } | ||
| log(level, message, data) { | ||
| if (!this.shouldLog(level)) return; | ||
| if (this.format === "json") { | ||
| const log = { | ||
| timestamp: this.formatTimestamp(), | ||
| name: this.name, | ||
| level, | ||
| message, | ||
| data | ||
| }; | ||
| console.log(JSON.stringify(log)); | ||
| } else if (this.format === "pretty") { | ||
| const color = levelColors[level]; | ||
| const emoji = levelEmojis[level]; | ||
| const timestamp = colors.gray + this.formatTimestamp() + colors.reset; | ||
| const nameStr = colors.cyan + `[${this.name}]` + colors.reset; | ||
| const levelStr = color + colors.bright + level.toUpperCase() + colors.reset; | ||
| const dataStr = data ? colors.dim + ` ${JSON.stringify(data)}` + colors.reset : ""; | ||
| console.log(`${timestamp} ${emoji} ${nameStr} ${levelStr} ${message}${dataStr}`); | ||
| } else { | ||
| const timestamp = this.formatTimestamp(); | ||
| const dataStr = data ? ` ${JSON.stringify(data)}` : ""; | ||
| console.log(`[${timestamp}] [${this.name}] ${level.toUpperCase()}: ${message}${dataStr}`); | ||
| } | ||
| } | ||
| debug(message, data) { | ||
| this.log("debug", message, data); | ||
| } | ||
| info(message, data) { | ||
| this.log("info", message, data); | ||
| } | ||
| success(message, data) { | ||
| this.log("success", message, data); | ||
| } | ||
| warn(message, data) { | ||
| this.log("warn", message, data); | ||
| } | ||
| error(message, error, data) { | ||
| const logData = { ...data }; | ||
| if (error) { | ||
| if (this.format === "json") { | ||
| const errorLog = { | ||
| timestamp: this.formatTimestamp(), | ||
| name: this.name, | ||
| level: "error", | ||
| message, | ||
| data: logData, | ||
| error: this.formatError(error) | ||
| }; | ||
| if (this.shouldLog("error")) { | ||
| console.error(JSON.stringify(errorLog)); | ||
| } | ||
| return; | ||
| } else { | ||
| if (error instanceof Error) { | ||
| logData.error = error.message; | ||
| logData.stack = error.stack; | ||
| } else { | ||
| logData.error = String(error); | ||
| } | ||
| } | ||
| } | ||
| this.log("error", message, logData); | ||
| } | ||
| child(name) { | ||
| return new _Logger({ | ||
| name: `${this.name}:${name}`, | ||
| format: this.format, | ||
| level: this.level, | ||
| enabled: this.enabled | ||
| }); | ||
| } | ||
| setLevel(level) { | ||
| this.level = level; | ||
| this.severity = LogLevelSeverity[level]; | ||
| } | ||
| setEnabled(enabled) { | ||
| this.enabled = enabled; | ||
| } | ||
| }; | ||
| var createLogger = (options) => new Logger(options); | ||
| // lib/libsql-http-server.ts | ||
@@ -21,4 +167,12 @@ var LibSQLHttpServer = class { | ||
| // Track protocol version for current request | ||
| constructor(sql) { | ||
| logger; | ||
| constructor(sql, loggerOptions) { | ||
| this.sql = sql; | ||
| this.logger = new Logger({ | ||
| name: "LibSQLHttpServer", | ||
| format: loggerOptions?.format || "text", | ||
| level: loggerOptions?.level || "info", | ||
| enabled: loggerOptions?.enabled ?? true, | ||
| ...loggerOptions | ||
| }); | ||
| } | ||
@@ -106,3 +260,3 @@ /** | ||
| } catch (error) { | ||
| console.error("LibSQL HTTP Server error:", error); | ||
| this.logger.error("LibSQL HTTP Server error", error); | ||
| return new Response(JSON.stringify({ error: error.message || "Internal server error" }), { | ||
@@ -163,4 +317,4 @@ status: 500, | ||
| } catch (parseError) { | ||
| console.error("[LibSQL HTTP] Failed to parse request body:", parseError.message); | ||
| return new Response(JSON.stringify({ error: "Invalid JSON in request body" }), { | ||
| this.logger.error("Failed to parse request body", parseError); | ||
| return new Response(JSON.stringify({ error: "Invalid JSON request body" }), { | ||
| status: 400, | ||
@@ -207,6 +361,8 @@ headers: { "Content-Type": "application/json" } | ||
| } catch (error) { | ||
| console.error(`[LibSQL HTTP] Request ${i + 1} failed:`, error.message); | ||
| this.logger.error(`Request ${i + 1} failed`, error); | ||
| results.push({ | ||
| type: "error", | ||
| error: { message: error.message || "Request failed" } | ||
| error: { | ||
| message: error.message || "Query execution failed" | ||
| } | ||
| }); | ||
@@ -226,10 +382,8 @@ } | ||
| } catch (error) { | ||
| console.error("[LibSQL HTTP] V2 Pipeline error:", error); | ||
| console.error("[LibSQL HTTP] Error details:", { | ||
| this.logger.error("V2 Pipeline error", error, { | ||
| message: error.message, | ||
| stack: error.stack, | ||
| name: error.name | ||
| stack: error.stack | ||
| }); | ||
| return new Response(JSON.stringify({ error: error.message || "Pipeline execution failed" }), { | ||
| status: 400, | ||
| status: 500, | ||
| headers: { "Content-Type": "application/json" } | ||
@@ -652,4 +806,12 @@ }); | ||
| libsqlServer; | ||
| logger; | ||
| constructor(ctx, env) { | ||
| super(ctx, env); | ||
| this.logger = new Logger({ | ||
| name: "AuthDO", | ||
| format: env.loggerOptions?.format || "text", | ||
| level: env.loggerOptions?.level || "info", | ||
| enabled: env.loggerOptions?.enabled ?? true, | ||
| ...env.loggerOptions | ||
| }); | ||
| this.db = new Kysely({ | ||
@@ -662,5 +824,8 @@ dialect: new DODialect({ ctx }), | ||
| this.ctx.blockConcurrencyWhile(async () => { | ||
| this.logger.debug("Initializing Better Auth tables"); | ||
| await this.initBetterAuthTables(); | ||
| this.logger.success("Better Auth tables initialized"); | ||
| }); | ||
| this.auth = betterAuth({ | ||
| logger: env.logger ? env.logger : void 0, | ||
| database: { | ||
@@ -694,3 +859,3 @@ db: this.db, | ||
| after: createAuthMiddleware(async (ctx2) => { | ||
| if (ctx2.path.includes("/sign-up")) { | ||
| if (ctx2.path.includes("/sign-up") || ctx2.path.includes("/callback/:id")) { | ||
| const newSession = ctx2.context.newSession; | ||
@@ -708,3 +873,3 @@ if (newSession) { | ||
| if (!apiKeyResponse) { | ||
| console.error("Failed to create API key:", apiKeyResponse); | ||
| this.logger.error("Failed to create API key", null, { response: apiKeyResponse }); | ||
| return; | ||
@@ -723,3 +888,3 @@ } | ||
| }); | ||
| this.libsqlServer = new LibSQLHttpServer(this.ctx.storage.sql); | ||
| this.libsqlServer = new LibSQLHttpServer(this.ctx.storage.sql, env.loggerOptions); | ||
| } | ||
@@ -802,2 +967,28 @@ getDB() { | ||
| sqlUrl.pathname = sqlPath; | ||
| if (!this.env.libsqlToken) { | ||
| return new Response(JSON.stringify({ error: "Unauthorized" }), { | ||
| status: 401, | ||
| headers: { "Content-Type": "application/json" } | ||
| }); | ||
| } | ||
| const authorizationHeader = request.headers.get("Authorization"); | ||
| if (!authorizationHeader || !authorizationHeader.startsWith("Bearer ")) { | ||
| return new Response(JSON.stringify({ error: "Unauthorized" }), { | ||
| status: 401, | ||
| headers: { "Content-Type": "application/json" } | ||
| }); | ||
| } | ||
| const token = authorizationHeader.split(" ")[1]; | ||
| if (!token) { | ||
| return new Response(JSON.stringify({ error: "Unauthorized" }), { | ||
| status: 401, | ||
| headers: { "Content-Type": "application/json" } | ||
| }); | ||
| } | ||
| if (token !== this.env.libsqlToken) { | ||
| return new Response(JSON.stringify({ error: "Unauthorized" }), { | ||
| status: 401, | ||
| headers: { "Content-Type": "application/json" } | ||
| }); | ||
| } | ||
| return await this.libsqlServer.handleRequest(new Request(sqlUrl, request)); | ||
@@ -808,3 +999,3 @@ } | ||
| } catch (error) { | ||
| console.error("Auth error:", error); | ||
| this.logger.error("Authentication error occurred", error); | ||
| return new Response(JSON.stringify({ error: "Authentication error" }), { | ||
@@ -858,3 +1049,8 @@ status: 500, | ||
| } | ||
| var SqlServerRouterEnvSchema = z.object({ | ||
| APP_DO_ID: z.string().optional().default("app"), | ||
| prefixRegex: z.string().optional().default(/^\/api\/sqld/.source) | ||
| }); | ||
| function sqlServerRouter() { | ||
| const factory = createFactory(); | ||
| const router = new Hono(); | ||
@@ -891,5 +1087,5 @@ router.use( | ||
| ); | ||
| router.get("/v2", async (c) => { | ||
| console.log("v2", c.req.url); | ||
| const appDO = getDO(c, c.env.APP_DO_ID || "app"); | ||
| const v2Handler = factory.createHandlers(async (c) => { | ||
| const env = SqlServerRouterEnvSchema.parse(c.env); | ||
| const appDO = getDO(c, env.APP_DO_ID); | ||
| const url = new URL(c.req.url); | ||
@@ -900,5 +1096,5 @@ url.pathname = "/sql/v2"; | ||
| }); | ||
| router.get("/v3", async (c) => { | ||
| console.log("v3", c.req.url); | ||
| const appDO = getDO(c, c.env.APP_DO_ID || "app"); | ||
| const v3Handler = factory.createHandlers(async (c) => { | ||
| const env = SqlServerRouterEnvSchema.parse(c.env); | ||
| const appDO = getDO(c, env.APP_DO_ID); | ||
| const url = new URL(c.req.url); | ||
@@ -909,5 +1105,5 @@ url.pathname = "/sql/v3"; | ||
| }); | ||
| router.get("/v3-protobuf", async (c) => { | ||
| console.log("v3-protobuf", c.req.url); | ||
| const appDO = getDO(c, c.env.APP_DO_ID || "app"); | ||
| const v3ProtobufHandler = factory.createHandlers(async (c) => { | ||
| const env = SqlServerRouterEnvSchema.parse(c.env); | ||
| const appDO = getDO(c, env.APP_DO_ID); | ||
| const url = new URL(c.req.url); | ||
@@ -918,46 +1114,70 @@ url.pathname = "/sql/v3-protobuf"; | ||
| }); | ||
| router.all("/v2/*", async (c) => { | ||
| console.log("v2/*", c.req.url); | ||
| const appDO = getDO(c, c.env.APP_DO_ID || "app"); | ||
| const v2AllHandler = factory.createHandlers(async (c) => { | ||
| const env = SqlServerRouterEnvSchema.parse(c.env); | ||
| const appDO = getDO(c, env.APP_DO_ID); | ||
| const url = new URL(c.req.url); | ||
| url.pathname = url.pathname.replace(c.env.prefixRegex || /^\/api\/sqld/, "/sql"); | ||
| url.pathname = url.pathname.replace( | ||
| env.prefixRegex ? new RegExp(env.prefixRegex) : /^\/api\/sqld/, | ||
| "/sql" | ||
| ); | ||
| const modifiedRequest = new Request(url, c.req.raw); | ||
| return appDO.fetch(modifiedRequest); | ||
| }); | ||
| router.all("/v3/*", async (c) => { | ||
| console.log("v3/*", c.req.url); | ||
| const appDO = getDO(c, c.env.APP_DO_ID || "app"); | ||
| const v3AllHandler = factory.createHandlers(async (c) => { | ||
| const env = SqlServerRouterEnvSchema.parse(c.env); | ||
| const appDO = getDO(c, env.APP_DO_ID); | ||
| const url = new URL(c.req.url); | ||
| url.pathname = url.pathname.replace(c.env.prefixRegex || /^\/api\/sqld/, "/sql"); | ||
| console.log("modified url", url.pathname); | ||
| url.pathname = url.pathname.replace( | ||
| env.prefixRegex ? new RegExp(env.prefixRegex) : /^\/api\/sqld/, | ||
| "/sql" | ||
| ); | ||
| const modifiedRequest = new Request(url, c.req.raw); | ||
| return appDO.fetch(modifiedRequest); | ||
| }); | ||
| router.all("/sql/*", async (c) => { | ||
| console.log("sql/*", c.req.url); | ||
| const appDO = getDO(c, c.env.APP_DO_ID || "app"); | ||
| const sqlAllHandler = factory.createHandlers(async (c) => { | ||
| const env = SqlServerRouterEnvSchema.parse(c.env); | ||
| const appDO = getDO(c, env.APP_DO_ID); | ||
| const url = new URL(c.req.url); | ||
| url.pathname = url.pathname.replace(c.env.prefixRegex || /^\/api\/sqld/, ""); | ||
| url.pathname = url.pathname.replace( | ||
| env.prefixRegex ? new RegExp(env.prefixRegex) : /^\/api\/sqld/, | ||
| "" | ||
| ); | ||
| const modifiedRequest = new Request(url, c.req.raw); | ||
| return appDO.fetch(modifiedRequest); | ||
| }); | ||
| router.all("/sql", async (c) => { | ||
| console.log("sql", c.req.url); | ||
| const appDO = getDO(c, c.env.APP_DO_ID || "app"); | ||
| const sqlHandler = factory.createHandlers(async (c) => { | ||
| const env = SqlServerRouterEnvSchema.parse(c.env); | ||
| const appDO = getDO(c, env.APP_DO_ID); | ||
| const url = new URL(c.req.url); | ||
| url.pathname = url.pathname.replace(c.env.prefixRegex || /^\/api\/sqld/, ""); | ||
| url.pathname = url.pathname.replace( | ||
| env.prefixRegex ? new RegExp(env.prefixRegex) : /^\/api\/sqld/, | ||
| "" | ||
| ); | ||
| const modifiedRequest = new Request(url, c.req.raw); | ||
| return appDO.fetch(modifiedRequest); | ||
| }); | ||
| router.get("/v2", ...v2Handler); | ||
| router.get("/v3", ...v3Handler); | ||
| router.get("/v3-protobuf", ...v3ProtobufHandler); | ||
| router.all("/v2/*", ...v2AllHandler); | ||
| router.all("/v3/*", ...v3AllHandler); | ||
| router.all("/sql/*", ...sqlAllHandler); | ||
| router.all("/sql", ...sqlHandler); | ||
| return router; | ||
| } | ||
| var BetterAuthRouterEnvSchema = z.object({ | ||
| APP_DO_ID: z.string().optional().default("app") | ||
| }); | ||
| function betterAuthRouter() { | ||
| const factory = createFactory(); | ||
| const router = new Hono(); | ||
| router.all("/*", (c) => { | ||
| const authDO = getDO(c, c.env.APP_DO_ID || "app"); | ||
| const handler = factory.createHandlers((c) => { | ||
| const env = BetterAuthRouterEnvSchema.parse(c.env); | ||
| const authDO = getDO(c, env.APP_DO_ID); | ||
| return authDO.fetch(c.req.raw); | ||
| }); | ||
| router.all("/*", ...handler); | ||
| return router; | ||
| } | ||
| export { AuthDO, LibSQLHttpServer, authMiddleware, betterAuthRouter, getBearerToken, getDO, requireAuth, sqlServerRouter }; | ||
| export { AuthDO, LibSQLHttpServer, Logger, authMiddleware, betterAuthRouter, createLogger, getBearerToken, getDO, requireAuth, sqlServerRouter }; |
+19
-26
| { | ||
| "name": "@mertdogar/better-auth-do-sqlite", | ||
| "libName": "better-auth-do-sqlite", | ||
| "version": "0.0.20", | ||
| "version": "0.0.22", | ||
| "description": "Complete authentication and database solution for Cloudflare Durable Objects with Better Auth and libSQL HTTP protocol support", | ||
@@ -20,9 +20,5 @@ "main": "./dist/index.cjs", | ||
| "format": "eslint . --fix", | ||
| "build-types": "node --import tsx ./tools/fix-dts.mts", | ||
| "build-common": "tsup lib/index.ts --format cjs,esm --clean --treeshake --dts", | ||
| "build-all": "tsup lib/index.ts --format cjs,esm --treeshake --dts --onSuccess \"npm run build-types\"", | ||
| "build": "npm run build-common -- --onSuccess \"npm run build-types\"", | ||
| "dev": "npm run build-common -- --sourcemap --watch --onSuccess \"npm run build-types\"", | ||
| "dev-all": "npm run build-all -- --watch", | ||
| "prepublishOnly": "npm run build-all", | ||
| "build": "tsup lib/index.ts --format cjs,esm --clean --treeshake --dts", | ||
| "dev": "tsup lib/index.ts --format cjs,esm --clean --treeshake --dts --sourcemap --watch", | ||
| "prepublishOnly": "npm run build", | ||
| "update-jsr-version": "node --import tsx ./tools/update-jsr-version.mts", | ||
@@ -32,5 +28,3 @@ "publish-package": "changeset publish", | ||
| "check-jsr": "npx jsr publish --allow-dirty --dry-run", | ||
| "change": "changeset", | ||
| "test": "vitest", | ||
| "test-coverage": "vitest --coverage" | ||
| "change": "changeset" | ||
| }, | ||
@@ -71,16 +65,15 @@ "repository": { | ||
| "@eslint/eslintrc": "^3.3.1", | ||
| "@eslint/js": "^9.25.1", | ||
| "@eslint/js": "^9.38.0", | ||
| "@libsql/client": "^0.15.15", | ||
| "@testing-library/dom": "^10.4.0", | ||
| "@types/node": "^22.15.3", | ||
| "@types/node": "^22.18.13", | ||
| "@types/tx2": "^1.0.3", | ||
| "@typescript-eslint/eslint-plugin": "^8.31.1", | ||
| "@typescript-eslint/parser": "^8.31.1", | ||
| "@typescript-eslint/utils": "^8.31.1", | ||
| "@vitest/coverage-v8": "^3.1.2", | ||
| "eslint": "^9.25.1", | ||
| "@typescript-eslint/eslint-plugin": "^8.46.2", | ||
| "@typescript-eslint/parser": "^8.46.2", | ||
| "@typescript-eslint/utils": "^8.46.2", | ||
| "eslint": "^9.38.0", | ||
| "eslint-config-prettier": "^10.0.1", | ||
| "eslint-plugin-prettier": "^5.2.3", | ||
| "globals": "^15.15.0", | ||
| "jsdom": "^26.1.0", | ||
| "globals": "^16.4.0", | ||
| "jsdom": "^27.0.1", | ||
| "prettier": "^3.5.0", | ||
@@ -91,4 +84,3 @@ "tslib": "^2.8.1", | ||
| "typescript": "^5.8.3", | ||
| "typescript-eslint": "^8.46.0", | ||
| "vitest": "^3.1.2" | ||
| "typescript-eslint": "^8.46.2" | ||
| }, | ||
@@ -106,8 +98,9 @@ "files": [ | ||
| "dependencies": { | ||
| "@cloudflare/workers-types": "4.20251004.0", | ||
| "better-auth": "1.3.26", | ||
| "hono": "4.9.10", | ||
| "@cloudflare/workers-types": "4.20251014.0", | ||
| "better-auth": "1.3.34", | ||
| "hono": "4.10.3", | ||
| "kysely": "0.28.8", | ||
| "kysely-plugin-serialize": "0.8.2" | ||
| "kysely-plugin-serialize": "0.8.2", | ||
| "zod": "^3.24.2" | ||
| } | ||
| } |
+68
-20
@@ -72,2 +72,3 @@ # @mertdogar/better-auth-do-sqlite | ||
| APP_DO: DurableObjectNamespace | ||
| APP_DO_ID?: string // Optional: defaults to 'app' | ||
| } | ||
@@ -92,6 +93,6 @@ ``` | ||
| // Apply auth middleware globally | ||
| app.use('*', authMiddleware('app')) | ||
| app.use('*', authMiddleware()) | ||
| // Mount Better Auth routes | ||
| app.route('/api/auth', betterAuthRouter('app')) | ||
| app.route('/api/auth', betterAuthRouter()) | ||
@@ -137,14 +138,26 @@ // Protected route example | ||
| ### `authMiddleware(bindingPrefix: string)` | ||
| ### `authMiddleware()` | ||
| Middleware that attaches authentication context to requests. | ||
| Hono middleware that injects session information into the context. | ||
| **Parameters:** | ||
| ```typescript | ||
| app.use('*', authMiddleware()) | ||
| ``` | ||
| - `bindingPrefix`: The prefix of your Durable Object binding (e.g., `'app'` for `APP_DO`) | ||
| The middleware reads `APP_DO_ID` from `c.env` (defaults to `'app'` if not set). | ||
| ### `betterAuthRouter<E>()` | ||
| Hono router that handles all Better Auth endpoints. Generic type parameter allows type-safe integration with your app's environment. | ||
| ```typescript | ||
| app.use('*', authMiddleware('app')) | ||
| // Basic usage | ||
| app.route('/api/auth', betterAuthRouter()) | ||
| // With explicit types | ||
| app.route('/api/auth', betterAuthRouter<{ Bindings: Env }>()) | ||
| ``` | ||
| The router reads `APP_DO_ID` from `c.env` (defaults to `'app'` if not set). | ||
| ### `betterAuthRouter(bindingPrefix: string)` | ||
@@ -170,3 +183,3 @@ | ||
| ### `getDO(context: HonoContext, bindingPrefix: string)` | ||
| ### `getDO(context: HonoContext, doId: string)` | ||
@@ -178,3 +191,3 @@ Helper function to get the Durable Object instance. | ||
| - `context`: Hono context object | ||
| - `bindingPrefix`: The prefix of your Durable Object binding | ||
| - `doId`: The Durable Object instance ID/name | ||
@@ -185,2 +198,16 @@ ```typescript | ||
| ### `sqlServerRouter<E>()` | ||
| Hono router that proxies libSQL HTTP protocol requests to the Durable Object. Generic type parameter allows type-safe integration. | ||
| ```typescript | ||
| // Basic usage | ||
| app.route('/api/sqld', sqlServerRouter()) | ||
| // With explicit types | ||
| app.route('/api/sqld', sqlServerRouter<{ Bindings: Env }>()) | ||
| ``` | ||
| The router reads `APP_DO_ID` and optional `prefixRegex` from `c.env`. | ||
| ## Configuration | ||
@@ -202,7 +229,15 @@ | ||
| The library expects a Durable Object binding with the pattern `{PREFIX}_DO`. For example: | ||
| The library expects a Durable Object binding named `APP_DO` in your `wrangler.jsonc`. The middleware and routers read `APP_DO_ID` from `c.env` to determine which Durable Object instance to use: | ||
| - If you use `authMiddleware('app')`, you need an `APP_DO` binding | ||
| - If you use `authMiddleware('auth')`, you need an `AUTH_DO` binding | ||
| ```typescript | ||
| // Default behavior: uses instance ID 'app' | ||
| app.use('*', authMiddleware()) | ||
| // Custom instance: set APP_DO_ID dynamically | ||
| app.use('/tenant/:id/*', async (c, next) => { | ||
| c.env.APP_DO_ID = c.req.param('id') | ||
| return authMiddleware()(c, next) | ||
| }) | ||
| ``` | ||
| ## Advanced Usage | ||
@@ -232,3 +267,3 @@ | ||
| You can use multiple Durable Object instances for different authentication domains: | ||
| You can use multiple Durable Object instances for different authentication domains by setting different `APP_DO_ID` values: | ||
@@ -242,11 +277,24 @@ ```typescript | ||
| const app = new Hono() | ||
| interface Env { | ||
| JWT_SECRET: string | ||
| USER_AUTH_DO: DurableObjectNamespace | ||
| ADMIN_AUTH_DO: DurableObjectNamespace | ||
| APP_DO: DurableObjectNamespace | ||
| } | ||
| // User routes | ||
| app.use('/user/*', authMiddleware('userAuth')) | ||
| app.route('/user/auth', betterAuthRouter('userAuth')) | ||
| const app = new Hono<{ Bindings: Env }>() | ||
| // Admin routes | ||
| app.use('/admin/*', authMiddleware('adminAuth')) | ||
| app.route('/admin/auth', betterAuthRouter('adminAuth')) | ||
| // User routes - set APP_DO_ID to 'userAuth' | ||
| app.use('/user/*', async (c, next) => { | ||
| c.env.APP_DO_ID = 'userAuth' | ||
| return authMiddleware()(c, next) | ||
| }) | ||
| app.route('/user/auth', betterAuthRouter()) | ||
| // Admin routes - set APP_DO_ID to 'adminAuth' | ||
| app.use('/admin/*', async (c, next) => { | ||
| c.env.APP_DO_ID = 'adminAuth' | ||
| return authMiddleware()(c, next) | ||
| }) | ||
| app.route('/admin/auth', betterAuthRouter()) | ||
| ``` | ||
@@ -253,0 +301,0 @@ |
-43
| # Changelog | ||
| All notable changes to this project will be documented in this file. | ||
| The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | ||
| and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
| ## [Unreleased] | ||
| ## [0.0.2] - 2025-10-06 | ||
| ### Fixed | ||
| - Fixed package entry points in `package.json` | ||
| - Corrected `types` path from `dist/lib/index.d.ts` to `dist/index.d.ts` | ||
| - Removed non-existent `browser` (UMD) export | ||
| - Updated `main` to point to CommonJS build (`dist/index.cjs`) | ||
| - Simplified `exports` configuration for better compatibility with Vite and other bundlers | ||
| ## [0.0.1] - 2025-10-06 | ||
| ### Added | ||
| - Initial release | ||
| - Better Auth adapter for Cloudflare Durable Objects with SQLite storage | ||
| - `@Authenticatable()` decorator for easy integration | ||
| - `AuthenticatableDurableObject` base class | ||
| - Full Better Auth support with automatic schema initialization | ||
| - libSQL HTTP protocol server (v1, v2, v3) | ||
| - Direct SQLite database access via HTTP | ||
| - Compatible with official `@libsql/client` | ||
| - Email & password authentication | ||
| - Session management with configurable expiration | ||
| - RPC methods for user management | ||
| - Hono middleware for route protection | ||
| - Comprehensive TypeScript type definitions | ||
| - Debug logging support | ||
| - Data type transformations (JS ↔ SQLite) | ||
| - Complete documentation and examples | ||
| [unreleased]: https://github.com/mertdogar/better-auth-do-sqlite/compare/v0.0.2...HEAD | ||
| [0.0.2]: https://github.com/mertdogar/better-auth-do-sqlite/compare/v0.0.1...v0.0.2 | ||
| [0.0.1]: https://github.com/mertdogar/better-auth-do-sqlite/releases/tag/v0.0.1 |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
423355
4.81%21
-8.7%5733
9.01%369
14.95%6
20%7
-12.5%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated