@effectify/node-better-auth 0.5.11 → 1.0.0-alpha.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.
package/dist/package.json
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effectify/node-better-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-alpha.0",
|
|
4
4
|
"description": "Integration of better-auth with Effect for Node.js applications",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/devx-op/effectify",
|
|
8
|
+
"directory": "packages/node/better-auth"
|
|
9
|
+
},
|
|
5
10
|
"type": "module",
|
|
6
11
|
"main": "./dist/src/index.js",
|
|
7
12
|
"module": "./dist/src/index.js",
|
|
@@ -25,21 +30,20 @@
|
|
|
25
30
|
"test": "vitest run"
|
|
26
31
|
},
|
|
27
32
|
"peerDependencies": {
|
|
28
|
-
"@effect/platform": "catalog:",
|
|
29
|
-
"@effect/platform-node": "catalog:",
|
|
30
33
|
"better-auth": "catalog:",
|
|
31
34
|
"effect": "catalog:"
|
|
32
35
|
},
|
|
33
36
|
"devDependencies": {
|
|
37
|
+
"@effect/platform-node": "catalog:",
|
|
34
38
|
"@effect/vitest": "catalog:",
|
|
35
|
-
"vitest": "catalog:",
|
|
36
|
-
"better-sqlite3": "catalog:",
|
|
37
39
|
"@types/better-sqlite3": "catalog:",
|
|
38
|
-
"@types/node": "catalog:"
|
|
40
|
+
"@types/node": "catalog:",
|
|
41
|
+
"better-sqlite3": "catalog:",
|
|
42
|
+
"effect": "catalog:",
|
|
43
|
+
"vitest": "catalog:"
|
|
39
44
|
},
|
|
40
45
|
"dependenciesMeta": {},
|
|
41
46
|
"dependencies": {
|
|
42
47
|
"zod": "catalog:"
|
|
43
|
-
}
|
|
44
|
-
"optionalDependencies": {}
|
|
48
|
+
}
|
|
45
49
|
}
|
|
@@ -1,27 +1,42 @@
|
|
|
1
1
|
import { betterAuth, type BetterAuthOptions, type Session, type User } from "better-auth";
|
|
2
|
-
import * as
|
|
2
|
+
import * as ServiceMap from "effect/ServiceMap";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
3
4
|
import * as Layer from "effect/Layer";
|
|
4
|
-
import * as Schema from "effect/Schema";
|
|
5
5
|
export declare namespace AuthService {
|
|
6
6
|
export type AuthInstance = ReturnType<typeof betterAuth>;
|
|
7
|
-
const AuthServiceContext_base:
|
|
8
|
-
|
|
9
|
-
}
|
|
7
|
+
const AuthServiceContext_base: ServiceMap.ServiceClass<AuthServiceContext, "AuthServiceContext", {
|
|
8
|
+
auth: import("better-auth").Auth<BetterAuthOptions>;
|
|
9
|
+
}> & {
|
|
10
|
+
readonly make: (options: BetterAuthOptions) => Effect.Effect<{
|
|
11
|
+
auth: import("better-auth").Auth<BetterAuthOptions>;
|
|
12
|
+
}, never, never>;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* AuthServiceContext provides access to the better-auth instance.
|
|
16
|
+
*
|
|
17
|
+
* In Effect v4, services are created with ServiceMap.Service and have a .Layer property
|
|
18
|
+
* for easy Layer composition.
|
|
19
|
+
*/
|
|
10
20
|
export class AuthServiceContext extends AuthServiceContext_base {
|
|
21
|
+
static readonly layer: (options: BetterAuthOptions) => Layer.Layer<AuthServiceContext, never, never>;
|
|
11
22
|
}
|
|
12
|
-
|
|
13
|
-
const AuthContext_base: Context.TagClass<AuthContext, "AuthContext", {
|
|
23
|
+
const AuthContext_base: ServiceMap.ServiceClass<AuthContext, "AuthContext", {
|
|
14
24
|
readonly user: User;
|
|
15
25
|
readonly session: Session;
|
|
16
26
|
}>;
|
|
27
|
+
/**
|
|
28
|
+
* AuthContext provides access to the authenticated user and session.
|
|
29
|
+
*
|
|
30
|
+
* This is request-scoped context provided at runtime during request handling.
|
|
31
|
+
*/
|
|
17
32
|
export class AuthContext extends AuthContext_base {
|
|
18
33
|
}
|
|
19
|
-
const Unauthorized_base:
|
|
20
|
-
readonly _tag:
|
|
21
|
-
} &
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
34
|
+
const Unauthorized_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
35
|
+
readonly _tag: "Unauthorized";
|
|
36
|
+
} & Readonly<A>;
|
|
37
|
+
export class Unauthorized extends Unauthorized_base<{
|
|
38
|
+
readonly details: string;
|
|
39
|
+
}> {
|
|
25
40
|
}
|
|
26
41
|
export {};
|
|
27
42
|
}
|
|
@@ -1,31 +1,50 @@
|
|
|
1
1
|
import { betterAuth } from "better-auth";
|
|
2
|
-
import * as
|
|
2
|
+
import * as ServiceMap from "effect/ServiceMap";
|
|
3
3
|
import * as Effect from "effect/Effect";
|
|
4
4
|
import * as Layer from "effect/Layer";
|
|
5
|
-
import * as
|
|
5
|
+
import * as Data from "effect/Data";
|
|
6
6
|
export var AuthService;
|
|
7
7
|
(function (AuthService) {
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* AuthServiceContext provides access to the better-auth instance.
|
|
10
|
+
*
|
|
11
|
+
* In Effect v4, services are created with ServiceMap.Service and have a .Layer property
|
|
12
|
+
* for easy Layer composition.
|
|
13
|
+
*/
|
|
14
|
+
class AuthServiceContext extends ServiceMap.Service()("AuthServiceContext", {
|
|
15
|
+
make: (options) => Effect.gen(function* () {
|
|
16
|
+
// Try to extract database path from Database instance for logging
|
|
17
|
+
const dbPathForLog = options.database &&
|
|
18
|
+
typeof options.database === "object" &&
|
|
19
|
+
"name" in options.database
|
|
20
|
+
? String(options.database.name || "unknown")
|
|
21
|
+
: "unknown";
|
|
22
|
+
yield* Effect.logInfo(`Creating auth instance with database path: ${dbPathForLog}`);
|
|
23
|
+
const isAdapter = options.database &&
|
|
24
|
+
typeof options.database === "object" &&
|
|
25
|
+
("createSession" in options.database ||
|
|
26
|
+
"createUser" in options.database ||
|
|
27
|
+
"client" in options.database);
|
|
28
|
+
if (!isAdapter) {
|
|
29
|
+
yield* Effect.logInfo("Auto-migration is disabled in better-auth v1.4.10+. Please run 'better-auth migrate' manually.");
|
|
30
|
+
}
|
|
31
|
+
return { auth: betterAuth(options) };
|
|
32
|
+
}),
|
|
33
|
+
}) {
|
|
34
|
+
// Build the layer yourself from the make effect
|
|
35
|
+
static layer = (options) => Layer.effect(this, this.make(options));
|
|
9
36
|
}
|
|
10
37
|
AuthService.AuthServiceContext = AuthServiceContext;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const isAdapter = options.database &&
|
|
18
|
-
typeof options.database === "object" &&
|
|
19
|
-
("createSession" in options.database || "createUser" in options.database || "client" in options.database);
|
|
20
|
-
if (!isAdapter) {
|
|
21
|
-
yield* Effect.logInfo("Auto-migration is disabled in better-auth v1.4.10+. Please run 'better-auth migrate' manually.");
|
|
22
|
-
}
|
|
23
|
-
return { auth: betterAuth(options) };
|
|
24
|
-
}));
|
|
25
|
-
class AuthContext extends Context.Tag("AuthContext")() {
|
|
38
|
+
/**
|
|
39
|
+
* AuthContext provides access to the authenticated user and session.
|
|
40
|
+
*
|
|
41
|
+
* This is request-scoped context provided at runtime during request handling.
|
|
42
|
+
*/
|
|
43
|
+
class AuthContext extends ServiceMap.Service()("AuthContext") {
|
|
26
44
|
}
|
|
27
45
|
AuthService.AuthContext = AuthContext;
|
|
28
|
-
|
|
46
|
+
// In v4, use Data.TaggedError instead of Schema.TaggedError for simple errors
|
|
47
|
+
class Unauthorized extends Data.TaggedError("Unauthorized") {
|
|
29
48
|
}
|
|
30
49
|
AuthService.Unauthorized = Unauthorized;
|
|
31
50
|
})(AuthService || (AuthService = {}));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const BetterAuthApiError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").
|
|
1
|
+
declare const BetterAuthApiError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
2
2
|
readonly _tag: "BetterAuthApiError";
|
|
3
3
|
} & Readonly<A>;
|
|
4
4
|
export declare class BetterAuthApiError extends BetterAuthApiError_base<{
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import * as HttpServerRequest from "@effect/platform/HttpServerRequest";
|
|
2
|
-
import * as HttpServerResponse from "@effect/platform/HttpServerResponse";
|
|
3
1
|
import type { Auth } from "better-auth";
|
|
4
|
-
import type { ConfigError } from "effect/ConfigError";
|
|
5
2
|
import * as Effect from "effect/Effect";
|
|
6
3
|
import { BetterAuthApiError } from "./better-auth-error.js";
|
|
4
|
+
import type { ConfigError } from "effect/Config";
|
|
5
|
+
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
|
|
6
|
+
import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
|
|
7
7
|
export declare const toEffectHandler: (auth: {
|
|
8
8
|
handler: Auth["handler"];
|
|
9
9
|
} | Auth["handler"]) => Effect.Effect<HttpServerResponse.HttpServerResponse, BetterAuthApiError | ConfigError, HttpServerRequest.HttpServerRequest>;
|
package/dist/src/lib/handler.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import * as HttpServerRequest from "@effect/platform/HttpServerRequest";
|
|
2
|
-
import * as HttpServerResponse from "@effect/platform/HttpServerResponse";
|
|
3
|
-
import * as NodeHttpServerRequest from "@effect/platform-node/NodeHttpServerRequest";
|
|
4
1
|
import { toNodeHandler } from "better-auth/node";
|
|
5
2
|
import * as Config from "effect/Config";
|
|
6
3
|
import * as Effect from "effect/Effect";
|
|
7
4
|
import { BetterAuthApiError } from "./better-auth-error.js";
|
|
5
|
+
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
|
|
6
|
+
import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
|
|
7
|
+
import * as NodeHttpServerRequest from "@effect/platform-node/NodeHttpServerRequest";
|
|
8
8
|
const TRAILING_SLASH_REGEX = /\/+$/;
|
|
9
9
|
const PROTOCOL_REGEX = /(https?:\/\/)+/;
|
|
10
10
|
export const toEffectHandler = (auth) => Effect.gen(function* () {
|
|
@@ -14,7 +14,10 @@ export const toEffectHandler = (auth) => Effect.gen(function* () {
|
|
|
14
14
|
const appUrl = yield* Config.url("BETTER_AUTH_URL");
|
|
15
15
|
// Debug: log the configured app URL so we can diagnose Config errors
|
|
16
16
|
yield* Effect.log(`toEffectHandler: BETTER_AUTH_URL=${String(appUrl)}`);
|
|
17
|
-
const normalizeUrl = (url) => url
|
|
17
|
+
const normalizeUrl = (url) => url
|
|
18
|
+
.toString()
|
|
19
|
+
.replace(TRAILING_SLASH_REGEX, "")
|
|
20
|
+
.replace(PROTOCOL_REGEX, "http://");
|
|
18
21
|
const allowedOrigins = [normalizeUrl(appUrl)];
|
|
19
22
|
const origin = nodeRequest.headers.origin ? normalizeUrl(appUrl) : "";
|
|
20
23
|
if (allowedOrigins.includes(origin)) {
|
|
@@ -31,7 +34,14 @@ export const toEffectHandler = (auth) => Effect.gen(function* () {
|
|
|
31
34
|
return HttpServerResponse.empty({ status: 200 });
|
|
32
35
|
}
|
|
33
36
|
// Log incoming request for debugging
|
|
34
|
-
|
|
37
|
+
const authHeader = nodeRequest.headers.authorization;
|
|
38
|
+
yield* Effect.log(`toEffectHandler: incoming ${nodeRequest.method} ${String(nodeRequest.url)} headers: cookie=${nodeRequest.headers.cookie?.substring(0, 50) || "none"}, auth=${authHeader?.substring(0, 30) || "none"}`);
|
|
39
|
+
// If no cookie but has Authorization header (bearer token), set it as cookie for better-auth
|
|
40
|
+
if (!nodeRequest.headers.cookie && authHeader?.startsWith("Bearer ")) {
|
|
41
|
+
const token = authHeader.slice(7); // Remove "Bearer " prefix
|
|
42
|
+
nodeRequest.headers.cookie = `better-auth.session_token=${token}`;
|
|
43
|
+
yield* Effect.log(`toEffectHandler: using token from Authorization header as cookie`);
|
|
44
|
+
}
|
|
35
45
|
try {
|
|
36
46
|
yield* Effect.tryPromise({
|
|
37
47
|
try: () => "handler" in auth
|
|
@@ -58,5 +68,7 @@ export const toEffectHandler = (auth) => Effect.gen(function* () {
|
|
|
58
68
|
}
|
|
59
69
|
return HttpServerResponse.empty({ status: 500 });
|
|
60
70
|
}
|
|
61
|
-
return HttpServerResponse.empty({
|
|
71
|
+
return HttpServerResponse.empty({
|
|
72
|
+
status: nodeResponse.writableFinished ? nodeResponse.statusCode : 499,
|
|
73
|
+
});
|
|
62
74
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effectify/node-better-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-alpha.0",
|
|
4
4
|
"description": "Integration of better-auth with Effect for Node.js applications",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/devx-op/effectify",
|
|
8
|
+
"directory": "packages/node/better-auth"
|
|
9
|
+
},
|
|
5
10
|
"type": "module",
|
|
6
11
|
"main": "./dist/src/index.js",
|
|
7
12
|
"module": "./dist/src/index.js",
|
|
@@ -22,23 +27,22 @@
|
|
|
22
27
|
"!**/*.tsbuildinfo"
|
|
23
28
|
],
|
|
24
29
|
"peerDependencies": {
|
|
25
|
-
"@effect/platform": "0.94.0",
|
|
26
|
-
"@effect/platform-node": "0.104.1",
|
|
27
30
|
"better-auth": "1.4.10",
|
|
28
|
-
"effect": "
|
|
31
|
+
"effect": "4.0.0-beta.33"
|
|
29
32
|
},
|
|
30
33
|
"devDependencies": {
|
|
31
|
-
"@effect/
|
|
32
|
-
"vitest": "4.0.
|
|
33
|
-
"better-sqlite3": "12.6.2",
|
|
34
|
+
"@effect/platform-node": "4.0.0-beta.33",
|
|
35
|
+
"@effect/vitest": "4.0.0-beta.33",
|
|
34
36
|
"@types/better-sqlite3": "7.6.13",
|
|
35
|
-
"@types/node": "20.19.25"
|
|
37
|
+
"@types/node": "20.19.25",
|
|
38
|
+
"better-sqlite3": "12.6.2",
|
|
39
|
+
"effect": "4.0.0-beta.33",
|
|
40
|
+
"vitest": "^4.1.0"
|
|
36
41
|
},
|
|
37
42
|
"dependenciesMeta": {},
|
|
38
43
|
"dependencies": {
|
|
39
44
|
"zod": "4.3.6"
|
|
40
45
|
},
|
|
41
|
-
"optionalDependencies": {},
|
|
42
46
|
"scripts": {
|
|
43
47
|
"test": "vitest run"
|
|
44
48
|
}
|