@tenxyte/core 0.1.5 → 0.9.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/src/client.ts DELETED
@@ -1,21 +0,0 @@
1
- import { TenxyteHttpClient, HttpClientOptions } from './http/client';
2
- import { AuthModule } from './modules/auth';
3
- import { SecurityModule } from './modules/security';
4
- import { RbacModule } from './modules/rbac';
5
- import { UserModule } from './modules/user';
6
-
7
- export class TenxyteClient {
8
- public http: TenxyteHttpClient;
9
- public auth: AuthModule;
10
- public security: SecurityModule;
11
- public rbac: RbacModule;
12
- public user: UserModule;
13
-
14
- constructor(options: HttpClientOptions) {
15
- this.http = new TenxyteHttpClient(options);
16
- this.auth = new AuthModule(this.http);
17
- this.security = new SecurityModule(this.http);
18
- this.rbac = new RbacModule(this.http);
19
- this.user = new UserModule(this.http);
20
- }
21
- }
package/src/config.ts DELETED
File without changes
@@ -1,162 +0,0 @@
1
- import type { TenxyteError } from '../types';
2
-
3
- export interface HttpClientOptions {
4
- baseUrl: string;
5
- timeoutMs?: number;
6
- headers?: Record<string, string>;
7
- }
8
-
9
- export type RequestConfig = Omit<RequestInit, 'body' | 'headers'> & {
10
- body?: unknown;
11
- headers?: Record<string, string>;
12
- params?: Record<string, string | number | boolean>;
13
- };
14
-
15
- /**
16
- * Core HTTP Client underlying the SDK.
17
- * Handles JSON parsing, standard headers, simple request processing,
18
- * and normalizing errors into TenxyteError format.
19
- */
20
- export class TenxyteHttpClient {
21
- private baseUrl: string;
22
- private defaultHeaders: Record<string, string>;
23
-
24
- // Interceptors
25
- private requestInterceptors: Array<(config: RequestConfig & { url: string }) => Promise<RequestConfig & { url: string }> | (RequestConfig & { url: string })> = [];
26
- private responseInterceptors: Array<(response: Response, request: { url: string; config: RequestConfig }) => Promise<Response> | Response> = [];
27
-
28
- constructor(options: HttpClientOptions) {
29
- this.baseUrl = options.baseUrl.replace(/\/$/, ''); // Remove trailing slash
30
- this.defaultHeaders = {
31
- 'Content-Type': 'application/json',
32
- Accept: 'application/json',
33
- ...options.headers,
34
- };
35
- }
36
-
37
- // Interceptor Registration
38
- addRequestInterceptor(interceptor: typeof this.requestInterceptors[0]) {
39
- this.requestInterceptors.push(interceptor);
40
- }
41
-
42
- addResponseInterceptor(interceptor: typeof this.responseInterceptors[0]) {
43
- this.responseInterceptors.push(interceptor);
44
- }
45
-
46
- /**
47
- * Main request method wrapping fetch
48
- */
49
- async request<T>(endpoint: string, config: RequestConfig = {}): Promise<T> {
50
- const urlStr = endpoint.startsWith('http')
51
- ? endpoint
52
- : `${this.baseUrl}${endpoint.startsWith('/') ? '' : '/'}${endpoint}`;
53
-
54
- let urlObj = new URL(urlStr);
55
-
56
- if (config.params) {
57
- Object.entries(config.params).forEach(([key, value]) => {
58
- if (value !== undefined && value !== null) {
59
- urlObj.searchParams.append(key, String(value));
60
- }
61
- });
62
- }
63
-
64
- let requestContext: any = {
65
- url: urlObj.toString(),
66
- ...config,
67
- headers: { ...this.defaultHeaders, ...(config.headers || {}) } as Record<string, string>,
68
- };
69
-
70
- // Handle FormData implicitly for multipart requests
71
- if (typeof FormData !== 'undefined' && requestContext.body instanceof FormData) {
72
- const headers = requestContext.headers as Record<string, string>;
73
- // Explicitly remove Content-Type so fetch can auto-assign the multipart boundary
74
- delete headers['Content-Type'];
75
- delete headers['content-type'];
76
- } else if (requestContext.body && typeof requestContext.body === 'object') {
77
- const contentType = (requestContext.headers as Record<string, string>)['Content-Type'] || '';
78
- if (contentType.toLowerCase().includes('application/json')) {
79
- requestContext.body = JSON.stringify(requestContext.body);
80
- }
81
- }
82
-
83
- // Run Request Interceptors
84
- for (const interceptor of this.requestInterceptors) {
85
- requestContext = await interceptor(requestContext);
86
- }
87
-
88
- const { url, ...fetchConfig } = requestContext as any;
89
-
90
- try {
91
- let response = await fetch(url, fetchConfig as RequestInit);
92
-
93
- // Run Response Interceptors (e.g., token refresh logic)
94
- for (const interceptor of this.responseInterceptors) {
95
- response = await interceptor(response, { url, config: fetchConfig as RequestConfig });
96
- }
97
-
98
- if (!response.ok) {
99
- throw await this.normalizeError(response);
100
- }
101
-
102
- // Handle NoContent
103
- if (response.status === 204) {
104
- return {} as T;
105
- }
106
-
107
- const contentType = response.headers.get('content-type');
108
- if (contentType && contentType.includes('application/json')) {
109
- return (await response.json()) as T;
110
- }
111
-
112
- return (await response.text()) as unknown as T;
113
- } catch (error: any) {
114
- if (error && error.code) {
115
- throw error; // Already normalized
116
- }
117
- throw {
118
- error: error.message || 'Network request failed',
119
- code: 'NETWORK_ERROR' as unknown as import('../types').TenxyteErrorCode,
120
- details: String(error)
121
- } as TenxyteError;
122
- }
123
- }
124
-
125
- private async normalizeError(response: Response): Promise<TenxyteError> {
126
- try {
127
- const body = await response.json();
128
- return {
129
- error: body.error || body.detail || 'API request failed',
130
- code: body.code || `HTTP_${response.status}`,
131
- details: body.details || body,
132
- retry_after: response.headers.has('Retry-After') ? parseInt(response.headers.get('Retry-After')!, 10) : undefined,
133
- } as TenxyteError;
134
- } catch (e) {
135
- return {
136
- error: `HTTP Error ${response.status}: ${response.statusText}`,
137
- code: `HTTP_${response.status}` as unknown as import('../types').TenxyteErrorCode,
138
- } as TenxyteError;
139
- }
140
- }
141
-
142
- // Convenience methods
143
- get<T>(endpoint: string, config?: Omit<RequestConfig, 'method' | 'body'>) {
144
- return this.request<T>(endpoint, { ...config, method: 'GET' });
145
- }
146
-
147
- post<T>(endpoint: string, data?: unknown, config?: Omit<RequestConfig, 'method' | 'body'>) {
148
- return this.request<T>(endpoint, { ...config, method: 'POST', body: data });
149
- }
150
-
151
- put<T>(endpoint: string, data?: unknown, config?: Omit<RequestConfig, 'method' | 'body'>) {
152
- return this.request<T>(endpoint, { ...config, method: 'PUT', body: data });
153
- }
154
-
155
- patch<T>(endpoint: string, data?: unknown, config?: Omit<RequestConfig, 'method' | 'body'>) {
156
- return this.request<T>(endpoint, { ...config, method: 'PATCH', body: data });
157
- }
158
-
159
- delete<T>(endpoint: string, config?: Omit<RequestConfig, 'method' | 'body'>) {
160
- return this.request<T>(endpoint, { ...config, method: 'DELETE' });
161
- }
162
- }
package/src/http/index.ts DELETED
@@ -1 +0,0 @@
1
- export * from './client';
@@ -1,117 +0,0 @@
1
- import type { TenxyteStorage } from '../storage';
2
- import type { RequestConfig, TenxyteHttpClient } from './client';
3
-
4
- export interface TenxyteContext {
5
- activeOrgSlug: string | null;
6
- agentTraceId: string | null;
7
- }
8
-
9
- export function createAuthInterceptor(storage: TenxyteStorage, context: TenxyteContext) {
10
- return async (request: RequestConfig & { url: string }) => {
11
- // Inject Authorization if present
12
- const token = await storage.getItem('tx_access');
13
- const headers = { ...(request.headers as Record<string, string>) || {} };
14
-
15
- if (token && !headers['Authorization']) {
16
- headers['Authorization'] = `Bearer ${token}`;
17
- }
18
-
19
- // Inject Contextual Headers based on SDK state
20
- if (context.activeOrgSlug && !headers['X-Org-Slug']) {
21
- headers['X-Org-Slug'] = context.activeOrgSlug;
22
- }
23
-
24
- if (context.agentTraceId && !headers['X-Prompt-Trace-ID']) {
25
- headers['X-Prompt-Trace-ID'] = context.agentTraceId;
26
- }
27
-
28
- return { ...request, headers };
29
- };
30
- }
31
-
32
- export function createRefreshInterceptor(
33
- client: TenxyteHttpClient,
34
- storage: TenxyteStorage,
35
- onSessionExpired: () => void
36
- ) {
37
- let isRefreshing = false;
38
- let refreshQueue: Array<(token: string | null) => void> = [];
39
-
40
- const processQueue = (error: Error | null, token: string | null = null) => {
41
- refreshQueue.forEach(prom => prom(token));
42
- refreshQueue = [];
43
- };
44
-
45
- return async (response: Response, request: { url: string; config: RequestConfig }): Promise<Response> => {
46
- // Only intercept 401s when not attempting to login/refresh itself
47
- if (response.status === 401 && !request.url.includes('/auth/refresh') && !request.url.includes('/auth/login')) {
48
- const refreshToken = await storage.getItem('tx_refresh');
49
-
50
- if (!refreshToken) {
51
- onSessionExpired();
52
- return response; // Pass through 401 if we cannot refresh
53
- }
54
-
55
- if (isRefreshing) {
56
- // Wait in queue for the refresh to complete
57
- return new Promise<Response>((resolve) => {
58
- refreshQueue.push((newToken: string | null) => {
59
- if (newToken) {
60
- const retryHeaders = { ...(request.config.headers as Record<string, string>), Authorization: `Bearer ${newToken}` };
61
- resolve(fetch(request.url, { ...request.config, headers: retryHeaders } as RequestInit));
62
- } else {
63
- resolve(response);
64
- }
65
- });
66
- });
67
- }
68
-
69
- // We are the first one, initiate refresh
70
- isRefreshing = true;
71
-
72
- try {
73
- const refreshResponse = await fetch(`${client['baseUrl']}/auth/refresh/`, {
74
- method: 'POST',
75
- headers: { 'Content-Type': 'application/json' },
76
- body: JSON.stringify({ refresh_token: refreshToken })
77
- });
78
-
79
- if (!refreshResponse.ok) {
80
- throw new Error('Refresh failed');
81
- }
82
-
83
- const data = await refreshResponse.json();
84
-
85
- await storage.setItem('tx_access', data.access);
86
- if (data.refresh) {
87
- await storage.setItem('tx_refresh', data.refresh);
88
- }
89
-
90
- isRefreshing = false;
91
- processQueue(null, data.access);
92
-
93
- // Retry original request seamlessly for the caller that initiated this
94
- const retryHeaders = { ...(request.config.headers as Record<string, string>), Authorization: `Bearer ${data.access}` };
95
- // We use fetch directly to return a true Response object back to the chain,
96
- // rather than using client.request which resolves the JSON.
97
- // Wait, the interceptor must return a Promise<Response>!
98
- const r = await fetch(request.url, { ...request.config, headers: retryHeaders } as RequestInit);
99
- return r;
100
-
101
- } catch (err) {
102
- // Refresh failed (invalid token, expired, network error)
103
- isRefreshing = false;
104
- await storage.removeItem('tx_access');
105
- await storage.removeItem('tx_refresh');
106
-
107
- processQueue(err as Error, null);
108
- onSessionExpired();
109
-
110
- // Pass original 401 back
111
- return response;
112
- }
113
- }
114
-
115
- return response;
116
- };
117
- }
package/src/index.ts DELETED
@@ -1,7 +0,0 @@
1
- export * from './client';
2
- export * from './http/client';
3
- export * from './modules/auth';
4
- export * from './modules/security';
5
- export * from './modules/rbac';
6
- export * from './modules/user';
7
- export * from './types';
package/src/modules/ai.ts DELETED
File without changes
@@ -1,95 +0,0 @@
1
- import { TenxyteHttpClient } from '../http/client';
2
- import { TokenPair, GeneratedSchema } from '../types';
3
-
4
- export interface LoginEmailOptions {
5
- totp_code?: string;
6
- }
7
-
8
- export interface LoginPhoneOptions {
9
- totp_code?: string;
10
- }
11
-
12
- export type RegisterRequest = any;
13
-
14
- export interface MagicLinkRequest {
15
- email: string;
16
- }
17
-
18
- export interface SocialLoginRequest {
19
- access_token?: string;
20
- authorization_code?: string;
21
- id_token?: string;
22
- }
23
-
24
- export class AuthModule {
25
- constructor(private client: TenxyteHttpClient) { }
26
-
27
- /**
28
- * Authenticate user with email and password
29
- */
30
- async loginWithEmail(
31
- data: GeneratedSchema['LoginEmail'],
32
- ): Promise<TokenPair> {
33
- return this.client.post<TokenPair>('/api/v1/auth/login/email/', data);
34
- }
35
-
36
- /**
37
- * Authenticate user with international phone number and password
38
- */
39
- async loginWithPhone(
40
- data: GeneratedSchema['LoginPhone'],
41
- ): Promise<TokenPair> {
42
- return this.client.post<TokenPair>('/api/v1/auth/login/phone/', data);
43
- }
44
-
45
- /**
46
- * Register a new user
47
- */
48
- async register(data: RegisterRequest): Promise<any> {
49
- return this.client.post<any>('/api/v1/auth/register/', data);
50
- }
51
-
52
- /**
53
- * Logout from the current session
54
- */
55
- async logout(refreshToken: string): Promise<void> {
56
- return this.client.post<void>('/api/v1/auth/logout/', { refresh_token: refreshToken });
57
- }
58
-
59
- /**
60
- * Logout from all sessions (revokes all refresh tokens)
61
- */
62
- async logoutAll(): Promise<void> {
63
- return this.client.post<void>('/api/v1/auth/logout/all/');
64
- }
65
-
66
- /**
67
- * Request a magic link for sign-in
68
- */
69
- async requestMagicLink(data: MagicLinkRequest): Promise<void> {
70
- return this.client.post<void>('/api/v1/auth/magic-link/request/', data);
71
- }
72
-
73
- /**
74
- * Verify a magic link token
75
- */
76
- async verifyMagicLink(token: string): Promise<TokenPair> {
77
- return this.client.get<TokenPair>(`/api/v1/auth/magic-link/verify/`, { params: { token } });
78
- }
79
-
80
- /**
81
- * Perform OAuth2 Social Authentication (e.g. Google, GitHub)
82
- */
83
- async loginWithSocial(provider: 'google' | 'github' | 'microsoft' | 'facebook', data: SocialLoginRequest): Promise<TokenPair> {
84
- return this.client.post<TokenPair>(`/api/v1/auth/social/${provider}/`, data);
85
- }
86
-
87
- /**
88
- * Handle Social Auth Callback (authorization code flow)
89
- */
90
- async handleSocialCallback(provider: 'google' | 'github' | 'microsoft' | 'facebook', code: string, redirectUri: string): Promise<TokenPair> {
91
- return this.client.get<TokenPair>(`/api/v1/auth/social/${provider}/callback/`, {
92
- params: { code, redirect_uri: redirectUri },
93
- });
94
- }
95
- }
File without changes
@@ -1,160 +0,0 @@
1
- import { TenxyteHttpClient } from '../http/client';
2
- import { decodeJwt, DecodedTenxyteToken } from '../utils/jwt';
3
-
4
- export interface Role {
5
- id: string;
6
- name: string;
7
- description?: string;
8
- is_default?: boolean;
9
- permissions?: string[];
10
- }
11
-
12
- export interface Permission {
13
- id: string;
14
- code: string;
15
- name: string;
16
- description?: string;
17
- }
18
-
19
- export class RbacModule {
20
- private cachedToken: string | null = null;
21
-
22
- constructor(private client: TenxyteHttpClient) { }
23
-
24
- /**
25
- * Cache a token to use for parameter-less synchronous checks.
26
- */
27
- setToken(token: string | null) {
28
- this.cachedToken = token;
29
- }
30
-
31
- private getDecodedToken(token?: string): DecodedTenxyteToken | null {
32
- const t = token || this.cachedToken;
33
- if (!t) return null;
34
- return decodeJwt(t);
35
- }
36
-
37
- // --- Synchronous Checks --- //
38
-
39
- hasRole(role: string, token?: string): boolean {
40
- const decoded = this.getDecodedToken(token);
41
- if (!decoded?.roles) return false;
42
- return decoded.roles.includes(role);
43
- }
44
-
45
- hasAnyRole(roles: string[], token?: string): boolean {
46
- const decoded = this.getDecodedToken(token);
47
- if (!decoded?.roles) return false;
48
- return roles.some(r => decoded.roles!.includes(r));
49
- }
50
-
51
- hasAllRoles(roles: string[], token?: string): boolean {
52
- const decoded = this.getDecodedToken(token);
53
- if (!decoded?.roles) return false;
54
- return roles.every(r => decoded.roles!.includes(r));
55
- }
56
-
57
- hasPermission(permission: string, token?: string): boolean {
58
- const decoded = this.getDecodedToken(token);
59
- if (!decoded?.permissions) return false;
60
- // Check exact match or wildcard, assuming backend handles wildcard expansion in JWT
61
- return decoded.permissions.includes(permission);
62
- }
63
-
64
- hasAnyPermission(permissions: string[], token?: string): boolean {
65
- const decoded = this.getDecodedToken(token);
66
- if (!decoded?.permissions) return false;
67
- return permissions.some(p => decoded.permissions!.includes(p));
68
- }
69
-
70
- hasAllPermissions(permissions: string[], token?: string): boolean {
71
- const decoded = this.getDecodedToken(token);
72
- if (!decoded?.permissions) return false;
73
- return permissions.every(p => decoded.permissions!.includes(p));
74
- }
75
-
76
- // --- Roles CRUD --- //
77
-
78
- async listRoles(): Promise<Role[]> {
79
- return this.client.get<Role[]>('/api/v1/auth/roles/');
80
- }
81
-
82
- async createRole(data: { name: string; description?: string; permission_codes?: string[]; is_default?: boolean }): Promise<Role> {
83
- return this.client.post<Role>('/api/v1/auth/roles/', data);
84
- }
85
-
86
- async getRole(roleId: string): Promise<Role> {
87
- return this.client.get<Role>(`/api/v1/auth/roles/${roleId}/`);
88
- }
89
-
90
- async updateRole(roleId: string, data: { name?: string; description?: string; permission_codes?: string[]; is_default?: boolean }): Promise<Role> {
91
- return this.client.put<Role>(`/api/v1/auth/roles/${roleId}/`, data);
92
- }
93
-
94
- async deleteRole(roleId: string): Promise<void> {
95
- return this.client.delete<void>(`/api/v1/auth/roles/${roleId}/`);
96
- }
97
-
98
- // --- Role Permissions Management --- //
99
-
100
- async getRolePermissions(roleId: string): Promise<Permission[]> {
101
- return this.client.get<Permission[]>(`/api/v1/auth/roles/${roleId}/permissions/`);
102
- }
103
-
104
- async addPermissionsToRole(roleId: string, permission_codes: string[]): Promise<void> {
105
- return this.client.post<void>(`/api/v1/auth/roles/${roleId}/permissions/`, { permission_codes });
106
- }
107
-
108
- async removePermissionsFromRole(roleId: string, permission_codes: string[]): Promise<void> {
109
- return this.client.delete<void>(`/api/v1/auth/roles/${roleId}/permissions/`, {
110
- // Note: DELETE request with body is supported via our fetch wrapper if enabled,
111
- // or we might need to rely on query strings. The schema specifies body or query.
112
- // Let's pass it in body via a custom config or URL params.
113
- body: { permission_codes }
114
- } as any);
115
- }
116
-
117
- // --- Permissions CRUD --- //
118
-
119
- async listPermissions(): Promise<Permission[]> {
120
- return this.client.get<Permission[]>('/api/v1/auth/permissions/');
121
- }
122
-
123
- async createPermission(data: { code: string; name: string; description?: string; parent_code?: string }): Promise<Permission> {
124
- return this.client.post<Permission>('/api/v1/auth/permissions/', data);
125
- }
126
-
127
- async getPermission(permissionId: string): Promise<Permission> {
128
- return this.client.get<Permission>(`/api/v1/auth/permissions/${permissionId}/`);
129
- }
130
-
131
- async updatePermission(permissionId: string, data: { name?: string; description?: string }): Promise<Permission> {
132
- return this.client.put<Permission>(`/api/v1/auth/permissions/${permissionId}/`, data);
133
- }
134
-
135
- async deletePermission(permissionId: string): Promise<void> {
136
- return this.client.delete<void>(`/api/v1/auth/permissions/${permissionId}/`);
137
- }
138
-
139
- // --- Direct Assignment (Users) --- //
140
-
141
- async assignRoleToUser(userId: string, roleCode: string): Promise<void> {
142
- return this.client.post<void>(`/api/v1/auth/users/${userId}/roles/`, { role_code: roleCode });
143
- }
144
-
145
- async removeRoleFromUser(userId: string, roleCode: string): Promise<void> {
146
- return this.client.delete<void>(`/api/v1/auth/users/${userId}/roles/`, {
147
- params: { role_code: roleCode }
148
- });
149
- }
150
-
151
- async assignPermissionsToUser(userId: string, permissionCodes: string[]): Promise<void> {
152
- return this.client.post<void>(`/api/v1/auth/users/${userId}/permissions/`, { permission_codes: permissionCodes });
153
- }
154
-
155
- async removePermissionsFromUser(userId: string, permissionCodes: string[]): Promise<void> {
156
- return this.client.delete<void>(`/api/v1/auth/users/${userId}/permissions/`, {
157
- body: { permission_codes: permissionCodes }
158
- } as any);
159
- }
160
- }