@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,91 @@
|
|
|
1
|
+
// @geenius/adapters — localStorage DB implementation
|
|
2
|
+
|
|
3
|
+
import type { ListOptions, QueryFilter, QueryCondition } from '../types'
|
|
4
|
+
import type { DbAdapter } from './interface'
|
|
5
|
+
|
|
6
|
+
const DB_PREFIX = 'geenius_db_'
|
|
7
|
+
|
|
8
|
+
function getCollection<T>(collection: string): (T & { id: string })[] {
|
|
9
|
+
try { return JSON.parse(localStorage.getItem(DB_PREFIX + collection) || '[]') } catch { return [] }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function saveCollection<T>(collection: string, data: T[]) {
|
|
13
|
+
localStorage.setItem(DB_PREFIX + collection, JSON.stringify(data))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function matchesCondition(item: Record<string, unknown>, cond: QueryCondition): boolean {
|
|
17
|
+
const val = item[cond.field]
|
|
18
|
+
switch (cond.operator) {
|
|
19
|
+
case 'eq': return val === cond.value
|
|
20
|
+
case 'neq': return val !== cond.value
|
|
21
|
+
case 'gt': return (val as number) > (cond.value as number)
|
|
22
|
+
case 'gte': return (val as number) >= (cond.value as number)
|
|
23
|
+
case 'lt': return (val as number) < (cond.value as number)
|
|
24
|
+
case 'lte': return (val as number) <= (cond.value as number)
|
|
25
|
+
case 'in': return Array.isArray(cond.value) && cond.value.includes(val)
|
|
26
|
+
case 'contains': return typeof val === 'string' && val.includes(cond.value as string)
|
|
27
|
+
default: return false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function matchesFilter(item: Record<string, unknown>, filter: QueryFilter): boolean {
|
|
32
|
+
return filter.every(cond => matchesCondition(item, cond))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createLocalStorageDbAdapter(): DbAdapter {
|
|
36
|
+
return {
|
|
37
|
+
async create(collection, data) {
|
|
38
|
+
const items = getCollection(collection)
|
|
39
|
+
const item = { ...data, id: crypto.randomUUID() } as any
|
|
40
|
+
items.push(item)
|
|
41
|
+
saveCollection(collection, items)
|
|
42
|
+
return item
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
async get(collection, id) {
|
|
46
|
+
return getCollection(collection).find((item: any) => item.id === id) as any ?? null
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
async update(collection, id, data) {
|
|
50
|
+
const items = getCollection(collection)
|
|
51
|
+
const idx = items.findIndex((item: any) => item.id === id)
|
|
52
|
+
if (idx === -1) return null
|
|
53
|
+
Object.assign(items[idx], data)
|
|
54
|
+
saveCollection(collection, items)
|
|
55
|
+
return items[idx] as any
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
async delete(collection, id) {
|
|
59
|
+
const items = getCollection(collection)
|
|
60
|
+
const idx = items.findIndex((item: any) => item.id === id)
|
|
61
|
+
if (idx === -1) return false
|
|
62
|
+
items.splice(idx, 1)
|
|
63
|
+
saveCollection(collection, items)
|
|
64
|
+
return true
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
async list(collection, options?: ListOptions) {
|
|
68
|
+
let items = getCollection(collection)
|
|
69
|
+
if (options?.orderBy) {
|
|
70
|
+
const dir = options.order === 'desc' ? -1 : 1
|
|
71
|
+
items.sort((a: any, b: any) => {
|
|
72
|
+
const av = a[options.orderBy!], bv = b[options.orderBy!]
|
|
73
|
+
return av < bv ? -dir : av > bv ? dir : 0
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
if (options?.offset) items = items.slice(options.offset)
|
|
77
|
+
if (options?.limit) items = items.slice(0, options.limit)
|
|
78
|
+
return items as any
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
async query(collection, filter) {
|
|
82
|
+
return getCollection(collection).filter(item => matchesFilter(item as any, filter)) as any
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
async count(collection, filter?) {
|
|
86
|
+
const items = getCollection(collection)
|
|
87
|
+
if (!filter || filter.length === 0) return items.length
|
|
88
|
+
return items.filter(item => matchesFilter(item as any, filter)).length
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// @geenius/adapters — MongoDB implementation (MVP tier)
|
|
2
|
+
// Wraps the MongoDB Node.js driver to conform to DbAdapter interface.
|
|
3
|
+
// Requires: mongodb
|
|
4
|
+
|
|
5
|
+
import type { ListOptions, QueryFilter } from '../types'
|
|
6
|
+
import type { DbAdapter } from './interface'
|
|
7
|
+
|
|
8
|
+
interface MongoCollection {
|
|
9
|
+
insertOne: (doc: any) => Promise<{ insertedId: any }>
|
|
10
|
+
findOne: (filter: any) => Promise<any>
|
|
11
|
+
updateOne: (filter: any, update: any) => Promise<any>
|
|
12
|
+
deleteOne: (filter: any) => Promise<{ deletedCount: number }>
|
|
13
|
+
find: (filter?: any) => MongoCursor
|
|
14
|
+
countDocuments: (filter?: any) => Promise<number>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface MongoCursor {
|
|
18
|
+
sort: (sort: any) => MongoCursor
|
|
19
|
+
skip: (n: number) => MongoCursor
|
|
20
|
+
limit: (n: number) => MongoCursor
|
|
21
|
+
toArray: () => Promise<any[]>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface MongoDb {
|
|
25
|
+
collection: (name: string) => MongoCollection
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface MongoDbAdapterOptions {
|
|
29
|
+
/** Pre-configured MongoDB database instance */
|
|
30
|
+
db: MongoDb
|
|
31
|
+
/** Optional collection name mapping */
|
|
32
|
+
collectionNames?: Record<string, string>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates a MongoDB-backed DbAdapter.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { MongoClient } from 'mongodb'
|
|
41
|
+
*
|
|
42
|
+
* const client = new MongoClient(process.env.MONGODB_URI!)
|
|
43
|
+
* const db = client.db('myapp')
|
|
44
|
+
* const adapter = createMongoDbAdapter({ db })
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function createMongoDbAdapter(options: MongoDbAdapterOptions): DbAdapter {
|
|
48
|
+
const { db, collectionNames = {} } = options
|
|
49
|
+
|
|
50
|
+
function col(collection: string): MongoCollection {
|
|
51
|
+
return db.collection(collectionNames[collection] || collection)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function buildMongoFilter(filter: QueryFilter): Record<string, any> {
|
|
55
|
+
const mongoFilter: Record<string, any> = {}
|
|
56
|
+
|
|
57
|
+
for (const cond of filter) {
|
|
58
|
+
switch (cond.operator) {
|
|
59
|
+
case 'eq': mongoFilter[cond.field] = cond.value; break
|
|
60
|
+
case 'neq': mongoFilter[cond.field] = { $ne: cond.value }; break
|
|
61
|
+
case 'gt': mongoFilter[cond.field] = { $gt: cond.value }; break
|
|
62
|
+
case 'gte': mongoFilter[cond.field] = { $gte: cond.value }; break
|
|
63
|
+
case 'lt': mongoFilter[cond.field] = { $lt: cond.value }; break
|
|
64
|
+
case 'lte': mongoFilter[cond.field] = { $lte: cond.value }; break
|
|
65
|
+
case 'in': mongoFilter[cond.field] = { $in: cond.value }; break
|
|
66
|
+
case 'contains':
|
|
67
|
+
mongoFilter[cond.field] = { $regex: cond.value, $options: 'i' }
|
|
68
|
+
break
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return mongoFilter
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
async create(collection, data) {
|
|
77
|
+
const id = crypto.randomUUID()
|
|
78
|
+
const doc = { ...data, id }
|
|
79
|
+
await col(collection).insertOne(doc)
|
|
80
|
+
return doc as any
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
async get(collection, id) {
|
|
84
|
+
const doc = await col(collection).findOne({ id })
|
|
85
|
+
if (!doc) return null
|
|
86
|
+
const { _id, ...rest } = doc
|
|
87
|
+
return rest as any
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
async update(collection, id, data) {
|
|
91
|
+
const result = await col(collection).updateOne({ id }, { $set: data })
|
|
92
|
+
if (!result) return null
|
|
93
|
+
return this.get(collection, id) as any
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
async delete(collection, id) {
|
|
97
|
+
const result = await col(collection).deleteOne({ id })
|
|
98
|
+
return result.deletedCount > 0
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
async list(collection, options?: ListOptions) {
|
|
102
|
+
let cursor = col(collection).find()
|
|
103
|
+
|
|
104
|
+
if (options?.orderBy) {
|
|
105
|
+
cursor = cursor.sort({ [options.orderBy]: options.order === 'desc' ? -1 : 1 })
|
|
106
|
+
}
|
|
107
|
+
if (options?.offset) cursor = cursor.skip(options.offset)
|
|
108
|
+
if (options?.limit) cursor = cursor.limit(options.limit)
|
|
109
|
+
|
|
110
|
+
const docs = await cursor.toArray()
|
|
111
|
+
return docs.map(({ _id, ...rest }: any) => rest)
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
async query(collection, filter) {
|
|
115
|
+
const mongoFilter = buildMongoFilter(filter)
|
|
116
|
+
const docs = await col(collection).find(mongoFilter).toArray()
|
|
117
|
+
return docs.map(({ _id, ...rest }: any) => rest)
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
async count(collection, filter?) {
|
|
121
|
+
const mongoFilter = filter ? buildMongoFilter(filter) : {}
|
|
122
|
+
return col(collection).countDocuments(mongoFilter)
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// @geenius/adapters — Neon/Drizzle DB implementation (MVP tier)
|
|
2
|
+
// Wraps a Drizzle ORM instance over Neon Postgres to conform to DbAdapter.
|
|
3
|
+
// Requires: drizzle-orm, @neondatabase/serverless
|
|
4
|
+
//
|
|
5
|
+
// The adapter accepts pre-built Drizzle operator functions via the options
|
|
6
|
+
// to avoid importing drizzle-orm at build time (it's an optional peer dep).
|
|
7
|
+
|
|
8
|
+
import type { ListOptions, QueryFilter } from '../types'
|
|
9
|
+
import type { DbAdapter } from './interface'
|
|
10
|
+
|
|
11
|
+
interface DrizzleTable {
|
|
12
|
+
[key: string]: any
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface DrizzleClient {
|
|
16
|
+
select: (fields?: any) => any
|
|
17
|
+
insert: (table: any) => any
|
|
18
|
+
update: (table: any) => any
|
|
19
|
+
delete: (table: any) => any
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Drizzle operator functions — injected by the consumer */
|
|
23
|
+
export interface DrizzleOperators {
|
|
24
|
+
eq: (col: any, value: any) => any
|
|
25
|
+
ne: (col: any, value: any) => any
|
|
26
|
+
gt: (col: any, value: any) => any
|
|
27
|
+
gte: (col: any, value: any) => any
|
|
28
|
+
lt: (col: any, value: any) => any
|
|
29
|
+
lte: (col: any, value: any) => any
|
|
30
|
+
inArray: (col: any, values: any[]) => any
|
|
31
|
+
like: (col: any, pattern: string) => any
|
|
32
|
+
and: (...conditions: any[]) => any
|
|
33
|
+
asc: (col: any) => any
|
|
34
|
+
desc: (col: any) => any
|
|
35
|
+
sql: any
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface NeonDbAdapterOptions {
|
|
39
|
+
/** Pre-configured Drizzle client instance */
|
|
40
|
+
client: DrizzleClient
|
|
41
|
+
/** Map of collection names to Drizzle table schema objects */
|
|
42
|
+
tables: Record<string, DrizzleTable>
|
|
43
|
+
/** Drizzle operator functions (import from 'drizzle-orm') */
|
|
44
|
+
operators: DrizzleOperators
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates a Neon-backed DbAdapter using Drizzle ORM.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* import { drizzle } from 'drizzle-orm/neon-http'
|
|
53
|
+
* import { neon } from '@neondatabase/serverless'
|
|
54
|
+
* import { eq, ne, gt, gte, lt, lte, inArray, like, and, asc, desc, sql } from 'drizzle-orm'
|
|
55
|
+
* import * as schema from './schema'
|
|
56
|
+
*
|
|
57
|
+
* const db = drizzle(neon(process.env.DATABASE_URL!))
|
|
58
|
+
*
|
|
59
|
+
* const adapter = createNeonDbAdapter({
|
|
60
|
+
* client: db,
|
|
61
|
+
* tables: { tasks: schema.tasks },
|
|
62
|
+
* operators: { eq, ne, gt, gte, lt, lte, inArray, like, and, asc, desc, sql },
|
|
63
|
+
* })
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export function createNeonDbAdapter(options: NeonDbAdapterOptions): DbAdapter {
|
|
67
|
+
const { client, tables, operators: ops } = options
|
|
68
|
+
|
|
69
|
+
function getTable(collection: string) {
|
|
70
|
+
const table = tables[collection]
|
|
71
|
+
if (!table) throw new Error(`No table configured for collection: ${collection}`)
|
|
72
|
+
return table
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
async create(collection, data) {
|
|
77
|
+
const table = getTable(collection)
|
|
78
|
+
const [result] = await client
|
|
79
|
+
.insert(table)
|
|
80
|
+
.values(data)
|
|
81
|
+
.returning()
|
|
82
|
+
return result as any
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
async get(collection, id) {
|
|
86
|
+
const table = getTable(collection)
|
|
87
|
+
const [result] = await client
|
|
88
|
+
.select()
|
|
89
|
+
.from(table)
|
|
90
|
+
.where(ops.eq(table.id, id))
|
|
91
|
+
.limit(1)
|
|
92
|
+
return (result as any) ?? null
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
async update(collection, id, data) {
|
|
96
|
+
const table = getTable(collection)
|
|
97
|
+
const [result] = await client
|
|
98
|
+
.update(table)
|
|
99
|
+
.set(data)
|
|
100
|
+
.where(ops.eq(table.id, id))
|
|
101
|
+
.returning()
|
|
102
|
+
return (result as any) ?? null
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
async delete(collection, id) {
|
|
106
|
+
const table = getTable(collection)
|
|
107
|
+
try {
|
|
108
|
+
await client.delete(table).where(ops.eq(table.id, id))
|
|
109
|
+
return true
|
|
110
|
+
} catch {
|
|
111
|
+
return false
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
async list(collection, options?: ListOptions) {
|
|
116
|
+
const table = getTable(collection)
|
|
117
|
+
let query = client.select().from(table)
|
|
118
|
+
|
|
119
|
+
if (options?.orderBy) {
|
|
120
|
+
const col = table[options.orderBy]
|
|
121
|
+
if (col) {
|
|
122
|
+
query = query.orderBy(options.order === 'desc' ? ops.desc(col) : ops.asc(col))
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (options?.limit) query = query.limit(options.limit)
|
|
126
|
+
if (options?.offset) query = query.offset(options.offset)
|
|
127
|
+
|
|
128
|
+
return query as any
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
async query(collection, filter) {
|
|
132
|
+
const table = getTable(collection)
|
|
133
|
+
|
|
134
|
+
const conditions = filter.map((cond) => {
|
|
135
|
+
const col = table[cond.field]
|
|
136
|
+
if (!col) throw new Error(`Column ${cond.field} not found in ${collection}`)
|
|
137
|
+
|
|
138
|
+
switch (cond.operator) {
|
|
139
|
+
case 'eq': return ops.eq(col, cond.value)
|
|
140
|
+
case 'neq': return ops.ne(col, cond.value)
|
|
141
|
+
case 'gt': return ops.gt(col, cond.value)
|
|
142
|
+
case 'gte': return ops.gte(col, cond.value)
|
|
143
|
+
case 'lt': return ops.lt(col, cond.value)
|
|
144
|
+
case 'lte': return ops.lte(col, cond.value)
|
|
145
|
+
case 'in': return ops.inArray(col, cond.value as any[])
|
|
146
|
+
case 'contains': return ops.like(col, `%${cond.value}%`)
|
|
147
|
+
default: throw new Error(`Unsupported operator: ${cond.operator}`)
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
return client
|
|
152
|
+
.select()
|
|
153
|
+
.from(table)
|
|
154
|
+
.where(ops.and(...conditions)) as any
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
async count(collection, filter?) {
|
|
158
|
+
const table = getTable(collection)
|
|
159
|
+
|
|
160
|
+
if (!filter || filter.length === 0) {
|
|
161
|
+
const [result] = await client
|
|
162
|
+
.select({ count: ops.sql`count(*)` })
|
|
163
|
+
.from(table)
|
|
164
|
+
return Number(result?.count ?? 0)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const items = await this.query(collection, filter)
|
|
168
|
+
return items.length
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// @geenius/adapters — Supabase DB implementation (MVP tier)
|
|
2
|
+
// Wraps the Supabase client to conform to DbAdapter interface.
|
|
3
|
+
// Requires: @supabase/supabase-js
|
|
4
|
+
|
|
5
|
+
import type { ListOptions, QueryFilter } from '../types'
|
|
6
|
+
import type { DbAdapter } from './interface'
|
|
7
|
+
|
|
8
|
+
interface SupabaseClient {
|
|
9
|
+
from: (table: string) => SupabaseQueryBuilder
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface SupabaseQueryBuilder {
|
|
13
|
+
select: (columns?: string) => SupabaseQueryBuilder
|
|
14
|
+
insert: (data: any) => SupabaseQueryBuilder
|
|
15
|
+
update: (data: any) => SupabaseQueryBuilder
|
|
16
|
+
delete: () => SupabaseQueryBuilder
|
|
17
|
+
eq: (column: string, value: any) => SupabaseQueryBuilder
|
|
18
|
+
neq: (column: string, value: any) => SupabaseQueryBuilder
|
|
19
|
+
gt: (column: string, value: any) => SupabaseQueryBuilder
|
|
20
|
+
gte: (column: string, value: any) => SupabaseQueryBuilder
|
|
21
|
+
lt: (column: string, value: any) => SupabaseQueryBuilder
|
|
22
|
+
lte: (column: string, value: any) => SupabaseQueryBuilder
|
|
23
|
+
in: (column: string, values: any[]) => SupabaseQueryBuilder
|
|
24
|
+
ilike: (column: string, pattern: string) => SupabaseQueryBuilder
|
|
25
|
+
order: (column: string, options?: { ascending?: boolean }) => SupabaseQueryBuilder
|
|
26
|
+
range: (from: number, to: number) => SupabaseQueryBuilder
|
|
27
|
+
limit: (count: number) => SupabaseQueryBuilder
|
|
28
|
+
single: () => SupabaseQueryBuilder
|
|
29
|
+
then: (resolve: (value: any) => void) => Promise<any>
|
|
30
|
+
// Result accessors
|
|
31
|
+
data?: any
|
|
32
|
+
error?: any
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface SupabaseDbAdapterOptions {
|
|
36
|
+
/** Pre-configured Supabase client instance */
|
|
37
|
+
client: SupabaseClient
|
|
38
|
+
/** Optional table name mapping: { collectionName: 'actual_table_name' } */
|
|
39
|
+
tableNames?: Record<string, string>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Creates a Supabase-backed DbAdapter.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* import { createClient } from '@supabase/supabase-js'
|
|
48
|
+
*
|
|
49
|
+
* const supabase = createClient(url, key)
|
|
50
|
+
* const db = createSupabaseDbAdapter({ client: supabase })
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function createSupabaseDbAdapter(options: SupabaseDbAdapterOptions): DbAdapter {
|
|
54
|
+
const { client, tableNames = {} } = options
|
|
55
|
+
|
|
56
|
+
function tableName(collection: string): string {
|
|
57
|
+
return tableNames[collection] || collection
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
async create(collection, data) {
|
|
62
|
+
const { data: result, error } = await client
|
|
63
|
+
.from(tableName(collection))
|
|
64
|
+
.insert(data)
|
|
65
|
+
.select()
|
|
66
|
+
.single() as any
|
|
67
|
+
|
|
68
|
+
if (error) throw new Error(`Supabase insert error: ${error.message}`)
|
|
69
|
+
return result
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
async get(collection, id) {
|
|
73
|
+
const { data, error } = await client
|
|
74
|
+
.from(tableName(collection))
|
|
75
|
+
.select()
|
|
76
|
+
.eq('id', id)
|
|
77
|
+
.single() as any
|
|
78
|
+
|
|
79
|
+
if (error) return null
|
|
80
|
+
return data
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
async update(collection, id, data) {
|
|
84
|
+
const { data: result, error } = await client
|
|
85
|
+
.from(tableName(collection))
|
|
86
|
+
.update(data)
|
|
87
|
+
.eq('id', id)
|
|
88
|
+
.select()
|
|
89
|
+
.single() as any
|
|
90
|
+
|
|
91
|
+
if (error) return null
|
|
92
|
+
return result
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
async delete(collection, id) {
|
|
96
|
+
const { error } = await client
|
|
97
|
+
.from(tableName(collection))
|
|
98
|
+
.delete()
|
|
99
|
+
.eq('id', id) as any
|
|
100
|
+
|
|
101
|
+
return !error
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
async list(collection, options?: ListOptions) {
|
|
105
|
+
let query = client.from(tableName(collection)).select()
|
|
106
|
+
|
|
107
|
+
if (options?.orderBy) {
|
|
108
|
+
query = query.order(options.orderBy, {
|
|
109
|
+
ascending: options.order !== 'desc',
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (options?.offset !== undefined && options?.limit !== undefined) {
|
|
114
|
+
query = query.range(options.offset, options.offset + options.limit - 1)
|
|
115
|
+
} else if (options?.limit) {
|
|
116
|
+
query = query.limit(options.limit)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const { data, error } = await query as any
|
|
120
|
+
if (error) throw new Error(`Supabase list error: ${error.message}`)
|
|
121
|
+
return data || []
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
async query(collection, filter) {
|
|
125
|
+
let query = client.from(tableName(collection)).select()
|
|
126
|
+
|
|
127
|
+
for (const cond of filter) {
|
|
128
|
+
switch (cond.operator) {
|
|
129
|
+
case 'eq': query = query.eq(cond.field, cond.value); break
|
|
130
|
+
case 'neq': query = query.neq(cond.field, cond.value); break
|
|
131
|
+
case 'gt': query = query.gt(cond.field, cond.value); break
|
|
132
|
+
case 'gte': query = query.gte(cond.field, cond.value); break
|
|
133
|
+
case 'lt': query = query.lt(cond.field, cond.value); break
|
|
134
|
+
case 'lte': query = query.lte(cond.field, cond.value); break
|
|
135
|
+
case 'in': query = query.in(cond.field, cond.value as any[]); break
|
|
136
|
+
case 'contains': query = query.ilike(cond.field, `%${cond.value}%`); break
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const { data, error } = await query as any
|
|
141
|
+
if (error) throw new Error(`Supabase query error: ${error.message}`)
|
|
142
|
+
return data || []
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
async count(collection, filter?) {
|
|
146
|
+
if (!filter || filter.length === 0) {
|
|
147
|
+
// Fetch all IDs and count — simple approach that avoids Supabase-specific count API
|
|
148
|
+
const { data } = await client
|
|
149
|
+
.from(tableName(collection))
|
|
150
|
+
.select('id') as any
|
|
151
|
+
return data?.length ?? 0
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const items = await this.query(collection, filter)
|
|
155
|
+
return items.length
|
|
156
|
+
},
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// @geenius-adapters/shared — root barrel export
|
|
2
|
+
|
|
3
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export type {
|
|
6
|
+
AuthUser, AuthSession,
|
|
7
|
+
ListOptions, QueryOperator, QueryCondition, QueryFilter,
|
|
8
|
+
Plan, Subscription, CheckoutParams, CheckoutResult,
|
|
9
|
+
ChatRole, ChatMessage, AiOptions, ChatResponse,
|
|
10
|
+
StoredFile,
|
|
11
|
+
AdapterDomain, DomainAdapterConfig, AdapterConfig,
|
|
12
|
+
AdapterStatusType, AdapterStatusInfo, AdapterRegistryEntry,
|
|
13
|
+
} from './types'
|
|
14
|
+
|
|
15
|
+
// ─── Configuration ───────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
configureAdapters, getAdapterConfig, isAdaptersConfigured,
|
|
19
|
+
resetAdapterConfig, getDomainConfig,
|
|
20
|
+
} from './config'
|
|
21
|
+
|
|
22
|
+
// ─── Constants & Registry ────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
ADAPTER_DOMAINS, ADAPTER_STATUSES,
|
|
26
|
+
DB_PROVIDERS, AUTH_PROVIDERS, AI_PROVIDERS, STORAGE_PROVIDERS, PAYMENT_PROVIDERS, ADMIN_PROVIDERS,
|
|
27
|
+
DOMAIN_LABELS, DOMAIN_ICONS, DOMAIN_DESCRIPTIONS,
|
|
28
|
+
PROVIDER_REGISTRY, getProvidersForDomain, getProviderMeta,
|
|
29
|
+
} from './constants'
|
|
30
|
+
export type {
|
|
31
|
+
DbProviderName, AuthProviderName, AiProviderName, StorageProviderName,
|
|
32
|
+
PaymentProviderName, AdminProviderName, AdapterStatus, ProviderMeta,
|
|
33
|
+
} from './constants'
|
|
34
|
+
|
|
35
|
+
// ─── Adapter Interfaces ──────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
export type { AuthAdapter, OAuthProvider } from './auth'
|
|
38
|
+
export type { DbAdapter } from './db'
|
|
39
|
+
export type { PaymentsAdapter } from './payments'
|
|
40
|
+
export type { AiAdapter } from './ai'
|
|
41
|
+
export type { FileStorageAdapter } from './storage'
|
|
42
|
+
export type { AdminAdapter, AdminMetrics, ManagedUser } from './admin'
|
|
43
|
+
|
|
44
|
+
// ─── localStorage implementations (Pronto tier) ─────────────────────────────
|
|
45
|
+
|
|
46
|
+
export { createLocalStorageAuthAdapter } from './auth'
|
|
47
|
+
export { createLocalStorageDbAdapter } from './db'
|
|
48
|
+
export { createLocalStoragePaymentsAdapter, createNoopPaymentsAdapter } from './payments'
|
|
49
|
+
export { createLocalStorageAiAdapter } from './ai'
|
|
50
|
+
export { createLocalStorageFileAdapter } from './storage'
|
|
51
|
+
export { createLocalStorageAdminAdapter } from './admin'
|
|
52
|
+
|
|
53
|
+
// ─── AI Provider implementations ─────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
export { createOpenAiAdapter } from './ai'
|
|
56
|
+
export type { OpenAiAdapterOptions } from './ai'
|
|
57
|
+
export { createAnthropicAdapter } from './ai'
|
|
58
|
+
export type { AnthropicAdapterOptions } from './ai'
|
|
59
|
+
export { createGeminiAdapter } from './ai'
|
|
60
|
+
export type { GeminiAdapterOptions } from './ai'
|
|
61
|
+
export { createOllamaAdapter } from './ai'
|
|
62
|
+
export type { OllamaAdapterOptions } from './ai'
|
|
63
|
+
export { createCloudflareAiGatewayAdapter } from './ai'
|
|
64
|
+
export type { CloudflareAiGatewayOptions } from './ai'
|
|
65
|
+
export { createVercelAiAdapter } from './ai'
|
|
66
|
+
export type { VercelAiAdapterOptions } from './ai'
|
|
67
|
+
|
|
68
|
+
// ─── Storage Provider implementations ────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
export { createR2FileAdapter } from './storage'
|
|
71
|
+
export type { R2FileAdapterOptions } from './storage'
|
|
72
|
+
export { createS3FileAdapter } from './storage'
|
|
73
|
+
export type { S3FileAdapterOptions } from './storage'
|
|
74
|
+
export { createUploadthingFileAdapter } from './storage'
|
|
75
|
+
export type { UploadthingFileAdapterOptions } from './storage'
|
|
76
|
+
export { createSupabaseFileAdapter } from './storage'
|
|
77
|
+
export type { SupabaseFileAdapterOptions } from './storage'
|
|
78
|
+
export { createConvexFileAdapter } from './storage'
|
|
79
|
+
export type { ConvexFileAdapterOptions } from './storage'
|
|
80
|
+
export { createMinioFileAdapter } from './storage'
|
|
81
|
+
export type { MinioFileAdapterOptions } from './storage'
|
|
82
|
+
|
|
83
|
+
// ─── Auth Provider implementations ──────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
export { createBetterAuthAdapter } from './auth'
|
|
86
|
+
export type { BetterAuthAdapterOptions } from './auth'
|
|
87
|
+
export { createConvexAuthAdapter } from './auth'
|
|
88
|
+
export type { ConvexAuthAdapterOptions } from './auth'
|
|
89
|
+
export { createClerkAuthAdapter } from './auth'
|
|
90
|
+
export type { ClerkAuthAdapterOptions } from './auth'
|
|
91
|
+
export { createSupabaseAuthAdapter } from './auth'
|
|
92
|
+
export type { SupabaseAuthAdapterOptions } from './auth'
|
|
93
|
+
|
|
94
|
+
// ─── DB Provider implementations ────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
export { createConvexDbAdapter } from './db'
|
|
97
|
+
export type { ConvexDbAdapterOptions } from './db'
|
|
98
|
+
export { createNeonDbAdapter } from './db'
|
|
99
|
+
export type { NeonDbAdapterOptions } from './db'
|
|
100
|
+
export { createSupabaseDbAdapter } from './db'
|
|
101
|
+
export type { SupabaseDbAdapterOptions } from './db'
|
|
102
|
+
export { createMongoDbAdapter } from './db'
|
|
103
|
+
export type { MongoDbAdapterOptions } from './db'
|
|
104
|
+
|
|
105
|
+
// ─── Payments Provider implementations ──────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
export { createStripePaymentsAdapter } from './payments'
|
|
108
|
+
export type { StripePaymentsAdapterOptions } from './payments'
|
|
109
|
+
|
|
110
|
+
// ─── Tier Gating ─────────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
export type { ProjectTier, TierGateConfig } from './tier-gate'
|
|
113
|
+
export {
|
|
114
|
+
isFeatureAvailable, isPackageAvailable,
|
|
115
|
+
getAvailableFeatures, getAvailablePackages,
|
|
116
|
+
getUpgradeFeatures, DEFAULT_TIER_GATE,
|
|
117
|
+
} from './tier-gate'
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// @geenius/adapters — Payments adapter interface
|
|
2
|
+
|
|
3
|
+
import type { Plan, Subscription, CheckoutParams, CheckoutResult } from '../types'
|
|
4
|
+
|
|
5
|
+
export interface PaymentsAdapter {
|
|
6
|
+
createCheckout(params: CheckoutParams): Promise<CheckoutResult>
|
|
7
|
+
getSubscription(userId: string): Promise<Subscription | null>
|
|
8
|
+
cancelSubscription(subscriptionId: string): Promise<void>
|
|
9
|
+
getPlans(): Promise<Plan[]>
|
|
10
|
+
isFeatureEnabled(userId: string, feature: string): Promise<boolean>
|
|
11
|
+
}
|