@geenius/adapters 0.1.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/.changeset/config.json +11 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +11 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +10 -0
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/ci.yml +23 -0
- package/.github/workflows/release.yml +29 -0
- package/.nvmrc +1 -0
- package/.project/ACCOUNT.yaml +4 -0
- package/.project/IDEAS.yaml +7 -0
- package/.project/PROJECT.yaml +11 -0
- package/.project/ROADMAP.yaml +15 -0
- package/CHANGELOG.md +11 -0
- package/CODE_OF_CONDUCT.md +16 -0
- package/CONTRIBUTING.md +26 -0
- package/LICENSE +21 -0
- package/README.md +202 -0
- package/SECURITY.md +15 -0
- package/SUPPORT.md +8 -0
- package/package.json +51 -0
- package/packages/convex/README.md +64 -0
- package/packages/convex/package.json +42 -0
- package/packages/convex/src/adapter.ts +39 -0
- package/packages/convex/src/index.ts +19 -0
- package/packages/convex/src/mutations.ts +142 -0
- package/packages/convex/src/queries.ts +106 -0
- package/packages/convex/src/schema.ts +54 -0
- package/packages/convex/src/types.ts +20 -0
- package/packages/convex/tsconfig.json +11 -0
- package/packages/convex/tsup.config.ts +10 -0
- package/packages/react/README.md +1 -0
- package/packages/react/package.json +45 -0
- package/packages/react/src/components/AdapterCard.tsx +49 -0
- package/packages/react/src/components/AdapterConfigForm.tsx +118 -0
- package/packages/react/src/components/AdapterList.tsx +84 -0
- package/packages/react/src/components/AdapterStatusBadge.tsx +30 -0
- package/packages/react/src/components/index.ts +4 -0
- package/packages/react/src/hooks/index.ts +75 -0
- package/packages/react/src/index.tsx +44 -0
- package/packages/react/src/pages/AdapterDetailPage.tsx +133 -0
- package/packages/react/src/pages/AdaptersPage.tsx +111 -0
- package/packages/react/src/pages/index.ts +2 -0
- package/packages/react/src/provider/AdapterProvider.tsx +115 -0
- package/packages/react/src/provider/index.ts +2 -0
- package/packages/react/tsconfig.json +18 -0
- package/packages/react/tsup.config.ts +10 -0
- package/packages/react-css/README.md +1 -0
- package/packages/react-css/package.json +44 -0
- package/packages/react-css/src/adapters.css +1576 -0
- package/packages/react-css/src/components/AdapterCard.tsx +34 -0
- package/packages/react-css/src/components/AdapterConfigForm.tsx +63 -0
- package/packages/react-css/src/components/AdapterList.tsx +40 -0
- package/packages/react-css/src/components/AdapterStatusBadge.tsx +21 -0
- package/packages/react-css/src/components/index.ts +4 -0
- package/packages/react-css/src/hooks/index.ts +75 -0
- package/packages/react-css/src/index.tsx +25 -0
- package/packages/react-css/src/pages/AdapterDetailPage.tsx +133 -0
- package/packages/react-css/src/pages/AdaptersPage.tsx +111 -0
- package/packages/react-css/src/pages/index.ts +2 -0
- package/packages/react-css/src/provider/AdapterProvider.tsx +115 -0
- package/packages/react-css/src/provider/index.ts +2 -0
- package/packages/react-css/src/styles.css +494 -0
- package/packages/react-css/tsconfig.json +19 -0
- package/packages/react-css/tsup.config.ts +2 -0
- package/packages/shared/README.md +1 -0
- package/packages/shared/package.json +39 -0
- package/packages/shared/src/__tests__/adapters.test.ts +545 -0
- package/packages/shared/src/admin/index.ts +2 -0
- package/packages/shared/src/admin/interface.ts +34 -0
- package/packages/shared/src/admin/localStorage.ts +109 -0
- package/packages/shared/src/ai/anthropic.ts +123 -0
- package/packages/shared/src/ai/cloudflare-gateway.ts +130 -0
- package/packages/shared/src/ai/gemini.ts +181 -0
- package/packages/shared/src/ai/index.ts +14 -0
- package/packages/shared/src/ai/interface.ts +11 -0
- package/packages/shared/src/ai/localStorage.ts +78 -0
- package/packages/shared/src/ai/ollama.ts +143 -0
- package/packages/shared/src/ai/openai.ts +120 -0
- package/packages/shared/src/ai/vercel-ai.ts +101 -0
- package/packages/shared/src/auth/better-auth.ts +118 -0
- package/packages/shared/src/auth/clerk.ts +151 -0
- package/packages/shared/src/auth/convex-auth.ts +125 -0
- package/packages/shared/src/auth/index.ts +10 -0
- package/packages/shared/src/auth/interface.ts +17 -0
- package/packages/shared/src/auth/localStorage.ts +125 -0
- package/packages/shared/src/auth/supabase-auth.ts +136 -0
- package/packages/shared/src/config.ts +57 -0
- package/packages/shared/src/constants.ts +122 -0
- package/packages/shared/src/db/convex.ts +146 -0
- package/packages/shared/src/db/index.ts +10 -0
- package/packages/shared/src/db/interface.ts +13 -0
- package/packages/shared/src/db/localStorage.ts +91 -0
- package/packages/shared/src/db/mongodb.ts +125 -0
- package/packages/shared/src/db/neon.ts +171 -0
- package/packages/shared/src/db/supabase.ts +158 -0
- package/packages/shared/src/index.ts +117 -0
- package/packages/shared/src/payments/index.ts +4 -0
- package/packages/shared/src/payments/interface.ts +11 -0
- package/packages/shared/src/payments/localStorage.ts +81 -0
- package/packages/shared/src/payments/stripe.ts +177 -0
- package/packages/shared/src/storage/convex.ts +113 -0
- package/packages/shared/src/storage/index.ts +14 -0
- package/packages/shared/src/storage/interface.ts +11 -0
- package/packages/shared/src/storage/localStorage.ts +95 -0
- package/packages/shared/src/storage/minio.ts +47 -0
- package/packages/shared/src/storage/r2.ts +123 -0
- package/packages/shared/src/storage/s3.ts +128 -0
- package/packages/shared/src/storage/supabase-storage.ts +116 -0
- package/packages/shared/src/storage/uploadthing.ts +126 -0
- package/packages/shared/src/styles/adapters.css +494 -0
- package/packages/shared/src/tier-gate.ts +119 -0
- package/packages/shared/src/types.ts +162 -0
- package/packages/shared/tsconfig.json +18 -0
- package/packages/shared/tsup.config.ts +9 -0
- package/packages/shared/vitest.config.ts +14 -0
- package/packages/solidjs/README.md +1 -0
- package/packages/solidjs/package.json +44 -0
- package/packages/solidjs/src/components/AdapterCard.tsx +24 -0
- package/packages/solidjs/src/components/AdapterConfigForm.tsx +54 -0
- package/packages/solidjs/src/components/AdapterList.tsx +28 -0
- package/packages/solidjs/src/components/AdapterStatusBadge.tsx +20 -0
- package/packages/solidjs/src/components/index.ts +4 -0
- package/packages/solidjs/src/index.tsx +17 -0
- package/packages/solidjs/src/pages/AdapterDetailPage.tsx +38 -0
- package/packages/solidjs/src/pages/AdaptersPage.tsx +39 -0
- package/packages/solidjs/src/pages/index.ts +2 -0
- package/packages/solidjs/src/primitives/index.ts +78 -0
- package/packages/solidjs/src/provider/AdapterProvider.tsx +62 -0
- package/packages/solidjs/src/provider/index.ts +2 -0
- package/packages/solidjs/tsconfig.json +20 -0
- package/packages/solidjs/tsup.config.ts +10 -0
- package/packages/solidjs-css/README.md +1 -0
- package/packages/solidjs-css/package.json +43 -0
- package/packages/solidjs-css/src/adapters.css +1576 -0
- package/packages/solidjs-css/src/components/AdapterCard.tsx +43 -0
- package/packages/solidjs-css/src/components/AdapterConfigForm.tsx +119 -0
- package/packages/solidjs-css/src/components/AdapterList.tsx +68 -0
- package/packages/solidjs-css/src/components/AdapterStatusBadge.tsx +24 -0
- package/packages/solidjs-css/src/components/index.ts +8 -0
- package/packages/solidjs-css/src/index.tsx +30 -0
- package/packages/solidjs-css/src/pages/AdapterDetailPage.tsx +107 -0
- package/packages/solidjs-css/src/pages/AdaptersPage.tsx +94 -0
- package/packages/solidjs-css/src/pages/index.ts +4 -0
- package/packages/solidjs-css/src/primitives/index.ts +1 -0
- package/packages/solidjs-css/src/provider/AdapterProvider.tsx +61 -0
- package/packages/solidjs-css/src/provider/index.ts +2 -0
- package/packages/solidjs-css/tsconfig.json +20 -0
- package/packages/solidjs-css/tsup.config.ts +2 -0
- package/pnpm-workspace.yaml +2 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// @geenius/adapters — localStorage Auth implementation
|
|
2
|
+
|
|
3
|
+
import type { AuthUser, AuthSession } from '../types'
|
|
4
|
+
import type { AuthAdapter } from './interface'
|
|
5
|
+
|
|
6
|
+
interface StoredUser extends AuthUser {
|
|
7
|
+
passwordHash: string
|
|
8
|
+
email: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const USERS_KEY = 'geenius_users'
|
|
12
|
+
const SESSION_KEY = 'geenius_session'
|
|
13
|
+
|
|
14
|
+
function getUsers(): StoredUser[] {
|
|
15
|
+
try { return JSON.parse(localStorage.getItem(USERS_KEY) || '[]') } catch { return [] }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function saveUsers(users: StoredUser[]) {
|
|
19
|
+
localStorage.setItem(USERS_KEY, JSON.stringify(users))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function simpleHash(str: string): string {
|
|
23
|
+
let hash = 0
|
|
24
|
+
for (let i = 0; i < str.length; i++) {
|
|
25
|
+
hash = ((hash << 5) - hash) + str.charCodeAt(i)
|
|
26
|
+
hash |= 0
|
|
27
|
+
}
|
|
28
|
+
return Math.abs(hash).toString(36)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createSession(userId: string): AuthSession {
|
|
32
|
+
const session: AuthSession = {
|
|
33
|
+
userId,
|
|
34
|
+
token: crypto.randomUUID(),
|
|
35
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
36
|
+
}
|
|
37
|
+
localStorage.setItem(SESSION_KEY, JSON.stringify(session))
|
|
38
|
+
return session
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function createLocalStorageAuthAdapter(): AuthAdapter {
|
|
42
|
+
return {
|
|
43
|
+
async signInWithOAuth(provider, options) {
|
|
44
|
+
// Mock OAuth flow — create a synthetic user and return a fake redirect URL.
|
|
45
|
+
// In real adapters this would redirect to the provider's authorization endpoint.
|
|
46
|
+
const mockEmail = `oauth-${provider}-user@example.com`
|
|
47
|
+
const users = getUsers()
|
|
48
|
+
let user = users.find(u => u.email === mockEmail)
|
|
49
|
+
if (!user) {
|
|
50
|
+
user = {
|
|
51
|
+
id: crypto.randomUUID(),
|
|
52
|
+
email: mockEmail,
|
|
53
|
+
name: `${provider.charAt(0).toUpperCase() + provider.slice(1)} User`,
|
|
54
|
+
createdAt: new Date().toISOString(),
|
|
55
|
+
passwordHash: '',
|
|
56
|
+
}
|
|
57
|
+
users.push(user)
|
|
58
|
+
saveUsers(users)
|
|
59
|
+
}
|
|
60
|
+
createSession(user.id)
|
|
61
|
+
const redirectUrl = options?.redirectUrl || '/'
|
|
62
|
+
return { url: redirectUrl }
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
async signUp(email, password, name) {
|
|
66
|
+
const users = getUsers()
|
|
67
|
+
if (users.find(u => u.email === email)) throw new Error('User already exists')
|
|
68
|
+
const user: StoredUser = {
|
|
69
|
+
id: crypto.randomUUID(),
|
|
70
|
+
email,
|
|
71
|
+
name: name || email.split('@')[0],
|
|
72
|
+
createdAt: new Date().toISOString(),
|
|
73
|
+
passwordHash: simpleHash(password),
|
|
74
|
+
}
|
|
75
|
+
users.push(user)
|
|
76
|
+
saveUsers(users)
|
|
77
|
+
return createSession(user.id)
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
async signIn(email, password) {
|
|
81
|
+
const user = getUsers().find(u => u.email === email)
|
|
82
|
+
if (!user || user.passwordHash !== simpleHash(password)) {
|
|
83
|
+
throw new Error('Invalid email or password')
|
|
84
|
+
}
|
|
85
|
+
return createSession(user.id)
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
async signOut() {
|
|
89
|
+
localStorage.removeItem(SESSION_KEY)
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
async getSession() {
|
|
93
|
+
try {
|
|
94
|
+
const s: AuthSession | null = JSON.parse(localStorage.getItem(SESSION_KEY) || 'null')
|
|
95
|
+
if (!s || !s.expiresAt) return null
|
|
96
|
+
if (new Date(s.expiresAt) < new Date()) {
|
|
97
|
+
localStorage.removeItem(SESSION_KEY)
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
return s
|
|
101
|
+
} catch { return null }
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
async getUser() {
|
|
105
|
+
const session = await this.getSession()
|
|
106
|
+
if (!session) return null
|
|
107
|
+
const stored = getUsers().find(u => u.id === session.userId)
|
|
108
|
+
if (!stored) return null
|
|
109
|
+
const { passwordHash: _, ...user } = stored
|
|
110
|
+
return user
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
async updateUser(updates) {
|
|
114
|
+
const session = await this.getSession()
|
|
115
|
+
if (!session) return null
|
|
116
|
+
const users = getUsers()
|
|
117
|
+
const idx = users.findIndex(u => u.id === session.userId)
|
|
118
|
+
if (idx === -1) return null
|
|
119
|
+
Object.assign(users[idx], updates)
|
|
120
|
+
saveUsers(users)
|
|
121
|
+
const { passwordHash: _, ...user } = users[idx]
|
|
122
|
+
return user
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// @geenius/adapters — Supabase Auth adapter
|
|
2
|
+
// Wraps the Supabase Auth client to conform to AuthAdapter interface.
|
|
3
|
+
// Requires: @supabase/supabase-js
|
|
4
|
+
|
|
5
|
+
import type { AuthUser, AuthSession } from '../types'
|
|
6
|
+
import type { AuthAdapter, OAuthProvider } from './interface'
|
|
7
|
+
|
|
8
|
+
interface SupabaseAuthClient {
|
|
9
|
+
auth: {
|
|
10
|
+
signInWithPassword: (params: { email: string; password: string }) => Promise<{
|
|
11
|
+
data: { session: { access_token: string; user: { id: string; email?: string; user_metadata: Record<string, any> } } | null }
|
|
12
|
+
error: { message: string } | null
|
|
13
|
+
}>
|
|
14
|
+
signUp: (params: { email: string; password: string; options?: { data?: Record<string, any> } }) => Promise<{
|
|
15
|
+
data: { session: { access_token: string; user: { id: string; email?: string; user_metadata: Record<string, any> } } | null }
|
|
16
|
+
error: { message: string } | null
|
|
17
|
+
}>
|
|
18
|
+
signOut: () => Promise<{ error: { message: string } | null }>
|
|
19
|
+
getSession: () => Promise<{
|
|
20
|
+
data: { session: { access_token: string; expires_at?: number; user: { id: string; email?: string; user_metadata: Record<string, any> } } | null }
|
|
21
|
+
error: { message: string } | null
|
|
22
|
+
}>
|
|
23
|
+
getUser: () => Promise<{
|
|
24
|
+
data: { user: { id: string; email?: string; user_metadata: Record<string, any> } | null }
|
|
25
|
+
error: { message: string } | null
|
|
26
|
+
}>
|
|
27
|
+
updateUser: (params: { data?: Record<string, any> }) => Promise<{
|
|
28
|
+
data: { user: { id: string; email?: string; user_metadata: Record<string, any> } | null }
|
|
29
|
+
error: { message: string } | null
|
|
30
|
+
}>
|
|
31
|
+
signInWithOAuth: (params: { provider: string; options?: { redirectTo?: string } }) => Promise<{
|
|
32
|
+
data: { url: string | null; provider: string } | null
|
|
33
|
+
error: { message: string } | null
|
|
34
|
+
}>
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SupabaseAuthAdapterOptions {
|
|
39
|
+
/** Pre-configured Supabase client instance (from createClient()) */
|
|
40
|
+
client: SupabaseAuthClient
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createSupabaseAuthAdapter(options: SupabaseAuthAdapterOptions): AuthAdapter {
|
|
44
|
+
const { client } = options
|
|
45
|
+
|
|
46
|
+
function mapUser(user: { id: string; email?: string; user_metadata: Record<string, any> }): AuthUser {
|
|
47
|
+
return {
|
|
48
|
+
id: user.id,
|
|
49
|
+
email: user.email || '',
|
|
50
|
+
name: user.user_metadata?.name || user.user_metadata?.full_name,
|
|
51
|
+
image: user.user_metadata?.avatar_url || user.user_metadata?.image,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
async signInWithOAuth(provider: OAuthProvider, options?: { redirectUrl?: string }): Promise<{ url: string }> {
|
|
57
|
+
const { data, error } = await client.auth.signInWithOAuth({
|
|
58
|
+
provider,
|
|
59
|
+
options: options?.redirectUrl ? { redirectTo: options.redirectUrl } : undefined,
|
|
60
|
+
})
|
|
61
|
+
if (error) throw new Error(error.message)
|
|
62
|
+
if (!data?.url) throw new Error(`Supabase OAuth: no redirect URL returned for provider "${provider}"`)
|
|
63
|
+
return { url: data.url }
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
async signIn(email: string, password: string): Promise<AuthSession> {
|
|
67
|
+
const { data, error } = await client.auth.signInWithPassword({ email, password })
|
|
68
|
+
if (error) throw new Error(error.message)
|
|
69
|
+
if (!data.session) throw new Error('Supabase sign in failed — no session returned')
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
userId: data.session.user.id,
|
|
73
|
+
token: data.session.access_token,
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
async signUp(email: string, password: string, name?: string): Promise<AuthSession> {
|
|
78
|
+
const { data, error } = await client.auth.signUp({
|
|
79
|
+
email,
|
|
80
|
+
password,
|
|
81
|
+
options: name ? { data: { name, full_name: name } } : undefined,
|
|
82
|
+
})
|
|
83
|
+
if (error) throw new Error(error.message)
|
|
84
|
+
if (!data.session) throw new Error('Supabase sign up failed — no session returned (check email confirmation settings)')
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
userId: data.session.user.id,
|
|
88
|
+
token: data.session.access_token,
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
async signOut(): Promise<void> {
|
|
93
|
+
const { error } = await client.auth.signOut()
|
|
94
|
+
if (error) throw new Error(error.message)
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
async getSession(): Promise<AuthSession | null> {
|
|
98
|
+
try {
|
|
99
|
+
const { data, error } = await client.auth.getSession()
|
|
100
|
+
if (error || !data.session) return null
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
userId: data.session.user.id,
|
|
104
|
+
token: data.session.access_token,
|
|
105
|
+
expiresAt: data.session.expires_at ? new Date(data.session.expires_at * 1000).toISOString() : undefined,
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
return null
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
async getUser(): Promise<AuthUser | null> {
|
|
113
|
+
try {
|
|
114
|
+
const { data, error } = await client.auth.getUser()
|
|
115
|
+
if (error || !data.user) return null
|
|
116
|
+
return mapUser(data.user)
|
|
117
|
+
} catch {
|
|
118
|
+
return null
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
async updateUser(updates: Partial<Pick<AuthUser, 'name' | 'image'>>): Promise<AuthUser | null> {
|
|
123
|
+
try {
|
|
124
|
+
const metadata: Record<string, any> = {}
|
|
125
|
+
if (updates.name) { metadata.name = updates.name; metadata.full_name = updates.name }
|
|
126
|
+
if (updates.image) { metadata.avatar_url = updates.image; metadata.image = updates.image }
|
|
127
|
+
|
|
128
|
+
const { data, error } = await client.auth.updateUser({ data: metadata })
|
|
129
|
+
if (error || !data.user) return null
|
|
130
|
+
return mapUser(data.user)
|
|
131
|
+
} catch {
|
|
132
|
+
return null
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// @geenius-adapters/shared — Configuration singleton
|
|
2
|
+
|
|
3
|
+
import type { AdapterConfig, AdapterDomain } from './types'
|
|
4
|
+
|
|
5
|
+
let _config: AdapterConfig | null = null
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Configure the adapter system.
|
|
9
|
+
* Call once at app startup before using any adapter hooks.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* configureAdapters({
|
|
14
|
+
* db: { provider: 'convex' },
|
|
15
|
+
* auth: { provider: 'better-auth' },
|
|
16
|
+
* ai: { provider: 'openai', apiKey: process.env.OPENAI_API_KEY },
|
|
17
|
+
* storage: { provider: 'r2' },
|
|
18
|
+
* payments: { provider: 'stripe' },
|
|
19
|
+
* })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function configureAdapters(config: AdapterConfig): void {
|
|
23
|
+
_config = config
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get the current adapter configuration.
|
|
28
|
+
* Throws if configureAdapters() has not been called.
|
|
29
|
+
*/
|
|
30
|
+
export function getAdapterConfig(): AdapterConfig {
|
|
31
|
+
if (!_config) {
|
|
32
|
+
throw new Error('Adapters not configured. Call configureAdapters() first.')
|
|
33
|
+
}
|
|
34
|
+
return _config
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if adapters have been configured.
|
|
39
|
+
*/
|
|
40
|
+
export function isAdaptersConfigured(): boolean {
|
|
41
|
+
return _config !== null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Reset adapter configuration (useful for testing).
|
|
46
|
+
*/
|
|
47
|
+
export function resetAdapterConfig(): void {
|
|
48
|
+
_config = null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get config for a specific adapter domain.
|
|
53
|
+
*/
|
|
54
|
+
export function getDomainConfig(domain: AdapterDomain) {
|
|
55
|
+
const config = getAdapterConfig()
|
|
56
|
+
return config[domain] ?? null
|
|
57
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// @geenius-adapters/shared — Constants & defaults
|
|
2
|
+
|
|
3
|
+
import type { AdapterDomain } from './types'
|
|
4
|
+
|
|
5
|
+
// ─── Adapter Domains ─────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
export const ADAPTER_DOMAINS: readonly AdapterDomain[] = [
|
|
8
|
+
'db', 'auth', 'payments', 'ai', 'storage', 'admin',
|
|
9
|
+
] as const
|
|
10
|
+
|
|
11
|
+
// ─── Provider Names per Domain ───────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export const DB_PROVIDERS = ['localStorage', 'convex', 'supabase', 'neon', 'mongodb'] as const
|
|
14
|
+
export const AUTH_PROVIDERS = ['localStorage', 'better-auth', 'convex-auth', 'clerk', 'supabase-auth'] as const
|
|
15
|
+
export const AI_PROVIDERS = ['localStorage', 'openai', 'anthropic', 'gemini', 'ollama', 'cloudflare-ai-gateway', 'vercel-ai-sdk'] as const
|
|
16
|
+
export const STORAGE_PROVIDERS = ['localStorage', 'r2', 's3', 'uploadthing', 'supabase-storage', 'convex-storage', 'minio'] as const
|
|
17
|
+
export const PAYMENT_PROVIDERS = ['localStorage', 'stripe', 'noop'] as const
|
|
18
|
+
export const ADMIN_PROVIDERS = ['localStorage'] as const
|
|
19
|
+
|
|
20
|
+
export type DbProviderName = (typeof DB_PROVIDERS)[number]
|
|
21
|
+
export type AuthProviderName = (typeof AUTH_PROVIDERS)[number]
|
|
22
|
+
export type AiProviderName = (typeof AI_PROVIDERS)[number]
|
|
23
|
+
export type StorageProviderName = (typeof STORAGE_PROVIDERS)[number]
|
|
24
|
+
export type PaymentProviderName = (typeof PAYMENT_PROVIDERS)[number]
|
|
25
|
+
export type AdminProviderName = (typeof ADMIN_PROVIDERS)[number]
|
|
26
|
+
|
|
27
|
+
// ─── Status Types ────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
export const ADAPTER_STATUSES = ['connected', 'disconnected', 'error', 'initializing'] as const
|
|
30
|
+
export type AdapterStatus = (typeof ADAPTER_STATUSES)[number]
|
|
31
|
+
|
|
32
|
+
// ─── Domain Labels ───────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
export const DOMAIN_LABELS: Record<AdapterDomain, string> = {
|
|
35
|
+
db: 'Database',
|
|
36
|
+
auth: 'Authentication',
|
|
37
|
+
payments: 'Payments',
|
|
38
|
+
ai: 'AI & LLM',
|
|
39
|
+
storage: 'File Storage',
|
|
40
|
+
admin: 'Admin Panel',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const DOMAIN_ICONS: Record<AdapterDomain, string> = {
|
|
44
|
+
db: '🗄️',
|
|
45
|
+
auth: '🔐',
|
|
46
|
+
payments: '💳',
|
|
47
|
+
ai: '🤖',
|
|
48
|
+
storage: '📁',
|
|
49
|
+
admin: '⚙️',
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const DOMAIN_DESCRIPTIONS: Record<AdapterDomain, string> = {
|
|
53
|
+
db: 'Connect to your preferred database backend for data persistence.',
|
|
54
|
+
auth: 'User authentication, sessions, and identity management.',
|
|
55
|
+
payments: 'Subscriptions, checkout, and billing management.',
|
|
56
|
+
ai: 'AI completions, chat, and embeddings via any LLM provider.',
|
|
57
|
+
storage: 'Upload, download, and manage files with any storage provider.',
|
|
58
|
+
admin: 'Admin dashboard metrics and user management.',
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ─── Provider Metadata ───────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
export interface ProviderMeta {
|
|
64
|
+
id: string
|
|
65
|
+
name: string
|
|
66
|
+
domain: AdapterDomain
|
|
67
|
+
description: string
|
|
68
|
+
/** Minimum tier required to use this provider */
|
|
69
|
+
tier: 'pronto' | 'lancio' | 'studio'
|
|
70
|
+
docUrl?: string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const PROVIDER_REGISTRY: ProviderMeta[] = [
|
|
74
|
+
// DB
|
|
75
|
+
{ id: 'localStorage', name: 'localStorage', domain: 'db', description: 'In-browser mock storage for prototyping', tier: 'pronto' },
|
|
76
|
+
{ id: 'convex', name: 'Convex', domain: 'db', description: 'Real-time cloud database with sub-50ms reactivity', tier: 'lancio' },
|
|
77
|
+
{ id: 'supabase', name: 'Supabase', domain: 'db', description: 'PostgreSQL + Auth + Storage', tier: 'lancio' },
|
|
78
|
+
{ id: 'neon', name: 'Neon', domain: 'db', description: 'Serverless PostgreSQL', tier: 'lancio' },
|
|
79
|
+
{ id: 'mongodb', name: 'MongoDB', domain: 'db', description: 'Document database via Prisma ORM', tier: 'lancio' },
|
|
80
|
+
// Auth
|
|
81
|
+
{ id: 'localStorage', name: 'localStorage', domain: 'auth', description: 'Mock auth for prototyping', tier: 'pronto' },
|
|
82
|
+
{ id: 'better-auth', name: 'Better Auth', domain: 'auth', description: 'Self-hosted auth with OAuth + MFA', tier: 'lancio' },
|
|
83
|
+
{ id: 'convex-auth', name: 'Convex Auth', domain: 'auth', description: 'Built-in Convex sessions', tier: 'lancio' },
|
|
84
|
+
{ id: 'clerk', name: 'Clerk', domain: 'auth', description: 'SaaS auth with pre-built UI', tier: 'lancio' },
|
|
85
|
+
{ id: 'supabase-auth', name: 'Supabase Auth', domain: 'auth', description: 'Built-in Supabase auth', tier: 'lancio' },
|
|
86
|
+
// AI
|
|
87
|
+
{ id: 'localStorage', name: 'localStorage', domain: 'ai', description: 'Mock AI for prototyping', tier: 'pronto' },
|
|
88
|
+
{ id: 'openai', name: 'OpenAI', domain: 'ai', description: 'GPT models with streaming + embeddings', tier: 'lancio' },
|
|
89
|
+
{ id: 'anthropic', name: 'Anthropic', domain: 'ai', description: 'Claude models with streaming', tier: 'lancio' },
|
|
90
|
+
{ id: 'gemini', name: 'Google Gemini', domain: 'ai', description: 'Gemini models, zero npm deps', tier: 'lancio' },
|
|
91
|
+
{ id: 'ollama', name: 'Ollama', domain: 'ai', description: 'Local LLMs, zero npm deps', tier: 'lancio' },
|
|
92
|
+
{ id: 'cloudflare-ai-gateway', name: 'CF AI Gateway', domain: 'ai', description: 'Caching/rate-limiting proxy', tier: 'studio' },
|
|
93
|
+
{ id: 'vercel-ai-sdk', name: 'Vercel AI SDK', domain: 'ai', description: 'generateText/embed wrappers', tier: 'studio' },
|
|
94
|
+
// Storage
|
|
95
|
+
{ id: 'localStorage', name: 'localStorage', domain: 'storage', description: 'Base64 in-browser storage', tier: 'pronto' },
|
|
96
|
+
{ id: 'r2', name: 'Cloudflare R2', domain: 'storage', description: 'S3-compatible, zero egress', tier: 'lancio' },
|
|
97
|
+
{ id: 's3', name: 'AWS S3', domain: 'storage', description: 'IAM roles + CloudFront CDN', tier: 'lancio' },
|
|
98
|
+
{ id: 'uploadthing', name: 'Uploadthing', domain: 'storage', description: 'REST API file upload SaaS', tier: 'lancio' },
|
|
99
|
+
{ id: 'supabase-storage', name: 'Supabase Storage', domain: 'storage', description: 'Native Supabase storage', tier: 'lancio' },
|
|
100
|
+
{ id: 'convex-storage', name: 'Convex Storage', domain: 'storage', description: 'Convex built-in file storage', tier: 'lancio' },
|
|
101
|
+
{ id: 'minio', name: 'Minio', domain: 'storage', description: 'Self-hosted S3-compatible', tier: 'studio' },
|
|
102
|
+
// Payments
|
|
103
|
+
{ id: 'localStorage', name: 'localStorage', domain: 'payments', description: 'Mock billing for prototyping', tier: 'pronto' },
|
|
104
|
+
{ id: 'stripe', name: 'Stripe', domain: 'payments', description: 'Full billing, subscriptions, checkout', tier: 'lancio' },
|
|
105
|
+
{ id: 'noop', name: 'Noop', domain: 'payments', description: 'No-op adapter — always returns success', tier: 'pronto' },
|
|
106
|
+
// Admin
|
|
107
|
+
{ id: 'localStorage', name: 'localStorage', domain: 'admin', description: 'Mock admin for prototyping', tier: 'pronto' },
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get all providers for a specific domain.
|
|
112
|
+
*/
|
|
113
|
+
export function getProvidersForDomain(domain: AdapterDomain): ProviderMeta[] {
|
|
114
|
+
return PROVIDER_REGISTRY.filter((p) => p.domain === domain)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get a specific provider by id and domain.
|
|
119
|
+
*/
|
|
120
|
+
export function getProviderMeta(domain: AdapterDomain, providerId: string): ProviderMeta | undefined {
|
|
121
|
+
return PROVIDER_REGISTRY.find((p) => p.domain === domain && p.id === providerId)
|
|
122
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// @geenius/adapters — Convex DB implementation (MVP tier)
|
|
2
|
+
// Wraps the Convex client to conform to DbAdapter interface.
|
|
3
|
+
// Requires: convex
|
|
4
|
+
|
|
5
|
+
import type { ListOptions, QueryFilter } from '../types'
|
|
6
|
+
import type { DbAdapter } from './interface'
|
|
7
|
+
|
|
8
|
+
interface ConvexClient {
|
|
9
|
+
query: (fn: any, args?: any) => Promise<any>
|
|
10
|
+
mutation: (fn: any, args?: any) => Promise<any>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ConvexDbAdapterOptions {
|
|
14
|
+
/** Pre-configured Convex client */
|
|
15
|
+
client: ConvexClient
|
|
16
|
+
/** Map of collection names to Convex function references */
|
|
17
|
+
functions: {
|
|
18
|
+
[collection: string]: {
|
|
19
|
+
create?: any
|
|
20
|
+
get?: any
|
|
21
|
+
update?: any
|
|
22
|
+
delete?: any
|
|
23
|
+
list?: any
|
|
24
|
+
query?: any
|
|
25
|
+
count?: any
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Creates a Convex-backed DbAdapter.
|
|
32
|
+
*
|
|
33
|
+
* Since Convex uses predefined queries/mutations rather than arbitrary SQL,
|
|
34
|
+
* you must provide function references for each collection. The adapter maps
|
|
35
|
+
* generic CRUD calls to these Convex functions.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* import { api } from '../convex/_generated/api'
|
|
40
|
+
*
|
|
41
|
+
* const db = createConvexDbAdapter({
|
|
42
|
+
* client: convex,
|
|
43
|
+
* functions: {
|
|
44
|
+
* tasks: {
|
|
45
|
+
* create: api.tasks.create,
|
|
46
|
+
* get: api.tasks.get,
|
|
47
|
+
* list: api.tasks.list,
|
|
48
|
+
* update: api.tasks.update,
|
|
49
|
+
* delete: api.tasks.remove,
|
|
50
|
+
* },
|
|
51
|
+
* },
|
|
52
|
+
* })
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function createConvexDbAdapter(options: ConvexDbAdapterOptions): DbAdapter {
|
|
56
|
+
const { client, functions } = options
|
|
57
|
+
|
|
58
|
+
function getFns(collection: string) {
|
|
59
|
+
const fns = functions[collection]
|
|
60
|
+
if (!fns) throw new Error(`No Convex functions configured for collection: ${collection}`)
|
|
61
|
+
return fns
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
async create(collection, data) {
|
|
66
|
+
const fns = getFns(collection)
|
|
67
|
+
if (!fns.create) throw new Error(`No create function for ${collection}`)
|
|
68
|
+
const id = await client.mutation(fns.create, data)
|
|
69
|
+
return { ...data, id } as any
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
async get(collection, id) {
|
|
73
|
+
const fns = getFns(collection)
|
|
74
|
+
if (!fns.get) throw new Error(`No get function for ${collection}`)
|
|
75
|
+
const doc = await client.query(fns.get, { id })
|
|
76
|
+
if (!doc) return null
|
|
77
|
+
return { ...doc, id: doc._id || id } as any
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
async update(collection, id, data) {
|
|
81
|
+
const fns = getFns(collection)
|
|
82
|
+
if (!fns.update) throw new Error(`No update function for ${collection}`)
|
|
83
|
+
await client.mutation(fns.update, { id, ...data })
|
|
84
|
+
return this.get(collection, id) as any
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
async delete(collection, id) {
|
|
88
|
+
const fns = getFns(collection)
|
|
89
|
+
if (!fns.delete) throw new Error(`No delete function for ${collection}`)
|
|
90
|
+
try {
|
|
91
|
+
await client.mutation(fns.delete, { id })
|
|
92
|
+
return true
|
|
93
|
+
} catch {
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
async list(collection, options?: ListOptions) {
|
|
99
|
+
const fns = getFns(collection)
|
|
100
|
+
if (!fns.list) throw new Error(`No list function for ${collection}`)
|
|
101
|
+
const docs = await client.query(fns.list, {
|
|
102
|
+
limit: options?.limit,
|
|
103
|
+
offset: options?.offset,
|
|
104
|
+
orderBy: options?.orderBy,
|
|
105
|
+
order: options?.order,
|
|
106
|
+
})
|
|
107
|
+
return (docs || []).map((d: any) => ({ ...d, id: d._id || d.id }))
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
async query(collection, filter) {
|
|
111
|
+
const fns = getFns(collection)
|
|
112
|
+
if (!fns.query) {
|
|
113
|
+
// Fallback: list all and filter client-side
|
|
114
|
+
const all = await this.list(collection)
|
|
115
|
+
return all.filter((item: any) =>
|
|
116
|
+
filter.every((cond) => {
|
|
117
|
+
const val = item[cond.field]
|
|
118
|
+
switch (cond.operator) {
|
|
119
|
+
case 'eq': return val === cond.value
|
|
120
|
+
case 'neq': return val !== cond.value
|
|
121
|
+
case 'gt': return (val as number) > (cond.value as number)
|
|
122
|
+
case 'gte': return (val as number) >= (cond.value as number)
|
|
123
|
+
case 'lt': return (val as number) < (cond.value as number)
|
|
124
|
+
case 'lte': return (val as number) <= (cond.value as number)
|
|
125
|
+
case 'in': return Array.isArray(cond.value) && cond.value.includes(val)
|
|
126
|
+
case 'contains': return typeof val === 'string' && val.includes(cond.value as string)
|
|
127
|
+
default: return false
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
const docs = await client.query(fns.query, { filter })
|
|
133
|
+
return (docs || []).map((d: any) => ({ ...d, id: d._id || d.id }))
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
async count(collection, filter?) {
|
|
137
|
+
const fns = getFns(collection)
|
|
138
|
+
if (fns.count) {
|
|
139
|
+
return client.query(fns.count, filter ? { filter } : undefined)
|
|
140
|
+
}
|
|
141
|
+
// Fallback: count via list/query
|
|
142
|
+
const items = filter ? await this.query(collection, filter) : await this.list(collection)
|
|
143
|
+
return items.length
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type { DbAdapter } from './interface'
|
|
2
|
+
export { createLocalStorageDbAdapter } from './localStorage'
|
|
3
|
+
export { createConvexDbAdapter } from './convex'
|
|
4
|
+
export type { ConvexDbAdapterOptions } from './convex'
|
|
5
|
+
export { createNeonDbAdapter } from './neon'
|
|
6
|
+
export type { NeonDbAdapterOptions } from './neon'
|
|
7
|
+
export { createSupabaseDbAdapter } from './supabase'
|
|
8
|
+
export type { SupabaseDbAdapterOptions } from './supabase'
|
|
9
|
+
export { createMongoDbAdapter } from './mongodb'
|
|
10
|
+
export type { MongoDbAdapterOptions } from './mongodb'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// @geenius/adapters — DB adapter interface
|
|
2
|
+
|
|
3
|
+
import type { ListOptions, QueryFilter } from '../types'
|
|
4
|
+
|
|
5
|
+
export interface DbAdapter {
|
|
6
|
+
create<T extends Record<string, unknown>>(collection: string, data: Omit<T, 'id'>): Promise<T & { id: string }>
|
|
7
|
+
get<T>(collection: string, id: string): Promise<T | null>
|
|
8
|
+
update<T extends Record<string, unknown>>(collection: string, id: string, data: Partial<T>): Promise<T | null>
|
|
9
|
+
delete(collection: string, id: string): Promise<boolean>
|
|
10
|
+
list<T>(collection: string, options?: ListOptions): Promise<T[]>
|
|
11
|
+
query<T>(collection: string, filter: QueryFilter): Promise<T[]>
|
|
12
|
+
count(collection: string, filter?: QueryFilter): Promise<number>
|
|
13
|
+
}
|