@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.
Files changed (151) hide show
  1. package/.changeset/config.json +11 -0
  2. package/.github/CODEOWNERS +1 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +11 -0
  5. package/.github/PULL_REQUEST_TEMPLATE.md +10 -0
  6. package/.github/dependabot.yml +11 -0
  7. package/.github/workflows/ci.yml +23 -0
  8. package/.github/workflows/release.yml +29 -0
  9. package/.nvmrc +1 -0
  10. package/.project/ACCOUNT.yaml +4 -0
  11. package/.project/IDEAS.yaml +7 -0
  12. package/.project/PROJECT.yaml +11 -0
  13. package/.project/ROADMAP.yaml +15 -0
  14. package/CHANGELOG.md +11 -0
  15. package/CODE_OF_CONDUCT.md +16 -0
  16. package/CONTRIBUTING.md +26 -0
  17. package/LICENSE +21 -0
  18. package/README.md +202 -0
  19. package/SECURITY.md +15 -0
  20. package/SUPPORT.md +8 -0
  21. package/package.json +51 -0
  22. package/packages/convex/README.md +64 -0
  23. package/packages/convex/package.json +42 -0
  24. package/packages/convex/src/adapter.ts +39 -0
  25. package/packages/convex/src/index.ts +19 -0
  26. package/packages/convex/src/mutations.ts +142 -0
  27. package/packages/convex/src/queries.ts +106 -0
  28. package/packages/convex/src/schema.ts +54 -0
  29. package/packages/convex/src/types.ts +20 -0
  30. package/packages/convex/tsconfig.json +11 -0
  31. package/packages/convex/tsup.config.ts +10 -0
  32. package/packages/react/README.md +1 -0
  33. package/packages/react/package.json +45 -0
  34. package/packages/react/src/components/AdapterCard.tsx +49 -0
  35. package/packages/react/src/components/AdapterConfigForm.tsx +118 -0
  36. package/packages/react/src/components/AdapterList.tsx +84 -0
  37. package/packages/react/src/components/AdapterStatusBadge.tsx +30 -0
  38. package/packages/react/src/components/index.ts +4 -0
  39. package/packages/react/src/hooks/index.ts +75 -0
  40. package/packages/react/src/index.tsx +44 -0
  41. package/packages/react/src/pages/AdapterDetailPage.tsx +133 -0
  42. package/packages/react/src/pages/AdaptersPage.tsx +111 -0
  43. package/packages/react/src/pages/index.ts +2 -0
  44. package/packages/react/src/provider/AdapterProvider.tsx +115 -0
  45. package/packages/react/src/provider/index.ts +2 -0
  46. package/packages/react/tsconfig.json +18 -0
  47. package/packages/react/tsup.config.ts +10 -0
  48. package/packages/react-css/README.md +1 -0
  49. package/packages/react-css/package.json +44 -0
  50. package/packages/react-css/src/adapters.css +1576 -0
  51. package/packages/react-css/src/components/AdapterCard.tsx +34 -0
  52. package/packages/react-css/src/components/AdapterConfigForm.tsx +63 -0
  53. package/packages/react-css/src/components/AdapterList.tsx +40 -0
  54. package/packages/react-css/src/components/AdapterStatusBadge.tsx +21 -0
  55. package/packages/react-css/src/components/index.ts +4 -0
  56. package/packages/react-css/src/hooks/index.ts +75 -0
  57. package/packages/react-css/src/index.tsx +25 -0
  58. package/packages/react-css/src/pages/AdapterDetailPage.tsx +133 -0
  59. package/packages/react-css/src/pages/AdaptersPage.tsx +111 -0
  60. package/packages/react-css/src/pages/index.ts +2 -0
  61. package/packages/react-css/src/provider/AdapterProvider.tsx +115 -0
  62. package/packages/react-css/src/provider/index.ts +2 -0
  63. package/packages/react-css/src/styles.css +494 -0
  64. package/packages/react-css/tsconfig.json +19 -0
  65. package/packages/react-css/tsup.config.ts +2 -0
  66. package/packages/shared/README.md +1 -0
  67. package/packages/shared/package.json +39 -0
  68. package/packages/shared/src/__tests__/adapters.test.ts +545 -0
  69. package/packages/shared/src/admin/index.ts +2 -0
  70. package/packages/shared/src/admin/interface.ts +34 -0
  71. package/packages/shared/src/admin/localStorage.ts +109 -0
  72. package/packages/shared/src/ai/anthropic.ts +123 -0
  73. package/packages/shared/src/ai/cloudflare-gateway.ts +130 -0
  74. package/packages/shared/src/ai/gemini.ts +181 -0
  75. package/packages/shared/src/ai/index.ts +14 -0
  76. package/packages/shared/src/ai/interface.ts +11 -0
  77. package/packages/shared/src/ai/localStorage.ts +78 -0
  78. package/packages/shared/src/ai/ollama.ts +143 -0
  79. package/packages/shared/src/ai/openai.ts +120 -0
  80. package/packages/shared/src/ai/vercel-ai.ts +101 -0
  81. package/packages/shared/src/auth/better-auth.ts +118 -0
  82. package/packages/shared/src/auth/clerk.ts +151 -0
  83. package/packages/shared/src/auth/convex-auth.ts +125 -0
  84. package/packages/shared/src/auth/index.ts +10 -0
  85. package/packages/shared/src/auth/interface.ts +17 -0
  86. package/packages/shared/src/auth/localStorage.ts +125 -0
  87. package/packages/shared/src/auth/supabase-auth.ts +136 -0
  88. package/packages/shared/src/config.ts +57 -0
  89. package/packages/shared/src/constants.ts +122 -0
  90. package/packages/shared/src/db/convex.ts +146 -0
  91. package/packages/shared/src/db/index.ts +10 -0
  92. package/packages/shared/src/db/interface.ts +13 -0
  93. package/packages/shared/src/db/localStorage.ts +91 -0
  94. package/packages/shared/src/db/mongodb.ts +125 -0
  95. package/packages/shared/src/db/neon.ts +171 -0
  96. package/packages/shared/src/db/supabase.ts +158 -0
  97. package/packages/shared/src/index.ts +117 -0
  98. package/packages/shared/src/payments/index.ts +4 -0
  99. package/packages/shared/src/payments/interface.ts +11 -0
  100. package/packages/shared/src/payments/localStorage.ts +81 -0
  101. package/packages/shared/src/payments/stripe.ts +177 -0
  102. package/packages/shared/src/storage/convex.ts +113 -0
  103. package/packages/shared/src/storage/index.ts +14 -0
  104. package/packages/shared/src/storage/interface.ts +11 -0
  105. package/packages/shared/src/storage/localStorage.ts +95 -0
  106. package/packages/shared/src/storage/minio.ts +47 -0
  107. package/packages/shared/src/storage/r2.ts +123 -0
  108. package/packages/shared/src/storage/s3.ts +128 -0
  109. package/packages/shared/src/storage/supabase-storage.ts +116 -0
  110. package/packages/shared/src/storage/uploadthing.ts +126 -0
  111. package/packages/shared/src/styles/adapters.css +494 -0
  112. package/packages/shared/src/tier-gate.ts +119 -0
  113. package/packages/shared/src/types.ts +162 -0
  114. package/packages/shared/tsconfig.json +18 -0
  115. package/packages/shared/tsup.config.ts +9 -0
  116. package/packages/shared/vitest.config.ts +14 -0
  117. package/packages/solidjs/README.md +1 -0
  118. package/packages/solidjs/package.json +44 -0
  119. package/packages/solidjs/src/components/AdapterCard.tsx +24 -0
  120. package/packages/solidjs/src/components/AdapterConfigForm.tsx +54 -0
  121. package/packages/solidjs/src/components/AdapterList.tsx +28 -0
  122. package/packages/solidjs/src/components/AdapterStatusBadge.tsx +20 -0
  123. package/packages/solidjs/src/components/index.ts +4 -0
  124. package/packages/solidjs/src/index.tsx +17 -0
  125. package/packages/solidjs/src/pages/AdapterDetailPage.tsx +38 -0
  126. package/packages/solidjs/src/pages/AdaptersPage.tsx +39 -0
  127. package/packages/solidjs/src/pages/index.ts +2 -0
  128. package/packages/solidjs/src/primitives/index.ts +78 -0
  129. package/packages/solidjs/src/provider/AdapterProvider.tsx +62 -0
  130. package/packages/solidjs/src/provider/index.ts +2 -0
  131. package/packages/solidjs/tsconfig.json +20 -0
  132. package/packages/solidjs/tsup.config.ts +10 -0
  133. package/packages/solidjs-css/README.md +1 -0
  134. package/packages/solidjs-css/package.json +43 -0
  135. package/packages/solidjs-css/src/adapters.css +1576 -0
  136. package/packages/solidjs-css/src/components/AdapterCard.tsx +43 -0
  137. package/packages/solidjs-css/src/components/AdapterConfigForm.tsx +119 -0
  138. package/packages/solidjs-css/src/components/AdapterList.tsx +68 -0
  139. package/packages/solidjs-css/src/components/AdapterStatusBadge.tsx +24 -0
  140. package/packages/solidjs-css/src/components/index.ts +8 -0
  141. package/packages/solidjs-css/src/index.tsx +30 -0
  142. package/packages/solidjs-css/src/pages/AdapterDetailPage.tsx +107 -0
  143. package/packages/solidjs-css/src/pages/AdaptersPage.tsx +94 -0
  144. package/packages/solidjs-css/src/pages/index.ts +4 -0
  145. package/packages/solidjs-css/src/primitives/index.ts +1 -0
  146. package/packages/solidjs-css/src/provider/AdapterProvider.tsx +61 -0
  147. package/packages/solidjs-css/src/provider/index.ts +2 -0
  148. package/packages/solidjs-css/tsconfig.json +20 -0
  149. package/packages/solidjs-css/tsup.config.ts +2 -0
  150. package/pnpm-workspace.yaml +2 -0
  151. 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
+ }