@mframework/adapter-betterauth 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ Starter Adapter Template
2
+ =========================
3
+
4
+ This package is a template to scaffold new adapter packages that integrate with Meeovi layers.
5
+
6
+ Usage
7
+ -----
8
+
9
+ - From this package directory, run:
10
+
11
+ ```
12
+ npm run create -- <short-name>
13
+ ```
14
+
15
+ - Example:
16
+
17
+ ```
18
+ npm run create -- shop
19
+ ```
20
+
21
+ This creates a new adapter package at `../adapter-shop` (relative to this template) with the package name `@mframework/adapter-shop`.
22
+
23
+ What you get
24
+ ------------
25
+
26
+ - A ready-to-edit adapter package with `index.ts` and `src/` files for `transport`, `auth`, `commerce`, `search`, and `utils`.
27
+ - Placeholders in files are marked as `__PACKAGE_NAME__` and `__SHORT_NAME__` to help you customize.
28
+
29
+ How to edit
30
+ -----------
31
+
32
+ 1. Open the new package folder.
33
+ 2. Replace placeholder endpoints and logic in `src/*` with your adapter's API.
34
+ 3. Update `package.json` fields as needed.
35
+ 4. Run `npm run build` inside the new adapter package to compile TypeScript.
36
+
37
+ Notes
38
+ -----
39
+
40
+ - The scaffold script performs simple text replacements; review generated files before publishing.
41
+ - This template is intentionally minimal — implement only the methods your backend supports.
42
+
43
+ CLI Flags
44
+ ---------
45
+
46
+ The starter CLI supports interactive and non-interactive modes. Flags:
47
+
48
+ - `--name` / `-n`: Adapter short name (e.g. `shop`).
49
+ - `--desc` / `-d`: Description for the generated package.
50
+ - `--layers` / `-l`: Comma-separated list of layers to scaffold (e.g. `commerce,auth`).
51
+ - `--dest`: Destination path for the generated package.
52
+ - `--no-install`: Skip running `npm install` after scaffolding.
53
+
54
+ Examples
55
+ --------
56
+
57
+ - Interactive:
58
+
59
+ ```
60
+ npm run create:interactive
61
+ ```
62
+
63
+ - Non-interactive (with install):
64
+
65
+ ```
66
+ npm run create:interactive -- --name shop --desc "Shop adapter" --layers commerce,auth
67
+ ```
68
+
69
+ - Non-interactive (skip install):
70
+
71
+ ```
72
+ npm run create:interactive -- --name shop --layers commerce --no-install
73
+ ```
74
+
75
+ Repo-level wrapper
76
+ ------------------
77
+
78
+ From the repo root you can use the provided script:
79
+
80
+ ```
81
+ scripts/create-adapter --name shop --layers commerce --no-install
82
+ ```
83
+
84
+
85
+ Note: adapter credentials and endpoints can be centrally configured in your main app's `.env` file. See the repository `.env.example` for recommended variable names.
@@ -0,0 +1,15 @@
1
+ export declare const installAuthAdapter: (config: {
2
+ baseUrl: string;
3
+ apiKey?: string;
4
+ }) => void;
5
+ export { auth, Auth } from './src/betterauth';
6
+ export { default as BetterAuthProvider } from './src/provider';
7
+ export { getAuthPlugins } from './src/plugins';
8
+ export * from './src/types';
9
+ export * from './src/auth';
10
+ export * from './src/utils';
11
+ export * from './src/transport';
12
+ export * from './src/framework';
13
+ export * from './src/registry';
14
+ export * from './src/validation';
15
+ export * from './src/config';
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ import { setAuthAdapter } from '@mframework/sdk';
2
+ import { createAuthTransport } from './src/transport';
3
+ import { createAuthAdapter } from './src/auth';
4
+ export const installAuthAdapter = (config) => {
5
+ const transport = createAuthTransport(config);
6
+ setAuthAdapter(createAuthAdapter(transport));
7
+ };
8
+ // Export the server-side BetterAuth instance (if present) so other layers can
9
+ // import the runtime `auth` instance from this package. The file is optional
10
+ // and will only exist when BetterAuth is configured for the adapter.
11
+ export { auth } from './src/betterauth';
12
+ export { default as BetterAuthProvider } from './src/provider';
13
+ export { getAuthPlugins } from './src/plugins';
14
+ export * from './src/types';
15
+ export * from './src/auth';
16
+ export * from './src/utils';
17
+ export * from './src/transport';
18
+ export * from './src/framework';
19
+ export * from './src/registry';
20
+ export * from './src/validation';
21
+ export * from './src/config';
@@ -0,0 +1,2 @@
1
+ import type { AuthAdapter, TransportAdapter } from '@mframework/sdk';
2
+ export declare const createAuthAdapter: (transport: TransportAdapter) => AuthAdapter;
@@ -0,0 +1,31 @@
1
+ import { unwrap } from './utils';
2
+ export const createAuthAdapter = (transport) => ({
3
+ async login(input) {
4
+ const res = await transport.request('POST', '/login', {
5
+ body: input
6
+ });
7
+ return unwrap(res);
8
+ },
9
+ async register(input) {
10
+ const res = await transport.request('POST', '/register', {
11
+ body: input
12
+ });
13
+ return unwrap(res);
14
+ },
15
+ async logout() {
16
+ const res = await transport.request('POST', '/logout');
17
+ return unwrap({ ...res, data: true });
18
+ },
19
+ async getSession() {
20
+ const res = await transport.request('GET', '/session');
21
+ return unwrap(res);
22
+ },
23
+ async refresh() {
24
+ const res = await transport.request('POST', '/refresh');
25
+ return unwrap(res);
26
+ },
27
+ async getUser() {
28
+ const res = await transport.request('GET', '/user');
29
+ return unwrap(res);
30
+ }
31
+ });
@@ -0,0 +1,11 @@
1
+ import "dotenv/config";
2
+ export declare const auth: import("better-auth").Auth<{
3
+ experimental: {
4
+ joins: true;
5
+ };
6
+ database: (options: import("better-auth").BetterAuthOptions) => import("better-auth").DBAdapter<import("better-auth").BetterAuthOptions>;
7
+ emailAndPassword: {
8
+ enabled: true;
9
+ };
10
+ }>;
11
+ export type Auth = typeof auth;
@@ -0,0 +1,12 @@
1
+ import "dotenv/config";
2
+ import { betterAuth } from "better-auth";
3
+ import { prismaAdapter } from "better-auth/adapters/prisma";
4
+ import { prisma } from "@mframework/api";
5
+ // Create and export the BetterAuth runtime instance using the centralized
6
+ // Prisma client exported from packages/modules/api. Layers can import `auth`
7
+ // from this package to get the configured auth instance.
8
+ export const auth = betterAuth({
9
+ experimental: { joins: true },
10
+ database: prismaAdapter(prisma, { provider: 'postgresql' }),
11
+ emailAndPassword: { enabled: true }
12
+ });
@@ -0,0 +1,8 @@
1
+ export interface AuthConfig {
2
+ baseUrl: any;
3
+ authProvider: string;
4
+ authUrl?: string;
5
+ sessionCookieName?: string;
6
+ }
7
+ export declare function setAuthConfig(newConfig: Partial<AuthConfig>): void;
8
+ export declare function getAuthConfig(): AuthConfig;
@@ -0,0 +1,12 @@
1
+ let config = {
2
+ authProvider: 'better-auth',
3
+ authUrl: '',
4
+ sessionCookieName: 'session',
5
+ baseUrl: undefined
6
+ };
7
+ export function setAuthConfig(newConfig) {
8
+ config = { ...config, ...newConfig };
9
+ }
10
+ export function getAuthConfig() {
11
+ return config;
12
+ }
@@ -0,0 +1,17 @@
1
+ export interface FrameworkContext {
2
+ getRequestURL?(): string;
3
+ getRequestHeaders?(): Record<string, string>;
4
+ getConfig?(): any;
5
+ reloadApp?(): void;
6
+ state?<T>(key: string, init: () => T): {
7
+ value: T;
8
+ };
9
+ }
10
+ /**
11
+ * Frameworks (Nuxt, React, etc.) call this once during initialization.
12
+ */
13
+ export declare function setFrameworkContext(newCtx: FrameworkContext): void;
14
+ /**
15
+ * Auth providers use this to access framework-specific helpers.
16
+ */
17
+ export declare function getFrameworkContext(): FrameworkContext;
@@ -0,0 +1,13 @@
1
+ let ctx = {};
2
+ /**
3
+ * Frameworks (Nuxt, React, etc.) call this once during initialization.
4
+ */
5
+ export function setFrameworkContext(newCtx) {
6
+ ctx = { ...ctx, ...newCtx };
7
+ }
8
+ /**
9
+ * Auth providers use this to access framework-specific helpers.
10
+ */
11
+ export function getFrameworkContext() {
12
+ return ctx;
13
+ }
@@ -0,0 +1,5 @@
1
+ export type AuthPluginOptions = {
2
+ subscription?: boolean;
3
+ };
4
+ export declare function getAuthPlugins(opts?: AuthPluginOptions): any[];
5
+ export default getAuthPlugins;
@@ -0,0 +1,19 @@
1
+ import { stripeClient } from '@better-auth/stripe/client';
2
+ import { polarClient } from '@polar-sh/better-auth';
3
+ import { adminClient, inferAdditionalFields } from 'better-auth/client/plugins';
4
+ export function getAuthPlugins(opts = {}) {
5
+ const { subscription = true } = opts;
6
+ return [
7
+ inferAdditionalFields({
8
+ user: {
9
+ polarCustomerId: {
10
+ type: 'string'
11
+ }
12
+ }
13
+ }),
14
+ adminClient(),
15
+ polarClient(),
16
+ stripeClient({ subscription })
17
+ ];
18
+ }
19
+ export default getAuthPlugins;
@@ -0,0 +1,2 @@
1
+ declare const BetterAuthProvider: any;
2
+ export default BetterAuthProvider;
@@ -0,0 +1,84 @@
1
+ import * as BetterAuth from 'better-auth';
2
+ import { getAuthConfig } from './config';
3
+ import { getFrameworkContext } from './framework';
4
+ // Create a single shared Better Auth client instance
5
+ let client = null;
6
+ function resolveClientFactory() {
7
+ return BetterAuth.Client ?? BetterAuth.default ?? BetterAuth;
8
+ }
9
+ function getClient() {
10
+ if (client)
11
+ return client;
12
+ const config = getAuthConfig();
13
+ const ctx = getFrameworkContext();
14
+ const Client = resolveClientFactory();
15
+ client = Client({
16
+ baseURL: config.baseUrl,
17
+ fetch: async (url, options = {}) => {
18
+ const baseHeaders = ctx.getRequestHeaders?.() || {};
19
+ let optionHeaders = {};
20
+ if (options.headers instanceof Headers) {
21
+ options.headers.forEach((value, key) => {
22
+ optionHeaders[key] = value;
23
+ });
24
+ }
25
+ else if (Array.isArray(options.headers)) {
26
+ for (const [k, v] of options.headers) {
27
+ optionHeaders[k] = v;
28
+ }
29
+ }
30
+ else if (typeof options.headers === 'object' && options.headers !== null) {
31
+ optionHeaders = options.headers;
32
+ }
33
+ const headers = {
34
+ ...baseHeaders,
35
+ ...optionHeaders
36
+ };
37
+ return fetch(url, { ...options, headers });
38
+ }
39
+ });
40
+ return client;
41
+ }
42
+ const BetterAuthProvider = {
43
+ async login(credentials) {
44
+ const client = getClient();
45
+ const result = await client.signIn(credentials);
46
+ if (result.error) {
47
+ throw new Error(result.error.message || 'Login failed');
48
+ }
49
+ return result.data;
50
+ },
51
+ async logout() {
52
+ const client = getClient();
53
+ await client.signOut();
54
+ const ctx = getFrameworkContext();
55
+ ctx.reloadApp?.();
56
+ },
57
+ async session() {
58
+ const client = getClient();
59
+ const result = await client.session();
60
+ if (result.error) {
61
+ return null;
62
+ }
63
+ return result.data;
64
+ },
65
+ async register(data) {
66
+ const client = getClient();
67
+ const result = await client.signUp(data);
68
+ if (result.error) {
69
+ throw new Error(result.error.message || 'Registration failed');
70
+ }
71
+ return result.data;
72
+ },
73
+ async refresh() {
74
+ const client = getClient();
75
+ const result = await client.refresh();
76
+ if (result.error) {
77
+ throw new Error(result.error.message || 'Session refresh failed');
78
+ }
79
+ return result.data;
80
+ }
81
+ };
82
+ // Register the provider under the canonical name so other layers can discover
83
+ // it using the existing registry mechanism.
84
+ export default BetterAuthProvider;
@@ -0,0 +1,4 @@
1
+ import type { AuthProvider } from './types';
2
+ export declare function registerAuthProvider(name: string, provider: AuthProvider): void;
3
+ export declare function setActiveAuthProvider(name: string): void;
4
+ export declare function getAuthProvider(): AuthProvider;
@@ -0,0 +1,21 @@
1
+ // allow missing entries in the map so TypeScript understands runtime checks
2
+ const providers = {};
3
+ let activeProvider = null;
4
+ export function registerAuthProvider(name, provider) {
5
+ providers[name] = provider;
6
+ }
7
+ export function setActiveAuthProvider(name) {
8
+ if (!providers[name]) {
9
+ throw new Error(`Auth provider "${name}" is not registered`);
10
+ }
11
+ activeProvider = name;
12
+ }
13
+ export function getAuthProvider() {
14
+ if (!activeProvider) {
15
+ throw new Error('No active auth provider has been set');
16
+ }
17
+ const prov = providers[activeProvider];
18
+ if (!prov)
19
+ throw new Error(`Auth provider "${activeProvider}" not found`);
20
+ return prov;
21
+ }
@@ -0,0 +1,5 @@
1
+ import type { TransportAdapter } from '@mframework/core';
2
+ export declare const createAuthTransport: (config: {
3
+ baseUrl: string;
4
+ apiKey?: string;
5
+ }) => TransportAdapter;
@@ -0,0 +1,42 @@
1
+ export const createAuthTransport = (config) => {
2
+ return {
3
+ async request(method, path, options = {}) {
4
+ try {
5
+ const url = new URL(path, config.baseUrl);
6
+ if (options.query) {
7
+ Object.entries(options.query).forEach(([key, value]) => {
8
+ url.searchParams.set(key, String(value));
9
+ });
10
+ }
11
+ const res = await fetch(url.toString(), {
12
+ method,
13
+ headers: {
14
+ 'Content-Type': 'application/json',
15
+ ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),
16
+ ...(options.headers || {})
17
+ },
18
+ body: options.body ? JSON.stringify(options.body) : undefined
19
+ });
20
+ const data = await res.json().catch(() => null);
21
+ if (!res.ok) {
22
+ return {
23
+ status: res.status,
24
+ data: null,
25
+ error: data?.message || 'Unknown error'
26
+ };
27
+ }
28
+ return {
29
+ status: res.status,
30
+ data
31
+ };
32
+ }
33
+ catch (err) {
34
+ return {
35
+ status: 500,
36
+ data: null,
37
+ error: err.message || 'Transport error'
38
+ };
39
+ }
40
+ }
41
+ };
42
+ };
@@ -0,0 +1,29 @@
1
+ export interface AuthSession {
2
+ user: {
3
+ id: string;
4
+ email?: string;
5
+ name?: string;
6
+ avatarUrl?: string;
7
+ [key: string]: any;
8
+ };
9
+ expiresAt?: string;
10
+ [key: string]: any;
11
+ }
12
+ export interface AuthCredentials {
13
+ email: string;
14
+ password: string;
15
+ [key: string]: any;
16
+ }
17
+ export interface AuthRegistration {
18
+ email: string;
19
+ password: string;
20
+ name?: string;
21
+ [key: string]: any;
22
+ }
23
+ export interface AuthProvider {
24
+ login(credentials: AuthCredentials): Promise<AuthSession>;
25
+ logout(): Promise<void>;
26
+ session(): Promise<AuthSession | null>;
27
+ register?(data: AuthRegistration): Promise<AuthSession>;
28
+ refresh?(): Promise<AuthSession>;
29
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { APIResponse, Result } from '@mframework/core';
2
+ export declare const unwrap: <T>(response: APIResponse<T>) => Result<T>;
@@ -0,0 +1,12 @@
1
+ export const unwrap = (response) => {
2
+ if (response.error) {
3
+ return {
4
+ ok: false,
5
+ error: response.error
6
+ };
7
+ }
8
+ return {
9
+ ok: true,
10
+ data: response.data
11
+ };
12
+ };
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+ export declare const loginSchema: z.ZodObject<{
3
+ email: z.ZodString;
4
+ password: z.ZodString;
5
+ }, z.core.$strip>;
6
+ export type LoginInput = z.infer<typeof loginSchema>;
7
+ export declare const registerSchema: z.ZodObject<{
8
+ email: z.ZodString;
9
+ password: z.ZodString;
10
+ confirmPassword: z.ZodString;
11
+ }, z.core.$strip>;
12
+ export type RegisterInput = z.infer<typeof registerSchema>;
13
+ export declare const forgotPasswordSchema: z.ZodObject<{
14
+ email: z.ZodString;
15
+ }, z.core.$strip>;
16
+ export type ForgotPasswordInput = z.infer<typeof forgotPasswordSchema>;
17
+ export declare const resetPasswordSchema: z.ZodObject<{
18
+ token: z.ZodString;
19
+ password: z.ZodString;
20
+ confirmPassword: z.ZodString;
21
+ }, z.core.$strip>;
22
+ export type ResetPasswordInput = z.infer<typeof resetPasswordSchema>;
@@ -0,0 +1,21 @@
1
+ import { z } from 'zod';
2
+ export const loginSchema = z.object({
3
+ email: z.string().email(),
4
+ password: z.string().min(8)
5
+ });
6
+ export const registerSchema = z.object({
7
+ email: z.string().email(),
8
+ password: z.string().min(8),
9
+ confirmPassword: z.string().min(8)
10
+ }).refine((data) => data.password === data.confirmPassword, {
11
+ message: 'Passwords do not match',
12
+ path: ['confirmPassword']
13
+ });
14
+ export const forgotPasswordSchema = z.object({
15
+ email: z.string().email()
16
+ });
17
+ export const resetPasswordSchema = z.object({
18
+ token: z.string(),
19
+ password: z.string().min(8),
20
+ confirmPassword: z.string().min(8)
21
+ });
package/index.ts ADDED
@@ -0,0 +1,28 @@
1
+ import {
2
+ setAuthAdapter
3
+ } from '@mframework/sdk'
4
+
5
+ import { createAuthTransport } from './src/transport'
6
+ import { createAuthAdapter } from './src/auth'
7
+
8
+ export const installAuthAdapter = (config: { baseUrl: string; apiKey?: string }) => {
9
+ const transport = createAuthTransport(config)
10
+
11
+ setAuthAdapter(createAuthAdapter(transport))
12
+ }
13
+
14
+ // Export the server-side BetterAuth instance (if present) so other layers can
15
+ // import the runtime `auth` instance from this package. The file is optional
16
+ // and will only exist when BetterAuth is configured for the adapter.
17
+ export { auth, Auth } from './src/betterauth'
18
+ export { default as BetterAuthProvider } from './src/provider'
19
+ export { getAuthPlugins } from './src/plugins'
20
+
21
+ export * from './src/types'
22
+ export * from './src/auth'
23
+ export * from './src/utils'
24
+ export * from './src/transport'
25
+ export * from './src/framework'
26
+ export * from './src/registry'
27
+ export * from './src/validation'
28
+ export * from './src/config'
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@mframework/adapter-betterauth",
3
+ "version": "0.0.1",
4
+ "description": "Official Better-Auth adapter for M Framework Auth Layer",
5
+ "main": "index.ts",
6
+ "types": "module",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./src/index.ts",
10
+ "types": "./src/types.d.ts"
11
+ }
12
+ },
13
+ "scripts": {
14
+ "test": "echo \"Error: no test specified\" && exit 1",
15
+ "build": "tsc -p tsconfig.json",
16
+ "create": "node ./scripts/init-adapter.js",
17
+ "create:interactive": "node ./scripts/create-adapter-cli.cjs"
18
+ },
19
+ "keywords": [
20
+ "meeovi",
21
+ "adapter",
22
+ "starter"
23
+ ],
24
+ "author": "M Framework",
25
+ "license": "MIT",
26
+ "type": "module",
27
+ "dependencies": {
28
+ "@better-auth/cli": "^1.4.10",
29
+ "@better-auth/oauth-provider": "^1.4.12",
30
+ "@better-auth/passkey": "^1.4.10",
31
+ "@better-auth/scim": "^1.4.15",
32
+ "@better-auth/sso": "^1.4.12",
33
+ "@better-auth/stripe": "^1.4.15",
34
+ "@mframework/core": "^0.0.1",
35
+ "@mframework/sdk": "^0.0.2",
36
+ "@polar-sh/better-auth": "^1.6.3",
37
+ "better-auth": "^1.4.12",
38
+ "typescript": "^5.9.3"
39
+ },
40
+ "files": [
41
+ "dist",
42
+ "src",
43
+ "README.md"
44
+ ]
45
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,52 @@
1
+ import type {
2
+ AuthAdapter,
3
+ TransportAdapter
4
+ } from '@mframework/sdk'
5
+
6
+ import type {
7
+ LoginInput,
8
+ RegisterInput,
9
+ Result,
10
+ Session,
11
+ User
12
+ } from '@mframework/core'
13
+
14
+ import { unwrap } from './utils'
15
+
16
+ export const createAuthAdapter = (
17
+ transport: TransportAdapter
18
+ ): AuthAdapter => ({
19
+ async login(input: LoginInput): Promise<Result<Session>> {
20
+ const res = await transport.request<Session>('POST', '/login', {
21
+ body: input
22
+ })
23
+ return unwrap(res)
24
+ },
25
+
26
+ async register(input: RegisterInput): Promise<Result<Session>> {
27
+ const res = await transport.request<Session>('POST', '/register', {
28
+ body: input
29
+ })
30
+ return unwrap(res)
31
+ },
32
+
33
+ async logout(): Promise<Result<true>> {
34
+ const res = await transport.request('POST', '/logout')
35
+ return unwrap({ ...res, data: true })
36
+ },
37
+
38
+ async getSession(): Promise<Result<Session>> {
39
+ const res = await transport.request<Session>('GET', '/session')
40
+ return unwrap(res)
41
+ },
42
+
43
+ async refresh(): Promise<Result<Session>> {
44
+ const res = await transport.request<Session>('POST', '/refresh')
45
+ return unwrap(res)
46
+ },
47
+
48
+ async getUser(): Promise<Result<User>> {
49
+ const res = await transport.request<User>('GET', '/user')
50
+ return unwrap(res)
51
+ }
52
+ })
@@ -0,0 +1,15 @@
1
+ import "dotenv/config"
2
+ import { betterAuth } from "better-auth"
3
+ import { prismaAdapter } from "better-auth/adapters/prisma"
4
+ import { prisma } from "@mframework/api"
5
+
6
+ // Create and export the BetterAuth runtime instance using the centralized
7
+ // Prisma client exported from packages/modules/api. Layers can import `auth`
8
+ // from this package to get the configured auth instance.
9
+ export const auth = betterAuth({
10
+ experimental: { joins: true },
11
+ database: prismaAdapter(prisma as any, { provider: 'postgresql' }),
12
+ emailAndPassword: { enabled: true }
13
+ })
14
+
15
+ export type Auth = typeof auth
package/src/config.ts ADDED
@@ -0,0 +1,22 @@
1
+
2
+ export interface AuthConfig {
3
+ baseUrl: any
4
+ authProvider: string
5
+ authUrl?: string
6
+ sessionCookieName?: string
7
+ }
8
+
9
+ let config: AuthConfig = {
10
+ authProvider: 'better-auth',
11
+ authUrl: '',
12
+ sessionCookieName: 'session',
13
+ baseUrl: undefined
14
+ }
15
+
16
+ export function setAuthConfig(newConfig: Partial<AuthConfig>) {
17
+ config = { ...config, ...newConfig }
18
+ }
19
+
20
+ export function getAuthConfig(): AuthConfig {
21
+ return config
22
+ }
@@ -0,0 +1,24 @@
1
+
2
+ export interface FrameworkContext {
3
+ getRequestURL?(): string
4
+ getRequestHeaders?(): Record<string, string>
5
+ getConfig?(): any
6
+ reloadApp?(): void
7
+ state?<T>(key: string, init: () => T): { value: T }
8
+ }
9
+
10
+ let ctx: FrameworkContext = {}
11
+
12
+ /**
13
+ * Frameworks (Nuxt, React, etc.) call this once during initialization.
14
+ */
15
+ export function setFrameworkContext(newCtx: FrameworkContext) {
16
+ ctx = { ...ctx, ...newCtx }
17
+ }
18
+
19
+ /**
20
+ * Auth providers use this to access framework-specific helpers.
21
+ */
22
+ export function getFrameworkContext(): FrameworkContext {
23
+ return ctx
24
+ }
package/src/plugins.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { stripeClient } from '@better-auth/stripe/client'
2
+ import { polarClient } from '@polar-sh/better-auth'
3
+ import { adminClient, inferAdditionalFields } from 'better-auth/client/plugins'
4
+
5
+ export type AuthPluginOptions = {
6
+ subscription?: boolean
7
+ }
8
+
9
+ export function getAuthPlugins(opts: AuthPluginOptions = {}): any[] {
10
+ const { subscription = true } = opts
11
+
12
+ return [
13
+ inferAdditionalFields({
14
+ user: {
15
+ polarCustomerId: {
16
+ type: 'string'
17
+ }
18
+ }
19
+ }),
20
+ adminClient(),
21
+ polarClient(),
22
+ stripeClient({ subscription })
23
+ ]
24
+ }
25
+
26
+ export default getAuthPlugins
@@ -0,0 +1,106 @@
1
+ import * as BetterAuth from 'better-auth'
2
+ import { getAuthConfig } from './config'
3
+ import { getFrameworkContext } from './framework'
4
+
5
+ // Create a single shared Better Auth client instance
6
+ let client: any = null
7
+
8
+ function resolveClientFactory() {
9
+ return (BetterAuth as any).Client ?? (BetterAuth as any).default ?? BetterAuth
10
+ }
11
+
12
+ function getClient() {
13
+ if (client) return client
14
+
15
+ const config = getAuthConfig()
16
+ const ctx = getFrameworkContext()
17
+
18
+ const Client = resolveClientFactory()
19
+ client = Client({
20
+ baseURL: config.baseUrl,
21
+ fetch: async (url: string | Request | URL, options: RequestInit = {}) => {
22
+ const baseHeaders = ctx.getRequestHeaders?.() || {}
23
+
24
+ let optionHeaders: Record<string, string> = {}
25
+
26
+ if (options.headers instanceof Headers) {
27
+ options.headers.forEach((value, key) => {
28
+ optionHeaders[key] = value
29
+ })
30
+ } else if (Array.isArray(options.headers)) {
31
+ for (const [k, v] of options.headers) {
32
+ optionHeaders[k] = v
33
+ }
34
+ } else if (typeof options.headers === 'object' && options.headers !== null) {
35
+ optionHeaders = options.headers as Record<string, string>
36
+ }
37
+
38
+ const headers = {
39
+ ...baseHeaders,
40
+ ...optionHeaders
41
+ }
42
+
43
+ return fetch(url, { ...options, headers })
44
+ }
45
+ })
46
+
47
+ return client
48
+ }
49
+
50
+ const BetterAuthProvider: any = {
51
+ async login(credentials: any) {
52
+ const client = getClient()
53
+ const result = await client.signIn(credentials)
54
+
55
+ if (result.error) {
56
+ throw new Error(result.error.message || 'Login failed')
57
+ }
58
+
59
+ return result.data
60
+ },
61
+
62
+ async logout() {
63
+ const client = getClient()
64
+ await client.signOut()
65
+
66
+ const ctx = getFrameworkContext()
67
+ ctx.reloadApp?.()
68
+ },
69
+
70
+ async session() {
71
+ const client = getClient()
72
+ const result = await client.session()
73
+
74
+ if (result.error) {
75
+ return null
76
+ }
77
+
78
+ return result.data
79
+ },
80
+
81
+ async register(data: any) {
82
+ const client = getClient()
83
+ const result = await client.signUp(data)
84
+
85
+ if (result.error) {
86
+ throw new Error(result.error.message || 'Registration failed')
87
+ }
88
+
89
+ return result.data
90
+ },
91
+
92
+ async refresh() {
93
+ const client = getClient()
94
+ const result = await client.refresh()
95
+
96
+ if (result.error) {
97
+ throw new Error(result.error.message || 'Session refresh failed')
98
+ }
99
+
100
+ return result.data
101
+ }
102
+ }
103
+
104
+ // Register the provider under the canonical name so other layers can discover
105
+ // it using the existing registry mechanism.
106
+ export default BetterAuthProvider
@@ -0,0 +1,25 @@
1
+ import type { AuthProvider } from './types'
2
+
3
+ // allow missing entries in the map so TypeScript understands runtime checks
4
+ const providers: Record<string, AuthProvider | undefined> = {}
5
+ let activeProvider: any | null = null
6
+
7
+ export function registerAuthProvider(name: string, provider: AuthProvider) {
8
+ providers[name] = provider
9
+ }
10
+
11
+ export function setActiveAuthProvider(name: string) {
12
+ if (!providers[name]) {
13
+ throw new Error(`Auth provider "${name}" is not registered`)
14
+ }
15
+ activeProvider = name
16
+ }
17
+
18
+ export function getAuthProvider(): AuthProvider {
19
+ if (!activeProvider) {
20
+ throw new Error('No active auth provider has been set')
21
+ }
22
+ const prov = providers[activeProvider!]
23
+ if (!prov) throw new Error(`Auth provider "${activeProvider}" not found`)
24
+ return prov
25
+ }
@@ -0,0 +1,48 @@
1
+ import type { TransportAdapter, RequestOptions, APIResponse } from '@mframework/core'
2
+
3
+ export const createAuthTransport = (config: { baseUrl: string; apiKey?: string }): TransportAdapter => {
4
+ return {
5
+ async request<T>(method: any, path: string | URL, options: RequestOptions = {}): Promise<APIResponse<T>> {
6
+ try {
7
+ const url = new URL(path, config.baseUrl)
8
+
9
+ if (options.query) {
10
+ Object.entries(options.query).forEach(([key, value]) => {
11
+ url.searchParams.set(key, String(value))
12
+ })
13
+ }
14
+
15
+ const res = await fetch(url.toString(), {
16
+ method,
17
+ headers: {
18
+ 'Content-Type': 'application/json',
19
+ ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),
20
+ ...(options.headers || {})
21
+ },
22
+ body: options.body ? JSON.stringify(options.body) : undefined
23
+ })
24
+
25
+ const data = await res.json().catch(() => null)
26
+
27
+ if (!res.ok) {
28
+ return {
29
+ status: res.status,
30
+ data: (null as any) as T,
31
+ error: data?.message || 'Unknown error'
32
+ }
33
+ }
34
+
35
+ return {
36
+ status: res.status,
37
+ data
38
+ }
39
+ } catch (err: any) {
40
+ return {
41
+ status: 500,
42
+ data: (null as any) as T,
43
+ error: err.message || 'Transport error'
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,28 @@
1
+ declare module '@mframework/sdk' {
2
+ export type TransportAdapter = {
3
+ request<T = any>(method: string, path: string, opts?: any): Promise<any>
4
+ }
5
+ export type AuthAdapter = any
6
+ export type CommerceAdapter = any
7
+ export type SearchAdapter = any
8
+ export function setAuthAdapter(adapter: AuthAdapter): void
9
+ export function setCommerceAdapter(adapter: CommerceAdapter): void
10
+ export function setSearchAdapter(adapter: SearchAdapter): void
11
+ }
12
+
13
+ declare module '@mframework/core' {
14
+ export type LoginInput = any
15
+ export type RegisterInput = any
16
+ export type Result<T = any> = any
17
+ export type Session = any
18
+ export type User = any
19
+ export type TransportAdapter = import('@mframework/sdk').TransportAdapter
20
+ export type RequestOptions = any
21
+ export type APIResponse<T = any> = any
22
+ }
23
+
24
+ declare module '@mframework/api' {
25
+ export const prisma: any
26
+ export function useDB(_event?: any): Promise<any>
27
+ export function isValidTable(name: string): boolean
28
+ }
package/src/types.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ // Re-export the canonical SDK/core types so consumers of this adapter can import
2
+ // from the adapter package and still get accurate types from the source
3
+ // modules.
4
+ export type { TransportAdapter, AuthAdapter, CommerceAdapter, SearchAdapter } from '@mframework/sdk'
5
+ export type { LoginInput, RegisterInput, Result, Session, User } from '@mframework/core'
6
+
7
+ // Re-export prisma utilities from the api package for convenience.
8
+ export { prisma, useDB, isValidTable } from '@mframework/api'
9
+
10
+ // Adapter exports
11
+ export function getAuthPlugins(opts?: any): any[]
12
+ export const BetterAuthProvider: any
13
+ export default BetterAuthProvider
package/src/types.ts ADDED
@@ -0,0 +1,33 @@
1
+
2
+ export interface AuthSession {
3
+ user: {
4
+ id: string
5
+ email?: string
6
+ name?: string
7
+ avatarUrl?: string
8
+ [key: string]: any
9
+ }
10
+ expiresAt?: string
11
+ [key: string]: any
12
+ }
13
+
14
+ export interface AuthCredentials {
15
+ email: string
16
+ password: string
17
+ [key: string]: any
18
+ }
19
+
20
+ export interface AuthRegistration {
21
+ email: string
22
+ password: string
23
+ name?: string
24
+ [key: string]: any
25
+ }
26
+
27
+ export interface AuthProvider {
28
+ login(credentials: AuthCredentials): Promise<AuthSession>
29
+ logout(): Promise<void>
30
+ session(): Promise<AuthSession | null>
31
+ register?(data: AuthRegistration): Promise<AuthSession>
32
+ refresh?(): Promise<AuthSession>
33
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,15 @@
1
+ import type { APIResponse, Result } from '@mframework/core'
2
+
3
+ export const unwrap = <T>(response: APIResponse<T>): Result<T> => {
4
+ if (response.error) {
5
+ return {
6
+ ok: false,
7
+ error: response.error
8
+ }
9
+ }
10
+
11
+ return {
12
+ ok: true,
13
+ data: response.data
14
+ }
15
+ }
@@ -0,0 +1,33 @@
1
+ import { z } from 'zod'
2
+
3
+ export const loginSchema = z.object({
4
+ email: z.string().email(),
5
+ password: z.string().min(8)
6
+ })
7
+
8
+ export type LoginInput = z.infer<typeof loginSchema>
9
+
10
+ export const registerSchema = z.object({
11
+ email: z.string().email(),
12
+ password: z.string().min(8),
13
+ confirmPassword: z.string().min(8)
14
+ }).refine((data) => data.password === data.confirmPassword, {
15
+ message: 'Passwords do not match',
16
+ path: ['confirmPassword']
17
+ })
18
+
19
+ export type RegisterInput = z.infer<typeof registerSchema>
20
+
21
+ export const forgotPasswordSchema = z.object({
22
+ email: z.string().email()
23
+ })
24
+
25
+ export type ForgotPasswordInput = z.infer<typeof forgotPasswordSchema>
26
+
27
+ export const resetPasswordSchema = z.object({
28
+ token: z.string(),
29
+ password: z.string().min(8),
30
+ confirmPassword: z.string().min(8)
31
+ })
32
+
33
+ export type ResetPasswordInput = z.infer<typeof resetPasswordSchema>