@dypai-ai/client-sdk 1.2.0 → 1.4.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 +9 -0
- package/dist/index.d.ts +106 -2
- package/dist/index.esm.js +1 -1
- package/dist/index.js +1 -1
- package/dist/react/index.d.ts +346 -1
- package/dist/react/index.esm.js +1 -1
- package/dist/react/index.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@dypai-ai/client-sdk` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.4.0] - 2026-04-03
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Realtime**: WebSocket client aligned with the DYPAI engine (`/realtime?token=…`).
|
|
10
|
+
- `DypaiClient.realtime` — `DypaiRealtimeClient` with Supabase-style `.channel()` / `.subscribe()` for `postgres_changes`, broadcast, and presence
|
|
11
|
+
- React: `useRealtime`, `useChannel`, `useChannelMessages`, `useChannels` (shared single WebSocket via `realtimeShared`)
|
|
12
|
+
- Exported types: `DypaiRealtimeClient`, `RealtimeChannel`, `RealtimeEvent`, `SubscribeState`, and related channel/presence types
|
|
13
|
+
|
|
5
14
|
## [1.1.0] - 2026-03-27
|
|
6
15
|
|
|
7
16
|
### Added
|
package/dist/index.d.ts
CHANGED
|
@@ -870,6 +870,102 @@ declare class AuthModule {
|
|
|
870
870
|
handleSessionExpired(): void;
|
|
871
871
|
}
|
|
872
872
|
|
|
873
|
+
/**
|
|
874
|
+
* DYPAI Realtime Client — Supabase-compatible API for real-time features.
|
|
875
|
+
*
|
|
876
|
+
* Supports:
|
|
877
|
+
* - Database changes (postgres_changes): INSERT/UPDATE/DELETE notifications
|
|
878
|
+
* - Broadcast: ephemeral client-to-client messaging
|
|
879
|
+
* - Presence: who's online tracking
|
|
880
|
+
*
|
|
881
|
+
* Usage:
|
|
882
|
+
* const channel = dypai.realtime.channel('room-1')
|
|
883
|
+
* channel
|
|
884
|
+
* .on('postgres_changes', { event: '*', table: 'tasks' }, callback)
|
|
885
|
+
* .on('broadcast', { event: 'typing' }, callback)
|
|
886
|
+
* .on('presence', { event: 'sync' }, callback)
|
|
887
|
+
* .subscribe()
|
|
888
|
+
*/
|
|
889
|
+
type RealtimeEvent = 'postgres_changes' | 'broadcast' | 'presence';
|
|
890
|
+
type SubscribeState = 'SUBSCRIBED' | 'TIMED_OUT' | 'CLOSED' | 'CHANNEL_ERROR';
|
|
891
|
+
type PresenceEvent = 'sync' | 'join' | 'leave' | '*';
|
|
892
|
+
type DbEvent = 'INSERT' | 'UPDATE' | 'DELETE' | '*';
|
|
893
|
+
interface DbChangeFilter {
|
|
894
|
+
event: DbEvent;
|
|
895
|
+
schema?: string;
|
|
896
|
+
table?: string;
|
|
897
|
+
filter?: string;
|
|
898
|
+
}
|
|
899
|
+
interface BroadcastFilter {
|
|
900
|
+
event: string;
|
|
901
|
+
}
|
|
902
|
+
interface PresenceFilter {
|
|
903
|
+
event: PresenceEvent;
|
|
904
|
+
}
|
|
905
|
+
interface PresenceEntry {
|
|
906
|
+
[key: string]: any;
|
|
907
|
+
}
|
|
908
|
+
interface ChannelConfig {
|
|
909
|
+
broadcast?: {
|
|
910
|
+
self?: boolean;
|
|
911
|
+
ack?: boolean;
|
|
912
|
+
};
|
|
913
|
+
presence?: {
|
|
914
|
+
key?: string;
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
declare class RealtimeChannel {
|
|
918
|
+
readonly topic: string;
|
|
919
|
+
private ws;
|
|
920
|
+
private bindings;
|
|
921
|
+
private config;
|
|
922
|
+
private client;
|
|
923
|
+
private _presenceState;
|
|
924
|
+
private _subscribed;
|
|
925
|
+
private _subscribedCallback?;
|
|
926
|
+
private _messageHandler?;
|
|
927
|
+
constructor(topic: string, client: DypaiRealtimeClient, config?: ChannelConfig);
|
|
928
|
+
on(type: 'postgres_changes', filter: DbChangeFilter, callback: (payload: any) => void): this;
|
|
929
|
+
on(type: 'broadcast', filter: BroadcastFilter, callback: (payload: any) => void): this;
|
|
930
|
+
on(type: 'presence', filter: PresenceFilter, callback: (payload: any) => void): this;
|
|
931
|
+
subscribe(callback?: (status: SubscribeState, err?: Error) => void): this;
|
|
932
|
+
/** @internal */
|
|
933
|
+
_resubscribe(ws: WebSocket): void;
|
|
934
|
+
send(args: {
|
|
935
|
+
type: 'broadcast';
|
|
936
|
+
event: string;
|
|
937
|
+
payload?: any;
|
|
938
|
+
}): Promise<'ok' | 'timed out' | 'error'>;
|
|
939
|
+
track(payload: Record<string, any>): Promise<'ok' | 'error'>;
|
|
940
|
+
untrack(): Promise<'ok' | 'error'>;
|
|
941
|
+
presenceState(): Record<string, PresenceEntry[]>;
|
|
942
|
+
unsubscribe(): Promise<'ok'>;
|
|
943
|
+
private _handleMessage;
|
|
944
|
+
private _parseFilter;
|
|
945
|
+
}
|
|
946
|
+
declare class DypaiRealtimeClient {
|
|
947
|
+
private baseUrl;
|
|
948
|
+
private tokenProvider;
|
|
949
|
+
private ws;
|
|
950
|
+
private channels;
|
|
951
|
+
private heartbeatTimer;
|
|
952
|
+
private reconnectAttempt;
|
|
953
|
+
private reconnectTimer;
|
|
954
|
+
private static RECONNECT_INTERVALS;
|
|
955
|
+
constructor(baseUrl: string, tokenProvider: () => string | null);
|
|
956
|
+
channel(topic: string, config?: {
|
|
957
|
+
config?: ChannelConfig;
|
|
958
|
+
}): RealtimeChannel;
|
|
959
|
+
getChannels(): RealtimeChannel[];
|
|
960
|
+
removeChannel(channel: RealtimeChannel): Promise<void>;
|
|
961
|
+
removeAllChannels(): Promise<void>;
|
|
962
|
+
/** Update auth token. Reconnects with new token if currently connected. */
|
|
963
|
+
setAuth(token?: string | null): Promise<void>;
|
|
964
|
+
disconnect(): void;
|
|
965
|
+
/** @internal */
|
|
966
|
+
_getOrCreateWs(): WebSocket;
|
|
967
|
+
}
|
|
968
|
+
|
|
873
969
|
interface CreateUserRequest {
|
|
874
970
|
email: string;
|
|
875
971
|
password: string;
|
|
@@ -981,6 +1077,14 @@ declare class DypaiClient<TDatabase = any, TApi extends EndpointMap = EndpointMa
|
|
|
981
1077
|
users: UsersModule;
|
|
982
1078
|
/** Cliente API tipado para endpoints personalizados */
|
|
983
1079
|
api: TApi extends EndpointMap ? TypedApiClient<TApi> : ApiClient;
|
|
1080
|
+
/** Realtime client for database changes, broadcast, and presence */
|
|
1081
|
+
realtime: DypaiRealtimeClient;
|
|
1082
|
+
private _baseUrl;
|
|
1083
|
+
private _apiKey?;
|
|
1084
|
+
/** Get the base URL of the DYPAI engine */
|
|
1085
|
+
get baseUrl(): string;
|
|
1086
|
+
/** Get the current auth token (JWT or API key) for making raw requests */
|
|
1087
|
+
getAuthToken(): string | null;
|
|
984
1088
|
constructor(config: DypaiConfig);
|
|
985
1089
|
/**
|
|
986
1090
|
* Alias para acceder a datos
|
|
@@ -1186,5 +1290,5 @@ declare const PACKAGE_INFO: {
|
|
|
1186
1290
|
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)"];
|
|
1187
1291
|
};
|
|
1188
1292
|
|
|
1189
|
-
export { DirectDBModule, DirectQueryBuilder, DypaiClient, DypaiError, PACKAGE_INFO, callApi, configureApiService, configureDypaiServices, createApiClient, createClient, getGlobalConfig, reloadDypaiConfig, resetGlobalConfig, setToastFunction, setTokenProvider, toast, toastError, toastInfo, toastSuccess, toastWarning, useToast };
|
|
1190
|
-
export type { ApiClient, ApiContextType, ApiOptions, ApiServiceConfig, ApiUploadOptions, AppConfig, AppData, AppRole, AppUserResponse, AuthChangeEvent, AuthResponse, CreateUserRequest, DypaiConfig, EndpointBody, EndpointDefinition, EndpointMap, EndpointParams, EndpointResponse, FilterCondition, HttpMethod, JoinResponse, LoginCredentials, OAuthOptions, PasswordRecoveryRequest, PasswordRecoveryResponse, PasswordResetRequest, PasswordResetResponse, RegisterData, ResetPasswordWithTokenRequest, Session, SessionResponse, ToastFunction, TypedApiClient, TypedGetOptions, TypedMutationOptions, UpdateUserRequest, User, UserData, UserListResponse };
|
|
1293
|
+
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 };
|
|
1294
|
+
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,n){super(t),this.status=e,this.code=i,this.details=n,this.name="DypaiError"}}function e(t){if(!t)return{};const e=t.user||t,i=e.app_metadata||{},n=e.user_metadata||{};return{id:e.id,email:e.email,phone:e.phone,role:i.role||n.role||e.role||null,created_at:e.created_at,updated_at:e.updated_at,confirmed_at:e.confirmed_at,last_sign_in_at:e.last_sign_in_at,app_metadata:i,user_metadata:n,roleDetails:{name:i.role||n.role||null,weight:0},appContext:{app_id:"default"}}}let i=!1;function n(t,...e){"error"===t?console.error(...e):i&&("warn"===t?console.warn(...e):console.log(...e))}async function r(e,i){let n={};try{n=await e.json()}catch{}const r=n.msg||n.error_description||n.message||i,s=n.error_code||n.error||n.code||void 0;return new t(r,e.status,s)}class s{constructor(){this.promise=new Promise((t,e)=>{this.resolve=t,this.reject=e})}}const o={getItem:t=>"undefined"==typeof window?null:window.localStorage.getItem(t),setItem:(t,e)=>{if("undefined"!=typeof window)try{window.localStorage.setItem(t,e)}catch(t){console.error("[DYPAI SDK] ❌ Error crítico guardando en localStorage (¿Quota/Permisos?):",t)}},removeItem:t=>{if("undefined"!=typeof window)try{window.localStorage.removeItem(t)}catch(t){}}};async function a(e){try{return{data:await e,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e.message||"Error de autenticación",e.status||400)}}}class c{constructor(t,e=null){this.config=t,this.t=null,this.i=null,this.o=null,this.h=null,this.l=[],this.u=null,this.p=null,this.m=!1,this.S=null,this.D=null,this.v=0,this.A=0,this.storage=t.storage||o,i=!!t.debug;const r=t.storageKey||this.deriveStorageKey(t.apiKey);n("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${r})`),this.STORAGE_KEY=`dypai-${r}-auth-session`,this.I=this.P().then(()=>{this.i&&(this.getUser().catch(()=>{}),this.startAutoRefresh());const t=this.p;this.p=null,t?setTimeout(()=>{this.m=!0,this.k(t)},0):this.m=!0}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.T.bind(this)),window.addEventListener("focus",this.T.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(n("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.P(!1))}))}async T(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(n("log","[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this.P(!0),this.i&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}get user(){return this.t}get token(){return this.i}get lastError(){return this.u}consumeCallbackType(){try{const t=sessionStorage.getItem("dypai-auth-callback-type");return sessionStorage.removeItem("dypai-auth-callback-type"),sessionStorage.removeItem("dypai-auth-callback-redirect"),t}catch{return null}}get isPasswordRecoveryCallback(){try{return"PASSWORD_RECOVERY"===sessionStorage.getItem("dypai-auth-callback-redirect")}catch{return!1}}isLoggedIn(){return!(!this.i||!this.t)}onAuthStateChange(t){return n("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.l.push(t),this.I.then(()=>{const e=this._();n("log",`[DYPAI SDK] 📣 INITIAL_SESSION para suscriptor (Sesión activa: ${!!e})`),t("INITIAL_SESSION",e)}).catch(e=>{n("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.l=this.l.filter(e=>e!==t)}}}}}async signInWithPassword(t){return a((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=password`,i={email:t.email||t.identifier||"",password:t.password},n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(i)});if(!n.ok)throw await r(n,"Login failed");const s=await n.json(),o={token:s.access_token,refreshToken:s.refresh_token,expiresIn:s.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(s.expires_in||3600),user:this.$(s)};return await this.K(o),o})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return a((async()=>{const i=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,{email:s,phone:o,password:a,user_data:c,...h}=t,l={email:s,phone:o,password:a,data:{...h,...c}};e?.redirectTo?l.redirect_to=e.redirectTo:"undefined"!=typeof window&&(l.redirect_to=`${window.location.origin}/`);const u=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(l)});if(!u.ok)throw await r(u,"Registration failed");const d=await u.json(),p=!d.access_token,f={token:d.access_token,refreshToken:d.refresh_token,expiresIn:d.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(d.expires_in||3600),user:this.$(d),confirmationRequired:p};return f.token?await this.K(f):n("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),f})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this.I,!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.h||0)-t<30&&this.o&&(n("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>n("warn","[DYPAI SDK] Error refreshing session in getSession:",t))),{data:{access_token:this.i,refresh_token:this.o||void 0,token_type:"bearer",user:this.t},error:null}}catch(e){return{data:null,error:new t(e.message,500)}}}async getUser(){return a((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,i=await fetch(e,{headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!i.ok)throw new t("Session invalid",i.status);const n=await i.json(),r=this.$(n);return this.O(r),r})())}async me(){return this.getUser()}async signInWithOAuth(e,i={}){return a((async()=>{if("undefined"==typeof window)throw new t("signInWithOAuth requiere un entorno de navegador (window no está disponible)",400);const{redirectTo:n=window.location.href,scopes:r}=i,s=this.config.baseUrl||"http://localhost:8000",o=r?.length?`&scopes=${encodeURIComponent(r.join(" "))}`:"",a=`${s}/auth/v1/authorize?provider=${e}${o}&redirect_to=${encodeURIComponent(n)}`;window.location.href=a})())}async signOut(){return a((async()=>{try{if(this.i){const t=this.config.baseUrl||"http://localhost:8000";await fetch(`${t}/auth/v1/logout`,{method:"POST",headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}})}}finally{this.R("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",n={email:t};e?.redirectTo?n.redirect_to=e.redirectTo:"undefined"!=typeof window&&(n.redirect_to=`${window.location.origin}/`);const s=await fetch(`${i}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!s.ok)throw await r(s,"Recovery failed");return await s.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t){return a((async()=>{const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/auth/v1/resend`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({email:t,type:"signup"})});if(!i.ok)throw await r(i,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.S)return n("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.S.promise;const e=new s;this.S=e;const i=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const t=await async function(t,e,i){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const n=new AbortController,r=setTimeout(()=>n.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:n.signal},async()=>(clearTimeout(r),await i()))}catch(n){if("AbortError"===n.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await i();throw n}}return await i()}(i,c.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.Y());e.resolve(t)}catch(i){const n=i instanceof t?i:new t(i.message||"Error refrescando sesión",401);e.resolve({data:null,error:n})}finally{this.S=null}})(),e.promise}async Y(){try{const e=await this.storage.getItem(this.STORAGE_KEY);if(e){const t=JSON.parse(e),i=t.expires_at||0,r=Math.floor(Date.now()/1e3);if(t.access_token!==this.i&&i-r>60)return n("log","[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.i=t.access_token,this.o=t.refresh_token,this.h=t.expires_at,this.t=t.user,this.v=0,this.k("TOKEN_REFRESHED"),{data:{token:this.i,refreshToken:this.o||void 0,expiresAt:this.h||void 0,user:this.t},error:null}}if(!this.o)throw new t("No hay refresh token disponible",401);n("log","[DYPAI SDK] 🔄 Iniciando refresco de sesión con Refresh Token...");const i=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=refresh_token`,r=new AbortController,s=setTimeout(()=>r.abort(),c.REFRESH_TIMEOUT_MS);let o;try{o=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({refresh_token:this.o}),signal:r.signal})}finally{clearTimeout(s)}if(!o.ok){let e={};try{e=await o.json()}catch{}const i=o.status,r=e.error||e.code||"",s=(e.error_description||e.msg||"").toLowerCase(),a="invalid_grant"===r||s.includes("invalid refresh token")||s.includes("revoked");throw i>=400&&i<500&&a?(n("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.R(`refreshSession failed (${i}: ${r})`)):n("warn",`[DYPAI SDK] ⚠️ Fallo en refresco (${i}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new t(e.msg||e.error_description||"Refresh session failed",i)}const a=await o.json(),h={token:a.access_token,refreshToken:a.refresh_token,expiresIn:a.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(a.expires_in||3600),user:this.$(a)};return this.v=0,await this.K(h),n("log",`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${h.expiresAt} (en ${h.expiresIn}s). Refresh token actualizado: ${!!h.refreshToken}`),{data:h,error:null}}catch(e){this.v++,this.A=Date.now();const i=e instanceof DOMException&&"AbortError"===e.name,r=i?new t("Refresh token timeout (servidor no responde)",408):e instanceof t?e:new t(e.message||"Error refrescando sesión",401),s=Math.min(c.RETRY_BASE_MS*Math.pow(2,this.v),c.MAX_RETRY_MS);return i?n("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${c.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${s}ms (intento ${this.v})`):n("error","[DYPAI SDK] ❌ Refresh falló:",r.message,`(status: ${r.status}). Backoff: ${s}ms (intento ${this.v})`),this.v>=c.MAX_REFRESH_FAILURES&&(n("error",`[DYPAI SDK] 🗑️ ${this.v} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.R(`${this.v} consecutive refresh failures`),this.v=0),{data:null,error:r}}}async signInWithOtp(e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",n=await fetch(`${i}/auth/v1/otp`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!n.ok){const e=await n.json();throw new t(e.detail||"OTP request failed",n.status)}return await n.json()})())}async verifyOtp(e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",n=await fetch(`${i}/auth/v1/verify`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!n.ok){const e=await n.json();throw new t(e.detail||"OTP verification failed",n.status)}const r=await n.json(),s={token:r.access_token,refreshToken:r.refresh_token,expiresIn:r.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(r.expires_in||3600),user:this.$(r)};return s.token&&await this.K(s),s})())}async updateUser(e){return a((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const i=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,n=await fetch(i,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!n.ok){const e=await n.json();throw new t(e.detail||"Update user failed",n.status)}const r=await n.json(),s=this.$(r);return this.O(s),s})())}async setPassword(t){return this.updateUser({password:t})}startAutoRefresh(){this.stopAutoRefresh(),n("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.U(),this.D=setInterval(()=>this.U(),c.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.D&&(n("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.D),this.D=null)}async U(){try{const t=Math.floor(Date.now()/1e3),e=this.h||0;if(!this.o||!e)return;if(1e3*(e-t)>c.AUTO_REFRESH_TICK_THRESHOLD*c.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.v>0){const t=Math.min(c.RETRY_BASE_MS*Math.pow(2,this.v),c.MAX_RETRY_MS);if(Date.now()-this.A<t)return}n("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){n("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}$(t){return t?e(t):(n("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}async K(t){this.u=null,t.token&&(this.i=t.token,this.o=t.refreshToken||null,this.h=t.expiresAt||null,await this.O(t.user,t.token,t.refreshToken,t.expiresAt),n("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.h}, has_refresh: ${!!this.o})`),this.startAutoRefresh())}async O(t,e,i,r){this.t=t,e&&(this.i=e),void 0!==i&&(this.o=i||null),void 0!==r&&(this.h=r||null);try{if(this.i&&this.t){const t={access_token:this.i,refresh_token:this.o,expires_at:this.h,user:this.t};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t))}else n("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){n("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}if(this.m){const t=e?"SIGNED_IN":"USER_UPDATED";this.k(t)}}async R(t="unknown"){n("log",`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.i=null,this.o=null,this.t=null,this.h=null,this.stopAutoRefresh();try{await this.storage.removeItem(this.STORAGE_KEY),n("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){n("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.k("SIGNED_OUT")}async P(t=!0){n("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.N())return void n("log","[DYPAI SDK] ✅ Sesión establecida desde callback URL. Saltando recuperación de localStorage.");const e=await this.storage.getItem(this.STORAGE_KEY);if(e){let t;n("log","[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");try{t=JSON.parse(e)}catch{return console.warn("[DYPAI SDK] ⚠️ Sesión en storage corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!t||"object"!=typeof t||"string"!=typeof t.access_token)return console.warn("[DYPAI SDK] ⚠️ Sesión en storage con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);this.i=t.access_token,this.o="string"==typeof t.refresh_token?t.refresh_token:null,this.h="number"==typeof t.expires_at?t.expires_at:null,this.t=t.user&&"object"==typeof t.user?t.user:null}else{n("log","[DYPAI SDK] ℹ️ No hay sesión consolidada. Buscando llaves antiguas para migración...");const t=this.STORAGE_KEY.replace("dypai-","").replace("-auth-session",""),e=await this.storage.getItem(`dypai-auth-token-${t}`),i=await this.storage.getItem(`dypai-auth-user-${t}`);if(!e||!i)return void n("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let r;n("log","[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");try{r=JSON.parse(i)}catch{return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage corrupto (JSON inválido). Ignorando migración...")}if(!r||"object"!=typeof r)return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage con formato inválido. Ignorando migración...");this.i=e,this.t=r;const s=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),o=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.o=s,this.h=o?parseInt(o,10):null,await this.O(r,this.i||void 0,this.o||void 0,this.h||void 0);const a=[`dypai-auth-token-${t}`,`dypai-auth-refresh-token-${t}`,`dypai-auth-user-${t}`,`dypai-auth-expires-at-${t}`];for(const t of a)try{await this.storage.removeItem(t)}catch(t){}n("log","[DYPAI SDK] ✨ Migración completada con éxito.")}}const i=Math.floor(Date.now()/1e3),r=this.h&&this.h<=i;if(n("log",`[DYPAI SDK] 🕒 Token expira en: ${this.h}. Ahora es: ${i}. Diferencia: ${(this.h||0)-i}s`),r&&this.o&&t){n("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void n("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.h&&n("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.p="SIGNED_IN"}catch(t){n("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async N(){if("undefined"==typeof window)return!1;try{const t=window.location.hash.substring(1);if(!t)return!1;const e=new URLSearchParams(t);if(e.get("error")){const t=e.get("error_code")||"unknown",i=e.get("error_description")?.replace(/\+/g," ")||"Authentication error";return n("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${i}`),this.u={code:t,message:i},window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.k("AUTH_ERROR"),!0}const i=e.get("access_token");if(!i)return!1;const r=e.get("refresh_token"),s=e.get("type"),o=parseInt(e.get("expires_in")||"3600",10);n("log",`[DYPAI SDK] 🔗 Auth callback detectado en URL (type: ${s||"unknown"}). Procesando...`);try{s&&sessionStorage.setItem("dypai-auth-callback-type",s),"recovery"!==s&&"invite"!==s||sessionStorage.setItem("dypai-auth-callback-redirect","PASSWORD_RECOVERY")}catch{}window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.i=i,this.o=r||null,this.h=Math.floor(Date.now()/1e3)+o;const a=this.config.baseUrl||"http://localhost:8000",c=await fetch(`${a}/auth/v1/user`,{headers:{Authorization:`Bearer ${i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!c.ok)return n("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${c.status}). Limpiando...`),this.i=null,this.o=null,this.h=null,this.u={code:"token_invalid",message:"The authentication link is invalid or has expired"},this.k("AUTH_ERROR"),!0;const h=await c.json(),l=this.$(h),u={token:i,refreshToken:r||void 0,expiresIn:o,expiresAt:this.h,user:l};return await this.K(u),this.p="recovery"===s||"invite"===s?"PASSWORD_RECOVERY":"SIGNED_IN",n("log",`[DYPAI SDK] 🔑 Callback type=${s}. Pending event: ${this.p}`),!0}catch(t){return n("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}_(){return this.i&&this.t?{access_token:this.i,refresh_token:this.o||void 0,expires_at:this.h||void 0,token_type:"bearer",user:this.t}:null}k(t="USER_UPDATED"){const e=this._();n("log",`[DYPAI SDK] 📢 Notificando a ${this.l.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.l.forEach(i=>i(t,e))}handleSessionExpired(){this.R("handleSessionExpired called (likely 401 from API)")}}c.REFRESH_TIMEOUT_MS=15e3,c.MAX_REFRESH_FAILURES=5,c.AUTO_REFRESH_TICK_DURATION_MS=3e4,c.AUTO_REFRESH_TICK_THRESHOLD=3,c.RETRY_BASE_MS=200,c.MAX_RETRY_MS=3e4,c.LOCK_ACQUIRE_TIMEOUT_MS=5e3;class h{get direct(){if(!this.C)throw new Error("client.db.direct is not available. Pass serviceRoleKey to createClient() to enable direct database access.\n\nExample:\n const client = createClient(url, { serviceRoleKey: process.env.DYPAI_SERVICE_ROLE_KEY });\n\nWARNING: serviceRoleKey grants admin-level access. Only use from server-side code (scripts, migrations, seeds). Never expose it in browser/client-side code.");return this.C}set direct(t){this.C=t}constructor(t){this.api=t}from(t){return new l(t,this.api)}}class l{constructor(t,e){this.table=t,this.api=e}async select(t={}){return this.api.get(this.table,{params:t})}async insert(t){return this.api.post(this.table,t)}async update(t,e){return this.api.patch(`${this.table}/${t}`,e)}async delete(t){return this.api.delete(`${this.table}/${t}`)}}const u=1e3;class d{constructor(t){this.api=t}from(t){return new p(t,this.api)}async sql(t,e,i){return this.api.post("/api/v0/admin/db/sql",{query:t,params:e||[],...void 0!==i?.limit&&{limit:i.limit}})}}class p{constructor(t,e){this.table=t,this.api=e,this.L="public",this.j=[],this.q=100,this.M=0,this.J="ASC"}schema(t){return this.L=t,this}eq(t,e){return this.j.push({column:t,operator:"eq",value:e}),this}neq(t,e){return this.j.push({column:t,operator:"neq",value:e}),this}gt(t,e){return this.j.push({column:t,operator:"gt",value:e}),this}gte(t,e){return this.j.push({column:t,operator:"gte",value:e}),this}lt(t,e){return this.j.push({column:t,operator:"lt",value:e}),this}lte(t,e){return this.j.push({column:t,operator:"lte",value:e}),this}like(t,e){return this.j.push({column:t,operator:"ilike",value:e}),this}ilike(t,e){return this.like(t,e)}isNull(t){return this.j.push({column:t,operator:"is_null",value:null}),this}is(t,e){return this.isNull(t)}notNull(t){return this.j.push({column:t,operator:"not_null",value:null}),this}in(t,e){return this.j.push({column:t,operator:"in",value:e}),this}contains(t,e){return this.j.push({column:t,operator:"array_contains",value:e}),this}containedBy(t,e){return this.j.push({column:t,operator:"contained_by",value:e}),this}overlaps(t,e){return this.j.push({column:t,operator:"overlaps",value:e}),this}textSearch(t,e){return this.j.push({column:t,operator:"fts",value:e}),this}phraseSearch(t,e){return this.j.push({column:t,operator:"phfts",value:e}),this}not(t,e,i){return this.j.push({column:t,operator:"not",value:{operator:e,value:i}}),this}or(t){return this.j.push({operator:"or",or:t}),this}limit(t){return this.q=t,this}offset(t){return this.M=t,this}orderBy(t,e="ASC"){return this.B=t,this.J=e,this}async select(t){const e=[...this.j];let i=this.G;if("string"==typeof t)i=t;else if(t&&"object"==typeof t)for(const[i,n]of Object.entries(t))e.push({column:i,operator:"eq",value:n});return this.execute("select",{...i&&{columns:i},filters:e,limit:this.q,offset:this.M,sort_by:this.B,order:this.J})}async single(){const e=await this.select();if(e.error)return e;const i=e.data;return!i||Array.isArray(i)&&0===i.length?{data:null,error:new t("No rows found.",404)}:{data:Array.isArray(i)?i[0]:i,error:null}}async maybeSingle(){const t=await this.select();if(t.error)return t;const e=t.data;return!e||Array.isArray(e)&&0===e.length?{data:null,error:null}:{data:Array.isArray(e)?e[0]:e,error:null}}async count(){const t=await this.execute("select",{columns:"count(*) as count",filters:[...this.j],limit:1,offset:0});if(t.error)return t;const e=t.data;return{data:Array.isArray(e)&&e.length>0?Number(e[0].count):0,error:null}}async insert(t){if(Array.isArray(t)&&t.length>u){const e=[];for(let i=0;i<t.length;i+=u){const n=t.slice(i,i+u),{data:r,error:s}=await this.execute("insert",{data:n,mode:"bulk"});if(s)return{data:null,error:s};r&&e.push(...Array.isArray(r)?r:[r])}return{data:e,error:null}}const e=Array.isArray(t)?"bulk":"single";return this.execute("insert",{data:t,mode:e})}async update(e){return 0===this.j.length?{data:null,error:new t("Update requires at least one filter. Use .eq(), .in(), etc.",400)}:this.execute("update",{data:e,filters:this.j})}async delete(){return 0===this.j.length?{data:null,error:new t("Delete requires at least one filter.",400)}:this.execute("delete",{filters:this.j})}async upsert(t,e="id"){return this.execute("upsert",{data:t,conflict_column:e})}async execute(t,e){return this.api.post("/api/v0/admin/db/query",{operation:t,schema_name:this.L,table_name:this.table,...e})}}class f{constructor(t){this.api=t}async list(t={}){const i=await this.api.get("admin/users",{params:t});return i.data?.users&&(i.data.users=i.data.users.map(e)),i}async create(t){const i=await this.api.post("admin/users",t);return i.data&&(i.data=e(i.data)),i}async update(t,i){const n=await this.api.put(`admin/users/${t}`,i);return n.data&&(n.data=e(n.data)),n}async delete(t){return this.api.delete(`admin/users/${t}`)}}let w=null;function y(t){w=t}const m=t=>{const{title:e,description:i,variant:n="default"}=t,r=`${"error"===n?"❌":"success"===n?"✅":"warning"===n?"⚠️":"info"===n?"ℹ️":"📢"} ${e}${i?`: ${i}`:""}`;"error"===n?console.error(r):"warning"===n&&console.warn(r)};function S(){const t=w||m;return{toast:t,toastSuccess:(e,i)=>t({title:e,description:i,variant:"success"}),toastError:(e,i)=>t({title:e,description:i,variant:"error"}),toastWarning:(e,i)=>t({title:e,description:i,variant:"warning"}),toastInfo:(e,i)=>t({title:e,description:i,variant:"info"})}}const g=t=>(w||m)(t),D=(t,e)=>g({title:t,description:e,variant:"success"}),v=(t,e)=>g({title:t,description:e,variant:"error"}),A=(t,e)=>g({title:t,description:e,variant:"warning"}),I=(t,e)=>g({title:t,description:e,variant:"info"});let P={},k=null;function b(t){k=t}function T(t){P={...P,...t}}const E=new Map;function _(){let t=P;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...P}}catch(t){}return t}async function $(e,i,n,r,s,o,a,c,h){const l=_(),u=c||null;if(!u&&!n.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof n||!n.trim())throw new Error("Endpoint debe ser un string válido");let d;if(n.startsWith("http"))d=n;else{const t=u.replace(/\/+$/,"");d=n.startsWith("/")?t+n:t+"/api/v0/"+n}if(o&&Object.keys(o).length>0){const t=new URLSearchParams,e=(i,n)=>{null!=n&&(Array.isArray(n)?n.forEach((t,n)=>e(`${i}[${n}]`,t)):"object"==typeof n?Object.entries(n).forEach(([t,n])=>e(`${i}[${t}]`,n)):t.append(i,String(n)))};Object.entries(o).forEach(([t,i])=>e(t,i));const i=t.toString();i&&(d+=`?${i}`)}const p="GET"===i?`${i}:${d}:${JSON.stringify(r)}`:null;if(p&&E.has(p))return E.get(p);const f=r instanceof FormData,w={...l.headers||{},...f?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...a&&{"x-api-key":a}},y={method:i,headers:w,credentials:"include"};r&&"GET"!==i&&"DELETE"!==i&&(y.body=f?r:JSON.stringify(r));const m=l.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!m)throw new Error("Fetch no disponible.");const S=(async()=>{const e=_().toast||g,u=(void 0!==s?s:!1!==l.showToasts)&&e;try{const p=await m(d,y);if(!p.ok){let d,f="Error en la petición";try{const t=await p.text();try{const e=JSON.parse(t);d=e,f=e.message||e.msg||e.error_description||e.error||f}catch{t.length<200&&(f=t)}}catch{}if(401===p.status&&!h&&l.onTokenExpired)try{const t=await l.onTokenExpired();if(t)return $(t,i,n,r,s,o,a,c,!0)}catch(t){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",t)}throw u&&e({title:"Error",description:f,variant:"error"}),new t(f,p.status,void 0,d)}!u||"POST"!==i&&"PUT"!==i&&"PATCH"!==i&&"DELETE"!==i||e({title:"Éxito",description:"Operación completada",variant:"success"});const f=p.headers.get("content-type")||"";return f.includes("application/pdf")||f.includes("image/")||f.includes("audio/")||f.includes("video/")||f.includes("application/octet-stream")||f.includes("application/zip")||f.includes("application/vnd.openxmlformats-officedocument")?await p.blob():f.includes("application/json")?await p.json():await p.text()}finally{p&&E.delete(p)}})();return p&&E.set(p,S),S}async function K(t,e,i,n){const r=n?.method||(i?"POST":"GET"),{data:s,error:o}=await("GET"===r?t.get(e,{params:n?.params}):t.post(e,i,{params:n?.params}));if(o)throw o;if(s instanceof Blob){const t=window.URL.createObjectURL(s),e=document.createElement("a");e.href=t,e.download=n?.fileName||"archivo-descargado",document.body.appendChild(e),e.click(),window.URL.revokeObjectURL(t),document.body.removeChild(e)}else if(s&&"object"==typeof s&&("url"in s||"signed_url"in s||"signedUrl"in s)){const t=s.url||s.signed_url||s.signedUrl;if("string"==typeof t){const e=new URL(t).searchParams.get("response-content-disposition");let i;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(i=t[1].replace(/"/g,""))}const r=n?.fileName||i;if(r){const e=document.createElement("a");e.href=t,e.download=r,document.body.appendChild(e),e.click(),document.body.removeChild(e)}else window.open(t,"_blank")}}}async function O(e,i,n,r){const{data:s,error:o}=await e.post(i,{file_path:n.name,content_type:n.type||"application/octet-stream",size_bytes:n.size,confirm:!1,client_upload:!0,...r?.params||{}});if(o)throw o;const a=function(t){if(!t||"object"!=typeof t)return null;const e=t.upload_url||t.uploadUrl||t.url?t:null;if(e?.upload_url)return R(e);const i=[t.data,t.result,t.output,t.payload].filter(Boolean);for(const t of i)if(t&&(t.upload_url||t.uploadUrl||t.url))return R(t);const n=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of n){const e=Array.isArray(t)?t:Object.values(t);for(const t of e)if(t&&(t.upload_url||t.uploadUrl||t.url))return R(t)}return null}(s);if(!a?.upload_url)throw new t("The workflow did not return a valid upload URL (missing storage upload node?)",400);const{upload_url:c,method:h="PUT",headers:l={},file_path:u,storage_path:d}=a;r?.onProgress&&r.onProgress(10);const p=await fetch(c,{method:h,headers:{"Content-Type":n.type||"application/octet-stream",...l},body:n});if(!p.ok)throw new t("Direct upload to cloud storage failed",p.status);r?.onProgress&&r.onProgress(90);const f=r?.confirmEndpoint||i,{data:w,error:y}=await e.post(f,{...r?.params,bucket:a.bucket||r?.params?.bucket,file_path:u,storage_path:d,filename:n.name,content_type:n.type||"application/octet-stream",size_bytes:n.size,confirm:!0,client_upload:!0});if(y)throw y;return r?.onProgress&&r.onProgress(100),w}function R(t){return t&&"object"==typeof t?{...t,upload_url:t.upload_url||t.uploadUrl||t.url,storage_path:t.storage_path||t.storagePath}:null}function Y(e){const i={get:U(e,"GET"),post:U(e,"POST"),put:U(e,"PUT"),patch:U(e,"PATCH"),delete:U(e,"DELETE"),upload:async(e,n,r)=>{try{return{data:await O(i,e,n,r),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Upload failed",e?.status??500)}}},download:async(e,n,r)=>{try{return await K(i,e,n,{...r,method:"POST"}),{data:void 0,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Download failed",e?.status??500)}}}};return i}function U(e,i){const n="POST"===i||"PUT"===i||"PATCH"===i;return async(r,s,o)=>{const a=e();let c,h={};n?(c=s,h=o||{}):(h=s||{},c=void 0);const l=h.token||a.token||(k?k():"")||"",u=h.apiKey||a.apiKey;try{return{data:await $(l,i,r,c,h.showToasts,h.params,u,a.baseUrl),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Error desconocido",e?.status??500)}}}}function N(e){const i=()=>"function"==typeof e?e():e,n={get:C(i,"GET"),post:C(i,"POST"),put:C(i,"PUT"),patch:C(i,"PATCH"),delete:C(i,"DELETE"),upload:async(e,i,r)=>{try{return{data:await O(n,e,i,r),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Upload failed",e?.status??500)}}},download:async(e,i,r)=>{try{return await K(n,e,i,{...r,method:"POST"}),{data:void 0,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Download failed",e?.status??500)}}}};return n}function C(e,i){return async(n,r,s)=>{const o=e(),a=await async function(t,e,i){let n,r={};"POST"===t||"PUT"===t||"PATCH"===t?(n=e,r=i||{}):(r=e||{},n=void 0);let s=r.token;return!s&&k&&(s=k()||""),s=s||"",{token:s,apiKey:r.apiKey,body:n,params:r.params,showToasts:r.showToasts}}(i,r,s),c=a.token||o.token||"",h=a.apiKey||o.apiKey;try{return{data:await $(c,i,n,a.body,a.showToasts,a.params,h,o.baseUrl),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Error desconocido",e?.status??500)}}}}class x{constructor(t){const{baseUrl:e,apiKey:i}=t,n=t.auth?.storageKey||t.storageKey;t.global&&function(t){P={...P,...t}}(t.global),this.auth=new c({baseUrl:e,apiKey:i,storageKey:n,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const r=Y(()=>({token:this.auth.token,apiKey:i,baseUrl:e}));if(this.auth.api=r,this.db=new h(r),t.serviceRoleKey)if("undefined"!=typeof window&&void 0!==window.document)console.error("[DYPAI SDK] ❌ serviceRoleKey detected in a browser environment. This is a security risk — the key grants admin-level database access. db.direct has been disabled. Use serviceRoleKey only from server-side code (Node.js, Bun, Deno, scripts).");else{const i=Y(()=>({token:t.serviceRoleKey,baseUrl:e}));this.db.direct=new d(i)}if(this.users=new f(r),this.api=N(()=>({token:this.auth.token||"",apiKey:i,baseUrl:e})),b(()=>this.auth.token),"undefined"!=typeof window&&t.redirects){const e=t.redirects;this.auth.I?.then(()=>{if(this.auth.isPasswordRecoveryCallback&&e.passwordRecovery)this.auth.consumeCallbackType(),window.location.pathname.includes(e.passwordRecovery)||window.location.replace(e.passwordRecovery);else if(this.auth.lastError&&e.authError)window.location.pathname.includes(e.authError)||window.location.replace(e.authError);else if(this.auth.token&&e.signIn){const t=this.auth.consumeCallbackType();t&&"recovery"!==t&&"invite"!==t&&(window.location.pathname.includes(e.signIn)||window.location.replace(e.signIn))}})}T({onTokenExpired:async()=>{const t=await this.auth.refreshSession();if(t.error)throw t.error;return t.data?.token||null},onUnauthorized:()=>this.auth.handleSessionExpired()})}from(t){return this.db.from(t)}async me(){return this.auth.getUser()}}function L(t,e,i){if(!t)throw new Error("createClient() requiere la URL base");return new x({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?i:e})}let j={};function q(t){j={...j,toast:g,...t},J()}function M(){return{...j}}function J(){try{const{configureApiService:t}=require("../services/ApiService");t(j)}catch(t){}}function z(){j={},J()}async function B(){J()}const G={name:"@dypai-ai/client-sdk",version:"1.1.0",description:"Official JavaScript/TypeScript SDK for DYPAI",features:["Authentication (email, OAuth, OTP)","Database CRUD","Direct database access (service role)","Custom endpoints (typed API)","File upload/download (Smart Upload)","User management (admin)","React hooks (useAuth, useEndpoint, useAction, useUpload)"]};export{d as DirectDBModule,p as DirectQueryBuilder,x as DypaiClient,t as DypaiError,G as PACKAGE_INFO,$ as callApi,T as configureApiService,q as configureDypaiServices,N as createApiClient,L as createClient,M as getGlobalConfig,B as reloadDypaiConfig,z as resetGlobalConfig,y as setToastFunction,b as setTokenProvider,g as toast,v as toastError,I as toastInfo,D as toastSuccess,A as toastWarning,S as useToast};
|
|
1
|
+
class t extends Error{constructor(t,e=500,s,i){super(t),this.status=e,this.code=s,this.details=i,this.name="DypaiError"}}function e(t){if(!t)return{};const e=t.user||t,s=e.app_metadata||{},i=e.user_metadata||{};return{id:e.id,email:e.email,phone:e.phone,role:s.role||i.role||e.role||null,created_at:e.created_at,updated_at:e.updated_at,confirmed_at:e.confirmed_at,last_sign_in_at:e.last_sign_in_at,app_metadata:s,user_metadata:i,roleDetails:{name:s.role||i.role||null,weight:0},appContext:{app_id:"default"}}}let s=!1;function i(t,...e){"error"===t?console.error(...e):s&&("warn"===t?console.warn(...e):console.log(...e))}async function n(e,s){let i={};try{i=await e.json()}catch{}const n=i.msg||i.error_description||i.message||s,r=i.error_code||i.error||i.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=[],this.u=null,this.p=null,this.S=!1,this.m=null,this.v=null,this.D=0,this.A=0,this.storage=t.storage||o,s=!!t.debug;const n=t.storageKey||this.deriveStorageKey(t.apiKey);i("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${n})`),this.STORAGE_KEY=`dypai-${n}-auth-session`,this.k=this.I().then(()=>{this.i&&(this.getUser().catch(()=>{}),this.startAutoRefresh());const t=this.p;this.p=null,t?setTimeout(()=>{this.S=!0,this.P(t)},0):this.S=!0}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this._.bind(this)),window.addEventListener("focus",this._.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(i("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.I(!1))}))}async _(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(i("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"}get user(){return this.t}get token(){return this.i}get lastError(){return this.u}consumeCallbackType(){try{const t=sessionStorage.getItem("dypai-auth-callback-type");return sessionStorage.removeItem("dypai-auth-callback-type"),sessionStorage.removeItem("dypai-auth-callback-redirect"),t}catch{return null}}get isPasswordRecoveryCallback(){try{return"PASSWORD_RECOVERY"===sessionStorage.getItem("dypai-auth-callback-redirect")}catch{return!1}}isLoggedIn(){return!(!this.i||!this.t)}onAuthStateChange(t){return i("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.l.push(t),this.k.then(()=>{const e=this.T();i("log",`[DYPAI SDK] 📣 INITIAL_SESSION para suscriptor (Sesión activa: ${!!e})`),t("INITIAL_SESSION",e)}).catch(e=>{i("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.l=this.l.filter(e=>e!==t)}}}}}async signInWithPassword(t){return a((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=password`,s={email:t.email||t.identifier||"",password:t.password},i=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(s)});if(!i.ok)throw await n(i,"Login failed");const r=await i.json(),o={token:r.access_token,refreshToken:r.refresh_token,expiresIn:r.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(r.expires_in||3600),user:this.$(r)};return await this.O(o),o})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return a((async()=>{const s=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,{email:r,phone:o,password:a,user_data:c,...h}=t,l={email:r,phone:o,password:a,data:{...h,...c}};e?.redirectTo?l.redirect_to=e.redirectTo:"undefined"!=typeof window&&(l.redirect_to=`${window.location.origin}/`);const u=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(l)});if(!u.ok)throw await n(u,"Registration failed");const d=await u.json(),p=!d.access_token,f={token:d.access_token,refreshToken:d.refresh_token,expiresIn:d.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(d.expires_in||3600),user:this.$(d),confirmationRequired:p};return f.token?await this.O(f):i("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),f})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this.k,!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.h||0)-t<30&&this.o&&(i("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>i("warn","[DYPAI SDK] Error refreshing session in getSession:",t))),{data:{access_token:this.i,refresh_token:this.o||void 0,token_type:"bearer",user:this.t},error:null}}catch(e){return{data:null,error:new t(e.message,500)}}}async getUser(){return a((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,s=await fetch(e,{headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!s.ok)throw new t("Session invalid",s.status);const i=await s.json(),n=this.$(i);return this.K(n),n})())}async me(){return this.getUser()}async signInWithOAuth(e,s={}){return a((async()=>{if("undefined"==typeof window)throw new t("signInWithOAuth requiere un entorno de navegador (window no está disponible)",400);const{redirectTo:i=window.location.href,scopes:n}=s,r=this.config.baseUrl||"http://localhost:8000",o=n?.length?`&scopes=${encodeURIComponent(n.join(" "))}`:"",a=`${r}/auth/v1/authorize?provider=${e}${o}&redirect_to=${encodeURIComponent(i)}`;window.location.href=a})())}async signOut(){return a((async()=>{try{if(this.i){const t=this.config.baseUrl||"http://localhost:8000";await fetch(`${t}/auth/v1/logout`,{method:"POST",headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}})}}finally{this.R("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return a((async()=>{const s=this.config.baseUrl||"http://localhost:8000",i={email:t};e?.redirectTo?i.redirect_to=e.redirectTo:"undefined"!=typeof window&&(i.redirect_to=`${window.location.origin}/`);const r=await fetch(`${s}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});if(!r.ok)throw await n(r,"Recovery failed");return await r.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t){return a((async()=>{const e=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${e}/auth/v1/resend`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({email:t,type:"signup"})});if(!s.ok)throw await n(s,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.m)return i("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.m.promise;const e=new r;this.m=e;const s=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const t=await async function(t,e,s){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const i=new AbortController,n=setTimeout(()=>i.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:i.signal},async()=>(clearTimeout(n),await s()))}catch(i){if("AbortError"===i.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await s();throw i}}return await s()}(s,c.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.N());e.resolve(t)}catch(s){const i=s instanceof t?s:new t(s.message||"Error refrescando sesión",401);e.resolve({data:null,error:i})}finally{this.m=null}})(),e.promise}async N(){try{const e=await this.storage.getItem(this.STORAGE_KEY);if(e){const t=JSON.parse(e),s=t.expires_at||0,n=Math.floor(Date.now()/1e3);if(t.access_token!==this.i&&s-n>60)return i("log","[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.i=t.access_token,this.o=t.refresh_token,this.h=t.expires_at,this.t=t.user,this.D=0,this.P("TOKEN_REFRESHED"),{data:{token:this.i,refreshToken:this.o||void 0,expiresAt:this.h||void 0,user:this.t},error:null}}if(!this.o)throw new t("No hay refresh token disponible",401);i("log","[DYPAI SDK] 🔄 Iniciando refresco de sesión con Refresh Token...");const s=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=refresh_token`,n=new AbortController,r=setTimeout(()=>n.abort(),c.REFRESH_TIMEOUT_MS);let o;try{o=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({refresh_token:this.o}),signal:n.signal})}finally{clearTimeout(r)}if(!o.ok){let e={};try{e=await o.json()}catch{}const s=o.status,n=e.error||e.code||"",r=(e.error_description||e.msg||"").toLowerCase(),a="invalid_grant"===n||r.includes("invalid refresh token")||r.includes("revoked");throw s>=400&&s<500&&a?(i("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.R(`refreshSession failed (${s}: ${n})`)):i("warn",`[DYPAI SDK] ⚠️ Fallo en refresco (${s}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new t(e.msg||e.error_description||"Refresh session failed",s)}const a=await o.json(),h={token:a.access_token,refreshToken:a.refresh_token,expiresIn:a.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(a.expires_in||3600),user:this.$(a)};return this.D=0,await this.O(h),i("log",`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${h.expiresAt} (en ${h.expiresIn}s). Refresh token actualizado: ${!!h.refreshToken}`),{data:h,error:null}}catch(e){this.D++,this.A=Date.now();const s=e instanceof DOMException&&"AbortError"===e.name,n=s?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 s?i("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${c.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${r}ms (intento ${this.D})`):i("error","[DYPAI SDK] ❌ Refresh falló:",n.message,`(status: ${n.status}). Backoff: ${r}ms (intento ${this.D})`),this.D>=c.MAX_REFRESH_FAILURES&&(i("error",`[DYPAI SDK] 🗑️ ${this.D} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.R(`${this.D} consecutive refresh failures`),this.D=0),{data:null,error:n}}}async signInWithOtp(e){return a((async()=>{const s=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${s}/auth/v1/otp`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!i.ok){const e=await i.json();throw new t(e.detail||"OTP request failed",i.status)}return await i.json()})())}async verifyOtp(e){return a((async()=>{const s=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${s}/auth/v1/verify`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!i.ok){const e=await i.json();throw new t(e.detail||"OTP verification failed",i.status)}const n=await i.json(),r={token:n.access_token,refreshToken:n.refresh_token,expiresIn:n.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(n.expires_in||3600),user:this.$(n)};return r.token&&await this.O(r),r})())}async updateUser(e){return a((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const s=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,i=await fetch(s,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!i.ok){const e=await i.json();throw new t(e.detail||"Update user failed",i.status)}const n=await i.json(),r=this.$(n);return this.K(r),r})())}async setPassword(t){return this.updateUser({password:t})}startAutoRefresh(){this.stopAutoRefresh(),i("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.Y(),this.v=setInterval(()=>this.Y(),c.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.v&&(i("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.v),this.v=null)}async Y(){try{const t=Math.floor(Date.now()/1e3),e=this.h||0;if(!this.o||!e)return;if(1e3*(e-t)>c.AUTO_REFRESH_TICK_THRESHOLD*c.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.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}i("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){i("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}$(t){return t?e(t):(i("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}async O(t){this.u=null,t.token&&(this.i=t.token,this.o=t.refreshToken||null,this.h=t.expiresAt||null,await this.K(t.user,t.token,t.refreshToken,t.expiresAt),i("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.h}, has_refresh: ${!!this.o})`),this.startAutoRefresh())}async K(t,e,s,n){this.t=t,e&&(this.i=e),void 0!==s&&(this.o=s||null),void 0!==n&&(this.h=n||null);try{if(this.i&&this.t){const t={access_token:this.i,refresh_token:this.o,expires_at:this.h,user:this.t};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t))}else i("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){i("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}if(this.S){const t=e?"SIGNED_IN":"USER_UPDATED";this.P(t)}}async R(t="unknown"){i("log",`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.i=null,this.o=null,this.t=null,this.h=null,this.stopAutoRefresh();try{await this.storage.removeItem(this.STORAGE_KEY),i("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){i("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.P("SIGNED_OUT")}async I(t=!0){i("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.U())return void i("log","[DYPAI SDK] ✅ Sesión establecida desde callback URL. Saltando recuperación de localStorage.");const e=await this.storage.getItem(this.STORAGE_KEY);if(e){let t;i("log","[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");try{t=JSON.parse(e)}catch{return console.warn("[DYPAI SDK] ⚠️ Sesión en storage corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!t||"object"!=typeof t||"string"!=typeof t.access_token)return console.warn("[DYPAI SDK] ⚠️ Sesión en storage con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);this.i=t.access_token,this.o="string"==typeof t.refresh_token?t.refresh_token:null,this.h="number"==typeof t.expires_at?t.expires_at:null,this.t=t.user&&"object"==typeof t.user?t.user:null}else{i("log","[DYPAI SDK] ℹ️ No hay sesión consolidada. Buscando llaves antiguas para migración...");const t=this.STORAGE_KEY.replace("dypai-","").replace("-auth-session",""),e=await this.storage.getItem(`dypai-auth-token-${t}`),s=await this.storage.getItem(`dypai-auth-user-${t}`);if(!e||!s)return void i("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let n;i("log","[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");try{n=JSON.parse(s)}catch{return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage corrupto (JSON inválido). Ignorando migración...")}if(!n||"object"!=typeof n)return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage con formato inválido. Ignorando migración...");this.i=e,this.t=n;const r=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),o=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.o=r,this.h=o?parseInt(o,10):null,await this.K(n,this.i||void 0,this.o||void 0,this.h||void 0);const a=[`dypai-auth-token-${t}`,`dypai-auth-refresh-token-${t}`,`dypai-auth-user-${t}`,`dypai-auth-expires-at-${t}`];for(const t of a)try{await this.storage.removeItem(t)}catch(t){}i("log","[DYPAI SDK] ✨ Migración completada con éxito.")}}const s=Math.floor(Date.now()/1e3),n=this.h&&this.h<=s;if(i("log",`[DYPAI SDK] 🕒 Token expira en: ${this.h}. Ahora es: ${s}. Diferencia: ${(this.h||0)-s}s`),n&&this.o&&t){i("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void i("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.h&&i("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.p="SIGNED_IN"}catch(t){i("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async U(){if("undefined"==typeof window)return!1;try{const t=window.location.hash.substring(1);if(!t)return!1;const e=new URLSearchParams(t);if(e.get("error")){const t=e.get("error_code")||"unknown",s=e.get("error_description")?.replace(/\+/g," ")||"Authentication error";return i("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${s}`),this.u={code:t,message:s},window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.P("AUTH_ERROR"),!0}const s=e.get("access_token");if(!s)return!1;const n=e.get("refresh_token"),r=e.get("type"),o=parseInt(e.get("expires_in")||"3600",10);i("log",`[DYPAI SDK] 🔗 Auth callback detectado en URL (type: ${r||"unknown"}). Procesando...`);try{r&&sessionStorage.setItem("dypai-auth-callback-type",r),"recovery"!==r&&"invite"!==r||sessionStorage.setItem("dypai-auth-callback-redirect","PASSWORD_RECOVERY")}catch{}window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.i=s,this.o=n||null,this.h=Math.floor(Date.now()/1e3)+o;const a=this.config.baseUrl||"http://localhost:8000",c=await fetch(`${a}/auth/v1/user`,{headers:{Authorization:`Bearer ${s}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!c.ok)return i("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${c.status}). Limpiando...`),this.i=null,this.o=null,this.h=null,this.u={code:"token_invalid",message:"The authentication link is invalid or has expired"},this.P("AUTH_ERROR"),!0;const h=await c.json(),l=this.$(h),u={token:s,refreshToken:n||void 0,expiresIn:o,expiresAt:this.h,user:l};return await this.O(u),this.p="recovery"===r||"invite"===r?"PASSWORD_RECOVERY":"SIGNED_IN",i("log",`[DYPAI SDK] 🔑 Callback type=${r}. Pending event: ${this.p}`),!0}catch(t){return i("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}T(){return this.i&&this.t?{access_token:this.i,refresh_token:this.o||void 0,expires_at:this.h||void 0,token_type:"bearer",user:this.t}:null}P(t="USER_UPDATED"){const e=this.T();i("log",`[DYPAI SDK] 📢 Notificando a ${this.l.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.l.forEach(s=>s(t,e))}handleSessionExpired(){this.R("handleSessionExpired called (likely 401 from API)")}}c.REFRESH_TIMEOUT_MS=15e3,c.MAX_REFRESH_FAILURES=5,c.AUTO_REFRESH_TICK_DURATION_MS=3e4,c.AUTO_REFRESH_TICK_THRESHOLD=3,c.RETRY_BASE_MS=200,c.MAX_RETRY_MS=3e4,c.LOCK_ACQUIRE_TIMEOUT_MS=5e3;class h{constructor(t,e,s){this.ws=null,this.bindings=[],this.C={},this.j=!1,this.topic=t,this.client=e,this.config=s||{}}on(t,e,s){return this.bindings.push({type:t,filter:e,callback:s}),this}subscribe(t){this.L=t;const e=this.client.J();this.ws=e;const s=()=>{const s=this.bindings.filter(t=>"postgres_changes"===t.type);for(const t of s)e.send(JSON.stringify({type:"subscribe",table:t.filter.table||"*",event:t.filter.event||"*",filter:t.filter.filter?this.M(t.filter.filter):void 0,schema:t.filter.schema||"public"}));const i=this.bindings.some(t=>"broadcast"===t.type),n=this.bindings.some(t=>"presence"===t.type);(i||n)&&e.send(JSON.stringify({type:"join",channel:this.topic,presence:!!n||void 0})),this.j=!0,t?.("SUBSCRIBED")};e.readyState===WebSocket.OPEN?s():e.readyState===WebSocket.CONNECTING&&e.addEventListener("open",s,{once:!0}),this.q&&e.removeEventListener("message",this.q);const i=t=>{try{const e=JSON.parse("string"==typeof t.data?t.data:"");this.W(e)}catch{}};return this.q=i,e.addEventListener("message",i),e.addEventListener("close",()=>{this.j=!1,t?.("CLOSED")},{once:!0}),e.addEventListener("error",()=>{t?.("CHANNEL_ERROR",new Error("WebSocket error"))},{once:!0}),this}B(t){const e=this.bindings.filter(t=>"postgres_changes"===t.type);for(const s of e)t.send(JSON.stringify({type:"subscribe",table:s.filter.table||"*",event:s.filter.event||"*",filter:s.filter.filter?this.M(s.filter.filter):void 0,schema:s.filter.schema||"public"}));const s=this.bindings.some(t=>"broadcast"===t.type),i=this.bindings.some(t=>"presence"===t.type);(s||i)&&t.send(JSON.stringify({type:"join",channel:this.topic,presence:!!i||void 0})),this.L?.("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.C}}async unsubscribe(){if(this.q&&this.ws&&(this.ws.removeEventListener("message",this.q),this.q=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.j=!1,this.bindings=[],this.C={},"ok"}W(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.C={};for(const[s,i]of Object.entries(t.presences||{})){const t=i[e]||s;this.C[t]||(this.C[t]=[]),this.C[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[s,i]of Object.entries(t.joins)){const t=i[e]||s;this.C[t]||(this.C[t]=[]),this.C[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.C[t]}))}if(t.leaves)for(const[s,i]of Object.entries(t.leaves)){const t=i[e]||s;this.C[t]=(this.C[t]||[]).filter(t=>t.userId!==s),0===this.C[t].length&&delete this.C[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.C[t]||[]}))}for(const t of this.bindings)"presence"!==t.type||"sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"})}}M(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 s=this.channels.get(t);if(s)return s;const i=new h(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.J())}disconnect(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.ws?.close(),this.ws=null}J(){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||"")}`,s=new WebSocket(e);return this.ws=s,s.addEventListener("open",()=>{this.reconnectAttempt=0;for(const t of this.channels.values())t.j&&t.B(s);this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>{s.readyState===WebSocket.OPEN&&s.send(JSON.stringify({type:"heartbeat"}))},25e3)}),s.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.J()},t)}}),s}}l.RECONNECT_INTERVALS=[1e3,2e3,5e3,1e4,3e4];class u{get direct(){if(!this.G)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.G}set direct(t){this.G=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 p=1e3;class f{constructor(t){this.api=t}from(t){return new y(t,this.api)}async sql(t,e,s){return this.api.post("/api/v0/admin/db/sql",{query:t,params:e||[],...void 0!==s?.limit&&{limit:s.limit}})}}class y{constructor(t,e){this.table=t,this.api=e,this.F="public",this.H=[],this.V=100,this.X=0,this.Z="ASC"}schema(t){return this.F=t,this}eq(t,e){return this.H.push({column:t,operator:"eq",value:e}),this}neq(t,e){return this.H.push({column:t,operator:"neq",value:e}),this}gt(t,e){return this.H.push({column:t,operator:"gt",value:e}),this}gte(t,e){return this.H.push({column:t,operator:"gte",value:e}),this}lt(t,e){return this.H.push({column:t,operator:"lt",value:e}),this}lte(t,e){return this.H.push({column:t,operator:"lte",value:e}),this}like(t,e){return this.H.push({column:t,operator:"ilike",value:e}),this}ilike(t,e){return this.like(t,e)}isNull(t){return this.H.push({column:t,operator:"is_null",value:null}),this}is(t,e){return this.isNull(t)}notNull(t){return this.H.push({column:t,operator:"not_null",value:null}),this}in(t,e){return this.H.push({column:t,operator:"in",value:e}),this}contains(t,e){return this.H.push({column:t,operator:"array_contains",value:e}),this}containedBy(t,e){return this.H.push({column:t,operator:"contained_by",value:e}),this}overlaps(t,e){return this.H.push({column:t,operator:"overlaps",value:e}),this}textSearch(t,e){return this.H.push({column:t,operator:"fts",value:e}),this}phraseSearch(t,e){return this.H.push({column:t,operator:"phfts",value:e}),this}not(t,e,s){return this.H.push({column:t,operator:"not",value:{operator:e,value:s}}),this}or(t){return this.H.push({operator:"or",or:t}),this}limit(t){return this.V=t,this}offset(t){return this.X=t,this}orderBy(t,e="ASC"){return this.tt=t,this.Z=e,this}async select(t){const e=[...this.H];let s=this.et;if("string"==typeof t)s=t;else if(t&&"object"==typeof t)for(const[s,i]of Object.entries(t))e.push({column:s,operator:"eq",value:i});return this.execute("select",{...s&&{columns:s},filters:e,limit:this.V,offset:this.X,sort_by:this.tt,order:this.Z})}async single(){const e=await this.select();if(e.error)return e;const s=e.data;return!s||Array.isArray(s)&&0===s.length?{data:null,error:new t("No rows found.",404)}:{data:Array.isArray(s)?s[0]:s,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.H],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>p){const e=[];for(let s=0;s<t.length;s+=p){const i=t.slice(s,s+p),{data:n,error:r}=await this.execute("insert",{data:i,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.H.length?{data:null,error:new t("Update requires at least one filter. Use .eq(), .in(), etc.",400)}:this.execute("update",{data:e,filters:this.H})}async delete(){return 0===this.H.length?{data:null,error:new t("Delete requires at least one filter.",400)}:this.execute("delete",{filters:this.H})}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.F,table_name:this.table,...e})}}class w{constructor(t){this.api=t}async list(t={}){const s=await this.api.get("admin/users",{params:t});return s.data?.users&&(s.data.users=s.data.users.map(e)),s}async create(t){const s=await this.api.post("admin/users",t);return s.data&&(s.data=e(s.data)),s}async update(t,s){const i=await this.api.put(`admin/users/${t}`,s);return i.data&&(i.data=e(i.data)),i}async delete(t){return this.api.delete(`admin/users/${t}`)}}let S=null;function m(t){S=t}const g=t=>{const{title:e,description:s,variant:i="default"}=t,n=`${"error"===i?"❌":"success"===i?"✅":"warning"===i?"⚠️":"info"===i?"ℹ️":"📢"} ${e}${s?`: ${s}`:""}`;"error"===i?console.error(n):"warning"===i&&console.warn(n)};function v(){const t=S||g;return{toast:t,toastSuccess:(e,s)=>t({title:e,description:s,variant:"success"}),toastError:(e,s)=>t({title:e,description:s,variant:"error"}),toastWarning:(e,s)=>t({title:e,description:s,variant:"warning"}),toastInfo:(e,s)=>t({title:e,description:s,variant:"info"})}}const D=t=>(S||g)(t),b=(t,e)=>D({title:t,description:e,variant:"success"}),A=(t,e)=>D({title:t,description:e,variant:"error"}),k=(t,e)=>D({title:t,description:e,variant:"warning"}),I=(t,e)=>D({title:t,description:e,variant:"info"});let P={},_=null;function T(t){_=t}function E(t){P={...P,...t}}const $=new Map;function O(){let t=P;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...P}}catch(t){}return t}async function K(e,s,i,n,r,o,a,c,h){const l=O(),u=c||null;if(!u&&!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 d;if(i.startsWith("http"))d=i;else{const t=u.replace(/\/+$/,"");d=i.startsWith("/")?t+i:t+"/api/v0/"+i}if(o&&Object.keys(o).length>0){const t=new URLSearchParams,e=(s,i)=>{null!=i&&(Array.isArray(i)?i.forEach((t,i)=>e(`${s}[${i}]`,t)):"object"==typeof i?Object.entries(i).forEach(([t,i])=>e(`${s}[${t}]`,i)):t.append(s,String(i)))};Object.entries(o).forEach(([t,s])=>e(t,s));const s=t.toString();s&&(d+=`?${s}`)}const p="GET"===s?`${s}:${d}:${JSON.stringify(n)}`:null;if(p&&$.has(p))return $.get(p);const f=n instanceof FormData,y={...l.headers||{},...f?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...a&&{"x-api-key":a}},w={method:s,headers:y,credentials:"include"};n&&"GET"!==s&&"DELETE"!==s&&(w.body=f?n:JSON.stringify(n));const S=l.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!S)throw new Error("Fetch no disponible.");const m=(async()=>{const e=O().toast||D,u=(void 0!==r?r:!1!==l.showToasts)&&e;try{const p=await S(d,w);if(!p.ok){let d,f="Error en la petición";try{const t=await p.text();try{const e=JSON.parse(t);d=e,f=e.message||e.msg||e.error_description||e.error||f}catch{t.length<200&&(f=t)}}catch{}if(401===p.status&&!h&&l.onTokenExpired)try{const t=await l.onTokenExpired();if(t)return K(t,s,i,n,r,o,a,c,!0)}catch(t){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",t)}throw u&&e({title:"Error",description:f,variant:"error"}),new t(f,p.status,void 0,d)}!u||"POST"!==s&&"PUT"!==s&&"PATCH"!==s&&"DELETE"!==s||e({title:"Éxito",description:"Operación completada",variant:"success"});const f=p.headers.get("content-type")||"";return f.includes("application/pdf")||f.includes("image/")||f.includes("audio/")||f.includes("video/")||f.includes("application/octet-stream")||f.includes("application/zip")||f.includes("application/vnd.openxmlformats-officedocument")?await p.blob():f.includes("application/json")?await p.json():await p.text()}finally{p&&$.delete(p)}})();return p&&$.set(p,m),m}async function R(t,e,s,i){const n=i?.method||(s?"POST":"GET"),{data:r,error:o}=await("GET"===n?t.get(e,{params:i?.params}):t.post(e,s,{params:i?.params}));if(o)throw o;if(r instanceof Blob){const t=window.URL.createObjectURL(r),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(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 s;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(s=t[1].replace(/"/g,""))}const n=i?.fileName||s;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 N(e,s,i,n){const{data:r,error:o}=await e.post(s,{file_path:i.name,content_type:i.type||"application/octet-stream",size_bytes:i.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 Y(e);const s=[t.data,t.result,t.output,t.payload].filter(Boolean);for(const t of s)if(t&&(t.upload_url||t.uploadUrl||t.url))return Y(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 Y(t)}return null}(r);if(!a?.upload_url)throw new t("The workflow did not return a valid upload URL (missing storage upload node?)",400);const{upload_url:c,method:h="PUT",headers:l={},file_path:u,storage_path:d}=a;n?.onProgress&&n.onProgress(10);const p=await fetch(c,{method:h,headers:{"Content-Type":i.type||"application/octet-stream",...l},body:i});if(!p.ok)throw new t("Direct upload to cloud storage failed",p.status);n?.onProgress&&n.onProgress(90);const f=n?.confirmEndpoint||s,{data:y,error:w}=await e.post(f,{...n?.params,bucket:a.bucket||n?.params?.bucket,file_path:u,storage_path:d,filename:i.name,content_type:i.type||"application/octet-stream",size_bytes:i.size,confirm:!0,client_upload:!0});if(w)throw w;return n?.onProgress&&n.onProgress(100),y}function Y(t){return t&&"object"==typeof t?{...t,upload_url:t.upload_url||t.uploadUrl||t.url,storage_path:t.storage_path||t.storagePath}:null}function U(e){const s={get:C(e,"GET"),post:C(e,"POST"),put:C(e,"PUT"),patch:C(e,"PATCH"),delete:C(e,"DELETE"),upload:async(e,i,n)=>{try{return{data:await N(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 R(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 C(e,s){const i="POST"===s||"PUT"===s||"PATCH"===s;return async(n,r,o)=>{const a=e();let c,h={};i?(c=r,h=o||{}):(h=r||{},c=void 0);const l=h.token||a.token||(_?_():"")||"",u=h.apiKey||a.apiKey;try{return{data:await K(l,s,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 s=()=>"function"==typeof e?e():e,i={get:j(s,"GET"),post:j(s,"POST"),put:j(s,"PUT"),patch:j(s,"PATCH"),delete:j(s,"DELETE"),upload:async(e,s,n)=>{try{return{data:await N(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 R(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 j(e,s){return async(i,n,r)=>{const o=e(),a=await async function(t,e,s){let i,n={};"POST"===t||"PUT"===t||"PATCH"===t?(i=e,n=s||{}):(n=e||{},i=void 0);let r=n.token;return!r&&_&&(r=_()||""),r=r||"",{token:r,apiKey:n.apiKey,body:i,params:n.params,showToasts:n.showToasts}}(s,n,r),c=a.token||o.token||"",h=a.apiKey||o.apiKey;try{return{data:await K(c,s,i,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 L{get baseUrl(){return this.st}getAuthToken(){return this.auth.token||this.it||null}constructor(t){const{baseUrl:e,apiKey:s}=t;this.st=e,this.it=s,this.realtime=new l(e,()=>this.getAuthToken());const i=t.auth?.storageKey||t.storageKey;t.global&&function(t){P={...P,...t}}(t.global),this.auth=new c({baseUrl:e,apiKey:s,storageKey:i,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const n=U(()=>({token:this.auth.token,apiKey:s,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 s=U(()=>({token:t.serviceRoleKey,baseUrl:e}));this.db.direct=new f(s)}if(this.users=new w(n),this.api=x(()=>({token:this.auth.token||"",apiKey:s,baseUrl:e})),T(()=>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))}})}E({onTokenExpired:async()=>{const t=await this.auth.refreshSession();if(t.error)throw t.error;return t.data?.token||null},onUnauthorized:()=>this.auth.handleSessionExpired()})}from(t){return this.db.from(t)}async me(){return this.auth.getUser()}}function J(t,e,s){if(!t)throw new Error("createClient() requiere la URL base");return new L({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?s:e})}let M={};function q(t){M={...M,toast:D,...t},B()}function W(){return{...M}}function B(){try{const{configureApiService:t}=require("../services/ApiService");t(M)}catch(t){}}function z(){M={},B()}async function G(){B()}const F={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{f as DirectDBModule,y as DirectQueryBuilder,L as DypaiClient,t as DypaiError,l as DypaiRealtimeClient,F as PACKAGE_INFO,h as RealtimeChannel,K as callApi,E as configureApiService,q as configureDypaiServices,x as createApiClient,J as createClient,W as getGlobalConfig,G as reloadDypaiConfig,z as resetGlobalConfig,m as setToastFunction,T as setTokenProvider,D as toast,A as toastError,I as toastInfo,b as toastSuccess,k as toastWarning,v as useToast};
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";class DypaiError extends Error{constructor(t,e=500,r,o){super(t),this.status=e,this.code=r,this.details=o,this.name="DypaiError"}}function normalizeUser(t){if(!t)return{};const e=t.user||t,r=e.app_metadata||{},o=e.user_metadata||{};return{id:e.id,email:e.email,phone:e.phone,role:r.role||o.role||e.role||null,created_at:e.created_at,updated_at:e.updated_at,confirmed_at:e.confirmed_at,last_sign_in_at:e.last_sign_in_at,app_metadata:r,user_metadata:o,roleDetails:{name:r.role||o.role||null,weight:0},appContext:{app_id:"default"}}}let _debugEnabled=!1;function _log(t,...e){"error"===t?console.error(...e):_debugEnabled&&("warn"===t?console.warn(...e):console.log(...e))}async function parseAuthError(t,e){let r={};try{r=await t.json()}catch{}const o=r.msg||r.error_description||r.message||e,i=r.error_code||r.error||r.code||void 0;return new DypaiError(o,t.status,i)}class Deferred{constructor(){this.promise=new Promise((t,e)=>{this.resolve=t,this.reject=e})}}const localStorageAdapter={getItem:t=>"undefined"==typeof window?null:window.localStorage.getItem(t),setItem:(t,e)=>{if("undefined"!=typeof window)try{window.localStorage.setItem(t,e)}catch(t){console.error("[DYPAI SDK] ❌ Error crítico guardando en localStorage (¿Quota/Permisos?):",t)}},removeItem:t=>{if("undefined"!=typeof window)try{window.localStorage.removeItem(t)}catch(t){}}};async function wrapAuthResponse(t){try{return{data:await t,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t.message||"Error de autenticación",t.status||400)}}}class AuthModule{constructor(t,e=null){this.config=t,this.t=null,this.o=null,this.i=null,this.l=null,this.h=[],this.u=null,this.p=null,this.D=!1,this.m=null,this.A=null,this.S=0,this.v=0,this.storage=t.storage||localStorageAdapter,_debugEnabled=!!t.debug;const r=t.storageKey||this.deriveStorageKey(t.apiKey);_log("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${r})`),this.STORAGE_KEY=`dypai-${r}-auth-session`,this._=this.P().then(()=>{this.o&&(this.getUser().catch(()=>{}),this.startAutoRefresh());const t=this.p;this.p=null,t?setTimeout(()=>{this.D=!0,this.k(t)},0):this.D=!0}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.I.bind(this)),window.addEventListener("focus",this.I.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(_log("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.P(!1))}))}async I(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(_log("log","[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this.P(!0),this.o&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}get user(){return this.t}get token(){return this.o}get lastError(){return this.u}consumeCallbackType(){try{const t=sessionStorage.getItem("dypai-auth-callback-type");return sessionStorage.removeItem("dypai-auth-callback-type"),sessionStorage.removeItem("dypai-auth-callback-redirect"),t}catch{return null}}get isPasswordRecoveryCallback(){try{return"PASSWORD_RECOVERY"===sessionStorage.getItem("dypai-auth-callback-redirect")}catch{return!1}}isLoggedIn(){return!(!this.o||!this.t)}onAuthStateChange(t){return _log("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.h.push(t),this._.then(()=>{const e=this.T();_log("log",`[DYPAI SDK] 📣 INITIAL_SESSION para suscriptor (Sesión activa: ${!!e})`),t("INITIAL_SESSION",e)}).catch(e=>{_log("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.h=this.h.filter(e=>e!==t)}}}}}async signInWithPassword(t){return wrapAuthResponse((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=password`,r={email:t.email||t.identifier||"",password:t.password},o=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(r)});if(!o.ok)throw await parseAuthError(o,"Login failed");const i=await o.json(),n={token:i.access_token,refreshToken:i.refresh_token,expiresIn:i.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(i.expires_in||3600),user:this.$(i)};return await this.C(n),n})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return wrapAuthResponse((async()=>{const r=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,{email:o,phone:i,password:n,user_data:s,...a}=t,l={email:o,phone:i,password:n,data:{...a,...s}};e?.redirectTo?l.redirect_to=e.redirectTo:"undefined"!=typeof window&&(l.redirect_to=`${window.location.origin}/`);const c=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(l)});if(!c.ok)throw await parseAuthError(c,"Registration failed");const h=await c.json(),u=!h.access_token,d={token:h.access_token,refreshToken:h.refresh_token,expiresIn:h.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(h.expires_in||3600),user:this.$(h),confirmationRequired:u};return d.token?await this.C(d):_log("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),d})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this._,!this.o||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.l||0)-t<30&&this.i&&(_log("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>_log("warn","[DYPAI SDK] Error refreshing session in getSession:",t))),{data:{access_token:this.o,refresh_token:this.i||void 0,token_type:"bearer",user:this.t},error:null}}catch(t){return{data:null,error:new DypaiError(t.message,500)}}}async getUser(){return wrapAuthResponse((async()=>{if(!this.o)throw new DypaiError("No hay sesión activa",401);const t=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,e=await fetch(t,{headers:{Authorization:`Bearer ${this.o}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!e.ok)throw new DypaiError("Session invalid",e.status);const r=await e.json(),o=this.$(r);return this.R(o),o})())}async me(){return this.getUser()}async signInWithOAuth(t,e={}){return wrapAuthResponse((async()=>{if("undefined"==typeof window)throw new DypaiError("signInWithOAuth requiere un entorno de navegador (window no está disponible)",400);const{redirectTo:r=window.location.href,scopes:o}=e,i=this.config.baseUrl||"http://localhost:8000",n=o?.length?`&scopes=${encodeURIComponent(o.join(" "))}`:"",s=`${i}/auth/v1/authorize?provider=${t}${n}&redirect_to=${encodeURIComponent(r)}`;window.location.href=s})())}async signOut(){return wrapAuthResponse((async()=>{try{if(this.o){const t=this.config.baseUrl||"http://localhost:8000";await fetch(`${t}/auth/v1/logout`,{method:"POST",headers:{Authorization:`Bearer ${this.o}`,...this.config.apiKey&&{apikey:this.config.apiKey}}})}}finally{this.K("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return wrapAuthResponse((async()=>{const r=this.config.baseUrl||"http://localhost:8000",o={email:t};e?.redirectTo?o.redirect_to=e.redirectTo:"undefined"!=typeof window&&(o.redirect_to=`${window.location.origin}/`);const i=await fetch(`${r}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)});if(!i.ok)throw await parseAuthError(i,"Recovery failed");return await i.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${e}/auth/v1/resend`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({email:t,type:"signup"})});if(!r.ok)throw await parseAuthError(r,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.m)return _log("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.m.promise;const t=new Deferred;this.m=t;const e=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const r=await async function(t,e,r){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const o=new AbortController,i=setTimeout(()=>o.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:o.signal},async()=>(clearTimeout(i),await r()))}catch(o){if("AbortError"===o.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await r();throw o}}return await r()}(e,AuthModule.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.O());t.resolve(r)}catch(e){const r=e instanceof DypaiError?e:new DypaiError(e.message||"Error refrescando sesión",401);t.resolve({data:null,error:r})}finally{this.m=null}})(),t.promise}async O(){try{const t=await this.storage.getItem(this.STORAGE_KEY);if(t){const e=JSON.parse(t),r=e.expires_at||0,o=Math.floor(Date.now()/1e3);if(e.access_token!==this.o&&r-o>60)return _log("log","[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.o=e.access_token,this.i=e.refresh_token,this.l=e.expires_at,this.t=e.user,this.S=0,this.k("TOKEN_REFRESHED"),{data:{token:this.o,refreshToken:this.i||void 0,expiresAt:this.l||void 0,user:this.t},error:null}}if(!this.i)throw new DypaiError("No hay refresh token disponible",401);_log("log","[DYPAI SDK] 🔄 Iniciando refresco de sesión con Refresh Token...");const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=refresh_token`,r=new AbortController,o=setTimeout(()=>r.abort(),AuthModule.REFRESH_TIMEOUT_MS);let i;try{i=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({refresh_token:this.i}),signal:r.signal})}finally{clearTimeout(o)}if(!i.ok){let t={};try{t=await i.json()}catch{}const e=i.status,r=t.error||t.code||"",o=(t.error_description||t.msg||"").toLowerCase(),n="invalid_grant"===r||o.includes("invalid refresh token")||o.includes("revoked");throw e>=400&&e<500&&n?(_log("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.K(`refreshSession failed (${e}: ${r})`)):_log("warn",`[DYPAI SDK] ⚠️ Fallo en refresco (${e}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new DypaiError(t.msg||t.error_description||"Refresh session failed",e)}const n=await i.json(),s={token:n.access_token,refreshToken:n.refresh_token,expiresIn:n.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(n.expires_in||3600),user:this.$(n)};return this.S=0,await this.C(s),_log("log",`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${s.expiresAt} (en ${s.expiresIn}s). Refresh token actualizado: ${!!s.refreshToken}`),{data:s,error:null}}catch(t){this.S++,this.v=Date.now();const e=t instanceof DOMException&&"AbortError"===t.name,r=e?new DypaiError("Refresh token timeout (servidor no responde)",408):t instanceof DypaiError?t:new DypaiError(t.message||"Error refrescando sesión",401),o=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.S),AuthModule.MAX_RETRY_MS);return e?_log("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${AuthModule.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${o}ms (intento ${this.S})`):_log("error","[DYPAI SDK] ❌ Refresh falló:",r.message,`(status: ${r.status}). Backoff: ${o}ms (intento ${this.S})`),this.S>=AuthModule.MAX_REFRESH_FAILURES&&(_log("error",`[DYPAI SDK] 🗑️ ${this.S} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.K(`${this.S} consecutive refresh failures`),this.S=0),{data:null,error:r}}}async signInWithOtp(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${e}/auth/v1/otp`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(t)});if(!r.ok){const t=await r.json();throw new DypaiError(t.detail||"OTP request failed",r.status)}return await r.json()})())}async verifyOtp(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${e}/auth/v1/verify`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(t)});if(!r.ok){const t=await r.json();throw new DypaiError(t.detail||"OTP verification failed",r.status)}const o=await r.json(),i={token:o.access_token,refreshToken:o.refresh_token,expiresIn:o.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(o.expires_in||3600),user:this.$(o)};return i.token&&await this.C(i),i})())}async updateUser(t){return wrapAuthResponse((async()=>{if(!this.o)throw new DypaiError("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,r=await fetch(e,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.o}`,...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(t)});if(!r.ok){const t=await r.json();throw new DypaiError(t.detail||"Update user failed",r.status)}const o=await r.json(),i=this.$(o);return this.R(i),i})())}async setPassword(t){return this.updateUser({password:t})}startAutoRefresh(){this.stopAutoRefresh(),_log("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.U(),this.A=setInterval(()=>this.U(),AuthModule.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.A&&(_log("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.A),this.A=null)}async U(){try{const t=Math.floor(Date.now()/1e3),e=this.l||0;if(!this.i||!e)return;if(1e3*(e-t)>AuthModule.AUTO_REFRESH_TICK_THRESHOLD*AuthModule.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.S>0){const t=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.S),AuthModule.MAX_RETRY_MS);if(Date.now()-this.v<t)return}_log("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){_log("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}$(t){return t?normalizeUser(t):(_log("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}async C(t){this.u=null,t.token&&(this.o=t.token,this.i=t.refreshToken||null,this.l=t.expiresAt||null,await this.R(t.user,t.token,t.refreshToken,t.expiresAt),_log("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.l}, has_refresh: ${!!this.i})`),this.startAutoRefresh())}async R(t,e,r,o){this.t=t,e&&(this.o=e),void 0!==r&&(this.i=r||null),void 0!==o&&(this.l=o||null);try{if(this.o&&this.t){const t={access_token:this.o,refresh_token:this.i,expires_at:this.l,user:this.t};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t))}else _log("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){_log("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}if(this.D){const t=e?"SIGNED_IN":"USER_UPDATED";this.k(t)}}async K(t="unknown"){_log("log",`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.o=null,this.i=null,this.t=null,this.l=null,this.stopAutoRefresh();try{await this.storage.removeItem(this.STORAGE_KEY),_log("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){_log("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.k("SIGNED_OUT")}async P(t=!0){_log("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.Y())return void _log("log","[DYPAI SDK] ✅ Sesión establecida desde callback URL. Saltando recuperación de localStorage.");const e=await this.storage.getItem(this.STORAGE_KEY);if(e){let t;_log("log","[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");try{t=JSON.parse(e)}catch{return console.warn("[DYPAI SDK] ⚠️ Sesión en storage corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!t||"object"!=typeof t||"string"!=typeof t.access_token)return console.warn("[DYPAI SDK] ⚠️ Sesión en storage con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);this.o=t.access_token,this.i="string"==typeof t.refresh_token?t.refresh_token:null,this.l="number"==typeof t.expires_at?t.expires_at:null,this.t=t.user&&"object"==typeof t.user?t.user:null}else{_log("log","[DYPAI SDK] ℹ️ No hay sesión consolidada. Buscando llaves antiguas para migración...");const t=this.STORAGE_KEY.replace("dypai-","").replace("-auth-session",""),e=await this.storage.getItem(`dypai-auth-token-${t}`),r=await this.storage.getItem(`dypai-auth-user-${t}`);if(!e||!r)return void _log("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let o;_log("log","[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");try{o=JSON.parse(r)}catch{return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage corrupto (JSON inválido). Ignorando migración...")}if(!o||"object"!=typeof o)return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage con formato inválido. Ignorando migración...");this.o=e,this.t=o;const i=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),n=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.i=i,this.l=n?parseInt(n,10):null,await this.R(o,this.o||void 0,this.i||void 0,this.l||void 0);const s=[`dypai-auth-token-${t}`,`dypai-auth-refresh-token-${t}`,`dypai-auth-user-${t}`,`dypai-auth-expires-at-${t}`];for(const t of s)try{await this.storage.removeItem(t)}catch(t){}_log("log","[DYPAI SDK] ✨ Migración completada con éxito.")}}const r=Math.floor(Date.now()/1e3),o=this.l&&this.l<=r;if(_log("log",`[DYPAI SDK] 🕒 Token expira en: ${this.l}. Ahora es: ${r}. Diferencia: ${(this.l||0)-r}s`),o&&this.i&&t){_log("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void _log("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.l&&_log("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.p="SIGNED_IN"}catch(t){_log("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async Y(){if("undefined"==typeof window)return!1;try{const t=window.location.hash.substring(1);if(!t)return!1;const e=new URLSearchParams(t);if(e.get("error")){const t=e.get("error_code")||"unknown",r=e.get("error_description")?.replace(/\+/g," ")||"Authentication error";return _log("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${r}`),this.u={code:t,message:r},window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.k("AUTH_ERROR"),!0}const r=e.get("access_token");if(!r)return!1;const o=e.get("refresh_token"),i=e.get("type"),n=parseInt(e.get("expires_in")||"3600",10);_log("log",`[DYPAI SDK] 🔗 Auth callback detectado en URL (type: ${i||"unknown"}). Procesando...`);try{i&&sessionStorage.setItem("dypai-auth-callback-type",i),"recovery"!==i&&"invite"!==i||sessionStorage.setItem("dypai-auth-callback-redirect","PASSWORD_RECOVERY")}catch{}window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.o=r,this.i=o||null,this.l=Math.floor(Date.now()/1e3)+n;const s=this.config.baseUrl||"http://localhost:8000",a=await fetch(`${s}/auth/v1/user`,{headers:{Authorization:`Bearer ${r}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!a.ok)return _log("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${a.status}). Limpiando...`),this.o=null,this.i=null,this.l=null,this.u={code:"token_invalid",message:"The authentication link is invalid or has expired"},this.k("AUTH_ERROR"),!0;const l=await a.json(),c=this.$(l),h={token:r,refreshToken:o||void 0,expiresIn:n,expiresAt:this.l,user:c};return await this.C(h),this.p="recovery"===i||"invite"===i?"PASSWORD_RECOVERY":"SIGNED_IN",_log("log",`[DYPAI SDK] 🔑 Callback type=${i}. Pending event: ${this.p}`),!0}catch(t){return _log("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}T(){return this.o&&this.t?{access_token:this.o,refresh_token:this.i||void 0,expires_at:this.l||void 0,token_type:"bearer",user:this.t}:null}k(t="USER_UPDATED"){const e=this.T();_log("log",`[DYPAI SDK] 📢 Notificando a ${this.h.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.h.forEach(r=>r(t,e))}handleSessionExpired(){this.K("handleSessionExpired called (likely 401 from API)")}}AuthModule.REFRESH_TIMEOUT_MS=15e3,AuthModule.MAX_REFRESH_FAILURES=5,AuthModule.AUTO_REFRESH_TICK_DURATION_MS=3e4,AuthModule.AUTO_REFRESH_TICK_THRESHOLD=3,AuthModule.RETRY_BASE_MS=200,AuthModule.MAX_RETRY_MS=3e4,AuthModule.LOCK_ACQUIRE_TIMEOUT_MS=5e3;class DataModule{get direct(){if(!this.M)throw new Error("client.db.direct is not available. Pass serviceRoleKey to createClient() to enable direct database access.\n\nExample:\n const client = createClient(url, { serviceRoleKey: process.env.DYPAI_SERVICE_ROLE_KEY });\n\nWARNING: serviceRoleKey grants admin-level access. Only use from server-side code (scripts, migrations, seeds). Never expose it in browser/client-side code.");return this.M}set direct(t){this.M=t}constructor(t){this.api=t}from(t){return new QueryBuilder(t,this.api)}}class QueryBuilder{constructor(t,e){this.table=t,this.api=e}async select(t={}){return this.api.get(this.table,{params:t})}async insert(t){return this.api.post(this.table,t)}async update(t,e){return this.api.patch(`${this.table}/${t}`,e)}async delete(t){return this.api.delete(`${this.table}/${t}`)}}class DirectDBModule{constructor(t){this.api=t}from(t){return new DirectQueryBuilder(t,this.api)}async sql(t,e,r){return this.api.post("/api/v0/admin/db/sql",{query:t,params:e||[],...void 0!==r?.limit&&{limit:r.limit}})}}class DirectQueryBuilder{constructor(t,e){this.table=t,this.api=e,this.N="public",this.L=[],this.j=100,this.q=0,this.B="ASC"}schema(t){return this.N=t,this}eq(t,e){return this.L.push({column:t,operator:"eq",value:e}),this}neq(t,e){return this.L.push({column:t,operator:"neq",value:e}),this}gt(t,e){return this.L.push({column:t,operator:"gt",value:e}),this}gte(t,e){return this.L.push({column:t,operator:"gte",value:e}),this}lt(t,e){return this.L.push({column:t,operator:"lt",value:e}),this}lte(t,e){return this.L.push({column:t,operator:"lte",value:e}),this}like(t,e){return this.L.push({column:t,operator:"ilike",value:e}),this}ilike(t,e){return this.like(t,e)}isNull(t){return this.L.push({column:t,operator:"is_null",value:null}),this}is(t,e){return this.isNull(t)}notNull(t){return this.L.push({column:t,operator:"not_null",value:null}),this}in(t,e){return this.L.push({column:t,operator:"in",value:e}),this}contains(t,e){return this.L.push({column:t,operator:"array_contains",value:e}),this}containedBy(t,e){return this.L.push({column:t,operator:"contained_by",value:e}),this}overlaps(t,e){return this.L.push({column:t,operator:"overlaps",value:e}),this}textSearch(t,e){return this.L.push({column:t,operator:"fts",value:e}),this}phraseSearch(t,e){return this.L.push({column:t,operator:"phfts",value:e}),this}not(t,e,r){return this.L.push({column:t,operator:"not",value:{operator:e,value:r}}),this}or(t){return this.L.push({operator:"or",or:t}),this}limit(t){return this.j=t,this}offset(t){return this.q=t,this}orderBy(t,e="ASC"){return this.F=t,this.B=e,this}async select(t){const e=[...this.L];let r=this.J;if("string"==typeof t)r=t;else if(t&&"object"==typeof t)for(const[r,o]of Object.entries(t))e.push({column:r,operator:"eq",value:o});return this.execute("select",{...r&&{columns:r},filters:e,limit:this.j,offset:this.q,sort_by:this.F,order:this.B})}async single(){const t=await this.select();if(t.error)return t;const e=t.data;return!e||Array.isArray(e)&&0===e.length?{data:null,error:new DypaiError("No rows found.",404)}:{data:Array.isArray(e)?e[0]:e,error:null}}async maybeSingle(){const t=await this.select();if(t.error)return t;const e=t.data;return!e||Array.isArray(e)&&0===e.length?{data:null,error:null}:{data:Array.isArray(e)?e[0]:e,error:null}}async count(){const t=await this.execute("select",{columns:"count(*) as count",filters:[...this.L],limit:1,offset:0});if(t.error)return t;const e=t.data;return{data:Array.isArray(e)&&e.length>0?Number(e[0].count):0,error:null}}async insert(t){if(Array.isArray(t)&&t.length>1e3){const e=[];for(let r=0;r<t.length;r+=1e3){const o=t.slice(r,r+1e3),{data:i,error:n}=await this.execute("insert",{data:o,mode:"bulk"});if(n)return{data:null,error:n};i&&e.push(...Array.isArray(i)?i:[i])}return{data:e,error:null}}const e=Array.isArray(t)?"bulk":"single";return this.execute("insert",{data:t,mode:e})}async update(t){return 0===this.L.length?{data:null,error:new DypaiError("Update requires at least one filter. Use .eq(), .in(), etc.",400)}:this.execute("update",{data:t,filters:this.L})}async delete(){return 0===this.L.length?{data:null,error:new DypaiError("Delete requires at least one filter.",400)}:this.execute("delete",{filters:this.L})}async upsert(t,e="id"){return this.execute("upsert",{data:t,conflict_column:e})}async execute(t,e){return this.api.post("/api/v0/admin/db/query",{operation:t,schema_name:this.N,table_name:this.table,...e})}}class UsersModule{constructor(t){this.api=t}async list(t={}){const e=await this.api.get("admin/users",{params:t});return e.data?.users&&(e.data.users=e.data.users.map(normalizeUser)),e}async create(t){const e=await this.api.post("admin/users",t);return e.data&&(e.data=normalizeUser(e.data)),e}async update(t,e){const r=await this.api.put(`admin/users/${t}`,e);return r.data&&(r.data=normalizeUser(r.data)),r}async delete(t){return this.api.delete(`admin/users/${t}`)}}let customToastFunction=null;const fallbackToast=t=>{const{title:e,description:r,variant:o="default"}=t,i=`${"error"===o?"❌":"success"===o?"✅":"warning"===o?"⚠️":"info"===o?"ℹ️":"📢"} ${e}${r?`: ${r}`:""}`;"error"===o?console.error(i):"warning"===o&&console.warn(i)},toast=t=>(customToastFunction||fallbackToast)(t);let serviceConfig={},globalTokenProvider=null;function setTokenProvider(t){globalTokenProvider=t}function configureApiService(t){serviceConfig={...serviceConfig,...t}}const pendingRequests=new Map;function getCompleteConfig(){let t=serviceConfig;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...serviceConfig}}catch(t){}return t}async function callApi(t,e,r,o,i,n,s,a,l){const c=getCompleteConfig(),h=a||null;if(!h&&!r.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof r||!r.trim())throw new Error("Endpoint debe ser un string válido");let u;if(r.startsWith("http"))u=r;else{const t=h.replace(/\/+$/,"");u=r.startsWith("/")?t+r:t+"/api/v0/"+r}if(n&&Object.keys(n).length>0){const t=new URLSearchParams,e=(r,o)=>{null!=o&&(Array.isArray(o)?o.forEach((t,o)=>e(`${r}[${o}]`,t)):"object"==typeof o?Object.entries(o).forEach(([t,o])=>e(`${r}[${t}]`,o)):t.append(r,String(o)))};Object.entries(n).forEach(([t,r])=>e(t,r));const r=t.toString();r&&(u+=`?${r}`)}const d="GET"===e?`${e}:${u}:${JSON.stringify(o)}`:null;if(d&&pendingRequests.has(d))return pendingRequests.get(d);const p=o instanceof FormData,f={...c.headers||{},...p?{}:{"Content-Type":"application/json"},...t&&{Authorization:`Bearer ${t}`},...s&&{"x-api-key":s}},y={method:e,headers:f,credentials:"include"};o&&"GET"!==e&&"DELETE"!==e&&(y.body=p?o:JSON.stringify(o));const w=c.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!w)throw new Error("Fetch no disponible.");const g=(async()=>{const t=getCompleteConfig().toast||toast,h=(void 0!==i?i:!1!==c.showToasts)&&t;try{const d=await w(u,y);if(!d.ok){let u,p="Error en la petición";try{const t=await d.text();try{const e=JSON.parse(t);u=e,p=e.message||e.msg||e.error_description||e.error||p}catch{t.length<200&&(p=t)}}catch{}if(401===d.status&&!l&&c.onTokenExpired)try{const t=await c.onTokenExpired();if(t)return callApi(t,e,r,o,i,n,s,a,!0)}catch(t){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",t)}throw h&&t({title:"Error",description:p,variant:"error"}),new DypaiError(p,d.status,void 0,u)}!h||"POST"!==e&&"PUT"!==e&&"PATCH"!==e&&"DELETE"!==e||t({title:"Éxito",description:"Operación completada",variant:"success"});const p=d.headers.get("content-type")||"";return p.includes("application/pdf")||p.includes("image/")||p.includes("audio/")||p.includes("video/")||p.includes("application/octet-stream")||p.includes("application/zip")||p.includes("application/vnd.openxmlformats-officedocument")?await d.blob():p.includes("application/json")?await d.json():await d.text()}finally{d&&pendingRequests.delete(d)}})();return d&&pendingRequests.set(d,g),g}async function handleSmartDownload(t,e,r,o){const i=o?.method||(r?"POST":"GET"),{data:n,error:s}=await("GET"===i?t.get(e,{params:o?.params}):t.post(e,r,{params:o?.params}));if(s)throw s;if(n instanceof Blob){const t=window.URL.createObjectURL(n),e=document.createElement("a");e.href=t,e.download=o?.fileName||"archivo-descargado",document.body.appendChild(e),e.click(),window.URL.revokeObjectURL(t),document.body.removeChild(e)}else if(n&&"object"==typeof n&&("url"in n||"signed_url"in n||"signedUrl"in n)){const t=n.url||n.signed_url||n.signedUrl;if("string"==typeof t){const e=new URL(t).searchParams.get("response-content-disposition");let r;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(r=t[1].replace(/"/g,""))}const i=o?.fileName||r;if(i){const e=document.createElement("a");e.href=t,e.download=i,document.body.appendChild(e),e.click(),document.body.removeChild(e)}else window.open(t,"_blank")}}}async function handleSmartUpload(t,e,r,o){const{data:i,error:n}=await t.post(e,{file_path:r.name,content_type:r.type||"application/octet-stream",size_bytes:r.size,confirm:!1,client_upload:!0,...o?.params||{}});if(n)throw n;const s=function(t){if(!t||"object"!=typeof t)return null;const e=t.upload_url||t.uploadUrl||t.url?t:null;if(e?.upload_url)return normalizeUploadPayload(e);const r=[t.data,t.result,t.output,t.payload].filter(Boolean);for(const t of r)if(t&&(t.upload_url||t.uploadUrl||t.url))return normalizeUploadPayload(t);const o=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of o){const e=Array.isArray(t)?t:Object.values(t);for(const t of e)if(t&&(t.upload_url||t.uploadUrl||t.url))return normalizeUploadPayload(t)}return null}(i);if(!s?.upload_url)throw new DypaiError("The workflow did not return a valid upload URL (missing storage upload node?)",400);const{upload_url:a,method:l="PUT",headers:c={},file_path:h,storage_path:u}=s;o?.onProgress&&o.onProgress(10);const d=await fetch(a,{method:l,headers:{"Content-Type":r.type||"application/octet-stream",...c},body:r});if(!d.ok)throw new DypaiError("Direct upload to cloud storage failed",d.status);o?.onProgress&&o.onProgress(90);const p=o?.confirmEndpoint||e,{data:f,error:y}=await t.post(p,{...o?.params,bucket:s.bucket||o?.params?.bucket,file_path:h,storage_path:u,filename:r.name,content_type:r.type||"application/octet-stream",size_bytes:r.size,confirm:!0,client_upload:!0});if(y)throw y;return o?.onProgress&&o.onProgress(100),f}function normalizeUploadPayload(t){return t&&"object"==typeof t?{...t,upload_url:t.upload_url||t.uploadUrl||t.url,storage_path:t.storage_path||t.storagePath}:null}function createInternalApiClient(t){const e={get:createMethod(t,"GET"),post:createMethod(t,"POST"),put:createMethod(t,"PUT"),patch:createMethod(t,"PATCH"),delete:createMethod(t,"DELETE"),upload:async(t,r,o)=>{try{return{data:await handleSmartUpload(e,t,r,o),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Upload failed",t?.status??500)}}},download:async(t,r,o)=>{try{return await handleSmartDownload(e,t,r,{...o,method:"POST"}),{data:void 0,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Download failed",t?.status??500)}}}};return e}function createMethod(t,e){const r="POST"===e||"PUT"===e||"PATCH"===e;return async(o,i,n)=>{const s=t();let a,l={};r?(a=i,l=n||{}):(l=i||{},a=void 0);const c=l.token||s.token||(globalTokenProvider?globalTokenProvider():"")||"",h=l.apiKey||s.apiKey;try{return{data:await callApi(c,e,o,a,l.showToasts,l.params,h,s.baseUrl),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Error desconocido",t?.status??500)}}}}function createApiClient(t){const e=()=>"function"==typeof t?t():t,r={get:createMethodFromCtx(e,"GET"),post:createMethodFromCtx(e,"POST"),put:createMethodFromCtx(e,"PUT"),patch:createMethodFromCtx(e,"PATCH"),delete:createMethodFromCtx(e,"DELETE"),upload:async(t,e,o)=>{try{return{data:await handleSmartUpload(r,t,e,o),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Upload failed",t?.status??500)}}},download:async(t,e,o)=>{try{return await handleSmartDownload(r,t,e,{...o,method:"POST"}),{data:void 0,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Download failed",t?.status??500)}}}};return r}function createMethodFromCtx(t,e){return async(r,o,i)=>{const n=t(),s=await async function(t,e,r){let o,i={};"POST"===t||"PUT"===t||"PATCH"===t?(o=e,i=r||{}):(i=e||{},o=void 0);let n=i.token;return!n&&globalTokenProvider&&(n=globalTokenProvider()||""),n=n||"",{token:n,apiKey:i.apiKey,body:o,params:i.params,showToasts:i.showToasts}}(e,o,i),a=s.token||n.token||"",l=s.apiKey||n.apiKey;try{return{data:await callApi(a,e,r,s.body,s.showToasts,s.params,l,n.baseUrl),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Error desconocido",t?.status??500)}}}}class DypaiClient{constructor(t){const{baseUrl:e,apiKey:r}=t,o=t.auth?.storageKey||t.storageKey;t.global&&function(t){serviceConfig={...serviceConfig,...t}}(t.global),this.auth=new AuthModule({baseUrl:e,apiKey:r,storageKey:o,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const i=createInternalApiClient(()=>({token:this.auth.token,apiKey:r,baseUrl:e}));if(this.auth.api=i,this.db=new DataModule(i),t.serviceRoleKey)if("undefined"!=typeof window&&void 0!==window.document)console.error("[DYPAI SDK] ❌ serviceRoleKey detected in a browser environment. This is a security risk — the key grants admin-level database access. db.direct has been disabled. Use serviceRoleKey only from server-side code (Node.js, Bun, Deno, scripts).");else{const r=createInternalApiClient(()=>({token:t.serviceRoleKey,baseUrl:e}));this.db.direct=new DirectDBModule(r)}if(this.users=new UsersModule(i),this.api=createApiClient(()=>({token:this.auth.token||"",apiKey:r,baseUrl:e})),setTokenProvider(()=>this.auth.token),"undefined"!=typeof window&&t.redirects){const e=t.redirects;this.auth._?.then(()=>{if(this.auth.isPasswordRecoveryCallback&&e.passwordRecovery)this.auth.consumeCallbackType(),window.location.pathname.includes(e.passwordRecovery)||window.location.replace(e.passwordRecovery);else if(this.auth.lastError&&e.authError)window.location.pathname.includes(e.authError)||window.location.replace(e.authError);else if(this.auth.token&&e.signIn){const t=this.auth.consumeCallbackType();t&&"recovery"!==t&&"invite"!==t&&(window.location.pathname.includes(e.signIn)||window.location.replace(e.signIn))}})}configureApiService({onTokenExpired:async()=>{const t=await this.auth.refreshSession();if(t.error)throw t.error;return t.data?.token||null},onUnauthorized:()=>this.auth.handleSessionExpired()})}from(t){return this.db.from(t)}async me(){return this.auth.getUser()}}let globalConfig={};function applyConfigToAllServices(){try{const{configureApiService:t}=require("../services/ApiService");t(globalConfig)}catch(t){}}exports.DirectDBModule=DirectDBModule,exports.DirectQueryBuilder=DirectQueryBuilder,exports.DypaiClient=DypaiClient,exports.DypaiError=DypaiError,exports.PACKAGE_INFO={name:"@dypai-ai/client-sdk",version:"1.1.0",description:"Official JavaScript/TypeScript SDK for DYPAI",features:["Authentication (email, OAuth, OTP)","Database CRUD","Direct database access (service role)","Custom endpoints (typed API)","File upload/download (Smart Upload)","User management (admin)","React hooks (useAuth, useEndpoint, useAction, useUpload)"]},exports.callApi=callApi,exports.configureApiService=configureApiService,exports.configureDypaiServices=function(t){globalConfig={...globalConfig,toast:toast,...t},applyConfigToAllServices()},exports.createApiClient=createApiClient,exports.createClient=function(t,e,r){if(!t)throw new Error("createClient() requiere la URL base");return new DypaiClient({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?r:e})},exports.getGlobalConfig=function(){return{...globalConfig}},exports.reloadDypaiConfig=async function(){applyConfigToAllServices()},exports.resetGlobalConfig=function(){globalConfig={},applyConfigToAllServices()},exports.setToastFunction=function(t){customToastFunction=t},exports.setTokenProvider=setTokenProvider,exports.toast=toast,exports.toastError=(t,e)=>toast({title:t,description:e,variant:"error"}),exports.toastInfo=(t,e)=>toast({title:t,description:e,variant:"info"}),exports.toastSuccess=(t,e)=>toast({title:t,description:e,variant:"success"}),exports.toastWarning=(t,e)=>toast({title:t,description:e,variant:"warning"}),exports.useToast=function(){const t=customToastFunction||fallbackToast;return{toast:t,toastSuccess:(e,r)=>t({title:e,description:r,variant:"success"}),toastError:(e,r)=>t({title:e,description:r,variant:"error"}),toastWarning:(e,r)=>t({title:e,description:r,variant:"warning"}),toastInfo:(e,r)=>t({title:e,description:r,variant:"info"})}};
|
|
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.app_metadata||{},i=e.user_metadata||{};return{id:e.id,email:e.email,phone:e.phone,role:r.role||i.role||e.role||null,created_at:e.created_at,updated_at:e.updated_at,confirmed_at:e.confirmed_at,last_sign_in_at:e.last_sign_in_at,app_metadata:r,user_metadata:i,roleDetails:{name:r.role||i.role||null,weight:0},appContext:{app_id:"default"}}}let _debugEnabled=!1;function _log(t,...e){"error"===t?console.error(...e):_debugEnabled&&("warn"===t?console.warn(...e):console.log(...e))}async function parseAuthError(t,e){let r={};try{r=await t.json()}catch{}const i=r.msg||r.error_description||r.message||e,s=r.error_code||r.error||r.code||void 0;return new DypaiError(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.l=null,this.h=[],this.u=null,this.p=null,this.m=!1,this.D=null,this.S=null,this.v=0,this.A=0,this.storage=t.storage||localStorageAdapter,_debugEnabled=!!t.debug;const r=t.storageKey||this.deriveStorageKey(t.apiKey);_log("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${r})`),this.STORAGE_KEY=`dypai-${r}-auth-session`,this._=this.k().then(()=>{this.i&&(this.getUser().catch(()=>{}),this.startAutoRefresh());const t=this.p;this.p=null,t?setTimeout(()=>{this.m=!0,this.I(t)},0):this.m=!0}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.P.bind(this)),window.addEventListener("focus",this.P.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(_log("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.k(!1))}))}async P(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(_log("log","[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this.k(!0),this.i&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}get user(){return this.t}get token(){return this.i}get lastError(){return this.u}consumeCallbackType(){try{const t=sessionStorage.getItem("dypai-auth-callback-type");return sessionStorage.removeItem("dypai-auth-callback-type"),sessionStorage.removeItem("dypai-auth-callback-redirect"),t}catch{return null}}get isPasswordRecoveryCallback(){try{return"PASSWORD_RECOVERY"===sessionStorage.getItem("dypai-auth-callback-redirect")}catch{return!1}}isLoggedIn(){return!(!this.i||!this.t)}onAuthStateChange(t){return _log("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.h.push(t),this._.then(()=>{const e=this.T();_log("log",`[DYPAI SDK] 📣 INITIAL_SESSION para suscriptor (Sesión activa: ${!!e})`),t("INITIAL_SESSION",e)}).catch(e=>{_log("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.h=this.h.filter(e=>e!==t)}}}}}async signInWithPassword(t){return wrapAuthResponse((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=password`,r={email:t.email||t.identifier||"",password:t.password},i=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(r)});if(!i.ok)throw await parseAuthError(i,"Login failed");const s=await i.json(),o={token:s.access_token,refreshToken:s.refresh_token,expiresIn:s.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(s.expires_in||3600),user:this.C(s)};return await this.R(o),o})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return wrapAuthResponse((async()=>{const r=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,{email:i,phone:s,password:o,user_data:n,...a}=t,c={email:i,phone:s,password:o,data:{...a,...n}};e?.redirectTo?c.redirect_to=e.redirectTo:"undefined"!=typeof window&&(c.redirect_to=`${window.location.origin}/`);const l=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(c)});if(!l.ok)throw await parseAuthError(l,"Registration failed");const h=await l.json(),u=!h.access_token,d={token:h.access_token,refreshToken:h.refresh_token,expiresIn:h.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(h.expires_in||3600),user:this.C(h),confirmationRequired:u};return d.token?await this.R(d):_log("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),d})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this._,!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.l||0)-t<30&&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.o||void 0,token_type:"bearer",user:this.t},error:null}}catch(t){return{data:null,error:new DypaiError(t.message,500)}}}async getUser(){return wrapAuthResponse((async()=>{if(!this.i)throw new DypaiError("No hay sesión activa",401);const t=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,e=await fetch(t,{headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!e.ok)throw new DypaiError("Session invalid",e.status);const r=await e.json(),i=this.C(r);return this.$(i),i})())}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=i?.length?`&scopes=${encodeURIComponent(i.join(" "))}`:"",n=`${s}/auth/v1/authorize?provider=${t}${o}&redirect_to=${encodeURIComponent(r)}`;window.location.href=n})())}async signOut(){return wrapAuthResponse((async()=>{try{if(this.i){const t=this.config.baseUrl||"http://localhost:8000";await fetch(`${t}/auth/v1/logout`,{method:"POST",headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}})}}finally{this.O("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.redirect_to=e.redirectTo:"undefined"!=typeof window&&(i.redirect_to=`${window.location.origin}/`);const s=await fetch(`${r}/auth/v1/recover`,{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){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${e}/auth/v1/resend`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({email:t,type:"signup"})});if(!r.ok)throw await parseAuthError(r,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.D)return _log("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.D.promise;const t=new Deferred;this.D=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.U());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.D=null}})(),t.promise}async U(){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.refresh_token,this.l=e.expires_at,this.t=e.user,this.v=0,this.I("TOKEN_REFRESHED"),{data:{token:this.i,refreshToken:this.o||void 0,expiresAt:this.l||void 0,user:this.t},error:null}}if(!this.o)throw new DypaiError("No hay refresh token disponible",401);_log("log","[DYPAI SDK] 🔄 Iniciando refresco de sesión con Refresh Token...");const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=refresh_token`,r=new AbortController,i=setTimeout(()=>r.abort(),AuthModule.REFRESH_TIMEOUT_MS);let s;try{s=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({refresh_token:this.o}),signal:r.signal})}finally{clearTimeout(i)}if(!s.ok){let t={};try{t=await s.json()}catch{}const e=s.status,r=t.error||t.code||"",i=(t.error_description||t.msg||"").toLowerCase(),o="invalid_grant"===r||i.includes("invalid refresh token")||i.includes("revoked");throw e>=400&&e<500&&o?(_log("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.O(`refreshSession failed (${e}: ${r})`)):_log("warn",`[DYPAI SDK] ⚠️ Fallo en refresco (${e}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new DypaiError(t.msg||t.error_description||"Refresh session failed",e)}const o=await s.json(),n={token:o.access_token,refreshToken:o.refresh_token,expiresIn:o.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(o.expires_in||3600),user:this.C(o)};return this.v=0,await this.R(n),_log("log",`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${n.expiresAt} (en ${n.expiresIn}s). Refresh token actualizado: ${!!n.refreshToken}`),{data:n,error:null}}catch(t){this.v++,this.A=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.v),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.v})`):_log("error","[DYPAI SDK] ❌ Refresh falló:",r.message,`(status: ${r.status}). Backoff: ${i}ms (intento ${this.v})`),this.v>=AuthModule.MAX_REFRESH_FAILURES&&(_log("error",`[DYPAI SDK] 🗑️ ${this.v} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.O(`${this.v} consecutive refresh failures`),this.v=0),{data:null,error:r}}}async signInWithOtp(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${e}/auth/v1/otp`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(t)});if(!r.ok){const t=await r.json();throw new DypaiError(t.detail||"OTP request failed",r.status)}return await r.json()})())}async verifyOtp(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${e}/auth/v1/verify`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(t)});if(!r.ok){const t=await r.json();throw new DypaiError(t.detail||"OTP verification failed",r.status)}const i=await r.json(),s={token:i.access_token,refreshToken:i.refresh_token,expiresIn:i.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(i.expires_in||3600),user:this.C(i)};return s.token&&await this.R(s),s})())}async updateUser(t){return wrapAuthResponse((async()=>{if(!this.i)throw new DypaiError("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,r=await fetch(e,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(t)});if(!r.ok){const t=await r.json();throw new DypaiError(t.detail||"Update user failed",r.status)}const i=await r.json(),s=this.C(i);return this.$(s),s})())}async setPassword(t){return this.updateUser({password:t})}startAutoRefresh(){this.stopAutoRefresh(),_log("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.K(),this.S=setInterval(()=>this.K(),AuthModule.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.S&&(_log("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.S),this.S=null)}async K(){try{const t=Math.floor(Date.now()/1e3),e=this.l||0;if(!this.o||!e)return;if(1e3*(e-t)>AuthModule.AUTO_REFRESH_TICK_THRESHOLD*AuthModule.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.v>0){const t=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.v),AuthModule.MAX_RETRY_MS);if(Date.now()-this.A<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)}}C(t){return t?normalizeUser(t):(_log("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}async R(t){this.u=null,t.token&&(this.i=t.token,this.o=t.refreshToken||null,this.l=t.expiresAt||null,await this.$(t.user,t.token,t.refreshToken,t.expiresAt),_log("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.l}, has_refresh: ${!!this.o})`),this.startAutoRefresh())}async $(t,e,r,i){this.t=t,e&&(this.i=e),void 0!==r&&(this.o=r||null),void 0!==i&&(this.l=i||null);try{if(this.i&&this.t){const t={access_token:this.i,refresh_token:this.o,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.m){const t=e?"SIGNED_IN":"USER_UPDATED";this.I(t)}}async O(t="unknown"){_log("log",`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.i=null,this.o=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.I("SIGNED_OUT")}async k(t=!0){_log("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.N())return void _log("log","[DYPAI SDK] ✅ Sesión establecida desde callback URL. Saltando recuperación de localStorage.");const e=await this.storage.getItem(this.STORAGE_KEY);if(e){let t;_log("log","[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");try{t=JSON.parse(e)}catch{return console.warn("[DYPAI SDK] ⚠️ Sesión en storage corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!t||"object"!=typeof t||"string"!=typeof t.access_token)return console.warn("[DYPAI SDK] ⚠️ Sesión en storage con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);this.i=t.access_token,this.o="string"==typeof t.refresh_token?t.refresh_token:null,this.l="number"==typeof t.expires_at?t.expires_at:null,this.t=t.user&&"object"==typeof t.user?t.user:null}else{_log("log","[DYPAI SDK] ℹ️ No hay sesión consolidada. Buscando llaves antiguas para migración...");const t=this.STORAGE_KEY.replace("dypai-","").replace("-auth-session",""),e=await this.storage.getItem(`dypai-auth-token-${t}`),r=await this.storage.getItem(`dypai-auth-user-${t}`);if(!e||!r)return void _log("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let i;_log("log","[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");try{i=JSON.parse(r)}catch{return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage corrupto (JSON inválido). Ignorando migración...")}if(!i||"object"!=typeof i)return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage con formato inválido. Ignorando migración...");this.i=e,this.t=i;const s=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),o=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.o=s,this.l=o?parseInt(o,10):null,await this.$(i,this.i||void 0,this.o||void 0,this.l||void 0);const n=[`dypai-auth-token-${t}`,`dypai-auth-refresh-token-${t}`,`dypai-auth-user-${t}`,`dypai-auth-expires-at-${t}`];for(const t of n)try{await this.storage.removeItem(t)}catch(t){}_log("log","[DYPAI SDK] ✨ Migración completada con éxito.")}}const r=Math.floor(Date.now()/1e3),i=this.l&&this.l<=r;if(_log("log",`[DYPAI SDK] 🕒 Token expira en: ${this.l}. Ahora es: ${r}. Diferencia: ${(this.l||0)-r}s`),i&&this.o&&t){_log("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void _log("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.l&&_log("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.p="SIGNED_IN"}catch(t){_log("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async N(){if("undefined"==typeof window)return!1;try{const t=window.location.hash.substring(1);if(!t)return!1;const e=new URLSearchParams(t);if(e.get("error")){const t=e.get("error_code")||"unknown",r=e.get("error_description")?.replace(/\+/g," ")||"Authentication error";return _log("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${r}`),this.u={code:t,message:r},window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.I("AUTH_ERROR"),!0}const r=e.get("access_token");if(!r)return!1;const i=e.get("refresh_token"),s=e.get("type"),o=parseInt(e.get("expires_in")||"3600",10);_log("log",`[DYPAI SDK] 🔗 Auth callback detectado en URL (type: ${s||"unknown"}). Procesando...`);try{s&&sessionStorage.setItem("dypai-auth-callback-type",s),"recovery"!==s&&"invite"!==s||sessionStorage.setItem("dypai-auth-callback-redirect","PASSWORD_RECOVERY")}catch{}window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.i=r,this.o=i||null,this.l=Math.floor(Date.now()/1e3)+o;const n=this.config.baseUrl||"http://localhost:8000",a=await fetch(`${n}/auth/v1/user`,{headers:{Authorization:`Bearer ${r}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!a.ok)return _log("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${a.status}). Limpiando...`),this.i=null,this.o=null,this.l=null,this.u={code:"token_invalid",message:"The authentication link is invalid or has expired"},this.I("AUTH_ERROR"),!0;const c=await a.json(),l=this.C(c),h={token:r,refreshToken:i||void 0,expiresIn:o,expiresAt:this.l,user:l};return await this.R(h),this.p="recovery"===s||"invite"===s?"PASSWORD_RECOVERY":"SIGNED_IN",_log("log",`[DYPAI SDK] 🔑 Callback type=${s}. Pending event: ${this.p}`),!0}catch(t){return _log("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}T(){return this.i&&this.t?{access_token:this.i,refresh_token:this.o||void 0,expires_at:this.l||void 0,token_type:"bearer",user:this.t}:null}I(t="USER_UPDATED"){const e=this.T();_log("log",`[DYPAI SDK] 📢 Notificando a ${this.h.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.h.forEach(r=>r(t,e))}handleSessionExpired(){this.O("handleSessionExpired called (likely 401 from API)")}}AuthModule.REFRESH_TIMEOUT_MS=15e3,AuthModule.MAX_REFRESH_FAILURES=5,AuthModule.AUTO_REFRESH_TICK_DURATION_MS=3e4,AuthModule.AUTO_REFRESH_TICK_THRESHOLD=3,AuthModule.RETRY_BASE_MS=200,AuthModule.MAX_RETRY_MS=3e4,AuthModule.LOCK_ACQUIRE_TIMEOUT_MS=5e3;class RealtimeChannel{constructor(t,e,r){this.ws=null,this.bindings=[],this.M={},this.Y=!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.j=t;const e=this.client.L();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.J(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.Y=!0,t?.("SUBSCRIBED")};e.readyState===WebSocket.OPEN?r():e.readyState===WebSocket.CONNECTING&&e.addEventListener("open",r,{once:!0}),this.B&&e.removeEventListener("message",this.B);const i=t=>{try{const e=JSON.parse("string"==typeof t.data?t.data:"");this.q(e)}catch{}};return this.B=i,e.addEventListener("message",i),e.addEventListener("close",()=>{this.Y=!1,t?.("CLOSED")},{once:!0}),e.addEventListener("error",()=>{t?.("CHANNEL_ERROR",new Error("WebSocket error"))},{once:!0}),this}W(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.J(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.j?.("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.M}}async unsubscribe(){if(this.B&&this.ws&&(this.ws.removeEventListener("message",this.B),this.B=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.Y=!1,this.bindings=[],this.M={},"ok"}q(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.M={};for(const[r,i]of Object.entries(t.presences||{})){const t=i[e]||r;this.M[t]||(this.M[t]=[]),this.M[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.M[t]||(this.M[t]=[]),this.M[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.M[t]}))}if(t.leaves)for(const[r,i]of Object.entries(t.leaves)){const t=i[e]||r;this.M[t]=(this.M[t]||[]).filter(t=>t.userId!==r),0===this.M[t].length&&delete this.M[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.M[t]||[]}))}for(const t of this.bindings)"presence"!==t.type||"sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"})}}J(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.L())}disconnect(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.ws?.close(),this.ws=null}L(){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.Y&&t.W(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.L()},t)}}),r}}DypaiRealtimeClient.RECONNECT_INTERVALS=[1e3,2e3,5e3,1e4,3e4];class DataModule{get direct(){if(!this.F)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.F}set direct(t){this.F=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.G="public",this.H=[],this.V=100,this.X=0,this.Z="ASC"}schema(t){return this.G=t,this}eq(t,e){return this.H.push({column:t,operator:"eq",value:e}),this}neq(t,e){return this.H.push({column:t,operator:"neq",value:e}),this}gt(t,e){return this.H.push({column:t,operator:"gt",value:e}),this}gte(t,e){return this.H.push({column:t,operator:"gte",value:e}),this}lt(t,e){return this.H.push({column:t,operator:"lt",value:e}),this}lte(t,e){return this.H.push({column:t,operator:"lte",value:e}),this}like(t,e){return this.H.push({column:t,operator:"ilike",value:e}),this}ilike(t,e){return this.like(t,e)}isNull(t){return this.H.push({column:t,operator:"is_null",value:null}),this}is(t,e){return this.isNull(t)}notNull(t){return this.H.push({column:t,operator:"not_null",value:null}),this}in(t,e){return this.H.push({column:t,operator:"in",value:e}),this}contains(t,e){return this.H.push({column:t,operator:"array_contains",value:e}),this}containedBy(t,e){return this.H.push({column:t,operator:"contained_by",value:e}),this}overlaps(t,e){return this.H.push({column:t,operator:"overlaps",value:e}),this}textSearch(t,e){return this.H.push({column:t,operator:"fts",value:e}),this}phraseSearch(t,e){return this.H.push({column:t,operator:"phfts",value:e}),this}not(t,e,r){return this.H.push({column:t,operator:"not",value:{operator:e,value:r}}),this}or(t){return this.H.push({operator:"or",or:t}),this}limit(t){return this.V=t,this}offset(t){return this.X=t,this}orderBy(t,e="ASC"){return this.tt=t,this.Z=e,this}async select(t){const e=[...this.H];let r=this.et;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.V,offset:this.X,sort_by:this.tt,order:this.Z})}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.H],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.H.length?{data:null,error:new DypaiError("Update requires at least one filter. Use .eq(), .in(), etc.",400)}:this.execute("update",{data:t,filters:this.H})}async delete(){return 0===this.H.length?{data:null,error:new DypaiError("Delete requires at least one filter.",400)}:this.execute("delete",{filters:this.H})}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.G,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,c){const l=getCompleteConfig(),h=a||null;if(!h&&!r.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof r||!r.trim())throw new Error("Endpoint debe ser un string válido");let u;if(r.startsWith("http"))u=r;else{const t=h.replace(/\/+$/,"");u=r.startsWith("/")?t+r:t+"/api/v0/"+r}if(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&&(u+=`?${r}`)}const d="GET"===e?`${e}:${u}:${JSON.stringify(i)}`:null;if(d&&pendingRequests.has(d))return pendingRequests.get(d);const p=i instanceof FormData,f={...l.headers||{},...p?{}:{"Content-Type":"application/json"},...t&&{Authorization:`Bearer ${t}`},...n&&{"x-api-key":n}},y={method:e,headers:f,credentials:"include"};i&&"GET"!==e&&"DELETE"!==e&&(y.body=p?i:JSON.stringify(i));const w=l.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!w)throw new Error("Fetch no disponible.");const g=(async()=>{const t=getCompleteConfig().toast||toast,h=(void 0!==s?s:!1!==l.showToasts)&&t;try{const d=await w(u,y);if(!d.ok){let u,p="Error en la petición";try{const t=await d.text();try{const e=JSON.parse(t);u=e,p=e.message||e.msg||e.error_description||e.error||p}catch{t.length<200&&(p=t)}}catch{}if(401===d.status&&!c&&l.onTokenExpired)try{const t=await l.onTokenExpired();if(t)return callApi(t,e,r,i,s,o,n,a,!0)}catch(t){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",t)}throw h&&t({title:"Error",description:p,variant:"error"}),new DypaiError(p,d.status,void 0,u)}!h||"POST"!==e&&"PUT"!==e&&"PATCH"!==e&&"DELETE"!==e||t({title:"Éxito",description:"Operación completada",variant:"success"});const p=d.headers.get("content-type")||"";return p.includes("application/pdf")||p.includes("image/")||p.includes("audio/")||p.includes("video/")||p.includes("application/octet-stream")||p.includes("application/zip")||p.includes("application/vnd.openxmlformats-officedocument")?await d.blob():p.includes("application/json")?await d.json():await d.text()}finally{d&&pendingRequests.delete(d)}})();return d&&pendingRequests.set(d,g),g}async function handleSmartDownload(t,e,r,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:c="PUT",headers:l={},file_path:h,storage_path:u}=n;i?.onProgress&&i.onProgress(10);const d=await fetch(a,{method:c,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:h,storage_path:u,filename:r.name,content_type:r.type||"application/octet-stream",size_bytes:r.size,confirm:!0,client_upload:!0});if(y)throw y;return 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,c={};r?(a=s,c=o||{}):(c=s||{},a=void 0);const l=c.token||n.token||(globalTokenProvider?globalTokenProvider():"")||"",h=c.apiKey||n.apiKey;try{return{data:await callApi(l,e,i,a,c.showToasts,c.params,h,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||"",c=n.apiKey||o.apiKey;try{return{data:await callApi(a,e,r,n.body,n.showToasts,n.params,c,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.rt}getAuthToken(){return this.auth.token||this.it||null}constructor(t){const{baseUrl:e,apiKey:r}=t;this.rt=e,this.it=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)}if(this.users=new UsersModule(s),this.api=createApiClient(()=>({token:this.auth.token||"",apiKey:r,baseUrl:e})),setTokenProvider(()=>this.auth.token),"undefined"!=typeof window&&t.redirects){const e=t.redirects;this.auth._?.then(()=>{if(this.auth.isPasswordRecoveryCallback&&e.passwordRecovery)this.auth.consumeCallbackType(),window.location.pathname.includes(e.passwordRecovery)||window.location.replace(e.passwordRecovery);else if(this.auth.lastError&&e.authError)window.location.pathname.includes(e.authError)||window.location.replace(e.authError);else if(this.auth.token&&e.signIn){const t=this.auth.consumeCallbackType();t&&"recovery"!==t&&"invite"!==t&&(window.location.pathname.includes(e.signIn)||window.location.replace(e.signIn))}})}configureApiService({onTokenExpired:async()=>{const t=await this.auth.refreshSession();if(t.error)throw t.error;return t.data?.token||null},onUnauthorized:()=>this.auth.handleSessionExpired()})}from(t){return this.db.from(t)}async me(){return this.auth.getUser()}}let globalConfig={};function applyConfigToAllServices(){try{const{configureApiService:t}=require("../services/ApiService");t(globalConfig)}catch(t){}}exports.DirectDBModule=DirectDBModule,exports.DirectQueryBuilder=DirectQueryBuilder,exports.DypaiClient=DypaiClient,exports.DypaiError=DypaiError,exports.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,r){if(!t)throw new Error("createClient() requiere la URL base");return new DypaiClient({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?r:e})},exports.getGlobalConfig=function(){return{...globalConfig}},exports.reloadDypaiConfig=async function(){applyConfigToAllServices()},exports.resetGlobalConfig=function(){globalConfig={},applyConfigToAllServices()},exports.setToastFunction=function(t){customToastFunction=t},exports.setTokenProvider=setTokenProvider,exports.toast=toast,exports.toastError=(t,e)=>toast({title:t,description:e,variant:"error"}),exports.toastInfo=(t,e)=>toast({title:t,description:e,variant:"info"}),exports.toastSuccess=(t,e)=>toast({title:t,description:e,variant:"success"}),exports.toastWarning=(t,e)=>toast({title:t,description:e,variant:"warning"}),exports.useToast=function(){const t=customToastFunction||fallbackToast;return{toast:t,toastSuccess:(e,r)=>t({title:e,description:r,variant:"success"}),toastError:(e,r)=>t({title:e,description:r,variant:"error"}),toastWarning:(e,r)=>t({title:e,description:r,variant:"warning"}),toastInfo:(e,r)=>t({title:e,description:r,variant:"info"})}};
|
package/dist/react/index.d.ts
CHANGED
|
@@ -289,6 +289,350 @@ interface UseUploadReturn {
|
|
|
289
289
|
*/
|
|
290
290
|
declare function useUpload(endpoint: string, options?: UseUploadOptions): UseUploadReturn;
|
|
291
291
|
|
|
292
|
+
/** A single content part in a message (Vercel AI SDK compatible format) */
|
|
293
|
+
interface MessagePart {
|
|
294
|
+
type: 'text' | 'tool-call' | 'tool-result' | 'image' | 'file';
|
|
295
|
+
text?: string;
|
|
296
|
+
toolName?: string;
|
|
297
|
+
args?: any;
|
|
298
|
+
result?: any;
|
|
299
|
+
image?: string;
|
|
300
|
+
data?: string;
|
|
301
|
+
mediaType?: string;
|
|
302
|
+
}
|
|
303
|
+
/** A chat message */
|
|
304
|
+
interface ChatMessage {
|
|
305
|
+
id: string;
|
|
306
|
+
role: 'user' | 'assistant' | 'system' | 'tool';
|
|
307
|
+
content: string;
|
|
308
|
+
parts?: MessagePart[];
|
|
309
|
+
metadata?: Record<string, any>;
|
|
310
|
+
createdAt?: Date;
|
|
311
|
+
}
|
|
312
|
+
/** Chat session info */
|
|
313
|
+
interface ChatSession {
|
|
314
|
+
id: string;
|
|
315
|
+
title: string;
|
|
316
|
+
visibility: 'public' | 'private';
|
|
317
|
+
messageCount: number;
|
|
318
|
+
createdAt: string;
|
|
319
|
+
updatedAt: string;
|
|
320
|
+
}
|
|
321
|
+
type ChatStatus = 'ready' | 'submitted' | 'streaming' | 'error';
|
|
322
|
+
/** Options for the useChat hook */
|
|
323
|
+
interface UseChatOptions {
|
|
324
|
+
/** Chat/session ID. Auto-generated if not provided. Persisted in localStorage if persistSession is true. */
|
|
325
|
+
id?: string;
|
|
326
|
+
/** Initial messages to display before loading from server */
|
|
327
|
+
initialMessages?: ChatMessage[];
|
|
328
|
+
/** Load chat history from server on mount (default: true) */
|
|
329
|
+
loadHistory?: boolean;
|
|
330
|
+
/** Extra body fields sent with every message */
|
|
331
|
+
body?: Record<string, any>;
|
|
332
|
+
/** Extra headers sent with every request */
|
|
333
|
+
headers?: Record<string, string>;
|
|
334
|
+
/** Called when the assistant finishes responding */
|
|
335
|
+
onFinish?: (message: ChatMessage, allMessages: ChatMessage[]) => void;
|
|
336
|
+
/** Called on error */
|
|
337
|
+
onError?: (error: DypaiError | Error) => void;
|
|
338
|
+
/** Called when a tool call is received (for client-side tool rendering) */
|
|
339
|
+
onToolCall?: (toolCall: {
|
|
340
|
+
toolName: string;
|
|
341
|
+
args: any;
|
|
342
|
+
}) => void;
|
|
343
|
+
}
|
|
344
|
+
/** Return type of useChat */
|
|
345
|
+
interface UseChatReturn {
|
|
346
|
+
/** All messages in the conversation */
|
|
347
|
+
messages: ChatMessage[];
|
|
348
|
+
/** Current status of the chat */
|
|
349
|
+
status: ChatStatus;
|
|
350
|
+
/** Current input value (controlled) */
|
|
351
|
+
input: string;
|
|
352
|
+
/** Set the input value */
|
|
353
|
+
setInput: (value: string) => void;
|
|
354
|
+
/** Send a message (uses input value if no text provided) */
|
|
355
|
+
sendMessage: (text?: string) => Promise<void>;
|
|
356
|
+
/** Replace all messages */
|
|
357
|
+
setMessages: (messages: ChatMessage[]) => void;
|
|
358
|
+
/** Stop the current streaming response */
|
|
359
|
+
stop: () => void;
|
|
360
|
+
/** Whether the assistant is currently responding */
|
|
361
|
+
isLoading: boolean;
|
|
362
|
+
/** Last error */
|
|
363
|
+
error: DypaiError | Error | null;
|
|
364
|
+
/** The chat/session ID */
|
|
365
|
+
chatId: string;
|
|
366
|
+
/** Start a new chat session (clears messages and generates new ID) */
|
|
367
|
+
newChat: () => void;
|
|
368
|
+
}
|
|
369
|
+
/** Generate a unique chat ID */
|
|
370
|
+
declare function generateChatId(): string;
|
|
371
|
+
/**
|
|
372
|
+
* Convert a ChatMessage to plain text.
|
|
373
|
+
* Extracts text from parts array or falls back to content string.
|
|
374
|
+
*/
|
|
375
|
+
declare function getMessageText(message: ChatMessage): string;
|
|
376
|
+
/**
|
|
377
|
+
* Check if a message contains tool calls (agent is using tools).
|
|
378
|
+
* Useful for showing "Searching..." or "Processing..." indicators.
|
|
379
|
+
*/
|
|
380
|
+
declare function getToolCalls(message: ChatMessage): Array<{
|
|
381
|
+
toolName: string;
|
|
382
|
+
args?: any;
|
|
383
|
+
}>;
|
|
384
|
+
/**
|
|
385
|
+
* React hook for AI chat with streaming, memory, and history.
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
* ```tsx
|
|
389
|
+
* function ChatPage() {
|
|
390
|
+
* const { messages, input, setInput, sendMessage, isLoading } = useChat('support-bot');
|
|
391
|
+
*
|
|
392
|
+
* return (
|
|
393
|
+
* <div>
|
|
394
|
+
* {messages.map(m => <div key={m.id}>{m.role}: {m.content}</div>)}
|
|
395
|
+
* <input value={input} onChange={e => setInput(e.target.value)} />
|
|
396
|
+
* <button onClick={() => sendMessage()} disabled={isLoading}>Send</button>
|
|
397
|
+
* </div>
|
|
398
|
+
* );
|
|
399
|
+
* }
|
|
400
|
+
* ```
|
|
401
|
+
*
|
|
402
|
+
* @example
|
|
403
|
+
* ```tsx
|
|
404
|
+
* // With custom session ID and callbacks
|
|
405
|
+
* const chat = useChat('support-bot', {
|
|
406
|
+
* id: 'session-abc',
|
|
407
|
+
* onFinish: (msg) => console.log('Response:', msg.content),
|
|
408
|
+
* onError: (err) => toast.error(err.message),
|
|
409
|
+
* body: { language: 'es' },
|
|
410
|
+
* });
|
|
411
|
+
* ```
|
|
412
|
+
*/
|
|
413
|
+
declare function useChat(endpoint: string, options?: UseChatOptions): UseChatReturn;
|
|
414
|
+
|
|
415
|
+
interface UseChatListOptions {
|
|
416
|
+
/** Filter by agent endpoint ID */
|
|
417
|
+
endpointId?: string;
|
|
418
|
+
/** Max chats to load (default 20) */
|
|
419
|
+
limit?: number;
|
|
420
|
+
/** Auto-load on mount (default true) */
|
|
421
|
+
enabled?: boolean;
|
|
422
|
+
}
|
|
423
|
+
interface UseChatListReturn {
|
|
424
|
+
/** List of chat sessions */
|
|
425
|
+
chats: ChatSession[];
|
|
426
|
+
/** Whether the list is loading */
|
|
427
|
+
isLoading: boolean;
|
|
428
|
+
/** Last error */
|
|
429
|
+
error: Error | null;
|
|
430
|
+
/** Refresh the list */
|
|
431
|
+
refetch: () => Promise<void>;
|
|
432
|
+
/** Delete a chat */
|
|
433
|
+
deleteChat: (chatId: string) => Promise<void>;
|
|
434
|
+
/** Rename a chat */
|
|
435
|
+
renameChat: (chatId: string, title: string) => Promise<void>;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Hook for listing and managing the user's AI chat sessions.
|
|
439
|
+
*
|
|
440
|
+
* @example
|
|
441
|
+
* ```tsx
|
|
442
|
+
* function ChatList() {
|
|
443
|
+
* const { chats, isLoading, deleteChat, renameChat } = useChatList();
|
|
444
|
+
*
|
|
445
|
+
* return (
|
|
446
|
+
* <ul>
|
|
447
|
+
* {chats.map(chat => (
|
|
448
|
+
* <li key={chat.id}>
|
|
449
|
+
* {chat.title} ({chat.messageCount} messages)
|
|
450
|
+
* <button onClick={() => deleteChat(chat.id)}>Delete</button>
|
|
451
|
+
* </li>
|
|
452
|
+
* ))}
|
|
453
|
+
* </ul>
|
|
454
|
+
* );
|
|
455
|
+
* }
|
|
456
|
+
* ```
|
|
457
|
+
*/
|
|
458
|
+
declare function useChatList(options?: UseChatListOptions): UseChatListReturn;
|
|
459
|
+
|
|
460
|
+
interface RealtimeChange<T = any> {
|
|
461
|
+
table: string;
|
|
462
|
+
event: 'INSERT' | 'UPDATE' | 'DELETE';
|
|
463
|
+
record: T | null;
|
|
464
|
+
old_record: T | null;
|
|
465
|
+
timestamp: string;
|
|
466
|
+
}
|
|
467
|
+
interface UseRealtimeOptions<T = any> {
|
|
468
|
+
/** Events to listen for (default: all) */
|
|
469
|
+
event?: '*' | 'INSERT' | 'UPDATE' | 'DELETE';
|
|
470
|
+
/** Filter changes by field values (e.g., { status: 'active' }) */
|
|
471
|
+
filter?: Record<string, any>;
|
|
472
|
+
/** Called on every change */
|
|
473
|
+
onInsert?: (record: T) => void;
|
|
474
|
+
onUpdate?: (record: T, oldRecord: T) => void;
|
|
475
|
+
onDelete?: (oldRecord: T) => void;
|
|
476
|
+
onChange?: (change: RealtimeChange<T>) => void;
|
|
477
|
+
/** Disable the subscription (default: true) */
|
|
478
|
+
enabled?: boolean;
|
|
479
|
+
}
|
|
480
|
+
interface UseRealtimeReturn<T = any> {
|
|
481
|
+
/** Latest changes received */
|
|
482
|
+
changes: RealtimeChange<T>[];
|
|
483
|
+
/** Whether the WebSocket is connected */
|
|
484
|
+
isConnected: boolean;
|
|
485
|
+
/** Connection status */
|
|
486
|
+
status: 'connecting' | 'connected' | 'disconnected' | 'error';
|
|
487
|
+
/** Last error */
|
|
488
|
+
error: Error | null;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Subscribe to real-time database changes for a table.
|
|
492
|
+
*
|
|
493
|
+
* @example
|
|
494
|
+
* ```tsx
|
|
495
|
+
* function TaskList() {
|
|
496
|
+
* const { data: tasks, refetch } = useEndpoint('list_tasks');
|
|
497
|
+
* useRealtime('tasks', {
|
|
498
|
+
* onInsert: () => refetch(),
|
|
499
|
+
* onUpdate: () => refetch(),
|
|
500
|
+
* onDelete: () => refetch(),
|
|
501
|
+
* });
|
|
502
|
+
* return <ul>{tasks?.map(t => <li key={t.id}>{t.title}</li>)}</ul>;
|
|
503
|
+
* }
|
|
504
|
+
* ```
|
|
505
|
+
*/
|
|
506
|
+
declare function useRealtime<T = any>(table: string, options?: UseRealtimeOptions<T>): UseRealtimeReturn<T>;
|
|
507
|
+
|
|
508
|
+
interface PresenceEntry {
|
|
509
|
+
userId: string;
|
|
510
|
+
data?: Record<string, any>;
|
|
511
|
+
joinedAt: string;
|
|
512
|
+
}
|
|
513
|
+
interface BroadcastMessage<T = any> {
|
|
514
|
+
event: string;
|
|
515
|
+
payload: T;
|
|
516
|
+
sender: string;
|
|
517
|
+
timestamp: string;
|
|
518
|
+
}
|
|
519
|
+
interface UseChannelOptions {
|
|
520
|
+
presence?: boolean | Record<string, any>;
|
|
521
|
+
onBroadcast?: (message: BroadcastMessage) => void;
|
|
522
|
+
onJoin?: (userId: string, data: PresenceEntry) => void;
|
|
523
|
+
onLeave?: (userId: string, data: PresenceEntry) => void;
|
|
524
|
+
enabled?: boolean;
|
|
525
|
+
}
|
|
526
|
+
interface UseChannelReturn {
|
|
527
|
+
broadcast: (event: string, payload?: any) => void;
|
|
528
|
+
presences: Record<string, PresenceEntry>;
|
|
529
|
+
isConnected: boolean;
|
|
530
|
+
memberCount: number;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Join a realtime channel for broadcast messages and presence tracking.
|
|
534
|
+
* Uses the same shared WebSocket as useRealtime.
|
|
535
|
+
*
|
|
536
|
+
* @example
|
|
537
|
+
* ```tsx
|
|
538
|
+
* function Editor() {
|
|
539
|
+
* const { presences, broadcast, memberCount } = useChannel('doc:abc', {
|
|
540
|
+
* presence: { cursor: { x: 0, y: 0 } },
|
|
541
|
+
* onBroadcast: (msg) => {
|
|
542
|
+
* if (msg.event === 'cursor_move') updateCursor(msg.sender, msg.payload);
|
|
543
|
+
* },
|
|
544
|
+
* });
|
|
545
|
+
*
|
|
546
|
+
* return <span>{memberCount} online</span>;
|
|
547
|
+
* }
|
|
548
|
+
* ```
|
|
549
|
+
*/
|
|
550
|
+
declare function useChannel(channel: string, options?: UseChannelOptions): UseChannelReturn;
|
|
551
|
+
|
|
552
|
+
interface ChannelMessage {
|
|
553
|
+
id: string;
|
|
554
|
+
channel_id: string;
|
|
555
|
+
user_id: string;
|
|
556
|
+
sender_email?: string;
|
|
557
|
+
content: string;
|
|
558
|
+
metadata?: Record<string, any>;
|
|
559
|
+
created_at: string;
|
|
560
|
+
}
|
|
561
|
+
interface Channel {
|
|
562
|
+
id: string;
|
|
563
|
+
name: string;
|
|
564
|
+
type: string;
|
|
565
|
+
member_count: number;
|
|
566
|
+
message_count: number;
|
|
567
|
+
created_at: string;
|
|
568
|
+
updated_at: string;
|
|
569
|
+
}
|
|
570
|
+
interface UseChannelMessagesOptions {
|
|
571
|
+
/** Load history on mount (default: true) */
|
|
572
|
+
loadHistory?: boolean;
|
|
573
|
+
/** Max messages to load (default: 50) */
|
|
574
|
+
limit?: number;
|
|
575
|
+
}
|
|
576
|
+
interface UseChannelMessagesReturn {
|
|
577
|
+
/** All messages (history + real-time) */
|
|
578
|
+
messages: ChannelMessage[];
|
|
579
|
+
/** Send a message to the channel */
|
|
580
|
+
send: (content: string, metadata?: Record<string, any>) => Promise<void>;
|
|
581
|
+
/** Whether messages are loading */
|
|
582
|
+
isLoading: boolean;
|
|
583
|
+
/** Whether connected to realtime */
|
|
584
|
+
isConnected: boolean;
|
|
585
|
+
/** Error */
|
|
586
|
+
error: Error | null;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Persistent channel messaging with real-time updates.
|
|
590
|
+
* Messages are stored in the database and broadcast via WebSocket.
|
|
591
|
+
*
|
|
592
|
+
* @example
|
|
593
|
+
* ```tsx
|
|
594
|
+
* function TeamChat() {
|
|
595
|
+
* const { messages, send, isLoading } = useChannelMessages('ch_abc123');
|
|
596
|
+
*
|
|
597
|
+
* return (
|
|
598
|
+
* <div>
|
|
599
|
+
* {messages.map(m => (
|
|
600
|
+
* <div key={m.id}>
|
|
601
|
+
* <b>{m.sender_email}</b>: {m.content}
|
|
602
|
+
* </div>
|
|
603
|
+
* ))}
|
|
604
|
+
* <button onClick={() => send('Hello!')}>Send</button>
|
|
605
|
+
* </div>
|
|
606
|
+
* );
|
|
607
|
+
* }
|
|
608
|
+
* ```
|
|
609
|
+
*/
|
|
610
|
+
declare function useChannelMessages(channelId: string, options?: UseChannelMessagesOptions): UseChannelMessagesReturn;
|
|
611
|
+
interface UseChannelsReturn {
|
|
612
|
+
channels: Channel[];
|
|
613
|
+
isLoading: boolean;
|
|
614
|
+
error: Error | null;
|
|
615
|
+
createChannel: (name: string, members?: string[], type?: string) => Promise<Channel>;
|
|
616
|
+
refetch: () => Promise<void>;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* List and manage the user's channels.
|
|
620
|
+
*
|
|
621
|
+
* @example
|
|
622
|
+
* ```tsx
|
|
623
|
+
* function ChannelList() {
|
|
624
|
+
* const { channels, createChannel } = useChannels();
|
|
625
|
+
* return (
|
|
626
|
+
* <ul>
|
|
627
|
+
* {channels.map(ch => <li key={ch.id}>{ch.name} ({ch.member_count} members)</li>)}
|
|
628
|
+
* <button onClick={() => createChannel('General')}>New Channel</button>
|
|
629
|
+
* </ul>
|
|
630
|
+
* );
|
|
631
|
+
* }
|
|
632
|
+
* ```
|
|
633
|
+
*/
|
|
634
|
+
declare function useChannels(): UseChannelsReturn;
|
|
635
|
+
|
|
292
636
|
/** Props for the {@link ProtectedRoute} component. */
|
|
293
637
|
interface ProtectedRouteProps {
|
|
294
638
|
children: ReactNode;
|
|
@@ -346,4 +690,5 @@ interface ProtectedRouteProps {
|
|
|
346
690
|
*/
|
|
347
691
|
declare function ProtectedRoute({ children, redirectTo, roles, loadingComponent, unauthorizedComponent, unauthenticatedComponent, }: ProtectedRouteProps): react_jsx_runtime.JSX.Element | null;
|
|
348
692
|
|
|
349
|
-
export { DypaiProvider, ProtectedRoute, useAction, useAuth, useDypai, useDypaiClient, useEndpoint, useUpload };
|
|
693
|
+
export { DypaiProvider, ProtectedRoute, generateChatId, getMessageText, getToolCalls, useAction, useAuth, useChannel, useChannelMessages, useChannels, useChat, useChatList, useDypai, useDypaiClient, useEndpoint, useRealtime, useUpload };
|
|
694
|
+
export type { BroadcastMessage, Channel, ChannelMessage, ChatMessage, ChatSession, ChatStatus, MessagePart, PresenceEntry, RealtimeChange, UseChannelMessagesOptions, UseChannelMessagesReturn, UseChannelOptions, UseChannelReturn, UseChannelsReturn, UseChatListOptions, UseChatListReturn, UseChatOptions, UseChatReturn, UseRealtimeOptions, UseRealtimeReturn };
|
package/dist/react/index.esm.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as r,Fragment as n}from"react/jsx-runtime";import{createContext as t,useState as e,useEffect as a,useContext as i,useCallback as o,useRef as u}from"react";const l=t(null);function s({client:n,children:t}){const[i,o]=e(null),[u,s]=e(null),[c,d]=e(!0),[h,w]=e(null);a(()=>{const{data:{subscription:r}}=n.auth.onAuthStateChange((r,n)=>{w(r),"SIGNED_IN"===r||"TOKEN_REFRESHED"===r||"INITIAL_SESSION"===r?(s(n??null),o(n?.user??null),d(!1)):"SIGNED_OUT"===r?(s(null),o(null),d(!1)):"PASSWORD_RECOVERY"===r?(s(n??null),o(n?.user??null),d(!1)):"AUTH_ERROR"===r?d(!1):"USER_UPDATED"===r&&n&&(s(n),o(n.user??null))});return()=>{r?.unsubscribe()}},[n]);const p={client:n,user:i,session:u,isLoading:c,isAuthenticated:!!i,authEvent:h};return r(l.Provider,{value:p,children:t})}function c(){const r=i(l);if(!r)throw new Error('useDypai must be used within a <DypaiProvider>. Wrap your app with <DypaiProvider url="...">.');return r}function d(){return c().client}function h(){const{client:r,user:n,isLoading:t,isAuthenticated:e,authEvent:a}=c(),i=o(async(n,t)=>{const{error:e}=await r.auth.signInWithPassword({email:n,password:t});return{error:e}},[r]),u=o(async(n,t,e)=>{const{data:a,error:i}=await r.auth.signUp({email:n,password:t,...e});return{error:i,confirmationRequired:a?.confirmationRequired}},[r]),l=o(async()=>{await r.auth.signOut()},[r]),s=o(async(n,t)=>{const{error:e}=await r.auth.resetPasswordForEmail(n,t);return{error:e}},[r]),d=o(async n=>{const{error:t}=await r.auth.setPassword(n);return{error:t}},[r]),h=o(async n=>{await r.auth.signInWithOAuth(n)},[r]),w=o(async n=>{const{error:t}=await r.auth.signInWithOtp({email:n});return{error:t}},[r]);return{user:n,isLoading:t,isAuthenticated:e,authEvent:a,lastError:r.auth.lastError,signIn:i,signUp:u,signOut:l,resetPassword:s,setPassword:d,signInWithOAuth:h,signInWithOtp:w}}function w(r,n={}){const t=d(),{params:i,enabled:l=!0,refetchInterval:s=0}=n,[c,h]=e(null),[w,p]=e(null),[f,E]=e(!0),[m,g]=e(!1),y=u(!0),I=JSON.stringify(i||{}),S=o(async(n=!1)=>{if(!l)return void E(!1);n?g(!0):E(!0),p(null);const{data:e,error:a}=await t.api.get(r,{params:i});y.current&&(a?(p(a),h(null)):h(e),E(!1),g(!1))},[t,r,I,l]);return a(()=>(y.current=!0,S(),()=>{y.current=!1}),[S]),a(()=>{if(!s||!l)return;const r=setInterval(()=>S(!0),s);return()=>clearInterval(r)},[S,s,l]),{data:c,error:w,isLoading:f,isRefetching:m,refetch:o(async()=>{await S(!0)},[S])}}function p(r,n={}){const t=d(),{method:a="POST",onSuccess:i,onError:u}=n,[l,s]=e(null),[c,h]=e(null),[w,p]=e(!1);return{mutate:o(async n=>{let e;switch(p(!0),h(null),a){case"PUT":e=await t.api.put(r,n);break;case"PATCH":e=await t.api.patch(r,n);break;case"DELETE":e=await t.api.delete(r,{params:n});break;default:e=await t.api.post(r,n)}return p(!1),e.error?(h(e.error),u?.(e.error),{data:null,error:e.error}):(s(e.data),i?.(e.data),{data:e.data,error:null})},[t,r,a,i,u]),data:l,error:c,isLoading:w,reset:o(()=>{s(null),h(null),p(!1)},[])}}function f(r,n={}){const t=d(),{onSuccess:a,onError:i}=n,[u,l]=e(0),[s,c]=e(!1),[h,w]=e(null),[p,f]=e(null);return{upload:o(async(n,e)=>{c(!0),l(0),w(null);const{data:o,error:u}=await t.api.upload(r,n,{params:e,onProgress:l});return c(!1),u?(w(u),i?.(u),{data:null,error:u}):(f(o),a?.(o),{data:o,error:null})},[t,r,a,i]),progress:u,isUploading:s,error:h,data:p,reset:o(()=>{l(0),c(!1),w(null),f(null)},[])}}function E({children:t,redirectTo:e,roles:a,loadingComponent:i,unauthorizedComponent:o,unauthenticatedComponent:u}){const{user:l,isLoading:s,isAuthenticated:d}=c();if(s)return r(n,{children:i||null});if(!d)return e&&"undefined"!=typeof window?(window.location.replace(e),null):r(n,{children:u||null});if(a&&a.length>0){const t=l?.role;if(!t||!a.includes(t))return r(n,{children:o||null})}return r(n,{children:t})}export{s as DypaiProvider,E as ProtectedRoute,p as useAction,h as useAuth,c as useDypai,d as useDypaiClient,w as useEndpoint,f as useUpload};
|
|
1
|
+
import{jsx as t,Fragment as e}from"react/jsx-runtime";import{createContext as n,useState as r,useEffect as o,useContext as a,useCallback as s,useRef as i}from"react";const c=n(null);function l({client:e,children:n}){const[a,s]=r(null),[i,l]=r(null),[u,d]=r(!0),[h,f]=r(null);o(()=>{const{data:{subscription:t}}=e.auth.onAuthStateChange((t,e)=>{f(t),"SIGNED_IN"===t||"TOKEN_REFRESHED"===t||"INITIAL_SESSION"===t?(l(e??null),s(e?.user??null),d(!1)):"SIGNED_OUT"===t?(l(null),s(null),d(!1)):"PASSWORD_RECOVERY"===t?(l(e??null),s(e?.user??null),d(!1)):"AUTH_ERROR"===t?d(!1):"USER_UPDATED"===t&&e&&(l(e),s(e.user??null))});return()=>{t?.unsubscribe()}},[e]);const p={client:e,user:a,session:i,isLoading:u,isAuthenticated:!!a,authEvent:h};return t(c.Provider,{value:p,children:n})}function u(){const t=a(c);if(!t)throw new Error('useDypai must be used within a <DypaiProvider>. Wrap your app with <DypaiProvider url="...">.');return t}function d(){return u().client}function h(){const{client:t,user:e,isLoading:n,isAuthenticated:r,authEvent:o}=u(),a=s(async(e,n)=>{const{error:r}=await t.auth.signInWithPassword({email:e,password:n});return{error:r}},[t]),i=s(async(e,n,r)=>{const{data:o,error:a}=await t.auth.signUp({email:e,password:n,...r});return{error:a,confirmationRequired:o?.confirmationRequired}},[t]),c=s(async()=>{await t.auth.signOut()},[t]),l=s(async(e,n)=>{const{error:r}=await t.auth.resetPasswordForEmail(e,n);return{error:r}},[t]),d=s(async e=>{const{error:n}=await t.auth.setPassword(e);return{error:n}},[t]),h=s(async e=>{await t.auth.signInWithOAuth(e)},[t]),f=s(async e=>{const{error:n}=await t.auth.signInWithOtp({email:e});return{error:n}},[t]);return{user:e,isLoading:n,isAuthenticated:r,authEvent:o,lastError:t.auth.lastError,signIn:a,signUp:i,signOut:c,resetPassword:l,setPassword:d,signInWithOAuth:h,signInWithOtp:f}}function f(t,e={}){const n=d(),{params:a,enabled:c=!0,refetchInterval:l=0}=e,[u,h]=r(null),[f,p]=r(null),[w,y]=r(!0),[m,b]=r(!1),S=i(!0),T=JSON.stringify(a||{}),E=s(async(e=!1)=>{if(!c)return void y(!1);e?b(!0):y(!0),p(null);const{data:r,error:o}=await n.api.get(t,{params:a});S.current&&(o?(p(o),h(null)):h(r),y(!1),b(!1))},[n,t,T,c]);return o(()=>(S.current=!0,E(),()=>{S.current=!1}),[E]),o(()=>{if(!l||!c)return;const t=setInterval(()=>E(!0),l);return()=>clearInterval(t)},[E,l,c]),{data:u,error:f,isLoading:w,isRefetching:m,refetch:s(async()=>{await E(!0)},[E])}}function p(t,e={}){const n=d(),{method:o="POST",onSuccess:a,onError:i}=e,[c,l]=r(null),[u,h]=r(null),[f,p]=r(!1);return{mutate:s(async e=>{let r;switch(p(!0),h(null),o){case"PUT":r=await n.api.put(t,e);break;case"PATCH":r=await n.api.patch(t,e);break;case"DELETE":r=await n.api.delete(t,{params:e});break;default:r=await n.api.post(t,e)}return p(!1),r.error?(h(r.error),i?.(r.error),{data:null,error:r.error}):(l(r.data),a?.(r.data),{data:r.data,error:null})},[n,t,o,a,i]),data:c,error:u,isLoading:f,reset:s(()=>{l(null),h(null),p(!1)},[])}}function w(t,e={}){const n=d(),{onSuccess:o,onError:a}=e,[i,c]=r(0),[l,u]=r(!1),[h,f]=r(null),[p,w]=r(null);return{upload:s(async(e,r)=>{u(!0),c(0),f(null);const{data:s,error:i}=await n.api.upload(t,e,{params:r,onProgress:c});return u(!1),i?(f(i),a?.(i),{data:null,error:i}):(w(s),o?.(s),{data:s,error:null})},[n,t,o,a]),progress:i,isUploading:l,error:h,data:p,reset:s(()=>{c(0),u(!1),f(null),w(null)},[])}}function y(){return crypto.randomUUID?.()||`${Date.now()}-${Math.random().toString(36).slice(2)}`}function m(t){return t.parts?.length?t.parts.filter(t=>"text"===t.type).map(t=>t.text||"").join(""):t.content||""}function b(t){return t.parts?.length?t.parts.filter(t=>"tool-call"===t.type).map(t=>({toolName:t.toolName||"unknown",args:t.args})):[]}function S(){return y()}function T(t,e={}){const n=d(),[a,c]=r(e.id||S),[l,u]=r(e.initialMessages||[]),[h,f]=r("ready"),[p,w]=r(""),[y,m]=r(null),b=i(null);o(()=>{if(!1===e.loadHistory)return;if(!e.id)return;let t=!1;return(async()=>{try{const e=n.baseUrl,r=n.getAuthToken()||"",o=await fetch(`${e}/api/v0/chats/${a}/messages?limit=50`,{headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"}});if(!o.ok||t)return;const s=await o.json();if(s.messages&&!t){const t=s.messages.map(t=>({id:t.id,role:t.role,content:(t.parts||[]).filter(t=>"text"===t.type).map(t=>t.text).join(""),parts:t.parts,metadata:t.metadata,createdAt:new Date(t.created_at)}));u(t)}}catch{}})(),()=>{t=!0}},[a,e.id,e.loadHistory]);const T=s(async r=>{const o=r||p;if(!o.trim())return;r||w(""),m(null);const s={id:S(),role:"user",content:o,parts:[{type:"text",text:o}],createdAt:new Date},i=[...l,s];u(i),f("submitted");const c={id:S(),role:"assistant",content:"",parts:[],createdAt:new Date};try{const r=n.baseUrl,s=n.getAuthToken()||"",l=new AbortController;b.current=l;const d={session_id:a,messages:[{role:"user",content:o}],...e.body||{}},h=await fetch(`${r}/api/v0/${t}`,{method:"POST",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json",Accept:"text/event-stream",...e.headers||{}},body:JSON.stringify(d),signal:l.signal});if(!h.ok){const t=await h.json().catch(()=>({error:h.statusText}));throw new Error(t.error||`HTTP ${h.status}`)}if((h.headers.get("content-type")||"").includes("application/json")){const t=await h.json(),n=t.content||t.text||JSON.stringify(t);c.content=n,c.parts=[{type:"text",text:n}];const r=[...i,{...c}];return u(r),f("ready"),void e.onFinish?.(c,r)}f("streaming"),u([...i,c]);const p=h.body?.getReader();if(!p)throw new Error("No response body");const w=new TextDecoder;let y="";for(;;){const{done:t,value:e}=await p.read();if(t)break;const n=w.decode(e,{stream:!0}).split("\n").filter(t=>!/^[A-Z][a-zA-Z-]+:\s/.test(t)).join("\n");n&&(y+=n,c.content=y,c.parts=[{type:"text",text:y}],u([...i,{...c}]))}const m={...c,content:y,parts:[{type:"text",text:y}]},S=[...i,m];u(S),f("ready"),e.onFinish?.(m,S)}catch(t){if("AbortError"===t.name)return void f("ready");m(t),f("error"),e.onError?.(t)}finally{b.current=null}},[p,l,a,t,n,e]),E=s(()=>{b.current?.abort(),b.current=null,f("ready")},[]),g=s(()=>{E(),u([]),m(null),f("ready"),w(""),c(S())},[E]);return{messages:l,status:h,input:p,setInput:w,sendMessage:T,setMessages:u,stop:E,newChat:g,isLoading:"submitted"===h||"streaming"===h,error:y,chatId:a}}function E(t={}){const e=d(),{endpointId:n,limit:a=20,enabled:i=!0}=t,[c,l]=r([]),[u,h]=r(!1),[f,p]=r(null),w=s(async()=>{h(!0),p(null);try{const t=e.baseUrl,r=e.getAuthToken()||"",o=new URLSearchParams({limit:String(a)});n&&o.set("endpoint_id",n);const s=await fetch(`${t}/api/v0/chats?${o}`,{headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"}});if(!s.ok){const t=await s.json().catch(()=>({}));throw new Error(t.error||`HTTP ${s.status}`)}const i=((await s.json()).chats||[]).map(t=>({id:t.id,title:t.title,visibility:t.visibility,messageCount:parseInt(t.message_count||"0"),createdAt:t.created_at,updatedAt:t.updated_at}));l(i)}catch(t){p(t)}finally{h(!1)}},[e,n,a]),y=s(async t=>{const n=e.baseUrl,r=e.getAuthToken()||"",o=await fetch(`${n}/api/v0/chats/${t}`,{method:"DELETE",headers:{Authorization:`Bearer ${r}`}});if(!o.ok){const t=await o.json().catch(()=>({}));throw new Error(t.error||`HTTP ${o.status}`)}l(e=>e.filter(e=>e.id!==t))},[e]),m=s(async(t,n)=>{const r=e.baseUrl,o=e.getAuthToken()||"",a=await fetch(`${r}/api/v0/chats/${t}`,{method:"PATCH",headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json"},body:JSON.stringify({title:n})});if(!a.ok){const t=await a.json().catch(()=>({}));throw new Error(t.error||`HTTP ${a.status}`)}l(e=>e.map(e=>e.id===t?{...e,title:n}:e))},[e]);return o(()=>{i&&w()},[i,w]),{chats:c,isLoading:u,error:f,refetch:w,deleteChat:y,renameChat:m}}let g=null,v="disconnected",$=null,A=null,O=0,C="",I="";const P=[1e3,2e3,5e3,1e4,3e4],N=new Set,D=[],k=[];function L(t,e){if(g&&(g.readyState===WebSocket.OPEN||g.readyState===WebSocket.CONNECTING))return g;C=t,I=e;const n=t.replace(/^http/,"ws")+`/realtime?token=${encodeURIComponent(e)}`,r=new WebSocket(n);return g=r,v="connecting",r.onopen=()=>{v="connected",O=0;for(const t of D)r.send(JSON.stringify({type:"subscribe",...t}));for(const t of k)r.send(JSON.stringify({type:"join",...t}));A&&clearInterval(A),A=setInterval(()=>{r.readyState===WebSocket.OPEN&&r.send(JSON.stringify({type:"heartbeat"}))},25e3)},r.onmessage=t=>{try{const e=JSON.parse("string"==typeof t.data?t.data:"");for(const t of N)t(e)}catch{}},r.onclose=()=>{if(v="disconnected",g=null,A&&clearInterval(A),N.size>0&&!$){const t=P[Math.min(O,P.length-1)];O++,$=setTimeout(()=>{$=null,N.size>0&&C&&I&&L(C,I)},t)}},r.onerror=()=>{v="error"},r}function R(t){return N.add(t),()=>{N.delete(t),0===N.size&&g&&(g.close(),g=null)}}function j(t){return!(!g||g.readyState!==WebSocket.OPEN||(g.send(JSON.stringify(t)),0))}function x(t,e={}){const n=d(),{event:a="*",filter:s,enabled:c=!0}=e,[l,u]=r([]),[h,f]=r("disconnected"),[p,w]=r(null),y=i(e);return y.current=e,o(()=>{if(!c)return;const e=n.baseUrl,r=n.getAuthToken()||"";if(!r)return void w(new Error("Authentication required for realtime"));const o=L(e,r);f(v),function(t){const e=D.findIndex(e=>e.table===t.table);e>=0&&D.splice(e,1),D.push(t)}({table:t,event:a,filter:s});const i=()=>{o.readyState===WebSocket.OPEN&&(o.send(JSON.stringify({type:"subscribe",table:t,event:a,filter:s})),f("connected"))};o.readyState===WebSocket.OPEN?i():o.addEventListener("open",i,{once:!0});const l=R(e=>{if("connected"!==e.type&&"subscribe"!==e.type||f("connected"),"change"!==e.type||e.table!==t)return;if("*"!==a&&e.event!==a)return;const n={table:e.table,event:e.event,record:e.record,old_record:e.old_record,timestamp:e.timestamp};u(t=>[...t.slice(-99),n]),y.current.onChange?.(n),"INSERT"===e.event&&e.record&&y.current.onInsert?.(e.record),"UPDATE"===e.event&&e.record&&y.current.onUpdate?.(e.record,e.old_record),"DELETE"===e.event&&e.old_record&&y.current.onDelete?.(e.old_record)});return()=>{l(),function(t){const e=D.findIndex(e=>e.table===t);e>=0&&D.splice(e,1)}(t),j({type:"unsubscribe",table:t})}},[t,a,c,n]),{changes:l,isConnected:"connected"===h,status:h,error:p}}function _(t,e={}){const n=d(),{presence:a,enabled:c=!0}=e,[l,u]=r({}),[h,f]=r(!1),p=i(e);return p.current=e,o(()=>{if(!c||!t)return;const e=n.baseUrl,r=n.getAuthToken()||"";if(!r)return;const o=L(e,r);!function(t){const e=k.findIndex(e=>e.channel===t.channel);e>=0&&k.splice(e,1),k.push(t)}({channel:t,presence:a||void 0});const s=()=>{o.readyState===WebSocket.OPEN&&o.send(JSON.stringify({type:"join",channel:t,presence:a||void 0}))};o.readyState===WebSocket.OPEN?s():o.addEventListener("open",s,{once:!0});const i=R(e=>{"connected"!==e.type&&"join"!==e.type||f(!0),"broadcast"===e.type&&e.channel===t&&p.current.onBroadcast?.({event:e.event,payload:e.payload,sender:e.sender,timestamp:e.timestamp}),"presence_state"===e.type&&e.channel===t&&u(e.presences||{}),"presence_diff"===e.type&&e.channel===t&&u(t=>{const n={...t};if(e.joins)for(const[t,r]of Object.entries(e.joins))n[t]=r,p.current.onJoin?.(t,r);if(e.leaves)for(const[t,r]of Object.entries(e.leaves))delete n[t],p.current.onLeave?.(t,r);return n})});return()=>{i(),function(t){const e=k.findIndex(e=>e.channel===t);e>=0&&k.splice(e,1)}(t),j({type:"leave",channel:t}),f(!1)}},[t,c,n]),{broadcast:s((e,n)=>{j({type:"broadcast",channel:t,event:e,payload:n})},[t]),presences:l,isConnected:h,memberCount:Object.keys(l).length}}function H(t,e={}){const n=d(),{loadHistory:a=!0,limit:c=50}=e,[l,u]=r([]),[h,f]=r(!1),[p,w]=r(null),y=i(new Set);o(()=>{if(!a||!t)return;let e=!1;return(async()=>{f(!0);try{const r=n.baseUrl,o=n.getAuthToken()||"",a=await fetch(`${r}/api/v0/channels/${t}/messages?limit=${c}`,{headers:{Authorization:`Bearer ${o}`}});if(!a.ok)throw new Error(`HTTP ${a.status}`);const s=await a.json();if(!e&&s.messages){const t=s.messages;t.forEach(t=>y.current.add(t.id)),u(t)}}catch(t){e||w(t)}finally{e||f(!1)}})(),()=>{e=!0}},[t,a,c,n]);const{isConnected:m}=x("channel_messages",{event:"INSERT",filter:{channel_id:t},onInsert:t=>{y.current.has(t.id)||(y.current.add(t.id),u(e=>[...e,t]))}});return{messages:l,send:s(async(e,r)=>{try{const o=n.baseUrl,a=n.getAuthToken()||"",s=await fetch(`${o}/api/v0/channels/${t}/messages`,{method:"POST",headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"},body:JSON.stringify({content:e,metadata:r})});if(!s.ok){const t=await s.json().catch(()=>({}));throw new Error(t.error||`HTTP ${s.status}`)}const i=await s.json();i.message&&(y.current.add(i.message.id),u(t=>[...t,i.message]))}catch(t){throw w(t),t}},[t,n]),isLoading:h,isConnected:m,error:p}}function J(){const t=d(),[e,n]=r([]),[a,i]=r(!1),[c,l]=r(null),u=s(async()=>{i(!0);try{const e=t.baseUrl,r=t.getAuthToken()||"",o=await fetch(`${e}/api/v0/channels`,{headers:{Authorization:`Bearer ${r}`}});if(!o.ok)throw new Error(`HTTP ${o.status}`);const a=await o.json();n(a.channels||[])}catch(t){l(t)}finally{i(!1)}},[t]);return o(()=>{u()},[u]),{channels:e,isLoading:a,error:c,createChannel:s(async(e,r=[],o="group")=>{const a=t.baseUrl,s=t.getAuthToken()||"",i=await fetch(`${a}/api/v0/channels`,{method:"POST",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},body:JSON.stringify({name:e,type:o,members:r})});if(!i.ok){const t=await i.json().catch(()=>({}));throw new Error(t.error||`HTTP ${i.status}`)}const c=await i.json();return n(t=>[c,...t]),c},[t]),refetch:u}}function W({children:n,redirectTo:r,roles:o,loadingComponent:a,unauthorizedComponent:s,unauthenticatedComponent:i}){const{user:c,isLoading:l,isAuthenticated:d}=u();if(l)return t(e,{children:a||null});if(!d)return r&&"undefined"!=typeof window?(window.location.replace(r),null):t(e,{children:i||null});if(o&&o.length>0){const n=c?.role;if(!n||!o.includes(n))return t(e,{children:s||null})}return t(e,{children:n})}export{l as DypaiProvider,W as ProtectedRoute,y as generateChatId,m as getMessageText,b as getToolCalls,p as useAction,h as useAuth,_ as useChannel,H as useChannelMessages,J as useChannels,T as useChat,E as useChatList,u as useDypai,d as useDypaiClient,f as useEndpoint,x as useRealtime,w as useUpload};
|
package/dist/react/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var jsxRuntime=require("react/jsx-runtime"),react=require("react");const DypaiContext=react.createContext(null);function useDypai(){const r=react.useContext(DypaiContext);if(!r)throw new Error('useDypai must be used within a <DypaiProvider>. Wrap your app with <DypaiProvider url="...">.');return r}function useDypaiClient(){return useDypai().client}exports.DypaiProvider=function({client:r,children:e}){const[t,n]=react.useState(null),[a,i]=react.useState(null),[s,u]=react.useState(!0),[c,o]=react.useState(null);react.useEffect(()=>{const{data:{subscription:e}}=r.auth.onAuthStateChange((r,e)=>{o(r),"SIGNED_IN"===r||"TOKEN_REFRESHED"===r||"INITIAL_SESSION"===r?(i(e??null),n(e?.user??null),u(!1)):"SIGNED_OUT"===r?(i(null),n(null),u(!1)):"PASSWORD_RECOVERY"===r?(i(e??null),n(e?.user??null),u(!1)):"AUTH_ERROR"===r?u(!1):"USER_UPDATED"===r&&e&&(i(e),n(e.user??null))});return()=>{e?.unsubscribe()}},[r]);const l={client:r,user:t,session:a,isLoading:s,isAuthenticated:!!t,authEvent:c};return jsxRuntime.jsx(DypaiContext.Provider,{value:l,children:e})},exports.ProtectedRoute=function({children:r,redirectTo:e,roles:t,loadingComponent:n,unauthorizedComponent:a,unauthenticatedComponent:i}){const{user:s,isLoading:u,isAuthenticated:c}=useDypai();if(u)return jsxRuntime.jsx(jsxRuntime.Fragment,{children:n||null});if(!c)return e&&"undefined"!=typeof window?(window.location.replace(e),null):jsxRuntime.jsx(jsxRuntime.Fragment,{children:i||null});if(t&&t.length>0){const r=s?.role;if(!r||!t.includes(r))return jsxRuntime.jsx(jsxRuntime.Fragment,{children:a||null})}return jsxRuntime.jsx(jsxRuntime.Fragment,{children:r})},exports.useAction=function(r,e={}){const t=useDypaiClient(),{method:n="POST",onSuccess:a,onError:i}=e,[s,u]=react.useState(null),[c,o]=react.useState(null),[l,d]=react.useState(!1);return{mutate:react.useCallback(async e=>{let s;switch(d(!0),o(null),n){case"PUT":s=await t.api.put(r,e);break;case"PATCH":s=await t.api.patch(r,e);break;case"DELETE":s=await t.api.delete(r,{params:e});break;default:s=await t.api.post(r,e)}return d(!1),s.error?(o(s.error),i?.(s.error),{data:null,error:s.error}):(u(s.data),a?.(s.data),{data:s.data,error:null})},[t,r,n,a,i]),data:s,error:c,isLoading:l,reset:react.useCallback(()=>{u(null),o(null),d(!1)},[])}},exports.useAuth=function(){const{client:r,user:e,isLoading:t,isAuthenticated:n,authEvent:a}=useDypai(),i=react.useCallback(async(e,t)=>{const{error:n}=await r.auth.signInWithPassword({email:e,password:t});return{error:n}},[r]),s=react.useCallback(async(e,t,n)=>{const{data:a,error:i}=await r.auth.signUp({email:e,password:t,...n});return{error:i,confirmationRequired:a?.confirmationRequired}},[r]),u=react.useCallback(async()=>{await r.auth.signOut()},[r]),c=react.useCallback(async(e,t)=>{const{error:n}=await r.auth.resetPasswordForEmail(e,t);return{error:n}},[r]),o=react.useCallback(async e=>{const{error:t}=await r.auth.setPassword(e);return{error:t}},[r]),l=react.useCallback(async e=>{await r.auth.signInWithOAuth(e)},[r]),d=react.useCallback(async e=>{const{error:t}=await r.auth.signInWithOtp({email:e});return{error:t}},[r]);return{user:e,isLoading:t,isAuthenticated:n,authEvent:a,lastError:r.auth.lastError,signIn:i,signUp:s,signOut:u,resetPassword:c,setPassword:o,signInWithOAuth:l,signInWithOtp:d}},exports.useDypai=useDypai,exports.useDypaiClient=useDypaiClient,exports.useEndpoint=function(r,e={}){const t=useDypaiClient(),{params:n,enabled:a=!0,refetchInterval:i=0}=e,[s,u]=react.useState(null),[c,o]=react.useState(null),[l,d]=react.useState(!0),[p,y]=react.useState(!1),h=react.useRef(!0),w=JSON.stringify(n||{}),m=react.useCallback(async(e=!1)=>{if(!a)return void d(!1);e?y(!0):d(!0),o(null);const{data:i,error:s}=await t.api.get(r,{params:n});h.current&&(s?(o(s),u(null)):u(i),d(!1),y(!1))},[t,r,w,a]);return react.useEffect(()=>(h.current=!0,m(),()=>{h.current=!1}),[m]),react.useEffect(()=>{if(!i||!a)return;const r=setInterval(()=>m(!0),i);return()=>clearInterval(r)},[m,i,a]),{data:s,error:c,isLoading:l,isRefetching:p,refetch:react.useCallback(async()=>{await m(!0)},[m])}},exports.useUpload=function(r,e={}){const t=useDypaiClient(),{onSuccess:n,onError:a}=e,[i,s]=react.useState(0),[u,c]=react.useState(!1),[o,l]=react.useState(null),[d,p]=react.useState(null);return{upload:react.useCallback(async(e,i)=>{c(!0),s(0),l(null);const{data:u,error:o}=await t.api.upload(r,e,{params:i,onProgress:s});return c(!1),o?(l(o),a?.(o),{data:null,error:o}):(p(u),n?.(u),{data:u,error:null})},[t,r,n,a]),progress:i,isUploading:u,error:o,data:d,reset:react.useCallback(()=>{s(0),c(!1),l(null),p(null)},[])}};
|
|
1
|
+
"use strict";var jsxRuntime=require("react/jsx-runtime"),react=require("react");const DypaiContext=react.createContext(null);function useDypai(){const e=react.useContext(DypaiContext);if(!e)throw new Error('useDypai must be used within a <DypaiProvider>. Wrap your app with <DypaiProvider url="...">.');return e}function useDypaiClient(){return useDypai().client}function generateChatId(){return crypto.randomUUID?.()||`${Date.now()}-${Math.random().toString(36).slice(2)}`}function generateId(){return generateChatId()}let sharedWs=null,sharedWsStatus="disconnected",reconnectTimer=null,heartbeatTimer=null,reconnectAttempt=0,currentBaseUrl="",currentToken="";const RECONNECT_INTERVALS=[1e3,2e3,5e3,1e4,3e4],messageHandlers=new Set,pendingSubscriptions=[],pendingChannelJoins=[];function getOrCreateSharedWs(e,t){if(sharedWs&&(sharedWs.readyState===WebSocket.OPEN||sharedWs.readyState===WebSocket.CONNECTING))return sharedWs;currentBaseUrl=e,currentToken=t;const n=e.replace(/^http/,"ws")+`/realtime?token=${encodeURIComponent(t)}`,r=new WebSocket(n);return sharedWs=r,sharedWsStatus="connecting",r.onopen=()=>{sharedWsStatus="connected",reconnectAttempt=0;for(const e of pendingSubscriptions)r.send(JSON.stringify({type:"subscribe",...e}));for(const e of pendingChannelJoins)r.send(JSON.stringify({type:"join",...e}));heartbeatTimer&&clearInterval(heartbeatTimer),heartbeatTimer=setInterval(()=>{r.readyState===WebSocket.OPEN&&r.send(JSON.stringify({type:"heartbeat"}))},25e3)},r.onmessage=e=>{try{const t=JSON.parse("string"==typeof e.data?e.data:"");for(const e of messageHandlers)e(t)}catch{}},r.onclose=()=>{if(sharedWsStatus="disconnected",sharedWs=null,heartbeatTimer&&clearInterval(heartbeatTimer),messageHandlers.size>0&&!reconnectTimer){const e=RECONNECT_INTERVALS[Math.min(reconnectAttempt,RECONNECT_INTERVALS.length-1)];reconnectAttempt++,reconnectTimer=setTimeout(()=>{reconnectTimer=null,messageHandlers.size>0&¤tBaseUrl&¤tToken&&getOrCreateSharedWs(currentBaseUrl,currentToken)},e)}},r.onerror=()=>{sharedWsStatus="error"},r}function addMessageHandler(e){return messageHandlers.add(e),()=>{messageHandlers.delete(e),0===messageHandlers.size&&sharedWs&&(sharedWs.close(),sharedWs=null)}}function sendMessage(e){return!(!sharedWs||sharedWs.readyState!==WebSocket.OPEN||(sharedWs.send(JSON.stringify(e)),0))}function useRealtime(e,t={}){const n=useDypaiClient(),{event:r="*",filter:a,enabled:s=!0}=t,[o,c]=react.useState([]),[i,u]=react.useState("disconnected"),[l,d]=react.useState(null),p=react.useRef(t);return p.current=t,react.useEffect(()=>{if(!s)return;const t=n.baseUrl,o=n.getAuthToken()||"";if(!o)return void d(new Error("Authentication required for realtime"));const i=getOrCreateSharedWs(t,o);u(sharedWsStatus),function(e){const t=pendingSubscriptions.findIndex(t=>t.table===e.table);t>=0&&pendingSubscriptions.splice(t,1),pendingSubscriptions.push(e)}({table:e,event:r,filter:a});const l=()=>{i.readyState===WebSocket.OPEN&&(i.send(JSON.stringify({type:"subscribe",table:e,event:r,filter:a})),u("connected"))};i.readyState===WebSocket.OPEN?l():i.addEventListener("open",l,{once:!0});const h=addMessageHandler(t=>{if("connected"!==t.type&&"subscribe"!==t.type||u("connected"),"change"!==t.type||t.table!==e)return;if("*"!==r&&t.event!==r)return;const n={table:t.table,event:t.event,record:t.record,old_record:t.old_record,timestamp:t.timestamp};c(e=>[...e.slice(-99),n]),p.current.onChange?.(n),"INSERT"===t.event&&t.record&&p.current.onInsert?.(t.record),"UPDATE"===t.event&&t.record&&p.current.onUpdate?.(t.record,t.old_record),"DELETE"===t.event&&t.old_record&&p.current.onDelete?.(t.old_record)});return()=>{h(),function(e){const t=pendingSubscriptions.findIndex(t=>t.table===e);t>=0&&pendingSubscriptions.splice(t,1)}(e),sendMessage({type:"unsubscribe",table:e})}},[e,r,s,n]),{changes:o,isConnected:"connected"===i,status:i,error:l}}exports.DypaiProvider=function({client:e,children:t}){const[n,r]=react.useState(null),[a,s]=react.useState(null),[o,c]=react.useState(!0),[i,u]=react.useState(null);react.useEffect(()=>{const{data:{subscription:t}}=e.auth.onAuthStateChange((e,t)=>{u(e),"SIGNED_IN"===e||"TOKEN_REFRESHED"===e||"INITIAL_SESSION"===e?(s(t??null),r(t?.user??null),c(!1)):"SIGNED_OUT"===e?(s(null),r(null),c(!1)):"PASSWORD_RECOVERY"===e?(s(t??null),r(t?.user??null),c(!1)):"AUTH_ERROR"===e?c(!1):"USER_UPDATED"===e&&t&&(s(t),r(t.user??null))});return()=>{t?.unsubscribe()}},[e]);const l={client:e,user:n,session:a,isLoading:o,isAuthenticated:!!n,authEvent:i};return jsxRuntime.jsx(DypaiContext.Provider,{value:l,children:t})},exports.ProtectedRoute=function({children:e,redirectTo:t,roles:n,loadingComponent:r,unauthorizedComponent:a,unauthenticatedComponent:s}){const{user:o,isLoading:c,isAuthenticated:i}=useDypai();if(c)return jsxRuntime.jsx(jsxRuntime.Fragment,{children:r||null});if(!i)return t&&"undefined"!=typeof window?(window.location.replace(t),null):jsxRuntime.jsx(jsxRuntime.Fragment,{children:s||null});if(n&&n.length>0){const e=o?.role;if(!e||!n.includes(e))return jsxRuntime.jsx(jsxRuntime.Fragment,{children:a||null})}return jsxRuntime.jsx(jsxRuntime.Fragment,{children:e})},exports.generateChatId=generateChatId,exports.getMessageText=function(e){return e.parts?.length?e.parts.filter(e=>"text"===e.type).map(e=>e.text||"").join(""):e.content||""},exports.getToolCalls=function(e){return e.parts?.length?e.parts.filter(e=>"tool-call"===e.type).map(e=>({toolName:e.toolName||"unknown",args:e.args})):[]},exports.useAction=function(e,t={}){const n=useDypaiClient(),{method:r="POST",onSuccess:a,onError:s}=t,[o,c]=react.useState(null),[i,u]=react.useState(null),[l,d]=react.useState(!1);return{mutate:react.useCallback(async t=>{let o;switch(d(!0),u(null),r){case"PUT":o=await n.api.put(e,t);break;case"PATCH":o=await n.api.patch(e,t);break;case"DELETE":o=await n.api.delete(e,{params:t});break;default:o=await n.api.post(e,t)}return d(!1),o.error?(u(o.error),s?.(o.error),{data:null,error:o.error}):(c(o.data),a?.(o.data),{data:o.data,error:null})},[n,e,r,a,s]),data:o,error:i,isLoading:l,reset:react.useCallback(()=>{c(null),u(null),d(!1)},[])}},exports.useAuth=function(){const{client:e,user:t,isLoading:n,isAuthenticated:r,authEvent:a}=useDypai(),s=react.useCallback(async(t,n)=>{const{error:r}=await e.auth.signInWithPassword({email:t,password:n});return{error:r}},[e]),o=react.useCallback(async(t,n,r)=>{const{data:a,error:s}=await e.auth.signUp({email:t,password:n,...r});return{error:s,confirmationRequired:a?.confirmationRequired}},[e]),c=react.useCallback(async()=>{await e.auth.signOut()},[e]),i=react.useCallback(async(t,n)=>{const{error:r}=await e.auth.resetPasswordForEmail(t,n);return{error:r}},[e]),u=react.useCallback(async t=>{const{error:n}=await e.auth.setPassword(t);return{error:n}},[e]),l=react.useCallback(async t=>{await e.auth.signInWithOAuth(t)},[e]),d=react.useCallback(async t=>{const{error:n}=await e.auth.signInWithOtp({email:t});return{error:n}},[e]);return{user:t,isLoading:n,isAuthenticated:r,authEvent:a,lastError:e.auth.lastError,signIn:s,signUp:o,signOut:c,resetPassword:i,setPassword:u,signInWithOAuth:l,signInWithOtp:d}},exports.useChannel=function(e,t={}){const n=useDypaiClient(),{presence:r,enabled:a=!0}=t,[s,o]=react.useState({}),[c,i]=react.useState(!1),u=react.useRef(t);return u.current=t,react.useEffect(()=>{if(!a||!e)return;const t=n.baseUrl,s=n.getAuthToken()||"";if(!s)return;const c=getOrCreateSharedWs(t,s);!function(e){const t=pendingChannelJoins.findIndex(t=>t.channel===e.channel);t>=0&&pendingChannelJoins.splice(t,1),pendingChannelJoins.push(e)}({channel:e,presence:r||void 0});const l=()=>{c.readyState===WebSocket.OPEN&&c.send(JSON.stringify({type:"join",channel:e,presence:r||void 0}))};c.readyState===WebSocket.OPEN?l():c.addEventListener("open",l,{once:!0});const d=addMessageHandler(t=>{"connected"!==t.type&&"join"!==t.type||i(!0),"broadcast"===t.type&&t.channel===e&&u.current.onBroadcast?.({event:t.event,payload:t.payload,sender:t.sender,timestamp:t.timestamp}),"presence_state"===t.type&&t.channel===e&&o(t.presences||{}),"presence_diff"===t.type&&t.channel===e&&o(e=>{const n={...e};if(t.joins)for(const[e,r]of Object.entries(t.joins))n[e]=r,u.current.onJoin?.(e,r);if(t.leaves)for(const[e,r]of Object.entries(t.leaves))delete n[e],u.current.onLeave?.(e,r);return n})});return()=>{d(),function(e){const t=pendingChannelJoins.findIndex(t=>t.channel===e);t>=0&&pendingChannelJoins.splice(t,1)}(e),sendMessage({type:"leave",channel:e}),i(!1)}},[e,a,n]),{broadcast:react.useCallback((t,n)=>{sendMessage({type:"broadcast",channel:e,event:t,payload:n})},[e]),presences:s,isConnected:c,memberCount:Object.keys(s).length}},exports.useChannelMessages=function(e,t={}){const n=useDypaiClient(),{loadHistory:r=!0,limit:a=50}=t,[s,o]=react.useState([]),[c,i]=react.useState(!1),[u,l]=react.useState(null),d=react.useRef(new Set);react.useEffect(()=>{if(!r||!e)return;let t=!1;return(async()=>{i(!0);try{const r=n.baseUrl,s=n.getAuthToken()||"",c=await fetch(`${r}/api/v0/channels/${e}/messages?limit=${a}`,{headers:{Authorization:`Bearer ${s}`}});if(!c.ok)throw new Error(`HTTP ${c.status}`);const i=await c.json();if(!t&&i.messages){const e=i.messages;e.forEach(e=>d.current.add(e.id)),o(e)}}catch(e){t||l(e)}finally{t||i(!1)}})(),()=>{t=!0}},[e,r,a,n]);const{isConnected:p}=useRealtime("channel_messages",{event:"INSERT",filter:{channel_id:e},onInsert:e=>{d.current.has(e.id)||(d.current.add(e.id),o(t=>[...t,e]))}});return{messages:s,send:react.useCallback(async(t,r)=>{try{const a=n.baseUrl,s=n.getAuthToken()||"",c=await fetch(`${a}/api/v0/channels/${e}/messages`,{method:"POST",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},body:JSON.stringify({content:t,metadata:r})});if(!c.ok){const e=await c.json().catch(()=>({}));throw new Error(e.error||`HTTP ${c.status}`)}const i=await c.json();i.message&&(d.current.add(i.message.id),o(e=>[...e,i.message]))}catch(e){throw l(e),e}},[e,n]),isLoading:c,isConnected:p,error:u}},exports.useChannels=function(){const e=useDypaiClient(),[t,n]=react.useState([]),[r,a]=react.useState(!1),[s,o]=react.useState(null),c=react.useCallback(async()=>{a(!0);try{const t=e.baseUrl,r=e.getAuthToken()||"",a=await fetch(`${t}/api/v0/channels`,{headers:{Authorization:`Bearer ${r}`}});if(!a.ok)throw new Error(`HTTP ${a.status}`);const s=await a.json();n(s.channels||[])}catch(e){o(e)}finally{a(!1)}},[e]);return react.useEffect(()=>{c()},[c]),{channels:t,isLoading:r,error:s,createChannel:react.useCallback(async(t,r=[],a="group")=>{const s=e.baseUrl,o=e.getAuthToken()||"",c=await fetch(`${s}/api/v0/channels`,{method:"POST",headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json"},body:JSON.stringify({name:t,type:a,members:r})});if(!c.ok){const e=await c.json().catch(()=>({}));throw new Error(e.error||`HTTP ${c.status}`)}const i=await c.json();return n(e=>[i,...e]),i},[e]),refetch:c}},exports.useChat=function(e,t={}){const n=useDypaiClient(),[r,a]=react.useState(t.id||generateId),[s,o]=react.useState(t.initialMessages||[]),[c,i]=react.useState("ready"),[u,l]=react.useState(""),[d,p]=react.useState(null),h=react.useRef(null);react.useEffect(()=>{if(!1===t.loadHistory)return;if(!t.id)return;let e=!1;return(async()=>{try{const t=n.baseUrl,a=n.getAuthToken()||"",s=await fetch(`${t}/api/v0/chats/${r}/messages?limit=50`,{headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"}});if(!s.ok||e)return;const c=await s.json();if(c.messages&&!e){const e=c.messages.map(e=>({id:e.id,role:e.role,content:(e.parts||[]).filter(e=>"text"===e.type).map(e=>e.text).join(""),parts:e.parts,metadata:e.metadata,createdAt:new Date(e.created_at)}));o(e)}}catch{}})(),()=>{e=!0}},[r,t.id,t.loadHistory]);const f=react.useCallback(async a=>{const c=a||u;if(!c.trim())return;a||l(""),p(null);const d={id:generateId(),role:"user",content:c,parts:[{type:"text",text:c}],createdAt:new Date},f=[...s,d];o(f),i("submitted");const y={id:generateId(),role:"assistant",content:"",parts:[],createdAt:new Date};try{const a=n.baseUrl,s=n.getAuthToken()||"",u=new AbortController;h.current=u;const l={session_id:r,messages:[{role:"user",content:c}],...t.body||{}},d=await fetch(`${a}/api/v0/${e}`,{method:"POST",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json",Accept:"text/event-stream",...t.headers||{}},body:JSON.stringify(l),signal:u.signal});if(!d.ok){const e=await d.json().catch(()=>({error:d.statusText}));throw new Error(e.error||`HTTP ${d.status}`)}if((d.headers.get("content-type")||"").includes("application/json")){const e=await d.json(),n=e.content||e.text||JSON.stringify(e);y.content=n,y.parts=[{type:"text",text:n}];const r=[...f,{...y}];return o(r),i("ready"),void t.onFinish?.(y,r)}i("streaming"),o([...f,y]);const p=d.body?.getReader();if(!p)throw new Error("No response body");const m=new TextDecoder;let w="";for(;;){const{done:e,value:t}=await p.read();if(e)break;const n=m.decode(t,{stream:!0}).split("\n").filter(e=>!/^[A-Z][a-zA-Z-]+:\s/.test(e)).join("\n");n&&(w+=n,y.content=w,y.parts=[{type:"text",text:w}],o([...f,{...y}]))}const g={...y,content:w,parts:[{type:"text",text:w}]},b=[...f,g];o(b),i("ready"),t.onFinish?.(g,b)}catch(e){if("AbortError"===e.name)return void i("ready");p(e),i("error"),t.onError?.(e)}finally{h.current=null}},[u,s,r,e,n,t]),y=react.useCallback(()=>{h.current?.abort(),h.current=null,i("ready")},[]),m=react.useCallback(()=>{y(),o([]),p(null),i("ready"),l(""),a(generateId())},[y]);return{messages:s,status:c,input:u,setInput:l,sendMessage:f,setMessages:o,stop:y,newChat:m,isLoading:"submitted"===c||"streaming"===c,error:d,chatId:r}},exports.useChatList=function(e={}){const t=useDypaiClient(),{endpointId:n,limit:r=20,enabled:a=!0}=e,[s,o]=react.useState([]),[c,i]=react.useState(!1),[u,l]=react.useState(null),d=react.useCallback(async()=>{i(!0),l(null);try{const e=t.baseUrl,a=t.getAuthToken()||"",s=new URLSearchParams({limit:String(r)});n&&s.set("endpoint_id",n);const c=await fetch(`${e}/api/v0/chats?${s}`,{headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"}});if(!c.ok){const e=await c.json().catch(()=>({}));throw new Error(e.error||`HTTP ${c.status}`)}const i=((await c.json()).chats||[]).map(e=>({id:e.id,title:e.title,visibility:e.visibility,messageCount:parseInt(e.message_count||"0"),createdAt:e.created_at,updatedAt:e.updated_at}));o(i)}catch(e){l(e)}finally{i(!1)}},[t,n,r]),p=react.useCallback(async e=>{const n=t.baseUrl,r=t.getAuthToken()||"",a=await fetch(`${n}/api/v0/chats/${e}`,{method:"DELETE",headers:{Authorization:`Bearer ${r}`}});if(!a.ok){const e=await a.json().catch(()=>({}));throw new Error(e.error||`HTTP ${a.status}`)}o(t=>t.filter(t=>t.id!==e))},[t]),h=react.useCallback(async(e,n)=>{const r=t.baseUrl,a=t.getAuthToken()||"",s=await fetch(`${r}/api/v0/chats/${e}`,{method:"PATCH",headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"},body:JSON.stringify({title:n})});if(!s.ok){const e=await s.json().catch(()=>({}));throw new Error(e.error||`HTTP ${s.status}`)}o(t=>t.map(t=>t.id===e?{...t,title:n}:t))},[t]);return react.useEffect(()=>{a&&d()},[a,d]),{chats:s,isLoading:c,error:u,refetch:d,deleteChat:p,renameChat:h}},exports.useDypai=useDypai,exports.useDypaiClient=useDypaiClient,exports.useEndpoint=function(e,t={}){const n=useDypaiClient(),{params:r,enabled:a=!0,refetchInterval:s=0}=t,[o,c]=react.useState(null),[i,u]=react.useState(null),[l,d]=react.useState(!0),[p,h]=react.useState(!1),f=react.useRef(!0),y=JSON.stringify(r||{}),m=react.useCallback(async(t=!1)=>{if(!a)return void d(!1);t?h(!0):d(!0),u(null);const{data:s,error:o}=await n.api.get(e,{params:r});f.current&&(o?(u(o),c(null)):c(s),d(!1),h(!1))},[n,e,y,a]);return react.useEffect(()=>(f.current=!0,m(),()=>{f.current=!1}),[m]),react.useEffect(()=>{if(!s||!a)return;const e=setInterval(()=>m(!0),s);return()=>clearInterval(e)},[m,s,a]),{data:o,error:i,isLoading:l,isRefetching:p,refetch:react.useCallback(async()=>{await m(!0)},[m])}},exports.useRealtime=useRealtime,exports.useUpload=function(e,t={}){const n=useDypaiClient(),{onSuccess:r,onError:a}=t,[s,o]=react.useState(0),[c,i]=react.useState(!1),[u,l]=react.useState(null),[d,p]=react.useState(null);return{upload:react.useCallback(async(t,s)=>{i(!0),o(0),l(null);const{data:c,error:u}=await n.api.upload(e,t,{params:s,onProgress:o});return i(!1),u?(l(u),a?.(u),{data:null,error:u}):(p(c),r?.(c),{data:c,error:null})},[n,e,r,a]),progress:s,isUploading:c,error:u,data:d,reset:react.useCallback(()=>{o(0),i(!1),l(null),p(null)},[])}};
|
package/package.json
CHANGED