@tenxyte/core 0.9.0 → 0.9.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tenxyte
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,11 +1,21 @@
1
1
  # @tenxyte/core
2
2
 
3
- The official core JavaScript/TypeScript SDK for the Tenxyte API.
4
- This SDK is the foundation for interacting securely with Tenxyte's robust authentication, multi-tenant organization management, and Advanced AI Security (AIRS) capabilities.
3
+ The official core JavaScript/TypeScript SDK for the **Tenxyte** API — a unified platform for authentication, multi-tenant organizations, RBAC, GDPR compliance, and AI agent security.
5
4
 
6
- ## Installation
5
+ ## Features
6
+
7
+ - **Authentication** — Email/password, phone, magic link, social OAuth2, registration
8
+ - **Security** — 2FA/TOTP, OTP verification, WebAuthn/Passkeys (FIDO2), password management
9
+ - **RBAC** — Role & permission management, synchronous JWT checks, user role assignment
10
+ - **User Management** — Profile CRUD, avatar upload, admin user operations
11
+ - **B2B Multi-Tenancy** — Organization CRUD, member management, invitations, context switching
12
+ - **AI Agent Security (AIRS)** — Agent tokens, circuit breakers, Human-in-the-Loop, usage reporting
13
+ - **Applications** — API client management, credential regeneration
14
+ - **Admin** — Audit logs, login attempts, blacklisted/refresh token management
15
+ - **GDPR** — Account deletion flows, data export, admin deletion request processing
16
+ - **Dashboard** — Global, auth, security, GDPR, and per-org statistics
7
17
 
8
- You can install the package using `npm`, `yarn`, or `pnpm`:
18
+ ## Installation
9
19
 
10
20
  ```bash
11
21
  npm install @tenxyte/core
@@ -15,170 +25,420 @@ yarn add @tenxyte/core
15
25
  pnpm add @tenxyte/core
16
26
  ```
17
27
 
18
- ## Initialization
19
-
20
- The single entry point for all operations is the `TenxyteClient`. You must initialize it with your API's base URL and (if you're using App-Centric auth) the `appKey` in headers.
21
-
22
- > **Important**: Never expose an `appSecret` in frontend environments like React or Vue client bundles. Use it exclusively in server-side processes.
28
+ ## Quick Start
23
29
 
24
30
  ```typescript
25
31
  import { TenxyteClient } from '@tenxyte/core';
26
32
 
27
33
  const tx = new TenxyteClient({
28
- baseUrl: 'https://api.my-backend.com',
29
- headers: {
30
- 'X-Access-Key': 'your-public-app-key' // Optional, based on your backend configs.
31
- }
34
+ baseUrl: 'https://api.my-backend.com',
35
+ headers: { 'X-Access-Key': 'your-public-app-key' },
36
+ });
37
+
38
+ // Login
39
+ const tokens = await tx.auth.loginWithEmail({
40
+ email: 'user@example.com',
41
+ password: 'secure_password!',
42
+ device_info: '',
32
43
  });
44
+
45
+ // Check authentication state
46
+ const isLoggedIn = await tx.isAuthenticated();
47
+ const user = await tx.getCurrentUser();
33
48
  ```
34
49
 
35
- The SDK is composed of separate functional modules: `auth`, `security`, `rbac`, `user`, `b2b`, and `ai`.
50
+ > **Important**: Never expose `X-Access-Secret` in frontend bundles. Use it exclusively server-side.
36
51
 
37
52
  ---
38
53
 
39
- ## Authentication Flows
54
+ ## Configuration
55
+
56
+ The `TenxyteClient` accepts a single configuration object. Only `baseUrl` is required.
40
57
 
41
- ### Standard Email / Password
42
58
  ```typescript
43
- try {
44
- const { user, tokens } = await tx.auth.loginWithEmail('user@example.com', 'secure_password!');
45
- console.log(`Welcome back, ${user.first_name}!`);
46
- } catch (error) {
47
- if (error.code === '2FA_REQUIRED') {
48
- // Collect TOTP code from user...
49
- await tx.auth.loginWithEmail('user@example.com', 'secure_password!', { totpCode: '123456' });
50
- }
51
- }
59
+ const tx = new TenxyteClient({
60
+ // Required
61
+ baseUrl: 'https://api.my-service.com',
62
+
63
+ // Optional extra headers for every request
64
+ headers: { 'X-Access-Key': 'pkg_abc123' },
65
+
66
+ // Optional — token storage backend (default: MemoryStorage)
67
+ // Use LocalStorageAdapter for browser persistence
68
+ storage: new LocalStorageAdapter(),
69
+
70
+ // Optional — auto-refresh 401s silently (default: true)
71
+ autoRefresh: true,
72
+
73
+ // Optional — auto-inject device fingerprint into auth requests (default: true)
74
+ autoDeviceInfo: true,
75
+
76
+ // Optional — global request timeout in ms (default: undefined)
77
+ timeoutMs: 10_000,
78
+
79
+ // Optional — retry config for 429/5xx with exponential backoff
80
+ retryConfig: { maxRetries: 3, baseDelayMs: 500 },
81
+
82
+ // Optional — callback when session cannot be recovered
83
+ onSessionExpired: () => router.push('/login'),
84
+
85
+ // Optional — pluggable logger (default: silent no-op)
86
+ logger: console,
87
+ logLevel: 'debug', // 'silent' | 'error' | 'warn' | 'debug'
88
+
89
+ // Optional — override auto-detected device info
90
+ deviceInfoOverride: { app_name: 'MyApp', app_version: '2.0.0' },
91
+ });
52
92
  ```
53
93
 
54
- ### Social Login (OAuth2)
94
+ ---
95
+
96
+ ## Modules
97
+
98
+ ### Authentication (`tx.auth`)
99
+
55
100
  ```typescript
56
- // Direct token exchange
57
- const response = await tx.auth.loginWithSocial('google', {
58
- id_token: 'google_id_token_jwt'
101
+ // Email/password login
102
+ const tokens = await tx.auth.loginWithEmail({
103
+ email: 'user@example.com',
104
+ password: 'password123',
105
+ device_info: '',
106
+ totp_code: '123456', // optional, for 2FA
107
+ });
108
+
109
+ // Phone login
110
+ const tokens = await tx.auth.loginWithPhone({
111
+ phone_country_code: '+1',
112
+ phone_number: '5551234567',
113
+ password: 'password123',
114
+ device_info: '',
115
+ });
116
+
117
+ // Registration
118
+ const result = await tx.auth.register({
119
+ email: 'new@example.com',
120
+ password: 'StrongP@ss1',
121
+ first_name: 'Jane',
122
+ last_name: 'Doe',
59
123
  });
60
124
 
61
- // Or using OAuth code exchange
62
- const callback = await tx.auth.handleSocialCallback('github', 'auth_code', 'https://myapp.com/callback');
125
+ // Magic Link (passwordless)
126
+ await tx.auth.requestMagicLink({ email: 'user@example.com', validation_url: 'https://myapp.com/verify' });
127
+ const tokens = await tx.auth.verifyMagicLink(urlToken);
128
+
129
+ // Social OAuth2
130
+ const tokens = await tx.auth.loginWithSocial('google', { id_token: 'jwt...' });
131
+ const tokens = await tx.auth.handleSocialCallback('github', 'auth_code', 'https://myapp.com/cb');
132
+
133
+ // Session management
134
+ await tx.auth.logout('refresh_token_value');
135
+ await tx.auth.logoutAll();
136
+ await tx.auth.refreshToken('refresh_token_value');
63
137
  ```
64
138
 
65
- ### Passwordless (Magic Link)
139
+ ### Security (`tx.security`)
140
+
66
141
  ```typescript
67
- // 1. Request the link
68
- await tx.auth.requestMagicLink('user@example.com', 'https://myapp.com/verify-magic');
142
+ // 2FA (TOTP)
143
+ const status = await tx.security.get2FAStatus();
144
+ const { secret, qr_code_url, backup_codes } = await tx.security.setup2FA();
145
+ await tx.security.confirm2FA('123456');
146
+ await tx.security.disable2FA('123456');
69
147
 
70
- // 2. On the callback page, verify the token returned in the URL
71
- const { user, tokens } = await tx.auth.verifyMagicLink(urlParams.get('token'));
72
- ```
148
+ // OTP
149
+ await tx.security.requestOtp({ delivery_method: 'email', purpose: 'login' });
150
+ const result = await tx.security.verifyOtp({ otp: '123456', purpose: 'login' });
73
151
 
74
- ---
152
+ // Password management
153
+ await tx.security.resetPasswordRequest({ email: 'user@example.com' });
154
+ await tx.security.resetPasswordConfirm({ token: '...', new_password: 'NewP@ss1' });
155
+ await tx.security.changePassword({ old_password: 'old', new_password: 'new' });
75
156
 
76
- ## Authorization & RBAC
157
+ // WebAuthn / Passkeys
158
+ await tx.security.registerWebAuthn('My Laptop');
159
+ const session = await tx.security.authenticateWebAuthn('user@example.com');
160
+ const creds = await tx.security.listWebAuthnCredentials();
161
+ await tx.security.deleteWebAuthnCredential(credentialId);
162
+ ```
77
163
 
78
- The SDK automatically intercepts your requests to attach `Authorization: Bearer <token>` when available.
79
- By utilizing the embedded `EventEmitter`, you can listen to rotation and expiration changes.
164
+ ### RBAC (`tx.rbac`)
80
165
 
81
166
  ```typescript
82
- tx.http.addResponseInterceptor(async (response) => {
83
- // You can intercept logic, or use tx.on(...) to be built later over the SDK Event layer.
84
- return response;
85
- });
167
+ // Synchronous JWT checks (no network call)
168
+ tx.rbac.setToken(accessToken);
169
+ const isAdmin = tx.rbac.hasRole('admin');
170
+ const canEdit = tx.rbac.hasPermission('users.edit');
171
+ const hasAny = tx.rbac.hasAnyRole(['admin', 'manager']);
172
+ const hasAll = tx.rbac.hasAllRoles(['admin', 'superadmin']);
173
+
174
+ // CRUD operations (network calls)
175
+ const roles = await tx.rbac.listRoles();
176
+ await tx.rbac.createRole({ code: 'editor', name: 'Editor' });
177
+ await tx.rbac.assignRoleToUser('user-id', 'editor');
178
+ await tx.rbac.removeRoleFromUser('user-id', 'editor');
179
+
180
+ const permissions = await tx.rbac.listPermissions();
181
+ await tx.rbac.assignPermissionsToUser('user-id', ['posts.create', 'posts.edit']);
182
+ await tx.rbac.removePermissionsFromUser('user-id', ['posts.create']);
183
+
184
+ // Fetch user's roles/permissions from backend
185
+ const userRoles = await tx.rbac.getUserRoles('user-id');
186
+ const userPerms = await tx.rbac.getUserPermissions('user-id');
86
187
  ```
87
188
 
88
- ### Verifying Roles and Permissions
189
+ ### User Management (`tx.user`)
190
+
89
191
  ```typescript
90
- // Fetch user roles & direct permissions across their active scope
192
+ const profile = await tx.user.getProfile();
193
+ await tx.user.updateProfile({ first_name: 'Updated' });
194
+ await tx.user.uploadAvatar(fileFormData);
195
+ await tx.user.deleteAccount('my-password');
91
196
  const myRoles = await tx.user.getMyRoles();
92
197
 
93
- // List backend global roles
94
- const roles = await tx.rbac.listRoles();
198
+ // Admin operations
199
+ const users = await tx.user.listUsers({ page: 1, page_size: 20 });
200
+ const user = await tx.user.getUser('user-id');
201
+ await tx.user.adminUpdateUser('user-id', { is_active: false });
202
+ await tx.user.adminDeleteUser('user-id');
203
+ await tx.user.banUser('user-id', 'spam');
95
204
  ```
96
205
 
97
- ---
206
+ ### B2B Organizations (`tx.b2b`)
98
207
 
99
- ## Advanced Security
208
+ ```typescript
209
+ // Context switching — auto-injects X-Org-Slug header
210
+ tx.b2b.switchOrganization('acme-corp');
211
+ const slug = tx.b2b.getCurrentOrganizationSlug(); // 'acme-corp'
212
+ tx.b2b.clearOrganization();
100
213
 
101
- ### WebAuthn / Passkeys
102
- The `security` module natively wraps browser credentials APIs to seamlessly interact with Tenxyte's FIDO2 bindings.
214
+ // Organization CRUD
215
+ const orgs = await tx.b2b.listOrganizations();
216
+ const org = await tx.b2b.createOrganization({ name: 'Acme Corp', slug: 'acme-corp' });
217
+ await tx.b2b.updateOrganization('acme-corp', { name: 'Acme Corp Inc.' });
218
+ await tx.b2b.deleteOrganization('acme-corp');
103
219
 
104
- ```typescript
105
- // Register a new device/Passkey for the authenticated user
106
- await tx.security.registerWebAuthn('My MacBook Chrome');
220
+ // Members
221
+ const members = await tx.b2b.listMembers('acme-corp');
222
+ await tx.b2b.addMember('acme-corp', { user_id: 'uid', role_code: 'member' });
223
+ await tx.b2b.updateMember('acme-corp', 'uid', { role_code: 'admin' });
224
+ await tx.b2b.removeMember('acme-corp', 'uid');
107
225
 
108
- // Authenticate securely (Without needing a password)
109
- const session = await tx.security.authenticateWebAuthn('user@example.com');
226
+ // Invitations
227
+ await tx.b2b.inviteMember('acme-corp', { email: 'dev@example.com', role_code: 'admin' });
228
+ const roles = await tx.b2b.listOrgRoles('acme-corp');
110
229
  ```
111
230
 
112
- ### 2FA (TOTP) Enrollment
231
+ ### AI Agent Security (`tx.ai`)
232
+
113
233
  ```typescript
114
- const { secret, qr_code_url, backup_codes } = await tx.security.setup2FA();
115
- // Show the QR code to the user, then confirm their first valid code
116
- await tx.security.confirm2FA(userProvidedCode);
234
+ // Agent token lifecycle
235
+ const agentData = await tx.ai.createAgentToken({
236
+ agent_id: 'Invoice-Parser-Bot',
237
+ permissions: ['invoices.read', 'invoices.create'],
238
+ budget_limit_usd: 5.00,
239
+ circuit_breaker: { max_requests: 100, window_seconds: 60 },
240
+ });
241
+
242
+ tx.ai.setAgentToken(agentData.token); // SDK switches to AgentBearer auth
243
+ tx.ai.isAgentMode(); // true
244
+ tx.ai.clearAgentToken(); // back to standard Bearer
245
+
246
+ // Token management
247
+ const tokens = await tx.ai.listAgentTokens();
248
+ const token = await tx.ai.getAgentToken('token-id');
249
+ await tx.ai.revokeAgentToken('token-id');
250
+ await tx.ai.suspendAgentToken('token-id');
251
+ await tx.ai.revokeAllAgentTokens();
252
+
253
+ // Human-in-the-Loop
254
+ const pending = await tx.ai.listPendingActions();
255
+ await tx.ai.confirmPendingAction('confirmation-token');
256
+ await tx.ai.denyPendingAction('confirmation-token');
257
+
258
+ // Monitoring
259
+ await tx.ai.sendHeartbeat('token-id');
260
+ await tx.ai.reportUsage('token-id', {
261
+ cost_usd: 0.015,
262
+ prompt_tokens: 1540,
263
+ completion_tokens: 420,
264
+ });
265
+
266
+ // Traceability
267
+ tx.ai.setTraceId('trace-1234'); // adds X-Prompt-Trace-ID header
268
+ tx.ai.clearTraceId();
117
269
  ```
118
270
 
119
- ---
271
+ ### Applications (`tx.applications`)
120
272
 
121
- ## B2B Organizations (Multi-Tenancy)
273
+ ```typescript
274
+ const apps = await tx.applications.listApplications();
275
+ const app = await tx.applications.createApplication({
276
+ name: 'My API Client',
277
+ description: 'Backend service',
278
+ });
279
+ const detail = await tx.applications.getApplication('app-id');
280
+ await tx.applications.updateApplication('app-id', { name: 'Renamed' });
281
+ await tx.applications.patchApplication('app-id', { description: 'Updated desc' });
282
+ await tx.applications.deleteApplication('app-id');
283
+ const newCreds = await tx.applications.regenerateCredentials('app-id');
284
+ ```
122
285
 
123
- Tenxyte natively supports complex multi-tenant B2B topologies. Using `switchOrganization` instructs the SDK to pass the context `X-Org-Slug` downstream transparently.
286
+ ### Admin (`tx.admin`)
124
287
 
125
288
  ```typescript
126
- // Activate context
127
- tx.b2b.switchOrganization('acme-corp');
289
+ // Audit logs
290
+ const logs = await tx.admin.listAuditLogs({ page: 1 });
291
+ const log = await tx.admin.getAuditLog('log-id');
128
292
 
129
- // All subsequent calls inject `X-Org-Slug: acme-corp`.
130
- const members = await tx.b2b.listMembers('acme-corp');
293
+ // Login attempts
294
+ const attempts = await tx.admin.listLoginAttempts({ user_id: 'uid' });
131
295
 
132
- // Invite a collaborator into this organization
133
- await tx.b2b.inviteMember('acme-corp', { email: 'dev@example.com', role_code: 'admin' });
296
+ // Blacklisted tokens
297
+ const blacklisted = await tx.admin.listBlacklistedTokens();
298
+ await tx.admin.cleanupBlacklistedTokens();
134
299
 
135
- // Clear context
136
- tx.b2b.clearOrganization();
300
+ // Refresh tokens
301
+ const refreshTokens = await tx.admin.listRefreshTokens({ user_id: 'uid' });
302
+ await tx.admin.revokeRefreshToken('token-id');
137
303
  ```
138
304
 
139
- ---
305
+ ### GDPR (`tx.gdpr`)
140
306
 
141
- ## AIRS (AI Responsibility & Security)
307
+ ```typescript
308
+ // User-facing
309
+ await tx.gdpr.requestAccountDeletion({ reason: 'No longer needed' });
310
+ await tx.gdpr.confirmAccountDeletion('confirmation-code');
311
+ await tx.gdpr.cancelAccountDeletion();
312
+ const status = await tx.gdpr.getDeletionStatus();
313
+ const data = await tx.gdpr.exportUserData();
314
+
315
+ // Admin-facing
316
+ const requests = await tx.gdpr.listDeletionRequests({ status: 'pending' });
317
+ const request = await tx.gdpr.getDeletionRequest('request-id');
318
+ await tx.gdpr.processDeletionRequest('request-id', { action: 'approve' });
319
+ await tx.gdpr.processExpiredDeletions();
320
+ ```
142
321
 
143
- If your architecture includes orchestrating authenticated LLM agents that take action via Tenxyte endpoints, you must use **AgentTokens**.
322
+ ### Dashboard (`tx.dashboard`)
144
323
 
145
324
  ```typescript
146
- // 1. Authenticated User delegates secure permissions to an Agent
147
- const agentTokenData = await tx.ai.createAgentToken({
148
- agent_id: 'Invoice-Parser-Bot',
149
- permissions: ['invoices.read', 'invoices.create'],
150
- budget_limit_usd: 5.00, // strict budget enforcing
151
- circuit_breaker: { max_requests: 100, window_seconds: 60 }
152
- });
325
+ const global = await tx.dashboard.getStats({ period: 'last_30_days' });
326
+ const auth = await tx.dashboard.getAuthStats();
327
+ const security = await tx.dashboard.getSecurityStats();
328
+ const gdpr = await tx.dashboard.getGdprStats();
329
+ const orgStats = await tx.dashboard.getOrganizationStats('acme-corp');
330
+ ```
153
331
 
154
- // 2. Instruct the SDK to flip into Agent Mode
155
- tx.ai.setAgentToken(agentTokenData.token);
332
+ ---
156
333
 
157
- // The SDK will now authorize using `AgentBearer <token>`.
158
- // 3. Keep the agent alive
159
- await tx.ai.sendHeartbeat(agentTokenData.id);
334
+ ## SDK Events
160
335
 
161
- // 4. Report LLM consumption cost transparently back to backend
162
- await tx.ai.reportUsage(agentTokenData.id, {
163
- cost_usd: 0.015,
164
- prompt_tokens: 1540,
165
- completion_tokens: 420
336
+ The SDK emits events via a built-in `EventEmitter`. Use `tx.on()`, `tx.once()`, and `tx.off()` to subscribe.
337
+
338
+ | Event | Payload | When |
339
+ |---|---|---|
340
+ | `session:expired` | `void` | Refresh token expired/revoked, session unrecoverable |
341
+ | `token:refreshed` | `{ accessToken: string }` | Access token silently rotated via auto-refresh |
342
+ | `token:stored` | `{ accessToken: string; refreshToken?: string }` | Tokens persisted after login, register, or refresh |
343
+ | `agent:awaiting_approval` | `{ action: unknown }` | AI agent action requires human confirmation (HTTP 202) |
344
+ | `error` | `{ error: unknown }` | Unrecoverable SDK error not tied to a specific call |
345
+
346
+ ```typescript
347
+ // React to session expiry
348
+ tx.on('session:expired', () => {
349
+ router.push('/login');
166
350
  });
167
351
 
168
- // Disable agent mode and return to standard User flow
169
- tx.ai.clearAgentToken();
352
+ // Track token refreshes
353
+ tx.on('token:refreshed', ({ accessToken }) => {
354
+ console.log('Token refreshed silently');
355
+ });
356
+
357
+ // HITL notification
358
+ tx.on('agent:awaiting_approval', ({ action }) => {
359
+ showApprovalDialog(action);
360
+ });
170
361
  ```
171
362
 
172
- ### Human In The Loop (HITL) & Auditing
363
+ ---
364
+
365
+ ## High-Level Helpers
366
+
173
367
  ```typescript
174
- // Linking operations to prompt identifiers for debugging
175
- tx.ai.setTraceId('trace-1234abcd-prompt');
176
- // Request will now include X-Prompt-Trace-ID
368
+ // Check if user is authenticated (synchronous JWT expiry check)
369
+ const isLoggedIn = await tx.isAuthenticated();
370
+
371
+ // Get the raw access token
372
+ const token = await tx.getAccessToken();
177
373
 
178
- // Any requests generating a `HTTP 202 Accepted` indicate HITL.
179
- const pendingActions = await tx.ai.listPendingActions();
180
- await tx.ai.confirmPendingAction(pendingActions[0].confirmation_token);
374
+ // Get decoded JWT payload (no network call)
375
+ const user = await tx.getCurrentUser();
376
+
377
+ // Check token expiry
378
+ const expired = await tx.isTokenExpired();
379
+
380
+ // Get full SDK state snapshot (for framework wrappers)
381
+ const state = await tx.getState();
382
+ // { isAuthenticated, user, accessToken, activeOrg, isAgentMode }
181
383
  ```
182
384
 
385
+ ---
386
+
387
+ ## Migration Guide: v0.8 → v0.9
388
+
389
+ ### Breaking Changes
390
+
391
+ 1. **Constructor signature changed** — The client now accepts a `TenxyteClientConfig` object:
392
+ ```typescript
393
+ // Before (v0.8)
394
+ const tx = new TenxyteClient({ baseUrl: '...', headers: { ... } });
395
+
396
+ // After (v0.9) — same, but new options available
397
+ const tx = new TenxyteClient({
398
+ baseUrl: '...',
399
+ headers: { ... },
400
+ autoRefresh: true, // NEW
401
+ autoDeviceInfo: true, // NEW
402
+ retryConfig: { ... }, // NEW
403
+ });
404
+ ```
405
+
406
+ 2. **`loginWithEmail` now requires `device_info`**:
407
+ ```typescript
408
+ // Before (v0.8)
409
+ await tx.auth.loginWithEmail({ email, password });
410
+
411
+ // After (v0.9)
412
+ await tx.auth.loginWithEmail({ email, password, device_info: '' });
413
+ ```
414
+
415
+ 3. **`requestMagicLink` now requires `validation_url`**:
416
+ ```typescript
417
+ // Before
418
+ await tx.auth.requestMagicLink({ email });
419
+
420
+ // After
421
+ await tx.auth.requestMagicLink({ email, validation_url: 'https://...' });
422
+ ```
423
+
424
+ 4. **Auto-session management** — Tokens are now automatically stored and the `Authorization` header is automatically injected. You no longer need to manage this manually.
425
+
426
+ 5. **New modules added** — `tx.applications`, `tx.admin`, `tx.gdpr`, `tx.dashboard` are now available.
427
+
428
+ 6. **`register()` return type changed** — Now returns `RegisterResponse` (may include tokens if auto-login is enabled).
429
+
430
+ ### New Features in v0.9
431
+
432
+ - Auto-refresh interceptor (silent 401 → refresh → retry)
433
+ - Configurable retry with exponential backoff (429/5xx)
434
+ - Device info auto-injection
435
+ - Pluggable logger with log levels
436
+ - High-level helpers (`isAuthenticated`, `getCurrentUser`, `isTokenExpired`)
437
+ - `getState()` for framework wrapper integration
438
+ - EventEmitter for reactive state (`session:expired`, `token:refreshed`, etc.)
439
+
440
+ ---
441
+
183
442
  ## License
443
+
184
444
  MIT