@stacksjs/orm 0.74.27 → 0.74.29
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/dist/api-routes.d.ts +1 -2
- package/dist/routes.js +1 -1
- package/package.json +23 -23
package/dist/api-routes.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { IndexPaginator } from './auto-crud';
|
|
2
2
|
import type { ModelCreateData, ModelRow } from './model-types';
|
|
3
|
-
/** The listing envelope: rows
|
|
3
|
+
/** The listing envelope: rows and the flat paginator. */
|
|
4
4
|
export declare interface ApiIndexResponse<TRow> extends IndexPaginator {
|
|
5
5
|
data: TRow[]
|
|
6
|
-
meta: Record<string, unknown>
|
|
7
6
|
}
|
|
8
7
|
/** The single-row envelope, used by show, store and update alike. */
|
|
9
8
|
export declare interface ApiItemResponse<TRow> {
|
package/dist/routes.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import ct from"process";import{route as U}from"@stacksjs/router";import{env as H}from"@stacksjs/env";import{projectPath as ke,storagePath as dt}from"@stacksjs/path";import{createQueryBuilder as ft,defaultConfig as Qe,setConfig as We}from"@stacksjs/query-builder";import{HttpError as B}from"@stacksjs/error-handling";import{log as q}from"@stacksjs/logging";function Ze(e){let t=e;return t?.code==="SQLITE_CONSTRAINT_UNIQUE"||t?.code==="SQLITE_CONSTRAINT"||t?.code==="23505"||t?.errno===1062||/unique|duplicate/i.test(t?.message??"")}function de(e,t,n){let r=e;if(e instanceof Error&&typeof r.status==="number"&&Number.isInteger(r.status)&&r.status>=400&&r.status<600){let s={error:e.message};if(r.details!==void 0)s.details=r.details;return{status:r.status,body:s}}if(Ze(e))return{status:409,body:{error:`${t} already exists`}};return{status:500,body:{error:`Failed to ${n} ${t}`,detail:String(e)}}}function _(e){return e.replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/([A-Z])([A-Z][a-z])/g,"$1_$2").toLowerCase()}function fe(e){let t={};for(let[n,r]of Object.entries(e))t[_(n)]=r;return t}function Te(e,t){let n=String(t||"").replace(/^\/+|\/+$/g,""),r=String(e).replace(/^\/+/,"");return`/api/${n?`${n}/`:""}${r}`}function Ae(e){if(!e)return null;let t=e.attributes??{};if(Object.prototype.hasOwnProperty.call(t,"teamId")||Object.prototype.hasOwnProperty.call(t,"team_id"))return"team_id";return e.belongsTo?.some((n)=>n==="Team")?"team_id":null}function Pe(e){if(!e)return null;let t=e.attributes??{};if(Object.prototype.hasOwnProperty.call(t,"userId")||Object.prototype.hasOwnProperty.call(t,"user_id"))return"user_id";return e.belongsTo?.some((n)=>n==="User")?"user_id":null}function De(e){return e?.ownership===!1}function $e(e,t,n){let r=Object.keys(e).find((o)=>_(o)===_(t));if(Array.isArray(n)){if(r===void 0)return{data:e,error:"Ownership value is required"};if(!n.some((o)=>String(o)===String(e[r])))return{data:e,error:"Ownership value is not available to this caller"};return{data:{...e,[t]:e[r]}}}if(n===null||n===void 0)return{data:e,error:"Caller has no ownership identity"};let s={...e};for(let o of Object.keys(s))if(_(o)===_(t))delete s[o];return s[t]=n,{data:s}}function Ie(e){if(!e)return[];let t=Object.entries(e.attributes??{}).filter(([,n])=>n?.fillable===!0).map(([n])=>n);for(let n of e.belongsTo??[]){if(typeof n!=="string"||!n.trim())continue;let r=_(n.trim()).split("_").filter(Boolean);if(r.length===0)continue;let s=`${r[0]}${r.slice(1).map((o)=>`${o[0]?.toUpperCase()}${o.slice(1)}`).join("")}Id`;t.push(s)}return[...new Set(t)]}function ge(e,t){if(!e||t.length===0)return{};let n={};for(let r of t){if(r in e){n[r]=e[r];continue}let s=_(r);if(s!==r&&s in e)n[r]=e[s]}return n}function et(e,t){if(e?.name!=="date"||t instanceof Date||typeof t!=="string")return t;let n=/^(\d{4})-(\d{2})-(\d{2})$/.exec(t);if(!n)return t;let r=Number(n[1]),s=Number(n[2]),o=Number(n[3]),a=new Date(Date.UTC(r,s-1,o));if(a.getUTCFullYear()!==r||a.getUTCMonth()!==s-1||a.getUTCDate()!==o)return t;return a}function pe(e,t){if(!t.length)return e;let n={...e};for(let r of t)delete n[r],delete n[_(r)];return n}function X(e,t){if(!e||t.length===0)return e;let n={...e};for(let r of t)delete n[r],delete n[_(r)];return n}var tt=["id","uuid","created_at","updated_at","deleted_at"];function Ne(e,t){let n=new Map;for(let r of[...Object.keys(e??{}),...tt]){let s=_(r);if(!/^\w+$/.test(s))continue;n.set(r,s),n.set(s,s)}for(let r of t)n.delete(r),n.delete(_(r));return n}function ve(e,t,n){if(!t)return e;let r=String(t).split(",").map((o)=>o.trim()).filter(Boolean),s=e;for(let o of r){let a=o.startsWith("-"),l=a?o.slice(1):o;if(!/^\w+$/.test(l))continue;let w=n.get(l);if(!w)continue;s=s.orderBy(w,a?"desc":"asc")}return s}var nt={string:{get:(e)=>e!=null?String(e):null,set:(e)=>e!=null?String(e):null},number:{get:(e)=>e!=null?Number(e):null,set:(e)=>e!=null?Number(e):null},integer:{get:(e)=>e!=null?Math.trunc(Number(e)):null,set:(e)=>e!=null?Math.trunc(Number(e)):null},float:{get:(e)=>e!=null?Number.parseFloat(String(e)):null,set:(e)=>e!=null?Number.parseFloat(String(e)):null},boolean:{get:(e)=>e===1||e==="1"||e===!0||e==="true",set:(e)=>e===!0||e===1||e==="1"||e==="true"?1:0},json:{get:(e)=>e==null?null:typeof e==="string"?rt(e):e,set:(e)=>e==null?null:typeof e==="string"?e:JSON.stringify(e)},datetime:{get:(e)=>e?new Date(e):null,set:(e)=>e instanceof Date?e.toISOString():e},date:{get:(e)=>e?new Date(e):null,set:(e)=>e instanceof Date?e.toISOString().split("T")[0]:e},array:{get:(e)=>e==null?[]:Array.isArray(e)?e:typeof e==="string"?st(e):[],set:(e)=>e==null?null:Array.isArray(e)?JSON.stringify(e):e}};function rt(e){try{return JSON.parse(e)}catch{return e}}function st(e){try{return JSON.parse(e)}catch{return[]}}function ye(e,t,n){if(!e||typeof e!=="object"||!t||Object.keys(t).length===0)return e;let r={...e};for(let[s,o]of Object.entries(t)){let a=typeof o==="string"?nt[o]:o;if(!a||typeof a[n]!=="function")continue;if(Object.prototype.hasOwnProperty.call(r,s))r[s]=a[n](r[s]);let l=_(s);if(l!==s&&Object.prototype.hasOwnProperty.call(r,l))r[l]=a[n](r[l])}return r}function me(e,t,n){let r=t?.attributes??{},s={};for(let[o,a]of Object.entries(r)){let l=a?.validation?.rule;if(!l||typeof l.validate!=="function")continue;let w=Object.prototype.hasOwnProperty.call(e,o);if(!w&&n==="updating")continue;let c=a!==null&&typeof a==="object"&&Object.prototype.hasOwnProperty.call(a,"default"),P=w?e[o]:c?a.default:void 0,v=et(l,P),D=l.validate(v);if(!D?.valid&&Array.isArray(D?.errors)&&D.errors.length>0)s[o]=D.errors.map((Q)=>a?.validation?.message?.[Q?.code]??Q?.message??"invalid")}return Object.keys(s).length===0?{valid:!0}:{valid:!1,errors:s}}function Ce(e,t,n){let r=xe(n);return e.find((s)=>s.method===t&&xe(String(s.path??""))===r)}function xe(e){return e.replace(/\{[^}]*\}/g,"{}").replace(/:[^/]+/g,"{}")}function ce(e){if(Array.isArray(e))return e.filter((t)=>typeof t==="string"&&t.length>0);return typeof e==="string"&&e?[e]:[]}function Ee(e){let t=typeof e==="object"&&e!==null&&"middleware"in e,n=e?.middleware;if(!t)return{read:["auth"],write:["auth"],declared:!1};if(n&&typeof n==="object"&&!Array.isArray(n)){let s=n;return{read:"read"in s?ce(s.read):["auth"],write:"write"in s?ce(s.write):["auth"],declared:!0}}let r=ce(n);return{read:r,write:r,declared:!0}}var Oe=15,ot=100;function je(e){let t=Number.parseInt(e.get("page")||String(1),10),n=Number.isFinite(t)?Math.max(1,t):1,r=Number.parseInt(e.get("per_page")||String(Oe),10),s=Math.min(Number.isFinite(r)?Math.max(1,r):Oe,ot);return{page:n,perPage:s,offset:(n-1)*s}}function oe(e,t){let n=new URL(e.toString());return n.searchParams.set("page",String(t)),`${n.pathname}${n.search}`}function ie(e,t,n,r,s,o){let a=(t-1)*n,l=r===0,w={page:t,per_page:n,from:l?null:a+1,to:l?null:a+r,has_more_pages:s,prev_page_url:t>1?oe(e,t-1):null,next_page_url:s?oe(e,t+1):null};if(o!==void 0&&!Number.isNaN(o)){let c=Math.max(1,Math.ceil(o/n));w.total=o,w.last_page=c,w.first_page_url=oe(e,1),w.last_page_url=oe(e,c)}return w}function we(e,t,n,r,s,o){let{page:a,...l}=ie(e,t,n,r,s,o);return{current_page:a,...l}}function he(e,t){return(t??e??"").toString().trim().toLowerCase()==="warn"?"warn":"deny"}function Me(e,t){if(e.length===0)return null;let n=[...e].sort().join(", ");if(t==="deny")return`[orm] security.api.rowScoping is 'deny': NOT registering store/update/destroy for ${e.length} model(s) `+`with no row-level scoping \u2014 ${n}. Give a model an \`ownership\` config or a \`team_id\` column to restore its writes.`;return`[orm] ${e.length} model(s) publish mutating routes with no row-level scoping, so an authenticated caller can write any row \u2014 `+`${n}. Scope one with an \`ownership\` config or a \`team_id\` column; intended for public catalog tables. Set \`security.api.rowScoping: 'deny'\` (config/security.ts) to stop generating these routes instead.`}import{readdirSync as it}from"fs";import{basename as at,extname as Ue,join as ut}from"path";function lt(e){let t=[],n=(r)=>{let s;try{s=it(r,{withFileTypes:!0})}catch{return}for(let o of s){let a=ut(r,o.name);if(o.isDirectory()){n(a);continue}if(![".ts",".js"].includes(Ue(o.name)))continue;if(o.name.startsWith(".")||o.name.startsWith("index"))continue;t.push(a)}};return n(e),t.sort()}async function Fe(e,t,n){for(let r of lt(e))try{let s=Ue(r),o=await import(`${r}?t=${Date.now()}`),a=o.default??o,l=a.name??at(r,s);t[l]={...a,name:l}}catch(s){n?.(r,s)}}async function Be(e){let t={};return await Fe(e.defaultsRoot,t,e.onImportError),await Fe(e.userRoot,t,e.onImportError),t}function ae(e){if(e?.ownership===!1)return null;if(e?.ownership)return e.ownership;let t=Ae(e);if(t)return{field:t,resolve:async(r,s)=>{let{resolveAuthenticatedTeamId:o}=await import("@stacksjs/auth");return o(be(s))}};let n=Pe(e);if(n)return{field:n,resolve:async(r)=>r?.id??null};return null}function be(e){let t=e?.headers?.get?.("authorization"),n=t&&/^Bearer\s+/i.test(t)?t.replace(/^Bearer\s+/i,"").trim():null,r=e?.headers?.get?.("cookie")||"";return{bearerToken:()=>n,cookies:{get:(s)=>{for(let o of r.split(";")){let a=o.indexOf("=");if(a===-1)continue;if(o.slice(0,a).trim()===s)return decodeURIComponent(o.slice(a+1).trim())}return null}}}}function kt(e="customer_id"){return{field:e,resolve:async(t)=>{let n=t?.id;if(n==null)return null;try{let{Customer:r}=await import("@stacksjs/orm");return(await r.where("user_id",n).first())?.id??null}catch{return null}}}}function St(e="id"){return{field:e,resolve:async(t)=>t?.id??null}}function xt(e="site_id"){return{field:e,resolve:async(t,n)=>{try{let[{resolveAuthenticatedTeamId:r},{Site:s}]=await Promise.all([import("@stacksjs/auth"),import("@stacksjs/orm")]),o=await r(be(n));if(o==null)return[];return(await s.where("team_id",o).get()??[]).map((l)=>l.id).filter((l)=>l!=null)}catch{return[]}}}}function Ot(e="id"){return{field:e,resolve:async(t,n)=>{try{let{resolveAuthenticatedTeamId:r}=await import("@stacksjs/auth");return await r(be(n))}catch{return null}}}}function Tt(e,t){return{field:t,resolve:async(n,r)=>{try{let s=await import("@stacksjs/orm"),o=s.models?.[e]??s[e]?.definition??null,a=ae(o);if(!a?.field||typeof a.resolve!=="function")return[];let l=await a.resolve(n,r);if(l==null||Array.isArray(l)&&l.length===0)return[];let w=s[e];if(!w)return[];return((Array.isArray(l)?await w.whereIn(a.field,l).get():await w.where(a.field,l).get())??[]).map((P)=>P.id).filter((P)=>P!=null)}catch{return[]}}}}var gt=ke("config/qb.ts");try{let e=(await import(gt)).default;We(e??Qe)}catch{q.debug("[orm] No config/qb.ts override found - deriving config from DB_CONNECTION");let e=H.DB_CONNECTION||"sqlite";We({...Qe,dialect:e,database:e==="sqlite"?{database:H.DB_DATABASE_PATH||"database/stacks.sqlite"}:{database:H.DB_DATABASE||"stacks",host:H.DB_HOST||"127.0.0.1",port:H.DB_PORT||(e==="postgres"?5432:3306),username:H.DB_USERNAME||(e==="postgres"?"postgres":"root"),password:H.DB_PASSWORD||""}})}var Re=await Be({defaultsRoot:dt("framework/defaults/app/Models"),userRoot:ke("app/Models"),onImportError:(e,t)=>q.warn(`[orm] Could not load model ${e}: ${t.message}`)}),pt=ft(),A=pt;function J(e,t){let n=Ce(U.routes,e,t);if(!n)return!1;let r=String(n.path??"");return q.info(`[orm] Skipping generated ${e} ${t} - already defined${r&&r!==t?` as ${r}`:""}.`),!0}function Ve(e){if(!e.attributes)return[];return Object.entries(e.attributes).filter(([t,n])=>n.hidden===!0).map(([t])=>t)}function Le(e){if(e.endsWith("y")&&!/[aeiou]y$/i.test(e))return`${e.slice(0,-1)}ies`;if(/(?:s|x|z|ch|sh)$/i.test(e))return`${e}es`;return`${e}s`}async function ze(e,t,n,r){if(!e.length)return e;let s=n.split(",").map((c)=>c.trim()).filter(Boolean);if(!s.length)return e;let o=Array.isArray(t.belongsTo)?t.belongsTo:[],a=Array.isArray(t.hasMany)?t.hasMany:[],l=Array.isArray(t.hasOne)?t.hasOne:[],w=new Map;for(let c of o)w.set(_(c),{kind:"belongsTo",modelName:c});for(let c of l)w.set(_(c),{kind:"hasOne",modelName:c});for(let c of a)w.set(Le(_(c)),{kind:"hasMany",modelName:c});for(let c of s){let P=w.get(c);if(!P)continue;let v=r[P.modelName];if(!v)continue;let D=v.table||Le(_(P.modelName)),Q=Ve(v),V=(W)=>X(re(W,v),Q);if(P.kind==="belongsTo"||P.kind==="hasOne"){let W=`${_(P.modelName)}_id`,C=[...new Set(e.map((S)=>S[W]).filter((S)=>S!=null))];if(!C.length){for(let S of e)S[c]=null;continue}let G=await A.selectFrom(D).whereIn("id",C).get(),L=new Map;for(let S of G??[])L.set(String(S.id),V(S));for(let S of e)S[c]=L.get(String(S[W]))??null}else{let W=String(t.name??""),C=`${_(W)}_id`,G=[...new Set(e.map((T)=>T.id).filter((T)=>T!=null))];if(!G.length){for(let T of e)T[c]=[];continue}let L=await A.selectFrom(D).whereIn(C,G).get(),S=new Map;for(let T of L??[]){let E=String(T[C]),ee=S.get(E)??[];ee.push(V(T)),S.set(E,ee)}for(let T of e)T[c]=S.get(String(T.id))??[]}}return e}async function He(e,t){let n=t?.set;if(!n||typeof n!=="object")return e;let r={...e};for(let[s,o]of Object.entries(n)){if(typeof o!=="function"||!(s in r))continue;try{r[s]=await o(r)}catch{}}return r}function Je(e,t){return ye(e,t?.casts,"set")}function re(e,t){return ye(e,t?.casts,"get")}function d(e,t=200){return new Response(JSON.stringify(e),{status:t,headers:{"Content-Type":"application/json"}})}var ue=1048576;async function _e(e){if(Number(e.headers?.get?.("content-length")??0)>ue)throw new B(413,`Request body exceeds ${ue}-byte limit`);if(e.jsonBody&&typeof e.jsonBody==="object")return e.jsonBody;if(e.formBody&&typeof e.formBody==="object")return e.formBody;try{let n=await e.clone().text();if(n.length>ue)throw new B(413,`Request body exceeds ${ue}-byte limit`);if(!n)return{};let r=JSON.parse(n);return r&&typeof r==="object"&&!Array.isArray(r)?r:{}}catch(n){if(n instanceof B)throw n;throw new B(400,`Invalid JSON body: ${n.message}`)}}function le(e){if(e==null)return null;if(typeof e==="number")return Number.isFinite(e)&&e>0?e:null;if(typeof e==="string"){let t=e.trim();if(!t)return null;if(/^\d+$/.test(t)){let n=Number(t);return Number.isFinite(n)&&n>0?n:null}return t}return null}function yt(e){let t=e.bearerToken;if(typeof t==="function"){let r=t.call(e);if(r)return r}let n=e.headers?.get?.("authorization")||e.headers?.get?.("Authorization")||"";if(typeof n==="string"&&n.startsWith("Bearer "))return n.substring(7);return null}async function Y(e){let t=e._authenticatedUser;if(t)return t;let n=yt(e);if(!n)return null;try{let{Auth:r}=await import("@stacksjs/auth"),s=await r.getUserFromToken(n);if(s)e._authenticatedUser=s;return s||null}catch{return null}}function ne(e,t){if(e==null||t==null)return!1;if(Array.isArray(t))return t.some((n)=>String(n)===String(e));return String(e)===String(t)}async function Z(e,t,n){let r=ae(e);if(!r||!r.field||typeof r.resolve!=="function")return{enforced:!1,value:null,field:"",bypass:!1};let s=typeof r.bypass==="function"?!!r.bypass(t,n):!1,o=null;try{o=await r.resolve(t,n)}catch{o=null}return{enforced:!0,value:o,field:String(r.field),bypass:s}}async function qe(e,t,n,r){let s=e?.authedFill?.[t];if(!s)return{};if(typeof s==="function")try{let o=await s(n,r);return o&&typeof o==="object"?o:{}}catch{return{}}if(typeof s==="object"){let o={};for(let[a,l]of Object.entries(s))try{o[a]=typeof l==="function"?await l(n,r):l}catch{}return o}return{}}var Ge=await(async()=>{let e=ct.env.STACKS_API_ROW_SCOPING;try{let t=(await import(ke("config/security.ts"))).default;return he(t?.api?.rowScoping,e)}catch{return he(void 0,e)}})(),Ke=[];for(let[e,t]of Object.entries(Re)){let n=t.traits?.useApi;if(!n)continue;let r=typeof n==="object"?n:{},s=r.uri||t.table||e.toLowerCase()+"s",o=r.routes||["index","show","store","update","destroy"],a=t.table||s,l=Ie(t),w=Ve(t),c=Te(s,r.prefix),P=Ne(t.attributes,w),{read:v,write:D,declared:Q}=Ee(n),V=["store","update","destroy"].some((p)=>o.includes(p));if(V&&Q&&D.length===0)q.warn(`[orm] ${e}: registering UNAUTHENTICATED mutating routes at ${c} (explicit \`middleware: []\` opt-out)`);if(["index","show"].some((p)=>o.includes(p))&&Q&&v.length===0)q.debug(`[orm] ${e}: registering public read routes at ${c} (declared \`middleware.read: []\`)`);let C=!!ae(t),G=C&&!v.includes("auth")?["auth",...v]:v,L=De(t),S=V&&!C&&!L;if(S)Ke.push(e);if(V&&L)q.debug(`[orm] ${e}: registering unscoped mutating routes at ${c} (declared \`ownership: false\`)`);let T=S&&Ge==="deny",E=(p,i=G)=>{let u=p;for(let f of i)if(u&&typeof u.middleware==="function")u=u.middleware(f);return u};if(o.includes("index")&&!J("GET",c))E(U.get(c,async(p)=>{try{let i=new URL(p.url),{page:u,perPage:f,offset:h}=je(i.searchParams),m=i.searchParams.get("sort"),g=A.selectFrom(a);g=ve(g,m,P);let N=new Set(["page","per_page","sort","fields","search","include","limit","offset","with_count","withTrashed","onlyTrashed"]);for(let[x,O]of i.searchParams.entries()){if(N.has(x)||!O)continue;if(!/^[a-z_][a-z0-9_]*$/i.test(x))continue;let z=P.get(x);if(z)g=g.where(z,"=",O)}let R=i.searchParams.get("search");if(R&&l.length>0){let x=l.slice(0,5);g=g.where((O)=>{for(let z of x)O=O.orWhere(_(z),"like",`%${R}%`);return O})}let k=i.searchParams.get("fields");if(k){let x=k.split(",").map((O)=>O.trim()).filter((O)=>/^[a-z_][a-z0-9_]*$/i.test(O)).map((O)=>_(O));if(x.length>0)g=g.select(x)}let I=!!t.traits?.useSoftDeletes,b=i.searchParams.get("withTrashed")==="true",j=i.searchParams.get("onlyTrashed")==="true";if(I&&!b)g=j?g.whereNotNull("deleted_at"):g.whereNull("deleted_at");let y=C?await Z(t,await Y(p),p):{enforced:!1,value:null,field:"",bypass:!1};if(y.enforced&&!y.bypass){if(y.value==null||Array.isArray(y.value)&&y.value.length===0){let x=we(i,u,f,0,!1,void 0);return new Response(JSON.stringify({data:[],...x,meta:ie(i,u,f,0,!1,void 0)}),{status:200,headers:{"Content-Type":"application/json","Cache-Control":"private, no-store",Vary:"Authorization"}})}g=Array.isArray(y.value)?g.where(y.field,"in",y.value):g.where(y.field,"=",y.value)}let M=await g.limit(f+1).offset(h).get()||[],F=M.length>f,te=(F?M.slice(0,f):M).map((x)=>X(re(x,t),w)),Se=i.searchParams.get("include");if(Se)te=await ze(te,t,Se,Re);let Xe=i.searchParams.get("with_count")==="true",K;if(Xe)try{let x=A.selectFrom(a);if(y.enforced&&!y.bypass&&y.value!=null)x=Array.isArray(y.value)?x.where(y.field,"in",y.value):x.where(y.field,"=",y.value);let O=await x.count(),z=typeof O==="number"?O:Number(O?.count??O);if(Number.isFinite(z))K=z}catch{}let se={"Content-Type":"application/json"};if(K!==void 0&&!Number.isNaN(K))se["X-Total-Count"]=String(K);se["Cache-Control"]=y.enforced&&!y.bypass?"private, no-store":"public, max-age=15, must-revalidate",se.Vary="Authorization";let Ye=we(i,u,f,te.length,F,K);return new Response(JSON.stringify({data:te,...Ye,meta:ie(i,u,f,te.length,F,K)}),{status:200,headers:se})}catch(i){if(i instanceof B){let u={error:i.message};if(i.details!==void 0)u.details=i.details;return d(u,i.status||400)}return d({error:`Failed to fetch ${s}`,detail:String(i)},500)}}));if(o.includes("show")&&!J("GET",`${c}/{id}`))E(U.get(`${c}/{id}`,async(p)=>{try{let i=le(p.params?.id);if(i==null)return d({error:"Invalid ID parameter"},400);let u=await A.selectFrom(a).where({id:i}).executeTakeFirst();if(!u)return d({error:`${e} not found`},404);let f=new URL(p.url);if(t.traits?.useSoftDeletes&&u.deleted_at&&f.searchParams.get("withTrashed")!=="true")return d({error:`${e} not found`},404);let h=C?await Z(t,await Y(p),p):{enforced:!1,value:null,field:"",bypass:!1};if(h.enforced&&!h.bypass){if(h.value==null||!ne(u[h.field],h.value))return d({error:`${e} not found`},404)}let m=X(re(u,t),w),g=f.searchParams.get("include");if(g){let[b]=await ze([m],t,g,Re);m=b}let N=m?.updated_at||m?.created_at,R=N?`W/"${m.id}-${String(N)}"`:void 0,k=p.headers?.get?.("if-none-match");if(R&&k&&k===R)return new Response(null,{status:304,headers:{ETag:R}});let I={"Content-Type":"application/json"};if(R)I.ETag=R;return I["Cache-Control"]=h.enforced&&!h.bypass?"private, no-store":"public, max-age=30, must-revalidate",I.Vary="Authorization",new Response(JSON.stringify({data:m}),{status:200,headers:I})}catch(i){if(i instanceof B){let u={error:i.message};if(i.details!==void 0)u.details=i.details;return d(u,i.status||400)}return d({error:`Failed to fetch ${e}`,detail:String(i)},500)}}));if(!T&&o.includes("store")&&!J("POST",c))E(U.post(c,async(p)=>{try{let i=await _e(p),u=pe(i,w),f=ge(u,l),h=await Y(p);if(h){let b=await qe(t,"creating",h,p);for(let[j,y]of Object.entries(b))if(y!==void 0&&y!==null&&f[j]===void 0)f[j]=y}let m=await Z(t,h,p);if(m.enforced&&!m.bypass){if(!h)return d({error:"Auth required"},401);let b=$e(f,m.field,m.value);if(b.error)return d({error:b.error},403);f=b.data}if(Object.keys(f).length===0)return d({error:"No fillable fields provided"},422);let g=me(f,t,"creating");if(!g.valid)return d({success:!1,message:"Validation failed",errors:g.errors},422);if(t.traits?.useTimestamps!==!1){let b=new Date().toISOString();f.created_at=b,f.updated_at=b}let N=await He(f,t),R=fe(Je(N,t)),k=await A.insertInto(a).values(R).execute(),I=R;try{let b=k?.lastInsertRowid??k?.insertId??k;if(b){let j=await A.selectFrom(a).where({id:b}).executeTakeFirst();if(j)I=j}}catch{}return d({data:X(re(I,t),w)},201)}catch(i){let{status:u,body:f}=de(i,e,"create");return d(f,u)}}),D);if(!T&&o.includes("update")){let p=async(i)=>{try{let u=le(i.params?.id);if(u==null)return d({error:"Invalid ID parameter"},400);let f=await _e(i),h=pe(f,w),m=ge(h,l),g=await Y(i);if(g){let y=await qe(t,"updating",g,i);for(let[M,F]of Object.entries(y))if(F!==void 0&&F!==null&&m[M]===void 0)m[M]=F}if(Object.keys(m).length===0)return d({error:"No fillable fields provided"},422);let N=me(m,t,"updating");if(!N.valid)return d({success:!1,message:"Validation failed",errors:N.errors},422);let R=await A.selectFrom(a).where({id:u}).executeTakeFirst();if(!R)return d({error:`${e} not found`},404);let k=await Z(t,g,i);if(k.enforced&&!k.bypass){if(!g)return d({error:"Auth required"},401);let y=R[k.field];if(!ne(y,k.value))return d({error:`Not your ${e}`},403);let M=Object.keys(m).find((F)=>_(F)===_(k.field));if(M!==void 0&&!ne(m[M],k.value))return d({error:`Cannot reassign ${e} ownership`},403)}if(t.traits?.useTimestamps!==!1)m.updated_at=new Date().toISOString();let I=await He(m,t),b=fe(Je(I,t));await A.updateTable(a).set(b).where({id:u}).execute();let j={...R,...b};try{let y=await A.selectFrom(a).where({id:u}).executeTakeFirst();if(y)j=y}catch{}return d({data:X(re(j,t),w)})}catch(u){let{status:f,body:h}=de(u,e,"update");return d(h,f)}};if(!J("PUT",`${c}/{id}`))E(U.put(`${c}/{id}`,p),D);if(!J("PATCH",`${c}/{id}`))E(U.patch(`${c}/{id}`,p),D)}let ee=!!t.traits?.useSoftDeletes;if(!T&&o.includes("destroy")&&!J("DELETE",`${c}/{id}`))E(U.delete(`${c}/{id}`,async(p)=>{try{let i=le(p.params?.id);if(i==null)return d({error:"Invalid ID parameter"},400);let u=await A.selectFrom(a).where({id:i}).executeTakeFirst();if(!u)return d({error:`${e} not found`},404);let f=await Y(p),h=await Z(t,f,p);if(h.enforced&&!h.bypass){if(!f)return d({error:"Auth required"},401);let m=u[h.field];if(!ne(m,h.value))return d({error:`Not your ${e}`},403)}if(ee){let m=new Date().toISOString();await A.updateTable(a).set({deleted_at:m,updated_at:m}).where({id:i}).execute()}else await A.deleteFrom(a).where({id:i}).execute();return new Response(null,{status:204})}catch(i){if(i instanceof B){let u={error:i.message};if(i.details!==void 0)u.details=i.details;return d(u,i.status||400)}return d({error:`Failed to delete ${e}`,detail:String(i)},500)}}),D);if(!T&&o.includes("destroy")&&!J("POST",`${c}/bulk-delete`))E(U.post(`${c}/bulk-delete`,async(p)=>{try{let u=(await _e(p))?.ids;if(!Array.isArray(u)||u.length===0)return d({error:"An array of IDs is required"},422);if(u.length>100)return d({error:"Cannot delete more than 100 records at once"},422);let f=[],h=[];for(let R of u){let k=le(R);if(k!=null)f.push(k);else h.push(R)}if(h.length>0)return d({error:"Invalid IDs in batch",invalid:h},422);let m=await Y(p),g=await Z(t,m,p);if(g.enforced&&!g.bypass){if(!m)return d({error:"Auth required"},401);if(g.value==null)return d({error:"Caller has no ownership identity"},403);let R=await A.selectFrom(a).select(["id",g.field]).execute(),k=new Set(R.filter((b)=>ne(b[g.field],g.value)).map((b)=>String(b.id))),I=f.filter((b)=>!k.has(String(b)));if(I.length>0)return d({error:`Cannot delete ${e} you don't own`,ids:I},403)}let N=new Date().toISOString();for(let R of f)if(ee)await A.updateTable(a).set({deleted_at:N,updated_at:N}).where({id:R}).execute();else await A.deleteFrom(a).where({id:R}).execute();return d({message:`Successfully deleted ${f.length} ${s}`})}catch(i){if(i instanceof B){let u={error:i.message};if(i.details!==void 0)u.details=i.details;return d(u,i.status||400)}return d({error:`Failed to bulk delete ${s}`,detail:String(i)},500)}}),D)}{let e=Me(Ke,Ge);if(e)q.warn(e)}var Ut=U;export{Ut as default};
|
|
2
|
+
import ct from"process";import{route as F}from"@stacksjs/router";import{env as H}from"@stacksjs/env";import{projectPath as _e,storagePath as dt}from"@stacksjs/path";import{createQueryBuilder as ft,defaultConfig as Be,setConfig as Qe}from"@stacksjs/query-builder";import{HttpError as B}from"@stacksjs/error-handling";import{log as q}from"@stacksjs/logging";function Ye(e){let t=e;return t?.code==="SQLITE_CONSTRAINT_UNIQUE"||t?.code==="SQLITE_CONSTRAINT"||t?.code==="23505"||t?.errno===1062||/unique|duplicate/i.test(t?.message??"")}function ce(e,t,n){let r=e;if(e instanceof Error&&typeof r.status==="number"&&Number.isInteger(r.status)&&r.status>=400&&r.status<600){let s={error:e.message};if(r.details!==void 0)s.details=r.details;return{status:r.status,body:s}}if(Ye(e))return{status:409,body:{error:`${t} already exists`}};return{status:500,body:{error:`Failed to ${n} ${t}`,detail:String(e)}}}function _(e){return e.replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/([A-Z])([A-Z][a-z])/g,"$1_$2").toLowerCase()}function de(e){let t={};for(let[n,r]of Object.entries(e))t[_(n)]=r;return t}function Oe(e,t){let n=String(t||"").replace(/^\/+|\/+$/g,""),r=String(e).replace(/^\/+/,"");return`/api/${n?`${n}/`:""}${r}`}function Te(e){if(!e)return null;let t=e.attributes??{};if(Object.prototype.hasOwnProperty.call(t,"teamId")||Object.prototype.hasOwnProperty.call(t,"team_id"))return"team_id";return e.belongsTo?.some((n)=>n==="Team")?"team_id":null}function Ae(e){if(!e)return null;let t=e.attributes??{};if(Object.prototype.hasOwnProperty.call(t,"userId")||Object.prototype.hasOwnProperty.call(t,"user_id"))return"user_id";return e.belongsTo?.some((n)=>n==="User")?"user_id":null}function Pe(e){return e?.ownership===!1}function De(e,t,n){let r=Object.keys(e).find((o)=>_(o)===_(t));if(Array.isArray(n)){if(r===void 0)return{data:e,error:"Ownership value is required"};if(!n.some((o)=>String(o)===String(e[r])))return{data:e,error:"Ownership value is not available to this caller"};return{data:{...e,[t]:e[r]}}}if(n===null||n===void 0)return{data:e,error:"Caller has no ownership identity"};let s={...e};for(let o of Object.keys(s))if(_(o)===_(t))delete s[o];return s[t]=n,{data:s}}function $e(e){if(!e)return[];let t=Object.entries(e.attributes??{}).filter(([,n])=>n?.fillable===!0).map(([n])=>n);for(let n of e.belongsTo??[]){if(typeof n!=="string"||!n.trim())continue;let r=_(n.trim()).split("_").filter(Boolean);if(r.length===0)continue;let s=`${r[0]}${r.slice(1).map((o)=>`${o[0]?.toUpperCase()}${o.slice(1)}`).join("")}Id`;t.push(s)}return[...new Set(t)]}function fe(e,t){if(!e||t.length===0)return{};let n={};for(let r of t){if(r in e){n[r]=e[r];continue}let s=_(r);if(s!==r&&s in e)n[r]=e[s]}return n}function Ze(e,t){if(e?.name!=="date"||t instanceof Date||typeof t!=="string")return t;let n=/^(\d{4})-(\d{2})-(\d{2})$/.exec(t);if(!n)return t;let r=Number(n[1]),s=Number(n[2]),o=Number(n[3]),i=new Date(Date.UTC(r,s-1,o));if(i.getUTCFullYear()!==r||i.getUTCMonth()!==s-1||i.getUTCDate()!==o)return t;return i}function ge(e,t){if(!t.length)return e;let n={...e};for(let r of t)delete n[r],delete n[_(r)];return n}function K(e,t){if(!e||t.length===0)return e;let n={...e};for(let r of t)delete n[r],delete n[_(r)];return n}var et=["id","uuid","created_at","updated_at","deleted_at"];function Ie(e,t){let n=new Map;for(let r of[...Object.keys(e??{}),...et]){let s=_(r);if(!/^\w+$/.test(s))continue;n.set(r,s),n.set(s,s)}for(let r of t)n.delete(r),n.delete(_(r));return n}function Ne(e,t,n){if(!t)return e;let r=String(t).split(",").map((o)=>o.trim()).filter(Boolean),s=e;for(let o of r){let i=o.startsWith("-"),l=i?o.slice(1):o;if(!/^\w+$/.test(l))continue;let w=n.get(l);if(!w)continue;s=s.orderBy(w,i?"desc":"asc")}return s}var tt={string:{get:(e)=>e!=null?String(e):null,set:(e)=>e!=null?String(e):null},number:{get:(e)=>e!=null?Number(e):null,set:(e)=>e!=null?Number(e):null},integer:{get:(e)=>e!=null?Math.trunc(Number(e)):null,set:(e)=>e!=null?Math.trunc(Number(e)):null},float:{get:(e)=>e!=null?Number.parseFloat(String(e)):null,set:(e)=>e!=null?Number.parseFloat(String(e)):null},boolean:{get:(e)=>e===1||e==="1"||e===!0||e==="true",set:(e)=>e===!0||e===1||e==="1"||e==="true"?1:0},json:{get:(e)=>e==null?null:typeof e==="string"?nt(e):e,set:(e)=>e==null?null:typeof e==="string"?e:JSON.stringify(e)},datetime:{get:(e)=>e?new Date(e):null,set:(e)=>e instanceof Date?e.toISOString():e},date:{get:(e)=>e?new Date(e):null,set:(e)=>e instanceof Date?e.toISOString().split("T")[0]:e},array:{get:(e)=>e==null?[]:Array.isArray(e)?e:typeof e==="string"?rt(e):[],set:(e)=>e==null?null:Array.isArray(e)?JSON.stringify(e):e}};function nt(e){try{return JSON.parse(e)}catch{return e}}function rt(e){try{return JSON.parse(e)}catch{return[]}}function pe(e,t,n){if(!e||typeof e!=="object"||!t||Object.keys(t).length===0)return e;let r={...e};for(let[s,o]of Object.entries(t)){let i=typeof o==="string"?tt[o]:o;if(!i||typeof i[n]!=="function")continue;if(Object.prototype.hasOwnProperty.call(r,s))r[s]=i[n](r[s]);let l=_(s);if(l!==s&&Object.prototype.hasOwnProperty.call(r,l))r[l]=i[n](r[l])}return r}function ye(e,t,n){let r=t?.attributes??{},s={};for(let[o,i]of Object.entries(r)){let l=i?.validation?.rule;if(!l||typeof l.validate!=="function")continue;let w=Object.prototype.hasOwnProperty.call(e,o);if(!w&&n==="updating")continue;let c=i!==null&&typeof i==="object"&&Object.prototype.hasOwnProperty.call(i,"default"),P=w?e[o]:c?i.default:void 0,v=Ze(l,P),D=l.validate(v);if(!D?.valid&&Array.isArray(D?.errors)&&D.errors.length>0)s[o]=D.errors.map((Q)=>i?.validation?.message?.[Q?.code]??Q?.message??"invalid")}return Object.keys(s).length===0?{valid:!0}:{valid:!1,errors:s}}function ve(e,t,n){let r=Se(n);return e.find((s)=>s.method===t&&Se(String(s.path??""))===r)}function Se(e){return e.replace(/\{[^}]*\}/g,"{}").replace(/:[^/]+/g,"{}")}function le(e){if(Array.isArray(e))return e.filter((t)=>typeof t==="string"&&t.length>0);return typeof e==="string"&&e?[e]:[]}function Ce(e){let t=typeof e==="object"&&e!==null&&"middleware"in e,n=e?.middleware;if(!t)return{read:["auth"],write:["auth"],declared:!1};if(n&&typeof n==="object"&&!Array.isArray(n)){let s=n;return{read:"read"in s?le(s.read):["auth"],write:"write"in s?le(s.write):["auth"],declared:!0}}let r=le(n);return{read:r,write:r,declared:!0}}var xe=15,st=100;function Ee(e){let t=Number.parseInt(e.get("page")||String(1),10),n=Number.isFinite(t)?Math.max(1,t):1,r=Number.parseInt(e.get("per_page")||String(xe),10),s=Math.min(Number.isFinite(r)?Math.max(1,r):xe,st);return{page:n,perPage:s,offset:(n-1)*s}}function oe(e,t){let n=new URL(e.toString());return n.searchParams.set("page",String(t)),`${n.pathname}${n.search}`}function ot(e,t,n,r,s,o){let i=(t-1)*n,l=r===0,w={page:t,per_page:n,from:l?null:i+1,to:l?null:i+r,has_more_pages:s,prev_page_url:t>1?oe(e,t-1):null,next_page_url:s?oe(e,t+1):null};if(o!==void 0&&!Number.isNaN(o)){let c=Math.max(1,Math.ceil(o/n));w.total=o,w.last_page=c,w.first_page_url=oe(e,1),w.last_page_url=oe(e,c)}return w}function me(e,t,n,r,s,o){let{page:i,...l}=ot(e,t,n,r,s,o);return{current_page:i,...l}}function we(e,t){return(t??e??"").toString().trim().toLowerCase()==="warn"?"warn":"deny"}function je(e,t){if(e.length===0)return null;let n=[...e].sort().join(", ");if(t==="deny")return`[orm] security.api.rowScoping is 'deny': NOT registering store/update/destroy for ${e.length} model(s) `+`with no row-level scoping \u2014 ${n}. Give a model an \`ownership\` config or a \`team_id\` column to restore its writes.`;return`[orm] ${e.length} model(s) publish mutating routes with no row-level scoping, so an authenticated caller can write any row \u2014 `+`${n}. Scope one with an \`ownership\` config or a \`team_id\` column; intended for public catalog tables. Set \`security.api.rowScoping: 'deny'\` (config/security.ts) to stop generating these routes instead.`}import{readdirSync as it}from"fs";import{basename as at,extname as Fe,join as ut}from"path";function lt(e){let t=[],n=(r)=>{let s;try{s=it(r,{withFileTypes:!0})}catch{return}for(let o of s){let i=ut(r,o.name);if(o.isDirectory()){n(i);continue}if(![".ts",".js"].includes(Fe(o.name)))continue;if(o.name.startsWith(".")||o.name.startsWith("index"))continue;t.push(i)}};return n(e),t.sort()}async function Me(e,t,n){for(let r of lt(e))try{let s=Fe(r),o=await import(`${r}?t=${Date.now()}`),i=o.default??o,l=i.name??at(r,s);t[l]={...i,name:l}}catch(s){n?.(r,s)}}async function Ue(e){let t={};return await Me(e.defaultsRoot,t,e.onImportError),await Me(e.userRoot,t,e.onImportError),t}function ie(e){if(e?.ownership===!1)return null;if(e?.ownership)return e.ownership;let t=Te(e);if(t)return{field:t,resolve:async(r,s)=>{let{resolveAuthenticatedTeamId:o}=await import("@stacksjs/auth");return o(he(s))}};let n=Ae(e);if(n)return{field:n,resolve:async(r)=>r?.id??null};return null}function he(e){let t=e?.headers?.get?.("authorization"),n=t&&/^Bearer\s+/i.test(t)?t.replace(/^Bearer\s+/i,"").trim():null,r=e?.headers?.get?.("cookie")||"";return{bearerToken:()=>n,cookies:{get:(s)=>{for(let o of r.split(";")){let i=o.indexOf("=");if(i===-1)continue;if(o.slice(0,i).trim()===s)return decodeURIComponent(o.slice(i+1).trim())}return null}}}}function kt(e="customer_id"){return{field:e,resolve:async(t)=>{let n=t?.id;if(n==null)return null;try{let{Customer:r}=await import("@stacksjs/orm");return(await r.where("user_id",n).first())?.id??null}catch{return null}}}}function St(e="id"){return{field:e,resolve:async(t)=>t?.id??null}}function xt(e="site_id"){return{field:e,resolve:async(t,n)=>{try{let[{resolveAuthenticatedTeamId:r},{Site:s}]=await Promise.all([import("@stacksjs/auth"),import("@stacksjs/orm")]),o=await r(he(n));if(o==null)return[];return(await s.where("team_id",o).get()??[]).map((l)=>l.id).filter((l)=>l!=null)}catch{return[]}}}}function Ot(e="id"){return{field:e,resolve:async(t,n)=>{try{let{resolveAuthenticatedTeamId:r}=await import("@stacksjs/auth");return await r(he(n))}catch{return null}}}}function Tt(e,t){return{field:t,resolve:async(n,r)=>{try{let s=await import("@stacksjs/orm"),o=s.models?.[e]??s[e]?.definition??null,i=ie(o);if(!i?.field||typeof i.resolve!=="function")return[];let l=await i.resolve(n,r);if(l==null||Array.isArray(l)&&l.length===0)return[];let w=s[e];if(!w)return[];return((Array.isArray(l)?await w.whereIn(i.field,l).get():await w.where(i.field,l).get())??[]).map((P)=>P.id).filter((P)=>P!=null)}catch{return[]}}}}var gt=_e("config/qb.ts");try{let e=(await import(gt)).default;Qe(e??Be)}catch{q.debug("[orm] No config/qb.ts override found - deriving config from DB_CONNECTION");let e=H.DB_CONNECTION||"sqlite";Qe({...Be,dialect:e,database:e==="sqlite"?{database:H.DB_DATABASE_PATH||"database/stacks.sqlite"}:{database:H.DB_DATABASE||"stacks",host:H.DB_HOST||"127.0.0.1",port:H.DB_PORT||(e==="postgres"?5432:3306),username:H.DB_USERNAME||(e==="postgres"?"postgres":"root"),password:H.DB_PASSWORD||""}})}var be=await Ue({defaultsRoot:dt("framework/defaults/app/Models"),userRoot:_e("app/Models"),onImportError:(e,t)=>q.warn(`[orm] Could not load model ${e}: ${t.message}`)}),pt=ft(),A=pt;function J(e,t){let n=ve(F.routes,e,t);if(!n)return!1;let r=String(n.path??"");return q.info(`[orm] Skipping generated ${e} ${t} - already defined${r&&r!==t?` as ${r}`:""}.`),!0}function qe(e){if(!e.attributes)return[];return Object.entries(e.attributes).filter(([t,n])=>n.hidden===!0).map(([t])=>t)}function We(e){if(e.endsWith("y")&&!/[aeiou]y$/i.test(e))return`${e.slice(0,-1)}ies`;if(/(?:s|x|z|ch|sh)$/i.test(e))return`${e}es`;return`${e}s`}async function Le(e,t,n,r){if(!e.length)return e;let s=n.split(",").map((c)=>c.trim()).filter(Boolean);if(!s.length)return e;let o=Array.isArray(t.belongsTo)?t.belongsTo:[],i=Array.isArray(t.hasMany)?t.hasMany:[],l=Array.isArray(t.hasOne)?t.hasOne:[],w=new Map;for(let c of o)w.set(_(c),{kind:"belongsTo",modelName:c});for(let c of l)w.set(_(c),{kind:"hasOne",modelName:c});for(let c of i)w.set(We(_(c)),{kind:"hasMany",modelName:c});for(let c of s){let P=w.get(c);if(!P)continue;let v=r[P.modelName];if(!v)continue;let D=v.table||We(_(P.modelName)),Q=qe(v),V=(W)=>K(ne(W,v),Q);if(P.kind==="belongsTo"||P.kind==="hasOne"){let W=`${_(P.modelName)}_id`,C=[...new Set(e.map((S)=>S[W]).filter((S)=>S!=null))];if(!C.length){for(let S of e)S[c]=null;continue}let G=await A.selectFrom(D).whereIn("id",C).get(),L=new Map;for(let S of G??[])L.set(String(S.id),V(S));for(let S of e)S[c]=L.get(String(S[W]))??null}else{let W=String(t.name??""),C=`${_(W)}_id`,G=[...new Set(e.map((T)=>T.id).filter((T)=>T!=null))];if(!G.length){for(let T of e)T[c]=[];continue}let L=await A.selectFrom(D).whereIn(C,G).get(),S=new Map;for(let T of L??[]){let E=String(T[C]),Z=S.get(E)??[];Z.push(V(T)),S.set(E,Z)}for(let T of e)T[c]=S.get(String(T.id))??[]}}return e}async function ze(e,t){let n=t?.set;if(!n||typeof n!=="object")return e;let r={...e};for(let[s,o]of Object.entries(n)){if(typeof o!=="function"||!(s in r))continue;try{r[s]=await o(r)}catch{}}return r}function He(e,t){return pe(e,t?.casts,"set")}function ne(e,t){return pe(e,t?.casts,"get")}function d(e,t=200){return new Response(JSON.stringify(e),{status:t,headers:{"Content-Type":"application/json"}})}var ae=1048576;async function Re(e){if(Number(e.headers?.get?.("content-length")??0)>ae)throw new B(413,`Request body exceeds ${ae}-byte limit`);if(e.jsonBody&&typeof e.jsonBody==="object")return e.jsonBody;if(e.formBody&&typeof e.formBody==="object")return e.formBody;try{let n=await e.clone().text();if(n.length>ae)throw new B(413,`Request body exceeds ${ae}-byte limit`);if(!n)return{};let r=JSON.parse(n);return r&&typeof r==="object"&&!Array.isArray(r)?r:{}}catch(n){if(n instanceof B)throw n;throw new B(400,`Invalid JSON body: ${n.message}`)}}function ue(e){if(e==null)return null;if(typeof e==="number")return Number.isFinite(e)&&e>0?e:null;if(typeof e==="string"){let t=e.trim();if(!t)return null;if(/^\d+$/.test(t)){let n=Number(t);return Number.isFinite(n)&&n>0?n:null}return t}return null}function yt(e){let t=e.bearerToken;if(typeof t==="function"){let r=t.call(e);if(r)return r}let n=e.headers?.get?.("authorization")||e.headers?.get?.("Authorization")||"";if(typeof n==="string"&&n.startsWith("Bearer "))return n.substring(7);return null}async function X(e){let t=e._authenticatedUser;if(t)return t;let n=yt(e);if(!n)return null;try{let{Auth:r}=await import("@stacksjs/auth"),s=await r.getUserFromToken(n);if(s)e._authenticatedUser=s;return s||null}catch{return null}}function te(e,t){if(e==null||t==null)return!1;if(Array.isArray(t))return t.some((n)=>String(n)===String(e));return String(e)===String(t)}async function Y(e,t,n){let r=ie(e);if(!r||!r.field||typeof r.resolve!=="function")return{enforced:!1,value:null,field:"",bypass:!1};let s=typeof r.bypass==="function"?!!r.bypass(t,n):!1,o=null;try{o=await r.resolve(t,n)}catch{o=null}return{enforced:!0,value:o,field:String(r.field),bypass:s}}async function Je(e,t,n,r){let s=e?.authedFill?.[t];if(!s)return{};if(typeof s==="function")try{let o=await s(n,r);return o&&typeof o==="object"?o:{}}catch{return{}}if(typeof s==="object"){let o={};for(let[i,l]of Object.entries(s))try{o[i]=typeof l==="function"?await l(n,r):l}catch{}return o}return{}}var Ve=await(async()=>{let e=ct.env.STACKS_API_ROW_SCOPING;try{let t=(await import(_e("config/security.ts"))).default;return we(t?.api?.rowScoping,e)}catch{return we(void 0,e)}})(),Ge=[];for(let[e,t]of Object.entries(be)){let n=t.traits?.useApi;if(!n)continue;let r=typeof n==="object"?n:{},s=r.uri||t.table||e.toLowerCase()+"s",o=r.routes||["index","show","store","update","destroy"],i=t.table||s,l=$e(t),w=qe(t),c=Oe(s,r.prefix),P=Ie(t.attributes,w),{read:v,write:D,declared:Q}=Ce(n),V=["store","update","destroy"].some((p)=>o.includes(p));if(V&&Q&&D.length===0)q.warn(`[orm] ${e}: registering UNAUTHENTICATED mutating routes at ${c} (explicit \`middleware: []\` opt-out)`);if(["index","show"].some((p)=>o.includes(p))&&Q&&v.length===0)q.debug(`[orm] ${e}: registering public read routes at ${c} (declared \`middleware.read: []\`)`);let C=!!ie(t),G=C&&!v.includes("auth")?["auth",...v]:v,L=Pe(t),S=V&&!C&&!L;if(S)Ge.push(e);if(V&&L)q.debug(`[orm] ${e}: registering unscoped mutating routes at ${c} (declared \`ownership: false\`)`);let T=S&&Ve==="deny",E=(p,a=G)=>{let u=p;for(let f of a)if(u&&typeof u.middleware==="function")u=u.middleware(f);return u};if(o.includes("index")&&!J("GET",c))E(F.get(c,async(p)=>{try{let a=new URL(p.url),{page:u,perPage:f,offset:h}=Ee(a.searchParams),m=a.searchParams.get("sort"),g=A.selectFrom(i);g=Ne(g,m,P);let N=new Set(["page","per_page","sort","fields","search","include","limit","offset","with_count","withTrashed","onlyTrashed"]);for(let[x,O]of a.searchParams.entries()){if(N.has(x)||!O)continue;if(!/^[a-z_][a-z0-9_]*$/i.test(x))continue;let z=P.get(x);if(z)g=g.where(z,"=",O)}let R=a.searchParams.get("search");if(R&&l.length>0){let x=l.slice(0,5);g=g.where((O)=>{for(let z of x)O=O.orWhere(_(z),"like",`%${R}%`);return O})}let k=a.searchParams.get("fields");if(k){let x=k.split(",").map((O)=>O.trim()).filter((O)=>/^[a-z_][a-z0-9_]*$/i.test(O)).map((O)=>_(O));if(x.length>0)g=g.select(x)}let I=!!t.traits?.useSoftDeletes,b=a.searchParams.get("withTrashed")==="true",j=a.searchParams.get("onlyTrashed")==="true";if(I&&!b)g=j?g.whereNotNull("deleted_at"):g.whereNull("deleted_at");let y=C?await Y(t,await X(p),p):{enforced:!1,value:null,field:"",bypass:!1};if(y.enforced&&!y.bypass){if(y.value==null||Array.isArray(y.value)&&y.value.length===0){let x=me(a,u,f,0,!1,void 0);return new Response(JSON.stringify({data:[],...x}),{status:200,headers:{"Content-Type":"application/json","Cache-Control":"private, no-store",Vary:"Authorization"}})}g=Array.isArray(y.value)?g.where(y.field,"in",y.value):g.where(y.field,"=",y.value)}let M=await g.limit(f+1).offset(h).get()||[],U=M.length>f,re=(U?M.slice(0,f):M).map((x)=>K(ne(x,t),w)),ke=a.searchParams.get("include");if(ke)re=await Le(re,t,ke,be);let Ke=a.searchParams.get("with_count")==="true",ee;if(Ke)try{let x=A.selectFrom(i);if(y.enforced&&!y.bypass&&y.value!=null)x=Array.isArray(y.value)?x.where(y.field,"in",y.value):x.where(y.field,"=",y.value);let O=await x.count(),z=typeof O==="number"?O:Number(O?.count??O);if(Number.isFinite(z))ee=z}catch{}let se={"Content-Type":"application/json"};if(ee!==void 0&&!Number.isNaN(ee))se["X-Total-Count"]=String(ee);se["Cache-Control"]=y.enforced&&!y.bypass?"private, no-store":"public, max-age=15, must-revalidate",se.Vary="Authorization";let Xe=me(a,u,f,re.length,U,ee);return new Response(JSON.stringify({data:re,...Xe}),{status:200,headers:se})}catch(a){if(a instanceof B){let u={error:a.message};if(a.details!==void 0)u.details=a.details;return d(u,a.status||400)}return d({error:`Failed to fetch ${s}`,detail:String(a)},500)}}));if(o.includes("show")&&!J("GET",`${c}/{id}`))E(F.get(`${c}/{id}`,async(p)=>{try{let a=ue(p.params?.id);if(a==null)return d({error:"Invalid ID parameter"},400);let u=await A.selectFrom(i).where({id:a}).executeTakeFirst();if(!u)return d({error:`${e} not found`},404);let f=new URL(p.url);if(t.traits?.useSoftDeletes&&u.deleted_at&&f.searchParams.get("withTrashed")!=="true")return d({error:`${e} not found`},404);let h=C?await Y(t,await X(p),p):{enforced:!1,value:null,field:"",bypass:!1};if(h.enforced&&!h.bypass){if(h.value==null||!te(u[h.field],h.value))return d({error:`${e} not found`},404)}let m=K(ne(u,t),w),g=f.searchParams.get("include");if(g){let[b]=await Le([m],t,g,be);m=b}let N=m?.updated_at||m?.created_at,R=N?`W/"${m.id}-${String(N)}"`:void 0,k=p.headers?.get?.("if-none-match");if(R&&k&&k===R)return new Response(null,{status:304,headers:{ETag:R}});let I={"Content-Type":"application/json"};if(R)I.ETag=R;return I["Cache-Control"]=h.enforced&&!h.bypass?"private, no-store":"public, max-age=30, must-revalidate",I.Vary="Authorization",new Response(JSON.stringify({data:m}),{status:200,headers:I})}catch(a){if(a instanceof B){let u={error:a.message};if(a.details!==void 0)u.details=a.details;return d(u,a.status||400)}return d({error:`Failed to fetch ${e}`,detail:String(a)},500)}}));if(!T&&o.includes("store")&&!J("POST",c))E(F.post(c,async(p)=>{try{let a=await Re(p),u=ge(a,w),f=fe(u,l),h=await X(p);if(h){let b=await Je(t,"creating",h,p);for(let[j,y]of Object.entries(b))if(y!==void 0&&y!==null&&f[j]===void 0)f[j]=y}let m=await Y(t,h,p);if(m.enforced&&!m.bypass){if(!h)return d({error:"Auth required"},401);let b=De(f,m.field,m.value);if(b.error)return d({error:b.error},403);f=b.data}if(Object.keys(f).length===0)return d({error:"No fillable fields provided"},422);let g=ye(f,t,"creating");if(!g.valid)return d({success:!1,message:"Validation failed",errors:g.errors},422);if(t.traits?.useTimestamps!==!1){let b=new Date().toISOString();f.created_at=b,f.updated_at=b}let N=await ze(f,t),R=de(He(N,t)),k=await A.insertInto(i).values(R).execute(),I=R;try{let b=k?.lastInsertRowid??k?.insertId??k;if(b){let j=await A.selectFrom(i).where({id:b}).executeTakeFirst();if(j)I=j}}catch{}return d({data:K(ne(I,t),w)},201)}catch(a){let{status:u,body:f}=ce(a,e,"create");return d(f,u)}}),D);if(!T&&o.includes("update")){let p=async(a)=>{try{let u=ue(a.params?.id);if(u==null)return d({error:"Invalid ID parameter"},400);let f=await Re(a),h=ge(f,w),m=fe(h,l),g=await X(a);if(g){let y=await Je(t,"updating",g,a);for(let[M,U]of Object.entries(y))if(U!==void 0&&U!==null&&m[M]===void 0)m[M]=U}if(Object.keys(m).length===0)return d({error:"No fillable fields provided"},422);let N=ye(m,t,"updating");if(!N.valid)return d({success:!1,message:"Validation failed",errors:N.errors},422);let R=await A.selectFrom(i).where({id:u}).executeTakeFirst();if(!R)return d({error:`${e} not found`},404);let k=await Y(t,g,a);if(k.enforced&&!k.bypass){if(!g)return d({error:"Auth required"},401);let y=R[k.field];if(!te(y,k.value))return d({error:`Not your ${e}`},403);let M=Object.keys(m).find((U)=>_(U)===_(k.field));if(M!==void 0&&!te(m[M],k.value))return d({error:`Cannot reassign ${e} ownership`},403)}if(t.traits?.useTimestamps!==!1)m.updated_at=new Date().toISOString();let I=await ze(m,t),b=de(He(I,t));await A.updateTable(i).set(b).where({id:u}).execute();let j={...R,...b};try{let y=await A.selectFrom(i).where({id:u}).executeTakeFirst();if(y)j=y}catch{}return d({data:K(ne(j,t),w)})}catch(u){let{status:f,body:h}=ce(u,e,"update");return d(h,f)}};if(!J("PUT",`${c}/{id}`))E(F.put(`${c}/{id}`,p),D);if(!J("PATCH",`${c}/{id}`))E(F.patch(`${c}/{id}`,p),D)}let Z=!!t.traits?.useSoftDeletes;if(!T&&o.includes("destroy")&&!J("DELETE",`${c}/{id}`))E(F.delete(`${c}/{id}`,async(p)=>{try{let a=ue(p.params?.id);if(a==null)return d({error:"Invalid ID parameter"},400);let u=await A.selectFrom(i).where({id:a}).executeTakeFirst();if(!u)return d({error:`${e} not found`},404);let f=await X(p),h=await Y(t,f,p);if(h.enforced&&!h.bypass){if(!f)return d({error:"Auth required"},401);let m=u[h.field];if(!te(m,h.value))return d({error:`Not your ${e}`},403)}if(Z){let m=new Date().toISOString();await A.updateTable(i).set({deleted_at:m,updated_at:m}).where({id:a}).execute()}else await A.deleteFrom(i).where({id:a}).execute();return new Response(null,{status:204})}catch(a){if(a instanceof B){let u={error:a.message};if(a.details!==void 0)u.details=a.details;return d(u,a.status||400)}return d({error:`Failed to delete ${e}`,detail:String(a)},500)}}),D);if(!T&&o.includes("destroy")&&!J("POST",`${c}/bulk-delete`))E(F.post(`${c}/bulk-delete`,async(p)=>{try{let u=(await Re(p))?.ids;if(!Array.isArray(u)||u.length===0)return d({error:"An array of IDs is required"},422);if(u.length>100)return d({error:"Cannot delete more than 100 records at once"},422);let f=[],h=[];for(let R of u){let k=ue(R);if(k!=null)f.push(k);else h.push(R)}if(h.length>0)return d({error:"Invalid IDs in batch",invalid:h},422);let m=await X(p),g=await Y(t,m,p);if(g.enforced&&!g.bypass){if(!m)return d({error:"Auth required"},401);if(g.value==null)return d({error:"Caller has no ownership identity"},403);let R=await A.selectFrom(i).select(["id",g.field]).execute(),k=new Set(R.filter((b)=>te(b[g.field],g.value)).map((b)=>String(b.id))),I=f.filter((b)=>!k.has(String(b)));if(I.length>0)return d({error:`Cannot delete ${e} you don't own`,ids:I},403)}let N=new Date().toISOString();for(let R of f)if(Z)await A.updateTable(i).set({deleted_at:N,updated_at:N}).where({id:R}).execute();else await A.deleteFrom(i).where({id:R}).execute();return d({message:`Successfully deleted ${f.length} ${s}`})}catch(a){if(a instanceof B){let u={error:a.message};if(a.details!==void 0)u.details=a.details;return d(u,a.status||400)}return d({error:`Failed to bulk delete ${s}`,detail:String(a)},500)}}),D)}{let e=je(Ge,Ve);if(e)q.warn(e)}var Ut=F;export{Ut as default};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/orm",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.74.
|
|
5
|
+
"version": "0.74.29",
|
|
6
6
|
"description": "The Stacks ORM integration",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -63,31 +63,31 @@
|
|
|
63
63
|
"prepublishOnly": "bun run build"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@stacksjs/cache": "0.74.
|
|
67
|
-
"@stacksjs/cli": "0.74.
|
|
68
|
-
"@stacksjs/config": "0.74.
|
|
69
|
-
"@stacksjs/database": "0.74.
|
|
70
|
-
"@stacksjs/env": "0.74.
|
|
71
|
-
"@stacksjs/error-handling": "0.74.
|
|
72
|
-
"@stacksjs/events": "0.74.
|
|
73
|
-
"@stacksjs/logging": "0.74.
|
|
74
|
-
"@stacksjs/model-meta": "0.74.
|
|
75
|
-
"@stacksjs/pagination": "0.74.
|
|
76
|
-
"@stacksjs/path": "0.74.
|
|
77
|
-
"@stacksjs/query-builder": "0.74.
|
|
78
|
-
"@stacksjs/realtime": "0.74.
|
|
79
|
-
"@stacksjs/router": "0.74.
|
|
80
|
-
"@stacksjs/storage": "0.74.
|
|
81
|
-
"@stacksjs/strings": "0.74.
|
|
66
|
+
"@stacksjs/cache": "0.74.29",
|
|
67
|
+
"@stacksjs/cli": "0.74.29",
|
|
68
|
+
"@stacksjs/config": "0.74.29",
|
|
69
|
+
"@stacksjs/database": "0.74.29",
|
|
70
|
+
"@stacksjs/env": "0.74.29",
|
|
71
|
+
"@stacksjs/error-handling": "0.74.29",
|
|
72
|
+
"@stacksjs/events": "0.74.29",
|
|
73
|
+
"@stacksjs/logging": "0.74.29",
|
|
74
|
+
"@stacksjs/model-meta": "0.74.29",
|
|
75
|
+
"@stacksjs/pagination": "0.74.29",
|
|
76
|
+
"@stacksjs/path": "0.74.29",
|
|
77
|
+
"@stacksjs/query-builder": "0.74.29",
|
|
78
|
+
"@stacksjs/realtime": "0.74.29",
|
|
79
|
+
"@stacksjs/router": "0.74.29",
|
|
80
|
+
"@stacksjs/storage": "0.74.29",
|
|
81
|
+
"@stacksjs/strings": "0.74.29",
|
|
82
82
|
"@stacksjs/ts-validation": "^0.5.6",
|
|
83
|
-
"@stacksjs/validation": "0.74.
|
|
83
|
+
"@stacksjs/validation": "0.74.29",
|
|
84
84
|
"bun-query-builder": "^0.2.68"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
|
-
"@stacksjs/auth": "0.74.
|
|
88
|
-
"@stacksjs/payments": "0.74.
|
|
89
|
-
"@stacksjs/queue": "0.74.
|
|
90
|
-
"@stacksjs/search-engine": "0.74.
|
|
87
|
+
"@stacksjs/auth": "0.74.29",
|
|
88
|
+
"@stacksjs/payments": "0.74.29",
|
|
89
|
+
"@stacksjs/queue": "0.74.29",
|
|
90
|
+
"@stacksjs/search-engine": "0.74.29",
|
|
91
91
|
"stripe": "22.3.2"
|
|
92
92
|
},
|
|
93
93
|
"peerDependenciesMeta": {
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
}
|
|
109
109
|
},
|
|
110
110
|
"devDependencies": {
|
|
111
|
-
"@stacksjs/types": "0.74.
|
|
111
|
+
"@stacksjs/types": "0.74.29",
|
|
112
112
|
"better-dx": "^0.2.24",
|
|
113
113
|
"stripe": "22.3.2"
|
|
114
114
|
}
|