UNPKG

kolenkainc-honohub

Version:

The CMS for building apps and APIs with Hono

2 lines (1 loc) 7.13 kB
import{Hono as e}from"hono";import{zValidator as t}from"@hono/zod-validator";import{count as o,eq as n,sql as r,or as a,ilike as i,desc as s,asc as c,getTableName as l,getTableColumns as u,inArray as f}from"drizzle-orm";import{createInsertSchema as m}from"drizzle-zod";import{HTTPException as d}from"hono/http-exception";import{z as h}from"zod";function g(t){const{plugins:o}=t;let n=new e;for(const e of o)try{const o=e.bootstrap?.({app:n,config:t});o&&(n=o)}catch(t){console.error(`[${e.name}] Hub Plugin Bootstrap Error`,t)}return n}function p(l,u){let f=(new e).use((async(e,t)=>{if(!await u.access(e))throw new d(403,{message:"Access denied"});await t()}));const g=l.db;let p=m(u.schema);u.admin.fields&&(p=p.pick(u.admin.fields.reduce(((e,t)=>{let o=t;return"object"==typeof t&&"name"in t&&(o=t.name),e[o]=!0,e}),{})));const y=g.select({count:o()}).from(u.schema).prepare(`${u.slug}_count_query`),w=g.select().from(u.schema).where(n(u.queryKey,r.placeholder("id"))).prepare(`${u.slug}_retrieve_query`),b=g.delete(u.schema).where(n(u.queryKey,r.placeholder("id"))).$dynamic();"$returningId"in b?b.$returningId?.():b.returning(),b.prepare(`${u.slug}_delete_query`);const x=("boolean"!=typeof u.pagination?u.pagination?.defaultLimit:void 0)??10,q=h.object({limit:h.coerce.number().nonnegative().default(x),offset:h.coerce.number().nonnegative().default(0),search:h.string().optional(),sortBy:h.string().optional()});if(f.get("/",t("query",q),(async e=>{for(const t of u.hooks.beforeRead??[])await t({context:e});const t=e.req.valid("query"),n=g.select().from(u.schema).$dynamic(),r=g.select({count:o()}).from(u.schema);t.search&&u.listSearchableFields.length>0&&n.where(a(...u.listSearchableFields.map((e=>i(u.schema[e],`%${t.search}%`)))));const l=t.sortBy??u.defaultSort;let f,m,h,p,y=String(l);if(y.startsWith("-")?(y=y.slice(1),f=s):f=c,l&&y in u.schema&&n.orderBy(f(u.schema[y])),u.pagination){if(u.pagination.maxLimit&&t.limit>u.pagination.maxLimit)throw new d(400,{message:"The limit value exceeds the maximum allowed limit."});n.limit(t.limit).offset(t.offset),[m,h]=await Promise.all([n.execute(),r.then((e=>e[0].count))]),p={results:m,count:h}}else p=await n.execute();for(const t of u.hooks.afterRead??[]){const o=await t({context:e,doc:p});void 0!==o&&(p=o)}return e.json(p)})),f.get("/count",(async e=>e.json({count:await y.execute().then((e=>e[0].count))}))),f.post("/",(async e=>{let t=await("application/json"===e.req.header("Content-Type")?e.req.json():e.req.formData());if(!t)throw new d(404,{message:"Request body is missing or empty"});for(const o of u.hooks.beforeValidate??[]){const n=await o({context:e,data:t});void 0!==n&&(t=n)}const o=await p.safeParseAsync(t);if(!o.success)return e.json(o,400);let n=o.data;for(const t of u.hooks.beforeChange??[]){const o=await t({context:e,data:n});void 0!==o&&(n=o)}const r=g.insert(u.schema).values(n).$dynamic();"$returningId"in r&&"function"==typeof r.$returningId?r.$returningId?.():r.returning();let a=await r.execute();for(const t of u.hooks.afterChange??[]){const o=await t({context:e,doc:a,previousDoc:n});void 0!==o&&(a=o)}return e.json(a)})),f.get("/:id",(async e=>{for(const t of u.hooks.beforeRead??[])await t({context:e});let t=await w.execute({id:e.req.param("id")}).then((e=>e[0]));if(!t)throw new d(404,{message:"Document not found"});for(const o of u.hooks.afterRead??[]){const n=await o({context:e,doc:t});void 0!==n&&(t=n)}return e.json(t)})),f.put("/:id",(async e=>{let t=await("application/json"===e.req.header("Content-Type")?e.req.json():e.req.formData());if(!t)throw new d(404,{message:"Request body is missing or empty"});const o=await w.execute({id:e.req.param("id")}).then((e=>e[0]));if(!o)throw new d(404,{message:"Document not found"});for(const n of u.hooks.beforeValidate??[]){const r=await n({context:e,data:t,originalDoc:o});void 0!==r&&(t=r)}const r=await p.safeParseAsync(t);if(!r.success)return e.json(r,400);let a=r.data;for(const t of u.hooks.beforeChange??[]){const n=await t({context:e,data:a,originalDoc:o});void 0!==n&&(a=n)}const i=g.update(u.schema).set(a).where(n(u.queryKey,e.req.param("id"))).$dynamic();"$returningId"in i&&"function"==typeof i.$returningId?i.$returningId?.():i.returning();let s=await i.execute();for(const t of u.hooks.afterChange??[]){const o=await t({context:e,doc:s,previousDoc:a});void 0!==o&&(s=o)}return e.json(s)})),f.delete("/:id",(async e=>{for(const t of u.hooks.beforeDelete??[])await t({context:e});let t=await b.execute({id:e.req.param("id")});for(const o of u.hooks.afterDelete??[]){const n=await o({context:e,doc:t});void 0!==n&&(t=n)}return e.json(t)})),u.admin.actions.length>0){const o=new e;for(const{name:e,action:n}of u.admin.actions??[])o.post(`/${e}`,t("json",h.object({items:h.array(h.any()).min(1).max(100)})),(async e=>{const{items:t}=e.req.valid("json");try{await n({items:t,context:e,db:g,config:u})}catch(e){throw console.error(e),new d(400,{message:"Unable to complete the action!"})}return e.json(null)}));f.route("/actions",o)}for(const e of u.plugins)try{const t=e.bootstrap?.({app:f,config:u});t&&(f=t)}catch(t){console.error(`[${e.name}] Collection Plugin Bootstrap Error`,t)}return f}function y(){return{name:"honhub-rest-api",bootstrap(e){for(let t=e.config.collections.length-1;t>=0;t--){const o=e.config.collections[t];e.app.route(`collections/${o.slug}`,p(e.config,o))}return e.app}}}function w(e){return g({...e,plugins:[y(),...e.plugins]})}function b(e){const{schema:t,slug:o=l(t),admin:n={},queryKey:r=x(t),access:a=()=>!0,defaultSort:i,listSearchableFields:s=[],pagination:c=!1,plugins:u=[],hooks:m={}}=e;if(c){if(c.defaultLimit&&c.defaultLimit<=0)throw new Error("defaultLimit must be greater than zero.");if(c.maxLimit&&c.maxLimit<=0)throw new Error("maxLimit must be greater than zero.");if(c.maxLimit&&c.defaultLimit&&c.maxLimit<c.defaultLimit)throw new Error("maxLimit must be greater than defaultLimit.")}const h=[],g={name:"bulk_delete",label:"Bulk Delete",icon:"TrashIcon",level:!0,action:({items:e,db:t,config:o})=>{const n=[];for(const t of e)t&&"object"==typeof t&&o.queryKey?.name in t?n.push(t[o.queryKey.name]):new d(400,{message:`Unable to find the query key '${o.queryKey.name}' in the given entries.`});t.delete(o.schema).where(f(o.queryKey,n)).execute()}};n&&!1!==n.actions&&h.push(g,...Array.isArray(n.actions)?n.actions:[]);let p={slug:o,admin:{...n,label:n.label??o,actions:h},schema:t,queryKey:r,access:a,defaultSort:i,listSearchableFields:s,pagination:c,plugins:u,hooks:m};for(const e of u)try{const t=e.register?.(p);t&&(p=t)}catch(t){console.error(`[${e.name}] Collection Plugin Registration Error`,t)}return p}function x(e){const t=u(e);for(const[e,o]of Object.entries(t))if(o.primary)return o;throw new Error(`Unable to find primary key for ${l(e)} table!`)}function q(e){const{db:t,collections:o=[],plugins:n=[],serverUrl:r="/",routes:a=[]}=e;let i={db:t,collections:o,plugins:n,serverUrl:r,routes:a};for(const e of n)try{const t=e.register?.(i);t&&(i=t)}catch(t){console.error(`[${e.name}] Hub Plugin Registration Error`,t)}return i}export{g as createBase,w as createHub,b as defineCollection,q as defineHub,y as useRestAPI};