cloudbase-sdkv1 1.0.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/README.md +97 -0
- package/auth/authLoader.js +18 -0
- package/auth/firebaseAuth.js +74 -0
- package/auth/supbaseAuth.js +91 -0
- package/cli/index.js +0 -0
- package/config/configLoader.js +31 -0
- package/examples/README.md +15 -0
- package/examples/firebase.js +33 -0
- package/examples/supabase.js +31 -0
- package/index.js +5 -0
- package/orm/model.js +31 -0
- package/package.json +19 -0
- package/packages/adapters/firebase/index.js +78 -0
- package/packages/adapters/supabase/index.js +121 -0
- package/packages/core/adapterLoader.js +18 -0
- package/packages/core/client.js +81 -0
- package/packages/core/errors.js +23 -0
- package/parser/queryParser.js +58 -0
- package/providers/firebase/firebaseApp.js +18 -0
- package/storage/firebaseStorage.js +35 -0
- package/storage/storageLoader.js +18 -0
- package/storage/supabaseStorage.js +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Cloudbase
|
|
2
|
+
|
|
3
|
+
Cloudbase is a lightweight, server-side SDK to switch between cloud providers with a unified API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install cloudbase
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
Create `cloudbase.config.js` in your project root:
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
module.exports = {
|
|
17
|
+
provider: "supabase",
|
|
18
|
+
url: "https://your-project.supabase.co",
|
|
19
|
+
key: "your-service-key"
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
For Firebase (server-side):
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
module.exports = {
|
|
27
|
+
provider: "firebase",
|
|
28
|
+
serviceAccount: require("./serviceAccount.json"),
|
|
29
|
+
storageBucket: "your-project-id.appspot.com"
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
const { createClient } = require("cloudbase")
|
|
37
|
+
|
|
38
|
+
const db = createClient()
|
|
39
|
+
|
|
40
|
+
const User = db.model("users")
|
|
41
|
+
|
|
42
|
+
await User.create({ name: "Aman", age: 21 })
|
|
43
|
+
|
|
44
|
+
const adults = await User.find({ age: { gt: 18 } })
|
|
45
|
+
|
|
46
|
+
const one = await User.findOne({ age: { gt: 18 } })
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Pagination
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
await User.find({}, { limit: 10, offset: 0 })
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Sorting
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
await User.find({}, { orderBy: "age", order: "desc" })
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Auth
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
const auth = db.auth()
|
|
65
|
+
|
|
66
|
+
await auth.signup("user@example.com", "password123")
|
|
67
|
+
await auth.signin("user@example.com")
|
|
68
|
+
await auth.logout()
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Firebase server-side helpers
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
const auth = db.auth()
|
|
75
|
+
const link = await auth.generateSignInWithEmailLink("user@example.com", actionCodeSettings)
|
|
76
|
+
const reset = await auth.generatePasswordResetLink("user@example.com", actionCodeSettings)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Storage
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
const storage = db.storage()
|
|
83
|
+
await storage.upload("avatars/user-1.png", buffer)
|
|
84
|
+
const url = await storage.downloadUrl("avatars/user-1.png")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Unified Errors
|
|
88
|
+
|
|
89
|
+
All provider errors are wrapped as:
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
{
|
|
93
|
+
name: "CloudbaseError",
|
|
94
|
+
type: "AUTH_ERROR" | "DB_ERROR" | "STORAGE_ERROR" | "CONFIG_ERROR",
|
|
95
|
+
message: "..."
|
|
96
|
+
}
|
|
97
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const FirebaseAuth = require("./firebaseAuth")
|
|
2
|
+
const SupabaseAuth = require("./supbaseAuth")
|
|
3
|
+
|
|
4
|
+
const authProviders = {
|
|
5
|
+
firebase: FirebaseAuth,
|
|
6
|
+
supabase: SupabaseAuth
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function loadAuth(provider, config){
|
|
10
|
+
const AuthProvider = authProviders[provider]
|
|
11
|
+
if (!AuthProvider) {
|
|
12
|
+
throw new Error("Unsupported auth provider")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return new AuthProvider(config)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = loadAuth
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const getFirebaseApp = require("../providers/firebase/firebaseApp")
|
|
2
|
+
|
|
3
|
+
class FirebaseAuth {
|
|
4
|
+
|
|
5
|
+
constructor(config){
|
|
6
|
+
getFirebaseApp(config)
|
|
7
|
+
const admin = require("firebase-admin")
|
|
8
|
+
this.auth = admin.auth()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async signup(email, password){
|
|
12
|
+
const user = await this.auth.createUser({
|
|
13
|
+
email,
|
|
14
|
+
password
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
return { user, session: null }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async signin(email){
|
|
21
|
+
const user = await this.auth.getUserByEmail(email)
|
|
22
|
+
return { user, session: null }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async createCustomToken(uid, claims) {
|
|
26
|
+
const token = await this.auth.createCustomToken(uid, claims)
|
|
27
|
+
return { token }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async verifyIdToken(idToken) {
|
|
31
|
+
const decoded = await this.auth.verifyIdToken(idToken)
|
|
32
|
+
return { decoded }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async setCustomClaims(uid, claims) {
|
|
36
|
+
await this.auth.setCustomUserClaims(uid, claims)
|
|
37
|
+
return { success: true }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async generatePasswordResetLink(email, actionCodeSettings){
|
|
41
|
+
const link = await this.auth.generatePasswordResetLink(email, actionCodeSettings)
|
|
42
|
+
return { link }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async generateEmailVerificationLink(email, actionCodeSettings){
|
|
46
|
+
const link = await this.auth.generateEmailVerificationLink(email, actionCodeSettings)
|
|
47
|
+
return { link }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async generateSignInWithEmailLink(email, actionCodeSettings){
|
|
51
|
+
const link = await this.auth.generateSignInWithEmailLink(email, actionCodeSettings)
|
|
52
|
+
return { link }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async revokeRefreshTokens(uid){
|
|
56
|
+
await this.auth.revokeRefreshTokens(uid)
|
|
57
|
+
return { success: true }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async listUsers(maxResults = 1000, pageToken) {
|
|
61
|
+
return this.auth.listUsers(maxResults, pageToken)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async logout(uid){
|
|
65
|
+
if (!uid) {
|
|
66
|
+
return { success: true }
|
|
67
|
+
}
|
|
68
|
+
await this.auth.revokeRefreshTokens(uid)
|
|
69
|
+
return { success:true }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = FirebaseAuth
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const { createClient } = require("@supabase/supabase-js")
|
|
2
|
+
|
|
3
|
+
class SupabaseAuth {
|
|
4
|
+
|
|
5
|
+
constructor(config){
|
|
6
|
+
this.client = createClient(config.url, config.key)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async signup(email, password){
|
|
10
|
+
return this.signupWithEmailPassword(email, password)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async signin(email, password){
|
|
14
|
+
return this.signinWithEmailPassword(email, password)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async signupWithEmailPassword(email, password){
|
|
18
|
+
|
|
19
|
+
const { data, error } = await this.client.auth.signUp({
|
|
20
|
+
email,
|
|
21
|
+
password
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
if(error) throw error
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
user: data.user,
|
|
28
|
+
session: data.session
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async signinWithEmailPassword(email, password){
|
|
33
|
+
|
|
34
|
+
const { data, error } =
|
|
35
|
+
await this.client.auth.signInWithPassword({
|
|
36
|
+
email,
|
|
37
|
+
password
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
if(error) throw error
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
user: data.user,
|
|
44
|
+
session: data.session
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async logout(){
|
|
49
|
+
|
|
50
|
+
const { error } = await this.client.auth.signOut()
|
|
51
|
+
|
|
52
|
+
if(error) throw error
|
|
53
|
+
|
|
54
|
+
return { success:true }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async sendEmailLink(email, options = {}){
|
|
58
|
+
const { data, error } = await this.client.auth.signInWithOtp({
|
|
59
|
+
email,
|
|
60
|
+
options
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
if(error) throw error
|
|
64
|
+
|
|
65
|
+
return { data }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async signinWithOAuth(provider, options = {}){
|
|
69
|
+
const { data, error } = await this.client.auth.signInWithOAuth({
|
|
70
|
+
provider,
|
|
71
|
+
options
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
if(error) throw error
|
|
75
|
+
|
|
76
|
+
return { data }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async handleOAuthCallback(url){
|
|
80
|
+
const { data, error } = await this.client.auth.exchangeCodeForSession(url)
|
|
81
|
+
if(error) throw error
|
|
82
|
+
return { data }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
onAuthStateChange(handler){
|
|
86
|
+
return this.client.auth.onAuthStateChange(handler)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = SupabaseAuth
|
package/cli/index.js
ADDED
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const fs = require("fs")
|
|
2
|
+
const path = require("path")
|
|
3
|
+
const { wrapError } = require("../packages/core/errors")
|
|
4
|
+
|
|
5
|
+
function loadConfig(providedConfig) {
|
|
6
|
+
if (providedConfig && typeof providedConfig === "object") {
|
|
7
|
+
return providedConfig
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const configPath = path.join(process.cwd(), "cloudbase.config.js")
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(configPath)) {
|
|
13
|
+
throw wrapError(
|
|
14
|
+
new Error("Missing cloudbase.config.js and no config provided to createClient"),
|
|
15
|
+
"CONFIG_ERROR",
|
|
16
|
+
{ path: configPath }
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const config = require(configPath)
|
|
22
|
+
if (!config || typeof config !== "object") {
|
|
23
|
+
throw new Error("cloudbase.config.js must export an object")
|
|
24
|
+
}
|
|
25
|
+
return config
|
|
26
|
+
} catch (err) {
|
|
27
|
+
throw wrapError(err, "CONFIG_ERROR", { path: configPath })
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = loadConfig
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const { createClient } = require("..")
|
|
2
|
+
|
|
3
|
+
const db = createClient({
|
|
4
|
+
provider: "firebase",
|
|
5
|
+
serviceAccount: require("../serviceAccount.json"),
|
|
6
|
+
storageBucket: "your-project-id.appspot.com"
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
async function run() {
|
|
10
|
+
const User = db.model("users")
|
|
11
|
+
|
|
12
|
+
await User.create({ name: "Aman", age: 21 })
|
|
13
|
+
|
|
14
|
+
const adults = await User.find({ age: { gt: 18 } }, { limit: 10, offset: 0 })
|
|
15
|
+
console.log("Adults:", adults)
|
|
16
|
+
|
|
17
|
+
const one = await User.findOne({ age: { gt: 18 } })
|
|
18
|
+
console.log("One:", one)
|
|
19
|
+
|
|
20
|
+
const auth = db.auth()
|
|
21
|
+
await auth.generateSignInWithEmailLink("user@example.com", {
|
|
22
|
+
url: "https://example.com/finishSignIn",
|
|
23
|
+
handleCodeInApp: true
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const storage = db.storage()
|
|
27
|
+
await storage.list("avatars")
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
run().catch(err => {
|
|
31
|
+
console.error(err)
|
|
32
|
+
process.exit(1)
|
|
33
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const { createClient } = require("..")
|
|
2
|
+
|
|
3
|
+
const db = createClient({
|
|
4
|
+
provider: "supabase",
|
|
5
|
+
url: "project_url",
|
|
6
|
+
key: "your-service-key",
|
|
7
|
+
bucket: "public"
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
async function run() {
|
|
11
|
+
const User = db.model("users")
|
|
12
|
+
|
|
13
|
+
await User.create({ name: "Aman", age: 21 })
|
|
14
|
+
|
|
15
|
+
const adults = await User.find({ age: { gt: 18 } }, { limit: 10, offset: 0 })
|
|
16
|
+
console.log("Adults:", adults)
|
|
17
|
+
|
|
18
|
+
const one = await User.findOne({ age: { gt: 18 } })
|
|
19
|
+
console.log("One:", one)
|
|
20
|
+
|
|
21
|
+
const auth = db.auth()
|
|
22
|
+
await auth.signup("user@example.com", "password123")
|
|
23
|
+
|
|
24
|
+
const storage = db.storage().from("public")
|
|
25
|
+
await storage.list("", { limit: 10 })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
run().catch(err => {
|
|
29
|
+
console.error(err)
|
|
30
|
+
process.exit(1)
|
|
31
|
+
})
|
package/index.js
ADDED
package/orm/model.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
class Model {
|
|
2
|
+
|
|
3
|
+
constructor(name, client) {
|
|
4
|
+
this.name = name
|
|
5
|
+
this.client = client
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async create(data) {
|
|
9
|
+
return this.client.insert(this.name, data)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async find(query = {}, options = {}) {
|
|
13
|
+
return this.client.find(this.name, query, options)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async findOne(query = {}, options = {}) {
|
|
17
|
+
const results = await this.find(query, { ...options, limit: 1 })
|
|
18
|
+
return results && results.length ? results[0] : null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async update(query, data) {
|
|
22
|
+
return this.client.update(this.name, query, data)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async delete(query) {
|
|
26
|
+
return this.client.delete(this.name, query)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = Model
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cloudbase-sdkv1",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
7
|
+
},
|
|
8
|
+
"author": "",
|
|
9
|
+
"license": "ISC",
|
|
10
|
+
"description": "",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@supabase/supabase-js": "^2.99.1",
|
|
13
|
+
"firebase-admin": "^13.7.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"tsup": "^8.5.1",
|
|
17
|
+
"typescript": "^5.9.3"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const getFirebaseApp = require("../../../providers/firebase/firebaseApp")
|
|
2
|
+
const parseQuery = require("../../../parser/queryParser")
|
|
3
|
+
|
|
4
|
+
class FirebaseAdapter {
|
|
5
|
+
|
|
6
|
+
constructor(config) {
|
|
7
|
+
getFirebaseApp(config)
|
|
8
|
+
const admin = require("firebase-admin")
|
|
9
|
+
this.db = admin.firestore()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async insert(collectionName, data) {
|
|
13
|
+
return this.db.collection(collectionName).add(data)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async find(collectionName, queryObject = {}, options = {}) {
|
|
17
|
+
const conditions = parseQuery(queryObject)
|
|
18
|
+
let request = this.db.collection(collectionName)
|
|
19
|
+
|
|
20
|
+
for (const cond of conditions) {
|
|
21
|
+
let op = cond.operator
|
|
22
|
+
if (cond.operator === "eq") op = "=="
|
|
23
|
+
if (cond.operator === "gt") op = ">"
|
|
24
|
+
if (cond.operator === "lt") op = "<"
|
|
25
|
+
if (cond.operator === "gte") op = ">="
|
|
26
|
+
if (cond.operator === "lte") op = "<="
|
|
27
|
+
request = request.where(cond.field, op, cond.value)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (options.orderBy) {
|
|
31
|
+
const direction = options.order === "desc" ? "desc" : "asc"
|
|
32
|
+
request = request.orderBy(options.orderBy, direction)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (Number.isInteger(options.limit)) {
|
|
36
|
+
request = request.limit(options.limit)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (Number.isInteger(options.offset)) {
|
|
40
|
+
request = request.offset(options.offset)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const snapshot = await request.get()
|
|
44
|
+
return snapshot.docs.map(docSnap => ({ id: docSnap.id, ...docSnap.data() }))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async update(collectionName, queryObject, data) {
|
|
48
|
+
const { ids, docs } = await this._resolveDocs(collectionName, queryObject)
|
|
49
|
+
const updates = docs.map((docRef, index) => docRef.update(data).then(() => ids[index]))
|
|
50
|
+
return Promise.all(updates)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async delete(collectionName, queryObject) {
|
|
54
|
+
const { ids, docs } = await this._resolveDocs(collectionName, queryObject)
|
|
55
|
+
const deletions = docs.map((docRef, index) => docRef.delete().then(() => ids[index]))
|
|
56
|
+
return Promise.all(deletions)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async _resolveDocs(collectionName, queryObject = {}) {
|
|
60
|
+
if (queryObject && (queryObject.id || queryObject._id)) {
|
|
61
|
+
const id = queryObject.id || queryObject._id
|
|
62
|
+
return {
|
|
63
|
+
ids: [id],
|
|
64
|
+
docs: [this.db.collection(collectionName).doc(id)]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const results = await this.find(collectionName, queryObject)
|
|
69
|
+
const ids = results.map(item => item.id).filter(Boolean)
|
|
70
|
+
return {
|
|
71
|
+
ids,
|
|
72
|
+
docs: ids.map(id => this.db.collection(collectionName).doc(id))
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = FirebaseAdapter
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const { createClient } = require("@supabase/supabase-js")
|
|
2
|
+
const parseQuery = require("../../../parser/queryParser")
|
|
3
|
+
|
|
4
|
+
class SupabaseAdapter {
|
|
5
|
+
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.client = createClient(config.url, config.key)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async insert(table, data) {
|
|
11
|
+
return this.client.from(table).insert(data)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async find(table, query = {}, options = {}) {
|
|
16
|
+
|
|
17
|
+
const conditions = parseQuery(query)
|
|
18
|
+
|
|
19
|
+
let request = this.client.from(table).select("*")
|
|
20
|
+
|
|
21
|
+
for (const cond of conditions) {
|
|
22
|
+
|
|
23
|
+
if (cond.operator === "eq") {
|
|
24
|
+
request = request.eq(cond.field, cond.value)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (cond.operator === "gt") {
|
|
28
|
+
request = request.gt(cond.field, cond.value)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (cond.operator === "gte") {
|
|
32
|
+
request = request.gte(cond.field, cond.value)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (cond.operator === "lt") {
|
|
36
|
+
request = request.lt(cond.field, cond.value)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (cond.operator === "lte") {
|
|
40
|
+
request = request.lte(cond.field, cond.value)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (options.orderBy) {
|
|
46
|
+
const ascending = options.order !== "desc"
|
|
47
|
+
request = request.order(options.orderBy, { ascending })
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (Number.isInteger(options.offset) && Number.isInteger(options.limit)) {
|
|
51
|
+
request = request.range(options.offset, options.offset + options.limit - 1)
|
|
52
|
+
} else if (Number.isInteger(options.limit)) {
|
|
53
|
+
request = request.limit(options.limit)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { data, error } = await request
|
|
57
|
+
|
|
58
|
+
if (error) throw error
|
|
59
|
+
|
|
60
|
+
return data
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async update(table, query = {}, data) {
|
|
64
|
+
const conditions = parseQuery(query)
|
|
65
|
+
|
|
66
|
+
let request = this.client.from(table).update(data)
|
|
67
|
+
|
|
68
|
+
for (const cond of conditions) {
|
|
69
|
+
if (cond.operator === "eq") {
|
|
70
|
+
request = request.eq(cond.field, cond.value)
|
|
71
|
+
}
|
|
72
|
+
if (cond.operator === "gt") {
|
|
73
|
+
request = request.gt(cond.field, cond.value)
|
|
74
|
+
}
|
|
75
|
+
if (cond.operator === "gte") {
|
|
76
|
+
request = request.gte(cond.field, cond.value)
|
|
77
|
+
}
|
|
78
|
+
if (cond.operator === "lt") {
|
|
79
|
+
request = request.lt(cond.field, cond.value)
|
|
80
|
+
}
|
|
81
|
+
if (cond.operator === "lte") {
|
|
82
|
+
request = request.lte(cond.field, cond.value)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const { data: updated, error } = await request.select()
|
|
87
|
+
if (error) throw error
|
|
88
|
+
return updated
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async delete(table, query = {}) {
|
|
92
|
+
const conditions = parseQuery(query)
|
|
93
|
+
|
|
94
|
+
let request = this.client.from(table).delete()
|
|
95
|
+
|
|
96
|
+
for (const cond of conditions) {
|
|
97
|
+
if (cond.operator === "eq") {
|
|
98
|
+
request = request.eq(cond.field, cond.value)
|
|
99
|
+
}
|
|
100
|
+
if (cond.operator === "gt") {
|
|
101
|
+
request = request.gt(cond.field, cond.value)
|
|
102
|
+
}
|
|
103
|
+
if (cond.operator === "gte") {
|
|
104
|
+
request = request.gte(cond.field, cond.value)
|
|
105
|
+
}
|
|
106
|
+
if (cond.operator === "lt") {
|
|
107
|
+
request = request.lt(cond.field, cond.value)
|
|
108
|
+
}
|
|
109
|
+
if (cond.operator === "lte") {
|
|
110
|
+
request = request.lte(cond.field, cond.value)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const { data: deleted, error } = await request.select()
|
|
115
|
+
if (error) throw error
|
|
116
|
+
return deleted
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = SupabaseAdapter
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const FirebaseAdapter = require("../adapters/firebase")
|
|
2
|
+
const SupabaseAdapter = require("../adapters/supabase")
|
|
3
|
+
|
|
4
|
+
const adapterProviders = {
|
|
5
|
+
firebase: FirebaseAdapter,
|
|
6
|
+
supabase: SupabaseAdapter
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function loadAdapter(provider, config) {
|
|
10
|
+
const Adapter = adapterProviders[provider]
|
|
11
|
+
if (!Adapter) {
|
|
12
|
+
throw new Error("Unsupported provider")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return new Adapter(config)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = loadAdapter
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const loadAdapter = require("./adapterLoader")
|
|
2
|
+
const Model = require("../../orm/model")
|
|
3
|
+
const loadAuth = require("../../auth/authLoader")
|
|
4
|
+
const loadStorage = require("../../storage/storageLoader")
|
|
5
|
+
const loadConfig = require("../../config/configLoader")
|
|
6
|
+
const { wrapError } = require("./errors")
|
|
7
|
+
|
|
8
|
+
function wrapProvider(provider, type) {
|
|
9
|
+
return new Proxy(provider, {
|
|
10
|
+
get(target, prop) {
|
|
11
|
+
const value = target[prop]
|
|
12
|
+
if (typeof value !== "function") {
|
|
13
|
+
return value
|
|
14
|
+
}
|
|
15
|
+
return (...args) => {
|
|
16
|
+
try {
|
|
17
|
+
const result = value.apply(target, args)
|
|
18
|
+
if (result && typeof result.then === "function") {
|
|
19
|
+
return result.catch(err => {
|
|
20
|
+
throw wrapError(err, type)
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
return result
|
|
24
|
+
} catch (err) {
|
|
25
|
+
throw wrapError(err, type)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class CloudClient {
|
|
33
|
+
|
|
34
|
+
constructor(config){
|
|
35
|
+
this.config = config
|
|
36
|
+
this.adapter = wrapProvider(loadAdapter(config.provider, config), "DB_ERROR")
|
|
37
|
+
this.authProvider = wrapProvider(loadAuth(config.provider, config), "AUTH_ERROR")
|
|
38
|
+
this.storageProvider = wrapProvider(loadStorage(config.provider, config), "STORAGE_ERROR")
|
|
39
|
+
this.capabilities = {
|
|
40
|
+
auth: true,
|
|
41
|
+
database: true,
|
|
42
|
+
storage: true
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
model(name){
|
|
47
|
+
return new Model(name, this)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
auth(){
|
|
51
|
+
return this.authProvider
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
storage(){
|
|
55
|
+
return this.storageProvider
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async insert(collection, data) {
|
|
59
|
+
return this.adapter.insert(collection, data)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async find(collection, query, options) {
|
|
63
|
+
return this.adapter.find(collection, query, options)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async update(collection, query, data) {
|
|
67
|
+
return this.adapter.update(collection, query, data)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async delete(collection, query) {
|
|
71
|
+
return this.adapter.delete(collection, query)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function createClient(config) {
|
|
77
|
+
const resolvedConfig = loadConfig(config)
|
|
78
|
+
return new CloudClient(resolvedConfig)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = createClient
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class CloudbaseError extends Error {
|
|
2
|
+
constructor(type, message, details = {}, cause = null) {
|
|
3
|
+
super(message)
|
|
4
|
+
this.name = "CloudbaseError"
|
|
5
|
+
this.type = type
|
|
6
|
+
this.details = details
|
|
7
|
+
this.cause = cause
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function wrapError(err, type, details = {}) {
|
|
12
|
+
if (err instanceof CloudbaseError) {
|
|
13
|
+
return err
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const message = err && err.message ? err.message : "Unknown error"
|
|
17
|
+
return new CloudbaseError(type, message, details, err)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
CloudbaseError,
|
|
22
|
+
wrapError
|
|
23
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
function parseQuery(query) {
|
|
2
|
+
|
|
3
|
+
const conditions = []
|
|
4
|
+
|
|
5
|
+
for (const field in query) {
|
|
6
|
+
|
|
7
|
+
const value = query[field]
|
|
8
|
+
|
|
9
|
+
if (typeof value === "object") {
|
|
10
|
+
|
|
11
|
+
if (value.gt) {
|
|
12
|
+
conditions.push({
|
|
13
|
+
field,
|
|
14
|
+
operator: "gt",
|
|
15
|
+
value: value.gt
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (value.gte) {
|
|
20
|
+
conditions.push({
|
|
21
|
+
field,
|
|
22
|
+
operator: "gte",
|
|
23
|
+
value: value.gte
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (value.lt) {
|
|
28
|
+
conditions.push({
|
|
29
|
+
field,
|
|
30
|
+
operator: "lt",
|
|
31
|
+
value: value.lt
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (value.lte) {
|
|
36
|
+
conditions.push({
|
|
37
|
+
field,
|
|
38
|
+
operator: "lte",
|
|
39
|
+
value: value.lte
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
} else {
|
|
44
|
+
|
|
45
|
+
conditions.push({
|
|
46
|
+
field,
|
|
47
|
+
operator: "eq",
|
|
48
|
+
value
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return conditions
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = parseQuery
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const admin = require("firebase-admin")
|
|
2
|
+
|
|
3
|
+
function getFirebaseApp(config) {
|
|
4
|
+
if (!config.serviceAccount) {
|
|
5
|
+
throw new Error("Missing firebase serviceAccount in config")
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (admin.apps.length) {
|
|
9
|
+
return admin.app()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return admin.initializeApp({
|
|
13
|
+
credential: admin.credential.cert(config.serviceAccount),
|
|
14
|
+
storageBucket: config.storageBucket
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = getFirebaseApp
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const getFirebaseApp = require("../providers/firebase/firebaseApp")
|
|
2
|
+
|
|
3
|
+
class FirebaseStorage {
|
|
4
|
+
constructor(config) {
|
|
5
|
+
getFirebaseApp(config)
|
|
6
|
+
const admin = require("firebase-admin")
|
|
7
|
+
this.bucket = admin.storage().bucket()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async upload(path, file, metadata) {
|
|
11
|
+
const fileRef = this.bucket.file(path)
|
|
12
|
+
await fileRef.save(file, { metadata })
|
|
13
|
+
return { path }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async downloadUrl(path) {
|
|
17
|
+
const [url] = await this.bucket.file(path).getSignedUrl({
|
|
18
|
+
action: "read",
|
|
19
|
+
expires: Date.now() + 60 * 60 * 1000
|
|
20
|
+
})
|
|
21
|
+
return { url }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async list(path) {
|
|
25
|
+
const [files] = await this.bucket.getFiles({ prefix: path })
|
|
26
|
+
return files.map(file => ({ name: file.name, path: file.name }))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async remove(path) {
|
|
30
|
+
await this.bucket.file(path).delete()
|
|
31
|
+
return { success: true }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = FirebaseStorage
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const FirebaseStorage = require("./firebaseStorage")
|
|
2
|
+
const SupabaseStorage = require("./supabaseStorage")
|
|
3
|
+
|
|
4
|
+
const storageProviders = {
|
|
5
|
+
firebase: FirebaseStorage,
|
|
6
|
+
supabase: SupabaseStorage
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function loadStorage(provider, config) {
|
|
10
|
+
const StorageProvider = storageProviders[provider]
|
|
11
|
+
if (!StorageProvider) {
|
|
12
|
+
throw new Error("Unsupported storage provider")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return new StorageProvider(config)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = loadStorage
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const { createClient } = require("@supabase/supabase-js")
|
|
2
|
+
|
|
3
|
+
class SupabaseStorage {
|
|
4
|
+
constructor(config) {
|
|
5
|
+
this.client = createClient(config.url, config.key)
|
|
6
|
+
this.bucket = config.bucket || "public"
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
from(bucket) {
|
|
10
|
+
this.bucket = bucket
|
|
11
|
+
return this
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async upload(path, file, options = {}) {
|
|
15
|
+
const { data, error } = await this.client.storage
|
|
16
|
+
.from(this.bucket)
|
|
17
|
+
.upload(path, file, options)
|
|
18
|
+
|
|
19
|
+
if (error) throw error
|
|
20
|
+
return data
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async download(path) {
|
|
24
|
+
const { data, error } = await this.client.storage
|
|
25
|
+
.from(this.bucket)
|
|
26
|
+
.download(path)
|
|
27
|
+
|
|
28
|
+
if (error) throw error
|
|
29
|
+
return data
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async list(path, options = {}) {
|
|
33
|
+
const { data, error } = await this.client.storage
|
|
34
|
+
.from(this.bucket)
|
|
35
|
+
.list(path, options)
|
|
36
|
+
|
|
37
|
+
if (error) throw error
|
|
38
|
+
return data
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async remove(paths = []) {
|
|
42
|
+
const { data, error } = await this.client.storage
|
|
43
|
+
.from(this.bucket)
|
|
44
|
+
.remove(paths)
|
|
45
|
+
|
|
46
|
+
if (error) throw error
|
|
47
|
+
return data
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getPublicUrl(path) {
|
|
51
|
+
return this.client.storage.from(this.bucket).getPublicUrl(path)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async getSignedUrl(path, expiresIn = 60) {
|
|
55
|
+
const { data, error } = await this.client.storage
|
|
56
|
+
.from(this.bucket)
|
|
57
|
+
.createSignedUrl(path, expiresIn)
|
|
58
|
+
|
|
59
|
+
if (error) throw error
|
|
60
|
+
return data
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = SupabaseStorage
|