@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,19 @@
|
|
|
1
|
+
// @geenius-adapters/convex — Barrel
|
|
2
|
+
|
|
3
|
+
export { default as schema } from './schema'
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
registerAdapter,
|
|
7
|
+
updateAdapterStatus,
|
|
8
|
+
updateAdapter,
|
|
9
|
+
removeAdapter,
|
|
10
|
+
} from './mutations'
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
listAdapters,
|
|
14
|
+
listAdaptersByDomain,
|
|
15
|
+
getAdapter,
|
|
16
|
+
getActiveAdapter,
|
|
17
|
+
getAdapterStatusSummary,
|
|
18
|
+
getAdapterHealthLogs,
|
|
19
|
+
} from './queries'
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// @geenius-adapters/convex — Mutations
|
|
2
|
+
|
|
3
|
+
import { mutation } from './_generated/server'
|
|
4
|
+
import { v } from 'convex/values'
|
|
5
|
+
|
|
6
|
+
const domainValidator = v.union(
|
|
7
|
+
v.literal('db'), v.literal('auth'), v.literal('payments'),
|
|
8
|
+
v.literal('ai'), v.literal('storage'), v.literal('admin'),
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
const statusValidator = v.union(
|
|
12
|
+
v.literal('connected'), v.literal('disconnected'),
|
|
13
|
+
v.literal('error'), v.literal('initializing'),
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Register or update an adapter for a given domain.
|
|
18
|
+
*/
|
|
19
|
+
export const registerAdapter = mutation({
|
|
20
|
+
args: {
|
|
21
|
+
domain: domainValidator,
|
|
22
|
+
provider: v.string(),
|
|
23
|
+
config: v.object({
|
|
24
|
+
provider: v.string(),
|
|
25
|
+
apiKey: v.optional(v.string()),
|
|
26
|
+
baseUrl: v.optional(v.string()),
|
|
27
|
+
options: v.optional(v.any()),
|
|
28
|
+
}),
|
|
29
|
+
createdBy: v.optional(v.string()),
|
|
30
|
+
},
|
|
31
|
+
handler: async (ctx, args) => {
|
|
32
|
+
// Check if adapter already exists for this domain
|
|
33
|
+
const existing = await ctx.db
|
|
34
|
+
.query('adapter_registry')
|
|
35
|
+
.withIndex('by_domain_provider', (q) => q.eq('domain', args.domain).eq('provider', args.provider))
|
|
36
|
+
.first()
|
|
37
|
+
|
|
38
|
+
const now = Date.now()
|
|
39
|
+
|
|
40
|
+
if (existing) {
|
|
41
|
+
await ctx.db.patch(existing._id, {
|
|
42
|
+
config: args.config,
|
|
43
|
+
status: 'initializing',
|
|
44
|
+
updatedAt: now,
|
|
45
|
+
})
|
|
46
|
+
return existing._id
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return await ctx.db.insert('adapter_registry', {
|
|
50
|
+
domain: args.domain,
|
|
51
|
+
provider: args.provider,
|
|
52
|
+
status: 'initializing',
|
|
53
|
+
config: args.config,
|
|
54
|
+
connectedAt: undefined,
|
|
55
|
+
lastHealthCheck: undefined,
|
|
56
|
+
errorMessage: undefined,
|
|
57
|
+
createdBy: args.createdBy,
|
|
58
|
+
createdAt: now,
|
|
59
|
+
updatedAt: now,
|
|
60
|
+
})
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Update an adapter's status.
|
|
66
|
+
*/
|
|
67
|
+
export const updateAdapterStatus = mutation({
|
|
68
|
+
args: {
|
|
69
|
+
adapterId: v.id('adapter_registry'),
|
|
70
|
+
status: statusValidator,
|
|
71
|
+
latency: v.optional(v.number()),
|
|
72
|
+
errorMessage: v.optional(v.string()),
|
|
73
|
+
},
|
|
74
|
+
handler: async (ctx, args) => {
|
|
75
|
+
const now = Date.now()
|
|
76
|
+
const update: Record<string, unknown> = {
|
|
77
|
+
status: args.status,
|
|
78
|
+
lastHealthCheck: now,
|
|
79
|
+
updatedAt: now,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (args.status === 'connected') {
|
|
83
|
+
update.connectedAt = now
|
|
84
|
+
update.errorMessage = undefined
|
|
85
|
+
}
|
|
86
|
+
if (args.status === 'error' && args.errorMessage) {
|
|
87
|
+
update.errorMessage = args.errorMessage
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
await ctx.db.patch(args.adapterId, update)
|
|
91
|
+
|
|
92
|
+
// Log health check
|
|
93
|
+
await ctx.db.insert('adapter_health_logs', {
|
|
94
|
+
adapterId: args.adapterId,
|
|
95
|
+
domain: (await ctx.db.get(args.adapterId))!.domain,
|
|
96
|
+
status: args.status,
|
|
97
|
+
latency: args.latency,
|
|
98
|
+
errorMessage: args.errorMessage,
|
|
99
|
+
timestamp: now,
|
|
100
|
+
})
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Update adapter configuration.
|
|
106
|
+
*/
|
|
107
|
+
export const updateAdapter = mutation({
|
|
108
|
+
args: {
|
|
109
|
+
adapterId: v.id('adapter_registry'),
|
|
110
|
+
config: v.object({
|
|
111
|
+
provider: v.string(),
|
|
112
|
+
apiKey: v.optional(v.string()),
|
|
113
|
+
baseUrl: v.optional(v.string()),
|
|
114
|
+
options: v.optional(v.any()),
|
|
115
|
+
}),
|
|
116
|
+
},
|
|
117
|
+
handler: async (ctx, args) => {
|
|
118
|
+
await ctx.db.patch(args.adapterId, {
|
|
119
|
+
config: args.config,
|
|
120
|
+
status: 'initializing',
|
|
121
|
+
updatedAt: Date.now(),
|
|
122
|
+
})
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Remove an adapter from the registry.
|
|
128
|
+
*/
|
|
129
|
+
export const removeAdapter = mutation({
|
|
130
|
+
args: { adapterId: v.id('adapter_registry') },
|
|
131
|
+
handler: async (ctx, args) => {
|
|
132
|
+
// Remove health logs
|
|
133
|
+
const logs = await ctx.db
|
|
134
|
+
.query('adapter_health_logs')
|
|
135
|
+
.withIndex('by_adapter', (q) => q.eq('adapterId', args.adapterId))
|
|
136
|
+
.collect()
|
|
137
|
+
for (const log of logs) {
|
|
138
|
+
await ctx.db.delete(log._id)
|
|
139
|
+
}
|
|
140
|
+
await ctx.db.delete(args.adapterId)
|
|
141
|
+
},
|
|
142
|
+
})
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// @geenius-adapters/convex — Queries
|
|
2
|
+
|
|
3
|
+
import { query } from './_generated/server'
|
|
4
|
+
import { v } from 'convex/values'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* List all registered adapters.
|
|
8
|
+
*/
|
|
9
|
+
export const listAdapters = query({
|
|
10
|
+
args: {},
|
|
11
|
+
handler: async (ctx) => {
|
|
12
|
+
return await ctx.db.query('adapter_registry').collect()
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* List adapters by domain.
|
|
18
|
+
*/
|
|
19
|
+
export const listAdaptersByDomain = query({
|
|
20
|
+
args: {
|
|
21
|
+
domain: v.union(
|
|
22
|
+
v.literal('db'), v.literal('auth'), v.literal('payments'),
|
|
23
|
+
v.literal('ai'), v.literal('storage'), v.literal('admin'),
|
|
24
|
+
),
|
|
25
|
+
},
|
|
26
|
+
handler: async (ctx, args) => {
|
|
27
|
+
return await ctx.db
|
|
28
|
+
.query('adapter_registry')
|
|
29
|
+
.withIndex('by_domain', (q) => q.eq('domain', args.domain))
|
|
30
|
+
.collect()
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get a single adapter by ID.
|
|
36
|
+
*/
|
|
37
|
+
export const getAdapter = query({
|
|
38
|
+
args: { adapterId: v.id('adapter_registry') },
|
|
39
|
+
handler: async (ctx, args) => {
|
|
40
|
+
return await ctx.db.get(args.adapterId)
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get the active adapter for a specific domain.
|
|
46
|
+
* Returns the first connected adapter, or the first adapter if none connected.
|
|
47
|
+
*/
|
|
48
|
+
export const getActiveAdapter = query({
|
|
49
|
+
args: {
|
|
50
|
+
domain: v.union(
|
|
51
|
+
v.literal('db'), v.literal('auth'), v.literal('payments'),
|
|
52
|
+
v.literal('ai'), v.literal('storage'), v.literal('admin'),
|
|
53
|
+
),
|
|
54
|
+
},
|
|
55
|
+
handler: async (ctx, args) => {
|
|
56
|
+
const adapters = await ctx.db
|
|
57
|
+
.query('adapter_registry')
|
|
58
|
+
.withIndex('by_domain', (q) => q.eq('domain', args.domain))
|
|
59
|
+
.collect()
|
|
60
|
+
|
|
61
|
+
return adapters.find((a) => a.status === 'connected') ?? adapters[0] ?? null
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get adapter status summary — one entry per domain.
|
|
67
|
+
*/
|
|
68
|
+
export const getAdapterStatusSummary = query({
|
|
69
|
+
args: {},
|
|
70
|
+
handler: async (ctx) => {
|
|
71
|
+
const all = await ctx.db.query('adapter_registry').collect()
|
|
72
|
+
const domains = ['db', 'auth', 'payments', 'ai', 'storage', 'admin'] as const
|
|
73
|
+
|
|
74
|
+
return domains.map((domain) => {
|
|
75
|
+
const domainAdapters = all.filter((a) => a.domain === domain)
|
|
76
|
+
const connected = domainAdapters.find((a) => a.status === 'connected')
|
|
77
|
+
return {
|
|
78
|
+
domain,
|
|
79
|
+
provider: connected?.provider ?? domainAdapters[0]?.provider ?? null,
|
|
80
|
+
status: connected?.status ?? domainAdapters[0]?.status ?? 'disconnected',
|
|
81
|
+
count: domainAdapters.length,
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get health logs for a specific adapter.
|
|
89
|
+
*/
|
|
90
|
+
export const getAdapterHealthLogs = query({
|
|
91
|
+
args: {
|
|
92
|
+
adapterId: v.id('adapter_registry'),
|
|
93
|
+
limit: v.optional(v.number()),
|
|
94
|
+
},
|
|
95
|
+
handler: async (ctx, args) => {
|
|
96
|
+
const q = ctx.db
|
|
97
|
+
.query('adapter_health_logs')
|
|
98
|
+
.withIndex('by_adapter', (q) => q.eq('adapterId', args.adapterId))
|
|
99
|
+
.order('desc')
|
|
100
|
+
|
|
101
|
+
if (args.limit) {
|
|
102
|
+
return await q.take(args.limit)
|
|
103
|
+
}
|
|
104
|
+
return await q.take(50)
|
|
105
|
+
},
|
|
106
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// @geenius-adapters/convex — Schema
|
|
2
|
+
|
|
3
|
+
import { defineSchema, defineTable } from 'convex/server'
|
|
4
|
+
import { v } from 'convex/values'
|
|
5
|
+
|
|
6
|
+
export default defineSchema({
|
|
7
|
+
// ─── Adapter Registry ──────────────────────────────────────
|
|
8
|
+
adapter_registry: defineTable({
|
|
9
|
+
domain: v.union(
|
|
10
|
+
v.literal('db'), v.literal('auth'), v.literal('payments'),
|
|
11
|
+
v.literal('ai'), v.literal('storage'), v.literal('admin'),
|
|
12
|
+
),
|
|
13
|
+
provider: v.string(),
|
|
14
|
+
status: v.union(
|
|
15
|
+
v.literal('connected'), v.literal('disconnected'),
|
|
16
|
+
v.literal('error'), v.literal('initializing'),
|
|
17
|
+
),
|
|
18
|
+
config: v.object({
|
|
19
|
+
provider: v.string(),
|
|
20
|
+
apiKey: v.optional(v.string()),
|
|
21
|
+
baseUrl: v.optional(v.string()),
|
|
22
|
+
options: v.optional(v.any()),
|
|
23
|
+
}),
|
|
24
|
+
connectedAt: v.optional(v.number()),
|
|
25
|
+
lastHealthCheck: v.optional(v.number()),
|
|
26
|
+
errorMessage: v.optional(v.string()),
|
|
27
|
+
// Metadata
|
|
28
|
+
createdBy: v.optional(v.string()),
|
|
29
|
+
createdAt: v.number(),
|
|
30
|
+
updatedAt: v.number(),
|
|
31
|
+
})
|
|
32
|
+
.index('by_domain', ['domain'])
|
|
33
|
+
.index('by_status', ['status'])
|
|
34
|
+
.index('by_domain_provider', ['domain', 'provider']),
|
|
35
|
+
|
|
36
|
+
// ─── Adapter Health Logs ───────────────────────────────────
|
|
37
|
+
adapter_health_logs: defineTable({
|
|
38
|
+
adapterId: v.id('adapter_registry'),
|
|
39
|
+
domain: v.union(
|
|
40
|
+
v.literal('db'), v.literal('auth'), v.literal('payments'),
|
|
41
|
+
v.literal('ai'), v.literal('storage'), v.literal('admin'),
|
|
42
|
+
),
|
|
43
|
+
status: v.union(
|
|
44
|
+
v.literal('connected'), v.literal('disconnected'),
|
|
45
|
+
v.literal('error'), v.literal('initializing'),
|
|
46
|
+
),
|
|
47
|
+
latency: v.optional(v.number()),
|
|
48
|
+
errorMessage: v.optional(v.string()),
|
|
49
|
+
timestamp: v.number(),
|
|
50
|
+
})
|
|
51
|
+
.index('by_adapter', ['adapterId'])
|
|
52
|
+
.index('by_domain', ['domain'])
|
|
53
|
+
.index('by_timestamp', ['timestamp']),
|
|
54
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for Convex adapter
|
|
3
|
+
*/
|
|
4
|
+
export interface ConvexAdapterConfig {
|
|
5
|
+
deploymentUrl?: string
|
|
6
|
+
apiKey?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Interface for Convex database adapter
|
|
11
|
+
*/
|
|
12
|
+
export interface ConvexDbAdapter {
|
|
13
|
+
query: (queryFn: string, args?: Record<string, unknown>) => Promise<unknown>
|
|
14
|
+
mutation: (
|
|
15
|
+
mutationFn: string,
|
|
16
|
+
args?: Record<string, unknown>,
|
|
17
|
+
) => Promise<unknown>
|
|
18
|
+
isReady: () => boolean
|
|
19
|
+
disconnect: () => Promise<void>
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# ✦ @geenius-adapters/react\n\n> Geenius Adapters — React hooks and providers\n\n---\n\n## Overview\nBuilt with Steve Jobs-level minimalism and Jony Ive-level craftsmanship, this package is designed to deliver unparalleled developer experience (DX) and rock-solid performance.\n\n## Installation\n\n```bash\npnpm add @geenius-adapters/react\n```\n\n## Usage\n\n```typescript\nimport { init } from '@geenius-adapters/react';\n\n// Initialize the module with absolute precision\ninit({\n mode: 'premium',\n});\n```\n\n## Architecture\n- **Zero-config**: It just works.\n- **Strictly Typed**: Fully written in TypeScript for flawless IntelliSense.\n- **Framework Agnostic**: seamlessly integrates into the Geenius ecosystem.\n\n---\n\n*Designed by Antigravity HQ*\n
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@geenius-adapters/react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Geenius Adapters — React hooks and providers",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup",
|
|
17
|
+
"lint": "tsc --noEmit",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"clean": "rm -rf dist"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"react": ">=18.0.0"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@geenius-adapters/shared": "workspace:*"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/react": "^19.0.0",
|
|
35
|
+
"react": "^19.2.4",
|
|
36
|
+
"tsup": "^8.5.1",
|
|
37
|
+
"typescript": "~6.0.2",
|
|
38
|
+
"vitest": "^4.1.0"
|
|
39
|
+
},
|
|
40
|
+
"author": "Antigravity HQ",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=20.0.0"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// @geenius-adapters/react — AdapterCard
|
|
2
|
+
|
|
3
|
+
import type { AdapterDomain, AdapterStatusInfo } from '@geenius-adapters/shared'
|
|
4
|
+
import { DOMAIN_LABELS, DOMAIN_ICONS, DOMAIN_DESCRIPTIONS } from '@geenius-adapters/shared'
|
|
5
|
+
import { AdapterStatusBadge } from './AdapterStatusBadge'
|
|
6
|
+
|
|
7
|
+
export interface AdapterCardProps {
|
|
8
|
+
domain: AdapterDomain
|
|
9
|
+
status: AdapterStatusInfo
|
|
10
|
+
onClick?: () => void
|
|
11
|
+
className?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function AdapterCard({ domain, status, onClick, className = '' }: AdapterCardProps) {
|
|
15
|
+
const label = DOMAIN_LABELS[domain]
|
|
16
|
+
const icon = DOMAIN_ICONS[domain]
|
|
17
|
+
const desc = DOMAIN_DESCRIPTIONS[domain]
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<button
|
|
21
|
+
type="button"
|
|
22
|
+
onClick={onClick}
|
|
23
|
+
className={`group flex w-full items-start gap-4 rounded-2xl border border-white/10 bg-white/[0.03] p-5 text-left backdrop-blur-sm transition-all hover:border-white/15 hover:bg-white/[0.05] ${className}`}
|
|
24
|
+
aria-label={`${label} adapter — ${status.status}`}
|
|
25
|
+
>
|
|
26
|
+
<div className="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-xl bg-white/5 text-2xl transition-transform group-hover:scale-105">
|
|
27
|
+
{icon}
|
|
28
|
+
</div>
|
|
29
|
+
<div className="min-w-0 flex-1">
|
|
30
|
+
<div className="flex items-center justify-between gap-2">
|
|
31
|
+
<h3 className="truncate text-sm font-bold text-white/90">{label}</h3>
|
|
32
|
+
<AdapterStatusBadge status={status.status} />
|
|
33
|
+
</div>
|
|
34
|
+
<p className="mt-0.5 text-xs text-white/40 line-clamp-2">{desc}</p>
|
|
35
|
+
{status.provider && status.provider !== 'none' && (
|
|
36
|
+
<div className="mt-2 flex items-center gap-2">
|
|
37
|
+
<span className="rounded-md bg-white/5 px-2 py-0.5 text-[0.625rem] font-semibold text-white/50">{status.provider}</span>
|
|
38
|
+
{status.latency && (
|
|
39
|
+
<span className="text-[0.625rem] text-white/30">{status.latency}ms</span>
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
42
|
+
)}
|
|
43
|
+
{status.error && (
|
|
44
|
+
<p className="mt-1.5 truncate text-[0.625rem] text-red-400/80">{status.error}</p>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
</button>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// @geenius-adapters/react — AdapterConfigForm
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import type { AdapterDomain, DomainAdapterConfig } from '@geenius-adapters/shared'
|
|
5
|
+
import { DOMAIN_LABELS, getProvidersForDomain } from '@geenius-adapters/shared'
|
|
6
|
+
|
|
7
|
+
export interface AdapterConfigFormProps {
|
|
8
|
+
domain: AdapterDomain
|
|
9
|
+
initialConfig?: DomainAdapterConfig
|
|
10
|
+
onSave: (config: DomainAdapterConfig) => Promise<void> | void
|
|
11
|
+
onCancel?: () => void
|
|
12
|
+
className?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function AdapterConfigForm({ domain, initialConfig, onSave, onCancel, className = '' }: AdapterConfigFormProps) {
|
|
16
|
+
const providers = getProvidersForDomain(domain)
|
|
17
|
+
const [provider, setProvider] = useState(initialConfig?.provider ?? providers[0]?.id ?? '')
|
|
18
|
+
const [apiKey, setApiKey] = useState(initialConfig?.apiKey ?? '')
|
|
19
|
+
const [baseUrl, setBaseUrl] = useState(initialConfig?.baseUrl ?? '')
|
|
20
|
+
const [isSaving, setIsSaving] = useState(false)
|
|
21
|
+
const [error, setError] = useState<string | null>(null)
|
|
22
|
+
|
|
23
|
+
const selectedMeta = providers.find((p) => p.id === provider)
|
|
24
|
+
const needsApiKey = !['localStorage', 'noop'].includes(provider)
|
|
25
|
+
const needsBaseUrl = ['ollama', 'minio', 'neon', 'supabase'].includes(provider)
|
|
26
|
+
|
|
27
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
28
|
+
e.preventDefault()
|
|
29
|
+
setError(null)
|
|
30
|
+
if (!provider) { setError('Select a provider'); return }
|
|
31
|
+
if (needsApiKey && !apiKey.trim()) { setError('API key is required'); return }
|
|
32
|
+
setIsSaving(true)
|
|
33
|
+
try {
|
|
34
|
+
await onSave({ provider, apiKey: apiKey || undefined, baseUrl: baseUrl || undefined })
|
|
35
|
+
} catch (e) {
|
|
36
|
+
setError(e instanceof Error ? e.message : 'Failed to save')
|
|
37
|
+
} finally {
|
|
38
|
+
setIsSaving(false)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<form onSubmit={handleSubmit} className={`space-y-5 rounded-2xl border border-white/10 bg-white/[0.03] p-6 backdrop-blur-sm ${className}`} noValidate>
|
|
44
|
+
<div>
|
|
45
|
+
<h3 className="text-lg font-bold text-white/90">Configure {DOMAIN_LABELS[domain]}</h3>
|
|
46
|
+
<p className="mt-1 text-xs text-white/40">Select and configure your preferred provider.</p>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
{error && <div className="rounded-lg border border-red-500/20 bg-red-500/10 px-4 py-3 text-sm text-red-400" role="alert">{error}</div>}
|
|
50
|
+
|
|
51
|
+
<div>
|
|
52
|
+
<label htmlFor={`adapter-${domain}-provider`} className="mb-1.5 block text-xs font-semibold uppercase tracking-wider text-white/40">Provider</label>
|
|
53
|
+
<div className="grid gap-2 sm:grid-cols-2">
|
|
54
|
+
{providers.map((p) => (
|
|
55
|
+
<button
|
|
56
|
+
key={p.id}
|
|
57
|
+
type="button"
|
|
58
|
+
onClick={() => setProvider(p.id)}
|
|
59
|
+
className={`flex flex-col rounded-xl border p-3 text-left transition-all ${
|
|
60
|
+
provider === p.id
|
|
61
|
+
? 'border-indigo-500/30 bg-indigo-500/10'
|
|
62
|
+
: 'border-white/10 bg-white/[0.02] hover:bg-white/[0.04]'
|
|
63
|
+
}`}
|
|
64
|
+
>
|
|
65
|
+
<span className="text-xs font-semibold text-white/80">{p.name}</span>
|
|
66
|
+
<span className="mt-0.5 text-[0.625rem] text-white/40">{p.description}</span>
|
|
67
|
+
<span className={`mt-1 self-start rounded px-1.5 py-0.5 text-[0.5625rem] font-bold uppercase ${
|
|
68
|
+
p.tier === 'pronto' ? 'bg-emerald-500/10 text-emerald-400' : p.tier === 'mvp' ? 'bg-blue-500/10 text-blue-400' : 'bg-purple-500/10 text-purple-400'
|
|
69
|
+
}`}>{p.tier}</span>
|
|
70
|
+
</button>
|
|
71
|
+
))}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
{needsApiKey && (
|
|
76
|
+
<div>
|
|
77
|
+
<label htmlFor={`adapter-${domain}-apiKey`} className="mb-1.5 block text-xs font-semibold uppercase tracking-wider text-white/40">API Key</label>
|
|
78
|
+
<input
|
|
79
|
+
id={`adapter-${domain}-apiKey`}
|
|
80
|
+
type="password"
|
|
81
|
+
value={apiKey}
|
|
82
|
+
onChange={(e) => setApiKey(e.target.value)}
|
|
83
|
+
placeholder={`Enter ${selectedMeta?.name ?? provider} API key`}
|
|
84
|
+
autoComplete="off"
|
|
85
|
+
className="w-full rounded-xl border border-white/10 bg-white/[0.02] px-4 py-3 text-sm text-white/90 placeholder:text-white/20 focus:border-indigo-500/50 focus:outline-none focus:ring-2 focus:ring-indigo-500/20"
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
|
|
90
|
+
{needsBaseUrl && (
|
|
91
|
+
<div>
|
|
92
|
+
<label htmlFor={`adapter-${domain}-url`} className="mb-1.5 block text-xs font-semibold uppercase tracking-wider text-white/40">Base URL</label>
|
|
93
|
+
<input
|
|
94
|
+
id={`adapter-${domain}-url`}
|
|
95
|
+
type="url"
|
|
96
|
+
value={baseUrl}
|
|
97
|
+
onChange={(e) => setBaseUrl(e.target.value)}
|
|
98
|
+
placeholder={`https://...`}
|
|
99
|
+
className="w-full rounded-xl border border-white/10 bg-white/[0.02] px-4 py-3 text-sm text-white/90 placeholder:text-white/20 focus:border-indigo-500/50 focus:outline-none focus:ring-2 focus:ring-indigo-500/20"
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
|
|
104
|
+
<div className="flex items-center justify-end gap-3 pt-2">
|
|
105
|
+
{onCancel && (
|
|
106
|
+
<button type="button" onClick={onCancel} className="rounded-xl px-5 py-2.5 text-sm font-semibold text-white/50 hover:text-white/70">Cancel</button>
|
|
107
|
+
)}
|
|
108
|
+
<button
|
|
109
|
+
type="submit"
|
|
110
|
+
disabled={isSaving || !provider}
|
|
111
|
+
className="flex items-center gap-2 rounded-xl bg-indigo-500 px-6 py-2.5 text-sm font-semibold text-white hover:bg-indigo-400 disabled:opacity-50"
|
|
112
|
+
>
|
|
113
|
+
{isSaving ? 'Saving…' : 'Save Configuration'}
|
|
114
|
+
</button>
|
|
115
|
+
</div>
|
|
116
|
+
</form>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// @geenius-adapters/react — AdapterList
|
|
2
|
+
|
|
3
|
+
import { useState, useMemo } from 'react'
|
|
4
|
+
import { useAdapters } from '../hooks'
|
|
5
|
+
import { AdapterCard } from './AdapterCard'
|
|
6
|
+
import { ADAPTER_DOMAINS, DOMAIN_LABELS } from '@geenius-adapters/shared'
|
|
7
|
+
import type { AdapterDomain, AdapterStatusType } from '@geenius-adapters/shared'
|
|
8
|
+
|
|
9
|
+
export interface AdapterListProps {
|
|
10
|
+
onSelect?: (domain: AdapterDomain) => void
|
|
11
|
+
filterStatus?: AdapterStatusType
|
|
12
|
+
className?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function AdapterList({ onSelect, filterStatus, className = '' }: AdapterListProps) {
|
|
16
|
+
const { statuses, isLoading } = useAdapters()
|
|
17
|
+
const [search, setSearch] = useState('')
|
|
18
|
+
|
|
19
|
+
const filteredDomains = useMemo(() => {
|
|
20
|
+
let domains = [...ADAPTER_DOMAINS]
|
|
21
|
+
if (search) {
|
|
22
|
+
const q = search.toLowerCase()
|
|
23
|
+
domains = domains.filter((d) => {
|
|
24
|
+
const label = DOMAIN_LABELS[d].toLowerCase()
|
|
25
|
+
const provider = statuses[d]?.provider?.toLowerCase() ?? ''
|
|
26
|
+
return label.includes(q) || provider.includes(q) || d.includes(q)
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
if (filterStatus) {
|
|
30
|
+
domains = domains.filter((d) => statuses[d]?.status === filterStatus)
|
|
31
|
+
}
|
|
32
|
+
return domains
|
|
33
|
+
}, [search, filterStatus, statuses])
|
|
34
|
+
|
|
35
|
+
if (isLoading) {
|
|
36
|
+
return (
|
|
37
|
+
<div className={`space-y-3 ${className}`} role="status" aria-label="Loading adapters">
|
|
38
|
+
{[1, 2, 3, 4].map((i) => (
|
|
39
|
+
<div key={i} className="animate-pulse rounded-2xl border border-white/5 bg-white/[0.02] p-5">
|
|
40
|
+
<div className="flex items-start gap-4">
|
|
41
|
+
<div className="h-12 w-12 rounded-xl bg-white/10" />
|
|
42
|
+
<div className="flex-1 space-y-2">
|
|
43
|
+
<div className="h-3 w-24 rounded bg-white/10" />
|
|
44
|
+
<div className="h-2 w-48 rounded bg-white/5" />
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
))}
|
|
49
|
+
</div>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className={className}>
|
|
55
|
+
<div className="mb-4">
|
|
56
|
+
<input
|
|
57
|
+
type="text"
|
|
58
|
+
value={search}
|
|
59
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
60
|
+
placeholder="Search adapters..."
|
|
61
|
+
aria-label="Search adapters"
|
|
62
|
+
className="w-full rounded-xl border border-white/10 bg-white/[0.02] px-4 py-2.5 text-sm text-white/90 placeholder:text-white/20 focus:border-indigo-500/50 focus:outline-none focus:ring-2 focus:ring-indigo-500/20"
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
{filteredDomains.length === 0 ? (
|
|
67
|
+
<div className="rounded-xl border border-white/5 bg-white/[0.01] p-8 text-center">
|
|
68
|
+
<p className="text-sm text-white/40">{search ? 'No adapters match your search' : 'No adapters configured'}</p>
|
|
69
|
+
</div>
|
|
70
|
+
) : (
|
|
71
|
+
<div className="grid gap-3 sm:grid-cols-2" role="list" aria-label="Adapter list">
|
|
72
|
+
{filteredDomains.map((domain) => (
|
|
73
|
+
<AdapterCard
|
|
74
|
+
key={domain}
|
|
75
|
+
domain={domain}
|
|
76
|
+
status={statuses[domain]}
|
|
77
|
+
onClick={() => onSelect?.(domain)}
|
|
78
|
+
/>
|
|
79
|
+
))}
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
)
|
|
84
|
+
}
|