@taruvi/sdk 1.5.0-beta.0 → 1.5.0-beta.2
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 +58 -1295
- package/package.json +10 -2
- package/.claude/settings.local.json +0 -19
- package/.github/worflows/publish.yml +0 -57
- package/.github/workflows/publish.yml +0 -58
- package/.kiro/settings/lsp.json +0 -198
- package/MODULE_NAMING_CHANGES.md +0 -81
- package/PARAMETER_NAMING_CHANGES.md +0 -106
- package/USAGE_EXAMPLE.md +0 -86
- package/src/client.ts +0 -88
- package/src/index.ts +0 -51
- package/src/lib/analytics/AnalyticsClient.ts +0 -24
- package/src/lib/analytics/types.ts +0 -8
- package/src/lib/app/AppClient.ts +0 -54
- package/src/lib/app/types.ts +0 -50
- package/src/lib/auth/AuthClient.ts +0 -126
- package/src/lib/auth/types.ts +0 -123
- package/src/lib/database/DatabaseClient.ts +0 -298
- package/src/lib/database/types.ts +0 -156
- package/src/lib/functions/FunctionsClient.ts +0 -27
- package/src/lib/functions/types.ts +0 -27
- package/src/lib/policy/PolicyClient.ts +0 -79
- package/src/lib/policy/types.ts +0 -39
- package/src/lib/secrets/SecretsClient.ts +0 -75
- package/src/lib/secrets/types.ts +0 -59
- package/src/lib/settings/SettingsClient.ts +0 -22
- package/src/lib/settings/types.ts +0 -9
- package/src/lib/storage/StorageClient.ts +0 -131
- package/src/lib/storage/types.ts +0 -86
- package/src/lib/users/UserClient.ts +0 -63
- package/src/lib/users/types.ts +0 -123
- package/src/lib-internal/errors/ErrorClient.ts +0 -114
- package/src/lib-internal/errors/index.ts +0 -3
- package/src/lib-internal/errors/types.ts +0 -29
- package/src/lib-internal/http/HttpClient.ts +0 -116
- package/src/lib-internal/http/types.ts +0 -12
- package/src/lib-internal/routes/AnalyticsRoutes.ts +0 -3
- package/src/lib-internal/routes/AppRoutes.ts +0 -9
- package/src/lib-internal/routes/AuthRoutes.ts +0 -0
- package/src/lib-internal/routes/DatabaseRoutes.ts +0 -10
- package/src/lib-internal/routes/FunctionRoutes.ts +0 -3
- package/src/lib-internal/routes/PolicyRoutes.ts +0 -4
- package/src/lib-internal/routes/SecretsRoutes.ts +0 -5
- package/src/lib-internal/routes/SettingsRoutes.ts +0 -4
- package/src/lib-internal/routes/StorageRoutes.ts +0 -15
- package/src/lib-internal/routes/UserRoutes.ts +0 -12
- package/src/lib-internal/routes/index.ts +0 -0
- package/src/lib-internal/token/TokenClient.ts +0 -108
- package/src/lib-internal/token/types.ts +0 -0
- package/src/types.ts +0 -104
- package/src/utils/enums.ts +0 -24
- package/src/utils/utils.ts +0 -38
- package/tests/fixtures/mockClient.ts +0 -19
- package/tests/mocks/db.json +0 -1
- package/tests/unit/analytics/AnalyticsClient.test.ts +0 -84
- package/tests/unit/app/AppClient.test.ts +0 -114
- package/tests/unit/auth/AuthClient.test.ts +0 -91
- package/tests/unit/client/Client.test.ts +0 -87
- package/tests/unit/database/DatabaseClient.test.ts +0 -652
- package/tests/unit/edge-cases/robustness.test.ts +0 -258
- package/tests/unit/errors/errors.test.ts +0 -236
- package/tests/unit/functions/FunctionsClient.test.ts +0 -99
- package/tests/unit/policy/PolicyClient.test.ts +0 -180
- package/tests/unit/secrets/SecretsClient.test.ts +0 -146
- package/tests/unit/settings/SettingsClient.test.ts +0 -50
- package/tests/unit/storage/StorageClient.test.ts +0 -252
- package/tests/unit/users/UserClient.test.ts +0 -150
- package/tsconfig.json +0 -44
- package/vitest.config.ts +0 -7
package/README.md
CHANGED
|
@@ -1,1329 +1,92 @@
|
|
|
1
|
-
# Taruvi SDK
|
|
1
|
+
# Taruvi SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript SDK for the Taruvi platform. It gives developers a **consistent, typed way to query the Taruvi backend** — shared configuration, session authentication, fluent query builders, and structured errors across data, storage, auth, users, functions, analytics, policy, secrets, and app services.
|
|
4
|
+
|
|
5
|
+
**Package:** `@taruvi/sdk` · **Version:** 1.5.0-beta.1 · **License:** MIT
|
|
6
|
+
|
|
7
|
+
**Branches:** `main` is for **stable** releases; `beta` is for **experimental** releases. Bumping `version` in `package.json` and pushing to either branch triggers CI/CD (`npm test`, then publish to npm with the matching tag). See [Releases and branches](docs/08-releases-and-branches.md).
|
|
4
8
|
|
|
5
9
|
## Installation
|
|
6
10
|
|
|
7
11
|
```bash
|
|
12
|
+
# Stable (from main)
|
|
8
13
|
npm install @taruvi/sdk
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## What's New
|
|
12
|
-
|
|
13
|
-
Recent updates to the SDK:
|
|
14
|
-
|
|
15
|
-
- **Analytics Service**: New service for executing analytics queries with `execute()` method. Pass query name and parameters to run predefined analytics.
|
|
16
|
-
- **Policy Service**: New service for checking resource-level permissions with `checkResource()` method. Supports batch resource checking with entity types, tables, and attributes.
|
|
17
|
-
- **App Service**: New service to retrieve app roles with `roles()` method.
|
|
18
|
-
- **User Types**: Added `UserCreateRequest`, `UserResponse`, and `UserDataResponse` types for user management.
|
|
19
|
-
- **Auth Service**: Web UI Flow with `login()`, `signup()`, `logout()` methods that redirect to backend pages. Token refresh with rotation support, `getCurrentUser()` for JWT decoding.
|
|
20
|
-
- **Database Service**: Added `create()` method for creating records. Added `populate()` method for eager loading related records. Comprehensive filter support with Django-style operators (`__gte`, `__lte`, `__icontains`, etc.). Use **`orderBy()`** for sorting: one field (`orderBy('created_at', 'desc')`), multiple fields (`orderBy([{ field, order }])`), or a raw `ordering` string (`orderBy('-salary,hire_date')`). Hierarchy traversal uses **`.include('descendants' | 'ancestors' | 'both')`** — not `FilterOperator`. Import **`PgRangeValue`** for typed PG range columns in row data.
|
|
21
|
-
- **Storage Service**: Added `download()` method. Enhanced filter support with size, date, MIME type, visibility filters. `delete()` now accepts array of paths for bulk deletion.
|
|
22
|
-
- **Client**: Automatic token extraction from URL hash after OAuth callback - no manual token handling needed.
|
|
23
|
-
- **Types**: Comprehensive `StorageFilters` and `DatabaseFilters` interfaces with full operator support.
|
|
24
|
-
|
|
25
|
-
## Core Setup
|
|
26
|
-
|
|
27
|
-
### 1. Initialize Client
|
|
28
|
-
|
|
29
|
-
```typescript
|
|
30
|
-
import { Client } from '@taruvi/sdk'
|
|
31
|
-
|
|
32
|
-
const taruviClient = new Client({
|
|
33
|
-
apiKey: "your-site-api-key",
|
|
34
|
-
appSlug: "your-app-slug",
|
|
35
|
-
apiUrl: "https://taruvi-site.taruvi.cloud"
|
|
36
|
-
})
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### 2. Pass to Components (React)
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
// App.tsx
|
|
43
|
-
<Route path="/page" element={<MyPage taruviClient={taruviClient} />} />
|
|
44
|
-
|
|
45
|
-
// MyPage.tsx
|
|
46
|
-
interface MyPageProps {
|
|
47
|
-
taruviClient: Client;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export default function MyPage({ taruviClient }: MyPageProps) {
|
|
51
|
-
// Use SDK here
|
|
52
|
-
}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### 3. Automatic Token Handling
|
|
56
|
-
|
|
57
|
-
The Client automatically extracts authentication tokens from URL hash after OAuth callback:
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
// After login redirect, URL contains:
|
|
61
|
-
// #session_token=xxx&access_token=yyy&refresh_token=zzz&expires_in=172800&token_type=Bearer
|
|
62
|
-
|
|
63
|
-
// Client automatically:
|
|
64
|
-
// 1. Extracts tokens from URL hash
|
|
65
|
-
// 2. Stores them in TokenClient
|
|
66
|
-
// 3. Clears the hash from URL
|
|
67
|
-
|
|
68
|
-
const taruviClient = new Client({ apiKey, appSlug, baseUrl })
|
|
69
|
-
// Tokens are now available automatically if present in URL hash
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## Auth Service
|
|
75
|
-
|
|
76
|
-
### Check Authentication
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
import { Auth } from '@taruvi/sdk'
|
|
80
|
-
|
|
81
|
-
const auth = new Auth(taruviClient)
|
|
82
|
-
const isAuthenticated = auth.isUserAuthenticated() // Returns boolean (synchronous)
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### Login Flow (Web UI Flow with Redirect)
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
import { useEffect } from "react"
|
|
89
|
-
import { Auth } from '@taruvi/sdk'
|
|
90
|
-
|
|
91
|
-
export default function Login({ taruviClient }) {
|
|
92
|
-
const auth = new Auth(taruviClient)
|
|
93
|
-
|
|
94
|
-
useEffect(() => {
|
|
95
|
-
const isAuthenticated = auth.isUserAuthenticated()
|
|
96
|
-
|
|
97
|
-
if (isAuthenticated) {
|
|
98
|
-
window.location.href = "/dashboard"
|
|
99
|
-
} else {
|
|
100
|
-
// Redirects to backend login page, then returns with tokens in URL hash
|
|
101
|
-
auth.login() // Optional: pass callback URL
|
|
102
|
-
}
|
|
103
|
-
}, [])
|
|
104
|
-
|
|
105
|
-
return <div>Checking authentication...</div>
|
|
106
|
-
}
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Signup Flow
|
|
110
|
-
|
|
111
|
-
```typescript
|
|
112
|
-
import { Auth } from '@taruvi/sdk'
|
|
113
|
-
|
|
114
|
-
const auth = new Auth(taruviClient)
|
|
115
|
-
|
|
116
|
-
// Redirect to signup page (Web UI Flow)
|
|
117
|
-
auth.signup() // Optional: pass callback URL
|
|
118
|
-
auth.signup("/dashboard") // Redirect to dashboard after signup
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### Logout
|
|
122
14
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const auth = new Auth(taruviClient)
|
|
127
|
-
|
|
128
|
-
// Clear tokens and redirect to logout page
|
|
129
|
-
await auth.logout() // Optional: pass callback URL
|
|
130
|
-
await auth.logout("/") // Redirect to home after logout
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
### Get Current User
|
|
134
|
-
|
|
135
|
-
```typescript
|
|
136
|
-
import { Auth } from '@taruvi/sdk'
|
|
137
|
-
|
|
138
|
-
const auth = new Auth(taruviClient)
|
|
139
|
-
|
|
140
|
-
// Get user info from decoded JWT access token
|
|
141
|
-
const user = auth.getCurrentUser()
|
|
142
|
-
console.log(user?.user_id)
|
|
143
|
-
console.log(user?.username)
|
|
144
|
-
console.log(user?.email)
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
### Token Management
|
|
148
|
-
|
|
149
|
-
```typescript
|
|
150
|
-
import { Auth } from '@taruvi/sdk'
|
|
151
|
-
|
|
152
|
-
const auth = new Auth(taruviClient)
|
|
153
|
-
|
|
154
|
-
// Get tokens
|
|
155
|
-
const accessToken = auth.getAccessToken()
|
|
156
|
-
const refreshToken = auth.getRefreshToken()
|
|
157
|
-
|
|
158
|
-
// Check if token is expired
|
|
159
|
-
const isExpired = auth.isTokenExpired()
|
|
160
|
-
|
|
161
|
-
// Refresh access token (returns new access AND refresh tokens due to rotation)
|
|
162
|
-
const newTokens = await auth.refreshAccessToken()
|
|
163
|
-
if (newTokens) {
|
|
164
|
-
console.log(newTokens.access)
|
|
165
|
-
console.log(newTokens.refresh)
|
|
166
|
-
console.log(newTokens.expires_in)
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
---
|
|
171
|
-
|
|
172
|
-
## User Service
|
|
173
|
-
|
|
174
|
-
### Get Current User
|
|
175
|
-
|
|
176
|
-
```typescript
|
|
177
|
-
import { User } from '@taruvi/sdk'
|
|
178
|
-
|
|
179
|
-
const user = new User(taruviClient)
|
|
180
|
-
const userData = await user.getUserData()
|
|
181
|
-
|
|
182
|
-
console.log(userData.data.username)
|
|
183
|
-
console.log(userData.data.email)
|
|
184
|
-
console.log(userData.data.full_name)
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### Create User (Registration)
|
|
188
|
-
|
|
189
|
-
```typescript
|
|
190
|
-
import { User } from '@taruvi/sdk'
|
|
191
|
-
|
|
192
|
-
const user = new User(taruviClient)
|
|
193
|
-
|
|
194
|
-
const newUser = await user.createUser({
|
|
195
|
-
username: "john_doe",
|
|
196
|
-
email: "john@example.com",
|
|
197
|
-
password: "secure123",
|
|
198
|
-
confirm_password: "secure123",
|
|
199
|
-
first_name: "John",
|
|
200
|
-
last_name: "Doe",
|
|
201
|
-
is_active: true,
|
|
202
|
-
is_staff: false,
|
|
203
|
-
attributes: ""
|
|
204
|
-
})
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### Update User
|
|
208
|
-
|
|
209
|
-
```typescript
|
|
210
|
-
const user = new User(taruviClient)
|
|
211
|
-
|
|
212
|
-
await user.updateUser("john_doe", {
|
|
213
|
-
email: "newemail@example.com",
|
|
214
|
-
first_name: "Johnny"
|
|
215
|
-
})
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### Delete User
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
const user = new User(taruviClient)
|
|
222
|
-
await user.deleteUser("john_doe")
|
|
15
|
+
# Experimental (from beta)
|
|
16
|
+
npm install @taruvi/sdk@beta
|
|
223
17
|
```
|
|
224
18
|
|
|
225
|
-
|
|
19
|
+
**Peer dependencies:** `axios` (>=1), `typescript` (>=5.7), optional `@types/node` for server use.
|
|
226
20
|
|
|
227
|
-
|
|
228
|
-
const user = new User(taruviClient)
|
|
229
|
-
|
|
230
|
-
const users = await user.list({
|
|
231
|
-
search: "john",
|
|
232
|
-
is_active: true,
|
|
233
|
-
is_staff: false,
|
|
234
|
-
is_superuser: false,
|
|
235
|
-
is_deleted: false,
|
|
236
|
-
ordering: "-date_joined",
|
|
237
|
-
page: 1,
|
|
238
|
-
page_size: 20
|
|
239
|
-
})
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
### Get User Apps
|
|
21
|
+
## Quick start
|
|
243
22
|
|
|
244
23
|
```typescript
|
|
245
|
-
|
|
246
|
-
const apps = await user.getUserApps("john_doe")
|
|
24
|
+
import { Client, Database } from '@taruvi/sdk'
|
|
247
25
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
26
|
+
const client = new Client({
|
|
27
|
+
apiKey: 'your-site-api-key',
|
|
28
|
+
appSlug: 'your-app-slug',
|
|
29
|
+
apiUrl: 'https://taruvi-site.taruvi.cloud',
|
|
251
30
|
})
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
### Complete Registration Form Example
|
|
255
|
-
|
|
256
|
-
```typescript
|
|
257
|
-
import { useState } from "react"
|
|
258
|
-
import { User } from "@taruvi/sdk"
|
|
259
|
-
import { useNavigate } from "react-router"
|
|
260
|
-
|
|
261
|
-
export default function Register({ taruviClient }) {
|
|
262
|
-
const navigate = useNavigate()
|
|
263
|
-
const [formData, setFormData] = useState({
|
|
264
|
-
username: "",
|
|
265
|
-
email: "",
|
|
266
|
-
password: "",
|
|
267
|
-
confirm_password: "",
|
|
268
|
-
first_name: "",
|
|
269
|
-
last_name: "",
|
|
270
|
-
is_active: true,
|
|
271
|
-
is_staff: false
|
|
272
|
-
})
|
|
273
|
-
const [error, setError] = useState("")
|
|
274
|
-
const [loading, setLoading] = useState(false)
|
|
275
|
-
|
|
276
|
-
const handleSubmit = async (e) => {
|
|
277
|
-
e.preventDefault()
|
|
278
|
-
|
|
279
|
-
if (formData.password !== formData.confirm_password) {
|
|
280
|
-
setError("Passwords do not match")
|
|
281
|
-
return
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
setLoading(true)
|
|
285
|
-
try {
|
|
286
|
-
const userClient = new User(taruviClient)
|
|
287
|
-
await userClient.createUser(formData)
|
|
288
|
-
navigate("/login")
|
|
289
|
-
} catch (err) {
|
|
290
|
-
setError(err.message || "Failed to create user")
|
|
291
|
-
} finally {
|
|
292
|
-
setLoading(false)
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return (
|
|
297
|
-
<form onSubmit={handleSubmit}>
|
|
298
|
-
<input
|
|
299
|
-
name="username"
|
|
300
|
-
value={formData.username}
|
|
301
|
-
onChange={(e) => setFormData({...formData, username: e.target.value})}
|
|
302
|
-
required
|
|
303
|
-
/>
|
|
304
|
-
{/* Add other fields */}
|
|
305
|
-
<button type="submit" disabled={loading}>
|
|
306
|
-
{loading ? "Creating..." : "Register"}
|
|
307
|
-
</button>
|
|
308
|
-
{error && <div>{error}</div>}
|
|
309
|
-
</form>
|
|
310
|
-
)
|
|
311
|
-
}
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
---
|
|
315
|
-
|
|
316
|
-
## Database Service (CRUD Operations)
|
|
317
|
-
|
|
318
|
-
### Fetch All Records
|
|
319
|
-
|
|
320
|
-
```typescript
|
|
321
|
-
import { Database } from '@taruvi/sdk'
|
|
322
|
-
|
|
323
|
-
const db = new Database(taruviClient)
|
|
324
|
-
const response = await db.from("accounts").execute()
|
|
325
|
-
|
|
326
|
-
if (response.data) {
|
|
327
|
-
console.log(response.data) // Array of records
|
|
328
|
-
}
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
### Fetch Single Record
|
|
332
|
-
|
|
333
|
-
```typescript
|
|
334
|
-
const db = new Database(taruviClient)
|
|
335
|
-
const record = await db.from("accounts").get("record-id").execute()
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
### Create Record
|
|
339
|
-
|
|
340
|
-
```typescript
|
|
341
|
-
const db = new Database(taruviClient)
|
|
342
|
-
await db.from("accounts").create({
|
|
343
|
-
name: "John Doe",
|
|
344
|
-
email: "john@example.com",
|
|
345
|
-
status: "active"
|
|
346
|
-
}).execute()
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
### Update Record
|
|
350
|
-
|
|
351
|
-
```typescript
|
|
352
|
-
const db = new Database(taruviClient)
|
|
353
|
-
await db.from("accounts").get("record-id").update({
|
|
354
|
-
name: "Updated Name",
|
|
355
|
-
status: "active"
|
|
356
|
-
}).execute()
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
### Delete Record
|
|
360
|
-
|
|
361
|
-
```typescript
|
|
362
|
-
const db = new Database(taruviClient)
|
|
363
|
-
await db.from("accounts").delete("record-id").execute()
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
### Filter records
|
|
367
|
-
|
|
368
|
-
`Database` supports **DRF-style flat filters** (`filters(field, operator, value)`) and a **JSON filter tree** sent as the `filters` query param (`filters(tree)`).
|
|
369
|
-
|
|
370
|
-
```typescript
|
|
371
|
-
const db = new Database(taruviClient)
|
|
372
|
-
|
|
373
|
-
// Flat: one condition per call (chain for AND)
|
|
374
|
-
const filtered = await db
|
|
375
|
-
.from("accounts")
|
|
376
|
-
.filters("status", "eq", "active")
|
|
377
|
-
.filters("country", "eq", "USA")
|
|
378
|
-
.execute()
|
|
379
31
|
|
|
380
|
-
|
|
381
|
-
const advanced = await db
|
|
382
|
-
.from("accounts")
|
|
383
|
-
.filters("age", "gte", 18)
|
|
384
|
-
.filters("age", "lt", 65)
|
|
385
|
-
.filters("name", "icontains", "john")
|
|
386
|
-
.filters("created_at", "gte", "2024-01-01")
|
|
387
|
-
.orderBy("created_at", "desc")
|
|
388
|
-
.execute()
|
|
32
|
+
const db = new Database(client)
|
|
389
33
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
.
|
|
34
|
+
const response = await db
|
|
35
|
+
.from('accounts')
|
|
36
|
+
.filters('status', 'eq', 'active')
|
|
37
|
+
.orderBy('created_at', 'desc')
|
|
393
38
|
.page(1)
|
|
394
39
|
.pageSize(20)
|
|
395
40
|
.execute()
|
|
396
|
-
|
|
397
|
-
// JSON tree → `?filters=<url-encoded JSON>` (e.g. from Refine / your UI)
|
|
398
|
-
import type { BackendFilterTreeRoot } from "@taruvi/sdk"
|
|
399
|
-
const tree: BackendFilterTreeRoot = [
|
|
400
|
-
{
|
|
401
|
-
operator: "and",
|
|
402
|
-
value: [
|
|
403
|
-
{ field: "is_active", operator: "eq", value: true },
|
|
404
|
-
{ field: "hire_date", operator: "lt", value: "2021-01-01" },
|
|
405
|
-
],
|
|
406
|
-
},
|
|
407
|
-
]
|
|
408
|
-
await db.from("employees").filters(tree).execute()
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
### Populate related records
|
|
412
|
-
|
|
413
|
-
Use `populate()` to eager load related records (foreign key relationships):
|
|
414
|
-
|
|
415
|
-
```typescript
|
|
416
|
-
const db = new Database(taruviClient)
|
|
417
|
-
|
|
418
|
-
// Populate a single relation
|
|
419
|
-
const orders = await db
|
|
420
|
-
.from("orders")
|
|
421
|
-
.populate(["customer"])
|
|
422
|
-
.execute()
|
|
423
|
-
|
|
424
|
-
// Each order now includes the full customer object instead of just the ID
|
|
425
|
-
console.log(orders.data[0].customer.name)
|
|
426
|
-
console.log(orders.data[0].customer.email)
|
|
427
|
-
|
|
428
|
-
// Populate multiple relations
|
|
429
|
-
const invoices = await db
|
|
430
|
-
.from("invoices")
|
|
431
|
-
.populate(["customer", "created_by", "items"])
|
|
432
|
-
.execute()
|
|
433
|
-
|
|
434
|
-
// Combine with flat filters + populate
|
|
435
|
-
const recentOrders = await db
|
|
436
|
-
.from("orders")
|
|
437
|
-
.filters("status", "eq", "completed")
|
|
438
|
-
.filters("created_at", "gte", "2024-01-01")
|
|
439
|
-
.orderBy("created_at", "desc")
|
|
440
|
-
.populate(["customer", "product"])
|
|
441
|
-
.execute()
|
|
442
|
-
|
|
443
|
-
// Combine with pagination + populate
|
|
444
|
-
const paginatedOrders = await db
|
|
445
|
-
.from("orders")
|
|
446
|
-
.page(1)
|
|
447
|
-
.pageSize(10)
|
|
448
|
-
.populate(["customer"])
|
|
449
|
-
.execute()
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
### Complete CRUD Example (CRM Table)
|
|
453
|
-
|
|
454
|
-
```typescript
|
|
455
|
-
import { useEffect, useState } from 'react'
|
|
456
|
-
import { Database } from '@taruvi/sdk'
|
|
457
|
-
|
|
458
|
-
export default function CrmTable({ taruviClient }) {
|
|
459
|
-
const [contacts, setContacts] = useState([])
|
|
460
|
-
|
|
461
|
-
const fetchContacts = async () => {
|
|
462
|
-
const db = new Database(taruviClient)
|
|
463
|
-
const response = await db.from("accounts").execute()
|
|
464
|
-
if (response.data) {
|
|
465
|
-
setContacts(response.data)
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
useEffect(() => {
|
|
470
|
-
fetchContacts()
|
|
471
|
-
}, [])
|
|
472
|
-
|
|
473
|
-
const handleCreate = async (data) => {
|
|
474
|
-
const db = new Database(taruviClient)
|
|
475
|
-
await db.from("accounts").create(data).execute()
|
|
476
|
-
fetchContacts() // Refresh
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
const handleDelete = async (id) => {
|
|
480
|
-
const db = new Database(taruviClient)
|
|
481
|
-
await db.from("accounts").delete(id).execute()
|
|
482
|
-
fetchContacts() // Refresh
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
const handleUpdate = async (id, data) => {
|
|
486
|
-
const db = new Database(taruviClient)
|
|
487
|
-
await db.from("accounts").get(id).update(data).execute()
|
|
488
|
-
fetchContacts() // Refresh
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
return (
|
|
492
|
-
<div>
|
|
493
|
-
<button onClick={() => handleCreate({ name: 'New Contact', status: 'active' })}>
|
|
494
|
-
Add Contact
|
|
495
|
-
</button>
|
|
496
|
-
<table>
|
|
497
|
-
{contacts.map(contact => (
|
|
498
|
-
<tr key={contact.id}>
|
|
499
|
-
<td>{contact.name}</td>
|
|
500
|
-
<td>
|
|
501
|
-
<button onClick={() => handleUpdate(contact.id, { status: 'updated' })}>
|
|
502
|
-
Edit
|
|
503
|
-
</button>
|
|
504
|
-
<button onClick={() => handleDelete(contact.id)}>
|
|
505
|
-
Delete
|
|
506
|
-
</button>
|
|
507
|
-
</td>
|
|
508
|
-
</tr>
|
|
509
|
-
))}
|
|
510
|
-
</table>
|
|
511
|
-
</div>
|
|
512
|
-
)
|
|
513
|
-
}
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
---
|
|
517
|
-
|
|
518
|
-
## Storage Service (File Management)
|
|
519
|
-
|
|
520
|
-
### List Files in Bucket
|
|
521
|
-
|
|
522
|
-
```typescript
|
|
523
|
-
import { Storage } from '@taruvi/sdk'
|
|
524
|
-
|
|
525
|
-
const storage = new Storage(taruviClient)
|
|
526
|
-
const files = await storage.from("documents").execute()
|
|
527
|
-
|
|
528
|
-
console.log(files.data) // Array of file objects
|
|
529
|
-
```
|
|
530
|
-
|
|
531
|
-
### Upload Files
|
|
532
|
-
|
|
533
|
-
```typescript
|
|
534
|
-
const storage = new Storage(taruviClient)
|
|
535
|
-
|
|
536
|
-
const filesData = {
|
|
537
|
-
files: [file1, file2], // File objects from input
|
|
538
|
-
metadatas: [{ name: "file1" }, { name: "file2" }],
|
|
539
|
-
paths: ["file1.pdf", "file2.pdf"]
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
await storage.from("documents").upload(filesData).execute()
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
### Download File
|
|
546
|
-
|
|
547
|
-
```typescript
|
|
548
|
-
const storage = new Storage(taruviClient)
|
|
549
|
-
const file = await storage.from("documents").download("path/to/file.pdf").execute()
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
### Delete Files
|
|
553
|
-
|
|
554
|
-
```typescript
|
|
555
|
-
const storage = new Storage(taruviClient)
|
|
556
|
-
|
|
557
|
-
// Delete single file
|
|
558
|
-
await storage.from("documents").delete(["path/to/file.pdf"]).execute()
|
|
559
|
-
|
|
560
|
-
// Delete multiple files
|
|
561
|
-
await storage.from("documents").delete([
|
|
562
|
-
"path/to/file1.pdf",
|
|
563
|
-
"path/to/file2.pdf",
|
|
564
|
-
"path/to/file3.pdf"
|
|
565
|
-
]).execute()
|
|
566
41
|
```
|
|
567
42
|
|
|
568
|
-
|
|
43
|
+
## Documentation
|
|
569
44
|
|
|
570
|
-
|
|
571
|
-
const storage = new Storage(taruviClient)
|
|
572
|
-
await storage
|
|
573
|
-
.from("documents")
|
|
574
|
-
.update("path/to/file.pdf", {
|
|
575
|
-
visibility: "public",
|
|
576
|
-
metadata: { category: "reports" }
|
|
577
|
-
})
|
|
578
|
-
.execute()
|
|
579
|
-
```
|
|
45
|
+
Full guides live in **[docs/](docs/README.md)**:
|
|
580
46
|
|
|
581
|
-
|
|
47
|
+
| Guide | Topic |
|
|
48
|
+
|-------|--------|
|
|
49
|
+
| [Introduction](docs/01-introduction.md) | Purpose, setup, DI, backend-handled login flow |
|
|
50
|
+
| [Builder pattern](docs/02-builder-pattern.md) | Immutable queries and execution |
|
|
51
|
+
| [Architecture](docs/03-architecture.md) | `lib` vs `lib-internal`, request flow |
|
|
52
|
+
| [Clients](docs/04-clients.md) | What each service does |
|
|
53
|
+
| [API reference](docs/05-api-reference.md) | All public methods |
|
|
54
|
+
| [Examples](docs/06-examples.md) | Chaining patterns and workflows |
|
|
55
|
+
| [Advanced & troubleshooting](docs/07-advanced-topics.md) | Typing, filters, CORS, packaging |
|
|
56
|
+
| [Releases & branches](docs/08-releases-and-branches.md) | Stable vs beta, CI/CD publish workflow |
|
|
582
57
|
|
|
583
|
-
|
|
584
|
-
const storage = new Storage(taruviClient)
|
|
58
|
+
## Services at a glance
|
|
585
59
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
60
|
+
| Client | Use for |
|
|
61
|
+
|--------|---------|
|
|
62
|
+
| `Database` | Table CRUD, filters, pagination, graph/edges |
|
|
63
|
+
| `Storage` | Bucket files: list, upload, download, delete |
|
|
64
|
+
| `Auth` | Browser login/signup/logout, session token |
|
|
65
|
+
| `User` | User admin, roles, preferences |
|
|
66
|
+
| `Functions` | Invoke serverless functions |
|
|
67
|
+
| `Analytics` | Run predefined analytics queries |
|
|
68
|
+
| `Settings` | Site metadata, user attribute schema |
|
|
69
|
+
| `Secrets` | Read secret values |
|
|
70
|
+
| `Policy` | Permission checks |
|
|
71
|
+
| `App` | App roles and settings |
|
|
597
72
|
|
|
598
|
-
|
|
599
|
-
const advanced = await storage
|
|
600
|
-
.from("documents")
|
|
601
|
-
.filter({
|
|
602
|
-
// Size filters (bytes)
|
|
603
|
-
size__gte: 1024, // >= 1KB
|
|
604
|
-
size__lte: 10485760, // <= 10MB
|
|
73
|
+
## What's included
|
|
605
74
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
75
|
+
- **Immutable builder pattern** for `Database`, `Storage`, `App`, and `Secrets.get()` — each chain step returns a new instance so parallel queries never overwrite each other's URLs or filters.
|
|
76
|
+
- **Session authentication** via `X-Session-Token`; automatic token extraction from URL hash after OAuth redirect in the browser.
|
|
77
|
+
- **Typed errors** — `AuthError`, `NotFoundError`, `ValidationError`, and others exported from the package.
|
|
609
78
|
|
|
610
|
-
|
|
611
|
-
filename__icontains: "report",
|
|
612
|
-
file__startswith: "invoice",
|
|
613
|
-
prefix: "uploads/2024/",
|
|
79
|
+
## Development
|
|
614
80
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
// Visibility & user filters
|
|
620
|
-
visibility: "public",
|
|
621
|
-
created_by_me: true,
|
|
622
|
-
created_by__username: "john",
|
|
623
|
-
|
|
624
|
-
// Pagination & sorting
|
|
625
|
-
page: 1,
|
|
626
|
-
pageSize: 50,
|
|
627
|
-
ordering: "-created_at" // Sort by created_at descending
|
|
628
|
-
})
|
|
629
|
-
.execute()
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
### Complete File Upload Example
|
|
633
|
-
|
|
634
|
-
```typescript
|
|
635
|
-
import { useState, useRef } from 'react'
|
|
636
|
-
import { Storage } from '@taruvi/sdk'
|
|
637
|
-
|
|
638
|
-
export default function FileUploader({ taruviClient }) {
|
|
639
|
-
const [files, setFiles] = useState([])
|
|
640
|
-
const [uploading, setUploading] = useState(false)
|
|
641
|
-
const fileInputRef = useRef(null)
|
|
642
|
-
|
|
643
|
-
const handleFileSelect = (e) => {
|
|
644
|
-
const selectedFiles = Array.from(e.target.files)
|
|
645
|
-
setFiles(selectedFiles)
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
const uploadFiles = async () => {
|
|
649
|
-
if (files.length === 0) return
|
|
650
|
-
|
|
651
|
-
setUploading(true)
|
|
652
|
-
try {
|
|
653
|
-
const storage = new Storage(taruviClient)
|
|
654
|
-
|
|
655
|
-
const uploadData = {
|
|
656
|
-
files: files,
|
|
657
|
-
metadatas: files.map(f => ({ name: f.name })),
|
|
658
|
-
paths: files.map(f => f.name)
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
await storage.from("documents").upload(uploadData).execute()
|
|
662
|
-
|
|
663
|
-
alert("Upload successful!")
|
|
664
|
-
setFiles([])
|
|
665
|
-
} catch (error) {
|
|
666
|
-
alert("Upload failed: " + error.message)
|
|
667
|
-
} finally {
|
|
668
|
-
setUploading(false)
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
return (
|
|
673
|
-
<div>
|
|
674
|
-
<input
|
|
675
|
-
ref={fileInputRef}
|
|
676
|
-
type="file"
|
|
677
|
-
multiple
|
|
678
|
-
onChange={handleFileSelect}
|
|
679
|
-
/>
|
|
680
|
-
<button onClick={uploadFiles} disabled={uploading || files.length === 0}>
|
|
681
|
-
{uploading ? "Uploading..." : `Upload ${files.length} file(s)`}
|
|
682
|
-
</button>
|
|
683
|
-
</div>
|
|
684
|
-
)
|
|
685
|
-
}
|
|
686
|
-
```
|
|
687
|
-
|
|
688
|
-
---
|
|
689
|
-
|
|
690
|
-
## Functions Service (Serverless)
|
|
691
|
-
|
|
692
|
-
### Execute Function (Sync)
|
|
693
|
-
|
|
694
|
-
```typescript
|
|
695
|
-
import { Functions } from '@taruvi/sdk'
|
|
696
|
-
|
|
697
|
-
const functions = new Functions(taruviClient)
|
|
698
|
-
|
|
699
|
-
const result = await functions.execute("my-function", {
|
|
700
|
-
async: false,
|
|
701
|
-
params: {
|
|
702
|
-
key1: "value1",
|
|
703
|
-
key2: 123
|
|
704
|
-
}
|
|
705
|
-
})
|
|
706
|
-
|
|
707
|
-
console.log(result.data) // Function response
|
|
708
|
-
```
|
|
709
|
-
|
|
710
|
-
### Execute Function (Async)
|
|
711
|
-
|
|
712
|
-
```typescript
|
|
713
|
-
const functions = new Functions(taruviClient)
|
|
714
|
-
|
|
715
|
-
const result = await functions.execute("long-running-task", {
|
|
716
|
-
async: true,
|
|
717
|
-
params: { data: "value" }
|
|
718
|
-
})
|
|
719
|
-
|
|
720
|
-
console.log(result.invocation.invocation_id) // Track async execution
|
|
721
|
-
console.log(result.invocation.celery_task_id)
|
|
722
|
-
console.log(result.invocation.status)
|
|
723
|
-
```
|
|
724
|
-
|
|
725
|
-
### Complete Function Executor Example
|
|
726
|
-
|
|
727
|
-
```typescript
|
|
728
|
-
import { useState } from 'react'
|
|
729
|
-
import { Functions } from '@taruvi/sdk'
|
|
730
|
-
|
|
731
|
-
export default function FunctionExecutor({ taruviClient }) {
|
|
732
|
-
const [functionSlug, setFunctionSlug] = useState("")
|
|
733
|
-
const [params, setParams] = useState({})
|
|
734
|
-
const [isAsync, setIsAsync] = useState(false)
|
|
735
|
-
const [result, setResult] = useState(null)
|
|
736
|
-
const [loading, setLoading] = useState(false)
|
|
737
|
-
|
|
738
|
-
const executeFunction = async () => {
|
|
739
|
-
setLoading(true)
|
|
740
|
-
try {
|
|
741
|
-
const functions = new Functions(taruviClient)
|
|
742
|
-
const response = await functions.execute(functionSlug, {
|
|
743
|
-
async: isAsync,
|
|
744
|
-
params: params
|
|
745
|
-
})
|
|
746
|
-
setResult(response)
|
|
747
|
-
} catch (error) {
|
|
748
|
-
console.error(error)
|
|
749
|
-
} finally {
|
|
750
|
-
setLoading(false)
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
return (
|
|
755
|
-
<div>
|
|
756
|
-
<input
|
|
757
|
-
placeholder="Function slug"
|
|
758
|
-
value={functionSlug}
|
|
759
|
-
onChange={(e) => setFunctionSlug(e.target.value)}
|
|
760
|
-
/>
|
|
761
|
-
<label>
|
|
762
|
-
<input
|
|
763
|
-
type="checkbox"
|
|
764
|
-
checked={isAsync}
|
|
765
|
-
onChange={(e) => setIsAsync(e.target.checked)}
|
|
766
|
-
/>
|
|
767
|
-
Async
|
|
768
|
-
</label>
|
|
769
|
-
<button onClick={executeFunction} disabled={loading}>
|
|
770
|
-
Execute
|
|
771
|
-
</button>
|
|
772
|
-
{result && <pre>{JSON.stringify(result, null, 2)}</pre>}
|
|
773
|
-
</div>
|
|
774
|
-
)
|
|
775
|
-
}
|
|
776
|
-
```
|
|
777
|
-
|
|
778
|
-
---
|
|
779
|
-
|
|
780
|
-
## Analytics Service
|
|
781
|
-
|
|
782
|
-
### Execute Analytics Query
|
|
783
|
-
|
|
784
|
-
```typescript
|
|
785
|
-
import { Analytics } from '@taruvi/sdk'
|
|
786
|
-
|
|
787
|
-
const analytics = new Analytics(taruviClient)
|
|
788
|
-
|
|
789
|
-
const result = await analytics.execute("monthly-sales-report", {
|
|
790
|
-
params: {
|
|
791
|
-
start_date: "2024-01-01",
|
|
792
|
-
end_date: "2024-12-31"
|
|
793
|
-
}
|
|
794
|
-
})
|
|
795
|
-
|
|
796
|
-
console.log(result.data) // Analytics query result
|
|
797
|
-
```
|
|
798
|
-
|
|
799
|
-
### Execute with Typed Response
|
|
800
|
-
|
|
801
|
-
```typescript
|
|
802
|
-
interface SalesData {
|
|
803
|
-
total_sales: number
|
|
804
|
-
orders_count: number
|
|
805
|
-
average_order_value: number
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
const analytics = new Analytics(taruviClient)
|
|
809
|
-
|
|
810
|
-
const result = await analytics.execute<SalesData>("sales-summary", {
|
|
811
|
-
params: { period: "monthly" }
|
|
812
|
-
})
|
|
813
|
-
|
|
814
|
-
console.log(result.data?.total_sales)
|
|
815
|
-
console.log(result.data?.orders_count)
|
|
816
|
-
```
|
|
817
|
-
|
|
818
|
-
### Complete Analytics Dashboard Example
|
|
819
|
-
|
|
820
|
-
```typescript
|
|
821
|
-
import { useEffect, useState } from 'react'
|
|
822
|
-
import { Analytics } from '@taruvi/sdk'
|
|
823
|
-
|
|
824
|
-
export default function AnalyticsDashboard({ taruviClient }) {
|
|
825
|
-
const [metrics, setMetrics] = useState(null)
|
|
826
|
-
const [loading, setLoading] = useState(true)
|
|
827
|
-
|
|
828
|
-
useEffect(() => {
|
|
829
|
-
const fetchMetrics = async () => {
|
|
830
|
-
try {
|
|
831
|
-
const analytics = new Analytics(taruviClient)
|
|
832
|
-
const result = await analytics.execute("dashboard-metrics", {
|
|
833
|
-
params: {
|
|
834
|
-
date_range: "last_30_days"
|
|
835
|
-
}
|
|
836
|
-
})
|
|
837
|
-
setMetrics(result.data)
|
|
838
|
-
} catch (error) {
|
|
839
|
-
console.error("Failed to fetch analytics:", error)
|
|
840
|
-
} finally {
|
|
841
|
-
setLoading(false)
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
fetchMetrics()
|
|
845
|
-
}, [])
|
|
846
|
-
|
|
847
|
-
if (loading) return <div>Loading analytics...</div>
|
|
848
|
-
|
|
849
|
-
return (
|
|
850
|
-
<div>
|
|
851
|
-
<h2>Dashboard Metrics</h2>
|
|
852
|
-
<pre>{JSON.stringify(metrics, null, 2)}</pre>
|
|
853
|
-
</div>
|
|
854
|
-
)
|
|
855
|
-
}
|
|
856
|
-
```
|
|
857
|
-
|
|
858
|
-
---
|
|
859
|
-
|
|
860
|
-
## Settings Service
|
|
861
|
-
|
|
862
|
-
### Get Site Settings
|
|
863
|
-
|
|
864
|
-
```typescript
|
|
865
|
-
import { Settings } from '@taruvi/sdk'
|
|
866
|
-
|
|
867
|
-
const settings = new Settings(taruviClient)
|
|
868
|
-
const config = await settings.get()
|
|
869
|
-
|
|
870
|
-
console.log(config) // Site configuration object
|
|
871
|
-
```
|
|
872
|
-
|
|
873
|
-
---
|
|
874
|
-
|
|
875
|
-
## Secrets Service
|
|
876
|
-
|
|
877
|
-
### List Secrets
|
|
878
|
-
|
|
879
|
-
```typescript
|
|
880
|
-
import { Secrets } from '@taruvi/sdk'
|
|
881
|
-
|
|
882
|
-
const secrets = new Secrets(taruviClient)
|
|
883
|
-
const result = await secrets.list().execute()
|
|
884
|
-
|
|
885
|
-
console.log(result.items) // Array of secrets
|
|
886
|
-
```
|
|
887
|
-
|
|
888
|
-
### Get Secret
|
|
889
|
-
|
|
890
|
-
```typescript
|
|
891
|
-
const secrets = new Secrets(taruviClient)
|
|
892
|
-
const secret = await secrets.get("MY_SECRET_KEY").execute()
|
|
893
|
-
|
|
894
|
-
console.log(secret) // Secret object with value
|
|
895
|
-
```
|
|
896
|
-
|
|
897
|
-
### Update Secret
|
|
898
|
-
|
|
899
|
-
```typescript
|
|
900
|
-
const secrets = new Secrets(taruviClient)
|
|
901
|
-
|
|
902
|
-
await secrets.update("MY_SECRET", {
|
|
903
|
-
value: "my-secret-value"
|
|
904
|
-
}).execute()
|
|
905
|
-
```
|
|
906
|
-
|
|
907
|
-
---
|
|
908
|
-
|
|
909
|
-
## Policy Service (Resource Permissions)
|
|
910
|
-
|
|
911
|
-
### Check Resource Permissions
|
|
912
|
-
|
|
913
|
-
```typescript
|
|
914
|
-
import { Policy } from '@taruvi/sdk'
|
|
915
|
-
|
|
916
|
-
const policy = new Policy(taruviClient)
|
|
917
|
-
|
|
918
|
-
// Check permissions for multiple resources
|
|
919
|
-
const result = await policy.checkResource([
|
|
920
|
-
{
|
|
921
|
-
entityType: "crm",
|
|
922
|
-
tableName: "accounts",
|
|
923
|
-
recordId: "record-123",
|
|
924
|
-
attributes: { owner_id: "user-456" },
|
|
925
|
-
actions: ["read", "update"]
|
|
926
|
-
},
|
|
927
|
-
{
|
|
928
|
-
entityType: "docs",
|
|
929
|
-
tableName: "documents",
|
|
930
|
-
recordId: "doc-789",
|
|
931
|
-
attributes: {},
|
|
932
|
-
actions: ["delete"]
|
|
933
|
-
}
|
|
934
|
-
])
|
|
935
|
-
```
|
|
936
|
-
|
|
937
|
-
### Complete Permission Check Example
|
|
938
|
-
|
|
939
|
-
```typescript
|
|
940
|
-
import { useState, useEffect } from 'react'
|
|
941
|
-
import { Policy } from '@taruvi/sdk'
|
|
942
|
-
|
|
943
|
-
export default function ResourceGuard({ taruviClient, children, resource }) {
|
|
944
|
-
const [allowed, setAllowed] = useState(false)
|
|
945
|
-
const [loading, setLoading] = useState(true)
|
|
946
|
-
|
|
947
|
-
useEffect(() => {
|
|
948
|
-
const checkPermission = async () => {
|
|
949
|
-
try {
|
|
950
|
-
const policy = new Policy(taruviClient)
|
|
951
|
-
const result = await policy.checkResource([
|
|
952
|
-
{
|
|
953
|
-
entityType: resource.entityType,
|
|
954
|
-
tableName: resource.table,
|
|
955
|
-
recordId: resource.id,
|
|
956
|
-
attributes: resource.attributes || {},
|
|
957
|
-
actions: ["read"]
|
|
958
|
-
}
|
|
959
|
-
])
|
|
960
|
-
setAllowed(result.allowed)
|
|
961
|
-
} catch (error) {
|
|
962
|
-
console.error("Permission check failed:", error)
|
|
963
|
-
setAllowed(false)
|
|
964
|
-
} finally {
|
|
965
|
-
setLoading(false)
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
checkPermission()
|
|
969
|
-
}, [resource])
|
|
970
|
-
|
|
971
|
-
if (loading) return <div>Checking permissions...</div>
|
|
972
|
-
if (!allowed) return <div>Access denied</div>
|
|
973
|
-
return children
|
|
974
|
-
}
|
|
975
|
-
```
|
|
976
|
-
|
|
977
|
-
---
|
|
978
|
-
|
|
979
|
-
## App Service
|
|
980
|
-
|
|
981
|
-
### Get App Roles
|
|
982
|
-
|
|
983
|
-
```typescript
|
|
984
|
-
import { App } from '@taruvi/sdk'
|
|
985
|
-
|
|
986
|
-
const app = new App(taruviClient)
|
|
987
|
-
const roles = await app.roles().execute()
|
|
988
|
-
|
|
989
|
-
console.log(roles) // Array of role objects with id, name, permissions
|
|
990
|
-
```
|
|
991
|
-
|
|
992
|
-
### Complete Roles List Example
|
|
993
|
-
|
|
994
|
-
```typescript
|
|
995
|
-
import { useEffect, useState } from 'react'
|
|
996
|
-
import { App } from '@taruvi/sdk'
|
|
997
|
-
|
|
998
|
-
export default function RolesList({ taruviClient }) {
|
|
999
|
-
const [roles, setRoles] = useState([])
|
|
1000
|
-
const [loading, setLoading] = useState(true)
|
|
1001
|
-
|
|
1002
|
-
useEffect(() => {
|
|
1003
|
-
const fetchRoles = async () => {
|
|
1004
|
-
try {
|
|
1005
|
-
const app = new App(taruviClient)
|
|
1006
|
-
const response = await app.roles().execute()
|
|
1007
|
-
setRoles(response.data || [])
|
|
1008
|
-
} catch (error) {
|
|
1009
|
-
console.error("Failed to fetch roles:", error)
|
|
1010
|
-
} finally {
|
|
1011
|
-
setLoading(false)
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
fetchRoles()
|
|
1015
|
-
}, [])
|
|
1016
|
-
|
|
1017
|
-
if (loading) return <div>Loading roles...</div>
|
|
1018
|
-
|
|
1019
|
-
return (
|
|
1020
|
-
<ul>
|
|
1021
|
-
{roles.map(role => (
|
|
1022
|
-
<li key={role.id}>
|
|
1023
|
-
<strong>{role.name}</strong>
|
|
1024
|
-
<span>{role.permissions?.join(", ")}</span>
|
|
1025
|
-
</li>
|
|
1026
|
-
))}
|
|
1027
|
-
</ul>
|
|
1028
|
-
)
|
|
1029
|
-
}
|
|
1030
|
-
```
|
|
1031
|
-
|
|
1032
|
-
---
|
|
1033
|
-
|
|
1034
|
-
## Common Patterns
|
|
1035
|
-
|
|
1036
|
-
### Loading States
|
|
1037
|
-
|
|
1038
|
-
```typescript
|
|
1039
|
-
const [data, setData] = useState(null)
|
|
1040
|
-
const [loading, setLoading] = useState(true)
|
|
1041
|
-
const [error, setError] = useState(null)
|
|
1042
|
-
|
|
1043
|
-
useEffect(() => {
|
|
1044
|
-
const fetchData = async () => {
|
|
1045
|
-
setLoading(true)
|
|
1046
|
-
try {
|
|
1047
|
-
const db = new Database(taruviClient)
|
|
1048
|
-
const response = await db.from("table").execute()
|
|
1049
|
-
setData(response.data)
|
|
1050
|
-
} catch (err) {
|
|
1051
|
-
setError(err.message)
|
|
1052
|
-
} finally {
|
|
1053
|
-
setLoading(false)
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
fetchData()
|
|
1057
|
-
}, [])
|
|
1058
|
-
|
|
1059
|
-
if (loading) return <div>Loading...</div>
|
|
1060
|
-
if (error) return <div>Error: {error}</div>
|
|
1061
|
-
return <div>{/* Render data */}</div>
|
|
1062
|
-
```
|
|
1063
|
-
|
|
1064
|
-
### Error Handling
|
|
1065
|
-
|
|
1066
|
-
```typescript
|
|
1067
|
-
try {
|
|
1068
|
-
const user = new User(taruviClient)
|
|
1069
|
-
await user.createUser(data)
|
|
1070
|
-
} catch (error) {
|
|
1071
|
-
if (error.response?.status === 400) {
|
|
1072
|
-
console.error("Validation error:", error.response.data)
|
|
1073
|
-
} else if (error.response?.status === 401) {
|
|
1074
|
-
console.error("Unauthorized")
|
|
1075
|
-
} else {
|
|
1076
|
-
console.error("Unknown error:", error.message)
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
```
|
|
1080
|
-
|
|
1081
|
-
### Refresh Data After Mutation
|
|
1082
|
-
|
|
1083
|
-
```typescript
|
|
1084
|
-
const [items, setItems] = useState([])
|
|
1085
|
-
|
|
1086
|
-
const fetchItems = async () => {
|
|
1087
|
-
const db = new Database(taruviClient)
|
|
1088
|
-
const response = await db.from("items").execute()
|
|
1089
|
-
setItems(response.data || [])
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
const createItem = async (data) => {
|
|
1093
|
-
const db = new Database(taruviClient)
|
|
1094
|
-
await db.from("items").create(data).execute()
|
|
1095
|
-
await fetchItems() // Refresh list
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
const deleteItem = async (id) => {
|
|
1099
|
-
const db = new Database(taruviClient)
|
|
1100
|
-
await db.from("items").delete(id).execute()
|
|
1101
|
-
await fetchItems() // Refresh list
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
const updateItem = async (id, data) => {
|
|
1105
|
-
const db = new Database(taruviClient)
|
|
1106
|
-
await db.from("items").get(id).update(data).execute()
|
|
1107
|
-
await fetchItems() // Refresh list
|
|
1108
|
-
}
|
|
1109
|
-
```
|
|
1110
|
-
|
|
1111
|
-
---
|
|
1112
|
-
|
|
1113
|
-
## TypeScript Types
|
|
1114
|
-
|
|
1115
|
-
### Import Types
|
|
1116
|
-
|
|
1117
|
-
```typescript
|
|
1118
|
-
import type {
|
|
1119
|
-
TaruviConfig,
|
|
1120
|
-
AuthTokens,
|
|
1121
|
-
UserCreateRequest,
|
|
1122
|
-
UserResponse,
|
|
1123
|
-
UserDataResponse,
|
|
1124
|
-
FunctionRequest,
|
|
1125
|
-
FunctionResponse,
|
|
1126
|
-
FunctionInvocation,
|
|
1127
|
-
DatabaseRequest,
|
|
1128
|
-
DatabaseResponse,
|
|
1129
|
-
DatabaseFilters,
|
|
1130
|
-
StorageRequest,
|
|
1131
|
-
StorageUpdateRequest,
|
|
1132
|
-
StorageResponse,
|
|
1133
|
-
StorageFilters,
|
|
1134
|
-
SettingsResponse,
|
|
1135
|
-
SecretRequest,
|
|
1136
|
-
SecretResponse,
|
|
1137
|
-
Principal,
|
|
1138
|
-
Resource,
|
|
1139
|
-
Resources,
|
|
1140
|
-
RoleResponse,
|
|
1141
|
-
AnalyticsRequest,
|
|
1142
|
-
AnalyticsResponse
|
|
1143
|
-
} from '@taruvi/sdk'
|
|
1144
|
-
```
|
|
1145
|
-
|
|
1146
|
-
### Type Usage
|
|
1147
|
-
|
|
1148
|
-
```typescript
|
|
1149
|
-
const config: TaruviConfig = {
|
|
1150
|
-
apiKey: "key",
|
|
1151
|
-
appSlug: "app",
|
|
1152
|
-
apiUrl: "https://api.taruvi.cloud",
|
|
1153
|
-
deskUrl: "https://desk.taruvi.cloud", // optional
|
|
1154
|
-
token: "existing-token" // optional
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
const userData: UserCreateRequest = {
|
|
1158
|
-
username: "john",
|
|
1159
|
-
email: "john@example.com",
|
|
1160
|
-
password: "pass123",
|
|
1161
|
-
confirm_password: "pass123",
|
|
1162
|
-
first_name: "John",
|
|
1163
|
-
last_name: "Doe",
|
|
1164
|
-
is_active: true,
|
|
1165
|
-
is_staff: false,
|
|
1166
|
-
attributes: ""
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
// Database filters with operators
|
|
1170
|
-
const dbFilters: DatabaseFilters = {
|
|
1171
|
-
page: 1,
|
|
1172
|
-
pageSize: 20,
|
|
1173
|
-
ordering: "-created_at",
|
|
1174
|
-
status: "active",
|
|
1175
|
-
age__gte: 18,
|
|
1176
|
-
name__icontains: "john"
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
// Storage filters with comprehensive options
|
|
1180
|
-
const storageFilters: StorageFilters = {
|
|
1181
|
-
page: 1,
|
|
1182
|
-
pageSize: 50,
|
|
1183
|
-
search: "invoice",
|
|
1184
|
-
visibility: "public",
|
|
1185
|
-
mimetype_category: "document",
|
|
1186
|
-
size__gte: 1024,
|
|
1187
|
-
size__lte: 10485760,
|
|
1188
|
-
created_at__gte: "2024-01-01",
|
|
1189
|
-
ordering: "-created_at",
|
|
1190
|
-
created_by_me: true
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
// Policy types for permission checking
|
|
1194
|
-
const principal: Principal = {
|
|
1195
|
-
id: "user-123",
|
|
1196
|
-
roles: ["admin", "editor"],
|
|
1197
|
-
attr: { department: "engineering" }
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
const resources: Resources = [
|
|
1201
|
-
{
|
|
1202
|
-
entityType: "crm",
|
|
1203
|
-
tableName: "accounts",
|
|
1204
|
-
recordId: "acc-456",
|
|
1205
|
-
attributes: { owner_id: "user-123" },
|
|
1206
|
-
actions: ["read", "update", "delete"]
|
|
1207
|
-
}
|
|
1208
|
-
]
|
|
1209
|
-
```
|
|
1210
|
-
|
|
1211
|
-
---
|
|
1212
|
-
|
|
1213
|
-
## Filter Operators Reference
|
|
1214
|
-
|
|
1215
|
-
### Database Filter Operators (Django-style)
|
|
1216
|
-
|
|
1217
|
-
The Database service supports Django-style field lookups:
|
|
1218
|
-
|
|
1219
|
-
| Operator | Description | Example |
|
|
1220
|
-
|----------|-------------|---------|
|
|
1221
|
-
| `field` | Exact match | `{ status: "active" }` |
|
|
1222
|
-
| `field__gte` | Greater than or equal | `{ age__gte: 18 }` |
|
|
1223
|
-
| `field__gt` | Greater than | `{ age__gt: 17 }` |
|
|
1224
|
-
| `field__lte` | Less than or equal | `{ age__lte: 65 }` |
|
|
1225
|
-
| `field__lt` | Less than | `{ age__lt: 66 }` |
|
|
1226
|
-
| `field__icontains` | Case-insensitive contains | `{ name__icontains: "john" }` |
|
|
1227
|
-
| `field__contains` | Case-sensitive contains | `{ name__contains: "John" }` |
|
|
1228
|
-
| `field__istartswith` | Case-insensitive starts with | `{ email__istartswith: "admin" }` |
|
|
1229
|
-
| `field__startswith` | Case-sensitive starts with | `{ code__startswith: "PRE" }` |
|
|
1230
|
-
| `field__iendswith` | Case-insensitive ends with | `{ domain__iendswith: ".com" }` |
|
|
1231
|
-
| `field__endswith` | Case-sensitive ends with | `{ filename__endswith: ".pdf" }` |
|
|
1232
|
-
| `field__in` | Value in list | `{ status__in: ["active", "pending"] }` |
|
|
1233
|
-
| `field__isnull` | Is null check | `{ deleted_at__isnull: true }` |
|
|
1234
|
-
| `ordering` | Sort results | `{ ordering: "-created_at" }` (- for desc) |
|
|
1235
|
-
| `page` | Page number | `{ page: 1 }` |
|
|
1236
|
-
| `pageSize` | Items per page | `{ pageSize: 20 }` |
|
|
1237
|
-
|
|
1238
|
-
### Populate (Eager Loading)
|
|
1239
|
-
|
|
1240
|
-
Use the `populate()` method to eager load related records:
|
|
1241
|
-
|
|
1242
|
-
```typescript
|
|
1243
|
-
// Populate accepts an array of relation field names
|
|
1244
|
-
db.from("orders").populate(["customer", "items"]).execute()
|
|
1245
|
-
|
|
1246
|
-
// This adds ?populate=customer,items to the query string
|
|
1247
|
-
```
|
|
1248
|
-
|
|
1249
|
-
| Parameter | Type | Description |
|
|
1250
|
-
|-----------|------|-------------|
|
|
1251
|
-
| `populate` | `string[]` | Array of relation field names to eager load |
|
|
1252
|
-
|
|
1253
|
-
### Storage Filter Options
|
|
1254
|
-
|
|
1255
|
-
The Storage service supports these specialized filters:
|
|
1256
|
-
|
|
1257
|
-
| Category | Filters | Description |
|
|
1258
|
-
|----------|---------|-------------|
|
|
1259
|
-
| **Size** | `size__gte`, `size__lte`, `size__gt`, `size__lt`, `min_size`, `max_size` | Filter by file size in bytes |
|
|
1260
|
-
| **Dates** | `created_at__gte`, `created_at__lte`, `created_after`, `created_before`, `updated_at__gte`, `updated_at__lte` | Filter by dates (ISO 8601 format) |
|
|
1261
|
-
| **Search** | `search`, `filename__icontains`, `prefix`, `file`, `file__icontains`, `file__startswith`, `file__istartswith`, `metadata_search` | Search for files |
|
|
1262
|
-
| **MIME Type** | `mimetype`, `mimetype__in`, `mimetype_category` | Filter by file type (document, image, video, audio, etc.) |
|
|
1263
|
-
| **Visibility** | `visibility` | Filter by public/private visibility |
|
|
1264
|
-
| **User** | `created_by_me`, `modified_by_me`, `created_by__username`, `created_by__username__icontains` | Filter by user |
|
|
1265
|
-
| **Pagination** | `page`, `pageSize` | Paginate results |
|
|
1266
|
-
| **Sorting** | `ordering` | Sort results (e.g., "-created_at") |
|
|
1267
|
-
|
|
1268
|
-
---
|
|
1269
|
-
|
|
1270
|
-
## Quick Reference
|
|
1271
|
-
|
|
1272
|
-
| Service | Import | Purpose |
|
|
1273
|
-
|---------|--------|---------|
|
|
1274
|
-
| `Client` | `import { Client }` | Main SDK client |
|
|
1275
|
-
| `Auth` | `import { Auth }` | Authentication |
|
|
1276
|
-
| `User` | `import { User }` | User management |
|
|
1277
|
-
| `Database` | `import { Database }` | App data CRUD |
|
|
1278
|
-
| `Storage` | `import { Storage }` | File management |
|
|
1279
|
-
| `Functions` | `import { Functions }` | Serverless functions |
|
|
1280
|
-
| `Settings` | `import { Settings }` | Site configuration |
|
|
1281
|
-
| `Secrets` | `import { Secrets }` | Sensitive data |
|
|
1282
|
-
| `Policy` | `import { Policy }` | Resource permissions |
|
|
1283
|
-
| `App` | `import { App }` | App roles & config |
|
|
1284
|
-
| `Analytics` | `import { Analytics }` | Analytics queries |
|
|
1285
|
-
|
|
1286
|
-
---
|
|
1287
|
-
|
|
1288
|
-
## Chaining Pattern
|
|
1289
|
-
|
|
1290
|
-
All query-building services use method chaining:
|
|
1291
|
-
|
|
1292
|
-
```typescript
|
|
1293
|
-
// Database
|
|
1294
|
-
const db = new Database(taruviClient)
|
|
1295
|
-
await db.from("table").get("id").update(data).execute()
|
|
1296
|
-
await db.from("table").filters("status", "eq", "active").execute()
|
|
1297
|
-
await db.from("table").page(1).populate(["related_field"]).execute()
|
|
1298
|
-
await db.from("table").create({ name: "New" }).execute()
|
|
1299
|
-
|
|
1300
|
-
// Storage
|
|
1301
|
-
const storage = new Storage(taruviClient)
|
|
1302
|
-
await storage.from("bucket").delete(["path/to/file.pdf"]).execute()
|
|
1303
|
-
await storage.from("bucket").filter({ search: "query" }).execute()
|
|
1304
|
-
await storage.from("bucket").download("path/to/file.pdf").execute()
|
|
81
|
+
```bash
|
|
82
|
+
npm run build # TypeScript compile
|
|
83
|
+
npm test # Vitest unit tests
|
|
1305
84
|
```
|
|
1306
85
|
|
|
1307
|
-
|
|
86
|
+
## Additional resources
|
|
1308
87
|
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
## Environment Variables
|
|
1312
|
-
|
|
1313
|
-
```env
|
|
1314
|
-
VITE_TARUVI_API_KEY=your-api-key
|
|
1315
|
-
VITE_TARUVI_APP_SLUG=your-app
|
|
1316
|
-
VITE_TARUVI_API_URL=https://taruvi-site.taruvi.cloud
|
|
1317
|
-
```
|
|
1318
|
-
|
|
1319
|
-
```typescript
|
|
1320
|
-
const client = new Client({
|
|
1321
|
-
apiKey: import.meta.env.VITE_TARUVI_API_KEY,
|
|
1322
|
-
appSlug: import.meta.env.VITE_TARUVI_APP_SLUG,
|
|
1323
|
-
apiUrl: import.meta.env.VITE_TARUVI_API_URL
|
|
1324
|
-
})
|
|
1325
|
-
```
|
|
88
|
+
- [SDK design context](SDK_DESIGN_CONTEXT.md) — backend API contract notes (SDK ↔ API mapping)
|
|
1326
89
|
|
|
1327
|
-
|
|
90
|
+
## Author
|
|
1328
91
|
|
|
1329
|
-
|
|
92
|
+
Curran C Doddabele · EOX Vantage
|