deesse 0.2.7 → 0.2.9

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.
Files changed (48) hide show
  1. package/dist/cache.d.ts +16 -0
  2. package/dist/cache.d.ts.map +1 -0
  3. package/dist/cache.js +59 -0
  4. package/dist/cache.js.map +1 -0
  5. package/dist/client.d.ts +27 -0
  6. package/dist/client.d.ts.map +1 -0
  7. package/dist/client.js +24 -0
  8. package/dist/client.js.map +1 -0
  9. package/dist/config/define.d.ts +16 -1
  10. package/dist/config/define.d.ts.map +1 -1
  11. package/dist/config/define.js +10 -1
  12. package/dist/config/define.js.map +1 -1
  13. package/dist/config/index.d.ts +1 -1
  14. package/dist/config/index.d.ts.map +1 -1
  15. package/dist/config/page.d.ts +2 -0
  16. package/dist/config/page.d.ts.map +1 -1
  17. package/dist/config/page.js +1 -0
  18. package/dist/config/page.js.map +1 -1
  19. package/dist/index.d.ts +24 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +104 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/lib/admin.d.ts +24 -0
  24. package/dist/lib/admin.d.ts.map +1 -0
  25. package/dist/lib/admin.js +50 -0
  26. package/dist/lib/admin.js.map +1 -0
  27. package/dist/lib/validation.d.ts +32 -0
  28. package/dist/lib/validation.d.ts.map +1 -0
  29. package/dist/lib/validation.js +80 -0
  30. package/dist/lib/validation.js.map +1 -0
  31. package/dist/server.d.ts +16 -0
  32. package/dist/server.d.ts.map +1 -0
  33. package/dist/server.js +17 -0
  34. package/dist/server.js.map +1 -0
  35. package/package.json +3 -1
  36. package/src/cache.ts +81 -0
  37. package/src/client.ts +39 -0
  38. package/src/config/define.ts +28 -2
  39. package/src/config/index.ts +1 -1
  40. package/src/config/page.ts +3 -0
  41. package/src/index.ts +137 -1
  42. package/src/lib/admin.ts +68 -0
  43. package/src/lib/validation.ts +89 -0
  44. package/src/server.ts +31 -0
  45. package/tsconfig.json +1 -0
  46. package/tsconfig.tsbuildinfo +1 -1
  47. package/.turbo/turbo-build.log +0 -4
  48. package/.turbo/turbo-type-check.log +0 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE9D,MAAM,MAAM,MAAM,GAAG;IACnB,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,UAAU,CAAC;QACzC,QAAQ,EAAE,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;QAC5C,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,gBAAgB,EAAE,CAAC;KAC7B,CAAC,CAAC,CAAC,CAAC;IACL,QAAQ,EAAE,kBAAkB,CAAC;CAC9B,CAAC;AAEF,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAc3D"}
package/dist/server.js ADDED
@@ -0,0 +1,17 @@
1
+ import { betterAuth } from "better-auth";
2
+ import { drizzleAdapter } from "@better-auth/drizzle-adapter";
3
+ export function createDeesse(config) {
4
+ const auth = betterAuth({
5
+ database: drizzleAdapter(config.database, {
6
+ provider: "pg",
7
+ }),
8
+ baseURL: config.auth.baseURL,
9
+ secret: config.secret,
10
+ plugins: config.auth.plugins,
11
+ });
12
+ return {
13
+ auth,
14
+ database: config.database,
15
+ };
16
+ }
17
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAY9D,MAAM,UAAU,YAAY,CAAC,MAAsB;IACjD,MAAM,IAAI,GAAG,UAAU,CAAC;QACtB,QAAQ,EAAE,cAAc,CAAC,MAAM,CAAC,QAAQ,EAAE;YACxC,QAAQ,EAAE,IAAI;SACf,CAAC;QACF,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO;QAC5B,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO;KAC7B,CAAC,CAAC;IAEH,OAAO;QACL,IAAI;QACJ,QAAQ,EAAE,MAAM,CAAC,QAAQ;KAC1B,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deesse",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,6 +19,8 @@
19
19
  "clean": "rm -rf dist"
20
20
  },
21
21
  "dependencies": {
22
+ "@better-auth/drizzle-adapter": "^1.0.0",
23
+ "better-auth": "^1.0.0",
22
24
  "drizzle-orm": "^0.38.0",
23
25
  "lucide-react": "^0.468.0",
24
26
  "pg": "^8.13.0",
package/src/cache.ts ADDED
@@ -0,0 +1,81 @@
1
+ // Generic cache utility with immutable state
2
+
3
+ export type CacheState<T> = {
4
+ instances: Map<string, T>;
5
+ promises: Map<string, Promise<T>>;
6
+ };
7
+
8
+ export const emptyCache = <T>(): CacheState<T> => ({
9
+ instances: new Map(),
10
+ promises: new Map(),
11
+ });
12
+
13
+ export const setCacheInstance = <T>(state: CacheState<T>, key: string, instance: T): CacheState<T> => ({
14
+ instances: new Map(state.instances).set(key, instance),
15
+ promises: (() => {
16
+ const m = new Map(state.promises);
17
+ m.delete(key);
18
+ return m;
19
+ })(),
20
+ });
21
+
22
+ export const setCachePromise = <T>(state: CacheState<T>, key: string, promise: Promise<T>): CacheState<T> => ({
23
+ instances: state.instances,
24
+ promises: new Map(state.promises).set(key, promise),
25
+ });
26
+
27
+ export const getCacheEntry = <T>(state: CacheState<T>, key: string): T | Promise<T> | undefined => {
28
+ const instance = state.instances.get(key);
29
+ if (instance !== undefined) return instance;
30
+ return state.promises.get(key);
31
+ };
32
+
33
+ export const removeCachePromise = <T>(state: CacheState<T>, key: string): CacheState<T> => ({
34
+ instances: state.instances,
35
+ promises: (() => {
36
+ const m = new Map(state.promises);
37
+ m.delete(key);
38
+ return m;
39
+ })(),
40
+ });
41
+
42
+ export type CreateCache = <T, Options>(
43
+ createInstance: (options: Options) => Promise<T>
44
+ ) => {
45
+ get(key: string, options: Options): Promise<T>;
46
+ getState(): Readonly<CacheState<T>>;
47
+ clear(): void;
48
+ };
49
+
50
+ export const createCache: CreateCache = <T, Options>(
51
+ createInstance: (options: Options) => Promise<T>
52
+ ) => {
53
+ let state: CacheState<T> = emptyCache<T>();
54
+
55
+ return {
56
+ async get(key: string, options: Options): Promise<T> {
57
+ const cached = getCacheEntry(state, key);
58
+ if (cached !== undefined) return cached as T;
59
+
60
+ const promise = createInstance(options);
61
+ state = setCachePromise(state, key, promise);
62
+
63
+ try {
64
+ const instance = await promise;
65
+ state = setCacheInstance(state, key, instance);
66
+ return instance;
67
+ } catch (err) {
68
+ state = removeCachePromise(state, key);
69
+ throw err;
70
+ }
71
+ },
72
+
73
+ getState(): Readonly<CacheState<T>> {
74
+ return state;
75
+ },
76
+
77
+ clear(): void {
78
+ state = emptyCache<T>();
79
+ },
80
+ };
81
+ };
package/src/client.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { createAuthClient } from "better-auth/react";
2
+ import type {
3
+ AuthClient,
4
+ BetterAuthClientOptions,
5
+ } from "better-auth/client";
6
+
7
+ export interface DeesseClientOptions {
8
+ auth: BetterAuthClientOptions;
9
+ }
10
+
11
+ export interface DeesseClient {
12
+ auth: AuthClient<BetterAuthClientOptions>;
13
+ }
14
+
15
+ /**
16
+ * Creates a type-safe authentication client.
17
+ * Wraps better-auth's createAuthClient with a simpler API.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * import { createClient } from "deesse";
22
+ *
23
+ * export const client = createClient({
24
+ * auth: {
25
+ * baseURL: "/api/auth",
26
+ * },
27
+ * });
28
+ *
29
+ * // In a component:
30
+ * const { data, isPending } = client.auth.useSession();
31
+ * ```
32
+ */
33
+ export function createClient(
34
+ options: DeesseClientOptions,
35
+ ): DeesseClient {
36
+ const auth = createAuthClient(options.auth) as unknown as AuthClient<BetterAuthClientOptions>;
37
+
38
+ return { auth };
39
+ }
@@ -1,13 +1,39 @@
1
1
  import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
2
+ import type { BetterAuthPlugin } from 'better-auth';
2
3
  import type { Plugin } from './plugin';
3
4
  import type { PageTree } from './page';
5
+ import { admin } from 'better-auth/plugins';
4
6
 
5
7
  export type Config = {
8
+ name?: string;
6
9
  database: PostgresJsDatabase;
7
10
  plugins?: Plugin[];
8
11
  pages?: PageTree[];
12
+ secret: string;
13
+ auth: {
14
+ baseURL: string;
15
+ };
9
16
  };
10
17
 
11
- export function defineConfig(config: Config) {
12
- return config;
18
+ /**
19
+ * Internal config type used at runtime - includes admin plugin
20
+ */
21
+ export type InternalConfig = Config & {
22
+ auth: {
23
+ baseURL: string;
24
+ plugins: BetterAuthPlugin[];
25
+ };
26
+ };
27
+
28
+ export function defineConfig(config: Config): InternalConfig {
29
+ // Always include admin plugin - user cannot remove it
30
+ const authPlugins: BetterAuthPlugin[] = [admin()];
31
+
32
+ return {
33
+ ...config,
34
+ auth: {
35
+ ...config.auth,
36
+ plugins: authPlugins,
37
+ },
38
+ } as InternalConfig;
13
39
  }
@@ -1,5 +1,5 @@
1
1
  export { defineConfig } from "./define";
2
- export type { Config } from "./define";
2
+ export type { Config, InternalConfig } from "./define";
3
3
  export { plugin } from "./plugin";
4
4
  export type { Plugin } from "./plugin";
5
5
  export { page, section } from "./page";
@@ -13,6 +13,7 @@ export type Section = {
13
13
  type: "section";
14
14
  name: string;
15
15
  slug: string;
16
+ bottom?: boolean;
16
17
  children: PageTree[];
17
18
  };
18
19
 
@@ -40,12 +41,14 @@ export function page(config: {
40
41
  export function section(config: {
41
42
  name: string;
42
43
  slug?: string;
44
+ bottom?: boolean;
43
45
  children: PageTree[];
44
46
  }): Section {
45
47
  return {
46
48
  type: "section",
47
49
  name: config.name,
48
50
  slug: config.slug ?? toSlug(config.name),
51
+ bottom: config.bottom,
49
52
  children: config.children,
50
53
  };
51
54
  }
package/src/index.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  // @deessejs/deesse core package
2
2
 
3
+ import type { InternalConfig } from "./config/define";
4
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
5
+ import { createDeesse, type Deesse } from "./server";
6
+
3
7
  export { defineConfig } from "./config";
4
- export type { Config } from "./config";
8
+ export type { Config, InternalConfig } from "./config";
5
9
  export { plugin } from "./config";
6
10
  export type { Plugin } from "./config";
7
11
  export { page, section } from "./config";
@@ -9,3 +13,135 @@ export type { Page, Section, PageTree } from "./config";
9
13
 
10
14
  export { z } from "zod";
11
15
  export type { ZodSchema } from "zod";
16
+
17
+ export type { Deesse } from "./server";
18
+
19
+ export { createClient } from "./client";
20
+ export type { DeesseClient, DeesseClientOptions } from "./client";
21
+
22
+ export { isDatabaseEmpty, requireDatabaseNotEmpty, validateAdminEmail } from "./lib/admin";
23
+ export type { EmailValidationOptions } from "./lib/admin";
24
+ export { isPublicEmailDomain, isAllowedAdminEmail, getAllowedDomains, validateAdminEmailDomain, PUBLIC_EMAIL_DOMAINS } from "./lib/validation";
25
+
26
+ /**
27
+ * Symbol-based global storage for Deesse instance.
28
+ * Using Symbol.for ensures uniqueness across the process.
29
+ */
30
+ const DEESSE_GLOBAL_KEY = Symbol.for("@deessejs/core.instance");
31
+
32
+ interface GlobalDeesseCache {
33
+ instance: Deesse | undefined;
34
+ config: InternalConfig | undefined;
35
+ pool: unknown | undefined;
36
+ }
37
+
38
+ function getGlobalCache(): GlobalDeesseCache {
39
+ const g = global as typeof global & {
40
+ [DEESSE_GLOBAL_KEY]?: GlobalDeesseCache;
41
+ };
42
+ if (!g[DEESSE_GLOBAL_KEY]) {
43
+ g[DEESSE_GLOBAL_KEY] = {
44
+ instance: undefined,
45
+ config: undefined,
46
+ pool: undefined,
47
+ };
48
+ }
49
+ return g[DEESSE_GLOBAL_KEY]!;
50
+ }
51
+
52
+ /**
53
+ * Deep equality check for config comparison.
54
+ * Required because config objects are recreated on HMR.
55
+ */
56
+ function isConfigEqual(a: InternalConfig, b: InternalConfig): boolean {
57
+ if (a.secret !== b.secret) return false;
58
+ if (a.name !== b.name) return false;
59
+ if (a.auth.baseURL !== b.auth.baseURL) return false;
60
+ return true;
61
+ }
62
+
63
+ /**
64
+ * Extract pool reference from database.
65
+ * For pg Pool passed to drizzle-orm/node-postgres, the pool is stored in $client.
66
+ */
67
+ function extractPool(db: PostgresJsDatabase): unknown {
68
+ return (db as unknown as { $client?: unknown }).$client;
69
+ }
70
+
71
+ /**
72
+ * Get the Deesse singleton instance.
73
+ * Cached on global to persist across HMR.
74
+ */
75
+ export const getDeesse = async (
76
+ config: InternalConfig
77
+ ): Promise<Deesse> => {
78
+ const cache = getGlobalCache();
79
+
80
+ // Case 1: Instance exists and config is semantically equal
81
+ if (cache.instance && cache.config && isConfigEqual(cache.config, config)) {
82
+ return cache.instance;
83
+ }
84
+
85
+ // Case 2: Instance exists but config changed - hot reload
86
+ // IMPORTANT: Don't close the pool! cache.instance still uses the old pool.
87
+ // The new config's pool will be garbage collected when the HMR module reference is dropped.
88
+ if (
89
+ cache.instance &&
90
+ cache.config &&
91
+ !isConfigEqual(cache.config, config)
92
+ ) {
93
+ console.info("[deesse] Config changed, performing hot reload...");
94
+ cache.config = config;
95
+ return cache.instance;
96
+ }
97
+
98
+ // Case 3: No instance exists - create one
99
+ const instance = createDeesse(config);
100
+ cache.pool = extractPool(instance.database);
101
+ cache.instance = instance;
102
+ cache.config = config;
103
+
104
+ return instance;
105
+ };
106
+
107
+ /**
108
+ * Clear the Deesse singleton cache.
109
+ * Primarily useful for testing.
110
+ */
111
+ export const clearDeesseCache = (): void => {
112
+ const cache = getGlobalCache();
113
+ cache.instance = undefined;
114
+ cache.config = undefined;
115
+ cache.pool = undefined;
116
+ };
117
+
118
+ /**
119
+ * Graceful shutdown - call this on process exit.
120
+ * Ensures all database connections are properly closed.
121
+ */
122
+ export const shutdownDeesse = async (): Promise<void> => {
123
+ const cache = getGlobalCache();
124
+
125
+ if (cache.pool) {
126
+ console.info("[deesse] Closing database pool...");
127
+ const pool = cache.pool as { end?: () => Promise<void> };
128
+ if (pool && typeof pool.end === "function") {
129
+ await pool.end();
130
+ }
131
+ cache.pool = undefined;
132
+ }
133
+
134
+ cache.instance = undefined;
135
+ cache.config = undefined;
136
+ };
137
+
138
+ // Register graceful shutdown hooks
139
+ if (process.env["NODE_ENV"] !== "test") {
140
+ const shutdown = () => {
141
+ shutdownDeesse()
142
+ .catch(console.error)
143
+ .finally(() => process.exit(0));
144
+ };
145
+ process.on("SIGINT", shutdown);
146
+ process.on("SIGTERM", shutdown);
147
+ }
@@ -0,0 +1,68 @@
1
+ import type { Auth } from "better-auth";
2
+
3
+ /**
4
+ * Check if the database has any users.
5
+ * Returns true if the database is empty (no users).
6
+ */
7
+ export async function isDatabaseEmpty(auth: Auth): Promise<boolean> {
8
+ try {
9
+ const result = await (auth.api as any).listUsers({ limit: 1 });
10
+ return !result?.users || result.users.length === 0;
11
+ } catch {
12
+ // If listUsers fails, assume not empty (safer default)
13
+ return false;
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Require that the database is NOT empty.
19
+ * Throws if no users exist.
20
+ */
21
+ export async function requireDatabaseNotEmpty(auth: Auth): Promise<void> {
22
+ if (await isDatabaseEmpty(auth)) {
23
+ throw new Error(
24
+ "Database is empty. Cannot proceed with this operation. " +
25
+ "Use the First Admin Setup page to create the initial admin account."
26
+ );
27
+ }
28
+ }
29
+
30
+ export interface EmailValidationOptions {
31
+ allowedDomains?: string[];
32
+ blockedDomains?: string[];
33
+ requireOrganization?: boolean;
34
+ }
35
+
36
+ /**
37
+ * Validate an admin email against configured rules.
38
+ */
39
+ export function validateAdminEmail(
40
+ email: string,
41
+ options: EmailValidationOptions = {}
42
+ ): { valid: boolean; error?: string } {
43
+ const domain = email.split('@')[1]?.toLowerCase();
44
+
45
+ if (!domain) {
46
+ return { valid: false, error: "Invalid email format" };
47
+ }
48
+
49
+ // Check blocked domains
50
+ if (options.blockedDomains?.includes(domain)) {
51
+ return { valid: false, error: `Email domain ${domain} is blocked` };
52
+ }
53
+
54
+ // Check allowed domains (if specified)
55
+ if (options.allowedDomains?.length && !options.allowedDomains.includes(domain)) {
56
+ return { valid: false, error: `Email must be from: ${options.allowedDomains.join(', ')}` };
57
+ }
58
+
59
+ // Require organization (no public email domains)
60
+ if (options.requireOrganization) {
61
+ const PUBLIC_DOMAINS = ['gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com', 'icloud.com'];
62
+ if (PUBLIC_DOMAINS.includes(domain)) {
63
+ return { valid: false, error: "Personal email domains are not allowed. Use an organizational email." };
64
+ }
65
+ }
66
+
67
+ return { valid: true };
68
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Email validation utilities for admin email enforcement
3
+ */
4
+
5
+ export const PUBLIC_EMAIL_DOMAINS = [
6
+ 'gmail.com',
7
+ 'yahoo.com',
8
+ 'hotmail.com',
9
+ 'outlook.com',
10
+ 'icloud.com',
11
+ 'mail.com',
12
+ 'aol.com',
13
+ 'protonmail.com',
14
+ 'zoho.com',
15
+ 'yandex.com',
16
+ 'gmx.com',
17
+ ] as const;
18
+
19
+ export type PublicEmailDomain = (typeof PUBLIC_EMAIL_DOMAINS)[number];
20
+
21
+ /**
22
+ * Check if an email uses a public email domain
23
+ */
24
+ export function isPublicEmailDomain(email: string): boolean {
25
+ const domain = email.split('@')[1]?.toLowerCase();
26
+ return PUBLIC_EMAIL_DOMAINS.includes(domain as PublicEmailDomain);
27
+ }
28
+
29
+ /**
30
+ * Get allowed domains from ADMIN_ALLOWED_DOMAINS environment variable.
31
+ * Returns empty array if not configured (no restrictions).
32
+ */
33
+ export function getAllowedDomains(): string[] {
34
+ const envValue = process.env['ADMIN_ALLOWED_DOMAINS'];
35
+ if (!envValue) return [];
36
+ return envValue
37
+ .split(',')
38
+ .map((d) => d.trim().toLowerCase())
39
+ .filter(Boolean);
40
+ }
41
+
42
+ /**
43
+ * Check if an email is from an allowed domain.
44
+ * If no allowed domains are configured, all domains are allowed.
45
+ */
46
+ export function isAllowedAdminEmail(email: string): boolean {
47
+ const allowed = getAllowedDomains();
48
+ if (!allowed.length) return true; // No restriction configured
49
+ const domain = email.split('@')[1]?.toLowerCase();
50
+ return allowed.includes(domain);
51
+ }
52
+
53
+ /**
54
+ * Validate admin email against organizational requirements.
55
+ * Returns an error message if validation fails.
56
+ */
57
+ export function validateAdminEmailDomain(
58
+ email: string
59
+ ): { valid: true } | { valid: false; code: string; message: string; suggestion?: string } {
60
+ // Check if email is from a public domain (warning level, not blocking)
61
+ const isPublic = isPublicEmailDomain(email);
62
+ const allowed = getAllowedDomains();
63
+
64
+ // If allowed domains are configured, enforce them strictly
65
+ if (allowed.length > 0) {
66
+ const domain = email.split('@')[1]?.toLowerCase();
67
+ if (!allowed.includes(domain)) {
68
+ return {
69
+ valid: false,
70
+ code: 'INVALID_EMAIL_DOMAIN',
71
+ message: `Admin email must be from an allowed domain. Allowed: ${allowed.join(', ')}`,
72
+ suggestion: 'Set ADMIN_ALLOWED_DOMAINS environment variable to configure allowed email domains',
73
+ };
74
+ }
75
+ }
76
+
77
+ // If email is from a public domain, return warning info (but allow through)
78
+ if (isPublic && allowed.length === 0) {
79
+ const domain = email.split('@')[1]?.toLowerCase();
80
+ return {
81
+ valid: false,
82
+ code: 'PUBLIC_EMAIL_DOMAIN',
83
+ message: `${email} is a public email domain. Admin accounts should use organizational email addresses.`,
84
+ suggestion: `Set ADMIN_ALLOWED_DOMAINS environment variable to restrict to organizational domains only (e.g., ADMIN_ALLOWED_DOMAINS=${domain})`,
85
+ };
86
+ }
87
+
88
+ return { valid: true };
89
+ }
package/src/server.ts ADDED
@@ -0,0 +1,31 @@
1
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
+ import type { BetterAuthPlugin } from "better-auth";
3
+ import type { InternalConfig } from "./config/define";
4
+ import { betterAuth } from "better-auth";
5
+ import { drizzleAdapter } from "@better-auth/drizzle-adapter";
6
+
7
+ export type Deesse = {
8
+ auth: Awaited<ReturnType<typeof betterAuth<{
9
+ database: ReturnType<typeof drizzleAdapter>;
10
+ baseURL: string;
11
+ secret: string;
12
+ plugins: BetterAuthPlugin[];
13
+ }>>>;
14
+ database: PostgresJsDatabase;
15
+ };
16
+
17
+ export function createDeesse(config: InternalConfig): Deesse {
18
+ const auth = betterAuth({
19
+ database: drizzleAdapter(config.database, {
20
+ provider: "pg",
21
+ }),
22
+ baseURL: config.auth.baseURL,
23
+ secret: config.secret,
24
+ plugins: config.auth.plugins,
25
+ });
26
+
27
+ return {
28
+ auth,
29
+ database: config.database,
30
+ };
31
+ }
package/tsconfig.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "extends": "../../tsconfig.json",
3
3
  "compilerOptions": {
4
+ "composite": false,
4
5
  "module": "ESNext",
5
6
  "moduleResolution": "bundler",
6
7
  "outDir": "./dist",