@dypai-ai/client-sdk 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @dypai-ai/client-sdk
2
+
3
+ Cliente JavaScript para Dypai Engine.
4
+
5
+ Contacta al equipo de desarrollo para más información.
@@ -0,0 +1,5 @@
1
+ # @dypai-ai/client-sdk
2
+
3
+ Cliente JavaScript para Dypai Engine.
4
+
5
+ Contacta al equipo de desarrollo para más información.
@@ -0,0 +1,958 @@
1
+ /**
2
+ * Tipos para el paquete @dypai/client-sdk
3
+ */
4
+ type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
5
+ interface ApiOptions {
6
+ token: string;
7
+ apiKey?: string;
8
+ body?: any;
9
+ params?: Record<string, any>;
10
+ showToasts?: boolean;
11
+ }
12
+ interface ApiContextType {
13
+ token: string;
14
+ apiKey?: string;
15
+ baseUrl?: string;
16
+ }
17
+ interface SupportedStorage {
18
+ getItem(key: string): string | null | Promise<string | null>;
19
+ setItem(key: string, value: string): void | Promise<void>;
20
+ removeItem(key: string): void | Promise<void>;
21
+ }
22
+ interface AuthClientOptions {
23
+ storage?: SupportedStorage;
24
+ storageKey?: string;
25
+ autoRefreshToken?: boolean;
26
+ persistSession?: boolean;
27
+ }
28
+ /**
29
+ * Error estandarizado de Dypai.
30
+ * Permite manejar errores de forma consistente (ej: if (error.status === 401))
31
+ */
32
+ declare class DypaiError extends Error {
33
+ status: number;
34
+ code?: string | undefined;
35
+ details?: any | undefined;
36
+ constructor(message: string, status?: number, code?: string | undefined, details?: any | undefined);
37
+ }
38
+ /**
39
+ * Respuesta estandarizada del SDK (Estilo Supabase)
40
+ * @typeParam T - Tipo de los datos en caso de éxito
41
+ */
42
+ type DypaiResponse<T> = {
43
+ data: T;
44
+ error: null;
45
+ } | {
46
+ data: null;
47
+ error: DypaiError;
48
+ };
49
+ /**
50
+ * Configuración global para el cliente API.
51
+ */
52
+ interface GlobalClientOptions {
53
+ /**
54
+ * Implementación personalizada de fetch.
55
+ * Útil para Node.js, tests, o para mockear peticiones.
56
+ */
57
+ fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
58
+ /**
59
+ * Headers globales que se enviarán en todas las peticiones.
60
+ */
61
+ headers?: Record<string, string>;
62
+ }
63
+ /**
64
+ * Define la estructura de un endpoint tipado.
65
+ * Usado para inferir tipos de request/response automáticamente.
66
+ */
67
+ interface EndpointDefinition {
68
+ /** Tipo de respuesta del endpoint */
69
+ response?: unknown;
70
+ /** Tipo del body para POST/PUT/PATCH */
71
+ body?: unknown;
72
+ /** Tipo de los query params */
73
+ params?: Record<string, unknown>;
74
+ }
75
+ /**
76
+ * Mapa de endpoints tipados.
77
+ * El usuario define su API así:
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * interface MyApi extends EndpointMap {
82
+ * 'get-pacientes': { response: Paciente[], params: { active?: boolean } };
83
+ * 'create-cita': { body: CitaInput, response: Cita };
84
+ * 'update-paciente': { body: Partial<Paciente>, response: Paciente, params: { id: string } };
85
+ * }
86
+ * ```
87
+ */
88
+ interface EndpointMap {
89
+ [endpoint: string]: EndpointDefinition;
90
+ }
91
+ /**
92
+ * Extrae el tipo de respuesta de un endpoint
93
+ */
94
+ type EndpointResponse<TMap extends EndpointMap, TEndpoint extends keyof TMap> = TMap[TEndpoint] extends {
95
+ response: infer R;
96
+ } ? R : any;
97
+ /**
98
+ * Extrae el tipo del body de un endpoint
99
+ */
100
+ type EndpointBody<TMap extends EndpointMap, TEndpoint extends keyof TMap> = TMap[TEndpoint] extends {
101
+ body: infer B;
102
+ } ? B : any;
103
+ /**
104
+ * Extrae el tipo de los params de un endpoint
105
+ */
106
+ type EndpointParams<TMap extends EndpointMap, TEndpoint extends keyof TMap> = TMap[TEndpoint] extends {
107
+ params: infer P;
108
+ } ? P : Record<string, any>;
109
+ /**
110
+ * Opciones para llamadas GET tipadas
111
+ */
112
+ interface TypedGetOptions<TParams> {
113
+ params?: TParams;
114
+ showToasts?: boolean;
115
+ }
116
+ /**
117
+ * Opciones para llamadas POST/PUT/PATCH tipadas
118
+ */
119
+ interface TypedMutationOptions<TParams> {
120
+ params?: TParams;
121
+ showToasts?: boolean;
122
+ }
123
+ /**
124
+ * Cliente API tipado con inferencia de tipos para endpoints.
125
+ * Permite autocompletado para endpoints conocidos, pero acepta strings arbitrarios (fallback a any).
126
+ */
127
+ interface TypedApiClient<TMap extends EndpointMap = EndpointMap> {
128
+ /**
129
+ * GET request tipado
130
+ */
131
+ get<TEndpoint extends (keyof TMap & string) | (string & {})>(endpoint: TEndpoint, options?: TypedGetOptions<TEndpoint extends keyof TMap ? EndpointParams<TMap, TEndpoint> : Record<string, any>>): Promise<TEndpoint extends keyof TMap ? EndpointResponse<TMap, TEndpoint> : any>;
132
+ /**
133
+ * POST request tipado
134
+ */
135
+ post<TEndpoint extends (keyof TMap & string) | (string & {})>(endpoint: TEndpoint, body: TEndpoint extends keyof TMap ? EndpointBody<TMap, TEndpoint> : any, options?: TypedMutationOptions<TEndpoint extends keyof TMap ? EndpointParams<TMap, TEndpoint> : Record<string, any>>): Promise<TEndpoint extends keyof TMap ? EndpointResponse<TMap, TEndpoint> : any>;
136
+ /**
137
+ * PUT request tipado
138
+ */
139
+ put<TEndpoint extends (keyof TMap & string) | (string & {})>(endpoint: TEndpoint, body: TEndpoint extends keyof TMap ? EndpointBody<TMap, TEndpoint> : any, options?: TypedMutationOptions<TEndpoint extends keyof TMap ? EndpointParams<TMap, TEndpoint> : Record<string, any>>): Promise<TEndpoint extends keyof TMap ? EndpointResponse<TMap, TEndpoint> : any>;
140
+ /**
141
+ * PATCH request tipado
142
+ */
143
+ patch<TEndpoint extends (keyof TMap & string) | (string & {})>(endpoint: TEndpoint, body?: TEndpoint extends keyof TMap ? Partial<EndpointBody<TMap, TEndpoint>> : any, options?: TypedMutationOptions<TEndpoint extends keyof TMap ? EndpointParams<TMap, TEndpoint> : Record<string, any>>): Promise<TEndpoint extends keyof TMap ? EndpointResponse<TMap, TEndpoint> : any>;
144
+ /**
145
+ * DELETE request tipado
146
+ */
147
+ delete<TEndpoint extends (keyof TMap & string) | (string & {})>(endpoint: TEndpoint, options?: TypedGetOptions<TEndpoint extends keyof TMap ? EndpointParams<TMap, TEndpoint> : Record<string, any>>): Promise<TEndpoint extends keyof TMap ? EndpointResponse<TMap, TEndpoint> : any>;
148
+ }
149
+ /**
150
+ * Tipo para el cliente API sin tipado (legacy/fallback)
151
+ */
152
+ interface ApiClient {
153
+ get: (endpoint: string, options?: {
154
+ params?: Record<string, any>;
155
+ [key: string]: any;
156
+ }) => Promise<any>;
157
+ post: (endpoint: string, body: any, options?: {
158
+ params?: Record<string, any>;
159
+ [key: string]: any;
160
+ }) => Promise<any>;
161
+ put: (endpoint: string, body: any, options?: {
162
+ params?: Record<string, any>;
163
+ [key: string]: any;
164
+ }) => Promise<any>;
165
+ patch: (endpoint: string, body?: any, options?: {
166
+ params?: Record<string, any>;
167
+ [key: string]: any;
168
+ }) => Promise<any>;
169
+ delete: (endpoint: string, options?: {
170
+ params?: Record<string, any>;
171
+ [key: string]: any;
172
+ }) => Promise<any>;
173
+ }
174
+ interface AppConfig {
175
+ DYPAI_API_URL: string;
176
+ DYPAI_API_KEY?: string;
177
+ VITE_DYPAI_DEV_USERNAME?: string;
178
+ VITE_DYPAI_DEV_PASSWORD?: string;
179
+ [key: string]: any;
180
+ }
181
+ interface UserData {
182
+ token: string;
183
+ userId: string;
184
+ username: string;
185
+ email: string;
186
+ }
187
+ interface AppData {
188
+ config: AppConfig;
189
+ userData: UserData;
190
+ }
191
+ interface ToastFunction {
192
+ (props: {
193
+ title?: string;
194
+ description?: string;
195
+ variant?: 'default' | 'success' | 'error' | 'warning' | 'info';
196
+ }): void;
197
+ }
198
+ interface ApiServiceConfig {
199
+ toast?: ToastFunction;
200
+ showToasts?: boolean;
201
+ onUnauthorized?: () => void;
202
+ onTokenExpired?: () => Promise<string | null>;
203
+ }
204
+
205
+ declare class DataModule<TDatabase = any> {
206
+ private api;
207
+ constructor(api: ApiClient);
208
+ /**
209
+ * Inicia una operación sobre una tabla/colección específica.
210
+ * En el backend esto mapea a /api/v0/{table}
211
+ * @param table Nombre de la tabla o workflow
212
+ */
213
+ from<TTableName extends keyof TDatabase & string>(table: TTableName): QueryBuilder;
214
+ }
215
+ declare class QueryBuilder {
216
+ private table;
217
+ private api;
218
+ constructor(table: string, api: ApiClient);
219
+ select(filters?: Record<string, any>): Promise<any>;
220
+ insert(data: Record<string, any>): Promise<any>;
221
+ update(id: string | number, data: Record<string, any>): Promise<any>;
222
+ delete(id: string | number): Promise<any>;
223
+ }
224
+
225
+ interface AppRole {
226
+ name: string;
227
+ weight: number;
228
+ description?: string;
229
+ }
230
+ interface User {
231
+ id: string;
232
+ email: string;
233
+ name?: string;
234
+ username?: string;
235
+ role?: string;
236
+ _role_details?: AppRole;
237
+ _app_context?: {
238
+ app_id: string;
239
+ };
240
+ created_at?: string;
241
+ updated_at?: string;
242
+ app_metadata?: any;
243
+ user_metadata?: any;
244
+ [key: string]: any;
245
+ }
246
+ interface AppUserResponse {
247
+ user: User;
248
+ role: AppRole;
249
+ app_id: string;
250
+ }
251
+ interface AuthResponse {
252
+ user: User;
253
+ token: string;
254
+ refreshToken?: string;
255
+ expiresIn?: number;
256
+ expiresAt?: number;
257
+ }
258
+ interface LoginCredentials {
259
+ email?: string;
260
+ phone?: string;
261
+ identifier?: string;
262
+ password: string;
263
+ }
264
+ interface RegisterData {
265
+ email?: string;
266
+ phone?: string;
267
+ password: string;
268
+ user_data?: Record<string, any>;
269
+ [key: string]: any;
270
+ }
271
+ interface OAuthOptions {
272
+ redirectTo?: string;
273
+ scopes?: string[];
274
+ }
275
+ interface JoinResponse {
276
+ message: string;
277
+ joined: boolean;
278
+ }
279
+ interface PasswordRecoveryRequest {
280
+ email: string;
281
+ }
282
+ interface PasswordResetRequest {
283
+ password: string;
284
+ }
285
+ interface ResetPasswordWithTokenRequest {
286
+ token: string;
287
+ password: string;
288
+ }
289
+ interface PasswordRecoveryResponse {
290
+ message: string;
291
+ }
292
+ interface PasswordResetResponse {
293
+ message: string;
294
+ }
295
+ interface OTPRequest {
296
+ email?: string;
297
+ phone?: string;
298
+ create_user?: boolean;
299
+ data?: Record<string, any>;
300
+ }
301
+ interface VerifyOTPRequest {
302
+ email?: string;
303
+ phone?: string;
304
+ token: string;
305
+ type?: 'sms' | 'signup' | 'magiclink' | 'recovery' | 'invite';
306
+ }
307
+ interface UpdateUserAttributes {
308
+ email?: string;
309
+ password?: string;
310
+ data?: Record<string, any>;
311
+ }
312
+ /**
313
+ * Sesión de autenticación (similar a Supabase)
314
+ */
315
+ interface Session {
316
+ access_token: string;
317
+ refresh_token?: string;
318
+ expires_in?: number;
319
+ expires_at?: number;
320
+ token_type?: string;
321
+ user: User;
322
+ }
323
+ type AuthChangeEvent = 'SIGNED_IN' | 'SIGNED_OUT' | 'USER_UPDATED' | 'TOKEN_REFRESHED' | 'INITIAL_SESSION' | 'PASSWORD_RECOVERY';
324
+ /**
325
+ * Respuesta de getSession (similar a Supabase)
326
+ */
327
+ interface SessionResponse {
328
+ data: {
329
+ session: Session | null;
330
+ };
331
+ error: Error | null;
332
+ }
333
+
334
+ type AuthListener = (event: AuthChangeEvent, session: Session | null) => void;
335
+ declare class AuthModule {
336
+ private config;
337
+ private _user;
338
+ private _token;
339
+ private _refreshToken;
340
+ private _expiresAt;
341
+ private _listeners;
342
+ private _refreshTimeout;
343
+ private readonly STORAGE_KEY;
344
+ private storage;
345
+ private _sessionRecovered;
346
+ constructor(config: {
347
+ baseUrl?: string;
348
+ apiKey?: string;
349
+ } & AuthClientOptions, _apiClient?: ApiClient | null);
350
+ private _handleVisibilityChange;
351
+ private deriveStorageKey;
352
+ get user(): User | null;
353
+ get token(): string | null;
354
+ onAuthStateChange(listener: AuthListener): {
355
+ data: {
356
+ subscription: {
357
+ unsubscribe: () => void;
358
+ };
359
+ };
360
+ };
361
+ /**
362
+ * Login con email y contraseña
363
+ */
364
+ signInWithPassword(credentials: LoginCredentials): Promise<DypaiResponse<AuthResponse>>;
365
+ /** Alias para signInWithPassword */
366
+ login(credentials: LoginCredentials): Promise<DypaiResponse<AuthResponse>>;
367
+ /**
368
+ * Registro de nuevo usuario
369
+ */
370
+ signUp(data: RegisterData): Promise<DypaiResponse<AuthResponse>>;
371
+ /** Alias para signUp */
372
+ register(data: RegisterData): Promise<DypaiResponse<AuthResponse>>;
373
+ /**
374
+ * Obtiene la sesión actual
375
+ */
376
+ getSession(): Promise<DypaiResponse<Session | null>>;
377
+ /**
378
+ * Obtiene los datos del usuario actual desde el servidor
379
+ */
380
+ getUser(): Promise<DypaiResponse<User>>;
381
+ /** Alias para getUser */
382
+ me(): Promise<DypaiResponse<User>>;
383
+ /**
384
+ * Inicia sesión con OAuth
385
+ */
386
+ signInWithOAuth(provider: 'google' | 'github', options?: OAuthOptions): Promise<DypaiResponse<void>>;
387
+ /**
388
+ * Cierra la sesión
389
+ */
390
+ signOut(): Promise<DypaiResponse<void>>;
391
+ /** Alias para signOut */
392
+ logout(): Promise<DypaiResponse<void>>;
393
+ /**
394
+ * Solicita recuperación de contraseña
395
+ */
396
+ resetPasswordForEmail(email: string): Promise<DypaiResponse<PasswordRecoveryResponse>>;
397
+ /** Alias para resetPasswordForEmail */
398
+ recoverPassword(data: PasswordRecoveryRequest): Promise<DypaiResponse<PasswordRecoveryResponse>>;
399
+ /**
400
+ * Refresca la sesión actual utilizando el refresh token.
401
+ */
402
+ refreshSession(): Promise<DypaiResponse<AuthResponse>>;
403
+ /**
404
+ * Envía un código OTP (One Time Password) al email o teléfono indicado.
405
+ * Utilizado para login sin contraseña (Magic Link o SMS OTP).
406
+ */
407
+ signInWithOtp(credentials: OTPRequest): Promise<DypaiResponse<any>>;
408
+ /**
409
+ * Verifica un código OTP enviado por email o teléfono.
410
+ * Si es válido, inicia la sesión del usuario.
411
+ */
412
+ verifyOtp(params: VerifyOTPRequest): Promise<DypaiResponse<AuthResponse>>;
413
+ /**
414
+ * Actualiza los datos del usuario actual (email, contraseña o metadatos).
415
+ * Requiere que el usuario esté autenticado.
416
+ */
417
+ updateUser(attributes: UpdateUserAttributes): Promise<DypaiResponse<User>>;
418
+ /**
419
+ * Programa un refresco de sesión automático antes de que el token caduque.
420
+ */
421
+ private _scheduleRefresh;
422
+ private _normalizeUser;
423
+ private _handleAuthResponse;
424
+ private _updateUser;
425
+ private _clearSession;
426
+ private _recoverSession;
427
+ private _getSessionObject;
428
+ private _notifyListeners;
429
+ handleSessionExpired(): void;
430
+ }
431
+
432
+ interface CreateUserRequest {
433
+ email: string;
434
+ password: string;
435
+ email_confirm?: boolean;
436
+ user_metadata?: Record<string, any>;
437
+ app_metadata?: Record<string, any>;
438
+ }
439
+ interface UpdateUserRequest {
440
+ email?: string;
441
+ password?: string;
442
+ email_confirm?: boolean;
443
+ user_metadata?: Record<string, any>;
444
+ app_metadata?: Record<string, any>;
445
+ }
446
+ interface UserListResponse {
447
+ users: User[];
448
+ aud: string;
449
+ }
450
+
451
+ /**
452
+ * Módulo para gestión administrativa de usuarios
453
+ */
454
+ declare class UsersModule {
455
+ private api;
456
+ constructor(api: ApiClient);
457
+ /**
458
+ * Obtiene la lista de usuarios de la aplicación.
459
+ * Requiere rol de admin.
460
+ */
461
+ list(params?: {
462
+ page?: number;
463
+ per_page?: number;
464
+ }): Promise<DypaiResponse<UserListResponse>>;
465
+ /**
466
+ * Crea un nuevo usuario en la aplicación.
467
+ */
468
+ create(data: CreateUserRequest): Promise<DypaiResponse<User>>;
469
+ /**
470
+ * Actualiza un usuario existente.
471
+ */
472
+ update(userId: string, data: UpdateUserRequest): Promise<DypaiResponse<User>>;
473
+ /**
474
+ * Elimina un usuario.
475
+ */
476
+ delete(userId: string): Promise<DypaiResponse<{
477
+ success: boolean;
478
+ }>>;
479
+ }
480
+
481
+ /**
482
+ * Tipos para el módulo de Storage
483
+ */
484
+ interface UploadOptions {
485
+ path?: string;
486
+ onProgress?: (percent: number) => void;
487
+ params?: Record<string, any>;
488
+ uploadEndpoint?: string;
489
+ confirmEndpoint?: string;
490
+ contentType?: string;
491
+ cacheControl?: string;
492
+ metadata?: Record<string, string>;
493
+ }
494
+ interface DownloadOptions {
495
+ fileName?: string;
496
+ method?: 'GET' | 'POST';
497
+ params?: Record<string, any>;
498
+ downloadEndpoint?: string;
499
+ }
500
+ interface SignedUrlOptions {
501
+ expiresIn?: number;
502
+ params?: Record<string, any>;
503
+ endpoint?: string;
504
+ }
505
+ interface ListOptions {
506
+ prefix?: string;
507
+ limit?: number;
508
+ offset?: number;
509
+ sortBy?: 'name' | 'created_at' | 'updated_at' | 'last_accessed_at';
510
+ order?: 'asc' | 'desc';
511
+ search?: string;
512
+ params?: Record<string, any>;
513
+ endpoint?: string;
514
+ }
515
+ interface UploadResponse {
516
+ files: Array<{
517
+ file_name: string;
518
+ file_path: string;
519
+ size_bytes: number;
520
+ content_type: string;
521
+ public_url?: string;
522
+ id?: string;
523
+ }>;
524
+ bucket: string;
525
+ }
526
+ interface SignedUrlResponse {
527
+ signedUrl: string;
528
+ expiresIn: number;
529
+ path: string;
530
+ }
531
+ interface StorageObject {
532
+ id: string;
533
+ name: string;
534
+ path: string;
535
+ size: number;
536
+ mime_type?: string;
537
+ created_at: string;
538
+ updated_at: string;
539
+ last_accessed_at?: string;
540
+ metadata?: Record<string, any>;
541
+ }
542
+ interface ListResponse {
543
+ data: StorageObject[];
544
+ total?: number;
545
+ hasMore?: boolean;
546
+ }
547
+
548
+ /**
549
+ * Módulo de Storage (Almacenamiento)
550
+ *
551
+ * Proporciona una API tipo Supabase para gestionar archivos.
552
+ * Internamente usa Smart Storage con workflows del Engine.
553
+ */
554
+ declare class StorageModule {
555
+ private api;
556
+ constructor(api: ApiClient);
557
+ /**
558
+ * Selecciona un bucket para operaciones
559
+ * @param bucketName Nombre del bucket
560
+ * @returns Builder para operaciones en el bucket
561
+ *
562
+ * @example
563
+ * ```typescript
564
+ * const { data, error } = await dypai.storage
565
+ * .from('imagenes')
566
+ * .upload('avatar.jpg', file);
567
+ * ```
568
+ */
569
+ from(bucketName: string): StorageBucketBuilder;
570
+ }
571
+ /**
572
+ * Builder para operaciones en un bucket específico
573
+ */
574
+ declare class StorageBucketBuilder {
575
+ private bucketName;
576
+ private api;
577
+ constructor(bucketName: string, api: ApiClient);
578
+ /**
579
+ * Sube un archivo al bucket
580
+ *
581
+ * @param path Path donde guardar el archivo (ej: 'carpeta/archivo.jpg')
582
+ * @param file Archivo a subir
583
+ * @param options Opciones adicionales
584
+ *
585
+ * @example
586
+ * ```typescript
587
+ * const file = document.getElementById('input').files[0];
588
+ * const { data, error } = await dypai.storage
589
+ * .from('documentos')
590
+ * .upload('facturas/2024/inv-001.pdf', file, {
591
+ * onProgress: (pct) => console.log(`${pct}%`)
592
+ * });
593
+ * ```
594
+ */
595
+ upload(path: string, file: File, options?: UploadOptions): Promise<DypaiResponse<UploadResponse>>;
596
+ /**
597
+ * Descarga un archivo del bucket
598
+ *
599
+ * @param path Path del archivo a descargar
600
+ * @param options Opciones adicionales
601
+ *
602
+ * @example
603
+ * ```typescript
604
+ * await dypai.storage
605
+ * .from('documentos')
606
+ * .download('facturas/2024/inv-001.pdf', {
607
+ * fileName: 'factura.pdf'
608
+ * });
609
+ * ```
610
+ */
611
+ download(path: string, options?: DownloadOptions): Promise<DypaiResponse<void>>;
612
+ /**
613
+ * Obtiene una URL firmada para acceso temporal
614
+ *
615
+ * @param path Path del archivo
616
+ * @param options Opciones adicionales
617
+ *
618
+ * @example
619
+ * ```typescript
620
+ * const { data, error } = await dypai.storage
621
+ * .from('documentos')
622
+ * .createSignedUrl('facturas/inv-001.pdf', 15);
623
+ *
624
+ * if (data) {
625
+ * console.log('URL:', data.signedUrl);
626
+ * }
627
+ * ```
628
+ */
629
+ createSignedUrl(path: string, expiresIn?: number, options?: Omit<SignedUrlOptions, 'expiresIn'>): Promise<DypaiResponse<SignedUrlResponse>>;
630
+ /**
631
+ * Obtiene la URL pública de un archivo (solo para buckets públicos)
632
+ *
633
+ * @param path Path del archivo
634
+ *
635
+ * @example
636
+ * ```typescript
637
+ * const { data } = await dypai.storage
638
+ * .from('public-images')
639
+ * .getPublicUrl('logo.png');
640
+ *
641
+ * console.log('URL pública:', data.publicUrl);
642
+ * ```
643
+ */
644
+ getPublicUrl(path: string): {
645
+ data: {
646
+ publicUrl: string;
647
+ };
648
+ };
649
+ /**
650
+ * Lista archivos en el bucket
651
+ *
652
+ * @param path Path/carpeta a listar (opcional, lista raíz si no se especifica)
653
+ * @param options Opciones adicionales
654
+ *
655
+ * @example
656
+ * ```typescript
657
+ * const { data, error } = await dypai.storage
658
+ * .from('documentos')
659
+ * .list('facturas/2024', {
660
+ * limit: 20,
661
+ * sortBy: 'created_at',
662
+ * order: 'desc'
663
+ * });
664
+ *
665
+ * if (data) {
666
+ * data.data.forEach(file => {
667
+ * console.log(file.name, file.size);
668
+ * });
669
+ * }
670
+ * ```
671
+ */
672
+ list(path?: string, options?: ListOptions): Promise<DypaiResponse<ListResponse>>;
673
+ /**
674
+ * Elimina un archivo del bucket
675
+ *
676
+ * @param path Path del archivo a eliminar
677
+ * @param options Opciones adicionales
678
+ *
679
+ * @example
680
+ * ```typescript
681
+ * const { error } = await dypai.storage
682
+ * .from('documentos')
683
+ * .remove('facturas/2024/inv-001.pdf');
684
+ *
685
+ * if (!error) {
686
+ * console.log('Archivo eliminado');
687
+ * }
688
+ * ```
689
+ */
690
+ remove(path: string | string[], options?: {
691
+ endpoint?: string;
692
+ params?: Record<string, any>;
693
+ }): Promise<DypaiResponse<{
694
+ success: boolean;
695
+ }>>;
696
+ /**
697
+ * Mueve/renombra un archivo
698
+ *
699
+ * @param fromPath Path origen
700
+ * @param toPath Path destino
701
+ * @param options Opciones adicionales
702
+ *
703
+ * @example
704
+ * ```typescript
705
+ * await dypai.storage
706
+ * .from('documentos')
707
+ * .move('temp/archivo.pdf', 'final/archivo.pdf');
708
+ * ```
709
+ */
710
+ move(fromPath: string, toPath: string, options?: {
711
+ endpoint?: string;
712
+ params?: Record<string, any>;
713
+ }): Promise<DypaiResponse<{
714
+ success: boolean;
715
+ }>>;
716
+ /**
717
+ * Copia un archivo
718
+ *
719
+ * @param fromPath Path origen
720
+ * @param toPath Path destino
721
+ * @param options Opciones adicionales
722
+ *
723
+ * @example
724
+ * ```typescript
725
+ * await dypai.storage
726
+ * .from('documentos')
727
+ * .copy('original.pdf', 'copia.pdf');
728
+ * ```
729
+ */
730
+ copy(fromPath: string, toPath: string, options?: {
731
+ endpoint?: string;
732
+ params?: Record<string, any>;
733
+ }): Promise<DypaiResponse<{
734
+ success: boolean;
735
+ }>>;
736
+ }
737
+
738
+ interface DypaiConfig {
739
+ /** URL base de la API (ej: 'http://localhost:8000') - Debe apuntar al Gateway unificado */
740
+ baseUrl: string;
741
+ /** API Key pública (Anon Key) */
742
+ apiKey: string;
743
+ /** Opciones de autenticación */
744
+ auth?: AuthClientOptions;
745
+ /** Opciones globales (fetch, headers) */
746
+ global?: GlobalClientOptions;
747
+ /** Opcional: ID para aislar localStorage entre múltiples clientes */
748
+ storageKey?: string;
749
+ }
750
+ /**
751
+ * Cliente unificado de Dypai.
752
+ *
753
+ * @typeParam TDatabase - Estructura de la base de datos (opcional)
754
+ * @typeParam TApi - Mapa de endpoints tipados (opcional)
755
+ */
756
+ declare class DypaiClient<TDatabase = any, TApi extends EndpointMap = EndpointMap> {
757
+ /** Módulo de autenticación */
758
+ auth: AuthModule;
759
+ /** Módulo de base de datos (CRUD) */
760
+ db: DataModule<TDatabase>;
761
+ /** Módulo de gestión de usuarios (admin) */
762
+ users: UsersModule;
763
+ /** Módulo de almacenamiento (Storage) */
764
+ storage: StorageModule;
765
+ /** Cliente API tipado para endpoints personalizados */
766
+ api: TApi extends EndpointMap ? TypedApiClient<TApi> : ApiClient;
767
+ constructor(config: DypaiConfig);
768
+ /**
769
+ * Alias para acceder a datos (estilo Supabase)
770
+ */
771
+ from<TTableName extends keyof TDatabase & string>(table: TTableName): QueryBuilder;
772
+ /**
773
+ * Obtiene el usuario actual
774
+ */
775
+ me(): Promise<DypaiResponse<User>>;
776
+ }
777
+ /**
778
+ * Crea una instancia del cliente Dypai.
779
+ * Requiere URL y API Key (modo standalone estilo Supabase).
780
+ *
781
+ * @typeParam TDatabase - Estructura de la base de datos (opcional)
782
+ * @typeParam TApi - Mapa de endpoints tipados (opcional)
783
+ */
784
+ declare function createClient<TDatabase = any, TApi extends EndpointMap = EndpointMap>(url: string, apiKey: string, options?: Omit<DypaiConfig, 'baseUrl' | 'apiKey'>): DypaiClient<TDatabase, TApi>;
785
+
786
+ /**
787
+ * @dypai/client-sdk - Cliente HTTP centralizado
788
+ *
789
+ * Gestiona llamadas al Gateway unificado (Auth + Engine).
790
+ */
791
+
792
+ type TokenProvider = () => string | null;
793
+ declare function setTokenProvider(provider: TokenProvider): void;
794
+ declare function configureApiService(config: ApiServiceConfig): void;
795
+ declare function callApi(token: string, method: HttpMethod, endpoint: string, body?: any, showToastsOverride?: boolean, params?: Record<string, any>, apiKey?: string, baseUrl?: string): Promise<any>;
796
+ declare function createApiClient(context: ApiContextType | (() => ApiContextType)): ApiClient;
797
+
798
+ /**
799
+ * Configuración global para todos los servicios DYPAI
800
+ *
801
+ * Este archivo maneja la configuración centralizada para:
802
+ * - ApiService (API autenticada)
803
+ */
804
+
805
+ /**
806
+ * Configura todos los servicios DYPAI de una vez (recomendado)
807
+ * Esta es la función principal que deberías usar al inicio de tu aplicación
808
+ *
809
+ * @param config Configuración global para todos los servicios
810
+ * @example
811
+ * ```typescript
812
+ * import { configureDypaiServices } from '@dypai/client-sdk';
813
+ *
814
+ * configureDypaiServices({
815
+ * showToasts: true,
816
+ * toast: myCustomToast
817
+ * });
818
+ * ```
819
+ */
820
+ declare function configureDypaiServices(config: ApiServiceConfig): void;
821
+ /**
822
+ * Obtiene la configuración global actual
823
+ * @returns Configuración global compartida
824
+ */
825
+ declare function getGlobalConfig(): ApiServiceConfig;
826
+ /**
827
+ * Resetea toda la configuración global (útil para testing)
828
+ */
829
+ declare function resetGlobalConfig(): void;
830
+ /**
831
+ * Fuerza la recarga de la configuración detectando cambios en variables de entorno
832
+ * Útil cuando se cambian variables de entorno en tiempo de ejecución
833
+ *
834
+ * @example
835
+ * ```typescript
836
+ * import { reloadDypaiConfig } from '@dypai/client-sdk';
837
+ *
838
+ * // Después de cambiar variables de entorno
839
+ * await reloadDypaiConfig();
840
+ * ```
841
+ */
842
+ declare function reloadDypaiConfig(): Promise<void>;
843
+
844
+ /**
845
+ * Hook básico para notificaciones toast en el paquete @dypai/client-sdk
846
+ *
847
+ * Este es un hook minimalista que puede ser reemplazado por la implementación
848
+ * de la aplicación host mediante la configuración del ApiService.
849
+ */
850
+
851
+ /**
852
+ * Configura una función toast personalizada para usar en el paquete
853
+ * @param toastFn Función toast de la aplicación host
854
+ */
855
+ declare function setToastFunction(toastFn: ToastFunction): void;
856
+ /**
857
+ * Hook que retorna la función toast configurada o el fallback
858
+ */
859
+ declare function useToast(): {
860
+ toast: ToastFunction;
861
+ toastSuccess: (title: string, description?: string) => void;
862
+ toastError: (title: string, description?: string) => void;
863
+ toastWarning: (title: string, description?: string) => void;
864
+ toastInfo: (title: string, description?: string) => void;
865
+ };
866
+ /**
867
+ * Función toast independiente que puede ser usada sin hook
868
+ */
869
+ declare const toast: (props: Parameters<ToastFunction>[0]) => void;
870
+ declare const toastSuccess: (title: string, description?: string) => void;
871
+ declare const toastError: (title: string, description?: string) => void;
872
+ declare const toastWarning: (title: string, description?: string) => void;
873
+ declare const toastInfo: (title: string, description?: string) => void;
874
+
875
+ /**
876
+ * @dypai-ai/client-sdk
877
+ *
878
+ * Paquete centralizado para manejo de llamadas API con autenticación DYPAI
879
+ *
880
+ * ⚠️ IMPORTANTE: Debes crear el cliente primero (igual que Supabase). No puedes usar el paquete sin crear una instancia del cliente.
881
+ *
882
+ * @example Crear el cliente (OBLIGATORIO - Paso 1)
883
+ * ```typescript
884
+ * // src/lib/dypai.ts
885
+ * import { createClient } from '@dypai/client-sdk';
886
+ *
887
+ * // Crear el cliente con tu URL y API Key
888
+ * export const dypai = createClient(
889
+ * 'https://api.dypai.es', // URL de la API
890
+ * 'tu-anon-key-publica' // API Key pública
891
+ * );
892
+ *
893
+ * // Con variables de entorno (Vite)
894
+ * export const dypai = createClient(
895
+ * import.meta.env.VITE_DYPAI_API_URL,
896
+ * import.meta.env.VITE_DYPAI_API_KEY
897
+ * );
898
+ * ```
899
+ *
900
+ * @example Uso básico con el cliente (Paso 2)
901
+ * ```typescript
902
+ * // En cualquier componente o archivo
903
+ * import { dypai } from '../lib/dypai'; // Tu cliente configurado
904
+ *
905
+ * // Autenticación
906
+ * const { data, error } = await dypai.auth.signInWithPassword({ email: 'user@test.com', password: '123' });
907
+ *
908
+ * // Base de Datos (CRUD) con autocompletado
909
+ * const { data: productos, error: dbError } = await dypai.from('productos').select({ limit: 10 });
910
+ *
911
+ * // API personalizada (endpoints)
912
+ * const facturas = await dypai.api.get('mis_facturas');
913
+ *
914
+ * // Storage (Almacenamiento) - Estilo Supabase
915
+ * const { data, error } = await dypai.storage
916
+ * .from('documentos')
917
+ * .upload('facturas/2024/inv-001.pdf', file);
918
+ *
919
+ * const { data: url } = await dypai.storage
920
+ * .from('documentos')
921
+ * .getSignedUrl('facturas/2024/inv-001.pdf', { expiresIn: 15 });
922
+ * ```
923
+ *
924
+ * @example Uso avanzado con tipado de Base de Datos y API
925
+ * ```typescript
926
+ * // Define tus interfaces
927
+ * interface Database {
928
+ * productos: { id: string; nombre: string; precio: number };
929
+ * }
930
+ *
931
+ * export const dypai = createClient<Database>(url, key);
932
+ *
933
+ * // 'productos' tiene autocompletado y el resultado está tipado
934
+ * const { data } = await dypai.from('productos').select();
935
+ * ```
936
+ *
937
+ * @example Uso con servicios estáticos (Legacy - No recomendado para nuevas apps)
938
+ * ```typescript
939
+ * // ⚠️ Solo si usas configuración global
940
+ * // import { ApiService, FileService } from '@dypai/client-sdk'; // DEPRECATED
941
+ * // Usa createClient() en su lugar
942
+ * ```
943
+ *
944
+ */
945
+ /**
946
+ * Nuevo Cliente Unificado DYPAI
947
+ * Forma recomendada de acceder a toda la funcionalidad (Auth, Data, etc.)
948
+ */
949
+
950
+ declare const PACKAGE_INFO: {
951
+ readonly name: "@dypai-ai/client-sdk";
952
+ readonly version: "0.0.1";
953
+ readonly description: "Cliente JavaScript para Dypai Engine";
954
+ readonly features: readonly ["API REST autenticada", "Smart Storage (Upload/Download delegado)", "Gestión de usuarios (Admin)"];
955
+ };
956
+
957
+ export { DypaiClient, PACKAGE_INFO, callApi, configureApiService, configureDypaiServices, createApiClient, createClient, getGlobalConfig, reloadDypaiConfig, resetGlobalConfig, setToastFunction, setTokenProvider, toast, toastError, toastInfo, toastSuccess, toastWarning, useToast };
958
+ export type { ApiClient, ApiContextType, ApiOptions, ApiServiceConfig, AppConfig, AppData, AppRole, AppUserResponse, AuthChangeEvent, AuthResponse, CreateUserRequest, DownloadOptions, DypaiConfig, EndpointBody, EndpointDefinition, EndpointMap, EndpointParams, EndpointResponse, HttpMethod, JoinResponse, ListOptions, ListResponse, LoginCredentials, OAuthOptions, PasswordRecoveryRequest, PasswordRecoveryResponse, PasswordResetRequest, PasswordResetResponse, RegisterData, ResetPasswordWithTokenRequest, Session, SessionResponse, SignedUrlOptions, SignedUrlResponse, StorageObject, ToastFunction, TypedApiClient, TypedGetOptions, TypedMutationOptions, UpdateUserRequest, UploadOptions, UploadResponse, User, UserData, UserListResponse };
@@ -0,0 +1 @@
1
+ class t extends Error{constructor(t,e=500,s,n){super(t),this.status=e,this.code=s,this.details=n,this.name="DypaiError"}}const e={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 s(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 n{constructor(t,s=null){this.config=t,this.t=null,this.i=null,this.o=null,this.h=null,this.u=[],this.l=null,this.storage=t.storage||e;const n=t.storageKey||this.deriveStorageKey(t.apiKey);console.log(`[DYPAI SDK] 🛠️ Inicializando AuthModule (apiKey: ${t.apiKey?.substring(0,8)}..., storageKey: ${n})`),this.STORAGE_KEY=`dypai-${n}-auth-session`,this.p=this.S().then(()=>{this.i&&this.getUser().catch(()=>{})}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.m.bind(this)),window.addEventListener("focus",this.m.bind(this)))}async m(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(console.log("[DYPAI SDK] 👁️ Ventana visible. Verificando estado de sesión..."),await this.S())}deriveStorageKey(t){return t?t.substring(0,8):"default"}get user(){return this.t}get token(){return this.i}onAuthStateChange(t){return console.log("[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.u.push(t),this.p.then(()=>{const e=this._(),s=e?"SIGNED_IN":"INITIAL_SESSION";console.log(`[DYPAI SDK] 📣 Notificando estado inicial a nuevo suscriptor: ${s} (Sesión activa: ${!!e})`),t(s,e)}).catch(e=>{console.error("[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.u=this.u.filter(e=>e!==t)}}}}}async signInWithPassword(e){return s((async()=>{const s=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=password`,n={email:e.email||e.identifier||"",password:e.password},i=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(n)});if(!i.ok){const e=await i.json();throw new t(e.msg||e.error_description||"Login failed",i.status)}const a=await i.json(),o={token:a.access_token,refreshToken:a.refresh_token,expiresIn:a.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(a.expires_in||3600),user:this.D(a)};return this.v(o),o})())}async login(t){return this.signInWithPassword(t)}async signUp(e){return s((async()=>{const s=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,n={email:e.email,password:e.password,data:e.user_data||{}},i=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(n)});if(!i.ok){const e=await i.json();throw new t(e.msg||e.error_description||"Registration failed",i.status)}const a=await i.json(),o={token:a.access_token,refreshToken:a.refresh_token,expiresIn:a.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(a.expires_in||3600),user:this.D(a)};return o.token&&this.v(o),o})())}async register(t){return this.signUp(t)}async getSession(){try{if(!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.h||0)-t<30&&this.o&&(console.log("[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>console.warn("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 s((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,s=await fetch(e,{headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!s.ok)throw new t("Session invalid",s.status);const n=await s.json(),i=this.D(n);return this.$(i),i})())}async me(){return this.getUser()}async signInWithOAuth(t,e={}){return s((async()=>{const{redirectTo:s=window.location.href}=e,n=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/authorize?provider=${t}&redirect_to=${encodeURIComponent(s)}`;window.location.href=n})())}async signOut(){return s((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.I()}})())}async logout(){return this.signOut()}async resetPasswordForEmail(e){return s((async()=>{const s=this.config.baseUrl||"http://localhost:8000",n=await fetch(`${s}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:e})});if(!n.ok)throw new t("Recovery failed",n.status);return await n.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async refreshSession(){return console.log("[DYPAI SDK] 🔄 Intentando refrescar sesión con Refresh Token..."),s((async()=>{if(!this.o)throw new t("No hay refresh token disponible",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/token?grant_type=refresh_token`,s=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({refresh_token:this.o})});if(!s.ok){const e=await s.json();throw 400!==s.status&&401!==s.status||this.I(),new t(e.msg||e.error_description||"Refresh session failed",s.status)}const n=await s.json(),i={token:n.access_token,refreshToken:n.refresh_token,expiresIn:n.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(n.expires_in||3600),user:this.D(n)};return this.v(i),i})())}async signInWithOtp(e){return s((async()=>{const s=this.config.baseUrl||"http://localhost:8000",n=await fetch(`${s}/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 s((async()=>{const s=this.config.baseUrl||"http://localhost:8000",n=await fetch(`${s}/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 i=await n.json(),a={token:i.access_token,refreshToken:i.refresh_token,expiresIn:i.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(i.expires_in||3600),user:this.D(i)};return a.token&&this.v(a),a})())}async updateUser(e){return s((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const s=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,n=await fetch(s,{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 i=await n.json(),a=this.D(i);return this.$(a),a})())}T(t){this.l&&(clearTimeout(this.l),this.l=null);const e=t-Math.floor(Date.now()/1e3)-60;e>0?(console.log(`[DYPAI SDK] ⏱️ Refresco de sesión programado en ${e} segundos.`),this.l=setTimeout(()=>{this.refreshSession().catch(t=>{console.error("Error in auto-refresh:",t)})},1e3*e)):this.o&&this.refreshSession().catch(()=>{})}D(t){if(!t)return console.warn("[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{};const e=t.user||t,s=e.app_metadata||{},n=e.user_metadata||{};return{id:e.id,email:e.email,phone:e.phone,role:s.role||e.role,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:s,user_metadata:n,k:{name:s.role||"authenticated",weight:0},P:{app_id:"default"}}}v(t){t.token&&(this.i=t.token,this.o=t.refreshToken||null,this.h=t.expiresAt||null,this.$(t.user,t.token,t.refreshToken,t.expiresAt))}async $(t,e,s,n){this.t=t,e&&(this.i=e),void 0!==s&&(this.o=s||null),void 0!==n&&(this.h=n||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 console.warn("[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.",{hasToken:!!this.i,hasUser:!!this.t})}catch(t){console.error("[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}this.h&&this.T(this.h);const i=e?"SIGNED_IN":"USER_UPDATED";this.A(i)}async I(){console.log("[DYPAI SDK] 🧹 Limpiando sesión del estado y storage."),this.i=null,this.o=null,this.t=null,this.h=null,this.l&&(clearTimeout(this.l),this.l=null);try{await this.storage.removeItem(this.STORAGE_KEY)}catch(t){console.error("[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.A()}async S(){console.log("[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{const t=await this.storage.getItem(this.STORAGE_KEY);if(t){console.log("[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");const e=JSON.parse(t);this.i=e.access_token,this.o=e.refresh_token,this.h=e.expires_at,this.t=e.user}else{console.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}`),s=await this.storage.getItem(`dypai-auth-user-${t}`);if(!e||!s)return void console.log("[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{console.log("[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");const n=JSON.parse(s);this.i=e,this.t=n;const i=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),a=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.o=i,this.h=a?parseInt(a,10):null,await this.$(n,this.i||void 0,this.o||void 0,this.h||void 0);const o=[`dypai-auth-token-${t}`,`dypai-auth-refresh-token-${t}`,`dypai-auth-user-${t}`,`dypai-auth-expires-at-${t}`];for(const t of o)try{await this.storage.removeItem(t)}catch(t){}console.log("[DYPAI SDK] ✨ Migración completada con éxito.")}}const e=Math.floor(Date.now()/1e3);console.log(`[DYPAI SDK] 🕒 Token expira en: ${this.h}. Ahora es: ${e}. Diferencia: ${(this.h||0)-e}s`),this.h&&this.h<=e&&this.o?(console.log("[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente..."),await this.refreshSession().catch(t=>{console.error("[DYPAI SDK] ❌ El refresco inicial falló (posible error de red):",t)})):this.h&&(console.log("[DYPAI SDK] ✨ Sesión válida. Programando refresco futuro."),this.T(this.h)),this.A("SIGNED_IN")}catch(t){console.error("[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}_(){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}A(t="USER_UPDATED"){const e=this._();console.log(`[DYPAI SDK] 📢 Notificando a ${this.u.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.u.forEach(s=>s(t,e))}handleSessionExpired(){this.I()}}class i{constructor(t){this.api=t}from(t){return new a(t,this.api)}}class a{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}`)}}async function o(e){try{return{data:await e,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e.message||"Error desconocido",e.status||500)}}}class r{constructor(t){this.api=t}async list(t={}){return o(this.api.get("admin/users",{params:t}))}async create(t){return o(this.api.post("admin/users",{body:t}))}async update(t,e){return o(this.api.put(`admin/users/${t}`,{body:e}))}async delete(t){return o(this.api.delete(`admin/users/${t}`))}}let c=null;function h(t){c=t}const u=t=>{const{title:e,description:s,variant:n="default"}=t,i=`${"error"===n?"❌":"success"===n?"✅":"warning"===n?"⚠️":"info"===n?"ℹ️":"📢"} ${e}${s?`: ${s}`:""}`;"error"===n?console.error(i):"warning"===n?console.warn(i):console.log(i)};function l(){const t=c||u;return{toast:t,toastSuccess:(e,s)=>t({title:e,description:s,variant:"success"}),toastError:(e,s)=>t({title:e,description:s,variant:"error"}),toastWarning:(e,s)=>t({title:e,description:s,variant:"warning"}),toastInfo:(e,s)=>t({title:e,description:s,variant:"info"})}}const d=t=>(c||u)(t),p=(t,e)=>d({title:t,description:e,variant:"success"}),f=(t,e)=>d({title:t,description:e,variant:"error"}),y=(t,e)=>d({title:t,description:e,variant:"warning"}),w=(t,e)=>d({title:t,description:e,variant:"info"});let S={},g=null;function m(t){g=t}function _(t){S={...S,...t}}const D=new Map;function v(){let t=S;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...S}}catch(t){}return t}async function $(e,s,n,i,a,o,r,c){const h=v(),u=c||null;if(!u&&!n.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url, key).");if("string"!=typeof n||!n.trim())throw new Error("Endpoint debe ser un string válido");let l;if(n.startsWith("http"))l=n;else{const t=u.replace(/\/+$/,"");l=n.startsWith("/")?t+n:t+"/api/v0/"+n}if(o&&Object.keys(o).length>0){const t=new URLSearchParams,e=(s,n)=>{null!=n&&(Array.isArray(n)?n.forEach((t,n)=>e(`${s}[${n}]`,t)):"object"==typeof n?Object.entries(n).forEach(([t,n])=>e(`${s}[${t}]`,n)):t.append(s,String(n)))};Object.entries(o).forEach(([t,s])=>e(t,s));const s=t.toString();s&&(l+=`?${s}`)}const p="GET"===s?`${s}:${l}:${JSON.stringify(i)}`:null;if(p&&D.has(p))return D.get(p);const f=i instanceof FormData,y={...h.headers||{},...f?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...r&&{"x-api-key":r}},w={method:s,headers:y,credentials:"include"};i&&"GET"!==s&&"DELETE"!==s&&(w.body=f?i:JSON.stringify(i));const S=h.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!S)throw new Error("Fetch no disponible.");const g=(async()=>{const e=v().toast||d,u=(void 0!==a?a:!1!==h.showToasts)&&e;try{const d=await S(l,w);if(!d.ok){let l,p="Error en la petición";try{const t=await d.text();try{const e=JSON.parse(t);l=e,p=e.message||e.msg||e.error_description||e.error||p}catch{t.length<200&&(p=t)}}catch{}if(401===d.status){if(h.onTokenExpired){console.log("[DYPAI SDK] 🔄 Token caducado detectado. Intentando refrescar...");const t=await h.onTokenExpired();if(t)return console.log("[DYPAI SDK] ✅ Token refrescado con éxito. Reintentando petición..."),$(t,s,n,i,a,o,r,c)}h.onUnauthorized&&h.onUnauthorized()}throw u&&e({title:"Error",description:p,variant:"error"}),new t(p,d.status,void 0,l)}!u||"POST"!==s&&"PUT"!==s&&"PATCH"!==s&&"DELETE"!==s||e({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{p&&D.delete(p)}})();return p&&D.set(p,g),g}function I(t,e){return async(s,n,i)=>{const a=t();let o,r={};n&&"object"==typeof n&&("token"in n||"params"in n||"apiKey"in n)?(r=n,o=r.body):(o=n,r=i||{});const c=r.token||a.token||(g?g():"")||"",h=r.apiKey||a.apiKey;return $(c,e,s,o,r.showToasts,r.params,h,a.baseUrl)}}function T(t){const e=()=>"function"==typeof t?t():t;return{get:k(e,"GET"),post:k(e,"POST"),put:k(e,"PUT"),patch:k(e,"PATCH"),delete:k(e,"DELETE")}}function k(t,e){return async(s,n,i)=>{const a=t(),o=await async function(t,e){let s,n={};t&&"object"==typeof t&&("token"in t||"params"in t||"apiKey"in t)?(n=t,s=n.body):(s=t,n=e||{});let i=n.token;return!i&&g&&(i=g()||""),i=i||"",{token:i,apiKey:n.apiKey,body:s,params:n.params,showToasts:n.showToasts}}(n,i),r=o.token||a.token||"",c=o.apiKey||a.apiKey;return $(r,e,s,o.body,o.showToasts,o.params,c,a.baseUrl)}}class P{constructor(t){this.api=t}from(t){return new b(t,this.api)}}class b{constructor(t,e){this.bucketName=t,this.api=e}async upload(e,s,n={}){return A(async()=>{const i=n.uploadEndpoint||`storage_upload_${this.bucketName}`,a={file_path:e,...n.params},o=await async function(e,s,n,i){const a=await e.post(s,{file_path:n.name,content_type:n.type||"application/octet-stream",size_bytes:n.size,confirm:!1,...i?.params||{}});if(!a||!a.upload_url)throw new t("El workflow no devolvió una URL de subida válida (¿falta get_upload_url?)",400);const{upload_url:o,method:r="PUT",headers:c={},file_path:h}=a;i?.onProgress&&i.onProgress(10);const u=await fetch(o,{method:r,headers:{"Content-Type":n.type||"application/octet-stream",...c},body:n});if(!u.ok)throw new t("Error en la subida directa a la nube",u.status);i?.onProgress&&i.onProgress(90);const l=i?.confirmEndpoint||s,d=await e.post(l,{...i?.params,bucket:a.bucket||i?.params?.bucket,file_path:h,filename:n.name,content_type:n.type||"application/octet-stream",size_bytes:n.size,confirm:!0});return i?.onProgress&&i.onProgress(100),d}(this.api,i,s,{confirmEndpoint:n.confirmEndpoint,params:a,onProgress:n.onProgress});return o})}async download(t,e={}){return A(async()=>{const s=e.downloadEndpoint||`storage_download_${this.bucketName}`,n={bucket:this.bucketName,file_path:t,...e.params};await async function(t,e,s,n){const i=n?.method,a=await("GET"===i?t.get(e,{params:n?.params}):t.post(e,s,{params:n?.params}));if(a instanceof Blob){const t=window.URL.createObjectURL(a),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(a&&"object"==typeof a&&("url"in a||"signed_url"in a||"signedUrl"in a)){const t=a.url||a.signed_url||a.signedUrl;"string"==typeof t&&window.open(t,"_blank")}}(this.api,s,n,{fileName:e.fileName,method:e.method||"POST"})})}async createSignedUrl(t,e=15,s={}){return A(async()=>{const n=s.endpoint||`storage_signed_url_${this.bucketName}`,i={bucket:this.bucketName,file_path:t,expires_minutes:e,...s.params},a=await this.api.post(n,i);return{signedUrl:a.signed_url||a.signedUrl||a.url,expiresIn:a.expires_in||a.expiresIn||e,path:t}})}getPublicUrl(t){const e=t.replace(/^\/+/,"");return{data:{publicUrl:`${this.api.baseUrl||""}/storage/v1/render/public/${this.bucketName}/${e}`}}}async list(t="",e={}){return A(async()=>{const s=e.endpoint||`storage_list_${this.bucketName}`,n={bucket:this.bucketName,prefix:t||e.prefix||"",limit:e.limit,offset:e.offset,sort_by:e.sortBy,order:e.order,search:e.search,...e.params},i=await this.api.post(s,n);return{data:i.data||i.files||[],total:i.total,hasMore:i.has_more||i.hasMore||!1}})}async remove(t,e={}){return A(async()=>{const s=e.endpoint||`storage_delete_${this.bucketName}`,n=Array.isArray(t)?t:[t],i={bucket:this.bucketName,paths:n,...e.params};return await this.api.post(s,i),{success:!0}})}async move(t,e,s={}){return A(async()=>{const n=s.endpoint||`storage_move_${this.bucketName}`,i={bucket:this.bucketName,from_path:t,to_path:e,...s.params};return await this.api.post(n,i),{success:!0}})}async copy(t,e,s={}){return A(async()=>{const n=s.endpoint||`storage_copy_${this.bucketName}`,i={bucket:this.bucketName,from_path:t,to_path:e,...s.params};return await this.api.post(n,i),{success:!0}})}}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 storage",e.status||500)}}}class E{constructor(t){const{baseUrl:e,apiKey:s}=t,a=t.auth?.storageKey||t.storageKey;t.global&&function(t){S={...S,...t}}(t.global),this.auth=new n({baseUrl:e,apiKey:s,storageKey:a,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const o={get:I(c=()=>({token:this.auth.token,apiKey:s,baseUrl:e}),"GET"),post:I(c,"POST"),put:I(c,"PUT"),patch:I(c,"PATCH"),delete:I(c,"DELETE")};var c;this.auth.api=o,this.db=new i(o),this.users=new r(o),this.storage=new P(o),this.api=T(()=>({token:this.auth.token||"",apiKey:s,baseUrl:e})),m(()=>this.auth.token),_({onTokenExpired:async()=>{const t=await this.auth.refreshSession();return t.data?.token||null},onUnauthorized:()=>this.auth.handleSessionExpired()})}from(t){return this.db.from(t)}async me(){return this.auth.getUser()}}function K(t,e,s){if(!t||!e)throw new Error("createClient() requiere URL y API Key");return new E({baseUrl:t,apiKey:e,...s})}let O={};function U(t){O={...O,toast:d,...t},x()}function N(){return{...O}}function x(){try{const{configureApiService:t}=require("../services/ApiService");t(O)}catch(t){}}function Y(){O={},x()}async function C(){x()}const R={name:"@dypai-ai/client-sdk",version:"0.0.1",description:"Cliente JavaScript para Dypai Engine",features:["API REST autenticada","Smart Storage (Upload/Download delegado)","Gestión de usuarios (Admin)"]};export{E as DypaiClient,R as PACKAGE_INFO,$ as callApi,_ as configureApiService,U as configureDypaiServices,T as createApiClient,K as createClient,N as getGlobalConfig,C as reloadDypaiConfig,Y as resetGlobalConfig,h as setToastFunction,m as setTokenProvider,d as toast,f as toastError,w as toastInfo,p as toastSuccess,y as toastWarning,l as useToast};
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";class DypaiError extends Error{constructor(t,e=500,s,i){super(t),this.status=e,this.code=s,this.details=i,this.name="DypaiError"}}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.i=null,this.o=null,this.h=null,this.l=[],this.u=null,this.storage=t.storage||localStorageAdapter;const s=t.storageKey||this.deriveStorageKey(t.apiKey);console.log(`[DYPAI SDK] 🛠️ Inicializando AuthModule (apiKey: ${t.apiKey?.substring(0,8)}..., storageKey: ${s})`),this.STORAGE_KEY=`dypai-${s}-auth-session`,this.p=this.S().then(()=>{this.i&&this.getUser().catch(()=>{})}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.D.bind(this)),window.addEventListener("focus",this.D.bind(this)))}async D(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(console.log("[DYPAI SDK] 👁️ Ventana visible. Verificando estado de sesión..."),await this.S())}deriveStorageKey(t){return t?t.substring(0,8):"default"}get user(){return this.t}get token(){return this.i}onAuthStateChange(t){return console.log("[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.l.push(t),this.p.then(()=>{const e=this.m(),s=e?"SIGNED_IN":"INITIAL_SESSION";console.log(`[DYPAI SDK] 📣 Notificando estado inicial a nuevo suscriptor: ${s} (Sesión activa: ${!!e})`),t(s,e)}).catch(e=>{console.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 wrapAuthResponse((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=password`,s={email:t.email||t.identifier||"",password:t.password},i=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(s)});if(!i.ok){const t=await i.json();throw new DypaiError(t.msg||t.error_description||"Login failed",i.status)}const n=await i.json(),o={token:n.access_token,refreshToken:n.refresh_token,expiresIn:n.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(n.expires_in||3600),user:this.v(n)};return this.A(o),o})())}async login(t){return this.signInWithPassword(t)}async signUp(t){return wrapAuthResponse((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,s={email:t.email,password:t.password,data:t.user_data||{}},i=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(s)});if(!i.ok){const t=await i.json();throw new DypaiError(t.msg||t.error_description||"Registration failed",i.status)}const n=await i.json(),o={token:n.access_token,refreshToken:n.refresh_token,expiresIn:n.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(n.expires_in||3600),user:this.v(n)};return o.token&&this.A(o),o})())}async register(t){return this.signUp(t)}async getSession(){try{if(!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.h||0)-t<30&&this.o&&(console.log("[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>console.warn("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(t){return{data:null,error:new DypaiError(t.message,500)}}}async getUser(){return wrapAuthResponse((async()=>{if(!this.i)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.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!e.ok)throw new DypaiError("Session invalid",e.status);const s=await e.json(),i=this.v(s);return this.T(i),i})())}async me(){return this.getUser()}async signInWithOAuth(t,e={}){return wrapAuthResponse((async()=>{const{redirectTo:s=window.location.href}=e,i=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/authorize?provider=${t}&redirect_to=${encodeURIComponent(s)}`;window.location.href=i})())}async signOut(){return wrapAuthResponse((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._()}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${e}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:t})});if(!s.ok)throw new DypaiError("Recovery failed",s.status);return await s.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async refreshSession(){return console.log("[DYPAI SDK] 🔄 Intentando refrescar sesión con Refresh Token..."),wrapAuthResponse((async()=>{if(!this.o)throw new DypaiError("No hay refresh token disponible",401);const t=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/token?grant_type=refresh_token`,e=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({refresh_token:this.o})});if(!e.ok){const t=await e.json();throw 400!==e.status&&401!==e.status||this._(),new DypaiError(t.msg||t.error_description||"Refresh session failed",e.status)}const s=await e.json(),i={token:s.access_token,refreshToken:s.refresh_token,expiresIn:s.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(s.expires_in||3600),user:this.v(s)};return this.A(i),i})())}async signInWithOtp(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",s=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(!s.ok){const t=await s.json();throw new DypaiError(t.detail||"OTP request failed",s.status)}return await s.json()})())}async verifyOtp(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",s=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(!s.ok){const t=await s.json();throw new DypaiError(t.detail||"OTP verification failed",s.status)}const i=await s.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.v(i)};return n.token&&this.A(n),n})())}async updateUser(t){return wrapAuthResponse((async()=>{if(!this.i)throw new DypaiError("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,s=await fetch(e,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(t)});if(!s.ok){const t=await s.json();throw new DypaiError(t.detail||"Update user failed",s.status)}const i=await s.json(),n=this.v(i);return this.T(n),n})())}k(t){this.u&&(clearTimeout(this.u),this.u=null);const e=t-Math.floor(Date.now()/1e3)-60;e>0?(console.log(`[DYPAI SDK] ⏱️ Refresco de sesión programado en ${e} segundos.`),this.u=setTimeout(()=>{this.refreshSession().catch(t=>{console.error("Error in auto-refresh:",t)})},1e3*e)):this.o&&this.refreshSession().catch(()=>{})}v(t){if(!t)return console.warn("[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{};const e=t.user||t,s=e.app_metadata||{},i=e.user_metadata||{};return{id:e.id,email:e.email,phone:e.phone,role:s.role||e.role,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:s,user_metadata:i,P:{name:s.role||"authenticated",weight:0},$:{app_id:"default"}}}A(t){t.token&&(this.i=t.token,this.o=t.refreshToken||null,this.h=t.expiresAt||null,this.T(t.user,t.token,t.refreshToken,t.expiresAt))}async T(t,e,s,i){this.t=t,e&&(this.i=e),void 0!==s&&(this.o=s||null),void 0!==i&&(this.h=i||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 console.warn("[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.",{hasToken:!!this.i,hasUser:!!this.t})}catch(t){console.error("[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}this.h&&this.k(this.h);const n=e?"SIGNED_IN":"USER_UPDATED";this.I(n)}async _(){console.log("[DYPAI SDK] 🧹 Limpiando sesión del estado y storage."),this.i=null,this.o=null,this.t=null,this.h=null,this.u&&(clearTimeout(this.u),this.u=null);try{await this.storage.removeItem(this.STORAGE_KEY)}catch(t){console.error("[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.I()}async S(){console.log("[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{const t=await this.storage.getItem(this.STORAGE_KEY);if(t){console.log("[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");const e=JSON.parse(t);this.i=e.access_token,this.o=e.refresh_token,this.h=e.expires_at,this.t=e.user}else{console.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}`),s=await this.storage.getItem(`dypai-auth-user-${t}`);if(!e||!s)return void console.log("[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{console.log("[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");const i=JSON.parse(s);this.i=e,this.t=i;const n=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),o=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.o=n,this.h=o?parseInt(o,10):null,await this.T(i,this.i||void 0,this.o||void 0,this.h||void 0);const r=[`dypai-auth-token-${t}`,`dypai-auth-refresh-token-${t}`,`dypai-auth-user-${t}`,`dypai-auth-expires-at-${t}`];for(const t of r)try{await this.storage.removeItem(t)}catch(t){}console.log("[DYPAI SDK] ✨ Migración completada con éxito.")}}const e=Math.floor(Date.now()/1e3);console.log(`[DYPAI SDK] 🕒 Token expira en: ${this.h}. Ahora es: ${e}. Diferencia: ${(this.h||0)-e}s`),this.h&&this.h<=e&&this.o?(console.log("[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente..."),await this.refreshSession().catch(t=>{console.error("[DYPAI SDK] ❌ El refresco inicial falló (posible error de red):",t)})):this.h&&(console.log("[DYPAI SDK] ✨ Sesión válida. Programando refresco futuro."),this.k(this.h)),this.I("SIGNED_IN")}catch(t){console.error("[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}m(){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}I(t="USER_UPDATED"){const e=this.m();console.log(`[DYPAI SDK] 📢 Notificando a ${this.l.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.l.forEach(s=>s(t,e))}handleSessionExpired(){this._()}}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}`)}}async function wrapResponse(t){try{return{data:await t,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t.message||"Error desconocido",t.status||500)}}}class UsersModule{constructor(t){this.api=t}async list(t={}){return wrapResponse(this.api.get("admin/users",{params:t}))}async create(t){return wrapResponse(this.api.post("admin/users",{body:t}))}async update(t,e){return wrapResponse(this.api.put(`admin/users/${t}`,{body:e}))}async delete(t){return wrapResponse(this.api.delete(`admin/users/${t}`))}}let customToastFunction=null;const fallbackToast=t=>{const{title:e,description:s,variant:i="default"}=t,n=`${"error"===i?"❌":"success"===i?"✅":"warning"===i?"⚠️":"info"===i?"ℹ️":"📢"} ${e}${s?`: ${s}`:""}`;"error"===i?console.error(n):"warning"===i?console.warn(n):console.log(n)},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,s,i,n,o,r,a){const c=getCompleteConfig(),h=a||null;if(!h&&!s.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url, key).");if("string"!=typeof s||!s.trim())throw new Error("Endpoint debe ser un string válido");let l;if(s.startsWith("http"))l=s;else{const t=h.replace(/\/+$/,"");l=s.startsWith("/")?t+s:t+"/api/v0/"+s}if(o&&Object.keys(o).length>0){const t=new URLSearchParams,e=(s,i)=>{null!=i&&(Array.isArray(i)?i.forEach((t,i)=>e(`${s}[${i}]`,t)):"object"==typeof i?Object.entries(i).forEach(([t,i])=>e(`${s}[${t}]`,i)):t.append(s,String(i)))};Object.entries(o).forEach(([t,s])=>e(t,s));const s=t.toString();s&&(l+=`?${s}`)}const u="GET"===e?`${e}:${l}:${JSON.stringify(i)}`:null;if(u&&pendingRequests.has(u))return pendingRequests.get(u);const p=i instanceof FormData,d={...c.headers||{},...p?{}:{"Content-Type":"application/json"},...t&&{Authorization:`Bearer ${t}`},...r&&{"x-api-key":r}},y={method:e,headers:d,credentials:"include"};i&&"GET"!==e&&"DELETE"!==e&&(y.body=p?i:JSON.stringify(i));const f=c.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!f)throw new Error("Fetch no disponible.");const w=(async()=>{const t=getCompleteConfig().toast||toast,h=(void 0!==n?n:!1!==c.showToasts)&&t;try{const u=await f(l,y);if(!u.ok){let l,p="Error en la petición";try{const t=await u.text();try{const e=JSON.parse(t);l=e,p=e.message||e.msg||e.error_description||e.error||p}catch{t.length<200&&(p=t)}}catch{}if(401===u.status){if(c.onTokenExpired){console.log("[DYPAI SDK] 🔄 Token caducado detectado. Intentando refrescar...");const t=await c.onTokenExpired();if(t)return console.log("[DYPAI SDK] ✅ Token refrescado con éxito. Reintentando petición..."),callApi(t,e,s,i,n,o,r,a)}c.onUnauthorized&&c.onUnauthorized()}throw h&&t({title:"Error",description:p,variant:"error"}),new DypaiError(p,u.status,void 0,l)}!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,w),w}function createMethod(t,e){return async(s,i,n)=>{const o=t();let r,a={};i&&"object"==typeof i&&("token"in i||"params"in i||"apiKey"in i)?(a=i,r=a.body):(r=i,a=n||{});const c=a.token||o.token||(globalTokenProvider?globalTokenProvider():"")||"",h=a.apiKey||o.apiKey;return callApi(c,e,s,r,a.showToasts,a.params,h,o.baseUrl)}}function createApiClient(t){const e=()=>"function"==typeof t?t():t;return{get:createMethodFromCtx(e,"GET"),post:createMethodFromCtx(e,"POST"),put:createMethodFromCtx(e,"PUT"),patch:createMethodFromCtx(e,"PATCH"),delete:createMethodFromCtx(e,"DELETE")}}function createMethodFromCtx(t,e){return async(s,i,n)=>{const o=t(),r=await async function(t,e){let s,i={};t&&"object"==typeof t&&("token"in t||"params"in t||"apiKey"in t)?(i=t,s=i.body):(s=t,i=e||{});let n=i.token;return!n&&globalTokenProvider&&(n=globalTokenProvider()||""),n=n||"",{token:n,apiKey:i.apiKey,body:s,params:i.params,showToasts:i.showToasts}}(i,n),a=r.token||o.token||"",c=r.apiKey||o.apiKey;return callApi(a,e,s,r.body,r.showToasts,r.params,c,o.baseUrl)}}class StorageModule{constructor(t){this.api=t}from(t){return new StorageBucketBuilder(t,this.api)}}class StorageBucketBuilder{constructor(t,e){this.bucketName=t,this.api=e}async upload(t,e,s={}){return wrapStorageResponse(async()=>{const i=s.uploadEndpoint||`storage_upload_${this.bucketName}`,n={file_path:t,...s.params},o=await async function(t,e,s,i){const n=await t.post(e,{file_path:s.name,content_type:s.type||"application/octet-stream",size_bytes:s.size,confirm:!1,...i?.params||{}});if(!n||!n.upload_url)throw new DypaiError("El workflow no devolvió una URL de subida válida (¿falta get_upload_url?)",400);const{upload_url:o,method:r="PUT",headers:a={},file_path:c}=n;i?.onProgress&&i.onProgress(10);const h=await fetch(o,{method:r,headers:{"Content-Type":s.type||"application/octet-stream",...a},body:s});if(!h.ok)throw new DypaiError("Error en la subida directa a la nube",h.status);i?.onProgress&&i.onProgress(90);const l=i?.confirmEndpoint||e,u=await t.post(l,{...i?.params,bucket:n.bucket||i?.params?.bucket,file_path:c,filename:s.name,content_type:s.type||"application/octet-stream",size_bytes:s.size,confirm:!0});return i?.onProgress&&i.onProgress(100),u}(this.api,i,e,{confirmEndpoint:s.confirmEndpoint,params:n,onProgress:s.onProgress});return o})}async download(t,e={}){return wrapStorageResponse(async()=>{const s=e.downloadEndpoint||`storage_download_${this.bucketName}`,i={bucket:this.bucketName,file_path:t,...e.params};await async function(t,e,s,i){const n=i?.method,o=await("GET"===n?t.get(e,{params:i?.params}):t.post(e,s,{params:i?.params}));if(o instanceof Blob){const t=window.URL.createObjectURL(o),e=document.createElement("a");e.href=t,e.download=i?.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;"string"==typeof t&&window.open(t,"_blank")}}(this.api,s,i,{fileName:e.fileName,method:e.method||"POST"})})}async createSignedUrl(t,e=15,s={}){return wrapStorageResponse(async()=>{const i=s.endpoint||`storage_signed_url_${this.bucketName}`,n={bucket:this.bucketName,file_path:t,expires_minutes:e,...s.params},o=await this.api.post(i,n);return{signedUrl:o.signed_url||o.signedUrl||o.url,expiresIn:o.expires_in||o.expiresIn||e,path:t}})}getPublicUrl(t){const e=t.replace(/^\/+/,"");return{data:{publicUrl:`${this.api.baseUrl||""}/storage/v1/render/public/${this.bucketName}/${e}`}}}async list(t="",e={}){return wrapStorageResponse(async()=>{const s=e.endpoint||`storage_list_${this.bucketName}`,i={bucket:this.bucketName,prefix:t||e.prefix||"",limit:e.limit,offset:e.offset,sort_by:e.sortBy,order:e.order,search:e.search,...e.params},n=await this.api.post(s,i);return{data:n.data||n.files||[],total:n.total,hasMore:n.has_more||n.hasMore||!1}})}async remove(t,e={}){return wrapStorageResponse(async()=>{const s=e.endpoint||`storage_delete_${this.bucketName}`,i=Array.isArray(t)?t:[t],n={bucket:this.bucketName,paths:i,...e.params};return await this.api.post(s,n),{success:!0}})}async move(t,e,s={}){return wrapStorageResponse(async()=>{const i=s.endpoint||`storage_move_${this.bucketName}`,n={bucket:this.bucketName,from_path:t,to_path:e,...s.params};return await this.api.post(i,n),{success:!0}})}async copy(t,e,s={}){return wrapStorageResponse(async()=>{const i=s.endpoint||`storage_copy_${this.bucketName}`,n={bucket:this.bucketName,from_path:t,to_path:e,...s.params};return await this.api.post(i,n),{success:!0}})}}async function wrapStorageResponse(t){try{return{data:await t(),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t.message||"Error de storage",t.status||500)}}}class DypaiClient{constructor(t){const{baseUrl:e,apiKey:s}=t,i=t.auth?.storageKey||t.storageKey;t.global&&function(t){serviceConfig={...serviceConfig,...t}}(t.global),this.auth=new AuthModule({baseUrl:e,apiKey:s,storageKey:i,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const n={get:createMethod(o=()=>({token:this.auth.token,apiKey:s,baseUrl:e}),"GET"),post:createMethod(o,"POST"),put:createMethod(o,"PUT"),patch:createMethod(o,"PATCH"),delete:createMethod(o,"DELETE")};var o;this.auth.api=n,this.db=new DataModule(n),this.users=new UsersModule(n),this.storage=new StorageModule(n),this.api=createApiClient(()=>({token:this.auth.token||"",apiKey:s,baseUrl:e})),setTokenProvider(()=>this.auth.token),configureApiService({onTokenExpired:async()=>{const t=await this.auth.refreshSession();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.PACKAGE_INFO={name:"@dypai-ai/client-sdk",version:"0.0.1",description:"Cliente JavaScript para Dypai Engine",features:["API REST autenticada","Smart Storage (Upload/Download delegado)","Gestión de usuarios (Admin)"]},exports.callApi=callApi,exports.configureApiService=configureApiService,exports.configureDypaiServices=function(t){globalConfig={...globalConfig,toast:toast,...t},applyConfigToAllServices()},exports.createApiClient=createApiClient,exports.createClient=function(t,e,s){if(!t||!e)throw new Error("createClient() requiere URL y API Key");return new DypaiClient({baseUrl:t,apiKey:e,...s})},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,s)=>t({title:e,description:s,variant:"success"}),toastError:(e,s)=>t({title:e,description:s,variant:"error"}),toastWarning:(e,s)=>t({title:e,description:s,variant:"warning"}),toastInfo:(e,s)=>t({title:e,description:s,variant:"info"})}};
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@dypai-ai/client-sdk",
3
+ "version": "0.0.1",
4
+ "description": "Cliente JavaScript para Dypai Engine",
5
+ "type": "module",
6
+ "private": false,
7
+ "main": "dist/index.js",
8
+ "module": "dist/index.esm.js",
9
+ "types": "dist/index.d.ts",
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "rollup -c",
15
+ "build:watch": "rollup -c -w",
16
+ "dev": "NODE_ENV=development rollup -c -w",
17
+ "dev:watch": "NODE_ENV=development rollup -c -w",
18
+ "dev:test": "vite",
19
+ "type-check": "tsc --noEmit",
20
+ "lint": "eslint src --ext .ts,.tsx",
21
+ "lint:fix": "eslint src --ext .ts,.tsx --fix",
22
+ "test": "jest",
23
+ "test:watch": "jest --watch",
24
+ "clean": "rimraf dist",
25
+ "postbuild_DISABLED": "node scripts/obfuscate-dts.cjs",
26
+ "prepublishOnly": "npm run clean && npm run build && npm run type-check && 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.')\"",
27
+ "postpublish": "node -e \"if(require('fs').existsSync('README.md.backup')) { require('fs').copyFileSync('README.md.backup', 'README.md'); require('fs').unlinkSync('README.md.backup'); }\"",
28
+ "publish:public": "npm publish --access public"
29
+ },
30
+ "keywords": [
31
+ "api",
32
+ "http",
33
+ "client",
34
+ "service"
35
+ ],
36
+ "author": "DYPAI Team",
37
+ "license": "MIT",
38
+ "peerDependencies": {
39
+ "react": ">=16.8.0",
40
+ "react-dom": ">=16.8.0"
41
+ },
42
+ "devDependencies": {
43
+ "@rollup/plugin-commonjs": "^25.0.0",
44
+ "@rollup/plugin-node-resolve": "^15.0.0",
45
+ "@rollup/plugin-terser": "^0.4.4",
46
+ "@rollup/plugin-typescript": "^11.0.0",
47
+ "@types/jest": "^29.0.0",
48
+ "@types/react": "^18.0.0",
49
+ "@types/react-dom": "^18.0.0",
50
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
51
+ "@typescript-eslint/parser": "^6.0.0",
52
+ "dotenv": "^17.2.2",
53
+ "eslint": "^8.0.0",
54
+ "eslint-plugin-react": "^7.0.0",
55
+ "eslint-plugin-react-hooks": "^4.0.0",
56
+ "jest": "^29.0.0",
57
+ "rimraf": "^5.0.0",
58
+ "rollup": "^4.0.0",
59
+ "rollup-plugin-dts": "^6.0.0",
60
+ "tslib": "^2.8.1",
61
+ "typescript": "^5.0.0",
62
+ "vite": "^7.1.5"
63
+ },
64
+ "publishConfig": {
65
+ "access": "public"
66
+ },
67
+ "engines": {
68
+ "node": ">=16.0.0"
69
+ }
70
+ }