@stacksjs/commerce 0.70.53 → 0.70.55

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 (165) hide show
  1. package/package.json +3 -2
  2. package/src/carts/destroy.ts +54 -0
  3. package/src/carts/fetch.ts +20 -0
  4. package/src/carts/index.ts +13 -0
  5. package/src/carts/store.ts +75 -0
  6. package/src/carts/update.ts +78 -0
  7. package/src/coupons/destroy.ts +49 -0
  8. package/src/coupons/fetch.ts +510 -0
  9. package/src/coupons/index.ts +30 -0
  10. package/src/coupons/store.ts +46 -0
  11. package/src/coupons/update.ts +147 -0
  12. package/src/customers/destroy.ts +62 -0
  13. package/src/customers/fetch.ts +20 -0
  14. package/src/customers/index.ts +17 -0
  15. package/src/customers/store.ts +45 -0
  16. package/src/customers/update.ts +44 -0
  17. package/src/devices/destroy.ts +54 -0
  18. package/src/devices/export.ts +98 -0
  19. package/src/devices/fetch.ts +125 -0
  20. package/src/devices/index.ts +31 -0
  21. package/src/devices/store.ts +82 -0
  22. package/src/devices/update.ts +125 -0
  23. package/src/errors/destroy.ts +54 -0
  24. package/src/errors/fetch.ts +197 -0
  25. package/src/errors/index.ts +28 -0
  26. package/src/errors/update.ts +116 -0
  27. package/src/gift-cards/destroy.ts +87 -0
  28. package/src/gift-cards/fetch.ts +431 -0
  29. package/src/gift-cards/index.ts +26 -0
  30. package/src/gift-cards/store.ts +56 -0
  31. package/src/gift-cards/update.ts +102 -0
  32. package/src/index.ts +78 -0
  33. package/src/orders/destroy.ts +111 -0
  34. package/src/orders/events.ts +121 -0
  35. package/src/orders/export.ts +158 -0
  36. package/src/orders/fetch.ts +379 -0
  37. package/src/orders/guards.ts +261 -0
  38. package/src/orders/index.ts +91 -0
  39. package/src/orders/place-order.ts +274 -0
  40. package/src/orders/store.ts +51 -0
  41. package/src/orders/totals.ts +190 -0
  42. package/src/orders/update.ts +164 -0
  43. package/src/orders/webhook.ts +312 -0
  44. package/src/payments/destroy.ts +56 -0
  45. package/src/payments/fetch.ts +288 -0
  46. package/src/payments/index.ts +16 -0
  47. package/src/payments/store.ts +63 -0
  48. package/src/payments/update.ts +52 -0
  49. package/src/product.ts +0 -0
  50. package/src/products/categories/destroy.ts +103 -0
  51. package/src/products/categories/fetch.ts +285 -0
  52. package/src/products/categories/index.ts +36 -0
  53. package/src/products/categories/store.ts +87 -0
  54. package/src/products/categories/update.ts +251 -0
  55. package/src/products/index.ts +35 -0
  56. package/src/products/items/destroy.ts +54 -0
  57. package/src/products/items/fetch.ts +282 -0
  58. package/src/products/items/index.ts +26 -0
  59. package/src/products/items/store.ts +84 -0
  60. package/src/products/items/update.ts +324 -0
  61. package/src/products/manufacturers/destroy.ts +56 -0
  62. package/src/products/manufacturers/fetch.ts +117 -0
  63. package/src/products/manufacturers/index.ts +24 -0
  64. package/src/products/manufacturers/store.ts +82 -0
  65. package/src/products/manufacturers/update.ts +113 -0
  66. package/src/products/reviews/destroy.ts +56 -0
  67. package/src/products/reviews/fetch.ts +63 -0
  68. package/src/products/reviews/index.ts +25 -0
  69. package/src/products/reviews/store.ts +46 -0
  70. package/src/products/reviews/update.ts +82 -0
  71. package/src/products/units/destroy.ts +56 -0
  72. package/src/products/units/fetch.ts +20 -0
  73. package/src/products/units/index.ts +13 -0
  74. package/src/products/units/store.ts +162 -0
  75. package/src/products/units/update.ts +159 -0
  76. package/src/products/variants/destroy.ts +56 -0
  77. package/src/products/variants/fetch.ts +20 -0
  78. package/src/products/variants/index.ts +21 -0
  79. package/src/products/variants/store.ts +136 -0
  80. package/src/products/variants/update.ts +104 -0
  81. package/src/receipts/destroy.ts +54 -0
  82. package/src/receipts/export.ts +0 -0
  83. package/src/receipts/fetch.ts +222 -0
  84. package/src/receipts/index.ts +24 -0
  85. package/src/receipts/printer.ts +69 -0
  86. package/src/receipts/store.ts +69 -0
  87. package/src/receipts/update.ts +124 -0
  88. package/src/shippings/delivery-routes/destroy.ts +54 -0
  89. package/src/shippings/delivery-routes/fetch.ts +42 -0
  90. package/src/shippings/delivery-routes/index.ts +26 -0
  91. package/src/shippings/delivery-routes/store.ts +69 -0
  92. package/src/shippings/delivery-routes/update.ts +120 -0
  93. package/src/shippings/digital-deliveries/destroy.ts +111 -0
  94. package/src/shippings/digital-deliveries/fetch.ts +20 -0
  95. package/src/shippings/digital-deliveries/index.ts +26 -0
  96. package/src/shippings/digital-deliveries/store.ts +76 -0
  97. package/src/shippings/digital-deliveries/update.ts +129 -0
  98. package/src/shippings/drivers/destroy.ts +78 -0
  99. package/src/shippings/drivers/fetch.ts +20 -0
  100. package/src/shippings/drivers/index.ts +24 -0
  101. package/src/shippings/drivers/store.ts +69 -0
  102. package/src/shippings/drivers/update.ts +117 -0
  103. package/src/shippings/index.ts +39 -0
  104. package/src/shippings/license-keys/destroy.ts +111 -0
  105. package/src/shippings/license-keys/fetch.ts +20 -0
  106. package/src/shippings/license-keys/index.ts +26 -0
  107. package/src/shippings/license-keys/store.ts +77 -0
  108. package/src/shippings/license-keys/update.ts +117 -0
  109. package/src/shippings/shipping-methods/destroy.ts +111 -0
  110. package/src/shippings/shipping-methods/fetch.ts +62 -0
  111. package/src/shippings/shipping-methods/index.ts +28 -0
  112. package/src/shippings/shipping-methods/store.ts +123 -0
  113. package/src/shippings/shipping-methods/update.ts +120 -0
  114. package/src/shippings/shipping-rates/destroy.ts +102 -0
  115. package/src/shippings/shipping-rates/fetch.ts +320 -0
  116. package/src/shippings/shipping-rates/index.ts +36 -0
  117. package/src/shippings/shipping-rates/store.ts +102 -0
  118. package/src/shippings/shipping-rates/update.ts +139 -0
  119. package/src/shippings/shipping-zones/destroy.ts +111 -0
  120. package/src/shippings/shipping-zones/fetch.ts +96 -0
  121. package/src/shippings/shipping-zones/index.ts +26 -0
  122. package/src/shippings/shipping-zones/store.ts +74 -0
  123. package/src/shippings/shipping-zones/update.ts +156 -0
  124. package/src/tax/destroy.ts +54 -0
  125. package/src/tax/fetch.ts +20 -0
  126. package/src/tax/index.ts +24 -0
  127. package/src/tax/store.ts +69 -0
  128. package/src/tax/update.ts +119 -0
  129. package/src/tests/coupons.test.ts +90 -0
  130. package/src/tests/customers.test.ts +39 -0
  131. package/src/tests/device.test.ts +111 -0
  132. package/src/tests/digitals.test.ts +31 -0
  133. package/src/tests/drivers.test.ts +25 -0
  134. package/src/tests/gift-cards.test.ts +36 -0
  135. package/src/tests/license.test.ts +31 -0
  136. package/src/tests/manufacturers.test.ts +80 -0
  137. package/src/tests/orders.test.ts +25 -0
  138. package/src/tests/payments.test.ts +75 -0
  139. package/src/tests/rates.test.ts +367 -0
  140. package/src/tests/receipts.test.ts +287 -0
  141. package/src/tests/restaurant.test.ts +522 -0
  142. package/src/tests/reviews.test.ts +165 -0
  143. package/src/tests/routes.test.ts +51 -0
  144. package/src/tests/setup.ts +149 -0
  145. package/src/tests/shipping.test.ts +88 -0
  146. package/src/tests/tax.test.ts +25 -0
  147. package/src/tests/units.test.ts +110 -0
  148. package/src/tests/variants.test.ts +133 -0
  149. package/src/tests/waitlist.test.ts +303 -0
  150. package/src/tests/zones.test.ts +86 -0
  151. package/src/types.ts +312 -0
  152. package/src/utils/typed-stats.ts +67 -0
  153. package/src/waitlists/index.ts +19 -0
  154. package/src/waitlists/products/destroy.ts +65 -0
  155. package/src/waitlists/products/export.ts +113 -0
  156. package/src/waitlists/products/fetch.ts +362 -0
  157. package/src/waitlists/products/index.ts +33 -0
  158. package/src/waitlists/products/store.ts +112 -0
  159. package/src/waitlists/products/update.ts +125 -0
  160. package/src/waitlists/restaurant/destroy.ts +65 -0
  161. package/src/waitlists/restaurant/export.ts +107 -0
  162. package/src/waitlists/restaurant/fetch.ts +538 -0
  163. package/src/waitlists/restaurant/index.ts +43 -0
  164. package/src/waitlists/restaurant/store.ts +109 -0
  165. package/src/waitlists/restaurant/update.ts +198 -0
@@ -0,0 +1,54 @@
1
+ import { db } from '@stacksjs/database'
2
+
3
+ /**
4
+ * Delete a product item by ID
5
+ *
6
+ * @param id The ID of the product item to delete
7
+ * @returns True if the product item was deleted, false otherwise
8
+ */
9
+ export async function destroy(id: number): Promise<boolean> {
10
+ try {
11
+ const result = await db
12
+ .deleteFrom('products')
13
+ .where('id', '=', id)
14
+ .executeTakeFirst()
15
+
16
+ return Number(result.numDeletedRows) > 0
17
+ }
18
+ catch (error) {
19
+ if (error instanceof Error) {
20
+ throw new TypeError(`Failed to delete product item: ${error.message}`)
21
+ }
22
+
23
+ throw error
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Delete multiple product items by ID
29
+ *
30
+ * @param ids Array of product item IDs to delete
31
+ * @returns Number of product items deleted
32
+ */
33
+ export async function bulkDestroy(ids: number[]): Promise<number> {
34
+ if (!ids.length)
35
+ return 0
36
+
37
+ try {
38
+ // Perform the delete operation
39
+ const result = await db
40
+ .deleteFrom('products')
41
+ .where('id', 'in', ids)
42
+ .executeTakeFirst()
43
+
44
+ // Return the number of deleted rows
45
+ return Number(result.numDeletedRows)
46
+ }
47
+ catch (error) {
48
+ if (error instanceof Error) {
49
+ throw new TypeError(`Failed to delete product items: ${error.message}`)
50
+ }
51
+
52
+ throw error
53
+ }
54
+ }
@@ -0,0 +1,282 @@
1
+ type ProductJsonResponse = ModelRow<typeof Product>
2
+ import type { StacksExpressionBuilder } from '@stacksjs/database'
3
+ import { db, sql } from '@stacksjs/database'
4
+
5
+ /**
6
+ * Fetch a product item by ID
7
+ */
8
+ export async function fetchById(id: number): Promise<ProductJsonResponse | undefined> {
9
+ const model = await db
10
+ .selectFrom('products')
11
+ .where('id', '=', id)
12
+ .selectAll()
13
+ .executeTakeFirst()
14
+
15
+ if (model) {
16
+ let manufacturer = null
17
+ let category = null
18
+
19
+ if (model.manufacturer_id) {
20
+ manufacturer = await db
21
+ .selectFrom('manufacturers')
22
+ .where('id', '=', model.manufacturer_id)
23
+ .selectAll()
24
+ .executeTakeFirst()
25
+ }
26
+
27
+ if (model.category_id) {
28
+ category = await db
29
+ .selectFrom('categories')
30
+ .where('id', '=', model.category_id)
31
+ .selectAll()
32
+ .executeTakeFirst()
33
+ }
34
+
35
+ return {
36
+ ...model,
37
+ manufacturer,
38
+ category,
39
+ } as unknown as ProductJsonResponse
40
+ }
41
+
42
+ return undefined
43
+ }
44
+
45
+ /**
46
+ * Fetch all product items with their manufacturers and categories
47
+ */
48
+ export async function fetchAll(): Promise<ProductJsonResponse[]> {
49
+ // Fetch all products
50
+ const models = await db.selectFrom('products').selectAll().execute()
51
+
52
+ // Get the IDs of all manufacturers and categories
53
+ const manufacturerIds = models.map((model: any) => model.manufacturer_id).filter((id: any) => id !== null && id !== undefined)
54
+ const categoryIds = models.map((model: any) => model.category_id).filter((id: any) => id !== null && id !== undefined)
55
+
56
+ let manufacturersQuery = db.selectFrom('manufacturers') as any
57
+ let categoriesQuery = db.selectFrom('categories') as any
58
+
59
+ if (manufacturerIds.length > 0) {
60
+ manufacturersQuery = manufacturersQuery.where('id', 'in', manufacturerIds)
61
+ }
62
+
63
+ if (categoryIds.length > 0) {
64
+ categoriesQuery = categoriesQuery.where('id', 'in', categoryIds)
65
+ }
66
+
67
+ // Fetch manufacturers and categories for these specific IDs using WHERE IN
68
+ const allManufacturers = await manufacturersQuery.selectAll().execute()
69
+ const allCategories = await categoriesQuery.selectAll().execute()
70
+
71
+ // Group manufacturers and categories by ID
72
+ const manufacturersById = allManufacturers.reduce((acc: any, manufacturer: any) => {
73
+ acc[manufacturer.id] = manufacturer
74
+ return acc
75
+ }, {} as Record<number, typeof allManufacturers[0]>)
76
+
77
+ const categoriesById = allCategories.reduce((acc: any, category: any) => {
78
+ acc[category.id] = category
79
+ return acc
80
+ }, {} as Record<number, typeof allCategories[0]>)
81
+
82
+ // Attach manufacturers and categories to each product
83
+ return models.map((model: any) => ({
84
+ ...model,
85
+ manufacturer: model.manufacturer_id ? manufacturersById[model.manufacturer_id] : null,
86
+ category: model.category_id ? categoriesById[model.category_id] : null,
87
+ }))
88
+ }
89
+
90
+ /**
91
+ * Get products by manufacturer
92
+ *
93
+ * @param manufacturerId Manufacturer identifier
94
+ * @returns List of products for the specified manufacturer
95
+ */
96
+ export async function getProductsByManufacturer(manufacturerId: number): Promise<ProductJsonResponse[]> {
97
+ try {
98
+ const products = await db
99
+ .selectFrom('products')
100
+ .selectAll()
101
+ .where('manufacturer_id', '=', manufacturerId)
102
+ .orderBy('name')
103
+ .execute()
104
+
105
+ return products as ProductJsonResponse[]
106
+ }
107
+ catch (error) {
108
+ if (error instanceof Error) {
109
+ throw new TypeError(`Failed to get products by manufacturer: ${error.message}`)
110
+ }
111
+
112
+ throw error
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Get products by category
118
+ *
119
+ * @param categoryId Category identifier
120
+ * @returns List of products for the specified category
121
+ */
122
+ export async function getProductsByCategory(categoryId: number): Promise<ProductJsonResponse[]> {
123
+ try {
124
+ const products = await db
125
+ .selectFrom('products')
126
+ .selectAll()
127
+ .where('category_id', '=', categoryId)
128
+ .orderBy('name')
129
+ .execute()
130
+
131
+ return products as ProductJsonResponse[]
132
+ }
133
+ catch (error) {
134
+ if (error instanceof Error) {
135
+ throw new TypeError(`Failed to get products by category: ${error.message}`)
136
+ }
137
+
138
+ throw error
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Get product by UUID
144
+ *
145
+ * @param uuid Product UUID
146
+ * @returns Matching product or undefined
147
+ */
148
+ export async function getProductByUuid(uuid: string): Promise<ProductJsonResponse | undefined> {
149
+ try {
150
+ const product = await db
151
+ .selectFrom('products')
152
+ .selectAll()
153
+ .where('uuid', '=', uuid)
154
+ .executeTakeFirst()
155
+
156
+ return product as ProductJsonResponse | undefined
157
+ }
158
+ catch (error) {
159
+ if (error instanceof Error) {
160
+ throw new TypeError(`Failed to get product by UUID: ${error.message}`)
161
+ }
162
+
163
+ throw error
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Format product options for dropdown menus or selectors
169
+ *
170
+ * @returns Array of formatted product options with id, name, uuid, and price
171
+ */
172
+ export async function formatProductOptions(): Promise<{ id: number, name: string, uuid: string, price: number }[]> {
173
+ try {
174
+ const results = await db
175
+ .selectFrom('products')
176
+ .select(['id', 'name', 'uuid', 'price'])
177
+ .orderBy('name')
178
+ .execute()
179
+
180
+ // Filter out any results with undefined/null values to match the return type
181
+ return results.filter((result: any) =>
182
+ result.name !== null
183
+ && result.name !== undefined
184
+ && result.uuid !== null
185
+ && result.uuid !== undefined
186
+ && result.price !== null
187
+ && result.price !== undefined,
188
+ ) as { id: number, name: string, uuid: string, price: number }[]
189
+ }
190
+ catch (error) {
191
+ if (error instanceof Error) {
192
+ throw new TypeError(`Failed to format product options: ${error.message}`)
193
+ }
194
+
195
+ throw error
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Get products by price range
201
+ *
202
+ * @param minPrice Minimum price
203
+ * @param maxPrice Maximum price
204
+ * @returns List of products within the specified price range
205
+ */
206
+ export async function getProductsByPriceRange(minPrice: number, maxPrice: number): Promise<ProductJsonResponse[]> {
207
+ try {
208
+ const products = await db
209
+ .selectFrom('products')
210
+ .selectAll()
211
+ .where('price', '>=', minPrice)
212
+ .where('price', '<=', maxPrice)
213
+ .orderBy('price')
214
+ .execute()
215
+
216
+ return products as ProductJsonResponse[]
217
+ }
218
+ catch (error) {
219
+ if (error instanceof Error) {
220
+ throw new TypeError(`Failed to get products by price range: ${error.message}`)
221
+ }
222
+
223
+ throw error
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Top-selling products by units sold.
229
+ *
230
+ * Aggregates `order_items` (the line-item table — each row is one
231
+ * product on one order) grouping by product, summing quantity for the
232
+ * "units sold" rank and the line-item revenue (price × quantity) for
233
+ * the revenue side. Joins back to `products` so we can return the
234
+ * product name + current inventory inline; if a product was deleted
235
+ * after sales were recorded, that row is skipped (INNER JOIN).
236
+ *
237
+ * Revenue uses the price stored on `order_items` (price-at-sale)
238
+ * rather than the current `products.price`, so historical numbers
239
+ * stay correct even when prices change.
240
+ */
241
+ export interface TopProductRow {
242
+ id: number
243
+ name: string
244
+ units_sold: number
245
+ revenue: number
246
+ inventory_count: number
247
+ }
248
+
249
+ export async function fetchTopProducts(limit: number = 5): Promise<TopProductRow[]> {
250
+ try {
251
+ const rows = await db
252
+ .selectFrom('order_items')
253
+ .innerJoin('products', 'products.id', 'order_items.product_id')
254
+ .select([
255
+ 'products.id as id',
256
+ 'products.name as name',
257
+ 'products.inventory_count as inventory_count',
258
+ ((eb: StacksExpressionBuilder) => eb.fn.sum('order_items.quantity').as('units_sold')) as any,
259
+ // Multiply line-item price × quantity inline. Falls back to the
260
+ // current product price if order_items.price is null (older data).
261
+ sql<number>`SUM(COALESCE(order_items.price, products.price) * order_items.quantity)`.as('revenue') as any,
262
+ ])
263
+ .groupBy(['products.id', 'products.name', 'products.inventory_count'])
264
+ .orderBy('units_sold', 'desc')
265
+ .limit(limit)
266
+ .execute()
267
+
268
+ return rows.map((r: any) => ({
269
+ id: Number(r.id),
270
+ name: String(r.name ?? 'Unnamed product'),
271
+ units_sold: Number(r.units_sold ?? 0),
272
+ revenue: Number(r.revenue ?? 0),
273
+ inventory_count: Number(r.inventory_count ?? 0),
274
+ }))
275
+ }
276
+ catch (error) {
277
+ if (error instanceof Error) {
278
+ throw new TypeError(`Failed to fetch top products: ${error.message}`)
279
+ }
280
+ throw error
281
+ }
282
+ }
@@ -0,0 +1,26 @@
1
+ // Functions from destroy.ts
2
+ export {
3
+ bulkDestroy,
4
+ destroy,
5
+ } from './destroy'
6
+
7
+ // Functions from fetch.ts
8
+ export type { TopProductRow } from './fetch'
9
+ export {
10
+ fetchAll,
11
+ fetchById,
12
+ fetchTopProducts,
13
+ } from './fetch'
14
+
15
+ // Functions from store.ts
16
+ export {
17
+ bulkStore,
18
+ store,
19
+ } from './store'
20
+
21
+ // Functions from update.ts
22
+ export {
23
+ update,
24
+ updateAvailability,
25
+ updateInventory,
26
+ } from './update'
@@ -0,0 +1,84 @@
1
+ type ProductJsonResponse = ModelRow<typeof Product>
2
+ type NewProduct = NewModelData<typeof Product>
3
+ import { randomUUIDv7 } from 'bun'
4
+ import { db } from '@stacksjs/database'
5
+ import { fetchById } from './fetch'
6
+
7
+ /**
8
+ * Create a new product item
9
+ *
10
+ * @param data The product item data to store
11
+ * @returns The newly created product item record
12
+ */
13
+ export async function store(data: NewProduct): Promise<ProductJsonResponse> {
14
+ try {
15
+ const itemData = {
16
+ ...data,
17
+ uuid: randomUUIDv7(),
18
+ }
19
+
20
+ const result = await db
21
+ .insertInto('products')
22
+ .values(itemData)
23
+ .executeTakeFirst()
24
+
25
+ if (!result)
26
+ throw new Error('Failed to create product item')
27
+
28
+ const insertId = Number(result.insertId) || Number(result.numInsertedOrUpdatedRows)
29
+
30
+ const model = await fetchById(insertId)
31
+
32
+ if (!model)
33
+ throw new Error('Failed to create product item')
34
+
35
+ return model
36
+ }
37
+ catch (error) {
38
+ if (error instanceof Error) {
39
+ throw new TypeError(`Failed to create product item: ${error.message}`)
40
+ }
41
+
42
+ throw error
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Create multiple product items at once
48
+ *
49
+ * @param data Array of product item data to store
50
+ * @returns Number of product items created
51
+ */
52
+ export async function bulkStore(data: NewProduct[]): Promise<number> {
53
+ if (!data.length)
54
+ return 0
55
+
56
+ let createdCount = 0
57
+
58
+ try {
59
+ await (db as any).transaction().execute(async (trx: any) => {
60
+ for (const item of data) {
61
+ const itemData = {
62
+ ...item,
63
+ uuid: randomUUIDv7(),
64
+ }
65
+
66
+ await trx
67
+ .insertInto('products')
68
+ .values(itemData)
69
+ .execute()
70
+
71
+ createdCount++
72
+ }
73
+ })
74
+
75
+ return createdCount
76
+ }
77
+ catch (error) {
78
+ if (error instanceof Error) {
79
+ throw new TypeError(`Failed to create product items in bulk: ${error.message}`)
80
+ }
81
+
82
+ throw error
83
+ }
84
+ }