@rovela-ai/sdk 0.21.0 → 0.22.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 (71) hide show
  1. package/dist/admin/api/products.d.ts.map +1 -1
  2. package/dist/admin/api/products.js +4 -0
  3. package/dist/admin/api/products.js.map +1 -1
  4. package/dist/admin/api/tax-zones.d.ts.map +1 -1
  5. package/dist/admin/api/tax-zones.js +7 -0
  6. package/dist/admin/api/tax-zones.js.map +1 -1
  7. package/dist/admin/components/OrderDetails.d.ts.map +1 -1
  8. package/dist/admin/components/OrderDetails.js +5 -1
  9. package/dist/admin/components/OrderDetails.js.map +1 -1
  10. package/dist/admin/components/ProductForm.d.ts.map +1 -1
  11. package/dist/admin/components/ProductForm.js +25 -1
  12. package/dist/admin/components/ProductForm.js.map +1 -1
  13. package/dist/admin/components/TaxSettings.d.ts.map +1 -1
  14. package/dist/admin/components/TaxSettings.js +14 -2
  15. package/dist/admin/components/TaxSettings.js.map +1 -1
  16. package/dist/admin/types.d.ts +15 -0
  17. package/dist/admin/types.d.ts.map +1 -1
  18. package/dist/checkout/api/manual.d.ts.map +1 -1
  19. package/dist/checkout/api/manual.js +7 -0
  20. package/dist/checkout/api/manual.js.map +1 -1
  21. package/dist/checkout/api/shipping.d.ts +0 -18
  22. package/dist/checkout/api/shipping.d.ts.map +1 -1
  23. package/dist/checkout/api/shipping.js +33 -3
  24. package/dist/checkout/api/shipping.js.map +1 -1
  25. package/dist/checkout/components/CheckoutFlow.d.ts.map +1 -1
  26. package/dist/checkout/components/CheckoutFlow.js +30 -13
  27. package/dist/checkout/components/CheckoutFlow.js.map +1 -1
  28. package/dist/checkout/components/OrderSummary.d.ts +1 -1
  29. package/dist/checkout/components/OrderSummary.d.ts.map +1 -1
  30. package/dist/checkout/components/OrderSummary.js +5 -2
  31. package/dist/checkout/components/OrderSummary.js.map +1 -1
  32. package/dist/checkout/server/create-checkout-session.d.ts.map +1 -1
  33. package/dist/checkout/server/create-checkout-session.js +38 -9
  34. package/dist/checkout/server/create-checkout-session.js.map +1 -1
  35. package/dist/checkout/server/handle-webhook.js +6 -0
  36. package/dist/checkout/server/handle-webhook.js.map +1 -1
  37. package/dist/checkout/server/order-service.d.ts.map +1 -1
  38. package/dist/checkout/server/order-service.js +21 -0
  39. package/dist/checkout/server/order-service.js.map +1 -1
  40. package/dist/checkout/server/pricing.d.ts +28 -1
  41. package/dist/checkout/server/pricing.d.ts.map +1 -1
  42. package/dist/checkout/server/pricing.js +84 -6
  43. package/dist/checkout/server/pricing.js.map +1 -1
  44. package/dist/checkout/types.d.ts +32 -0
  45. package/dist/checkout/types.d.ts.map +1 -1
  46. package/dist/core/db/queries.d.ts +106 -25
  47. package/dist/core/db/queries.d.ts.map +1 -1
  48. package/dist/core/db/queries.js +83 -2
  49. package/dist/core/db/queries.js.map +1 -1
  50. package/dist/core/db/schema.d.ts +65 -0
  51. package/dist/core/db/schema.d.ts.map +1 -1
  52. package/dist/core/db/schema.js +26 -0
  53. package/dist/core/db/schema.js.map +1 -1
  54. package/dist/emails/send/orders.d.ts +4 -0
  55. package/dist/emails/send/orders.d.ts.map +1 -1
  56. package/dist/emails/send/orders.js +1 -0
  57. package/dist/emails/send/orders.js.map +1 -1
  58. package/dist/emails/templates/order-confirmation.d.ts +9 -0
  59. package/dist/emails/templates/order-confirmation.d.ts.map +1 -1
  60. package/dist/emails/templates/order-confirmation.js +19 -8
  61. package/dist/emails/templates/order-confirmation.js.map +1 -1
  62. package/dist/emails/types.d.ts +5 -0
  63. package/dist/emails/types.d.ts.map +1 -1
  64. package/dist/products/api/products.d.ts.map +1 -1
  65. package/dist/products/api/products.js +18 -3
  66. package/dist/products/api/products.js.map +1 -1
  67. package/docs/changelog/epoch-09-catalog-tax.md +91 -0
  68. package/package.json +1 -1
  69. package/templates/store-template/.claude/skills/manage-products/SKILL.md +1 -1
  70. package/templates/store-template/app/account/orders/[id]/invoice/page.tsx +19 -4
  71. package/templates/store-template/package.json +1 -1
@@ -37,6 +37,9 @@ export async function ensureCatalogTranslationsSchema() {
37
37
  `);
38
38
  await db.execute(sql `
39
39
  ALTER TABLE products ADD COLUMN IF NOT EXISTS metafields jsonb NOT NULL DEFAULT '[]'::jsonb
40
+ `);
41
+ await db.execute(sql `
42
+ ALTER TABLE products ADD COLUMN IF NOT EXISTS tax_class varchar(50)
40
43
  `);
41
44
  catalogTranslationsEnsured = true;
42
45
  }
@@ -59,7 +62,10 @@ export async function findProducts(options = {}) {
59
62
  const db = getDb();
60
63
  const conditions = [];
61
64
  if (options.categoryId) {
62
- conditions.push(eq(schema.products.categoryId, options.categoryId));
65
+ const categoryIds = options.categoryId;
66
+ conditions.push(Array.isArray(categoryIds)
67
+ ? inArray(schema.products.categoryId, categoryIds)
68
+ : eq(schema.products.categoryId, categoryIds));
63
69
  }
64
70
  if (options.status) {
65
71
  conditions.push(eq(schema.products.status, options.status));
@@ -156,7 +162,10 @@ export async function countProducts(options = {}) {
156
162
  const db = getDb();
157
163
  const conditions = [];
158
164
  if (options.categoryId) {
159
- conditions.push(eq(schema.products.categoryId, options.categoryId));
165
+ const categoryIds = options.categoryId;
166
+ conditions.push(Array.isArray(categoryIds)
167
+ ? inArray(schema.products.categoryId, categoryIds)
168
+ : eq(schema.products.categoryId, categoryIds));
160
169
  }
161
170
  if (options.status) {
162
171
  conditions.push(eq(schema.products.status, options.status));
@@ -429,6 +438,51 @@ export async function findCategories() {
429
438
  .from(schema.categories)
430
439
  .orderBy(asc(schema.categories.order), asc(schema.categories.name));
431
440
  }
441
+ /**
442
+ * productId → tax_class for a set of products, one query. Used by the
443
+ * pre-checkout estimate endpoint so its per-class tax preview matches the
444
+ * final quote. Unknown ids are simply absent from the map.
445
+ */
446
+ export async function findTaxClassesByProductIds(productIds) {
447
+ if (productIds.length === 0)
448
+ return {};
449
+ await ensureCatalogTranslationsSchemaSafe();
450
+ const db = getDb();
451
+ const rows = await db
452
+ .select({ id: schema.products.id, taxClass: schema.products.taxClass })
453
+ .from(schema.products)
454
+ .where(inArray(schema.products.id, productIds));
455
+ const out = {};
456
+ for (const row of rows)
457
+ out[row.id] = row.taxClass ?? null;
458
+ return out;
459
+ }
460
+ /**
461
+ * All descendant category ids of a category (children, grandchildren, …),
462
+ * excluding the category itself. Recursive CTE over categories.parent_id —
463
+ * correct at any depth, one round trip. Powers the tree-aware public catalog
464
+ * filter: a parent-category link shows its leaves' products (imported
465
+ * PrestaShop catalogs assign products to LEAF categories).
466
+ */
467
+ export async function findCategoryDescendantIds(categoryId) {
468
+ const db = getDb();
469
+ const result = await db.execute(sql `
470
+ WITH RECURSIVE descendants AS (
471
+ SELECT id FROM categories WHERE parent_id = ${categoryId}
472
+ UNION ALL
473
+ SELECT c.id FROM categories c
474
+ INNER JOIN descendants d ON c.parent_id = d.id
475
+ )
476
+ SELECT id FROM descendants
477
+ `);
478
+ // Driver-shape hedge (same as the redemption claim below): neon-http returns
479
+ // { rows } on current drizzle, a bare array on older ones.
480
+ const raw = result;
481
+ const rows = Array.isArray(raw)
482
+ ? raw
483
+ : (raw.rows ?? []);
484
+ return rows.map((r) => String(r.id));
485
+ }
432
486
  /**
433
487
  * Find a category by slug
434
488
  */
@@ -1825,6 +1879,8 @@ export async function ensureVisibilityColumns() {
1825
1879
  await db.execute(sql `ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS visible_to_groups jsonb NOT NULL DEFAULT '[]'::jsonb`);
1826
1880
  await db.execute(sql `ALTER TABLE shipping_rates ADD COLUMN IF NOT EXISTS visible_to_groups jsonb NOT NULL DEFAULT '[]'::jsonb`);
1827
1881
  await db.execute(sql `ALTER TABLE tax_zones ADD COLUMN IF NOT EXISTS visible_to_groups jsonb NOT NULL DEFAULT '[]'::jsonb`);
1882
+ // Per-class rates (0.22.0) ride the same tax-zone chokepoints.
1883
+ await db.execute(sql `ALTER TABLE tax_zones ADD COLUMN IF NOT EXISTS class_rates jsonb NOT NULL DEFAULT '{}'::jsonb`);
1828
1884
  visibilityColumnsEnsured = true;
1829
1885
  }
1830
1886
  catch (err) {
@@ -1882,6 +1938,7 @@ export async function ensurePaymentMethodsSchema() {
1882
1938
  await db.execute(sql `CREATE INDEX IF NOT EXISTS payment_methods_sort_order_idx ON payment_methods (sort_order)`);
1883
1939
  await db.execute(sql `ALTER TABLE orders ADD COLUMN IF NOT EXISTS payment_provider varchar(20) NOT NULL DEFAULT 'stripe'`);
1884
1940
  await db.execute(sql `ALTER TABLE orders ADD COLUMN IF NOT EXISTS payment_method_id uuid`);
1941
+ await db.execute(sql `ALTER TABLE orders ADD COLUMN IF NOT EXISTS tax_breakdown jsonb`);
1885
1942
  await db.execute(sql `ALTER TABLE orders ADD COLUMN IF NOT EXISTS payment_method_label varchar(100)`);
1886
1943
  // Per-order currency snapshot (SDK 0.7.0 — pricing core). Rides this
1887
1944
  // ensure because it already covers every order read/write chokepoint.
@@ -3401,6 +3458,25 @@ export async function findShippingOptionsForCountry(country, orderAmount, groupI
3401
3458
  // Convert to array and sort by carrier name
3402
3459
  return Array.from(carrierMap.values()).sort((a, b) => a.carrier.name.localeCompare(b.carrier.name));
3403
3460
  }
3461
+ /**
3462
+ * Sanitize a class-rates map at the write boundary: string keys (trimmed,
3463
+ * ≤50 chars — the products.tax_class width), finite rates clamped 0-100.
3464
+ * Invalid entries are dropped, never thrown — a bad row must not brick the
3465
+ * zone editor.
3466
+ */
3467
+ export function sanitizeClassRates(input) {
3468
+ if (!input || typeof input !== 'object' || Array.isArray(input))
3469
+ return {};
3470
+ const out = {};
3471
+ for (const [key, value] of Object.entries(input)) {
3472
+ const name = String(key).trim().slice(0, 50);
3473
+ const rate = typeof value === 'number' ? value : Number(value);
3474
+ if (!name || !Number.isFinite(rate) || rate < 0 || rate > 100)
3475
+ continue;
3476
+ out[name] = rate;
3477
+ }
3478
+ return out;
3479
+ }
3404
3480
  /**
3405
3481
  * Find all tax zones ordered by priority (sortOrder ascending)
3406
3482
  */
@@ -3418,6 +3494,7 @@ export async function findTaxZones() {
3418
3494
  countries: zone.countries || [],
3419
3495
  states: zone.states || [],
3420
3496
  taxRate: Number(zone.taxRate),
3497
+ classRates: sanitizeClassRates(zone.classRates),
3421
3498
  isActive: zone.isActive,
3422
3499
  sortOrder: zone.sortOrder,
3423
3500
  visibleToGroups: Array.isArray(zone.visibleToGroups) ? zone.visibleToGroups : [],
@@ -3452,6 +3529,7 @@ export async function createTaxZone(data) {
3452
3529
  countries: data.countries.map((c) => c.toUpperCase()),
3453
3530
  states: (data.states || []).map((s) => s.toUpperCase()),
3454
3531
  taxRate: String(data.taxRate),
3532
+ classRates: sanitizeClassRates(data.classRates),
3455
3533
  isActive: data.isActive ?? true,
3456
3534
  sortOrder: data.sortOrder ?? 0,
3457
3535
  visibleToGroups: data.visibleToGroups ?? [],
@@ -3474,6 +3552,8 @@ export async function updateTaxZone(id, data) {
3474
3552
  updateData.states = data.states.map((s) => s.toUpperCase());
3475
3553
  if (data.taxRate !== undefined)
3476
3554
  updateData.taxRate = String(data.taxRate);
3555
+ if (data.classRates !== undefined)
3556
+ updateData.classRates = sanitizeClassRates(data.classRates);
3477
3557
  if (data.isActive !== undefined)
3478
3558
  updateData.isActive = data.isActive;
3479
3559
  if (data.sortOrder !== undefined)
@@ -3537,6 +3617,7 @@ export async function findTaxRateForAddress(country, state, groupId) {
3537
3617
  zoneId: zone.id,
3538
3618
  zoneName: zone.name,
3539
3619
  taxRate: zone.taxRate,
3620
+ classRates: zone.classRates,
3540
3621
  };
3541
3622
  }
3542
3623
  return null; // No matching tax zone