@stacksjs/commerce 0.75.112 → 0.75.114

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.
Files changed (230) hide show
  1. package/README.md +15 -0
  2. package/dist/auctions/bids/fetch.js +1 -0
  3. package/dist/auctions/bids/index.js +1 -0
  4. package/dist/auctions/bids/place.js +1 -0
  5. package/dist/auctions/engine/anti-snipe.js +1 -0
  6. package/dist/auctions/engine/increments.js +1 -0
  7. package/dist/auctions/engine/index.js +1 -0
  8. package/dist/auctions/engine/proxy.js +1 -0
  9. package/dist/auctions/engine/winners.js +1 -0
  10. package/dist/auctions/index.js +1 -0
  11. package/dist/auctions/lots/fetch.js +1 -0
  12. package/dist/auctions/lots/index.js +1 -0
  13. package/dist/auctions/lots/lifecycle.js +1 -0
  14. package/dist/auctions/notifications.js +1 -0
  15. package/dist/auctions/pledges/index.js +1 -0
  16. package/dist/auctions/realtime.js +1 -0
  17. package/dist/auctions/rules.js +1 -0
  18. package/dist/auctions/types.js +0 -0
  19. package/dist/carts/destroy.js +1 -0
  20. package/dist/carts/fetch.js +1 -0
  21. package/dist/carts/index.js +1 -0
  22. package/dist/carts/store.js +1 -0
  23. package/dist/carts/update.js +1 -0
  24. package/dist/catalog.js +1 -2
  25. package/dist/coupons/destroy.js +1 -0
  26. package/dist/coupons/fetch.js +1 -0
  27. package/dist/coupons/index.js +1 -0
  28. package/dist/coupons/store.js +1 -0
  29. package/dist/coupons/update.js +7 -0
  30. package/dist/customers/destroy.js +1 -0
  31. package/dist/customers/fetch.js +1 -0
  32. package/dist/customers/index.js +1 -0
  33. package/dist/customers/store.js +1 -0
  34. package/dist/customers/update.js +1 -0
  35. package/dist/devices/destroy.js +1 -0
  36. package/dist/devices/export.js +1 -0
  37. package/dist/devices/fetch.js +1 -0
  38. package/dist/devices/index.js +1 -0
  39. package/dist/devices/store.js +1 -0
  40. package/dist/devices/update.js +1 -0
  41. package/dist/errors/destroy.js +1 -0
  42. package/dist/errors/fetch.js +1 -0
  43. package/dist/errors/index.js +1 -0
  44. package/dist/errors/update.js +1 -0
  45. package/dist/gift-cards/destroy.js +1 -0
  46. package/dist/gift-cards/fetch.js +1 -0
  47. package/dist/gift-cards/index.js +1 -0
  48. package/dist/gift-cards/store.js +1 -0
  49. package/dist/gift-cards/update.js +10 -0
  50. package/dist/imports/account-mapping.js +1 -0
  51. package/dist/imports/account-types.js +0 -0
  52. package/dist/imports/account-writer.js +1 -0
  53. package/dist/imports/admin-http.js +1 -0
  54. package/dist/imports/credentials.js +1 -0
  55. package/dist/imports/http.js +1 -0
  56. package/dist/imports/identity.js +1 -0
  57. package/dist/imports/index.js +1 -0
  58. package/dist/imports/money.js +1 -0
  59. package/dist/imports/shopify-admin.js +1 -0
  60. package/dist/imports/shopify.js +1 -0
  61. package/dist/imports/shopware-admin.js +1 -0
  62. package/dist/imports/shopware.js +1 -0
  63. package/dist/imports/text.js +1 -0
  64. package/dist/imports/types.js +0 -0
  65. package/dist/imports/woocommerce-admin.js +1 -0
  66. package/dist/imports/woocommerce.js +1 -0
  67. package/dist/imports/writer.js +1 -0
  68. package/dist/index.d.ts +3 -0
  69. package/dist/index.js +1 -28
  70. package/dist/inventory.d.ts +2 -0
  71. package/dist/inventory.js +1 -0
  72. package/dist/money.js +1 -2
  73. package/dist/orders/destroy.js +1 -0
  74. package/dist/orders/events.js +1 -0
  75. package/dist/orders/export.js +1 -0
  76. package/dist/orders/fetch.js +1 -0
  77. package/dist/orders/guards.js +1 -0
  78. package/dist/orders/index.js +1 -0
  79. package/dist/orders/place-order.js +1 -0
  80. package/dist/orders/store.js +1 -0
  81. package/dist/orders/totals.js +1 -0
  82. package/dist/orders/transition.js +1 -0
  83. package/dist/orders/update.js +1 -0
  84. package/dist/orders/webhook.js +1 -0
  85. package/dist/payments/destroy.js +1 -0
  86. package/dist/payments/fetch.js +1 -0
  87. package/dist/payments/index.js +1 -0
  88. package/dist/payments/store.js +1 -0
  89. package/dist/payments/update.js +8 -0
  90. package/dist/product.d.ts +1 -0
  91. package/dist/product.js +0 -0
  92. package/dist/products/categories/destroy.js +1 -0
  93. package/dist/products/categories/fetch.js +1 -0
  94. package/dist/products/categories/index.js +1 -0
  95. package/dist/products/categories/store.js +1 -0
  96. package/dist/products/categories/types.js +0 -0
  97. package/dist/products/categories/update.js +1 -0
  98. package/dist/products/index.js +1 -0
  99. package/dist/products/items/destroy.js +1 -0
  100. package/dist/products/items/fetch.js +1 -0
  101. package/dist/products/items/index.d.ts +1 -0
  102. package/dist/products/items/index.js +1 -0
  103. package/dist/products/items/store.js +1 -0
  104. package/dist/products/items/update.d.ts +2 -0
  105. package/dist/products/items/update.js +1 -0
  106. package/dist/products/manufacturers/destroy.js +1 -0
  107. package/dist/products/manufacturers/fetch.js +1 -0
  108. package/dist/products/manufacturers/index.js +1 -0
  109. package/dist/products/manufacturers/store.js +1 -0
  110. package/dist/products/manufacturers/update.js +1 -0
  111. package/dist/products/reviews/destroy.js +1 -0
  112. package/dist/products/reviews/fetch.js +1 -0
  113. package/dist/products/reviews/index.js +1 -0
  114. package/dist/products/reviews/stats.js +1 -0
  115. package/dist/products/reviews/store.js +1 -0
  116. package/dist/products/reviews/update.js +1 -0
  117. package/dist/products/units/destroy.js +1 -0
  118. package/dist/products/units/fetch.js +1 -0
  119. package/dist/products/units/index.js +1 -0
  120. package/dist/products/units/store.js +1 -0
  121. package/dist/products/units/types.js +0 -0
  122. package/dist/products/units/update.js +1 -0
  123. package/dist/products/variants/destroy.js +1 -0
  124. package/dist/products/variants/fetch.js +1 -0
  125. package/dist/products/variants/index.js +1 -0
  126. package/dist/products/variants/store.js +1 -0
  127. package/dist/products/variants/update.js +1 -0
  128. package/dist/receipts/destroy.js +1 -0
  129. package/dist/receipts/export.d.ts +1 -0
  130. package/dist/receipts/export.js +0 -0
  131. package/dist/receipts/fetch.js +1 -0
  132. package/dist/receipts/index.js +1 -0
  133. package/dist/receipts/printer.d.ts +37 -0
  134. package/dist/receipts/printer.js +1 -0
  135. package/dist/receipts/store.js +1 -0
  136. package/dist/receipts/timestamp.js +1 -0
  137. package/dist/receipts/update.js +1 -0
  138. package/dist/refund-allocation.js +1 -2
  139. package/dist/register.js +1 -2
  140. package/dist/releases.js +1 -2
  141. package/dist/sales-tax.js +1 -2
  142. package/dist/shippings/couriers/destroy.js +1 -0
  143. package/dist/shippings/couriers/fetch.js +1 -0
  144. package/dist/shippings/couriers/index.js +1 -0
  145. package/dist/shippings/couriers/store.js +1 -0
  146. package/dist/shippings/couriers/update.js +1 -0
  147. package/dist/shippings/delivery-routes/destroy.js +1 -0
  148. package/dist/shippings/delivery-routes/fetch.js +1 -0
  149. package/dist/shippings/delivery-routes/index.js +1 -0
  150. package/dist/shippings/delivery-routes/store.js +1 -0
  151. package/dist/shippings/delivery-routes/update.js +1 -0
  152. package/dist/shippings/delivery-routes/validate-write.js +1 -0
  153. package/dist/shippings/digital-deliveries/destroy.js +1 -0
  154. package/dist/shippings/digital-deliveries/fetch.js +1 -0
  155. package/dist/shippings/digital-deliveries/index.js +1 -0
  156. package/dist/shippings/digital-deliveries/store.js +1 -0
  157. package/dist/shippings/digital-deliveries/update.js +1 -0
  158. package/dist/shippings/geocoding/index.js +1 -0
  159. package/dist/shippings/geocoding/nominatim.js +1 -0
  160. package/dist/shippings/geocoding/types.js +1 -0
  161. package/dist/shippings/index.js +1 -0
  162. package/dist/shippings/license-keys/destroy.js +1 -0
  163. package/dist/shippings/license-keys/fetch.js +1 -0
  164. package/dist/shippings/license-keys/index.js +1 -0
  165. package/dist/shippings/license-keys/store.js +1 -0
  166. package/dist/shippings/license-keys/update.js +1 -0
  167. package/dist/shippings/license-keys/validate-write.js +1 -0
  168. package/dist/shippings/shipping-methods/destroy.js +1 -0
  169. package/dist/shippings/shipping-methods/fetch.js +1 -0
  170. package/dist/shippings/shipping-methods/index.js +1 -0
  171. package/dist/shippings/shipping-methods/store.js +1 -0
  172. package/dist/shippings/shipping-methods/update.js +1 -0
  173. package/dist/shippings/shipping-rates/destroy.js +1 -0
  174. package/dist/shippings/shipping-rates/fetch.js +1 -0
  175. package/dist/shippings/shipping-rates/index.js +1 -0
  176. package/dist/shippings/shipping-rates/store.js +1 -0
  177. package/dist/shippings/shipping-rates/update.js +1 -0
  178. package/dist/shippings/shipping-rates/validate-write.js +1 -0
  179. package/dist/shippings/shipping-zones/destroy.js +1 -0
  180. package/dist/shippings/shipping-zones/fetch.js +1 -0
  181. package/dist/shippings/shipping-zones/index.js +1 -0
  182. package/dist/shippings/shipping-zones/store.js +1 -0
  183. package/dist/shippings/shipping-zones/update.js +1 -0
  184. package/dist/shippings/tracking/events.js +1 -0
  185. package/dist/shippings/tracking/geo.js +1 -0
  186. package/dist/shippings/tracking/index.js +1 -0
  187. package/dist/shippings/tracking/ping.js +1 -0
  188. package/dist/shippings/tracking/stops.js +1 -0
  189. package/dist/shippings/write-data.js +1 -0
  190. package/dist/tax/breakdown.js +1 -0
  191. package/dist/tax/destroy.js +1 -0
  192. package/dist/tax/fetch.js +1 -0
  193. package/dist/tax/index.js +1 -0
  194. package/dist/tax/store.js +1 -0
  195. package/dist/tax/types.js +0 -0
  196. package/dist/tax/update.js +1 -0
  197. package/dist/tax-input.d.ts +7 -0
  198. package/dist/tax-input.js +1 -0
  199. package/dist/tests/fixtures/returning-all-writes.d.ts +1 -0
  200. package/dist/tests/fixtures/returning-all-writes.js +1 -0
  201. package/dist/tests/fixtures/soft-delete-counts.d.ts +1 -0
  202. package/dist/tests/fixtures/soft-delete-counts.js +1 -0
  203. package/dist/tests/fixtures/write-counts.d.ts +1 -0
  204. package/dist/tests/fixtures/write-counts.js +8 -0
  205. package/dist/tests/setup.d.ts +5 -0
  206. package/dist/tests/setup.js +1 -0
  207. package/dist/types.js +0 -0
  208. package/dist/utils/inserted-id.js +1 -0
  209. package/dist/utils/inventory-adjustment.js +5 -0
  210. package/dist/utils/model-row.js +1 -0
  211. package/dist/utils/mutation-count.js +1 -0
  212. package/dist/utils/typed-stats.js +1 -0
  213. package/dist/waitlists/index.js +1 -0
  214. package/dist/waitlists/products/destroy.js +1 -0
  215. package/dist/waitlists/products/export.d.ts +41 -0
  216. package/dist/waitlists/products/export.js +1 -0
  217. package/dist/waitlists/products/fetch.js +1 -0
  218. package/dist/waitlists/products/index.js +1 -0
  219. package/dist/waitlists/products/store.js +1 -0
  220. package/dist/waitlists/products/update.js +1 -0
  221. package/dist/waitlists/products/write-data.js +1 -0
  222. package/dist/waitlists/restaurant/destroy.js +1 -0
  223. package/dist/waitlists/restaurant/export.d.ts +39 -0
  224. package/dist/waitlists/restaurant/export.js +1 -0
  225. package/dist/waitlists/restaurant/fetch.js +1 -0
  226. package/dist/waitlists/restaurant/index.js +1 -0
  227. package/dist/waitlists/restaurant/store.js +1 -0
  228. package/dist/waitlists/restaurant/update.js +1 -0
  229. package/dist/waitlists/restaurant/write-data.js +1 -0
  230. package/package.json +13 -12
package/dist/sales-tax.js CHANGED
@@ -1,2 +1 @@
1
- // @bun
2
- function s(t){let n=Number(t);return Number.isFinite(n)?n/100:0}function b(t,n,p={}){let a=Math.max(0,Math.round(t)),m=[],i=0,u=0;for(let e of n){let x=s(e.rate),o=Math.round(a*x),r=Boolean(p.exempt&&e.exemptible);if(r)u+=o;else i+=o;m.push({id:Number(e.id),code:String(e.code??""),name:String(e.name??""),rate:x,amount:r?0:o,exempted:r})}return{taxable:a,components:m,tax:i,exempted:u}}export{b as breakdownFor,s as multiplierOf};
1
+ export function multiplierOf(rate){const percent=Number(rate);return Number.isFinite(percent)?percent/100:0}export function breakdownFor(taxable,rates,options={}){const base=Math.max(0,Math.round(taxable)),components=[];let tax=0,exempted=0;for(const row of rates){const rate=multiplierOf(row.rate),full=Math.round(base*rate),lifted=Boolean(options.exempt&&row.exemptible);if(lifted)exempted+=full;else tax+=full;components.push({id:Number(row.id),code:String(row.code??""),name:String(row.name??""),rate,amount:lifted?0:full,exempted:lifted})}return{taxable:base,components,tax,exempted}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";import{mutationCount}from"../../utils/mutation-count";import{fetchById}from"./fetch";export async function destroy(id){try{const result=await db.deleteFrom("couriers").where("id","=",id).executeTakeFirst();return mutationCount(result)>0}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete courier: ${error.message}`);throw error}}export async function bulkDestroy(ids){if(!ids.length)return 0;let deletedCount=0;try{await db.transaction(async(trx)=>{for(const id of ids)if(await fetchById(id)){await trx.deleteFrom("couriers").where("id","=",id).execute();deletedCount++}});return deletedCount}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete couriers in bulk: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";import{asModelRow,asModelRows}from"../../utils/model-row";export async function fetchById(id){const row=await db.selectFrom("couriers").where("id","=",id).selectAll().executeTakeFirst();return asModelRow(row,!0)}export async function fetchAll(){return asModelRows(await db.selectFrom("couriers").selectAll().execute())}
@@ -0,0 +1 @@
1
+ export{bulkDestroy,destroy}from"./destroy";export{fetchAll,fetchById}from"./fetch";export{bulkStore,store}from"./store";export{update,updateContact,updateStatus}from"./update";
@@ -0,0 +1 @@
1
+ import{randomUUIDv7}from"bun";import{db}from"@stacksjs/database/runtime";import{asModelRow}from"../../utils/model-row";import{mutationCount}from"../../utils/mutation-count";import{courierWriteData}from"../write-data";export async function store(data){try{const uuid=randomUUIDv7(),courierData={...courierWriteData(data),uuid};await db.insertInto("couriers").values(courierData).executeTakeFirst();const result=await db.selectFrom("couriers").where("uuid","=",uuid).selectAll().executeTakeFirst();if(!result)throw Error("Failed to resolve created courier");return asModelRow(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to create courier: ${error.message}`);throw error}}export async function bulkStore(data){if(!data.length)return 0;try{const courierDataArray=data.map((item)=>({...courierWriteData(item),uuid:randomUUIDv7()})),result=await db.insertInto("couriers").values(courierDataArray).executeTakeFirst();return mutationCount(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to create couriers in bulk: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";import{asModelRow}from"../../utils/model-row";import{formatDate}from"@stacksjs/orm";import{courierWriteData}from"../write-data";export async function update(id,data){try{if(!id)throw Error("Courier ID is required for update");const result=await db.updateTable("couriers").set({...courierWriteData(data),updated_at:formatDate(new Date)}).where("id","=",id).returningAll().executeTakeFirst();if(!result)return;return asModelRow(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to update courier: ${error.message}`);throw error}}export async function updateStatus(id,status){try{const result=await db.updateTable("couriers").set({status,updated_at:formatDate(new Date)}).where("id","=",id).returningAll().executeTakeFirst();if(!result)throw Error("Failed to update courier status");return asModelRow(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to update courier status: ${error.message}`);throw error}}export async function updateContact(id,phone){try{const updateData={updated_at:formatDate(new Date)};if(phone!==void 0)updateData.phone=phone;const result=await db.updateTable("couriers").set(updateData).where("id","=",id).returningAll().executeTakeFirst();if(!result)throw Error("Failed to update contact information");return asModelRow(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to update contact information: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";import{mutationCount}from"../../utils/mutation-count";export async function destroy(id){try{const result=await db.deleteFrom("delivery_routes").where("id","=",id).executeTakeFirst();return mutationCount(result)>0}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete delivery route: ${error.message}`);throw error}}export async function bulkDestroy(ids){if(!ids.length)return 0;try{const result=await db.deleteFrom("delivery_routes").where("id","in",ids).executeTakeFirst();return mutationCount(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete delivery routes: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";export async function fetchById(id){return await db.selectFrom("delivery_routes").where("id","=",id).selectAll().executeTakeFirst()}export async function fetchAll(){return await db.selectFrom("delivery_routes").selectAll().execute()}export async function fetchActive(){return await db.selectFrom("delivery_routes").where("last_active",">=",Date.now()-86400000).selectAll().execute()}export async function fetchByCourier(courier){return await db.selectFrom("delivery_routes").where("courier","=",courier).selectAll().execute()}
@@ -0,0 +1 @@
1
+ export{bulkDestroy,destroy}from"./destroy";export{fetchActive,fetchAll,fetchByCourier,fetchById}from"./fetch";export{store,updateLastActive}from"./store";export{update,updateMetrics,updateStops}from"./update";export{DeliveryRouteInputError,validateDeliveryRouteWrite}from"./validate-write";
@@ -0,0 +1 @@
1
+ import{randomUUIDv7}from"bun";import{db}from"@stacksjs/database/runtime";import{deliveryRouteWriteData}from"../write-data";import{fetchById}from"./fetch";import{DeliveryRouteInputError,validateDeliveryRouteWrite}from"./validate-write";export async function store(data){try{const uuid=randomUUIDv7(),columns=deliveryRouteWriteData(data),routeData={...await validateDeliveryRouteWrite(columns),uuid};await db.insertInto("delivery_routes").values(routeData).executeTakeFirst();const result=await db.selectFrom("delivery_routes").where("uuid","=",uuid).selectAll().executeTakeFirst();if(!result)throw Error("Failed to create delivery route");return result}catch(error){if(error instanceof DeliveryRouteInputError)throw error;if(error instanceof Error)throw TypeError(`Failed to create delivery route: ${error.message}`);throw error}}export async function updateLastActive(id){try{await db.updateTable("delivery_routes").set({last_active:Date.now(),updated_at:new Date().toISOString()}).where("id","=",id).execute();const result=await fetchById(id);if(!result)throw Error("Failed to update delivery route last active");return result}catch(error){if(error instanceof Error)throw TypeError(`Failed to update delivery route last active: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";import{formatDate}from"@stacksjs/orm";import{deliveryRouteWriteData}from"../write-data";import{DeliveryRouteInputError,validateDeliveryRouteWrite}from"./validate-write";export async function update(id,data){try{if(!id)throw Error("Delivery route ID is required for update");const current=await db.selectFrom("delivery_routes").where("id","=",id).selectAll().executeTakeFirst();if(!current)return;const input=deliveryRouteWriteData(data),validated=await validateDeliveryRouteWrite(input,current),result=await db.updateTable("delivery_routes").set({...validated,updated_at:formatDate(new Date)}).where("id","=",id).returningAll().executeTakeFirst();if(!result)return;return result}catch(error){if(error instanceof DeliveryRouteInputError)throw error;if(error instanceof Error)throw TypeError(`Failed to update delivery route: ${error.message}`);throw error}}export async function updateStops(id,stops){try{const result=await db.updateTable("delivery_routes").set({stops,updated_at:formatDate(new Date)}).where("id","=",id).returningAll().executeTakeFirst();if(!result)throw Error("Failed to update delivery route stops");return result}catch(error){if(error instanceof Error)throw TypeError(`Failed to update delivery route stops: ${error.message}`);throw error}}export async function updateMetrics(id,delivery_time,total_distance){try{const updateData={updated_at:formatDate(new Date)};if(delivery_time!==void 0)updateData.delivery_time=delivery_time;if(total_distance!==void 0)updateData.total_distance=total_distance;const result=await db.updateTable("delivery_routes").set(updateData).where("id","=",id).returningAll().executeTakeFirst();if(!result)throw Error("Failed to update delivery metrics");return result}catch(error){if(error instanceof Error)throw TypeError(`Failed to update delivery metrics: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";export class DeliveryRouteInputError extends TypeError{constructor(message){super(message);this.name="DeliveryRouteInputError"}}function numericValue(input,current,key){return Number(input[key]??current?.[key])}function timestampValue(input,current){const value=input.last_active??current?.last_active;if(value===void 0||value===null)return Date.now();if(value instanceof Date)return value.getTime();if(typeof value==="number")return value;if(typeof value==="string"&&/^\d{10,13}$/.test(value))return Number(value);return Number.NaN}export async function validateDeliveryRouteWrite(input,current){const courierId=numericValue(input,current,"courier_id"),stops=numericValue(input,current,"stops"),deliveryTime=numericValue(input,current,"delivery_time"),totalDistance=numericValue(input,current,"total_distance"),lastActive=timestampValue(input,current);if(!Number.isSafeInteger(courierId)||courierId<1)throw new DeliveryRouteInputError("Courier must be a positive integer.");if(!Number.isSafeInteger(stops)||stops<0)throw new DeliveryRouteInputError("Stops must be a non-negative integer.");if(!Number.isSafeInteger(deliveryTime)||deliveryTime<0)throw new DeliveryRouteInputError("Delivery time must be a non-negative integer.");if(!Number.isSafeInteger(totalDistance)||totalDistance<0)throw new DeliveryRouteInputError("Total distance must be a non-negative integer.");if(!Number.isSafeInteger(lastActive)||lastActive<0)throw new DeliveryRouteInputError("Last active must be a valid Unix timestamp.");const courier=await db.selectFrom("couriers").where("id","=",courierId).select(["id","name","vehicle_number"]).executeTakeFirst();if(!courier)throw new DeliveryRouteInputError(`Courier ${courierId} was not found.`);return{...input,courier_id:Number(courier.id),courier:courier.name,vehicle:courier.vehicle_number,last_active:lastActive}}
@@ -0,0 +1 @@
1
+ import{db,matchedRows}from"@stacksjs/database/runtime";import{mutationCount}from"../../utils/mutation-count";export async function destroy(id){try{const result=await db.deleteFrom("digital_deliveries").where("id","=",id).executeTakeFirst();return mutationCount(result)>0}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete digital delivery: ${error.message}`);throw error}}export async function softDelete(id){try{return(await matchedRows(db.updateTable("digital_deliveries").set({status:"inactive"}).where("id","=",id))).length>0}catch(error){if(error instanceof Error)throw TypeError(`Failed to soft delete digital delivery: ${error.message}`);throw error}}export async function bulkDestroy(ids){if(!ids.length)return 0;try{const result=await db.deleteFrom("digital_deliveries").where("id","in",ids).executeTakeFirst();return mutationCount(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete digital deliveries: ${error.message}`);throw error}}export async function bulkSoftDelete(ids){if(!ids.length)return 0;try{return(await matchedRows(db.updateTable("digital_deliveries").set({status:"inactive"}).where("id","in",ids))).length}catch(error){if(error instanceof Error)throw TypeError(`Failed to soft delete digital deliveries: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";import{asModelRow,asModelRows}from"../../utils/model-row";export async function fetchById(id){const row=await db.selectFrom("digital_deliveries").where("id","=",id).selectAll().executeTakeFirst();return asModelRow(row,!0)}export async function fetchAll(){return asModelRows(await db.selectFrom("digital_deliveries").selectAll().execute())}
@@ -0,0 +1 @@
1
+ export{bulkDestroy,bulkSoftDelete,destroy,softDelete}from"./destroy";export{fetchAll,fetchById}from"./fetch";export{bulkStore,store}from"./store";export{update,updateDeliverySettings,updateStatus}from"./update";
@@ -0,0 +1 @@
1
+ import{randomUUIDv7}from"bun";import{db}from"@stacksjs/database/runtime";import{asModelRow}from"../../utils/model-row";import{mutationCount}from"../../utils/mutation-count";import{digitalDeliveryWriteData}from"../write-data";export async function store(data){try{const uuid=randomUUIDv7(),deliveryData={...digitalDeliveryWriteData(data),uuid};await db.insertInto("digital_deliveries").values(deliveryData).executeTakeFirst();const digitalDelivery=await db.selectFrom("digital_deliveries").where("uuid","=",uuid).selectAll().executeTakeFirst();if(!digitalDelivery)throw Error("Failed to resolve created digital delivery");return asModelRow(digitalDelivery)}catch(error){if(error instanceof Error)throw TypeError(`Failed to create digital delivery: ${error.message}`);throw error}}export async function bulkStore(data){if(!data.length)return 0;try{const deliveryDataArray=data.map((item)=>({...digitalDeliveryWriteData(item),uuid:randomUUIDv7()})),result=await db.insertInto("digital_deliveries").values(deliveryDataArray).executeTakeFirst();return mutationCount(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to create digital deliveries in bulk: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";import{asModelRow}from"../../utils/model-row";import{formatDate}from"@stacksjs/orm";import{digitalDeliveryWriteData}from"../write-data";export async function update(id,data){try{if(!id)throw Error("Digital delivery ID is required for update");const result=await db.updateTable("digital_deliveries").set({...digitalDeliveryWriteData(data),updated_at:formatDate(new Date)}).where("id","=",id).returningAll().executeTakeFirst();if(!result)return;return asModelRow(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to update digital delivery: ${error.message}`);throw error}}export async function updateStatus(id,status){try{const result=await db.updateTable("digital_deliveries").set({status,updated_at:formatDate(new Date)}).where("id","=",id).returningAll().executeTakeFirst();if(!result)throw Error("Failed to update digital delivery status");return asModelRow(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to update digital delivery status: ${error.message}`);throw error}}export async function updateDeliverySettings(id,download_limit,expiry_days,requires_login,automatic_delivery){try{const updateData={updated_at:formatDate(new Date)};if(download_limit!==void 0)updateData.download_limit=download_limit;if(expiry_days!==void 0)updateData.expiry_days=expiry_days;if(requires_login!==void 0)updateData.requires_login=requires_login;if(automatic_delivery!==void 0)updateData.automatic_delivery=automatic_delivery;const result=await db.updateTable("digital_deliveries").set(updateData).where("id","=",id).returningAll().executeTakeFirst();if(!result)throw Error("Failed to update delivery settings");return asModelRow(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to update delivery settings: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{distanceInMeters}from"../tracking/geo";import{nominatimDriver}from"./nominatim";import{formatQuery,PRECISION_RANK}from"./types";export{nominatimDriver}from"./nominatim";export{formatQuery,PRECISION_RANK}from"./types";let configured=null;export function setGeocodingDriver(driver){configured=driver}export function geocodingDriver(){return configured??nominatimDriver()}export async function geocode(query,options={}){const driver=options.driver??geocodingDriver(),minimum=options.minimumPrecision??"street",key=`geocode:${driver.name}:${formatQuery(query).toLowerCase()}`,cached=await readCache(key);if(cached!==void 0)return cached;const result=await driver.geocode(query),usable=result&&PRECISION_RANK[result.precision]>=PRECISION_RANK[minimum]?result:null;await writeCache(key,usable,usable?options.cacheSeconds??2592000:600);return usable}export function checkCoverage(destination,origins,radiusMeters){if(origins.length===0)return null;let nearest=origins[0],shortest=distanceInMeters(destination,nearest);for(const origin of origins.slice(1)){const distance=distanceInMeters(destination,origin);if(distance<shortest){shortest=distance;nearest=origin}}return{covered:shortest<=radiusMeters,distanceMeters:Math.round(shortest),nearest}}async function readCache(key){try{const cache=(await import("@stacksjs/cache").catch(()=>null))?.cache;if(!cache)return;const raw=await cache.get(key);if(raw===null||raw===void 0)return;return raw==="null"?null:JSON.parse(String(raw))}catch{return}}async function writeCache(key,value,seconds){try{const cache=(await import("@stacksjs/cache").catch(()=>null))?.cache;if(!cache)return;await cache.set(key,value===null?"null":JSON.stringify(value),seconds)}catch{}}
@@ -0,0 +1 @@
1
+ import{formatQuery}from"./types";const ENDPOINT="https://nominatim.openstreetmap.org/search",MIN_REQUEST_INTERVAL_MS=1000;let lastRequestAt=0,queue=Promise.resolve();async function rateLimited(work){const run=queue.then(async()=>{const since=Date.now()-lastRequestAt;if(since<MIN_REQUEST_INTERVAL_MS)await Bun.sleep(MIN_REQUEST_INTERVAL_MS-since);lastRequestAt=Date.now();return work()});queue=run.then(()=>{return},()=>{return});return run}function precisionFrom(entry){const address=entry.address??{},addressType=String(entry.addresstype??""),type=String(entry.type??""),category=String(entry.category??entry.class??"");if(address.house_number||type==="house"||type==="building"||addressType==="building")return"rooftop";if(address.road||category==="highway"||addressType==="road")return"street";if(addressType==="postcode"||address.postcode&&!address.road)return"postal";if(["city","town","village","suburb","neighbourhood"].includes(addressType))return"locality";return"unknown"}export function nominatimDriver(options={}){const userAgent=options.userAgent??`${process.env.APP_NAME??"Stacks"} (${process.env.APP_URL??"stacks-app"})`,endpoint=options.endpoint??ENDPOINT;return{name:"nominatim",async geocode(query){const search=formatQuery(query);if(!search)return null;const url=new URL(endpoint);url.searchParams.set("q",search);url.searchParams.set("format","jsonv2");url.searchParams.set("limit","1");url.searchParams.set("addressdetails","1");if(query.country)url.searchParams.set("countrycodes",query.country.toLowerCase());const response=await rateLimited(()=>fetch(url,{headers:{"user-agent":userAgent,accept:"application/json"},signal:AbortSignal.timeout(8000)}));if(!response.ok)throw Error(`Nominatim responded ${response.status}`);const entry=(await response.json())?.[0];if(!entry)return null;const latitude=Number(entry.lat),longitude=Number(entry.lon);if(!Number.isFinite(latitude)||!Number.isFinite(longitude))return null;const precision=precisionFrom(entry);return{latitude,longitude,formatted:String(entry.display_name??search),confidence:{rooftop:1,street:0.8,postal:0.5,locality:0.3,unknown:0.1}[precision],precision,provider:"nominatim"}}}}
@@ -0,0 +1 @@
1
+ export const PRECISION_RANK={unknown:0,locality:1,postal:2,street:3,rooftop:4};export function formatQuery(query){return[query.street,query.city,query.region,query.postalCode,query.country].map((part)=>String(part??"").trim()).filter(Boolean).join(", ")}
@@ -0,0 +1 @@
1
+ import*as routes from"./delivery-routes";import*as digital from"./digital-deliveries";import*as couriers from"./couriers";import*as geocoding from"./geocoding";import*as licenses from"./license-keys";import*as methods from"./shipping-methods";import*as rates from"./shipping-rates";import*as tracking from"./tracking";import*as zones from"./shipping-zones";export{digital,couriers,geocoding,licenses,methods,rates,routes,tracking,zones};const shippings={digital,couriers,geocoding,licenses,methods,rates,routes,tracking,zones};export default shippings;
@@ -0,0 +1 @@
1
+ import{db,matchedRows}from"@stacksjs/database/runtime";import{mutationCount}from"../../utils/mutation-count";export async function destroy(id){try{const result=await db.deleteFrom("license_keys").where("id","=",id).executeTakeFirst();return mutationCount(result)>0}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete license key: ${error.message}`);throw error}}export async function softDelete(id){try{return(await matchedRows(db.updateTable("license_keys").set({status:"inactive"}).where("id","=",id))).length>0}catch(error){if(error instanceof Error)throw TypeError(`Failed to soft delete license key: ${error.message}`);throw error}}export async function bulkDestroy(ids){if(!ids.length)return 0;try{const result=await db.deleteFrom("license_keys").where("id","in",ids).executeTakeFirst();return mutationCount(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete license keys: ${error.message}`);throw error}}export async function bulkSoftDelete(ids){if(!ids.length)return 0;try{return(await matchedRows(db.updateTable("license_keys").set({status:"inactive"}).where("id","in",ids))).length}catch(error){if(error instanceof Error)throw TypeError(`Failed to soft delete license keys: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";import{asModelRow,asModelRows}from"../../utils/model-row";export async function fetchById(id){const row=await db.selectFrom("license_keys").where("id","=",id).selectAll().executeTakeFirst();return asModelRow(row,!0)}export async function fetchAll(){return asModelRows(await db.selectFrom("license_keys").selectAll().execute())}
@@ -0,0 +1 @@
1
+ export{bulkDestroy,bulkSoftDelete,destroy,softDelete}from"./destroy";export{fetchAll,fetchById}from"./fetch";export{bulkStore,store}from"./store";export{update,updateExpiration,updateStatus}from"./update";export{LicenseKeyInputError,validateLicenseKeyWrite}from"./validate-write";
@@ -0,0 +1 @@
1
+ import{randomUUIDv7}from"bun";import{db}from"@stacksjs/database/runtime";import{asModelRow}from"../../utils/model-row";import{mutationCount}from"../../utils/mutation-count";import{licenseKeyWriteData}from"../write-data";import{LicenseKeyInputError,validateLicenseKeyWrite}from"./validate-write";export async function store(data){try{const uuid=randomUUIDv7(),input=licenseKeyWriteData(data),licenseData={...await validateLicenseKeyWrite(input),uuid};await db.insertInto("license_keys").values(licenseData).executeTakeFirst();const licenseKey=await db.selectFrom("license_keys").where("uuid","=",uuid).selectAll().executeTakeFirst();if(!licenseKey)throw Error("Failed to resolve created license key");return asModelRow(licenseKey)}catch(error){if(error instanceof LicenseKeyInputError)throw error;if(error instanceof Error){if(error.message.includes("UNIQUE constraint failed: license_keys.key"))throw new LicenseKeyInputError("A license key with this value already exists.");throw TypeError(`Failed to create license key: ${error.message}`)}throw error}}export async function bulkStore(data){if(!data.length)return 0;try{const licenseDataArray=await Promise.all(data.map(async(item)=>({...await validateLicenseKeyWrite(licenseKeyWriteData(item)),uuid:randomUUIDv7()}))),result=await db.insertInto("license_keys").values(licenseDataArray).executeTakeFirst();return mutationCount(result)}catch(error){if(error instanceof LicenseKeyInputError)throw error;if(error instanceof Error)throw TypeError(`Failed to create license keys in bulk: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";import{asModelRow}from"../../utils/model-row";import{formatDate}from"@stacksjs/orm";import{licenseKeyWriteData}from"../write-data";import{LicenseKeyInputError,validateLicenseKeyWrite}from"./validate-write";export async function update(id,data){try{if(!id)throw Error("License key ID is required for update");const current=await db.selectFrom("license_keys").where("id","=",id).selectAll().executeTakeFirst();if(!current)return;const input=licenseKeyWriteData(data),validated=await validateLicenseKeyWrite(input,current),result=await db.updateTable("license_keys").set({...validated,updated_at:formatDate(new Date)}).where("id","=",id).returningAll().executeTakeFirst();if(!result)return;return asModelRow(result)}catch(error){if(error instanceof LicenseKeyInputError)throw error;if(error instanceof Error){if(error.message.includes("UNIQUE constraint failed: license_keys.key"))throw new LicenseKeyInputError("A license key with this value already exists.");throw TypeError(`Failed to update license key: ${error.message}`)}throw error}}export async function updateStatus(id,status){try{const result=await db.updateTable("license_keys").set({status,updated_at:formatDate(new Date)}).where("id","=",id).returningAll().executeTakeFirst();if(!result)throw Error("Failed to update license key status");return asModelRow(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to update license key status: ${error.message}`);throw error}}export async function updateExpiration(id,expiryDate){try{const updateData={updated_at:formatDate(new Date)};if(expiryDate!==void 0)updateData.expiry_date=expiryDate;const result=await db.updateTable("license_keys").set(updateData).where("id","=",id).returningAll().executeTakeFirst();if(!result)throw Error("Failed to update expiration information");return asModelRow(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to update expiration information: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";const templates=["Standard License","Premium License","Enterprise License"],statuses=["active","inactive","unassigned"];export class LicenseKeyInputError extends TypeError{constructor(message){super(message);this.name="LicenseKeyInputError"}}function mergedValue(input,current,key){return input[key]!==void 0?input[key]:current?.[key]}function optionalId(input,current,key,label){const value=mergedValue(input,current,key);if(value===void 0||value===null||value==="")return null;const id=Number(value);if(!Number.isSafeInteger(id)||id<1)throw new LicenseKeyInputError(`${label} must be a positive integer or null.`);return id}function expiryTimestamp(value){if(typeof value==="number")return value>2147483647?Math.floor(value/1000):value;if(typeof value==="string"&&/^\d{10,13}$/.test(value))return value.length===13?Math.floor(Number(value)/1000):Number(value);if(typeof value==="string"){const normalized=/^\d{4}-\d{2}-\d{2} \d/.test(value)?`${value.replace(" ","T")}Z`:value;return Math.floor(new Date(normalized).getTime()/1000)}return Number.NaN}export async function validateLicenseKeyWrite(input,current){const key=String(mergedValue(input,current,"key")||"").trim().toUpperCase(),template=String(mergedValue(input,current,"template")||""),status=String(mergedValue(input,current,"status")||""),expiryDate=expiryTimestamp(mergedValue(input,current,"expiry_date")),customerId=optionalId(input,current,"customer_id","Customer"),productId=optionalId(input,current,"product_id","Product"),orderId=optionalId(input,current,"order_id","Order");if(!/^[A-Z0-9]{4}(?:-[A-Z0-9]{4}){4}$/.test(key))throw new LicenseKeyInputError("License key must use five groups of four uppercase letters or numbers.");if(!templates.includes(template))throw new LicenseKeyInputError(`Template must be one of: ${templates.join(", ")}.`);if(!statuses.includes(status))throw new LicenseKeyInputError(`Status must be one of: ${statuses.join(", ")}.`);if(!Number.isSafeInteger(expiryDate)||expiryDate<0||expiryDate>2147483647)throw new LicenseKeyInputError("Expiry date must be a Unix timestamp between 1970 and 2038.");const[customer,product,order]=await Promise.all([customerId?db.selectFrom("customers").where("id","=",customerId).select("id").executeTakeFirst():null,productId?db.selectFrom("products").where("id","=",productId).select("id").executeTakeFirst():null,orderId?db.selectFrom("orders").where("id","=",orderId).select(["id","customer_id"]).executeTakeFirst():null]);if(customerId&&!customer)throw new LicenseKeyInputError(`Customer ${customerId} was not found.`);if(productId&&!product)throw new LicenseKeyInputError(`Product ${productId} was not found.`);if(orderId&&!order)throw new LicenseKeyInputError(`Order ${orderId} was not found.`);if(customerId&&order?.customer_id&&Number(order.customer_id)!==customerId)throw new LicenseKeyInputError(`Order ${orderId} belongs to Customer ${order.customer_id}, not Customer ${customerId}.`);return{...input,key,template,expiry_date:expiryDate,status,customer_id:customerId,product_id:productId,order_id:orderId}}
@@ -0,0 +1 @@
1
+ import{db,matchedRows}from"@stacksjs/database/runtime";import{mutationCount}from"../../utils/mutation-count";export async function destroy(id){try{const result=await db.deleteFrom("shipping_methods").where("id","=",id).executeTakeFirst();return mutationCount(result)>0}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete shipping method: ${error.message}`);throw error}}export async function softDelete(id){try{return(await matchedRows(db.updateTable("shipping_methods").set({status:"inactive"}).where("id","=",id))).length>0}catch(error){if(error instanceof Error)throw TypeError(`Failed to soft delete shipping method: ${error.message}`);throw error}}export async function bulkDestroy(ids){if(!ids.length)return 0;try{const result=await db.deleteFrom("shipping_methods").where("id","in",ids).executeTakeFirst();return mutationCount(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete shipping methods: ${error.message}`);throw error}}export async function bulkSoftDelete(ids){if(!ids.length)return 0;try{return(await matchedRows(db.updateTable("shipping_methods").set({status:"inactive"}).where("id","in",ids))).length}catch(error){if(error instanceof Error)throw TypeError(`Failed to soft delete shipping methods: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";export async function fetchById(id){const model=await db.selectFrom("shipping_methods").where("id","=",id).selectAll().executeTakeFirst();if(model){const shippingZones=await db.selectFrom("shipping_zones").where("shipping_method_id","=",id).selectAll().execute();return{...model,shippingZones}}return}export async function fetchAll(){const models=await db.selectFrom("shipping_methods").selectAll().execute();if(models.length===0)return[];const shippingMethodIds=[...new Set(models.map((model)=>model.id))],shippingZonesByMethodId=(await db.selectFrom("shipping_zones").where("shipping_method_id","in",shippingMethodIds).selectAll().execute()).reduce((acc,zone)=>{const methodId=zone.shipping_method_id;if(methodId!==null&&methodId!==void 0){if(!acc[methodId])acc[methodId]=[];acc[methodId].push(zone)}return acc},{});return models.map((model)=>({...model,shipping_zones:shippingZonesByMethodId[model.id]||[]}))}
@@ -0,0 +1 @@
1
+ export{bulkDestroy,bulkSoftDelete,destroy,softDelete}from"./destroy";export{fetchAll,fetchById}from"./fetch";export{bulkStore,formatShippingOptions,getActiveShippingMethods,store}from"./store";export{update,updatePricing,updateStatus}from"./update";
@@ -0,0 +1 @@
1
+ import{randomUUIDv7}from"bun";import{db}from"@stacksjs/database/runtime";import{asModelRow,asModelRows}from"../../utils/model-row";import{mutationCount}from"../../utils/mutation-count";import{shippingMethodWriteData}from"../write-data";export async function store(data){try{const uuid=randomUUIDv7(),shippingData={...shippingMethodWriteData(data),uuid};await db.insertInto("shipping_methods").values(shippingData).executeTakeFirst();const model=await db.selectFrom("shipping_methods").where("uuid","=",uuid).selectAll().executeTakeFirst();if(!model)throw Error("Failed to resolve created shipping method");return asModelRow(model)}catch(error){if(error instanceof Error)throw TypeError(`Failed to create shipping method: ${error.message}`);throw error}}export async function bulkStore(data){if(!data.length)return 0;try{const shippingDataArray=data.map((item)=>({...shippingMethodWriteData(item),uuid:randomUUIDv7()})),result=await db.insertInto("shipping_methods").values(shippingDataArray).executeTakeFirst();return mutationCount(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to create shipping methods in bulk: ${error.message}`);throw error}}export function formatShippingOptions(){try{return db.selectFrom("shipping_methods").select(["id","name","status","base_rate"]).orderBy("name").execute()}catch(error){if(error instanceof Error)throw TypeError(`Failed to format shipping options: ${error.message}`);throw error}}export async function getActiveShippingMethods(){try{const rows=await db.selectFrom("shipping_methods").selectAll().where("status","=","active").orderBy("name").execute();return asModelRows(rows)}catch(error){if(error instanceof Error)throw TypeError(`Failed to get active shipping methods: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";import{asModelRow}from"../../utils/model-row";import{formatDate}from"@stacksjs/orm";import{shippingMethodWriteData}from"../write-data";export async function update(id,data){try{if(!id)throw Error("Shipping method ID is required for update");const result=await db.updateTable("shipping_methods").set({...shippingMethodWriteData(data),updated_at:formatDate(new Date)}).where("id","=",id).returningAll().executeTakeFirst();if(!result)return;return asModelRow(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to update shipping method: ${error.message}`);throw error}}export async function updateStatus(id,status){try{const result=await db.updateTable("shipping_methods").set({status,updated_at:formatDate(new Date)}).where("id","=",id).returningAll().executeTakeFirst();if(!result)throw Error("Failed to update shipping method status");return asModelRow(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to update shipping method status: ${error.message}`);throw error}}export async function updatePricing(id,base_rate,free_shipping){try{const updateData={updated_at:formatDate(new Date)};if(base_rate!==void 0)updateData.base_rate=base_rate;if(free_shipping!==void 0)updateData.free_shipping=free_shipping;const result=await db.updateTable("shipping_methods").set(updateData).where("id","=",id).returningAll().executeTakeFirst();if(!result)throw Error("Failed to update pricing information");return asModelRow(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to update pricing information: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";import{mutationCount}from"../../utils/mutation-count";export async function destroy(id){try{const result=await db.deleteFrom("shipping_rates").where("id","=",id).executeTakeFirst();return mutationCount(result)>0}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete shipping rate: ${error.message}`);throw error}}export async function bulkDestroy(ids){if(!ids.length)return 0;try{const result=await db.deleteFrom("shipping_rates").where("id","in",ids).executeTakeFirst();return mutationCount(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete shipping rates: ${error.message}`);throw error}}export async function destroyByZone(zone){try{const result=await db.deleteFrom("shipping_rates").where("shipping_zone_id","=",zone).executeTakeFirst();return mutationCount(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete shipping rates by zone: ${error.message}`);throw error}}export async function destroyByMethod(method){try{const result=await db.deleteFrom("shipping_rates").where("shipping_method_id","=",method).executeTakeFirst();return mutationCount(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete shipping rates by method: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";import{asModelRow,asModelRows}from"../../utils/model-row";export async function fetchById(id){const model=await db.selectFrom("shipping_rates").where("id","=",id).selectAll().executeTakeFirst();if(model){let shippingZone=null,shippingMethod=null;if(model.shipping_zone_id)shippingZone=await db.selectFrom("shipping_zones").where("id","=",model.shipping_zone_id).selectAll().executeTakeFirst();if(model.shipping_method_id)shippingMethod=await db.selectFrom("shipping_methods").where("id","=",model.shipping_method_id).selectAll().executeTakeFirst();return{...model,shipping_zone:shippingZone,shipping_method:shippingMethod}}return}export async function fetchAll(){const models=await db.selectFrom("shipping_rates").selectAll().execute();if(models.length===0)return[];const shippingZoneIds=[...new Set(models.map((model)=>model.shipping_zone_id).filter((id)=>id!==null&&id!==void 0))],shippingMethodIds=[...new Set(models.map((model)=>model.shipping_method_id).filter((id)=>id!==null&&id!==void 0))],[allShippingZones,allShippingMethods]=await Promise.all([shippingZoneIds.length?db.selectFrom("shipping_zones").where("id","in",shippingZoneIds).selectAll().execute():[],shippingMethodIds.length?db.selectFrom("shipping_methods").where("id","in",shippingMethodIds).selectAll().execute():[]]),shippingZonesById=allShippingZones.reduce((acc,zone)=>{acc[zone.id]=zone;return acc},{}),shippingMethodsById=allShippingMethods.reduce((acc,method)=>{acc[method.id]=method;return acc},{});return models.map((model)=>({...model,shipping_zone:model.shipping_zone_id?shippingZonesById[model.shipping_zone_id]:[],shipping_method:model.shipping_method_id?shippingMethodsById[model.shipping_method_id]:[]}))}export async function getRatesByZone(zoneId){try{const rates=await db.selectFrom("shipping_rates").selectAll().where("shipping_zone_id","=",zoneId).orderBy("weight_from").execute();return asModelRows(rates)}catch(error){if(error instanceof Error)throw TypeError(`Failed to get shipping rates by zone: ${error.message}`);throw error}}export async function getRateByWeightAndZone(weight,zoneId){try{const rate=await db.selectFrom("shipping_rates").selectAll().where("shipping_zone_id","=",zoneId).where("weight_from","<=",weight).where("weight_to",">=",weight).executeTakeFirst();return asModelRow(rate,!0)}catch(error){if(error instanceof Error)throw TypeError(`Failed to get shipping rate by weight and zone: ${error.message}`);throw error}}export async function resolveZoneForAddress(address){if(!address?.countryCode)return null;try{const zones=await db.selectFrom("shipping_zones").selectAll().where("countries","like",`%${address.countryCode}%`).where("status","=","active").execute();if(zones.length===0)return null;let best=null;for(const zone of zones){let score=1;if(address.region&&zone.regions&&includesToken(zone.regions,address.region))score=2;if(address.postalCode&&zone.postal_codes&&includesToken(zone.postal_codes,address.postalCode))score=3;if(!best||score>best.score)best={id:zone.id,score}}return best?.id??null}catch{return null}}export async function getRateByWeightAndAddress(weight,address){const zoneId=await resolveZoneForAddress(address);if(zoneId==null)return;return getRateByWeightAndZone(weight,zoneId)}export async function validateZoneMatchesAddress(zoneId,address){return await resolveZoneForAddress(address)===zoneId}function includesToken(haystack,needle){if(!haystack||!needle)return!1;const target=needle.trim().toUpperCase();for(const token of haystack.split(/[,|;]/))if(token.trim().toUpperCase()===target)return!0;return!1}export async function formatShippingRateOptions(){try{return(await db.selectFrom("shipping_rates").select(["id","shipping_method_id","shipping_zone_id","rate"]).orderBy("shipping_method_id").execute()).filter((result)=>result.shipping_method_id!==null&&result.shipping_method_id!==void 0&&result.shipping_zone_id!==null&&result.shipping_zone_id!==void 0)}catch(error){if(error instanceof Error)throw TypeError(`Failed to format shipping rate options: ${error.message}`);throw error}}export async function getShippingRatesByMethod(methodId){try{const rates=await db.selectFrom("shipping_rates").selectAll().where("shipping_method_id","=",methodId).orderBy("weight_from").execute();return asModelRows(rates)}catch(error){if(error instanceof Error)throw TypeError(`Failed to get shipping rates by method: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ export{bulkDestroy,destroy,destroyByMethod,destroyByZone}from"./destroy";export{fetchAll,fetchById,formatShippingRateOptions,getRateByWeightAndAddress,getRateByWeightAndZone,getRatesByZone,getShippingRatesByMethod,resolveZoneForAddress,validateZoneMatchesAddress}from"./fetch";export{bulkStore,store}from"./store";export{bulkUpdate,update,updateByMethod,updateByZone}from"./update";export{ShippingRateInputError,validateShippingRateWrite}from"./validate-write";
@@ -0,0 +1 @@
1
+ import{randomUUIDv7}from"bun";import{db}from"@stacksjs/database/runtime";import{asModelRow}from"../../utils/model-row";import{mutationCount}from"../../utils/mutation-count";import{shippingRateWriteData}from"../write-data";import{ShippingRateInputError,validateShippingRateWrite}from"./validate-write";export async function store(data){try{const input=shippingRateWriteData(data),methodId=Number(input.shipping_method_id),zoneId=Number(input.shipping_zone_id);await validateShippingRateWrite(input);const uuid=randomUUIDv7(),rateData={...input,shipping_method_id:methodId,shipping_zone_id:zoneId,uuid};await db.insertInto("shipping_rates").values(rateData).executeTakeFirst();const model=await db.selectFrom("shipping_rates").where("uuid","=",uuid).selectAll().executeTakeFirst();if(!model)throw Error("Failed to resolve created shipping rate");return asModelRow(model)}catch(error){if(error instanceof ShippingRateInputError)throw error;if(error instanceof Error)throw TypeError(`Failed to create shipping rate: ${error.message}`);throw error}}export async function bulkStore(data){if(!data.length)return 0;try{for(const item of data){const input=shippingRateWriteData(item);await validateShippingRateWrite(input)}const rateDataArray=data.map((item)=>({...shippingRateWriteData(item),uuid:randomUUIDv7()})),result=await db.insertInto("shipping_rates").values(rateDataArray).executeTakeFirst();return mutationCount(result)}catch(error){if(error instanceof ShippingRateInputError)throw error;if(error instanceof Error)throw TypeError(`Failed to create shipping rates in bulk: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db,matchedRows}from"@stacksjs/database/runtime";import{asModelRow}from"../../utils/model-row";import{formatDate}from"@stacksjs/orm";import{shippingRateWriteData}from"../write-data";import{ShippingRateInputError,validateShippingRateWrite}from"./validate-write";export async function update(id,data){try{if(!id)throw Error("Shipping rate ID is required for update");const current=await db.selectFrom("shipping_rates").where("id","=",id).selectAll().executeTakeFirst();if(!current)return;const input=shippingRateWriteData(data);await validateShippingRateWrite(input,current);const result=await db.updateTable("shipping_rates").set({...input,updated_at:formatDate(new Date)}).where("id","=",id).returningAll().executeTakeFirst();if(!result)return;return asModelRow(result)}catch(error){if(error instanceof ShippingRateInputError)throw error;if(error instanceof Error)throw TypeError(`Failed to update shipping rate: ${error.message}`);throw error}}export async function bulkUpdate(updates){if(!updates.length)return 0;try{let updatedCount=0;for(const{id,data}of updates)if((await matchedRows(db.updateTable("shipping_rates").set({...shippingRateWriteData(data),updated_at:formatDate(new Date)}).where("id","=",id))).length>0)updatedCount++;return updatedCount}catch(error){if(error instanceof Error)throw TypeError(`Failed to update shipping rates in bulk: ${error.message}`);throw error}}export async function updateByZone(zone,data){try{return(await matchedRows(db.updateTable("shipping_rates").set({...shippingRateWriteData(data),updated_at:formatDate(new Date)}).where("shipping_zone_id","=",zone))).length}catch(error){if(error instanceof Error)throw TypeError(`Failed to update shipping rates by zone: ${error.message}`);throw error}}export async function updateByMethod(method,data){try{return(await matchedRows(db.updateTable("shipping_rates").set({...shippingRateWriteData(data),updated_at:formatDate(new Date)}).where("shipping_method_id","=",method))).length}catch(error){if(error instanceof Error)throw TypeError(`Failed to update shipping rates by method: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{fetchById as fetchShippingMethodById}from"../shipping-methods/fetch";import{fetchById as fetchShippingZoneById}from"../shipping-zones/fetch";export class ShippingRateInputError extends TypeError{constructor(message){super(message);this.name="ShippingRateInputError"}}function numericValue(input,current,key){return Number(input[key]??current?.[key])}export async function validateShippingRateWrite(input,current){const methodId=numericValue(input,current,"shipping_method_id"),zoneId=numericValue(input,current,"shipping_zone_id"),weightFrom=numericValue(input,current,"weight_from"),weightTo=numericValue(input,current,"weight_to"),rate=numericValue(input,current,"rate");if(!Number.isSafeInteger(methodId)||methodId<1)throw new ShippingRateInputError("Shipping method must be a positive integer.");if(!Number.isSafeInteger(zoneId)||zoneId<1)throw new ShippingRateInputError("Shipping zone must be a positive integer.");if(!Number.isFinite(weightFrom)||weightFrom<0)throw new ShippingRateInputError("Weight from must be a non-negative number.");if(!Number.isFinite(weightTo)||weightTo<weightFrom)throw new ShippingRateInputError("Weight to must be greater than or equal to weight from.");if(!Number.isSafeInteger(rate)||rate<0)throw new ShippingRateInputError("Rate must be a non-negative integer in minor currency units.");const[method,zone]=await Promise.all([fetchShippingMethodById(methodId),fetchShippingZoneById(zoneId)]);if(!method)throw new ShippingRateInputError(`Shipping method ${methodId} was not found.`);if(!zone)throw new ShippingRateInputError(`Shipping zone ${zoneId} was not found.`);if(Number(zone.shipping_method_id)!==methodId)throw new ShippingRateInputError(`Shipping zone ${zoneId} is not assigned to shipping method ${methodId}.`)}
@@ -0,0 +1 @@
1
+ import{db,matchedRows}from"@stacksjs/database/runtime";import{mutationCount}from"../../utils/mutation-count";export async function destroy(id){try{const result=await db.deleteFrom("shipping_zones").where("id","=",id).executeTakeFirst();return mutationCount(result)>0}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete shipping zone: ${error.message}`);throw error}}export async function softDelete(id){try{return(await matchedRows(db.updateTable("shipping_zones").set({status:"inactive"}).where("id","=",id))).length>0}catch(error){if(error instanceof Error)throw TypeError(`Failed to soft delete shipping zone: ${error.message}`);throw error}}export async function bulkDestroy(ids){if(!ids.length)return 0;try{const result=await db.deleteFrom("shipping_zones").where("id","in",ids).executeTakeFirst();return mutationCount(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete shipping zones: ${error.message}`);throw error}}export async function bulkSoftDelete(ids){if(!ids.length)return 0;try{return(await matchedRows(db.updateTable("shipping_zones").set({status:"inactive"}).where("id","in",ids))).length}catch(error){if(error instanceof Error)throw TypeError(`Failed to soft delete shipping zones: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";import{asModelRow,asModelRows}from"../../utils/model-row";export async function fetchAll(){return asModelRows(await db.selectFrom("shipping_zones").selectAll().execute())}export async function fetchById(id){const row=await db.selectFrom("shipping_zones").where("id","=",id).selectAll().executeTakeFirst();return asModelRow(row,!0)}export function formatZoneOptions(){try{return db.selectFrom("shipping_zones").select(["id","name","status","countries"]).orderBy("name").execute()}catch(error){if(error instanceof Error)throw TypeError(`Failed to format shipping zone options: ${error.message}`);throw error}}export async function getActiveShippingZones(){try{const activeZones=await db.selectFrom("shipping_zones").selectAll().where("status","=","active").orderBy("name").execute();return asModelRows(activeZones)}catch(error){if(error instanceof Error)throw TypeError(`Failed to get active shipping zones: ${error.message}`);throw error}}export async function getZonesByCountry(countryCode){try{const zones=await db.selectFrom("shipping_zones").selectAll().where("countries","like",`%${countryCode}%`).where("status","=","active").orderBy("name").execute();return asModelRows(zones)}catch(error){if(error instanceof Error)throw TypeError(`Failed to get shipping zones by country: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ export{bulkDestroy,bulkSoftDelete,destroy,softDelete}from"./destroy";export{fetchAll,fetchById,formatZoneOptions,getActiveShippingZones,getZonesByCountry}from"./fetch";export{bulkStore,store}from"./store";export{update,updateCountries,updateRegionsAndPostalCodes,updateStatus}from"./update";
@@ -0,0 +1 @@
1
+ import{randomUUIDv7}from"bun";import{db}from"@stacksjs/database/runtime";import{asModelRow}from"../../utils/model-row";import{mutationCount}from"../../utils/mutation-count";import{shippingZoneWriteData}from"../write-data";export async function store(data){try{const uuid=randomUUIDv7(),zoneData={...shippingZoneWriteData(data),uuid};await db.insertInto("shipping_zones").values(zoneData).executeTakeFirst();const model=await db.selectFrom("shipping_zones").where("uuid","=",uuid).selectAll().executeTakeFirst();if(!model)throw Error("Failed to resolve created shipping zone");return asModelRow(model)}catch(error){if(error instanceof Error)throw TypeError(`Failed to create shipping zone: ${error.message}`);throw error}}export async function bulkStore(data){if(!data.length)return 0;try{const zoneDataArray=data.map((item)=>({...shippingZoneWriteData(item),uuid:randomUUIDv7()})),result=await db.insertInto("shipping_zones").values(zoneDataArray).executeTakeFirst();return mutationCount(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to create shipping zones in bulk: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";import{asModelRow}from"../../utils/model-row";import{formatDate}from"@stacksjs/orm";import{shippingZoneWriteData}from"../write-data";export async function update(id,data){try{if(!id)throw Error("Shipping zone ID is required for update");const result=await db.updateTable("shipping_zones").set({...shippingZoneWriteData(data),updated_at:formatDate(new Date)}).where("id","=",id).returningAll().executeTakeFirst();if(!result)return;return asModelRow(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to update shipping zone: ${error.message}`);throw error}}export async function updateStatus(id,status){try{const result=await db.updateTable("shipping_zones").set({status,updated_at:formatDate(new Date)}).where("id","=",id).returningAll().executeTakeFirst();if(!result)throw Error("Failed to update shipping zone status");return asModelRow(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to update shipping zone status: ${error.message}`);throw error}}export async function updateCountries(id,countries){try{const result=await db.updateTable("shipping_zones").set({countries,updated_at:formatDate(new Date)}).where("id","=",id).returningAll().executeTakeFirst();if(!result)throw Error("Failed to update shipping zone countries");return asModelRow(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to update shipping zone countries: ${error.message}`);throw error}}export async function updateRegionsAndPostalCodes(id,regions,postal_codes){try{const updateData={updated_at:formatDate(new Date)};if(regions!==void 0)updateData.regions=regions;if(postal_codes!==void 0)updateData.postal_codes=postal_codes;const result=await db.updateTable("shipping_zones").set(updateData).where("id","=",id).returningAll().executeTakeFirst();if(!result)throw Error("Failed to update regions and postal codes");return asModelRow(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to update regions and postal codes: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ export function orderTrackingChannel(orderId){return`order.${orderId}`}export function routeTrackingChannel(routeId){return`delivery-route.${routeId}`}async function emitEvent(eventName,payload){try{const mod=await import("@stacksjs/events").catch(()=>null);if(!mod)return;const dispatch=mod.dispatch;if(typeof dispatch!=="function")return;dispatch(eventName,payload)}catch{}}export async function broadcastToChannel(channelName,event,payload){try{const mod=await import("@stacksjs/realtime").catch(()=>null);if(!mod)return;const channel=mod.channel;if(typeof channel!=="function")return;await channel(channelName).broadcast(event,payload,"private")}catch{}}export async function emitDeliveryPosition(orderIds,routeId,payload){await Promise.all([...orderIds.map((orderId)=>broadcastToChannel(orderTrackingChannel(orderId),"delivery:position",payload)),routeId==null?Promise.resolve():broadcastToChannel(routeTrackingChannel(routeId),"delivery:position",payload)])}export async function emitDeliveryAssigned(stop){await emitEvent("delivery:assigned",{stop});if(stop.order_id!=null)await broadcastToChannel(orderTrackingChannel(stop.order_id),"delivery:assigned",{stop})}export async function emitDeliveryStarted(stop){await emitEvent("delivery:started",{stop});if(stop.order_id!=null)await broadcastToChannel(orderTrackingChannel(stop.order_id),"delivery:started",{stop})}export async function emitDeliveryNearby(stop,distanceMeters,etaSeconds){await emitEvent("delivery:nearby",{stop,distanceMeters,etaSeconds});if(stop.order_id!=null)await broadcastToChannel(orderTrackingChannel(stop.order_id),"delivery:nearby",{stop,distanceMeters,etaSeconds})}export async function emitDeliveryArrived(stop){await emitEvent("delivery:arrived",{stop});if(stop.order_id!=null)await broadcastToChannel(orderTrackingChannel(stop.order_id),"delivery:arrived",{stop})}export async function emitDeliveryCompleted(stop){await emitEvent("delivery:completed",{stop});if(stop.order_id!=null)await broadcastToChannel(orderTrackingChannel(stop.order_id),"delivery:completed",{stop})}export async function emitDeliveryFailed(stop,reason){await emitEvent("delivery:failed",{stop,reason})}
@@ -0,0 +1 @@
1
+ const EARTH_RADIUS_M=6371008.8,toRadians=(degrees)=>degrees*Math.PI/180;export function distanceInMeters(from,to){const dLat=toRadians(to.latitude-from.latitude),dLon=toRadians(to.longitude-from.longitude),lat1=toRadians(from.latitude),lat2=toRadians(to.latitude),a=Math.sin(dLat/2)**2+Math.sin(dLon/2)**2*Math.cos(lat1)*Math.cos(lat2);return 2*EARTH_RADIUS_M*Math.asin(Math.sqrt(a))}export function bearingInDegrees(from,to){const lat1=toRadians(from.latitude),lat2=toRadians(to.latitude),dLon=toRadians(to.longitude-from.longitude),y=Math.sin(dLon)*Math.cos(lat2),x=Math.cos(lat1)*Math.sin(lat2)-Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);return(Math.atan2(y,x)*180/Math.PI+360)%360}export function estimateSecondsRemaining(distanceMeters,speedMetersPerSecond,options={}){const detourFactor=options.detourFactor??1.35,minimumSeconds=options.minimumSeconds??30;if(!Number.isFinite(speedMetersPerSecond)||speedMetersPerSecond<=0.5)return null;const seconds=distanceMeters*detourFactor/speedMetersPerSecond;return Math.max(minimumSeconds,Math.round(seconds))}export function isWithin(point,target,radiusMeters){return distanceInMeters(point,target)<=radiusMeters}export function hasCoordinates(value){return typeof value?.latitude==="number"&&typeof value?.longitude==="number"&&Number.isFinite(value.latitude)&&Number.isFinite(value.longitude)}
@@ -0,0 +1 @@
1
+ export{broadcastToChannel,emitDeliveryArrived,emitDeliveryAssigned,emitDeliveryCompleted,emitDeliveryFailed,emitDeliveryNearby,emitDeliveryPosition,emitDeliveryStarted,orderTrackingChannel,routeTrackingChannel}from"./events";export{bearingInDegrees,distanceInMeters,estimateSecondsRemaining,hasCoordinates,isWithin}from"./geo";export{ARRIVAL_RADIUS_METERS,NEARBY_RADIUS_METERS,recordCourierPing}from"./ping";export{assignStop,completeStop,failStop,startRoute,startStop}from"./stops";
@@ -0,0 +1 @@
1
+ import{randomUUIDv7}from"bun";import{db}from"@stacksjs/database/runtime";import{emitDeliveryArrived,emitDeliveryNearby,emitDeliveryPosition}from"./events";import{bearingInDegrees,distanceInMeters,estimateSecondsRemaining,hasCoordinates}from"./geo";export const NEARBY_RADIUS_METERS=400,ARRIVAL_RADIUS_METERS=60;const MAX_TRUSTED_ACCURACY_METERS=250;export async function recordCourierPing(input){const recordedAt=input.recordedAt??new Date().toISOString(),trusted=typeof input.accuracy!=="number"||input.accuracy<=MAX_TRUSTED_ACCURACY_METERS,courier=await db.selectFrom("couriers").where("id","=",input.courierId).select(["id","latitude","longitude"]).executeTakeFirst();if(!courier)throw Error(`Unknown courier: ${input.courierId}`);const previous=hasCoordinates(courier)?{latitude:courier.latitude,longitude:courier.longitude}:null,heading=typeof input.heading==="number"?input.heading:previous?bearingInDegrees(previous,input):null,route=await activeRouteForCourier(input.courierId),stop=route?await currentStopForRoute(route.id):null;let distanceToStopMeters=null,etaSeconds=null,crossedNearby=!1,crossedArrival=!1;const stopPosition=stop&&hasCoordinates({latitude:stop.latitude??void 0,longitude:stop.longitude??void 0})?{latitude:stop.latitude,longitude:stop.longitude}:null;if(trusted&&stop&&stopPosition){distanceToStopMeters=Math.round(distanceInMeters(input,stopPosition));etaSeconds=estimateSecondsRemaining(distanceToStopMeters,input.speed??0);crossedArrival=distanceToStopMeters<=ARRIVAL_RADIUS_METERS&&stop.arrived_at==null;crossedNearby=!crossedArrival&&distanceToStopMeters<=NEARBY_RADIUS_METERS&&stop.status==="en_route"&&stop.notified_nearby_at==null}const pingId=await insertPing({courierId:input.courierId,routeId:route?.id??null,latitude:input.latitude,longitude:input.longitude,heading,speed:input.speed??null,accuracy:input.accuracy??null,recordedAt});if(trusted)await db.updateTable("couriers").set({latitude:input.latitude,longitude:input.longitude,heading,speed:input.speed??0,last_ping_at:recordedAt}).where("id","=",input.courierId).execute();if(stop&&(distanceToStopMeters!=null||etaSeconds!=null))await db.updateTable("delivery_stops").set({eta_at:etaSeconds==null?stop.eta_at:new Date(Date.now()+etaSeconds*1000).toISOString()}).where("id","=",stop.id).execute();if(trusted)await emitDeliveryPosition(stop?.order_id==null?[]:[stop.order_id],route?.id??null,{courierId:input.courierId,routeId:route?.id??null,latitude:input.latitude,longitude:input.longitude,heading,speed:input.speed??null,accuracy:input.accuracy??null,recordedAt,distanceToStopMeters,etaSeconds});if(crossedNearby&&stop){await db.updateTable("delivery_stops").set({notified_nearby_at:recordedAt}).where("id","=",stop.id).execute();await emitDeliveryNearby(stop,distanceToStopMeters,etaSeconds)}if(crossedArrival&&stop){await db.updateTable("delivery_stops").set({status:"arrived",arrived_at:recordedAt}).where("id","=",stop.id).execute();await emitDeliveryArrived({...stop,status:"arrived",arrived_at:recordedAt})}return{pingId,routeId:route?.id??null,stopId:stop?.id??null,distanceToStopMeters,etaSeconds,crossedNearby,crossedArrival}}async function activeRouteForCourier(courierId){return await db.selectFrom("delivery_routes").where("courier_id","=",courierId).where("status","=","active").select(["id"]).orderBy("id","desc").executeTakeFirst()??null}async function currentStopForRoute(routeId){const rows=await db.selectFrom("delivery_stops").where("delivery_route_id","=",routeId).where("status","in",["en_route","pending","arrived"]).select(["id","order_id","status","latitude","longitude","eta_at","arrived_at","notified_nearby_at"]).orderBy("sequence","asc").execute();return rows.find((row)=>row.status==="en_route")??rows.find((row)=>row.status==="arrived")??rows[0]??null}async function insertPing(values){const uuid=randomUUIDv7();await db.insertInto("courier_pings").values({uuid,courier_id:values.courierId,delivery_route_id:values.routeId,latitude:values.latitude,longitude:values.longitude,heading:values.heading,speed:values.speed??0,accuracy:values.accuracy,recorded_at:values.recordedAt}).executeTakeFirst();return(await db.selectFrom("courier_pings").where("uuid","=",uuid).select(["id"]).executeTakeFirst())?.id??null}
@@ -0,0 +1 @@
1
+ import{randomUUIDv7}from"bun";import{db}from"@stacksjs/database/runtime";import{emitDeliveryAssigned,emitDeliveryCompleted,emitDeliveryFailed,emitDeliveryStarted}from"./events";function isPickup(stop){return stop.type==="pickup"}export async function assignStop(input){const uuid=randomUUIDv7(),sequence=input.sequence??await nextSequence(input.deliveryRouteId);await db.insertInto("delivery_stops").values({uuid,delivery_route_id:input.deliveryRouteId,order_id:input.orderId??null,sequence,status:"pending",address:input.address,latitude:input.latitude??null,longitude:input.longitude??null,recipient_name:input.recipientName??null,recipient_phone:input.recipientPhone??null,eta_at:input.etaAt??null,type:input.type??"dropoff"}).executeTakeFirst();const stop=await stopByUuid(uuid);if(stop)await emitDeliveryAssigned(stop);return stop??{}}export async function startStop(stopId){await db.updateTable("delivery_stops").set({status:"en_route"}).where("id","=",stopId).execute();const stop=await stopById(stopId);if(!stop)return null;if(stop.order_id!=null&&!isPickup(stop))await setOrderStatus(stop.order_id,"OUT_FOR_DELIVERY");await emitDeliveryStarted(stop);return stop}export async function completeStop(stopId,notes){const completedAt=new Date().toISOString();await db.updateTable("delivery_stops").set({status:"completed",completed_at:completedAt,...await hasArrived(stopId)?{}:{arrived_at:completedAt},...notes?{notes}:{}}).where("id","=",stopId).execute();const stop=await stopById(stopId);if(!stop)return null;if(stop.order_id!=null)await setOrderStatus(stop.order_id,isPickup(stop)?"OUT_FOR_DELIVERY":"DELIVERED");await emitDeliveryCompleted(stop);await closeRouteIfDone(stop.delivery_route_id);return stop}export async function failStop(stopId,reason){await db.updateTable("delivery_stops").set({status:"failed",notes:reason}).where("id","=",stopId).execute();const stop=await stopById(stopId);if(!stop)return null;if(stop.order_id!=null&&!isPickup(stop))await setOrderStatus(stop.order_id,"SHIPPED");await emitDeliveryFailed(stop,reason);await closeRouteIfDone(stop.delivery_route_id);return stop}export async function startRoute(routeId){await db.updateTable("delivery_routes").set({status:"active",started_at:new Date().toISOString()}).where("id","=",routeId).execute()}async function closeRouteIfDone(routeId){if(routeId==null)return;if(await db.selectFrom("delivery_stops").where("delivery_route_id","=",routeId).where("status","in",["pending","en_route","arrived"]).select(["id"]).executeTakeFirst())return;const route=await db.selectFrom("delivery_routes").where("id","=",routeId).select(["courier_id"]).executeTakeFirst();await db.updateTable("delivery_routes").set({status:"completed",completed_at:new Date().toISOString()}).where("id","=",routeId).execute();if(route?.courier_id!=null)await db.updateTable("couriers").set({status:"active"}).where("id","=",route.courier_id).execute()}async function setOrderStatus(orderId,next){const order=await db.selectFrom("orders").where("id","=",orderId).select(["id","status"]).executeTakeFirst();if(!order)return;const events=await import("../../orders/events"),{canTransition,emitForStatus}=events,path=transitionPath(order.status,next,canTransition);if(path.length===0){const{log}=await import("@stacksjs/logging").catch(()=>({log:null}));log?.warn(`[delivery] refused to move order ${orderId} from ${order.status} to ${next}: no legal transition`);return}for(const step of path){await db.updateTable("orders").set({status:step}).where("id","=",orderId).execute();await emitForStatus(step,{...order,status:step})}}function transitionPath(from,to,canTransition){if(from===to)return[];if(canTransition(from,to))return[to];for(const middle of["PROCESSING","SHIPPED"]){if(middle===from||middle===to)continue;if(canTransition(from,middle)&&canTransition(middle,to))return[middle,to]}return[]}async function nextSequence(routeId){return(await db.selectFrom("delivery_stops").where("delivery_route_id","=",routeId).select(["sequence"]).execute()).reduce((max,row)=>Math.max(max,row.sequence??0),0)+1}async function hasArrived(stopId){return(await db.selectFrom("delivery_stops").where("id","=",stopId).select(["arrived_at"]).executeTakeFirst())?.arrived_at!=null}async function stopById(id){return await db.selectFrom("delivery_stops").where("id","=",id).selectAll().executeTakeFirst()??null}async function stopByUuid(uuid){return await db.selectFrom("delivery_stops").where("uuid","=",uuid).selectAll().executeTakeFirst()??null}
@@ -0,0 +1 @@
1
+ function normalizedColumns(input,aliases){const result={};for(const[column,candidates]of Object.entries(aliases))for(const candidate of candidates){if(!Object.prototype.hasOwnProperty.call(input,candidate)||input[candidate]===void 0)continue;result[column]=input[candidate];break}return result}export function shippingMethodWriteData(input){return normalizedColumns(input,{name:["name"],description:["description"],base_rate:["base_rate","baseRate"],free_shipping:["free_shipping","freeShipping"],status:["status"]})}export function shippingRateWriteData(input){return normalizedColumns(input,{weight_from:["weight_from","weightFrom"],weight_to:["weight_to","weightTo"],rate:["rate"],shipping_method_id:["shipping_method_id","shippingMethodId","shippingmethod_id"],shipping_zone_id:["shipping_zone_id","shippingZoneId","shippingzone_id"]})}export function shippingZoneWriteData(input){return normalizedColumns(input,{name:["name"],countries:["countries"],regions:["regions"],postal_codes:["postal_codes","postalCodes"],status:["status"],shipping_method_id:["shipping_method_id","shippingMethodId","shippingmethod_id"]})}export function digitalDeliveryWriteData(input){return normalizedColumns(input,{name:["name"],description:["description"],download_limit:["download_limit","downloadLimit"],expiry_days:["expiry_days","expiryDays"],requires_login:["requires_login","requiresLogin"],automatic_delivery:["automatic_delivery","automaticDelivery"],status:["status"]})}export function licenseKeyWriteData(input){return normalizedColumns(input,{key:["key"],template:["template"],expiry_date:["expiry_date","expiryDate"],status:["status"],customer_id:["customer_id","customerId"],product_id:["product_id","productId"],order_id:["order_id","orderId"]})}export function courierWriteData(input){return normalizedColumns(input,{name:["name"],phone:["phone"],vehicle_number:["vehicle_number","vehicleNumber"],license:["license"],status:["status"],user_id:["user_id","userId"]})}export function deliveryRouteWriteData(input){return normalizedColumns(input,{courier:["courier"],courier_id:["courier_id","courierId"],vehicle:["vehicle"],stops:["stops"],delivery_time:["delivery_time","deliveryTime"],total_distance:["total_distance","totalDistance"],last_active:["last_active","lastActive"]})}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";import{breakdownFor}from"../sales-tax";export{breakdownFor}from"../sales-tax";export async function activeTaxRates(options={}){let query=db.selectFrom("tax_rates").where("status","=","active").selectAll();if(options.country)query=query.where("country","=",options.country);const rows=await query.execute();if(!options.codes?.length)return rows;const wanted=new Set(options.codes);return rows.filter((row)=>wanted.has(String(row.code??"")))}export async function taxFor(taxable,options={}){return breakdownFor(taxable,await activeTaxRates(options),options)}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";import{mutationCount}from"../utils/mutation-count";export async function destroy(id){try{const result=await db.deleteFrom("tax_rates").where("id","=",id).executeTakeFirst();return mutationCount(result)>0}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete tax rate: ${error.message}`);throw error}}export async function bulkDestroy(ids){if(!ids.length)return 0;try{const result=await db.deleteFrom("tax_rates").where("id","in",ids).executeTakeFirst();return mutationCount(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete tax rates: ${error.message}`);throw error}}
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";import{asModelRow,asModelRows}from"../utils/model-row";export async function fetchById(id){const row=await db.selectFrom("tax_rates").where("id","=",id).selectAll().executeTakeFirst();return asModelRow(row,!0)}export async function fetchAll(){return asModelRows(await db.selectFrom("tax_rates").selectAll().execute())}
@@ -0,0 +1 @@
1
+ export{activeTaxRates,breakdownFor,taxFor}from"./breakdown";export{bulkDestroy,destroy}from"./destroy";export{fetchAll,fetchById}from"./fetch";export{bulkStore,store}from"./store";export{update,updateDefaultStatus,updateRate,updateStatus}from"./update";
@@ -0,0 +1 @@
1
+ import{randomUUIDv7}from"bun";import{parseTaxRateWriteData}from"../tax-input";import{db}from"@stacksjs/database/runtime";import{asModelRow}from"../utils/model-row";export async function store(data){data=parseTaxRateWriteData(data);try{return await db.transaction(async(trx)=>{const taxData={...data,uuid:randomUUIDv7()};if(taxData.is_default)await trx.updateTable("tax_rates").set({is_default:!1}).execute();const result=await trx.insertInto("tax_rates").values(taxData).returningAll().executeTakeFirst();if(!result)throw Error("Failed to create tax rate");return asModelRow(result)})}catch(error){if(error instanceof Error)throw TypeError(`Failed to create tax rate: ${error.message}`);throw error}}export async function bulkStore(data){data=data.map(parseTaxRateWriteData);if(!data.length)return 0;try{return await db.transaction(async(trx)=>{let createdCount=0;for(const item of data){const taxData={...item,uuid:randomUUIDv7()};if(taxData.is_default)await trx.updateTable("tax_rates").set({is_default:!1}).execute();if(await trx.insertInto("tax_rates").values(taxData).executeTakeFirst())createdCount++}return createdCount})}catch(error){if(error instanceof Error)throw TypeError(`Failed to create tax rates in bulk: ${error.message}`);throw error}}
File without changes
@@ -0,0 +1 @@
1
+ import{parseTaxRateWriteData}from"../tax-input";import{storedBoolean}from"@stacksjs/validation/input";import{db}from"@stacksjs/database/runtime";import{asModelRow}from"../utils/model-row";import{formatDate}from"@stacksjs/orm";export async function update(id,data){data=parseTaxRateWriteData(data);try{if(!id)throw Error("Tax rate ID is required for update");return await db.transaction(async(trx)=>{const current=await trx.selectFrom("tax_rates").select("is_default").where("id","=",id).executeTakeFirst();if(!current)return;const isDefault=data.is_default??storedBoolean(current.is_default,!1),updatedAt=formatDate(new Date);if(isDefault)await trx.updateTable("tax_rates").set({is_default:!1,updated_at:updatedAt}).where("id","!=",id).execute();const result=await trx.updateTable("tax_rates").set({...data,updated_at:updatedAt}).where("id","=",id).returningAll().executeTakeFirst();if(!result)return;return asModelRow(result)})}catch(error){if(error instanceof Error)throw TypeError(`Failed to update tax rate: ${error.message}`);throw error}}export async function updateStatus(id,status){status=parseTaxRateWriteData({status}).status;try{const result=await db.updateTable("tax_rates").set({status,updated_at:formatDate(new Date)}).where("id","=",id).returningAll().executeTakeFirst();if(!result)throw Error("Failed to update tax rate status");return asModelRow(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to update tax rate status: ${error.message}`);throw error}}export async function updateRate(id,rate){rate=parseTaxRateWriteData({rate}).rate;try{const result=await db.updateTable("tax_rates").set({rate,updated_at:formatDate(new Date)}).where("id","=",id).returningAll().executeTakeFirst();if(!result)throw Error("Failed to update rate information");return asModelRow(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to update rate information: ${error.message}`);throw error}}export async function updateDefaultStatus(id,isDefault){isDefault=parseTaxRateWriteData({is_default:isDefault}).is_default;try{return await db.transaction(async(trx)=>{if(!await trx.selectFrom("tax_rates").select("id").where("id","=",id).executeTakeFirst())return!1;const updatedAt=formatDate(new Date);if(isDefault)await trx.updateTable("tax_rates").set({is_default:!1,updated_at:updatedAt}).where("id","!=",id).execute();await trx.updateTable("tax_rates").set({is_default:isDefault,updated_at:updatedAt}).where("id","=",id).execute();return!0})}catch(error){if(error instanceof Error)throw TypeError(`Failed to update tax rate default status: ${error.message}`);throw error}}
@@ -0,0 +1,7 @@
1
+ import type { TaxRateWriteData } from './tax/types';
2
+ /** Parse an explicit multi-component configuration. A missing rates array never removes tax. */
3
+ export declare function parseSalesTaxSetup(body: unknown, options?: SalesTaxInputOptions): SalesTaxSetup;
4
+ /** Native tax write paths normalize explicit values instead of testing their truthiness. */
5
+ export declare function parseTaxRateWriteData(input: TaxRateWriteData): TaxRateWriteData;
6
+ export declare interface SalesTaxSetup { label: string, rates: Array<{ code: string, name: string, rate: number }> }
7
+ export declare interface SalesTaxInputOptions { maxRate?: number, maxRates?: number }
@@ -0,0 +1 @@
1
+ import{InputValidationError,parseBooleanInput,parseEnumInput,parseNumberInput,parseTextInput}from"@stacksjs/validation/input";export function parseSalesTaxSetup(body,options={}){if(!body||typeof body!=="object"||Array.isArray(body))throw new InputValidationError("Send a JSON object");const input=body,label=input.label===void 0?"Sales tax":parseTextInput(input.label,"label",!0,40);if(!Array.isArray(input.rates))throw new InputValidationError("rates must be an array; send [] to remove sales tax");const maximum=options.maxRates??6;if(input.rates.length>maximum)throw new InputValidationError(`At most ${maximum} tax rates`);const codes=new Set,rates=input.rates.map((value)=>{if(!value||typeof value!=="object"||Array.isArray(value))throw new InputValidationError("Each tax rate must be an object");const entry=value,rate=parseNumberInput(entry.rate,"Tax rate",0,options.maxRate??100);if(rate===null)throw new InputValidationError("A tax rate is required");const name=parseTextInput(entry.name,"name",!0,60),code=(entry.code==null||entry.code===""?name:parseTextInput(entry.code,"code",!0)).toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"").slice(0,40)||"tax";if(codes.has(code))throw new InputValidationError("Each tax rate needs a distinct code");codes.add(code);return{code,name,rate:Math.round(rate*1e4)/1e4}});return{label,rates}}export function parseTaxRateWriteData(input){if(!input||typeof input!=="object"||Array.isArray(input))throw new InputValidationError("Send a tax rate object");const values={...input};if(input.rate!==void 0){const rate=parseNumberInput(input.rate,"rate",0,100);if(rate===null)throw new InputValidationError("rate is required");values.rate=rate}for(const key of["is_default","exemptible"])if(key in input)values[key]=parseBooleanInput(input[key],key);if(input.status!==void 0)values.status=parseEnumInput(input.status,"status",["active","inactive"]);for(const key of["name","type","country"])if(key in input)values[key]=parseTextInput(input[key],key,!0,key==="name"?255:100);if("code"in input)values.code=parseTextInput(input.code,"code",!1,64)??"";if("region"in input)values.region=parseTextInput(input.region,"region");return values}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ import assert from"node:assert/strict";import{mkdtempSync,rmSync,writeFileSync}from"node:fs";import{tmpdir}from"node:os";import{basename,dirname,join}from"node:path";import process from"node:process";const dialect=process.env.DB_CONNECTION;assert(dialect==="sqlite"||dialect==="postgres"||dialect==="mysql");const configPath=process.env.STACKS_COMMERCE_RETURNING_ALL_CONFIG;assert(configPath&&basename(dirname(configPath)).startsWith("stacks-commerce-returning-all-"));if(dialect==="sqlite")assert.equal(dirname(process.env.DB_DATABASE_PATH),dirname(configPath));else{assert.equal(process.env.DB_DATABASE_PATH,":memory:");assert(process.env.DB_DATABASE?.startsWith("stacks_commerce_returning_all_"));assert(["127.0.0.1","localhost","[::1]"].includes(process.env.DB_HOST))}const{overridesReady}=await import("@stacksjs/config");await overridesReady;const{db,ensureDatabaseConfigLoaded,initializeDbConfig,resetDatabaseConnection}=await import("@stacksjs/database");await ensureDatabaseConfigLoaded();initializeDbConfig({app:{env:"test"},database:{default:dialect,connections:dialect==="sqlite"?{sqlite:{database:process.env.DB_DATABASE_PATH}}:{[dialect]:{name:process.env.DB_DATABASE,host:process.env.DB_HOST,port:Number(process.env.DB_PORT),username:process.env.DB_USERNAME,password:process.env.DB_PASSWORD}},queryLogging:{enabled:!1}}});const TABLES=["carts","categories","product_units","product_variants","products","shipping_methods","shipping_rates","shipping_zones"],{buildMigrationPlan,generateSql,loadModels}=await import("bun-query-builder"),modelsDir=join(import.meta.dir,"../../../../../defaults/app/Models"),models={...await loadModels({modelsDir}),...await loadModels({modelsDir:join(modelsDir,"commerce")})},plan=buildMigrationPlan(models,{dialect});plan.tables=plan.tables.filter((table)=>TABLES.includes(table.table));assert.deepEqual(plan.tables.map((table)=>table.table).sort(),TABLES);for(const table of plan.tables)for(const column of table.columns)delete column.references;const scratch=mkdtempSync(join(tmpdir(),"stacks-commerce-returning-all-sql-"));writeFileSync(join(scratch,"package.json"),"{}");const cwd=process.cwd();let statements;try{process.chdir(scratch);statements=generateSql(plan)}finally{process.chdir(cwd);rmSync(scratch,{recursive:!0,force:!0})}const{update:updateCart}=await import("../../carts/update"),{update:updateItem}=await import("../../products/items/update"),{update:updateVariant}=await import("../../products/variants/update"),{update:updateUnit}=await import("../../products/units/update"),{update:updateRate}=await import("../../shippings/shipping-rates/update"),{store:storeCategory}=await import("../../products/categories/store");async function stored(table,id){return await db.selectFrom(table).selectAll().where("id","=",id).executeTakeFirst()}const failures=[];async function check(name,run){try{await run();console.log(`PASS ${name}`)}catch(error){failures.push(`${name}: ${error instanceof Error?error.message:String(error)}`)}}try{for(const statement of statements)await db.unsafe(statement).execute();await db.insertInto("carts").values({id:1,status:"active",expires_at:"2030-02-01 00:00:00",applied_coupon_id:"none"}).execute();await db.insertInto("products").values({id:1,name:"before",price:100,preparation_time:5}).execute();await db.insertInto("product_variants").values({id:1,variant:"Size",type:"dropdown",status:"active"}).execute();await db.insertInto("product_units").values({id:1,name:"gram",abbreviation:"g",type:"weight"}).execute();await db.insertInto("shipping_methods").values({id:1,name:"standard",base_rate:500,status:"active"}).execute();await db.insertInto("shipping_zones").values({id:1,name:"domestic",status:"active",shipping_method_id:1}).execute();await db.insertInto("shipping_rates").values({id:1,weight_from:0,weight_to:10,rate:100,shipping_method_id:1,shipping_zone_id:1}).execute();await check("carts update",async()=>{const row=await updateCart(1,{notes:"after"});assert.equal(row?.notes,"after",`${dialect}: the returned cart`);assert.equal((await stored("carts",1))?.notes,"after",`${dialect}: the stored cart`)});await check("product items update",async()=>{const row=await updateItem(1,{name:"after"});assert.equal(row?.name,"after",`${dialect}: the returned product`);assert.equal((await stored("products",1))?.name,"after",`${dialect}: the stored product`)});await check("product variants update",async()=>{const row=await updateVariant(1,{variant:"Colour"});assert.equal(row?.variant,"Colour",`${dialect}: the returned variant`);assert.equal((await stored("product_variants",1))?.variant,"Colour",`${dialect}: the stored variant`)});await check("product units update",async()=>{const row=await updateUnit(1,{name:"kilogram"});assert.equal(row?.name,"kilogram",`${dialect}: the returned unit`);assert.equal((await stored("product_units",1))?.name,"kilogram",`${dialect}: the stored unit`)});await check("shipping rates update",async()=>{const row=await updateRate(1,{rate:250});assert.equal(Number(row?.rate),250,`${dialect}: the returned rate`);assert.equal(Number((await stored("shipping_rates",1))?.rate),250,`${dialect}: the stored rate`)});await check("categories store",async()=>{const row=await storeCategory({name:"Shoes",slug:`shoes-${dialect}`,display_order:1});assert(row?.id,`${dialect}: the inserted category comes back with its id`);assert.equal(row.name,"Shoes");assert.equal((await stored("categories",row.id))?.slug,`shoes-${dialect}`,`${dialect}: the stored category`)});assert.deepEqual(failures,[],`${dialect}: every returningAll() write must apply and return its row`);console.log("commerce returningAll writes OK")}finally{resetDatabaseConnection()}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ import assert from"node:assert/strict";import{mkdtempSync,rmSync,writeFileSync}from"node:fs";import{tmpdir}from"node:os";import{basename,dirname,join}from"node:path";import process from"node:process";const dialect=process.env.DB_CONNECTION;assert(dialect==="sqlite"||dialect==="postgres"||dialect==="mysql");const configPath=process.env.STACKS_COMMERCE_SOFT_DELETE_CONFIG;assert(configPath&&basename(dirname(configPath)).startsWith("stacks-commerce-soft-delete-"));if(dialect==="sqlite")assert.equal(dirname(process.env.DB_DATABASE_PATH),dirname(configPath));else{assert.equal(process.env.DB_DATABASE_PATH,":memory:");assert(process.env.DB_DATABASE?.startsWith("stacks_commerce_soft_delete_"));assert(["127.0.0.1","localhost","[::1]"].includes(process.env.DB_HOST))}const{overridesReady}=await import("@stacksjs/config");await overridesReady;const{db,ensureDatabaseConfigLoaded,initializeDbConfig,resetDatabaseConnection}=await import("@stacksjs/database");await ensureDatabaseConfigLoaded();initializeDbConfig({app:{env:"test"},database:{default:dialect,connections:dialect==="sqlite"?{sqlite:{database:process.env.DB_DATABASE_PATH}}:{[dialect]:{name:process.env.DB_DATABASE,host:process.env.DB_HOST,port:Number(process.env.DB_PORT),username:process.env.DB_USERNAME,password:process.env.DB_PASSWORD}},queryLogging:{enabled:!1}}});const TABLES=["carts","digital_deliveries","gift_cards","license_keys","orders","product_units","product_variants","products","shipping_methods","shipping_rates","shipping_zones"],{buildMigrationPlan,generateSql,loadModels}=await import("bun-query-builder"),modelsDir=join(import.meta.dir,"../../../../../defaults/app/Models"),models={...await loadModels({modelsDir}),...await loadModels({modelsDir:join(modelsDir,"commerce")})},plan=buildMigrationPlan(models,{dialect});plan.tables=plan.tables.filter((table)=>TABLES.includes(table.table));assert.deepEqual(plan.tables.map((table)=>table.table).sort(),TABLES);for(const table of plan.tables)for(const column of table.columns)delete column.references;const scratch=mkdtempSync(join(tmpdir(),"stacks-commerce-soft-delete-sql-"));writeFileSync(join(scratch,"package.json"),"{}");const cwd=process.cwd();let statements;try{process.chdir(scratch);statements=generateSql(plan)}finally{process.chdir(cwd);rmSync(scratch,{recursive:!0,force:!0})}const orders=await import("../../orders/destroy"),digitalDeliveries=await import("../../shippings/digital-deliveries/destroy"),licenseKeys=await import("../../shippings/license-keys/destroy"),shippingMethods=await import("../../shippings/shipping-methods/destroy"),shippingZones=await import("../../shippings/shipping-zones/destroy"),{deactivate:deactivateGiftCard}=await import("../../gift-cards/destroy"),{bulkUpdate:bulkUpdateVariants,updateStatus:updateVariantStatus}=await import("../../products/variants/update"),{bulkUpdate:bulkUpdateCarts}=await import("../../carts/update"),{updateByZone:updateRatesByZone}=await import("../../shippings/shipping-rates/update"),{bulkUpdate:bulkUpdateItems}=await import("../../products/items/update"),{bulkUpdate:bulkUpdateUnits}=await import("../../products/units/update"),MODULES=[{name:"orders",table:"orders",deleted:"CANCELED",module:orders,row:(id,status)=>({id,status,total_amount:1000,order_type:"DELIVERY"}),live:"PENDING"},{name:"shippings/digital-deliveries",table:"digital_deliveries",deleted:"inactive",module:digitalDeliveries,row:(id,status)=>({id,status,name:`delivery-${id}`,description:"seeded",expiry_days:7}),live:"active"},{name:"shippings/license-keys",table:"license_keys",deleted:"inactive",module:licenseKeys,row:(id,status)=>({id,status,key:`KEY-${id}`,template:"Standard License",expiry_date:"2030-01-01"}),live:"active"},{name:"shippings/shipping-methods",table:"shipping_methods",deleted:"inactive",module:shippingMethods,row:(id,status)=>({id,status,name:`method-${id}`,base_rate:500}),live:"active"},{name:"shippings/shipping-zones",table:"shipping_zones",deleted:"inactive",module:shippingZones,row:(id,status)=>({id,status,name:`zone-${id}`}),live:"active"}];async function statusOf(table,id){return(await db.selectFrom(table).selectAll().where("id","=",id).executeTakeFirst())?.status}const failures=[];async function check(name,run){try{await run();console.log(`PASS ${name}`)}catch(error){failures.push(`${name}: ${error instanceof Error?error.message:String(error)}`)}}try{for(const statement of statements)await db.unsafe(statement).execute();for(const{name,table,deleted,live,module,row}of MODULES)await check(`${name} soft delete counts rows already in the target state`,async()=>{for(const[id,status]of[[1,deleted],[2,live],[3,deleted],[4,live]])await db.insertInto(table).values(row(id,status)).execute();assert.equal(await module.softDelete(1),!0,`${dialect}: soft-deleting an already soft-deleted row must report success`);assert.equal(await statusOf(table,1),deleted,"the row is still there, still soft-deleted");assert.equal(await module.softDelete(2),!0,`${dialect}: soft-deleting a live row must report success`);assert.equal(await statusOf(table,2),deleted);assert.equal(await module.bulkSoftDelete([3,4]),2,`${dialect}: bulk soft delete must count rows already in the target state`);assert.equal(await statusOf(table,3),deleted);assert.equal(await statusOf(table,4),deleted);assert.equal(await module.softDelete(404),!1,`${dialect}: soft-deleting a row that does not exist must report failure`);assert.equal(await module.bulkSoftDelete([404,405]),0)});const{setSystemTime}=await import("bun:test");setSystemTime(new Date("2030-01-02T03:04:05.000Z"));try{await check("gift card deactivate is idempotent within one second",async()=>{await db.insertInto("gift_cards").values({id:1,code:"GC-1",initial_balance:5000,current_balance:5000,status:"ACTIVE"}).execute();assert.equal(await deactivateGiftCard(1),!0,`${dialect}: deactivating an active gift card must report success`);assert.equal(await deactivateGiftCard(1),!0,`${dialect}: deactivating an already deactivated gift card must report success`);assert.equal(await statusOf("gift_cards",1),"DEACTIVATED")});await check("variant updateStatus reports success for the status it already has",async()=>{await db.insertInto("product_variants").values({id:1,variant:"Size",type:"dropdown",status:"active"}).execute();await db.insertInto("product_variants").values({id:2,variant:"Colour",type:"dropdown",status:"draft"}).execute();assert.equal(await updateVariantStatus(1,"active"),!0,`${dialect}: re-applying a variant's current status must report success`);assert.equal(await updateVariantStatus(1,"active"),!0,`${dialect}: re-applying it again in the same second must report success`);assert.equal(await updateVariantStatus(2,"active"),!0,`${dialect}: changing a variant's status must report success`);assert.equal(await statusOf("product_variants",2),"active");assert.equal(await updateVariantStatus(404,"active"),!1,`${dialect}: a variant that does not exist must report failure`)});await check("variant bulkUpdate counts rows already holding the values",async()=>{const rows=await db.selectFrom("product_variants").selectAll().where("id","in",[1,2]).execute();assert.equal(rows.length,2);const resaved=await bulkUpdateVariants(rows.map((row)=>({id:row.id,status:row.status})));assert.equal(resaved,2,`${dialect}: bulk update must count rows that already hold the values written`)});await check("cart bulkUpdate counts rows already holding the values, inside a transaction",async()=>{for(const id of[1,2])await db.insertInto("carts").values({id,status:"active",expires_at:"2030-02-01 00:00:00",applied_coupon_id:"none"}).execute();const rows=[{id:1,status:"active"},{id:2,status:"active"}];assert.equal(await bulkUpdateCarts(rows),2);assert.equal(await bulkUpdateCarts(rows),2,`${dialect}: bulk update must count carts that already hold the values written`)});await check("shipping rate updateByZone counts rates already at that rate",async()=>{for(const id of[1,2])await db.insertInto("shipping_rates").values({id,weight_from:0,weight_to:10,rate:250,shipping_zone_id:7}).execute();assert.equal(await updateRatesByZone(7,{rate:250}),2);assert.equal(await updateRatesByZone(7,{rate:250}),2,`${dialect}: updateByZone must count rates already at the requested rate`)});await check("product bulkUpdate counts rows already holding the values",async()=>{for(const id of[1,2])await db.insertInto("products").values({id,name:`product-${id}`,price:100,preparation_time:5}).execute();const rows=[{id:1,name:"product-1"},{id:2,name:"product-2"}];assert.equal(await bulkUpdateItems(rows),2);assert.equal(await bulkUpdateItems(rows),2,`${dialect}: bulk update must count products that already hold the values written`)});await check("product unit bulkUpdate counts rows already holding the values",async()=>{for(const id of[1,2])await db.insertInto("product_units").values({id,name:`unit-${id}`,abbreviation:`u${id}`,type:"weight"}).execute();const rows=[{id:1,name:"unit-1"},{id:2,name:"unit-2"}];assert.equal(await bulkUpdateUnits(rows),2);assert.equal(await bulkUpdateUnits(rows),2,`${dialect}: bulk update must count units that already hold the values written`)})}finally{setSystemTime()}assert.deepEqual(failures,[],`${dialect}: every write must report the rows its predicate matched`);console.log("commerce soft delete counts OK")}finally{resetDatabaseConnection()}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import assert from"node:assert/strict";import{mkdtempSync,rmSync,writeFileSync}from"node:fs";import{tmpdir}from"node:os";import{basename,dirname,join}from"node:path";import process from"node:process";const dialect=process.env.DB_CONNECTION;assert(dialect==="sqlite"||dialect==="postgres"||dialect==="mysql");const configPath=process.env.STACKS_COMMERCE_WRITE_COUNTS_CONFIG;assert(configPath&&basename(dirname(configPath)).startsWith("stacks-commerce-write-counts-"));if(dialect==="sqlite")assert.equal(dirname(process.env.DB_DATABASE_PATH),dirname(configPath));else{assert.equal(process.env.DB_DATABASE_PATH,":memory:");assert(process.env.DB_DATABASE?.startsWith("stacks_commerce_write_counts_"));assert(["127.0.0.1","localhost","[::1]"].includes(process.env.DB_HOST))}const{overridesReady}=await import("@stacksjs/config");await overridesReady;const{Database}=await import("bun:sqlite"),{db,ensureDatabaseConfigLoaded,initializeDbConfig,resetDatabaseConnection,sqlDateTime}=await import("@stacksjs/database");await ensureDatabaseConfigLoaded();initializeDbConfig({app:{env:"test"},database:{default:dialect,connections:dialect==="sqlite"?{sqlite:{database:process.env.DB_DATABASE_PATH}}:{[dialect]:{name:process.env.DB_DATABASE,host:process.env.DB_HOST,port:Number(process.env.DB_PORT),username:process.env.DB_USERNAME,password:process.env.DB_PASSWORD}},queryLogging:{enabled:!1}}});const TABLES=["carts","categories","coupons","gift_cards","payments","receipts"],{buildMigrationPlan,generateSql,loadModels}=await import("bun-query-builder"),modelsDir=join(import.meta.dir,"../../../../../defaults/app/Models"),models={...await loadModels({modelsDir}),...await loadModels({modelsDir:join(modelsDir,"commerce")})},plan=buildMigrationPlan(models,{dialect});plan.tables=plan.tables.filter((table)=>TABLES.includes(table.table));assert.deepEqual(plan.tables.map((table)=>table.table).sort(),TABLES);for(const table of plan.tables)for(const column of table.columns)delete column.references;const scratch=mkdtempSync(join(tmpdir(),"stacks-commerce-write-counts-sql-"));writeFileSync(join(scratch,"package.json"),"{}");const cwd=process.cwd();let statements;try{process.chdir(scratch);statements=generateSql(plan)}finally{process.chdir(cwd);rmSync(scratch,{recursive:!0,force:!0})}const{deactivate,deactivateChildCategories}=await import("../../products/categories/destroy"),{registerPersistentQueryHooks}=await import("@stacksjs/query-builder"),{redeem}=await import("../../coupons/update"),{store:storeCoupon}=await import("../../coupons/store"),{updateBalance}=await import("../../gift-cards/update"),{store:storeGiftCard}=await import("../../gift-cards/store"),{cleanupAbandonedCarts}=await import("../../orders/guards"),{recordRefund}=await import("../../payments/update"),{store:storePayment}=await import("../../payments/store"),{bulkStore}=await import("../../receipts/store");async function count(table){return(await db.selectFrom(table).selectAll().execute()).length}async function column(table,id,name){return(await db.selectFrom(table).selectAll().where("id","=",id).executeTakeFirst())?.[name]}function deleteOnAnotherConnection(id){if(dialect==="sqlite"){const handle=new Database(process.env.DB_DATABASE_PATH);try{handle.run("DELETE FROM categories WHERE id = ?",[id])}finally{handle.close()}return!0}const adapter=dialect==="postgres"?"postgres":"mysql",child=Bun.spawnSync([process.execPath,"-e",`
2
+ const db = new Bun.SQL({ adapter: '${adapter}', hostname: process.env.DB_HOST,
3
+ port: Number(process.env.DB_PORT), database: process.env.DB_DATABASE,
4
+ username: process.env.DB_USERNAME, password: process.env.DB_PASSWORD,
5
+ tls: process.env.DB_SSL === 'true' ? 'require' : 'disable' })
6
+ try { await db.unsafe('DELETE FROM categories WHERE id = ${id}') }
7
+ finally { await db.close() }
8
+ `],{cwd:tmpdir(),env:process.env,stdout:"pipe",stderr:"pipe",timeout:5000});if(child.exitCode!==0)console.error(new TextDecoder().decode(child.stderr));return child.exitCode===0}const failures=[];async function check(name,run){try{await run();console.log(`PASS ${name}`)}catch(error){failures.push(`${name}: ${error instanceof Error?error.message:String(error)}`)}}try{for(const statement of statements)await db.unsafe(statement).execute();const today=new Date;await check("orders cleanupAbandonedCarts",async()=>{const old="2020-01-01 00:00:00",fresh=today.toISOString().slice(0,19).replace("T"," ");for(const[index,updatedAt]of[old,old,fresh].entries())await db.insertInto("carts").values({uuid:`cart-${index}`,applied_coupon_id:"",expires_at:updatedAt,created_at:updatedAt,updated_at:updatedAt}).execute();const before=await count("carts"),{deleted}=await cleanupAbandonedCarts({olderThanDays:30}),removed=before-await count("carts");assert.equal(removed,2,"the two abandoned carts were deleted");assert.equal(deleted,removed,`cleanupAbandonedCarts reported ${deleted} but removed ${removed} carts`)});await check("coupons redeem",async()=>{const today=new Date,day=(offset)=>new Date(today.getTime()+offset*86400000).toISOString().slice(0,10),coupon=await storeCoupon({code:`REDEEM-${dialect}`,discount_type:"percentage",discount_value:10,product_id:1,start_date:day(-1),end_date:day(30),is_active:!0,usage_limit:2});assert(coupon?.id,"seed coupon");const id=Number(coupon.id),first=await redeem(id);assert.equal(Number(await column("coupons",id,"usage_count")),1,"the redemption was committed");assert.equal(first.ok,!0,`redeem reported ${JSON.stringify(first)} for a redemption it committed`);assert.equal((await redeem(id)).ok,!0);const third=await redeem(id);assert.equal(third.ok,!1);assert.equal(third.ok===!1&&third.reason,"limit-reached");assert.equal(Number(await column("coupons",id,"usage_count")),2,"a rejected redemption must not count")});await check("gift cards updateBalance",async()=>{const card=await storeGiftCard({code:`GC-WRITE-COUNTS-${dialect}`,initial_balance:100,current_balance:100,currency:"USD",status:"ACTIVE",is_active:!0});assert(card?.id,"seed gift card");const spent=await updateBalance(Number(card.id),-40);assert.equal(Number(await column("gift_cards",card.id,"current_balance")),60);assert.equal(Number(spent?.current_balance),60);const used=await updateBalance(Number(card.id),-60);assert.equal(await column("gift_cards",card.id,"status"),"USED");assert.equal(used?.status,"USED");await assert.rejects(updateBalance(Number(card.id),-1),/not active/,"a spent card rejects another redemption")});await check("gift cards updateBalance by zero",async()=>{const card=await storeGiftCard({code:`GC-ZERO-${dialect}`,initial_balance:100,current_balance:100,currency:"USD",status:"ACTIVE",is_active:!0});assert(card?.id,"seed gift card");const{setSystemTime}=await import("bun:test");setSystemTime(new Date("2030-01-02T03:04:05.000Z"));try{await updateBalance(Number(card.id),0);const again=await updateBalance(Number(card.id),0);assert.equal(Number(again?.current_balance),100,`${dialect}: a zero adjustment must return the card unchanged`);await assert.rejects(updateBalance(Number(card.id),-1000),/Insufficient/,"an over-spend is still insufficient");assert.equal(Number(await column("gift_cards",card.id,"current_balance")),100)}finally{setSystemTime()}});await check("gift cards updateBalance on an expired card",async()=>{const card=await storeGiftCard({code:`GC-EXPIRED-${dialect}`,initial_balance:100,current_balance:100,currency:"USD",status:"ACTIVE",is_active:!0,expiry_date:"2020-01-01 00:00:00"});assert(card?.id,"seed gift card");await assert.rejects(updateBalance(Number(card.id),-10),/expired/,`${dialect}: a redemption on an expired card must say it expired`);await assert.rejects(updateBalance(Number(card.id),0),/expired/,`${dialect}: a zero adjustment on an expired card must be refused, not reported as success`);assert.equal(Number(await column("gift_cards",card.id,"current_balance")),100)});await check("payments recordRefund",async()=>{const payment=await storePayment({amount:1000,method:"creditCard",status:"completed",transaction_id:`TXN-WRITE-COUNTS-${dialect}`});assert(payment?.id,"seed payment");const partial=await recordRefund(Number(payment.id),250);assert.equal(Number(await column("payments",payment.id,"refund_amount")),250);assert.equal(partial.status,"partiallyRefunded");const full=await recordRefund(Number(payment.id),750);assert.equal(await column("payments",payment.id,"status"),"refunded");assert.equal(full.status,"refunded");await assert.rejects(recordRefund(Number(payment.id),1),/cannot be refunded/,"a fully refunded payment rejects another refund")});await check("product categories deactivate",async()=>{let next=0;const seedCategory=async(name,parentId)=>{next+=1;await db.insertInto("categories").values({uuid:crypto.randomUUID(),name,slug:`${name}-${dialect}-${next}`,display_order:next,is_active:!0,parent_category_id:parentId,created_at:sqlDateTime(),updated_at:sqlDateTime()}).execute();const rows=await db.selectFrom("categories").selectAll().execute();return Number(rows[rows.length-1].id)},id=await seedCategory("parent",null);assert.equal(await deactivate(id),!0,"deactivating a category that exists");assert.equal(Boolean(await column("categories",id,"is_active")),!1);assert.equal(await deactivate(id),!0,"deactivating a category that is already inactive");for(const child of["a","b"])await seedCategory(`child-${child}`,id);const{setSystemTime}=await import("bun:test");setSystemTime(new Date("2030-01-02T03:04:05.000Z"));try{assert.equal(await deactivateChildCategories(String(id)),2);assert.equal(await deactivateChildCategories(String(id)),2,`${dialect}: children that are already inactive are still the children deactivated`)}finally{setSystemTime()}const doomed=await seedCategory("doomed",null);let removed=!1;const unregister=registerPersistentQueryHooks({onQueryStart(event){if(event.kind!=="update"||removed)return;removed=deleteOnAnotherConnection(doomed)}});try{const claimed=await deactivate(doomed);assert.equal(removed,!0,"the competing delete must have run");assert.equal(claimed,!1,`${dialect}: deactivate must not report success for a row that is gone`)}finally{unregister()}assert.equal(await db.selectFrom("categories").selectAll().where("id","=",doomed).executeTakeFirst(),void 0)});await check("receipts bulkStore",async()=>{const receipt=(index)=>({printer:"Front Counter",document:`Receipt #${index}`,timestamp:Date.parse("2026-01-10T12:00:00Z"),status:"success",size:12,pages:1,duration:2}),before=await count("receipts"),reported=await bulkStore([receipt(1),receipt(2),receipt(3)]),inserted=await count("receipts")-before;assert.equal(inserted,3,"the three receipts were inserted");assert.equal(reported,inserted,`bulkStore reported ${reported} but inserted ${inserted} receipts`)});assert.deepEqual(failures,[],`${dialect}: every commerce write must apply and report the rows it changed`);console.log("commerce write counts OK")}finally{resetDatabaseConnection()}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Wipe every model-derived table between tests. DELETE (not DROP) keeps
3
+ * the schema warm; with FK references stripped, order doesn't matter.
4
+ */
5
+ export declare function refreshDatabase(): Promise<void>;
@@ -0,0 +1 @@
1
+ import{existsSync,mkdtempSync,rmSync,unlinkSync,writeFileSync}from"node:fs";import{tmpdir}from"node:os";import{join}from"node:path";import process from"node:process";const DB_PATH=join(tmpdir(),`stacks-commerce-${process.pid}.sqlite`);process.env.DB_CONNECTION="sqlite";process.env.DB_DATABASE_PATH=DB_PATH;process.env.APP_ENV="test";const{acquireDbConfigLock,db,ensureDatabaseConfigLoaded,initializeDbConfig}=await import("@stacksjs/database"),{buildMigrationPlan,generateSql,loadModels}=await import("bun-query-builder"),defaultsModelsDir=join(import.meta.dir,"../../../../defaults/app/Models"),commerceModelsDir=join(defaultsModelsDir,"commerce");async function forceConfig(){const releaseDbConfigLock=await acquireDbConfigLock();try{await ensureDatabaseConfigLoaded();initializeDbConfig({app:{env:"test"},database:{default:"sqlite",connections:{sqlite:{database:DB_PATH,prefix:""}}}})}finally{releaseDbConfigLock()}}for(const suffix of["","-wal","-shm"])if(existsSync(`${DB_PATH}${suffix}`))unlinkSync(`${DB_PATH}${suffix}`);await forceConfig();const models={...await loadModels({modelsDir:defaultsModelsDir}),...await loadModels({modelsDir:commerceModelsDir})},plan=buildMigrationPlan(models,{dialect:"sqlite"});for(const table of plan.tables)for(const column of table.columns)delete column.references;const scratchDir=mkdtempSync(join(tmpdir(),"stacks-commerce-migrations-"));writeFileSync(join(scratchDir,"package.json"),"{}");const originalCwd=process.cwd();let statements;try{process.chdir(scratchDir);statements=generateSql(plan)}finally{process.chdir(originalCwd);rmSync(scratchDir,{recursive:!0,force:!0})}for(const statement of statements)await db.unsafe(statement).execute();const tableNames=plan.tables.map((table)=>table.table);export async function refreshDatabase(){await forceConfig();for(const table of tableNames)await db.unsafe(`DELETE FROM ${table}`).execute()}process.on("exit",()=>{for(const suffix of["","-wal","-shm"])try{if(existsSync(`${DB_PATH}${suffix}`))unlinkSync(`${DB_PATH}${suffix}`)}catch{}});
package/dist/types.js ADDED
File without changes
@@ -0,0 +1 @@
1
+ export function insertedId(result){if(typeof result==="bigint")return normalize(result);if(typeof result==="number")return normalize(result);if(!result||typeof result!=="object")return;if(Array.isArray(result)){for(const item of result){const found=insertedId(item);if(found!==void 0)return found}return}const record=result;for(const key of["lastInsertRowid","insertId","lastInsertId"]){const value=record[key];if(value===void 0||value===null)continue;const id=normalize(value);if(id!==void 0)return id}return}function normalize(value){const id=typeof value==="bigint"?Number(value):Number(value);if(!Number.isSafeInteger(id)||id<=0)return;return id}
@@ -0,0 +1,5 @@
1
+ import{sqlHelpers}from"@stacksjs/database/runtime";import{env}from"@stacksjs/env";import{mutationCount}from"./mutation-count";export function inventoryAdjustmentStatement(driver,productId,delta,updatedAt){const p=sqlHelpers(driver).param;return{query:`UPDATE products
2
+ SET inventory_count = inventory_count + ${p(1)},
3
+ updated_at = ${p(2)}
4
+ WHERE id = ${p(3)}
5
+ AND inventory_count + ${p(4)} >= 0`,parameters:[delta,updatedAt,productId,delta]}}export async function adjustInventoryOnConnection(connection,productId,delta,updatedAt){const statement=inventoryAdjustmentStatement(env.DB_CONNECTION||"sqlite",productId,delta,updatedAt),pending=await connection.unsafe(statement.query,statement.parameters);return mutationCount(pending)}
@@ -0,0 +1 @@
1
+ function toDeclaredSpelling(column){return column.replace(/_([a-z0-9])/g,(_,char)=>char.toUpperCase())}export function asModelRow(row){if(row===null||typeof row!=="object")return row;const out={...row};for(const[key,value]of Object.entries(out)){if(!key.includes("_"))continue;const declared=toDeclaredSpelling(key);if(!(declared in out))out[declared]=value}return out}export function asModelRows(rows){return rows.map((row)=>asModelRow(row))}
@@ -0,0 +1 @@
1
+ export{mutationCount}from"@stacksjs/database/runtime";
@@ -0,0 +1 @@
1
+ import{aggregateFunctions,db}from"@stacksjs/database/runtime";export async function aggregateStats(table,descriptors,applyWhere){let query=db.selectFrom(table);if(applyWhere)query=applyWhere(query);query=query.select(Object.entries(descriptors).map(([alias,d])=>{const col=d.column??"id";let expr=aggregateFunctions[d.kind](col);if(d.filter)expr=expr.filterWhere(d.filter.column,d.filter.op,d.filter.value);return expr.as(alias)}));const row=await query.executeTakeFirst(),out={};for(const alias of Object.keys(descriptors)){const v=row?.[alias];out[alias]=typeof v==="number"?v:Number(v??0)||0}return out}
@@ -0,0 +1 @@
1
+ import*as products from"./products";import*as restaurant from"./restaurant";export{products,restaurant};const waitlists={products,restaurant};export default waitlists;
@@ -0,0 +1 @@
1
+ import{db}from"@stacksjs/database/runtime";import{asModelRow}from"../../utils/model-row";import{mutationCount}from"../../utils/mutation-count";export async function destroy(id){try{const waitlistProduct=await db.selectFrom("waitlist_products").where("id","=",id).selectAll().executeTakeFirst();if(!waitlistProduct)return;await db.deleteFrom("waitlist_products").where("id","=",id).execute();return asModelRow(waitlistProduct)}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete waitlist product: ${error.message}`);throw error}}export async function bulkDestroy(ids){if(!ids.length)return 0;try{const result=await db.deleteFrom("waitlist_products").where("id","in",ids).executeTakeFirst();return mutationCount(result)}catch(error){if(error instanceof Error)throw TypeError(`Failed to delete waitlist products in bulk: ${error.message}`);throw error}}
@@ -0,0 +1,41 @@
1
+ import type { ModelRow, WaitlistProduct } from '@stacksjs/orm';
2
+ import type { SpreadsheetWrapper } from 'ts-spreadsheets';
3
+ /**
4
+ * Export waitlist products to a spreadsheet
5
+ * @param format The format of the spreadsheet (default is CSV)
6
+ * @returns Spreadsheet object ready for download or storage
7
+ */
8
+ export declare function exportWaitlist(format?: 'csv' | 'excel'): Promise<SpreadsheetWrapper>;
9
+ /**
10
+ * Export waitlist products and automatically download
11
+ * @param format The format of the spreadsheet (default is CSV)
12
+ * @param filename Optional filename for the download
13
+ * @returns Download response
14
+ */
15
+ export declare function downloadWaitlist(format?: 'csv' | 'excel', filename?: string): Promise<Response>;
16
+ /**
17
+ * Store waitlist export to disk
18
+ * @param format The format of the spreadsheet (default is CSV)
19
+ * @param path Optional path to store the file
20
+ * @returns Path where the file is stored
21
+ */
22
+ export declare function storeWaitlistExport(format?: 'csv' | 'excel', path?: string): Promise<void>;
23
+ /**
24
+ * Represents the structure of an exported waitlist product
25
+ */
26
+ export declare interface ExportedWaitlistProduct {
27
+ 'ID': number
28
+ 'Name': string
29
+ 'Email': string
30
+ 'Phone': string
31
+ 'Party Size': number
32
+ 'Notification Preference': string
33
+ 'Source': string
34
+ 'Status': string
35
+ 'Created At': string
36
+ 'Notified At': string
37
+ 'Purchased At': string
38
+ 'Cancelled At': string
39
+ 'Notes': string
40
+ }
41
+ declare type WaitlistProductJsonResponse = ModelRow<typeof WaitlistProduct>;
@@ -0,0 +1 @@
1
+ import{createSpreadsheet}from"ts-spreadsheets";import{fetchAll}from"./fetch";export async function exportWaitlist(format="csv"){const waitlistProducts=await fetchAll(),spreadsheetData=prepareWaitlistForExport(waitlistProducts);return createSpreadsheet(spreadsheetData,{type:format})}function prepareWaitlistForExport(waitlistProducts){const headings=["ID","Name","Email","Phone","Party Size","Notification Preference","Source","Status","Created At","Notified At","Purchased At","Cancelled At","Notes"],data=waitlistProducts.map((product)=>{return[product.id,product.name,product.email,product.phone||"N/A",product.quantity,product.notificationPreference,product.source,product.status,product.created_at,product.notifiedAt||"N/A",product.purchasedAt||"N/A",product.cancelledAt||"N/A",product.notes||"N/A"]});return{headings,data}}export async function downloadWaitlist(format="csv",filename){const spreadsheet=await exportWaitlist(format),defaultFilename=`waitlist_export_${new Date().toISOString().split("T")[0]}.${format}`;return spreadsheet.download(filename||defaultFilename)}export async function storeWaitlistExport(format="csv",path){const spreadsheet=await exportWaitlist(format),defaultPath=`waitlist_export_${new Date().toISOString().split("T")[0]}.${format}`;spreadsheet.store(path||defaultPath)}