@taruvi/sdk 1.3.9 → 1.4.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taruvi/sdk",
3
- "version": "1.3.9",
3
+ "version": "1.4.1",
4
4
  "description": "Taruvi SDK",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
package/src/index.ts CHANGED
@@ -17,7 +17,6 @@ export { Secrets } from "./lib/secrets/SecretsClient.js"
17
17
  export { Policy } from "./lib/policy/PolicyClient.js"
18
18
  export { App } from "./lib/app/AppClient.js"
19
19
  export { Analytics } from "./lib/analytics/AnalyticsClient.js"
20
- export { Graph } from "./lib/graphs/GraphClient.js"
21
20
 
22
21
  // Export core types
23
22
  export type { TaruviConfig, TaruviResponse, PaginationInfo, StorageFilters, DatabaseFilters } from "./types.js"
@@ -27,7 +26,7 @@ export type { AuthTokens } from "./lib-internal/token/TokenClient.js"
27
26
  export type { UserCreateRequest, UserData, UserUpdateRequest, UserListFilters, UserApp, UserResponse, UserListResponse, UserAppsResponse, AssignRolesRequest, RevokeRolesRequest, RolesResponse, UserGroup, UserPermission, UserRole, UserPreferences, UserPreferencesUpdate, UserPreferencesResponse } from "./lib/users/types.js"
28
27
 
29
28
  // Policy types
30
- export type { Principal, Resource, Resources, PolicyCheckResult, PolicyCheckBatchResult, ResourceCheckResponse } from "./lib/policy/types.js"
29
+ export type { Principal, Resource, Resources, PolicyCheckResult, PolicyCheckBatchResult, ResourceCheckResponse, GetAllowedActionsOptions } from "./lib/policy/types.js"
31
30
 
32
31
  // App types
33
32
  export type { RoleData, AppSettingsData, RoleResponse, RolesListResponse, AppSettingsResponse } from "./lib/app/types.js"
@@ -36,7 +35,7 @@ export type { RoleData, AppSettingsData, RoleResponse, RolesListResponse, AppSet
36
35
  export type { FunctionRequest, FunctionResponse, FunctionInvocation } from "./lib/functions/types.js"
37
36
 
38
37
  // Database types
39
- export type { DatabaseRequest, DatabaseResponse, DatabaseSingleResponse, FilterOperator, SortOrder } from "./lib/database/types.js"
38
+ export type { DatabaseRequest, DatabaseResponse, DatabaseSingleResponse, FilterOperator, SortOrder, GraphInclude, GraphFormat, EdgeRequest, EdgeResponse, EdgeDeleteRequest } from "./lib/database/types.js"
40
39
 
41
40
  // Storage types
42
41
  export type { StorageRequest, StorageUpdateRequest, StorageObject, StorageResponse, StorageListResponse, StorageUploadBatchResponse, StorageDeleteBatchResponse } from "./lib/storage/types.js"
@@ -48,7 +47,4 @@ export type { SiteSettingsData, SettingsResponse } from "./lib/settings/types.js
48
47
  export type { SecretCreateRequest, SecretUpdateRequest, SecretData, SecretResponse, SecretsListResponse, SecretsBatchResponse, SecretsBatchMetadataResponse, GetSecretOptions, GetSecretsOptions } from "./lib/secrets/types.js"
49
48
 
50
49
  // Analytics types
51
- export type { AnalyticsRequest, AnalyticsResponse } from "./lib/analytics/types.js"
52
-
53
- // Graph types
54
- export type { GraphInclude, GraphFormat, GraphQueryParams, EdgeRequest, EdgeResponse, EdgeDeleteRequest } from "./lib/graphs/types.js"
50
+ export type { AnalyticsRequest, AnalyticsResponse } from "./lib/analytics/types.js"
@@ -1,10 +1,17 @@
1
1
  import type { Client } from "../../client.js";
2
- import { DatabaseRoutes, type DatabaseRouteKey } from "../../lib-internal/routes/DatabaseRoutes.js";
2
+ import { DatabaseRoutes } from "../../lib-internal/routes/DatabaseRoutes.js";
3
3
  import { HttpMethod } from "../../lib-internal/http/types.js";
4
4
  import type { TaruviConfig, DatabaseFilters, TaruviResponse } from "../../types.js";
5
- import type { UrlParams, FilterOperator, SortOrder } from "./types.js";
5
+ import type { UrlParams, FilterOperator, SortOrder, GraphInclude, GraphFormat, EdgeRequest, EdgeDeleteRequest } from "./types.js";
6
6
  import { buildQueryString } from "../../utils/utils.js";
7
7
 
8
+ interface GraphQueryParams {
9
+ include?: GraphInclude
10
+ depth?: number
11
+ format?: GraphFormat
12
+ relationship_type?: string[]
13
+ }
14
+
8
15
  // Used to access app data
9
16
  export class Database<T = Record<string, unknown>> {
10
17
  private client: Client
@@ -13,28 +20,53 @@ export class Database<T = Record<string, unknown>> {
13
20
  private operation: HttpMethod | undefined
14
21
  private body: object | undefined
15
22
  private queryParams: DatabaseFilters | undefined
23
+ private graphParams: GraphQueryParams
24
+ private isEdges: boolean
16
25
 
17
- constructor(client: Client, urlParams: UrlParams = {}, operation?: HttpMethod | undefined, body?: object | undefined, queryParams?: DatabaseFilters) {
26
+ constructor(client: Client, urlParams: UrlParams = {}, operation?: HttpMethod | undefined, body?: object | undefined, queryParams?: DatabaseFilters, graphParams: GraphQueryParams = {}, isEdges: boolean = false) {
18
27
  this.client = client
19
28
  this.urlParams = urlParams
20
29
  this.operation = operation
21
30
  this.body = body
22
31
  this.config = this.client.getConfig()
23
32
  this.queryParams = queryParams
33
+ this.graphParams = graphParams
34
+ this.isEdges = isEdges
24
35
  }
25
36
 
26
37
  from<U = Record<string, unknown>>(dataTables: string): Database<U> {
27
- return new Database<U>(this.client, { ...this.urlParams, dataTables }, undefined, undefined)
38
+ return new Database<U>(this.client, { ...this.urlParams, dataTables }, undefined, undefined, undefined, {}, this.isEdges)
39
+ }
40
+
41
+ edges(): Database<T> {
42
+ return new Database<T>(this.client, { ...this.urlParams }, undefined, undefined, this.queryParams, { ...this.graphParams }, true)
43
+ }
44
+
45
+ // Graph traversal methods
46
+ include(direction: GraphInclude): Database<T> {
47
+ return new Database<T>(this.client, { ...this.urlParams }, this.operation, this.body, this.queryParams, { ...this.graphParams, include: direction }, this.isEdges)
48
+ }
49
+
50
+ depth(n: number): Database<T> {
51
+ return new Database<T>(this.client, { ...this.urlParams }, this.operation, this.body, this.queryParams, { ...this.graphParams, depth: n }, this.isEdges)
52
+ }
53
+
54
+ format(fmt: GraphFormat): Database<T> {
55
+ return new Database<T>(this.client, { ...this.urlParams }, this.operation, this.body, this.queryParams, { ...this.graphParams, format: fmt }, this.isEdges)
56
+ }
57
+
58
+ types(types: string[]): Database<T> {
59
+ return new Database<T>(this.client, { ...this.urlParams }, this.operation, this.body, this.queryParams, { ...this.graphParams, relationship_type: types }, this.isEdges)
28
60
  }
29
61
 
62
+ // Filter & query methods
30
63
  filter(field: string, operator: FilterOperator, value: string | number | boolean | (string | number)[]): Database<T> {
31
64
  const filterKey = operator === 'eq' ? field : `${field}__${operator}`
32
- // For 'in' and 'nin' operators, join array values with comma
33
65
  const filterValue = Array.isArray(value) ? value.join(',') : value
34
66
  return new Database<T>(this.client, { ...this.urlParams }, undefined, undefined, {
35
67
  ...this.queryParams,
36
68
  [filterKey]: filterValue
37
- })
69
+ }, { ...this.graphParams }, this.isEdges)
38
70
  }
39
71
 
40
72
  sort(field: string, order: SortOrder = 'asc'): Database<T> {
@@ -42,72 +74,77 @@ export class Database<T = Record<string, unknown>> {
42
74
  return new Database<T>(this.client, { ...this.urlParams }, undefined, undefined, {
43
75
  ...this.queryParams,
44
76
  ordering
45
- })
77
+ }, { ...this.graphParams }, this.isEdges)
46
78
  }
47
79
 
48
80
  pageSize(size: number): Database<T> {
49
81
  return new Database<T>(this.client, { ...this.urlParams }, undefined, undefined, {
50
82
  ...this.queryParams,
51
83
  page_size: size
52
- })
84
+ }, { ...this.graphParams }, this.isEdges)
53
85
  }
54
86
 
55
87
  page(num: number): Database<T> {
56
88
  return new Database<T>(this.client, { ...this.urlParams }, undefined, undefined, {
57
89
  ...this.queryParams,
58
90
  page: num
59
- })
91
+ }, { ...this.graphParams }, this.isEdges)
60
92
  }
61
93
 
62
94
  populate(populate: string[]): Database<T> {
63
95
  return new Database<T>(this.client, { ...this.urlParams }, undefined, undefined, {
64
96
  ...this.queryParams,
65
97
  populate: populate.join(',')
66
- })
98
+ }, { ...this.graphParams }, this.isEdges)
67
99
  }
68
100
 
69
101
  search(query: string): Database<T> {
70
102
  return new Database<T>(this.client, { ...this.urlParams }, undefined, undefined, {
71
103
  ...this.queryParams,
72
104
  search: query
73
- })
105
+ }, { ...this.graphParams }, this.isEdges)
74
106
  }
75
107
 
76
108
  aggregate(...expressions: string[]): Database<T> {
77
109
  return new Database<T>(this.client, { ...this.urlParams }, undefined, undefined, {
78
110
  ...this.queryParams,
79
111
  _aggregate: expressions.join(',')
80
- })
112
+ }, { ...this.graphParams }, this.isEdges)
81
113
  }
82
114
 
83
115
  groupBy(...fields: string[]): Database<T> {
84
116
  return new Database<T>(this.client, { ...this.urlParams }, undefined, undefined, {
85
117
  ...this.queryParams,
86
118
  _group_by: fields.join(',')
87
- })
119
+ }, { ...this.graphParams }, this.isEdges)
88
120
  }
89
121
 
90
122
  having(condition: string): Database<T> {
91
123
  return new Database<T>(this.client, { ...this.urlParams }, undefined, undefined, {
92
124
  ...this.queryParams,
93
125
  _having: condition
94
- })
126
+ }, { ...this.graphParams }, this.isEdges)
95
127
  }
96
128
 
129
+ // CRUD methods
97
130
  get(recordId: string): Database<T> {
98
- return new Database<T>(this.client, { ...this.urlParams, recordId }, HttpMethod.GET)
131
+ return new Database<T>(this.client, { ...this.urlParams, recordId }, HttpMethod.GET, undefined, this.queryParams, { ...this.graphParams }, this.isEdges)
99
132
  }
100
133
 
101
- create(body: Partial<T> | Partial<T>[]): Database<T> {
102
- return new Database<T>(this.client, { ...this.urlParams }, HttpMethod.POST, body as object)
134
+ create(body: Partial<T> | Partial<T>[] | EdgeRequest[]): Database<T> {
135
+ return new Database<T>(this.client, { ...this.urlParams }, HttpMethod.POST, body as object, this.queryParams, { ...this.graphParams }, this.isEdges)
103
136
  }
104
137
 
105
- update(body: Partial<T> | Partial<T>[]): Database<T> {
106
- return new Database<T>(this.client, { ...this.urlParams }, HttpMethod.PATCH, body as object)
138
+ update(body: Partial<T> | EdgeRequest): Database<T> {
139
+ return new Database<T>(this.client, { ...this.urlParams }, HttpMethod.PATCH, body as object, this.queryParams, { ...this.graphParams }, this.isEdges)
107
140
  }
108
141
 
109
- delete(recordId: string): Database<T> {
110
- return new Database<T>(this.client, { ...this.urlParams, recordId }, HttpMethod.DELETE)
142
+ delete(recordIdOrEdgeIds: string | number[]): Database<T> {
143
+ if (Array.isArray(recordIdOrEdgeIds)) {
144
+ const body: EdgeDeleteRequest = { edge_ids: recordIdOrEdgeIds }
145
+ return new Database<T>(this.client, { ...this.urlParams }, HttpMethod.DELETE, body, this.queryParams, { ...this.graphParams }, this.isEdges)
146
+ }
147
+ return new Database<T>(this.client, { ...this.urlParams, recordId: recordIdOrEdgeIds }, HttpMethod.DELETE, undefined, this.queryParams, { ...this.graphParams }, this.isEdges)
111
148
  }
112
149
 
113
150
  async first(): Promise<T | null> {
@@ -127,22 +164,22 @@ export class Database<T = Record<string, unknown>> {
127
164
  return Array.isArray(response.data) ? response.data.length : 0
128
165
  }
129
166
 
167
+ private getTableName(): string {
168
+ const table = this.urlParams.dataTables
169
+ if (!table) throw new Error('Table name is required. Call .from(tableName) first.')
170
+ return this.isEdges ? `${table}_edges` : table
171
+ }
172
+
130
173
  private buildRoute(): string {
131
- return (
132
- DatabaseRoutes.baseUrl(this.config.appSlug) +
133
- (Object.keys(this.urlParams) as DatabaseRouteKey[]).reduce((acc, key) => {
134
- const value = this.urlParams[key]
135
- const routeBuilder = DatabaseRoutes[key]
136
-
137
- if (value && routeBuilder) {
138
- acc += routeBuilder(value)
139
- }
174
+ const tableName = this.getTableName()
175
+ const base = DatabaseRoutes.baseUrl(this.config.appSlug) +
176
+ DatabaseRoutes.dataTables(tableName) +
177
+ (this.urlParams.recordId ? DatabaseRoutes.recordId(this.urlParams.recordId) : '') +
178
+ '/'
140
179
 
141
- return acc
142
- }, "") +
143
- "/" +
144
- buildQueryString(this.queryParams)
145
- )
180
+ // Merge database filters and graph params into one query string
181
+ const allParams: Record<string, unknown> = { ...this.queryParams, ...this.graphParams }
182
+ return base + buildQueryString(allParams)
146
183
  }
147
184
 
148
185
  async execute(): Promise<TaruviResponse<T | T[]>> {
@@ -150,9 +187,7 @@ export class Database<T = Record<string, unknown>> {
150
187
  throw new Error('Table name is required. Call .from(tableName) first.')
151
188
  }
152
189
 
153
- // Build the API URL
154
190
  const url = this.buildRoute()
155
-
156
191
  const operation = this.operation || HttpMethod.GET
157
192
 
158
193
  switch (operation) {
@@ -166,6 +201,9 @@ export class Database<T = Record<string, unknown>> {
166
201
  return await this.client.httpClient.patch(url, this.body)
167
202
 
168
203
  case HttpMethod.DELETE:
204
+ if (this.body) {
205
+ return await this.client.httpClient.delete(url, this.body)
206
+ }
169
207
  return await this.client.httpClient.delete(url)
170
208
 
171
209
  case HttpMethod.GET:
@@ -63,4 +63,28 @@ export interface DatabaseRequest {
63
63
 
64
64
  // Response types - uses standard wrapper
65
65
  export type DatabaseResponse<T = unknown> = TaruviResponse<T[]>
66
- export type DatabaseSingleResponse<T = unknown> = TaruviResponse<T>
66
+ export type DatabaseSingleResponse<T = unknown> = TaruviResponse<T>
67
+
68
+ // Graph traversal types
69
+ export type GraphInclude = 'descendants' | 'ancestors' | 'both'
70
+ export type GraphFormat = 'tree' | 'graph'
71
+
72
+ // Edge types
73
+ export interface EdgeRequest {
74
+ from_id: number | string
75
+ to_id: number | string
76
+ type: string
77
+ metadata?: Record<string, unknown>
78
+ }
79
+
80
+ export interface EdgeResponse {
81
+ id: number
82
+ from_id: number | string
83
+ to_id: number | string
84
+ type: string
85
+ metadata?: Record<string, unknown>
86
+ }
87
+
88
+ export interface EdgeDeleteRequest {
89
+ edge_ids: number[]
90
+ }
@@ -1,7 +1,7 @@
1
1
  import type { Client } from "../../client.js"
2
2
  import { PolicyRoutes } from "../../lib-internal/routes/PolicyRoutes.js"
3
3
  import type { TaruviConfig } from "../../types.js"
4
- import type { Resources, Resource, Principal, PolicyCheckBatchResult, GetAllowedActionsOptions } from "./types.js"
4
+ import type { Resources, Resource, PolicyCheckBatchResult, GetAllowedActionsOptions } from "./types.js"
5
5
 
6
6
  export class Policy {
7
7
  private client: Client
@@ -1,7 +1,8 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest'
2
2
  import { Database } from '../../../src/lib/database/DatabaseClient.js'
3
3
  import { Client } from '../../../src/client.js'
4
- import type { DatabaseResponse, DatabaseSingleResponse } from '../../../src/lib/database/types.js'
4
+ import type { DatabaseResponse, DatabaseSingleResponse, EdgeResponse } from '../../../src/lib/database/types.js'
5
+ import type { TaruviResponse } from '../../../src/types.js'
5
6
 
6
7
  // Mock the Client
7
8
  const mockHttpClient = {
@@ -301,4 +302,186 @@ describe('Database', () => {
301
302
  expect((result as any).status).toBe('success')
302
303
  })
303
304
  })
305
+
306
+ describe('graph traversal', () => {
307
+ it('include() sets include param', async () => {
308
+ mockHttpClient.get.mockResolvedValue([])
309
+ await new Database(mockClient).from('employees').get('1').include('descendants').execute()
310
+ expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('include=descendants'))
311
+ })
312
+
313
+ it('include() supports ancestors', async () => {
314
+ mockHttpClient.get.mockResolvedValue([])
315
+ await new Database(mockClient).from('employees').get('4').include('ancestors').execute()
316
+ expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('include=ancestors'))
317
+ })
318
+
319
+ it('include() supports both', async () => {
320
+ mockHttpClient.get.mockResolvedValue([])
321
+ await new Database(mockClient).from('employees').get('2').include('both').execute()
322
+ expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('include=both'))
323
+ })
324
+
325
+ it('depth() sets depth param', async () => {
326
+ mockHttpClient.get.mockResolvedValue([])
327
+ await new Database(mockClient).from('employees').get('1').include('descendants').depth(3).execute()
328
+ expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('depth=3'))
329
+ })
330
+
331
+ it('format() sets format param to tree', async () => {
332
+ mockHttpClient.get.mockResolvedValue([])
333
+ await new Database(mockClient).from('employees').get('1').format('tree').depth(3).execute()
334
+ expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('format=tree'))
335
+ })
336
+
337
+ it('format() sets format param to graph', async () => {
338
+ mockHttpClient.get.mockResolvedValue([])
339
+ await new Database(mockClient).from('employees').format('graph').execute()
340
+ expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('format=graph'))
341
+ })
342
+
343
+ it('types() sets relationship_type as repeated params', async () => {
344
+ mockHttpClient.get.mockResolvedValue([])
345
+ await new Database(mockClient).from('employees').format('graph').types(['manager', 'dotted_line']).execute()
346
+ const url = mockHttpClient.get.mock.calls[0][0]
347
+ expect(url).toContain('relationship_type=manager')
348
+ expect(url).toContain('relationship_type=dotted_line')
349
+ })
350
+
351
+ it('types() with single type', async () => {
352
+ mockHttpClient.get.mockResolvedValue([])
353
+ await new Database(mockClient).from('employees').format('graph').types(['manager']).execute()
354
+ expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('relationship_type=manager'))
355
+ })
356
+
357
+ it('combines include, depth, and get', async () => {
358
+ mockHttpClient.get.mockResolvedValue([])
359
+ await new Database(mockClient).from('employees').get('1').include('descendants').depth(3).execute()
360
+ const url = mockHttpClient.get.mock.calls[0][0]
361
+ expect(url).toContain('/1/')
362
+ expect(url).toContain('include=descendants')
363
+ expect(url).toContain('depth=3')
364
+ })
365
+
366
+ it('combines format, types, and depth', async () => {
367
+ mockHttpClient.get.mockResolvedValue([])
368
+ await new Database(mockClient).from('employees').format('graph').types(['manager']).depth(2).execute()
369
+ const url = mockHttpClient.get.mock.calls[0][0]
370
+ expect(url).toContain('format=graph')
371
+ expect(url).toContain('relationship_type=manager')
372
+ expect(url).toContain('depth=2')
373
+ })
374
+ })
375
+
376
+ describe('graph response handling', () => {
377
+ it('returns descendants response', async () => {
378
+ const mockResponse = {
379
+ status: 'success',
380
+ data: {
381
+ data: { id: 1, name: 'Alice Chen', title: 'CEO' },
382
+ reports: [
383
+ { id: 2, name: 'Bob Smith', _depth: 1, _relationship_type: 'manager' },
384
+ { id: 3, name: 'Carol White', _depth: 1, _relationship_type: 'manager' }
385
+ ]
386
+ }
387
+ }
388
+ mockHttpClient.get.mockResolvedValue(mockResponse)
389
+ const result = await new Database(mockClient).from('employees').get('1').include('descendants').execute()
390
+ expect((result as any).data.reports).toHaveLength(2)
391
+ })
392
+
393
+ it('returns tree format with nested children', async () => {
394
+ const mockResponse = {
395
+ status: 'success',
396
+ data: [{
397
+ id: 1, name: 'Alice Chen', _depth: 0,
398
+ children: [
399
+ { id: 2, name: 'Bob Smith', _depth: 1, children: [] },
400
+ { id: 3, name: 'Carol White', _depth: 1, children: [] }
401
+ ]
402
+ }],
403
+ total: 3
404
+ }
405
+ mockHttpClient.get.mockResolvedValue(mockResponse)
406
+ const result = await new Database(mockClient).from('employees').get('1').format('tree').depth(2).execute()
407
+ expect((result as any).data[0].children).toHaveLength(2)
408
+ })
409
+
410
+ it('returns graph format with nodes and edges', async () => {
411
+ const mockResponse = {
412
+ status: 'success',
413
+ data: {
414
+ nodes: [{ id: 1, name: 'Alice Chen' }, { id: 2, name: 'Bob Smith' }],
415
+ edges: [{ id: 9, from_id: 2, to_id: 1, type: 'manager' }]
416
+ },
417
+ total: 2
418
+ }
419
+ mockHttpClient.get.mockResolvedValue(mockResponse)
420
+ const result = await new Database(mockClient).from('employees').format('graph').types(['manager']).execute()
421
+ expect((result as any).data.nodes).toHaveLength(2)
422
+ expect((result as any).data.edges).toHaveLength(1)
423
+ })
424
+ })
425
+
426
+ describe('edges()', () => {
427
+ it('targets _edges table for list', async () => {
428
+ mockHttpClient.get.mockResolvedValue({ edges: [], total: 0 })
429
+ await new Database(mockClient).from('employees').edges().execute()
430
+ expect(mockHttpClient.get).toHaveBeenCalledWith('api/apps/test-app/datatables/employees_edges/data/')
431
+ })
432
+
433
+ it('create() calls POST on edges route', async () => {
434
+ const edges = [
435
+ { from_id: 5, to_id: 2, type: 'manager' },
436
+ { from_id: 5, to_id: 3, type: 'dotted_line', metadata: { project: 'AI' } }
437
+ ]
438
+ mockHttpClient.post.mockResolvedValue({ status: 'success', data: edges, total: 2 })
439
+ await new Database(mockClient).from('employees').edges().create(edges).execute()
440
+ expect(mockHttpClient.post).toHaveBeenCalledWith(
441
+ 'api/apps/test-app/datatables/employees_edges/data/',
442
+ edges
443
+ )
444
+ })
445
+
446
+ it('update() calls PATCH with edge ID', async () => {
447
+ const edge = { from_id: 5, to_id: 3, type: 'dotted_line' }
448
+ mockHttpClient.patch.mockResolvedValue({ id: 9, ...edge })
449
+ await new Database(mockClient).from('employees').edges().get('9').update(edge).execute()
450
+ expect(mockHttpClient.patch).toHaveBeenCalledWith(
451
+ 'api/apps/test-app/datatables/employees_edges/data/9/',
452
+ edge
453
+ )
454
+ })
455
+
456
+ it('delete() calls DELETE with edge_ids body', async () => {
457
+ mockHttpClient.delete.mockResolvedValue({ deleted: 2 })
458
+ await new Database(mockClient).from('employees').edges().delete([9, 10]).execute()
459
+ expect(mockHttpClient.delete).toHaveBeenCalledWith(
460
+ 'api/apps/test-app/datatables/employees_edges/data/',
461
+ { edge_ids: [9, 10] }
462
+ )
463
+ })
464
+
465
+ it('returns created edges matching EdgeResponse', async () => {
466
+ const mockResponse: TaruviResponse<EdgeResponse[]> = {
467
+ status: 'success',
468
+ message: 'Edges created successfully',
469
+ data: [{ id: 10, from_id: 5, to_id: 2, type: 'manager', metadata: {} }],
470
+ total: 1
471
+ }
472
+ mockHttpClient.post.mockResolvedValue(mockResponse)
473
+ const result = await new Database(mockClient).from('employees').edges().create([{ from_id: 5, to_id: 2, type: 'manager' }]).execute() as TaruviResponse<EdgeResponse[]>
474
+ expect(result.data).toHaveLength(1)
475
+ expect(result.data[0].id).toBe(10)
476
+ })
477
+
478
+ it('does not affect non-edge queries', async () => {
479
+ mockHttpClient.get.mockResolvedValue([])
480
+ const base = new Database(mockClient).from('employees')
481
+ await base.edges().execute()
482
+ await base.execute()
483
+ expect(mockHttpClient.get).toHaveBeenNthCalledWith(1, 'api/apps/test-app/datatables/employees_edges/data/')
484
+ expect(mockHttpClient.get).toHaveBeenNthCalledWith(2, 'api/apps/test-app/datatables/employees/data/')
485
+ })
486
+ })
304
487
  })
@@ -1,7 +1,6 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest'
2
2
  import { Database } from '../../../src/lib/database/DatabaseClient.js'
3
3
  import { Storage } from '../../../src/lib/storage/StorageClient.js'
4
- import { Graph } from '../../../src/lib/graphs/GraphClient.js'
5
4
  import { Client } from '../../../src/client.js'
6
5
 
7
6
  const mockHttpClient = {
@@ -61,9 +60,9 @@ describe('Non-JSON / empty / malformed responses', () => {
61
60
  await expect(new Database(mockClient).from('accounts').execute()).rejects.toThrow('Network Error')
62
61
  })
63
62
 
64
- it('propagates network error for Graph', async () => {
63
+ it('propagates network error for Database graph', async () => {
65
64
  mockHttpClient.get.mockRejectedValue(new Error('ECONNREFUSED'))
66
- await expect(new Graph(mockClient).from('employees').execute()).rejects.toThrow('ECONNREFUSED')
65
+ await expect(new Database(mockClient).from('employees').execute()).rejects.toThrow('ECONNREFUSED')
67
66
  })
68
67
 
69
68
  it('propagates network error for Storage', async () => {
@@ -126,7 +125,7 @@ describe('Encoding edge cases', () => {
126
125
 
127
126
  it('encodes graph relationship types with special chars', async () => {
128
127
  mockHttpClient.get.mockResolvedValue([])
129
- await new Graph(mockClient).from('employees').types(['reports_to']).execute()
128
+ await new Database(mockClient).from('employees').types(['reports_to']).execute()
130
129
  expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('relationship_type=reports_to'))
131
130
  })
132
131
 
@@ -180,9 +179,9 @@ describe('Builder immutability', () => {
180
179
  expect(mockHttpClient.get.mock.calls[0][0]).not.toContain('page=2')
181
180
  })
182
181
 
183
- it('Graph: chaining does not mutate original instance', async () => {
182
+ it('Database graph: chaining does not mutate original instance', async () => {
184
183
  mockHttpClient.get.mockResolvedValue([])
185
- const base = new Graph(mockClient).from('employees')
184
+ const base = new Database(mockClient).from('employees')
186
185
  const descendants = base.get('1').include('descendants').depth(3)
187
186
  const ancestors = base.get('4').include('ancestors')
188
187
 
@@ -201,9 +200,9 @@ describe('Builder immutability', () => {
201
200
  expect(ancUrl).not.toContain('depth=3')
202
201
  })
203
202
 
204
- it('Graph: format does not leak between chains', async () => {
203
+ it('Database graph: format does not leak between chains', async () => {
205
204
  mockHttpClient.get.mockResolvedValue([])
206
- const base = new Graph(mockClient).from('employees')
205
+ const base = new Database(mockClient).from('employees')
207
206
  const tree = base.format('tree')
208
207
  const graph = base.format('graph')
209
208
 
@@ -243,12 +242,12 @@ describe('Builder immutability', () => {
243
242
  expect(mockHttpClient.get.mock.calls[1][0]).not.toContain('/123/')
244
243
  })
245
244
 
246
- it('Graph: edge operations do not affect traversal chain', async () => {
245
+ it('Database: edge operations do not affect traversal chain', async () => {
247
246
  mockHttpClient.get.mockResolvedValue([])
248
247
  mockHttpClient.post.mockResolvedValue({})
249
- const base = new Graph(mockClient).from('employees')
248
+ const base = new Database(mockClient).from('employees')
250
249
  const traversal = base.get('1').include('descendants')
251
- const edge = base.create([{ from: 1, to: 2, type: 'manager' }])
250
+ const edge = base.edges().create([{ from_id: 1, to_id: 2, type: 'manager' }])
252
251
 
253
252
  await traversal.execute()
254
253
  await edge.execute()
@@ -1,106 +0,0 @@
1
- import type { Client } from "../../client.js"
2
- import type { TaruviConfig } from "../../types.js"
3
- import type { GraphInclude, GraphFormat, GraphQueryParams, GraphUrlParams, EdgeRequest, EdgeDeleteRequest } from "./types.js"
4
- import { GraphRoutes, GraphEdgeRoutes, type GraphRouteKey } from "../../lib-internal/routes/GraphRoutes.js"
5
- import { buildQueryString } from "../../utils/utils.js"
6
- import { HttpMethod } from "../../lib-internal/http/types.js"
7
-
8
- export class Graph<T = Record<string, unknown>> {
9
- private client: Client
10
- private config: TaruviConfig
11
- private urlParams: GraphUrlParams
12
- private queryParams: GraphQueryParams
13
- private operation: HttpMethod | undefined
14
- private body: object | undefined
15
- private edgeRoute: string | undefined
16
-
17
- constructor(client: Client, urlParams: GraphUrlParams = {}, queryParams: GraphQueryParams = {}, operation?: HttpMethod, body?: object, edgeRoute?: string) {
18
- this.client = client
19
- this.config = this.client.getConfig()
20
- this.urlParams = urlParams
21
- this.queryParams = queryParams
22
- this.operation = operation
23
- this.body = body
24
- this.edgeRoute = edgeRoute
25
- }
26
-
27
- from<U = Record<string, unknown>>(dataTables: string): Graph<U> {
28
- return new Graph<U>(this.client, { ...this.urlParams, dataTables }, { ...this.queryParams })
29
- }
30
-
31
- get(recordId: string): Graph<T> {
32
- return new Graph<T>(this.client, { ...this.urlParams, recordId }, { ...this.queryParams })
33
- }
34
-
35
- include(direction: GraphInclude): Graph<T> {
36
- return new Graph<T>(this.client, { ...this.urlParams }, { ...this.queryParams, include: direction })
37
- }
38
-
39
- depth(n: number): Graph<T> {
40
- return new Graph<T>(this.client, { ...this.urlParams }, { ...this.queryParams, depth: n })
41
- }
42
-
43
- format(fmt: GraphFormat): Graph<T> {
44
- return new Graph<T>(this.client, { ...this.urlParams }, { ...this.queryParams, format: fmt })
45
- }
46
-
47
- types(types: string[]): Graph<T> {
48
- return new Graph<T>(this.client, { ...this.urlParams }, { ...this.queryParams, relationship_type: types })
49
- }
50
-
51
- list(): Graph<T> {
52
- const route = GraphEdgeRoutes.baseUrl(this.config.appSlug) + GraphEdgeRoutes.edges(this.urlParams.dataTables!) + "/"
53
- return new Graph<T>(this.client, { ...this.urlParams }, {}, HttpMethod.GET, undefined, route)
54
- }
55
-
56
- create(edges: EdgeRequest[]): Graph<T> {
57
- const route = GraphEdgeRoutes.baseUrl(this.config.appSlug) + GraphEdgeRoutes.edges(this.urlParams.dataTables!) + "/"
58
- return new Graph<T>(this.client, { ...this.urlParams }, {}, HttpMethod.POST, edges as unknown as object, route)
59
- }
60
-
61
- update(edgeId: string, edge: EdgeRequest): Graph<T> {
62
- const route = GraphEdgeRoutes.baseUrl(this.config.appSlug) + GraphEdgeRoutes.edges(this.urlParams.dataTables!) + GraphEdgeRoutes.edgeId(edgeId) + "/"
63
- return new Graph<T>(this.client, { ...this.urlParams }, {}, HttpMethod.PATCH, edge, route)
64
- }
65
-
66
- delete(edgeIds: number[]): Graph<T> {
67
- const route = GraphEdgeRoutes.baseUrl(this.config.appSlug) + GraphEdgeRoutes.edges(this.urlParams.dataTables!) + "/"
68
- const body: EdgeDeleteRequest = { edge_ids: edgeIds }
69
- return new Graph<T>(this.client, { ...this.urlParams }, {}, HttpMethod.DELETE, body, route)
70
- }
71
-
72
- private buildRoute(): string {
73
- if (this.edgeRoute) return this.edgeRoute
74
-
75
- return (
76
- GraphRoutes.baseUrl(this.config.appSlug) +
77
- (Object.keys(this.urlParams) as GraphRouteKey[]).reduce((acc, key) => {
78
- const value = this.urlParams[key]
79
- const routeBuilder = GraphRoutes[key]
80
- if (value && routeBuilder) {
81
- acc += routeBuilder(value)
82
- }
83
- return acc
84
- }, "") +
85
- "/" +
86
- buildQueryString(this.queryParams as Record<string, unknown>)
87
- )
88
- }
89
-
90
- async execute(): Promise<T | T[]> {
91
- const url = this.buildRoute()
92
- const operation = this.operation || HttpMethod.GET
93
-
94
- switch (operation) {
95
- case HttpMethod.POST:
96
- return await this.client.httpClient.post(url, this.body)
97
- case HttpMethod.PATCH:
98
- return await this.client.httpClient.patch(url, this.body)
99
- case HttpMethod.DELETE:
100
- return await this.client.httpClient.delete(url, this.body)
101
- case HttpMethod.GET:
102
- default:
103
- return await this.client.httpClient.get(url)
104
- }
105
- }
106
- }
@@ -1,33 +0,0 @@
1
- export type GraphInclude = 'descendants' | 'ancestors' | 'both'
2
- export type GraphFormat = 'tree' | 'graph'
3
-
4
- export interface GraphQueryParams {
5
- include?: GraphInclude
6
- depth?: number
7
- format?: GraphFormat
8
- relationship_type?: string[]
9
- }
10
-
11
- export interface GraphUrlParams {
12
- dataTables?: string
13
- recordId?: string
14
- }
15
-
16
- export interface EdgeRequest {
17
- from: number | string
18
- to: number | string
19
- type: string
20
- metadata?: Record<string, unknown>
21
- }
22
-
23
- export interface EdgeResponse {
24
- id: number
25
- from: number | string
26
- to: number | string
27
- type: string
28
- metadata?: Record<string, unknown>
29
- }
30
-
31
- export interface EdgeDeleteRequest {
32
- edge_ids: number[]
33
- }
@@ -1,14 +0,0 @@
1
- export const GraphRoutes = {
2
- baseUrl: (appSlug: string) => `api/apps/${appSlug}`,
3
- dataTables: (tableName: string): string => `/datatables/${tableName}/data`,
4
- recordId: (recordId: string): string => `/${recordId}`
5
- }
6
-
7
- export const GraphEdgeRoutes = {
8
- baseUrl: (appSlug: string) => `api/apps/${appSlug}`,
9
- edges: (tableName: string): string => `/datatables/${tableName}_edges/data`,
10
- edgeId: (edgeId: string): string => `/${edgeId}`
11
- }
12
-
13
- type AllRouteKeys = keyof typeof GraphRoutes
14
- export type GraphRouteKey = Exclude<AllRouteKeys, 'baseUrl'>
@@ -1,329 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest'
2
- import { Graph } from '../../../src/lib/graphs/GraphClient.js'
3
- import { Client } from '../../../src/client.js'
4
- import type { EdgeResponse } from '../../../src/lib/graphs/types.js'
5
- import type { TaruviResponse } from '../../../src/types.js'
6
-
7
- const mockHttpClient = {
8
- get: vi.fn(),
9
- post: vi.fn(),
10
- patch: vi.fn(),
11
- delete: vi.fn()
12
- }
13
-
14
- const mockClient = {
15
- getConfig: () => ({ apiKey: 'test-key', appSlug: 'test-app', apiUrl: 'https://api.test.com' }),
16
- httpClient: mockHttpClient
17
- } as unknown as Client
18
-
19
- describe('Graph', () => {
20
- beforeEach(() => {
21
- vi.clearAllMocks()
22
- })
23
-
24
- describe('from()', () => {
25
- it('returns new Graph instance', () => {
26
- const graph = new Graph(mockClient)
27
- expect(graph.from('employees')).toBeInstanceOf(Graph)
28
- })
29
- })
30
-
31
- describe('traversal query params', () => {
32
- it('include() sets include param', async () => {
33
- mockHttpClient.get.mockResolvedValue([])
34
- await new Graph(mockClient).from('employees').get('1').include('descendants').execute()
35
- expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('include=descendants'))
36
- })
37
-
38
- it('include() supports ancestors', async () => {
39
- mockHttpClient.get.mockResolvedValue([])
40
- await new Graph(mockClient).from('employees').get('4').include('ancestors').execute()
41
- expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('include=ancestors'))
42
- })
43
-
44
- it('include() supports both', async () => {
45
- mockHttpClient.get.mockResolvedValue([])
46
- await new Graph(mockClient).from('employees').get('2').include('both').execute()
47
- expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('include=both'))
48
- })
49
-
50
- it('depth() sets depth param', async () => {
51
- mockHttpClient.get.mockResolvedValue([])
52
- await new Graph(mockClient).from('employees').get('1').include('descendants').depth(3).execute()
53
- expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('depth=3'))
54
- })
55
-
56
- it('format() sets format param to tree', async () => {
57
- mockHttpClient.get.mockResolvedValue([])
58
- await new Graph(mockClient).from('employees').get('1').format('tree').depth(3).execute()
59
- expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('format=tree'))
60
- })
61
-
62
- it('format() sets format param to graph', async () => {
63
- mockHttpClient.get.mockResolvedValue([])
64
- await new Graph(mockClient).from('employees').format('graph').execute()
65
- expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('format=graph'))
66
- })
67
-
68
- it('types() sets relationship_type as repeated params', async () => {
69
- mockHttpClient.get.mockResolvedValue([])
70
- await new Graph(mockClient).from('employees').format('graph').types(['manager', 'dotted_line']).execute()
71
- const url = mockHttpClient.get.mock.calls[0][0]
72
- expect(url).toContain('relationship_type=manager')
73
- expect(url).toContain('relationship_type=dotted_line')
74
- })
75
-
76
- it('types() with single type', async () => {
77
- mockHttpClient.get.mockResolvedValue([])
78
- await new Graph(mockClient).from('employees').format('graph').types(['manager']).execute()
79
- expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('relationship_type=manager'))
80
- })
81
- })
82
-
83
- describe('chaining', () => {
84
- it('combines include, depth, and get', async () => {
85
- mockHttpClient.get.mockResolvedValue([])
86
- await new Graph(mockClient).from('employees').get('1').include('descendants').depth(3).execute()
87
- const url = mockHttpClient.get.mock.calls[0][0]
88
- expect(url).toContain('/1/')
89
- expect(url).toContain('include=descendants')
90
- expect(url).toContain('depth=3')
91
- })
92
-
93
- it('combines format, types, and depth', async () => {
94
- mockHttpClient.get.mockResolvedValue([])
95
- await new Graph(mockClient).from('employees').format('graph').types(['manager']).depth(2).execute()
96
- const url = mockHttpClient.get.mock.calls[0][0]
97
- expect(url).toContain('format=graph')
98
- expect(url).toContain('relationship_type=manager')
99
- expect(url).toContain('depth=2')
100
- })
101
- })
102
-
103
- describe('URL building', () => {
104
- it('builds correct base URL for data queries', async () => {
105
- mockHttpClient.get.mockResolvedValue([])
106
- await new Graph(mockClient).from('employees').execute()
107
- expect(mockHttpClient.get).toHaveBeenCalledWith('api/apps/test-app/datatables/employees/data/')
108
- })
109
-
110
- it('builds correct URL with record ID', async () => {
111
- mockHttpClient.get.mockResolvedValue({})
112
- await new Graph(mockClient).from('employees').get('1').execute()
113
- expect(mockHttpClient.get).toHaveBeenCalledWith('api/apps/test-app/datatables/employees/data/1/')
114
- })
115
-
116
- it('appends query string', async () => {
117
- mockHttpClient.get.mockResolvedValue([])
118
- await new Graph(mockClient).from('employees').get('1').include('descendants').depth(2).execute()
119
- const url = mockHttpClient.get.mock.calls[0][0]
120
- expect(url).toContain('api/apps/test-app/datatables/employees/data/1/')
121
- expect(url).toContain('?')
122
- })
123
- })
124
-
125
- describe('edge CRUD', () => {
126
- it('list() calls GET on edges route', async () => {
127
- mockHttpClient.get.mockResolvedValue({ edges: [], total: 0 })
128
- await new Graph(mockClient).from('employees').list().execute()
129
- expect(mockHttpClient.get).toHaveBeenCalledWith('api/apps/test-app/datatables/employees_edges/data/')
130
- })
131
-
132
- it('create() calls POST with array of edges', async () => {
133
- const edges = [
134
- { from: 5, to: 2, type: 'manager' },
135
- { from: 5, to: 3, type: 'dotted_line', metadata: { project: 'AI' } }
136
- ]
137
- mockHttpClient.post.mockResolvedValue({ status: 'success', data: edges, total: 2 })
138
- await new Graph(mockClient).from('employees').create(edges).execute()
139
- expect(mockHttpClient.post).toHaveBeenCalledWith(
140
- 'api/apps/test-app/datatables/employees_edges/data/',
141
- edges
142
- )
143
- })
144
-
145
- it('create() with metadata', async () => {
146
- const edges = [{ from: 5, to: 3, type: 'dotted_line', metadata: { percentage: 30 } }]
147
- mockHttpClient.post.mockResolvedValue({ status: 'success', data: edges, total: 1 })
148
- await new Graph(mockClient).from('employees').create(edges).execute()
149
- expect(mockHttpClient.post).toHaveBeenCalledWith(
150
- 'api/apps/test-app/datatables/employees_edges/data/',
151
- edges
152
- )
153
- })
154
-
155
- it('update() calls PATCH with edge ID in URL', async () => {
156
- const edge = { from: 5, to: 3, type: 'dotted_line' }
157
- mockHttpClient.patch.mockResolvedValue({ id: 9, ...edge })
158
- await new Graph(mockClient).from('employees').update('9', edge).execute()
159
- expect(mockHttpClient.patch).toHaveBeenCalledWith(
160
- 'api/apps/test-app/datatables/employees_edges/data/9/',
161
- edge
162
- )
163
- })
164
-
165
- it('delete() calls DELETE with edge_ids object', async () => {
166
- mockHttpClient.delete.mockResolvedValue({ deleted: 2 })
167
- await new Graph(mockClient).from('employees').delete([9, 10]).execute()
168
- expect(mockHttpClient.delete).toHaveBeenCalledWith(
169
- 'api/apps/test-app/datatables/employees_edges/data/',
170
- { edge_ids: [9, 10] }
171
- )
172
- })
173
- })
174
-
175
- describe('execute()', () => {
176
- it('defaults to GET for traversal queries', async () => {
177
- mockHttpClient.get.mockResolvedValue([])
178
- await new Graph(mockClient).from('employees').execute()
179
- expect(mockHttpClient.get).toHaveBeenCalled()
180
- expect(mockHttpClient.post).not.toHaveBeenCalled()
181
- })
182
- })
183
-
184
- describe('response handling', () => {
185
- it('returns descendants response with base record and related records', async () => {
186
- const mockResponse = {
187
- status: 'success',
188
- message: 'Data retrieved successfully',
189
- data: {
190
- data: { id: 1, name: 'Alice Chen', title: 'CEO' },
191
- reports: [
192
- { id: 2, name: 'Bob Smith', _depth: 1, _relationship_type: 'manager' },
193
- { id: 3, name: 'Carol White', _depth: 1, _relationship_type: 'manager' }
194
- ]
195
- }
196
- }
197
- mockHttpClient.get.mockResolvedValue(mockResponse)
198
- const result = await new Graph(mockClient).from('employees').get('1').include('descendants').execute()
199
- expect(result).toEqual(mockResponse)
200
- expect((result as any).data.data.id).toBe(1)
201
- expect((result as any).data.reports).toHaveLength(2)
202
- expect((result as any).data.reports[0]._depth).toBe(1)
203
- expect((result as any).data.reports[0]._relationship_type).toBe('manager')
204
- })
205
-
206
- it('returns ancestors response with manager chain', async () => {
207
- const mockResponse = {
208
- status: 'success',
209
- message: 'Data retrieved successfully',
210
- data: {
211
- data: { id: 4, name: 'David Lee' },
212
- manager: [
213
- { id: 2, name: 'Bob Smith', _depth: 1, _relationship_type: 'manager' },
214
- { id: 1, name: 'Alice Chen', _depth: 2, _relationship_type: 'manager' }
215
- ]
216
- }
217
- }
218
- mockHttpClient.get.mockResolvedValue(mockResponse)
219
- const result = await new Graph(mockClient).from('employees').get('4').include('ancestors').execute()
220
- expect((result as any).data.manager).toHaveLength(2)
221
- expect((result as any).data.manager[1]._depth).toBe(2)
222
- })
223
-
224
- it('returns both directions response', async () => {
225
- const mockResponse = {
226
- status: 'success',
227
- message: 'Data retrieved successfully',
228
- data: {
229
- data: { id: 2, name: 'Bob Smith' },
230
- manager: [{ id: 1, name: 'Alice Chen', _depth: 1, _relationship_type: 'manager' }],
231
- reports: [{ id: 4, name: 'David Lee', _depth: 1, _relationship_type: 'manager' }]
232
- }
233
- }
234
- mockHttpClient.get.mockResolvedValue(mockResponse)
235
- const result = await new Graph(mockClient).from('employees').get('2').include('both').depth(1).execute()
236
- expect((result as any).data.manager).toHaveLength(1)
237
- expect((result as any).data.reports).toHaveLength(1)
238
- })
239
-
240
- it('returns tree format with nested children', async () => {
241
- const mockResponse = {
242
- status: 'success',
243
- message: 'Tree data retrieved successfully',
244
- data: [{
245
- id: 1,
246
- name: 'Alice Chen',
247
- _depth: 0,
248
- children: [
249
- { id: 2, name: 'Bob Smith', _depth: 1, _relationship_type: 'manager', children: [] },
250
- { id: 3, name: 'Carol White', _depth: 1, _relationship_type: 'manager', children: [] }
251
- ]
252
- }],
253
- total: 3
254
- }
255
- mockHttpClient.get.mockResolvedValue(mockResponse)
256
- const result = await new Graph(mockClient).from('employees').get('1').format('tree').depth(2).execute()
257
- expect((result as any).data[0].children).toHaveLength(2)
258
- expect((result as any).data[0].children[0]._relationship_type).toBe('manager')
259
- expect((result as any).total).toBe(3)
260
- })
261
-
262
- it('returns graph format with nodes and edges', async () => {
263
- const mockResponse = {
264
- status: 'success',
265
- message: 'Graph data retrieved successfully',
266
- data: {
267
- nodes: [
268
- { id: 1, name: 'Alice Chen', title: 'CEO' },
269
- { id: 2, name: 'Bob Smith', title: 'VP Engineering' }
270
- ],
271
- edges: [
272
- { id: 9, from: 2, to: 1, type: 'manager', metadata: { primary: true } }
273
- ]
274
- },
275
- total: 2
276
- }
277
- mockHttpClient.get.mockResolvedValue(mockResponse)
278
- const result = await new Graph(mockClient).from('employees').format('graph').types(['manager']).execute()
279
- expect((result as any).data.nodes).toHaveLength(2)
280
- expect((result as any).data.edges).toHaveLength(1)
281
- expect((result as any).data.edges[0].type).toBe('manager')
282
- })
283
-
284
- it('returns edge list response', async () => {
285
- const mockResponse = {
286
- edges: [
287
- { id: 1, from: 5, to: 2, type: 'manager', metadata: {} },
288
- { id: 2, from: 5, to: 3, type: 'dotted_line', metadata: { percentage: 30 } }
289
- ],
290
- total: 2
291
- }
292
- mockHttpClient.get.mockResolvedValue(mockResponse)
293
- const result = await new Graph(mockClient).from('employees').list().execute()
294
- expect((result as any).edges).toHaveLength(2)
295
- expect((result as any).edges[0].type).toBe('manager')
296
- expect((result as any).total).toBe(2)
297
- })
298
-
299
- it('returns created edges response matching TaruviResponse<EdgeResponse[]>', async () => {
300
- const mockResponse: TaruviResponse<EdgeResponse[]> = {
301
- status: 'success',
302
- message: 'Edges created successfully',
303
- data: [
304
- { id: 10, from: 5, to: 2, type: 'manager', metadata: {} }
305
- ],
306
- total: 1
307
- }
308
- mockHttpClient.post.mockResolvedValue(mockResponse)
309
- const result = await new Graph(mockClient).from('employees').create([{ from: 5, to: 2, type: 'manager' }]).execute() as TaruviResponse<EdgeResponse[]>
310
- expect(result.data).toHaveLength(1)
311
- expect(result.data[0].id).toBe(10)
312
- })
313
-
314
- it('returns updated edge matching EdgeResponse type', async () => {
315
- const mockResponse: EdgeResponse = { id: 9, from: 5, to: 3, type: 'dotted_line', metadata: {} }
316
- mockHttpClient.patch.mockResolvedValue(mockResponse)
317
- const result = await new Graph(mockClient).from('employees').update('9', { from: 5, to: 3, type: 'dotted_line' }).execute() as EdgeResponse
318
- expect(result.id).toBe(9)
319
- expect(result.type).toBe('dotted_line')
320
- })
321
-
322
- it('returns delete count response', async () => {
323
- const mockResponse = { deleted: 3 }
324
- mockHttpClient.delete.mockResolvedValue(mockResponse)
325
- const result = await new Graph(mockClient).from('employees').delete([1, 2, 3]).execute()
326
- expect((result as any).deleted).toBe(3)
327
- })
328
- })
329
- })