UNPKG

bknd

Version:

Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, React Router, Astro, Cloudflare, Bun, Node, AWS Lambda & more.

2 lines (1 loc) 7.88 kB
import{s as y,jsc as L,mergeObject as H,pickHeaders2 as Q}from"bknd/utils";var D=y.partialObject({dpr:y.number({minimum:1,maximum:3}),fit:y.string({enum:["scale-down","contain","cover","crop","pad"]}),format:y.string({enum:["auto","avif","webp","jpeg","baseline-jpeg","json"],default:"auto"}),height:y.number(),width:y.number(),metadata:y.string({enum:["copyright","keep","none"]}),quality:y.number({minimum:1,maximum:100})});function W({accessUrl:t="/api/plugin/image/optimize",resolvePath:o="/api/media/file",explain:n=!1,defaultOptions:e={},fixedOptions:i={},fallbackRedirect:a=!1}={}){if(["/api","/admin","/api/plugin"].includes(t)||t.length<2)throw new Error(`Disallowed accessUrl: ${t}`);return l=>({name:"cf-image-optimization",onBuilt:()=>{n&&l.server.get(t,async u=>u.json({searchParams:D.toJSON()})),l.server.get(`${t}/:path{.+$}`,L("query",D),async u=>{let s=u.req.raw,c=new URL(s.url);if(!l.module.media?.storage)throw new Error("No media storage configured");let h=u.req.param("path");if(!h)throw new Error("No url provided");let x=`${c.origin}${o}/${h}`,O=H(structuredClone(e),u.req.valid("query"),structuredClone(i));if(O.format&&O.format==="auto"){let b=s.headers.get("Accept");/image\/avif/.test(b)?O.format="avif":/image\/webp/.test(b)&&(O.format="webp")}let p=new Request(x,{headers:s.headers}),m=await fetch(p,{cf:{image:O}});if(!m.ok&&a)return new Response(null,{status:307,headers:{Location:x}});let g=Q(m.headers,["Content-Type","Content-Length","Age","Date","Last-Modified"]);return g.set("Cache-Control","public, max-age=31536000, immutable"),new Response(m.body,{status:m.status,statusText:m.statusText,headers:g})})}})}import{showRoutes as J}from"hono/dev";function G({once:t=!1}={}){let o=!1;return n=>({name:"bknd-show-routes",onBuilt:()=>{t&&o||(o=!0,J(n.server))}})}import{App as K}from"bknd";function V({enabled:t=!0,includeSecrets:o=!1,includeFirstBoot:n=!1,write:e}){let i=!0,a=(r,l=!1)=>l?r.toJSON(!0):r.modules.extractSecrets().configs;return r=>({name:"bknd-sync-config",onBuilt:async()=>{t&&(r.emgr.onEvent(K.Events.AppConfigUpdatedEvent,async()=>{await e?.(a(r,o))},{id:"sync-config"}),i&&n&&(i=!1,await e?.(a(r,o))))}})}import{App as Y,EntityTypescript as j}from"bknd";function X({enabled:t=!0,includeFirstBoot:o=!1,write:n}){let e=!0;return i=>({name:"bknd-sync-types",onBuilt:async()=>{t&&(i.emgr.onEvent(Y.Events.AppConfigUpdatedEvent,async()=>{await n?.(new j(i.em))},{id:"sync-types"}),e&&o&&(e=!1,await n?.(new j(i.em))))}})}import{ModuleManagerEvents as Z}from"bknd";function N({enabled:t=!0,includeFirstBoot:o=!1,write:n}){let e=!0;return i=>({name:"bknd-sync-secrets",onBuilt:async()=>{if(!t)return;let a=i.modules;i.emgr.onEvent(Z.ModuleManagerSecretsExtractedEvent,async({params:{secrets:r}})=>{await n?.(r)},{id:"sync-secrets"}),e&&o&&(e=!1,await n?.(a.extractSecrets().secrets))}})}import{em as ee,entity as te,datetime as q,DatabaseEvents as B}from"bknd";import{$console as ne}from"bknd/utils";function ie({entities:t=[],setUpdatedOnCreate:o=!0}){return n=>({name:"timestamps",schema:()=>{if(t.length===0){ne.warn("No entities specified for timestamps plugin");return}let e=n.em.entities.map(i=>i.name);return ee(Object.fromEntries(t.filter(i=>e.includes(i)).map(i=>[i,te(i,{created_at:q(),updated_at:q()})])))},onBuilt:async()=>{n.emgr.onEvent(B.MutatorInsertBefore,e=>{let{entity:i,data:a}=e.params;return t.includes(i.name)?{...a,created_at:new Date,updated_at:o?new Date:null}:a},{mode:"sync",id:"bknd-timestamps"}),n.emgr.onEvent(B.MutatorUpdateBefore,async e=>{let{entity:i,data:a}=e.params;return t.includes(i.name)?{...a,updated_at:new Date}:a},{mode:"sync",id:"bknd-timestamps"})}})}import{datetime as _,em as oe,entity as ae,enumm as re,Exception as S,text as k,DatabaseEvents as R}from"bknd";import{invariant as E,s as d,jsc as v,HttpStatus as P,threwAsync as se,randomString as pe,$console as me,pickKeys as M}from"bknd/utils";import{Hono as le}from"hono";var ue={action:re({enum:["login","register"]}),code:k().required(),email:k().required(),created_at:_(),expires_at:_().required(),used_at:_()},w=class extends S{name="OTPError";code=P.BAD_REQUEST};function ce({generateCode:t,apiBasePath:o="/api/auth/otp",ttl:n=600,entity:e="users_otp",entityConfig:i,generateEmail:a,showActualErrors:r=!1,allowExternalMutations:l=!1,sendEmail:u=!0}={}){return s=>({name:"email-otp",schema:()=>oe({[e]:ae(e,ue,{name:"Users OTP",sort_dir:"desc",primary_format:s.module.data.config.default_primary_format,...i},"generated")},({index:c},A)=>{let h=A[e];c(h).on(["email","expires_at","code"])}),onBuilt:async()=>{let c=s.module.auth;E(c&&c.enabled===!0,"Auth is not enabled"),E(!u||s.drivers?.email,"Email driver is not registered");let A=t??(()=>Math.floor(1e5+Math.random()*9e5).toString()),h=a??(p=>({subject:"OTP Code",body:`Your OTP code is: ${p.code}`})),x=s.em.fork(),O=new le().post("/login",v("json",d.strictObject({email:d.string({format:"email"}),code:d.string({minLength:1}).optional()})),v("query",d.object({redirect:d.string().optional()})),async p=>{let{email:m,code:g}=p.req.valid("json"),{redirect:b}=p.req.valid("query"),C=await U(s,m);if(g){let f=await z(s,e,m,g,"login");await x.mutator(e).updateOne(f.id,{used_at:new Date});let T=await c.authenticator.jwt(C);return c.authenticator.respondWithUser(p,{user:C,token:T},{redirect:b})}else{let f=await F(s,{generateCode:A,ttl:n,entity:e},C,"login");return u&&await I(s,f,{generateEmail:h}),p.json({sent:!0,data:M(f,["email","action","expires_at"])},P.CREATED)}}).post("/register",v("json",d.object({email:d.string({format:"email"}),code:d.string({minLength:1}).optional()})),v("query",d.object({redirect:d.string().optional()})),async p=>{let{email:m,code:g,...b}=p.req.valid("json"),{redirect:C}=p.req.valid("query");if(!await se(U(s,m)))throw new S("User already exists",P.BAD_REQUEST);if(g){let f=await z(s,e,m,g,"register");await x.mutator(e).updateOne(f.id,{used_at:new Date});let T=await s.createUser({...b,email:m,password:pe(32,!0)}),$=await c.authenticator.jwt(T);return c.authenticator.respondWithUser(p,{user:T,token:$},{redirect:C})}else{let f=await F(s,{generateCode:A,ttl:n,entity:e},{email:m},"register");return u&&await I(s,f,{generateEmail:h}),p.json({sent:!0,data:M(f,["email","action","expires_at"])},P.CREATED)}}).onError(p=>{throw r||p instanceof w?p:new S("Invalid credentials",P.BAD_REQUEST)});s.server.route(o,O),l!==!0&&fe(s,e)}})}async function U(t,o){let n=t.module.auth.config.entity_name,{data:e}=await t.em.repo(n).findOne({email:o});if(!e)throw new S("User not found",P.BAD_REQUEST);return e}async function F(t,o,n,e){let{generateCode:i,ttl:a,entity:r}=o,l=i?.(n);if(!l)throw new w("Failed to generate code");await de(t,r,n.email,a);let{data:u}=await t.em.fork().mutator(r).insertOne({code:l,email:n.email,action:e,created_at:new Date,expires_at:new Date(Date.now()+a*1e3)});return me.log("[OTP Code]",l),u}async function I(t,o,n){let{generateEmail:e}=n,{subject:i,body:a}=await e(o);await t.drivers?.email?.send(o.email,i,a)}async function z(t,o,n,e,i){E(n,"[OTP Plugin]: Email is required"),E(e,"[OTP Plugin]: Code is required");let a=t.em.fork(),{data:r}=await a.repo(o).findOne({email:n,code:e,action:i});if(!r)throw new w("Invalid code");if(r.expires_at<new Date)throw new w("Code expired");if(r.used_at)throw new w("Code already used");return r}async function de(t,o,n,e){E(e>0,"[OTP Plugin]: TTL must be greater than 0"),E(n,"[OTP Plugin]: Email is required"),await t.em.fork().mutator(o).updateWhere({expires_at:new Date(Date.now()-1e3)},{email:n,used_at:{$isnull:!0}})}function fe(t,o){[R.MutatorInsertBefore,R.MutatorUpdateBefore].forEach(n=>{t.emgr.onEvent(n,e=>{if(e.params.entity.name===o)throw new w("Mutations of the OTP entity are not allowed")},{mode:"sync",id:"bknd-email-otp"})})}export{W as cloudflareImageOptimization,ce as emailOTP,G as showRoutes,V as syncConfig,N as syncSecrets,X as syncTypes,ie as timestamps};