@taruvi/sdk 1.0.6 → 1.0.8

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.
@@ -8,6 +8,16 @@
8
8
  npm install @taruvi-io/sdk
9
9
  ```
10
10
 
11
+ ## What's New
12
+
13
+ Recent updates to the SDK:
14
+
15
+ - **Auth Service**: New Web UI Flow with `login()`, `signup()`, `logout()` methods that redirect to backend pages. Token refresh with rotation support, `getCurrentUser()` for JWT decoding.
16
+ - **Database Service**: Added `create()` method for creating records. Comprehensive filter support with Django-style operators (`__gte`, `__lte`, `__icontains`, etc.).
17
+ - **Storage Service**: Added `download()` method. Enhanced filter support with size, date, MIME type, visibility filters. `delete()` now accepts array of paths for bulk deletion.
18
+ - **Client**: Automatic token extraction from URL hash after OAuth callback - no manual token handling needed.
19
+ - **Types**: Comprehensive `StorageFilters` and `DatabaseFilters` interfaces with full operator support.
20
+
11
21
  ## Core Setup
12
22
 
13
23
  ### 1. Initialize Client
@@ -38,6 +48,23 @@ export default function MyPage({ taruviClient }: MyPageProps) {
38
48
  }
39
49
  ```
40
50
 
51
+ ### 3. Automatic Token Handling
52
+
53
+ The Client automatically extracts authentication tokens from URL hash after OAuth callback:
54
+
55
+ ```typescript
56
+ // After login redirect, URL contains:
57
+ // #session_token=xxx&access_token=yyy&refresh_token=zzz&expires_in=172800&token_type=Bearer
58
+
59
+ // Client automatically:
60
+ // 1. Extracts tokens from URL hash
61
+ // 2. Stores them in TokenClient
62
+ // 3. Clears the hash from URL
63
+
64
+ const taruviClient = new Client({ apiKey, appSlug, baseUrl })
65
+ // Tokens are now available automatically if present in URL hash
66
+ ```
67
+
41
68
  ---
42
69
 
43
70
  ## Auth Service
@@ -48,10 +75,10 @@ export default function MyPage({ taruviClient }: MyPageProps) {
48
75
  import { Auth } from '@taruvi-io/sdk'
49
76
 
50
77
  const auth = new Auth(taruviClient)
51
- const isAuthenticated = await auth.isUserAuthenticated() // Returns boolean
78
+ const isAuthenticated = auth.isUserAuthenticated() // Returns boolean (synchronous)
52
79
  ```
53
80
 
54
- ### Login Flow
81
+ ### Login Flow (Web UI Flow with Redirect)
55
82
 
56
83
  ```typescript
57
84
  import { useEffect } from "react"
@@ -61,23 +88,81 @@ export default function Login({ taruviClient }) {
61
88
  const auth = new Auth(taruviClient)
62
89
 
63
90
  useEffect(() => {
64
- const checkAuthAndRedirect = async () => {
65
- const isAuthenticated = await auth.isUserAuthenticated()
66
-
67
- if (isAuthenticated) {
68
- window.location.href = "/dashboard"
69
- } else {
70
- await auth.authenticateUser()
71
- }
91
+ const isAuthenticated = auth.isUserAuthenticated()
92
+
93
+ if (isAuthenticated) {
94
+ window.location.href = "/dashboard"
95
+ } else {
96
+ // Redirects to backend login page, then returns with tokens in URL hash
97
+ auth.login() // Optional: pass callback URL
72
98
  }
73
-
74
- checkAuthAndRedirect()
75
99
  }, [])
76
100
 
77
101
  return <div>Checking authentication...</div>
78
102
  }
79
103
  ```
80
104
 
105
+ ### Signup Flow
106
+
107
+ ```typescript
108
+ import { Auth } from '@taruvi-io/sdk'
109
+
110
+ const auth = new Auth(taruviClient)
111
+
112
+ // Redirect to signup page (Web UI Flow)
113
+ auth.signup() // Optional: pass callback URL
114
+ auth.signup("/dashboard") // Redirect to dashboard after signup
115
+ ```
116
+
117
+ ### Logout
118
+
119
+ ```typescript
120
+ import { Auth } from '@taruvi-io/sdk'
121
+
122
+ const auth = new Auth(taruviClient)
123
+
124
+ // Clear tokens and redirect to logout page
125
+ auth.logout() // Optional: pass callback URL
126
+ auth.logout("/") // Redirect to home after logout
127
+ ```
128
+
129
+ ### Get Current User
130
+
131
+ ```typescript
132
+ import { Auth } from '@taruvi-io/sdk'
133
+
134
+ const auth = new Auth(taruviClient)
135
+
136
+ // Get user info from decoded JWT access token
137
+ const user = auth.getCurrentUser()
138
+ console.log(user?.user_id)
139
+ console.log(user?.username)
140
+ console.log(user?.email)
141
+ ```
142
+
143
+ ### Token Management
144
+
145
+ ```typescript
146
+ import { Auth } from '@taruvi-io/sdk'
147
+
148
+ const auth = new Auth(taruviClient)
149
+
150
+ // Get tokens
151
+ const accessToken = auth.getAccessToken()
152
+ const refreshToken = auth.getRefreshToken()
153
+
154
+ // Check if token is expired
155
+ const isExpired = auth.isTokenExpired()
156
+
157
+ // Refresh access token (returns new access AND refresh tokens due to rotation)
158
+ const newTokens = await auth.refreshAccessToken()
159
+ if (newTokens) {
160
+ console.log(newTokens.access)
161
+ console.log(newTokens.refresh)
162
+ console.log(newTokens.expires_in)
163
+ }
164
+ ```
165
+
81
166
  ---
82
167
 
83
168
  ## User Service
@@ -217,6 +302,17 @@ const db = new Database(taruviClient)
217
302
  const record = await db.from("accounts").get("record-id").execute()
218
303
  ```
219
304
 
305
+ ### Create Record
306
+
307
+ ```typescript
308
+ const db = new Database(taruviClient)
309
+ await db.from("accounts").create({
310
+ name: "John Doe",
311
+ email: "john@example.com",
312
+ status: "active"
313
+ }).execute()
314
+ ```
315
+
220
316
  ### Update Record
221
317
 
222
318
  ```typescript
@@ -238,6 +334,8 @@ await db.from("accounts").delete("record-id").execute()
238
334
 
239
335
  ```typescript
240
336
  const db = new Database(taruviClient)
337
+
338
+ // Simple field filters
241
339
  const filtered = await db
242
340
  .from("accounts")
243
341
  .filter({
@@ -245,6 +343,27 @@ const filtered = await db
245
343
  country: "USA"
246
344
  })
247
345
  .execute()
346
+
347
+ // Advanced filters with operators
348
+ const advanced = await db
349
+ .from("accounts")
350
+ .filter({
351
+ age__gte: 18, // age >= 18
352
+ age__lt: 65, // age < 65
353
+ name__icontains: "john", // case-insensitive contains
354
+ created_at__gte: "2024-01-01",
355
+ ordering: "-created_at" // Sort by created_at descending
356
+ })
357
+ .execute()
358
+
359
+ // Pagination
360
+ const paginated = await db
361
+ .from("accounts")
362
+ .filter({
363
+ page: 1,
364
+ pageSize: 20
365
+ })
366
+ .execute()
248
367
  ```
249
368
 
250
369
  ### Complete CRUD Example (CRM Table)
@@ -268,6 +387,12 @@ export default function CrmTable({ taruviClient }) {
268
387
  fetchContacts()
269
388
  }, [])
270
389
 
390
+ const handleCreate = async (data) => {
391
+ const db = new Database(taruviClient)
392
+ await db.from("accounts").create(data).execute()
393
+ fetchContacts() // Refresh
394
+ }
395
+
271
396
  const handleDelete = async (id) => {
272
397
  const db = new Database(taruviClient)
273
398
  await db.from("accounts").delete(id).execute()
@@ -281,21 +406,26 @@ export default function CrmTable({ taruviClient }) {
281
406
  }
282
407
 
283
408
  return (
284
- <table>
285
- {contacts.map(contact => (
286
- <tr key={contact.id}>
287
- <td>{contact.name}</td>
288
- <td>
289
- <button onClick={() => handleUpdate(contact.id, { status: 'updated' })}>
290
- Edit
291
- </button>
292
- <button onClick={() => handleDelete(contact.id)}>
293
- Delete
294
- </button>
295
- </td>
296
- </tr>
297
- ))}
298
- </table>
409
+ <div>
410
+ <button onClick={() => handleCreate({ name: 'New Contact', status: 'active' })}>
411
+ Add Contact
412
+ </button>
413
+ <table>
414
+ {contacts.map(contact => (
415
+ <tr key={contact.id}>
416
+ <td>{contact.name}</td>
417
+ <td>
418
+ <button onClick={() => handleUpdate(contact.id, { status: 'updated' })}>
419
+ Edit
420
+ </button>
421
+ <button onClick={() => handleDelete(contact.id)}>
422
+ Delete
423
+ </button>
424
+ </td>
425
+ </tr>
426
+ ))}
427
+ </table>
428
+ </div>
299
429
  )
300
430
  }
301
431
  ```
@@ -309,7 +439,7 @@ export default function CrmTable({ taruviClient }) {
309
439
  ```typescript
310
440
  import { Storage } from '@taruvi-io/sdk'
311
441
 
312
- const storage = new Storage(taruviClient, {})
442
+ const storage = new Storage(taruviClient)
313
443
  const files = await storage.from("documents").execute()
314
444
 
315
445
  console.log(files.data) // Array of file objects
@@ -318,7 +448,7 @@ console.log(files.data) // Array of file objects
318
448
  ### Upload Files
319
449
 
320
450
  ```typescript
321
- const storage = new Storage(taruviClient, {})
451
+ const storage = new Storage(taruviClient)
322
452
 
323
453
  const filesData = {
324
454
  files: [file1, file2], // File objects from input
@@ -329,17 +459,33 @@ const filesData = {
329
459
  await storage.from("documents").upload(filesData).execute()
330
460
  ```
331
461
 
332
- ### Delete File
462
+ ### Download File
333
463
 
334
464
  ```typescript
335
- const storage = new Storage(taruviClient, {})
336
- await storage.from("documents").delete("path/to/file.pdf").execute()
465
+ const storage = new Storage(taruviClient)
466
+ const file = await storage.from("documents").download("path/to/file.pdf").execute()
467
+ ```
468
+
469
+ ### Delete Files
470
+
471
+ ```typescript
472
+ const storage = new Storage(taruviClient)
473
+
474
+ // Delete single file
475
+ await storage.from("documents").delete(["path/to/file.pdf"]).execute()
476
+
477
+ // Delete multiple files
478
+ await storage.from("documents").delete([
479
+ "path/to/file1.pdf",
480
+ "path/to/file2.pdf",
481
+ "path/to/file3.pdf"
482
+ ]).execute()
337
483
  ```
338
484
 
339
485
  ### Update File Metadata
340
486
 
341
487
  ```typescript
342
- const storage = new Storage(taruviClient, {})
488
+ const storage = new Storage(taruviClient)
343
489
  await storage
344
490
  .from("documents")
345
491
  .update("path/to/file.pdf", {
@@ -353,7 +499,9 @@ await storage
353
499
  ### Filter Files
354
500
 
355
501
  ```typescript
356
- const storage = new Storage(taruviClient, {})
502
+ const storage = new Storage(taruviClient)
503
+
504
+ // Basic filters
357
505
  const filtered = await storage
358
506
  .from("documents")
359
507
  .filter({
@@ -364,6 +512,39 @@ const filtered = await storage
364
512
  ordering: "-created_at"
365
513
  })
366
514
  .execute()
515
+
516
+ // Advanced filters with operators
517
+ const advanced = await storage
518
+ .from("documents")
519
+ .filter({
520
+ // Size filters (bytes)
521
+ size__gte: 1024, // >= 1KB
522
+ size__lte: 10485760, // <= 10MB
523
+
524
+ // Date filters (ISO 8601)
525
+ created_at__gte: "2024-01-01",
526
+ created_at__lte: "2024-12-31",
527
+
528
+ // Search filters
529
+ filename__icontains: "report",
530
+ file__startswith: "invoice",
531
+ prefix: "uploads/2024/",
532
+
533
+ // MIME type filters
534
+ mimetype: "application/pdf",
535
+ mimetype_category: "image", // image, document, video, audio, etc.
536
+
537
+ // Visibility & user filters
538
+ visibility: "public",
539
+ created_by_me: true,
540
+ created_by__username: "john",
541
+
542
+ // Pagination & sorting
543
+ page: 1,
544
+ pageSize: 50,
545
+ ordering: "-created_at" // Sort by created_at descending
546
+ })
547
+ .execute()
367
548
  ```
368
549
 
369
550
  ### Complete File Upload Example
@@ -387,8 +568,8 @@ export default function FileUploader({ taruviClient }) {
387
568
 
388
569
  setUploading(true)
389
570
  try {
390
- const storage = new Storage(taruviClient, {})
391
-
571
+ const storage = new Storage(taruviClient)
572
+
392
573
  const uploadData = {
393
574
  files: files,
394
575
  metadatas: files.map(f => ({ name: f.name })),
@@ -396,7 +577,7 @@ export default function FileUploader({ taruviClient }) {
396
577
  }
397
578
 
398
579
  await storage.from("documents").upload(uploadData).execute()
399
-
580
+
400
581
  alert("Upload successful!")
401
582
  setFiles([])
402
583
  } catch (error) {
@@ -626,6 +807,12 @@ const fetchItems = async () => {
626
807
  setItems(response.data || [])
627
808
  }
628
809
 
810
+ const createItem = async (data) => {
811
+ const db = new Database(taruviClient)
812
+ await db.from("items").create(data).execute()
813
+ await fetchItems() // Refresh list
814
+ }
815
+
629
816
  const deleteItem = async (id) => {
630
817
  const db = new Database(taruviClient)
631
818
  await db.from("items").delete(id).execute()
@@ -646,15 +833,25 @@ const updateItem = async (id, data) => {
646
833
  ### Import Types
647
834
 
648
835
  ```typescript
649
- import type {
836
+ import type {
650
837
  TaruviConfig,
838
+ AuthTokens,
651
839
  UserCreateRequest,
652
840
  UserResponse,
653
841
  UserDataResponse,
654
842
  FunctionRequest,
655
843
  FunctionResponse,
844
+ FunctionInvocation,
845
+ DatabaseRequest,
846
+ DatabaseResponse,
847
+ DatabaseFilters,
848
+ StorageRequest,
849
+ StorageUpdateRequest,
850
+ StorageResponse,
656
851
  StorageFilters,
657
- DatabaseFilters
852
+ SettingsResponse,
853
+ SecretRequest,
854
+ SecretResponse
658
855
  } from '@taruvi-io/sdk'
659
856
  ```
660
857
 
@@ -664,7 +861,9 @@ import type {
664
861
  const config: TaruviConfig = {
665
862
  apiKey: "key",
666
863
  appSlug: "app",
667
- baseUrl: "https://api.taruvi.cloud"
864
+ baseUrl: "https://api.taruvi.cloud",
865
+ deskUrl: "https://desk.taruvi.cloud", // optional
866
+ token: "existing-token" // optional
668
867
  }
669
868
 
670
869
  const userData: UserCreateRequest = {
@@ -673,12 +872,80 @@ const userData: UserCreateRequest = {
673
872
  password: "pass123",
674
873
  confirm_password: "pass123",
675
874
  first_name: "John",
676
- last_name: "Doe"
875
+ last_name: "Doe",
876
+ is_active: true,
877
+ is_staff: false
878
+ }
879
+
880
+ // Database filters with operators
881
+ const dbFilters: DatabaseFilters = {
882
+ page: 1,
883
+ pageSize: 20,
884
+ ordering: "-created_at",
885
+ status: "active",
886
+ age__gte: 18,
887
+ name__icontains: "john"
888
+ }
889
+
890
+ // Storage filters with comprehensive options
891
+ const storageFilters: StorageFilters = {
892
+ page: 1,
893
+ pageSize: 50,
894
+ search: "invoice",
895
+ visibility: "public",
896
+ mimetype_category: "document",
897
+ size__gte: 1024,
898
+ size__lte: 10485760,
899
+ created_at__gte: "2024-01-01",
900
+ ordering: "-created_at",
901
+ created_by_me: true
677
902
  }
678
903
  ```
679
904
 
680
905
  ---
681
906
 
907
+ ## Filter Operators Reference
908
+
909
+ ### Database Filter Operators (Django-style)
910
+
911
+ The Database service supports Django-style field lookups:
912
+
913
+ | Operator | Description | Example |
914
+ |----------|-------------|---------|
915
+ | `field` | Exact match | `{ status: "active" }` |
916
+ | `field__gte` | Greater than or equal | `{ age__gte: 18 }` |
917
+ | `field__gt` | Greater than | `{ age__gt: 17 }` |
918
+ | `field__lte` | Less than or equal | `{ age__lte: 65 }` |
919
+ | `field__lt` | Less than | `{ age__lt: 66 }` |
920
+ | `field__icontains` | Case-insensitive contains | `{ name__icontains: "john" }` |
921
+ | `field__contains` | Case-sensitive contains | `{ name__contains: "John" }` |
922
+ | `field__istartswith` | Case-insensitive starts with | `{ email__istartswith: "admin" }` |
923
+ | `field__startswith` | Case-sensitive starts with | `{ code__startswith: "PRE" }` |
924
+ | `field__iendswith` | Case-insensitive ends with | `{ domain__iendswith: ".com" }` |
925
+ | `field__endswith` | Case-sensitive ends with | `{ filename__endswith: ".pdf" }` |
926
+ | `field__in` | Value in list | `{ status__in: ["active", "pending"] }` |
927
+ | `field__isnull` | Is null check | `{ deleted_at__isnull: true }` |
928
+ | `ordering` | Sort results | `{ ordering: "-created_at" }` (- for desc) |
929
+ | `page` | Page number | `{ page: 1 }` |
930
+ | `pageSize` | Items per page | `{ pageSize: 20 }` |
931
+
932
+ ### Storage Filter Options
933
+
934
+ The Storage service supports these specialized filters:
935
+
936
+ | Category | Filters | Description |
937
+ |----------|---------|-------------|
938
+ | **Size** | `size__gte`, `size__lte`, `size__gt`, `size__lt`, `min_size`, `max_size` | Filter by file size in bytes |
939
+ | **Dates** | `created_at__gte`, `created_at__lte`, `created_after`, `created_before`, `updated_at__gte`, `updated_at__lte` | Filter by dates (ISO 8601 format) |
940
+ | **Search** | `search`, `filename__icontains`, `prefix`, `file`, `file__icontains`, `file__startswith`, `file__istartswith`, `metadata_search` | Search for files |
941
+ | **MIME Type** | `mimetype`, `mimetype__in`, `mimetype_category` | Filter by file type (document, image, video, audio, etc.) |
942
+ | **Visibility** | `visibility` | Filter by public/private visibility |
943
+ | **User** | `created_by_me`, `modified_by_me`, `created_by__username`, `created_by__username__icontains` | Filter by user |
944
+ | **Pagination** | `page`, `pageSize` | Paginate results |
945
+ | **Sorting** | `ordering` | Sort results (e.g., "-created_at") |
946
+
947
+ ---
948
+
682
949
  ## Quick Reference
683
950
 
684
951
  | Service | Import | Purpose |
@@ -700,12 +967,16 @@ All query-building services use method chaining:
700
967
 
701
968
  ```typescript
702
969
  // Database
970
+ const db = new Database(taruviClient)
703
971
  await db.from("table").get("id").update(data).execute()
704
972
  await db.from("table").filter({ status: "active" }).execute()
973
+ await db.from("table").create({ name: "New" }).execute()
705
974
 
706
975
  // Storage
707
- await storage.from("bucket").delete("path").execute()
976
+ const storage = new Storage(taruviClient)
977
+ await storage.from("bucket").delete(["path/to/file.pdf"]).execute()
708
978
  await storage.from("bucket").filter({ search: "query" }).execute()
979
+ await storage.from("bucket").download("path/to/file.pdf").execute()
709
980
  ```
710
981
 
711
982
  **Always call `.execute()` at the end to run the query!**
@@ -730,4 +1001,4 @@ const client = new Client({
730
1001
 
731
1002
  ---
732
1003
 
733
- **Generated from production code examples • Last updated: 2025-12-15**
1004
+ **Generated from production code examples • Last updated: 2025-12-17**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taruvi/sdk",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Taruvi SDK",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -0,0 +1,24 @@
1
+ import type { Client } from "../../client.js"
2
+
3
+ export class App {
4
+
5
+ private client: Client
6
+ private urlParams: {}
7
+
8
+ constructor(client: Client, urlParams: {}, filters?: {}) {
9
+ this.client = client
10
+ this.urlParams = urlParams
11
+ }
12
+
13
+ roles() {
14
+ return new App(this.client, {...this.urlParams})
15
+ }
16
+
17
+ filter(filters: {}) {
18
+ return new App(this.client, { ...this.urlParams }, filters)
19
+ }
20
+
21
+ execute() {
22
+
23
+ }
24
+ }
File without changes
@@ -14,7 +14,7 @@ export class Database {
14
14
  private body: object | undefined
15
15
  private filters: DatabaseFilters | undefined
16
16
 
17
- constructor(client: Client, urlParams: UrlParams, operation?: HttpMethod | undefined, body?: object | undefined, filters?: DatabaseFilters) {
17
+ constructor(client: Client, urlParams: UrlParams = {}, operation?: HttpMethod | undefined, body?: object | undefined, filters?: DatabaseFilters) {
18
18
  this.client = client
19
19
  this.urlParams = urlParams
20
20
  this.operation = operation
@@ -40,7 +40,7 @@ export class Database {
40
40
  }
41
41
 
42
42
  update(body: any): Database {
43
- return new Database(this.client, this.urlParams = { ...this.urlParams }, HttpMethod.PUT, body)
43
+ return new Database(this.client, this.urlParams = { ...this.urlParams }, HttpMethod.PATCH, body)
44
44
  }
45
45
 
46
46
  delete(recordId?: any): Database {
@@ -67,7 +67,6 @@ export class Database {
67
67
  async execute() {
68
68
  // Build the API URL
69
69
  const url = this.buildRoute()
70
- // const fullUrl = `sites/eox_site/${url}` //remove for productions because baseurl is in the appscope, no need for site
71
70
 
72
71
  const operation = this.operation || HttpMethod.GET
73
72
 
@@ -75,11 +74,11 @@ export class Database {
75
74
  case HttpMethod.POST:
76
75
  return await this.client.httpClient.post(url, this.body)
77
76
 
78
- case HttpMethod.PUT:
77
+ case HttpMethod.PATCH:
79
78
  if (!this.urlParams.recordId) {
80
- throw new Error('PUT operation requires a record ID. Use .get(recordId) before .update()')
79
+ throw new Error('PATCH operation requires a record ID.')
81
80
  }
82
- return await this.client.httpClient.put(url, this.body)
81
+ return await this.client.httpClient.patch(url, this.body)
83
82
 
84
83
  case HttpMethod.DELETE:
85
84
  return await this.client.httpClient.delete(url)
@@ -15,7 +15,7 @@ export class Storage {
15
15
  private filters: StorageFilters | undefined
16
16
 
17
17
 
18
- constructor(client: Client, urlParams: BucketUrlParams, operation?: HttpMethod | undefined, body?: object, filters?: StorageFilters) {
18
+ constructor(client: Client, urlParams: BucketUrlParams = {} as BucketUrlParams, operation?: HttpMethod | undefined, body?: object, filters?: StorageFilters) {
19
19
  this.client = client
20
20
  this.urlParams = urlParams
21
21
  this.operation = operation