@dypai-ai/client-sdk 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,53 @@
2
2
 
3
3
  All notable changes to `@dypai-ai/client-sdk` will be documented in this file.
4
4
 
5
+ ## [1.10.0] - 2026-04-10
6
+
7
+ ### Added
8
+
9
+ - **Typed error hierarchy** (`src/errors.ts`): SDK now exposes a Supabase-style error class hierarchy so consumers can handle errors with `instanceof` / type guards instead of inspecting status codes.
10
+ - `AuthSessionMissingError` — no active session when one is required
11
+ - `AuthInvalidCredentialsError` — wrong email/password on sign in
12
+ - `AuthInvalidTokenError` — JWT/reset/magic link token is invalid, expired, or revoked
13
+ - `AuthWeakPasswordError` — password fails configured strength requirements (with `reasons[]`)
14
+ - `AuthEmailNotVerifiedError` — operation requires a verified email
15
+ - `AuthUserAlreadyExistsError` — sign-up with an existing email
16
+ - `RetryableFetchError` — transient network/server error (status 0/408/429/5xx)
17
+ - Type guards: `isDypaiError`, `isAuthSessionMissingError`, `isAuthInvalidCredentialsError`, `isAuthInvalidTokenError`, `isAuthWeakPasswordError`, `isAuthEmailNotVerifiedError`, `isAuthUserAlreadyExistsError`, `isRetryableFetchError`, `isRetryableStatus`
18
+ - `classifyAuthError(message, status, code)` — internal helper that converts a generic auth error response into the most specific subclass
19
+
20
+ ### Changed
21
+
22
+ - **`parseAuthError`** in `AuthModule` now returns a typed subclass instead of generic `DypaiError` when the error matches a known pattern (invalid credentials, expired token, weak password, etc.).
23
+ - **Internal `throw new DypaiError('No hay sesión activa', 401)`** replaced with `throw new AuthSessionMissingError(...)` across all auth methods (`getUser`, `getSession`, `updateUser`, `enableTwoFactor`, `disableTwoFactor`, `createOrganization`, `inviteMember`, `listOrganizations`).
24
+
25
+ ## [1.9.0] - 2026-04-10
26
+
27
+ ### Changed
28
+
29
+ - **Dual-token model**: `AuthModule` now stores both the **JWT** (for API calls, asymmetric EdDSA from better-auth's JWT plugin) and the **better-auth session token** (for auth operations like `get-session`, `refresh`, `sign-out`). API calls send the JWT in `Authorization`; auth operations use the session token. Fixes refresh-on-page-reload and prevents token mismatches.
30
+ - **401 handling (Supabase-style)**: API service no longer retries on 401. The auto-refresh ticker (every 30s) refreshes proactively before expiry. On 401, `onUnauthorized` clears the session and surfaces the error. Removes retry storms when multiple components fire simultaneously.
31
+ - **Session validation on init**: `_recoverSession` now validates the stored session against the server immediately, catching stale tokens (e.g. after auth system migration) instead of waiting for an API 401.
32
+ - **`setPassword` auto-login**: When the engine returns session tokens after a successful password reset, the SDK stores them so the user lands on the app already logged in.
33
+ - **`DypaiClient` redirect preservation**: Auto-redirect after password recovery now preserves the original query params (`?token=...&type=recovery`), fixing the case where the destination page received an empty URL.
34
+
35
+ ### Added
36
+
37
+ - **BroadcastChannel for cross-tab sync**: Uses native `BroadcastChannel` (with storage event fallback) to instantly notify other tabs of `SIGNED_OUT` and `SESSION_UPDATED` events.
38
+ - **Smart retry on refresh errors**: Distinguishes terminal 4xx errors (clear session) from transient 5xx/408/429 errors (keep session, retry on next tick).
39
+ - **Clock skew tolerance**: 10-second margin in token expiration checks to tolerate desynced client clocks.
40
+ - **Storage versioning + migration**: Sessions are now stored with `version: 2`. Old v1 sessions (without `session_token`) are detected and cleaned automatically on the next page load.
41
+
42
+ ### Removed
43
+
44
+ - **`onTokenExpired` callback**: No longer needed. Token refresh is proactive via the auto-refresh ticker, not reactive on 401. Use `onUnauthorized` to handle terminal auth failures.
45
+ - **`_isRetry` parameter on `callApi`**: Internal parameter removed alongside the retry logic.
46
+
47
+ ### Migration notes
48
+
49
+ - **Existing user sessions will be invalidated once.** Tokens stored in localStorage from previous SDK versions (v1 format, HS256) are not compatible with the new asymmetric verification (EdDSA via JWKS). Users will be logged out automatically and need to sign in again to receive a v2 session.
50
+ - No application code changes are required. The public SDK API (`signIn`, `signUp`, `signOut`, `useAuth`, `onAuthStateChange`, etc.) is unchanged.
51
+
5
52
  ## [1.5.0] - 2026-04-09
6
53
 
7
54
  ### Changed
package/dist/index.d.ts CHANGED
@@ -230,7 +230,6 @@ interface ApiServiceConfig {
230
230
  toast?: ToastFunction;
231
231
  showToasts?: boolean;
232
232
  onUnauthorized?: () => void;
233
- onTokenExpired?: () => Promise<string | null>;
234
233
  }
235
234
 
236
235
  /** Filter condition for structured queries. */
@@ -685,16 +684,25 @@ declare class AuthModule {
685
684
  private static readonly RETRY_BASE_MS;
686
685
  private static readonly MAX_RETRY_MS;
687
686
  private static readonly LOCK_ACQUIRE_TIMEOUT_MS;
687
+ private static readonly CLOCK_SKEW_TOLERANCE_S;
688
688
  private static readonly CALLBACK_TYPE_STORAGE_KEY;
689
689
  private static readonly CALLBACK_REDIRECT_STORAGE_KEY;
690
690
  private static readonly RESET_TOKEN_STORAGE_KEY;
691
691
  private readonly STORAGE_KEY;
692
692
  private storage;
693
693
  private _sessionRecovered;
694
+ private _broadcastChannel;
694
695
  constructor(config: {
695
696
  baseUrl?: string;
696
697
  apiKey?: string;
697
698
  } & AuthClientOptions, _apiClient?: ApiClient | null);
699
+ /**
700
+ * Determines if an HTTP status code is retryable (transient network/server error).
701
+ * Following the pattern used by Supabase auth-js.
702
+ */
703
+ private _isRetryableStatus;
704
+ /** Broadcast a message to other tabs via BroadcastChannel */
705
+ private _broadcast;
698
706
  private _handleVisibilityChange;
699
707
  private deriveStorageKey;
700
708
  private getBaseUrl;
@@ -1164,6 +1172,99 @@ declare class DypaiClient<TDatabase = any, TApi extends EndpointMap = EndpointMa
1164
1172
  */
1165
1173
  declare function createClient<TDatabase = any, TApi extends EndpointMap = EndpointMap>(url: string, apiKeyOrOptions?: string | Omit<DypaiConfig, 'baseUrl' | 'apiKey'>, maybeOptions?: Omit<DypaiConfig, 'baseUrl' | 'apiKey'>): DypaiClient<TDatabase, TApi>;
1166
1174
 
1175
+ /**
1176
+ * errors.ts — Typed error hierarchy for the DYPAI SDK.
1177
+ *
1178
+ * All errors thrown by the SDK extend `DypaiError` (the base class), allowing
1179
+ * consumers to handle errors with `instanceof` checks.
1180
+ *
1181
+ * @example
1182
+ * ```ts
1183
+ * import { isAuthSessionMissingError, isRetryableFetchError } from '@dypai-ai/client-sdk';
1184
+ *
1185
+ * const { error } = await dypai.auth.getSession();
1186
+ * if (isAuthSessionMissingError(error)) {
1187
+ * router.push('/login');
1188
+ * } else if (isRetryableFetchError(error)) {
1189
+ * showToast('Network error, retrying...');
1190
+ * }
1191
+ * ```
1192
+ */
1193
+
1194
+ declare function isDypaiError(error: unknown): error is DypaiError;
1195
+ /**
1196
+ * Thrown when the SDK has no active session and one is required for the operation.
1197
+ *
1198
+ * Common causes:
1199
+ * - User not signed in
1200
+ * - Session was cleared (logout, terminal 401)
1201
+ * - Stored session was invalid and got cleaned up
1202
+ *
1203
+ * Recovery: redirect the user to the sign-in page.
1204
+ */
1205
+ declare class AuthSessionMissingError extends DypaiError {
1206
+ constructor(message?: string);
1207
+ }
1208
+ declare function isAuthSessionMissingError(error: unknown): error is AuthSessionMissingError;
1209
+ /**
1210
+ * Thrown when the credentials provided to `signInWithPassword` are wrong
1211
+ * (invalid email/password combination).
1212
+ */
1213
+ declare class AuthInvalidCredentialsError extends DypaiError {
1214
+ constructor(message?: string);
1215
+ }
1216
+ declare function isAuthInvalidCredentialsError(error: unknown): error is AuthInvalidCredentialsError;
1217
+ /**
1218
+ * Thrown when a token (JWT, reset token, magic link token) is invalid,
1219
+ * expired, or has been revoked.
1220
+ */
1221
+ declare class AuthInvalidTokenError extends DypaiError {
1222
+ constructor(message?: string);
1223
+ }
1224
+ declare function isAuthInvalidTokenError(error: unknown): error is AuthInvalidTokenError;
1225
+ /**
1226
+ * Thrown when a password fails the configured strength requirements.
1227
+ * The `reasons` array (when present) contains the failed checks.
1228
+ */
1229
+ declare class AuthWeakPasswordError extends DypaiError {
1230
+ reasons: string[];
1231
+ constructor(message?: string, reasons?: string[]);
1232
+ }
1233
+ declare function isAuthWeakPasswordError(error: unknown): error is AuthWeakPasswordError;
1234
+ /**
1235
+ * Thrown when an email or phone needs to be verified before the operation
1236
+ * can complete (e.g. signing in with an unverified email).
1237
+ */
1238
+ declare class AuthEmailNotVerifiedError extends DypaiError {
1239
+ constructor(message?: string);
1240
+ }
1241
+ declare function isAuthEmailNotVerifiedError(error: unknown): error is AuthEmailNotVerifiedError;
1242
+ /**
1243
+ * Thrown when the user already exists during a sign-up attempt.
1244
+ */
1245
+ declare class AuthUserAlreadyExistsError extends DypaiError {
1246
+ constructor(message?: string);
1247
+ }
1248
+ declare function isAuthUserAlreadyExistsError(error: unknown): error is AuthUserAlreadyExistsError;
1249
+ /**
1250
+ * Thrown when an HTTP request fails with a transient error (network down,
1251
+ * server overloaded, gateway timeout). The SDK keeps the session and may
1252
+ * retry on the next auto-refresh tick.
1253
+ *
1254
+ * Statuses considered retryable: 0 (network), 408, 429, 500-599.
1255
+ */
1256
+ declare class RetryableFetchError extends DypaiError {
1257
+ constructor(message: string, status?: number);
1258
+ }
1259
+ declare function isRetryableFetchError(error: unknown): error is RetryableFetchError;
1260
+ /** Returns true if a given HTTP status is considered retryable. */
1261
+ declare function isRetryableStatus(status: number): boolean;
1262
+ /**
1263
+ * Convert a generic error or HTTP response into a typed DypaiError subclass
1264
+ * by inspecting the message, status, and error code.
1265
+ */
1266
+ declare function classifyAuthError(message: string, status: number, code?: string): DypaiError;
1267
+
1167
1268
  /**
1168
1269
  * @dypai/client-sdk - Cliente HTTP centralizado
1169
1270
  *
@@ -1349,5 +1450,5 @@ declare const PACKAGE_INFO: {
1349
1450
  readonly features: readonly ["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)"];
1350
1451
  };
1351
1452
 
1352
- export { DirectDBModule, DirectQueryBuilder, DypaiClient, DypaiError, DypaiRealtimeClient, PACKAGE_INFO, RealtimeChannel, callApi, configureApiService, configureDypaiServices, createApiClient, createClient, getGlobalConfig, reloadDypaiConfig, resetGlobalConfig, setToastFunction, setTokenProvider, toast, toastError, toastInfo, toastSuccess, toastWarning, useToast };
1453
+ export { AuthEmailNotVerifiedError, AuthInvalidCredentialsError, AuthInvalidTokenError, AuthSessionMissingError, AuthUserAlreadyExistsError, AuthWeakPasswordError, DirectDBModule, DirectQueryBuilder, DypaiClient, DypaiError, DypaiRealtimeClient, PACKAGE_INFO, RealtimeChannel, RetryableFetchError, callApi, classifyAuthError, configureApiService, configureDypaiServices, createApiClient, createClient, getGlobalConfig, isAuthEmailNotVerifiedError, isAuthInvalidCredentialsError, isAuthInvalidTokenError, isAuthSessionMissingError, isAuthUserAlreadyExistsError, isAuthWeakPasswordError, isDypaiError, isRetryableFetchError, isRetryableStatus, reloadDypaiConfig, resetGlobalConfig, setToastFunction, setTokenProvider, toast, toastError, toastInfo, toastSuccess, toastWarning, useToast };
1353
1454
  export type { ApiClient, ApiContextType, ApiOptions, ApiServiceConfig, ApiUploadOptions, AppConfig, AppData, AppRole, AppUserResponse, AuthChangeEvent, AuthResponse, BroadcastFilter, ChannelConfig, CreateUserRequest, DbChangeFilter, DypaiConfig, EndpointBody, EndpointDefinition, EndpointMap, EndpointParams, EndpointResponse, FilterCondition, HttpMethod, JoinResponse, LoginCredentials, OAuthOptions, PasswordRecoveryRequest, PasswordRecoveryResponse, PasswordResetRequest, PasswordResetResponse, PresenceEntry, PresenceFilter, RealtimeEvent, RegisterData, ResetPasswordWithTokenRequest, Session, SessionResponse, SubscribeState, ToastFunction, TypedApiClient, TypedGetOptions, TypedMutationOptions, UpdateUserRequest, User, UserData, UserListResponse };
package/dist/index.esm.js CHANGED
@@ -1 +1 @@
1
- class t extends Error{constructor(t,e=500,i,s){super(t),this.status=e,this.code=i,this.details=s,this.name="DypaiError"}}function e(t){if(!t)return{};const e=t.user||t,i=e.name||e.user_metadata?.full_name||null,s=e.emailVerified??e.confirmed_at??null,n=e.app_metadata||{},r=e.user_metadata||{full_name:i};return{id:e.id,email:e.email,phone:e.phone||null,role:n.role||r.role||e.role||"authenticated",created_at:e.createdAt||e.created_at,updated_at:e.updatedAt||e.updated_at,confirmed_at:s?"boolean"==typeof s?(new Date).toISOString():s:null,last_sign_in_at:e.last_sign_in_at||null,app_metadata:n,user_metadata:{...r,full_name:i},roleDetails:{name:n.role||r.role||null,weight:0},appContext:{app_id:"default"}}}let i=!1;function s(t,...e){"error"===t?console.error(...e):i&&("warn"===t?console.warn(...e):console.log(...e))}async function n(e,i){let s={};try{s=await e.json()}catch{}const n=s.msg||s.error_description||s.message||i,r=s.error_code||s.error||s.code||void 0;return new t(n,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=null,this.u=[],this.p=null,this.m=null,this.S=!1,this.v=null,this.k=null,this.D=0,this.A=0,this.storage=t.storage||o,i=!!t.debug;const n=t.storageKey||this.deriveStorageKey(t.apiKey);s("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${n})`),this.STORAGE_KEY=`dypai-${n}-auth-session`,this._=this.P().then(()=>{this.i&&(this.getUser().catch(()=>{}),this.startAutoRefresh());const t=this.m;this.m=null,t?setTimeout(()=>{this.S=!0,this.T(t)},0):this.S=!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&&(s("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.P(!1))}))}async I(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(s("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"}getBaseUrl(){return this.config.baseUrl||"http://localhost:8000"}O(){if("undefined"==typeof window)return;const t=new URLSearchParams(window.location.search);["token","type","error","error_code","error_description","access_token","refresh_token","expires_in"].forEach(e=>t.delete(e));const e=t.toString(),i=`${window.location.pathname}${e?`?${e}`:""}`;window.history.replaceState({},document.title,i)}$(t){try{if(!t)return;sessionStorage.setItem(c.CALLBACK_TYPE_STORAGE_KEY,t),"recovery"===t||"invite"===t?sessionStorage.setItem(c.CALLBACK_REDIRECT_STORAGE_KEY,"PASSWORD_RECOVERY"):sessionStorage.removeItem(c.CALLBACK_REDIRECT_STORAGE_KEY)}catch{}}R(t){try{sessionStorage.setItem(c.RESET_TOKEN_STORAGE_KEY,t)}catch{}}N(){try{return sessionStorage.getItem(c.RESET_TOKEN_STORAGE_KEY)}catch{return null}}U(){try{sessionStorage.removeItem(c.RESET_TOKEN_STORAGE_KEY)}catch{}}get user(){return this.t}get token(){return this.i}get lastError(){return this.p}consumeCallbackType(){try{const t=sessionStorage.getItem(c.CALLBACK_TYPE_STORAGE_KEY);return sessionStorage.removeItem(c.CALLBACK_TYPE_STORAGE_KEY),sessionStorage.removeItem(c.CALLBACK_REDIRECT_STORAGE_KEY),t}catch{return null}}get isPasswordRecoveryCallback(){try{return"PASSWORD_RECOVERY"===sessionStorage.getItem(c.CALLBACK_REDIRECT_STORAGE_KEY)}catch{return!1}}isLoggedIn(){return!(!this.i||!this.t)}onAuthStateChange(t){return s("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.u.push(t),this._.then(()=>{const e=this.K();s("log",`[DYPAI SDK] 📣 INITIAL_SESSION para suscriptor (Sesión activa: ${!!e})`),t("INITIAL_SESSION",e)}).catch(e=>{s("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.u=this.u.filter(e=>e!==t)}}}}}async signInWithPassword(t){return a((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-in/email`,i={email:t.email||t.identifier||"",password:t.password},s=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(i)});if(!s.ok)throw await n(s,"Login failed");const r=await s.json(),o=this.C(r);return await this.Y(o),o})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return a((async()=>{const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-up/email`,{email:r,phone:o,password:a,user_data:c,...h}=t,l=r||"",u=h.full_name||h.name||l.split("@")[0],d={...h,...c||{}},f={email:l,password:a,name:u};Object.keys(d).length>0&&Object.assign(f,d),e?.redirectTo?f.callbackURL=e.redirectTo:"undefined"!=typeof window&&(f.callbackURL=window.location.href.split("#")[0]);const p=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(f)});if(!p.ok)throw await n(p,"Registration failed");const w=await p.json(),y=this.C(w),m={...y,confirmationRequired:!y.token};return m.token?await this.Y(m):s("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),m})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this._,!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.l||0)-t<30&&this.h&&(s("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>s("warn","[DYPAI SDK] Error refreshing session in getSession:",t))),{data:{access_token:this.i,refresh_token:this.h||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()=>{await this._;const e=this.o||this.i;if(!e)throw new t("No hay sesión activa",401);const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,s=await fetch(i,{headers:{Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw new t("Session invalid",s.status);const n=await s.json(),r=n?.jwt||s.headers.get("set-auth-jwt");r&&(this.i=r);const o=this.L(n);return this.j(o),o})())}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:s=window.location.href,scopes:n}=i,r=this.config.baseUrl||"http://localhost:8000",o=new URLSearchParams({provider:e,callbackURL:s});n?.length&&o.set("scopes",n.join(" "));const a=`${r}/api/v0/auth/sign-in/social?${o.toString()}`;window.location.href=a})())}async signOut(){return a((async()=>{try{const t=this.o||this.i;if(t){const e=this.config.baseUrl||"http://localhost:8000";await fetch(`${e}/api/v0/auth/sign-out`,{method:"POST",headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}})}}finally{this.J("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",s={email:t};e?.redirectTo?s.redirectTo=e.redirectTo:"undefined"!=typeof window&&(s.redirectTo=window.location.href.split("#")[0]);const r=await fetch(`${i}/api/v0/auth/request-password-reset`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!r.ok)throw await n(r,"Recovery failed");return await r.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t,e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",s={email:t};e?.redirectTo?s.callbackURL=e.redirectTo:"undefined"!=typeof window&&(s.callbackURL=window.location.href.split("#")[0]);const r=await fetch(`${i}/api/v0/auth/send-verification-email`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(s)});if(!r.ok)throw await n(r,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.v)return s("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.v.promise;const e=new r;this.v=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 s=new AbortController,n=setTimeout(()=>s.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:s.signal},async()=>(clearTimeout(n),await i()))}catch(s){if("AbortError"===s.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await i();throw s}}return await i()}(i,c.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.B());e.resolve(t)}catch(i){const s=i instanceof t?i:new t(i.message||"Error refrescando sesión",401);e.resolve({data:null,error:s})}finally{this.v=null}})(),e.promise}async B(){try{const e=await this.storage.getItem(this.STORAGE_KEY);if(e){const t=JSON.parse(e),i=t.expires_at||0,n=Math.floor(Date.now()/1e3);if(t.access_token!==this.i&&i-n>60)return s("log","[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.i=t.access_token,this.o=t.session_token||t.access_token,this.h=t.refresh_token,this.l=t.expires_at,this.t=t.user,this.D=0,this.T("TOKEN_REFRESHED"),{data:{token:this.i,sessionToken:this.o||void 0,refreshToken:this.h||void 0,expiresAt:this.l||void 0,user:this.t},error:null}}const i=this.o||this.i;if(!i)throw new t("No hay sesión activa",401);s("log","[DYPAI SDK] 🔄 Verificando sesión activa con session_token...");const n=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,r=new AbortController,o=setTimeout(()=>r.abort(),c.REFRESH_TIMEOUT_MS);let a;try{a=await fetch(n,{method:"GET",headers:{Authorization:`Bearer ${i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},signal:r.signal})}finally{clearTimeout(o)}if(!a.ok){let e={};try{e=await a.json()}catch{}const i=a.status,n=e.error||e.code||"",r=(e.error_description||e.msg||"").toLowerCase(),o="invalid_grant"===n||r.includes("invalid refresh token")||r.includes("revoked");throw i>=400&&i<500&&o?(s("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.J(`refreshSession failed (${i}: ${n})`)):s("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 h=await a.json(),l=a.headers.get("set-auth-jwt"),u=h?.jwt,d=u||l,f=this.C(h,this.i);return d&&(f.token=d),f.sessionToken=i,this.D=0,await this.Y(f),s("log",`[DYPAI SDK] ✅ Session refresh exitoso (fresh JWT: ${!!d}).`),{data:f,error:null}}catch(e){this.D++,this.A=Date.now();const i=e instanceof DOMException&&"AbortError"===e.name,n=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.D),c.MAX_RETRY_MS);return i?s("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${c.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${r}ms (intento ${this.D})`):s("error","[DYPAI SDK] ❌ Refresh falló:",n.message,`(status: ${n.status}). Backoff: ${r}ms (intento ${this.D})`),this.D>=c.MAX_REFRESH_FAILURES&&(s("error",`[DYPAI SDK] 🗑️ ${this.D} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.J(`${this.D} consecutive refresh failures`),this.D=0),{data:null,error:n}}}async signInWithOtp(e){return a((async()=>{if(e.phone)throw new t("Phone OTP is not supported by the current DYPAI auth backend",400);if(!e.email)throw new t("Email is required for magic link sign-in",400);const i=await this.signInWithMagicLink(e.email);if(i.error)throw i.error;return{status:!0}})())}async verifyOtp(e){return a((async()=>{if(!e.token)throw new t("Verification token is required",400);if("magiclink"===e.type)return await this.q(e.token);if("signup"===e.type||"email_change"===e.type){const i=await this.W(e.token);if(i)return i;throw new t("Verification completed but no session was returned",400)}if("recovery"===e.type||"invite"===e.type)throw new t("Recovery links do not use verifyOtp(). Use setPassword() after the PASSWORD_RECOVERY callback.",400);throw new t("Unsupported verification type",400)})())}async updateUser(e){return a((async()=>{const i=this.o||this.i;if(!i)throw new t("No hay sesión activa",401);const s=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/update-user`,n=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i}`,...this.config.apiKey&&{"x-api-key":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(),o=this.L(r);return this.j(o),o})())}async setPassword(e){return a((async()=>{if("undefined"==typeof window)throw new t("setPassword requiere un entorno de navegador",400);const i=new URLSearchParams(window.location.search).get("token")||this.N();if(!i)throw new t("No reset token found in the current URL",400);const s=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${s}/api/v0/auth/reset-password`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({newPassword:e,token:i})});if(!r.ok)throw await n(r,"Reset password failed");return this.U(),this.O(),await r.json()})())}async signInWithMagicLink(t,e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",s=e?.redirectTo||("undefined"!=typeof window?window.location.href.split("#")[0]:void 0),r=await fetch(`${i}/api/v0/auth/sign-in/magic-link`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({email:t,callbackURL:s})});if(!r.ok)throw await n(r,"Magic link failed");return{message:"Magic link sent"}})())}async enableTwoFactor(){return a((async()=>{const e=this.o||this.i;if(!e)throw new t("No hay sesión activa",401);const i=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${i}/api/v0/auth/two-factor/enable`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw await n(s,"Enable 2FA failed");return await s.json()})())}async verifyTwoFactor(t){return a((async()=>{const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/two-factor/verify-totp`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.o||this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({code:t})});if(!i.ok)throw await n(i,"2FA verification failed");const s=await i.json(),r={token:s.jwt||s.token||this.i,expiresIn:604800,expiresAt:Math.floor(Date.now()/1e3)+604800,user:this.L(s)};return await this.Y(r),r})())}async disableTwoFactor(){return a((async()=>{const e=this.o||this.i;if(!e)throw new t("No hay sesión activa",401);const i=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${i}/api/v0/auth/two-factor/disable`,{method:"POST",headers:{Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw await n(s,"Disable 2FA failed");return{message:"2FA disabled"}})())}async createOrganization(e,i){return a((async()=>{const s=this.o||this.i;if(!s)throw new t("No hay sesión activa",401);const r=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${r}/api/v0/auth/organization/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({name:e,slug:i})});if(!o.ok)throw await n(o,"Create organization failed");return await o.json()})())}async inviteMember(e,i,s){return a((async()=>{const r=this.o||this.i;if(!r)throw new t("No hay sesión activa",401);const o=this.config.baseUrl||"http://localhost:8000",a=await fetch(`${o}/api/v0/auth/organization/invite-member`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${r}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({organizationId:e,email:i,role:s||"member"})});if(!a.ok)throw await n(a,"Invite member failed");return await a.json()})())}async listOrganizations(){return a((async()=>{const e=this.o||this.i;if(!e)throw new t("No hay sesión activa",401);const i=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${i}/api/v0/auth/organization/list`,{headers:{Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw await n(s,"List organizations failed");return await s.json()})())}startAutoRefresh(){this.stopAutoRefresh(),s("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.M(),this.k=setInterval(()=>this.M(),c.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.k&&(s("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.k),this.k=null)}async M(){try{const t=Math.floor(Date.now()/1e3),e=this.l||0;if(!this.h&&!this.o||!e)return;if(1e3*(e-t)>c.AUTO_REFRESH_TICK_THRESHOLD*c.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.D>0){const t=Math.min(c.RETRY_BASE_MS*Math.pow(2,this.D),c.MAX_RETRY_MS);if(Date.now()-this.A<t)return}s("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){s("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}L(t){return t?e(t):(s("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}C(t,e){const i="number"==typeof t?.expiresIn?t.expiresIn:"number"==typeof t?.expires_in?t.expires_in:604800,s=t?.jwt||void 0,n=t?.session_token||t?.token||t?.access_token||t?.session?.token||t?.session?.accessToken||t?.session?.access_token||e;return{token:s||n,sessionToken:n,refreshToken:t?.refreshToken||t?.refresh_token||t?.session?.refreshToken||t?.session?.refresh_token||void 0,expiresIn:i,expiresAt:Math.floor(Date.now()/1e3)+i,user:this.L(t)}}async Y(t){this.p=null,t.token&&(this.i=t.token,this.o=t.sessionToken||t.token,void 0!==t.refreshToken&&(this.h=t.refreshToken||null),void 0!==t.expiresAt&&(this.l=t.expiresAt||null),await this.j(t.user,t.token,t.refreshToken,t.expiresAt),s("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.l}, has_refresh: ${!!this.h}, has_session_token: ${!!this.o})`),this.startAutoRefresh())}async j(t,e,i,n){this.t=t,e&&(this.i=e),void 0!==i&&(this.h=i||null),void 0!==n&&(this.l=n||null);try{if(this.i&&this.t){const t={access_token:this.i,session_token:this.o,refresh_token:this.h,expires_at:this.l,user:this.t};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t))}else s("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){s("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}if(this.S){const t=e?"SIGNED_IN":"USER_UPDATED";this.T(t)}}async J(t="unknown"){s("log",`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.i=null,this.o=null,this.h=null,this.t=null,this.l=null,this.stopAutoRefresh();try{await this.storage.removeItem(this.STORAGE_KEY),s("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){s("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.T("SIGNED_OUT")}async q(t){const e=this.getBaseUrl(),i=new URL(`${e}/api/v0/auth/magic-link/verify`);i.searchParams.set("token",t);const s=await fetch(i.toString(),{method:"GET",headers:{...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw await n(s,"Magic link verification failed");const r=await s.json(),o=this.C(r);return await this.Y(o),o}async W(t){const e=this.getBaseUrl(),i=new URL(`${e}/api/v0/auth/verify-email`);i.searchParams.set("token",t);const s=await fetch(i.toString(),{method:"GET",headers:{...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw await n(s,"Email verification failed");if(!(s.headers.get("content-type")||s.headers.get("Content-Type")||"").includes("application/json"))return null;const r=await s.json(),o=this.C(r);return o.token&&o.user?.id?(await this.Y(o),o):null}async F(t,e){const i=e||("undefined"!=typeof window&&window.location.pathname.includes("reset-password")?"recovery":null);if(!i)return!1;if(s("log",`[DYPAI SDK] 🔗 Auth callback detectado en query (type: ${i}). Procesando...`),this.$(i),"recovery"===i||"invite"===i)return this.R(t),this.O(),this.m="PASSWORD_RECOVERY",!0;if(this.O(),"magiclink"===i)return await this.q(t),this.m="SIGNED_IN",!0;if("signup"===i||"email_change"===i){const e=await this.W(t);return this.m=e?.token?"SIGNED_IN":null,!0}return!1}async P(t=!0){s("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.G())return void s("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)return void s("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let t;s("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.session_token?t.session_token:t.access_token,this.h="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}if(t){s("log","[DYPAI SDK] 🔄 Validando sesión con el servidor...");const t=await this.refreshSession();if(t.error)return void s("warn","[DYPAI SDK] ⚠️ Sesión inválida. Limpiando:",t.error.message)}this.m="SIGNED_IN"}catch(t){s("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async G(){if("undefined"==typeof window)return!1;try{const t=new URLSearchParams(window.location.search),e=t.get("error"),i=t.get("token"),n=t.get("type");if(e){const i=t.get("error_code")||e,n=t.get("error_description")?.replace(/\+/g," ")||"Authentication error";return s("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${i} — ${n}`),this.p={code:i,message:n},this.O(),this.T("AUTH_ERROR"),!0}if(i)return await this.F(i,n);const r=window.location.hash.substring(1);if(!r)return!1;const o=new URLSearchParams(r);if(o.get("error")){const t=o.get("error_code")||"unknown",e=o.get("error_description")?.replace(/\+/g," ")||"Authentication error";return s("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${e}`),this.p={code:t,message:e},this.O(),this.T("AUTH_ERROR"),!0}const a=o.get("access_token");if(!a)return!1;const c=o.get("refresh_token"),h=o.get("type"),l=parseInt(o.get("expires_in")||"3600",10);s("log",`[DYPAI SDK] 🔗 Auth callback detectado en URL hash (type: ${h||"unknown"}). Procesando...`),this.$(h),this.O(),this.o=a,this.i=a,this.h=c||null,this.l=Math.floor(Date.now()/1e3)+l;const u=await fetch(`${this.getBaseUrl()}/api/v0/auth/get-session`,{headers:{Authorization:`Bearer ${a}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!u.ok)return s("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${u.status}). Limpiando...`),this.i=null,this.o=null,this.h=null,this.l=null,this.p={code:"token_invalid",message:"The authentication link is invalid or has expired"},this.T("AUTH_ERROR"),!0;const d=await u.json(),f=d?.jwt||u.headers.get("set-auth-jwt"),p=this.L(d),w={token:f||a,sessionToken:a,refreshToken:c||void 0,expiresIn:l,expiresAt:this.l,user:p};return await this.Y(w),this.m="recovery"===h||"invite"===h?"PASSWORD_RECOVERY":"SIGNED_IN",s("log",`[DYPAI SDK] 🔑 Callback type=${h}. Pending event: ${this.m}`),!0}catch(t){return s("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}K(){return this.i&&this.t?{access_token:this.i,refresh_token:this.h||void 0,expires_at:this.l||void 0,token_type:"bearer",user:this.t}:null}T(t="USER_UPDATED"){const e=this.K();s("log",`[DYPAI SDK] 📢 Notificando a ${this.u.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.u.forEach(i=>i(t,e))}handleSessionExpired(){this.J("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,c.CALLBACK_TYPE_STORAGE_KEY="dypai-auth-callback-type",c.CALLBACK_REDIRECT_STORAGE_KEY="dypai-auth-callback-redirect",c.RESET_TOKEN_STORAGE_KEY="dypai-auth-reset-token";class h{constructor(t,e,i){this.ws=null,this.bindings=[],this.V={},this.H=!1,this.topic=t,this.client=e,this.config=i||{}}on(t,e,i){return this.bindings.push({type:t,filter:e,callback:i}),this}subscribe(t){this.X=t;const e=this.client.Z();this.ws=e;const i=()=>{const i=this.bindings.filter(t=>"postgres_changes"===t.type);for(const t of i)e.send(JSON.stringify({type:"subscribe",table:t.filter.table||"*",event:t.filter.event||"*",filter:t.filter.filter?this.tt(t.filter.filter):void 0,schema:t.filter.schema||"public"}));const s=this.bindings.some(t=>"broadcast"===t.type),n=this.bindings.some(t=>"presence"===t.type);(s||n)&&e.send(JSON.stringify({type:"join",channel:this.topic,presence:!!n||void 0})),this.H=!0,t?.("SUBSCRIBED")};e.readyState===WebSocket.OPEN?i():e.readyState===WebSocket.CONNECTING&&e.addEventListener("open",i,{once:!0}),this.et&&e.removeEventListener("message",this.et);const s=t=>{try{const e=JSON.parse("string"==typeof t.data?t.data:"");this.it(e)}catch{}};return this.et=s,e.addEventListener("message",s),e.addEventListener("close",()=>{this.H=!1,t?.("CLOSED")},{once:!0}),e.addEventListener("error",()=>{t?.("CHANNEL_ERROR",new Error("WebSocket error"))},{once:!0}),this}st(t){const e=this.bindings.filter(t=>"postgres_changes"===t.type);for(const i of e)t.send(JSON.stringify({type:"subscribe",table:i.filter.table||"*",event:i.filter.event||"*",filter:i.filter.filter?this.tt(i.filter.filter):void 0,schema:i.filter.schema||"public"}));const i=this.bindings.some(t=>"broadcast"===t.type),s=this.bindings.some(t=>"presence"===t.type);(i||s)&&t.send(JSON.stringify({type:"join",channel:this.topic,presence:!!s||void 0})),this.X?.("SUBSCRIBED")}async send(t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return"error";try{return this.ws.send(JSON.stringify({type:"broadcast",channel:this.topic,event:t.event,payload:t.payload,self:this.config.broadcast?.self||!1})),"ok"}catch{return"error"}}async track(t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return"error";try{return this.ws.send(JSON.stringify({type:"presence",channel:this.topic,data:t})),"ok"}catch{return"error"}}async untrack(){return"ok"}presenceState(){return{...this.V}}async unsubscribe(){if(this.et&&this.ws&&(this.ws.removeEventListener("message",this.et),this.et=void 0),this.ws?.readyState===WebSocket.OPEN){const t=this.bindings.filter(t=>"postgres_changes"===t.type);for(const e of t)this.ws.send(JSON.stringify({type:"unsubscribe",table:e.filter.table}));this.ws.send(JSON.stringify({type:"leave",channel:this.topic}))}return this.H=!1,this.bindings=[],this.V={},"ok"}it(t){if("change"===t.type)for(const e of this.bindings)"postgres_changes"===e.type&&(e.filter.table&&"*"!==e.filter.table&&e.filter.table!==t.table||e.filter.event&&"*"!==e.filter.event&&e.filter.event!==t.event||e.callback({schema:"public",table:t.table,eventType:t.event,new:t.record||{},old:t.old_record||{},commit_timestamp:t.timestamp}));if("broadcast"===t.type&&t.channel===this.topic)for(const e of this.bindings)"broadcast"===e.type&&("*"!==e.filter.event&&e.filter.event!==t.event||e.callback({event:t.event,payload:t.payload,sender:t.sender,timestamp:t.timestamp}));if("presence_state"===t.type&&t.channel===this.topic){const e=this.config.presence?.key||"userId";this.V={};for(const[i,s]of Object.entries(t.presences||{})){const t=s[e]||i;this.V[t]||(this.V[t]=[]),this.V[t].push(s)}for(const t of this.bindings)"presence"===t.type&&("sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"}))}if("presence_diff"===t.type&&t.channel===this.topic){const e=this.config.presence?.key||"userId";if(t.joins)for(const[i,s]of Object.entries(t.joins)){const t=s[e]||i;this.V[t]||(this.V[t]=[]),this.V[t].push(s);for(const e of this.bindings)"presence"===e.type&&("join"!==e.filter.event&&"*"!==e.filter.event||e.callback({event:"join",key:t,newPresences:[s],currentPresences:this.V[t]}))}if(t.leaves)for(const[i,s]of Object.entries(t.leaves)){const t=s[e]||i;this.V[t]=(this.V[t]||[]).filter(t=>t.userId!==i),0===this.V[t].length&&delete this.V[t];for(const e of this.bindings)"presence"===e.type&&("leave"!==e.filter.event&&"*"!==e.filter.event||e.callback({event:"leave",key:t,leftPresences:[s],currentPresences:this.V[t]||[]}))}for(const t of this.bindings)"presence"!==t.type||"sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"})}}tt(t){const e=t.match(/^(\w+)=eq\.(.+)$/);if(e)return{[e[1]]:e[2]}}}class l{constructor(t,e){this.ws=null,this.channels=new Map,this.heartbeatTimer=null,this.reconnectAttempt=0,this.reconnectTimer=null,this.baseUrl=t,this.tokenProvider=e}channel(t,e){const i=this.channels.get(t);if(i)return i;const s=new h(t,this,e?.config);return this.channels.set(t,s),s}getChannels(){return Array.from(this.channels.values())}async removeChannel(t){await t.unsubscribe(),this.channels.delete(t.topic),0===this.channels.size&&this.disconnect()}async removeAllChannels(){for(const t of this.channels.values())await t.unsubscribe();this.channels.clear(),this.disconnect()}async setAuth(t){t&&(this.tokenProvider=()=>t),this.ws&&this.channels.size>0&&(this.disconnect(),this.Z())}disconnect(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.ws?.close(),this.ws=null}Z(){if(this.ws&&(this.ws.readyState===WebSocket.OPEN||this.ws.readyState===WebSocket.CONNECTING))return this.ws;const t=this.tokenProvider(),e=this.baseUrl.replace(/^http/,"ws")+`/realtime?token=${encodeURIComponent(t||"")}`,i=new WebSocket(e);return this.ws=i,i.addEventListener("open",()=>{this.reconnectAttempt=0;for(const t of this.channels.values())t.H&&t.st(i);this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>{i.readyState===WebSocket.OPEN&&i.send(JSON.stringify({type:"heartbeat"}))},25e3)}),i.addEventListener("close",()=>{if(this.ws=null,this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.channels.size>0&&!this.reconnectTimer){const t=l.RECONNECT_INTERVALS[Math.min(this.reconnectAttempt,l.RECONNECT_INTERVALS.length-1)];this.reconnectAttempt++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.channels.size>0&&this.Z()},t)}}),i}}l.RECONNECT_INTERVALS=[1e3,2e3,5e3,1e4,3e4];class u{get direct(){if(!this.nt)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.nt}set direct(t){this.nt=t}constructor(t){this.api=t}from(t){return new d(t,this.api)}}class d{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 f=1e3;class p{constructor(t){this.api=t}from(t){return new w(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 w{constructor(t,e){this.table=t,this.api=e,this.rt="public",this.ot=[],this.ct=100,this.ht=0,this.ut="ASC"}schema(t){return this.rt=t,this}eq(t,e){return this.ot.push({column:t,operator:"eq",value:e}),this}neq(t,e){return this.ot.push({column:t,operator:"neq",value:e}),this}gt(t,e){return this.ot.push({column:t,operator:"gt",value:e}),this}gte(t,e){return this.ot.push({column:t,operator:"gte",value:e}),this}lt(t,e){return this.ot.push({column:t,operator:"lt",value:e}),this}lte(t,e){return this.ot.push({column:t,operator:"lte",value:e}),this}like(t,e){return this.ot.push({column:t,operator:"ilike",value:e}),this}ilike(t,e){return this.like(t,e)}isNull(t){return this.ot.push({column:t,operator:"is_null",value:null}),this}is(t,e){return this.isNull(t)}notNull(t){return this.ot.push({column:t,operator:"not_null",value:null}),this}in(t,e){return this.ot.push({column:t,operator:"in",value:e}),this}contains(t,e){return this.ot.push({column:t,operator:"array_contains",value:e}),this}containedBy(t,e){return this.ot.push({column:t,operator:"contained_by",value:e}),this}overlaps(t,e){return this.ot.push({column:t,operator:"overlaps",value:e}),this}textSearch(t,e){return this.ot.push({column:t,operator:"fts",value:e}),this}phraseSearch(t,e){return this.ot.push({column:t,operator:"phfts",value:e}),this}not(t,e,i){return this.ot.push({column:t,operator:"not",value:{operator:e,value:i}}),this}or(t){return this.ot.push({operator:"or",or:t}),this}limit(t){return this.ct=t,this}offset(t){return this.ht=t,this}orderBy(t,e="ASC"){return this.dt=t,this.ut=e,this}async select(t){const e=[...this.ot];let i=this.ft;if("string"==typeof t)i=t;else if(t&&"object"==typeof t)for(const[i,s]of Object.entries(t))e.push({column:i,operator:"eq",value:s});return this.execute("select",{...i&&{columns:i},filters:e,limit:this.ct,offset:this.ht,sort_by:this.dt,order:this.ut})}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.ot],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>f){const e=[];for(let i=0;i<t.length;i+=f){const s=t.slice(i,i+f),{data:n,error:r}=await this.execute("insert",{data:s,mode:"bulk"});if(r)return{data:null,error:r};n&&e.push(...Array.isArray(n)?n:[n])}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.ot.length?{data:null,error:new t("Update requires at least one filter. Use .eq(), .in(), etc.",400)}:this.execute("update",{data:e,filters:this.ot})}async delete(){return 0===this.ot.length?{data:null,error:new t("Delete requires at least one filter.",400)}:this.execute("delete",{filters:this.ot})}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.rt,table_name:this.table,...e})}}class y{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 s=await this.api.put(`admin/users/${t}`,i);return s.data&&(s.data=e(s.data)),s}async delete(t){return this.api.delete(`admin/users/${t}`)}}let m=null;function S(t){m=t}const g=t=>{const{title:e,description:i,variant:s="default"}=t,n=`${"error"===s?"❌":"success"===s?"✅":"warning"===s?"⚠️":"info"===s?"ℹ️":"📢"} ${e}${i?`: ${i}`:""}`;"error"===s?console.error(n):"warning"===s&&console.warn(n)};function v(){const t=m||g;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 b=t=>(m||g)(t),k=(t,e)=>b({title:t,description:e,variant:"success"}),D=(t,e)=>b({title:t,description:e,variant:"error"}),A=(t,e)=>b({title:t,description:e,variant:"warning"}),_=(t,e)=>b({title:t,description:e,variant:"info"});let P={},T=null;function I(t){T=t}function E(t){P={...P,...t}}const O=new Map;function $(){let t=P;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...P}}catch(t){}return t}async function R(e,i,s,n,r,o,a,c){const h=$(),l=c||null;if(!l&&!s.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof s||!s.trim())throw new Error("Endpoint debe ser un string válido");let u;if(s.startsWith("http"))u=s;else{const t=l.replace(/\/+$/,"");u=s.startsWith("/")?t+s:t+"/api/v0/"+s}if(o&&Object.keys(o).length>0){const t=new URLSearchParams,e=(i,s)=>{null!=s&&(Array.isArray(s)?s.forEach((t,s)=>e(`${i}[${s}]`,t)):"object"==typeof s?Object.entries(s).forEach(([t,s])=>e(`${i}[${t}]`,s)):t.append(i,String(s)))};Object.entries(o).forEach(([t,i])=>e(t,i));const i=t.toString();i&&(u+=`?${i}`)}const d="GET"===i?`${i}:${u}:${JSON.stringify(n)}`:null;if(d&&O.has(d))return O.get(d);const f=n instanceof FormData,p={...h.headers||{},...f?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...a&&{"x-api-key":a}},w={method:i,headers:p,credentials:"include"};n&&"GET"!==i&&"DELETE"!==i&&(w.body=f?n:JSON.stringify(n));const y=h.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!y)throw new Error("Fetch no disponible.");const m=(async()=>{const e=$().toast||b,s=(void 0!==r?r:!1!==h.showToasts)&&e;try{const n=await y(u,w);if(!n.ok){let i,r="Error en la petición";try{const t=await n.text();try{const e=JSON.parse(t);i=e,r=e.message||e.msg||e.error_description||e.error||r}catch{t.length<200&&(r=t)}}catch{}throw 401===n.status&&h.onUnauthorized&&h.onUnauthorized(),s&&e({title:"Error",description:r,variant:"error"}),new t(r,n.status,void 0,i)}!s||"POST"!==i&&"PUT"!==i&&"PATCH"!==i&&"DELETE"!==i||e({title:"Éxito",description:"Operación completada",variant:"success"});const r=n.headers.get("content-type")||"";return r.includes("application/pdf")||r.includes("image/")||r.includes("audio/")||r.includes("video/")||r.includes("application/octet-stream")||r.includes("application/zip")||r.includes("application/vnd.openxmlformats-officedocument")?await n.blob():r.includes("application/json")?await n.json():await n.text()}finally{d&&O.delete(d)}})();return d&&O.set(d,m),m}async function N(t,e,i,s){const n=s?.method||(i?"POST":"GET"),{data:r,error:o}=await("GET"===n?t.get(e,{params:s?.params}):t.post(e,i,{params:s?.params}));if(o)throw o;if(r instanceof Blob){const t=window.URL.createObjectURL(r),e=document.createElement("a");e.href=t,e.download=s?.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 n=s?.fileName||i;if(n){const e=document.createElement("a");e.href=t,e.download=n,document.body.appendChild(e),e.click(),document.body.removeChild(e)}else window.open(t,"_blank")}}}async function U(e,i,s,n){const{data:r,error:o}=await e.post(i,{file_path:s.name,content_type:s.type||"application/octet-stream",size_bytes:s.size,confirm:!1,client_upload:!0,...n?.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 K(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 K(t);const s=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of s){const e=Array.isArray(t)?t:Object.values(t);for(const t of e)if(t&&(t.upload_url||t.uploadUrl||t.url))return K(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;n?.onProgress&&n.onProgress(10);const f=await fetch(c,{method:h,headers:{"Content-Type":s.type||"application/octet-stream",...l},body:s});if(!f.ok)throw new t("Direct upload to cloud storage failed",f.status);n?.onProgress&&n.onProgress(90);const p=n?.confirmEndpoint||i,{data:w,error:y}=await e.post(p,{...n?.params,bucket:a.bucket||n?.params?.bucket,file_path:u,storage_path:d,filename:s.name,content_type:s.type||"application/octet-stream",size_bytes:s.size,confirm:!0,client_upload:!0});if(y)throw y;return n?.onProgress&&n.onProgress(100),w}function K(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 C(e){const i={get:Y(e,"GET"),post:Y(e,"POST"),put:Y(e,"PUT"),patch:Y(e,"PATCH"),delete:Y(e,"DELETE"),upload:async(e,s,n)=>{try{return{data:await U(i,e,s,n),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Upload failed",e?.status??500)}}},download:async(e,s,n)=>{try{return await N(i,e,s,{...n,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 Y(e,i){const s="POST"===i||"PUT"===i||"PATCH"===i;return async(n,r,o)=>{const a=e();let c,h={};s?(c=r,h=o||{}):(h=r||{},c=void 0);const l=h.token||a.token||(T?T():"")||"",u=h.apiKey||a.apiKey;try{return{data:await R(l,i,n,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 x(e){const i=()=>"function"==typeof e?e():e,s={get:L(i,"GET"),post:L(i,"POST"),put:L(i,"PUT"),patch:L(i,"PATCH"),delete:L(i,"DELETE"),upload:async(e,i,n)=>{try{return{data:await U(s,e,i,n),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,n)=>{try{return await N(s,e,i,{...n,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 s}function L(e,i){return async(s,n,r)=>{const o=e(),a=await async function(t,e,i){let s,n={};"POST"===t||"PUT"===t||"PATCH"===t?(s=e,n=i||{}):(n=e||{},s=void 0);let r=n.token;return!r&&T&&(r=T()||""),r=r||"",{token:r,apiKey:n.apiKey,body:s,params:n.params,showToasts:n.showToasts}}(i,n,r),c=a.token||o.token||"",h=a.apiKey||o.apiKey;try{return{data:await R(c,i,s,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 j{get baseUrl(){return this.wt}getAuthToken(){return this.auth.token||this.yt||null}constructor(t){const{baseUrl:e,apiKey:i}=t;this.wt=e,this.yt=i,this.realtime=new l(e,()=>this.getAuthToken());const s=t.auth?.storageKey||t.storageKey;t.global&&function(t){P={...P,...t}}(t.global),this.auth=new c({baseUrl:e,apiKey:i,storageKey:s,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const n=C(()=>({token:this.auth.token,apiKey:i,baseUrl:e}));if(this.auth.api=n,this.db=new u(n),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=C(()=>({token:t.serviceRoleKey,baseUrl:e}));this.db.direct=new p(i)}if(this.users=new y(n),this.api=x(()=>({token:this.auth.token||"",apiKey:i,baseUrl:e})),I(()=>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))}})}E({onUnauthorized:()=>this.auth.handleSessionExpired()})}from(t){return this.db.from(t)}async me(){return this.auth.getUser()}}function z(t,e,i){if(!t)throw new Error("createClient() requiere la URL base");return new j({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?i:e})}let J={};function B(t){J={...J,toast:b,...t},W()}function q(){return{...J}}function W(){try{const{configureApiService:t}=require("../services/ApiService");t(J)}catch(t){}}function M(){J={},W()}async function F(){W()}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{p as DirectDBModule,w as DirectQueryBuilder,j as DypaiClient,t as DypaiError,l as DypaiRealtimeClient,G as PACKAGE_INFO,h as RealtimeChannel,R as callApi,E as configureApiService,B as configureDypaiServices,x as createApiClient,z as createClient,q as getGlobalConfig,F as reloadDypaiConfig,M as resetGlobalConfig,S as setToastFunction,I as setTokenProvider,b as toast,D as toastError,_ as toastInfo,k as toastSuccess,A as toastWarning,v as useToast};
1
+ class t extends Error{constructor(t,e=500,i,s){super(t),this.status=e,this.code=i,this.details=s,this.name="DypaiError"}}function e(t){if(!t)return{};const e=t.user||t,i=e.name||e.user_metadata?.full_name||null,s=e.emailVerified??e.confirmed_at??null,n=e.app_metadata||{},r=e.user_metadata||{full_name:i};return{id:e.id,email:e.email,phone:e.phone||null,role:n.role||r.role||e.role||"authenticated",created_at:e.createdAt||e.created_at,updated_at:e.updatedAt||e.updated_at,confirmed_at:s?"boolean"==typeof s?(new Date).toISOString():s:null,last_sign_in_at:e.last_sign_in_at||null,app_metadata:n,user_metadata:{...r,full_name:i},roleDetails:{name:n.role||r.role||null,weight:0},appContext:{app_id:"default"}}}function i(e){return e instanceof t}class s extends t{constructor(t="Auth session missing"){super(t,400,"session_missing"),this.name="AuthSessionMissingError"}}function n(t){return t instanceof s}class r extends t{constructor(t="Invalid login credentials"){super(t,400,"invalid_credentials"),this.name="AuthInvalidCredentialsError"}}function o(t){return t instanceof r}class a extends t{constructor(t="Invalid or expired token"){super(t,401,"invalid_token"),this.name="AuthInvalidTokenError"}}function c(t){return t instanceof a}class h extends t{constructor(t="Password is too weak",e=[]){super(t,400,"weak_password"),this.name="AuthWeakPasswordError",this.reasons=e}}function l(t){return t instanceof h}class u extends t{constructor(t="Email not verified"){super(t,403,"email_not_verified"),this.name="AuthEmailNotVerifiedError"}}function d(t){return t instanceof u}class f extends t{constructor(t="User already exists"){super(t,409,"user_already_exists"),this.name="AuthUserAlreadyExistsError"}}function p(t){return t instanceof f}class w extends t{constructor(t,e=0){super(t,e,"retryable_fetch"),this.name="RetryableFetchError"}}function y(t){return t instanceof w}function m(t){return 0===t||408===t||429===t||t>=500&&t<600}function v(e,i,n){const o=(e||"").toLowerCase(),c=(n||"").toLowerCase();return c.includes("invalid_token")||c.includes("invalid_grant")||o.includes("invalid token")||o.includes("expired")||o.includes("revoked")?new a(e):c.includes("session_missing")||o.includes("no session")||o.includes("session missing")?new s(e):c.includes("invalid_credentials")||c.includes("invalid_login")||o.includes("invalid credentials")||o.includes("invalid login")?new r(e):c.includes("email_not_verified")||o.includes("email not verified")?new u(e):c.includes("user_already_exists")||c.includes("user_exists")||o.includes("already exists")||o.includes("already registered")?new f(e):c.includes("weak_password")||o.includes("password is too weak")||o.includes("password too short")?new h(e):m(i)?new w(e,i):new t(e,i,n)}let S=!1;function g(t,...e){"error"===t?console.error(...e):S&&("warn"===t?console.warn(...e):console.log(...e))}async function b(t,e){let i={};try{i=await t.json()}catch{}const s=i.msg||i.error_description||i.message||e,n=i.error_code||i.error||i.code||void 0;return v(s,t.status,n)}class k{constructor(){this.promise=new Promise((t,e)=>{this.resolve=t,this.reject=e})}}const D={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 _{constructor(t,e=null){this.config=t,this.t=null,this.i=null,this.o=null,this.h=null,this.l=null,this.u=[],this.p=null,this.m=null,this.v=!1,this.S=null,this.k=null,this.D=0,this.A=0,this._=null,this.storage=t.storage||D,S=!!t.debug;const i=t.storageKey||this.deriveStorageKey(t.apiKey);if(g("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${i})`),this.STORAGE_KEY=`dypai-${i}-auth-session`,this.P=this.I().then(()=>{this.i&&(this.getUser().catch(()=>{}),this.startAutoRefresh());const t=this.m;this.m=null,t?setTimeout(()=>{this.v=!0,this.T(t)},0):this.v=!0}),"undefined"!=typeof window){if(window.addEventListener("visibilitychange",this.O.bind(this)),window.addEventListener("focus",this.O.bind(this)),"undefined"!=typeof BroadcastChannel)try{this._=new BroadcastChannel(`bc:${this.STORAGE_KEY}`),this._.addEventListener("message",t=>{const e=t.data;e&&"object"==typeof e&&(g("log",`[DYPAI SDK] 📡 BroadcastChannel: ${e.type}`),"SIGNED_OUT"===e.type?(this.i=null,this.o=null,this.h=null,this.t=null,this.l=null,this.stopAutoRefresh(),this.T("SIGNED_OUT")):"SESSION_UPDATED"===e.type&&this.I(!1))})}catch(t){g("warn","[DYPAI SDK] BroadcastChannel no disponible, usando storage event:",t)}window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(g("log","[DYPAI SDK] 🔄 Sesión actualizada (storage event). Sincronizando..."),this.I(!1))})}}$(t){return 0===t||408===t||429===t||t>=500&&t<600}R(t){if(this._)try{this._.postMessage({type:t,timestamp:Date.now()})}catch{}}async O(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(g("log","[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this.I(!0),this.i&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}getBaseUrl(){return this.config.baseUrl||"http://localhost:8000"}U(){if("undefined"==typeof window)return;const t=new URLSearchParams(window.location.search);["token","type","error","error_code","error_description","access_token","refresh_token","expires_in"].forEach(e=>t.delete(e));const e=t.toString(),i=`${window.location.pathname}${e?`?${e}`:""}`;window.history.replaceState({},document.title,i)}N(t){try{if(!t)return;sessionStorage.setItem(_.CALLBACK_TYPE_STORAGE_KEY,t),"recovery"===t||"invite"===t?sessionStorage.setItem(_.CALLBACK_REDIRECT_STORAGE_KEY,"PASSWORD_RECOVERY"):sessionStorage.removeItem(_.CALLBACK_REDIRECT_STORAGE_KEY)}catch{}}K(t){try{sessionStorage.setItem(_.RESET_TOKEN_STORAGE_KEY,t)}catch{}}C(){try{return sessionStorage.getItem(_.RESET_TOKEN_STORAGE_KEY)}catch{return null}}Y(){try{sessionStorage.removeItem(_.RESET_TOKEN_STORAGE_KEY)}catch{}}get user(){return this.t}get token(){return this.i}get lastError(){return this.p}consumeCallbackType(){try{const t=sessionStorage.getItem(_.CALLBACK_TYPE_STORAGE_KEY);return sessionStorage.removeItem(_.CALLBACK_TYPE_STORAGE_KEY),sessionStorage.removeItem(_.CALLBACK_REDIRECT_STORAGE_KEY),t}catch{return null}}get isPasswordRecoveryCallback(){try{return"PASSWORD_RECOVERY"===sessionStorage.getItem(_.CALLBACK_REDIRECT_STORAGE_KEY)}catch{return!1}}isLoggedIn(){return!(!this.i||!this.t)}onAuthStateChange(t){return g("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.u.push(t),this.P.then(()=>{const e=this.L();g("log",`[DYPAI SDK] 📣 INITIAL_SESSION para suscriptor (Sesión activa: ${!!e})`),t("INITIAL_SESSION",e)}).catch(e=>{g("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.u=this.u.filter(e=>e!==t)}}}}}async signInWithPassword(t){return A((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-in/email`,i={email:t.email||t.identifier||"",password:t.password},s=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(i)});if(!s.ok)throw await b(s,"Login failed");const n=await s.json(),r=this.j(n);return await this.B(r),r})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return A((async()=>{const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-up/email`,{email:s,phone:n,password:r,user_data:o,...a}=t,c=s||"",h=a.full_name||a.name||c.split("@")[0],l={...a,...o||{}},u={email:c,password:r,name:h};Object.keys(l).length>0&&Object.assign(u,l),e?.redirectTo?u.callbackURL=e.redirectTo:"undefined"!=typeof window&&(u.callbackURL=window.location.href.split("#")[0]);const d=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(u)});if(!d.ok)throw await b(d,"Registration failed");const f=await d.json(),p=this.j(f),w={...p,confirmationRequired:!p.token};return w.token?await this.B(w):g("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),w})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this.P,!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.l||0)-t<30+_.CLOCK_SKEW_TOLERANCE_S&&(this.h||this.o)&&(g("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>g("warn","[DYPAI SDK] Error refreshing session in getSession:",t))),{data:{access_token:this.i,refresh_token:this.h||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()=>{await this.P;const e=this.o||this.i;if(!e)throw new s("No hay sesión activa");const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,n=await fetch(i,{headers:{Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!n.ok)throw new t("Session invalid",n.status);const r=await n.json(),o=r?.jwt||n.headers.get("set-auth-jwt");o&&(this.i=o);const a=this.J(r);return this.M(a),a})())}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:s=window.location.href,scopes:n}=i,r=this.config.baseUrl||"http://localhost:8000",o=new URLSearchParams({provider:e,callbackURL:s});n?.length&&o.set("scopes",n.join(" "));const a=`${r}/api/v0/auth/sign-in/social?${o.toString()}`;window.location.href=a})())}async signOut(){return A((async()=>{try{const t=this.o||this.i;if(t){const e=this.config.baseUrl||"http://localhost:8000";await fetch(`${e}/api/v0/auth/sign-out`,{method:"POST",headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}})}}finally{this.W("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return A((async()=>{const i=this.config.baseUrl||"http://localhost:8000",s={email:t};e?.redirectTo?s.redirectTo=e.redirectTo:"undefined"!=typeof window&&(s.redirectTo=window.location.href.split("#")[0]);const n=await fetch(`${i}/api/v0/auth/request-password-reset`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!n.ok)throw await b(n,"Recovery failed");return await n.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t,e){return A((async()=>{const i=this.config.baseUrl||"http://localhost:8000",s={email:t};e?.redirectTo?s.callbackURL=e.redirectTo:"undefined"!=typeof window&&(s.callbackURL=window.location.href.split("#")[0]);const n=await fetch(`${i}/api/v0/auth/send-verification-email`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(s)});if(!n.ok)throw await b(n,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.S)return g("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.S.promise;const e=new k;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 s=new AbortController,n=setTimeout(()=>s.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:s.signal},async()=>(clearTimeout(n),await i()))}catch(s){if("AbortError"===s.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await i();throw s}}return await i()}(i,_.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.q());e.resolve(t)}catch(i){const s=i instanceof t?i:new t(i.message||"Error refrescando sesión",401);e.resolve({data:null,error:s})}finally{this.S=null}})(),e.promise}async q(){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 g("log","[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.i=t.access_token,this.o=t.session_token||t.access_token,this.h=t.refresh_token,this.l=t.expires_at,this.t=t.user,this.D=0,this.T("TOKEN_REFRESHED"),{data:{token:this.i,sessionToken:this.o||void 0,refreshToken:this.h||void 0,expiresAt:this.l||void 0,user:this.t},error:null}}const i=this.o||this.i;if(!i)throw new s("No hay sesión activa");g("log","[DYPAI SDK] 🔄 Verificando sesión activa con session_token...");const n=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,r=new AbortController,o=setTimeout(()=>r.abort(),_.REFRESH_TIMEOUT_MS);let a;try{a=await fetch(n,{method:"GET",headers:{Authorization:`Bearer ${i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},signal:r.signal})}finally{clearTimeout(o)}if(!a.ok){let e={};try{e=await a.json()}catch{}const i=a.status,s=e.error||e.code||"",n=(e.error_description||e.msg||"").toLowerCase(),r=this.$(i),o=i>=400&&i<500&&!r,c="invalid_grant"===s||n.includes("invalid refresh token")||n.includes("revoked")||n.includes("expired")||n.includes("invalid token");throw o&&c?(g("error",`[DYPAI SDK] ❌ Token inválido/revocado (${i}). Limpiando sesión.`),this.W(`refreshSession failed (${i}: ${s})`)):o?(g("error",`[DYPAI SDK] ❌ Error 4xx en refresh (${i}). Limpiando sesión.`),this.W(`refreshSession failed (${i})`)):g("warn",`[DYPAI SDK] ⚠️ Error transitorio en refresh (${i}). Manteniendo sesión.`),new t(e.msg||e.error_description||"Refresh session failed",i)}const c=await a.json(),h=a.headers.get("set-auth-jwt"),l=c?.jwt,u=l||h,d=this.j(c,this.i);return u&&(d.token=u),d.sessionToken=i,this.D=0,await this.B(d),g("log",`[DYPAI SDK] ✅ Session refresh exitoso (fresh JWT: ${!!u}).`),{data:d,error:null}}catch(e){this.D++,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),n=Math.min(_.RETRY_BASE_MS*Math.pow(2,this.D),_.MAX_RETRY_MS);return i?g("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${_.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${n}ms (intento ${this.D})`):g("error","[DYPAI SDK] ❌ Refresh falló:",s.message,`(status: ${s.status}). Backoff: ${n}ms (intento ${this.D})`),this.D>=_.MAX_REFRESH_FAILURES&&(g("error",`[DYPAI SDK] 🗑️ ${this.D} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.W(`${this.D} consecutive refresh failures`),this.D=0),{data:null,error:s}}}async signInWithOtp(e){return A((async()=>{if(e.phone)throw new t("Phone OTP is not supported by the current DYPAI auth backend",400);if(!e.email)throw new t("Email is required for magic link sign-in",400);const i=await this.signInWithMagicLink(e.email);if(i.error)throw i.error;return{status:!0}})())}async verifyOtp(e){return A((async()=>{if(!e.token)throw new t("Verification token is required",400);if("magiclink"===e.type)return await this.G(e.token);if("signup"===e.type||"email_change"===e.type){const i=await this.F(e.token);if(i)return i;throw new t("Verification completed but no session was returned",400)}if("recovery"===e.type||"invite"===e.type)throw new t("Recovery links do not use verifyOtp(). Use setPassword() after the PASSWORD_RECOVERY callback.",400);throw new t("Unsupported verification type",400)})())}async updateUser(e){return A((async()=>{const i=this.o||this.i;if(!i)throw new s("No hay sesión activa");const n=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/update-user`,r=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(e)});if(!r.ok){const e=await r.json();throw new t(e.detail||"Update user failed",r.status)}const o=await r.json(),a=this.J(o);return this.M(a),a})())}async setPassword(e){return A((async()=>{if("undefined"==typeof window)throw new t("setPassword requiere un entorno de navegador",400);const i=new URLSearchParams(window.location.search).get("token")||this.C();if(!i)throw new t("No reset token found in the current URL",400);const s=this.config.baseUrl||"http://localhost:8000",n=await fetch(`${s}/api/v0/auth/reset-password`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({newPassword:e,token:i})});if(!n.ok)throw await b(n,"Reset password failed");this.Y(),this.U();const r=await n.json();if(r?.jwt&&r?.session_token&&r?.user){const t={token:r.jwt,sessionToken:r.session_token,user:this.J(r),expiresAt:r.session?.expiresAt?Math.floor(new Date(r.session.expiresAt).getTime()/1e3):void 0};await this.B(t)}return{status:r?.status??!0}})())}async signInWithMagicLink(t,e){return A((async()=>{const i=this.config.baseUrl||"http://localhost:8000",s=e?.redirectTo||("undefined"!=typeof window?window.location.href.split("#")[0]:void 0),n=await fetch(`${i}/api/v0/auth/sign-in/magic-link`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({email:t,callbackURL:s})});if(!n.ok)throw await b(n,"Magic link failed");return{message:"Magic link sent"}})())}async enableTwoFactor(){return A((async()=>{const t=this.o||this.i;if(!t)throw new s("No hay sesión activa");const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/two-factor/enable`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw await b(i,"Enable 2FA failed");return await i.json()})())}async verifyTwoFactor(t){return A((async()=>{const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/two-factor/verify-totp`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.o||this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({code:t})});if(!i.ok)throw await b(i,"2FA verification failed");const s=await i.json(),n={token:s.jwt||s.token||this.i,expiresIn:604800,expiresAt:Math.floor(Date.now()/1e3)+604800,user:this.J(s)};return await this.B(n),n})())}async disableTwoFactor(){return A((async()=>{const t=this.o||this.i;if(!t)throw new s("No hay sesión activa");const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/two-factor/disable`,{method:"POST",headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw await b(i,"Disable 2FA failed");return{message:"2FA disabled"}})())}async createOrganization(t,e){return A((async()=>{const i=this.o||this.i;if(!i)throw new s("No hay sesión activa");const n=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${n}/api/v0/auth/organization/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({name:t,slug:e})});if(!r.ok)throw await b(r,"Create organization failed");return await r.json()})())}async inviteMember(t,e,i){return A((async()=>{const n=this.o||this.i;if(!n)throw new s("No hay sesión activa");const r=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${r}/api/v0/auth/organization/invite-member`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({organizationId:t,email:e,role:i||"member"})});if(!o.ok)throw await b(o,"Invite member failed");return await o.json()})())}async listOrganizations(){return A((async()=>{const t=this.o||this.i;if(!t)throw new s("No hay sesión activa");const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/organization/list`,{headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw await b(i,"List organizations failed");return await i.json()})())}startAutoRefresh(){this.stopAutoRefresh(),g("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.V(),this.k=setInterval(()=>this.V(),_.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.k&&(g("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.k),this.k=null)}async V(){try{const t=Math.floor(Date.now()/1e3),e=this.l||0;if(!this.h&&!this.o||!e)return;if(1e3*(e-t)>_.AUTO_REFRESH_TICK_THRESHOLD*_.AUTO_REFRESH_TICK_DURATION_MS+1e3*_.CLOCK_SKEW_TOLERANCE_S)return;if(this.D>0){const t=Math.min(_.RETRY_BASE_MS*Math.pow(2,this.D),_.MAX_RETRY_MS);if(Date.now()-this.A<t)return}g("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){g("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}J(t){return t?e(t):(g("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}j(t,e){const i="number"==typeof t?.expiresIn?t.expiresIn:"number"==typeof t?.expires_in?t.expires_in:604800,s=t?.jwt||void 0,n=t?.session_token||t?.token||t?.access_token||t?.session?.token||t?.session?.accessToken||t?.session?.access_token||e;return{token:s||n,sessionToken:n,refreshToken:t?.refreshToken||t?.refresh_token||t?.session?.refreshToken||t?.session?.refresh_token||void 0,expiresIn:i,expiresAt:Math.floor(Date.now()/1e3)+i,user:this.J(t)}}async B(t){this.p=null,t.token&&(this.i=t.token,this.o=t.sessionToken||t.token,void 0!==t.refreshToken&&(this.h=t.refreshToken||null),void 0!==t.expiresAt&&(this.l=t.expiresAt||null),await this.M(t.user,t.token,t.refreshToken,t.expiresAt),g("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.l}, has_refresh: ${!!this.h}, has_session_token: ${!!this.o})`),this.startAutoRefresh())}async M(t,e,i,s){this.t=t,e&&(this.i=e),void 0!==i&&(this.h=i||null),void 0!==s&&(this.l=s||null);try{if(this.i&&this.t){const t={access_token:this.i,session_token:this.o,refresh_token:this.h,expires_at:this.l,user:this.t,version:2};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t)),this.R("SESSION_UPDATED")}else g("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){g("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}if(this.v){const t=e?"SIGNED_IN":"USER_UPDATED";this.T(t)}}async W(t="unknown"){g("log",`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.i=null,this.o=null,this.h=null,this.t=null,this.l=null,this.stopAutoRefresh();try{await this.storage.removeItem(this.STORAGE_KEY),g("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){g("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.R("SIGNED_OUT"),this.T("SIGNED_OUT")}async G(t){const e=this.getBaseUrl(),i=new URL(`${e}/api/v0/auth/magic-link/verify`);i.searchParams.set("token",t);const s=await fetch(i.toString(),{method:"GET",headers:{...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw await b(s,"Magic link verification failed");const n=await s.json(),r=this.j(n);return await this.B(r),r}async F(t){const e=this.getBaseUrl(),i=new URL(`${e}/api/v0/auth/verify-email`);i.searchParams.set("token",t);const s=await fetch(i.toString(),{method:"GET",headers:{...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw await b(s,"Email verification failed");if(!(s.headers.get("content-type")||s.headers.get("Content-Type")||"").includes("application/json"))return null;const n=await s.json(),r=this.j(n);return r.token&&r.user?.id?(await this.B(r),r):null}async H(t,e){const i=e||("undefined"!=typeof window&&window.location.pathname.includes("reset-password")?"recovery":null);if(!i)return!1;if(g("log",`[DYPAI SDK] 🔗 Auth callback detectado en query (type: ${i}). Procesando...`),this.N(i),"recovery"===i||"invite"===i)return this.K(t),this.U(),this.m="PASSWORD_RECOVERY",!0;if(this.U(),"magiclink"===i)return await this.G(t),this.m="SIGNED_IN",!0;if("signup"===i||"email_change"===i){const e=await this.F(t);return this.m=e?.token?"SIGNED_IN":null,!0}return!1}async I(t=!0){g("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.X())return void g("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)return void g("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");let i;try{i=JSON.parse(e)}catch{return g("warn","[DYPAI SDK] ⚠️ Sesión corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!i||"object"!=typeof i||"string"!=typeof i.access_token)return g("warn","[DYPAI SDK] ⚠️ Sesión con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);if((i.version||1)<2&&!i.session_token)return g("warn","[DYPAI SDK] 🔄 Sesión en formato antiguo (v1) sin session_token. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);if(this.i=i.access_token,this.o="string"==typeof i.session_token?i.session_token:i.access_token,this.h="string"==typeof i.refresh_token?i.refresh_token:null,this.l="number"==typeof i.expires_at?i.expires_at:null,this.t=i.user&&"object"==typeof i.user?i.user:null,t){g("log","[DYPAI SDK] 🔄 Validando sesión con el servidor...");const t=await this.refreshSession();if(t.error)return void g("warn","[DYPAI SDK] ⚠️ Sesión inválida. Limpiando:",t.error.message)}this.m="SIGNED_IN"}catch(t){g("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async X(){if("undefined"==typeof window)return!1;try{const t=new URLSearchParams(window.location.search),e=t.get("error"),i=t.get("token"),s=t.get("type");if(e){const i=t.get("error_code")||e,s=t.get("error_description")?.replace(/\+/g," ")||"Authentication error";return g("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${i} — ${s}`),this.p={code:i,message:s},this.U(),this.T("AUTH_ERROR"),!0}if(i)return await this.H(i,s);const n=window.location.hash.substring(1);if(!n)return!1;const r=new URLSearchParams(n);if(r.get("error")){const t=r.get("error_code")||"unknown",e=r.get("error_description")?.replace(/\+/g," ")||"Authentication error";return g("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${e}`),this.p={code:t,message:e},this.U(),this.T("AUTH_ERROR"),!0}const o=r.get("access_token");if(!o)return!1;const a=r.get("refresh_token"),c=r.get("type"),h=parseInt(r.get("expires_in")||"3600",10);g("log",`[DYPAI SDK] 🔗 Auth callback detectado en URL hash (type: ${c||"unknown"}). Procesando...`),this.N(c),this.U(),this.o=o,this.i=o,this.h=a||null,this.l=Math.floor(Date.now()/1e3)+h;const l=await fetch(`${this.getBaseUrl()}/api/v0/auth/get-session`,{headers:{Authorization:`Bearer ${o}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!l.ok)return g("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${l.status}). Limpiando...`),this.i=null,this.o=null,this.h=null,this.l=null,this.p={code:"token_invalid",message:"The authentication link is invalid or has expired"},this.T("AUTH_ERROR"),!0;const u=await l.json(),d=u?.jwt||l.headers.get("set-auth-jwt"),f=this.J(u),p={token:d||o,sessionToken:o,refreshToken:a||void 0,expiresIn:h,expiresAt:this.l,user:f};return await this.B(p),this.m="recovery"===c||"invite"===c?"PASSWORD_RECOVERY":"SIGNED_IN",g("log",`[DYPAI SDK] 🔑 Callback type=${c}. Pending event: ${this.m}`),!0}catch(t){return g("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}L(){return this.i&&this.t?{access_token:this.i,refresh_token:this.h||void 0,expires_at:this.l||void 0,token_type:"bearer",user:this.t}:null}T(t="USER_UPDATED"){const e=this.L();g("log",`[DYPAI SDK] 📢 Notificando a ${this.u.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.u.forEach(i=>i(t,e))}handleSessionExpired(){this.W("handleSessionExpired called (likely 401 from API)")}}_.REFRESH_TIMEOUT_MS=15e3,_.MAX_REFRESH_FAILURES=5,_.AUTO_REFRESH_TICK_DURATION_MS=3e4,_.AUTO_REFRESH_TICK_THRESHOLD=3,_.RETRY_BASE_MS=200,_.MAX_RETRY_MS=3e4,_.LOCK_ACQUIRE_TIMEOUT_MS=5e3,_.CLOCK_SKEW_TOLERANCE_S=10,_.CALLBACK_TYPE_STORAGE_KEY="dypai-auth-callback-type",_.CALLBACK_REDIRECT_STORAGE_KEY="dypai-auth-callback-redirect",_.RESET_TOKEN_STORAGE_KEY="dypai-auth-reset-token";class P{constructor(t,e,i){this.ws=null,this.bindings=[],this.Z={},this.tt=!1,this.topic=t,this.client=e,this.config=i||{}}on(t,e,i){return this.bindings.push({type:t,filter:e,callback:i}),this}subscribe(t){this.et=t;const e=this.client.it();this.ws=e;const i=()=>{const i=this.bindings.filter(t=>"postgres_changes"===t.type);for(const t of i)e.send(JSON.stringify({type:"subscribe",table:t.filter.table||"*",event:t.filter.event||"*",filter:t.filter.filter?this.st(t.filter.filter):void 0,schema:t.filter.schema||"public"}));const s=this.bindings.some(t=>"broadcast"===t.type),n=this.bindings.some(t=>"presence"===t.type);(s||n)&&e.send(JSON.stringify({type:"join",channel:this.topic,presence:!!n||void 0})),this.tt=!0,t?.("SUBSCRIBED")};e.readyState===WebSocket.OPEN?i():e.readyState===WebSocket.CONNECTING&&e.addEventListener("open",i,{once:!0}),this.nt&&e.removeEventListener("message",this.nt);const s=t=>{try{const e=JSON.parse("string"==typeof t.data?t.data:"");this.rt(e)}catch{}};return this.nt=s,e.addEventListener("message",s),e.addEventListener("close",()=>{this.tt=!1,t?.("CLOSED")},{once:!0}),e.addEventListener("error",()=>{t?.("CHANNEL_ERROR",new Error("WebSocket error"))},{once:!0}),this}ot(t){const e=this.bindings.filter(t=>"postgres_changes"===t.type);for(const i of e)t.send(JSON.stringify({type:"subscribe",table:i.filter.table||"*",event:i.filter.event||"*",filter:i.filter.filter?this.st(i.filter.filter):void 0,schema:i.filter.schema||"public"}));const i=this.bindings.some(t=>"broadcast"===t.type),s=this.bindings.some(t=>"presence"===t.type);(i||s)&&t.send(JSON.stringify({type:"join",channel:this.topic,presence:!!s||void 0})),this.et?.("SUBSCRIBED")}async send(t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return"error";try{return this.ws.send(JSON.stringify({type:"broadcast",channel:this.topic,event:t.event,payload:t.payload,self:this.config.broadcast?.self||!1})),"ok"}catch{return"error"}}async track(t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return"error";try{return this.ws.send(JSON.stringify({type:"presence",channel:this.topic,data:t})),"ok"}catch{return"error"}}async untrack(){return"ok"}presenceState(){return{...this.Z}}async unsubscribe(){if(this.nt&&this.ws&&(this.ws.removeEventListener("message",this.nt),this.nt=void 0),this.ws?.readyState===WebSocket.OPEN){const t=this.bindings.filter(t=>"postgres_changes"===t.type);for(const e of t)this.ws.send(JSON.stringify({type:"unsubscribe",table:e.filter.table}));this.ws.send(JSON.stringify({type:"leave",channel:this.topic}))}return this.tt=!1,this.bindings=[],this.Z={},"ok"}rt(t){if("change"===t.type)for(const e of this.bindings)"postgres_changes"===e.type&&(e.filter.table&&"*"!==e.filter.table&&e.filter.table!==t.table||e.filter.event&&"*"!==e.filter.event&&e.filter.event!==t.event||e.callback({schema:"public",table:t.table,eventType:t.event,new:t.record||{},old:t.old_record||{},commit_timestamp:t.timestamp}));if("broadcast"===t.type&&t.channel===this.topic)for(const e of this.bindings)"broadcast"===e.type&&("*"!==e.filter.event&&e.filter.event!==t.event||e.callback({event:t.event,payload:t.payload,sender:t.sender,timestamp:t.timestamp}));if("presence_state"===t.type&&t.channel===this.topic){const e=this.config.presence?.key||"userId";this.Z={};for(const[i,s]of Object.entries(t.presences||{})){const t=s[e]||i;this.Z[t]||(this.Z[t]=[]),this.Z[t].push(s)}for(const t of this.bindings)"presence"===t.type&&("sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"}))}if("presence_diff"===t.type&&t.channel===this.topic){const e=this.config.presence?.key||"userId";if(t.joins)for(const[i,s]of Object.entries(t.joins)){const t=s[e]||i;this.Z[t]||(this.Z[t]=[]),this.Z[t].push(s);for(const e of this.bindings)"presence"===e.type&&("join"!==e.filter.event&&"*"!==e.filter.event||e.callback({event:"join",key:t,newPresences:[s],currentPresences:this.Z[t]}))}if(t.leaves)for(const[i,s]of Object.entries(t.leaves)){const t=s[e]||i;this.Z[t]=(this.Z[t]||[]).filter(t=>t.userId!==i),0===this.Z[t].length&&delete this.Z[t];for(const e of this.bindings)"presence"===e.type&&("leave"!==e.filter.event&&"*"!==e.filter.event||e.callback({event:"leave",key:t,leftPresences:[s],currentPresences:this.Z[t]||[]}))}for(const t of this.bindings)"presence"!==t.type||"sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"})}}st(t){const e=t.match(/^(\w+)=eq\.(.+)$/);if(e)return{[e[1]]:e[2]}}}class I{constructor(t,e){this.ws=null,this.channels=new Map,this.heartbeatTimer=null,this.reconnectAttempt=0,this.reconnectTimer=null,this.baseUrl=t,this.tokenProvider=e}channel(t,e){const i=this.channels.get(t);if(i)return i;const s=new P(t,this,e?.config);return this.channels.set(t,s),s}getChannels(){return Array.from(this.channels.values())}async removeChannel(t){await t.unsubscribe(),this.channels.delete(t.topic),0===this.channels.size&&this.disconnect()}async removeAllChannels(){for(const t of this.channels.values())await t.unsubscribe();this.channels.clear(),this.disconnect()}async setAuth(t){t&&(this.tokenProvider=()=>t),this.ws&&this.channels.size>0&&(this.disconnect(),this.it())}disconnect(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.ws?.close(),this.ws=null}it(){if(this.ws&&(this.ws.readyState===WebSocket.OPEN||this.ws.readyState===WebSocket.CONNECTING))return this.ws;const t=this.tokenProvider(),e=this.baseUrl.replace(/^http/,"ws")+`/realtime?token=${encodeURIComponent(t||"")}`,i=new WebSocket(e);return this.ws=i,i.addEventListener("open",()=>{this.reconnectAttempt=0;for(const t of this.channels.values())t.tt&&t.ot(i);this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>{i.readyState===WebSocket.OPEN&&i.send(JSON.stringify({type:"heartbeat"}))},25e3)}),i.addEventListener("close",()=>{if(this.ws=null,this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.channels.size>0&&!this.reconnectTimer){const t=I.RECONNECT_INTERVALS[Math.min(this.reconnectAttempt,I.RECONNECT_INTERVALS.length-1)];this.reconnectAttempt++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.channels.size>0&&this.it()},t)}}),i}}I.RECONNECT_INTERVALS=[1e3,2e3,5e3,1e4,3e4];class T{get direct(){if(!this.ct)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.ct}set direct(t){this.ct=t}constructor(t){this.api=t}from(t){return new E(t,this.api)}}class E{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 O=1e3;class ${constructor(t){this.api=t}from(t){return new R(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 R{constructor(t,e){this.table=t,this.api=e,this.ht="public",this.ut=[],this.dt=100,this.ft=0,this.wt="ASC"}schema(t){return this.ht=t,this}eq(t,e){return this.ut.push({column:t,operator:"eq",value:e}),this}neq(t,e){return this.ut.push({column:t,operator:"neq",value:e}),this}gt(t,e){return this.ut.push({column:t,operator:"gt",value:e}),this}gte(t,e){return this.ut.push({column:t,operator:"gte",value:e}),this}lt(t,e){return this.ut.push({column:t,operator:"lt",value:e}),this}lte(t,e){return this.ut.push({column:t,operator:"lte",value:e}),this}like(t,e){return this.ut.push({column:t,operator:"ilike",value:e}),this}ilike(t,e){return this.like(t,e)}isNull(t){return this.ut.push({column:t,operator:"is_null",value:null}),this}is(t,e){return this.isNull(t)}notNull(t){return this.ut.push({column:t,operator:"not_null",value:null}),this}in(t,e){return this.ut.push({column:t,operator:"in",value:e}),this}contains(t,e){return this.ut.push({column:t,operator:"array_contains",value:e}),this}containedBy(t,e){return this.ut.push({column:t,operator:"contained_by",value:e}),this}overlaps(t,e){return this.ut.push({column:t,operator:"overlaps",value:e}),this}textSearch(t,e){return this.ut.push({column:t,operator:"fts",value:e}),this}phraseSearch(t,e){return this.ut.push({column:t,operator:"phfts",value:e}),this}not(t,e,i){return this.ut.push({column:t,operator:"not",value:{operator:e,value:i}}),this}or(t){return this.ut.push({operator:"or",or:t}),this}limit(t){return this.dt=t,this}offset(t){return this.ft=t,this}orderBy(t,e="ASC"){return this.yt=t,this.wt=e,this}async select(t){const e=[...this.ut];let i=this.vt;if("string"==typeof t)i=t;else if(t&&"object"==typeof t)for(const[i,s]of Object.entries(t))e.push({column:i,operator:"eq",value:s});return this.execute("select",{...i&&{columns:i},filters:e,limit:this.dt,offset:this.ft,sort_by:this.yt,order:this.wt})}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.ut],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>O){const e=[];for(let i=0;i<t.length;i+=O){const s=t.slice(i,i+O),{data:n,error:r}=await this.execute("insert",{data:s,mode:"bulk"});if(r)return{data:null,error:r};n&&e.push(...Array.isArray(n)?n:[n])}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.ut.length?{data:null,error:new t("Update requires at least one filter. Use .eq(), .in(), etc.",400)}:this.execute("update",{data:e,filters:this.ut})}async delete(){return 0===this.ut.length?{data:null,error:new t("Delete requires at least one filter.",400)}:this.execute("delete",{filters:this.ut})}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.ht,table_name:this.table,...e})}}class U{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 s=await this.api.put(`admin/users/${t}`,i);return s.data&&(s.data=e(s.data)),s}async delete(t){return this.api.delete(`admin/users/${t}`)}}let N=null;function x(t){N=t}const K=t=>{const{title:e,description:i,variant:s="default"}=t,n=`${"error"===s?"❌":"success"===s?"✅":"warning"===s?"⚠️":"info"===s?"ℹ️":"📢"} ${e}${i?`: ${i}`:""}`;"error"===s?console.error(n):"warning"===s&&console.warn(n)};function C(){const t=N||K;return{toast:t,toastSuccess:(e,i)=>t({title:e,description:i,variant:"success"}),toastError:(e,i)=>t({title:e,description:i,variant:"error"}),toastWarning:(e,i)=>t({title:e,description:i,variant:"warning"}),toastInfo:(e,i)=>t({title:e,description:i,variant:"info"})}}const Y=t=>(N||K)(t),L=(t,e)=>Y({title:t,description:e,variant:"success"}),j=(t,e)=>Y({title:t,description:e,variant:"error"}),z=(t,e)=>Y({title:t,description:e,variant:"warning"}),B=(t,e)=>Y({title:t,description:e,variant:"info"});let J={},M=null;function W(t){M=t}function q(t){J={...J,...t}}const G=new Map;function F(){let t=J;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...J}}catch(t){}return t}async function V(e,i,s,n,r,o,a,c){const h=F(),l=c||null;if(!l&&!s.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof s||!s.trim())throw new Error("Endpoint debe ser un string válido");let u;if(s.startsWith("http"))u=s;else{const t=l.replace(/\/+$/,"");u=s.startsWith("/")?t+s:t+"/api/v0/"+s}if(o&&Object.keys(o).length>0){const t=new URLSearchParams,e=(i,s)=>{null!=s&&(Array.isArray(s)?s.forEach((t,s)=>e(`${i}[${s}]`,t)):"object"==typeof s?Object.entries(s).forEach(([t,s])=>e(`${i}[${t}]`,s)):t.append(i,String(s)))};Object.entries(o).forEach(([t,i])=>e(t,i));const i=t.toString();i&&(u+=`?${i}`)}const d="GET"===i?`${i}:${u}:${JSON.stringify(n)}`:null;if(d&&G.has(d))return G.get(d);const f=n instanceof FormData,p={...h.headers||{},...f?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...a&&{"x-api-key":a}},w={method:i,headers:p,credentials:"include"};n&&"GET"!==i&&"DELETE"!==i&&(w.body=f?n:JSON.stringify(n));const y=h.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!y)throw new Error("Fetch no disponible.");const m=(async()=>{const e=F().toast||Y,s=(void 0!==r?r:!1!==h.showToasts)&&e;try{const n=await y(u,w);if(!n.ok){let i,r="Error en la petición";try{const t=await n.text();try{const e=JSON.parse(t);i=e,r=e.message||e.msg||e.error_description||e.error||r}catch{t.length<200&&(r=t)}}catch{}throw 401===n.status&&h.onUnauthorized&&h.onUnauthorized(),s&&e({title:"Error",description:r,variant:"error"}),new t(r,n.status,void 0,i)}!s||"POST"!==i&&"PUT"!==i&&"PATCH"!==i&&"DELETE"!==i||e({title:"Éxito",description:"Operación completada",variant:"success"});const r=n.headers.get("content-type")||"";return r.includes("application/pdf")||r.includes("image/")||r.includes("audio/")||r.includes("video/")||r.includes("application/octet-stream")||r.includes("application/zip")||r.includes("application/vnd.openxmlformats-officedocument")?await n.blob():r.includes("application/json")?await n.json():await n.text()}finally{d&&G.delete(d)}})();return d&&G.set(d,m),m}async function H(t,e,i,s){const n=s?.method||(i?"POST":"GET"),{data:r,error:o}=await("GET"===n?t.get(e,{params:s?.params}):t.post(e,i,{params:s?.params}));if(o)throw o;if(r instanceof Blob){const t=window.URL.createObjectURL(r),e=document.createElement("a");e.href=t,e.download=s?.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 n=s?.fileName||i;if(n){const e=document.createElement("a");e.href=t,e.download=n,document.body.appendChild(e),e.click(),document.body.removeChild(e)}else window.open(t,"_blank")}}}async function Q(e,i,s,n){const{data:r,error:o}=await e.post(i,{file_path:s.name,content_type:s.type||"application/octet-stream",size_bytes:s.size,confirm:!1,client_upload:!0,...n?.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 X(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 X(t);const s=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of s){const e=Array.isArray(t)?t:Object.values(t);for(const t of e)if(t&&(t.upload_url||t.uploadUrl||t.url))return X(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;n?.onProgress&&n.onProgress(10);const f=await fetch(c,{method:h,headers:{"Content-Type":s.type||"application/octet-stream",...l},body:s});if(!f.ok)throw new t("Direct upload to cloud storage failed",f.status);n?.onProgress&&n.onProgress(90);const p=n?.confirmEndpoint||i,{data:w,error:y}=await e.post(p,{...n?.params,bucket:a.bucket||n?.params?.bucket,file_path:u,storage_path:d,filename:s.name,content_type:s.type||"application/octet-stream",size_bytes:s.size,confirm:!0,client_upload:!0});if(y)throw y;return n?.onProgress&&n.onProgress(100),w}function X(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 Z(e){const i={get:tt(e,"GET"),post:tt(e,"POST"),put:tt(e,"PUT"),patch:tt(e,"PATCH"),delete:tt(e,"DELETE"),upload:async(e,s,n)=>{try{return{data:await Q(i,e,s,n),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Upload failed",e?.status??500)}}},download:async(e,s,n)=>{try{return await H(i,e,s,{...n,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 tt(e,i){const s="POST"===i||"PUT"===i||"PATCH"===i;return async(n,r,o)=>{const a=e();let c,h={};s?(c=r,h=o||{}):(h=r||{},c=void 0);const l=h.token||a.token||(M?M():"")||"",u=h.apiKey||a.apiKey;try{return{data:await V(l,i,n,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 et(e){const i=()=>"function"==typeof e?e():e,s={get:it(i,"GET"),post:it(i,"POST"),put:it(i,"PUT"),patch:it(i,"PATCH"),delete:it(i,"DELETE"),upload:async(e,i,n)=>{try{return{data:await Q(s,e,i,n),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,n)=>{try{return await H(s,e,i,{...n,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 s}function it(e,i){return async(s,n,r)=>{const o=e(),a=await async function(t,e,i){let s,n={};"POST"===t||"PUT"===t||"PATCH"===t?(s=e,n=i||{}):(n=e||{},s=void 0);let r=n.token;return!r&&M&&(r=M()||""),r=r||"",{token:r,apiKey:n.apiKey,body:s,params:n.params,showToasts:n.showToasts}}(i,n,r),c=a.token||o.token||"",h=a.apiKey||o.apiKey;try{return{data:await V(c,i,s,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 st{get baseUrl(){return this.St}getAuthToken(){return this.auth.token||this.bt||null}constructor(t){const{baseUrl:e,apiKey:i}=t;this.St=e,this.bt=i,this.realtime=new I(e,()=>this.getAuthToken());const s=t.auth?.storageKey||t.storageKey;t.global&&function(t){J={...J,...t}}(t.global),this.auth=new _({baseUrl:e,apiKey:i,storageKey:s,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const n=Z(()=>({token:this.auth.token,apiKey:i,baseUrl:e}));if(this.auth.api=n,this.db=new T(n),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=Z(()=>({token:t.serviceRoleKey,baseUrl:e}));this.db.direct=new $(i)}this.users=new U(n),this.api=et(()=>({token:this.auth.token||"",apiKey:i,baseUrl:e})),W(()=>this.auth.token);const r="undefined"!=typeof window?window.location.search:"";if("undefined"!=typeof window&&t.redirects){const e=t.redirects;this.auth.P?.then(()=>{if(this.auth.isPasswordRecoveryCallback&&e.passwordRecovery){if(this.auth.consumeCallbackType(),!window.location.pathname.includes(e.passwordRecovery)){const t=e.passwordRecovery+(r||"");window.location.replace(t)}}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))}})}q({onUnauthorized:()=>this.auth.handleSessionExpired()})}from(t){return this.db.from(t)}async me(){return this.auth.getUser()}}function nt(t,e,i){if(!t)throw new Error("createClient() requiere la URL base");return new st({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?i:e})}let rt={};function ot(t){rt={...rt,toast:Y,...t},ct()}function at(){return{...rt}}function ct(){try{const{configureApiService:t}=require("../services/ApiService");t(rt)}catch(t){}}function ht(){rt={},ct()}async function lt(){ct()}const ut={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{u as AuthEmailNotVerifiedError,r as AuthInvalidCredentialsError,a as AuthInvalidTokenError,s as AuthSessionMissingError,f as AuthUserAlreadyExistsError,h as AuthWeakPasswordError,$ as DirectDBModule,R as DirectQueryBuilder,st as DypaiClient,t as DypaiError,I as DypaiRealtimeClient,ut as PACKAGE_INFO,P as RealtimeChannel,w as RetryableFetchError,V as callApi,v as classifyAuthError,q as configureApiService,ot as configureDypaiServices,et as createApiClient,nt as createClient,at as getGlobalConfig,d as isAuthEmailNotVerifiedError,o as isAuthInvalidCredentialsError,c as isAuthInvalidTokenError,n as isAuthSessionMissingError,p as isAuthUserAlreadyExistsError,l as isAuthWeakPasswordError,i as isDypaiError,y as isRetryableFetchError,m as isRetryableStatus,lt as reloadDypaiConfig,ht as resetGlobalConfig,x as setToastFunction,W as setTokenProvider,Y as toast,j as toastError,B as toastInfo,L as toastSuccess,z as toastWarning,C as useToast};
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";class DypaiError extends Error{constructor(t,e=500,i,r){super(t),this.status=e,this.code=i,this.details=r,this.name="DypaiError"}}function normalizeUser(t){if(!t)return{};const e=t.user||t,i=e.name||e.user_metadata?.full_name||null,r=e.emailVerified??e.confirmed_at??null,s=e.app_metadata||{},o=e.user_metadata||{full_name:i};return{id:e.id,email:e.email,phone:e.phone||null,role:s.role||o.role||e.role||"authenticated",created_at:e.createdAt||e.created_at,updated_at:e.updatedAt||e.updated_at,confirmed_at:r?"boolean"==typeof r?(new Date).toISOString():r:null,last_sign_in_at:e.last_sign_in_at||null,app_metadata:s,user_metadata:{...o,full_name:i},roleDetails:{name:s.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 i={};try{i=await t.json()}catch{}const r=i.msg||i.error_description||i.message||e,s=i.error_code||i.error||i.code||void 0;return new DypaiError(r,t.status,s)}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.i=null,this.o=null,this.h=null,this.l=null,this.u=[],this.p=null,this.m=null,this.v=!1,this.A=null,this.D=null,this.S=0,this._=0,this.storage=t.storage||localStorageAdapter,_debugEnabled=!!t.debug;const i=t.storageKey||this.deriveStorageKey(t.apiKey);_log("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${i})`),this.STORAGE_KEY=`dypai-${i}-auth-session`,this.k=this.T().then(()=>{this.i&&(this.getUser().catch(()=>{}),this.startAutoRefresh());const t=this.m;this.m=null,t?setTimeout(()=>{this.v=!0,this.P(t)},0):this.v=!0}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.R.bind(this)),window.addEventListener("focus",this.R.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(_log("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.T(!1))}))}async R(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(_log("log","[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this.T(!0),this.i&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}getBaseUrl(){return this.config.baseUrl||"http://localhost:8000"}I(){if("undefined"==typeof window)return;const t=new URLSearchParams(window.location.search);["token","type","error","error_code","error_description","access_token","refresh_token","expires_in"].forEach(e=>t.delete(e));const e=t.toString(),i=`${window.location.pathname}${e?`?${e}`:""}`;window.history.replaceState({},document.title,i)}C(t){try{if(!t)return;sessionStorage.setItem(AuthModule.CALLBACK_TYPE_STORAGE_KEY,t),"recovery"===t||"invite"===t?sessionStorage.setItem(AuthModule.CALLBACK_REDIRECT_STORAGE_KEY,"PASSWORD_RECOVERY"):sessionStorage.removeItem(AuthModule.CALLBACK_REDIRECT_STORAGE_KEY)}catch{}}O(t){try{sessionStorage.setItem(AuthModule.RESET_TOKEN_STORAGE_KEY,t)}catch{}}$(){try{return sessionStorage.getItem(AuthModule.RESET_TOKEN_STORAGE_KEY)}catch{return null}}U(){try{sessionStorage.removeItem(AuthModule.RESET_TOKEN_STORAGE_KEY)}catch{}}get user(){return this.t}get token(){return this.i}get lastError(){return this.p}consumeCallbackType(){try{const t=sessionStorage.getItem(AuthModule.CALLBACK_TYPE_STORAGE_KEY);return sessionStorage.removeItem(AuthModule.CALLBACK_TYPE_STORAGE_KEY),sessionStorage.removeItem(AuthModule.CALLBACK_REDIRECT_STORAGE_KEY),t}catch{return null}}get isPasswordRecoveryCallback(){try{return"PASSWORD_RECOVERY"===sessionStorage.getItem(AuthModule.CALLBACK_REDIRECT_STORAGE_KEY)}catch{return!1}}isLoggedIn(){return!(!this.i||!this.t)}onAuthStateChange(t){return _log("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.u.push(t),this.k.then(()=>{const e=this.M();_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.u=this.u.filter(e=>e!==t)}}}}}async signInWithPassword(t){return wrapAuthResponse((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-in/email`,i={email:t.email||t.identifier||"",password:t.password},r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(i)});if(!r.ok)throw await parseAuthError(r,"Login failed");const s=await r.json(),o=this.N(s);return await this.K(o),o})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return wrapAuthResponse((async()=>{const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-up/email`,{email:r,phone:s,password:o,user_data:n,...a}=t,h=r||"",c=a.full_name||a.name||h.split("@")[0],l={...a,...n||{}},u={email:h,password:o,name:c};Object.keys(l).length>0&&Object.assign(u,l),e?.redirectTo?u.callbackURL=e.redirectTo:"undefined"!=typeof window&&(u.callbackURL=window.location.href.split("#")[0]);const d=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(u)});if(!d.ok)throw await parseAuthError(d,"Registration failed");const p=await d.json(),f=this.N(p),y={...f,confirmationRequired:!f.token};return y.token?await this.K(y):_log("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),y})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this.k,!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.l||0)-t<30&&this.h&&(_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.i,refresh_token:this.h||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()=>{await this.k;const t=this.o||this.i;if(!t)throw new DypaiError("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,i=await fetch(e,{headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw new DypaiError("Session invalid",i.status);const r=await i.json(),s=r?.jwt||i.headers.get("set-auth-jwt");s&&(this.i=s);const o=this.Y(r);return this.L(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:i=window.location.href,scopes:r}=e,s=this.config.baseUrl||"http://localhost:8000",o=new URLSearchParams({provider:t,callbackURL:i});r?.length&&o.set("scopes",r.join(" "));const n=`${s}/api/v0/auth/sign-in/social?${o.toString()}`;window.location.href=n})())}async signOut(){return wrapAuthResponse((async()=>{try{const t=this.o||this.i;if(t){const e=this.config.baseUrl||"http://localhost:8000";await fetch(`${e}/api/v0/auth/sign-out`,{method:"POST",headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}})}}finally{this.j("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return wrapAuthResponse((async()=>{const i=this.config.baseUrl||"http://localhost:8000",r={email:t};e?.redirectTo?r.redirectTo=e.redirectTo:"undefined"!=typeof window&&(r.redirectTo=window.location.href.split("#")[0]);const s=await fetch(`${i}/api/v0/auth/request-password-reset`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!s.ok)throw await parseAuthError(s,"Recovery failed");return await s.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t,e){return wrapAuthResponse((async()=>{const i=this.config.baseUrl||"http://localhost:8000",r={email:t};e?.redirectTo?r.callbackURL=e.redirectTo:"undefined"!=typeof window&&(r.callbackURL=window.location.href.split("#")[0]);const s=await fetch(`${i}/api/v0/auth/send-verification-email`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(r)});if(!s.ok)throw await parseAuthError(s,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.A)return _log("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.A.promise;const t=new Deferred;this.A=t;const e=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const i=await async function(t,e,i){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const r=new AbortController,s=setTimeout(()=>r.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:r.signal},async()=>(clearTimeout(s),await i()))}catch(r){if("AbortError"===r.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await i();throw r}}return await i()}(e,AuthModule.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.B());t.resolve(i)}catch(e){const i=e instanceof DypaiError?e:new DypaiError(e.message||"Error refrescando sesión",401);t.resolve({data:null,error:i})}finally{this.A=null}})(),t.promise}async B(){try{const t=await this.storage.getItem(this.STORAGE_KEY);if(t){const e=JSON.parse(t),i=e.expires_at||0,r=Math.floor(Date.now()/1e3);if(e.access_token!==this.i&&i-r>60)return _log("log","[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.i=e.access_token,this.o=e.session_token||e.access_token,this.h=e.refresh_token,this.l=e.expires_at,this.t=e.user,this.S=0,this.P("TOKEN_REFRESHED"),{data:{token:this.i,sessionToken:this.o||void 0,refreshToken:this.h||void 0,expiresAt:this.l||void 0,user:this.t},error:null}}const e=this.o||this.i;if(!e)throw new DypaiError("No hay sesión activa",401);_log("log","[DYPAI SDK] 🔄 Verificando sesión activa con session_token...");const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,r=new AbortController,s=setTimeout(()=>r.abort(),AuthModule.REFRESH_TIMEOUT_MS);let o;try{o=await fetch(i,{method:"GET",headers:{Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},signal:r.signal})}finally{clearTimeout(s)}if(!o.ok){let t={};try{t=await o.json()}catch{}const e=o.status,i=t.error||t.code||"",r=(t.error_description||t.msg||"").toLowerCase(),s="invalid_grant"===i||r.includes("invalid refresh token")||r.includes("revoked");throw e>=400&&e<500&&s?(_log("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.j(`refreshSession failed (${e}: ${i})`)):_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 o.json(),a=o.headers.get("set-auth-jwt"),h=n?.jwt,c=h||a,l=this.N(n,this.i);return c&&(l.token=c),l.sessionToken=e,this.S=0,await this.K(l),_log("log",`[DYPAI SDK] ✅ Session refresh exitoso (fresh JWT: ${!!c}).`),{data:l,error:null}}catch(t){this.S++,this._=Date.now();const e=t instanceof DOMException&&"AbortError"===t.name,i=e?new DypaiError("Refresh token timeout (servidor no responde)",408):t instanceof DypaiError?t:new DypaiError(t.message||"Error refrescando sesión",401),r=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.S),AuthModule.MAX_RETRY_MS);return e?_log("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${AuthModule.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${r}ms (intento ${this.S})`):_log("error","[DYPAI SDK] ❌ Refresh falló:",i.message,`(status: ${i.status}). Backoff: ${r}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.j(`${this.S} consecutive refresh failures`),this.S=0),{data:null,error:i}}}async signInWithOtp(t){return wrapAuthResponse((async()=>{if(t.phone)throw new DypaiError("Phone OTP is not supported by the current DYPAI auth backend",400);if(!t.email)throw new DypaiError("Email is required for magic link sign-in",400);const e=await this.signInWithMagicLink(t.email);if(e.error)throw e.error;return{status:!0}})())}async verifyOtp(t){return wrapAuthResponse((async()=>{if(!t.token)throw new DypaiError("Verification token is required",400);if("magiclink"===t.type)return await this.q(t.token);if("signup"===t.type||"email_change"===t.type){const e=await this.J(t.token);if(e)return e;throw new DypaiError("Verification completed but no session was returned",400)}if("recovery"===t.type||"invite"===t.type)throw new DypaiError("Recovery links do not use verifyOtp(). Use setPassword() after the PASSWORD_RECOVERY callback.",400);throw new DypaiError("Unsupported verification type",400)})())}async updateUser(t){return wrapAuthResponse((async()=>{const e=this.o||this.i;if(!e)throw new DypaiError("No hay sesión activa",401);const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/update-user`,r=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":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 s=await r.json(),o=this.Y(s);return this.L(o),o})())}async setPassword(t){return wrapAuthResponse((async()=>{if("undefined"==typeof window)throw new DypaiError("setPassword requiere un entorno de navegador",400);const e=new URLSearchParams(window.location.search).get("token")||this.$();if(!e)throw new DypaiError("No reset token found in the current URL",400);const i=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${i}/api/v0/auth/reset-password`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({newPassword:t,token:e})});if(!r.ok)throw await parseAuthError(r,"Reset password failed");return this.U(),this.I(),await r.json()})())}async signInWithMagicLink(t,e){return wrapAuthResponse((async()=>{const i=this.config.baseUrl||"http://localhost:8000",r=e?.redirectTo||("undefined"!=typeof window?window.location.href.split("#")[0]:void 0),s=await fetch(`${i}/api/v0/auth/sign-in/magic-link`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({email:t,callbackURL:r})});if(!s.ok)throw await parseAuthError(s,"Magic link failed");return{message:"Magic link sent"}})())}async enableTwoFactor(){return wrapAuthResponse((async()=>{const t=this.o||this.i;if(!t)throw new DypaiError("No hay sesión activa",401);const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/two-factor/enable`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw await parseAuthError(i,"Enable 2FA failed");return await i.json()})())}async verifyTwoFactor(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/two-factor/verify-totp`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.o||this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({code:t})});if(!i.ok)throw await parseAuthError(i,"2FA verification failed");const r=await i.json(),s={token:r.jwt||r.token||this.i,expiresIn:604800,expiresAt:Math.floor(Date.now()/1e3)+604800,user:this.Y(r)};return await this.K(s),s})())}async disableTwoFactor(){return wrapAuthResponse((async()=>{const t=this.o||this.i;if(!t)throw new DypaiError("No hay sesión activa",401);const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/two-factor/disable`,{method:"POST",headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw await parseAuthError(i,"Disable 2FA failed");return{message:"2FA disabled"}})())}async createOrganization(t,e){return wrapAuthResponse((async()=>{const i=this.o||this.i;if(!i)throw new DypaiError("No hay sesión activa",401);const r=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${r}/api/v0/auth/organization/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({name:t,slug:e})});if(!s.ok)throw await parseAuthError(s,"Create organization failed");return await s.json()})())}async inviteMember(t,e,i){return wrapAuthResponse((async()=>{const r=this.o||this.i;if(!r)throw new DypaiError("No hay sesión activa",401);const s=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${s}/api/v0/auth/organization/invite-member`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${r}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({organizationId:t,email:e,role:i||"member"})});if(!o.ok)throw await parseAuthError(o,"Invite member failed");return await o.json()})())}async listOrganizations(){return wrapAuthResponse((async()=>{const t=this.o||this.i;if(!t)throw new DypaiError("No hay sesión activa",401);const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/organization/list`,{headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw await parseAuthError(i,"List organizations failed");return await i.json()})())}startAutoRefresh(){this.stopAutoRefresh(),_log("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.F(),this.D=setInterval(()=>this.F(),AuthModule.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.D&&(_log("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.D),this.D=null)}async F(){try{const t=Math.floor(Date.now()/1e3),e=this.l||0;if(!this.h&&!this.o||!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._<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)}}Y(t){return t?normalizeUser(t):(_log("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}N(t,e){const i="number"==typeof t?.expiresIn?t.expiresIn:"number"==typeof t?.expires_in?t.expires_in:604800,r=t?.jwt||void 0,s=t?.session_token||t?.token||t?.access_token||t?.session?.token||t?.session?.accessToken||t?.session?.access_token||e;return{token:r||s,sessionToken:s,refreshToken:t?.refreshToken||t?.refresh_token||t?.session?.refreshToken||t?.session?.refresh_token||void 0,expiresIn:i,expiresAt:Math.floor(Date.now()/1e3)+i,user:this.Y(t)}}async K(t){this.p=null,t.token&&(this.i=t.token,this.o=t.sessionToken||t.token,void 0!==t.refreshToken&&(this.h=t.refreshToken||null),void 0!==t.expiresAt&&(this.l=t.expiresAt||null),await this.L(t.user,t.token,t.refreshToken,t.expiresAt),_log("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.l}, has_refresh: ${!!this.h}, has_session_token: ${!!this.o})`),this.startAutoRefresh())}async L(t,e,i,r){this.t=t,e&&(this.i=e),void 0!==i&&(this.h=i||null),void 0!==r&&(this.l=r||null);try{if(this.i&&this.t){const t={access_token:this.i,session_token:this.o,refresh_token:this.h,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.v){const t=e?"SIGNED_IN":"USER_UPDATED";this.P(t)}}async j(t="unknown"){_log("log",`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.i=null,this.o=null,this.h=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.P("SIGNED_OUT")}async q(t){const e=this.getBaseUrl(),i=new URL(`${e}/api/v0/auth/magic-link/verify`);i.searchParams.set("token",t);const r=await fetch(i.toString(),{method:"GET",headers:{...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!r.ok)throw await parseAuthError(r,"Magic link verification failed");const s=await r.json(),o=this.N(s);return await this.K(o),o}async J(t){const e=this.getBaseUrl(),i=new URL(`${e}/api/v0/auth/verify-email`);i.searchParams.set("token",t);const r=await fetch(i.toString(),{method:"GET",headers:{...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!r.ok)throw await parseAuthError(r,"Email verification failed");if(!(r.headers.get("content-type")||r.headers.get("Content-Type")||"").includes("application/json"))return null;const s=await r.json(),o=this.N(s);return o.token&&o.user?.id?(await this.K(o),o):null}async W(t,e){const i=e||("undefined"!=typeof window&&window.location.pathname.includes("reset-password")?"recovery":null);if(!i)return!1;if(_log("log",`[DYPAI SDK] 🔗 Auth callback detectado en query (type: ${i}). Procesando...`),this.C(i),"recovery"===i||"invite"===i)return this.O(t),this.I(),this.m="PASSWORD_RECOVERY",!0;if(this.I(),"magiclink"===i)return await this.q(t),this.m="SIGNED_IN",!0;if("signup"===i||"email_change"===i){const e=await this.J(t);return this.m=e?.token?"SIGNED_IN":null,!0}return!1}async T(t=!0){_log("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.G())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)return void _log("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{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.i=t.access_token,this.o="string"==typeof t.session_token?t.session_token:t.access_token,this.h="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}if(t){_log("log","[DYPAI SDK] 🔄 Validando sesión con el servidor...");const t=await this.refreshSession();if(t.error)return void _log("warn","[DYPAI SDK] ⚠️ Sesión inválida. Limpiando:",t.error.message)}this.m="SIGNED_IN"}catch(t){_log("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async G(){if("undefined"==typeof window)return!1;try{const t=new URLSearchParams(window.location.search),e=t.get("error"),i=t.get("token"),r=t.get("type");if(e){const i=t.get("error_code")||e,r=t.get("error_description")?.replace(/\+/g," ")||"Authentication error";return _log("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${i} — ${r}`),this.p={code:i,message:r},this.I(),this.P("AUTH_ERROR"),!0}if(i)return await this.W(i,r);const s=window.location.hash.substring(1);if(!s)return!1;const o=new URLSearchParams(s);if(o.get("error")){const t=o.get("error_code")||"unknown",e=o.get("error_description")?.replace(/\+/g," ")||"Authentication error";return _log("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${e}`),this.p={code:t,message:e},this.I(),this.P("AUTH_ERROR"),!0}const n=o.get("access_token");if(!n)return!1;const a=o.get("refresh_token"),h=o.get("type"),c=parseInt(o.get("expires_in")||"3600",10);_log("log",`[DYPAI SDK] 🔗 Auth callback detectado en URL hash (type: ${h||"unknown"}). Procesando...`),this.C(h),this.I(),this.o=n,this.i=n,this.h=a||null,this.l=Math.floor(Date.now()/1e3)+c;const l=await fetch(`${this.getBaseUrl()}/api/v0/auth/get-session`,{headers:{Authorization:`Bearer ${n}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!l.ok)return _log("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${l.status}). Limpiando...`),this.i=null,this.o=null,this.h=null,this.l=null,this.p={code:"token_invalid",message:"The authentication link is invalid or has expired"},this.P("AUTH_ERROR"),!0;const u=await l.json(),d=u?.jwt||l.headers.get("set-auth-jwt"),p=this.Y(u),f={token:d||n,sessionToken:n,refreshToken:a||void 0,expiresIn:c,expiresAt:this.l,user:p};return await this.K(f),this.m="recovery"===h||"invite"===h?"PASSWORD_RECOVERY":"SIGNED_IN",_log("log",`[DYPAI SDK] 🔑 Callback type=${h}. Pending event: ${this.m}`),!0}catch(t){return _log("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}M(){return this.i&&this.t?{access_token:this.i,refresh_token:this.h||void 0,expires_at:this.l||void 0,token_type:"bearer",user:this.t}:null}P(t="USER_UPDATED"){const e=this.M();_log("log",`[DYPAI SDK] 📢 Notificando a ${this.u.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.u.forEach(i=>i(t,e))}handleSessionExpired(){this.j("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,AuthModule.CALLBACK_TYPE_STORAGE_KEY="dypai-auth-callback-type",AuthModule.CALLBACK_REDIRECT_STORAGE_KEY="dypai-auth-callback-redirect",AuthModule.RESET_TOKEN_STORAGE_KEY="dypai-auth-reset-token";class RealtimeChannel{constructor(t,e,i){this.ws=null,this.bindings=[],this.V={},this.H=!1,this.topic=t,this.client=e,this.config=i||{}}on(t,e,i){return this.bindings.push({type:t,filter:e,callback:i}),this}subscribe(t){this.X=t;const e=this.client.Z();this.ws=e;const i=()=>{const i=this.bindings.filter(t=>"postgres_changes"===t.type);for(const t of i)e.send(JSON.stringify({type:"subscribe",table:t.filter.table||"*",event:t.filter.event||"*",filter:t.filter.filter?this.tt(t.filter.filter):void 0,schema:t.filter.schema||"public"}));const r=this.bindings.some(t=>"broadcast"===t.type),s=this.bindings.some(t=>"presence"===t.type);(r||s)&&e.send(JSON.stringify({type:"join",channel:this.topic,presence:!!s||void 0})),this.H=!0,t?.("SUBSCRIBED")};e.readyState===WebSocket.OPEN?i():e.readyState===WebSocket.CONNECTING&&e.addEventListener("open",i,{once:!0}),this.et&&e.removeEventListener("message",this.et);const r=t=>{try{const e=JSON.parse("string"==typeof t.data?t.data:"");this.it(e)}catch{}};return this.et=r,e.addEventListener("message",r),e.addEventListener("close",()=>{this.H=!1,t?.("CLOSED")},{once:!0}),e.addEventListener("error",()=>{t?.("CHANNEL_ERROR",new Error("WebSocket error"))},{once:!0}),this}rt(t){const e=this.bindings.filter(t=>"postgres_changes"===t.type);for(const i of e)t.send(JSON.stringify({type:"subscribe",table:i.filter.table||"*",event:i.filter.event||"*",filter:i.filter.filter?this.tt(i.filter.filter):void 0,schema:i.filter.schema||"public"}));const i=this.bindings.some(t=>"broadcast"===t.type),r=this.bindings.some(t=>"presence"===t.type);(i||r)&&t.send(JSON.stringify({type:"join",channel:this.topic,presence:!!r||void 0})),this.X?.("SUBSCRIBED")}async send(t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return"error";try{return this.ws.send(JSON.stringify({type:"broadcast",channel:this.topic,event:t.event,payload:t.payload,self:this.config.broadcast?.self||!1})),"ok"}catch{return"error"}}async track(t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return"error";try{return this.ws.send(JSON.stringify({type:"presence",channel:this.topic,data:t})),"ok"}catch{return"error"}}async untrack(){return"ok"}presenceState(){return{...this.V}}async unsubscribe(){if(this.et&&this.ws&&(this.ws.removeEventListener("message",this.et),this.et=void 0),this.ws?.readyState===WebSocket.OPEN){const t=this.bindings.filter(t=>"postgres_changes"===t.type);for(const e of t)this.ws.send(JSON.stringify({type:"unsubscribe",table:e.filter.table}));this.ws.send(JSON.stringify({type:"leave",channel:this.topic}))}return this.H=!1,this.bindings=[],this.V={},"ok"}it(t){if("change"===t.type)for(const e of this.bindings)"postgres_changes"===e.type&&(e.filter.table&&"*"!==e.filter.table&&e.filter.table!==t.table||e.filter.event&&"*"!==e.filter.event&&e.filter.event!==t.event||e.callback({schema:"public",table:t.table,eventType:t.event,new:t.record||{},old:t.old_record||{},commit_timestamp:t.timestamp}));if("broadcast"===t.type&&t.channel===this.topic)for(const e of this.bindings)"broadcast"===e.type&&("*"!==e.filter.event&&e.filter.event!==t.event||e.callback({event:t.event,payload:t.payload,sender:t.sender,timestamp:t.timestamp}));if("presence_state"===t.type&&t.channel===this.topic){const e=this.config.presence?.key||"userId";this.V={};for(const[i,r]of Object.entries(t.presences||{})){const t=r[e]||i;this.V[t]||(this.V[t]=[]),this.V[t].push(r)}for(const t of this.bindings)"presence"===t.type&&("sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"}))}if("presence_diff"===t.type&&t.channel===this.topic){const e=this.config.presence?.key||"userId";if(t.joins)for(const[i,r]of Object.entries(t.joins)){const t=r[e]||i;this.V[t]||(this.V[t]=[]),this.V[t].push(r);for(const e of this.bindings)"presence"===e.type&&("join"!==e.filter.event&&"*"!==e.filter.event||e.callback({event:"join",key:t,newPresences:[r],currentPresences:this.V[t]}))}if(t.leaves)for(const[i,r]of Object.entries(t.leaves)){const t=r[e]||i;this.V[t]=(this.V[t]||[]).filter(t=>t.userId!==i),0===this.V[t].length&&delete this.V[t];for(const e of this.bindings)"presence"===e.type&&("leave"!==e.filter.event&&"*"!==e.filter.event||e.callback({event:"leave",key:t,leftPresences:[r],currentPresences:this.V[t]||[]}))}for(const t of this.bindings)"presence"!==t.type||"sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"})}}tt(t){const e=t.match(/^(\w+)=eq\.(.+)$/);if(e)return{[e[1]]:e[2]}}}class DypaiRealtimeClient{constructor(t,e){this.ws=null,this.channels=new Map,this.heartbeatTimer=null,this.reconnectAttempt=0,this.reconnectTimer=null,this.baseUrl=t,this.tokenProvider=e}channel(t,e){const i=this.channels.get(t);if(i)return i;const r=new RealtimeChannel(t,this,e?.config);return this.channels.set(t,r),r}getChannels(){return Array.from(this.channels.values())}async removeChannel(t){await t.unsubscribe(),this.channels.delete(t.topic),0===this.channels.size&&this.disconnect()}async removeAllChannels(){for(const t of this.channels.values())await t.unsubscribe();this.channels.clear(),this.disconnect()}async setAuth(t){t&&(this.tokenProvider=()=>t),this.ws&&this.channels.size>0&&(this.disconnect(),this.Z())}disconnect(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.ws?.close(),this.ws=null}Z(){if(this.ws&&(this.ws.readyState===WebSocket.OPEN||this.ws.readyState===WebSocket.CONNECTING))return this.ws;const t=this.tokenProvider(),e=this.baseUrl.replace(/^http/,"ws")+`/realtime?token=${encodeURIComponent(t||"")}`,i=new WebSocket(e);return this.ws=i,i.addEventListener("open",()=>{this.reconnectAttempt=0;for(const t of this.channels.values())t.H&&t.rt(i);this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>{i.readyState===WebSocket.OPEN&&i.send(JSON.stringify({type:"heartbeat"}))},25e3)}),i.addEventListener("close",()=>{if(this.ws=null,this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.channels.size>0&&!this.reconnectTimer){const t=DypaiRealtimeClient.RECONNECT_INTERVALS[Math.min(this.reconnectAttempt,DypaiRealtimeClient.RECONNECT_INTERVALS.length-1)];this.reconnectAttempt++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.channels.size>0&&this.Z()},t)}}),i}}DypaiRealtimeClient.RECONNECT_INTERVALS=[1e3,2e3,5e3,1e4,3e4];class DataModule{get direct(){if(!this.st)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.st}set direct(t){this.st=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,i){return this.api.post("/api/v0/admin/db/sql",{query:t,params:e||[],...void 0!==i?.limit&&{limit:i.limit}})}}class DirectQueryBuilder{constructor(t,e){this.table=t,this.api=e,this.ot="public",this.nt=[],this.ht=100,this.ct=0,this.ut="ASC"}schema(t){return this.ot=t,this}eq(t,e){return this.nt.push({column:t,operator:"eq",value:e}),this}neq(t,e){return this.nt.push({column:t,operator:"neq",value:e}),this}gt(t,e){return this.nt.push({column:t,operator:"gt",value:e}),this}gte(t,e){return this.nt.push({column:t,operator:"gte",value:e}),this}lt(t,e){return this.nt.push({column:t,operator:"lt",value:e}),this}lte(t,e){return this.nt.push({column:t,operator:"lte",value:e}),this}like(t,e){return this.nt.push({column:t,operator:"ilike",value:e}),this}ilike(t,e){return this.like(t,e)}isNull(t){return this.nt.push({column:t,operator:"is_null",value:null}),this}is(t,e){return this.isNull(t)}notNull(t){return this.nt.push({column:t,operator:"not_null",value:null}),this}in(t,e){return this.nt.push({column:t,operator:"in",value:e}),this}contains(t,e){return this.nt.push({column:t,operator:"array_contains",value:e}),this}containedBy(t,e){return this.nt.push({column:t,operator:"contained_by",value:e}),this}overlaps(t,e){return this.nt.push({column:t,operator:"overlaps",value:e}),this}textSearch(t,e){return this.nt.push({column:t,operator:"fts",value:e}),this}phraseSearch(t,e){return this.nt.push({column:t,operator:"phfts",value:e}),this}not(t,e,i){return this.nt.push({column:t,operator:"not",value:{operator:e,value:i}}),this}or(t){return this.nt.push({operator:"or",or:t}),this}limit(t){return this.ht=t,this}offset(t){return this.ct=t,this}orderBy(t,e="ASC"){return this.dt=t,this.ut=e,this}async select(t){const e=[...this.nt];let i=this.ft;if("string"==typeof t)i=t;else if(t&&"object"==typeof t)for(const[i,r]of Object.entries(t))e.push({column:i,operator:"eq",value:r});return this.execute("select",{...i&&{columns:i},filters:e,limit:this.ht,offset:this.ct,sort_by:this.dt,order:this.ut})}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.nt],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 i=0;i<t.length;i+=1e3){const r=t.slice(i,i+1e3),{data:s,error:o}=await this.execute("insert",{data:r,mode:"bulk"});if(o)return{data:null,error:o};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(t){return 0===this.nt.length?{data:null,error:new DypaiError("Update requires at least one filter. Use .eq(), .in(), etc.",400)}:this.execute("update",{data:t,filters:this.nt})}async delete(){return 0===this.nt.length?{data:null,error:new DypaiError("Delete requires at least one filter.",400)}:this.execute("delete",{filters:this.nt})}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.ot,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 i=await this.api.put(`admin/users/${t}`,e);return i.data&&(i.data=normalizeUser(i.data)),i}async delete(t){return this.api.delete(`admin/users/${t}`)}}let customToastFunction=null;const fallbackToast=t=>{const{title:e,description:i,variant:r="default"}=t,s=`${"error"===r?"❌":"success"===r?"✅":"warning"===r?"⚠️":"info"===r?"ℹ️":"📢"} ${e}${i?`: ${i}`:""}`;"error"===r?console.error(s):"warning"===r&&console.warn(s)},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,i,r,s,o,n,a){const h=getCompleteConfig(),c=a||null;if(!c&&!i.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof i||!i.trim())throw new Error("Endpoint debe ser un string válido");let l;if(i.startsWith("http"))l=i;else{const t=c.replace(/\/+$/,"");l=i.startsWith("/")?t+i:t+"/api/v0/"+i}if(o&&Object.keys(o).length>0){const t=new URLSearchParams,e=(i,r)=>{null!=r&&(Array.isArray(r)?r.forEach((t,r)=>e(`${i}[${r}]`,t)):"object"==typeof r?Object.entries(r).forEach(([t,r])=>e(`${i}[${t}]`,r)):t.append(i,String(r)))};Object.entries(o).forEach(([t,i])=>e(t,i));const i=t.toString();i&&(l+=`?${i}`)}const u="GET"===e?`${e}:${l}:${JSON.stringify(r)}`:null;if(u&&pendingRequests.has(u))return pendingRequests.get(u);const d=r instanceof FormData,p={...h.headers||{},...d?{}:{"Content-Type":"application/json"},...t&&{Authorization:`Bearer ${t}`},...n&&{"x-api-key":n}},f={method:e,headers:p,credentials:"include"};r&&"GET"!==e&&"DELETE"!==e&&(f.body=d?r:JSON.stringify(r));const y=h.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!y)throw new Error("Fetch no disponible.");const w=(async()=>{const t=getCompleteConfig().toast||toast,i=(void 0!==s?s:!1!==h.showToasts)&&t;try{const r=await y(l,f);if(!r.ok){let e,s="Error en la petición";try{const t=await r.text();try{const i=JSON.parse(t);e=i,s=i.message||i.msg||i.error_description||i.error||s}catch{t.length<200&&(s=t)}}catch{}throw 401===r.status&&h.onUnauthorized&&h.onUnauthorized(),i&&t({title:"Error",description:s,variant:"error"}),new DypaiError(s,r.status,void 0,e)}!i||"POST"!==e&&"PUT"!==e&&"PATCH"!==e&&"DELETE"!==e||t({title:"Éxito",description:"Operación completada",variant:"success"});const s=r.headers.get("content-type")||"";return s.includes("application/pdf")||s.includes("image/")||s.includes("audio/")||s.includes("video/")||s.includes("application/octet-stream")||s.includes("application/zip")||s.includes("application/vnd.openxmlformats-officedocument")?await r.blob():s.includes("application/json")?await r.json():await r.text()}finally{u&&pendingRequests.delete(u)}})();return u&&pendingRequests.set(u,w),w}async function handleSmartDownload(t,e,i,r){const s=r?.method||(i?"POST":"GET"),{data:o,error:n}=await("GET"===s?t.get(e,{params:r?.params}):t.post(e,i,{params:r?.params}));if(n)throw n;if(o instanceof Blob){const t=window.URL.createObjectURL(o),e=document.createElement("a");e.href=t,e.download=r?.fileName||"archivo-descargado",document.body.appendChild(e),e.click(),window.URL.revokeObjectURL(t),document.body.removeChild(e)}else if(o&&"object"==typeof o&&("url"in o||"signed_url"in o||"signedUrl"in o)){const t=o.url||o.signed_url||o.signedUrl;if("string"==typeof t){const e=new URL(t).searchParams.get("response-content-disposition");let i;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(i=t[1].replace(/"/g,""))}const s=r?.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 handleSmartUpload(t,e,i,r){const{data:s,error:o}=await t.post(e,{file_path:i.name,content_type:i.type||"application/octet-stream",size_bytes:i.size,confirm:!1,client_upload:!0,...r?.params||{}});if(o)throw o;const n=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 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 normalizeUploadPayload(t);const r=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of r){const e=Array.isArray(t)?t:Object.values(t);for(const t of e)if(t&&(t.upload_url||t.uploadUrl||t.url))return normalizeUploadPayload(t)}return null}(s);if(!n?.upload_url)throw new DypaiError("The workflow did not return a valid upload URL (missing storage upload node?)",400);const{upload_url:a,method:h="PUT",headers:c={},file_path:l,storage_path:u}=n;r?.onProgress&&r.onProgress(10);const d=await fetch(a,{method:h,headers:{"Content-Type":i.type||"application/octet-stream",...c},body:i});if(!d.ok)throw new DypaiError("Direct upload to cloud storage failed",d.status);r?.onProgress&&r.onProgress(90);const p=r?.confirmEndpoint||e,{data:f,error:y}=await t.post(p,{...r?.params,bucket:n.bucket||r?.params?.bucket,file_path:l,storage_path:u,filename:i.name,content_type:i.type||"application/octet-stream",size_bytes:i.size,confirm:!0,client_upload:!0});if(y)throw y;return r?.onProgress&&r.onProgress(100),f}function normalizeUploadPayload(t){return t&&"object"==typeof t?{...t,upload_url:t.upload_url||t.uploadUrl||t.url,storage_path:t.storage_path||t.storagePath}:null}function 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,i,r)=>{try{return{data:await handleSmartUpload(e,t,i,r),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Upload failed",t?.status??500)}}},download:async(t,i,r)=>{try{return await handleSmartDownload(e,t,i,{...r,method:"POST"}),{data:void 0,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Download failed",t?.status??500)}}}};return e}function createMethod(t,e){const i="POST"===e||"PUT"===e||"PATCH"===e;return async(r,s,o)=>{const n=t();let a,h={};i?(a=s,h=o||{}):(h=s||{},a=void 0);const c=h.token||n.token||(globalTokenProvider?globalTokenProvider():"")||"",l=h.apiKey||n.apiKey;try{return{data:await callApi(c,e,r,a,h.showToasts,h.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)}}}}function createApiClient(t){const e=()=>"function"==typeof t?t():t,i={get:createMethodFromCtx(e,"GET"),post:createMethodFromCtx(e,"POST"),put:createMethodFromCtx(e,"PUT"),patch:createMethodFromCtx(e,"PATCH"),delete:createMethodFromCtx(e,"DELETE"),upload:async(t,e,r)=>{try{return{data:await handleSmartUpload(i,t,e,r),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Upload failed",t?.status??500)}}},download:async(t,e,r)=>{try{return await handleSmartDownload(i,t,e,{...r,method:"POST"}),{data:void 0,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Download failed",t?.status??500)}}}};return i}function createMethodFromCtx(t,e){return async(i,r,s)=>{const o=t(),n=await async function(t,e,i){let r,s={};"POST"===t||"PUT"===t||"PATCH"===t?(r=e,s=i||{}):(s=e||{},r=void 0);let o=s.token;return!o&&globalTokenProvider&&(o=globalTokenProvider()||""),o=o||"",{token:o,apiKey:s.apiKey,body:r,params:s.params,showToasts:s.showToasts}}(e,r,s),a=n.token||o.token||"",h=n.apiKey||o.apiKey;try{return{data:await callApi(a,e,i,n.body,n.showToasts,n.params,h,o.baseUrl),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Error desconocido",t?.status??500)}}}}class DypaiClient{get baseUrl(){return this.yt}getAuthToken(){return this.auth.token||this.wt||null}constructor(t){const{baseUrl:e,apiKey:i}=t;this.yt=e,this.wt=i,this.realtime=new DypaiRealtimeClient(e,()=>this.getAuthToken());const r=t.auth?.storageKey||t.storageKey;t.global&&function(t){serviceConfig={...serviceConfig,...t}}(t.global),this.auth=new AuthModule({baseUrl:e,apiKey:i,storageKey:r,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const s=createInternalApiClient(()=>({token:this.auth.token,apiKey:i,baseUrl:e}));if(this.auth.api=s,this.db=new DataModule(s),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=createInternalApiClient(()=>({token:t.serviceRoleKey,baseUrl:e}));this.db.direct=new DirectDBModule(i)}if(this.users=new UsersModule(s),this.api=createApiClient(()=>({token:this.auth.token||"",apiKey:i,baseUrl:e})),setTokenProvider(()=>this.auth.token),"undefined"!=typeof window&&t.redirects){const e=t.redirects;this.auth.k?.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({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.DypaiRealtimeClient=DypaiRealtimeClient,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.RealtimeChannel=RealtimeChannel,exports.callApi=callApi,exports.configureApiService=configureApiService,exports.configureDypaiServices=function(t){globalConfig={...globalConfig,toast:toast,...t},applyConfigToAllServices()},exports.createApiClient=createApiClient,exports.createClient=function(t,e,i){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?i: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,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"})}};
1
+ "use strict";class DypaiError extends Error{constructor(t,e=500,r,i){super(t),this.status=e,this.code=r,this.details=i,this.name="DypaiError"}}function normalizeUser(t){if(!t)return{};const e=t.user||t,r=e.name||e.user_metadata?.full_name||null,i=e.emailVerified??e.confirmed_at??null,s=e.app_metadata||{},o=e.user_metadata||{full_name:r};return{id:e.id,email:e.email,phone:e.phone||null,role:s.role||o.role||e.role||"authenticated",created_at:e.createdAt||e.created_at,updated_at:e.updatedAt||e.updated_at,confirmed_at:i?"boolean"==typeof i?(new Date).toISOString():i:null,last_sign_in_at:e.last_sign_in_at||null,app_metadata:s,user_metadata:{...o,full_name:r},roleDetails:{name:s.role||o.role||null,weight:0},appContext:{app_id:"default"}}}class AuthSessionMissingError extends DypaiError{constructor(t="Auth session missing"){super(t,400,"session_missing"),this.name="AuthSessionMissingError"}}class AuthInvalidCredentialsError extends DypaiError{constructor(t="Invalid login credentials"){super(t,400,"invalid_credentials"),this.name="AuthInvalidCredentialsError"}}class AuthInvalidTokenError extends DypaiError{constructor(t="Invalid or expired token"){super(t,401,"invalid_token"),this.name="AuthInvalidTokenError"}}class AuthWeakPasswordError extends DypaiError{constructor(t="Password is too weak",e=[]){super(t,400,"weak_password"),this.name="AuthWeakPasswordError",this.reasons=e}}class AuthEmailNotVerifiedError extends DypaiError{constructor(t="Email not verified"){super(t,403,"email_not_verified"),this.name="AuthEmailNotVerifiedError"}}class AuthUserAlreadyExistsError extends DypaiError{constructor(t="User already exists"){super(t,409,"user_already_exists"),this.name="AuthUserAlreadyExistsError"}}class RetryableFetchError extends DypaiError{constructor(t,e=0){super(t,e,"retryable_fetch"),this.name="RetryableFetchError"}}function isRetryableStatus(t){return 0===t||408===t||429===t||t>=500&&t<600}function classifyAuthError(t,e,r){const i=(t||"").toLowerCase(),s=(r||"").toLowerCase();return s.includes("invalid_token")||s.includes("invalid_grant")||i.includes("invalid token")||i.includes("expired")||i.includes("revoked")?new AuthInvalidTokenError(t):s.includes("session_missing")||i.includes("no session")||i.includes("session missing")?new AuthSessionMissingError(t):s.includes("invalid_credentials")||s.includes("invalid_login")||i.includes("invalid credentials")||i.includes("invalid login")?new AuthInvalidCredentialsError(t):s.includes("email_not_verified")||i.includes("email not verified")?new AuthEmailNotVerifiedError(t):s.includes("user_already_exists")||s.includes("user_exists")||i.includes("already exists")||i.includes("already registered")?new AuthUserAlreadyExistsError(t):s.includes("weak_password")||i.includes("password is too weak")||i.includes("password too short")?new AuthWeakPasswordError(t):isRetryableStatus(e)?new RetryableFetchError(t,e):new DypaiError(t,e,r)}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 i=r.msg||r.error_description||r.message||e,s=r.error_code||r.error||r.code||void 0;return classifyAuthError(i,t.status,s)}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.i=null,this.o=null,this.h=null,this.l=null,this.u=[],this.p=null,this.A=null,this.m=!1,this.v=null,this.S=null,this.D=0,this._=0,this.k=null,this.storage=t.storage||localStorageAdapter,_debugEnabled=!!t.debug;const r=t.storageKey||this.deriveStorageKey(t.apiKey);if(_log("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${r})`),this.STORAGE_KEY=`dypai-${r}-auth-session`,this.P=this.T().then(()=>{this.i&&(this.getUser().catch(()=>{}),this.startAutoRefresh());const t=this.A;this.A=null,t?setTimeout(()=>{this.m=!0,this.I(t)},0):this.m=!0}),"undefined"!=typeof window){if(window.addEventListener("visibilitychange",this.R.bind(this)),window.addEventListener("focus",this.R.bind(this)),"undefined"!=typeof BroadcastChannel)try{this.k=new BroadcastChannel(`bc:${this.STORAGE_KEY}`),this.k.addEventListener("message",t=>{const e=t.data;e&&"object"==typeof e&&(_log("log",`[DYPAI SDK] 📡 BroadcastChannel: ${e.type}`),"SIGNED_OUT"===e.type?(this.i=null,this.o=null,this.h=null,this.t=null,this.l=null,this.stopAutoRefresh(),this.I("SIGNED_OUT")):"SESSION_UPDATED"===e.type&&this.T(!1))})}catch(t){_log("warn","[DYPAI SDK] BroadcastChannel no disponible, usando storage event:",t)}window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(_log("log","[DYPAI SDK] 🔄 Sesión actualizada (storage event). Sincronizando..."),this.T(!1))})}}C(t){return 0===t||408===t||429===t||t>=500&&t<600}O(t){if(this.k)try{this.k.postMessage({type:t,timestamp:Date.now()})}catch{}}async R(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(_log("log","[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this.T(!0),this.i&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}getBaseUrl(){return this.config.baseUrl||"http://localhost:8000"}$(){if("undefined"==typeof window)return;const t=new URLSearchParams(window.location.search);["token","type","error","error_code","error_description","access_token","refresh_token","expires_in"].forEach(e=>t.delete(e));const e=t.toString(),r=`${window.location.pathname}${e?`?${e}`:""}`;window.history.replaceState({},document.title,r)}U(t){try{if(!t)return;sessionStorage.setItem(AuthModule.CALLBACK_TYPE_STORAGE_KEY,t),"recovery"===t||"invite"===t?sessionStorage.setItem(AuthModule.CALLBACK_REDIRECT_STORAGE_KEY,"PASSWORD_RECOVERY"):sessionStorage.removeItem(AuthModule.CALLBACK_REDIRECT_STORAGE_KEY)}catch{}}M(t){try{sessionStorage.setItem(AuthModule.RESET_TOKEN_STORAGE_KEY,t)}catch{}}N(){try{return sessionStorage.getItem(AuthModule.RESET_TOKEN_STORAGE_KEY)}catch{return null}}K(){try{sessionStorage.removeItem(AuthModule.RESET_TOKEN_STORAGE_KEY)}catch{}}get user(){return this.t}get token(){return this.i}get lastError(){return this.p}consumeCallbackType(){try{const t=sessionStorage.getItem(AuthModule.CALLBACK_TYPE_STORAGE_KEY);return sessionStorage.removeItem(AuthModule.CALLBACK_TYPE_STORAGE_KEY),sessionStorage.removeItem(AuthModule.CALLBACK_REDIRECT_STORAGE_KEY),t}catch{return null}}get isPasswordRecoveryCallback(){try{return"PASSWORD_RECOVERY"===sessionStorage.getItem(AuthModule.CALLBACK_REDIRECT_STORAGE_KEY)}catch{return!1}}isLoggedIn(){return!(!this.i||!this.t)}onAuthStateChange(t){return _log("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.u.push(t),this.P.then(()=>{const e=this.Y();_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.u=this.u.filter(e=>e!==t)}}}}}async signInWithPassword(t){return wrapAuthResponse((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-in/email`,r={email:t.email||t.identifier||"",password:t.password},i=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(r)});if(!i.ok)throw await parseAuthError(i,"Login failed");const s=await i.json(),o=this.L(s);return await this.j(o),o})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return wrapAuthResponse((async()=>{const r=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-up/email`,{email:i,phone:s,password:o,user_data:n,...a}=t,h=i||"",l=a.full_name||a.name||h.split("@")[0],c={...a,...n||{}},u={email:h,password:o,name:l};Object.keys(c).length>0&&Object.assign(u,c),e?.redirectTo?u.callbackURL=e.redirectTo:"undefined"!=typeof window&&(u.callbackURL=window.location.href.split("#")[0]);const d=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(u)});if(!d.ok)throw await parseAuthError(d,"Registration failed");const p=await d.json(),f=this.L(p),y={...f,confirmationRequired:!f.token};return y.token?await this.j(y):_log("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),y})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this.P,!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.l||0)-t<30+AuthModule.CLOCK_SKEW_TOLERANCE_S&&(this.h||this.o)&&(_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.i,refresh_token:this.h||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()=>{await this.P;const t=this.o||this.i;if(!t)throw new AuthSessionMissingError("No hay sesión activa");const e=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,r=await fetch(e,{headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!r.ok)throw new DypaiError("Session invalid",r.status);const i=await r.json(),s=i?.jwt||r.headers.get("set-auth-jwt");s&&(this.i=s);const o=this.B(i);return this.q(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:i}=e,s=this.config.baseUrl||"http://localhost:8000",o=new URLSearchParams({provider:t,callbackURL:r});i?.length&&o.set("scopes",i.join(" "));const n=`${s}/api/v0/auth/sign-in/social?${o.toString()}`;window.location.href=n})())}async signOut(){return wrapAuthResponse((async()=>{try{const t=this.o||this.i;if(t){const e=this.config.baseUrl||"http://localhost:8000";await fetch(`${e}/api/v0/auth/sign-out`,{method:"POST",headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}})}}finally{this.F("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return wrapAuthResponse((async()=>{const r=this.config.baseUrl||"http://localhost:8000",i={email:t};e?.redirectTo?i.redirectTo=e.redirectTo:"undefined"!=typeof window&&(i.redirectTo=window.location.href.split("#")[0]);const s=await fetch(`${r}/api/v0/auth/request-password-reset`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});if(!s.ok)throw await parseAuthError(s,"Recovery failed");return await s.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t,e){return wrapAuthResponse((async()=>{const r=this.config.baseUrl||"http://localhost:8000",i={email:t};e?.redirectTo?i.callbackURL=e.redirectTo:"undefined"!=typeof window&&(i.callbackURL=window.location.href.split("#")[0]);const s=await fetch(`${r}/api/v0/auth/send-verification-email`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(i)});if(!s.ok)throw await parseAuthError(s,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.v)return _log("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.v.promise;const t=new Deferred;this.v=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 i=new AbortController,s=setTimeout(()=>i.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:i.signal},async()=>(clearTimeout(s),await r()))}catch(i){if("AbortError"===i.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await r();throw i}}return await r()}(e,AuthModule.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.W());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.v=null}})(),t.promise}async W(){try{const t=await this.storage.getItem(this.STORAGE_KEY);if(t){const e=JSON.parse(t),r=e.expires_at||0,i=Math.floor(Date.now()/1e3);if(e.access_token!==this.i&&r-i>60)return _log("log","[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.i=e.access_token,this.o=e.session_token||e.access_token,this.h=e.refresh_token,this.l=e.expires_at,this.t=e.user,this.D=0,this.I("TOKEN_REFRESHED"),{data:{token:this.i,sessionToken:this.o||void 0,refreshToken:this.h||void 0,expiresAt:this.l||void 0,user:this.t},error:null}}const e=this.o||this.i;if(!e)throw new AuthSessionMissingError("No hay sesión activa");_log("log","[DYPAI SDK] 🔄 Verificando sesión activa con session_token...");const r=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,i=new AbortController,s=setTimeout(()=>i.abort(),AuthModule.REFRESH_TIMEOUT_MS);let o;try{o=await fetch(r,{method:"GET",headers:{Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},signal:i.signal})}finally{clearTimeout(s)}if(!o.ok){let t={};try{t=await o.json()}catch{}const e=o.status,r=t.error||t.code||"",i=(t.error_description||t.msg||"").toLowerCase(),s=this.C(e),n=e>=400&&e<500&&!s,a="invalid_grant"===r||i.includes("invalid refresh token")||i.includes("revoked")||i.includes("expired")||i.includes("invalid token");throw n&&a?(_log("error",`[DYPAI SDK] ❌ Token inválido/revocado (${e}). Limpiando sesión.`),this.F(`refreshSession failed (${e}: ${r})`)):n?(_log("error",`[DYPAI SDK] ❌ Error 4xx en refresh (${e}). Limpiando sesión.`),this.F(`refreshSession failed (${e})`)):_log("warn",`[DYPAI SDK] ⚠️ Error transitorio en refresh (${e}). Manteniendo sesión.`),new DypaiError(t.msg||t.error_description||"Refresh session failed",e)}const n=await o.json(),a=o.headers.get("set-auth-jwt"),h=n?.jwt,l=h||a,c=this.L(n,this.i);return l&&(c.token=l),c.sessionToken=e,this.D=0,await this.j(c),_log("log",`[DYPAI SDK] ✅ Session refresh exitoso (fresh JWT: ${!!l}).`),{data:c,error:null}}catch(t){this.D++,this._=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),i=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.D),AuthModule.MAX_RETRY_MS);return e?_log("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${AuthModule.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${i}ms (intento ${this.D})`):_log("error","[DYPAI SDK] ❌ Refresh falló:",r.message,`(status: ${r.status}). Backoff: ${i}ms (intento ${this.D})`),this.D>=AuthModule.MAX_REFRESH_FAILURES&&(_log("error",`[DYPAI SDK] 🗑️ ${this.D} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.F(`${this.D} consecutive refresh failures`),this.D=0),{data:null,error:r}}}async signInWithOtp(t){return wrapAuthResponse((async()=>{if(t.phone)throw new DypaiError("Phone OTP is not supported by the current DYPAI auth backend",400);if(!t.email)throw new DypaiError("Email is required for magic link sign-in",400);const e=await this.signInWithMagicLink(t.email);if(e.error)throw e.error;return{status:!0}})())}async verifyOtp(t){return wrapAuthResponse((async()=>{if(!t.token)throw new DypaiError("Verification token is required",400);if("magiclink"===t.type)return await this.J(t.token);if("signup"===t.type||"email_change"===t.type){const e=await this.G(t.token);if(e)return e;throw new DypaiError("Verification completed but no session was returned",400)}if("recovery"===t.type||"invite"===t.type)throw new DypaiError("Recovery links do not use verifyOtp(). Use setPassword() after the PASSWORD_RECOVERY callback.",400);throw new DypaiError("Unsupported verification type",400)})())}async updateUser(t){return wrapAuthResponse((async()=>{const e=this.o||this.i;if(!e)throw new AuthSessionMissingError("No hay sesión activa");const r=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/update-user`,i=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(t)});if(!i.ok){const t=await i.json();throw new DypaiError(t.detail||"Update user failed",i.status)}const s=await i.json(),o=this.B(s);return this.q(o),o})())}async setPassword(t){return wrapAuthResponse((async()=>{if("undefined"==typeof window)throw new DypaiError("setPassword requiere un entorno de navegador",400);const e=new URLSearchParams(window.location.search).get("token")||this.N();if(!e)throw new DypaiError("No reset token found in the current URL",400);const r=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${r}/api/v0/auth/reset-password`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({newPassword:t,token:e})});if(!i.ok)throw await parseAuthError(i,"Reset password failed");this.K(),this.$();const s=await i.json();if(s?.jwt&&s?.session_token&&s?.user){const t={token:s.jwt,sessionToken:s.session_token,user:this.B(s),expiresAt:s.session?.expiresAt?Math.floor(new Date(s.session.expiresAt).getTime()/1e3):void 0};await this.j(t)}return{status:s?.status??!0}})())}async signInWithMagicLink(t,e){return wrapAuthResponse((async()=>{const r=this.config.baseUrl||"http://localhost:8000",i=e?.redirectTo||("undefined"!=typeof window?window.location.href.split("#")[0]:void 0),s=await fetch(`${r}/api/v0/auth/sign-in/magic-link`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({email:t,callbackURL:i})});if(!s.ok)throw await parseAuthError(s,"Magic link failed");return{message:"Magic link sent"}})())}async enableTwoFactor(){return wrapAuthResponse((async()=>{const t=this.o||this.i;if(!t)throw new AuthSessionMissingError("No hay sesión activa");const e=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${e}/api/v0/auth/two-factor/enable`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!r.ok)throw await parseAuthError(r,"Enable 2FA failed");return await r.json()})())}async verifyTwoFactor(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${e}/api/v0/auth/two-factor/verify-totp`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.o||this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({code:t})});if(!r.ok)throw await parseAuthError(r,"2FA verification failed");const i=await r.json(),s={token:i.jwt||i.token||this.i,expiresIn:604800,expiresAt:Math.floor(Date.now()/1e3)+604800,user:this.B(i)};return await this.j(s),s})())}async disableTwoFactor(){return wrapAuthResponse((async()=>{const t=this.o||this.i;if(!t)throw new AuthSessionMissingError("No hay sesión activa");const e=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${e}/api/v0/auth/two-factor/disable`,{method:"POST",headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!r.ok)throw await parseAuthError(r,"Disable 2FA failed");return{message:"2FA disabled"}})())}async createOrganization(t,e){return wrapAuthResponse((async()=>{const r=this.o||this.i;if(!r)throw new AuthSessionMissingError("No hay sesión activa");const i=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${i}/api/v0/auth/organization/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${r}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({name:t,slug:e})});if(!s.ok)throw await parseAuthError(s,"Create organization failed");return await s.json()})())}async inviteMember(t,e,r){return wrapAuthResponse((async()=>{const i=this.o||this.i;if(!i)throw new AuthSessionMissingError("No hay sesión activa");const s=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${s}/api/v0/auth/organization/invite-member`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({organizationId:t,email:e,role:r||"member"})});if(!o.ok)throw await parseAuthError(o,"Invite member failed");return await o.json()})())}async listOrganizations(){return wrapAuthResponse((async()=>{const t=this.o||this.i;if(!t)throw new AuthSessionMissingError("No hay sesión activa");const e=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${e}/api/v0/auth/organization/list`,{headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!r.ok)throw await parseAuthError(r,"List organizations failed");return await r.json()})())}startAutoRefresh(){this.stopAutoRefresh(),_log("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.V(),this.S=setInterval(()=>this.V(),AuthModule.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.S&&(_log("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.S),this.S=null)}async V(){try{const t=Math.floor(Date.now()/1e3),e=this.l||0;if(!this.h&&!this.o||!e)return;if(1e3*(e-t)>AuthModule.AUTO_REFRESH_TICK_THRESHOLD*AuthModule.AUTO_REFRESH_TICK_DURATION_MS+1e3*AuthModule.CLOCK_SKEW_TOLERANCE_S)return;if(this.D>0){const t=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.D),AuthModule.MAX_RETRY_MS);if(Date.now()-this._<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)}}B(t){return t?normalizeUser(t):(_log("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}L(t,e){const r="number"==typeof t?.expiresIn?t.expiresIn:"number"==typeof t?.expires_in?t.expires_in:604800,i=t?.jwt||void 0,s=t?.session_token||t?.token||t?.access_token||t?.session?.token||t?.session?.accessToken||t?.session?.access_token||e;return{token:i||s,sessionToken:s,refreshToken:t?.refreshToken||t?.refresh_token||t?.session?.refreshToken||t?.session?.refresh_token||void 0,expiresIn:r,expiresAt:Math.floor(Date.now()/1e3)+r,user:this.B(t)}}async j(t){this.p=null,t.token&&(this.i=t.token,this.o=t.sessionToken||t.token,void 0!==t.refreshToken&&(this.h=t.refreshToken||null),void 0!==t.expiresAt&&(this.l=t.expiresAt||null),await this.q(t.user,t.token,t.refreshToken,t.expiresAt),_log("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.l}, has_refresh: ${!!this.h}, has_session_token: ${!!this.o})`),this.startAutoRefresh())}async q(t,e,r,i){this.t=t,e&&(this.i=e),void 0!==r&&(this.h=r||null),void 0!==i&&(this.l=i||null);try{if(this.i&&this.t){const t={access_token:this.i,session_token:this.o,refresh_token:this.h,expires_at:this.l,user:this.t,version:2};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t)),this.O("SESSION_UPDATED")}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.m){const t=e?"SIGNED_IN":"USER_UPDATED";this.I(t)}}async F(t="unknown"){_log("log",`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.i=null,this.o=null,this.h=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.O("SIGNED_OUT"),this.I("SIGNED_OUT")}async J(t){const e=this.getBaseUrl(),r=new URL(`${e}/api/v0/auth/magic-link/verify`);r.searchParams.set("token",t);const i=await fetch(r.toString(),{method:"GET",headers:{...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw await parseAuthError(i,"Magic link verification failed");const s=await i.json(),o=this.L(s);return await this.j(o),o}async G(t){const e=this.getBaseUrl(),r=new URL(`${e}/api/v0/auth/verify-email`);r.searchParams.set("token",t);const i=await fetch(r.toString(),{method:"GET",headers:{...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw await parseAuthError(i,"Email verification failed");if(!(i.headers.get("content-type")||i.headers.get("Content-Type")||"").includes("application/json"))return null;const s=await i.json(),o=this.L(s);return o.token&&o.user?.id?(await this.j(o),o):null}async H(t,e){const r=e||("undefined"!=typeof window&&window.location.pathname.includes("reset-password")?"recovery":null);if(!r)return!1;if(_log("log",`[DYPAI SDK] 🔗 Auth callback detectado en query (type: ${r}). Procesando...`),this.U(r),"recovery"===r||"invite"===r)return this.M(t),this.$(),this.A="PASSWORD_RECOVERY",!0;if(this.$(),"magiclink"===r)return await this.J(t),this.A="SIGNED_IN",!0;if("signup"===r||"email_change"===r){const e=await this.G(t);return this.A=e?.token?"SIGNED_IN":null,!0}return!1}async T(t=!0){_log("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.X())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)return void _log("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");let r;try{r=JSON.parse(e)}catch{return _log("warn","[DYPAI SDK] ⚠️ Sesión corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!r||"object"!=typeof r||"string"!=typeof r.access_token)return _log("warn","[DYPAI SDK] ⚠️ Sesión con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);if((r.version||1)<2&&!r.session_token)return _log("warn","[DYPAI SDK] 🔄 Sesión en formato antiguo (v1) sin session_token. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);if(this.i=r.access_token,this.o="string"==typeof r.session_token?r.session_token:r.access_token,this.h="string"==typeof r.refresh_token?r.refresh_token:null,this.l="number"==typeof r.expires_at?r.expires_at:null,this.t=r.user&&"object"==typeof r.user?r.user:null,t){_log("log","[DYPAI SDK] 🔄 Validando sesión con el servidor...");const t=await this.refreshSession();if(t.error)return void _log("warn","[DYPAI SDK] ⚠️ Sesión inválida. Limpiando:",t.error.message)}this.A="SIGNED_IN"}catch(t){_log("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async X(){if("undefined"==typeof window)return!1;try{const t=new URLSearchParams(window.location.search),e=t.get("error"),r=t.get("token"),i=t.get("type");if(e){const r=t.get("error_code")||e,i=t.get("error_description")?.replace(/\+/g," ")||"Authentication error";return _log("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${r} — ${i}`),this.p={code:r,message:i},this.$(),this.I("AUTH_ERROR"),!0}if(r)return await this.H(r,i);const s=window.location.hash.substring(1);if(!s)return!1;const o=new URLSearchParams(s);if(o.get("error")){const t=o.get("error_code")||"unknown",e=o.get("error_description")?.replace(/\+/g," ")||"Authentication error";return _log("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${e}`),this.p={code:t,message:e},this.$(),this.I("AUTH_ERROR"),!0}const n=o.get("access_token");if(!n)return!1;const a=o.get("refresh_token"),h=o.get("type"),l=parseInt(o.get("expires_in")||"3600",10);_log("log",`[DYPAI SDK] 🔗 Auth callback detectado en URL hash (type: ${h||"unknown"}). Procesando...`),this.U(h),this.$(),this.o=n,this.i=n,this.h=a||null,this.l=Math.floor(Date.now()/1e3)+l;const c=await fetch(`${this.getBaseUrl()}/api/v0/auth/get-session`,{headers:{Authorization:`Bearer ${n}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!c.ok)return _log("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${c.status}). Limpiando...`),this.i=null,this.o=null,this.h=null,this.l=null,this.p={code:"token_invalid",message:"The authentication link is invalid or has expired"},this.I("AUTH_ERROR"),!0;const u=await c.json(),d=u?.jwt||c.headers.get("set-auth-jwt"),p=this.B(u),f={token:d||n,sessionToken:n,refreshToken:a||void 0,expiresIn:l,expiresAt:this.l,user:p};return await this.j(f),this.A="recovery"===h||"invite"===h?"PASSWORD_RECOVERY":"SIGNED_IN",_log("log",`[DYPAI SDK] 🔑 Callback type=${h}. Pending event: ${this.A}`),!0}catch(t){return _log("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}Y(){return this.i&&this.t?{access_token:this.i,refresh_token:this.h||void 0,expires_at:this.l||void 0,token_type:"bearer",user:this.t}:null}I(t="USER_UPDATED"){const e=this.Y();_log("log",`[DYPAI SDK] 📢 Notificando a ${this.u.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.u.forEach(r=>r(t,e))}handleSessionExpired(){this.F("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,AuthModule.CLOCK_SKEW_TOLERANCE_S=10,AuthModule.CALLBACK_TYPE_STORAGE_KEY="dypai-auth-callback-type",AuthModule.CALLBACK_REDIRECT_STORAGE_KEY="dypai-auth-callback-redirect",AuthModule.RESET_TOKEN_STORAGE_KEY="dypai-auth-reset-token";class RealtimeChannel{constructor(t,e,r){this.ws=null,this.bindings=[],this.Z={},this.tt=!1,this.topic=t,this.client=e,this.config=r||{}}on(t,e,r){return this.bindings.push({type:t,filter:e,callback:r}),this}subscribe(t){this.et=t;const e=this.client.rt();this.ws=e;const r=()=>{const r=this.bindings.filter(t=>"postgres_changes"===t.type);for(const t of r)e.send(JSON.stringify({type:"subscribe",table:t.filter.table||"*",event:t.filter.event||"*",filter:t.filter.filter?this.it(t.filter.filter):void 0,schema:t.filter.schema||"public"}));const i=this.bindings.some(t=>"broadcast"===t.type),s=this.bindings.some(t=>"presence"===t.type);(i||s)&&e.send(JSON.stringify({type:"join",channel:this.topic,presence:!!s||void 0})),this.tt=!0,t?.("SUBSCRIBED")};e.readyState===WebSocket.OPEN?r():e.readyState===WebSocket.CONNECTING&&e.addEventListener("open",r,{once:!0}),this.st&&e.removeEventListener("message",this.st);const i=t=>{try{const e=JSON.parse("string"==typeof t.data?t.data:"");this.ot(e)}catch{}};return this.st=i,e.addEventListener("message",i),e.addEventListener("close",()=>{this.tt=!1,t?.("CLOSED")},{once:!0}),e.addEventListener("error",()=>{t?.("CHANNEL_ERROR",new Error("WebSocket error"))},{once:!0}),this}nt(t){const e=this.bindings.filter(t=>"postgres_changes"===t.type);for(const r of e)t.send(JSON.stringify({type:"subscribe",table:r.filter.table||"*",event:r.filter.event||"*",filter:r.filter.filter?this.it(r.filter.filter):void 0,schema:r.filter.schema||"public"}));const r=this.bindings.some(t=>"broadcast"===t.type),i=this.bindings.some(t=>"presence"===t.type);(r||i)&&t.send(JSON.stringify({type:"join",channel:this.topic,presence:!!i||void 0})),this.et?.("SUBSCRIBED")}async send(t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return"error";try{return this.ws.send(JSON.stringify({type:"broadcast",channel:this.topic,event:t.event,payload:t.payload,self:this.config.broadcast?.self||!1})),"ok"}catch{return"error"}}async track(t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return"error";try{return this.ws.send(JSON.stringify({type:"presence",channel:this.topic,data:t})),"ok"}catch{return"error"}}async untrack(){return"ok"}presenceState(){return{...this.Z}}async unsubscribe(){if(this.st&&this.ws&&(this.ws.removeEventListener("message",this.st),this.st=void 0),this.ws?.readyState===WebSocket.OPEN){const t=this.bindings.filter(t=>"postgres_changes"===t.type);for(const e of t)this.ws.send(JSON.stringify({type:"unsubscribe",table:e.filter.table}));this.ws.send(JSON.stringify({type:"leave",channel:this.topic}))}return this.tt=!1,this.bindings=[],this.Z={},"ok"}ot(t){if("change"===t.type)for(const e of this.bindings)"postgres_changes"===e.type&&(e.filter.table&&"*"!==e.filter.table&&e.filter.table!==t.table||e.filter.event&&"*"!==e.filter.event&&e.filter.event!==t.event||e.callback({schema:"public",table:t.table,eventType:t.event,new:t.record||{},old:t.old_record||{},commit_timestamp:t.timestamp}));if("broadcast"===t.type&&t.channel===this.topic)for(const e of this.bindings)"broadcast"===e.type&&("*"!==e.filter.event&&e.filter.event!==t.event||e.callback({event:t.event,payload:t.payload,sender:t.sender,timestamp:t.timestamp}));if("presence_state"===t.type&&t.channel===this.topic){const e=this.config.presence?.key||"userId";this.Z={};for(const[r,i]of Object.entries(t.presences||{})){const t=i[e]||r;this.Z[t]||(this.Z[t]=[]),this.Z[t].push(i)}for(const t of this.bindings)"presence"===t.type&&("sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"}))}if("presence_diff"===t.type&&t.channel===this.topic){const e=this.config.presence?.key||"userId";if(t.joins)for(const[r,i]of Object.entries(t.joins)){const t=i[e]||r;this.Z[t]||(this.Z[t]=[]),this.Z[t].push(i);for(const e of this.bindings)"presence"===e.type&&("join"!==e.filter.event&&"*"!==e.filter.event||e.callback({event:"join",key:t,newPresences:[i],currentPresences:this.Z[t]}))}if(t.leaves)for(const[r,i]of Object.entries(t.leaves)){const t=i[e]||r;this.Z[t]=(this.Z[t]||[]).filter(t=>t.userId!==r),0===this.Z[t].length&&delete this.Z[t];for(const e of this.bindings)"presence"===e.type&&("leave"!==e.filter.event&&"*"!==e.filter.event||e.callback({event:"leave",key:t,leftPresences:[i],currentPresences:this.Z[t]||[]}))}for(const t of this.bindings)"presence"!==t.type||"sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"})}}it(t){const e=t.match(/^(\w+)=eq\.(.+)$/);if(e)return{[e[1]]:e[2]}}}class DypaiRealtimeClient{constructor(t,e){this.ws=null,this.channels=new Map,this.heartbeatTimer=null,this.reconnectAttempt=0,this.reconnectTimer=null,this.baseUrl=t,this.tokenProvider=e}channel(t,e){const r=this.channels.get(t);if(r)return r;const i=new RealtimeChannel(t,this,e?.config);return this.channels.set(t,i),i}getChannels(){return Array.from(this.channels.values())}async removeChannel(t){await t.unsubscribe(),this.channels.delete(t.topic),0===this.channels.size&&this.disconnect()}async removeAllChannels(){for(const t of this.channels.values())await t.unsubscribe();this.channels.clear(),this.disconnect()}async setAuth(t){t&&(this.tokenProvider=()=>t),this.ws&&this.channels.size>0&&(this.disconnect(),this.rt())}disconnect(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.ws?.close(),this.ws=null}rt(){if(this.ws&&(this.ws.readyState===WebSocket.OPEN||this.ws.readyState===WebSocket.CONNECTING))return this.ws;const t=this.tokenProvider(),e=this.baseUrl.replace(/^http/,"ws")+`/realtime?token=${encodeURIComponent(t||"")}`,r=new WebSocket(e);return this.ws=r,r.addEventListener("open",()=>{this.reconnectAttempt=0;for(const t of this.channels.values())t.tt&&t.nt(r);this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>{r.readyState===WebSocket.OPEN&&r.send(JSON.stringify({type:"heartbeat"}))},25e3)}),r.addEventListener("close",()=>{if(this.ws=null,this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.channels.size>0&&!this.reconnectTimer){const t=DypaiRealtimeClient.RECONNECT_INTERVALS[Math.min(this.reconnectAttempt,DypaiRealtimeClient.RECONNECT_INTERVALS.length-1)];this.reconnectAttempt++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.channels.size>0&&this.rt()},t)}}),r}}DypaiRealtimeClient.RECONNECT_INTERVALS=[1e3,2e3,5e3,1e4,3e4];class DataModule{get direct(){if(!this.ht)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.ht}set direct(t){this.ht=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.ct="public",this.ut=[],this.dt=100,this.ft=0,this.yt="ASC"}schema(t){return this.ct=t,this}eq(t,e){return this.ut.push({column:t,operator:"eq",value:e}),this}neq(t,e){return this.ut.push({column:t,operator:"neq",value:e}),this}gt(t,e){return this.ut.push({column:t,operator:"gt",value:e}),this}gte(t,e){return this.ut.push({column:t,operator:"gte",value:e}),this}lt(t,e){return this.ut.push({column:t,operator:"lt",value:e}),this}lte(t,e){return this.ut.push({column:t,operator:"lte",value:e}),this}like(t,e){return this.ut.push({column:t,operator:"ilike",value:e}),this}ilike(t,e){return this.like(t,e)}isNull(t){return this.ut.push({column:t,operator:"is_null",value:null}),this}is(t,e){return this.isNull(t)}notNull(t){return this.ut.push({column:t,operator:"not_null",value:null}),this}in(t,e){return this.ut.push({column:t,operator:"in",value:e}),this}contains(t,e){return this.ut.push({column:t,operator:"array_contains",value:e}),this}containedBy(t,e){return this.ut.push({column:t,operator:"contained_by",value:e}),this}overlaps(t,e){return this.ut.push({column:t,operator:"overlaps",value:e}),this}textSearch(t,e){return this.ut.push({column:t,operator:"fts",value:e}),this}phraseSearch(t,e){return this.ut.push({column:t,operator:"phfts",value:e}),this}not(t,e,r){return this.ut.push({column:t,operator:"not",value:{operator:e,value:r}}),this}or(t){return this.ut.push({operator:"or",or:t}),this}limit(t){return this.dt=t,this}offset(t){return this.ft=t,this}orderBy(t,e="ASC"){return this.wt=t,this.yt=e,this}async select(t){const e=[...this.ut];let r=this.At;if("string"==typeof t)r=t;else if(t&&"object"==typeof t)for(const[r,i]of Object.entries(t))e.push({column:r,operator:"eq",value:i});return this.execute("select",{...r&&{columns:r},filters:e,limit:this.dt,offset:this.ft,sort_by:this.wt,order:this.yt})}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.ut],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 i=t.slice(r,r+1e3),{data:s,error:o}=await this.execute("insert",{data:i,mode:"bulk"});if(o)return{data:null,error:o};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(t){return 0===this.ut.length?{data:null,error:new DypaiError("Update requires at least one filter. Use .eq(), .in(), etc.",400)}:this.execute("update",{data:t,filters:this.ut})}async delete(){return 0===this.ut.length?{data:null,error:new DypaiError("Delete requires at least one filter.",400)}:this.execute("delete",{filters:this.ut})}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.ct,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:i="default"}=t,s=`${"error"===i?"❌":"success"===i?"✅":"warning"===i?"⚠️":"info"===i?"ℹ️":"📢"} ${e}${r?`: ${r}`:""}`;"error"===i?console.error(s):"warning"===i&&console.warn(s)},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,i,s,o,n,a){const h=getCompleteConfig(),l=a||null;if(!l&&!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 c;if(r.startsWith("http"))c=r;else{const t=l.replace(/\/+$/,"");c=r.startsWith("/")?t+r:t+"/api/v0/"+r}if(o&&Object.keys(o).length>0){const t=new URLSearchParams,e=(r,i)=>{null!=i&&(Array.isArray(i)?i.forEach((t,i)=>e(`${r}[${i}]`,t)):"object"==typeof i?Object.entries(i).forEach(([t,i])=>e(`${r}[${t}]`,i)):t.append(r,String(i)))};Object.entries(o).forEach(([t,r])=>e(t,r));const r=t.toString();r&&(c+=`?${r}`)}const u="GET"===e?`${e}:${c}:${JSON.stringify(i)}`:null;if(u&&pendingRequests.has(u))return pendingRequests.get(u);const d=i instanceof FormData,p={...h.headers||{},...d?{}:{"Content-Type":"application/json"},...t&&{Authorization:`Bearer ${t}`},...n&&{"x-api-key":n}},f={method:e,headers:p,credentials:"include"};i&&"GET"!==e&&"DELETE"!==e&&(f.body=d?i:JSON.stringify(i));const y=h.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!y)throw new Error("Fetch no disponible.");const w=(async()=>{const t=getCompleteConfig().toast||toast,r=(void 0!==s?s:!1!==h.showToasts)&&t;try{const i=await y(c,f);if(!i.ok){let e,s="Error en la petición";try{const t=await i.text();try{const r=JSON.parse(t);e=r,s=r.message||r.msg||r.error_description||r.error||s}catch{t.length<200&&(s=t)}}catch{}throw 401===i.status&&h.onUnauthorized&&h.onUnauthorized(),r&&t({title:"Error",description:s,variant:"error"}),new DypaiError(s,i.status,void 0,e)}!r||"POST"!==e&&"PUT"!==e&&"PATCH"!==e&&"DELETE"!==e||t({title:"Éxito",description:"Operación completada",variant:"success"});const s=i.headers.get("content-type")||"";return s.includes("application/pdf")||s.includes("image/")||s.includes("audio/")||s.includes("video/")||s.includes("application/octet-stream")||s.includes("application/zip")||s.includes("application/vnd.openxmlformats-officedocument")?await i.blob():s.includes("application/json")?await i.json():await i.text()}finally{u&&pendingRequests.delete(u)}})();return u&&pendingRequests.set(u,w),w}async function handleSmartDownload(t,e,r,i){const s=i?.method||(r?"POST":"GET"),{data:o,error:n}=await("GET"===s?t.get(e,{params:i?.params}):t.post(e,r,{params:i?.params}));if(n)throw n;if(o instanceof Blob){const t=window.URL.createObjectURL(o),e=document.createElement("a");e.href=t,e.download=i?.fileName||"archivo-descargado",document.body.appendChild(e),e.click(),window.URL.revokeObjectURL(t),document.body.removeChild(e)}else if(o&&"object"==typeof o&&("url"in o||"signed_url"in o||"signedUrl"in o)){const t=o.url||o.signed_url||o.signedUrl;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 s=i?.fileName||r;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 handleSmartUpload(t,e,r,i){const{data:s,error:o}=await t.post(e,{file_path:r.name,content_type:r.type||"application/octet-stream",size_bytes:r.size,confirm:!1,client_upload:!0,...i?.params||{}});if(o)throw o;const n=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 i=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of i){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}(s);if(!n?.upload_url)throw new DypaiError("The workflow did not return a valid upload URL (missing storage upload node?)",400);const{upload_url:a,method:h="PUT",headers:l={},file_path:c,storage_path:u}=n;i?.onProgress&&i.onProgress(10);const d=await fetch(a,{method:h,headers:{"Content-Type":r.type||"application/octet-stream",...l},body:r});if(!d.ok)throw new DypaiError("Direct upload to cloud storage failed",d.status);i?.onProgress&&i.onProgress(90);const p=i?.confirmEndpoint||e,{data:f,error:y}=await t.post(p,{...i?.params,bucket:n.bucket||i?.params?.bucket,file_path:c,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 i?.onProgress&&i.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,i)=>{try{return{data:await handleSmartUpload(e,t,r,i),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,i)=>{try{return await handleSmartDownload(e,t,r,{...i,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(i,s,o)=>{const n=t();let a,h={};r?(a=s,h=o||{}):(h=s||{},a=void 0);const l=h.token||n.token||(globalTokenProvider?globalTokenProvider():"")||"",c=h.apiKey||n.apiKey;try{return{data:await callApi(l,e,i,a,h.showToasts,h.params,c,n.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,i)=>{try{return{data:await handleSmartUpload(r,t,e,i),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,i)=>{try{return await handleSmartDownload(r,t,e,{...i,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,i,s)=>{const o=t(),n=await async function(t,e,r){let i,s={};"POST"===t||"PUT"===t||"PATCH"===t?(i=e,s=r||{}):(s=e||{},i=void 0);let o=s.token;return!o&&globalTokenProvider&&(o=globalTokenProvider()||""),o=o||"",{token:o,apiKey:s.apiKey,body:i,params:s.params,showToasts:s.showToasts}}(e,i,s),a=n.token||o.token||"",h=n.apiKey||o.apiKey;try{return{data:await callApi(a,e,r,n.body,n.showToasts,n.params,h,o.baseUrl),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Error desconocido",t?.status??500)}}}}class DypaiClient{get baseUrl(){return this.vt}getAuthToken(){return this.auth.token||this.St||null}constructor(t){const{baseUrl:e,apiKey:r}=t;this.vt=e,this.St=r,this.realtime=new DypaiRealtimeClient(e,()=>this.getAuthToken());const i=t.auth?.storageKey||t.storageKey;t.global&&function(t){serviceConfig={...serviceConfig,...t}}(t.global),this.auth=new AuthModule({baseUrl:e,apiKey:r,storageKey:i,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const s=createInternalApiClient(()=>({token:this.auth.token,apiKey:r,baseUrl:e}));if(this.auth.api=s,this.db=new DataModule(s),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)}this.users=new UsersModule(s),this.api=createApiClient(()=>({token:this.auth.token||"",apiKey:r,baseUrl:e})),setTokenProvider(()=>this.auth.token);const o="undefined"!=typeof window?window.location.search:"";if("undefined"!=typeof window&&t.redirects){const e=t.redirects;this.auth.P?.then(()=>{if(this.auth.isPasswordRecoveryCallback&&e.passwordRecovery){if(this.auth.consumeCallbackType(),!window.location.pathname.includes(e.passwordRecovery)){const t=e.passwordRecovery+(o||"");window.location.replace(t)}}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({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.AuthEmailNotVerifiedError=AuthEmailNotVerifiedError,exports.AuthInvalidCredentialsError=AuthInvalidCredentialsError,exports.AuthInvalidTokenError=AuthInvalidTokenError,exports.AuthSessionMissingError=AuthSessionMissingError,exports.AuthUserAlreadyExistsError=AuthUserAlreadyExistsError,exports.AuthWeakPasswordError=AuthWeakPasswordError,exports.DirectDBModule=DirectDBModule,exports.DirectQueryBuilder=DirectQueryBuilder,exports.DypaiClient=DypaiClient,exports.DypaiError=DypaiError,exports.DypaiRealtimeClient=DypaiRealtimeClient,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.RealtimeChannel=RealtimeChannel,exports.RetryableFetchError=RetryableFetchError,exports.callApi=callApi,exports.classifyAuthError=classifyAuthError,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.isAuthEmailNotVerifiedError=function(t){return t instanceof AuthEmailNotVerifiedError},exports.isAuthInvalidCredentialsError=function(t){return t instanceof AuthInvalidCredentialsError},exports.isAuthInvalidTokenError=function(t){return t instanceof AuthInvalidTokenError},exports.isAuthSessionMissingError=function(t){return t instanceof AuthSessionMissingError},exports.isAuthUserAlreadyExistsError=function(t){return t instanceof AuthUserAlreadyExistsError},exports.isAuthWeakPasswordError=function(t){return t instanceof AuthWeakPasswordError},exports.isDypaiError=function(t){return t instanceof DypaiError},exports.isRetryableFetchError=function(t){return t instanceof RetryableFetchError},exports.isRetryableStatus=isRetryableStatus,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.9.0",
3
+ "version": "1.10.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,