@dypai-ai/client-sdk 0.0.37 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@dypai-ai/client-sdk` will be documented in this file.
4
+
5
+ ## [1.0.0] - 2026-03-26
6
+
7
+ First stable release.
8
+
9
+ ### Features
10
+
11
+ - **Authentication**: Sign in (email/password, OAuth, OTP/magic link), sign up, sign out, password recovery, session management, auto-refresh tokens, cross-tab sync via Web Locks API
12
+ - **Database CRUD**: Fluent query builder (`client.db.from('table').select()`)
13
+ - **Custom Endpoints**: Typed API client for calling DYPAI workflow endpoints (GET, POST, PUT, PATCH, DELETE)
14
+ - **File Upload/Download**: Smart Upload via presigned URLs with progress tracking
15
+ - **User Management**: Admin module for listing, creating, updating, and deleting users
16
+ - **React Integration**: `DypaiProvider`, `useAuth`, `useEndpoint`, `useAction`, `useUpload`, `ProtectedRoute`
17
+ - **TypeScript**: Full type safety with generic `createClient<Database, Api>()` for typed endpoints and database tables
18
+ - **Dual Build**: ESM + CJS outputs with TypeScript declarations
19
+ - **Auto-generated Docs**: TypeDoc integration with CI deploy to GitHub Pages
20
+
21
+ ### Testing
22
+
23
+ - 292 unit tests across 8 test suites
24
+ - 88% statement coverage, 75% branch coverage
25
+
26
+ ### Breaking Changes (from 0.0.x)
27
+
28
+ - `StorageModule` removed — use `client.api.upload()` / `client.api.download()` instead
29
+ - Old markdown docs removed — replaced by auto-generated TypeDoc at [dypai-solutions.github.io/client-sdk](https://dypai-solutions.github.io/client-sdk/)
30
+ - `prepublishOnly` no longer swaps README — full README is now published to npm
package/README.md CHANGED
@@ -1,5 +1,640 @@
1
1
  # @dypai-ai/client-sdk
2
2
 
3
- Cliente JavaScript para Dypai Engine.
3
+ The official JavaScript/TypeScript SDK for [DYPAI](https://dypai.ai) — backend-as-a-service with visual workflows, AI agents, and MCP.
4
4
 
5
- Contacta al equipo de desarrollo para más información.
5
+ ```bash
6
+ npm install @dypai-ai/client-sdk
7
+ ```
8
+
9
+ ## Quick Start
10
+
11
+ ```ts
12
+ import { createClient } from '@dypai-ai/client-sdk';
13
+
14
+ const dypai = createClient('https://your-project.dypai.ai');
15
+
16
+ // Sign in
17
+ const { data, error } = await dypai.auth.signInWithPassword({
18
+ email: 'user@example.com',
19
+ password: 'securepassword',
20
+ });
21
+
22
+ // Call an endpoint
23
+ const { data: products } = await dypai.api.get('list_products');
24
+ ```
25
+
26
+ ## Table of Contents
27
+
28
+ - [Installation](#installation)
29
+ - [Creating the Client](#creating-the-client)
30
+ - [Authentication](#authentication)
31
+ - [Database (CRUD)](#database-crud)
32
+ - [Endpoints (Custom API)](#endpoints-custom-api)
33
+ - [File Upload](#file-upload)
34
+ - [User Management (Admin)](#user-management-admin)
35
+ - [React Integration](#react-integration)
36
+ - [TypeScript Support](#typescript-support)
37
+ - [API Reference](#api-reference)
38
+
39
+ ---
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ npm install @dypai-ai/client-sdk
45
+ ```
46
+
47
+ React hooks (optional):
48
+
49
+ ```bash
50
+ # React hooks are included — no extra package needed
51
+ # Import from '@dypai-ai/client-sdk/react'
52
+ ```
53
+
54
+ ## Creating the Client
55
+
56
+ ```ts
57
+ import { createClient } from '@dypai-ai/client-sdk';
58
+
59
+ // Basic — JWT-authenticated apps
60
+ const dypai = createClient('https://your-project.dypai.ai');
61
+
62
+ // With API key — for public endpoints that don't require auth
63
+ const dypai = createClient(
64
+ 'https://your-project.dypai.ai',
65
+ 'your-public-api-key'
66
+ );
67
+
68
+ // With options
69
+ const dypai = createClient('https://your-project.dypai.ai', {
70
+ auth: {
71
+ autoRefreshToken: true, // default: true
72
+ persistSession: true, // default: true
73
+ },
74
+ redirects: {
75
+ passwordRecovery: '/set-password',
76
+ signIn: '/dashboard',
77
+ },
78
+ });
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Authentication
84
+
85
+ ### Sign Up
86
+
87
+ ```ts
88
+ const { data, error } = await dypai.auth.signUp({
89
+ email: 'user@example.com',
90
+ password: 'securepassword',
91
+ name: 'John Doe',
92
+ });
93
+
94
+ if (error) {
95
+ console.error(error.message);
96
+ } else if (data.confirmationRequired) {
97
+ console.log('Check your email to confirm your account');
98
+ }
99
+ ```
100
+
101
+ ### Sign In with Password
102
+
103
+ ```ts
104
+ const { data, error } = await dypai.auth.signInWithPassword({
105
+ email: 'user@example.com',
106
+ password: 'securepassword',
107
+ });
108
+
109
+ if (data) {
110
+ console.log('Welcome!', data.user.email);
111
+ }
112
+ ```
113
+
114
+ ### Sign In with OAuth
115
+
116
+ ```ts
117
+ await dypai.auth.signInWithOAuth('google');
118
+ // Redirects to Google login page
119
+ ```
120
+
121
+ ### Sign In with Magic Link (OTP)
122
+
123
+ ```ts
124
+ // Send magic link
125
+ const { error } = await dypai.auth.signInWithOtp({ email: 'user@example.com' });
126
+
127
+ // Verify OTP code
128
+ const { data, error } = await dypai.auth.verifyOtp({
129
+ email: 'user@example.com',
130
+ token: '123456',
131
+ type: 'email',
132
+ });
133
+ ```
134
+
135
+ ### Get Current User
136
+
137
+ ```ts
138
+ const { data: user, error } = await dypai.auth.getUser();
139
+ console.log(user.email, user.role);
140
+ ```
141
+
142
+ ### Get Session
143
+
144
+ ```ts
145
+ const { data: session, error } = await dypai.auth.getSession();
146
+ if (session) {
147
+ console.log('Token:', session.token);
148
+ console.log('User:', session.user.email);
149
+ }
150
+ ```
151
+
152
+ ### Password Recovery
153
+
154
+ ```ts
155
+ // Request reset email
156
+ const { error } = await dypai.auth.resetPasswordForEmail('user@example.com', {
157
+ redirectTo: 'https://myapp.com/set-password',
158
+ });
159
+
160
+ // Set new password (on the callback page)
161
+ const { error } = await dypai.auth.setPassword('new-secure-password');
162
+ ```
163
+
164
+ ### Resend Confirmation Email
165
+
166
+ ```ts
167
+ const { error } = await dypai.auth.resendConfirmationEmail('user@example.com');
168
+ ```
169
+
170
+ ### Update User Profile
171
+
172
+ ```ts
173
+ const { data, error } = await dypai.auth.updateUser({
174
+ data: { name: 'Jane Doe', avatar_url: 'https://...' },
175
+ });
176
+ ```
177
+
178
+ ### Sign Out
179
+
180
+ ```ts
181
+ await dypai.auth.signOut();
182
+ ```
183
+
184
+ ### Listen to Auth Changes
185
+
186
+ ```ts
187
+ const { data: { subscription } } = dypai.auth.onAuthStateChange((event, session) => {
188
+ console.log(event); // 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED' | ...
189
+
190
+ if (event === 'SIGNED_IN') {
191
+ console.log('User:', session.user.email);
192
+ }
193
+
194
+ if (event === 'SIGNED_OUT') {
195
+ window.location.href = '/login';
196
+ }
197
+ });
198
+
199
+ // Stop listening
200
+ subscription.unsubscribe();
201
+ ```
202
+
203
+ ---
204
+
205
+ ## Database (CRUD)
206
+
207
+ Interact with your project's database tables using a fluent API:
208
+
209
+ ### Select (Read)
210
+
211
+ ```ts
212
+ // Get all rows
213
+ const { data, error } = await dypai.db.from('products').select();
214
+
215
+ // With filters
216
+ const { data, error } = await dypai.db.from('products').select({
217
+ category: 'electronics',
218
+ active: true,
219
+ });
220
+ ```
221
+
222
+ ### Insert (Create)
223
+
224
+ ```ts
225
+ const { data, error } = await dypai.db.from('products').insert({
226
+ name: 'Wireless Headphones',
227
+ price: 59.99,
228
+ category: 'electronics',
229
+ });
230
+ ```
231
+
232
+ ### Update
233
+
234
+ ```ts
235
+ const { data, error } = await dypai.db.from('products').update('prod_123', {
236
+ price: 49.99,
237
+ on_sale: true,
238
+ });
239
+ ```
240
+
241
+ ### Delete
242
+
243
+ ```ts
244
+ const { data, error } = await dypai.db.from('products').delete('prod_123');
245
+ ```
246
+
247
+ ---
248
+
249
+ ## Endpoints (Custom API)
250
+
251
+ Call your custom DYPAI endpoints (workflows exposed as APIs):
252
+
253
+ ### GET
254
+
255
+ ```ts
256
+ const { data, error } = await dypai.api.get('list_products');
257
+
258
+ // With query parameters
259
+ const { data, error } = await dypai.api.get('search', {
260
+ params: { q: 'headphones', limit: 20 },
261
+ });
262
+ ```
263
+
264
+ ### POST
265
+
266
+ ```ts
267
+ const { data, error } = await dypai.api.post('create_order', {
268
+ product_id: 'prod_123',
269
+ quantity: 2,
270
+ });
271
+ ```
272
+
273
+ ### PUT / PATCH
274
+
275
+ ```ts
276
+ const { data, error } = await dypai.api.put('update_profile', {
277
+ name: 'Jane Doe',
278
+ bio: 'Developer',
279
+ });
280
+
281
+ const { data, error } = await dypai.api.patch('update_settings', {
282
+ theme: 'dark',
283
+ });
284
+ ```
285
+
286
+ ### DELETE
287
+
288
+ ```ts
289
+ const { data, error } = await dypai.api.delete('cancel_subscription', {
290
+ params: { subscription_id: 'sub_123' },
291
+ });
292
+ ```
293
+
294
+ ---
295
+
296
+ ## File Upload
297
+
298
+ Upload files using Smart Upload (signed URLs, direct upload to cloud storage):
299
+
300
+ ```ts
301
+ const file = document.querySelector('input[type="file"]').files[0];
302
+
303
+ const { data, error } = await dypai.api.upload('storage_files', file, {
304
+ params: {
305
+ operation: 'upload',
306
+ file_path: `avatars/${userId}.jpg`,
307
+ },
308
+ onProgress: (percent) => {
309
+ console.log(`Upload: ${percent}%`);
310
+ },
311
+ });
312
+ ```
313
+
314
+ ### Download
315
+
316
+ ```ts
317
+ await dypai.api.download('storage_files', {
318
+ operation: 'download',
319
+ file_path: 'reports/monthly.pdf',
320
+ });
321
+ ```
322
+
323
+ ---
324
+
325
+ ## User Management (Admin)
326
+
327
+ Manage application users (requires `manage_users` permission):
328
+
329
+ ### List Users
330
+
331
+ ```ts
332
+ const { data, error } = await dypai.users.list({ page: 1, per_page: 50 });
333
+ console.log(data.users); // [{ id, email, role, ... }, ...]
334
+ ```
335
+
336
+ ### Create User
337
+
338
+ ```ts
339
+ const { data, error } = await dypai.users.create({
340
+ email: 'new@example.com',
341
+ password: 'securepassword',
342
+ role: 'editor',
343
+ user_metadata: { name: 'New User' },
344
+ });
345
+ ```
346
+
347
+ ### Update User Role
348
+
349
+ ```ts
350
+ const { data, error } = await dypai.users.update('user_id', {
351
+ role: 'admin',
352
+ });
353
+ ```
354
+
355
+ ### Delete User
356
+
357
+ ```ts
358
+ const { data, error } = await dypai.users.delete('user_id');
359
+ ```
360
+
361
+ ---
362
+
363
+ ## React Integration
364
+
365
+ ### Setup
366
+
367
+ ```tsx
368
+ // src/lib/dypai.ts
369
+ import { createClient } from '@dypai-ai/client-sdk';
370
+
371
+ export const dypai = createClient(import.meta.env.VITE_DYPAI_URL);
372
+ ```
373
+
374
+ ```tsx
375
+ // src/App.tsx
376
+ import { DypaiProvider } from '@dypai-ai/client-sdk/react';
377
+ import { dypai } from './lib/dypai';
378
+
379
+ function App() {
380
+ return (
381
+ <DypaiProvider client={dypai}>
382
+ <Router />
383
+ </DypaiProvider>
384
+ );
385
+ }
386
+ ```
387
+
388
+ ### useAuth
389
+
390
+ ```tsx
391
+ import { useAuth } from '@dypai-ai/client-sdk/react';
392
+
393
+ function LoginPage() {
394
+ const { signIn, isLoading, isAuthenticated } = useAuth();
395
+
396
+ if (isAuthenticated) return <Navigate to="/dashboard" />;
397
+
398
+ const handleSubmit = async (e: FormEvent) => {
399
+ e.preventDefault();
400
+ const { error } = await signIn(email, password);
401
+ if (error) alert(error.message);
402
+ };
403
+
404
+ return (
405
+ <form onSubmit={handleSubmit}>
406
+ <input type="email" value={email} onChange={...} />
407
+ <input type="password" value={password} onChange={...} />
408
+ <button disabled={isLoading}>Sign In</button>
409
+ </form>
410
+ );
411
+ }
412
+ ```
413
+
414
+ ```tsx
415
+ function Navbar() {
416
+ const { user, isAuthenticated, signOut } = useAuth();
417
+
418
+ if (!isAuthenticated) return <Link to="/login">Sign In</Link>;
419
+
420
+ return (
421
+ <div>
422
+ <span>{user.email}</span>
423
+ <button onClick={signOut}>Sign Out</button>
424
+ </div>
425
+ );
426
+ }
427
+ ```
428
+
429
+ ### useEndpoint
430
+
431
+ ```tsx
432
+ import { useEndpoint } from '@dypai-ai/client-sdk/react';
433
+
434
+ function ProductList() {
435
+ const { data: products, isLoading, error, refetch } = useEndpoint('list_products');
436
+
437
+ if (isLoading) return <Spinner />;
438
+ if (error) return <Error message={error.message} />;
439
+
440
+ return (
441
+ <div>
442
+ {products.map(p => <ProductCard key={p.id} product={p} />)}
443
+ <button onClick={refetch}>Refresh</button>
444
+ </div>
445
+ );
446
+ }
447
+ ```
448
+
449
+ ```tsx
450
+ // With params and auto-refetch
451
+ function Notifications() {
452
+ const { data } = useEndpoint('get_notifications', {
453
+ params: { unread: true },
454
+ refetchInterval: 30000, // every 30s
455
+ });
456
+
457
+ return <Badge count={data?.length ?? 0} />;
458
+ }
459
+ ```
460
+
461
+ ```tsx
462
+ // Conditional fetching
463
+ function UserProfile({ userId }: { userId?: string }) {
464
+ const { data: profile } = useEndpoint('get_user_profile', {
465
+ params: { id: userId },
466
+ enabled: !!userId, // only fetch when userId exists
467
+ });
468
+
469
+ return profile ? <Profile data={profile} /> : null;
470
+ }
471
+ ```
472
+
473
+ ### useAction
474
+
475
+ ```tsx
476
+ import { useAction } from '@dypai-ai/client-sdk/react';
477
+
478
+ function CreateProduct() {
479
+ const { mutate, isLoading } = useAction('create_product', {
480
+ onSuccess: (data) => toast.success(`Created: ${data.name}`),
481
+ onError: (err) => toast.error(err.message),
482
+ });
483
+
484
+ const handleSubmit = async (formData: ProductForm) => {
485
+ const { data, error } = await mutate(formData);
486
+ };
487
+
488
+ return (
489
+ <form onSubmit={handleSubmit}>
490
+ {/* form fields */}
491
+ <button disabled={isLoading}>Create</button>
492
+ </form>
493
+ );
494
+ }
495
+ ```
496
+
497
+ ```tsx
498
+ // PUT, PATCH, DELETE
499
+ const { mutate: updateTask } = useAction('update_task', { method: 'PUT' });
500
+ const { mutate: patchSettings } = useAction('settings', { method: 'PATCH' });
501
+ const { mutate: deleteItem } = useAction('delete_item', { method: 'DELETE' });
502
+
503
+ await updateTask({ id: '123', done: true });
504
+ await patchSettings({ theme: 'dark' });
505
+ await deleteItem({ id: '123' });
506
+ ```
507
+
508
+ ### useUpload
509
+
510
+ ```tsx
511
+ import { useUpload } from '@dypai-ai/client-sdk/react';
512
+
513
+ function AvatarUpload() {
514
+ const { upload, progress, isUploading } = useUpload('storage_files', {
515
+ onSuccess: () => toast.success('Uploaded!'),
516
+ });
517
+
518
+ const handleFile = async (e: ChangeEvent<HTMLInputElement>) => {
519
+ const file = e.target.files?.[0];
520
+ if (!file) return;
521
+
522
+ await upload(file, {
523
+ operation: 'upload',
524
+ file_path: `avatars/${userId}.jpg`,
525
+ });
526
+ };
527
+
528
+ return (
529
+ <div>
530
+ <input type="file" accept="image/*" onChange={handleFile} disabled={isUploading} />
531
+ {isUploading && <ProgressBar value={progress} />}
532
+ </div>
533
+ );
534
+ }
535
+ ```
536
+
537
+ ### ProtectedRoute
538
+
539
+ ```tsx
540
+ import { ProtectedRoute } from '@dypai-ai/client-sdk/react';
541
+
542
+ // Basic protection
543
+ <ProtectedRoute>
544
+ <Dashboard />
545
+ </ProtectedRoute>
546
+
547
+ // With redirect
548
+ <ProtectedRoute redirectTo="/login">
549
+ <Dashboard />
550
+ </ProtectedRoute>
551
+
552
+ // Role-based access
553
+ <ProtectedRoute
554
+ roles={['admin', 'editor']}
555
+ loadingComponent={<Spinner />}
556
+ unauthorizedComponent={<AccessDenied />}
557
+ unauthenticatedComponent={<LoginPrompt />}
558
+ >
559
+ <AdminPanel />
560
+ </ProtectedRoute>
561
+ ```
562
+
563
+ ---
564
+
565
+ ## TypeScript Support
566
+
567
+ The SDK is fully typed. You can pass your database schema and endpoint map for complete type safety:
568
+
569
+ ```ts
570
+ // Define your database schema
571
+ interface Database {
572
+ products: {
573
+ id: string;
574
+ name: string;
575
+ price: number;
576
+ active: boolean;
577
+ };
578
+ orders: {
579
+ id: string;
580
+ product_id: string;
581
+ quantity: number;
582
+ status: 'pending' | 'shipped' | 'delivered';
583
+ };
584
+ }
585
+
586
+ // Define your endpoint map
587
+ interface Api {
588
+ list_products: {
589
+ response: Database['products'][];
590
+ };
591
+ create_order: {
592
+ body: { product_id: string; quantity: number };
593
+ response: Database['orders'];
594
+ };
595
+ }
596
+
597
+ // Create a fully typed client
598
+ const dypai = createClient<Database, Api>(
599
+ 'https://your-project.dypai.ai'
600
+ );
601
+
602
+ // Now everything has autocomplete and type checking
603
+ const { data } = await dypai.db.from('products').select(); // data: Product[]
604
+ const { data } = await dypai.api.post('create_order', { // body is typed
605
+ product_id: 'prod_123',
606
+ quantity: 2,
607
+ });
608
+ ```
609
+
610
+ ---
611
+
612
+ ## API Reference
613
+
614
+ Full auto-generated API docs: [dypai-solutions.github.io/client-sdk](https://dypai-solutions.github.io/client-sdk/)
615
+
616
+ ---
617
+
618
+ ## Response Format
619
+
620
+ All SDK methods return a consistent `{ data, error }` object:
621
+
622
+ ```ts
623
+ const { data, error } = await dypai.auth.signInWithPassword({ ... });
624
+
625
+ if (error) {
626
+ console.error(error.message); // Human-readable message
627
+ console.error(error.status); // HTTP status code
628
+ console.error(error.code); // Error code (if available)
629
+ return;
630
+ }
631
+
632
+ // data is guaranteed to be non-null here
633
+ console.log(data);
634
+ ```
635
+
636
+ ---
637
+
638
+ ## License
639
+
640
+ MIT
package/dist/index.d.ts CHANGED
@@ -926,9 +926,9 @@ declare const toastInfo: (title: string, description?: string) => void;
926
926
 
927
927
  declare const PACKAGE_INFO: {
928
928
  readonly name: "@dypai-ai/client-sdk";
929
- readonly version: "0.0.1";
930
- readonly description: "Cliente JavaScript para Dypai Engine";
931
- readonly features: readonly ["API REST autenticada", "Gestión de usuarios (Admin)", "Storage via endpoints personalizados"];
929
+ readonly version: "1.0.0";
930
+ readonly description: "Official JavaScript/TypeScript SDK for DYPAI";
931
+ readonly features: readonly ["Authentication (email, OAuth, OTP)", "Database CRUD", "Custom endpoints (typed API)", "File upload/download (Smart Upload)", "User management (admin)", "React hooks (useAuth, useEndpoint, useAction, useUpload)"];
932
932
  };
933
933
 
934
934
  export { DypaiClient, DypaiError, PACKAGE_INFO, callApi, configureApiService, configureDypaiServices, createApiClient, createClient, getGlobalConfig, reloadDypaiConfig, resetGlobalConfig, setToastFunction, setTokenProvider, toast, toastError, toastInfo, toastSuccess, toastWarning, useToast };
package/dist/index.esm.js CHANGED
@@ -1 +1 @@
1
- class t extends Error{constructor(t,e=500,i,n){super(t),this.status=e,this.code=i,this.details=n,this.name="DypaiError"}}function e(t){if(!t)return{};const e=t.user||t,i=e.app_metadata||{},n=e.user_metadata||{};return{id:e.id,email:e.email,phone:e.phone,role:i.role||n.role||e.role||null,created_at:e.created_at,updated_at:e.updated_at,confirmed_at:e.confirmed_at,last_sign_in_at:e.last_sign_in_at,app_metadata:i,user_metadata:n,roleDetails:{name:i.role||n.role||null,weight:0},appContext:{app_id:"default"}}}let i=!1;function n(t,...e){"error"===t?console.error(...e):i&&("warn"===t?console.warn(...e):console.log(...e))}async function s(e,i){let n={};try{n=await e.json()}catch{}const s=n.msg||n.error_description||n.message||i,r=n.error_code||n.error||n.code||void 0;return new t(s,e.status,r)}class r{constructor(){this.promise=new Promise((t,e)=>{this.resolve=t,this.reject=e})}}const o={getItem:t=>"undefined"==typeof window?null:window.localStorage.getItem(t),setItem:(t,e)=>{if("undefined"!=typeof window)try{window.localStorage.setItem(t,e)}catch(t){console.error("[DYPAI SDK] ❌ Error crítico guardando en localStorage (¿Quota/Permisos?):",t)}},removeItem:t=>{if("undefined"!=typeof window)try{window.localStorage.removeItem(t)}catch(t){}}};async function a(e){try{return{data:await e,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e.message||"Error de autenticación",e.status||400)}}}class c{constructor(t,e=null){this.config=t,this.t=null,this.i=null,this.o=null,this.h=null,this.l=[],this.u=null,this.p=null,this.S=!1,this.D=null,this.m=null,this.v=0,this.A=0,this.storage=t.storage||o,i=!!t.debug;const s=t.storageKey||this.deriveStorageKey(t.apiKey);n("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${s})`),this.STORAGE_KEY=`dypai-${s}-auth-session`,this.I=this.P().then(()=>{this.i&&(this.getUser().catch(()=>{}),this.startAutoRefresh());const t=this.p;this.p=null,t?setTimeout(()=>{this.S=!0,this.k(t)},0):this.S=!0}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.T.bind(this)),window.addEventListener("focus",this.T.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(n("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.P(!1))}))}async T(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(n("log","[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this.P(!0),this.i&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}get user(){return this.t}get token(){return this.i}get lastError(){return this.u}consumeCallbackType(){try{const t=sessionStorage.getItem("dypai-auth-callback-type");return sessionStorage.removeItem("dypai-auth-callback-type"),sessionStorage.removeItem("dypai-auth-callback-redirect"),t}catch{return null}}get isPasswordRecoveryCallback(){try{return"PASSWORD_RECOVERY"===sessionStorage.getItem("dypai-auth-callback-redirect")}catch{return!1}}isLoggedIn(){return!(!this.i||!this.t)}onAuthStateChange(t){return n("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.l.push(t),this.I.then(()=>{const e=this.$();n("log",`[DYPAI SDK] 📣 INITIAL_SESSION para suscriptor (Sesión activa: ${!!e})`),t("INITIAL_SESSION",e)}).catch(e=>{n("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.l=this.l.filter(e=>e!==t)}}}}}async signInWithPassword(t){return a((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=password`,i={email:t.email||t.identifier||"",password:t.password},n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(i)});if(!n.ok)throw await s(n,"Login failed");const r=await n.json(),o={token:r.access_token,refreshToken:r.refresh_token,expiresIn:r.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(r.expires_in||3600),user:this._(r)};return await this.K(o),o})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return a((async()=>{const i=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,{email:r,phone:o,password:a,user_data:c,...h}=t,l={email:r,phone:o,password:a,data:{...h,...c}};e?.redirectTo?l.redirect_to=e.redirectTo:"undefined"!=typeof window&&(l.redirect_to=`${window.location.origin}/`);const d=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(l)});if(!d.ok)throw await s(d,"Registration failed");const u=await d.json(),p=!u.access_token,f={token:u.access_token,refreshToken:u.refresh_token,expiresIn:u.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(u.expires_in||3600),user:this._(u),confirmationRequired:p};return f.token?await this.K(f):n("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),f})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this.I,!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.h||0)-t<30&&this.o&&(n("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>n("warn","[DYPAI SDK] Error refreshing session in getSession:",t))),{data:{access_token:this.i,refresh_token:this.o||void 0,token_type:"bearer",user:this.t},error:null}}catch(e){return{data:null,error:new t(e.message,500)}}}async getUser(){return a((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,i=await fetch(e,{headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!i.ok)throw new t("Session invalid",i.status);const n=await i.json(),s=this._(n);return this.O(s),s})())}async me(){return this.getUser()}async signInWithOAuth(e,i={}){return a((async()=>{if("undefined"==typeof window)throw new t("signInWithOAuth requiere un entorno de navegador (window no está disponible)",400);const{redirectTo:n=window.location.href,scopes:s}=i,r=this.config.baseUrl||"http://localhost:8000",o=s?.length?`&scopes=${encodeURIComponent(s.join(" "))}`:"",a=`${r}/auth/v1/authorize?provider=${e}${o}&redirect_to=${encodeURIComponent(n)}`;window.location.href=a})())}async signOut(){return a((async()=>{try{if(this.i){const t=this.config.baseUrl||"http://localhost:8000";await fetch(`${t}/auth/v1/logout`,{method:"POST",headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}})}}finally{this.Y("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",n={email:t};e?.redirectTo?n.redirect_to=e.redirectTo:"undefined"!=typeof window&&(n.redirect_to=`${window.location.origin}/`);const r=await fetch(`${i}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!r.ok)throw await s(r,"Recovery failed");return await r.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t){return a((async()=>{const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/auth/v1/resend`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({email:t,type:"signup"})});if(!i.ok)throw await s(i,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.D)return n("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.D.promise;const e=new r;this.D=e;const i=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const t=await async function(t,e,i){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const n=new AbortController,s=setTimeout(()=>n.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:n.signal},async()=>(clearTimeout(s),await i()))}catch(n){if("AbortError"===n.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await i();throw n}}return await i()}(i,c.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.R());e.resolve(t)}catch(i){const n=i instanceof t?i:new t(i.message||"Error refrescando sesión",401);e.resolve({data:null,error:n})}finally{this.D=null}})(),e.promise}async R(){try{const e=await this.storage.getItem(this.STORAGE_KEY);if(e){const t=JSON.parse(e),i=t.expires_at||0,s=Math.floor(Date.now()/1e3);if(t.access_token!==this.i&&i-s>60)return n("log","[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.i=t.access_token,this.o=t.refresh_token,this.h=t.expires_at,this.t=t.user,this.v=0,this.k("TOKEN_REFRESHED"),{data:{token:this.i,refreshToken:this.o||void 0,expiresAt:this.h||void 0,user:this.t},error:null}}if(!this.o)throw new t("No hay refresh token disponible",401);n("log","[DYPAI SDK] 🔄 Iniciando refresco de sesión con Refresh Token...");const i=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=refresh_token`,s=new AbortController,r=setTimeout(()=>s.abort(),c.REFRESH_TIMEOUT_MS);let o;try{o=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({refresh_token:this.o}),signal:s.signal})}finally{clearTimeout(r)}if(!o.ok){let e={};try{e=await o.json()}catch{}const i=o.status,s=e.error||e.code||"",r=(e.error_description||e.msg||"").toLowerCase(),a="invalid_grant"===s||r.includes("invalid refresh token")||r.includes("revoked");throw i>=400&&i<500&&a?(n("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.Y(`refreshSession failed (${i}: ${s})`)):n("warn",`[DYPAI SDK] ⚠️ Fallo en refresco (${i}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new t(e.msg||e.error_description||"Refresh session failed",i)}const a=await o.json(),h={token:a.access_token,refreshToken:a.refresh_token,expiresIn:a.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(a.expires_in||3600),user:this._(a)};return this.v=0,await this.K(h),n("log",`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${h.expiresAt} (en ${h.expiresIn}s). Refresh token actualizado: ${!!h.refreshToken}`),{data:h,error:null}}catch(e){this.v++,this.A=Date.now();const i=e instanceof DOMException&&"AbortError"===e.name,s=i?new t("Refresh token timeout (servidor no responde)",408):e instanceof t?e:new t(e.message||"Error refrescando sesión",401),r=Math.min(c.RETRY_BASE_MS*Math.pow(2,this.v),c.MAX_RETRY_MS);return i?n("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${c.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${r}ms (intento ${this.v})`):n("error","[DYPAI SDK] ❌ Refresh falló:",s.message,`(status: ${s.status}). Backoff: ${r}ms (intento ${this.v})`),this.v>=c.MAX_REFRESH_FAILURES&&(n("error",`[DYPAI SDK] 🗑️ ${this.v} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.Y(`${this.v} consecutive refresh failures`),this.v=0),{data:null,error:s}}}async signInWithOtp(e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",n=await fetch(`${i}/auth/v1/otp`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!n.ok){const e=await n.json();throw new t(e.detail||"OTP request failed",n.status)}return await n.json()})())}async verifyOtp(e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",n=await fetch(`${i}/auth/v1/verify`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!n.ok){const e=await n.json();throw new t(e.detail||"OTP verification failed",n.status)}const s=await n.json(),r={token:s.access_token,refreshToken:s.refresh_token,expiresIn:s.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(s.expires_in||3600),user:this._(s)};return r.token&&await this.K(r),r})())}async updateUser(e){return a((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const i=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,n=await fetch(i,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!n.ok){const e=await n.json();throw new t(e.detail||"Update user failed",n.status)}const s=await n.json(),r=this._(s);return this.O(r),r})())}async setPassword(t){return this.updateUser({password:t})}startAutoRefresh(){this.stopAutoRefresh(),n("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.U(),this.m=setInterval(()=>this.U(),c.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.m&&(n("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.m),this.m=null)}async U(){try{const t=Math.floor(Date.now()/1e3),e=this.h||0;if(!this.o||!e)return;if(1e3*(e-t)>c.AUTO_REFRESH_TICK_THRESHOLD*c.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.v>0){const t=Math.min(c.RETRY_BASE_MS*Math.pow(2,this.v),c.MAX_RETRY_MS);if(Date.now()-this.A<t)return}n("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){n("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}_(t){return t?e(t):(n("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}async K(t){this.u=null,t.token&&(this.i=t.token,this.o=t.refreshToken||null,this.h=t.expiresAt||null,await this.O(t.user,t.token,t.refreshToken,t.expiresAt),n("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.h}, has_refresh: ${!!this.o})`),this.startAutoRefresh())}async O(t,e,i,s){this.t=t,e&&(this.i=e),void 0!==i&&(this.o=i||null),void 0!==s&&(this.h=s||null);try{if(this.i&&this.t){const t={access_token:this.i,refresh_token:this.o,expires_at:this.h,user:this.t};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t))}else n("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){n("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}if(this.S){const t=e?"SIGNED_IN":"USER_UPDATED";this.k(t)}}async Y(t="unknown"){n("log",`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.i=null,this.o=null,this.t=null,this.h=null,this.stopAutoRefresh();try{await this.storage.removeItem(this.STORAGE_KEY),n("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){n("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.k("SIGNED_OUT")}async P(t=!0){n("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.N())return void n("log","[DYPAI SDK] ✅ Sesión establecida desde callback URL. Saltando recuperación de localStorage.");const e=await this.storage.getItem(this.STORAGE_KEY);if(e){let t;n("log","[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");try{t=JSON.parse(e)}catch{return console.warn("[DYPAI SDK] ⚠️ Sesión en storage corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!t||"object"!=typeof t||"string"!=typeof t.access_token)return console.warn("[DYPAI SDK] ⚠️ Sesión en storage con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);this.i=t.access_token,this.o="string"==typeof t.refresh_token?t.refresh_token:null,this.h="number"==typeof t.expires_at?t.expires_at:null,this.t=t.user&&"object"==typeof t.user?t.user:null}else{n("log","[DYPAI SDK] ℹ️ No hay sesión consolidada. Buscando llaves antiguas para migración...");const t=this.STORAGE_KEY.replace("dypai-","").replace("-auth-session",""),e=await this.storage.getItem(`dypai-auth-token-${t}`),i=await this.storage.getItem(`dypai-auth-user-${t}`);if(!e||!i)return void n("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let s;n("log","[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");try{s=JSON.parse(i)}catch{return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage corrupto (JSON inválido). Ignorando migración...")}if(!s||"object"!=typeof s)return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage con formato inválido. Ignorando migración...");this.i=e,this.t=s;const r=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),o=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.o=r,this.h=o?parseInt(o,10):null,await this.O(s,this.i||void 0,this.o||void 0,this.h||void 0);const a=[`dypai-auth-token-${t}`,`dypai-auth-refresh-token-${t}`,`dypai-auth-user-${t}`,`dypai-auth-expires-at-${t}`];for(const t of a)try{await this.storage.removeItem(t)}catch(t){}n("log","[DYPAI SDK] ✨ Migración completada con éxito.")}}const i=Math.floor(Date.now()/1e3),s=this.h&&this.h<=i;if(n("log",`[DYPAI SDK] 🕒 Token expira en: ${this.h}. Ahora es: ${i}. Diferencia: ${(this.h||0)-i}s`),s&&this.o&&t){n("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void n("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.h&&n("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.p="SIGNED_IN"}catch(t){n("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async N(){if("undefined"==typeof window)return!1;try{const t=window.location.hash.substring(1);if(!t)return!1;const e=new URLSearchParams(t);if(e.get("error")){const t=e.get("error_code")||"unknown",i=e.get("error_description")?.replace(/\+/g," ")||"Authentication error";return n("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${i}`),this.u={code:t,message:i},window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.k("AUTH_ERROR"),!0}const i=e.get("access_token");if(!i)return!1;const s=e.get("refresh_token"),r=e.get("type"),o=parseInt(e.get("expires_in")||"3600",10);n("log",`[DYPAI SDK] 🔗 Auth callback detectado en URL (type: ${r||"unknown"}). Procesando...`);try{r&&sessionStorage.setItem("dypai-auth-callback-type",r),"recovery"!==r&&"invite"!==r||sessionStorage.setItem("dypai-auth-callback-redirect","PASSWORD_RECOVERY")}catch{}window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.i=i,this.o=s||null,this.h=Math.floor(Date.now()/1e3)+o;const a=this.config.baseUrl||"http://localhost:8000",c=await fetch(`${a}/auth/v1/user`,{headers:{Authorization:`Bearer ${i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!c.ok)return n("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${c.status}). Limpiando...`),this.i=null,this.o=null,this.h=null,this.u={code:"token_invalid",message:"The authentication link is invalid or has expired"},this.k("AUTH_ERROR"),!0;const h=await c.json(),l=this._(h),d={token:i,refreshToken:s||void 0,expiresIn:o,expiresAt:this.h,user:l};return await this.K(d),this.p="recovery"===r||"invite"===r?"PASSWORD_RECOVERY":"SIGNED_IN",n("log",`[DYPAI SDK] 🔑 Callback type=${r}. Pending event: ${this.p}`),!0}catch(t){return n("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}$(){return this.i&&this.t?{access_token:this.i,refresh_token:this.o||void 0,expires_at:this.h||void 0,token_type:"bearer",user:this.t}:null}k(t="USER_UPDATED"){const e=this.$();n("log",`[DYPAI SDK] 📢 Notificando a ${this.l.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.l.forEach(i=>i(t,e))}handleSessionExpired(){this.Y("handleSessionExpired called (likely 401 from API)")}}c.REFRESH_TIMEOUT_MS=15e3,c.MAX_REFRESH_FAILURES=5,c.AUTO_REFRESH_TICK_DURATION_MS=3e4,c.AUTO_REFRESH_TICK_THRESHOLD=3,c.RETRY_BASE_MS=200,c.MAX_RETRY_MS=3e4,c.LOCK_ACQUIRE_TIMEOUT_MS=5e3;class h{constructor(t){this.api=t}from(t){return new l(t,this.api)}}class l{constructor(t,e){this.table=t,this.api=e}async select(t={}){return this.api.get(this.table,{params:t})}async insert(t){return this.api.post(this.table,t)}async update(t,e){return this.api.patch(`${this.table}/${t}`,e)}async delete(t){return this.api.delete(`${this.table}/${t}`)}}class d{constructor(t){this.api=t}async list(t={}){const i=await this.api.get("admin/users",{params:t});return i.data?.users&&(i.data.users=i.data.users.map(e)),i}async create(t){const i=await this.api.post("admin/users",t);return i.data&&(i.data=e(i.data)),i}async update(t,i){const n=await this.api.put(`admin/users/${t}`,i);return n.data&&(n.data=e(n.data)),n}async delete(t){return this.api.delete(`admin/users/${t}`)}}let u=null;function p(t){u=t}const f=t=>{const{title:e,description:i,variant:n="default"}=t,s=`${"error"===n?"❌":"success"===n?"✅":"warning"===n?"⚠️":"info"===n?"ℹ️":"📢"} ${e}${i?`: ${i}`:""}`;"error"===n?console.error(s):"warning"===n&&console.warn(s)};function w(){const t=u||f;return{toast:t,toastSuccess:(e,i)=>t({title:e,description:i,variant:"success"}),toastError:(e,i)=>t({title:e,description:i,variant:"error"}),toastWarning:(e,i)=>t({title:e,description:i,variant:"warning"}),toastInfo:(e,i)=>t({title:e,description:i,variant:"info"})}}const y=t=>(u||f)(t),S=(t,e)=>y({title:t,description:e,variant:"success"}),g=(t,e)=>y({title:t,description:e,variant:"error"}),D=(t,e)=>y({title:t,description:e,variant:"warning"}),m=(t,e)=>y({title:t,description:e,variant:"info"});let v={},A=null;function I(t){A=t}function P(t){v={...v,...t}}const k=new Map;function T(){let t=v;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...v}}catch(t){}return t}async function $(e,i,n,s,r,o,a,c,h){const l=T(),d=c||null;if(!d&&!n.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof n||!n.trim())throw new Error("Endpoint debe ser un string válido");let u;if(n.startsWith("http"))u=n;else{const t=d.replace(/\/+$/,"");u=n.startsWith("/")?t+n:t+"/api/v0/"+n}if(o&&Object.keys(o).length>0){const t=new URLSearchParams,e=(i,n)=>{null!=n&&(Array.isArray(n)?n.forEach((t,n)=>e(`${i}[${n}]`,t)):"object"==typeof n?Object.entries(n).forEach(([t,n])=>e(`${i}[${t}]`,n)):t.append(i,String(n)))};Object.entries(o).forEach(([t,i])=>e(t,i));const i=t.toString();i&&(u+=`?${i}`)}const p="GET"===i?`${i}:${u}:${JSON.stringify(s)}`:null;if(p&&k.has(p))return k.get(p);const f=s instanceof FormData,w={...l.headers||{},...f?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...a&&{"x-api-key":a}},S={method:i,headers:w,credentials:"include"};s&&"GET"!==i&&"DELETE"!==i&&(S.body=f?s:JSON.stringify(s));const g=l.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!g)throw new Error("Fetch no disponible.");const D=(async()=>{const e=T().toast||y,d=(void 0!==r?r:!1!==l.showToasts)&&e;try{const p=await g(u,S);if(!p.ok){let u,f="Error en la petición";try{const t=await p.text();try{const e=JSON.parse(t);u=e,f=e.message||e.msg||e.error_description||e.error||f}catch{t.length<200&&(f=t)}}catch{}if(401===p.status&&!h&&l.onTokenExpired)try{const t=await l.onTokenExpired();if(t)return $(t,i,n,s,r,o,a,c,!0)}catch(t){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",t)}throw d&&e({title:"Error",description:f,variant:"error"}),new t(f,p.status,void 0,u)}!d||"POST"!==i&&"PUT"!==i&&"PATCH"!==i&&"DELETE"!==i||e({title:"Éxito",description:"Operación completada",variant:"success"});const f=p.headers.get("content-type")||"";return f.includes("application/pdf")||f.includes("image/")||f.includes("audio/")||f.includes("video/")||f.includes("application/octet-stream")||f.includes("application/zip")||f.includes("application/vnd.openxmlformats-officedocument")?await p.blob():f.includes("application/json")?await p.json():await p.text()}finally{p&&k.delete(p)}})();return p&&k.set(p,D),D}async function E(t,e,i,n){const s=n?.method||(i?"POST":"GET"),{data:r,error:o}=await("GET"===s?t.get(e,{params:n?.params}):t.post(e,i,{params:n?.params}));if(o)throw o;if(r instanceof Blob){const t=window.URL.createObjectURL(r),e=document.createElement("a");e.href=t,e.download=n?.fileName||"archivo-descargado",document.body.appendChild(e),e.click(),window.URL.revokeObjectURL(t),document.body.removeChild(e)}else if(r&&"object"==typeof r&&("url"in r||"signed_url"in r||"signedUrl"in r)){const t=r.url||r.signed_url||r.signedUrl;if("string"==typeof t){const e=new URL(t).searchParams.get("response-content-disposition");let i;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(i=t[1].replace(/"/g,""))}const s=n?.fileName||i;if(s){const e=document.createElement("a");e.href=t,e.download=s,document.body.appendChild(e),e.click(),document.body.removeChild(e)}else window.open(t,"_blank")}}}async function _(e,i,n,s){const{data:r,error:o}=await e.post(i,{file_path:n.name,content_type:n.type||"application/octet-stream",size_bytes:n.size,confirm:!1,client_upload:!0,...s?.params||{}});if(o)throw o;const a=function(t){if(!t||"object"!=typeof t)return null;const e=t.upload_url||t.uploadUrl||t.url?t:null;if(e?.upload_url)return b(e);const i=[t.data,t.result,t.output,t.payload].filter(Boolean);for(const t of i)if(t&&(t.upload_url||t.uploadUrl||t.url))return b(t);const n=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of n){const e=Array.isArray(t)?t:Object.values(t);for(const t of e)if(t&&(t.upload_url||t.uploadUrl||t.url))return b(t)}return null}(r);if(!a?.upload_url)throw new t("The workflow did not return a valid upload URL (missing storage upload node?)",400);const{upload_url:c,method:h="PUT",headers:l={},file_path:d,storage_path:u}=a;s?.onProgress&&s.onProgress(10);const p=await fetch(c,{method:h,headers:{"Content-Type":n.type||"application/octet-stream",...l},body:n});if(!p.ok)throw new t("Direct upload to cloud storage failed",p.status);s?.onProgress&&s.onProgress(90);const f=s?.confirmEndpoint||i,{data:w,error:y}=await e.post(f,{...s?.params,bucket:a.bucket||s?.params?.bucket,file_path:d,storage_path:u,filename:n.name,content_type:n.type||"application/octet-stream",size_bytes:n.size,confirm:!0,client_upload:!0});if(y)throw y;return s?.onProgress&&s.onProgress(100),w}function b(t){return t&&"object"==typeof t?{...t,upload_url:t.upload_url||t.uploadUrl||t.url,storage_path:t.storage_path||t.storagePath}:null}function K(e,i){const n="POST"===i||"PUT"===i||"PATCH"===i;return async(s,r,o)=>{const a=e();let c,h={};n?(c=r,h=o||{}):(h=r||{},c=void 0);const l=h.token||a.token||(A?A():"")||"",d=h.apiKey||a.apiKey;try{return{data:await $(l,i,s,c,h.showToasts,h.params,d,a.baseUrl),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Error desconocido",e?.status??500)}}}}function O(e){const i=()=>"function"==typeof e?e():e,n={get:Y(i,"GET"),post:Y(i,"POST"),put:Y(i,"PUT"),patch:Y(i,"PATCH"),delete:Y(i,"DELETE"),upload:async(e,i,s)=>{try{return{data:await _(n,e,i,s),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Upload failed",e?.status??500)}}},download:async(e,i,s)=>{try{return await E(n,e,i,{...s,method:"POST"}),{data:void 0,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Download failed",e?.status??500)}}}};return n}function Y(e,i){return async(n,s,r)=>{const o=e(),a=await async function(t,e,i){let n,s={};"POST"===t||"PUT"===t||"PATCH"===t?(n=e,s=i||{}):(s=e||{},n=void 0);let r=s.token;return!r&&A&&(r=A()||""),r=r||"",{token:r,apiKey:s.apiKey,body:n,params:s.params,showToasts:s.showToasts}}(i,s,r),c=a.token||o.token||"",h=a.apiKey||o.apiKey;try{return{data:await $(c,i,n,a.body,a.showToasts,a.params,h,o.baseUrl),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Error desconocido",e?.status??500)}}}}class R{constructor(e){const{baseUrl:i,apiKey:n}=e,s=e.auth?.storageKey||e.storageKey;e.global&&function(t){v={...v,...t}}(e.global),this.auth=new c({baseUrl:i,apiKey:n,storageKey:s,storage:e.auth?.storage,autoRefreshToken:e.auth?.autoRefreshToken,persistSession:e.auth?.persistSession},null);const r=function(e){const i={get:K(e,"GET"),post:K(e,"POST"),put:K(e,"PUT"),patch:K(e,"PATCH"),delete:K(e,"DELETE"),upload:async(e,n,s)=>{try{return{data:await _(i,e,n,s),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Upload failed",e?.status??500)}}},download:async(e,n,s)=>{try{return await E(i,e,n,{...s,method:"POST"}),{data:void 0,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Download failed",e?.status??500)}}}};return i}(()=>({token:this.auth.token,apiKey:n,baseUrl:i}));if(this.auth.api=r,this.db=new h(r),this.users=new d(r),this.api=O(()=>({token:this.auth.token||"",apiKey:n,baseUrl:i})),I(()=>this.auth.token),"undefined"!=typeof window&&e.redirects){const t=e.redirects;this.auth.I?.then(()=>{if(this.auth.isPasswordRecoveryCallback&&t.passwordRecovery)this.auth.consumeCallbackType(),window.location.pathname.includes(t.passwordRecovery)||window.location.replace(t.passwordRecovery);else if(this.auth.lastError&&t.authError)window.location.pathname.includes(t.authError)||window.location.replace(t.authError);else if(this.auth.token&&t.signIn){const e=this.auth.consumeCallbackType();e&&"recovery"!==e&&"invite"!==e&&(window.location.pathname.includes(t.signIn)||window.location.replace(t.signIn))}})}P({onTokenExpired:async()=>{const t=await this.auth.refreshSession();if(t.error)throw t.error;return t.data?.token||null},onUnauthorized:()=>this.auth.handleSessionExpired()})}from(t){return this.db.from(t)}async me(){return this.auth.getUser()}}function U(t,e,i){if(!t)throw new Error("createClient() requiere la URL base");return new R({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?i:e})}let N={};function x(t){N={...N,toast:y,...t},L()}function C(){return{...N}}function L(){try{const{configureApiService:t}=require("../services/ApiService");t(N)}catch(t){}}function j(){N={},L()}async function M(){L()}const z={name:"@dypai-ai/client-sdk",version:"0.0.1",description:"Cliente JavaScript para Dypai Engine",features:["API REST autenticada","Gestión de usuarios (Admin)","Storage via endpoints personalizados"]};export{R as DypaiClient,t as DypaiError,z as PACKAGE_INFO,$ as callApi,P as configureApiService,x as configureDypaiServices,O as createApiClient,U as createClient,C as getGlobalConfig,M as reloadDypaiConfig,j as resetGlobalConfig,p as setToastFunction,I as setTokenProvider,y as toast,g as toastError,m as toastInfo,S as toastSuccess,D as toastWarning,w as useToast};
1
+ class t extends Error{constructor(t,e=500,i,n){super(t),this.status=e,this.code=i,this.details=n,this.name="DypaiError"}}function e(t){if(!t)return{};const e=t.user||t,i=e.app_metadata||{},n=e.user_metadata||{};return{id:e.id,email:e.email,phone:e.phone,role:i.role||n.role||e.role||null,created_at:e.created_at,updated_at:e.updated_at,confirmed_at:e.confirmed_at,last_sign_in_at:e.last_sign_in_at,app_metadata:i,user_metadata:n,roleDetails:{name:i.role||n.role||null,weight:0},appContext:{app_id:"default"}}}let i=!1;function n(t,...e){"error"===t?console.error(...e):i&&("warn"===t?console.warn(...e):console.log(...e))}async function s(e,i){let n={};try{n=await e.json()}catch{}const s=n.msg||n.error_description||n.message||i,o=n.error_code||n.error||n.code||void 0;return new t(s,e.status,o)}class o{constructor(){this.promise=new Promise((t,e)=>{this.resolve=t,this.reject=e})}}const r={getItem:t=>"undefined"==typeof window?null:window.localStorage.getItem(t),setItem:(t,e)=>{if("undefined"!=typeof window)try{window.localStorage.setItem(t,e)}catch(t){console.error("[DYPAI SDK] ❌ Error crítico guardando en localStorage (¿Quota/Permisos?):",t)}},removeItem:t=>{if("undefined"!=typeof window)try{window.localStorage.removeItem(t)}catch(t){}}};async function a(e){try{return{data:await e,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e.message||"Error de autenticación",e.status||400)}}}class c{constructor(t,e=null){this.config=t,this.t=null,this.i=null,this.o=null,this.h=null,this.l=[],this.u=null,this.p=null,this.S=!1,this.D=null,this.m=null,this.A=0,this.v=0,this.storage=t.storage||r,i=!!t.debug;const s=t.storageKey||this.deriveStorageKey(t.apiKey);n("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${s})`),this.STORAGE_KEY=`dypai-${s}-auth-session`,this.I=this.P().then(()=>{this.i&&(this.getUser().catch(()=>{}),this.startAutoRefresh());const t=this.p;this.p=null,t?setTimeout(()=>{this.S=!0,this.k(t)},0):this.S=!0}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.T.bind(this)),window.addEventListener("focus",this.T.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(n("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.P(!1))}))}async T(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(n("log","[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this.P(!0),this.i&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}get user(){return this.t}get token(){return this.i}get lastError(){return this.u}consumeCallbackType(){try{const t=sessionStorage.getItem("dypai-auth-callback-type");return sessionStorage.removeItem("dypai-auth-callback-type"),sessionStorage.removeItem("dypai-auth-callback-redirect"),t}catch{return null}}get isPasswordRecoveryCallback(){try{return"PASSWORD_RECOVERY"===sessionStorage.getItem("dypai-auth-callback-redirect")}catch{return!1}}isLoggedIn(){return!(!this.i||!this.t)}onAuthStateChange(t){return n("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.l.push(t),this.I.then(()=>{const e=this.$();n("log",`[DYPAI SDK] 📣 INITIAL_SESSION para suscriptor (Sesión activa: ${!!e})`),t("INITIAL_SESSION",e)}).catch(e=>{n("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.l=this.l.filter(e=>e!==t)}}}}}async signInWithPassword(t){return a((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=password`,i={email:t.email||t.identifier||"",password:t.password},n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(i)});if(!n.ok)throw await s(n,"Login failed");const o=await n.json(),r={token:o.access_token,refreshToken:o.refresh_token,expiresIn:o.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(o.expires_in||3600),user:this._(o)};return await this.O(r),r})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return a((async()=>{const i=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,{email:o,phone:r,password:a,user_data:c,...h}=t,l={email:o,phone:r,password:a,data:{...h,...c}};e?.redirectTo?l.redirect_to=e.redirectTo:"undefined"!=typeof window&&(l.redirect_to=`${window.location.origin}/`);const d=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(l)});if(!d.ok)throw await s(d,"Registration failed");const u=await d.json(),p=!u.access_token,f={token:u.access_token,refreshToken:u.refresh_token,expiresIn:u.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(u.expires_in||3600),user:this._(u),confirmationRequired:p};return f.token?await this.O(f):n("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),f})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this.I,!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.h||0)-t<30&&this.o&&(n("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>n("warn","[DYPAI SDK] Error refreshing session in getSession:",t))),{data:{access_token:this.i,refresh_token:this.o||void 0,token_type:"bearer",user:this.t},error:null}}catch(e){return{data:null,error:new t(e.message,500)}}}async getUser(){return a((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,i=await fetch(e,{headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!i.ok)throw new t("Session invalid",i.status);const n=await i.json(),s=this._(n);return this.K(s),s})())}async me(){return this.getUser()}async signInWithOAuth(e,i={}){return a((async()=>{if("undefined"==typeof window)throw new t("signInWithOAuth requiere un entorno de navegador (window no está disponible)",400);const{redirectTo:n=window.location.href,scopes:s}=i,o=this.config.baseUrl||"http://localhost:8000",r=s?.length?`&scopes=${encodeURIComponent(s.join(" "))}`:"",a=`${o}/auth/v1/authorize?provider=${e}${r}&redirect_to=${encodeURIComponent(n)}`;window.location.href=a})())}async signOut(){return a((async()=>{try{if(this.i){const t=this.config.baseUrl||"http://localhost:8000";await fetch(`${t}/auth/v1/logout`,{method:"POST",headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}})}}finally{this.Y("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",n={email:t};e?.redirectTo?n.redirect_to=e.redirectTo:"undefined"!=typeof window&&(n.redirect_to=`${window.location.origin}/`);const o=await fetch(`${i}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!o.ok)throw await s(o,"Recovery failed");return await o.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t){return a((async()=>{const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/auth/v1/resend`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({email:t,type:"signup"})});if(!i.ok)throw await s(i,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.D)return n("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.D.promise;const e=new o;this.D=e;const i=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const t=await async function(t,e,i){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const n=new AbortController,s=setTimeout(()=>n.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:n.signal},async()=>(clearTimeout(s),await i()))}catch(n){if("AbortError"===n.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await i();throw n}}return await i()}(i,c.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.R());e.resolve(t)}catch(i){const n=i instanceof t?i:new t(i.message||"Error refrescando sesión",401);e.resolve({data:null,error:n})}finally{this.D=null}})(),e.promise}async R(){try{const e=await this.storage.getItem(this.STORAGE_KEY);if(e){const t=JSON.parse(e),i=t.expires_at||0,s=Math.floor(Date.now()/1e3);if(t.access_token!==this.i&&i-s>60)return n("log","[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.i=t.access_token,this.o=t.refresh_token,this.h=t.expires_at,this.t=t.user,this.A=0,this.k("TOKEN_REFRESHED"),{data:{token:this.i,refreshToken:this.o||void 0,expiresAt:this.h||void 0,user:this.t},error:null}}if(!this.o)throw new t("No hay refresh token disponible",401);n("log","[DYPAI SDK] 🔄 Iniciando refresco de sesión con Refresh Token...");const i=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=refresh_token`,s=new AbortController,o=setTimeout(()=>s.abort(),c.REFRESH_TIMEOUT_MS);let r;try{r=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({refresh_token:this.o}),signal:s.signal})}finally{clearTimeout(o)}if(!r.ok){let e={};try{e=await r.json()}catch{}const i=r.status,s=e.error||e.code||"",o=(e.error_description||e.msg||"").toLowerCase(),a="invalid_grant"===s||o.includes("invalid refresh token")||o.includes("revoked");throw i>=400&&i<500&&a?(n("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.Y(`refreshSession failed (${i}: ${s})`)):n("warn",`[DYPAI SDK] ⚠️ Fallo en refresco (${i}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new t(e.msg||e.error_description||"Refresh session failed",i)}const a=await r.json(),h={token:a.access_token,refreshToken:a.refresh_token,expiresIn:a.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(a.expires_in||3600),user:this._(a)};return this.A=0,await this.O(h),n("log",`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${h.expiresAt} (en ${h.expiresIn}s). Refresh token actualizado: ${!!h.refreshToken}`),{data:h,error:null}}catch(e){this.A++,this.v=Date.now();const i=e instanceof DOMException&&"AbortError"===e.name,s=i?new t("Refresh token timeout (servidor no responde)",408):e instanceof t?e:new t(e.message||"Error refrescando sesión",401),o=Math.min(c.RETRY_BASE_MS*Math.pow(2,this.A),c.MAX_RETRY_MS);return i?n("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${c.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${o}ms (intento ${this.A})`):n("error","[DYPAI SDK] ❌ Refresh falló:",s.message,`(status: ${s.status}). Backoff: ${o}ms (intento ${this.A})`),this.A>=c.MAX_REFRESH_FAILURES&&(n("error",`[DYPAI SDK] 🗑️ ${this.A} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.Y(`${this.A} consecutive refresh failures`),this.A=0),{data:null,error:s}}}async signInWithOtp(e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",n=await fetch(`${i}/auth/v1/otp`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!n.ok){const e=await n.json();throw new t(e.detail||"OTP request failed",n.status)}return await n.json()})())}async verifyOtp(e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",n=await fetch(`${i}/auth/v1/verify`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!n.ok){const e=await n.json();throw new t(e.detail||"OTP verification failed",n.status)}const s=await n.json(),o={token:s.access_token,refreshToken:s.refresh_token,expiresIn:s.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(s.expires_in||3600),user:this._(s)};return o.token&&await this.O(o),o})())}async updateUser(e){return a((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const i=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,n=await fetch(i,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!n.ok){const e=await n.json();throw new t(e.detail||"Update user failed",n.status)}const s=await n.json(),o=this._(s);return this.K(o),o})())}async setPassword(t){return this.updateUser({password:t})}startAutoRefresh(){this.stopAutoRefresh(),n("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.U(),this.m=setInterval(()=>this.U(),c.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.m&&(n("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.m),this.m=null)}async U(){try{const t=Math.floor(Date.now()/1e3),e=this.h||0;if(!this.o||!e)return;if(1e3*(e-t)>c.AUTO_REFRESH_TICK_THRESHOLD*c.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.A>0){const t=Math.min(c.RETRY_BASE_MS*Math.pow(2,this.A),c.MAX_RETRY_MS);if(Date.now()-this.v<t)return}n("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){n("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}_(t){return t?e(t):(n("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}async O(t){this.u=null,t.token&&(this.i=t.token,this.o=t.refreshToken||null,this.h=t.expiresAt||null,await this.K(t.user,t.token,t.refreshToken,t.expiresAt),n("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.h}, has_refresh: ${!!this.o})`),this.startAutoRefresh())}async K(t,e,i,s){this.t=t,e&&(this.i=e),void 0!==i&&(this.o=i||null),void 0!==s&&(this.h=s||null);try{if(this.i&&this.t){const t={access_token:this.i,refresh_token:this.o,expires_at:this.h,user:this.t};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t))}else n("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){n("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}if(this.S){const t=e?"SIGNED_IN":"USER_UPDATED";this.k(t)}}async Y(t="unknown"){n("log",`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.i=null,this.o=null,this.t=null,this.h=null,this.stopAutoRefresh();try{await this.storage.removeItem(this.STORAGE_KEY),n("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){n("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.k("SIGNED_OUT")}async P(t=!0){n("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.N())return void n("log","[DYPAI SDK] ✅ Sesión establecida desde callback URL. Saltando recuperación de localStorage.");const e=await this.storage.getItem(this.STORAGE_KEY);if(e){let t;n("log","[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");try{t=JSON.parse(e)}catch{return console.warn("[DYPAI SDK] ⚠️ Sesión en storage corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!t||"object"!=typeof t||"string"!=typeof t.access_token)return console.warn("[DYPAI SDK] ⚠️ Sesión en storage con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);this.i=t.access_token,this.o="string"==typeof t.refresh_token?t.refresh_token:null,this.h="number"==typeof t.expires_at?t.expires_at:null,this.t=t.user&&"object"==typeof t.user?t.user:null}else{n("log","[DYPAI SDK] ℹ️ No hay sesión consolidada. Buscando llaves antiguas para migración...");const t=this.STORAGE_KEY.replace("dypai-","").replace("-auth-session",""),e=await this.storage.getItem(`dypai-auth-token-${t}`),i=await this.storage.getItem(`dypai-auth-user-${t}`);if(!e||!i)return void n("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let s;n("log","[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");try{s=JSON.parse(i)}catch{return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage corrupto (JSON inválido). Ignorando migración...")}if(!s||"object"!=typeof s)return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage con formato inválido. Ignorando migración...");this.i=e,this.t=s;const o=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),r=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.o=o,this.h=r?parseInt(r,10):null,await this.K(s,this.i||void 0,this.o||void 0,this.h||void 0);const a=[`dypai-auth-token-${t}`,`dypai-auth-refresh-token-${t}`,`dypai-auth-user-${t}`,`dypai-auth-expires-at-${t}`];for(const t of a)try{await this.storage.removeItem(t)}catch(t){}n("log","[DYPAI SDK] ✨ Migración completada con éxito.")}}const i=Math.floor(Date.now()/1e3),s=this.h&&this.h<=i;if(n("log",`[DYPAI SDK] 🕒 Token expira en: ${this.h}. Ahora es: ${i}. Diferencia: ${(this.h||0)-i}s`),s&&this.o&&t){n("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void n("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.h&&n("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.p="SIGNED_IN"}catch(t){n("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async N(){if("undefined"==typeof window)return!1;try{const t=window.location.hash.substring(1);if(!t)return!1;const e=new URLSearchParams(t);if(e.get("error")){const t=e.get("error_code")||"unknown",i=e.get("error_description")?.replace(/\+/g," ")||"Authentication error";return n("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${i}`),this.u={code:t,message:i},window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.k("AUTH_ERROR"),!0}const i=e.get("access_token");if(!i)return!1;const s=e.get("refresh_token"),o=e.get("type"),r=parseInt(e.get("expires_in")||"3600",10);n("log",`[DYPAI SDK] 🔗 Auth callback detectado en URL (type: ${o||"unknown"}). Procesando...`);try{o&&sessionStorage.setItem("dypai-auth-callback-type",o),"recovery"!==o&&"invite"!==o||sessionStorage.setItem("dypai-auth-callback-redirect","PASSWORD_RECOVERY")}catch{}window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.i=i,this.o=s||null,this.h=Math.floor(Date.now()/1e3)+r;const a=this.config.baseUrl||"http://localhost:8000",c=await fetch(`${a}/auth/v1/user`,{headers:{Authorization:`Bearer ${i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!c.ok)return n("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${c.status}). Limpiando...`),this.i=null,this.o=null,this.h=null,this.u={code:"token_invalid",message:"The authentication link is invalid or has expired"},this.k("AUTH_ERROR"),!0;const h=await c.json(),l=this._(h),d={token:i,refreshToken:s||void 0,expiresIn:r,expiresAt:this.h,user:l};return await this.O(d),this.p="recovery"===o||"invite"===o?"PASSWORD_RECOVERY":"SIGNED_IN",n("log",`[DYPAI SDK] 🔑 Callback type=${o}. Pending event: ${this.p}`),!0}catch(t){return n("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}$(){return this.i&&this.t?{access_token:this.i,refresh_token:this.o||void 0,expires_at:this.h||void 0,token_type:"bearer",user:this.t}:null}k(t="USER_UPDATED"){const e=this.$();n("log",`[DYPAI SDK] 📢 Notificando a ${this.l.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.l.forEach(i=>i(t,e))}handleSessionExpired(){this.Y("handleSessionExpired called (likely 401 from API)")}}c.REFRESH_TIMEOUT_MS=15e3,c.MAX_REFRESH_FAILURES=5,c.AUTO_REFRESH_TICK_DURATION_MS=3e4,c.AUTO_REFRESH_TICK_THRESHOLD=3,c.RETRY_BASE_MS=200,c.MAX_RETRY_MS=3e4,c.LOCK_ACQUIRE_TIMEOUT_MS=5e3;class h{constructor(t){this.api=t}from(t){return new l(t,this.api)}}class l{constructor(t,e){this.table=t,this.api=e}async select(t={}){return this.api.get(this.table,{params:t})}async insert(t){return this.api.post(this.table,t)}async update(t,e){return this.api.patch(`${this.table}/${t}`,e)}async delete(t){return this.api.delete(`${this.table}/${t}`)}}class d{constructor(t){this.api=t}async list(t={}){const i=await this.api.get("admin/users",{params:t});return i.data?.users&&(i.data.users=i.data.users.map(e)),i}async create(t){const i=await this.api.post("admin/users",t);return i.data&&(i.data=e(i.data)),i}async update(t,i){const n=await this.api.put(`admin/users/${t}`,i);return n.data&&(n.data=e(n.data)),n}async delete(t){return this.api.delete(`admin/users/${t}`)}}let u=null;function p(t){u=t}const f=t=>{const{title:e,description:i,variant:n="default"}=t,s=`${"error"===n?"❌":"success"===n?"✅":"warning"===n?"⚠️":"info"===n?"ℹ️":"📢"} ${e}${i?`: ${i}`:""}`;"error"===n?console.error(s):"warning"===n&&console.warn(s)};function w(){const t=u||f;return{toast:t,toastSuccess:(e,i)=>t({title:e,description:i,variant:"success"}),toastError:(e,i)=>t({title:e,description:i,variant:"error"}),toastWarning:(e,i)=>t({title:e,description:i,variant:"warning"}),toastInfo:(e,i)=>t({title:e,description:i,variant:"info"})}}const y=t=>(u||f)(t),S=(t,e)=>y({title:t,description:e,variant:"success"}),g=(t,e)=>y({title:t,description:e,variant:"error"}),D=(t,e)=>y({title:t,description:e,variant:"warning"}),m=(t,e)=>y({title:t,description:e,variant:"info"});let A={},v=null;function I(t){v=t}function P(t){A={...A,...t}}const k=new Map;function T(){let t=A;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...A}}catch(t){}return t}async function $(e,i,n,s,o,r,a,c,h){const l=T(),d=c||null;if(!d&&!n.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof n||!n.trim())throw new Error("Endpoint debe ser un string válido");let u;if(n.startsWith("http"))u=n;else{const t=d.replace(/\/+$/,"");u=n.startsWith("/")?t+n:t+"/api/v0/"+n}if(r&&Object.keys(r).length>0){const t=new URLSearchParams,e=(i,n)=>{null!=n&&(Array.isArray(n)?n.forEach((t,n)=>e(`${i}[${n}]`,t)):"object"==typeof n?Object.entries(n).forEach(([t,n])=>e(`${i}[${t}]`,n)):t.append(i,String(n)))};Object.entries(r).forEach(([t,i])=>e(t,i));const i=t.toString();i&&(u+=`?${i}`)}const p="GET"===i?`${i}:${u}:${JSON.stringify(s)}`:null;if(p&&k.has(p))return k.get(p);const f=s instanceof FormData,w={...l.headers||{},...f?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...a&&{"x-api-key":a}},S={method:i,headers:w,credentials:"include"};s&&"GET"!==i&&"DELETE"!==i&&(S.body=f?s:JSON.stringify(s));const g=l.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!g)throw new Error("Fetch no disponible.");const D=(async()=>{const e=T().toast||y,d=(void 0!==o?o:!1!==l.showToasts)&&e;try{const p=await g(u,S);if(!p.ok){let u,f="Error en la petición";try{const t=await p.text();try{const e=JSON.parse(t);u=e,f=e.message||e.msg||e.error_description||e.error||f}catch{t.length<200&&(f=t)}}catch{}if(401===p.status&&!h&&l.onTokenExpired)try{const t=await l.onTokenExpired();if(t)return $(t,i,n,s,o,r,a,c,!0)}catch(t){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",t)}throw d&&e({title:"Error",description:f,variant:"error"}),new t(f,p.status,void 0,u)}!d||"POST"!==i&&"PUT"!==i&&"PATCH"!==i&&"DELETE"!==i||e({title:"Éxito",description:"Operación completada",variant:"success"});const f=p.headers.get("content-type")||"";return f.includes("application/pdf")||f.includes("image/")||f.includes("audio/")||f.includes("video/")||f.includes("application/octet-stream")||f.includes("application/zip")||f.includes("application/vnd.openxmlformats-officedocument")?await p.blob():f.includes("application/json")?await p.json():await p.text()}finally{p&&k.delete(p)}})();return p&&k.set(p,D),D}async function E(t,e,i,n){const s=n?.method||(i?"POST":"GET"),{data:o,error:r}=await("GET"===s?t.get(e,{params:n?.params}):t.post(e,i,{params:n?.params}));if(r)throw r;if(o instanceof Blob){const t=window.URL.createObjectURL(o),e=document.createElement("a");e.href=t,e.download=n?.fileName||"archivo-descargado",document.body.appendChild(e),e.click(),window.URL.revokeObjectURL(t),document.body.removeChild(e)}else if(o&&"object"==typeof o&&("url"in o||"signed_url"in o||"signedUrl"in o)){const t=o.url||o.signed_url||o.signedUrl;if("string"==typeof t){const e=new URL(t).searchParams.get("response-content-disposition");let i;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(i=t[1].replace(/"/g,""))}const s=n?.fileName||i;if(s){const e=document.createElement("a");e.href=t,e.download=s,document.body.appendChild(e),e.click(),document.body.removeChild(e)}else window.open(t,"_blank")}}}async function _(e,i,n,s){const{data:o,error:r}=await e.post(i,{file_path:n.name,content_type:n.type||"application/octet-stream",size_bytes:n.size,confirm:!1,client_upload:!0,...s?.params||{}});if(r)throw r;const a=function(t){if(!t||"object"!=typeof t)return null;const e=t.upload_url||t.uploadUrl||t.url?t:null;if(e?.upload_url)return b(e);const i=[t.data,t.result,t.output,t.payload].filter(Boolean);for(const t of i)if(t&&(t.upload_url||t.uploadUrl||t.url))return b(t);const n=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of n){const e=Array.isArray(t)?t:Object.values(t);for(const t of e)if(t&&(t.upload_url||t.uploadUrl||t.url))return b(t)}return null}(o);if(!a?.upload_url)throw new t("The workflow did not return a valid upload URL (missing storage upload node?)",400);const{upload_url:c,method:h="PUT",headers:l={},file_path:d,storage_path:u}=a;s?.onProgress&&s.onProgress(10);const p=await fetch(c,{method:h,headers:{"Content-Type":n.type||"application/octet-stream",...l},body:n});if(!p.ok)throw new t("Direct upload to cloud storage failed",p.status);s?.onProgress&&s.onProgress(90);const f=s?.confirmEndpoint||i,{data:w,error:y}=await e.post(f,{...s?.params,bucket:a.bucket||s?.params?.bucket,file_path:d,storage_path:u,filename:n.name,content_type:n.type||"application/octet-stream",size_bytes:n.size,confirm:!0,client_upload:!0});if(y)throw y;return s?.onProgress&&s.onProgress(100),w}function b(t){return t&&"object"==typeof t?{...t,upload_url:t.upload_url||t.uploadUrl||t.url,storage_path:t.storage_path||t.storagePath}:null}function O(e,i){const n="POST"===i||"PUT"===i||"PATCH"===i;return async(s,o,r)=>{const a=e();let c,h={};n?(c=o,h=r||{}):(h=o||{},c=void 0);const l=h.token||a.token||(v?v():"")||"",d=h.apiKey||a.apiKey;try{return{data:await $(l,i,s,c,h.showToasts,h.params,d,a.baseUrl),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Error desconocido",e?.status??500)}}}}function K(e){const i=()=>"function"==typeof e?e():e,n={get:Y(i,"GET"),post:Y(i,"POST"),put:Y(i,"PUT"),patch:Y(i,"PATCH"),delete:Y(i,"DELETE"),upload:async(e,i,s)=>{try{return{data:await _(n,e,i,s),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Upload failed",e?.status??500)}}},download:async(e,i,s)=>{try{return await E(n,e,i,{...s,method:"POST"}),{data:void 0,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Download failed",e?.status??500)}}}};return n}function Y(e,i){return async(n,s,o)=>{const r=e(),a=await async function(t,e,i){let n,s={};"POST"===t||"PUT"===t||"PATCH"===t?(n=e,s=i||{}):(s=e||{},n=void 0);let o=s.token;return!o&&v&&(o=v()||""),o=o||"",{token:o,apiKey:s.apiKey,body:n,params:s.params,showToasts:s.showToasts}}(i,s,o),c=a.token||r.token||"",h=a.apiKey||r.apiKey;try{return{data:await $(c,i,n,a.body,a.showToasts,a.params,h,r.baseUrl),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Error desconocido",e?.status??500)}}}}class R{constructor(e){const{baseUrl:i,apiKey:n}=e,s=e.auth?.storageKey||e.storageKey;e.global&&function(t){A={...A,...t}}(e.global),this.auth=new c({baseUrl:i,apiKey:n,storageKey:s,storage:e.auth?.storage,autoRefreshToken:e.auth?.autoRefreshToken,persistSession:e.auth?.persistSession},null);const o=function(e){const i={get:O(e,"GET"),post:O(e,"POST"),put:O(e,"PUT"),patch:O(e,"PATCH"),delete:O(e,"DELETE"),upload:async(e,n,s)=>{try{return{data:await _(i,e,n,s),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Upload failed",e?.status??500)}}},download:async(e,n,s)=>{try{return await E(i,e,n,{...s,method:"POST"}),{data:void 0,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Download failed",e?.status??500)}}}};return i}(()=>({token:this.auth.token,apiKey:n,baseUrl:i}));if(this.auth.api=o,this.db=new h(o),this.users=new d(o),this.api=K(()=>({token:this.auth.token||"",apiKey:n,baseUrl:i})),I(()=>this.auth.token),"undefined"!=typeof window&&e.redirects){const t=e.redirects;this.auth.I?.then(()=>{if(this.auth.isPasswordRecoveryCallback&&t.passwordRecovery)this.auth.consumeCallbackType(),window.location.pathname.includes(t.passwordRecovery)||window.location.replace(t.passwordRecovery);else if(this.auth.lastError&&t.authError)window.location.pathname.includes(t.authError)||window.location.replace(t.authError);else if(this.auth.token&&t.signIn){const e=this.auth.consumeCallbackType();e&&"recovery"!==e&&"invite"!==e&&(window.location.pathname.includes(t.signIn)||window.location.replace(t.signIn))}})}P({onTokenExpired:async()=>{const t=await this.auth.refreshSession();if(t.error)throw t.error;return t.data?.token||null},onUnauthorized:()=>this.auth.handleSessionExpired()})}from(t){return this.db.from(t)}async me(){return this.auth.getUser()}}function U(t,e,i){if(!t)throw new Error("createClient() requiere la URL base");return new R({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?i:e})}let N={};function C(t){N={...N,toast:y,...t},L()}function x(){return{...N}}function L(){try{const{configureApiService:t}=require("../services/ApiService");t(N)}catch(t){}}function j(){N={},L()}async function M(){L()}const J={name:"@dypai-ai/client-sdk",version:"1.0.0",description:"Official JavaScript/TypeScript SDK for DYPAI",features:["Authentication (email, OAuth, OTP)","Database CRUD","Custom endpoints (typed API)","File upload/download (Smart Upload)","User management (admin)","React hooks (useAuth, useEndpoint, useAction, useUpload)"]};export{R as DypaiClient,t as DypaiError,J as PACKAGE_INFO,$ as callApi,P as configureApiService,C as configureDypaiServices,K as createApiClient,U as createClient,x as getGlobalConfig,M as reloadDypaiConfig,j as resetGlobalConfig,p as setToastFunction,I as setTokenProvider,y as toast,g as toastError,m as toastInfo,S as toastSuccess,D as toastWarning,w as useToast};
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";class DypaiError extends Error{constructor(t,e=500,o,r){super(t),this.status=e,this.code=o,this.details=r,this.name="DypaiError"}}function normalizeUser(t){if(!t)return{};const e=t.user||t,o=e.app_metadata||{},r=e.user_metadata||{};return{id:e.id,email:e.email,phone:e.phone,role:o.role||r.role||e.role||null,created_at:e.created_at,updated_at:e.updated_at,confirmed_at:e.confirmed_at,last_sign_in_at:e.last_sign_in_at,app_metadata:o,user_metadata:r,roleDetails:{name:o.role||r.role||null,weight:0},appContext:{app_id:"default"}}}let _debugEnabled=!1;function _log(t,...e){"error"===t?console.error(...e):_debugEnabled&&("warn"===t?console.warn(...e):console.log(...e))}async function parseAuthError(t,e){let o={};try{o=await t.json()}catch{}const r=o.msg||o.error_description||o.message||e,i=o.error_code||o.error||o.code||void 0;return new DypaiError(r,t.status,i)}class Deferred{constructor(){this.promise=new Promise((t,e)=>{this.resolve=t,this.reject=e})}}const localStorageAdapter={getItem:t=>"undefined"==typeof window?null:window.localStorage.getItem(t),setItem:(t,e)=>{if("undefined"!=typeof window)try{window.localStorage.setItem(t,e)}catch(t){console.error("[DYPAI SDK] ❌ Error crítico guardando en localStorage (¿Quota/Permisos?):",t)}},removeItem:t=>{if("undefined"!=typeof window)try{window.localStorage.removeItem(t)}catch(t){}}};async function wrapAuthResponse(t){try{return{data:await t,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t.message||"Error de autenticación",t.status||400)}}}class AuthModule{constructor(t,e=null){this.config=t,this.t=null,this.o=null,this.i=null,this.l=null,this.h=[],this.u=null,this.p=null,this.D=!1,this.S=null,this.A=null,this.m=0,this.v=0,this.storage=t.storage||localStorageAdapter,_debugEnabled=!!t.debug;const o=t.storageKey||this.deriveStorageKey(t.apiKey);_log("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${o})`),this.STORAGE_KEY=`dypai-${o}-auth-session`,this._=this.P().then(()=>{this.o&&(this.getUser().catch(()=>{}),this.startAutoRefresh());const t=this.p;this.p=null,t?setTimeout(()=>{this.D=!0,this.T(t)},0):this.D=!0}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.k.bind(this)),window.addEventListener("focus",this.k.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(_log("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.P(!1))}))}async k(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(_log("log","[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this.P(!0),this.o&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}get user(){return this.t}get token(){return this.o}get lastError(){return this.u}consumeCallbackType(){try{const t=sessionStorage.getItem("dypai-auth-callback-type");return sessionStorage.removeItem("dypai-auth-callback-type"),sessionStorage.removeItem("dypai-auth-callback-redirect"),t}catch{return null}}get isPasswordRecoveryCallback(){try{return"PASSWORD_RECOVERY"===sessionStorage.getItem("dypai-auth-callback-redirect")}catch{return!1}}isLoggedIn(){return!(!this.o||!this.t)}onAuthStateChange(t){return _log("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.h.push(t),this._.then(()=>{const e=this.I();_log("log",`[DYPAI SDK] 📣 INITIAL_SESSION para suscriptor (Sesión activa: ${!!e})`),t("INITIAL_SESSION",e)}).catch(e=>{_log("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.h=this.h.filter(e=>e!==t)}}}}}async signInWithPassword(t){return wrapAuthResponse((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=password`,o={email:t.email||t.identifier||"",password:t.password},r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(o)});if(!r.ok)throw await parseAuthError(r,"Login failed");const i=await r.json(),n={token:i.access_token,refreshToken:i.refresh_token,expiresIn:i.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(i.expires_in||3600),user:this.$(i)};return await this.C(n),n})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return wrapAuthResponse((async()=>{const o=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,{email:r,phone:i,password:n,user_data:a,...s}=t,l={email:r,phone:i,password:n,data:{...s,...a}};e?.redirectTo?l.redirect_to=e.redirectTo:"undefined"!=typeof window&&(l.redirect_to=`${window.location.origin}/`);const c=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(l)});if(!c.ok)throw await parseAuthError(c,"Registration failed");const h=await c.json(),d=!h.access_token,u={token:h.access_token,refreshToken:h.refresh_token,expiresIn:h.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(h.expires_in||3600),user:this.$(h),confirmationRequired:d};return u.token?await this.C(u):_log("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),u})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this._,!this.o||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.l||0)-t<30&&this.i&&(_log("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>_log("warn","[DYPAI SDK] Error refreshing session in getSession:",t))),{data:{access_token:this.o,refresh_token:this.i||void 0,token_type:"bearer",user:this.t},error:null}}catch(t){return{data:null,error:new DypaiError(t.message,500)}}}async getUser(){return wrapAuthResponse((async()=>{if(!this.o)throw new DypaiError("No hay sesión activa",401);const t=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,e=await fetch(t,{headers:{Authorization:`Bearer ${this.o}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!e.ok)throw new DypaiError("Session invalid",e.status);const o=await e.json(),r=this.$(o);return this.R(r),r})())}async me(){return this.getUser()}async signInWithOAuth(t,e={}){return wrapAuthResponse((async()=>{if("undefined"==typeof window)throw new DypaiError("signInWithOAuth requiere un entorno de navegador (window no está disponible)",400);const{redirectTo:o=window.location.href,scopes:r}=e,i=this.config.baseUrl||"http://localhost:8000",n=r?.length?`&scopes=${encodeURIComponent(r.join(" "))}`:"",a=`${i}/auth/v1/authorize?provider=${t}${n}&redirect_to=${encodeURIComponent(o)}`;window.location.href=a})())}async signOut(){return wrapAuthResponse((async()=>{try{if(this.o){const t=this.config.baseUrl||"http://localhost:8000";await fetch(`${t}/auth/v1/logout`,{method:"POST",headers:{Authorization:`Bearer ${this.o}`,...this.config.apiKey&&{apikey:this.config.apiKey}}})}}finally{this.K("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return wrapAuthResponse((async()=>{const o=this.config.baseUrl||"http://localhost:8000",r={email:t};e?.redirectTo?r.redirect_to=e.redirectTo:"undefined"!=typeof window&&(r.redirect_to=`${window.location.origin}/`);const i=await fetch(`${o}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!i.ok)throw await parseAuthError(i,"Recovery failed");return await i.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${e}/auth/v1/resend`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({email:t,type:"signup"})});if(!o.ok)throw await parseAuthError(o,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.S)return _log("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.S.promise;const t=new Deferred;this.S=t;const e=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const o=await async function(t,e,o){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const r=new AbortController,i=setTimeout(()=>r.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:r.signal},async()=>(clearTimeout(i),await o()))}catch(r){if("AbortError"===r.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await o();throw r}}return await o()}(e,AuthModule.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.O());t.resolve(o)}catch(e){const o=e instanceof DypaiError?e:new DypaiError(e.message||"Error refrescando sesión",401);t.resolve({data:null,error:o})}finally{this.S=null}})(),t.promise}async O(){try{const t=await this.storage.getItem(this.STORAGE_KEY);if(t){const e=JSON.parse(t),o=e.expires_at||0,r=Math.floor(Date.now()/1e3);if(e.access_token!==this.o&&o-r>60)return _log("log","[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.o=e.access_token,this.i=e.refresh_token,this.l=e.expires_at,this.t=e.user,this.m=0,this.T("TOKEN_REFRESHED"),{data:{token:this.o,refreshToken:this.i||void 0,expiresAt:this.l||void 0,user:this.t},error:null}}if(!this.i)throw new DypaiError("No hay refresh token disponible",401);_log("log","[DYPAI SDK] 🔄 Iniciando refresco de sesión con Refresh Token...");const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=refresh_token`,o=new AbortController,r=setTimeout(()=>o.abort(),AuthModule.REFRESH_TIMEOUT_MS);let i;try{i=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({refresh_token:this.i}),signal:o.signal})}finally{clearTimeout(r)}if(!i.ok){let t={};try{t=await i.json()}catch{}const e=i.status,o=t.error||t.code||"",r=(t.error_description||t.msg||"").toLowerCase(),n="invalid_grant"===o||r.includes("invalid refresh token")||r.includes("revoked");throw e>=400&&e<500&&n?(_log("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.K(`refreshSession failed (${e}: ${o})`)):_log("warn",`[DYPAI SDK] ⚠️ Fallo en refresco (${e}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new DypaiError(t.msg||t.error_description||"Refresh session failed",e)}const n=await i.json(),a={token:n.access_token,refreshToken:n.refresh_token,expiresIn:n.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(n.expires_in||3600),user:this.$(n)};return this.m=0,await this.C(a),_log("log",`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${a.expiresAt} (en ${a.expiresIn}s). Refresh token actualizado: ${!!a.refreshToken}`),{data:a,error:null}}catch(t){this.m++,this.v=Date.now();const e=t instanceof DOMException&&"AbortError"===t.name,o=e?new DypaiError("Refresh token timeout (servidor no responde)",408):t instanceof DypaiError?t:new DypaiError(t.message||"Error refrescando sesión",401),r=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.m),AuthModule.MAX_RETRY_MS);return e?_log("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${AuthModule.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${r}ms (intento ${this.m})`):_log("error","[DYPAI SDK] ❌ Refresh falló:",o.message,`(status: ${o.status}). Backoff: ${r}ms (intento ${this.m})`),this.m>=AuthModule.MAX_REFRESH_FAILURES&&(_log("error",`[DYPAI SDK] 🗑️ ${this.m} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.K(`${this.m} consecutive refresh failures`),this.m=0),{data:null,error:o}}}async signInWithOtp(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${e}/auth/v1/otp`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(t)});if(!o.ok){const t=await o.json();throw new DypaiError(t.detail||"OTP request failed",o.status)}return await o.json()})())}async verifyOtp(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${e}/auth/v1/verify`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(t)});if(!o.ok){const t=await o.json();throw new DypaiError(t.detail||"OTP verification failed",o.status)}const r=await o.json(),i={token:r.access_token,refreshToken:r.refresh_token,expiresIn:r.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(r.expires_in||3600),user:this.$(r)};return i.token&&await this.C(i),i})())}async updateUser(t){return wrapAuthResponse((async()=>{if(!this.o)throw new DypaiError("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,o=await fetch(e,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.o}`,...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(t)});if(!o.ok){const t=await o.json();throw new DypaiError(t.detail||"Update user failed",o.status)}const r=await o.json(),i=this.$(r);return this.R(i),i})())}async setPassword(t){return this.updateUser({password:t})}startAutoRefresh(){this.stopAutoRefresh(),_log("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.U(),this.A=setInterval(()=>this.U(),AuthModule.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.A&&(_log("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.A),this.A=null)}async U(){try{const t=Math.floor(Date.now()/1e3),e=this.l||0;if(!this.i||!e)return;if(1e3*(e-t)>AuthModule.AUTO_REFRESH_TICK_THRESHOLD*AuthModule.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.m>0){const t=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.m),AuthModule.MAX_RETRY_MS);if(Date.now()-this.v<t)return}_log("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){_log("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}$(t){return t?normalizeUser(t):(_log("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}async C(t){this.u=null,t.token&&(this.o=t.token,this.i=t.refreshToken||null,this.l=t.expiresAt||null,await this.R(t.user,t.token,t.refreshToken,t.expiresAt),_log("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.l}, has_refresh: ${!!this.i})`),this.startAutoRefresh())}async R(t,e,o,r){this.t=t,e&&(this.o=e),void 0!==o&&(this.i=o||null),void 0!==r&&(this.l=r||null);try{if(this.o&&this.t){const t={access_token:this.o,refresh_token:this.i,expires_at:this.l,user:this.t};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t))}else _log("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){_log("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}if(this.D){const t=e?"SIGNED_IN":"USER_UPDATED";this.T(t)}}async K(t="unknown"){_log("log",`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.o=null,this.i=null,this.t=null,this.l=null,this.stopAutoRefresh();try{await this.storage.removeItem(this.STORAGE_KEY),_log("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){_log("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.T("SIGNED_OUT")}async P(t=!0){_log("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.Y())return void _log("log","[DYPAI SDK] ✅ Sesión establecida desde callback URL. Saltando recuperación de localStorage.");const e=await this.storage.getItem(this.STORAGE_KEY);if(e){let t;_log("log","[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");try{t=JSON.parse(e)}catch{return console.warn("[DYPAI SDK] ⚠️ Sesión en storage corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!t||"object"!=typeof t||"string"!=typeof t.access_token)return console.warn("[DYPAI SDK] ⚠️ Sesión en storage con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);this.o=t.access_token,this.i="string"==typeof t.refresh_token?t.refresh_token:null,this.l="number"==typeof t.expires_at?t.expires_at:null,this.t=t.user&&"object"==typeof t.user?t.user:null}else{_log("log","[DYPAI SDK] ℹ️ No hay sesión consolidada. Buscando llaves antiguas para migración...");const t=this.STORAGE_KEY.replace("dypai-","").replace("-auth-session",""),e=await this.storage.getItem(`dypai-auth-token-${t}`),o=await this.storage.getItem(`dypai-auth-user-${t}`);if(!e||!o)return void _log("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let r;_log("log","[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");try{r=JSON.parse(o)}catch{return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage corrupto (JSON inválido). Ignorando migración...")}if(!r||"object"!=typeof r)return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage con formato inválido. Ignorando migración...");this.o=e,this.t=r;const i=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),n=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.i=i,this.l=n?parseInt(n,10):null,await this.R(r,this.o||void 0,this.i||void 0,this.l||void 0);const a=[`dypai-auth-token-${t}`,`dypai-auth-refresh-token-${t}`,`dypai-auth-user-${t}`,`dypai-auth-expires-at-${t}`];for(const t of a)try{await this.storage.removeItem(t)}catch(t){}_log("log","[DYPAI SDK] ✨ Migración completada con éxito.")}}const o=Math.floor(Date.now()/1e3),r=this.l&&this.l<=o;if(_log("log",`[DYPAI SDK] 🕒 Token expira en: ${this.l}. Ahora es: ${o}. Diferencia: ${(this.l||0)-o}s`),r&&this.i&&t){_log("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void _log("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.l&&_log("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.p="SIGNED_IN"}catch(t){_log("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async Y(){if("undefined"==typeof window)return!1;try{const t=window.location.hash.substring(1);if(!t)return!1;const e=new URLSearchParams(t);if(e.get("error")){const t=e.get("error_code")||"unknown",o=e.get("error_description")?.replace(/\+/g," ")||"Authentication error";return _log("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${o}`),this.u={code:t,message:o},window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.T("AUTH_ERROR"),!0}const o=e.get("access_token");if(!o)return!1;const r=e.get("refresh_token"),i=e.get("type"),n=parseInt(e.get("expires_in")||"3600",10);_log("log",`[DYPAI SDK] 🔗 Auth callback detectado en URL (type: ${i||"unknown"}). Procesando...`);try{i&&sessionStorage.setItem("dypai-auth-callback-type",i),"recovery"!==i&&"invite"!==i||sessionStorage.setItem("dypai-auth-callback-redirect","PASSWORD_RECOVERY")}catch{}window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.o=o,this.i=r||null,this.l=Math.floor(Date.now()/1e3)+n;const a=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${a}/auth/v1/user`,{headers:{Authorization:`Bearer ${o}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!s.ok)return _log("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${s.status}). Limpiando...`),this.o=null,this.i=null,this.l=null,this.u={code:"token_invalid",message:"The authentication link is invalid or has expired"},this.T("AUTH_ERROR"),!0;const l=await s.json(),c=this.$(l),h={token:o,refreshToken:r||void 0,expiresIn:n,expiresAt:this.l,user:c};return await this.C(h),this.p="recovery"===i||"invite"===i?"PASSWORD_RECOVERY":"SIGNED_IN",_log("log",`[DYPAI SDK] 🔑 Callback type=${i}. Pending event: ${this.p}`),!0}catch(t){return _log("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}I(){return this.o&&this.t?{access_token:this.o,refresh_token:this.i||void 0,expires_at:this.l||void 0,token_type:"bearer",user:this.t}:null}T(t="USER_UPDATED"){const e=this.I();_log("log",`[DYPAI SDK] 📢 Notificando a ${this.h.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.h.forEach(o=>o(t,e))}handleSessionExpired(){this.K("handleSessionExpired called (likely 401 from API)")}}AuthModule.REFRESH_TIMEOUT_MS=15e3,AuthModule.MAX_REFRESH_FAILURES=5,AuthModule.AUTO_REFRESH_TICK_DURATION_MS=3e4,AuthModule.AUTO_REFRESH_TICK_THRESHOLD=3,AuthModule.RETRY_BASE_MS=200,AuthModule.MAX_RETRY_MS=3e4,AuthModule.LOCK_ACQUIRE_TIMEOUT_MS=5e3;class DataModule{constructor(t){this.api=t}from(t){return new QueryBuilder(t,this.api)}}class QueryBuilder{constructor(t,e){this.table=t,this.api=e}async select(t={}){return this.api.get(this.table,{params:t})}async insert(t){return this.api.post(this.table,t)}async update(t,e){return this.api.patch(`${this.table}/${t}`,e)}async delete(t){return this.api.delete(`${this.table}/${t}`)}}class UsersModule{constructor(t){this.api=t}async list(t={}){const e=await this.api.get("admin/users",{params:t});return e.data?.users&&(e.data.users=e.data.users.map(normalizeUser)),e}async create(t){const e=await this.api.post("admin/users",t);return e.data&&(e.data=normalizeUser(e.data)),e}async update(t,e){const o=await this.api.put(`admin/users/${t}`,e);return o.data&&(o.data=normalizeUser(o.data)),o}async delete(t){return this.api.delete(`admin/users/${t}`)}}let customToastFunction=null;const fallbackToast=t=>{const{title:e,description:o,variant:r="default"}=t,i=`${"error"===r?"❌":"success"===r?"✅":"warning"===r?"⚠️":"info"===r?"ℹ️":"📢"} ${e}${o?`: ${o}`:""}`;"error"===r?console.error(i):"warning"===r&&console.warn(i)},toast=t=>(customToastFunction||fallbackToast)(t);let serviceConfig={},globalTokenProvider=null;function setTokenProvider(t){globalTokenProvider=t}function configureApiService(t){serviceConfig={...serviceConfig,...t}}const pendingRequests=new Map;function getCompleteConfig(){let t=serviceConfig;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...serviceConfig}}catch(t){}return t}async function callApi(t,e,o,r,i,n,a,s,l){const c=getCompleteConfig(),h=s||null;if(!h&&!o.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof o||!o.trim())throw new Error("Endpoint debe ser un string válido");let d;if(o.startsWith("http"))d=o;else{const t=h.replace(/\/+$/,"");d=o.startsWith("/")?t+o:t+"/api/v0/"+o}if(n&&Object.keys(n).length>0){const t=new URLSearchParams,e=(o,r)=>{null!=r&&(Array.isArray(r)?r.forEach((t,r)=>e(`${o}[${r}]`,t)):"object"==typeof r?Object.entries(r).forEach(([t,r])=>e(`${o}[${t}]`,r)):t.append(o,String(r)))};Object.entries(n).forEach(([t,o])=>e(t,o));const o=t.toString();o&&(d+=`?${o}`)}const u="GET"===e?`${e}:${d}:${JSON.stringify(r)}`:null;if(u&&pendingRequests.has(u))return pendingRequests.get(u);const p=r instanceof FormData,f={...c.headers||{},...p?{}:{"Content-Type":"application/json"},...t&&{Authorization:`Bearer ${t}`},...a&&{"x-api-key":a}},y={method:e,headers:f,credentials:"include"};r&&"GET"!==e&&"DELETE"!==e&&(y.body=p?r:JSON.stringify(r));const w=c.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!w)throw new Error("Fetch no disponible.");const g=(async()=>{const t=getCompleteConfig().toast||toast,h=(void 0!==i?i:!1!==c.showToasts)&&t;try{const u=await w(d,y);if(!u.ok){let d,p="Error en la petición";try{const t=await u.text();try{const e=JSON.parse(t);d=e,p=e.message||e.msg||e.error_description||e.error||p}catch{t.length<200&&(p=t)}}catch{}if(401===u.status&&!l&&c.onTokenExpired)try{const t=await c.onTokenExpired();if(t)return callApi(t,e,o,r,i,n,a,s,!0)}catch(t){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",t)}throw h&&t({title:"Error",description:p,variant:"error"}),new DypaiError(p,u.status,void 0,d)}!h||"POST"!==e&&"PUT"!==e&&"PATCH"!==e&&"DELETE"!==e||t({title:"Éxito",description:"Operación completada",variant:"success"});const p=u.headers.get("content-type")||"";return p.includes("application/pdf")||p.includes("image/")||p.includes("audio/")||p.includes("video/")||p.includes("application/octet-stream")||p.includes("application/zip")||p.includes("application/vnd.openxmlformats-officedocument")?await u.blob():p.includes("application/json")?await u.json():await u.text()}finally{u&&pendingRequests.delete(u)}})();return u&&pendingRequests.set(u,g),g}async function handleSmartDownload(t,e,o,r){const i=r?.method||(o?"POST":"GET"),{data:n,error:a}=await("GET"===i?t.get(e,{params:r?.params}):t.post(e,o,{params:r?.params}));if(a)throw a;if(n instanceof Blob){const t=window.URL.createObjectURL(n),e=document.createElement("a");e.href=t,e.download=r?.fileName||"archivo-descargado",document.body.appendChild(e),e.click(),window.URL.revokeObjectURL(t),document.body.removeChild(e)}else if(n&&"object"==typeof n&&("url"in n||"signed_url"in n||"signedUrl"in n)){const t=n.url||n.signed_url||n.signedUrl;if("string"==typeof t){const e=new URL(t).searchParams.get("response-content-disposition");let o;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(o=t[1].replace(/"/g,""))}const i=r?.fileName||o;if(i){const e=document.createElement("a");e.href=t,e.download=i,document.body.appendChild(e),e.click(),document.body.removeChild(e)}else window.open(t,"_blank")}}}async function handleSmartUpload(t,e,o,r){const{data:i,error:n}=await t.post(e,{file_path:o.name,content_type:o.type||"application/octet-stream",size_bytes:o.size,confirm:!1,client_upload:!0,...r?.params||{}});if(n)throw n;const a=function(t){if(!t||"object"!=typeof t)return null;const e=t.upload_url||t.uploadUrl||t.url?t:null;if(e?.upload_url)return normalizeUploadPayload(e);const o=[t.data,t.result,t.output,t.payload].filter(Boolean);for(const t of o)if(t&&(t.upload_url||t.uploadUrl||t.url))return normalizeUploadPayload(t);const r=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of r){const e=Array.isArray(t)?t:Object.values(t);for(const t of e)if(t&&(t.upload_url||t.uploadUrl||t.url))return normalizeUploadPayload(t)}return null}(i);if(!a?.upload_url)throw new DypaiError("The workflow did not return a valid upload URL (missing storage upload node?)",400);const{upload_url:s,method:l="PUT",headers:c={},file_path:h,storage_path:d}=a;r?.onProgress&&r.onProgress(10);const u=await fetch(s,{method:l,headers:{"Content-Type":o.type||"application/octet-stream",...c},body:o});if(!u.ok)throw new DypaiError("Direct upload to cloud storage failed",u.status);r?.onProgress&&r.onProgress(90);const p=r?.confirmEndpoint||e,{data:f,error:y}=await t.post(p,{...r?.params,bucket:a.bucket||r?.params?.bucket,file_path:h,storage_path:d,filename:o.name,content_type:o.type||"application/octet-stream",size_bytes:o.size,confirm:!0,client_upload:!0});if(y)throw y;return r?.onProgress&&r.onProgress(100),f}function normalizeUploadPayload(t){return t&&"object"==typeof t?{...t,upload_url:t.upload_url||t.uploadUrl||t.url,storage_path:t.storage_path||t.storagePath}:null}function createMethod(t,e){const o="POST"===e||"PUT"===e||"PATCH"===e;return async(r,i,n)=>{const a=t();let s,l={};o?(s=i,l=n||{}):(l=i||{},s=void 0);const c=l.token||a.token||(globalTokenProvider?globalTokenProvider():"")||"",h=l.apiKey||a.apiKey;try{return{data:await callApi(c,e,r,s,l.showToasts,l.params,h,a.baseUrl),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Error desconocido",t?.status??500)}}}}function createApiClient(t){const e=()=>"function"==typeof t?t():t,o={get:createMethodFromCtx(e,"GET"),post:createMethodFromCtx(e,"POST"),put:createMethodFromCtx(e,"PUT"),patch:createMethodFromCtx(e,"PATCH"),delete:createMethodFromCtx(e,"DELETE"),upload:async(t,e,r)=>{try{return{data:await handleSmartUpload(o,t,e,r),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Upload failed",t?.status??500)}}},download:async(t,e,r)=>{try{return await handleSmartDownload(o,t,e,{...r,method:"POST"}),{data:void 0,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Download failed",t?.status??500)}}}};return o}function createMethodFromCtx(t,e){return async(o,r,i)=>{const n=t(),a=await async function(t,e,o){let r,i={};"POST"===t||"PUT"===t||"PATCH"===t?(r=e,i=o||{}):(i=e||{},r=void 0);let n=i.token;return!n&&globalTokenProvider&&(n=globalTokenProvider()||""),n=n||"",{token:n,apiKey:i.apiKey,body:r,params:i.params,showToasts:i.showToasts}}(e,r,i),s=a.token||n.token||"",l=a.apiKey||n.apiKey;try{return{data:await callApi(s,e,o,a.body,a.showToasts,a.params,l,n.baseUrl),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Error desconocido",t?.status??500)}}}}class DypaiClient{constructor(t){const{baseUrl:e,apiKey:o}=t,r=t.auth?.storageKey||t.storageKey;t.global&&function(t){serviceConfig={...serviceConfig,...t}}(t.global),this.auth=new AuthModule({baseUrl:e,apiKey:o,storageKey:r,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const i=function(t){const e={get:createMethod(t,"GET"),post:createMethod(t,"POST"),put:createMethod(t,"PUT"),patch:createMethod(t,"PATCH"),delete:createMethod(t,"DELETE"),upload:async(t,o,r)=>{try{return{data:await handleSmartUpload(e,t,o,r),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Upload failed",t?.status??500)}}},download:async(t,o,r)=>{try{return await handleSmartDownload(e,t,o,{...r,method:"POST"}),{data:void 0,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Download failed",t?.status??500)}}}};return e}(()=>({token:this.auth.token,apiKey:o,baseUrl:e}));if(this.auth.api=i,this.db=new DataModule(i),this.users=new UsersModule(i),this.api=createApiClient(()=>({token:this.auth.token||"",apiKey:o,baseUrl:e})),setTokenProvider(()=>this.auth.token),"undefined"!=typeof window&&t.redirects){const e=t.redirects;this.auth._?.then(()=>{if(this.auth.isPasswordRecoveryCallback&&e.passwordRecovery)this.auth.consumeCallbackType(),window.location.pathname.includes(e.passwordRecovery)||window.location.replace(e.passwordRecovery);else if(this.auth.lastError&&e.authError)window.location.pathname.includes(e.authError)||window.location.replace(e.authError);else if(this.auth.token&&e.signIn){const t=this.auth.consumeCallbackType();t&&"recovery"!==t&&"invite"!==t&&(window.location.pathname.includes(e.signIn)||window.location.replace(e.signIn))}})}configureApiService({onTokenExpired:async()=>{const t=await this.auth.refreshSession();if(t.error)throw t.error;return t.data?.token||null},onUnauthorized:()=>this.auth.handleSessionExpired()})}from(t){return this.db.from(t)}async me(){return this.auth.getUser()}}let globalConfig={};function applyConfigToAllServices(){try{const{configureApiService:t}=require("../services/ApiService");t(globalConfig)}catch(t){}}exports.DypaiClient=DypaiClient,exports.DypaiError=DypaiError,exports.PACKAGE_INFO={name:"@dypai-ai/client-sdk",version:"0.0.1",description:"Cliente JavaScript para Dypai Engine",features:["API REST autenticada","Gestión de usuarios (Admin)","Storage via endpoints personalizados"]},exports.callApi=callApi,exports.configureApiService=configureApiService,exports.configureDypaiServices=function(t){globalConfig={...globalConfig,toast:toast,...t},applyConfigToAllServices()},exports.createApiClient=createApiClient,exports.createClient=function(t,e,o){if(!t)throw new Error("createClient() requiere la URL base");return new DypaiClient({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?o:e})},exports.getGlobalConfig=function(){return{...globalConfig}},exports.reloadDypaiConfig=async function(){applyConfigToAllServices()},exports.resetGlobalConfig=function(){globalConfig={},applyConfigToAllServices()},exports.setToastFunction=function(t){customToastFunction=t},exports.setTokenProvider=setTokenProvider,exports.toast=toast,exports.toastError=(t,e)=>toast({title:t,description:e,variant:"error"}),exports.toastInfo=(t,e)=>toast({title:t,description:e,variant:"info"}),exports.toastSuccess=(t,e)=>toast({title:t,description:e,variant:"success"}),exports.toastWarning=(t,e)=>toast({title:t,description:e,variant:"warning"}),exports.useToast=function(){const t=customToastFunction||fallbackToast;return{toast:t,toastSuccess:(e,o)=>t({title:e,description:o,variant:"success"}),toastError:(e,o)=>t({title:e,description:o,variant:"error"}),toastWarning:(e,o)=>t({title:e,description:o,variant:"warning"}),toastInfo:(e,o)=>t({title:e,description:o,variant:"info"})}};
1
+ "use strict";class DypaiError extends Error{constructor(t,e=500,o,r){super(t),this.status=e,this.code=o,this.details=r,this.name="DypaiError"}}function normalizeUser(t){if(!t)return{};const e=t.user||t,o=e.app_metadata||{},r=e.user_metadata||{};return{id:e.id,email:e.email,phone:e.phone,role:o.role||r.role||e.role||null,created_at:e.created_at,updated_at:e.updated_at,confirmed_at:e.confirmed_at,last_sign_in_at:e.last_sign_in_at,app_metadata:o,user_metadata:r,roleDetails:{name:o.role||r.role||null,weight:0},appContext:{app_id:"default"}}}let _debugEnabled=!1;function _log(t,...e){"error"===t?console.error(...e):_debugEnabled&&("warn"===t?console.warn(...e):console.log(...e))}async function parseAuthError(t,e){let o={};try{o=await t.json()}catch{}const r=o.msg||o.error_description||o.message||e,i=o.error_code||o.error||o.code||void 0;return new DypaiError(r,t.status,i)}class Deferred{constructor(){this.promise=new Promise((t,e)=>{this.resolve=t,this.reject=e})}}const localStorageAdapter={getItem:t=>"undefined"==typeof window?null:window.localStorage.getItem(t),setItem:(t,e)=>{if("undefined"!=typeof window)try{window.localStorage.setItem(t,e)}catch(t){console.error("[DYPAI SDK] ❌ Error crítico guardando en localStorage (¿Quota/Permisos?):",t)}},removeItem:t=>{if("undefined"!=typeof window)try{window.localStorage.removeItem(t)}catch(t){}}};async function wrapAuthResponse(t){try{return{data:await t,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t.message||"Error de autenticación",t.status||400)}}}class AuthModule{constructor(t,e=null){this.config=t,this.t=null,this.o=null,this.i=null,this.l=null,this.h=[],this.u=null,this.p=null,this.D=!1,this.S=null,this.A=null,this.m=0,this.v=0,this.storage=t.storage||localStorageAdapter,_debugEnabled=!!t.debug;const o=t.storageKey||this.deriveStorageKey(t.apiKey);_log("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${o})`),this.STORAGE_KEY=`dypai-${o}-auth-session`,this._=this.P().then(()=>{this.o&&(this.getUser().catch(()=>{}),this.startAutoRefresh());const t=this.p;this.p=null,t?setTimeout(()=>{this.D=!0,this.T(t)},0):this.D=!0}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.k.bind(this)),window.addEventListener("focus",this.k.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(_log("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.P(!1))}))}async k(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(_log("log","[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this.P(!0),this.o&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}get user(){return this.t}get token(){return this.o}get lastError(){return this.u}consumeCallbackType(){try{const t=sessionStorage.getItem("dypai-auth-callback-type");return sessionStorage.removeItem("dypai-auth-callback-type"),sessionStorage.removeItem("dypai-auth-callback-redirect"),t}catch{return null}}get isPasswordRecoveryCallback(){try{return"PASSWORD_RECOVERY"===sessionStorage.getItem("dypai-auth-callback-redirect")}catch{return!1}}isLoggedIn(){return!(!this.o||!this.t)}onAuthStateChange(t){return _log("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.h.push(t),this._.then(()=>{const e=this.I();_log("log",`[DYPAI SDK] 📣 INITIAL_SESSION para suscriptor (Sesión activa: ${!!e})`),t("INITIAL_SESSION",e)}).catch(e=>{_log("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.h=this.h.filter(e=>e!==t)}}}}}async signInWithPassword(t){return wrapAuthResponse((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=password`,o={email:t.email||t.identifier||"",password:t.password},r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(o)});if(!r.ok)throw await parseAuthError(r,"Login failed");const i=await r.json(),n={token:i.access_token,refreshToken:i.refresh_token,expiresIn:i.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(i.expires_in||3600),user:this.$(i)};return await this.C(n),n})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return wrapAuthResponse((async()=>{const o=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,{email:r,phone:i,password:n,user_data:a,...s}=t,l={email:r,phone:i,password:n,data:{...s,...a}};e?.redirectTo?l.redirect_to=e.redirectTo:"undefined"!=typeof window&&(l.redirect_to=`${window.location.origin}/`);const c=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(l)});if(!c.ok)throw await parseAuthError(c,"Registration failed");const h=await c.json(),u=!h.access_token,d={token:h.access_token,refreshToken:h.refresh_token,expiresIn:h.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(h.expires_in||3600),user:this.$(h),confirmationRequired:u};return d.token?await this.C(d):_log("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),d})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this._,!this.o||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.l||0)-t<30&&this.i&&(_log("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>_log("warn","[DYPAI SDK] Error refreshing session in getSession:",t))),{data:{access_token:this.o,refresh_token:this.i||void 0,token_type:"bearer",user:this.t},error:null}}catch(t){return{data:null,error:new DypaiError(t.message,500)}}}async getUser(){return wrapAuthResponse((async()=>{if(!this.o)throw new DypaiError("No hay sesión activa",401);const t=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,e=await fetch(t,{headers:{Authorization:`Bearer ${this.o}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!e.ok)throw new DypaiError("Session invalid",e.status);const o=await e.json(),r=this.$(o);return this.R(r),r})())}async me(){return this.getUser()}async signInWithOAuth(t,e={}){return wrapAuthResponse((async()=>{if("undefined"==typeof window)throw new DypaiError("signInWithOAuth requiere un entorno de navegador (window no está disponible)",400);const{redirectTo:o=window.location.href,scopes:r}=e,i=this.config.baseUrl||"http://localhost:8000",n=r?.length?`&scopes=${encodeURIComponent(r.join(" "))}`:"",a=`${i}/auth/v1/authorize?provider=${t}${n}&redirect_to=${encodeURIComponent(o)}`;window.location.href=a})())}async signOut(){return wrapAuthResponse((async()=>{try{if(this.o){const t=this.config.baseUrl||"http://localhost:8000";await fetch(`${t}/auth/v1/logout`,{method:"POST",headers:{Authorization:`Bearer ${this.o}`,...this.config.apiKey&&{apikey:this.config.apiKey}}})}}finally{this.O("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return wrapAuthResponse((async()=>{const o=this.config.baseUrl||"http://localhost:8000",r={email:t};e?.redirectTo?r.redirect_to=e.redirectTo:"undefined"!=typeof window&&(r.redirect_to=`${window.location.origin}/`);const i=await fetch(`${o}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!i.ok)throw await parseAuthError(i,"Recovery failed");return await i.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${e}/auth/v1/resend`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({email:t,type:"signup"})});if(!o.ok)throw await parseAuthError(o,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.S)return _log("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.S.promise;const t=new Deferred;this.S=t;const e=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const o=await async function(t,e,o){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const r=new AbortController,i=setTimeout(()=>r.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:r.signal},async()=>(clearTimeout(i),await o()))}catch(r){if("AbortError"===r.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await o();throw r}}return await o()}(e,AuthModule.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.K());t.resolve(o)}catch(e){const o=e instanceof DypaiError?e:new DypaiError(e.message||"Error refrescando sesión",401);t.resolve({data:null,error:o})}finally{this.S=null}})(),t.promise}async K(){try{const t=await this.storage.getItem(this.STORAGE_KEY);if(t){const e=JSON.parse(t),o=e.expires_at||0,r=Math.floor(Date.now()/1e3);if(e.access_token!==this.o&&o-r>60)return _log("log","[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.o=e.access_token,this.i=e.refresh_token,this.l=e.expires_at,this.t=e.user,this.m=0,this.T("TOKEN_REFRESHED"),{data:{token:this.o,refreshToken:this.i||void 0,expiresAt:this.l||void 0,user:this.t},error:null}}if(!this.i)throw new DypaiError("No hay refresh token disponible",401);_log("log","[DYPAI SDK] 🔄 Iniciando refresco de sesión con Refresh Token...");const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=refresh_token`,o=new AbortController,r=setTimeout(()=>o.abort(),AuthModule.REFRESH_TIMEOUT_MS);let i;try{i=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({refresh_token:this.i}),signal:o.signal})}finally{clearTimeout(r)}if(!i.ok){let t={};try{t=await i.json()}catch{}const e=i.status,o=t.error||t.code||"",r=(t.error_description||t.msg||"").toLowerCase(),n="invalid_grant"===o||r.includes("invalid refresh token")||r.includes("revoked");throw e>=400&&e<500&&n?(_log("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.O(`refreshSession failed (${e}: ${o})`)):_log("warn",`[DYPAI SDK] ⚠️ Fallo en refresco (${e}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new DypaiError(t.msg||t.error_description||"Refresh session failed",e)}const n=await i.json(),a={token:n.access_token,refreshToken:n.refresh_token,expiresIn:n.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(n.expires_in||3600),user:this.$(n)};return this.m=0,await this.C(a),_log("log",`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${a.expiresAt} (en ${a.expiresIn}s). Refresh token actualizado: ${!!a.refreshToken}`),{data:a,error:null}}catch(t){this.m++,this.v=Date.now();const e=t instanceof DOMException&&"AbortError"===t.name,o=e?new DypaiError("Refresh token timeout (servidor no responde)",408):t instanceof DypaiError?t:new DypaiError(t.message||"Error refrescando sesión",401),r=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.m),AuthModule.MAX_RETRY_MS);return e?_log("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${AuthModule.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${r}ms (intento ${this.m})`):_log("error","[DYPAI SDK] ❌ Refresh falló:",o.message,`(status: ${o.status}). Backoff: ${r}ms (intento ${this.m})`),this.m>=AuthModule.MAX_REFRESH_FAILURES&&(_log("error",`[DYPAI SDK] 🗑️ ${this.m} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.O(`${this.m} consecutive refresh failures`),this.m=0),{data:null,error:o}}}async signInWithOtp(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${e}/auth/v1/otp`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(t)});if(!o.ok){const t=await o.json();throw new DypaiError(t.detail||"OTP request failed",o.status)}return await o.json()})())}async verifyOtp(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${e}/auth/v1/verify`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(t)});if(!o.ok){const t=await o.json();throw new DypaiError(t.detail||"OTP verification failed",o.status)}const r=await o.json(),i={token:r.access_token,refreshToken:r.refresh_token,expiresIn:r.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(r.expires_in||3600),user:this.$(r)};return i.token&&await this.C(i),i})())}async updateUser(t){return wrapAuthResponse((async()=>{if(!this.o)throw new DypaiError("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,o=await fetch(e,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.o}`,...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(t)});if(!o.ok){const t=await o.json();throw new DypaiError(t.detail||"Update user failed",o.status)}const r=await o.json(),i=this.$(r);return this.R(i),i})())}async setPassword(t){return this.updateUser({password:t})}startAutoRefresh(){this.stopAutoRefresh(),_log("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.U(),this.A=setInterval(()=>this.U(),AuthModule.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.A&&(_log("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.A),this.A=null)}async U(){try{const t=Math.floor(Date.now()/1e3),e=this.l||0;if(!this.i||!e)return;if(1e3*(e-t)>AuthModule.AUTO_REFRESH_TICK_THRESHOLD*AuthModule.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.m>0){const t=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.m),AuthModule.MAX_RETRY_MS);if(Date.now()-this.v<t)return}_log("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){_log("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}$(t){return t?normalizeUser(t):(_log("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}async C(t){this.u=null,t.token&&(this.o=t.token,this.i=t.refreshToken||null,this.l=t.expiresAt||null,await this.R(t.user,t.token,t.refreshToken,t.expiresAt),_log("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.l}, has_refresh: ${!!this.i})`),this.startAutoRefresh())}async R(t,e,o,r){this.t=t,e&&(this.o=e),void 0!==o&&(this.i=o||null),void 0!==r&&(this.l=r||null);try{if(this.o&&this.t){const t={access_token:this.o,refresh_token:this.i,expires_at:this.l,user:this.t};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t))}else _log("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){_log("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}if(this.D){const t=e?"SIGNED_IN":"USER_UPDATED";this.T(t)}}async O(t="unknown"){_log("log",`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.o=null,this.i=null,this.t=null,this.l=null,this.stopAutoRefresh();try{await this.storage.removeItem(this.STORAGE_KEY),_log("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){_log("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.T("SIGNED_OUT")}async P(t=!0){_log("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.Y())return void _log("log","[DYPAI SDK] ✅ Sesión establecida desde callback URL. Saltando recuperación de localStorage.");const e=await this.storage.getItem(this.STORAGE_KEY);if(e){let t;_log("log","[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");try{t=JSON.parse(e)}catch{return console.warn("[DYPAI SDK] ⚠️ Sesión en storage corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!t||"object"!=typeof t||"string"!=typeof t.access_token)return console.warn("[DYPAI SDK] ⚠️ Sesión en storage con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);this.o=t.access_token,this.i="string"==typeof t.refresh_token?t.refresh_token:null,this.l="number"==typeof t.expires_at?t.expires_at:null,this.t=t.user&&"object"==typeof t.user?t.user:null}else{_log("log","[DYPAI SDK] ℹ️ No hay sesión consolidada. Buscando llaves antiguas para migración...");const t=this.STORAGE_KEY.replace("dypai-","").replace("-auth-session",""),e=await this.storage.getItem(`dypai-auth-token-${t}`),o=await this.storage.getItem(`dypai-auth-user-${t}`);if(!e||!o)return void _log("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let r;_log("log","[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");try{r=JSON.parse(o)}catch{return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage corrupto (JSON inválido). Ignorando migración...")}if(!r||"object"!=typeof r)return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage con formato inválido. Ignorando migración...");this.o=e,this.t=r;const i=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),n=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.i=i,this.l=n?parseInt(n,10):null,await this.R(r,this.o||void 0,this.i||void 0,this.l||void 0);const a=[`dypai-auth-token-${t}`,`dypai-auth-refresh-token-${t}`,`dypai-auth-user-${t}`,`dypai-auth-expires-at-${t}`];for(const t of a)try{await this.storage.removeItem(t)}catch(t){}_log("log","[DYPAI SDK] ✨ Migración completada con éxito.")}}const o=Math.floor(Date.now()/1e3),r=this.l&&this.l<=o;if(_log("log",`[DYPAI SDK] 🕒 Token expira en: ${this.l}. Ahora es: ${o}. Diferencia: ${(this.l||0)-o}s`),r&&this.i&&t){_log("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void _log("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.l&&_log("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.p="SIGNED_IN"}catch(t){_log("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async Y(){if("undefined"==typeof window)return!1;try{const t=window.location.hash.substring(1);if(!t)return!1;const e=new URLSearchParams(t);if(e.get("error")){const t=e.get("error_code")||"unknown",o=e.get("error_description")?.replace(/\+/g," ")||"Authentication error";return _log("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${o}`),this.u={code:t,message:o},window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.T("AUTH_ERROR"),!0}const o=e.get("access_token");if(!o)return!1;const r=e.get("refresh_token"),i=e.get("type"),n=parseInt(e.get("expires_in")||"3600",10);_log("log",`[DYPAI SDK] 🔗 Auth callback detectado en URL (type: ${i||"unknown"}). Procesando...`);try{i&&sessionStorage.setItem("dypai-auth-callback-type",i),"recovery"!==i&&"invite"!==i||sessionStorage.setItem("dypai-auth-callback-redirect","PASSWORD_RECOVERY")}catch{}window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.o=o,this.i=r||null,this.l=Math.floor(Date.now()/1e3)+n;const a=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${a}/auth/v1/user`,{headers:{Authorization:`Bearer ${o}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!s.ok)return _log("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${s.status}). Limpiando...`),this.o=null,this.i=null,this.l=null,this.u={code:"token_invalid",message:"The authentication link is invalid or has expired"},this.T("AUTH_ERROR"),!0;const l=await s.json(),c=this.$(l),h={token:o,refreshToken:r||void 0,expiresIn:n,expiresAt:this.l,user:c};return await this.C(h),this.p="recovery"===i||"invite"===i?"PASSWORD_RECOVERY":"SIGNED_IN",_log("log",`[DYPAI SDK] 🔑 Callback type=${i}. Pending event: ${this.p}`),!0}catch(t){return _log("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}I(){return this.o&&this.t?{access_token:this.o,refresh_token:this.i||void 0,expires_at:this.l||void 0,token_type:"bearer",user:this.t}:null}T(t="USER_UPDATED"){const e=this.I();_log("log",`[DYPAI SDK] 📢 Notificando a ${this.h.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.h.forEach(o=>o(t,e))}handleSessionExpired(){this.O("handleSessionExpired called (likely 401 from API)")}}AuthModule.REFRESH_TIMEOUT_MS=15e3,AuthModule.MAX_REFRESH_FAILURES=5,AuthModule.AUTO_REFRESH_TICK_DURATION_MS=3e4,AuthModule.AUTO_REFRESH_TICK_THRESHOLD=3,AuthModule.RETRY_BASE_MS=200,AuthModule.MAX_RETRY_MS=3e4,AuthModule.LOCK_ACQUIRE_TIMEOUT_MS=5e3;class DataModule{constructor(t){this.api=t}from(t){return new QueryBuilder(t,this.api)}}class QueryBuilder{constructor(t,e){this.table=t,this.api=e}async select(t={}){return this.api.get(this.table,{params:t})}async insert(t){return this.api.post(this.table,t)}async update(t,e){return this.api.patch(`${this.table}/${t}`,e)}async delete(t){return this.api.delete(`${this.table}/${t}`)}}class UsersModule{constructor(t){this.api=t}async list(t={}){const e=await this.api.get("admin/users",{params:t});return e.data?.users&&(e.data.users=e.data.users.map(normalizeUser)),e}async create(t){const e=await this.api.post("admin/users",t);return e.data&&(e.data=normalizeUser(e.data)),e}async update(t,e){const o=await this.api.put(`admin/users/${t}`,e);return o.data&&(o.data=normalizeUser(o.data)),o}async delete(t){return this.api.delete(`admin/users/${t}`)}}let customToastFunction=null;const fallbackToast=t=>{const{title:e,description:o,variant:r="default"}=t,i=`${"error"===r?"❌":"success"===r?"✅":"warning"===r?"⚠️":"info"===r?"ℹ️":"📢"} ${e}${o?`: ${o}`:""}`;"error"===r?console.error(i):"warning"===r&&console.warn(i)},toast=t=>(customToastFunction||fallbackToast)(t);let serviceConfig={},globalTokenProvider=null;function setTokenProvider(t){globalTokenProvider=t}function configureApiService(t){serviceConfig={...serviceConfig,...t}}const pendingRequests=new Map;function getCompleteConfig(){let t=serviceConfig;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...serviceConfig}}catch(t){}return t}async function callApi(t,e,o,r,i,n,a,s,l){const c=getCompleteConfig(),h=s||null;if(!h&&!o.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof o||!o.trim())throw new Error("Endpoint debe ser un string válido");let u;if(o.startsWith("http"))u=o;else{const t=h.replace(/\/+$/,"");u=o.startsWith("/")?t+o:t+"/api/v0/"+o}if(n&&Object.keys(n).length>0){const t=new URLSearchParams,e=(o,r)=>{null!=r&&(Array.isArray(r)?r.forEach((t,r)=>e(`${o}[${r}]`,t)):"object"==typeof r?Object.entries(r).forEach(([t,r])=>e(`${o}[${t}]`,r)):t.append(o,String(r)))};Object.entries(n).forEach(([t,o])=>e(t,o));const o=t.toString();o&&(u+=`?${o}`)}const d="GET"===e?`${e}:${u}:${JSON.stringify(r)}`:null;if(d&&pendingRequests.has(d))return pendingRequests.get(d);const p=r instanceof FormData,f={...c.headers||{},...p?{}:{"Content-Type":"application/json"},...t&&{Authorization:`Bearer ${t}`},...a&&{"x-api-key":a}},y={method:e,headers:f,credentials:"include"};r&&"GET"!==e&&"DELETE"!==e&&(y.body=p?r:JSON.stringify(r));const w=c.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!w)throw new Error("Fetch no disponible.");const g=(async()=>{const t=getCompleteConfig().toast||toast,h=(void 0!==i?i:!1!==c.showToasts)&&t;try{const d=await w(u,y);if(!d.ok){let u,p="Error en la petición";try{const t=await d.text();try{const e=JSON.parse(t);u=e,p=e.message||e.msg||e.error_description||e.error||p}catch{t.length<200&&(p=t)}}catch{}if(401===d.status&&!l&&c.onTokenExpired)try{const t=await c.onTokenExpired();if(t)return callApi(t,e,o,r,i,n,a,s,!0)}catch(t){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",t)}throw h&&t({title:"Error",description:p,variant:"error"}),new DypaiError(p,d.status,void 0,u)}!h||"POST"!==e&&"PUT"!==e&&"PATCH"!==e&&"DELETE"!==e||t({title:"Éxito",description:"Operación completada",variant:"success"});const p=d.headers.get("content-type")||"";return p.includes("application/pdf")||p.includes("image/")||p.includes("audio/")||p.includes("video/")||p.includes("application/octet-stream")||p.includes("application/zip")||p.includes("application/vnd.openxmlformats-officedocument")?await d.blob():p.includes("application/json")?await d.json():await d.text()}finally{d&&pendingRequests.delete(d)}})();return d&&pendingRequests.set(d,g),g}async function handleSmartDownload(t,e,o,r){const i=r?.method||(o?"POST":"GET"),{data:n,error:a}=await("GET"===i?t.get(e,{params:r?.params}):t.post(e,o,{params:r?.params}));if(a)throw a;if(n instanceof Blob){const t=window.URL.createObjectURL(n),e=document.createElement("a");e.href=t,e.download=r?.fileName||"archivo-descargado",document.body.appendChild(e),e.click(),window.URL.revokeObjectURL(t),document.body.removeChild(e)}else if(n&&"object"==typeof n&&("url"in n||"signed_url"in n||"signedUrl"in n)){const t=n.url||n.signed_url||n.signedUrl;if("string"==typeof t){const e=new URL(t).searchParams.get("response-content-disposition");let o;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(o=t[1].replace(/"/g,""))}const i=r?.fileName||o;if(i){const e=document.createElement("a");e.href=t,e.download=i,document.body.appendChild(e),e.click(),document.body.removeChild(e)}else window.open(t,"_blank")}}}async function handleSmartUpload(t,e,o,r){const{data:i,error:n}=await t.post(e,{file_path:o.name,content_type:o.type||"application/octet-stream",size_bytes:o.size,confirm:!1,client_upload:!0,...r?.params||{}});if(n)throw n;const a=function(t){if(!t||"object"!=typeof t)return null;const e=t.upload_url||t.uploadUrl||t.url?t:null;if(e?.upload_url)return normalizeUploadPayload(e);const o=[t.data,t.result,t.output,t.payload].filter(Boolean);for(const t of o)if(t&&(t.upload_url||t.uploadUrl||t.url))return normalizeUploadPayload(t);const r=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of r){const e=Array.isArray(t)?t:Object.values(t);for(const t of e)if(t&&(t.upload_url||t.uploadUrl||t.url))return normalizeUploadPayload(t)}return null}(i);if(!a?.upload_url)throw new DypaiError("The workflow did not return a valid upload URL (missing storage upload node?)",400);const{upload_url:s,method:l="PUT",headers:c={},file_path:h,storage_path:u}=a;r?.onProgress&&r.onProgress(10);const d=await fetch(s,{method:l,headers:{"Content-Type":o.type||"application/octet-stream",...c},body:o});if(!d.ok)throw new DypaiError("Direct upload to cloud storage failed",d.status);r?.onProgress&&r.onProgress(90);const p=r?.confirmEndpoint||e,{data:f,error:y}=await t.post(p,{...r?.params,bucket:a.bucket||r?.params?.bucket,file_path:h,storage_path:u,filename:o.name,content_type:o.type||"application/octet-stream",size_bytes:o.size,confirm:!0,client_upload:!0});if(y)throw y;return r?.onProgress&&r.onProgress(100),f}function normalizeUploadPayload(t){return t&&"object"==typeof t?{...t,upload_url:t.upload_url||t.uploadUrl||t.url,storage_path:t.storage_path||t.storagePath}:null}function createMethod(t,e){const o="POST"===e||"PUT"===e||"PATCH"===e;return async(r,i,n)=>{const a=t();let s,l={};o?(s=i,l=n||{}):(l=i||{},s=void 0);const c=l.token||a.token||(globalTokenProvider?globalTokenProvider():"")||"",h=l.apiKey||a.apiKey;try{return{data:await callApi(c,e,r,s,l.showToasts,l.params,h,a.baseUrl),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Error desconocido",t?.status??500)}}}}function createApiClient(t){const e=()=>"function"==typeof t?t():t,o={get:createMethodFromCtx(e,"GET"),post:createMethodFromCtx(e,"POST"),put:createMethodFromCtx(e,"PUT"),patch:createMethodFromCtx(e,"PATCH"),delete:createMethodFromCtx(e,"DELETE"),upload:async(t,e,r)=>{try{return{data:await handleSmartUpload(o,t,e,r),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Upload failed",t?.status??500)}}},download:async(t,e,r)=>{try{return await handleSmartDownload(o,t,e,{...r,method:"POST"}),{data:void 0,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Download failed",t?.status??500)}}}};return o}function createMethodFromCtx(t,e){return async(o,r,i)=>{const n=t(),a=await async function(t,e,o){let r,i={};"POST"===t||"PUT"===t||"PATCH"===t?(r=e,i=o||{}):(i=e||{},r=void 0);let n=i.token;return!n&&globalTokenProvider&&(n=globalTokenProvider()||""),n=n||"",{token:n,apiKey:i.apiKey,body:r,params:i.params,showToasts:i.showToasts}}(e,r,i),s=a.token||n.token||"",l=a.apiKey||n.apiKey;try{return{data:await callApi(s,e,o,a.body,a.showToasts,a.params,l,n.baseUrl),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Error desconocido",t?.status??500)}}}}class DypaiClient{constructor(t){const{baseUrl:e,apiKey:o}=t,r=t.auth?.storageKey||t.storageKey;t.global&&function(t){serviceConfig={...serviceConfig,...t}}(t.global),this.auth=new AuthModule({baseUrl:e,apiKey:o,storageKey:r,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const i=function(t){const e={get:createMethod(t,"GET"),post:createMethod(t,"POST"),put:createMethod(t,"PUT"),patch:createMethod(t,"PATCH"),delete:createMethod(t,"DELETE"),upload:async(t,o,r)=>{try{return{data:await handleSmartUpload(e,t,o,r),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Upload failed",t?.status??500)}}},download:async(t,o,r)=>{try{return await handleSmartDownload(e,t,o,{...r,method:"POST"}),{data:void 0,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Download failed",t?.status??500)}}}};return e}(()=>({token:this.auth.token,apiKey:o,baseUrl:e}));if(this.auth.api=i,this.db=new DataModule(i),this.users=new UsersModule(i),this.api=createApiClient(()=>({token:this.auth.token||"",apiKey:o,baseUrl:e})),setTokenProvider(()=>this.auth.token),"undefined"!=typeof window&&t.redirects){const e=t.redirects;this.auth._?.then(()=>{if(this.auth.isPasswordRecoveryCallback&&e.passwordRecovery)this.auth.consumeCallbackType(),window.location.pathname.includes(e.passwordRecovery)||window.location.replace(e.passwordRecovery);else if(this.auth.lastError&&e.authError)window.location.pathname.includes(e.authError)||window.location.replace(e.authError);else if(this.auth.token&&e.signIn){const t=this.auth.consumeCallbackType();t&&"recovery"!==t&&"invite"!==t&&(window.location.pathname.includes(e.signIn)||window.location.replace(e.signIn))}})}configureApiService({onTokenExpired:async()=>{const t=await this.auth.refreshSession();if(t.error)throw t.error;return t.data?.token||null},onUnauthorized:()=>this.auth.handleSessionExpired()})}from(t){return this.db.from(t)}async me(){return this.auth.getUser()}}let globalConfig={};function applyConfigToAllServices(){try{const{configureApiService:t}=require("../services/ApiService");t(globalConfig)}catch(t){}}exports.DypaiClient=DypaiClient,exports.DypaiError=DypaiError,exports.PACKAGE_INFO={name:"@dypai-ai/client-sdk",version:"1.0.0",description:"Official JavaScript/TypeScript SDK for DYPAI",features:["Authentication (email, OAuth, OTP)","Database CRUD","Custom endpoints (typed API)","File upload/download (Smart Upload)","User management (admin)","React hooks (useAuth, useEndpoint, useAction, useUpload)"]},exports.callApi=callApi,exports.configureApiService=configureApiService,exports.configureDypaiServices=function(t){globalConfig={...globalConfig,toast:toast,...t},applyConfigToAllServices()},exports.createApiClient=createApiClient,exports.createClient=function(t,e,o){if(!t)throw new Error("createClient() requiere la URL base");return new DypaiClient({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?o:e})},exports.getGlobalConfig=function(){return{...globalConfig}},exports.reloadDypaiConfig=async function(){applyConfigToAllServices()},exports.resetGlobalConfig=function(){globalConfig={},applyConfigToAllServices()},exports.setToastFunction=function(t){customToastFunction=t},exports.setTokenProvider=setTokenProvider,exports.toast=toast,exports.toastError=(t,e)=>toast({title:t,description:e,variant:"error"}),exports.toastInfo=(t,e)=>toast({title:t,description:e,variant:"info"}),exports.toastSuccess=(t,e)=>toast({title:t,description:e,variant:"success"}),exports.toastWarning=(t,e)=>toast({title:t,description:e,variant:"warning"}),exports.useToast=function(){const t=customToastFunction||fallbackToast;return{toast:t,toastSuccess:(e,o)=>t({title:e,description:o,variant:"success"}),toastError:(e,o)=>t({title:e,description:o,variant:"error"}),toastWarning:(e,o)=>t({title:e,description:o,variant:"warning"}),toastInfo:(e,o)=>t({title:e,description:o,variant:"info"})}};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dypai-ai/client-sdk",
3
- "version": "0.0.37",
4
- "description": "Cliente JavaScript para Dypai Engine",
3
+ "version": "1.0.0",
4
+ "description": "Official JavaScript/TypeScript SDK for DYPAI — backend-as-a-service with visual workflows, AI agents, and MCP.",
5
5
  "type": "module",
6
6
  "private": false,
7
7
  "main": "dist/index.js",
@@ -20,7 +20,8 @@
20
20
  }
21
21
  },
22
22
  "files": [
23
- "dist"
23
+ "dist",
24
+ "CHANGELOG.md"
24
25
  ],
25
26
  "scripts": {
26
27
  "build": "rollup -c",
@@ -36,18 +37,30 @@
36
37
  "docs": "typedoc",
37
38
  "docs:watch": "typedoc --watch",
38
39
  "clean": "rimraf dist docs",
39
- "postbuild_DISABLED": "node scripts/obfuscate-dts.cjs",
40
- "prepublishOnly": "npm run clean && npm run build && npm run type-check && npm run docs && node -e \"if(require('fs').existsSync('README.md.backup')) require('fs').unlinkSync('README.md.backup')\" && node -e \"require('fs').copyFileSync('README.md', 'README.md.backup')\" && node -e \"require('fs').writeFileSync('README.md', '# @dypai-ai/client-sdk\\n\\nCliente JavaScript para Dypai Engine.\\n\\nContacta al equipo de desarrollo para más información.')\"",
41
- "postpublish": "node -e \"if(require('fs').existsSync('README.md.backup')) { require('fs').copyFileSync('README.md.backup', 'README.md'); require('fs').unlinkSync('README.md.backup'); }\"",
40
+ "prepublishOnly": "npm run clean && npm run build && npm run type-check && npm run docs",
42
41
  "publish:public": "npm publish --access public"
43
42
  },
44
43
  "keywords": [
44
+ "dypai",
45
+ "sdk",
46
+ "backend-as-a-service",
47
+ "baas",
45
48
  "api",
46
- "http",
47
- "client",
48
- "service"
49
+ "auth",
50
+ "database",
51
+ "workflows",
52
+ "react",
53
+ "typescript"
49
54
  ],
50
- "author": "DYPAI Team",
55
+ "author": "DYPAI Solutions <dev@dypai.ai>",
56
+ "homepage": "https://dypai.ai",
57
+ "repository": {
58
+ "type": "git",
59
+ "url": "https://github.com/DYPAI-SOLUTIONS/client-sdk.git"
60
+ },
61
+ "bugs": {
62
+ "url": "https://github.com/DYPAI-SOLUTIONS/client-sdk/issues"
63
+ },
51
64
  "license": "MIT",
52
65
  "peerDependencies": {
53
66
  "react": ">=16.8.0",
@@ -66,7 +79,9 @@
66
79
  "@rollup/plugin-node-resolve": "^15.0.0",
67
80
  "@rollup/plugin-terser": "^0.4.4",
68
81
  "@rollup/plugin-typescript": "^11.0.0",
69
- "@types/jest": "^29.0.0",
82
+ "@testing-library/jest-dom": "^6.9.1",
83
+ "@testing-library/react": "^16.3.2",
84
+ "@types/jest": "^29.5.14",
70
85
  "@types/react": "^19.2.14",
71
86
  "@types/react-dom": "^19.2.3",
72
87
  "@typescript-eslint/eslint-plugin": "^6.0.0",
@@ -76,11 +91,14 @@
76
91
  "eslint-plugin-react": "^7.0.0",
77
92
  "eslint-plugin-react-hooks": "^4.0.0",
78
93
  "jest": "^29.0.0",
94
+ "jest-environment-jsdom": "^30.3.0",
79
95
  "react": "^19.2.4",
80
96
  "react-dom": "^19.2.4",
81
97
  "rimraf": "^5.0.0",
82
98
  "rollup": "^4.0.0",
83
99
  "rollup-plugin-dts": "^6.0.0",
100
+ "ts-jest": "^29.4.6",
101
+ "ts-node": "^10.9.2",
84
102
  "tslib": "^2.8.1",
85
103
  "typedoc": "^0.28.18",
86
104
  "typescript": "^5.0.0",
package/README.md.backup DELETED
@@ -1,5 +0,0 @@
1
- # @dypai-ai/client-sdk
2
-
3
- Cliente JavaScript para Dypai Engine.
4
-
5
- Contacta al equipo de desarrollo para más información.