@dypai-ai/client-sdk 1.3.0 → 1.5.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 +24 -0
- package/dist/index.d.ts +149 -7
- package/dist/index.esm.js +1 -1
- package/dist/index.js +1 -1
- package/dist/react/index.d.ts +212 -2
- package/dist/react/index.esm.js +1 -1
- package/dist/react/index.js +1 -1
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@dypai-ai/client-sdk` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.5.0] - 2026-04-09
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- **Authentication stack**: Client auth flows are aligned with the DYPAI **better-auth** backend (replacing the previous GoTrue-style integration). Session handling and token refresh remain compatible with `createClient()`; ensure your project’s auth API matches the current DYPAI control plane.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Auth (`AuthModule`)**: `signInWithOtp`, `signInWithMagicLink`, TOTP **2FA** (`enableTwoFactor`, `verifyTwoFactor`, `disableTwoFactor`), and **organizations** (`createOrganization`, `listOrganizations`, `inviteMember`).
|
|
14
|
+
- **React**: `useAuth` exposes the new auth and org helpers; **`useChat`** / **`useChatList`** for AI chat UIs (message streaming, sessions, tool calls).
|
|
15
|
+
|
|
16
|
+
### Dependencies
|
|
17
|
+
|
|
18
|
+
- `better-auth` added as a runtime dependency for typed client usage where applicable.
|
|
19
|
+
|
|
20
|
+
## [1.4.0] - 2026-04-03
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **Realtime**: WebSocket client aligned with the DYPAI engine (`/realtime?token=…`).
|
|
25
|
+
- `DypaiClient.realtime` — `DypaiRealtimeClient` with Supabase-style `.channel()` / `.subscribe()` for `postgres_changes`, broadcast, and presence
|
|
26
|
+
- React: `useRealtime`, `useChannel`, `useChannelMessages`, `useChannels` (shared single WebSocket via `realtimeShared`)
|
|
27
|
+
- Exported types: `DypaiRealtimeClient`, `RealtimeChannel`, `RealtimeEvent`, `SubscribeState`, and related channel/presence types
|
|
28
|
+
|
|
5
29
|
## [1.1.0] - 2026-03-27
|
|
6
30
|
|
|
7
31
|
### Added
|
package/dist/index.d.ts
CHANGED
|
@@ -798,9 +798,11 @@ declare class AuthModule {
|
|
|
798
798
|
/**
|
|
799
799
|
* Reenvía el email de confirmación de registro.
|
|
800
800
|
* Útil cuando el usuario no recibió el email original o este expiró.
|
|
801
|
-
*
|
|
801
|
+
* Reenvía el email de verificación.
|
|
802
802
|
*/
|
|
803
|
-
resendConfirmationEmail(email: string
|
|
803
|
+
resendConfirmationEmail(email: string, options?: {
|
|
804
|
+
redirectTo?: string;
|
|
805
|
+
}): Promise<DypaiResponse<{
|
|
804
806
|
message: string;
|
|
805
807
|
}>>;
|
|
806
808
|
/**
|
|
@@ -817,12 +819,14 @@ declare class AuthModule {
|
|
|
817
819
|
* Envía un código OTP (One Time Password) al email o teléfono indicado.
|
|
818
820
|
* Utilizado para login sin contraseña (Magic Link o SMS OTP).
|
|
819
821
|
*/
|
|
820
|
-
signInWithOtp(credentials: OTPRequest): Promise<DypaiResponse<
|
|
822
|
+
signInWithOtp(credentials: OTPRequest): Promise<DypaiResponse<{
|
|
823
|
+
status: boolean;
|
|
824
|
+
}>>;
|
|
821
825
|
/**
|
|
822
826
|
* Verifica un código OTP enviado por email o teléfono.
|
|
823
827
|
* Si es válido, inicia la sesión del usuario.
|
|
824
828
|
*/
|
|
825
|
-
verifyOtp(
|
|
829
|
+
verifyOtp(_params: VerifyOTPRequest): Promise<DypaiResponse<AuthResponse>>;
|
|
826
830
|
/**
|
|
827
831
|
* Actualiza los datos del usuario actual (email, contraseña o metadatos).
|
|
828
832
|
* Requiere que el usuario esté autenticado.
|
|
@@ -832,7 +836,46 @@ declare class AuthModule {
|
|
|
832
836
|
* Establece o actualiza la contraseña del usuario autenticado actual.
|
|
833
837
|
* Útil para flujos de invitación o recuperación donde el usuario llega con token válido.
|
|
834
838
|
*/
|
|
835
|
-
setPassword(password: string): Promise<DypaiResponse<
|
|
839
|
+
setPassword(password: string): Promise<DypaiResponse<{
|
|
840
|
+
status: boolean;
|
|
841
|
+
}>>;
|
|
842
|
+
/**
|
|
843
|
+
* Envía un magic link al email del usuario para login sin contraseña.
|
|
844
|
+
*/
|
|
845
|
+
signInWithMagicLink(email: string, options?: {
|
|
846
|
+
redirectTo?: string;
|
|
847
|
+
}): Promise<DypaiResponse<{
|
|
848
|
+
message: string;
|
|
849
|
+
}>>;
|
|
850
|
+
/**
|
|
851
|
+
* Habilita 2FA para el usuario actual. Devuelve el TOTP URI para escanear con una app.
|
|
852
|
+
*/
|
|
853
|
+
enableTwoFactor(): Promise<DypaiResponse<{
|
|
854
|
+
totpURI: string;
|
|
855
|
+
backupCodes: string[];
|
|
856
|
+
}>>;
|
|
857
|
+
/**
|
|
858
|
+
* Verifica un código TOTP de 2FA durante el login.
|
|
859
|
+
*/
|
|
860
|
+
verifyTwoFactor(code: string): Promise<DypaiResponse<AuthResponse>>;
|
|
861
|
+
/**
|
|
862
|
+
* Desactiva 2FA para el usuario actual.
|
|
863
|
+
*/
|
|
864
|
+
disableTwoFactor(): Promise<DypaiResponse<{
|
|
865
|
+
message: string;
|
|
866
|
+
}>>;
|
|
867
|
+
/**
|
|
868
|
+
* Crea una organización.
|
|
869
|
+
*/
|
|
870
|
+
createOrganization(name: string, slug?: string): Promise<DypaiResponse<any>>;
|
|
871
|
+
/**
|
|
872
|
+
* Invita a un miembro a una organización por email.
|
|
873
|
+
*/
|
|
874
|
+
inviteMember(organizationId: string, email: string, role?: string): Promise<DypaiResponse<any>>;
|
|
875
|
+
/**
|
|
876
|
+
* Lista las organizaciones del usuario actual.
|
|
877
|
+
*/
|
|
878
|
+
listOrganizations(): Promise<DypaiResponse<any[]>>;
|
|
836
879
|
/**
|
|
837
880
|
* Inicia el auto-refresh basado en ticks.
|
|
838
881
|
* Cada 30s verifica si el token está próximo a expirar y lo refresca.
|
|
@@ -850,6 +893,7 @@ declare class AuthModule {
|
|
|
850
893
|
*/
|
|
851
894
|
private _autoRefreshTick;
|
|
852
895
|
private _normalizeUser;
|
|
896
|
+
private _buildAuthResponse;
|
|
853
897
|
private _handleAuthResponse;
|
|
854
898
|
private _updateUser;
|
|
855
899
|
private _clearSession;
|
|
@@ -870,6 +914,102 @@ declare class AuthModule {
|
|
|
870
914
|
handleSessionExpired(): void;
|
|
871
915
|
}
|
|
872
916
|
|
|
917
|
+
/**
|
|
918
|
+
* DYPAI Realtime Client — Supabase-compatible API for real-time features.
|
|
919
|
+
*
|
|
920
|
+
* Supports:
|
|
921
|
+
* - Database changes (postgres_changes): INSERT/UPDATE/DELETE notifications
|
|
922
|
+
* - Broadcast: ephemeral client-to-client messaging
|
|
923
|
+
* - Presence: who's online tracking
|
|
924
|
+
*
|
|
925
|
+
* Usage:
|
|
926
|
+
* const channel = dypai.realtime.channel('room-1')
|
|
927
|
+
* channel
|
|
928
|
+
* .on('postgres_changes', { event: '*', table: 'tasks' }, callback)
|
|
929
|
+
* .on('broadcast', { event: 'typing' }, callback)
|
|
930
|
+
* .on('presence', { event: 'sync' }, callback)
|
|
931
|
+
* .subscribe()
|
|
932
|
+
*/
|
|
933
|
+
type RealtimeEvent = 'postgres_changes' | 'broadcast' | 'presence';
|
|
934
|
+
type SubscribeState = 'SUBSCRIBED' | 'TIMED_OUT' | 'CLOSED' | 'CHANNEL_ERROR';
|
|
935
|
+
type PresenceEvent = 'sync' | 'join' | 'leave' | '*';
|
|
936
|
+
type DbEvent = 'INSERT' | 'UPDATE' | 'DELETE' | '*';
|
|
937
|
+
interface DbChangeFilter {
|
|
938
|
+
event: DbEvent;
|
|
939
|
+
schema?: string;
|
|
940
|
+
table?: string;
|
|
941
|
+
filter?: string;
|
|
942
|
+
}
|
|
943
|
+
interface BroadcastFilter {
|
|
944
|
+
event: string;
|
|
945
|
+
}
|
|
946
|
+
interface PresenceFilter {
|
|
947
|
+
event: PresenceEvent;
|
|
948
|
+
}
|
|
949
|
+
interface PresenceEntry {
|
|
950
|
+
[key: string]: any;
|
|
951
|
+
}
|
|
952
|
+
interface ChannelConfig {
|
|
953
|
+
broadcast?: {
|
|
954
|
+
self?: boolean;
|
|
955
|
+
ack?: boolean;
|
|
956
|
+
};
|
|
957
|
+
presence?: {
|
|
958
|
+
key?: string;
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
declare class RealtimeChannel {
|
|
962
|
+
readonly topic: string;
|
|
963
|
+
private ws;
|
|
964
|
+
private bindings;
|
|
965
|
+
private config;
|
|
966
|
+
private client;
|
|
967
|
+
private _presenceState;
|
|
968
|
+
private _subscribed;
|
|
969
|
+
private _subscribedCallback?;
|
|
970
|
+
private _messageHandler?;
|
|
971
|
+
constructor(topic: string, client: DypaiRealtimeClient, config?: ChannelConfig);
|
|
972
|
+
on(type: 'postgres_changes', filter: DbChangeFilter, callback: (payload: any) => void): this;
|
|
973
|
+
on(type: 'broadcast', filter: BroadcastFilter, callback: (payload: any) => void): this;
|
|
974
|
+
on(type: 'presence', filter: PresenceFilter, callback: (payload: any) => void): this;
|
|
975
|
+
subscribe(callback?: (status: SubscribeState, err?: Error) => void): this;
|
|
976
|
+
/** @internal */
|
|
977
|
+
_resubscribe(ws: WebSocket): void;
|
|
978
|
+
send(args: {
|
|
979
|
+
type: 'broadcast';
|
|
980
|
+
event: string;
|
|
981
|
+
payload?: any;
|
|
982
|
+
}): Promise<'ok' | 'timed out' | 'error'>;
|
|
983
|
+
track(payload: Record<string, any>): Promise<'ok' | 'error'>;
|
|
984
|
+
untrack(): Promise<'ok' | 'error'>;
|
|
985
|
+
presenceState(): Record<string, PresenceEntry[]>;
|
|
986
|
+
unsubscribe(): Promise<'ok'>;
|
|
987
|
+
private _handleMessage;
|
|
988
|
+
private _parseFilter;
|
|
989
|
+
}
|
|
990
|
+
declare class DypaiRealtimeClient {
|
|
991
|
+
private baseUrl;
|
|
992
|
+
private tokenProvider;
|
|
993
|
+
private ws;
|
|
994
|
+
private channels;
|
|
995
|
+
private heartbeatTimer;
|
|
996
|
+
private reconnectAttempt;
|
|
997
|
+
private reconnectTimer;
|
|
998
|
+
private static RECONNECT_INTERVALS;
|
|
999
|
+
constructor(baseUrl: string, tokenProvider: () => string | null);
|
|
1000
|
+
channel(topic: string, config?: {
|
|
1001
|
+
config?: ChannelConfig;
|
|
1002
|
+
}): RealtimeChannel;
|
|
1003
|
+
getChannels(): RealtimeChannel[];
|
|
1004
|
+
removeChannel(channel: RealtimeChannel): Promise<void>;
|
|
1005
|
+
removeAllChannels(): Promise<void>;
|
|
1006
|
+
/** Update auth token. Reconnects with new token if currently connected. */
|
|
1007
|
+
setAuth(token?: string | null): Promise<void>;
|
|
1008
|
+
disconnect(): void;
|
|
1009
|
+
/** @internal */
|
|
1010
|
+
_getOrCreateWs(): WebSocket;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
873
1013
|
interface CreateUserRequest {
|
|
874
1014
|
email: string;
|
|
875
1015
|
password: string;
|
|
@@ -981,6 +1121,8 @@ declare class DypaiClient<TDatabase = any, TApi extends EndpointMap = EndpointMa
|
|
|
981
1121
|
users: UsersModule;
|
|
982
1122
|
/** Cliente API tipado para endpoints personalizados */
|
|
983
1123
|
api: TApi extends EndpointMap ? TypedApiClient<TApi> : ApiClient;
|
|
1124
|
+
/** Realtime client for database changes, broadcast, and presence */
|
|
1125
|
+
realtime: DypaiRealtimeClient;
|
|
984
1126
|
private _baseUrl;
|
|
985
1127
|
private _apiKey?;
|
|
986
1128
|
/** Get the base URL of the DYPAI engine */
|
|
@@ -1192,5 +1334,5 @@ declare const PACKAGE_INFO: {
|
|
|
1192
1334
|
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)"];
|
|
1193
1335
|
};
|
|
1194
1336
|
|
|
1195
|
-
export { DirectDBModule, DirectQueryBuilder, DypaiClient, DypaiError, PACKAGE_INFO, callApi, configureApiService, configureDypaiServices, createApiClient, createClient, getGlobalConfig, reloadDypaiConfig, resetGlobalConfig, setToastFunction, setTokenProvider, toast, toastError, toastInfo, toastSuccess, toastWarning, useToast };
|
|
1196
|
-
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 };
|
|
1337
|
+
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 };
|
|
1338
|
+
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 g(){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 S=t=>(w||m)(t),D=(t,e)=>S({title:t,description:e,variant:"success"}),v=(t,e)=>S({title:t,description:e,variant:"error"}),A=(t,e)=>S({title:t,description:e,variant:"warning"}),I=(t,e)=>S({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 g=(async()=>{const e=_().toast||S,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,g),g}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{get baseUrl(){return this.W}getAuthToken(){return this.auth.token||this.F||null}constructor(t){const{baseUrl:e,apiKey:i}=t;this.W=e,this.F=i;const 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:S,...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,S as toast,v as toastError,I as toastInfo,D as toastSuccess,A as toastWarning,g as useToast};
|
|
1
|
+
class t extends Error{constructor(t,e=500,i,s){super(t),this.status=e,this.code=i,this.details=s,this.name="DypaiError"}}function e(t){if(!t)return{};const e=t.user||t,i=e.name||e.user_metadata?.full_name||null,s=e.emailVerified??e.confirmed_at??null,n=e.app_metadata||{},r=e.user_metadata||{full_name:i};return{id:e.id,email:e.email,phone:e.phone||null,role:n.role||r.role||e.role||"authenticated",created_at:e.createdAt||e.created_at,updated_at:e.updatedAt||e.updated_at,confirmed_at:s?"boolean"==typeof s?(new Date).toISOString():s:null,last_sign_in_at:e.last_sign_in_at||null,app_metadata:n,user_metadata:{...r,full_name:i},roleDetails:{name:n.role||r.role||null,weight:0},appContext:{app_id:"default"}}}let i=!1;function s(t,...e){"error"===t?console.error(...e):i&&("warn"===t?console.warn(...e):console.log(...e))}async function n(e,i){let s={};try{s=await e.json()}catch{}const n=s.msg||s.error_description||s.message||i,r=s.error_code||s.error||s.code||void 0;return new t(n,e.status,r)}class r{constructor(){this.promise=new Promise((t,e)=>{this.resolve=t,this.reject=e})}}const a={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 o(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.v=null,this.D=0,this.A=0,this.storage=t.storage||a,i=!!t.debug;const n=t.storageKey||this.deriveStorageKey(t.apiKey);s("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${n})`),this.STORAGE_KEY=`dypai-${n}-auth-session`,this.k=this.P().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.T.bind(this)),window.addEventListener("focus",this.T.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(s("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.P(!1))}))}async T(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(s("log","[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this.P(!0),this.i&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}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 s("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.l.push(t),this.k.then(()=>{const e=this._();s("log",`[DYPAI SDK] 📣 INITIAL_SESSION para suscriptor (Sesión activa: ${!!e})`),t("INITIAL_SESSION",e)}).catch(e=>{s("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.l=this.l.filter(e=>e!==t)}}}}}async signInWithPassword(t){return o((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-in/email`,i={email:t.email||t.identifier||"",password:t.password},s=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(i)});if(!s.ok)throw await n(s,"Login failed");const r=await s.json(),a=this.O(r);return await this.$(a),a})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return o((async()=>{const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-up/email`,{email:r,phone:a,password:o,user_data:c,...h}=t,l=r||"",u=h.full_name||h.name||l.split("@")[0],d={...h,...c||{}},p={email:l,password:o,name:u};Object.keys(d).length>0&&Object.assign(p,d),e?.redirectTo?p.callbackURL=e.redirectTo:"undefined"!=typeof window&&(p.callbackURL=`${window.location.origin}/`);const f=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(p)});if(!f.ok)throw await n(f,"Registration failed");const w=await f.json(),y=this.O(w),m={...y,confirmationRequired:!y.token};return m.token?await this.$(m):s("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),m})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this.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&&(s("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>s("warn","[DYPAI SDK] Error refreshing session in getSession:",t))),{data:{access_token:this.i,refresh_token:this.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 o((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,i=await fetch(e,{headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw new t("Session invalid",i.status);const s=await i.json(),n=this.R(s);return this.N(n),n})())}async me(){return this.getUser()}async signInWithOAuth(e,i={}){return o((async()=>{if("undefined"==typeof window)throw new t("signInWithOAuth requiere un entorno de navegador (window no está disponible)",400);const{redirectTo:s=window.location.href,scopes:n}=i,r=this.config.baseUrl||"http://localhost:8000",a=new URLSearchParams({provider:e,callbackURL:s});n?.length&&a.set("scopes",n.join(" "));const o=`${r}/api/v0/auth/sign-in/social?${a.toString()}`;window.location.href=o})())}async signOut(){return o((async()=>{try{if(this.i){const t=this.config.baseUrl||"http://localhost:8000";await fetch(`${t}/api/v0/auth/sign-out`,{method:"POST",headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}})}}finally{this.K("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return o((async()=>{const i=this.config.baseUrl||"http://localhost:8000",s={email:t};e?.redirectTo?s.redirectTo=e.redirectTo:"undefined"!=typeof window&&(s.redirectTo=`${window.location.origin}/`);const r=await fetch(`${i}/api/v0/auth/request-password-reset`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!r.ok)throw await n(r,"Recovery failed");return await r.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t,e){return o((async()=>{const i=this.config.baseUrl||"http://localhost:8000",s={email:t};e?.redirectTo?s.callbackURL=e.redirectTo:"undefined"!=typeof window&&(s.callbackURL=`${window.location.origin}/`);const r=await fetch(`${i}/api/v0/auth/send-verification-email`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(s)});if(!r.ok)throw await n(r,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.S)return s("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.S.promise;const e=new r;this.S=e;const i=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const t=await async function(t,e,i){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const s=new AbortController,n=setTimeout(()=>s.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:s.signal},async()=>(clearTimeout(n),await i()))}catch(s){if("AbortError"===s.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await i();throw s}}return await i()}(i,c.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.U());e.resolve(t)}catch(i){const s=i instanceof t?i:new t(i.message||"Error refrescando sesión",401);e.resolve({data:null,error:s})}finally{this.S=null}})(),e.promise}async U(){try{const e=await this.storage.getItem(this.STORAGE_KEY);if(e){const t=JSON.parse(e),i=t.expires_at||0,n=Math.floor(Date.now()/1e3);if(t.access_token!==this.i&&i-n>60)return s("log","[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.i=t.access_token,this.o=t.refresh_token,this.h=t.expires_at,this.t=t.user,this.D=0,this.I("TOKEN_REFRESHED"),{data:{token:this.i,refreshToken:this.o||void 0,expiresAt:this.h||void 0,user:this.t},error:null}}if(!this.i)throw new t("No hay sesión activa",401);s("log","[DYPAI SDK] 🔄 Verificando sesión activa...");const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,n=new AbortController,r=setTimeout(()=>n.abort(),c.REFRESH_TIMEOUT_MS);let a;try{a=await fetch(i,{method:"GET",headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},signal:n.signal})}finally{clearTimeout(r)}if(!a.ok){let e={};try{e=await a.json()}catch{}const i=a.status,n=e.error||e.code||"",r=(e.error_description||e.msg||"").toLowerCase(),o="invalid_grant"===n||r.includes("invalid refresh token")||r.includes("revoked");throw i>=400&&i<500&&o?(s("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.K(`refreshSession failed (${i}: ${n})`)):s("warn",`[DYPAI SDK] ⚠️ Fallo en refresco (${i}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new t(e.msg||e.error_description||"Refresh session failed",i)}const o=await a.json(),h=this.O(o,this.i);return this.D=0,await this.$(h),s("log","[DYPAI SDK] ✅ Session check exitoso."),{data:h,error:null}}catch(e){this.D++,this.A=Date.now();const i=e instanceof DOMException&&"AbortError"===e.name,n=i?new t("Refresh token timeout (servidor no responde)",408):e instanceof t?e:new t(e.message||"Error refrescando sesión",401),r=Math.min(c.RETRY_BASE_MS*Math.pow(2,this.D),c.MAX_RETRY_MS);return i?s("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${c.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${r}ms (intento ${this.D})`):s("error","[DYPAI SDK] ❌ Refresh falló:",n.message,`(status: ${n.status}). Backoff: ${r}ms (intento ${this.D})`),this.D>=c.MAX_REFRESH_FAILURES&&(s("error",`[DYPAI SDK] 🗑️ ${this.D} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.K(`${this.D} consecutive refresh failures`),this.D=0),{data:null,error:n}}}async signInWithOtp(e){return o((async()=>{if(e.phone)throw new t("Phone OTP is not supported by the current DYPAI auth backend",400);if(!e.email)throw new t("Email is required for magic link sign-in",400);const i=await this.signInWithMagicLink(e.email);if(i.error)throw i.error;return{status:!0}})())}async verifyOtp(e){return o((async()=>{throw new t("OTP verification is not supported by the current DYPAI auth backend. Use magic link callbacks instead.",400)})())}async updateUser(e){return o((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/update-user`,s=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(e)});if(!s.ok){const e=await s.json();throw new t(e.detail||"Update user failed",s.status)}const n=await s.json(),r=this.R(n);return this.N(r),r})())}async setPassword(e){return o((async()=>{if("undefined"==typeof window)throw new t("setPassword requiere un entorno de navegador",400);const i=new URLSearchParams(window.location.search).get("token");if(!i)throw new t("No reset token found in the current URL",400);const s=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${s}/api/v0/auth/reset-password`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({newPassword:e,token:i})});if(!r.ok)throw await n(r,"Reset password failed");return await r.json()})())}async signInWithMagicLink(t,e){return o((async()=>{const i=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${i}/api/v0/auth/sign-in/magic-link`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({email:t,callbackURL:e?.redirectTo})});if(!s.ok)throw await n(s,"Magic link failed");return{message:"Magic link sent"}})())}async enableTwoFactor(){return o((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/two-factor/enable`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw await n(i,"Enable 2FA failed");return await i.json()})())}async verifyTwoFactor(t){return o((async()=>{const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/two-factor/verify-totp`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({code:t})});if(!i.ok)throw await n(i,"2FA verification failed");const s=await i.json(),r={token:s.token||this.i,expiresIn:604800,expiresAt:Math.floor(Date.now()/1e3)+604800,user:this.R(s)};return await this.$(r),r})())}async disableTwoFactor(){return o((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/two-factor/disable`,{method:"POST",headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw await n(i,"Disable 2FA failed");return{message:"2FA disabled"}})())}async createOrganization(e,i){return o((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const s=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${s}/api/v0/auth/organization/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({name:e,slug:i})});if(!r.ok)throw await n(r,"Create organization failed");return await r.json()})())}async inviteMember(e,i,s){return o((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const r=this.config.baseUrl||"http://localhost:8000",a=await fetch(`${r}/api/v0/auth/organization/invite-member`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({organizationId:e,email:i,role:s||"member"})});if(!a.ok)throw await n(a,"Invite member failed");return await a.json()})())}async listOrganizations(){return o((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/organization/list`,{headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw await n(i,"List organizations failed");return await i.json()})())}startAutoRefresh(){this.stopAutoRefresh(),s("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&&(s("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}s("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){s("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}R(t){return t?e(t):(s("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}O(t,e){const i="number"==typeof t?.expiresIn?t.expiresIn:"number"==typeof t?.expires_in?t.expires_in:604800;return{token:t?.token||e,refreshToken:t?.refreshToken||t?.refresh_token||t?.session?.refreshToken||t?.session?.refresh_token||void 0,expiresIn:i,expiresAt:Math.floor(Date.now()/1e3)+i,user:this.R(t)}}async $(t){this.u=null,t.token&&(this.i=t.token,void 0!==t.refreshToken&&(this.o=t.refreshToken||null),void 0!==t.expiresAt&&(this.h=t.expiresAt||null),await this.N(t.user,t.token,t.refreshToken,t.expiresAt),s("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.h}, has_refresh: ${!!this.o})`),this.startAutoRefresh())}async N(t,e,i,n){this.t=t,e&&(this.i=e),void 0!==i&&(this.o=i||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 s("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){s("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}if(this.m){const t=e?"SIGNED_IN":"USER_UPDATED";this.I(t)}}async K(t="unknown"){s("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),s("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){s("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.I("SIGNED_OUT")}async P(t=!0){s("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.C())return void s("log","[DYPAI SDK] ✅ Sesión establecida desde callback URL. Saltando recuperación de localStorage.");const e=await this.storage.getItem(this.STORAGE_KEY);if(!e)return void s("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let t;s("log","[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");try{t=JSON.parse(e)}catch{return console.warn("[DYPAI SDK] ⚠️ Sesión en storage corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!t||"object"!=typeof t||"string"!=typeof t.access_token)return console.warn("[DYPAI SDK] ⚠️ Sesión en storage con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);this.i=t.access_token,this.o="string"==typeof t.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}const i=Math.floor(Date.now()/1e3),n=this.h&&this.h<=i;if(s("log",`[DYPAI SDK] 🕒 Token expira en: ${this.h}. Ahora es: ${i}. Diferencia: ${(this.h||0)-i}s`),n&&this.o&&t){s("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void s("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.h&&s("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.p="SIGNED_IN"}catch(t){s("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async C(){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 s("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.I("AUTH_ERROR"),!0}const i=e.get("access_token");if(!i)return!1;const n=e.get("refresh_token"),r=e.get("type"),a=parseInt(e.get("expires_in")||"3600",10);s("log",`[DYPAI SDK] 🔗 Auth callback detectado en URL (type: ${r||"unknown"}). Procesando...`);try{r&&sessionStorage.setItem("dypai-auth-callback-type",r),"recovery"!==r&&"invite"!==r||sessionStorage.setItem("dypai-auth-callback-redirect","PASSWORD_RECOVERY")}catch{}window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.i=i,this.o=n||null,this.h=Math.floor(Date.now()/1e3)+a;const o=this.config.baseUrl||"http://localhost:8000",c=await fetch(`${o}/api/v0/auth/get-session`,{headers:{Authorization:`Bearer ${i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!c.ok)return s("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.I("AUTH_ERROR"),!0;const h=await c.json(),l=this.R(h),u={token:i,refreshToken:n||void 0,expiresIn:a,expiresAt:this.h,user:l};return await this.$(u),this.p="recovery"===r||"invite"===r?"PASSWORD_RECOVERY":"SIGNED_IN",s("log",`[DYPAI SDK] 🔑 Callback type=${r}. Pending event: ${this.p}`),!0}catch(t){return s("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}I(t="USER_UPDATED"){const e=this._();s("log",`[DYPAI SDK] 📢 Notificando a ${this.l.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.l.forEach(i=>i(t,e))}handleSessionExpired(){this.K("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,i){this.ws=null,this.bindings=[],this.L={},this.j=!1,this.topic=t,this.client=e,this.config=i||{}}on(t,e,i){return this.bindings.push({type:t,filter:e,callback:i}),this}subscribe(t){this.J=t;const e=this.client.B();this.ws=e;const i=()=>{const i=this.bindings.filter(t=>"postgres_changes"===t.type);for(const t of i)e.send(JSON.stringify({type:"subscribe",table:t.filter.table||"*",event:t.filter.event||"*",filter:t.filter.filter?this.q(t.filter.filter):void 0,schema:t.filter.schema||"public"}));const s=this.bindings.some(t=>"broadcast"===t.type),n=this.bindings.some(t=>"presence"===t.type);(s||n)&&e.send(JSON.stringify({type:"join",channel:this.topic,presence:!!n||void 0})),this.j=!0,t?.("SUBSCRIBED")};e.readyState===WebSocket.OPEN?i():e.readyState===WebSocket.CONNECTING&&e.addEventListener("open",i,{once:!0}),this.M&&e.removeEventListener("message",this.M);const s=t=>{try{const e=JSON.parse("string"==typeof t.data?t.data:"");this.W(e)}catch{}};return this.M=s,e.addEventListener("message",s),e.addEventListener("close",()=>{this.j=!1,t?.("CLOSED")},{once:!0}),e.addEventListener("error",()=>{t?.("CHANNEL_ERROR",new Error("WebSocket error"))},{once:!0}),this}F(t){const e=this.bindings.filter(t=>"postgres_changes"===t.type);for(const i of e)t.send(JSON.stringify({type:"subscribe",table:i.filter.table||"*",event:i.filter.event||"*",filter:i.filter.filter?this.q(i.filter.filter):void 0,schema:i.filter.schema||"public"}));const i=this.bindings.some(t=>"broadcast"===t.type),s=this.bindings.some(t=>"presence"===t.type);(i||s)&&t.send(JSON.stringify({type:"join",channel:this.topic,presence:!!s||void 0})),this.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.L}}async unsubscribe(){if(this.M&&this.ws&&(this.ws.removeEventListener("message",this.M),this.M=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.L={},"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.L={};for(const[i,s]of Object.entries(t.presences||{})){const t=s[e]||i;this.L[t]||(this.L[t]=[]),this.L[t].push(s)}for(const t of this.bindings)"presence"===t.type&&("sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"}))}if("presence_diff"===t.type&&t.channel===this.topic){const e=this.config.presence?.key||"userId";if(t.joins)for(const[i,s]of Object.entries(t.joins)){const t=s[e]||i;this.L[t]||(this.L[t]=[]),this.L[t].push(s);for(const e of this.bindings)"presence"===e.type&&("join"!==e.filter.event&&"*"!==e.filter.event||e.callback({event:"join",key:t,newPresences:[s],currentPresences:this.L[t]}))}if(t.leaves)for(const[i,s]of Object.entries(t.leaves)){const t=s[e]||i;this.L[t]=(this.L[t]||[]).filter(t=>t.userId!==i),0===this.L[t].length&&delete this.L[t];for(const e of this.bindings)"presence"===e.type&&("leave"!==e.filter.event&&"*"!==e.filter.event||e.callback({event:"leave",key:t,leftPresences:[s],currentPresences:this.L[t]||[]}))}for(const t of this.bindings)"presence"!==t.type||"sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"})}}q(t){const e=t.match(/^(\w+)=eq\.(.+)$/);if(e)return{[e[1]]:e[2]}}}class l{constructor(t,e){this.ws=null,this.channels=new Map,this.heartbeatTimer=null,this.reconnectAttempt=0,this.reconnectTimer=null,this.baseUrl=t,this.tokenProvider=e}channel(t,e){const i=this.channels.get(t);if(i)return i;const s=new h(t,this,e?.config);return this.channels.set(t,s),s}getChannels(){return Array.from(this.channels.values())}async removeChannel(t){await t.unsubscribe(),this.channels.delete(t.topic),0===this.channels.size&&this.disconnect()}async removeAllChannels(){for(const t of this.channels.values())await t.unsubscribe();this.channels.clear(),this.disconnect()}async setAuth(t){t&&(this.tokenProvider=()=>t),this.ws&&this.channels.size>0&&(this.disconnect(),this.B())}disconnect(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.ws?.close(),this.ws=null}B(){if(this.ws&&(this.ws.readyState===WebSocket.OPEN||this.ws.readyState===WebSocket.CONNECTING))return this.ws;const t=this.tokenProvider(),e=this.baseUrl.replace(/^http/,"ws")+`/realtime?token=${encodeURIComponent(t||"")}`,i=new WebSocket(e);return this.ws=i,i.addEventListener("open",()=>{this.reconnectAttempt=0;for(const t of this.channels.values())t.j&&t.F(i);this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>{i.readyState===WebSocket.OPEN&&i.send(JSON.stringify({type:"heartbeat"}))},25e3)}),i.addEventListener("close",()=>{if(this.ws=null,this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.channels.size>0&&!this.reconnectTimer){const t=l.RECONNECT_INTERVALS[Math.min(this.reconnectAttempt,l.RECONNECT_INTERVALS.length-1)];this.reconnectAttempt++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.channels.size>0&&this.B()},t)}}),i}}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 w(t,this.api)}async sql(t,e,i){return this.api.post("/api/v0/admin/db/sql",{query:t,params:e||[],...void 0!==i?.limit&&{limit:i.limit}})}}class w{constructor(t,e){this.table=t,this.api=e,this.H="public",this.V=[],this.X=100,this.Z=0,this.tt="ASC"}schema(t){return this.H=t,this}eq(t,e){return this.V.push({column:t,operator:"eq",value:e}),this}neq(t,e){return this.V.push({column:t,operator:"neq",value:e}),this}gt(t,e){return this.V.push({column:t,operator:"gt",value:e}),this}gte(t,e){return this.V.push({column:t,operator:"gte",value:e}),this}lt(t,e){return this.V.push({column:t,operator:"lt",value:e}),this}lte(t,e){return this.V.push({column:t,operator:"lte",value:e}),this}like(t,e){return this.V.push({column:t,operator:"ilike",value:e}),this}ilike(t,e){return this.like(t,e)}isNull(t){return this.V.push({column:t,operator:"is_null",value:null}),this}is(t,e){return this.isNull(t)}notNull(t){return this.V.push({column:t,operator:"not_null",value:null}),this}in(t,e){return this.V.push({column:t,operator:"in",value:e}),this}contains(t,e){return this.V.push({column:t,operator:"array_contains",value:e}),this}containedBy(t,e){return this.V.push({column:t,operator:"contained_by",value:e}),this}overlaps(t,e){return this.V.push({column:t,operator:"overlaps",value:e}),this}textSearch(t,e){return this.V.push({column:t,operator:"fts",value:e}),this}phraseSearch(t,e){return this.V.push({column:t,operator:"phfts",value:e}),this}not(t,e,i){return this.V.push({column:t,operator:"not",value:{operator:e,value:i}}),this}or(t){return this.V.push({operator:"or",or:t}),this}limit(t){return this.X=t,this}offset(t){return this.Z=t,this}orderBy(t,e="ASC"){return this.et=t,this.tt=e,this}async select(t){const e=[...this.V];let i=this.it;if("string"==typeof t)i=t;else if(t&&"object"==typeof t)for(const[i,s]of Object.entries(t))e.push({column:i,operator:"eq",value:s});return this.execute("select",{...i&&{columns:i},filters:e,limit:this.X,offset:this.Z,sort_by:this.et,order:this.tt})}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.V],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 i=0;i<t.length;i+=p){const s=t.slice(i,i+p),{data:n,error:r}=await this.execute("insert",{data:s,mode:"bulk"});if(r)return{data:null,error:r};n&&e.push(...Array.isArray(n)?n:[n])}return{data:e,error:null}}const e=Array.isArray(t)?"bulk":"single";return this.execute("insert",{data:t,mode:e})}async update(e){return 0===this.V.length?{data:null,error:new t("Update requires at least one filter. Use .eq(), .in(), etc.",400)}:this.execute("update",{data:e,filters:this.V})}async delete(){return 0===this.V.length?{data:null,error:new t("Delete requires at least one filter.",400)}:this.execute("delete",{filters:this.V})}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.H,table_name:this.table,...e})}}class y{constructor(t){this.api=t}async list(t={}){const i=await this.api.get("admin/users",{params:t});return i.data?.users&&(i.data.users=i.data.users.map(e)),i}async create(t){const i=await this.api.post("admin/users",t);return i.data&&(i.data=e(i.data)),i}async update(t,i){const s=await this.api.put(`admin/users/${t}`,i);return s.data&&(s.data=e(s.data)),s}async delete(t){return this.api.delete(`admin/users/${t}`)}}let m=null;function S(t){m=t}const g=t=>{const{title:e,description:i,variant:s="default"}=t,n=`${"error"===s?"❌":"success"===s?"✅":"warning"===s?"⚠️":"info"===s?"ℹ️":"📢"} ${e}${i?`: ${i}`:""}`;"error"===s?console.error(n):"warning"===s&&console.warn(n)};function v(){const t=m||g;return{toast:t,toastSuccess:(e,i)=>t({title:e,description:i,variant:"success"}),toastError:(e,i)=>t({title:e,description:i,variant:"error"}),toastWarning:(e,i)=>t({title:e,description:i,variant:"warning"}),toastInfo:(e,i)=>t({title:e,description:i,variant:"info"})}}const b=t=>(m||g)(t),D=(t,e)=>b({title:t,description:e,variant:"success"}),A=(t,e)=>b({title:t,description:e,variant:"error"}),k=(t,e)=>b({title:t,description:e,variant:"warning"}),P=(t,e)=>b({title:t,description:e,variant:"info"});let I={},T=null;function E(t){T=t}function _(t){I={...I,...t}}const O=new Map;function $(){let t=I;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...I}}catch(t){}return t}async function R(e,i,s,n,r,a,o,c,h){const l=$(),u=c||null;if(!u&&!s.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof s||!s.trim())throw new Error("Endpoint debe ser un string válido");let d;if(s.startsWith("http"))d=s;else{const t=u.replace(/\/+$/,"");d=s.startsWith("/")?t+s:t+"/api/v0/"+s}if(a&&Object.keys(a).length>0){const t=new URLSearchParams,e=(i,s)=>{null!=s&&(Array.isArray(s)?s.forEach((t,s)=>e(`${i}[${s}]`,t)):"object"==typeof s?Object.entries(s).forEach(([t,s])=>e(`${i}[${t}]`,s)):t.append(i,String(s)))};Object.entries(a).forEach(([t,i])=>e(t,i));const i=t.toString();i&&(d+=`?${i}`)}const p="GET"===i?`${i}:${d}:${JSON.stringify(n)}`:null;if(p&&O.has(p))return O.get(p);const f=n instanceof FormData,w={...l.headers||{},...f?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...o&&{"x-api-key":o}},y={method:i,headers:w,credentials:"include"};n&&"GET"!==i&&"DELETE"!==i&&(y.body=f?n:JSON.stringify(n));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||b,u=(void 0!==r?r:!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 R(t,i,s,n,r,a,o,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&&O.delete(p)}})();return p&&O.set(p,S),S}async function N(t,e,i,s){const n=s?.method||(i?"POST":"GET"),{data:r,error:a}=await("GET"===n?t.get(e,{params:s?.params}):t.post(e,i,{params:s?.params}));if(a)throw a;if(r instanceof Blob){const t=window.URL.createObjectURL(r),e=document.createElement("a");e.href=t,e.download=s?.fileName||"archivo-descargado",document.body.appendChild(e),e.click(),window.URL.revokeObjectURL(t),document.body.removeChild(e)}else if(r&&"object"==typeof r&&("url"in r||"signed_url"in r||"signedUrl"in r)){const t=r.url||r.signed_url||r.signedUrl;if("string"==typeof t){const e=new URL(t).searchParams.get("response-content-disposition");let i;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(i=t[1].replace(/"/g,""))}const n=s?.fileName||i;if(n){const e=document.createElement("a");e.href=t,e.download=n,document.body.appendChild(e),e.click(),document.body.removeChild(e)}else window.open(t,"_blank")}}}async function K(e,i,s,n){const{data:r,error:a}=await e.post(i,{file_path:s.name,content_type:s.type||"application/octet-stream",size_bytes:s.size,confirm:!1,client_upload:!0,...n?.params||{}});if(a)throw a;const o=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 U(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 U(t);const s=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of s){const e=Array.isArray(t)?t:Object.values(t);for(const t of e)if(t&&(t.upload_url||t.uploadUrl||t.url))return U(t)}return null}(r);if(!o?.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}=o;n?.onProgress&&n.onProgress(10);const p=await fetch(c,{method:h,headers:{"Content-Type":s.type||"application/octet-stream",...l},body:s});if(!p.ok)throw new t("Direct upload to cloud storage failed",p.status);n?.onProgress&&n.onProgress(90);const f=n?.confirmEndpoint||i,{data:w,error:y}=await e.post(f,{...n?.params,bucket:o.bucket||n?.params?.bucket,file_path:u,storage_path:d,filename:s.name,content_type:s.type||"application/octet-stream",size_bytes:s.size,confirm:!0,client_upload:!0});if(y)throw y;return n?.onProgress&&n.onProgress(100),w}function U(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:C(e,"GET"),post:C(e,"POST"),put:C(e,"PUT"),patch:C(e,"PATCH"),delete:C(e,"DELETE"),upload:async(e,s,n)=>{try{return{data:await K(i,e,s,n),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Upload failed",e?.status??500)}}},download:async(e,s,n)=>{try{return await N(i,e,s,{...n,method:"POST"}),{data:void 0,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Download failed",e?.status??500)}}}};return i}function C(e,i){const s="POST"===i||"PUT"===i||"PATCH"===i;return async(n,r,a)=>{const o=e();let c,h={};s?(c=r,h=a||{}):(h=r||{},c=void 0);const l=h.token||o.token||(T?T():"")||"",u=h.apiKey||o.apiKey;try{return{data:await R(l,i,n,c,h.showToasts,h.params,u,o.baseUrl),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Error desconocido",e?.status??500)}}}}function x(e){const i=()=>"function"==typeof e?e():e,s={get:L(i,"GET"),post:L(i,"POST"),put:L(i,"PUT"),patch:L(i,"PATCH"),delete:L(i,"DELETE"),upload:async(e,i,n)=>{try{return{data:await K(s,e,i,n),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Upload failed",e?.status??500)}}},download:async(e,i,n)=>{try{return await N(s,e,i,{...n,method:"POST"}),{data:void 0,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Download failed",e?.status??500)}}}};return s}function L(e,i){return async(s,n,r)=>{const a=e(),o=await async function(t,e,i){let s,n={};"POST"===t||"PUT"===t||"PATCH"===t?(s=e,n=i||{}):(n=e||{},s=void 0);let r=n.token;return!r&&T&&(r=T()||""),r=r||"",{token:r,apiKey:n.apiKey,body:s,params:n.params,showToasts:n.showToasts}}(i,n,r),c=o.token||a.token||"",h=o.apiKey||a.apiKey;try{return{data:await R(c,i,s,o.body,o.showToasts,o.params,h,a.baseUrl),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Error desconocido",e?.status??500)}}}}class j{get baseUrl(){return this.st}getAuthToken(){return this.auth.token||this.nt||null}constructor(t){const{baseUrl:e,apiKey:i}=t;this.st=e,this.nt=i,this.realtime=new l(e,()=>this.getAuthToken());const s=t.auth?.storageKey||t.storageKey;t.global&&function(t){I={...I,...t}}(t.global),this.auth=new c({baseUrl:e,apiKey:i,storageKey:s,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const n=Y(()=>({token:this.auth.token,apiKey:i,baseUrl:e}));if(this.auth.api=n,this.db=new u(n),t.serviceRoleKey)if("undefined"!=typeof window&&void 0!==window.document)console.error("[DYPAI SDK] ❌ serviceRoleKey detected in a browser environment. This is a security risk — the key grants admin-level database access. db.direct has been disabled. Use serviceRoleKey only from server-side code (Node.js, Bun, Deno, scripts).");else{const i=Y(()=>({token:t.serviceRoleKey,baseUrl:e}));this.db.direct=new f(i)}if(this.users=new y(n),this.api=x(()=>({token:this.auth.token||"",apiKey:i,baseUrl:e})),E(()=>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))}})}_({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 z(t,e,i){if(!t)throw new Error("createClient() requiere la URL base");return new j({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?i:e})}let J={};function B(t){J={...J,toast:b,...t},M()}function q(){return{...J}}function M(){try{const{configureApiService:t}=require("../services/ApiService");t(J)}catch(t){}}function W(){J={},M()}async function F(){M()}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{f as DirectDBModule,w as DirectQueryBuilder,j as DypaiClient,t as DypaiError,l as DypaiRealtimeClient,G as PACKAGE_INFO,h as RealtimeChannel,R as callApi,_ as configureApiService,B as configureDypaiServices,x as createApiClient,z as createClient,q as getGlobalConfig,F as reloadDypaiConfig,W as resetGlobalConfig,S as setToastFunction,E as setTokenProvider,b as toast,A as toastError,P as toastInfo,D 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.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.m=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,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.O(),this.A=setInterval(()=>this.O(),AuthModule.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.A&&(_log("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.A),this.A=null)}async O(){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{get baseUrl(){return this.G}getAuthToken(){return this.auth.token||this.W||null}constructor(t){const{baseUrl:e,apiKey:r}=t;this.G=e,this.W=r;const 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.name||e.user_metadata?.full_name||null,i=e.emailVerified??e.confirmed_at??null,s=e.app_metadata||{},o=e.user_metadata||{full_name:r};return{id:e.id,email:e.email,phone:e.phone||null,role:s.role||o.role||e.role||"authenticated",created_at:e.createdAt||e.created_at,updated_at:e.updatedAt||e.updated_at,confirmed_at:i?"boolean"==typeof i?(new Date).toISOString():i:null,last_sign_in_at:e.last_sign_in_at||null,app_metadata:s,user_metadata:{...o,full_name:r},roleDetails:{name:s.role||o.role||null,weight:0},appContext:{app_id:"default"}}}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.h=null,this.l=[],this.u=null,this.p=null,this.m=!1,this.D=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.k().then(()=>{this.i&&(this.getUser().catch(()=>{}),this.startAutoRefresh());const t=this.p;this.p=null,t?setTimeout(()=>{this.m=!0,this.P(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&&(_log("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.k(!1))}))}async T(){"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.l.push(t),this._.then(()=>{const e=this.I();_log("log",`[DYPAI SDK] 📣 INITIAL_SESSION para suscriptor (Sesión activa: ${!!e})`),t("INITIAL_SESSION",e)}).catch(e=>{_log("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.l=this.l.filter(e=>e!==t)}}}}}async signInWithPassword(t){return wrapAuthResponse((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-in/email`,r={email:t.email||t.identifier||"",password:t.password},i=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(r)});if(!i.ok)throw await parseAuthError(i,"Login failed");const s=await i.json(),o=this.R(s);return await this.C(o),o})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return wrapAuthResponse((async()=>{const r=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-up/email`,{email:i,phone:s,password:o,user_data:n,...a}=t,c=i||"",h=a.full_name||a.name||c.split("@")[0],l={...a,...n||{}},u={email:c,password:o,name:h};Object.keys(l).length>0&&Object.assign(u,l),e?.redirectTo?u.callbackURL=e.redirectTo:"undefined"!=typeof window&&(u.callbackURL=`${window.location.origin}/`);const d=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(u)});if(!d.ok)throw await parseAuthError(d,"Registration failed");const p=await d.json(),f=this.R(p),y={...f,confirmationRequired:!f.token};return y.token?await this.C(y):_log("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),y})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this._,!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.h||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"}/api/v0/auth/get-session`,e=await fetch(t,{headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!e.ok)throw new DypaiError("Session invalid",e.status);const r=await e.json(),i=this.O(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=new URLSearchParams({provider:t,callbackURL:r});i?.length&&o.set("scopes",i.join(" "));const n=`${s}/api/v0/auth/sign-in/social?${o.toString()}`;window.location.href=n})())}async signOut(){return wrapAuthResponse((async()=>{try{if(this.i){const t=this.config.baseUrl||"http://localhost:8000";await fetch(`${t}/api/v0/auth/sign-out`,{method:"POST",headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}})}}finally{this.U("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return wrapAuthResponse((async()=>{const r=this.config.baseUrl||"http://localhost:8000",i={email:t};e?.redirectTo?i.redirectTo=e.redirectTo:"undefined"!=typeof window&&(i.redirectTo=`${window.location.origin}/`);const s=await fetch(`${r}/api/v0/auth/request-password-reset`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});if(!s.ok)throw await parseAuthError(s,"Recovery failed");return await s.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t,e){return wrapAuthResponse((async()=>{const r=this.config.baseUrl||"http://localhost:8000",i={email:t};e?.redirectTo?i.callbackURL=e.redirectTo:"undefined"!=typeof window&&(i.callbackURL=`${window.location.origin}/`);const s=await fetch(`${r}/api/v0/auth/send-verification-email`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(i)});if(!s.ok)throw await parseAuthError(s,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.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.N());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 N(){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.h=e.expires_at,this.t=e.user,this.S=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.i)throw new DypaiError("No hay sesión activa",401);_log("log","[DYPAI SDK] 🔄 Verificando sesión activa...");const e=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,r=new AbortController,i=setTimeout(()=>r.abort(),AuthModule.REFRESH_TIMEOUT_MS);let s;try{s=await fetch(e,{method:"GET",headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},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.U(`refreshSession failed (${e}: ${r})`)):_log("warn",`[DYPAI SDK] ⚠️ Fallo en refresco (${e}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new DypaiError(t.msg||t.error_description||"Refresh session failed",e)}const o=await s.json(),n=this.R(o,this.i);return this.S=0,await this.C(n),_log("log","[DYPAI SDK] ✅ Session check exitoso."),{data:n,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),i=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: ${i}ms (intento ${this.S})`):_log("error","[DYPAI SDK] ❌ Refresh falló:",r.message,`(status: ${r.status}). Backoff: ${i}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.U(`${this.S} consecutive refresh failures`),this.S=0),{data:null,error:r}}}async signInWithOtp(t){return wrapAuthResponse((async()=>{if(t.phone)throw new DypaiError("Phone OTP is not supported by the current DYPAI auth backend",400);if(!t.email)throw new DypaiError("Email is required for magic link sign-in",400);const e=await this.signInWithMagicLink(t.email);if(e.error)throw e.error;return{status:!0}})())}async verifyOtp(t){return wrapAuthResponse((async()=>{throw new DypaiError("OTP verification is not supported by the current DYPAI auth backend. Use magic link callbacks instead.",400)})())}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"}/api/v0/auth/update-user`,r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(t)});if(!r.ok){const t=await r.json();throw new DypaiError(t.detail||"Update user failed",r.status)}const i=await r.json(),s=this.O(i);return this.$(s),s})())}async setPassword(t){return wrapAuthResponse((async()=>{if("undefined"==typeof window)throw new DypaiError("setPassword requiere un entorno de navegador",400);const e=new URLSearchParams(window.location.search).get("token");if(!e)throw new DypaiError("No reset token found in the current URL",400);const r=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${r}/api/v0/auth/reset-password`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({newPassword:t,token:e})});if(!i.ok)throw await parseAuthError(i,"Reset password failed");return await i.json()})())}async signInWithMagicLink(t,e){return wrapAuthResponse((async()=>{const r=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${r}/api/v0/auth/sign-in/magic-link`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({email:t,callbackURL:e?.redirectTo})});if(!i.ok)throw await parseAuthError(i,"Magic link failed");return{message:"Magic link sent"}})())}async enableTwoFactor(){return wrapAuthResponse((async()=>{if(!this.i)throw new DypaiError("No hay sesión activa",401);const t=this.config.baseUrl||"http://localhost:8000",e=await fetch(`${t}/api/v0/auth/two-factor/enable`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!e.ok)throw await parseAuthError(e,"Enable 2FA failed");return await e.json()})())}async verifyTwoFactor(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${e}/api/v0/auth/two-factor/verify-totp`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({code:t})});if(!r.ok)throw await parseAuthError(r,"2FA verification failed");const i=await r.json(),s={token:i.token||this.i,expiresIn:604800,expiresAt:Math.floor(Date.now()/1e3)+604800,user:this.O(i)};return await this.C(s),s})())}async disableTwoFactor(){return wrapAuthResponse((async()=>{if(!this.i)throw new DypaiError("No hay sesión activa",401);const t=this.config.baseUrl||"http://localhost:8000",e=await fetch(`${t}/api/v0/auth/two-factor/disable`,{method:"POST",headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!e.ok)throw await parseAuthError(e,"Disable 2FA failed");return{message:"2FA disabled"}})())}async createOrganization(t,e){return wrapAuthResponse((async()=>{if(!this.i)throw new DypaiError("No hay sesión activa",401);const r=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${r}/api/v0/auth/organization/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({name:t,slug:e})});if(!i.ok)throw await parseAuthError(i,"Create organization failed");return await i.json()})())}async inviteMember(t,e,r){return wrapAuthResponse((async()=>{if(!this.i)throw new DypaiError("No hay sesión activa",401);const i=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${i}/api/v0/auth/organization/invite-member`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({organizationId:t,email:e,role:r||"member"})});if(!s.ok)throw await parseAuthError(s,"Invite member failed");return await s.json()})())}async listOrganizations(){return wrapAuthResponse((async()=>{if(!this.i)throw new DypaiError("No hay sesión activa",401);const t=this.config.baseUrl||"http://localhost:8000",e=await fetch(`${t}/api/v0/auth/organization/list`,{headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!e.ok)throw await parseAuthError(e,"List organizations failed");return await e.json()})())}startAutoRefresh(){this.stopAutoRefresh(),_log("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.K(),this.A=setInterval(()=>this.K(),AuthModule.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.A&&(_log("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.A),this.A=null)}async K(){try{const t=Math.floor(Date.now()/1e3),e=this.h||0;if(!this.o||!e)return;if(1e3*(e-t)>AuthModule.AUTO_REFRESH_TICK_THRESHOLD*AuthModule.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.S>0){const t=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.S),AuthModule.MAX_RETRY_MS);if(Date.now()-this.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)}}O(t){return t?normalizeUser(t):(_log("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}R(t,e){const r="number"==typeof t?.expiresIn?t.expiresIn:"number"==typeof t?.expires_in?t.expires_in:604800;return{token:t?.token||e,refreshToken:t?.refreshToken||t?.refresh_token||t?.session?.refreshToken||t?.session?.refresh_token||void 0,expiresIn:r,expiresAt:Math.floor(Date.now()/1e3)+r,user:this.O(t)}}async C(t){this.u=null,t.token&&(this.i=t.token,void 0!==t.refreshToken&&(this.o=t.refreshToken||null),void 0!==t.expiresAt&&(this.h=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.h}, 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.h=i||null);try{if(this.i&&this.t){const t={access_token:this.i,refresh_token:this.o,expires_at:this.h,user:this.t};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t))}else _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.P(t)}}async U(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.h=null,this.stopAutoRefresh();try{await this.storage.removeItem(this.STORAGE_KEY),_log("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){_log("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.P("SIGNED_OUT")}async k(t=!0){_log("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.M())return void _log("log","[DYPAI SDK] ✅ Sesión establecida desde callback URL. Saltando recuperación de localStorage.");const e=await this.storage.getItem(this.STORAGE_KEY);if(!e)return void _log("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let t;_log("log","[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");try{t=JSON.parse(e)}catch{return console.warn("[DYPAI SDK] ⚠️ Sesión en storage corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!t||"object"!=typeof t||"string"!=typeof t.access_token)return console.warn("[DYPAI SDK] ⚠️ Sesión en storage con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);this.i=t.access_token,this.o="string"==typeof t.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}const r=Math.floor(Date.now()/1e3),i=this.h&&this.h<=r;if(_log("log",`[DYPAI SDK] 🕒 Token expira en: ${this.h}. Ahora es: ${r}. Diferencia: ${(this.h||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.h&&_log("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.p="SIGNED_IN"}catch(t){_log("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async M(){if("undefined"==typeof window)return!1;try{const t=window.location.hash.substring(1);if(!t)return!1;const e=new URLSearchParams(t);if(e.get("error")){const t=e.get("error_code")||"unknown",r=e.get("error_description")?.replace(/\+/g," ")||"Authentication error";return _log("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${r}`),this.u={code:t,message:r},window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.P("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.h=Math.floor(Date.now()/1e3)+o;const n=this.config.baseUrl||"http://localhost:8000",a=await fetch(`${n}/api/v0/auth/get-session`,{headers:{Authorization:`Bearer ${r}`,...this.config.apiKey&&{"x-api-key":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.h=null,this.u={code:"token_invalid",message:"The authentication link is invalid or has expired"},this.P("AUTH_ERROR"),!0;const c=await a.json(),h=this.O(c),l={token:r,refreshToken:i||void 0,expiresIn:o,expiresAt:this.h,user:h};return await this.C(l),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}}I(){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.I();_log("log",`[DYPAI SDK] 📢 Notificando a ${this.l.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.l.forEach(r=>r(t,e))}handleSessionExpired(){this.U("handleSessionExpired called (likely 401 from API)")}}AuthModule.REFRESH_TIMEOUT_MS=15e3,AuthModule.MAX_REFRESH_FAILURES=5,AuthModule.AUTO_REFRESH_TICK_DURATION_MS=3e4,AuthModule.AUTO_REFRESH_TICK_THRESHOLD=3,AuthModule.RETRY_BASE_MS=200,AuthModule.MAX_RETRY_MS=3e4,AuthModule.LOCK_ACQUIRE_TIMEOUT_MS=5e3;class RealtimeChannel{constructor(t,e,r){this.ws=null,this.bindings=[],this.Y={},this.L=!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.B();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.q(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.L=!0,t?.("SUBSCRIBED")};e.readyState===WebSocket.OPEN?r():e.readyState===WebSocket.CONNECTING&&e.addEventListener("open",r,{once:!0}),this.J&&e.removeEventListener("message",this.J);const i=t=>{try{const e=JSON.parse("string"==typeof t.data?t.data:"");this.F(e)}catch{}};return this.J=i,e.addEventListener("message",i),e.addEventListener("close",()=>{this.L=!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.q(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.Y}}async unsubscribe(){if(this.J&&this.ws&&(this.ws.removeEventListener("message",this.J),this.J=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.L=!1,this.bindings=[],this.Y={},"ok"}F(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.Y={};for(const[r,i]of Object.entries(t.presences||{})){const t=i[e]||r;this.Y[t]||(this.Y[t]=[]),this.Y[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.Y[t]||(this.Y[t]=[]),this.Y[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.Y[t]}))}if(t.leaves)for(const[r,i]of Object.entries(t.leaves)){const t=i[e]||r;this.Y[t]=(this.Y[t]||[]).filter(t=>t.userId!==r),0===this.Y[t].length&&delete this.Y[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.Y[t]||[]}))}for(const t of this.bindings)"presence"!==t.type||"sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"})}}q(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.B())}disconnect(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.ws?.close(),this.ws=null}B(){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.L&&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.B()},t)}}),r}}DypaiRealtimeClient.RECONNECT_INTERVALS=[1e3,2e3,5e3,1e4,3e4];class DataModule{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 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.H="public",this.V=[],this.X=100,this.Z=0,this.tt="ASC"}schema(t){return this.H=t,this}eq(t,e){return this.V.push({column:t,operator:"eq",value:e}),this}neq(t,e){return this.V.push({column:t,operator:"neq",value:e}),this}gt(t,e){return this.V.push({column:t,operator:"gt",value:e}),this}gte(t,e){return this.V.push({column:t,operator:"gte",value:e}),this}lt(t,e){return this.V.push({column:t,operator:"lt",value:e}),this}lte(t,e){return this.V.push({column:t,operator:"lte",value:e}),this}like(t,e){return this.V.push({column:t,operator:"ilike",value:e}),this}ilike(t,e){return this.like(t,e)}isNull(t){return this.V.push({column:t,operator:"is_null",value:null}),this}is(t,e){return this.isNull(t)}notNull(t){return this.V.push({column:t,operator:"not_null",value:null}),this}in(t,e){return this.V.push({column:t,operator:"in",value:e}),this}contains(t,e){return this.V.push({column:t,operator:"array_contains",value:e}),this}containedBy(t,e){return this.V.push({column:t,operator:"contained_by",value:e}),this}overlaps(t,e){return this.V.push({column:t,operator:"overlaps",value:e}),this}textSearch(t,e){return this.V.push({column:t,operator:"fts",value:e}),this}phraseSearch(t,e){return this.V.push({column:t,operator:"phfts",value:e}),this}not(t,e,r){return this.V.push({column:t,operator:"not",value:{operator:e,value:r}}),this}or(t){return this.V.push({operator:"or",or:t}),this}limit(t){return this.X=t,this}offset(t){return this.Z=t,this}orderBy(t,e="ASC"){return this.et=t,this.tt=e,this}async select(t){const e=[...this.V];let r=this.rt;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.X,offset:this.Z,sort_by:this.et,order:this.tt})}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.V],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.V.length?{data:null,error:new DypaiError("Update requires at least one filter. Use .eq(), .in(), etc.",400)}:this.execute("update",{data:t,filters:this.V})}async delete(){return 0===this.V.length?{data:null,error:new DypaiError("Delete requires at least one filter.",400)}:this.execute("delete",{filters:this.V})}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.H,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 h=getCompleteConfig(),l=a||null;if(!l&&!r.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof r||!r.trim())throw new Error("Endpoint debe ser un string válido");let u;if(r.startsWith("http"))u=r;else{const t=l.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={...h.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=h.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,l=(void 0!==s?s:!1!==h.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&&h.onTokenExpired)try{const t=await h.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 l&&t({title:"Error",description:p,variant:"error"}),new DypaiError(p,d.status,void 0,u)}!l||"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:h={},file_path:l,storage_path:u}=n;i?.onProgress&&i.onProgress(10);const d=await fetch(a,{method:c,headers:{"Content-Type":r.type||"application/octet-stream",...h},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:l,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 h=c.token||n.token||(globalTokenProvider?globalTokenProvider():"")||"",l=c.apiKey||n.apiKey;try{return{data:await callApi(h,e,i,a,c.showToasts,c.params,l,n.baseUrl),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Error desconocido",t?.status??500)}}}}function createApiClient(t){const e=()=>"function"==typeof t?t():t,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.it}getAuthToken(){return this.auth.token||this.st||null}constructor(t){const{baseUrl:e,apiKey:r}=t;this.it=e,this.st=r,this.realtime=new DypaiRealtimeClient(e,()=>this.getAuthToken());const i=t.auth?.storageKey||t.storageKey;t.global&&function(t){serviceConfig={...serviceConfig,...t}}(t.global),this.auth=new AuthModule({baseUrl:e,apiKey:r,storageKey:i,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const s=createInternalApiClient(()=>({token:this.auth.token,apiKey:r,baseUrl:e}));if(this.auth.api=s,this.db=new DataModule(s),t.serviceRoleKey)if("undefined"!=typeof window&&void 0!==window.document)console.error("[DYPAI SDK] ❌ serviceRoleKey detected in a browser environment. This is a security risk — the key grants admin-level database access. db.direct has been disabled. Use serviceRoleKey only from server-side code (Node.js, Bun, Deno, scripts).");else{const r=createInternalApiClient(()=>({token:t.serviceRoleKey,baseUrl:e}));this.db.direct=new DirectDBModule(r)}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
|
@@ -91,6 +91,40 @@ interface UseAuthReturn {
|
|
|
91
91
|
signInWithOtp: (email: string) => Promise<{
|
|
92
92
|
error: DypaiError | null;
|
|
93
93
|
}>;
|
|
94
|
+
/** Sign in with magic link (passwordless) */
|
|
95
|
+
signInWithMagicLink: (email: string) => Promise<{
|
|
96
|
+
error: DypaiError | null;
|
|
97
|
+
}>;
|
|
98
|
+
/** Enable 2FA (returns TOTP URI for authenticator app) */
|
|
99
|
+
enableTwoFactor: () => Promise<{
|
|
100
|
+
data: {
|
|
101
|
+
totpURI: string;
|
|
102
|
+
backupCodes: string[];
|
|
103
|
+
} | null;
|
|
104
|
+
error: DypaiError | null;
|
|
105
|
+
}>;
|
|
106
|
+
/** Verify 2FA code */
|
|
107
|
+
verifyTwoFactor: (code: string) => Promise<{
|
|
108
|
+
error: DypaiError | null;
|
|
109
|
+
}>;
|
|
110
|
+
/** Disable 2FA */
|
|
111
|
+
disableTwoFactor: () => Promise<{
|
|
112
|
+
error: DypaiError | null;
|
|
113
|
+
}>;
|
|
114
|
+
/** Create an organization */
|
|
115
|
+
createOrganization: (name: string, slug?: string) => Promise<{
|
|
116
|
+
data: any;
|
|
117
|
+
error: DypaiError | null;
|
|
118
|
+
}>;
|
|
119
|
+
/** List user's organizations */
|
|
120
|
+
listOrganizations: () => Promise<{
|
|
121
|
+
data: any[] | null;
|
|
122
|
+
error: DypaiError | null;
|
|
123
|
+
}>;
|
|
124
|
+
/** Invite member to organization */
|
|
125
|
+
inviteMember: (orgId: string, email: string, role?: string) => Promise<{
|
|
126
|
+
error: DypaiError | null;
|
|
127
|
+
}>;
|
|
94
128
|
}
|
|
95
129
|
/**
|
|
96
130
|
* Hook for authentication. Provides user state and auth actions.
|
|
@@ -457,6 +491,182 @@ interface UseChatListReturn {
|
|
|
457
491
|
*/
|
|
458
492
|
declare function useChatList(options?: UseChatListOptions): UseChatListReturn;
|
|
459
493
|
|
|
494
|
+
interface RealtimeChange<T = any> {
|
|
495
|
+
table: string;
|
|
496
|
+
event: 'INSERT' | 'UPDATE' | 'DELETE';
|
|
497
|
+
record: T | null;
|
|
498
|
+
old_record: T | null;
|
|
499
|
+
timestamp: string;
|
|
500
|
+
}
|
|
501
|
+
interface UseRealtimeOptions<T = any> {
|
|
502
|
+
/** Events to listen for (default: all) */
|
|
503
|
+
event?: '*' | 'INSERT' | 'UPDATE' | 'DELETE';
|
|
504
|
+
/** Filter changes by field values (e.g., { status: 'active' }) */
|
|
505
|
+
filter?: Record<string, any>;
|
|
506
|
+
/** Called on every change */
|
|
507
|
+
onInsert?: (record: T) => void;
|
|
508
|
+
onUpdate?: (record: T, oldRecord: T) => void;
|
|
509
|
+
onDelete?: (oldRecord: T) => void;
|
|
510
|
+
onChange?: (change: RealtimeChange<T>) => void;
|
|
511
|
+
/** Disable the subscription (default: true) */
|
|
512
|
+
enabled?: boolean;
|
|
513
|
+
}
|
|
514
|
+
interface UseRealtimeReturn<T = any> {
|
|
515
|
+
/** Latest changes received */
|
|
516
|
+
changes: RealtimeChange<T>[];
|
|
517
|
+
/** Whether the WebSocket is connected */
|
|
518
|
+
isConnected: boolean;
|
|
519
|
+
/** Connection status */
|
|
520
|
+
status: 'connecting' | 'connected' | 'disconnected' | 'error';
|
|
521
|
+
/** Last error */
|
|
522
|
+
error: Error | null;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Subscribe to real-time database changes for a table.
|
|
526
|
+
*
|
|
527
|
+
* @example
|
|
528
|
+
* ```tsx
|
|
529
|
+
* function TaskList() {
|
|
530
|
+
* const { data: tasks, refetch } = useEndpoint('list_tasks');
|
|
531
|
+
* useRealtime('tasks', {
|
|
532
|
+
* onInsert: () => refetch(),
|
|
533
|
+
* onUpdate: () => refetch(),
|
|
534
|
+
* onDelete: () => refetch(),
|
|
535
|
+
* });
|
|
536
|
+
* return <ul>{tasks?.map(t => <li key={t.id}>{t.title}</li>)}</ul>;
|
|
537
|
+
* }
|
|
538
|
+
* ```
|
|
539
|
+
*/
|
|
540
|
+
declare function useRealtime<T = any>(table: string, options?: UseRealtimeOptions<T>): UseRealtimeReturn<T>;
|
|
541
|
+
|
|
542
|
+
interface PresenceEntry {
|
|
543
|
+
userId: string;
|
|
544
|
+
data?: Record<string, any>;
|
|
545
|
+
joinedAt: string;
|
|
546
|
+
}
|
|
547
|
+
interface BroadcastMessage<T = any> {
|
|
548
|
+
event: string;
|
|
549
|
+
payload: T;
|
|
550
|
+
sender: string;
|
|
551
|
+
timestamp: string;
|
|
552
|
+
}
|
|
553
|
+
interface UseChannelOptions {
|
|
554
|
+
presence?: boolean | Record<string, any>;
|
|
555
|
+
onBroadcast?: (message: BroadcastMessage) => void;
|
|
556
|
+
onJoin?: (userId: string, data: PresenceEntry) => void;
|
|
557
|
+
onLeave?: (userId: string, data: PresenceEntry) => void;
|
|
558
|
+
enabled?: boolean;
|
|
559
|
+
}
|
|
560
|
+
interface UseChannelReturn {
|
|
561
|
+
broadcast: (event: string, payload?: any) => void;
|
|
562
|
+
presences: Record<string, PresenceEntry>;
|
|
563
|
+
isConnected: boolean;
|
|
564
|
+
memberCount: number;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Join a realtime channel for broadcast messages and presence tracking.
|
|
568
|
+
* Uses the same shared WebSocket as useRealtime.
|
|
569
|
+
*
|
|
570
|
+
* @example
|
|
571
|
+
* ```tsx
|
|
572
|
+
* function Editor() {
|
|
573
|
+
* const { presences, broadcast, memberCount } = useChannel('doc:abc', {
|
|
574
|
+
* presence: { cursor: { x: 0, y: 0 } },
|
|
575
|
+
* onBroadcast: (msg) => {
|
|
576
|
+
* if (msg.event === 'cursor_move') updateCursor(msg.sender, msg.payload);
|
|
577
|
+
* },
|
|
578
|
+
* });
|
|
579
|
+
*
|
|
580
|
+
* return <span>{memberCount} online</span>;
|
|
581
|
+
* }
|
|
582
|
+
* ```
|
|
583
|
+
*/
|
|
584
|
+
declare function useChannel(channel: string, options?: UseChannelOptions): UseChannelReturn;
|
|
585
|
+
|
|
586
|
+
interface ChannelMessage {
|
|
587
|
+
id: string;
|
|
588
|
+
channel_id: string;
|
|
589
|
+
user_id: string;
|
|
590
|
+
sender_email?: string;
|
|
591
|
+
content: string;
|
|
592
|
+
metadata?: Record<string, any>;
|
|
593
|
+
created_at: string;
|
|
594
|
+
}
|
|
595
|
+
interface Channel {
|
|
596
|
+
id: string;
|
|
597
|
+
name: string;
|
|
598
|
+
type: string;
|
|
599
|
+
member_count: number;
|
|
600
|
+
message_count: number;
|
|
601
|
+
created_at: string;
|
|
602
|
+
updated_at: string;
|
|
603
|
+
}
|
|
604
|
+
interface UseChannelMessagesOptions {
|
|
605
|
+
/** Load history on mount (default: true) */
|
|
606
|
+
loadHistory?: boolean;
|
|
607
|
+
/** Max messages to load (default: 50) */
|
|
608
|
+
limit?: number;
|
|
609
|
+
}
|
|
610
|
+
interface UseChannelMessagesReturn {
|
|
611
|
+
/** All messages (history + real-time) */
|
|
612
|
+
messages: ChannelMessage[];
|
|
613
|
+
/** Send a message to the channel */
|
|
614
|
+
send: (content: string, metadata?: Record<string, any>) => Promise<void>;
|
|
615
|
+
/** Whether messages are loading */
|
|
616
|
+
isLoading: boolean;
|
|
617
|
+
/** Whether connected to realtime */
|
|
618
|
+
isConnected: boolean;
|
|
619
|
+
/** Error */
|
|
620
|
+
error: Error | null;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Persistent channel messaging with real-time updates.
|
|
624
|
+
* Messages are stored in the database and broadcast via WebSocket.
|
|
625
|
+
*
|
|
626
|
+
* @example
|
|
627
|
+
* ```tsx
|
|
628
|
+
* function TeamChat() {
|
|
629
|
+
* const { messages, send, isLoading } = useChannelMessages('ch_abc123');
|
|
630
|
+
*
|
|
631
|
+
* return (
|
|
632
|
+
* <div>
|
|
633
|
+
* {messages.map(m => (
|
|
634
|
+
* <div key={m.id}>
|
|
635
|
+
* <b>{m.sender_email}</b>: {m.content}
|
|
636
|
+
* </div>
|
|
637
|
+
* ))}
|
|
638
|
+
* <button onClick={() => send('Hello!')}>Send</button>
|
|
639
|
+
* </div>
|
|
640
|
+
* );
|
|
641
|
+
* }
|
|
642
|
+
* ```
|
|
643
|
+
*/
|
|
644
|
+
declare function useChannelMessages(channelId: string, options?: UseChannelMessagesOptions): UseChannelMessagesReturn;
|
|
645
|
+
interface UseChannelsReturn {
|
|
646
|
+
channels: Channel[];
|
|
647
|
+
isLoading: boolean;
|
|
648
|
+
error: Error | null;
|
|
649
|
+
createChannel: (name: string, members?: string[], type?: string) => Promise<Channel>;
|
|
650
|
+
refetch: () => Promise<void>;
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* List and manage the user's channels.
|
|
654
|
+
*
|
|
655
|
+
* @example
|
|
656
|
+
* ```tsx
|
|
657
|
+
* function ChannelList() {
|
|
658
|
+
* const { channels, createChannel } = useChannels();
|
|
659
|
+
* return (
|
|
660
|
+
* <ul>
|
|
661
|
+
* {channels.map(ch => <li key={ch.id}>{ch.name} ({ch.member_count} members)</li>)}
|
|
662
|
+
* <button onClick={() => createChannel('General')}>New Channel</button>
|
|
663
|
+
* </ul>
|
|
664
|
+
* );
|
|
665
|
+
* }
|
|
666
|
+
* ```
|
|
667
|
+
*/
|
|
668
|
+
declare function useChannels(): UseChannelsReturn;
|
|
669
|
+
|
|
460
670
|
/** Props for the {@link ProtectedRoute} component. */
|
|
461
671
|
interface ProtectedRouteProps {
|
|
462
672
|
children: ReactNode;
|
|
@@ -514,5 +724,5 @@ interface ProtectedRouteProps {
|
|
|
514
724
|
*/
|
|
515
725
|
declare function ProtectedRoute({ children, redirectTo, roles, loadingComponent, unauthorizedComponent, unauthenticatedComponent, }: ProtectedRouteProps): react_jsx_runtime.JSX.Element | null;
|
|
516
726
|
|
|
517
|
-
export { DypaiProvider, ProtectedRoute, generateChatId, getMessageText, getToolCalls, useAction, useAuth, useChat, useChatList, useDypai, useDypaiClient, useEndpoint, useUpload };
|
|
518
|
-
export type { ChatMessage, ChatSession, ChatStatus, MessagePart, UseChatListOptions, UseChatListReturn, UseChatOptions, UseChatReturn };
|
|
727
|
+
export { DypaiProvider, ProtectedRoute, generateChatId, getMessageText, getToolCalls, useAction, useAuth, useChannel, useChannelMessages, useChannels, useChat, useChatList, useDypai, useDypaiClient, useEndpoint, useRealtime, useUpload };
|
|
728
|
+
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 t,Fragment as n}from"react/jsx-runtime";import{createContext as e,useState as r,useEffect as a,useContext as o,useCallback as i,useRef as s}from"react";const c=e(null);function l({client:n,children:e}){const[o,i]=r(null),[s,l]=r(null),[u,d]=r(!0),[h,p]=r(null);a(()=>{const{data:{subscription:t}}=n.auth.onAuthStateChange((t,n)=>{p(t),"SIGNED_IN"===t||"TOKEN_REFRESHED"===t||"INITIAL_SESSION"===t?(l(n??null),i(n?.user??null),d(!1)):"SIGNED_OUT"===t?(l(null),i(null),d(!1)):"PASSWORD_RECOVERY"===t?(l(n??null),i(n?.user??null),d(!1)):"AUTH_ERROR"===t?d(!1):"USER_UPDATED"===t&&n&&(l(n),i(n.user??null))});return()=>{t?.unsubscribe()}},[n]);const w={client:n,user:o,session:s,isLoading:u,isAuthenticated:!!o,authEvent:h};return t(c.Provider,{value:w,children:e})}function u(){const t=o(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:n,isLoading:e,isAuthenticated:r,authEvent:a}=u(),o=i(async(n,e)=>{const{error:r}=await t.auth.signInWithPassword({email:n,password:e});return{error:r}},[t]),s=i(async(n,e,r)=>{const{data:a,error:o}=await t.auth.signUp({email:n,password:e,...r});return{error:o,confirmationRequired:a?.confirmationRequired}},[t]),c=i(async()=>{await t.auth.signOut()},[t]),l=i(async(n,e)=>{const{error:r}=await t.auth.resetPasswordForEmail(n,e);return{error:r}},[t]),d=i(async n=>{const{error:e}=await t.auth.setPassword(n);return{error:e}},[t]),h=i(async n=>{await t.auth.signInWithOAuth(n)},[t]),p=i(async n=>{const{error:e}=await t.auth.signInWithOtp({email:n});return{error:e}},[t]);return{user:n,isLoading:e,isAuthenticated:r,authEvent:a,lastError:t.auth.lastError,signIn:o,signUp:s,signOut:c,resetPassword:l,setPassword:d,signInWithOAuth:h,signInWithOtp:p}}function p(t,n={}){const e=d(),{params:o,enabled:c=!0,refetchInterval:l=0}=n,[u,h]=r(null),[p,w]=r(null),[f,y]=r(!0),[m,E]=r(!1),g=s(!0),A=JSON.stringify(o||{}),T=i(async(n=!1)=>{if(!c)return void y(!1);n?E(!0):y(!0),w(null);const{data:r,error:a}=await e.api.get(t,{params:o});g.current&&(a?(w(a),h(null)):h(r),y(!1),E(!1))},[e,t,A,c]);return a(()=>(g.current=!0,T(),()=>{g.current=!1}),[T]),a(()=>{if(!l||!c)return;const t=setInterval(()=>T(!0),l);return()=>clearInterval(t)},[T,l,c]),{data:u,error:p,isLoading:f,isRefetching:m,refetch:i(async()=>{await T(!0)},[T])}}function w(t,n={}){const e=d(),{method:a="POST",onSuccess:o,onError:s}=n,[c,l]=r(null),[u,h]=r(null),[p,w]=r(!1);return{mutate:i(async n=>{let r;switch(w(!0),h(null),a){case"PUT":r=await e.api.put(t,n);break;case"PATCH":r=await e.api.patch(t,n);break;case"DELETE":r=await e.api.delete(t,{params:n});break;default:r=await e.api.post(t,n)}return w(!1),r.error?(h(r.error),s?.(r.error),{data:null,error:r.error}):(l(r.data),o?.(r.data),{data:r.data,error:null})},[e,t,a,o,s]),data:c,error:u,isLoading:p,reset:i(()=>{l(null),h(null),w(!1)},[])}}function f(t,n={}){const e=d(),{onSuccess:a,onError:o}=n,[s,c]=r(0),[l,u]=r(!1),[h,p]=r(null),[w,f]=r(null);return{upload:i(async(n,r)=>{u(!0),c(0),p(null);const{data:i,error:s}=await e.api.upload(t,n,{params:r,onProgress:c});return u(!1),s?(p(s),o?.(s),{data:null,error:s}):(f(i),a?.(i),{data:i,error:null})},[e,t,a,o]),progress:s,isUploading:l,error:h,data:w,reset:i(()=>{c(0),u(!1),p(null),f(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 E(t){return t.parts?.length?t.parts.filter(t=>"tool-call"===t.type).map(t=>({toolName:t.toolName||"unknown",args:t.args})):[]}function g(){return y()}function A(t,n={}){const e=d(),[o,c]=r(n.id||g),[l,u]=r(n.initialMessages||[]),[h,p]=r("ready"),[w,f]=r(""),[y,m]=r(null),E=s(null);a(()=>{if(!1===n.loadHistory)return;if(!n.id)return;let t=!1;return(async()=>{try{const n=e.baseUrl,r=e.getAuthToken()||"",a=await fetch(`${n}/api/v0/chats/${o}/messages?limit=50`,{headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"}});if(!a.ok||t)return;const i=await a.json();if(i.messages&&!t){const t=i.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}},[o,n.id,n.loadHistory]);const A=i(async r=>{const a=r||w;if(!a.trim())return;r||f(""),m(null);const i={id:g(),role:"user",content:a,parts:[{type:"text",text:a}],createdAt:new Date},s=[...l,i];u(s),p("submitted");const c={id:g(),role:"assistant",content:"",parts:[],createdAt:new Date};try{const r=e.baseUrl,i=e.getAuthToken()||"",l=new AbortController;E.current=l;const d={session_id:o,messages:[{role:"user",content:a}],...n.body||{}},h=await fetch(`${r}/api/v0/${t}`,{method:"POST",headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json",Accept:"text/event-stream",...n.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(),e=t.content||t.text||JSON.stringify(t);c.content=e,c.parts=[{type:"text",text:e}];const r=[...s,{...c}];return u(r),p("ready"),void n.onFinish?.(c,r)}p("streaming"),u([...s,c]);const w=h.body?.getReader();if(!w)throw new Error("No response body");const f=new TextDecoder;let y="";for(;;){const{done:t,value:n}=await w.read();if(t)break;const e=f.decode(n,{stream:!0}).split("\n").filter(t=>!/^[A-Z][a-zA-Z-]+:\s/.test(t)).join("\n");e&&(y+=e,c.content=y,c.parts=[{type:"text",text:y}],u([...s,{...c}]))}const m={...c,content:y,parts:[{type:"text",text:y}]},g=[...s,m];u(g),p("ready"),n.onFinish?.(m,g)}catch(t){if("AbortError"===t.name)return void p("ready");m(t),p("error"),n.onError?.(t)}finally{E.current=null}},[w,l,o,t,e,n]),T=i(()=>{E.current?.abort(),E.current=null,p("ready")},[]),$=i(()=>{T(),u([]),m(null),p("ready"),f(""),c(g())},[T]);return{messages:l,status:h,input:w,setInput:f,sendMessage:A,setMessages:u,stop:T,newChat:$,isLoading:"submitted"===h||"streaming"===h,error:y,chatId:o}}function T(t={}){const n=d(),{endpointId:e,limit:o=20,enabled:s=!0}=t,[c,l]=r([]),[u,h]=r(!1),[p,w]=r(null),f=i(async()=>{h(!0),w(null);try{const t=n.baseUrl,r=n.getAuthToken()||"",a=new URLSearchParams({limit:String(o)});e&&a.set("endpoint_id",e);const i=await fetch(`${t}/api/v0/chats?${a}`,{headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"}});if(!i.ok){const t=await i.json().catch(()=>({}));throw new Error(t.error||`HTTP ${i.status}`)}const s=((await i.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(s)}catch(t){w(t)}finally{h(!1)}},[n,e,o]),y=i(async t=>{const e=n.baseUrl,r=n.getAuthToken()||"",a=await fetch(`${e}/api/v0/chats/${t}`,{method:"DELETE",headers:{Authorization:`Bearer ${r}`}});if(!a.ok){const t=await a.json().catch(()=>({}));throw new Error(t.error||`HTTP ${a.status}`)}l(n=>n.filter(n=>n.id!==t))},[n]),m=i(async(t,e)=>{const r=n.baseUrl,a=n.getAuthToken()||"",o=await fetch(`${r}/api/v0/chats/${t}`,{method:"PATCH",headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"},body:JSON.stringify({title:e})});if(!o.ok){const t=await o.json().catch(()=>({}));throw new Error(t.error||`HTTP ${o.status}`)}l(n=>n.map(n=>n.id===t?{...n,title:e}:n))},[n]);return a(()=>{s&&f()},[s,f]),{chats:c,isLoading:u,error:p,refetch:f,deleteChat:y,renameChat:m}}function $({children:e,redirectTo:r,roles:a,loadingComponent:o,unauthorizedComponent:i,unauthenticatedComponent:s}){const{user:c,isLoading:l,isAuthenticated:d}=u();if(l)return t(n,{children:o||null});if(!d)return r&&"undefined"!=typeof window?(window.location.replace(r),null):t(n,{children:s||null});if(a&&a.length>0){const e=c?.role;if(!e||!a.includes(e))return t(n,{children:i||null})}return t(n,{children:e})}export{l as DypaiProvider,$ as ProtectedRoute,y as generateChatId,m as getMessageText,E as getToolCalls,w as useAction,h as useAuth,A as useChat,T as useChatList,u as useDypai,d as useDypaiClient,p 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 a,useContext as o,useCallback as s,useRef as i}from"react";const c=n(null);function l({client:e,children:n}){const[o,s]=r(null),[i,l]=r(null),[u,d]=r(!0),[h,f]=r(null);a(()=>{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:o,session:i,isLoading:u,isAuthenticated:!!o,authEvent:h};return t(c.Provider,{value:p,children:n})}function u(){const t=o(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:a}=u(),o=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:a,error:o}=await t.auth.signUp({email:e,password:n,...r});return{error:o,confirmationRequired:a?.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]),p=s(async e=>{const{error:n}=await t.auth.signInWithMagicLink(e);return{error:n}},[t]),w=s(async()=>{const{data:e,error:n}=await t.auth.enableTwoFactor();return{data:e,error:n}},[t]),y=s(async e=>{const{error:n}=await t.auth.verifyTwoFactor(e);return{error:n}},[t]),m=s(async()=>{const{error:e}=await t.auth.disableTwoFactor();return{error:e}},[t]),b=s(async(e,n)=>{const{data:r,error:a}=await t.auth.createOrganization(e,n);return{data:r,error:a}},[t]),T=s(async()=>{const{data:e,error:n}=await t.auth.listOrganizations();return{data:e,error:n}},[t]),g=s(async(e,n,r)=>{const{error:a}=await t.auth.inviteMember(e,n,r);return{error:a}},[t]);return{user:e,isLoading:n,isAuthenticated:r,authEvent:a,lastError:t.auth.lastError,signIn:o,signUp:i,signOut:c,resetPassword:l,setPassword:d,signInWithOAuth:h,signInWithOtp:f,signInWithMagicLink:p,enableTwoFactor:w,verifyTwoFactor:y,disableTwoFactor:m,createOrganization:b,listOrganizations:T,inviteMember:g}}function f(t,e={}){const n=d(),{params:o,enabled:c=!0,refetchInterval:l=0}=e,[u,h]=r(null),[f,p]=r(null),[w,y]=r(!0),[m,b]=r(!1),T=i(!0),g=JSON.stringify(o||{}),S=s(async(e=!1)=>{if(!c)return void y(!1);e?b(!0):y(!0),p(null);const{data:r,error:a}=await n.api.get(t,{params:o});T.current&&(a?(p(a),h(null)):h(r),y(!1),b(!1))},[n,t,g,c]);return a(()=>(T.current=!0,S(),()=>{T.current=!1}),[S]),a(()=>{if(!l||!c)return;const t=setInterval(()=>S(!0),l);return()=>clearInterval(t)},[S,l,c]),{data:u,error:f,isLoading:w,isRefetching:m,refetch:s(async()=>{await S(!0)},[S])}}function p(t,e={}){const n=d(),{method:a="POST",onSuccess:o,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),a){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),o?.(r.data),{data:r.data,error:null})},[n,t,a,o,i]),data:c,error:u,isLoading:f,reset:s(()=>{l(null),h(null),p(!1)},[])}}function w(t,e={}){const n=d(),{onSuccess:a,onError:o}=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),o?.(i),{data:null,error:i}):(w(s),a?.(s),{data:s,error:null})},[n,t,a,o]),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 T(){return y()}function g(t,e={}){const n=d(),[o,c]=r(e.id||T),[l,u]=r(e.initialMessages||[]),[h,f]=r("ready"),[p,w]=r(""),[y,m]=r(null),b=i(null);a(()=>{if(!1===e.loadHistory)return;if(!e.id)return;let t=!1;return(async()=>{try{const e=n.baseUrl,r=n.getAuthToken()||"",a=await fetch(`${e}/api/v0/chats/${o}/messages?limit=50`,{headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"}});if(!a.ok||t)return;const s=await a.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}},[o,e.id,e.loadHistory]);const g=s(async r=>{const a=r||p;if(!a.trim())return;r||w(""),m(null);const s={id:T(),role:"user",content:a,parts:[{type:"text",text:a}],createdAt:new Date},i=[...l,s];u(i),f("submitted");const c={id:T(),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:o,messages:[{role:"user",content:a}],...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}]},T=[...i,m];u(T),f("ready"),e.onFinish?.(m,T)}catch(t){if("AbortError"===t.name)return void f("ready");m(t),f("error"),e.onError?.(t)}finally{b.current=null}},[p,l,o,t,n,e]),S=s(()=>{b.current?.abort(),b.current=null,f("ready")},[]),E=s(()=>{S(),u([]),m(null),f("ready"),w(""),c(T())},[S]);return{messages:l,status:h,input:p,setInput:w,sendMessage:g,setMessages:u,stop:S,newChat:E,isLoading:"submitted"===h||"streaming"===h,error:y,chatId:o}}function S(t={}){const e=d(),{endpointId:n,limit:o=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()||"",a=new URLSearchParams({limit:String(o)});n&&a.set("endpoint_id",n);const s=await fetch(`${t}/api/v0/chats?${a}`,{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,o]),y=s(async t=>{const n=e.baseUrl,r=e.getAuthToken()||"",a=await fetch(`${n}/api/v0/chats/${t}`,{method:"DELETE",headers:{Authorization:`Bearer ${r}`}});if(!a.ok){const t=await a.json().catch(()=>({}));throw new Error(t.error||`HTTP ${a.status}`)}l(e=>e.filter(e=>e.id!==t))},[e]),m=s(async(t,n)=>{const r=e.baseUrl,a=e.getAuthToken()||"",o=await fetch(`${r}/api/v0/chats/${t}`,{method:"PATCH",headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"},body:JSON.stringify({title:n})});if(!o.ok){const t=await o.json().catch(()=>({}));throw new Error(t.error||`HTTP ${o.status}`)}l(e=>e.map(e=>e.id===t?{...e,title:n}:e))},[e]);return a(()=>{i&&w()},[i,w]),{chats:c,isLoading:u,error:f,refetch:w,deleteChat:y,renameChat:m}}let E=null,v="disconnected",$=null,A=null,O=0,I="",C="";const P=[1e3,2e3,5e3,1e4,3e4],N=new Set,D=[],k=[];function L(t,e){if(E&&(E.readyState===WebSocket.OPEN||E.readyState===WebSocket.CONNECTING))return E;I=t,C=e;const n=t.replace(/^http/,"ws")+`/realtime?token=${encodeURIComponent(e)}`,r=new WebSocket(n);return E=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",E=null,A&&clearInterval(A),N.size>0&&!$){const t=P[Math.min(O,P.length-1)];O++,$=setTimeout(()=>{$=null,N.size>0&&I&&C&&L(I,C)},t)}},r.onerror=()=>{v="error"},r}function R(t){return N.add(t),()=>{N.delete(t),0===N.size&&E&&(E.close(),E=null)}}function j(t){return!(!E||E.readyState!==WebSocket.OPEN||(E.send(JSON.stringify(t)),0))}function x(t,e={}){const n=d(),{event:o="*",filter:s,enabled:c=!0}=e,[l,u]=r([]),[h,f]=r("disconnected"),[p,w]=r(null),y=i(e);return y.current=e,a(()=>{if(!c)return;const e=n.baseUrl,r=n.getAuthToken()||"";if(!r)return void w(new Error("Authentication required for realtime"));const a=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:o,filter:s});const i=()=>{a.readyState===WebSocket.OPEN&&(a.send(JSON.stringify({type:"subscribe",table:t,event:o,filter:s})),f("connected"))};a.readyState===WebSocket.OPEN?i():a.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("*"!==o&&e.event!==o)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,o,c,n]),{changes:l,isConnected:"connected"===h,status:h,error:p}}function W(t,e={}){const n=d(),{presence:o,enabled:c=!0}=e,[l,u]=r({}),[h,f]=r(!1),p=i(e);return p.current=e,a(()=>{if(!c||!t)return;const e=n.baseUrl,r=n.getAuthToken()||"";if(!r)return;const a=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:o||void 0});const s=()=>{a.readyState===WebSocket.OPEN&&a.send(JSON.stringify({type:"join",channel:t,presence:o||void 0}))};a.readyState===WebSocket.OPEN?s():a.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 _(t,e={}){const n=d(),{loadHistory:o=!0,limit:c=50}=e,[l,u]=r([]),[h,f]=r(!1),[p,w]=r(null),y=i(new Set);a(()=>{if(!o||!t)return;let e=!1;return(async()=>{f(!0);try{const r=n.baseUrl,a=n.getAuthToken()||"",o=await fetch(`${r}/api/v0/channels/${t}/messages?limit=${c}`,{headers:{Authorization:`Bearer ${a}`}});if(!o.ok)throw new Error(`HTTP ${o.status}`);const s=await o.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,o,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 a=n.baseUrl,o=n.getAuthToken()||"",s=await fetch(`${a}/api/v0/channels/${t}/messages`,{method:"POST",headers:{Authorization:`Bearer ${o}`,"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 z(){const t=d(),[e,n]=r([]),[o,i]=r(!1),[c,l]=r(null),u=s(async()=>{i(!0);try{const e=t.baseUrl,r=t.getAuthToken()||"",a=await fetch(`${e}/api/v0/channels`,{headers:{Authorization:`Bearer ${r}`}});if(!a.ok)throw new Error(`HTTP ${a.status}`);const o=await a.json();n(o.channels||[])}catch(t){l(t)}finally{i(!1)}},[t]);return a(()=>{u()},[u]),{channels:e,isLoading:o,error:c,createChannel:s(async(e,r=[],a="group")=>{const o=t.baseUrl,s=t.getAuthToken()||"",i=await fetch(`${o}/api/v0/channels`,{method:"POST",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},body:JSON.stringify({name:e,type:a,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 H({children:n,redirectTo:r,roles:a,loadingComponent:o,unauthorizedComponent:s,unauthenticatedComponent:i}){const{user:c,isLoading:l,isAuthenticated:d}=u();if(l)return t(e,{children:o||null});if(!d)return r&&"undefined"!=typeof window?(window.location.replace(r),null):t(e,{children:i||null});if(a&&a.length>0){const n=c?.role;if(!n||!a.includes(n))return t(e,{children:s||null})}return t(e,{children:n})}export{l as DypaiProvider,H as ProtectedRoute,y as generateChatId,m as getMessageText,b as getToolCalls,p as useAction,h as useAuth,W as useChannel,_ as useChannelMessages,z as useChannels,g as useChat,S 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 t=react.useContext(DypaiContext);if(!t)throw new Error('useDypai must be used within a <DypaiProvider>. Wrap your app with <DypaiProvider url="...">.');return t}function useDypaiClient(){return useDypai().client}function generateChatId(){return crypto.randomUUID?.()||`${Date.now()}-${Math.random().toString(36).slice(2)}`}function generateId(){return generateChatId()}exports.DypaiProvider=function({client:t,children:e}){const[r,n]=react.useState(null),[a,i]=react.useState(null),[s,o]=react.useState(!0),[c,u]=react.useState(null);react.useEffect(()=>{const{data:{subscription:e}}=t.auth.onAuthStateChange((t,e)=>{u(t),"SIGNED_IN"===t||"TOKEN_REFRESHED"===t||"INITIAL_SESSION"===t?(i(e??null),n(e?.user??null),o(!1)):"SIGNED_OUT"===t?(i(null),n(null),o(!1)):"PASSWORD_RECOVERY"===t?(i(e??null),n(e?.user??null),o(!1)):"AUTH_ERROR"===t?o(!1):"USER_UPDATED"===t&&e&&(i(e),n(e.user??null))});return()=>{e?.unsubscribe()}},[t]);const l={client:t,user:r,session:a,isLoading:s,isAuthenticated:!!r,authEvent:c};return jsxRuntime.jsx(DypaiContext.Provider,{value:l,children:e})},exports.ProtectedRoute=function({children:t,redirectTo:e,roles:r,loadingComponent:n,unauthorizedComponent:a,unauthenticatedComponent:i}){const{user:s,isLoading:o,isAuthenticated:c}=useDypai();if(o)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(r&&r.length>0){const t=s?.role;if(!t||!r.includes(t))return jsxRuntime.jsx(jsxRuntime.Fragment,{children:a||null})}return jsxRuntime.jsx(jsxRuntime.Fragment,{children:t})},exports.generateChatId=generateChatId,exports.getMessageText=function(t){return t.parts?.length?t.parts.filter(t=>"text"===t.type).map(t=>t.text||"").join(""):t.content||""},exports.getToolCalls=function(t){return t.parts?.length?t.parts.filter(t=>"tool-call"===t.type).map(t=>({toolName:t.toolName||"unknown",args:t.args})):[]},exports.useAction=function(t,e={}){const r=useDypaiClient(),{method:n="POST",onSuccess:a,onError:i}=e,[s,o]=react.useState(null),[c,u]=react.useState(null),[l,d]=react.useState(!1);return{mutate:react.useCallback(async e=>{let s;switch(d(!0),u(null),n){case"PUT":s=await r.api.put(t,e);break;case"PATCH":s=await r.api.patch(t,e);break;case"DELETE":s=await r.api.delete(t,{params:e});break;default:s=await r.api.post(t,e)}return d(!1),s.error?(u(s.error),i?.(s.error),{data:null,error:s.error}):(o(s.data),a?.(s.data),{data:s.data,error:null})},[r,t,n,a,i]),data:s,error:c,isLoading:l,reset:react.useCallback(()=>{o(null),u(null),d(!1)},[])}},exports.useAuth=function(){const{client:t,user:e,isLoading:r,isAuthenticated:n,authEvent:a}=useDypai(),i=react.useCallback(async(e,r)=>{const{error:n}=await t.auth.signInWithPassword({email:e,password:r});return{error:n}},[t]),s=react.useCallback(async(e,r,n)=>{const{data:a,error:i}=await t.auth.signUp({email:e,password:r,...n});return{error:i,confirmationRequired:a?.confirmationRequired}},[t]),o=react.useCallback(async()=>{await t.auth.signOut()},[t]),c=react.useCallback(async(e,r)=>{const{error:n}=await t.auth.resetPasswordForEmail(e,r);return{error:n}},[t]),u=react.useCallback(async e=>{const{error:r}=await t.auth.setPassword(e);return{error:r}},[t]),l=react.useCallback(async e=>{await t.auth.signInWithOAuth(e)},[t]),d=react.useCallback(async e=>{const{error:r}=await t.auth.signInWithOtp({email:e});return{error:r}},[t]);return{user:e,isLoading:r,isAuthenticated:n,authEvent:a,lastError:t.auth.lastError,signIn:i,signUp:s,signOut:o,resetPassword:c,setPassword:u,signInWithOAuth:l,signInWithOtp:d}},exports.useChat=function(t,e={}){const r=useDypaiClient(),[n,a]=react.useState(e.id||generateId),[i,s]=react.useState(e.initialMessages||[]),[o,c]=react.useState("ready"),[u,l]=react.useState(""),[d,p]=react.useState(null),h=react.useRef(null);react.useEffect(()=>{if(!1===e.loadHistory)return;if(!e.id)return;let t=!1;return(async()=>{try{const e=r.baseUrl,a=r.getAuthToken()||"",i=await fetch(`${e}/api/v0/chats/${n}/messages?limit=50`,{headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"}});if(!i.ok||t)return;const o=await i.json();if(o.messages&&!t){const t=o.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)}));s(t)}}catch{}})(),()=>{t=!0}},[n,e.id,e.loadHistory]);const y=react.useCallback(async a=>{const o=a||u;if(!o.trim())return;a||l(""),p(null);const d={id:generateId(),role:"user",content:o,parts:[{type:"text",text:o}],createdAt:new Date},y=[...i,d];s(y),c("submitted");const w={id:generateId(),role:"assistant",content:"",parts:[],createdAt:new Date};try{const a=r.baseUrl,i=r.getAuthToken()||"",u=new AbortController;h.current=u;const l={session_id:n,messages:[{role:"user",content:o}],...e.body||{}},d=await fetch(`${a}/api/v0/${t}`,{method:"POST",headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json",Accept:"text/event-stream",...e.headers||{}},body:JSON.stringify(l),signal:u.signal});if(!d.ok){const t=await d.json().catch(()=>({error:d.statusText}));throw new Error(t.error||`HTTP ${d.status}`)}if((d.headers.get("content-type")||"").includes("application/json")){const t=await d.json(),r=t.content||t.text||JSON.stringify(t);w.content=r,w.parts=[{type:"text",text:r}];const n=[...y,{...w}];return s(n),c("ready"),void e.onFinish?.(w,n)}c("streaming"),s([...y,w]);const p=d.body?.getReader();if(!p)throw new Error("No response body");const f=new TextDecoder;let m="";for(;;){const{done:t,value:e}=await p.read();if(t)break;const r=f.decode(e,{stream:!0}).split("\n").filter(t=>!/^[A-Z][a-zA-Z-]+:\s/.test(t)).join("\n");r&&(m+=r,w.content=m,w.parts=[{type:"text",text:m}],s([...y,{...w}]))}const x={...w,content:m,parts:[{type:"text",text:m}]},g=[...y,x];s(g),c("ready"),e.onFinish?.(x,g)}catch(t){if("AbortError"===t.name)return void c("ready");p(t),c("error"),e.onError?.(t)}finally{h.current=null}},[u,i,n,t,r,e]),w=react.useCallback(()=>{h.current?.abort(),h.current=null,c("ready")},[]),f=react.useCallback(()=>{w(),s([]),p(null),c("ready"),l(""),a(generateId())},[w]);return{messages:i,status:o,input:u,setInput:l,sendMessage:y,setMessages:s,stop:w,newChat:f,isLoading:"submitted"===o||"streaming"===o,error:d,chatId:n}},exports.useChatList=function(t={}){const e=useDypaiClient(),{endpointId:r,limit:n=20,enabled:a=!0}=t,[i,s]=react.useState([]),[o,c]=react.useState(!1),[u,l]=react.useState(null),d=react.useCallback(async()=>{c(!0),l(null);try{const t=e.baseUrl,a=e.getAuthToken()||"",i=new URLSearchParams({limit:String(n)});r&&i.set("endpoint_id",r);const o=await fetch(`${t}/api/v0/chats?${i}`,{headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"}});if(!o.ok){const t=await o.json().catch(()=>({}));throw new Error(t.error||`HTTP ${o.status}`)}const c=((await o.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}));s(c)}catch(t){l(t)}finally{c(!1)}},[e,r,n]),p=react.useCallback(async t=>{const r=e.baseUrl,n=e.getAuthToken()||"",a=await fetch(`${r}/api/v0/chats/${t}`,{method:"DELETE",headers:{Authorization:`Bearer ${n}`}});if(!a.ok){const t=await a.json().catch(()=>({}));throw new Error(t.error||`HTTP ${a.status}`)}s(e=>e.filter(e=>e.id!==t))},[e]),h=react.useCallback(async(t,r)=>{const n=e.baseUrl,a=e.getAuthToken()||"",i=await fetch(`${n}/api/v0/chats/${t}`,{method:"PATCH",headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"},body:JSON.stringify({title:r})});if(!i.ok){const t=await i.json().catch(()=>({}));throw new Error(t.error||`HTTP ${i.status}`)}s(e=>e.map(e=>e.id===t?{...e,title:r}:e))},[e]);return react.useEffect(()=>{a&&d()},[a,d]),{chats:i,isLoading:o,error:u,refetch:d,deleteChat:p,renameChat:h}},exports.useDypai=useDypai,exports.useDypaiClient=useDypaiClient,exports.useEndpoint=function(t,e={}){const r=useDypaiClient(),{params:n,enabled:a=!0,refetchInterval:i=0}=e,[s,o]=react.useState(null),[c,u]=react.useState(null),[l,d]=react.useState(!0),[p,h]=react.useState(!1),y=react.useRef(!0),w=JSON.stringify(n||{}),f=react.useCallback(async(e=!1)=>{if(!a)return void d(!1);e?h(!0):d(!0),u(null);const{data:i,error:s}=await r.api.get(t,{params:n});y.current&&(s?(u(s),o(null)):o(i),d(!1),h(!1))},[r,t,w,a]);return react.useEffect(()=>(y.current=!0,f(),()=>{y.current=!1}),[f]),react.useEffect(()=>{if(!i||!a)return;const t=setInterval(()=>f(!0),i);return()=>clearInterval(t)},[f,i,a]),{data:s,error:c,isLoading:l,isRefetching:p,refetch:react.useCallback(async()=>{await f(!0)},[f])}},exports.useUpload=function(t,e={}){const r=useDypaiClient(),{onSuccess:n,onError:a}=e,[i,s]=react.useState(0),[o,c]=react.useState(!1),[u,l]=react.useState(null),[d,p]=react.useState(null);return{upload:react.useCallback(async(e,i)=>{c(!0),s(0),l(null);const{data:o,error:u}=await r.api.upload(t,e,{params:i,onProgress:s});return c(!1),u?(l(u),a?.(u),{data:null,error:u}):(p(o),n?.(o),{data:o,error:null})},[r,t,n,a]),progress:i,isUploading:o,error:u,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 r=e.replace(/^http/,"ws")+`/realtime?token=${encodeURIComponent(t)}`,n=new WebSocket(r);return sharedWs=n,sharedWsStatus="connecting",n.onopen=()=>{sharedWsStatus="connected",reconnectAttempt=0;for(const e of pendingSubscriptions)n.send(JSON.stringify({type:"subscribe",...e}));for(const e of pendingChannelJoins)n.send(JSON.stringify({type:"join",...e}));heartbeatTimer&&clearInterval(heartbeatTimer),heartbeatTimer=setInterval(()=>{n.readyState===WebSocket.OPEN&&n.send(JSON.stringify({type:"heartbeat"}))},25e3)},n.onmessage=e=>{try{const t=JSON.parse("string"==typeof e.data?e.data:"");for(const e of messageHandlers)e(t)}catch{}},n.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)}},n.onerror=()=>{sharedWsStatus="error"},n}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 r=useDypaiClient(),{event:n="*",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=r.baseUrl,o=r.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:n,filter:a});const l=()=>{i.readyState===WebSocket.OPEN&&(i.send(JSON.stringify({type:"subscribe",table:e,event:n,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("*"!==n&&t.event!==n)return;const r={table:t.table,event:t.event,record:t.record,old_record:t.old_record,timestamp:t.timestamp};c(e=>[...e.slice(-99),r]),p.current.onChange?.(r),"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,n,s,r]),{changes:o,isConnected:"connected"===i,status:i,error:l}}exports.DypaiProvider=function({client:e,children:t}){const[r,n]=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),n(t?.user??null),c(!1)):"SIGNED_OUT"===e?(s(null),n(null),c(!1)):"PASSWORD_RECOVERY"===e?(s(t??null),n(t?.user??null),c(!1)):"AUTH_ERROR"===e?c(!1):"USER_UPDATED"===e&&t&&(s(t),n(t.user??null))});return()=>{t?.unsubscribe()}},[e]);const l={client:e,user:r,session:a,isLoading:o,isAuthenticated:!!r,authEvent:i};return jsxRuntime.jsx(DypaiContext.Provider,{value:l,children:t})},exports.ProtectedRoute=function({children:e,redirectTo:t,roles:r,loadingComponent:n,unauthorizedComponent:a,unauthenticatedComponent:s}){const{user:o,isLoading:c,isAuthenticated:i}=useDypai();if(c)return jsxRuntime.jsx(jsxRuntime.Fragment,{children:n||null});if(!i)return t&&"undefined"!=typeof window?(window.location.replace(t),null):jsxRuntime.jsx(jsxRuntime.Fragment,{children:s||null});if(r&&r.length>0){const e=o?.role;if(!e||!r.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 r=useDypaiClient(),{method:n="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),n){case"PUT":o=await r.api.put(e,t);break;case"PATCH":o=await r.api.patch(e,t);break;case"DELETE":o=await r.api.delete(e,{params:t});break;default:o=await r.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})},[r,e,n,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:r,isAuthenticated:n,authEvent:a}=useDypai(),s=react.useCallback(async(t,r)=>{const{error:n}=await e.auth.signInWithPassword({email:t,password:r});return{error:n}},[e]),o=react.useCallback(async(t,r,n)=>{const{data:a,error:s}=await e.auth.signUp({email:t,password:r,...n});return{error:s,confirmationRequired:a?.confirmationRequired}},[e]),c=react.useCallback(async()=>{await e.auth.signOut()},[e]),i=react.useCallback(async(t,r)=>{const{error:n}=await e.auth.resetPasswordForEmail(t,r);return{error:n}},[e]),u=react.useCallback(async t=>{const{error:r}=await e.auth.setPassword(t);return{error:r}},[e]),l=react.useCallback(async t=>{await e.auth.signInWithOAuth(t)},[e]),d=react.useCallback(async t=>{const{error:r}=await e.auth.signInWithOtp({email:t});return{error:r}},[e]),p=react.useCallback(async t=>{const{error:r}=await e.auth.signInWithMagicLink(t);return{error:r}},[e]),h=react.useCallback(async()=>{const{data:t,error:r}=await e.auth.enableTwoFactor();return{data:t,error:r}},[e]),f=react.useCallback(async t=>{const{error:r}=await e.auth.verifyTwoFactor(t);return{error:r}},[e]),y=react.useCallback(async()=>{const{error:t}=await e.auth.disableTwoFactor();return{error:t}},[e]),m=react.useCallback(async(t,r)=>{const{data:n,error:a}=await e.auth.createOrganization(t,r);return{data:n,error:a}},[e]),w=react.useCallback(async()=>{const{data:t,error:r}=await e.auth.listOrganizations();return{data:t,error:r}},[e]),g=react.useCallback(async(t,r,n)=>{const{error:a}=await e.auth.inviteMember(t,r,n);return{error:a}},[e]);return{user:t,isLoading:r,isAuthenticated:n,authEvent:a,lastError:e.auth.lastError,signIn:s,signUp:o,signOut:c,resetPassword:i,setPassword:u,signInWithOAuth:l,signInWithOtp:d,signInWithMagicLink:p,enableTwoFactor:h,verifyTwoFactor:f,disableTwoFactor:y,createOrganization:m,listOrganizations:w,inviteMember:g}},exports.useChannel=function(e,t={}){const r=useDypaiClient(),{presence:n,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=r.baseUrl,s=r.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:n||void 0});const l=()=>{c.readyState===WebSocket.OPEN&&c.send(JSON.stringify({type:"join",channel:e,presence:n||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 r={...e};if(t.joins)for(const[e,n]of Object.entries(t.joins))r[e]=n,u.current.onJoin?.(e,n);if(t.leaves)for(const[e,n]of Object.entries(t.leaves))delete r[e],u.current.onLeave?.(e,n);return r})});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,r]),{broadcast:react.useCallback((t,r)=>{sendMessage({type:"broadcast",channel:e,event:t,payload:r})},[e]),presences:s,isConnected:c,memberCount:Object.keys(s).length}},exports.useChannelMessages=function(e,t={}){const r=useDypaiClient(),{loadHistory:n=!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(!n||!e)return;let t=!1;return(async()=>{i(!0);try{const n=r.baseUrl,s=r.getAuthToken()||"",c=await fetch(`${n}/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,n,a,r]);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,n)=>{try{const a=r.baseUrl,s=r.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:n})});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,r]),isLoading:c,isConnected:p,error:u}},exports.useChannels=function(){const e=useDypaiClient(),[t,r]=react.useState([]),[n,a]=react.useState(!1),[s,o]=react.useState(null),c=react.useCallback(async()=>{a(!0);try{const t=e.baseUrl,n=e.getAuthToken()||"",a=await fetch(`${t}/api/v0/channels`,{headers:{Authorization:`Bearer ${n}`}});if(!a.ok)throw new Error(`HTTP ${a.status}`);const s=await a.json();r(s.channels||[])}catch(e){o(e)}finally{a(!1)}},[e]);return react.useEffect(()=>{c()},[c]),{channels:t,isLoading:n,error:s,createChannel:react.useCallback(async(t,n=[],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:n})});if(!c.ok){const e=await c.json().catch(()=>({}));throw new Error(e.error||`HTTP ${c.status}`)}const i=await c.json();return r(e=>[i,...e]),i},[e]),refetch:c}},exports.useChat=function(e,t={}){const r=useDypaiClient(),[n,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=r.baseUrl,a=r.getAuthToken()||"",s=await fetch(`${t}/api/v0/chats/${n}/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}},[n,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=r.baseUrl,s=r.getAuthToken()||"",u=new AbortController;h.current=u;const l={session_id:n,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(),r=e.content||e.text||JSON.stringify(e);y.content=r,y.parts=[{type:"text",text:r}];const n=[...f,{...y}];return o(n),i("ready"),void t.onFinish?.(y,n)}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 r=m.decode(t,{stream:!0}).split("\n").filter(e=>!/^[A-Z][a-zA-Z-]+:\s/.test(e)).join("\n");r&&(w+=r,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,n,e,r,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:n}},exports.useChatList=function(e={}){const t=useDypaiClient(),{endpointId:r,limit:n=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(n)});r&&s.set("endpoint_id",r);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,r,n]),p=react.useCallback(async e=>{const r=t.baseUrl,n=t.getAuthToken()||"",a=await fetch(`${r}/api/v0/chats/${e}`,{method:"DELETE",headers:{Authorization:`Bearer ${n}`}});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,r)=>{const n=t.baseUrl,a=t.getAuthToken()||"",s=await fetch(`${n}/api/v0/chats/${e}`,{method:"PATCH",headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"},body:JSON.stringify({title:r})});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:r}: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 r=useDypaiClient(),{params:n,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(n||{}),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 r.api.get(e,{params:n});f.current&&(o?(u(o),c(null)):c(s),d(!1),h(!1))},[r,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 r=useDypaiClient(),{onSuccess:n,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 r.api.upload(e,t,{params:s,onProgress:o});return i(!1),u?(l(u),a?.(u),{data:null,error:u}):(p(c),n?.(c),{data:c,error:null})},[r,e,n,a]),progress:s,isUploading:c,error:u,data:d,reset:react.useCallback(()=>{o(0),i(!1),l(null),p(null)},[])}};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dypai-ai/client-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Official JavaScript/TypeScript SDK for DYPAI — backend-as-a-service with visual workflows, AI agents, and MCP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -109,5 +109,8 @@
|
|
|
109
109
|
},
|
|
110
110
|
"engines": {
|
|
111
111
|
"node": ">=16.0.0"
|
|
112
|
+
},
|
|
113
|
+
"dependencies": {
|
|
114
|
+
"better-auth": "^1.6.0"
|
|
112
115
|
}
|
|
113
116
|
}
|