UNPKG

kolenkainc-honohub

Version:

The CMS for building apps and APIs with Hono

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