bknd
Version:
Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, React Router, Astro, Cloudflare, Bun, Node, AWS Lambda & more.
3 lines • 15.5 kB
JavaScript
"use client";
import {$console,encodeSearch,objectTransform,isPlainObject}from'bknd/utils';import {decode}from'hono/jwt';import {createContext,useContext,useMemo,useState}from'react';import {jsx}from'react/jsx-runtime';import z,{useSWRConfig,mutate}from'swr';import H from'swr/infinite';var C=(i,e)=>"number"=="string"?[1,"1","true"].includes(i):false;function R(){try{return C(0)}catch{return false}}var y=class{constructor(e={},t){this._options=e;this.fetcher=t??fetch;}fetcher;getDefaultOptions(){return {}}get options(){return {host:"http://localhost",token:void 0,...this.getDefaultOptions(),...this._options}}key(){return this.options.basepath??""}getUrl(e){let t=this.options.basepath??"";return this.options.host+(t+"/"+e).replace(/\/{2,}/g,"/").replace(/\/$/,"")}request(e,t,n){let r=n?.method??"GET",o=Array.isArray(e)?e.join("/"):e,s=this.getUrl(o);t instanceof URLSearchParams?s+="?"+t.toString():typeof t=="object"&&Object.keys(t).length>0&&(s+="?"+encodeSearch(t));let a=new Headers(this.options.headers??{});for(let[u,c]of Object.entries(n?.headers??{}))a.set(u,c);a.has("Accept")||a.set("Accept","application/json"),this.options.token&&this.options.token_transport==="header"&&a.set("Authorization",`Bearer ${this.options.token}`);let d=n?.body;if(n&&"body"in n&&["POST","PATCH","PUT"].includes(r)){let u=a.get("Content-Type")??void 0;(!u||u.startsWith("application/json")||isPlainObject(d))&&(d=JSON.stringify(n.body),a.set("Content-Type","application/json"));}let p=new Request(s,{...n,method:r,body:d,headers:a});return new h(p,{fetcher:this.fetcher,verbose:this.options.verbose})}get(e,t,n){return this.request(e,t,{...n,method:"GET"})}post(e,t,n){return this.request(e,void 0,{...n,body:t,method:"POST"})}patch(e,t,n){return this.request(e,void 0,{...n,body:t,method:"PATCH"})}put(e,t,n){return this.request(e,void 0,{...n,body:t,method:"PUT"})}delete(e,t,n){return this.request(e,void 0,{...n,body:t,method:"DELETE"})}};function q(i,e,t){let n=typeof t<"u"?t:e,r=["raw","body","ok","status","res","data","toJSON"];return typeof n!="object"&&(n={}),new Proxy(n,{get(o,s,a){return s==="raw"||s==="res"?i:s==="body"?e:s==="data"?t:s==="ok"?i.ok:s==="status"?i.status:s==="toJSON"?()=>o:Reflect.get(o,s,a)},has(o,s){return r.includes(s)?true:Reflect.has(o,s)},ownKeys(o){return Array.from(new Set([...Reflect.ownKeys(o),...r]))},getOwnPropertyDescriptor(o,s){return r.includes(s)?{configurable:true,enumerable:true,value:Reflect.get({raw:i,body:e,ok:i.ok,status:i.status},s)}:Reflect.getOwnPropertyDescriptor(o,s)}})}var h=class i{constructor(e,t,n){this.request=e;this.options=t;this.refineData=n;}[Symbol.toStringTag];get verbose(){return this.options?.verbose??false}refine(e){return new i(this.request,this.options,e)}async execute(){R()&&await new Promise(s=>setTimeout(s,200));let e=this.options?.fetcher??fetch;this.verbose&&$console.debug("[FetchPromise] Request",{method:this.request.method,url:this.request.url});let t=await e(this.request);this.verbose&&$console.debug("[FetchPromise] Response",{res:t,ok:t.ok,status:t.status});let n,r,o=t.headers.get("Content-Type")??"";if(o.startsWith("application/json")?(n=await t.json(),typeof n=="object"&&(r="data"in n?n.data:n)):o.startsWith("text")?n=await t.text():n=t.body,this.refineData)try{r=this.refineData(r);}catch(s){console.warn("[FetchPromise] Error in refineData",s),r=void 0;}return q(t,n,r)}then(e,t){return this.execute().then(e,t)}catch(e){return this.then(void 0,e)}finally(e){return this.then(t=>(e?.(),t),t=>{throw e?.(),t})}path(){return new URL(this.request.url).pathname}key(e){let t=new URL(this.request.url);return e?.search!==false?this.path()+t.search:this.path()}keyArray(e){let t=new URL(this.request.url),n=this.path().split("/");return (e?.search!==false?[...n,t.searchParams.toString()]:n).filter(Boolean)}toString(){return this.key({search:true})}toJSON(){return {url:this.request.url,method:this.request.method}}};var k=class extends y{getDefaultOptions(){return {basepath:"/api/auth",credentials:"include"}}async login(e,t){let n=await this.post([e,"login"],t,{credentials:this.options.credentials});return n.ok&&n.body.token&&await this.options.onTokenUpdate?.(n.body.token),n}async register(e,t){let n=await this.post([e,"register"],t,{credentials:this.options.credentials});return n.ok&&n.body.token&&await this.options.onTokenUpdate?.(n.body.token),n}async actionSchema(e,t){return this.get([e,"actions",t,"schema.json"])}async action(e,t,n){return this.post([e,"actions",t],n)}async loginWithPassword(e){return this.login("password",e)}async registerWithPassword(e){return this.register("password",e)}me(){return this.get(["me"])}strategies(){return this.get(["strategies"])}async logout(){await this.options.onTokenUpdate?.(void 0);}};function P(i,e){let t=new Set(e),n={};for(let[r,o]of Object.entries(i))t.has(r)||(n[r]=o);return n}var A=class extends y{getDefaultOptions(){return {basepath:"/api/data",queryLengthLimit:1e3,defaultQuery:{limit:10}}}requireObjectSet(e,t){if(!e||typeof e!="object"||Object.keys(e).length===0)throw new Error(t??"object is required")}readOne(e,t,n={}){return this.get(["entity",e,t],n)}readOneBy(e,t={}){return this.readMany(e,{...t,limit:1,offset:0}).refine(n=>n[0])}readMany(e,t={}){let n=t??this.options.defaultQuery,r=this.get(["entity",e],n);return r.request.url.length<=this.options.queryLengthLimit?r:this.post(["entity",e,"query"],n)}readManyByReference(e,t,n,r={}){return this.get(["entity",e,t,n],r??this.options.defaultQuery)}createOne(e,t){return this.post(["entity",e],t)}createMany(e,t){if(!t||!Array.isArray(t)||t.length===0)throw new Error("input is required");return this.post(["entity",e],t)}updateOne(e,t,n){if(!t)throw new Error("ID is required");return this.patch(["entity",e,t],n)}updateMany(e,t,n){return this.requireObjectSet(t),this.patch(["entity",e],{update:n,where:t})}deleteOne(e,t){if(!t)throw new Error("ID is required");return this.delete(["entity",e,t])}deleteMany(e,t){return this.requireObjectSet(t),this.delete(["entity",e],t)}count(e,t={}){return this.post(["entity",e,"fn","count"],t)}exists(e,t={}){return this.post(["entity",e,"fn","exists"],t)}};var D=class extends y{getDefaultOptions(){return {basepath:"/api/media",upload_fetcher:fetch,init:{}}}listFiles(){return this.get(["files"])}getFile(e){return this.get(["file",e],void 0,{headers:{Accept:"*/*"}})}async getFileStream(e){let{res:t}=await this.getFile(e);if(!t.ok||!t.body)throw new Error("Failed to fetch file");return t.body}async download(e){let{res:t}=await this.getFile(e);if(!t.ok||!t.body)throw new Error("Failed to fetch file");return await t.blob()}getFileUploadUrl(e){return e?this.getUrl(`/upload/${e.path}`):this.getUrl("/upload")}getEntityUploadUrl(e,t,n){return this.getUrl(`/entity/${e}/${t}/${n}`)}getUploadHeaders(){return this.options.token_transport==="header"&&this.options.token?new Headers({Authorization:`Bearer ${this.options.token}`}):new Headers}uploadFile(e,t){let n={"Content-Type":"application/octet-stream",...t?._init?.headers||{}},r=t?.filename||"";try{typeof e.type<"u"&&(n["Content-Type"]=e.type),t?.filename||(r=e.name);}catch{}r&&r.length>0&&r.includes("/")&&(r=r.split("/").pop()||"");let o={...this.options.init,...t?._init||{},headers:n};if(t?.path)return this.post(t.path,e,o);if(!r||r.length===0)throw new Error("Invalid filename");return this.post(t?.path??["upload",r],e,o)}async upload(e,t={}){if(e instanceof Request||typeof e=="string"){let r=await(t.fetcher??this.options.upload_fetcher)(e);if(!r.ok||!r.body)throw new Error("Failed to fetch file");return this.uploadFile(r.body,t)}else if(e instanceof Response){if(!e.body)throw new Error("Invalid response");return this.uploadFile(e.body,{...t??{},_init:{...t._init??{},headers:{...t._init?.headers??{},"Content-Type":e.headers.get("Content-Type")||"application/octet-stream"}}})}return this.uploadFile(e,t)}async uploadToEntity(e,t,n,r,o){return this.upload(r,{...o,path:["entity",e,t,n]})}deleteFile(e){return this.delete(["file",e])}};var x=class extends y{getDefaultOptions(){return {basepath:"/api/system"}}readConfig(){return this.get("config")}readSchema(e){return this.get("schema",{config:e?.config?1:0,secrets:e?.secrets?1:0,fresh:e?.fresh?1:0})}setConfig(e,t,n){return this.post(["config","set",e].join("/")+`?force=${n?1:0}`,t)}addConfig(e,t,n){return this.post(["config","add",e,t],n)}patchConfig(e,t,n){return this.patch(["config","patch",e,t],n)}overwriteConfig(e,t,n){return this.put(["config","overwrite",e,t],n)}removeConfig(e,t){return this.delete(["config","remove",e,t])}};var m=class{constructor(e={}){this.options=e;this.verified=e.verified===true,"request"in e&&e.request?(this.options.host=e.host??new URL(e.request.url).origin,this.options.headers=e.headers??e.request.headers,this.extractToken()):"token"in e&&e.token?(this.token_transport="header",this.updateToken(e.token,{trigger:false})):"user"in e&&e.user?(this.token_transport="none",this.user=e.user,this.verified=e.verified!==false):this.extractToken(),this.buildApis();}token;user;verified=false;token_transport="header";system;data;auth;media;get fetcher(){return this.options.fetcher??fetch}get baseUrl(){return this.options.host??"http://localhost"}get tokenKey(){return this.options.key??"auth"}extractToken(){if(this.verified=false,this.options.headers){let e=K(this.options.headers.get("cookie"),"auth");if(e){this.token_transport="cookie",this.updateToken(e);return}let t=this.options.headers.get("authorization")?.replace("Bearer ","");if(t){this.token_transport="header",this.updateToken(t);return}}else this.storage&&this.storage.getItem(this.tokenKey).then(e=>{this.token_transport="header",this.updateToken(e?String(e):void 0);});}get storage(){return this.options.storage?{getItem:async e=>await this.options.storage.getItem(e),setItem:async(e,t)=>await this.options.storage.setItem(e,t),removeItem:async e=>await this.options.storage.removeItem(e)}:null}updateToken(e,t){if(this.token=e,this.verified=false,e?this.user=P(decode(e).payload,["iat","iss","exp"]):this.user=void 0,this.storage){let n=this.tokenKey;e?this.storage.setItem(n,e).then(()=>{this.options.onAuthStateChange?.(this.getAuthState());}):this.storage.removeItem(n).then(()=>{this.options.onAuthStateChange?.(this.getAuthState());});}else t?.trigger!==false&&this.options.onAuthStateChange?.(this.getAuthState());t?.rebuild&&this.buildApis();}markAuthVerified(e){return this.verified=e,this}isAuthVerified(){return this.verified}getAuthState(){return {token:this.token,user:this.user,verified:this.verified}}isAuthenticated(){let{token:e,user:t}=this.getAuthState();return !!e&&!!t}async getVerifiedAuthState(){return await this.verifyAuth(),this.getAuthState()}async verifyAuth(){if(!this.token){this.markAuthVerified(false);return}try{let{ok:e,data:t}=await this.auth.me(),n=t?.user;if(!e||!n)throw new Error;this.user=n,this.markAuthVerified(!0);}catch{this.markAuthVerified(false),this.updateToken(void 0);}}getUser(){return this.user||null}getParams(){return Object.freeze({host:this.baseUrl,token:this.token,headers:this.options.headers,token_transport:this.token_transport,verbose:this.options.verbose})}buildApis(){let e=this.getParams(),t=this.options.fetcher;this.system=new x(e,t),this.data=new A({...e,...this.options.data},t),this.auth=new k({...e,credentials:this.options.storage?"omit":"include",...this.options.auth,onTokenUpdate:n=>{this.updateToken(n,{rebuild:true}),this.options.auth?.onTokenUpdate?.(n);}},t),this.media=new D({...e,...this.options.media},t);}};function K(i,e){if(!i)return null;for(let t of i.split("; ")){let[n,r]=t.split("=");if(n===e&&r)return decodeURIComponent(r)}return null}var E=createContext(void 0),W=({children:i,host:e,baseUrl:t=e,...n})=>{let r=F(),o=O(),s=t??o?.baseUrl??"",a;if(r&&(a=r.user),!s)try{s=window.location.origin;}catch{}let d={user:a,...n,host:s},p=useMemo(()=>new m({...d,verbose:R(),onAuthStateChange:l=>{n.onAuthStateChange?.(l),(!u?.token||l.token!==u?.token)&&c(l);}}),[JSON.stringify(d)]),[u,c]=useState(d.user?p.getAuthState():void 0);return jsx(E.Provider,{value:{baseUrl:p.baseUrl,api:p,authState:u},children:i})},f=i=>{let e=useContext(E);return !e?.api||i&&i.length>0&&i!==e.baseUrl?new m({host:i??""}):e.api},O=()=>useContext(E),Q=()=>O()?.baseUrl;function F(){let i={logout_route:"/api/auth/logout",admin_basepath:""};return typeof window<"u"&&window.__BKND__?{...i,...window.__BKND__}:i}var Ce=(i,e)=>{let t=f(),n=i(t),r=e?.refine??(d=>d),o=()=>n.execute().then(r),s=n.key();return {...z(e?.enabled===false?null:s,o,e),promise:n,key:s,api:t}},ve=(i,e)=>{let[t,n]=useState(false),r=f(),o=p=>i(r,p),s=e?.refine??(p=>p),a=H((p,u)=>u&&!u.length?(n(true),null):o(p).request.url,p=>new h(new Request(p),{fetcher:r.fetcher},s).execute(),{revalidateFirstPage:false}),d=a.data?[].concat(...a.data):[];return {...a,_data:a.data,data:d,endReached:t,promise:o(a.size),key:o(a.size).key(),api:r}},I=i=>{let e=useSWRConfig().mutate,t=f();return async n=>{let r="";return typeof n=="string"?r=n:typeof n=="function"&&(r=n(t).key()),i?.exact?e(r):e(o=>typeof o=="string"&&o.startsWith(r))}};var g=class extends Error{constructor(t,n){let r=n;"error"in t&&(r=t.error,n&&(r=`${n}: ${r}`));super(r??"UseEntityApiError");this.response=t;}},ee=(i,e)=>{let t=f().data;return {create:async n=>{let r=await t.createOne(i,n);if(!r.ok)throw new g(r,`Failed to create entity "${i}"`);return r},read:async n=>{let r=e?await t.readOne(i,e,n):await t.readMany(i,n);if(!r.ok)throw new g(r,`Failed to read entity "${i}"`);return r},update:async(n,r=e)=>{if(!r)throw new Error("id is required");let o=await t.updateOne(i,r,n);if(!o.ok)throw new g(o,`Failed to update entity "${i}"`);return o},_delete:async(n=e)=>{if(!n)throw new Error("id is required");let r=await t.deleteOne(i,n);if(!r.ok)throw new g(r,`Failed to delete entity "${i}"`);return r}}};function w(i,e,t,n){return "/"+[...i.options?.basepath?.split("/")??[],e,...t?[t]:[]].filter(Boolean).join("/")+(n?"?"+encodeSearch(n):"")}var te=(i,e,t,n)=>{let r=f().data,o=w(r,i,e,t),{read:s,...a}=ee(i,e),d=()=>s(t??{}),p=z(n?.enabled===false?null:o,d,{revalidateOnFocus:false,keepPreviousData:true,...n}),u=async l=>{let T=w(r,i,l);return mutate(b=>typeof b=="string"&&b.startsWith(T),void 0,{revalidate:true})},c=objectTransform(a,l=>async(...T)=>{let b=await l(...T);return n?.revalidateOnMutate!==false&&await u(),b});return {...p,...c,mutate:u,mutateRaw:p.mutate,api:r,key:o}};async function U(i,e,t,n){function r(s,a){return typeof s<"u"&&typeof a<"u"&&"id"in s&&s.id===t?{...s,...a}:s}let o=w(i,e);return mutate(s=>typeof s=="string"&&s.startsWith(o),async s=>{if(!(typeof s>"u"))return Array.isArray(s)?s.map(a=>r(a,n)):r(s,n)},{revalidate:false})}var Ne=(i,e,t)=>{let{data:n,...r}=te(i,e,void 0,{...t,enabled:false});return {...r,mutate:e?s=>U(r.api,i,e,s):(s,a)=>U(r.api,i,s,a)}};var ne=i=>{let e=f(i?.baseUrl),t=I(),{authState:n}=O(),r=n?.verified??false;async function o(u){return (await e.auth.login("password",u)).data}async function s(u){return (await e.auth.register("password",u)).data}function a(u){e.updateToken(u);}async function d(){e.updateToken(void 0),t();}async function p(){await e.verifyAuth();}return {data:n,user:n?.user,token:n?.token,verified:r,login:o,register:s,logout:d,setToken:a,verify:p}};
export{m as Api,W as ClientProvider,h as FetchPromise,g as UseEntityApiError,w as makeKey,U as mutateEntityCache,f as useApi,ve as useApiInfiniteQuery,Ce as useApiQuery,ne as useAuth,Q as useBaseUrl,F as useBkndWindowContext,ee as useEntity,Ne as useEntityMutate,te as useEntityQuery,I as useInvalidate};