@taruvi/sdk 1.3.7 → 1.3.8-beta.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.
@@ -0,0 +1,57 @@
1
+ name: Publish to NPM
2
+
3
+ on:
4
+ push:
5
+ branches: [main, beta]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ with:
13
+ fetch-depth: 0
14
+
15
+ - uses: actions/setup-node@v4
16
+ with:
17
+ node-version: '20'
18
+ registry-url: 'https://registry.npmjs.org'
19
+
20
+ - name: Get secrets from Infisical
21
+ uses: Infisical/secrets-action@v1.0.7
22
+ with:
23
+ client-id: ${{ secrets.INFISICAL_CLIENT_ID }}
24
+ client-secret: ${{ secrets.INFISICAL_CLIENT_SECRET }}
25
+ env-slug: taruvi-test
26
+ project-slug: taruvi-qmc-d
27
+ domain: "https://environment.eoxvantage.com"
28
+
29
+ - run: npm install
30
+
31
+ - run: npm test
32
+
33
+ - name: Check if version changed
34
+ id: version_check
35
+ run: |
36
+ CURRENT_VERSION=$(node -p "require('./package.json').version")
37
+ echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
38
+
39
+ if git tag | grep -q "^v${CURRENT_VERSION}$"; then
40
+ echo "changed=false" >> $GITHUB_OUTPUT
41
+ else
42
+ echo "changed=true" >> $GITHUB_OUTPUT
43
+ fi
44
+
45
+ - name: Tag release
46
+ if: steps.version_check.outputs.changed == 'true'
47
+ run: |
48
+ git config user.name "github-actions[bot]"
49
+ git config user.email "github-actions[bot]@users.noreply.github.com"
50
+ git tag "v${{ steps.version_check.outputs.current_version }}"
51
+ git push origin "v${{ steps.version_check.outputs.current_version }}"
52
+
53
+ - name: Publish (beta)
54
+ if: steps.version_check.outputs.changed == 'true'
55
+ run: npm publish --access public --tag beta
56
+ env:
57
+ NODE_AUTH_TOKEN: ${{ env.NPM_TOKEN }}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taruvi/sdk",
3
- "version": "1.3.7",
3
+ "version": "1.3.8-beta.0",
4
4
  "description": "Taruvi SDK",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -13,17 +13,23 @@ export interface UserCreateRequest {
13
13
  }
14
14
 
15
15
  export interface UserCreateResponse {
16
- id: number
17
- uuid: string
16
+ id: string
18
17
  username: string
19
18
  email: string
20
19
  first_name: string
21
20
  last_name: string
21
+ full_name: string
22
22
  is_active: boolean
23
- is_staff: boolean
24
23
  is_superuser: boolean
25
24
  is_deleted: boolean
26
25
  date_joined: string
26
+ last_login: string
27
+ groups: UserGroup[]
28
+ user_permissions: UserPermission[]
29
+ attributes: Record<string, unknown>
30
+ missing_attributes: string[]
31
+ roles: UserRole[]
32
+ icon_url: string | null
27
33
  }
28
34
 
29
35
  export interface UserGroup {
@@ -47,23 +53,23 @@ export interface UserRole {
47
53
  }
48
54
 
49
55
  export interface UserDataResponse {
50
- id: number
56
+ id: string
51
57
  username: string
52
58
  email: string
53
59
  first_name: string
54
60
  last_name: string
55
61
  full_name: string
56
62
  is_active: boolean
57
- is_staff: boolean
58
63
  is_superuser: boolean
59
64
  is_deleted: boolean
60
- date_joined: string // ISO 8601 date-time string
61
- last_login: string // ISO 8601 date-time string
65
+ date_joined: string
66
+ last_login: string
62
67
  groups: UserGroup[]
63
68
  user_permissions: UserPermission[]
64
69
  attributes: Record<string, unknown>
65
70
  missing_attributes: string[]
66
71
  roles: UserRole[]
72
+ icon_url: string | null
67
73
  }
68
74
 
69
75
  export interface UserUpdateRequest {
@@ -76,17 +82,23 @@ export interface UserUpdateRequest {
76
82
  }
77
83
 
78
84
  export interface UserUpdateResponse {
79
- id: number
80
- uuid: string
85
+ id: string
81
86
  username: string
82
87
  email: string
83
88
  first_name: string
84
89
  last_name: string
90
+ full_name: string
85
91
  is_active: boolean
86
- is_staff: boolean
87
92
  is_superuser: boolean
88
93
  is_deleted: boolean
89
94
  date_joined: string
95
+ last_login: string
96
+ groups: UserGroup[]
97
+ user_permissions: UserPermission[]
98
+ attributes: Record<string, unknown>
99
+ missing_attributes: string[]
100
+ roles: UserRole[]
101
+ icon_url: string | null
90
102
  }
91
103
 
92
104
  export interface UserList {
@@ -66,6 +66,34 @@ export class Database<T = Record<string, unknown>> {
66
66
  })
67
67
  }
68
68
 
69
+ search(query: string): Database<T> {
70
+ return new Database<T>(this.client, { ...this.urlParams }, undefined, undefined, {
71
+ ...this.queryParams,
72
+ search: query
73
+ })
74
+ }
75
+
76
+ aggregate(...expressions: string[]): Database<T> {
77
+ return new Database<T>(this.client, { ...this.urlParams }, undefined, undefined, {
78
+ ...this.queryParams,
79
+ _aggregate: expressions.join(',')
80
+ })
81
+ }
82
+
83
+ groupBy(...fields: string[]): Database<T> {
84
+ return new Database<T>(this.client, { ...this.urlParams }, undefined, undefined, {
85
+ ...this.queryParams,
86
+ _group_by: fields.join(',')
87
+ })
88
+ }
89
+
90
+ having(condition: string): Database<T> {
91
+ return new Database<T>(this.client, { ...this.urlParams }, undefined, undefined, {
92
+ ...this.queryParams,
93
+ _having: condition
94
+ })
95
+ }
96
+
69
97
  get(recordId: string): Database<T> {
70
98
  return new Database<T>(this.client, { ...this.urlParams, recordId }, HttpMethod.GET)
71
99
  }
@@ -4,23 +4,43 @@ import type { TaruviResponse } from "../../types.js"
4
4
 
5
5
  export type DatabaseOperation = HttpMethod
6
6
 
7
- // Filter operators matching Python SDK
7
+ // Filter operators matching backend FilterParams.OPERATORS (32 operators)
8
8
  export type FilterOperator =
9
+ // Comparison
9
10
  | 'eq' // Equal
10
11
  | 'ne' // Not equal
11
12
  | 'gt' // Greater than
12
13
  | 'gte' // Greater than or equal
13
14
  | 'lt' // Less than
14
15
  | 'lte' // Less than or equal
16
+ // IN operators
15
17
  | 'in' // In array
16
18
  | 'nin' // Not in array
17
- | 'contains' // String contains (case-sensitive)
18
- | 'icontains' // String contains (case-insensitive)
19
- | 'startswith' // String starts with (case-sensitive)
20
- | 'istartswith' // String starts with (case-insensitive)
21
- | 'endswith' // String ends with (case-sensitive)
22
- | 'iendswith' // String ends with (case-insensitive)
23
- | 'isnull' // Is null
19
+ | 'ina' // In array (case-insensitive)
20
+ | 'nina' // Not in array (case-insensitive)
21
+ // String contains
22
+ | 'contains' // Contains (case-sensitive)
23
+ | 'ncontains' // Not contains (case-sensitive)
24
+ | 'containss' // Contains (case-sensitive, strict)
25
+ | 'ncontainss' // Not contains (case-sensitive, strict)
26
+ | 'icontains' // Contains (case-insensitive)
27
+ | 'nicontains' // Not contains (case-insensitive)
28
+ // Starts with
29
+ | 'startswith' // Starts with (case-sensitive)
30
+ | 'nstartswith' // Not starts with (case-sensitive)
31
+ | 'startswiths' // Starts with (case-sensitive, strict)
32
+ | 'nstartswiths' // Not starts with (case-sensitive, strict)
33
+ // Ends with
34
+ | 'endswith' // Ends with (case-sensitive)
35
+ | 'nendswith' // Not ends with (case-sensitive)
36
+ | 'endswiths' // Ends with (case-sensitive, strict)
37
+ | 'nendswiths' // Not ends with (case-sensitive, strict)
38
+ // Range
39
+ | 'between' // Between two values
40
+ | 'nbetween' // Not between two values
41
+ // Null checks
42
+ | 'null' // Is null
43
+ | 'nnull' // Is not null
24
44
 
25
45
  export type SortOrder = 'asc' | 'desc'
26
46
 
@@ -9,7 +9,9 @@ export interface FunctionInvocation {
9
9
  function: number
10
10
  function_name: string
11
11
  function_slug: string
12
+ user_id: string | null
12
13
  user_username: string
14
+ user_email: string
13
15
  task_status: string
14
16
  trigger_type: string
15
17
  created_at: string
@@ -11,4 +11,12 @@ export class Settings {
11
11
  async get<T = unknown>(): Promise<T> {
12
12
  return await this.client.httpClient.get<T>(SettingsRoutes.metadata)
13
13
  }
14
+
15
+ async getUserAttributes<T = unknown>(): Promise<T> {
16
+ return await this.client.httpClient.get<T>(SettingsRoutes.userAttributes)
17
+ }
18
+
19
+ async updateUserAttributes<T = unknown>(schema: Record<string, unknown>): Promise<T> {
20
+ return await this.client.httpClient.post<T>(SettingsRoutes.userAttributes, schema)
21
+ }
14
22
  }
@@ -43,8 +43,8 @@ export interface StorageObject {
43
43
  visibility?: string
44
44
  created_at: string
45
45
  updated_at: string
46
- created_by?: number
47
- modified_by?: number
46
+ created_by?: string
47
+ modified_by?: string
48
48
  }
49
49
 
50
50
  // Response types - uses standard wrapper
@@ -54,10 +54,10 @@ export class User {
54
54
  }
55
55
 
56
56
  async getPreferences(): Promise<UserPreferencesResponse> {
57
- return await this.client.httpClient.get<UserPreferencesResponse>(UserRoutes.getPreferences())
57
+ return await this.client.httpClient.get<UserPreferencesResponse>(UserRoutes.preferences())
58
58
  }
59
59
 
60
60
  async updatePreferences(body: UserPreferencesUpdate): Promise<UserPreferencesResponse> {
61
- return await this.client.httpClient.put<UserPreferencesResponse>(UserRoutes.getPreferences(), body)
61
+ return await this.client.httpClient.put<UserPreferencesResponse>(UserRoutes.preferences(), body)
62
62
  }
63
63
  }
@@ -6,7 +6,7 @@ export const GraphRoutes = {
6
6
 
7
7
  export const GraphEdgeRoutes = {
8
8
  baseUrl: (appSlug: string) => `api/apps/${appSlug}`,
9
- edges: (tableName: string): string => `/datatables/${tableName}/edges`,
9
+ edges: (tableName: string): string => `/datatables/${tableName}_edges/data`,
10
10
  edgeId: (edgeId: string): string => `/${edgeId}`
11
11
  }
12
12
 
@@ -1,3 +1,4 @@
1
1
  export const SettingsRoutes = {
2
- metadata: "api/settings/metadata/"
2
+ metadata: "api/settings/metadata/",
3
+ userAttributes: "api/settings/user-attributes/"
3
4
  }
@@ -1,12 +1,12 @@
1
1
  export const UserRoutes = {
2
2
  baseUrl: "api/users/",
3
3
  getCurrentUser: () => `${UserRoutes.baseUrl}me/`,
4
+ preferences: () => `${UserRoutes.baseUrl}me/preferences/`,
4
5
  getUser: (username: string) => `${UserRoutes.baseUrl}${username}/`,
5
6
  updateUser: (username: string) => `${UserRoutes.baseUrl}${username}/`,
6
7
  deleteUser: (username: string) => `${UserRoutes.baseUrl}${username}/`,
7
8
  listUser: (filter: string) => `${UserRoutes.baseUrl}${filter}`,
8
9
  getUserApps: (username: string) => `${UserRoutes.baseUrl}${username}/apps/`,
9
- getPreferences: () => `${UserRoutes.baseUrl}me/preferences/`,
10
10
  assignRoles: () => `api/assign/roles/`,
11
11
  revokeRoles: () => `api/revoke/roles/`
12
12
  } as const
@@ -194,6 +194,22 @@ export class TokenClient {
194
194
  return parseInt(expiresAt) < Date.now()
195
195
  }
196
196
 
197
+ /**
198
+ * Set access token directly (e.g., from external storage)
199
+ */
200
+ setAccessToken(token: string): void {
201
+ if (this.browserRunTime) {
202
+ if (typeof window === 'undefined' || typeof localStorage === 'undefined') return
203
+ try {
204
+ localStorage.setItem(TokenClient.ACCESS_TOKEN_KEY, token)
205
+ } catch (err) {
206
+ console.error('Failed to set access token:', err)
207
+ }
208
+ } else {
209
+ this.serverToken = token
210
+ }
211
+ }
212
+
197
213
  /**
198
214
  * Update access token after refresh
199
215
  * ⚠️ IMPORTANT: Taruvi uses refresh token rotation
package/src/types.ts CHANGED
@@ -85,6 +85,14 @@ export interface DatabaseFilters {
85
85
  // Populate relations
86
86
  populate?: string
87
87
 
88
+ // Search (translates to search_vector__search on backend)
89
+ search?: string
90
+
91
+ // Aggregates
92
+ _aggregate?: string
93
+ _group_by?: string
94
+ _having?: string
95
+
88
96
  // Dynamic filters - allows any field with operators
89
97
  [key: string]: string | number | boolean | undefined
90
98
  }
@@ -182,19 +182,19 @@ describe('Database', () => {
182
182
 
183
183
  describe('first()', () => {
184
184
  it('returns first item from array', async () => {
185
- mockHttpClient.get.mockResolvedValue([{ id: '1' }, { id: '2' }])
185
+ mockHttpClient.get.mockResolvedValue({ data: [{ id: '1' }, { id: '2' }] })
186
186
  const result = await new Database(mockClient).from('accounts').first()
187
187
  expect(result).toEqual({ id: '1' })
188
188
  })
189
189
 
190
190
  it('returns null for empty array', async () => {
191
- mockHttpClient.get.mockResolvedValue([])
191
+ mockHttpClient.get.mockResolvedValue({ data: [] })
192
192
  const result = await new Database(mockClient).from('accounts').first()
193
193
  expect(result).toBeNull()
194
194
  })
195
195
 
196
196
  it('returns single item if not array', async () => {
197
- mockHttpClient.get.mockResolvedValue({ id: '1' })
197
+ mockHttpClient.get.mockResolvedValue({ data: { id: '1' } })
198
198
  const result = await new Database(mockClient).from('accounts').get('1').first()
199
199
  expect(result).toEqual({ id: '1' })
200
200
  })
@@ -202,13 +202,13 @@ describe('Database', () => {
202
202
 
203
203
  describe('count()', () => {
204
204
  it('returns array length', async () => {
205
- mockHttpClient.get.mockResolvedValue([{ id: '1' }, { id: '2' }, { id: '3' }])
205
+ mockHttpClient.get.mockResolvedValue({ data: [{ id: '1' }, { id: '2' }, { id: '3' }] })
206
206
  const result = await new Database(mockClient).from('accounts').count()
207
207
  expect(result).toBe(3)
208
208
  })
209
209
 
210
210
  it('returns 0 for non-array', async () => {
211
- mockHttpClient.get.mockResolvedValue({ id: '1' })
211
+ mockHttpClient.get.mockResolvedValue({ data: { id: '1' } })
212
212
  const result = await new Database(mockClient).from('accounts').get('1').count()
213
213
  expect(result).toBe(0)
214
214
  })
@@ -248,12 +248,12 @@ describe('Builder immutability', () => {
248
248
  mockHttpClient.post.mockResolvedValue({})
249
249
  const base = new Graph(mockClient).from('employees')
250
250
  const traversal = base.get('1').include('descendants')
251
- const edge = base.createEdge([{ from: 1, to: 2, type: 'manager' }])
251
+ const edge = base.create([{ from: 1, to: 2, type: 'manager' }])
252
252
 
253
253
  await traversal.execute()
254
254
  await edge.execute()
255
255
 
256
256
  expect(mockHttpClient.get.mock.calls[0][0]).toContain('/data/1/')
257
- expect(mockHttpClient.post.mock.calls[0][0]).toContain('/edges/')
257
+ expect(mockHttpClient.post.mock.calls[0][0]).toContain('_edges/data/')
258
258
  })
259
259
  })
@@ -123,50 +123,50 @@ describe('Graph', () => {
123
123
  })
124
124
 
125
125
  describe('edge CRUD', () => {
126
- it('listEdges() calls GET on edges route', async () => {
126
+ it('list() calls GET on edges route', async () => {
127
127
  mockHttpClient.get.mockResolvedValue({ edges: [], total: 0 })
128
- await new Graph(mockClient).from('employees').listEdges().execute()
129
- expect(mockHttpClient.get).toHaveBeenCalledWith('api/apps/test-app/datatables/employees/edges/')
128
+ await new Graph(mockClient).from('employees').list().execute()
129
+ expect(mockHttpClient.get).toHaveBeenCalledWith('api/apps/test-app/datatables/employees_edges/data/')
130
130
  })
131
131
 
132
- it('createEdge() calls POST with array of edges', async () => {
132
+ it('create() calls POST with array of edges', async () => {
133
133
  const edges = [
134
134
  { from: 5, to: 2, type: 'manager' },
135
135
  { from: 5, to: 3, type: 'dotted_line', metadata: { project: 'AI' } }
136
136
  ]
137
137
  mockHttpClient.post.mockResolvedValue({ status: 'success', data: edges, total: 2 })
138
- await new Graph(mockClient).from('employees').createEdge(edges).execute()
138
+ await new Graph(mockClient).from('employees').create(edges).execute()
139
139
  expect(mockHttpClient.post).toHaveBeenCalledWith(
140
- 'api/apps/test-app/datatables/employees/edges/',
140
+ 'api/apps/test-app/datatables/employees_edges/data/',
141
141
  edges
142
142
  )
143
143
  })
144
144
 
145
- it('createEdge() with metadata', async () => {
145
+ it('create() with metadata', async () => {
146
146
  const edges = [{ from: 5, to: 3, type: 'dotted_line', metadata: { percentage: 30 } }]
147
147
  mockHttpClient.post.mockResolvedValue({ status: 'success', data: edges, total: 1 })
148
- await new Graph(mockClient).from('employees').createEdge(edges).execute()
148
+ await new Graph(mockClient).from('employees').create(edges).execute()
149
149
  expect(mockHttpClient.post).toHaveBeenCalledWith(
150
- 'api/apps/test-app/datatables/employees/edges/',
150
+ 'api/apps/test-app/datatables/employees_edges/data/',
151
151
  edges
152
152
  )
153
153
  })
154
154
 
155
- it('updateEdge() calls PATCH with edge ID in URL', async () => {
155
+ it('update() calls PATCH with edge ID in URL', async () => {
156
156
  const edge = { from: 5, to: 3, type: 'dotted_line' }
157
157
  mockHttpClient.patch.mockResolvedValue({ id: 9, ...edge })
158
- await new Graph(mockClient).from('employees').updateEdge('9', edge).execute()
158
+ await new Graph(mockClient).from('employees').update('9', edge).execute()
159
159
  expect(mockHttpClient.patch).toHaveBeenCalledWith(
160
- 'api/apps/test-app/datatables/employees/edges/9/',
160
+ 'api/apps/test-app/datatables/employees_edges/data/9/',
161
161
  edge
162
162
  )
163
163
  })
164
164
 
165
- it('deleteEdge() calls DELETE with edge_ids object', async () => {
165
+ it('delete() calls DELETE with edge_ids object', async () => {
166
166
  mockHttpClient.delete.mockResolvedValue({ deleted: 2 })
167
- await new Graph(mockClient).from('employees').deleteEdge([9, 10]).execute()
167
+ await new Graph(mockClient).from('employees').delete([9, 10]).execute()
168
168
  expect(mockHttpClient.delete).toHaveBeenCalledWith(
169
- 'api/apps/test-app/datatables/employees/edges/',
169
+ 'api/apps/test-app/datatables/employees_edges/data/',
170
170
  { edge_ids: [9, 10] }
171
171
  )
172
172
  })
@@ -290,7 +290,7 @@ describe('Graph', () => {
290
290
  total: 2
291
291
  }
292
292
  mockHttpClient.get.mockResolvedValue(mockResponse)
293
- const result = await new Graph(mockClient).from('employees').listEdges().execute()
293
+ const result = await new Graph(mockClient).from('employees').list().execute()
294
294
  expect((result as any).edges).toHaveLength(2)
295
295
  expect((result as any).edges[0].type).toBe('manager')
296
296
  expect((result as any).total).toBe(2)
@@ -306,7 +306,7 @@ describe('Graph', () => {
306
306
  total: 1
307
307
  }
308
308
  mockHttpClient.post.mockResolvedValue(mockResponse)
309
- const result = await new Graph(mockClient).from('employees').createEdge([{ from: 5, to: 2, type: 'manager' }]).execute() as TaruviResponse<EdgeResponse[]>
309
+ const result = await new Graph(mockClient).from('employees').create([{ from: 5, to: 2, type: 'manager' }]).execute() as TaruviResponse<EdgeResponse[]>
310
310
  expect(result.data).toHaveLength(1)
311
311
  expect(result.data[0].id).toBe(10)
312
312
  })
@@ -314,7 +314,7 @@ describe('Graph', () => {
314
314
  it('returns updated edge matching EdgeResponse type', async () => {
315
315
  const mockResponse: EdgeResponse = { id: 9, from: 5, to: 3, type: 'dotted_line', metadata: {} }
316
316
  mockHttpClient.patch.mockResolvedValue(mockResponse)
317
- const result = await new Graph(mockClient).from('employees').updateEdge('9', { from: 5, to: 3, type: 'dotted_line' }).execute() as EdgeResponse
317
+ const result = await new Graph(mockClient).from('employees').update('9', { from: 5, to: 3, type: 'dotted_line' }).execute() as EdgeResponse
318
318
  expect(result.id).toBe(9)
319
319
  expect(result.type).toBe('dotted_line')
320
320
  })
@@ -322,7 +322,7 @@ describe('Graph', () => {
322
322
  it('returns delete count response', async () => {
323
323
  const mockResponse = { deleted: 3 }
324
324
  mockHttpClient.delete.mockResolvedValue(mockResponse)
325
- const result = await new Graph(mockClient).from('employees').deleteEdge([1, 2, 3]).execute()
325
+ const result = await new Graph(mockClient).from('employees').delete([1, 2, 3]).execute()
326
326
  expect((result as any).deleted).toBe(3)
327
327
  })
328
328
  })
@@ -148,7 +148,7 @@ describe('Storage', () => {
148
148
  mockHttpClient.get.mockResolvedValue(new Blob())
149
149
  await new Storage(mockClient).from('documents').download('folder/file name.pdf').execute()
150
150
  expect(mockHttpClient.get).toHaveBeenCalledWith(
151
- 'api/apps/test-app/storage/buckets/documents/objects/folder%2Ffile%20name.pdf/'
151
+ 'api/apps/test-app/storage/buckets/documents/objects/folder%2Ffile%20name.pdf/?metadata=true'
152
152
  )
153
153
  })
154
154