@autumnsgrove/groveengine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/README.md +163 -0
  2. package/dist/auth/jwt.d.ts +14 -0
  3. package/dist/auth/jwt.js +109 -0
  4. package/dist/auth/session.d.ts +42 -0
  5. package/dist/auth/session.js +105 -0
  6. package/dist/components/admin/GutterManager.svelte +910 -0
  7. package/dist/components/admin/GutterManager.svelte.d.ts +15 -0
  8. package/dist/components/admin/MarkdownEditor.svelte +3114 -0
  9. package/dist/components/admin/MarkdownEditor.svelte.d.ts +43 -0
  10. package/dist/components/custom/CollapsibleSection.svelte +74 -0
  11. package/dist/components/custom/CollapsibleSection.svelte.d.ts +15 -0
  12. package/dist/components/custom/ContentWithGutter.svelte +646 -0
  13. package/dist/components/custom/ContentWithGutter.svelte.d.ts +19 -0
  14. package/dist/components/custom/GutterItem.svelte +201 -0
  15. package/dist/components/custom/GutterItem.svelte.d.ts +11 -0
  16. package/dist/components/custom/LeftGutter.svelte +271 -0
  17. package/dist/components/custom/LeftGutter.svelte.d.ts +17 -0
  18. package/dist/components/custom/MobileTOC.svelte +273 -0
  19. package/dist/components/custom/MobileTOC.svelte.d.ts +11 -0
  20. package/dist/components/custom/TableOfContents.svelte +163 -0
  21. package/dist/components/custom/TableOfContents.svelte.d.ts +11 -0
  22. package/dist/components/gallery/ImageGallery.svelte +681 -0
  23. package/dist/components/gallery/ImageGallery.svelte.d.ts +11 -0
  24. package/dist/components/gallery/Lightbox.svelte +107 -0
  25. package/dist/components/gallery/Lightbox.svelte.d.ts +19 -0
  26. package/dist/components/gallery/LightboxCaption.svelte +25 -0
  27. package/dist/components/gallery/LightboxCaption.svelte.d.ts +11 -0
  28. package/dist/components/gallery/ZoomableImage.svelte +163 -0
  29. package/dist/components/gallery/ZoomableImage.svelte.d.ts +17 -0
  30. package/dist/components/ui/Accordion.svelte +74 -0
  31. package/dist/components/ui/Accordion.svelte.d.ts +42 -0
  32. package/dist/components/ui/Badge.svelte +48 -0
  33. package/dist/components/ui/Badge.svelte.d.ts +26 -0
  34. package/dist/components/ui/Button.svelte +74 -0
  35. package/dist/components/ui/Button.svelte.d.ts +34 -0
  36. package/dist/components/ui/Card.svelte +102 -0
  37. package/dist/components/ui/Card.svelte.d.ts +46 -0
  38. package/dist/components/ui/Dialog.svelte +91 -0
  39. package/dist/components/ui/Dialog.svelte.d.ts +43 -0
  40. package/dist/components/ui/Input.svelte +81 -0
  41. package/dist/components/ui/Input.svelte.d.ts +35 -0
  42. package/dist/components/ui/Select.svelte +69 -0
  43. package/dist/components/ui/Select.svelte.d.ts +36 -0
  44. package/dist/components/ui/Sheet.svelte +98 -0
  45. package/dist/components/ui/Sheet.svelte.d.ts +45 -0
  46. package/dist/components/ui/Skeleton.svelte +31 -0
  47. package/dist/components/ui/Skeleton.svelte.d.ts +26 -0
  48. package/dist/components/ui/Table.svelte +59 -0
  49. package/dist/components/ui/Table.svelte.d.ts +44 -0
  50. package/dist/components/ui/Tabs.svelte +76 -0
  51. package/dist/components/ui/Tabs.svelte.d.ts +41 -0
  52. package/dist/components/ui/Textarea.svelte +81 -0
  53. package/dist/components/ui/Textarea.svelte.d.ts +35 -0
  54. package/dist/components/ui/Toast.svelte +18 -0
  55. package/dist/components/ui/Toast.svelte.d.ts +7 -0
  56. package/dist/components/ui/accordion/accordion-content.svelte +24 -0
  57. package/dist/components/ui/accordion/accordion-content.svelte.d.ts +4 -0
  58. package/dist/components/ui/accordion/accordion-item.svelte +12 -0
  59. package/dist/components/ui/accordion/accordion-item.svelte.d.ts +4 -0
  60. package/dist/components/ui/accordion/accordion-trigger.svelte +29 -0
  61. package/dist/components/ui/accordion/accordion-trigger.svelte.d.ts +7 -0
  62. package/dist/components/ui/accordion/index.d.ts +6 -0
  63. package/dist/components/ui/accordion/index.js +8 -0
  64. package/dist/components/ui/badge/badge.svelte +50 -0
  65. package/dist/components/ui/badge/badge.svelte.d.ts +60 -0
  66. package/dist/components/ui/badge/index.d.ts +2 -0
  67. package/dist/components/ui/badge/index.js +2 -0
  68. package/dist/components/ui/button/button.svelte +82 -0
  69. package/dist/components/ui/button/button.svelte.d.ts +132 -0
  70. package/dist/components/ui/button/index.d.ts +2 -0
  71. package/dist/components/ui/button/index.js +4 -0
  72. package/dist/components/ui/card/card-content.svelte +16 -0
  73. package/dist/components/ui/card/card-content.svelte.d.ts +5 -0
  74. package/dist/components/ui/card/card-description.svelte +16 -0
  75. package/dist/components/ui/card/card-description.svelte.d.ts +5 -0
  76. package/dist/components/ui/card/card-footer.svelte +16 -0
  77. package/dist/components/ui/card/card-footer.svelte.d.ts +5 -0
  78. package/dist/components/ui/card/card-header.svelte +16 -0
  79. package/dist/components/ui/card/card-header.svelte.d.ts +5 -0
  80. package/dist/components/ui/card/card-title.svelte +25 -0
  81. package/dist/components/ui/card/card-title.svelte.d.ts +8 -0
  82. package/dist/components/ui/card/card.svelte +20 -0
  83. package/dist/components/ui/card/card.svelte.d.ts +5 -0
  84. package/dist/components/ui/card/index.d.ts +7 -0
  85. package/dist/components/ui/card/index.js +9 -0
  86. package/dist/components/ui/dialog/dialog-content.svelte +38 -0
  87. package/dist/components/ui/dialog/dialog-content.svelte.d.ts +9 -0
  88. package/dist/components/ui/dialog/dialog-description.svelte +16 -0
  89. package/dist/components/ui/dialog/dialog-description.svelte.d.ts +4 -0
  90. package/dist/components/ui/dialog/dialog-footer.svelte +20 -0
  91. package/dist/components/ui/dialog/dialog-footer.svelte.d.ts +5 -0
  92. package/dist/components/ui/dialog/dialog-header.svelte +20 -0
  93. package/dist/components/ui/dialog/dialog-header.svelte.d.ts +5 -0
  94. package/dist/components/ui/dialog/dialog-overlay.svelte +19 -0
  95. package/dist/components/ui/dialog/dialog-overlay.svelte.d.ts +4 -0
  96. package/dist/components/ui/dialog/dialog-title.svelte +16 -0
  97. package/dist/components/ui/dialog/dialog-title.svelte.d.ts +4 -0
  98. package/dist/components/ui/dialog/index.d.ts +12 -0
  99. package/dist/components/ui/dialog/index.js +14 -0
  100. package/dist/components/ui/index.d.ts +26 -0
  101. package/dist/components/ui/index.js +29 -0
  102. package/dist/components/ui/input/index.d.ts +2 -0
  103. package/dist/components/ui/input/index.js +4 -0
  104. package/dist/components/ui/input/input.svelte +46 -0
  105. package/dist/components/ui/input/input.svelte.d.ts +13 -0
  106. package/dist/components/ui/select/index.d.ts +11 -0
  107. package/dist/components/ui/select/index.js +13 -0
  108. package/dist/components/ui/select/select-content.svelte +39 -0
  109. package/dist/components/ui/select/select-content.svelte.d.ts +7 -0
  110. package/dist/components/ui/select/select-group-heading.svelte +16 -0
  111. package/dist/components/ui/select/select-group-heading.svelte.d.ts +4 -0
  112. package/dist/components/ui/select/select-item.svelte +37 -0
  113. package/dist/components/ui/select/select-item.svelte.d.ts +4 -0
  114. package/dist/components/ui/select/select-scroll-down-button.svelte +19 -0
  115. package/dist/components/ui/select/select-scroll-down-button.svelte.d.ts +4 -0
  116. package/dist/components/ui/select/select-scroll-up-button.svelte +19 -0
  117. package/dist/components/ui/select/select-scroll-up-button.svelte.d.ts +4 -0
  118. package/dist/components/ui/select/select-separator.svelte +13 -0
  119. package/dist/components/ui/select/select-separator.svelte.d.ts +4 -0
  120. package/dist/components/ui/select/select-trigger.svelte +24 -0
  121. package/dist/components/ui/select/select-trigger.svelte.d.ts +4 -0
  122. package/dist/components/ui/separator/index.d.ts +2 -0
  123. package/dist/components/ui/separator/index.js +4 -0
  124. package/dist/components/ui/separator/separator.svelte +22 -0
  125. package/dist/components/ui/separator/separator.svelte.d.ts +4 -0
  126. package/dist/components/ui/sheet/index.d.ts +12 -0
  127. package/dist/components/ui/sheet/index.js +14 -0
  128. package/dist/components/ui/sheet/sheet-content.svelte +53 -0
  129. package/dist/components/ui/sheet/sheet-content.svelte.d.ts +62 -0
  130. package/dist/components/ui/sheet/sheet-description.svelte +16 -0
  131. package/dist/components/ui/sheet/sheet-description.svelte.d.ts +4 -0
  132. package/dist/components/ui/sheet/sheet-footer.svelte +20 -0
  133. package/dist/components/ui/sheet/sheet-footer.svelte.d.ts +5 -0
  134. package/dist/components/ui/sheet/sheet-header.svelte +20 -0
  135. package/dist/components/ui/sheet/sheet-header.svelte.d.ts +5 -0
  136. package/dist/components/ui/sheet/sheet-overlay.svelte +21 -0
  137. package/dist/components/ui/sheet/sheet-overlay.svelte.d.ts +6 -0
  138. package/dist/components/ui/sheet/sheet-title.svelte +16 -0
  139. package/dist/components/ui/sheet/sheet-title.svelte.d.ts +4 -0
  140. package/dist/components/ui/skeleton/index.d.ts +2 -0
  141. package/dist/components/ui/skeleton/index.js +4 -0
  142. package/dist/components/ui/skeleton/skeleton.svelte +17 -0
  143. package/dist/components/ui/skeleton/skeleton.svelte.d.ts +5 -0
  144. package/dist/components/ui/table/index.d.ts +9 -0
  145. package/dist/components/ui/table/index.js +11 -0
  146. package/dist/components/ui/table/table-body.svelte +16 -0
  147. package/dist/components/ui/table/table-body.svelte.d.ts +5 -0
  148. package/dist/components/ui/table/table-caption.svelte +16 -0
  149. package/dist/components/ui/table/table-caption.svelte.d.ts +5 -0
  150. package/dist/components/ui/table/table-cell.svelte +20 -0
  151. package/dist/components/ui/table/table-cell.svelte.d.ts +5 -0
  152. package/dist/components/ui/table/table-footer.svelte +16 -0
  153. package/dist/components/ui/table/table-footer.svelte.d.ts +5 -0
  154. package/dist/components/ui/table/table-head.svelte +23 -0
  155. package/dist/components/ui/table/table-head.svelte.d.ts +5 -0
  156. package/dist/components/ui/table/table-header.svelte +16 -0
  157. package/dist/components/ui/table/table-header.svelte.d.ts +5 -0
  158. package/dist/components/ui/table/table-row.svelte +23 -0
  159. package/dist/components/ui/table/table-row.svelte.d.ts +5 -0
  160. package/dist/components/ui/table/table.svelte +18 -0
  161. package/dist/components/ui/table/table.svelte.d.ts +5 -0
  162. package/dist/components/ui/tabs/index.d.ts +6 -0
  163. package/dist/components/ui/tabs/index.js +8 -0
  164. package/dist/components/ui/tabs/tabs-content.svelte +19 -0
  165. package/dist/components/ui/tabs/tabs-content.svelte.d.ts +4 -0
  166. package/dist/components/ui/tabs/tabs-list.svelte +19 -0
  167. package/dist/components/ui/tabs/tabs-list.svelte.d.ts +4 -0
  168. package/dist/components/ui/tabs/tabs-trigger.svelte +19 -0
  169. package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
  170. package/dist/components/ui/textarea/index.d.ts +2 -0
  171. package/dist/components/ui/textarea/index.js +4 -0
  172. package/dist/components/ui/textarea/textarea.svelte +24 -0
  173. package/dist/components/ui/textarea/textarea.svelte.d.ts +6 -0
  174. package/dist/components/ui/toast.d.ts +86 -0
  175. package/dist/components/ui/toast.js +99 -0
  176. package/dist/db/schema.sql +238 -0
  177. package/dist/index.d.ts +14 -0
  178. package/dist/index.js +20 -0
  179. package/dist/payments/index.d.ts +33 -0
  180. package/dist/payments/index.js +47 -0
  181. package/dist/payments/shop.d.ts +165 -0
  182. package/dist/payments/shop.js +588 -0
  183. package/dist/payments/stripe/client.d.ts +231 -0
  184. package/dist/payments/stripe/client.js +198 -0
  185. package/dist/payments/stripe/index.d.ts +18 -0
  186. package/dist/payments/stripe/index.js +17 -0
  187. package/dist/payments/stripe/provider.d.ts +50 -0
  188. package/dist/payments/stripe/provider.js +530 -0
  189. package/dist/payments/types.d.ts +355 -0
  190. package/dist/payments/types.js +7 -0
  191. package/dist/server/logger.d.ts +53 -0
  192. package/dist/server/logger.js +252 -0
  193. package/dist/styles/content.css +514 -0
  194. package/dist/styles/tokens.css +175 -0
  195. package/dist/utils/api.d.ts +20 -0
  196. package/dist/utils/api.js +109 -0
  197. package/dist/utils/cn.d.ts +15 -0
  198. package/dist/utils/cn.js +18 -0
  199. package/dist/utils/csrf.d.ts +22 -0
  200. package/dist/utils/csrf.js +72 -0
  201. package/dist/utils/debounce.d.ts +7 -0
  202. package/dist/utils/debounce.js +14 -0
  203. package/dist/utils/gallery.d.ts +66 -0
  204. package/dist/utils/gallery.js +181 -0
  205. package/dist/utils/gutter.d.ts +54 -0
  206. package/dist/utils/gutter.js +169 -0
  207. package/dist/utils/imageProcessor.d.ts +58 -0
  208. package/dist/utils/imageProcessor.js +205 -0
  209. package/dist/utils/json.d.ts +17 -0
  210. package/dist/utils/json.js +26 -0
  211. package/dist/utils/markdown.d.ts +101 -0
  212. package/dist/utils/markdown.js +947 -0
  213. package/dist/utils/sanitize.d.ts +25 -0
  214. package/dist/utils/sanitize.js +127 -0
  215. package/dist/utils/validation.d.ts +46 -0
  216. package/dist/utils/validation.js +169 -0
  217. package/dist/utils.d.ts +5 -0
  218. package/dist/utils.js +5 -0
  219. package/package.json +129 -0
@@ -0,0 +1,588 @@
1
+ /**
2
+ * Shop Database Operations
3
+ *
4
+ * Utilities for managing products, orders, and customers in D1.
5
+ */
6
+ // =============================================================================
7
+ // PRODUCT OPERATIONS
8
+ // =============================================================================
9
+ export async function getProducts(db, tenantId, options = {}) {
10
+ let query = `
11
+ SELECT * FROM products
12
+ WHERE tenant_id = ?
13
+ `;
14
+ const params = [tenantId];
15
+ if (options.status) {
16
+ query += ' AND status = ?';
17
+ params.push(options.status);
18
+ }
19
+ if (options.type) {
20
+ query += ' AND type = ?';
21
+ params.push(options.type);
22
+ }
23
+ if (options.category) {
24
+ query += ' AND category = ?';
25
+ params.push(options.category);
26
+ }
27
+ query += ' ORDER BY created_at DESC';
28
+ if (options.limit) {
29
+ query += ' LIMIT ?';
30
+ params.push(options.limit);
31
+ }
32
+ if (options.offset) {
33
+ query += ' OFFSET ?';
34
+ params.push(options.offset);
35
+ }
36
+ const result = await db.prepare(query).bind(...params).all();
37
+ const products = result.results.map(mapProductRow);
38
+ // Fetch variants for each product
39
+ for (const product of products) {
40
+ const variants = await getProductVariants(db, product.id);
41
+ product.variants = variants;
42
+ product.defaultVariantId = variants.find(v => v.isDefault)?.id;
43
+ }
44
+ return products;
45
+ }
46
+ export async function getProductBySlug(db, tenantId, slug) {
47
+ const row = await db
48
+ .prepare('SELECT * FROM products WHERE tenant_id = ? AND slug = ?')
49
+ .bind(tenantId, slug)
50
+ .first();
51
+ if (!row)
52
+ return null;
53
+ const product = mapProductRow(row);
54
+ const variants = await getProductVariants(db, product.id);
55
+ return {
56
+ ...product,
57
+ variants,
58
+ defaultVariantId: variants.find(v => v.isDefault)?.id,
59
+ };
60
+ }
61
+ export async function getProductById(db, productId) {
62
+ const row = await db
63
+ .prepare('SELECT * FROM products WHERE id = ?')
64
+ .bind(productId)
65
+ .first();
66
+ if (!row)
67
+ return null;
68
+ const product = mapProductRow(row);
69
+ const variants = await getProductVariants(db, product.id);
70
+ return {
71
+ ...product,
72
+ variants,
73
+ defaultVariantId: variants.find(v => v.isDefault)?.id,
74
+ };
75
+ }
76
+ export async function createProduct(db, tenantId, data) {
77
+ const id = crypto.randomUUID();
78
+ const now = Math.floor(Date.now() / 1000);
79
+ await db
80
+ .prepare(`INSERT INTO products (
81
+ id, tenant_id, name, slug, description, short_description,
82
+ type, status, images, featured_image, category, tags, metadata,
83
+ created_at, updated_at
84
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
85
+ .bind(id, tenantId, data.name, data.slug, data.description || null, data.shortDescription || null, data.type || 'physical', data.status || 'draft', JSON.stringify(data.images || []), data.featuredImage || null, data.category || null, JSON.stringify(data.tags || []), JSON.stringify(data.metadata || {}), now, now)
86
+ .run();
87
+ return { id };
88
+ }
89
+ export async function updateProduct(db, productId, data) {
90
+ const updates = [];
91
+ const params = [];
92
+ if (data.name !== undefined) {
93
+ updates.push('name = ?');
94
+ params.push(data.name);
95
+ }
96
+ if (data.slug !== undefined) {
97
+ updates.push('slug = ?');
98
+ params.push(data.slug);
99
+ }
100
+ if (data.description !== undefined) {
101
+ updates.push('description = ?');
102
+ params.push(data.description);
103
+ }
104
+ if (data.shortDescription !== undefined) {
105
+ updates.push('short_description = ?');
106
+ params.push(data.shortDescription);
107
+ }
108
+ if (data.type !== undefined) {
109
+ updates.push('type = ?');
110
+ params.push(data.type);
111
+ }
112
+ if (data.status !== undefined) {
113
+ updates.push('status = ?');
114
+ params.push(data.status);
115
+ }
116
+ if (data.images !== undefined) {
117
+ updates.push('images = ?');
118
+ params.push(JSON.stringify(data.images));
119
+ }
120
+ if (data.featuredImage !== undefined) {
121
+ updates.push('featured_image = ?');
122
+ params.push(data.featuredImage);
123
+ }
124
+ if (data.category !== undefined) {
125
+ updates.push('category = ?');
126
+ params.push(data.category);
127
+ }
128
+ if (data.tags !== undefined) {
129
+ updates.push('tags = ?');
130
+ params.push(JSON.stringify(data.tags));
131
+ }
132
+ if (data.providerProductId !== undefined) {
133
+ updates.push('provider_product_id = ?');
134
+ params.push(data.providerProductId);
135
+ }
136
+ if (data.metadata !== undefined) {
137
+ updates.push('metadata = ?');
138
+ params.push(JSON.stringify(data.metadata));
139
+ }
140
+ if (updates.length === 0)
141
+ return;
142
+ updates.push('updated_at = ?');
143
+ params.push(Math.floor(Date.now() / 1000));
144
+ params.push(productId);
145
+ await db
146
+ .prepare(`UPDATE products SET ${updates.join(', ')} WHERE id = ?`)
147
+ .bind(...params)
148
+ .run();
149
+ }
150
+ export async function deleteProduct(db, productId) {
151
+ await db.prepare('DELETE FROM products WHERE id = ?').bind(productId).run();
152
+ }
153
+ // =============================================================================
154
+ // VARIANT OPERATIONS
155
+ // =============================================================================
156
+ export async function getProductVariants(db, productId) {
157
+ const result = await db
158
+ .prepare('SELECT * FROM product_variants WHERE product_id = ? ORDER BY position')
159
+ .bind(productId)
160
+ .all();
161
+ return result.results.map(mapVariantRow);
162
+ }
163
+ export async function getVariantById(db, variantId) {
164
+ const row = await db
165
+ .prepare('SELECT * FROM product_variants WHERE id = ?')
166
+ .bind(variantId)
167
+ .first();
168
+ return row ? mapVariantRow(row) : null;
169
+ }
170
+ export async function createVariant(db, productId, tenantId, data) {
171
+ const id = crypto.randomUUID();
172
+ const now = Math.floor(Date.now() / 1000);
173
+ await db
174
+ .prepare(`INSERT INTO product_variants (
175
+ id, product_id, tenant_id, name, sku,
176
+ price_amount, price_currency, compare_at_price,
177
+ pricing_type, billing_interval, billing_interval_count,
178
+ inventory_quantity, inventory_policy, track_inventory,
179
+ download_url, download_limit, requires_shipping,
180
+ is_default, position, metadata, created_at, updated_at
181
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
182
+ .bind(id, productId, tenantId, data.name, data.sku || null, data.priceAmount, data.priceCurrency || 'usd', data.compareAtPrice || null, data.pricingType || 'one_time', data.billingInterval || null, data.billingIntervalCount || 1, data.inventoryQuantity ?? null, data.inventoryPolicy || 'deny', data.trackInventory ? 1 : 0, data.downloadUrl || null, data.downloadLimit || null, data.requiresShipping ? 1 : 0, data.isDefault ? 1 : 0, data.position ?? 0, JSON.stringify(data.metadata || {}), now, now)
183
+ .run();
184
+ return { id };
185
+ }
186
+ export async function updateVariant(db, variantId, data) {
187
+ const updates = [];
188
+ const params = [];
189
+ if (data.name !== undefined) {
190
+ updates.push('name = ?');
191
+ params.push(data.name);
192
+ }
193
+ if (data.sku !== undefined) {
194
+ updates.push('sku = ?');
195
+ params.push(data.sku);
196
+ }
197
+ if (data.priceAmount !== undefined) {
198
+ updates.push('price_amount = ?');
199
+ params.push(data.priceAmount);
200
+ }
201
+ if (data.compareAtPrice !== undefined) {
202
+ updates.push('compare_at_price = ?');
203
+ params.push(data.compareAtPrice);
204
+ }
205
+ if (data.pricingType !== undefined) {
206
+ updates.push('pricing_type = ?');
207
+ params.push(data.pricingType);
208
+ }
209
+ if (data.billingInterval !== undefined) {
210
+ updates.push('billing_interval = ?');
211
+ params.push(data.billingInterval);
212
+ }
213
+ if (data.billingIntervalCount !== undefined) {
214
+ updates.push('billing_interval_count = ?');
215
+ params.push(data.billingIntervalCount);
216
+ }
217
+ if (data.inventoryQuantity !== undefined) {
218
+ updates.push('inventory_quantity = ?');
219
+ params.push(data.inventoryQuantity);
220
+ }
221
+ if (data.inventoryPolicy !== undefined) {
222
+ updates.push('inventory_policy = ?');
223
+ params.push(data.inventoryPolicy);
224
+ }
225
+ if (data.trackInventory !== undefined) {
226
+ updates.push('track_inventory = ?');
227
+ params.push(data.trackInventory ? 1 : 0);
228
+ }
229
+ if (data.downloadUrl !== undefined) {
230
+ updates.push('download_url = ?');
231
+ params.push(data.downloadUrl);
232
+ }
233
+ if (data.downloadLimit !== undefined) {
234
+ updates.push('download_limit = ?');
235
+ params.push(data.downloadLimit);
236
+ }
237
+ if (data.requiresShipping !== undefined) {
238
+ updates.push('requires_shipping = ?');
239
+ params.push(data.requiresShipping ? 1 : 0);
240
+ }
241
+ if (data.providerPriceId !== undefined) {
242
+ updates.push('provider_price_id = ?');
243
+ params.push(data.providerPriceId);
244
+ }
245
+ if (data.isDefault !== undefined) {
246
+ updates.push('is_default = ?');
247
+ params.push(data.isDefault ? 1 : 0);
248
+ }
249
+ if (data.position !== undefined) {
250
+ updates.push('position = ?');
251
+ params.push(data.position);
252
+ }
253
+ if (data.metadata !== undefined) {
254
+ updates.push('metadata = ?');
255
+ params.push(JSON.stringify(data.metadata));
256
+ }
257
+ if (updates.length === 0)
258
+ return;
259
+ updates.push('updated_at = ?');
260
+ params.push(Math.floor(Date.now() / 1000));
261
+ params.push(variantId);
262
+ await db
263
+ .prepare(`UPDATE product_variants SET ${updates.join(', ')} WHERE id = ?`)
264
+ .bind(...params)
265
+ .run();
266
+ }
267
+ export async function deleteVariant(db, variantId) {
268
+ await db
269
+ .prepare('DELETE FROM product_variants WHERE id = ?')
270
+ .bind(variantId)
271
+ .run();
272
+ }
273
+ // =============================================================================
274
+ // ORDER OPERATIONS
275
+ // =============================================================================
276
+ export async function generateOrderNumber(db, tenantId) {
277
+ // Get the count of orders for this tenant
278
+ const result = await db
279
+ .prepare('SELECT COUNT(*) as count FROM orders WHERE tenant_id = ?')
280
+ .bind(tenantId)
281
+ .first();
282
+ const count = (result?.count || 0) + 1;
283
+ return `GRV-${count.toString().padStart(4, '0')}`;
284
+ }
285
+ export async function createOrder(db, tenantId, data) {
286
+ const id = crypto.randomUUID();
287
+ const orderNumber = await generateOrderNumber(db, tenantId);
288
+ const now = Math.floor(Date.now() / 1000);
289
+ const requiresShipping = data.lineItems.some(item => item.requiresShipping);
290
+ // Insert order
291
+ await db
292
+ .prepare(`INSERT INTO orders (
293
+ id, tenant_id, order_number, customer_id, customer_email, customer_name,
294
+ subtotal, tax_total, shipping_total, discount_total, total, currency,
295
+ status, payment_status, shipping_address, billing_address, requires_shipping,
296
+ provider_session_id, customer_notes, metadata, created_at, updated_at
297
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
298
+ .bind(id, tenantId, orderNumber, data.customerId || null, data.customerEmail, data.customerName || null, data.subtotal, data.taxTotal || 0, data.shippingTotal || 0, data.discountTotal || 0, data.total, data.currency || 'usd', 'pending', 'pending', data.shippingAddress ? JSON.stringify(data.shippingAddress) : null, data.billingAddress ? JSON.stringify(data.billingAddress) : null, requiresShipping ? 1 : 0, data.providerSessionId || null, data.customerNotes || null, JSON.stringify(data.metadata || {}), now, now)
299
+ .run();
300
+ // Insert line items
301
+ for (const item of data.lineItems) {
302
+ const lineItemId = crypto.randomUUID();
303
+ await db
304
+ .prepare(`INSERT INTO order_line_items (
305
+ id, order_id, tenant_id, product_id, variant_id, product_name, variant_name,
306
+ sku, quantity, unit_price, total_price, tax_amount, type, requires_shipping,
307
+ metadata, created_at
308
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
309
+ .bind(lineItemId, id, tenantId, item.productId || null, item.variantId || null, item.productName, item.variantName, item.sku || null, item.quantity, item.unitPrice, item.unitPrice * item.quantity, item.taxAmount || 0, 'product', item.requiresShipping ? 1 : 0, '{}', now)
310
+ .run();
311
+ }
312
+ return { id, orderNumber };
313
+ }
314
+ export async function getOrderById(db, orderId) {
315
+ const row = await db
316
+ .prepare('SELECT * FROM orders WHERE id = ?')
317
+ .bind(orderId)
318
+ .first();
319
+ if (!row)
320
+ return null;
321
+ const lineItemRows = await db
322
+ .prepare('SELECT * FROM order_line_items WHERE order_id = ?')
323
+ .bind(orderId)
324
+ .all();
325
+ return mapOrderRow(row, lineItemRows.results);
326
+ }
327
+ export async function getOrderBySessionId(db, sessionId) {
328
+ const row = await db
329
+ .prepare('SELECT * FROM orders WHERE provider_session_id = ?')
330
+ .bind(sessionId)
331
+ .first();
332
+ if (!row)
333
+ return null;
334
+ const lineItemRows = await db
335
+ .prepare('SELECT * FROM order_line_items WHERE order_id = ?')
336
+ .bind(row.id)
337
+ .all();
338
+ return mapOrderRow(row, lineItemRows.results);
339
+ }
340
+ export async function updateOrderStatus(db, orderId, data) {
341
+ const updates = [];
342
+ const params = [];
343
+ if (data.status !== undefined) {
344
+ updates.push('status = ?');
345
+ params.push(data.status);
346
+ }
347
+ if (data.paymentStatus !== undefined) {
348
+ updates.push('payment_status = ?');
349
+ params.push(data.paymentStatus);
350
+ }
351
+ if (data.providerPaymentId !== undefined) {
352
+ updates.push('provider_payment_id = ?');
353
+ params.push(data.providerPaymentId);
354
+ }
355
+ if (data.paidAt !== undefined) {
356
+ updates.push('paid_at = ?');
357
+ params.push(data.paidAt);
358
+ }
359
+ if (updates.length === 0)
360
+ return;
361
+ updates.push('updated_at = ?');
362
+ params.push(Math.floor(Date.now() / 1000));
363
+ params.push(orderId);
364
+ await db
365
+ .prepare(`UPDATE orders SET ${updates.join(', ')} WHERE id = ?`)
366
+ .bind(...params)
367
+ .run();
368
+ }
369
+ export async function getOrders(db, tenantId, options = {}) {
370
+ let query = 'SELECT * FROM orders WHERE tenant_id = ?';
371
+ const params = [tenantId];
372
+ if (options.status) {
373
+ query += ' AND status = ?';
374
+ params.push(options.status);
375
+ }
376
+ if (options.paymentStatus) {
377
+ query += ' AND payment_status = ?';
378
+ params.push(options.paymentStatus);
379
+ }
380
+ query += ' ORDER BY created_at DESC';
381
+ if (options.limit) {
382
+ query += ' LIMIT ?';
383
+ params.push(options.limit);
384
+ }
385
+ if (options.offset) {
386
+ query += ' OFFSET ?';
387
+ params.push(options.offset);
388
+ }
389
+ const result = await db.prepare(query).bind(...params).all();
390
+ const orders = [];
391
+ for (const row of result.results) {
392
+ const lineItemRows = await db
393
+ .prepare('SELECT * FROM order_line_items WHERE order_id = ?')
394
+ .bind(row.id)
395
+ .all();
396
+ orders.push(mapOrderRow(row, lineItemRows.results));
397
+ }
398
+ return orders;
399
+ }
400
+ // =============================================================================
401
+ // CUSTOMER OPERATIONS
402
+ // =============================================================================
403
+ export async function getOrCreateCustomer(db, tenantId, email, data) {
404
+ // Try to find existing customer
405
+ const existing = await db
406
+ .prepare('SELECT * FROM customers WHERE tenant_id = ? AND email = ?')
407
+ .bind(tenantId, email)
408
+ .first();
409
+ if (existing) {
410
+ return mapCustomerRow(existing);
411
+ }
412
+ // Create new customer
413
+ const id = crypto.randomUUID();
414
+ const now = Math.floor(Date.now() / 1000);
415
+ await db
416
+ .prepare(`INSERT INTO customers (
417
+ id, tenant_id, email, name, phone, metadata, created_at, updated_at
418
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
419
+ .bind(id, tenantId, email, data?.name || null, data?.phone || null, '{}', now, now)
420
+ .run();
421
+ return {
422
+ id,
423
+ tenantId,
424
+ email,
425
+ name: data?.name,
426
+ phone: data?.phone,
427
+ metadata: {},
428
+ createdAt: new Date(now * 1000),
429
+ updatedAt: new Date(now * 1000),
430
+ };
431
+ }
432
+ export async function updateCustomer(db, customerId, data) {
433
+ const updates = [];
434
+ const params = [];
435
+ if (data.name !== undefined) {
436
+ updates.push('name = ?');
437
+ params.push(data.name);
438
+ }
439
+ if (data.phone !== undefined) {
440
+ updates.push('phone = ?');
441
+ params.push(data.phone);
442
+ }
443
+ if (data.providerCustomerId !== undefined) {
444
+ updates.push('provider_customer_id = ?');
445
+ params.push(data.providerCustomerId);
446
+ }
447
+ if (data.defaultShippingAddress !== undefined) {
448
+ updates.push('default_shipping_address = ?');
449
+ params.push(JSON.stringify(data.defaultShippingAddress));
450
+ }
451
+ if (data.defaultBillingAddress !== undefined) {
452
+ updates.push('default_billing_address = ?');
453
+ params.push(JSON.stringify(data.defaultBillingAddress));
454
+ }
455
+ if (data.totalOrders !== undefined) {
456
+ updates.push('total_orders = ?');
457
+ params.push(data.totalOrders);
458
+ }
459
+ if (data.totalSpent !== undefined) {
460
+ updates.push('total_spent = ?');
461
+ params.push(data.totalSpent);
462
+ }
463
+ if (updates.length === 0)
464
+ return;
465
+ updates.push('updated_at = ?');
466
+ params.push(Math.floor(Date.now() / 1000));
467
+ params.push(customerId);
468
+ await db
469
+ .prepare(`UPDATE customers SET ${updates.join(', ')} WHERE id = ?`)
470
+ .bind(...params)
471
+ .run();
472
+ }
473
+ // =============================================================================
474
+ // ROW MAPPERS
475
+ // =============================================================================
476
+ function mapProductRow(row) {
477
+ return {
478
+ id: row.id,
479
+ tenantId: row.tenant_id,
480
+ name: row.name,
481
+ description: row.description || undefined,
482
+ type: row.type,
483
+ status: row.status,
484
+ images: JSON.parse(row.images || '[]'),
485
+ metadata: JSON.parse(row.metadata || '{}'),
486
+ createdAt: new Date(row.created_at * 1000),
487
+ updatedAt: new Date(row.updated_at * 1000),
488
+ };
489
+ }
490
+ function mapVariantRow(row) {
491
+ return {
492
+ id: row.id,
493
+ productId: row.product_id,
494
+ name: row.name,
495
+ sku: row.sku || undefined,
496
+ price: {
497
+ amount: row.price_amount,
498
+ currency: row.price_currency,
499
+ },
500
+ compareAtPrice: row.compare_at_price
501
+ ? { amount: row.compare_at_price, currency: row.price_currency }
502
+ : undefined,
503
+ pricingType: row.pricing_type,
504
+ recurring: row.billing_interval && row.pricing_type === 'recurring'
505
+ ? {
506
+ interval: row.billing_interval,
507
+ intervalCount: row.billing_interval_count || 1,
508
+ }
509
+ : undefined,
510
+ inventoryQuantity: row.inventory_quantity ?? undefined,
511
+ inventoryPolicy: row.inventory_policy,
512
+ downloadUrl: row.download_url || undefined,
513
+ downloadLimit: row.download_limit ?? undefined,
514
+ providerPriceId: row.provider_price_id || undefined,
515
+ isDefault: row.is_default === 1,
516
+ position: row.position,
517
+ createdAt: new Date(row.created_at * 1000),
518
+ updatedAt: new Date(row.updated_at * 1000),
519
+ };
520
+ }
521
+ function mapOrderRow(row, lineItemRows) {
522
+ return {
523
+ id: row.id,
524
+ tenantId: row.tenant_id,
525
+ customerId: row.customer_id || undefined,
526
+ customerEmail: row.customer_email,
527
+ lineItems: lineItemRows.map(mapLineItemRow),
528
+ subtotal: { amount: row.subtotal, currency: row.currency },
529
+ taxTotal: { amount: row.tax_total, currency: row.currency },
530
+ shippingTotal: { amount: row.shipping_total, currency: row.currency },
531
+ discountTotal: { amount: row.discount_total, currency: row.currency },
532
+ total: { amount: row.total, currency: row.currency },
533
+ status: row.status,
534
+ paymentStatus: row.payment_status,
535
+ providerOrderId: row.provider_payment_id || undefined,
536
+ providerSessionId: row.provider_session_id || undefined,
537
+ shippingAddress: row.shipping_address
538
+ ? JSON.parse(row.shipping_address)
539
+ : undefined,
540
+ billingAddress: row.billing_address
541
+ ? JSON.parse(row.billing_address)
542
+ : undefined,
543
+ fulfilledAt: row.fulfilled_at
544
+ ? new Date(row.fulfilled_at * 1000)
545
+ : undefined,
546
+ shippedAt: row.shipped_at ? new Date(row.shipped_at * 1000) : undefined,
547
+ trackingNumber: row.tracking_number || undefined,
548
+ trackingUrl: row.tracking_url || undefined,
549
+ notes: row.internal_notes || undefined,
550
+ createdAt: new Date(row.created_at * 1000),
551
+ updatedAt: new Date(row.updated_at * 1000),
552
+ };
553
+ }
554
+ function mapLineItemRow(row) {
555
+ return {
556
+ id: row.id,
557
+ variantId: row.variant_id || '',
558
+ productId: row.product_id || '',
559
+ productName: row.product_name,
560
+ variantName: row.variant_name,
561
+ quantity: row.quantity,
562
+ unitPrice: { amount: row.unit_price, currency: 'usd' },
563
+ totalPrice: { amount: row.total_price, currency: 'usd' },
564
+ taxAmount: row.tax_amount
565
+ ? { amount: row.tax_amount, currency: 'usd' }
566
+ : undefined,
567
+ metadata: JSON.parse(row.metadata || '{}'),
568
+ };
569
+ }
570
+ function mapCustomerRow(row) {
571
+ return {
572
+ id: row.id,
573
+ tenantId: row.tenant_id,
574
+ email: row.email,
575
+ name: row.name || undefined,
576
+ phone: row.phone || undefined,
577
+ defaultShippingAddress: row.default_shipping_address
578
+ ? JSON.parse(row.default_shipping_address)
579
+ : undefined,
580
+ defaultBillingAddress: row.default_billing_address
581
+ ? JSON.parse(row.default_billing_address)
582
+ : undefined,
583
+ providerCustomerId: row.provider_customer_id || undefined,
584
+ metadata: JSON.parse(row.metadata || '{}'),
585
+ createdAt: new Date(row.created_at * 1000),
586
+ updatedAt: new Date(row.updated_at * 1000),
587
+ };
588
+ }