@stacksjs/orm 0.72.102 → 0.73.0

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.
@@ -58,7 +58,7 @@ export default defineModel({
58
58
  },
59
59
  },
60
60
 
61
- hasOne: ['Subscriber', 'Driver', 'Author'],
61
+ hasOne: ['Subscriber', 'Courier', 'Author'],
62
62
 
63
63
  hasMany: [
64
64
  'PersonalAccessToken',
@@ -2,13 +2,13 @@ import { defineModel } from '@stacksjs/orm'
2
2
  import { schema } from '@stacksjs/validation'
3
3
 
4
4
  export default defineModel({
5
- name: 'Driver',
6
- table: 'drivers',
5
+ name: 'Courier',
6
+ table: 'couriers',
7
7
  primaryKey: 'id',
8
8
  autoIncrement: true,
9
9
 
10
10
  belongsTo: ['User'],
11
- hasMany: ['DeliveryRoute', 'DriverPing'],
11
+ hasMany: ['DeliveryRoute', 'CourierPing'],
12
12
 
13
13
  traits: {
14
14
  useUuid: true,
@@ -25,7 +25,7 @@ export default defineModel({
25
25
  },
26
26
 
27
27
  useApi: {
28
- uri: 'drivers',
28
+ uri: 'couriers',
29
29
  middleware: ['auth'],
30
30
  },
31
31
 
@@ -94,8 +94,8 @@ export default defineModel({
94
94
  /*
95
95
  * Last known position.
96
96
  *
97
- * Denormalised from the `driver_pings` series on purpose: "where is this
98
- * driver right now" is asked on every map frame and by every tracking
97
+ * Denormalised from the `courier_pings` series on purpose: "where is this
98
+ * courier right now" is asked on every map frame and by every tracking
99
99
  * page, and answering it with a MAX(recorded_at) subquery over a table
100
100
  * that grows by a row every few seconds is the wrong shape. The series is
101
101
  * the history; these three columns are the present.
@@ -149,7 +149,7 @@ export default defineModel({
149
149
 
150
150
  /*
151
151
  * When the last fix landed. A tracking map needs this to say "updated 4
152
- * seconds ago" and, more importantly, to stop claiming a driver is at a
152
+ * seconds ago" and, more importantly, to stop claiming a courier is at a
153
153
  * position that is ten minutes stale.
154
154
  */
155
155
  lastPingAt: {
@@ -2,9 +2,9 @@ import { defineModel } from '@stacksjs/orm'
2
2
  import { schema } from '@stacksjs/validation'
3
3
 
4
4
  /**
5
- * One position fix from a driver's device.
5
+ * One position fix from a courier's device.
6
6
  *
7
- * The append-only history behind `drivers.latitude/longitude`. A tracking map
7
+ * The append-only history behind `couriers.latitude/longitude`. A tracking map
8
8
  * needs the present position, but everything else about a delivery needs the
9
9
  * series: drawing the path already travelled, replaying a disputed drop,
10
10
  * measuring how long a stop actually took, and deriving an ETA from recent
@@ -12,12 +12,12 @@ import { schema } from '@stacksjs/validation'
12
12
  *
13
13
  * Deliberately not `useSearch` (indexing a row every few seconds is pointless
14
14
  * and expensive) and not `useApi` (writes come through
15
- * `recordDriverPing`, which does the fan-out, and reads are scoped to a
15
+ * `recordCourierPing`, which does the fan-out, and reads are scoped to a
16
16
  * route). `usePrunable` keeps the table from growing without bound.
17
17
  */
18
18
  export default defineModel({
19
- name: 'DriverPing',
20
- table: 'driver_pings',
19
+ name: 'CourierPing',
20
+ table: 'courier_pings',
21
21
  primaryKey: 'id',
22
22
  autoIncrement: true,
23
23
 
@@ -27,7 +27,7 @@ export default defineModel({
27
27
  useSeeder: { count: 0 },
28
28
  },
29
29
 
30
- belongsTo: ['Driver', 'DeliveryRoute'],
30
+ belongsTo: ['Courier', 'DeliveryRoute'],
31
31
 
32
32
  attributes: {
33
33
  latitude: {
@@ -89,7 +89,7 @@ export default defineModel({
89
89
 
90
90
  /**
91
91
  * When the device took the fix, which is not when the server received it.
92
- * A driver through a tunnel sends five fixes at once on the far side, and
92
+ * A courier through a tunnel sends five fixes at once on the far side, and
93
93
  * ordering them by arrival draws a path that never happened.
94
94
  */
95
95
  recordedAt: {
@@ -11,10 +11,10 @@ export default defineModel({
11
11
  useUuid: true,
12
12
  useTimestamps: true,
13
13
  useSearch: {
14
- displayable: ['id', 'driver', 'vehicle', 'stops', 'deliveryTime', 'totalDistance', 'lastActive'],
15
- searchable: ['driver', 'vehicle'],
14
+ displayable: ['id', 'courier', 'vehicle', 'stops', 'deliveryTime', 'totalDistance', 'lastActive'],
15
+ searchable: ['courier', 'vehicle'],
16
16
  sortable: ['stops', 'deliveryTime', 'totalDistance', 'lastActive', 'createdAt', 'updatedAt'],
17
- filterable: ['driver', 'vehicle'],
17
+ filterable: ['courier', 'vehicle'],
18
18
  },
19
19
 
20
20
  useSeeder: {
@@ -29,11 +29,11 @@ export default defineModel({
29
29
  observe: true,
30
30
  },
31
31
 
32
- belongsTo: ['Driver'],
33
- hasMany: ['DeliveryStop', 'DriverPing'],
32
+ belongsTo: ['Courier'],
33
+ hasMany: ['DeliveryStop', 'CourierPing'],
34
34
 
35
35
  attributes: {
36
- driver: {
36
+ courier: {
37
37
  order: 1,
38
38
  fillable: true,
39
39
  validation: {
@@ -6,11 +6,11 @@ import { schema } from '@stacksjs/validation'
6
6
  *
7
7
  * `DeliveryRoute` records that a route happened and how far it went; this is
8
8
  * the route itself. Without it `stops` is an integer, which is enough to bill
9
- * a driver and not nearly enough to tell a customer where their order is: the
9
+ * a courier and not nearly enough to tell a customer where their order is: the
10
10
  * link from an order to the vehicle carrying it runs through here.
11
11
  *
12
12
  * Sequence is the planned order. `status` is what actually happened, which
13
- * diverges the moment a driver skips a building and comes back to it.
13
+ * diverges the moment a courier skips a building and comes back to it.
14
14
  */
15
15
  export default defineModel({
16
16
  name: 'DeliveryStop',
@@ -26,7 +26,7 @@ export default defineModel({
26
26
  displayable: ['id', 'sequence', 'status', 'address', 'etaAt'],
27
27
  searchable: ['address', 'recipientName'],
28
28
  sortable: ['sequence', 'etaAt', 'createdAt'],
29
- filterable: ['status', 'deliveryRouteId'],
29
+ filterable: ['status', 'type', 'deliveryRouteId'],
30
30
  },
31
31
 
32
32
  useSeeder: { count: 0 },
@@ -82,7 +82,7 @@ export default defineModel({
82
82
 
83
83
  /**
84
84
  * Geocoded destination. Nullable because an address can arrive before it
85
- * resolves, and a stop with no coordinates is still a stop the driver can
85
+ * resolves, and a stop with no coordinates is still a stop the courier can
86
86
  * complete; it just cannot be drawn.
87
87
  */
88
88
  latitude: {
@@ -113,7 +113,7 @@ export default defineModel({
113
113
  factory: faker => faker.phone.number(),
114
114
  },
115
115
 
116
- /** Current estimate, rewritten as the driver moves. */
116
+ /** Current estimate, rewritten as the courier moves. */
117
117
  etaAt: {
118
118
  order: 8,
119
119
  fillable: true,
@@ -122,7 +122,7 @@ export default defineModel({
122
122
  },
123
123
 
124
124
  /**
125
- * When the customer was told the driver was close.
125
+ * When the customer was told the courier was close.
126
126
  *
127
127
  * The latch that makes "nearly there" fire once. Without it every ping
128
128
  * inside the radius sends another text, which is roughly one text every
@@ -135,7 +135,7 @@ export default defineModel({
135
135
  factory: () => null,
136
136
  },
137
137
 
138
- /** When the driver crossed the arrival radius. */
138
+ /** When the courier crossed the arrival radius. */
139
139
  arrivedAt: {
140
140
  order: 10,
141
141
  fillable: true,
@@ -151,13 +151,36 @@ export default defineModel({
151
151
  factory: () => null,
152
152
  },
153
153
 
154
- /** Why a stop failed, or anything the driver needs to record. */
154
+ /** Why a stop failed, or anything the courier needs to record. */
155
155
  notes: {
156
156
  order: 12,
157
157
  fillable: true,
158
158
  validation: { rule: schema.string().max(1000) },
159
159
  factory: () => '',
160
160
  },
161
+
162
+ /**
163
+ * Which leg of the run this stop is.
164
+ *
165
+ * A delivery is two stops, not one: collect from the merchant, then hand
166
+ * over to the customer. Without the distinction both legs look like a
167
+ * dropoff, so arriving at the restaurant would tell the customer their
168
+ * order is out for delivery, and collecting it would mark the order
169
+ * delivered before it had left the kitchen.
170
+ *
171
+ * Defaults to `dropoff` so existing single-stop routes keep their meaning.
172
+ */
173
+ type: {
174
+ order: 13,
175
+ required: true,
176
+ fillable: true,
177
+ default: 'dropoff',
178
+ validation: {
179
+ rule: schema.enum(['pickup', 'dropoff']),
180
+ message: { enum: 'Type must be one of: pickup, dropoff' },
181
+ },
182
+ factory: () => 'dropoff',
183
+ },
161
184
  },
162
185
 
163
186
  dashboard: {
package/dist/routes.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- var Je=Object.defineProperty;var qe=(e)=>e;function Ve(e,t){this[e]=qe.bind(null,t)}var ct=(e,t)=>{for(var n in t)Je(e,n,{get:t[n],enumerable:!0,configurable:!0,set:Ve.bind(t,n)})};var dt=(e,t)=>()=>(e&&(t=e(e=0)),t);var he=import.meta.require;import{route as F}from"@stacksjs/router";import{env as z}from"@stacksjs/env";import{projectPath as Ue,storagePath as at}from"@stacksjs/path";import{createQueryBuilder as ot,defaultConfig as Ee,setConfig as Ce}from"@stacksjs/query-builder";import{HttpError as U}from"@stacksjs/error-handling";import{log as Z}from"@stacksjs/logging";function Qe(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 ie(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(Qe(e))return{status:409,body:{error:`${t} already exists`}};return{status:500,body:{error:`Failed to ${n} ${t}`,detail:String(e)}}}function k(e){return e.replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/([A-Z])([A-Z][a-z])/g,"$1_$2").toLowerCase()}function ue(e){let t={};for(let[n,r]of Object.entries(e))t[k(n)]=r;return t}function Re(e,t){let n=String(t||"").replace(/^\/+|\/+$/g,""),r=String(e).replace(/^\/+/,"");return`/api/${n?`${n}/`:""}${r}`}function ke(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 Se(e,t,n){let r=Object.keys(e).find((a)=>k(a)===k(t));if(Array.isArray(n)){if(r===void 0)return{data:e,error:"Ownership value is required"};if(!n.some((a)=>String(a)===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 a of Object.keys(s))if(k(a)===k(t))delete s[a];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=k(n.trim()).split("_").filter(Boolean);if(r.length===0)continue;let s=`${r[0]}${r.slice(1).map((a)=>`${a[0]?.toUpperCase()}${a.slice(1)}`).join("")}Id`;t.push(s)}return[...new Set(t)]}function le(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=k(r);if(s!==r&&s in e)n[r]=e[s]}return n}function Xe(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]),a=Number(n[3]),i=new Date(Date.UTC(r,s-1,a));if(i.getUTCFullYear()!==r||i.getUTCMonth()!==s-1||i.getUTCDate()!==a)return t;return i}function ce(e,t){if(!t.length)return e;let n={...e};for(let r of t)delete n[r],delete n[k(r)];return n}function V(e,t){if(!e||t.length===0)return e;let n={...e};for(let r of t)delete n[r],delete n[k(r)];return n}var Ge=["id","uuid","created_at","updated_at","deleted_at"];function xe(e,t){let n=new Map;for(let r of[...Object.keys(e??{}),...Ge]){let s=k(r);if(!/^\w+$/.test(s))continue;n.set(r,s),n.set(s,s)}for(let r of t)n.delete(r),n.delete(k(r));return n}function Oe(e,t,n){if(!t)return e;let r=String(t).split(",").map((a)=>a.trim()).filter(Boolean),s=e;for(let a of r){let i=a.startsWith("-"),p=i?a.slice(1):a;if(!/^\w+$/.test(p))continue;let w=n.get(p);if(!w)continue;s=s.orderBy(w,i?"desc":"asc")}return s}var Ke={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"?Ye(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"?Ze(e):[],set:(e)=>e==null?null:Array.isArray(e)?JSON.stringify(e):e}};function Ye(e){try{return JSON.parse(e)}catch{return e}}function Ze(e){try{return JSON.parse(e)}catch{return[]}}function de(e,t,n){if(!e||typeof e!=="object"||!t||Object.keys(t).length===0)return e;let r={...e};for(let[s,a]of Object.entries(t)){let i=typeof a==="string"?Ke[a]:a;if(!i||typeof i[n]!=="function")continue;if(Object.prototype.hasOwnProperty.call(r,s))r[s]=i[n](r[s]);let p=k(s);if(p!==s&&Object.prototype.hasOwnProperty.call(r,p))r[p]=i[n](r[p])}return r}function fe(e,t,n){let r=t?.attributes??{},s={};for(let[a,i]of Object.entries(r)){let p=i?.validation?.rule;if(!p||typeof p.validate!=="function")continue;let w=Object.prototype.hasOwnProperty.call(e,a);if(!w&&n==="updating")continue;let c=i!==null&&typeof i==="object"&&Object.prototype.hasOwnProperty.call(i,"default"),E=w?e[a]:c?i.default:void 0,C=Xe(p,E),A=p.validate(C);if(!A?.valid&&Array.isArray(A?.errors)&&A.errors.length>0)s[a]=A.errors.map((B)=>i?.validation?.message?.[B?.code]??B?.message??"invalid")}return Object.keys(s).length===0?{valid:!0}:{valid:!1,errors:s}}function Te(e,t,n){let r=we(n);return e.find((s)=>s.method===t&&we(String(s.path??""))===r)}function we(e){return e.replace(/\{[^}]*\}/g,"{}").replace(/:[^/]+/g,"{}")}function oe(e){if(Array.isArray(e))return e.filter((t)=>typeof t==="string"&&t.length>0);return typeof e==="string"&&e?[e]:[]}function Ae(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?oe(s.read):["auth"],write:"write"in s?oe(s.write):["auth"],declared:!0}}let r=oe(n);return{read:r,write:r,declared:!0}}var be=15,et=100;function Ne(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(be),10),s=Math.min(Number.isFinite(r)?Math.max(1,r):be,et);return{page:n,perPage:s,offset:(n-1)*s}}function te(e,t){let n=new URL(e.toString());return n.searchParams.set("page",String(t)),`${n.pathname}${n.search}`}function ne(e,t,n,r,s,a){let i=(t-1)*n,p=r===0,w={page:t,per_page:n,from:p?null:i+1,to:p?null:i+r,has_more_pages:s,prev_page_url:t>1?te(e,t-1):null,next_page_url:s?te(e,t+1):null};if(a!==void 0&&!Number.isNaN(a)){let c=Math.max(1,Math.ceil(a/n));w.total=a,w.last_page=c,w.first_page_url=te(e,1),w.last_page_url=te(e,c)}return w}function ge(e,t,n,r,s,a){let{page:i,...p}=ne(e,t,n,r,s,a);return{current_page:i,...p}}import{readdirSync as tt}from"fs";import{basename as nt,extname as Ie,join as rt}from"path";function st(e){let t=[],n=(r)=>{let s;try{s=tt(r,{withFileTypes:!0})}catch{return}for(let a of s){let i=rt(r,a.name);if(a.isDirectory()){n(i);continue}if(![".ts",".js"].includes(Ie(a.name)))continue;if(a.name.startsWith(".")||a.name.startsWith("index"))continue;t.push(i)}};return n(e),t.sort()}async function $e(e,t,n){for(let r of st(e))try{let s=Ie(r),a=await import(`${r}?t=${Date.now()}`),i=a.default??a,p=i.name??nt(r,s);t[p]={...i,name:p}}catch(s){n?.(r,s)}}async function Pe(e){let t={};return await $e(e.defaultsRoot,t,e.onImportError),await $e(e.userRoot,t,e.onImportError),t}var it=Ue("config/qb.ts");try{let e=(await import(it)).default;Ce(e??Ee)}catch{Z.debug("[orm] No config/qb.ts override found - deriving config from DB_CONNECTION");let e=z.DB_CONNECTION||"sqlite";Ce({...Ee,dialect:e,database:e==="sqlite"?{database:z.DB_DATABASE_PATH||"database/stacks.sqlite"}:{database:z.DB_DATABASE||"stacks",host:z.DB_HOST||"127.0.0.1",port:z.DB_PORT||(e==="postgres"?5432:3306),username:z.DB_USERNAME||(e==="postgres"?"postgres":"root"),password:z.DB_PASSWORD||""}})}var pe=await Pe({defaultsRoot:at("framework/defaults/app/Models"),userRoot:Ue("app/Models"),onImportError:(e,t)=>Z.warn(`[orm] Could not load model ${e}: ${t.message}`)}),T=ot();function H(e,t){let n=Te(F.routes,e,t);if(!n)return!1;let r=String(n.path??"");return Z.info(`[orm] Skipping generated ${e} ${t} - already defined${r&&r!==t?` as ${r}`:""}.`),!0}function Be(e){if(!e.attributes)return[];return Object.entries(e.attributes).filter(([t,n])=>n.hidden===!0).map(([t])=>t)}function De(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 je(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 a=Array.isArray(t.belongsTo)?t.belongsTo:[],i=Array.isArray(t.hasMany)?t.hasMany:[],p=Array.isArray(t.hasOne)?t.hasOne:[],w=new Map;for(let c of a)w.set(k(c),{kind:"belongsTo",modelName:c});for(let c of p)w.set(k(c),{kind:"hasOne",modelName:c});for(let c of i)w.set(De(k(c)),{kind:"hasMany",modelName:c});for(let c of s){let E=w.get(c);if(!E)continue;let C=r[E.modelName];if(!C)continue;let A=C.table||De(k(E.modelName)),B=Be(C),ae=(L)=>V(Y(L,C),B);if(E.kind==="belongsTo"||E.kind==="hasOne"){let L=`${k(E.modelName)}_id`,j=[...new Set(e.map((_)=>_[L]).filter((_)=>_!=null))];if(!j.length){for(let _ of e)_[c]=null;continue}let J=await T.selectFrom(A).whereIn("id",j).get(),I=new Map;for(let _ of J??[])I.set(String(_.id),ae(_));for(let _ of e)_[c]=I.get(String(_[L]))??null}else{let L=String(t.name??""),j=`${k(L)}_id`,J=[...new Set(e.map((l)=>l.id).filter((l)=>l!=null))];if(!J.length){for(let l of e)l[c]=[];continue}let I=await T.selectFrom(A).whereIn(j,J).get(),_=new Map;for(let l of I??[]){let o=String(l[j]),u=_.get(o)??[];u.push(ae(l)),_.set(o,u)}for(let l of e)l[c]=_.get(String(l.id))??[]}}return e}async function Me(e,t){let n=t?.set;if(!n||typeof n!=="object")return e;let r={...e};for(let[s,a]of Object.entries(n)){if(typeof a!=="function"||!(s in r))continue;try{r[s]=await a(r)}catch{}}return r}function ve(e,t){return de(e,t?.casts,"set")}function Y(e,t){return de(e,t?.casts,"get")}function d(e,t=200){return new Response(JSON.stringify(e),{status:t,headers:{"Content-Type":"application/json"}})}var re=1048576;async function ye(e){if(Number(e.headers?.get?.("content-length")??0)>re)throw new U(413,`Request body exceeds ${re}-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>re)throw new U(413,`Request body exceeds ${re}-byte limit`);if(!n)return{};let r=JSON.parse(n);return r&&typeof r==="object"&&!Array.isArray(r)?r:{}}catch(n){if(n instanceof U)throw n;throw new U(400,`Invalid JSON body: ${n.message}`)}}function se(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 Le(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 Q(e){let t=e._authenticatedUser;if(t)return t;let n=Le(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 K(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)}function ut(e){let t=Le(e),n=e.headers?.get?.("cookie")||"";return{bearerToken:()=>t,cookies:{get:(r)=>{for(let s of n.split(";")){let a=s.indexOf("=");if(a===-1)continue;if(s.slice(0,a).trim()===r)return decodeURIComponent(s.slice(a+1).trim())}return null}}}}function We(e){if(e?.ownership)return e.ownership;let t=ke(e);if(!t)return null;return{field:t,resolve:async(n,r)=>{let{resolveAuthenticatedTeamId:s}=await import("@stacksjs/auth");return s(ut(r))}}}async function X(e,t,n){let r=We(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,a=null;try{a=await r.resolve(t,n)}catch{a=null}return{enforced:!0,value:a,field:String(r.field),bypass:s}}async function Fe(e,t,n,r){let s=e?.authedFill?.[t];if(!s)return{};if(typeof s==="function")try{let a=await s(n,r);return a&&typeof a==="object"?a:{}}catch{return{}}if(typeof s==="object"){let a={};for(let[i,p]of Object.entries(s))try{a[i]=typeof p==="function"?await p(n,r):p}catch{}return a}return{}}for(let[e,t]of Object.entries(pe)){let n=t.traits?.useApi;if(!n)continue;let r=typeof n==="object"?n:{},s=r.uri||t.table||e.toLowerCase()+"s",a=r.routes||["index","show","store","update","destroy"],i=t.table||s,p=_e(t),w=Be(t),c=Re(s,r.prefix),E=xe(t.attributes,w),{read:C,write:A,declared:B}=Ae(n);if(["store","update","destroy"].some((l)=>a.includes(l))&&B&&A.length===0)Z.warn(`[orm] ${e}: registering UNAUTHENTICATED mutating routes at ${c} (explicit \`middleware: []\` opt-out)`);if(["index","show"].some((l)=>a.includes(l))&&B&&C.length===0)Z.debug(`[orm] ${e}: registering public read routes at ${c} (declared \`middleware.read: []\`)`);let j=!!We(t),J=j&&!C.includes("auth")?["auth",...C]:C,I=(l,o=J)=>{let u=l;for(let f of o)if(u&&typeof u.middleware==="function")u=u.middleware(f);return u};if(a.includes("index")&&!H("GET",c))I(F.get(c,async(l)=>{try{let o=new URL(l.url),{page:u,perPage:f,offset:h}=Ne(o.searchParams),m=o.searchParams.get("sort"),g=T.selectFrom(i);g=Oe(g,m,E);let P=new Set(["page","per_page","sort","fields","search","include","limit","offset","with_count","withTrashed","onlyTrashed"]);for(let[x,O]of o.searchParams.entries()){if(P.has(x)||!O)continue;if(!/^[a-z_][a-z0-9_]*$/i.test(x))continue;let W=E.get(x);if(W)g=g.where(W,"=",O)}let R=o.searchParams.get("search");if(R&&p.length>0){let x=p.slice(0,5);g=g.where((O)=>{for(let W of x)O=O.orWhere(k(W),"like",`%${R}%`);return O})}let S=o.searchParams.get("fields");if(S){let x=S.split(",").map((O)=>O.trim()).filter((O)=>/^[a-z_][a-z0-9_]*$/i.test(O)).map((O)=>k(O));if(x.length>0)g=g.select(x)}let N=!!t.traits?.useSoftDeletes,b=o.searchParams.get("withTrashed")==="true",D=o.searchParams.get("onlyTrashed")==="true";if(N&&!b)g=D?g.whereNotNull("deleted_at"):g.whereNull("deleted_at");let y=j?await X(t,await Q(l),l):{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=ge(o,u,f,0,!1,void 0);return new Response(JSON.stringify({data:[],...x,meta:ne(o,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()||[],v=M.length>f,G=(v?M.slice(0,f):M).map((x)=>V(Y(x,t),w)),me=o.searchParams.get("include");if(me)G=await je(G,t,me,pe);let ze=o.searchParams.get("with_count")==="true",q;if(ze)try{let x=T.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(),W=typeof O==="number"?O:Number(O?.count??O);if(Number.isFinite(W))q=W}catch{}let ee={"Content-Type":"application/json"};if(q!==void 0&&!Number.isNaN(q))ee["X-Total-Count"]=String(q);ee["Cache-Control"]=y.enforced&&!y.bypass?"private, no-store":"public, max-age=15, must-revalidate",ee.Vary="Authorization";let He=ge(o,u,f,G.length,v,q);return new Response(JSON.stringify({data:G,...He,meta:ne(o,u,f,G.length,v,q)}),{status:200,headers:ee})}catch(o){if(o instanceof U){let u={error:o.message};if(o.details!==void 0)u.details=o.details;return d(u,o.status||400)}return d({error:`Failed to fetch ${s}`,detail:String(o)},500)}}));if(a.includes("show")&&!H("GET",`${c}/{id}`))I(F.get(`${c}/{id}`,async(l)=>{try{let o=se(l.params?.id);if(o==null)return d({error:"Invalid ID parameter"},400);let u=await T.selectFrom(i).where({id:o}).executeTakeFirst();if(!u)return d({error:`${e} not found`},404);let f=new URL(l.url);if(t.traits?.useSoftDeletes&&u.deleted_at&&f.searchParams.get("withTrashed")!=="true")return d({error:`${e} not found`},404);let h=j?await X(t,await Q(l),l):{enforced:!1,value:null,field:"",bypass:!1};if(h.enforced&&!h.bypass){if(h.value==null||!K(u[h.field],h.value))return d({error:`${e} not found`},404)}let m=V(Y(u,t),w),g=f.searchParams.get("include");if(g){let[b]=await je([m],t,g,pe);m=b}let P=m?.updated_at||m?.created_at,R=P?`W/"${m.id}-${String(P)}"`:void 0,S=l.headers?.get?.("if-none-match");if(R&&S&&S===R)return new Response(null,{status:304,headers:{ETag:R}});let N={"Content-Type":"application/json"};if(R)N.ETag=R;return N["Cache-Control"]=h.enforced&&!h.bypass?"private, no-store":"public, max-age=30, must-revalidate",N.Vary="Authorization",new Response(JSON.stringify({data:m}),{status:200,headers:N})}catch(o){if(o instanceof U){let u={error:o.message};if(o.details!==void 0)u.details=o.details;return d(u,o.status||400)}return d({error:`Failed to fetch ${e}`,detail:String(o)},500)}}));if(a.includes("store")&&!H("POST",c))I(F.post(c,async(l)=>{try{let o=await ye(l),u=ce(o,w),f=le(u,p),h=await Q(l);if(h){let b=await Fe(t,"creating",h,l);for(let[D,y]of Object.entries(b))if(y!==void 0&&y!==null&&f[D]===void 0)f[D]=y}let m=await X(t,h,l);if(m.enforced&&!m.bypass){if(!h)return d({error:"Auth required"},401);let b=Se(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=fe(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 P=await Me(f,t),R=ue(ve(P,t)),S=await T.insertInto(i).values(R).execute(),N=R;try{let b=S?.lastInsertRowid??S?.insertId??S;if(b){let D=await T.selectFrom(i).where({id:b}).executeTakeFirst();if(D)N=D}}catch{}return d({data:V(Y(N,t),w)},201)}catch(o){let{status:u,body:f}=ie(o,e,"create");return d(f,u)}}),A);if(a.includes("update")){let l=async(o)=>{try{let u=se(o.params?.id);if(u==null)return d({error:"Invalid ID parameter"},400);let f=await ye(o),h=ce(f,w),m=le(h,p),g=await Q(o);if(g){let y=await Fe(t,"updating",g,o);for(let[M,v]of Object.entries(y))if(v!==void 0&&v!==null&&m[M]===void 0)m[M]=v}if(Object.keys(m).length===0)return d({error:"No fillable fields provided"},422);let P=fe(m,t,"updating");if(!P.valid)return d({success:!1,message:"Validation failed",errors:P.errors},422);let R=await T.selectFrom(i).where({id:u}).executeTakeFirst();if(!R)return d({error:`${e} not found`},404);let S=await X(t,g,o);if(S.enforced&&!S.bypass){if(!g)return d({error:"Auth required"},401);let y=R[S.field];if(!K(y,S.value))return d({error:`Not your ${e}`},403);let M=Object.keys(m).find((v)=>k(v)===k(S.field));if(M!==void 0&&!K(m[M],S.value))return d({error:`Cannot reassign ${e} ownership`},403)}if(t.traits?.useTimestamps!==!1)m.updated_at=new Date().toISOString();let N=await Me(m,t),b=ue(ve(N,t));await T.updateTable(i).set(b).where({id:u}).execute();let D={...R,...b};try{let y=await T.selectFrom(i).where({id:u}).executeTakeFirst();if(y)D=y}catch{}return d({data:V(Y(D,t),w)})}catch(u){let{status:f,body:h}=ie(u,e,"update");return d(h,f)}};if(!H("PUT",`${c}/{id}`))I(F.put(`${c}/{id}`,l),A);if(!H("PATCH",`${c}/{id}`))I(F.patch(`${c}/{id}`,l),A)}let _=!!t.traits?.useSoftDeletes;if(a.includes("destroy")&&!H("DELETE",`${c}/{id}`))I(F.delete(`${c}/{id}`,async(l)=>{try{let o=se(l.params?.id);if(o==null)return d({error:"Invalid ID parameter"},400);let u=await T.selectFrom(i).where({id:o}).executeTakeFirst();if(!u)return d({error:`${e} not found`},404);let f=await Q(l),h=await X(t,f,l);if(h.enforced&&!h.bypass){if(!f)return d({error:"Auth required"},401);let m=u[h.field];if(!K(m,h.value))return d({error:`Not your ${e}`},403)}if(_){let m=new Date().toISOString();await T.updateTable(i).set({deleted_at:m,updated_at:m}).where({id:o}).execute()}else await T.deleteFrom(i).where({id:o}).execute();return new Response(null,{status:204})}catch(o){if(o instanceof U){let u={error:o.message};if(o.details!==void 0)u.details=o.details;return d(u,o.status||400)}return d({error:`Failed to delete ${e}`,detail:String(o)},500)}}),A);if(a.includes("destroy")&&!H("POST",`${c}/bulk-delete`))I(F.post(`${c}/bulk-delete`,async(l)=>{try{let u=(await ye(l))?.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 S=se(R);if(S!=null)f.push(S);else h.push(R)}if(h.length>0)return d({error:"Invalid IDs in batch",invalid:h},422);let m=await Q(l),g=await X(t,m,l);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 T.selectFrom(i).select(["id",g.field]).execute(),S=new Set(R.filter((b)=>K(b[g.field],g.value)).map((b)=>String(b.id))),N=f.filter((b)=>!S.has(String(b)));if(N.length>0)return d({error:`Cannot delete ${e} you don't own`,ids:N},403)}let P=new Date().toISOString();for(let R of f)if(_)await T.updateTable(i).set({deleted_at:P,updated_at:P}).where({id:R}).execute();else await T.deleteFrom(i).where({id:R}).execute();return d({message:`Successfully deleted ${f.length} ${s}`})}catch(o){if(o instanceof U){let u={error:o.message};if(o.details!==void 0)u.details=o.details;return d(u,o.status||400)}return d({error:`Failed to bulk delete ${s}`,detail:String(o)},500)}}),A)}var Tt=F;export{Tt as default};
2
+ var He=Object.defineProperty;var Je=(e)=>e;function qe(e,t){this[e]=Je.bind(null,t)}var dt=(e,t)=>{for(var n in t)He(e,n,{get:t[n],enumerable:!0,configurable:!0,set:qe.bind(t,n)})};var ft=(e,t)=>()=>(e&&(t=e(e=0)),t);var he=import.meta.require;import{route as M}from"@stacksjs/router";import{env as L}from"@stacksjs/env";import{projectPath as Ue,storagePath as ot}from"@stacksjs/path";import{createQueryBuilder as it,defaultConfig as Ie,setConfig as Ee}from"@stacksjs/query-builder";import{HttpError as U}from"@stacksjs/error-handling";import{log as Z}from"@stacksjs/logging";function Ve(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 ae(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(Ve(e))return{status:409,body:{error:`${t} already exists`}};return{status:500,body:{error:`Failed to ${n} ${t}`,detail:String(e)}}}function k(e){return e.replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/([A-Z])([A-Z][a-z])/g,"$1_$2").toLowerCase()}function ue(e){let t={};for(let[n,r]of Object.entries(e))t[k(n)]=r;return t}function Re(e,t){let n=String(t||"").replace(/^\/+|\/+$/g,""),r=String(e).replace(/^\/+/,"");return`/api/${n?`${n}/`:""}${r}`}function ke(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 _e(e,t,n){let r=Object.keys(e).find((o)=>k(o)===k(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(k(o)===k(t))delete s[o];return s[t]=n,{data:s}}function Se(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=k(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 ce(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=k(r);if(s!==r&&s in e)n[r]=e[s]}return n}function Xe(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 le(e,t){if(!t.length)return e;let n={...e};for(let r of t)delete n[r],delete n[k(r)];return n}function q(e,t){if(!e||t.length===0)return e;let n={...e};for(let r of t)delete n[r],delete n[k(r)];return n}var Ge=["id","uuid","created_at","updated_at","deleted_at"];function xe(e,t){let n=new Map;for(let r of[...Object.keys(e??{}),...Ge]){let s=k(r);if(!/^\w+$/.test(s))continue;n.set(r,s),n.set(s,s)}for(let r of t)n.delete(r),n.delete(k(r));return n}function Oe(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("-"),p=a?o.slice(1):o;if(!/^\w+$/.test(p))continue;let w=n.get(p);if(!w)continue;s=s.orderBy(w,a?"desc":"asc")}return s}var Ke={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"?Ye(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"?Ze(e):[],set:(e)=>e==null?null:Array.isArray(e)?JSON.stringify(e):e}};function Ye(e){try{return JSON.parse(e)}catch{return e}}function Ze(e){try{return JSON.parse(e)}catch{return[]}}function de(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"?Ke[o]:o;if(!a||typeof a[n]!=="function")continue;if(Object.prototype.hasOwnProperty.call(r,s))r[s]=a[n](r[s]);let p=k(s);if(p!==s&&Object.prototype.hasOwnProperty.call(r,p))r[p]=a[n](r[p])}return r}function fe(e,t,n){let r=t?.attributes??{},s={};for(let[o,a]of Object.entries(r)){let p=a?.validation?.rule;if(!p||typeof p.validate!=="function")continue;let w=Object.prototype.hasOwnProperty.call(e,o);if(!w&&n==="updating")continue;let l=a!==null&&typeof a==="object"&&Object.prototype.hasOwnProperty.call(a,"default"),I=w?e[o]:l?a.default:void 0,E=Xe(p,I),D=p.validate(E);if(!D?.valid&&Array.isArray(D?.errors)&&D.errors.length>0)s[o]=D.errors.map((B)=>a?.validation?.message?.[B?.code]??B?.message??"invalid")}return Object.keys(s).length===0?{valid:!0}:{valid:!1,errors:s}}function Te(e,t,n){let r=we(n);return e.find((s)=>s.method===t&&we(String(s.path??""))===r)}function we(e){return e.replace(/\{[^}]*\}/g,"{}").replace(/:[^/]+/g,"{}")}function ie(e){if(Array.isArray(e))return e.filter((t)=>typeof t==="string"&&t.length>0);return typeof e==="string"&&e?[e]:[]}function De(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?ie(s.read):["auth"],write:"write"in s?ie(s.write):["auth"],declared:!0}}let r=ie(n);return{read:r,write:r,declared:!0}}var be=15,et=100;function Ne(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(be),10),s=Math.min(Number.isFinite(r)?Math.max(1,r):be,et);return{page:n,perPage:s,offset:(n-1)*s}}function te(e,t){let n=new URL(e.toString());return n.searchParams.set("page",String(t)),`${n.pathname}${n.search}`}function ne(e,t,n,r,s,o){let a=(t-1)*n,p=r===0,w={page:t,per_page:n,from:p?null:a+1,to:p?null:a+r,has_more_pages:s,prev_page_url:t>1?te(e,t-1):null,next_page_url:s?te(e,t+1):null};if(o!==void 0&&!Number.isNaN(o)){let l=Math.max(1,Math.ceil(o/n));w.total=o,w.last_page=l,w.first_page_url=te(e,1),w.last_page_url=te(e,l)}return w}function ge(e,t,n,r,s,o){let{page:a,...p}=ne(e,t,n,r,s,o);return{current_page:a,...p}}import{readdirSync as tt}from"fs";import{basename as nt,extname as $e,join as rt}from"path";function st(e){let t=[],n=(r)=>{let s;try{s=tt(r,{withFileTypes:!0})}catch{return}for(let o of s){let a=rt(r,o.name);if(o.isDirectory()){n(a);continue}if(![".ts",".js"].includes($e(o.name)))continue;if(o.name.startsWith(".")||o.name.startsWith("index"))continue;t.push(a)}};return n(e),t.sort()}async function Ae(e,t,n){for(let r of st(e))try{let s=$e(r),o=await import(`${r}?t=${Date.now()}`),a=o.default??o,p=a.name??nt(r,s);t[p]={...a,name:p}}catch(s){n?.(r,s)}}async function Pe(e){let t={};return await Ae(e.defaultsRoot,t,e.onImportError),await Ae(e.userRoot,t,e.onImportError),t}var at=Ue("config/qb.ts");try{let e=(await import(at)).default;Ee(e??Ie)}catch{Z.debug("[orm] No config/qb.ts override found - deriving config from DB_CONNECTION");let e=L.DB_CONNECTION||"sqlite";Ee({...Ie,dialect:e,database:e==="sqlite"?{database:L.DB_DATABASE_PATH||"database/stacks.sqlite"}:{database:L.DB_DATABASE||"stacks",host:L.DB_HOST||"127.0.0.1",port:L.DB_PORT||(e==="postgres"?5432:3306),username:L.DB_USERNAME||(e==="postgres"?"postgres":"root"),password:L.DB_PASSWORD||""}})}var pe=await Pe({defaultsRoot:ot("framework/defaults/app/Models"),userRoot:Ue("app/Models"),onImportError:(e,t)=>Z.warn(`[orm] Could not load model ${e}: ${t.message}`)}),ut=it(),T=ut;function z(e,t){let n=Te(M.routes,e,t);if(!n)return!1;let r=String(n.path??"");return Z.info(`[orm] Skipping generated ${e} ${t} - already defined${r&&r!==t?` as ${r}`:""}.`),!0}function Be(e){if(!e.attributes)return[];return Object.entries(e.attributes).filter(([t,n])=>n.hidden===!0).map(([t])=>t)}function Ce(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 je(e,t,n,r){if(!e.length)return e;let s=n.split(",").map((l)=>l.trim()).filter(Boolean);if(!s.length)return e;let o=Array.isArray(t.belongsTo)?t.belongsTo:[],a=Array.isArray(t.hasMany)?t.hasMany:[],p=Array.isArray(t.hasOne)?t.hasOne:[],w=new Map;for(let l of o)w.set(k(l),{kind:"belongsTo",modelName:l});for(let l of p)w.set(k(l),{kind:"hasOne",modelName:l});for(let l of a)w.set(Ce(k(l)),{kind:"hasMany",modelName:l});for(let l of s){let I=w.get(l);if(!I)continue;let E=r[I.modelName];if(!E)continue;let D=E.table||Ce(k(I.modelName)),B=Be(E),oe=(Q)=>q(Y(Q,E),B);if(I.kind==="belongsTo"||I.kind==="hasOne"){let Q=`${k(I.modelName)}_id`,j=[...new Set(e.map((S)=>S[Q]).filter((S)=>S!=null))];if(!j.length){for(let S of e)S[l]=null;continue}let H=await T.selectFrom(D).whereIn("id",j).get(),A=new Map;for(let S of H??[])A.set(String(S.id),oe(S));for(let S of e)S[l]=A.get(String(S[Q]))??null}else{let Q=String(t.name??""),j=`${k(Q)}_id`,H=[...new Set(e.map((c)=>c.id).filter((c)=>c!=null))];if(!H.length){for(let c of e)c[l]=[];continue}let A=await T.selectFrom(D).whereIn(j,H).get(),S=new Map;for(let c of A??[]){let i=String(c[j]),u=S.get(i)??[];u.push(oe(c)),S.set(i,u)}for(let c of e)c[l]=S.get(String(c.id))??[]}}return e}async function ve(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 Fe(e,t){return de(e,t?.casts,"set")}function Y(e,t){return de(e,t?.casts,"get")}function d(e,t=200){return new Response(JSON.stringify(e),{status:t,headers:{"Content-Type":"application/json"}})}var re=1048576;async function ye(e){if(Number(e.headers?.get?.("content-length")??0)>re)throw new U(413,`Request body exceeds ${re}-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>re)throw new U(413,`Request body exceeds ${re}-byte limit`);if(!n)return{};let r=JSON.parse(n);return r&&typeof r==="object"&&!Array.isArray(r)?r:{}}catch(n){if(n instanceof U)throw n;throw new U(400,`Invalid JSON body: ${n.message}`)}}function se(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 Qe(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 V(e){let t=e._authenticatedUser;if(t)return t;let n=Qe(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 K(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)}function ct(e){let t=Qe(e),n=e.headers?.get?.("cookie")||"";return{bearerToken:()=>t,cookies:{get:(r)=>{for(let s of n.split(";")){let o=s.indexOf("=");if(o===-1)continue;if(s.slice(0,o).trim()===r)return decodeURIComponent(s.slice(o+1).trim())}return null}}}}function We(e){if(e?.ownership)return e.ownership;let t=ke(e);if(!t)return null;return{field:t,resolve:async(n,r)=>{let{resolveAuthenticatedTeamId:s}=await import("@stacksjs/auth");return s(ct(r))}}}async function X(e,t,n){let r=We(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 Me(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,p]of Object.entries(s))try{o[a]=typeof p==="function"?await p(n,r):p}catch{}return o}return{}}for(let[e,t]of Object.entries(pe)){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,p=Se(t),w=Be(t),l=Re(s,r.prefix),I=xe(t.attributes,w),{read:E,write:D,declared:B}=De(n);if(["store","update","destroy"].some((c)=>o.includes(c))&&B&&D.length===0)Z.warn(`[orm] ${e}: registering UNAUTHENTICATED mutating routes at ${l} (explicit \`middleware: []\` opt-out)`);if(["index","show"].some((c)=>o.includes(c))&&B&&E.length===0)Z.debug(`[orm] ${e}: registering public read routes at ${l} (declared \`middleware.read: []\`)`);let j=!!We(t),H=j&&!E.includes("auth")?["auth",...E]:E,A=(c,i=H)=>{let u=c;for(let f of i)if(u&&typeof u.middleware==="function")u=u.middleware(f);return u};if(o.includes("index")&&!z("GET",l))A(M.get(l,async(c)=>{try{let i=new URL(c.url),{page:u,perPage:f,offset:h}=Ne(i.searchParams),m=i.searchParams.get("sort"),g=T.selectFrom(a);g=Oe(g,m,I);let P=new Set(["page","per_page","sort","fields","search","include","limit","offset","with_count","withTrashed","onlyTrashed"]);for(let[x,O]of i.searchParams.entries()){if(P.has(x)||!O)continue;if(!/^[a-z_][a-z0-9_]*$/i.test(x))continue;let W=I.get(x);if(W)g=g.where(W,"=",O)}let R=i.searchParams.get("search");if(R&&p.length>0){let x=p.slice(0,5);g=g.where((O)=>{for(let W of x)O=O.orWhere(k(W),"like",`%${R}%`);return O})}let _=i.searchParams.get("fields");if(_){let x=_.split(",").map((O)=>O.trim()).filter((O)=>/^[a-z_][a-z0-9_]*$/i.test(O)).map((O)=>k(O));if(x.length>0)g=g.select(x)}let N=!!t.traits?.useSoftDeletes,b=i.searchParams.get("withTrashed")==="true",C=i.searchParams.get("onlyTrashed")==="true";if(N&&!b)g=C?g.whereNotNull("deleted_at"):g.whereNull("deleted_at");let y=j?await X(t,await V(c),c):{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=ge(i,u,f,0,!1,void 0);return new Response(JSON.stringify({data:[],...x,meta:ne(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 v=await g.limit(f+1).offset(h).get()||[],F=v.length>f,G=(F?v.slice(0,f):v).map((x)=>q(Y(x,t),w)),me=i.searchParams.get("include");if(me)G=await je(G,t,me,pe);let Le=i.searchParams.get("with_count")==="true",J;if(Le)try{let x=T.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(),W=typeof O==="number"?O:Number(O?.count??O);if(Number.isFinite(W))J=W}catch{}let ee={"Content-Type":"application/json"};if(J!==void 0&&!Number.isNaN(J))ee["X-Total-Count"]=String(J);ee["Cache-Control"]=y.enforced&&!y.bypass?"private, no-store":"public, max-age=15, must-revalidate",ee.Vary="Authorization";let ze=ge(i,u,f,G.length,F,J);return new Response(JSON.stringify({data:G,...ze,meta:ne(i,u,f,G.length,F,J)}),{status:200,headers:ee})}catch(i){if(i instanceof U){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")&&!z("GET",`${l}/{id}`))A(M.get(`${l}/{id}`,async(c)=>{try{let i=se(c.params?.id);if(i==null)return d({error:"Invalid ID parameter"},400);let u=await T.selectFrom(a).where({id:i}).executeTakeFirst();if(!u)return d({error:`${e} not found`},404);let f=new URL(c.url);if(t.traits?.useSoftDeletes&&u.deleted_at&&f.searchParams.get("withTrashed")!=="true")return d({error:`${e} not found`},404);let h=j?await X(t,await V(c),c):{enforced:!1,value:null,field:"",bypass:!1};if(h.enforced&&!h.bypass){if(h.value==null||!K(u[h.field],h.value))return d({error:`${e} not found`},404)}let m=q(Y(u,t),w),g=f.searchParams.get("include");if(g){let[b]=await je([m],t,g,pe);m=b}let P=m?.updated_at||m?.created_at,R=P?`W/"${m.id}-${String(P)}"`:void 0,_=c.headers?.get?.("if-none-match");if(R&&_&&_===R)return new Response(null,{status:304,headers:{ETag:R}});let N={"Content-Type":"application/json"};if(R)N.ETag=R;return N["Cache-Control"]=h.enforced&&!h.bypass?"private, no-store":"public, max-age=30, must-revalidate",N.Vary="Authorization",new Response(JSON.stringify({data:m}),{status:200,headers:N})}catch(i){if(i instanceof U){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(o.includes("store")&&!z("POST",l))A(M.post(l,async(c)=>{try{let i=await ye(c),u=le(i,w),f=ce(u,p),h=await V(c);if(h){let b=await Me(t,"creating",h,c);for(let[C,y]of Object.entries(b))if(y!==void 0&&y!==null&&f[C]===void 0)f[C]=y}let m=await X(t,h,c);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=fe(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 P=await ve(f,t),R=ue(Fe(P,t)),_=await T.insertInto(a).values(R).execute(),N=R;try{let b=_?.lastInsertRowid??_?.insertId??_;if(b){let C=await T.selectFrom(a).where({id:b}).executeTakeFirst();if(C)N=C}}catch{}return d({data:q(Y(N,t),w)},201)}catch(i){let{status:u,body:f}=ae(i,e,"create");return d(f,u)}}),D);if(o.includes("update")){let c=async(i)=>{try{let u=se(i.params?.id);if(u==null)return d({error:"Invalid ID parameter"},400);let f=await ye(i),h=le(f,w),m=ce(h,p),g=await V(i);if(g){let y=await Me(t,"updating",g,i);for(let[v,F]of Object.entries(y))if(F!==void 0&&F!==null&&m[v]===void 0)m[v]=F}if(Object.keys(m).length===0)return d({error:"No fillable fields provided"},422);let P=fe(m,t,"updating");if(!P.valid)return d({success:!1,message:"Validation failed",errors:P.errors},422);let R=await T.selectFrom(a).where({id:u}).executeTakeFirst();if(!R)return d({error:`${e} not found`},404);let _=await X(t,g,i);if(_.enforced&&!_.bypass){if(!g)return d({error:"Auth required"},401);let y=R[_.field];if(!K(y,_.value))return d({error:`Not your ${e}`},403);let v=Object.keys(m).find((F)=>k(F)===k(_.field));if(v!==void 0&&!K(m[v],_.value))return d({error:`Cannot reassign ${e} ownership`},403)}if(t.traits?.useTimestamps!==!1)m.updated_at=new Date().toISOString();let N=await ve(m,t),b=ue(Fe(N,t));await T.updateTable(a).set(b).where({id:u}).execute();let C={...R,...b};try{let y=await T.selectFrom(a).where({id:u}).executeTakeFirst();if(y)C=y}catch{}return d({data:q(Y(C,t),w)})}catch(u){let{status:f,body:h}=ae(u,e,"update");return d(h,f)}};if(!z("PUT",`${l}/{id}`))A(M.put(`${l}/{id}`,c),D);if(!z("PATCH",`${l}/{id}`))A(M.patch(`${l}/{id}`,c),D)}let S=!!t.traits?.useSoftDeletes;if(o.includes("destroy")&&!z("DELETE",`${l}/{id}`))A(M.delete(`${l}/{id}`,async(c)=>{try{let i=se(c.params?.id);if(i==null)return d({error:"Invalid ID parameter"},400);let u=await T.selectFrom(a).where({id:i}).executeTakeFirst();if(!u)return d({error:`${e} not found`},404);let f=await V(c),h=await X(t,f,c);if(h.enforced&&!h.bypass){if(!f)return d({error:"Auth required"},401);let m=u[h.field];if(!K(m,h.value))return d({error:`Not your ${e}`},403)}if(S){let m=new Date().toISOString();await T.updateTable(a).set({deleted_at:m,updated_at:m}).where({id:i}).execute()}else await T.deleteFrom(a).where({id:i}).execute();return new Response(null,{status:204})}catch(i){if(i instanceof U){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(o.includes("destroy")&&!z("POST",`${l}/bulk-delete`))A(M.post(`${l}/bulk-delete`,async(c)=>{try{let u=(await ye(c))?.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 _=se(R);if(_!=null)f.push(_);else h.push(R)}if(h.length>0)return d({error:"Invalid IDs in batch",invalid:h},422);let m=await V(c),g=await X(t,m,c);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 T.selectFrom(a).select(["id",g.field]).execute(),_=new Set(R.filter((b)=>K(b[g.field],g.value)).map((b)=>String(b.id))),N=f.filter((b)=>!_.has(String(b)));if(N.length>0)return d({error:`Cannot delete ${e} you don't own`,ids:N},403)}let P=new Date().toISOString();for(let R of f)if(S)await T.updateTable(a).set({deleted_at:P,updated_at:P}).where({id:R}).execute();else await T.deleteFrom(a).where({id:R}).execute();return d({message:`Successfully deleted ${f.length} ${s}`})}catch(i){if(i instanceof U){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)}var Dt=M;export{Dt as default};
package/dist/utils.d.ts CHANGED
@@ -3,7 +3,7 @@ export declare function modelTableName(model: Model | ModelPath): Promise<string
3
3
  export declare function getModelName(model: Model, modelPath: string): string;
4
4
  export declare function getTableName(model: Model, modelPath: string): TableNames;
5
5
  export declare function getPivotTableName(formattedModelName: string, modelRelationTable: string): string;
6
- export declare function hasRelations(obj: any, key: string): boolean;
6
+ export declare function hasRelations(obj: object, key: string): boolean;
7
7
  export declare function getRelations(model: Model, modelName: string): Promise<RelationConfig[]>;
8
8
  export declare function getRelationType(relation: string): string;
9
9
  export declare function getRelationCount(relation: string): string;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/orm",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.72.102",
5
+ "version": "0.73.0",
6
6
  "description": "The Stacks ORM integration",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -67,9 +67,9 @@
67
67
  "bun-query-builder": "^0.2.53"
68
68
  },
69
69
  "devDependencies": {
70
- "@stacksjs/config": "0.72.102",
70
+ "@stacksjs/config": "0.73.0",
71
71
  "better-dx": "^0.2.24",
72
- "@stacksjs/query-builder": "0.72.102",
73
- "@stacksjs/types": "0.72.102"
72
+ "@stacksjs/query-builder": "0.73.0",
73
+ "@stacksjs/types": "0.73.0"
74
74
  }
75
75
  }