@taruvi/sdk 1.5.0-beta.1 → 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 -306
- 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
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Error codes matching the backend's ErrorCode enum.
|
|
3
|
-
* Maps 1:1 with base.responses.error_codes.ErrorCode in Python.
|
|
4
|
-
*/
|
|
5
|
-
export enum ErrorCode {
|
|
6
|
-
BAD_REQUEST = 'BAD_REQUEST',
|
|
7
|
-
VALIDATION_ERROR = 'VALIDATION_ERROR',
|
|
8
|
-
UNAUTHORIZED = 'UNAUTHORIZED',
|
|
9
|
-
FORBIDDEN = 'FORBIDDEN',
|
|
10
|
-
NOT_FOUND = 'NOT_FOUND',
|
|
11
|
-
CONFLICT = 'CONFLICT',
|
|
12
|
-
INTERNAL_ERROR = 'INTERNAL_ERROR',
|
|
13
|
-
RATE_LIMITED = 'RATE_LIMITED',
|
|
14
|
-
GATEWAY_TIMEOUT = 'GATEWAY_TIMEOUT',
|
|
15
|
-
NETWORK_ERROR = 'NETWORK_ERROR'
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Backend error response shape.
|
|
20
|
-
* Matches AppException.to_dict() output.
|
|
21
|
-
*/
|
|
22
|
-
export interface ErrorResponseBody {
|
|
23
|
-
status: 'error'
|
|
24
|
-
code: string
|
|
25
|
-
message: string
|
|
26
|
-
detail?: string
|
|
27
|
-
errors?: Record<string, unknown>
|
|
28
|
-
data?: unknown
|
|
29
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import type { TaruviConfig } from "../../types.js";
|
|
2
|
-
import type { TokenClient } from "../token/TokenClient.js";
|
|
3
|
-
import axios, { AxiosError, type AxiosInstance, type InternalAxiosRequestConfig } from "axios";
|
|
4
|
-
import { createErrorFromResponse, NetworkError, TaruviError } from "../errors/index.js";
|
|
5
|
-
import type { ErrorResponseBody } from "../errors/index.js";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* HttpClient handles all HTTP requests to the Taruvi API.
|
|
9
|
-
* Sends session token via X-Session-Token header.
|
|
10
|
-
* Clears tokens on 401 auth failures.
|
|
11
|
-
*
|
|
12
|
-
* @internal
|
|
13
|
-
*/
|
|
14
|
-
export class HttpClient {
|
|
15
|
-
private tokenClient: TokenClient
|
|
16
|
-
private axiosInstance: AxiosInstance
|
|
17
|
-
|
|
18
|
-
constructor(config: TaruviConfig, tokenClient: TokenClient) {
|
|
19
|
-
this.tokenClient = tokenClient
|
|
20
|
-
this.axiosInstance = axios.create({ baseURL: config.apiUrl, withCredentials: true })
|
|
21
|
-
this.setupInterceptors()
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
private setupInterceptors(): void {
|
|
25
|
-
// Request interceptor: attach session token
|
|
26
|
-
console.log("test")
|
|
27
|
-
this.axiosInstance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
|
|
28
|
-
const isFormData = config.data instanceof FormData
|
|
29
|
-
if (!isFormData) {
|
|
30
|
-
config.headers['Content-Type'] = 'application/json'
|
|
31
|
-
}
|
|
32
|
-
const sessionToken = this.tokenClient.getSessionToken()
|
|
33
|
-
if (sessionToken) {
|
|
34
|
-
config.headers['X-Session-Token'] = sessionToken
|
|
35
|
-
}
|
|
36
|
-
return config
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
// Response interceptor: clear tokens on authentication failure
|
|
40
|
-
// Only 401 (Unauthorized) means the session is invalid
|
|
41
|
-
// 403 (Forbidden) means authenticated but lacking permission — don't clear tokens
|
|
42
|
-
this.axiosInstance.interceptors.response.use(
|
|
43
|
-
(response) => response,
|
|
44
|
-
(error: AxiosError) => {
|
|
45
|
-
const status = error.response?.status
|
|
46
|
-
if (status === 401) {
|
|
47
|
-
this.tokenClient.clearTokens()
|
|
48
|
-
}
|
|
49
|
-
return Promise.reject(error)
|
|
50
|
-
}
|
|
51
|
-
)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
private handleError(error: unknown): never {
|
|
55
|
-
if (error instanceof TaruviError) {
|
|
56
|
-
throw error
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (error instanceof AxiosError) {
|
|
60
|
-
if (error.response) {
|
|
61
|
-
const body = error.response.data as ErrorResponseBody | undefined
|
|
62
|
-
throw createErrorFromResponse(error.response.status, body)
|
|
63
|
-
}
|
|
64
|
-
throw new NetworkError(error.message)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
throw error
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async get<T>(endpoint: string, options?: { responseType?: 'json' | 'blob' }): Promise<T> {
|
|
71
|
-
try {
|
|
72
|
-
const { data } = await this.axiosInstance.get<T>(`/${endpoint}`, {
|
|
73
|
-
...(options?.responseType && { responseType: options.responseType }),
|
|
74
|
-
})
|
|
75
|
-
return data as T
|
|
76
|
-
} catch (error) {
|
|
77
|
-
this.handleError(error)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async post<T, D = unknown>(endpoint: string, body: D): Promise<T> {
|
|
82
|
-
try {
|
|
83
|
-
const { data } = await this.axiosInstance.post<T>(`/${endpoint}`, body)
|
|
84
|
-
return data
|
|
85
|
-
} catch (error) {
|
|
86
|
-
this.handleError(error)
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async put<T, D = unknown>(endpoint: string, body: D): Promise<T> {
|
|
91
|
-
try {
|
|
92
|
-
const { data } = await this.axiosInstance.put<T>(`/${endpoint}`, body)
|
|
93
|
-
return data
|
|
94
|
-
} catch (error) {
|
|
95
|
-
this.handleError(error)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async delete<T, D = unknown>(endpoint: string, body?: D): Promise<T> {
|
|
100
|
-
try {
|
|
101
|
-
const { data } = await this.axiosInstance.delete<T>(`/${endpoint}`, { data: body })
|
|
102
|
-
return data
|
|
103
|
-
} catch (error) {
|
|
104
|
-
this.handleError(error)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async patch<T, D = unknown>(endpoint: string, body: D): Promise<T> {
|
|
109
|
-
try {
|
|
110
|
-
const { data } = await this.axiosInstance.patch<T>(`/${endpoint}`, body)
|
|
111
|
-
return data
|
|
112
|
-
} catch (error) {
|
|
113
|
-
this.handleError(error)
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export const AppRoutes = {
|
|
2
|
-
baseUrl: (appSlug: string) => `api/apps/${appSlug}`,
|
|
3
|
-
roles: (): string => `/roles`,
|
|
4
|
-
settings: (): string => "/settings/"
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
type AllRouteKeys = keyof typeof AppRoutes
|
|
8
|
-
export type AppRouteKey = Exclude<AllRouteKeys, 'baseUrl'>
|
|
9
|
-
export type AppUrlParams = Partial<Record<AppRouteKey, string>>
|
|
File without changes
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export const DatabaseRoutes = {
|
|
2
|
-
baseUrl: (appSlug: string) => `api/apps/${appSlug}`,
|
|
3
|
-
dataTables: (tableName: string): string => `/datatables/${tableName}/data`,
|
|
4
|
-
recordId: (recordId: string): string => `/${recordId}`,
|
|
5
|
-
upsert: (): string => `/upsert`
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
type AllRouteKeys = keyof typeof DatabaseRoutes
|
|
9
|
-
export type DatabaseRouteKey = Exclude<AllRouteKeys, 'baseUrl'>
|
|
10
|
-
export type DatabaseUrlParams = Partial<Record<DatabaseRouteKey, string>>
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export const StorageRoutes = {
|
|
2
|
-
baseUrl: (appslug: string, bucket: string) => `api/apps/${appslug}/storage/buckets/${bucket}/objects`,
|
|
3
|
-
path: (path: string) => "/" + encodeURIComponent(path),
|
|
4
|
-
upload: () => "/batch-upload",
|
|
5
|
-
delete: () => "/batch-delete"
|
|
6
|
-
// bucket: (appslug: string, bucketslug: string) => `${StorageRoutesClone.baseUrl(appslug)}/${bucketslug}`
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export type StoragePathKey = 'path'
|
|
10
|
-
export type StorageFlagKey = 'upload' | 'delete'
|
|
11
|
-
export type StorageRouteKey = StoragePathKey | StorageFlagKey
|
|
12
|
-
|
|
13
|
-
export type BucketUrlParams = Partial<
|
|
14
|
-
Record<StorageRouteKey, string | true>
|
|
15
|
-
>
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export const UserRoutes = {
|
|
2
|
-
baseUrl: "api/users/",
|
|
3
|
-
getCurrentUser: () => `${UserRoutes.baseUrl}me/`,
|
|
4
|
-
preferences: () => `${UserRoutes.baseUrl}me/preferences/`,
|
|
5
|
-
getUser: (username: string) => `${UserRoutes.baseUrl}${username}/`,
|
|
6
|
-
updateUser: (username: string) => `${UserRoutes.baseUrl}${username}/`,
|
|
7
|
-
deleteUser: (username: string) => `${UserRoutes.baseUrl}${username}/`,
|
|
8
|
-
listUser: (filter: string) => `${UserRoutes.baseUrl}${filter}`,
|
|
9
|
-
getUserApps: (username: string) => `${UserRoutes.baseUrl}${username}/apps/`,
|
|
10
|
-
assignRoles: () => `api/assign/roles/`,
|
|
11
|
-
revokeRoles: () => `api/revoke/roles/`
|
|
12
|
-
} as const
|
|
File without changes
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { getRuntimeEnvironment } from "../../utils/utils.js"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* TokenClient - Manages session token for browser authentication
|
|
5
|
-
*/
|
|
6
|
-
export interface AuthTokens {
|
|
7
|
-
sessionToken: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export class TokenClient {
|
|
11
|
-
private static readonly SESSION_TOKEN_KEY = 'session_token';
|
|
12
|
-
|
|
13
|
-
private runTimeEnvironment: string
|
|
14
|
-
private browserRunTime: boolean
|
|
15
|
-
private serverToken: string | null = null
|
|
16
|
-
|
|
17
|
-
constructor(token?: string) {
|
|
18
|
-
this.runTimeEnvironment = getRuntimeEnvironment()
|
|
19
|
-
this.browserRunTime = this.runTimeEnvironment == "Browser"
|
|
20
|
-
|
|
21
|
-
if (!this.browserRunTime && token) {
|
|
22
|
-
this.serverToken = token
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Get session token
|
|
28
|
-
*/
|
|
29
|
-
getSessionToken(): string | null {
|
|
30
|
-
if (this.browserRunTime) {
|
|
31
|
-
if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
|
|
32
|
-
return null
|
|
33
|
-
}
|
|
34
|
-
return localStorage.getItem(TokenClient.SESSION_TOKEN_KEY)
|
|
35
|
-
}
|
|
36
|
-
return this.serverToken
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Alias for getSessionToken (used by server-side code)
|
|
41
|
-
*/
|
|
42
|
-
getToken(): string | null {
|
|
43
|
-
return this.getSessionToken()
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Store session token
|
|
48
|
-
*/
|
|
49
|
-
setTokens(tokens: AuthTokens): void {
|
|
50
|
-
if (!this.browserRunTime) {
|
|
51
|
-
console.warn('Token storage is only available in browser environment')
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
|
|
56
|
-
return
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
localStorage.setItem(TokenClient.SESSION_TOKEN_KEY, tokens.sessionToken)
|
|
61
|
-
} catch (err) {
|
|
62
|
-
console.error('Failed to store session token:', err)
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Set session token directly
|
|
68
|
-
*/
|
|
69
|
-
setAccessToken(token: string): void {
|
|
70
|
-
if (this.browserRunTime) {
|
|
71
|
-
if (typeof window === 'undefined' || typeof localStorage === 'undefined') return
|
|
72
|
-
try {
|
|
73
|
-
localStorage.setItem(TokenClient.SESSION_TOKEN_KEY, token)
|
|
74
|
-
} catch (err) {
|
|
75
|
-
console.error('Failed to set session token:', err)
|
|
76
|
-
}
|
|
77
|
-
} else {
|
|
78
|
-
this.serverToken = token
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Check if user is authenticated (has a session token)
|
|
84
|
-
*/
|
|
85
|
-
isAuthenticated(): boolean {
|
|
86
|
-
return !!this.getSessionToken()
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Clear session token
|
|
91
|
-
*/
|
|
92
|
-
clearTokens(): void {
|
|
93
|
-
if (!this.browserRunTime) {
|
|
94
|
-
this.serverToken = null
|
|
95
|
-
return
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
|
|
99
|
-
return
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
localStorage.removeItem(TokenClient.SESSION_TOKEN_KEY)
|
|
104
|
-
} catch (err) {
|
|
105
|
-
console.error('Failed to clear session token:', err)
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
File without changes
|
package/src/types.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { MimeTypeCategory, Visibility } from './utils/enums.js'
|
|
2
|
-
|
|
3
|
-
export interface TaruviConfig {
|
|
4
|
-
apiKey: string // Identifies which site the client belongs to
|
|
5
|
-
appSlug: string // Identifies which app the client belongs to
|
|
6
|
-
apiUrl: string // Base API URL
|
|
7
|
-
deskUrl?: string // URL for the desk/login page
|
|
8
|
-
token?: string // Optional: Pre-existing auth token
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// Standard response wrapper matching backend AppDataResponse
|
|
12
|
-
export interface TaruviResponse<T = unknown> {
|
|
13
|
-
status: "success" | "error"
|
|
14
|
-
message: string
|
|
15
|
-
data: T
|
|
16
|
-
total?: number
|
|
17
|
-
pagination?: PaginationInfo
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface PaginationInfo {
|
|
21
|
-
offset: number
|
|
22
|
-
limit: number
|
|
23
|
-
count: number
|
|
24
|
-
current_page: number
|
|
25
|
-
total_pages: number
|
|
26
|
-
has_next: boolean
|
|
27
|
-
has_previous: boolean
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface StorageFilters {
|
|
31
|
-
// Pagination (DRF style)
|
|
32
|
-
page?: number
|
|
33
|
-
page_size?: number
|
|
34
|
-
|
|
35
|
-
// Range Filters - Size (in bytes)
|
|
36
|
-
size__gte?: number
|
|
37
|
-
size__lte?: number
|
|
38
|
-
size__gt?: number
|
|
39
|
-
size__lt?: number
|
|
40
|
-
min_size?: number
|
|
41
|
-
max_size?: number
|
|
42
|
-
|
|
43
|
-
// Range Filters - Dates (ISO 8601)
|
|
44
|
-
created_at__gte?: string
|
|
45
|
-
created_at__lte?: string
|
|
46
|
-
created_after?: string
|
|
47
|
-
created_before?: string
|
|
48
|
-
updated_at__gte?: string
|
|
49
|
-
updated_at__lte?: string
|
|
50
|
-
|
|
51
|
-
// Search Filters
|
|
52
|
-
search?: string
|
|
53
|
-
filename__icontains?: string
|
|
54
|
-
prefix?: string
|
|
55
|
-
file?: string
|
|
56
|
-
file__icontains?: string
|
|
57
|
-
file__startswith?: string
|
|
58
|
-
file__istartswith?: string
|
|
59
|
-
metadata_search?: string
|
|
60
|
-
|
|
61
|
-
// MIME Type Filters
|
|
62
|
-
mimetype?: string
|
|
63
|
-
mimetype__in?: string
|
|
64
|
-
mimetype_category?: MimeTypeCategory
|
|
65
|
-
|
|
66
|
-
// Visibility & User Filters
|
|
67
|
-
visibility?: Visibility
|
|
68
|
-
created_by_me?: boolean
|
|
69
|
-
modified_by_me?: boolean
|
|
70
|
-
created_by__username?: string
|
|
71
|
-
created_by__username__icontains?: string
|
|
72
|
-
|
|
73
|
-
// Sorting
|
|
74
|
-
ordering?: string
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface DatabaseFilters {
|
|
78
|
-
// Pagination (DRF style)
|
|
79
|
-
page?: number
|
|
80
|
-
page_size?: number
|
|
81
|
-
|
|
82
|
-
// Sorting (DRF style: "-field" for desc, "field" for asc)
|
|
83
|
-
ordering?: string
|
|
84
|
-
|
|
85
|
-
// Populate relations
|
|
86
|
-
populate?: string
|
|
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
|
-
|
|
96
|
-
/**
|
|
97
|
-
* JSON filter tree for the `filters` query param (set via `Database.filters(tree)`).
|
|
98
|
-
* Do not use the flat `filters(field, …)` triple overload with `field === 'filters'`.
|
|
99
|
-
*/
|
|
100
|
-
filters?: string
|
|
101
|
-
|
|
102
|
-
// Dynamic filters - allows any field with operators
|
|
103
|
-
[key: string]: string | number | boolean | undefined
|
|
104
|
-
}
|
package/src/utils/enums.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export const MimeTypeCategory = {
|
|
2
|
-
IMAGE: 'image',
|
|
3
|
-
VIDEO: 'video',
|
|
4
|
-
AUDIO: 'audio',
|
|
5
|
-
APPLICATION: 'application',
|
|
6
|
-
TEXT: 'text'
|
|
7
|
-
} as const
|
|
8
|
-
|
|
9
|
-
export type MimeTypeCategory = typeof MimeTypeCategory[keyof typeof MimeTypeCategory]
|
|
10
|
-
|
|
11
|
-
export const Visibility = {
|
|
12
|
-
PUBLIC: 'public',
|
|
13
|
-
PRIVATE: 'private'
|
|
14
|
-
} as const
|
|
15
|
-
|
|
16
|
-
export type Visibility = typeof Visibility[keyof typeof Visibility]
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
export const QueryParams = {
|
|
20
|
-
include: "include",
|
|
21
|
-
depth: "depth",
|
|
22
|
-
format: "format",
|
|
23
|
-
graph_type: "graph_type",
|
|
24
|
-
}
|
package/src/utils/utils.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
const _window = typeof window !== 'undefined' ? window : undefined
|
|
2
|
-
const _navigator = typeof navigator !== 'undefined' ? navigator : undefined
|
|
3
|
-
const _document = typeof document !== 'undefined' ? document : undefined
|
|
4
|
-
|
|
5
|
-
export const isBrowser = (): boolean => {
|
|
6
|
-
return _window !== undefined && _document !== undefined
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export const isReactNative = (): boolean => {
|
|
10
|
-
return _navigator !== undefined &&
|
|
11
|
-
// @ts-ignore - navigator.product is deprecated but still used in RN detection
|
|
12
|
-
_navigator.product === 'ReactNative'
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const getRuntimeEnvironment = (): string => {
|
|
16
|
-
if (isBrowser()) return 'Browser'
|
|
17
|
-
if (isReactNative()) return 'ReactNative'
|
|
18
|
-
return 'Server'
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Builds a query string. Keys may include pre-flattened bracket names (e.g. CrudFilters `filters[0][field]=...`). */
|
|
22
|
-
export function buildQueryString(queryParams: Record<string, unknown> | undefined): string {
|
|
23
|
-
if (!queryParams || Object.keys(queryParams).length === 0) {
|
|
24
|
-
return ''
|
|
25
|
-
}
|
|
26
|
-
const params = new URLSearchParams()
|
|
27
|
-
Object.entries(queryParams).forEach(([key, value]) => {
|
|
28
|
-
if (value !== undefined && value !== null) {
|
|
29
|
-
if (Array.isArray(value)) {
|
|
30
|
-
value.forEach((v) => params.append(key, String(v)))
|
|
31
|
-
} else {
|
|
32
|
-
params.append(key, String(value))
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
})
|
|
36
|
-
const queryString = params.toString()
|
|
37
|
-
return queryString ? `?${queryString}` : ''
|
|
38
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { vi } from 'vitest'
|
|
2
|
-
import { Client } from '../../src/client.js'
|
|
3
|
-
|
|
4
|
-
export const mockHttpClient = {
|
|
5
|
-
get: vi.fn(),
|
|
6
|
-
post: vi.fn(),
|
|
7
|
-
put: vi.fn(),
|
|
8
|
-
patch: vi.fn(),
|
|
9
|
-
delete: vi.fn()
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const mockClient = {
|
|
13
|
-
getConfig: () => ({ apiKey: 'test-key', appSlug: 'test-app', apiUrl: 'https://api.test.com' }),
|
|
14
|
-
httpClient: mockHttpClient
|
|
15
|
-
} as unknown as Client
|
|
16
|
-
|
|
17
|
-
export function resetMocks() {
|
|
18
|
-
vi.clearAllMocks()
|
|
19
|
-
}
|
package/tests/mocks/db.json
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
-
import { Analytics } from '../../../src/lib/analytics/AnalyticsClient.js'
|
|
3
|
-
import { Client } from '../../../src/client.js'
|
|
4
|
-
|
|
5
|
-
const mockHttpClient = {
|
|
6
|
-
post: vi.fn()
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const mockClient = {
|
|
10
|
-
getConfig: () => ({ apiKey: 'test-key', appSlug: 'test-app', apiUrl: 'https://api.test.com' }),
|
|
11
|
-
httpClient: mockHttpClient
|
|
12
|
-
} as unknown as Client
|
|
13
|
-
|
|
14
|
-
describe('Analytics', () => {
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
vi.clearAllMocks()
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
describe('execute()', () => {
|
|
20
|
-
it('executes analytics query with default options', async () => {
|
|
21
|
-
const response = { data: { total_sales: 1000 } }
|
|
22
|
-
mockHttpClient.post.mockResolvedValue(response)
|
|
23
|
-
|
|
24
|
-
const analytics = new Analytics(mockClient)
|
|
25
|
-
const result = await analytics.execute('sales-report')
|
|
26
|
-
|
|
27
|
-
expect(mockHttpClient.post).toHaveBeenCalledWith(
|
|
28
|
-
'api/apps/test-app/analytics/queries/sales-report/execute/',
|
|
29
|
-
{ params: {} }
|
|
30
|
-
)
|
|
31
|
-
expect(result).toEqual(response)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('executes analytics query with params', async () => {
|
|
35
|
-
const response = { data: { total_sales: 5000 } }
|
|
36
|
-
mockHttpClient.post.mockResolvedValue(response)
|
|
37
|
-
|
|
38
|
-
const analytics = new Analytics(mockClient)
|
|
39
|
-
await analytics.execute('monthly-report', {
|
|
40
|
-
params: {
|
|
41
|
-
start_date: '2024-01-01',
|
|
42
|
-
end_date: '2024-12-31'
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
expect(mockHttpClient.post).toHaveBeenCalledWith(
|
|
47
|
-
'api/apps/test-app/analytics/queries/monthly-report/execute/',
|
|
48
|
-
{
|
|
49
|
-
params: {
|
|
50
|
-
start_date: '2024-01-01',
|
|
51
|
-
end_date: '2024-12-31'
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
)
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('supports typed response', async () => {
|
|
58
|
-
interface SalesData {
|
|
59
|
-
total_sales: number
|
|
60
|
-
orders_count: number
|
|
61
|
-
}
|
|
62
|
-
const response = { data: { total_sales: 10000, orders_count: 50 } }
|
|
63
|
-
mockHttpClient.post.mockResolvedValue(response)
|
|
64
|
-
|
|
65
|
-
const analytics = new Analytics(mockClient)
|
|
66
|
-
const result = await analytics.execute<SalesData>('sales-summary')
|
|
67
|
-
|
|
68
|
-
expect(result.data?.total_sales).toBe(10000)
|
|
69
|
-
expect(result.data?.orders_count).toBe(50)
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
it('builds correct URL with app slug and query slug', async () => {
|
|
73
|
-
mockHttpClient.post.mockResolvedValue({})
|
|
74
|
-
|
|
75
|
-
const analytics = new Analytics(mockClient)
|
|
76
|
-
await analytics.execute('dashboard-metrics')
|
|
77
|
-
|
|
78
|
-
expect(mockHttpClient.post).toHaveBeenCalledWith(
|
|
79
|
-
'api/apps/test-app/analytics/queries/dashboard-metrics/execute/',
|
|
80
|
-
expect.any(Object)
|
|
81
|
-
)
|
|
82
|
-
})
|
|
83
|
-
})
|
|
84
|
-
})
|