@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,128 @@
1
+ // @geenius/adapters — AWS S3 file storage adapter
2
+ // Requires: @aws-sdk/client-s3, @aws-sdk/s3-request-presigner
3
+
4
+ import type { StoredFile } from '../types'
5
+ import type { FileStorageAdapter } from './interface'
6
+
7
+ export interface S3FileAdapterOptions {
8
+ /** AWS region */
9
+ region: string
10
+ /** S3 bucket name */
11
+ bucket: string
12
+ /** AWS access key ID (optional if using IAM roles) */
13
+ accessKeyId?: string
14
+ /** AWS secret access key (optional if using IAM roles) */
15
+ secretAccessKey?: string
16
+ /** Custom endpoint (for S3-compatible services) */
17
+ endpoint?: string
18
+ /** Custom public URL prefix (e.g. CloudFront distribution) */
19
+ publicUrl?: string
20
+ /** Presigned URL expiry in seconds (default: 3600) */
21
+ presignedExpiry?: number
22
+ }
23
+
24
+ export function createS3FileAdapter(options: S3FileAdapterOptions): FileStorageAdapter {
25
+ const { region, bucket, accessKeyId, secretAccessKey, endpoint, publicUrl, presignedExpiry = 3600 } = options
26
+
27
+ let s3Client: any = null
28
+ let getSignedUrl: any = null
29
+ let commands: any = null
30
+
31
+ async function getClient() {
32
+ if (!s3Client) {
33
+ const { S3Client } = await import('@aws-sdk/client-s3')
34
+ const presigner = await import('@aws-sdk/s3-request-presigner')
35
+ const s3Commands = await import('@aws-sdk/client-s3')
36
+
37
+ const clientConfig: any = { region, endpoint }
38
+ if (accessKeyId && secretAccessKey) {
39
+ clientConfig.credentials = { accessKeyId, secretAccessKey }
40
+ }
41
+
42
+ s3Client = new S3Client(clientConfig)
43
+ getSignedUrl = presigner.getSignedUrl
44
+ commands = s3Commands
45
+ }
46
+ return { client: s3Client, getSignedUrl, commands }
47
+ }
48
+
49
+ return {
50
+ async upload(file, path, name?) {
51
+ const { client, commands: cmd } = await getClient()
52
+ const id = crypto.randomUUID()
53
+ const fileName = name || (file instanceof File ? file.name : `file-${id.slice(0, 8)}`)
54
+ const key = path ? `${path}/${fileName}` : fileName
55
+ const buffer = new Uint8Array(await file.arrayBuffer())
56
+
57
+ await client.send(new cmd.PutObjectCommand({
58
+ Bucket: bucket,
59
+ Key: key,
60
+ Body: buffer,
61
+ ContentType: file.type || 'application/octet-stream',
62
+ Metadata: { 'original-name': fileName, 'file-id': id },
63
+ }))
64
+
65
+ const url = publicUrl
66
+ ? `${publicUrl}/${key}`
67
+ : `https://${bucket}.s3.${region}.amazonaws.com/${key}`
68
+
69
+ return {
70
+ id,
71
+ name: fileName,
72
+ path: key,
73
+ size: file.size,
74
+ mimeType: file.type || 'application/octet-stream',
75
+ url,
76
+ createdAt: new Date().toISOString(),
77
+ }
78
+ },
79
+
80
+ async download(fileId) {
81
+ const { client, commands: cmd } = await getClient()
82
+ const response = await client.send(new cmd.GetObjectCommand({
83
+ Bucket: bucket,
84
+ Key: fileId,
85
+ }))
86
+ const bytes = await response.Body?.transformToByteArray()
87
+ if (!bytes) throw new Error(`Failed to download file: ${fileId}`)
88
+ return new Blob([bytes], { type: response.ContentType || 'application/octet-stream' })
89
+ },
90
+
91
+ async delete(fileId) {
92
+ const { client, commands: cmd } = await getClient()
93
+ try {
94
+ await client.send(new cmd.DeleteObjectCommand({ Bucket: bucket, Key: fileId }))
95
+ return true
96
+ } catch {
97
+ return false
98
+ }
99
+ },
100
+
101
+ async list(prefix?) {
102
+ const { client, commands: cmd } = await getClient()
103
+ const response = await client.send(new cmd.ListObjectsV2Command({
104
+ Bucket: bucket,
105
+ Prefix: prefix || undefined,
106
+ }))
107
+
108
+ return (response.Contents || []).map((obj: any): StoredFile => ({
109
+ id: obj.Key,
110
+ name: obj.Key.split('/').pop() || obj.Key,
111
+ path: obj.Key,
112
+ size: obj.Size || 0,
113
+ mimeType: 'application/octet-stream',
114
+ url: publicUrl
115
+ ? `${publicUrl}/${obj.Key}`
116
+ : `https://${bucket}.s3.${region}.amazonaws.com/${obj.Key}`,
117
+ createdAt: obj.LastModified?.toISOString() || new Date().toISOString(),
118
+ }))
119
+ },
120
+
121
+ async getUrl(fileId) {
122
+ if (publicUrl) return `${publicUrl}/${fileId}`
123
+ const { client, getSignedUrl: sign, commands: cmd } = await getClient()
124
+ const command = new cmd.GetObjectCommand({ Bucket: bucket, Key: fileId })
125
+ return sign(client, command, { expiresIn: presignedExpiry })
126
+ },
127
+ }
128
+ }
@@ -0,0 +1,116 @@
1
+ // @geenius/adapters — Supabase Storage file adapter
2
+ // Requires: @supabase/supabase-js
3
+
4
+ import type { StoredFile } from '../types'
5
+ import type { FileStorageAdapter } from './interface'
6
+
7
+ export interface SupabaseFileAdapterOptions {
8
+ /** Supabase project URL */
9
+ supabaseUrl: string
10
+ /** Supabase service role key or anon key */
11
+ supabaseKey: string
12
+ /** Storage bucket name (default: 'files') */
13
+ bucket?: string
14
+ }
15
+
16
+ export function createSupabaseFileAdapter(options: SupabaseFileAdapterOptions): FileStorageAdapter {
17
+ const { supabaseUrl, supabaseKey, bucket = 'files' } = options
18
+
19
+ let supabase: any = null
20
+
21
+ async function getClient() {
22
+ if (!supabase) {
23
+ const { createClient } = await import('@supabase/supabase-js')
24
+ supabase = createClient(supabaseUrl, supabaseKey)
25
+ }
26
+ return supabase
27
+ }
28
+
29
+ return {
30
+ async upload(file, path, name?) {
31
+ const client = await getClient()
32
+ const id = crypto.randomUUID()
33
+ const fileName = name || (file instanceof File ? file.name : `file-${id.slice(0, 8)}`)
34
+ const filePath = path ? `${path}/${fileName}` : fileName
35
+
36
+ const { error } = await client.storage
37
+ .from(bucket)
38
+ .upload(filePath, file, {
39
+ contentType: file.type || 'application/octet-stream',
40
+ upsert: false,
41
+ })
42
+
43
+ if (error) throw new Error(`Supabase upload failed: ${error.message}`)
44
+
45
+ const { data: urlData } = client.storage
46
+ .from(bucket)
47
+ .getPublicUrl(filePath)
48
+
49
+ return {
50
+ id,
51
+ name: fileName,
52
+ path: filePath,
53
+ size: file.size,
54
+ mimeType: file.type || 'application/octet-stream',
55
+ url: urlData.publicUrl,
56
+ createdAt: new Date().toISOString(),
57
+ }
58
+ },
59
+
60
+ async download(fileId) {
61
+ const client = await getClient()
62
+ const { data, error } = await client.storage
63
+ .from(bucket)
64
+ .download(fileId)
65
+
66
+ if (error || !data) throw new Error(`Supabase download failed: ${error?.message || 'No data'}`)
67
+ return data
68
+ },
69
+
70
+ async delete(fileId) {
71
+ const client = await getClient()
72
+ const { error } = await client.storage
73
+ .from(bucket)
74
+ .remove([fileId])
75
+
76
+ return !error
77
+ },
78
+
79
+ async list(prefix?) {
80
+ const client = await getClient()
81
+ const { data, error } = await client.storage
82
+ .from(bucket)
83
+ .list(prefix || '', { limit: 1000, sortBy: { column: 'created_at', order: 'desc' } })
84
+
85
+ if (error || !data) return []
86
+
87
+ return data
88
+ .filter((f: any) => f.name) // filter out folders
89
+ .map((f: any): StoredFile => {
90
+ const filePath = prefix ? `${prefix}/${f.name}` : f.name
91
+ const { data: urlData } = client.storage
92
+ .from(bucket)
93
+ .getPublicUrl(filePath)
94
+
95
+ return {
96
+ id: f.id || filePath,
97
+ name: f.name,
98
+ path: filePath,
99
+ size: f.metadata?.size || 0,
100
+ mimeType: f.metadata?.mimetype || 'application/octet-stream',
101
+ url: urlData.publicUrl,
102
+ createdAt: f.created_at || new Date().toISOString(),
103
+ }
104
+ })
105
+ },
106
+
107
+ async getUrl(fileId) {
108
+ const client = await getClient()
109
+ const { data } = client.storage
110
+ .from(bucket)
111
+ .getPublicUrl(fileId)
112
+
113
+ return data.publicUrl
114
+ },
115
+ }
116
+ }
@@ -0,0 +1,126 @@
1
+ // @geenius/adapters — Uploadthing file storage adapter
2
+ // Requires: uploadthing
3
+
4
+ import type { StoredFile } from '../types'
5
+ import type { FileStorageAdapter } from './interface'
6
+
7
+ export interface UploadthingFileAdapterOptions {
8
+ /** Uploadthing API token */
9
+ token: string
10
+ /** Base URL for the Uploadthing API (default: https://api.uploadthing.com) */
11
+ apiUrl?: string
12
+ }
13
+
14
+ export function createUploadthingFileAdapter(options: UploadthingFileAdapterOptions): FileStorageAdapter {
15
+ const { token, apiUrl = 'https://api.uploadthing.com' } = options
16
+
17
+ const headers = {
18
+ 'Content-Type': 'application/json',
19
+ 'x-uploadthing-api-key': token,
20
+ }
21
+
22
+ return {
23
+ async upload(file, path, name?) {
24
+ const id = crypto.randomUUID()
25
+ const fileName = name || (file instanceof File ? file.name : `file-${id.slice(0, 8)}`)
26
+
27
+ // Request a presigned URL from Uploadthing
28
+ const presignResponse = await fetch(`${apiUrl}/v6/uploadFiles`, {
29
+ method: 'POST',
30
+ headers,
31
+ body: JSON.stringify({
32
+ files: [{
33
+ name: fileName,
34
+ size: file.size,
35
+ type: file.type || 'application/octet-stream',
36
+ customId: id,
37
+ }],
38
+ acl: 'public-read',
39
+ contentDisposition: 'inline',
40
+ }),
41
+ })
42
+
43
+ if (!presignResponse.ok) {
44
+ throw new Error(`Uploadthing presign failed: ${presignResponse.statusText}`)
45
+ }
46
+
47
+ const presignData = await presignResponse.json()
48
+ const upload = presignData.data?.[0]
49
+
50
+ if (!upload?.url) {
51
+ throw new Error('Uploadthing did not return an upload URL')
52
+ }
53
+
54
+ // Upload the file to the presigned URL
55
+ const formData = new FormData()
56
+ for (const [key, value] of Object.entries(upload.fields || {})) {
57
+ formData.append(key, value as string)
58
+ }
59
+ formData.append('file', file)
60
+
61
+ const uploadResponse = await fetch(upload.url, {
62
+ method: 'POST',
63
+ body: formData,
64
+ })
65
+
66
+ if (!uploadResponse.ok) {
67
+ throw new Error(`Uploadthing upload failed: ${uploadResponse.statusText}`)
68
+ }
69
+
70
+ return {
71
+ id: upload.key || id,
72
+ name: fileName,
73
+ path: path ? `${path}/${fileName}` : fileName,
74
+ size: file.size,
75
+ mimeType: file.type || 'application/octet-stream',
76
+ url: upload.fileUrl || `https://utfs.io/f/${upload.key}`,
77
+ createdAt: new Date().toISOString(),
78
+ }
79
+ },
80
+
81
+ async download(fileId) {
82
+ const url = fileId.startsWith('http') ? fileId : `https://utfs.io/f/${fileId}`
83
+ const response = await fetch(url)
84
+ if (!response.ok) throw new Error(`Failed to download file: ${fileId}`)
85
+ return response.blob()
86
+ },
87
+
88
+ async delete(fileId) {
89
+ try {
90
+ const response = await fetch(`${apiUrl}/v6/deleteFiles`, {
91
+ method: 'POST',
92
+ headers,
93
+ body: JSON.stringify({ fileKeys: [fileId] }),
94
+ })
95
+ return response.ok
96
+ } catch {
97
+ return false
98
+ }
99
+ },
100
+
101
+ async list(prefix?) {
102
+ const response = await fetch(`${apiUrl}/v6/listFiles`, {
103
+ method: 'POST',
104
+ headers,
105
+ body: JSON.stringify({ prefix }),
106
+ })
107
+
108
+ if (!response.ok) return []
109
+
110
+ const data = await response.json()
111
+ return (data.files || []).map((f: any): StoredFile => ({
112
+ id: f.key,
113
+ name: f.name || f.key,
114
+ path: f.key,
115
+ size: f.size || 0,
116
+ mimeType: f.type || 'application/octet-stream',
117
+ url: f.url || `https://utfs.io/f/${f.key}`,
118
+ createdAt: f.uploadedAt || new Date().toISOString(),
119
+ }))
120
+ },
121
+
122
+ async getUrl(fileId) {
123
+ return fileId.startsWith('http') ? fileId : `https://utfs.io/f/${fileId}`
124
+ },
125
+ }
126
+ }