@dypai-ai/client-sdk 1.1.0 → 1.2.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/README.md CHANGED
@@ -322,6 +322,100 @@ await dypai.api.download('storage_files', {
322
322
 
323
323
  ---
324
324
 
325
+ ## Direct Database Access (Admin)
326
+
327
+ > **Server-side only.** This is for scripts, migrations, seeds, and backend code.
328
+ > Never expose the `serviceRoleKey` in browser/client-side code.
329
+ > For end-user data access, use [Endpoints](#endpoints-custom-api) or [Database CRUD](#database-crud).
330
+
331
+ Direct database access bypasses endpoints and workflows. It requires a `serviceRoleKey`:
332
+
333
+ ```ts
334
+ import { createClient } from '@dypai-ai/client-sdk';
335
+
336
+ // Server-side only (Node.js, Bun, Deno)
337
+ const dypai = createClient('https://your-project.dypai.ai', {
338
+ serviceRoleKey: process.env.DYPAI_SERVICE_ROLE_KEY,
339
+ });
340
+ ```
341
+
342
+ ### Select with Filters
343
+
344
+ ```ts
345
+ const { data } = await dypai.db.direct
346
+ .from('products')
347
+ .eq('category', 'electronics')
348
+ .gt('price', 50)
349
+ .orderBy('price', 'DESC')
350
+ .limit(20)
351
+ .select();
352
+ ```
353
+
354
+ ### Bulk Insert
355
+
356
+ ```ts
357
+ // Auto-chunked at 1,000 rows per request
358
+ const { data, error } = await dypai.db.direct
359
+ .from('products')
360
+ .insert(tenThousandRows);
361
+ ```
362
+
363
+ ### Update with Filters
364
+
365
+ ```ts
366
+ await dypai.db.direct
367
+ .from('products')
368
+ .eq('category', 'deprecated')
369
+ .update({ active: false });
370
+ ```
371
+
372
+ ### Delete with Filters
373
+
374
+ ```ts
375
+ await dypai.db.direct
376
+ .from('products')
377
+ .eq('id', 'abc123')
378
+ .delete();
379
+ ```
380
+
381
+ ### Upsert
382
+
383
+ ```ts
384
+ await dypai.db.direct
385
+ .from('products')
386
+ .upsert({ id: 'prod_1', name: 'Widget', price: 12.99 }, 'id');
387
+ ```
388
+
389
+ ### Raw SQL
390
+
391
+ ```ts
392
+ // Migrations
393
+ await dypai.db.direct.sql('ALTER TABLE products ADD COLUMN sku TEXT');
394
+
395
+ // Queries with bind parameters ($1, $2, ...)
396
+ const { data } = await dypai.db.direct.sql(
397
+ 'SELECT category, COUNT(*) as count FROM products WHERE price > $1 GROUP BY category',
398
+ [25]
399
+ );
400
+ ```
401
+
402
+ ### Available Filters
403
+
404
+ | Method | SQL |
405
+ |--------|-----|
406
+ | `.eq(col, val)` | `col = val` |
407
+ | `.neq(col, val)` | `col != val` |
408
+ | `.gt(col, val)` | `col > val` |
409
+ | `.gte(col, val)` | `col >= val` |
410
+ | `.lt(col, val)` | `col < val` |
411
+ | `.lte(col, val)` | `col <= val` |
412
+ | `.like(col, val)` | `col ILIKE %val%` |
413
+ | `.in(col, [a,b])` | `col IN (a, b)` |
414
+ | `.isNull(col)` | `col IS NULL` |
415
+ | `.notNull(col)` | `col IS NOT NULL` |
416
+
417
+ ---
418
+
325
419
  ## User Management (Admin)
326
420
 
327
421
  Manage application users (requires `manage_users` permission):
package/dist/index.d.ts CHANGED
@@ -235,26 +235,33 @@ interface ApiServiceConfig {
235
235
 
236
236
  /** Filter condition for structured queries. */
237
237
  interface FilterCondition {
238
- column: string;
238
+ column?: string;
239
239
  operator: string;
240
240
  value?: any;
241
+ or?: FilterCondition[];
241
242
  }
242
243
  /**
243
244
  * Direct database access module — bypasses endpoints/workflows.
244
245
  * Requires `serviceRoleKey` (admin-level access).
245
246
  *
247
+ * **WARNING: This is for server-side use only (scripts, migrations, seeds, backend).
248
+ * NEVER expose the serviceRoleKey in browser/client-side code.
249
+ * For end-user data access, use `client.db.from()` (which goes through endpoints with proper auth).**
250
+ *
246
251
  * @typeParam TDatabase - Optional type map of table names to row types.
247
252
  *
248
253
  * @example
249
254
  * ```ts
250
- * const client = createClient(url, { serviceRoleKey: 'sk_...' });
255
+ * // Server-side only (Node.js, Bun, Deno)
256
+ * const client = createClient(url, {
257
+ * serviceRoleKey: process.env.DYPAI_SERVICE_ROLE_KEY,
258
+ * });
251
259
  *
252
- * // CRUD
253
- * await client.db.direct.from('products').insert([...rows]);
254
- * const { data } = await client.db.direct.from('products').gt('price', 50).select();
260
+ * // Bulk import
261
+ * await client.db.direct.from('products').insert(csvRows);
255
262
  *
256
- * // Raw SQL
257
- * await client.db.direct.sql('SELECT count(*) FROM products WHERE price > $1', [50]);
263
+ * // Migration
264
+ * await client.db.direct.sql('ALTER TABLE products ADD COLUMN sku TEXT');
258
265
  * ```
259
266
  */
260
267
  declare class DirectDBModule<TDatabase = any> {
@@ -286,28 +293,18 @@ declare class DirectDBModule<TDatabase = any> {
286
293
  }
287
294
  /**
288
295
  * Fluent query builder for direct database access.
289
- * Supports chainable filters (`.eq()`, `.gt()`, etc.) and terminal operations
290
- * (`.select()`, `.insert()`, `.update()`, `.delete()`, `.upsert()`).
296
+ * Supports chainable filters, column selection, pagination, and all CRUD operations.
291
297
  *
292
298
  * @example
293
299
  * ```ts
294
- * // Select with filters
295
300
  * const { data } = await client.db.direct
296
301
  * .from('products')
302
+ * .select('id, name, price')
297
303
  * .eq('category', 'electronics')
298
304
  * .gt('price', 50)
299
305
  * .orderBy('price', 'DESC')
300
306
  * .limit(20)
301
307
  * .select();
302
- *
303
- * // Bulk insert (auto-chunked at 1000 rows)
304
- * await client.db.direct.from('products').insert(tenThousandRows);
305
- *
306
- * // Update with filter
307
- * await client.db.direct.from('products').eq('active', false).update({ archived: true });
308
- *
309
- * // Delete with filter
310
- * await client.db.direct.from('products').eq('id', 'abc').delete();
311
308
  * ```
312
309
  */
313
310
  declare class DirectQueryBuilder {
@@ -315,6 +312,7 @@ declare class DirectQueryBuilder {
315
312
  private api;
316
313
  private _schema;
317
314
  private _filters;
315
+ private _columns?;
318
316
  private _limit;
319
317
  private _offset;
320
318
  private _sortBy?;
@@ -336,12 +334,57 @@ declare class DirectQueryBuilder {
336
334
  lte(column: string, value: any): this;
337
335
  /** Case-insensitive LIKE: `column ILIKE %value%` */
338
336
  like(column: string, value: string): this;
339
- /** IN array: `column IN (values)` */
340
- in(column: string, values: any[]): this;
337
+ /** Case-insensitive LIKE (alias for `.like()`). */
338
+ ilike(column: string, value: string): this;
341
339
  /** IS NULL: `column IS NULL` */
342
340
  isNull(column: string): this;
341
+ /** Alias for `.isNull()` — matches Supabase convention `is(column, null)`. */
342
+ is(column: string, _value: null): this;
343
343
  /** IS NOT NULL: `column IS NOT NULL` */
344
344
  notNull(column: string): this;
345
+ /** IN array: `column IN (values)` */
346
+ in(column: string, values: any[]): this;
347
+ /** Contains: `column @> value` (array/JSONB contains) */
348
+ contains(column: string, value: any): this;
349
+ /** Contained by: `column <@ value` (array/JSONB is subset of) */
350
+ containedBy(column: string, value: any): this;
351
+ /** Overlaps: `column && value` (arrays share elements) */
352
+ overlaps(column: string, value: any[]): this;
353
+ /** Full-text search: `to_tsvector(column) @@ plainto_tsquery(value)` */
354
+ textSearch(column: string, query: string): this;
355
+ /** Phrase search: `to_tsvector(column) @@ phraseto_tsquery(value)` */
356
+ phraseSearch(column: string, query: string): this;
357
+ /**
358
+ * Negate a filter: `NOT (column op value)`.
359
+ *
360
+ * @example
361
+ * ```ts
362
+ * .not('status', 'eq', 'deleted') // status != 'deleted'
363
+ * .not('name', 'like', 'test') // name NOT ILIKE '%test%'
364
+ * .not('category', 'in', ['a', 'b']) // category NOT IN ('a', 'b')
365
+ * .not('deleted_at', 'is_null', null) // deleted_at IS NOT NULL
366
+ * ```
367
+ */
368
+ not(column: string, operator: string, value?: any): this;
369
+ /**
370
+ * OR filter group: at least one condition must match.
371
+ *
372
+ * @example
373
+ * ```ts
374
+ * // WHERE (status = 'active' OR status = 'pending')
375
+ * .or([
376
+ * { column: 'status', operator: 'eq', value: 'active' },
377
+ * { column: 'status', operator: 'eq', value: 'pending' },
378
+ * ])
379
+ *
380
+ * // WHERE (price > 100 OR category = 'premium')
381
+ * .or([
382
+ * { column: 'price', operator: 'gt', value: 100 },
383
+ * { column: 'category', operator: 'eq', value: 'premium' },
384
+ * ])
385
+ * ```
386
+ */
387
+ or(conditions: FilterCondition[]): this;
345
388
  /** Set maximum rows to return (default: 100, max: 10000). */
346
389
  limit(n: number): this;
347
390
  /** Set offset for pagination. */
@@ -350,9 +393,52 @@ declare class DirectQueryBuilder {
350
393
  orderBy(column: string, direction?: 'ASC' | 'DESC'): this;
351
394
  /**
352
395
  * Fetch rows matching the current filters.
353
- * @param filters - Optional shorthand filters: `{ active: true }` adds `eq('active', true)`.
396
+ *
397
+ * @param columnsOrFilters - Either a column selection string (`'id, name, price'`)
398
+ * or a shorthand filter object (`{ active: true }`).
399
+ *
400
+ * @example
401
+ * ```ts
402
+ * // All columns
403
+ * .select()
404
+ *
405
+ * // Specific columns
406
+ * .select('id, name, price')
407
+ *
408
+ * // Shorthand filters (legacy)
409
+ * .select({ active: true })
410
+ * ```
354
411
  */
355
- select(filters?: Record<string, any>): Promise<DypaiResponse<any>>;
412
+ select(columnsOrFilters?: string | Record<string, any>): Promise<DypaiResponse<any>>;
413
+ /**
414
+ * Fetch a single row. Returns the first match or error if none found.
415
+ *
416
+ * @example
417
+ * ```ts
418
+ * const { data: user } = await client.db.direct.from('users').eq('id', userId).single();
419
+ * ```
420
+ */
421
+ single(): Promise<DypaiResponse<any>>;
422
+ /**
423
+ * Fetch a single row or null. Like `.single()` but returns `null` instead of error when no match.
424
+ *
425
+ * @example
426
+ * ```ts
427
+ * const { data: user } = await client.db.direct.from('users').eq('email', email).maybeSingle();
428
+ * // data is null if not found (no error)
429
+ * ```
430
+ */
431
+ maybeSingle(): Promise<DypaiResponse<any>>;
432
+ /**
433
+ * Count rows matching the current filters.
434
+ *
435
+ * @example
436
+ * ```ts
437
+ * const { data: count } = await client.db.direct.from('products').eq('active', true).count();
438
+ * console.log(count); // 42
439
+ * ```
440
+ */
441
+ count(): Promise<DypaiResponse<number>>;
356
442
  /**
357
443
  * Insert one row or many rows. Arrays over 1000 rows are auto-chunked.
358
444
  * @param data - A single row object or an array of row objects.
@@ -398,11 +484,19 @@ declare class DirectQueryBuilder {
398
484
  */
399
485
  declare class DataModule<TDatabase = any> {
400
486
  private api;
487
+ private _direct?;
401
488
  /**
402
489
  * Direct database access — CRUD and raw SQL without endpoints/workflows.
403
- * Only available when `serviceRoleKey` is passed to `createClient()`.
490
+ *
491
+ * **WARNING: This is admin-level access. Only use from server-side code
492
+ * (scripts, migrations, seeds, backend). NEVER expose the serviceRoleKey
493
+ * in client-side/browser code.**
494
+ *
495
+ * Requires `serviceRoleKey` in `createClient()` config. Throws if not configured.
404
496
  */
405
- direct?: DirectDBModule<TDatabase>;
497
+ get direct(): DirectDBModule<TDatabase>;
498
+ /** @internal */
499
+ set direct(value: DirectDBModule<TDatabase> | undefined);
406
500
  constructor(api: ApiClient);
407
501
  /**
408
502
  * Inicia una operación sobre una tabla/colección específica.
@@ -849,6 +943,9 @@ interface DypaiConfig {
849
943
  * Service role key for direct database access (admin-level).
850
944
  * When provided, enables `client.db.direct` for CRUD and raw SQL
851
945
  * without creating endpoints or workflows.
946
+ *
947
+ * **WARNING: Server-side only. Never expose this key in browser code.**
948
+ * For end-user data access, use endpoints with `client.db.from()` or `client.api`.
852
949
  */
853
950
  serviceRoleKey?: string;
854
951
  /** Opcional: ID para aislar localStorage entre múltiples clientes */
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.m=null,this.D=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.O(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 u=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(l)});if(!u.ok)throw await s(u,"Registration failed");const d=await u.json(),p=!d.access_token,f={token:d.access_token,refreshToken:d.refresh_token,expiresIn:d.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(d.expires_in||3600),user:this._(d),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,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.m)return n("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.m.promise;const e=new r;this.m=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.U());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.m=null}})(),e.promise}async U(){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.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.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.O(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.K(r),r})())}async setPassword(t){return this.updateUser({password:t})}startAutoRefresh(){this.stopAutoRefresh(),n("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.R(),this.D=setInterval(()=>this.R(),c.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.D&&(n("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.D),this.D=null)}async R(){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 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 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.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"),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),u={token:i,refreshToken:s||void 0,expiresIn:o,expiresAt:this.h,user:l};return await this.O(u),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}`)}}const u=1e3;class d{constructor(t){this.api=t}from(t){return new p(t,this.api)}async sql(t,e,i){return this.api.post("/api/v0/admin/db/sql",{query:t,params:e||[],...void 0!==i?.limit&&{limit:i.limit}})}}class p{constructor(t,e){this.table=t,this.api=e,this.C="public",this.L=[],this.j=100,this.q=0,this.M="ASC"}schema(t){return this.C=t,this}eq(t,e){return this.L.push({column:t,operator:"eq",value:e}),this}neq(t,e){return this.L.push({column:t,operator:"neq",value:e}),this}gt(t,e){return this.L.push({column:t,operator:"gt",value:e}),this}gte(t,e){return this.L.push({column:t,operator:"gte",value:e}),this}lt(t,e){return this.L.push({column:t,operator:"lt",value:e}),this}lte(t,e){return this.L.push({column:t,operator:"lte",value:e}),this}like(t,e){return this.L.push({column:t,operator:"ilike",value:e}),this}in(t,e){return this.L.push({column:t,operator:"in",value:e}),this}isNull(t){return this.L.push({column:t,operator:"is_null",value:null}),this}notNull(t){return this.L.push({column:t,operator:"not_null",value:null}),this}limit(t){return this.j=t,this}offset(t){return this.q=t,this}orderBy(t,e="ASC"){return this.J=t,this.M=e,this}async select(t){const e=[...this.L];if(t)for(const[i,n]of Object.entries(t))e.push({column:i,operator:"eq",value:n});return this.execute("select",{filters:e,limit:this.j,offset:this.q,sort_by:this.J,order:this.M})}async insert(t){if(Array.isArray(t)&&t.length>u){const e=[];for(let i=0;i<t.length;i+=u){const n=t.slice(i,i+u),{data:s,error:r}=await this.execute("insert",{data:n,mode:"bulk"});if(r)return{data:null,error:r};s&&e.push(...Array.isArray(s)?s:[s])}return{data:e,error:null}}const e=Array.isArray(t)?"bulk":"single";return this.execute("insert",{data:t,mode:e})}async update(e){return 0===this.L.length?{data:null,error:new t("Update requires at least one filter. Use .eq(), .in(), etc.",400)}:this.execute("update",{data:e,filters:this.L})}async delete(){return 0===this.L.length?{data:null,error:new t("Delete requires at least one filter.",400)}:this.execute("delete",{filters:this.L})}async upsert(t,e="id"){return this.execute("upsert",{data:t,conflict_column:e})}async execute(t,e){return this.api.post("/api/v0/admin/db/query",{operation:t,schema_name:this.C,table_name:this.table,...e})}}class f{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 w=null;function y(t){w=t}const S=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 g(){const t=w||S;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 m=t=>(w||S)(t),D=(t,e)=>m({title:t,description:e,variant:"success"}),v=(t,e)=>m({title:t,description:e,variant:"error"}),A=(t,e)=>m({title:t,description:e,variant:"warning"}),I=(t,e)=>m({title:t,description:e,variant:"info"});let P={},k=null;function T(t){k=t}function b(t){P={...P,...t}}const $=new Map;function E(){let t=P;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...P}}catch(t){}return t}async function _(e,i,n,s,r,o,a,c,h){const l=E(),u=c||null;if(!u&&!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 d;if(n.startsWith("http"))d=n;else{const t=u.replace(/\/+$/,"");d=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&&(d+=`?${i}`)}const p="GET"===i?`${i}:${d}:${JSON.stringify(s)}`:null;if(p&&$.has(p))return $.get(p);const f=s instanceof FormData,w={...l.headers||{},...f?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...a&&{"x-api-key":a}},y={method:i,headers:w,credentials:"include"};s&&"GET"!==i&&"DELETE"!==i&&(y.body=f?s:JSON.stringify(s));const S=l.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!S)throw new Error("Fetch no disponible.");const g=(async()=>{const e=E().toast||m,u=(void 0!==r?r:!1!==l.showToasts)&&e;try{const p=await S(d,y);if(!p.ok){let d,f="Error en la petición";try{const t=await p.text();try{const e=JSON.parse(t);d=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 u&&e({title:"Error",description:f,variant:"error"}),new t(f,p.status,void 0,d)}!u||"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&&$.delete(p)}})();return p&&$.set(p,g),g}async function O(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 K(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 Y(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 Y(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 Y(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:u,storage_path:d}=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:u,storage_path:d,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 Y(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 U(e){const i={get:R(e,"GET"),post:R(e,"POST"),put:R(e,"PUT"),patch:R(e,"PATCH"),delete:R(e,"DELETE"),upload:async(e,n,s)=>{try{return{data:await K(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 O(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}function R(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||(k?k():"")||"",u=h.apiKey||a.apiKey;try{return{data:await _(l,i,s,c,h.showToasts,h.params,u,a.baseUrl),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Error desconocido",e?.status??500)}}}}function N(e){const i=()=>"function"==typeof e?e():e,n={get:C(i,"GET"),post:C(i,"POST"),put:C(i,"PUT"),patch:C(i,"PATCH"),delete:C(i,"DELETE"),upload:async(e,i,s)=>{try{return{data:await K(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 O(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 C(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&&k&&(r=k()||""),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 x{constructor(t){const{baseUrl:e,apiKey:i}=t,n=t.auth?.storageKey||t.storageKey;t.global&&function(t){P={...P,...t}}(t.global),this.auth=new c({baseUrl:e,apiKey:i,storageKey:n,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const s=U(()=>({token:this.auth.token,apiKey:i,baseUrl:e}));if(this.auth.api=s,this.db=new h(s),t.serviceRoleKey){const i=U(()=>({token:t.serviceRoleKey,baseUrl:e}));this.db.direct=new d(i)}if(this.users=new f(s),this.api=N(()=>({token:this.auth.token||"",apiKey:i,baseUrl:e})),T(()=>this.auth.token),"undefined"!=typeof window&&t.redirects){const e=t.redirects;this.auth.I?.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))}})}b({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 L(t,e,i){if(!t)throw new Error("createClient() requiere la URL base");return new x({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?i:e})}let j={};function q(t){j={...j,toast:m,...t},J()}function M(){return{...j}}function J(){try{const{configureApiService:t}=require("../services/ApiService");t(j)}catch(t){}}function z(){j={},J()}async function B(){J()}const G={name:"@dypai-ai/client-sdk",version:"1.1.0",description:"Official JavaScript/TypeScript SDK for DYPAI",features:["Authentication (email, OAuth, OTP)","Database CRUD","Direct database access (service role)","Custom endpoints (typed API)","File upload/download (Smart Upload)","User management (admin)","React hooks (useAuth, useEndpoint, useAction, useUpload)"]};export{d as DirectDBModule,p as DirectQueryBuilder,x as DypaiClient,t as DypaiError,G as PACKAGE_INFO,_ as callApi,b as configureApiService,q as configureDypaiServices,N as createApiClient,L as createClient,M as getGlobalConfig,B as reloadDypaiConfig,z as resetGlobalConfig,y as setToastFunction,T as setTokenProvider,m as toast,v as toastError,I as toastInfo,D as toastSuccess,A as toastWarning,g 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 r(e,i){let n={};try{n=await e.json()}catch{}const r=n.msg||n.error_description||n.message||i,s=n.error_code||n.error||n.code||void 0;return new t(r,e.status,s)}class s{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.m=!1,this.S=null,this.D=null,this.v=0,this.A=0,this.storage=t.storage||o,i=!!t.debug;const r=t.storageKey||this.deriveStorageKey(t.apiKey);n("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${r})`),this.STORAGE_KEY=`dypai-${r}-auth-session`,this.I=this.P().then(()=>{this.i&&(this.getUser().catch(()=>{}),this.startAutoRefresh());const t=this.p;this.p=null,t?setTimeout(()=>{this.m=!0,this.k(t)},0):this.m=!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 r(n,"Login failed");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 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:s,phone:o,password:a,user_data:c,...h}=t,l={email:s,phone:o,password:a,data:{...h,...c}};e?.redirectTo?l.redirect_to=e.redirectTo:"undefined"!=typeof window&&(l.redirect_to=`${window.location.origin}/`);const u=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(l)});if(!u.ok)throw await r(u,"Registration failed");const d=await u.json(),p=!d.access_token,f={token:d.access_token,refreshToken:d.refresh_token,expiresIn:d.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(d.expires_in||3600),user:this.$(d),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(),r=this.$(n);return this.O(r),r})())}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:r}=i,s=this.config.baseUrl||"http://localhost:8000",o=r?.length?`&scopes=${encodeURIComponent(r.join(" "))}`:"",a=`${s}/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.R("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 s=await fetch(`${i}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!s.ok)throw await r(s,"Recovery failed");return await s.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 r(i,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.S)return n("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.S.promise;const e=new s;this.S=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,r=setTimeout(()=>n.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:n.signal},async()=>(clearTimeout(r),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.Y());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.S=null}})(),e.promise}async Y(){try{const e=await this.storage.getItem(this.STORAGE_KEY);if(e){const t=JSON.parse(e),i=t.expires_at||0,r=Math.floor(Date.now()/1e3);if(t.access_token!==this.i&&i-r>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`,r=new AbortController,s=setTimeout(()=>r.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:r.signal})}finally{clearTimeout(s)}if(!o.ok){let e={};try{e=await o.json()}catch{}const i=o.status,r=e.error||e.code||"",s=(e.error_description||e.msg||"").toLowerCase(),a="invalid_grant"===r||s.includes("invalid refresh token")||s.includes("revoked");throw i>=400&&i<500&&a?(n("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.R(`refreshSession failed (${i}: ${r})`)):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,r=i?new t("Refresh token timeout (servidor no responde)",408):e instanceof t?e:new t(e.message||"Error refrescando sesión",401),s=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: ${s}ms (intento ${this.v})`):n("error","[DYPAI SDK] ❌ Refresh falló:",r.message,`(status: ${r.status}). Backoff: ${s}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.R(`${this.v} consecutive refresh failures`),this.v=0),{data:null,error:r}}}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 r=await n.json(),s={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 s.token&&await this.K(s),s})())}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 r=await n.json(),s=this.$(r);return this.O(s),s})())}async setPassword(t){return this.updateUser({password:t})}startAutoRefresh(){this.stopAutoRefresh(),n("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.U(),this.D=setInterval(()=>this.U(),c.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.D&&(n("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.D),this.D=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,r){this.t=t,e&&(this.i=e),void 0!==i&&(this.o=i||null),void 0!==r&&(this.h=r||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.m){const t=e?"SIGNED_IN":"USER_UPDATED";this.k(t)}}async R(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 r;n("log","[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");try{r=JSON.parse(i)}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.i=e,this.t=r;const s=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),o=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.o=s,this.h=o?parseInt(o,10):null,await this.O(r,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),r=this.h&&this.h<=i;if(n("log",`[DYPAI SDK] 🕒 Token expira en: ${this.h}. Ahora es: ${i}. Diferencia: ${(this.h||0)-i}s`),r&&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 r=e.get("refresh_token"),s=e.get("type"),o=parseInt(e.get("expires_in")||"3600",10);n("log",`[DYPAI SDK] 🔗 Auth callback detectado en URL (type: ${s||"unknown"}). Procesando...`);try{s&&sessionStorage.setItem("dypai-auth-callback-type",s),"recovery"!==s&&"invite"!==s||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=r||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),u={token:i,refreshToken:r||void 0,expiresIn:o,expiresAt:this.h,user:l};return await this.K(u),this.p="recovery"===s||"invite"===s?"PASSWORD_RECOVERY":"SIGNED_IN",n("log",`[DYPAI SDK] 🔑 Callback type=${s}. 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.R("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{get direct(){if(!this.C)throw new Error("client.db.direct is not available. Pass serviceRoleKey to createClient() to enable direct database access.\n\nExample:\n const client = createClient(url, { serviceRoleKey: process.env.DYPAI_SERVICE_ROLE_KEY });\n\nWARNING: serviceRoleKey grants admin-level access. Only use from server-side code (scripts, migrations, seeds). Never expose it in browser/client-side code.");return this.C}set direct(t){this.C=t}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}`)}}const u=1e3;class d{constructor(t){this.api=t}from(t){return new p(t,this.api)}async sql(t,e,i){return this.api.post("/api/v0/admin/db/sql",{query:t,params:e||[],...void 0!==i?.limit&&{limit:i.limit}})}}class p{constructor(t,e){this.table=t,this.api=e,this.L="public",this.j=[],this.q=100,this.M=0,this.J="ASC"}schema(t){return this.L=t,this}eq(t,e){return this.j.push({column:t,operator:"eq",value:e}),this}neq(t,e){return this.j.push({column:t,operator:"neq",value:e}),this}gt(t,e){return this.j.push({column:t,operator:"gt",value:e}),this}gte(t,e){return this.j.push({column:t,operator:"gte",value:e}),this}lt(t,e){return this.j.push({column:t,operator:"lt",value:e}),this}lte(t,e){return this.j.push({column:t,operator:"lte",value:e}),this}like(t,e){return this.j.push({column:t,operator:"ilike",value:e}),this}ilike(t,e){return this.like(t,e)}isNull(t){return this.j.push({column:t,operator:"is_null",value:null}),this}is(t,e){return this.isNull(t)}notNull(t){return this.j.push({column:t,operator:"not_null",value:null}),this}in(t,e){return this.j.push({column:t,operator:"in",value:e}),this}contains(t,e){return this.j.push({column:t,operator:"array_contains",value:e}),this}containedBy(t,e){return this.j.push({column:t,operator:"contained_by",value:e}),this}overlaps(t,e){return this.j.push({column:t,operator:"overlaps",value:e}),this}textSearch(t,e){return this.j.push({column:t,operator:"fts",value:e}),this}phraseSearch(t,e){return this.j.push({column:t,operator:"phfts",value:e}),this}not(t,e,i){return this.j.push({column:t,operator:"not",value:{operator:e,value:i}}),this}or(t){return this.j.push({operator:"or",or:t}),this}limit(t){return this.q=t,this}offset(t){return this.M=t,this}orderBy(t,e="ASC"){return this.B=t,this.J=e,this}async select(t){const e=[...this.j];let i=this.G;if("string"==typeof t)i=t;else if(t&&"object"==typeof t)for(const[i,n]of Object.entries(t))e.push({column:i,operator:"eq",value:n});return this.execute("select",{...i&&{columns:i},filters:e,limit:this.q,offset:this.M,sort_by:this.B,order:this.J})}async single(){const e=await this.select();if(e.error)return e;const i=e.data;return!i||Array.isArray(i)&&0===i.length?{data:null,error:new t("No rows found.",404)}:{data:Array.isArray(i)?i[0]:i,error:null}}async maybeSingle(){const t=await this.select();if(t.error)return t;const e=t.data;return!e||Array.isArray(e)&&0===e.length?{data:null,error:null}:{data:Array.isArray(e)?e[0]:e,error:null}}async count(){const t=await this.execute("select",{columns:"count(*) as count",filters:[...this.j],limit:1,offset:0});if(t.error)return t;const e=t.data;return{data:Array.isArray(e)&&e.length>0?Number(e[0].count):0,error:null}}async insert(t){if(Array.isArray(t)&&t.length>u){const e=[];for(let i=0;i<t.length;i+=u){const n=t.slice(i,i+u),{data:r,error:s}=await this.execute("insert",{data:n,mode:"bulk"});if(s)return{data:null,error:s};r&&e.push(...Array.isArray(r)?r:[r])}return{data:e,error:null}}const e=Array.isArray(t)?"bulk":"single";return this.execute("insert",{data:t,mode:e})}async update(e){return 0===this.j.length?{data:null,error:new t("Update requires at least one filter. Use .eq(), .in(), etc.",400)}:this.execute("update",{data:e,filters:this.j})}async delete(){return 0===this.j.length?{data:null,error:new t("Delete requires at least one filter.",400)}:this.execute("delete",{filters:this.j})}async upsert(t,e="id"){return this.execute("upsert",{data:t,conflict_column:e})}async execute(t,e){return this.api.post("/api/v0/admin/db/query",{operation:t,schema_name:this.L,table_name:this.table,...e})}}class f{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 w=null;function y(t){w=t}const m=t=>{const{title:e,description:i,variant:n="default"}=t,r=`${"error"===n?"❌":"success"===n?"✅":"warning"===n?"⚠️":"info"===n?"ℹ️":"📢"} ${e}${i?`: ${i}`:""}`;"error"===n?console.error(r):"warning"===n&&console.warn(r)};function S(){const t=w||m;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 g=t=>(w||m)(t),D=(t,e)=>g({title:t,description:e,variant:"success"}),v=(t,e)=>g({title:t,description:e,variant:"error"}),A=(t,e)=>g({title:t,description:e,variant:"warning"}),I=(t,e)=>g({title:t,description:e,variant:"info"});let P={},k=null;function b(t){k=t}function T(t){P={...P,...t}}const E=new Map;function _(){let t=P;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...P}}catch(t){}return t}async function $(e,i,n,r,s,o,a,c,h){const l=_(),u=c||null;if(!u&&!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 d;if(n.startsWith("http"))d=n;else{const t=u.replace(/\/+$/,"");d=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&&(d+=`?${i}`)}const p="GET"===i?`${i}:${d}:${JSON.stringify(r)}`:null;if(p&&E.has(p))return E.get(p);const f=r instanceof FormData,w={...l.headers||{},...f?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...a&&{"x-api-key":a}},y={method:i,headers:w,credentials:"include"};r&&"GET"!==i&&"DELETE"!==i&&(y.body=f?r:JSON.stringify(r));const m=l.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!m)throw new Error("Fetch no disponible.");const S=(async()=>{const e=_().toast||g,u=(void 0!==s?s:!1!==l.showToasts)&&e;try{const p=await m(d,y);if(!p.ok){let d,f="Error en la petición";try{const t=await p.text();try{const e=JSON.parse(t);d=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,r,s,o,a,c,!0)}catch(t){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",t)}throw u&&e({title:"Error",description:f,variant:"error"}),new t(f,p.status,void 0,d)}!u||"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&&E.delete(p)}})();return p&&E.set(p,S),S}async function K(t,e,i,n){const r=n?.method||(i?"POST":"GET"),{data:s,error:o}=await("GET"===r?t.get(e,{params:n?.params}):t.post(e,i,{params:n?.params}));if(o)throw o;if(s instanceof Blob){const t=window.URL.createObjectURL(s),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(s&&"object"==typeof s&&("url"in s||"signed_url"in s||"signedUrl"in s)){const t=s.url||s.signed_url||s.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 r=n?.fileName||i;if(r){const e=document.createElement("a");e.href=t,e.download=r,document.body.appendChild(e),e.click(),document.body.removeChild(e)}else window.open(t,"_blank")}}}async function O(e,i,n,r){const{data:s,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,...r?.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 R(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 R(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 R(t)}return null}(s);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:u,storage_path:d}=a;r?.onProgress&&r.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);r?.onProgress&&r.onProgress(90);const f=r?.confirmEndpoint||i,{data:w,error:y}=await e.post(f,{...r?.params,bucket:a.bucket||r?.params?.bucket,file_path:u,storage_path:d,filename:n.name,content_type:n.type||"application/octet-stream",size_bytes:n.size,confirm:!0,client_upload:!0});if(y)throw y;return r?.onProgress&&r.onProgress(100),w}function R(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 Y(e){const i={get:U(e,"GET"),post:U(e,"POST"),put:U(e,"PUT"),patch:U(e,"PATCH"),delete:U(e,"DELETE"),upload:async(e,n,r)=>{try{return{data:await O(i,e,n,r),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,r)=>{try{return await K(i,e,n,{...r,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}function U(e,i){const n="POST"===i||"PUT"===i||"PATCH"===i;return async(r,s,o)=>{const a=e();let c,h={};n?(c=s,h=o||{}):(h=s||{},c=void 0);const l=h.token||a.token||(k?k():"")||"",u=h.apiKey||a.apiKey;try{return{data:await $(l,i,r,c,h.showToasts,h.params,u,a.baseUrl),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Error desconocido",e?.status??500)}}}}function N(e){const i=()=>"function"==typeof e?e():e,n={get:C(i,"GET"),post:C(i,"POST"),put:C(i,"PUT"),patch:C(i,"PATCH"),delete:C(i,"DELETE"),upload:async(e,i,r)=>{try{return{data:await O(n,e,i,r),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,r)=>{try{return await K(n,e,i,{...r,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 C(e,i){return async(n,r,s)=>{const o=e(),a=await async function(t,e,i){let n,r={};"POST"===t||"PUT"===t||"PATCH"===t?(n=e,r=i||{}):(r=e||{},n=void 0);let s=r.token;return!s&&k&&(s=k()||""),s=s||"",{token:s,apiKey:r.apiKey,body:n,params:r.params,showToasts:r.showToasts}}(i,r,s),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 x{constructor(t){const{baseUrl:e,apiKey:i}=t,n=t.auth?.storageKey||t.storageKey;t.global&&function(t){P={...P,...t}}(t.global),this.auth=new c({baseUrl:e,apiKey:i,storageKey:n,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const r=Y(()=>({token:this.auth.token,apiKey:i,baseUrl:e}));if(this.auth.api=r,this.db=new h(r),t.serviceRoleKey)if("undefined"!=typeof window&&void 0!==window.document)console.error("[DYPAI SDK] ❌ serviceRoleKey detected in a browser environment. This is a security risk — the key grants admin-level database access. db.direct has been disabled. Use serviceRoleKey only from server-side code (Node.js, Bun, Deno, scripts).");else{const i=Y(()=>({token:t.serviceRoleKey,baseUrl:e}));this.db.direct=new d(i)}if(this.users=new f(r),this.api=N(()=>({token:this.auth.token||"",apiKey:i,baseUrl:e})),b(()=>this.auth.token),"undefined"!=typeof window&&t.redirects){const e=t.redirects;this.auth.I?.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))}})}T({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 L(t,e,i){if(!t)throw new Error("createClient() requiere la URL base");return new x({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?i:e})}let j={};function q(t){j={...j,toast:g,...t},J()}function M(){return{...j}}function J(){try{const{configureApiService:t}=require("../services/ApiService");t(j)}catch(t){}}function z(){j={},J()}async function B(){J()}const G={name:"@dypai-ai/client-sdk",version:"1.1.0",description:"Official JavaScript/TypeScript SDK for DYPAI",features:["Authentication (email, OAuth, OTP)","Database CRUD","Direct database access (service role)","Custom endpoints (typed API)","File upload/download (Smart Upload)","User management (admin)","React hooks (useAuth, useEndpoint, useAction, useUpload)"]};export{d as DirectDBModule,p as DirectQueryBuilder,x as DypaiClient,t as DypaiError,G as PACKAGE_INFO,$ as callApi,T as configureApiService,q as configureDypaiServices,N as createApiClient,L as createClient,M as getGlobalConfig,B as reloadDypaiConfig,z as resetGlobalConfig,y as setToastFunction,b as setTokenProvider,g as toast,v as toastError,I as toastInfo,D as toastSuccess,A as toastWarning,S as useToast};
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";class DypaiError extends Error{constructor(t,e=500,r,o){super(t),this.status=e,this.code=r,this.details=o,this.name="DypaiError"}}function normalizeUser(t){if(!t)return{};const e=t.user||t,r=e.app_metadata||{},o=e.user_metadata||{};return{id:e.id,email:e.email,phone:e.phone,role:r.role||o.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:r,user_metadata:o,roleDetails:{name:r.role||o.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 r={};try{r=await t.json()}catch{}const o=r.msg||r.error_description||r.message||e,i=r.error_code||r.error||r.code||void 0;return new DypaiError(o,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.m=null,this.S=null,this.A=0,this.v=0,this.storage=t.storage||localStorageAdapter,_debugEnabled=!!t.debug;const r=t.storageKey||this.deriveStorageKey(t.apiKey);_log("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${r})`),this.STORAGE_KEY=`dypai-${r}-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.k(t)},0):this.D=!0}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.I.bind(this)),window.addEventListener("focus",this.I.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 I(){"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.T();_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`,r={email:t.email||t.identifier||"",password:t.password},o=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(r)});if(!o.ok)throw await parseAuthError(o,"Login failed");const i=await o.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 r=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,{email:o,phone:i,password:n,user_data:s,...a}=t,l={email:o,phone:i,password:n,data:{...a,...s}};e?.redirectTo?l.redirect_to=e.redirectTo:"undefined"!=typeof window&&(l.redirect_to=`${window.location.origin}/`);const c=await fetch(r,{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 r=await e.json(),o=this.$(r);return this.R(o),o})())}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:r=window.location.href,scopes:o}=e,i=this.config.baseUrl||"http://localhost:8000",n=o?.length?`&scopes=${encodeURIComponent(o.join(" "))}`:"",s=`${i}/auth/v1/authorize?provider=${t}${n}&redirect_to=${encodeURIComponent(r)}`;window.location.href=s})())}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.U("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return wrapAuthResponse((async()=>{const r=this.config.baseUrl||"http://localhost:8000",o={email:t};e?.redirectTo?o.redirect_to=e.redirectTo:"undefined"!=typeof window&&(o.redirect_to=`${window.location.origin}/`);const i=await fetch(`${r}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)});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",r=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(!r.ok)throw await parseAuthError(r,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.m)return _log("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.m.promise;const t=new Deferred;this.m=t;const e=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const r=await async function(t,e,r){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const o=new AbortController,i=setTimeout(()=>o.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:o.signal},async()=>(clearTimeout(i),await r()))}catch(o){if("AbortError"===o.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await r();throw o}}return await r()}(e,AuthModule.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.O());t.resolve(r)}catch(e){const r=e instanceof DypaiError?e:new DypaiError(e.message||"Error refrescando sesión",401);t.resolve({data:null,error:r})}finally{this.m=null}})(),t.promise}async O(){try{const t=await this.storage.getItem(this.STORAGE_KEY);if(t){const e=JSON.parse(t),r=e.expires_at||0,o=Math.floor(Date.now()/1e3);if(e.access_token!==this.o&&r-o>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.A=0,this.k("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`,r=new AbortController,o=setTimeout(()=>r.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:r.signal})}finally{clearTimeout(o)}if(!i.ok){let t={};try{t=await i.json()}catch{}const e=i.status,r=t.error||t.code||"",o=(t.error_description||t.msg||"").toLowerCase(),n="invalid_grant"===r||o.includes("invalid refresh token")||o.includes("revoked");throw e>=400&&e<500&&n?(_log("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.U(`refreshSession failed (${e}: ${r})`)):_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(),s={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.A=0,await this.C(s),_log("log",`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${s.expiresAt} (en ${s.expiresIn}s). Refresh token actualizado: ${!!s.refreshToken}`),{data:s,error:null}}catch(t){this.A++,this.v=Date.now();const e=t instanceof DOMException&&"AbortError"===t.name,r=e?new DypaiError("Refresh token timeout (servidor no responde)",408):t instanceof DypaiError?t:new DypaiError(t.message||"Error refrescando sesión",401),o=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.A),AuthModule.MAX_RETRY_MS);return e?_log("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${AuthModule.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${o}ms (intento ${this.A})`):_log("error","[DYPAI SDK] ❌ Refresh falló:",r.message,`(status: ${r.status}). Backoff: ${o}ms (intento ${this.A})`),this.A>=AuthModule.MAX_REFRESH_FAILURES&&(_log("error",`[DYPAI SDK] 🗑️ ${this.A} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.U(`${this.A} consecutive refresh failures`),this.A=0),{data:null,error:r}}}async signInWithOtp(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",r=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(!r.ok){const t=await r.json();throw new DypaiError(t.detail||"OTP request failed",r.status)}return await r.json()})())}async verifyOtp(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",r=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(!r.ok){const t=await r.json();throw new DypaiError(t.detail||"OTP verification failed",r.status)}const o=await r.json(),i={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 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`,r=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(!r.ok){const t=await r.json();throw new DypaiError(t.detail||"Update user failed",r.status)}const o=await r.json(),i=this.$(o);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.K(),this.S=setInterval(()=>this.K(),AuthModule.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.S&&(_log("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.S),this.S=null)}async K(){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.A>0){const t=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.A),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,r,o){this.t=t,e&&(this.o=e),void 0!==r&&(this.i=r||null),void 0!==o&&(this.l=o||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.k(t)}}async U(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.k("SIGNED_OUT")}async P(t=!0){_log("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.M())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}`),r=await this.storage.getItem(`dypai-auth-user-${t}`);if(!e||!r)return void _log("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let o;_log("log","[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");try{o=JSON.parse(r)}catch{return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage corrupto (JSON inválido). Ignorando migración...")}if(!o||"object"!=typeof o)return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage con formato inválido. Ignorando migración...");this.o=e,this.t=o;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(o,this.o||void 0,this.i||void 0,this.l||void 0);const s=[`dypai-auth-token-${t}`,`dypai-auth-refresh-token-${t}`,`dypai-auth-user-${t}`,`dypai-auth-expires-at-${t}`];for(const t of s)try{await this.storage.removeItem(t)}catch(t){}_log("log","[DYPAI SDK] ✨ Migración completada con éxito.")}}const r=Math.floor(Date.now()/1e3),o=this.l&&this.l<=r;if(_log("log",`[DYPAI SDK] 🕒 Token expira en: ${this.l}. Ahora es: ${r}. Diferencia: ${(this.l||0)-r}s`),o&&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 M(){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",r=e.get("error_description")?.replace(/\+/g," ")||"Authentication error";return _log("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${r}`),this.u={code:t,message:r},window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.k("AUTH_ERROR"),!0}const r=e.get("access_token");if(!r)return!1;const o=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=r,this.i=o||null,this.l=Math.floor(Date.now()/1e3)+n;const s=this.config.baseUrl||"http://localhost:8000",a=await fetch(`${s}/auth/v1/user`,{headers:{Authorization:`Bearer ${r}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!a.ok)return _log("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${a.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.k("AUTH_ERROR"),!0;const l=await a.json(),c=this.$(l),h={token:r,refreshToken:o||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}}T(){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}k(t="USER_UPDATED"){const e=this.T();_log("log",`[DYPAI SDK] 📢 Notificando a ${this.h.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.h.forEach(r=>r(t,e))}handleSessionExpired(){this.U("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 DirectDBModule{constructor(t){this.api=t}from(t){return new DirectQueryBuilder(t,this.api)}async sql(t,e,r){return this.api.post("/api/v0/admin/db/sql",{query:t,params:e||[],...void 0!==r?.limit&&{limit:r.limit}})}}class DirectQueryBuilder{constructor(t,e){this.table=t,this.api=e,this.Y="public",this.N=[],this.L=100,this.q=0,this.j="ASC"}schema(t){return this.Y=t,this}eq(t,e){return this.N.push({column:t,operator:"eq",value:e}),this}neq(t,e){return this.N.push({column:t,operator:"neq",value:e}),this}gt(t,e){return this.N.push({column:t,operator:"gt",value:e}),this}gte(t,e){return this.N.push({column:t,operator:"gte",value:e}),this}lt(t,e){return this.N.push({column:t,operator:"lt",value:e}),this}lte(t,e){return this.N.push({column:t,operator:"lte",value:e}),this}like(t,e){return this.N.push({column:t,operator:"ilike",value:e}),this}in(t,e){return this.N.push({column:t,operator:"in",value:e}),this}isNull(t){return this.N.push({column:t,operator:"is_null",value:null}),this}notNull(t){return this.N.push({column:t,operator:"not_null",value:null}),this}limit(t){return this.L=t,this}offset(t){return this.q=t,this}orderBy(t,e="ASC"){return this.B=t,this.j=e,this}async select(t){const e=[...this.N];if(t)for(const[r,o]of Object.entries(t))e.push({column:r,operator:"eq",value:o});return this.execute("select",{filters:e,limit:this.L,offset:this.q,sort_by:this.B,order:this.j})}async insert(t){if(Array.isArray(t)&&t.length>1e3){const e=[];for(let r=0;r<t.length;r+=1e3){const o=t.slice(r,r+1e3),{data:i,error:n}=await this.execute("insert",{data:o,mode:"bulk"});if(n)return{data:null,error:n};i&&e.push(...Array.isArray(i)?i:[i])}return{data:e,error:null}}const e=Array.isArray(t)?"bulk":"single";return this.execute("insert",{data:t,mode:e})}async update(t){return 0===this.N.length?{data:null,error:new DypaiError("Update requires at least one filter. Use .eq(), .in(), etc.",400)}:this.execute("update",{data:t,filters:this.N})}async delete(){return 0===this.N.length?{data:null,error:new DypaiError("Delete requires at least one filter.",400)}:this.execute("delete",{filters:this.N})}async upsert(t,e="id"){return this.execute("upsert",{data:t,conflict_column:e})}async execute(t,e){return this.api.post("/api/v0/admin/db/query",{operation:t,schema_name:this.Y,table_name:this.table,...e})}}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 r=await this.api.put(`admin/users/${t}`,e);return r.data&&(r.data=normalizeUser(r.data)),r}async delete(t){return this.api.delete(`admin/users/${t}`)}}let customToastFunction=null;const fallbackToast=t=>{const{title:e,description:r,variant:o="default"}=t,i=`${"error"===o?"❌":"success"===o?"✅":"warning"===o?"⚠️":"info"===o?"ℹ️":"📢"} ${e}${r?`: ${r}`:""}`;"error"===o?console.error(i):"warning"===o&&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,r,o,i,n,s,a,l){const c=getCompleteConfig(),h=a||null;if(!h&&!r.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof r||!r.trim())throw new Error("Endpoint debe ser un string válido");let u;if(r.startsWith("http"))u=r;else{const t=h.replace(/\/+$/,"");u=r.startsWith("/")?t+r:t+"/api/v0/"+r}if(n&&Object.keys(n).length>0){const t=new URLSearchParams,e=(r,o)=>{null!=o&&(Array.isArray(o)?o.forEach((t,o)=>e(`${r}[${o}]`,t)):"object"==typeof o?Object.entries(o).forEach(([t,o])=>e(`${r}[${t}]`,o)):t.append(r,String(o)))};Object.entries(n).forEach(([t,r])=>e(t,r));const r=t.toString();r&&(u+=`?${r}`)}const d="GET"===e?`${e}:${u}:${JSON.stringify(o)}`:null;if(d&&pendingRequests.has(d))return pendingRequests.get(d);const p=o instanceof FormData,f={...c.headers||{},...p?{}:{"Content-Type":"application/json"},...t&&{Authorization:`Bearer ${t}`},...s&&{"x-api-key":s}},y={method:e,headers:f,credentials:"include"};o&&"GET"!==e&&"DELETE"!==e&&(y.body=p?o:JSON.stringify(o));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,r,o,i,n,s,a,!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,r,o){const i=o?.method||(r?"POST":"GET"),{data:n,error:s}=await("GET"===i?t.get(e,{params:o?.params}):t.post(e,r,{params:o?.params}));if(s)throw s;if(n instanceof Blob){const t=window.URL.createObjectURL(n),e=document.createElement("a");e.href=t,e.download=o?.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 r;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(r=t[1].replace(/"/g,""))}const i=o?.fileName||r;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,r,o){const{data:i,error:n}=await t.post(e,{file_path:r.name,content_type:r.type||"application/octet-stream",size_bytes:r.size,confirm:!1,client_upload:!0,...o?.params||{}});if(n)throw n;const s=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 r=[t.data,t.result,t.output,t.payload].filter(Boolean);for(const t of r)if(t&&(t.upload_url||t.uploadUrl||t.url))return normalizeUploadPayload(t);const o=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of o){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(!s?.upload_url)throw new DypaiError("The workflow did not return a valid upload URL (missing storage upload node?)",400);const{upload_url:a,method:l="PUT",headers:c={},file_path:h,storage_path:u}=s;o?.onProgress&&o.onProgress(10);const d=await fetch(a,{method:l,headers:{"Content-Type":r.type||"application/octet-stream",...c},body:r});if(!d.ok)throw new DypaiError("Direct upload to cloud storage failed",d.status);o?.onProgress&&o.onProgress(90);const p=o?.confirmEndpoint||e,{data:f,error:y}=await t.post(p,{...o?.params,bucket:s.bucket||o?.params?.bucket,file_path:h,storage_path:u,filename:r.name,content_type:r.type||"application/octet-stream",size_bytes:r.size,confirm:!0,client_upload:!0});if(y)throw y;return o?.onProgress&&o.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 createInternalApiClient(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,r,o)=>{try{return{data:await handleSmartUpload(e,t,r,o),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Upload failed",t?.status??500)}}},download:async(t,r,o)=>{try{return await handleSmartDownload(e,t,r,{...o,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}function createMethod(t,e){const r="POST"===e||"PUT"===e||"PATCH"===e;return async(o,i,n)=>{const s=t();let a,l={};r?(a=i,l=n||{}):(l=i||{},a=void 0);const c=l.token||s.token||(globalTokenProvider?globalTokenProvider():"")||"",h=l.apiKey||s.apiKey;try{return{data:await callApi(c,e,o,a,l.showToasts,l.params,h,s.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,r={get:createMethodFromCtx(e,"GET"),post:createMethodFromCtx(e,"POST"),put:createMethodFromCtx(e,"PUT"),patch:createMethodFromCtx(e,"PATCH"),delete:createMethodFromCtx(e,"DELETE"),upload:async(t,e,o)=>{try{return{data:await handleSmartUpload(r,t,e,o),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,o)=>{try{return await handleSmartDownload(r,t,e,{...o,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 r}function createMethodFromCtx(t,e){return async(r,o,i)=>{const n=t(),s=await async function(t,e,r){let o,i={};"POST"===t||"PUT"===t||"PATCH"===t?(o=e,i=r||{}):(i=e||{},o=void 0);let n=i.token;return!n&&globalTokenProvider&&(n=globalTokenProvider()||""),n=n||"",{token:n,apiKey:i.apiKey,body:o,params:i.params,showToasts:i.showToasts}}(e,o,i),a=s.token||n.token||"",l=s.apiKey||n.apiKey;try{return{data:await callApi(a,e,r,s.body,s.showToasts,s.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:r}=t,o=t.auth?.storageKey||t.storageKey;t.global&&function(t){serviceConfig={...serviceConfig,...t}}(t.global),this.auth=new AuthModule({baseUrl:e,apiKey:r,storageKey:o,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const i=createInternalApiClient(()=>({token:this.auth.token,apiKey:r,baseUrl:e}));if(this.auth.api=i,this.db=new DataModule(i),t.serviceRoleKey){const r=createInternalApiClient(()=>({token:t.serviceRoleKey,baseUrl:e}));this.db.direct=new DirectDBModule(r)}if(this.users=new UsersModule(i),this.api=createApiClient(()=>({token:this.auth.token||"",apiKey:r,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.DirectDBModule=DirectDBModule,exports.DirectQueryBuilder=DirectQueryBuilder,exports.DypaiClient=DypaiClient,exports.DypaiError=DypaiError,exports.PACKAGE_INFO={name:"@dypai-ai/client-sdk",version:"1.1.0",description:"Official JavaScript/TypeScript SDK for DYPAI",features:["Authentication (email, OAuth, OTP)","Database CRUD","Direct database access (service role)","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,r){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?r: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,r)=>t({title:e,description:r,variant:"success"}),toastError:(e,r)=>t({title:e,description:r,variant:"error"}),toastWarning:(e,r)=>t({title:e,description:r,variant:"warning"}),toastInfo:(e,r)=>t({title:e,description:r,variant:"info"})}};
1
+ "use strict";class DypaiError extends Error{constructor(t,e=500,r,o){super(t),this.status=e,this.code=r,this.details=o,this.name="DypaiError"}}function normalizeUser(t){if(!t)return{};const e=t.user||t,r=e.app_metadata||{},o=e.user_metadata||{};return{id:e.id,email:e.email,phone:e.phone,role:r.role||o.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:r,user_metadata:o,roleDetails:{name:r.role||o.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 r={};try{r=await t.json()}catch{}const o=r.msg||r.error_description||r.message||e,i=r.error_code||r.error||r.code||void 0;return new DypaiError(o,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.m=null,this.A=null,this.S=0,this.v=0,this.storage=t.storage||localStorageAdapter,_debugEnabled=!!t.debug;const r=t.storageKey||this.deriveStorageKey(t.apiKey);_log("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${r})`),this.STORAGE_KEY=`dypai-${r}-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.k(t)},0):this.D=!0}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.I.bind(this)),window.addEventListener("focus",this.I.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 I(){"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.T();_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`,r={email:t.email||t.identifier||"",password:t.password},o=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(r)});if(!o.ok)throw await parseAuthError(o,"Login failed");const i=await o.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 r=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,{email:o,phone:i,password:n,user_data:s,...a}=t,l={email:o,phone:i,password:n,data:{...a,...s}};e?.redirectTo?l.redirect_to=e.redirectTo:"undefined"!=typeof window&&(l.redirect_to=`${window.location.origin}/`);const c=await fetch(r,{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 r=await e.json(),o=this.$(r);return this.R(o),o})())}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:r=window.location.href,scopes:o}=e,i=this.config.baseUrl||"http://localhost:8000",n=o?.length?`&scopes=${encodeURIComponent(o.join(" "))}`:"",s=`${i}/auth/v1/authorize?provider=${t}${n}&redirect_to=${encodeURIComponent(r)}`;window.location.href=s})())}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 r=this.config.baseUrl||"http://localhost:8000",o={email:t};e?.redirectTo?o.redirect_to=e.redirectTo:"undefined"!=typeof window&&(o.redirect_to=`${window.location.origin}/`);const i=await fetch(`${r}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)});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",r=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(!r.ok)throw await parseAuthError(r,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.m)return _log("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.m.promise;const t=new Deferred;this.m=t;const e=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const r=await async function(t,e,r){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const o=new AbortController,i=setTimeout(()=>o.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:o.signal},async()=>(clearTimeout(i),await r()))}catch(o){if("AbortError"===o.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await r();throw o}}return await r()}(e,AuthModule.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.O());t.resolve(r)}catch(e){const r=e instanceof DypaiError?e:new DypaiError(e.message||"Error refrescando sesión",401);t.resolve({data:null,error:r})}finally{this.m=null}})(),t.promise}async O(){try{const t=await this.storage.getItem(this.STORAGE_KEY);if(t){const e=JSON.parse(t),r=e.expires_at||0,o=Math.floor(Date.now()/1e3);if(e.access_token!==this.o&&r-o>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.S=0,this.k("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`,r=new AbortController,o=setTimeout(()=>r.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:r.signal})}finally{clearTimeout(o)}if(!i.ok){let t={};try{t=await i.json()}catch{}const e=i.status,r=t.error||t.code||"",o=(t.error_description||t.msg||"").toLowerCase(),n="invalid_grant"===r||o.includes("invalid refresh token")||o.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}: ${r})`)):_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(),s={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.S=0,await this.C(s),_log("log",`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${s.expiresAt} (en ${s.expiresIn}s). Refresh token actualizado: ${!!s.refreshToken}`),{data:s,error:null}}catch(t){this.S++,this.v=Date.now();const e=t instanceof DOMException&&"AbortError"===t.name,r=e?new DypaiError("Refresh token timeout (servidor no responde)",408):t instanceof DypaiError?t:new DypaiError(t.message||"Error refrescando sesión",401),o=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.S),AuthModule.MAX_RETRY_MS);return e?_log("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${AuthModule.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${o}ms (intento ${this.S})`):_log("error","[DYPAI SDK] ❌ Refresh falló:",r.message,`(status: ${r.status}). Backoff: ${o}ms (intento ${this.S})`),this.S>=AuthModule.MAX_REFRESH_FAILURES&&(_log("error",`[DYPAI SDK] 🗑️ ${this.S} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.K(`${this.S} consecutive refresh failures`),this.S=0),{data:null,error:r}}}async signInWithOtp(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",r=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(!r.ok){const t=await r.json();throw new DypaiError(t.detail||"OTP request failed",r.status)}return await r.json()})())}async verifyOtp(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",r=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(!r.ok){const t=await r.json();throw new DypaiError(t.detail||"OTP verification failed",r.status)}const o=await r.json(),i={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 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`,r=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(!r.ok){const t=await r.json();throw new DypaiError(t.detail||"Update user failed",r.status)}const o=await r.json(),i=this.$(o);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.S>0){const t=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.S),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,r,o){this.t=t,e&&(this.o=e),void 0!==r&&(this.i=r||null),void 0!==o&&(this.l=o||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.k(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.k("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}`),r=await this.storage.getItem(`dypai-auth-user-${t}`);if(!e||!r)return void _log("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let o;_log("log","[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");try{o=JSON.parse(r)}catch{return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage corrupto (JSON inválido). Ignorando migración...")}if(!o||"object"!=typeof o)return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage con formato inválido. Ignorando migración...");this.o=e,this.t=o;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(o,this.o||void 0,this.i||void 0,this.l||void 0);const s=[`dypai-auth-token-${t}`,`dypai-auth-refresh-token-${t}`,`dypai-auth-user-${t}`,`dypai-auth-expires-at-${t}`];for(const t of s)try{await this.storage.removeItem(t)}catch(t){}_log("log","[DYPAI SDK] ✨ Migración completada con éxito.")}}const r=Math.floor(Date.now()/1e3),o=this.l&&this.l<=r;if(_log("log",`[DYPAI SDK] 🕒 Token expira en: ${this.l}. Ahora es: ${r}. Diferencia: ${(this.l||0)-r}s`),o&&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",r=e.get("error_description")?.replace(/\+/g," ")||"Authentication error";return _log("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${r}`),this.u={code:t,message:r},window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.k("AUTH_ERROR"),!0}const r=e.get("access_token");if(!r)return!1;const o=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=r,this.i=o||null,this.l=Math.floor(Date.now()/1e3)+n;const s=this.config.baseUrl||"http://localhost:8000",a=await fetch(`${s}/auth/v1/user`,{headers:{Authorization:`Bearer ${r}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!a.ok)return _log("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${a.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.k("AUTH_ERROR"),!0;const l=await a.json(),c=this.$(l),h={token:r,refreshToken:o||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}}T(){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}k(t="USER_UPDATED"){const e=this.T();_log("log",`[DYPAI SDK] 📢 Notificando a ${this.h.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.h.forEach(r=>r(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{get direct(){if(!this.M)throw new Error("client.db.direct is not available. Pass serviceRoleKey to createClient() to enable direct database access.\n\nExample:\n const client = createClient(url, { serviceRoleKey: process.env.DYPAI_SERVICE_ROLE_KEY });\n\nWARNING: serviceRoleKey grants admin-level access. Only use from server-side code (scripts, migrations, seeds). Never expose it in browser/client-side code.");return this.M}set direct(t){this.M=t}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 DirectDBModule{constructor(t){this.api=t}from(t){return new DirectQueryBuilder(t,this.api)}async sql(t,e,r){return this.api.post("/api/v0/admin/db/sql",{query:t,params:e||[],...void 0!==r?.limit&&{limit:r.limit}})}}class DirectQueryBuilder{constructor(t,e){this.table=t,this.api=e,this.N="public",this.L=[],this.j=100,this.q=0,this.B="ASC"}schema(t){return this.N=t,this}eq(t,e){return this.L.push({column:t,operator:"eq",value:e}),this}neq(t,e){return this.L.push({column:t,operator:"neq",value:e}),this}gt(t,e){return this.L.push({column:t,operator:"gt",value:e}),this}gte(t,e){return this.L.push({column:t,operator:"gte",value:e}),this}lt(t,e){return this.L.push({column:t,operator:"lt",value:e}),this}lte(t,e){return this.L.push({column:t,operator:"lte",value:e}),this}like(t,e){return this.L.push({column:t,operator:"ilike",value:e}),this}ilike(t,e){return this.like(t,e)}isNull(t){return this.L.push({column:t,operator:"is_null",value:null}),this}is(t,e){return this.isNull(t)}notNull(t){return this.L.push({column:t,operator:"not_null",value:null}),this}in(t,e){return this.L.push({column:t,operator:"in",value:e}),this}contains(t,e){return this.L.push({column:t,operator:"array_contains",value:e}),this}containedBy(t,e){return this.L.push({column:t,operator:"contained_by",value:e}),this}overlaps(t,e){return this.L.push({column:t,operator:"overlaps",value:e}),this}textSearch(t,e){return this.L.push({column:t,operator:"fts",value:e}),this}phraseSearch(t,e){return this.L.push({column:t,operator:"phfts",value:e}),this}not(t,e,r){return this.L.push({column:t,operator:"not",value:{operator:e,value:r}}),this}or(t){return this.L.push({operator:"or",or:t}),this}limit(t){return this.j=t,this}offset(t){return this.q=t,this}orderBy(t,e="ASC"){return this.F=t,this.B=e,this}async select(t){const e=[...this.L];let r=this.J;if("string"==typeof t)r=t;else if(t&&"object"==typeof t)for(const[r,o]of Object.entries(t))e.push({column:r,operator:"eq",value:o});return this.execute("select",{...r&&{columns:r},filters:e,limit:this.j,offset:this.q,sort_by:this.F,order:this.B})}async single(){const t=await this.select();if(t.error)return t;const e=t.data;return!e||Array.isArray(e)&&0===e.length?{data:null,error:new DypaiError("No rows found.",404)}:{data:Array.isArray(e)?e[0]:e,error:null}}async maybeSingle(){const t=await this.select();if(t.error)return t;const e=t.data;return!e||Array.isArray(e)&&0===e.length?{data:null,error:null}:{data:Array.isArray(e)?e[0]:e,error:null}}async count(){const t=await this.execute("select",{columns:"count(*) as count",filters:[...this.L],limit:1,offset:0});if(t.error)return t;const e=t.data;return{data:Array.isArray(e)&&e.length>0?Number(e[0].count):0,error:null}}async insert(t){if(Array.isArray(t)&&t.length>1e3){const e=[];for(let r=0;r<t.length;r+=1e3){const o=t.slice(r,r+1e3),{data:i,error:n}=await this.execute("insert",{data:o,mode:"bulk"});if(n)return{data:null,error:n};i&&e.push(...Array.isArray(i)?i:[i])}return{data:e,error:null}}const e=Array.isArray(t)?"bulk":"single";return this.execute("insert",{data:t,mode:e})}async update(t){return 0===this.L.length?{data:null,error:new DypaiError("Update requires at least one filter. Use .eq(), .in(), etc.",400)}:this.execute("update",{data:t,filters:this.L})}async delete(){return 0===this.L.length?{data:null,error:new DypaiError("Delete requires at least one filter.",400)}:this.execute("delete",{filters:this.L})}async upsert(t,e="id"){return this.execute("upsert",{data:t,conflict_column:e})}async execute(t,e){return this.api.post("/api/v0/admin/db/query",{operation:t,schema_name:this.N,table_name:this.table,...e})}}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 r=await this.api.put(`admin/users/${t}`,e);return r.data&&(r.data=normalizeUser(r.data)),r}async delete(t){return this.api.delete(`admin/users/${t}`)}}let customToastFunction=null;const fallbackToast=t=>{const{title:e,description:r,variant:o="default"}=t,i=`${"error"===o?"❌":"success"===o?"✅":"warning"===o?"⚠️":"info"===o?"ℹ️":"📢"} ${e}${r?`: ${r}`:""}`;"error"===o?console.error(i):"warning"===o&&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,r,o,i,n,s,a,l){const c=getCompleteConfig(),h=a||null;if(!h&&!r.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof r||!r.trim())throw new Error("Endpoint debe ser un string válido");let u;if(r.startsWith("http"))u=r;else{const t=h.replace(/\/+$/,"");u=r.startsWith("/")?t+r:t+"/api/v0/"+r}if(n&&Object.keys(n).length>0){const t=new URLSearchParams,e=(r,o)=>{null!=o&&(Array.isArray(o)?o.forEach((t,o)=>e(`${r}[${o}]`,t)):"object"==typeof o?Object.entries(o).forEach(([t,o])=>e(`${r}[${t}]`,o)):t.append(r,String(o)))};Object.entries(n).forEach(([t,r])=>e(t,r));const r=t.toString();r&&(u+=`?${r}`)}const d="GET"===e?`${e}:${u}:${JSON.stringify(o)}`:null;if(d&&pendingRequests.has(d))return pendingRequests.get(d);const p=o instanceof FormData,f={...c.headers||{},...p?{}:{"Content-Type":"application/json"},...t&&{Authorization:`Bearer ${t}`},...s&&{"x-api-key":s}},y={method:e,headers:f,credentials:"include"};o&&"GET"!==e&&"DELETE"!==e&&(y.body=p?o:JSON.stringify(o));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,r,o,i,n,s,a,!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,r,o){const i=o?.method||(r?"POST":"GET"),{data:n,error:s}=await("GET"===i?t.get(e,{params:o?.params}):t.post(e,r,{params:o?.params}));if(s)throw s;if(n instanceof Blob){const t=window.URL.createObjectURL(n),e=document.createElement("a");e.href=t,e.download=o?.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 r;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(r=t[1].replace(/"/g,""))}const i=o?.fileName||r;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,r,o){const{data:i,error:n}=await t.post(e,{file_path:r.name,content_type:r.type||"application/octet-stream",size_bytes:r.size,confirm:!1,client_upload:!0,...o?.params||{}});if(n)throw n;const s=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 r=[t.data,t.result,t.output,t.payload].filter(Boolean);for(const t of r)if(t&&(t.upload_url||t.uploadUrl||t.url))return normalizeUploadPayload(t);const o=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of o){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(!s?.upload_url)throw new DypaiError("The workflow did not return a valid upload URL (missing storage upload node?)",400);const{upload_url:a,method:l="PUT",headers:c={},file_path:h,storage_path:u}=s;o?.onProgress&&o.onProgress(10);const d=await fetch(a,{method:l,headers:{"Content-Type":r.type||"application/octet-stream",...c},body:r});if(!d.ok)throw new DypaiError("Direct upload to cloud storage failed",d.status);o?.onProgress&&o.onProgress(90);const p=o?.confirmEndpoint||e,{data:f,error:y}=await t.post(p,{...o?.params,bucket:s.bucket||o?.params?.bucket,file_path:h,storage_path:u,filename:r.name,content_type:r.type||"application/octet-stream",size_bytes:r.size,confirm:!0,client_upload:!0});if(y)throw y;return o?.onProgress&&o.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 createInternalApiClient(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,r,o)=>{try{return{data:await handleSmartUpload(e,t,r,o),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Upload failed",t?.status??500)}}},download:async(t,r,o)=>{try{return await handleSmartDownload(e,t,r,{...o,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}function createMethod(t,e){const r="POST"===e||"PUT"===e||"PATCH"===e;return async(o,i,n)=>{const s=t();let a,l={};r?(a=i,l=n||{}):(l=i||{},a=void 0);const c=l.token||s.token||(globalTokenProvider?globalTokenProvider():"")||"",h=l.apiKey||s.apiKey;try{return{data:await callApi(c,e,o,a,l.showToasts,l.params,h,s.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,r={get:createMethodFromCtx(e,"GET"),post:createMethodFromCtx(e,"POST"),put:createMethodFromCtx(e,"PUT"),patch:createMethodFromCtx(e,"PATCH"),delete:createMethodFromCtx(e,"DELETE"),upload:async(t,e,o)=>{try{return{data:await handleSmartUpload(r,t,e,o),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,o)=>{try{return await handleSmartDownload(r,t,e,{...o,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 r}function createMethodFromCtx(t,e){return async(r,o,i)=>{const n=t(),s=await async function(t,e,r){let o,i={};"POST"===t||"PUT"===t||"PATCH"===t?(o=e,i=r||{}):(i=e||{},o=void 0);let n=i.token;return!n&&globalTokenProvider&&(n=globalTokenProvider()||""),n=n||"",{token:n,apiKey:i.apiKey,body:o,params:i.params,showToasts:i.showToasts}}(e,o,i),a=s.token||n.token||"",l=s.apiKey||n.apiKey;try{return{data:await callApi(a,e,r,s.body,s.showToasts,s.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:r}=t,o=t.auth?.storageKey||t.storageKey;t.global&&function(t){serviceConfig={...serviceConfig,...t}}(t.global),this.auth=new AuthModule({baseUrl:e,apiKey:r,storageKey:o,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const i=createInternalApiClient(()=>({token:this.auth.token,apiKey:r,baseUrl:e}));if(this.auth.api=i,this.db=new DataModule(i),t.serviceRoleKey)if("undefined"!=typeof window&&void 0!==window.document)console.error("[DYPAI SDK] ❌ serviceRoleKey detected in a browser environment. This is a security risk — the key grants admin-level database access. db.direct has been disabled. Use serviceRoleKey only from server-side code (Node.js, Bun, Deno, scripts).");else{const r=createInternalApiClient(()=>({token:t.serviceRoleKey,baseUrl:e}));this.db.direct=new DirectDBModule(r)}if(this.users=new UsersModule(i),this.api=createApiClient(()=>({token:this.auth.token||"",apiKey:r,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.DirectDBModule=DirectDBModule,exports.DirectQueryBuilder=DirectQueryBuilder,exports.DypaiClient=DypaiClient,exports.DypaiError=DypaiError,exports.PACKAGE_INFO={name:"@dypai-ai/client-sdk",version:"1.1.0",description:"Official JavaScript/TypeScript SDK for DYPAI",features:["Authentication (email, OAuth, OTP)","Database CRUD","Direct database access (service role)","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,r){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?r: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,r)=>t({title:e,description:r,variant:"success"}),toastError:(e,r)=>t({title:e,description:r,variant:"error"}),toastWarning:(e,r)=>t({title:e,description:r,variant:"warning"}),toastInfo:(e,r)=>t({title:e,description:r,variant:"info"})}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dypai-ai/client-sdk",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
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,