@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.
- package/dist/auto-crud.d.ts +4 -4
- package/dist/define-model.d.ts +44 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +13 -13
- package/dist/models/User.ts +1 -1
- package/dist/models/commerce/{Driver.ts → Courier.ts} +7 -7
- package/dist/models/commerce/{DriverPing.ts → CourierPing.ts} +7 -7
- package/dist/models/commerce/DeliveryRoute.ts +6 -6
- package/dist/models/commerce/DeliveryStop.ts +31 -8
- package/dist/routes.js +1 -1
- package/dist/utils.d.ts +1 -1
- package/package.json +4 -4
package/dist/models/User.ts
CHANGED
|
@@ -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: '
|
|
6
|
-
table: '
|
|
5
|
+
name: 'Courier',
|
|
6
|
+
table: 'couriers',
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
10
|
belongsTo: ['User'],
|
|
11
|
-
hasMany: ['DeliveryRoute', '
|
|
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: '
|
|
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 `
|
|
98
|
-
*
|
|
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
|
|
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
|
|
5
|
+
* One position fix from a courier's device.
|
|
6
6
|
*
|
|
7
|
-
* The append-only history behind `
|
|
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
|
-
* `
|
|
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: '
|
|
20
|
-
table: '
|
|
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: ['
|
|
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
|
|
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', '
|
|
15
|
-
searchable: ['
|
|
14
|
+
displayable: ['id', 'courier', 'vehicle', 'stops', 'deliveryTime', 'totalDistance', 'lastActive'],
|
|
15
|
+
searchable: ['courier', 'vehicle'],
|
|
16
16
|
sortable: ['stops', 'deliveryTime', 'totalDistance', 'lastActive', 'createdAt', 'updatedAt'],
|
|
17
|
-
filterable: ['
|
|
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: ['
|
|
33
|
-
hasMany: ['DeliveryStop', '
|
|
32
|
+
belongsTo: ['Courier'],
|
|
33
|
+
hasMany: ['DeliveryStop', 'CourierPing'],
|
|
34
34
|
|
|
35
35
|
attributes: {
|
|
36
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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.
|
|
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.
|
|
70
|
+
"@stacksjs/config": "0.73.0",
|
|
71
71
|
"better-dx": "^0.2.24",
|
|
72
|
-
"@stacksjs/query-builder": "0.
|
|
73
|
-
"@stacksjs/types": "0.
|
|
72
|
+
"@stacksjs/query-builder": "0.73.0",
|
|
73
|
+
"@stacksjs/types": "0.73.0"
|
|
74
74
|
}
|
|
75
75
|
}
|