@rovela-ai/sdk 0.21.0 → 0.23.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 (124) hide show
  1. package/dist/admin/api/email-sending.d.ts +24 -0
  2. package/dist/admin/api/email-sending.d.ts.map +1 -0
  3. package/dist/admin/api/email-sending.js +155 -0
  4. package/dist/admin/api/email-sending.js.map +1 -0
  5. package/dist/admin/api/index.d.ts +2 -0
  6. package/dist/admin/api/index.d.ts.map +1 -1
  7. package/dist/admin/api/index.js +4 -0
  8. package/dist/admin/api/index.js.map +1 -1
  9. package/dist/admin/api/integrations-brevo.d.ts +24 -0
  10. package/dist/admin/api/integrations-brevo.d.ts.map +1 -0
  11. package/dist/admin/api/integrations-brevo.js +286 -0
  12. package/dist/admin/api/integrations-brevo.js.map +1 -0
  13. package/dist/admin/api/integrations.d.ts +2 -0
  14. package/dist/admin/api/integrations.d.ts.map +1 -1
  15. package/dist/admin/api/integrations.js +1 -0
  16. package/dist/admin/api/integrations.js.map +1 -1
  17. package/dist/admin/api/products.d.ts.map +1 -1
  18. package/dist/admin/api/products.js +4 -0
  19. package/dist/admin/api/products.js.map +1 -1
  20. package/dist/admin/api/settings.d.ts.map +1 -1
  21. package/dist/admin/api/settings.js +4 -1
  22. package/dist/admin/api/settings.js.map +1 -1
  23. package/dist/admin/api/tax-zones.d.ts.map +1 -1
  24. package/dist/admin/api/tax-zones.js +7 -0
  25. package/dist/admin/api/tax-zones.js.map +1 -1
  26. package/dist/admin/components/EmailsManager.d.ts.map +1 -1
  27. package/dist/admin/components/EmailsManager.js +2 -1
  28. package/dist/admin/components/EmailsManager.js.map +1 -1
  29. package/dist/admin/components/IntegrationsManager.d.ts.map +1 -1
  30. package/dist/admin/components/IntegrationsManager.js +98 -2
  31. package/dist/admin/components/IntegrationsManager.js.map +1 -1
  32. package/dist/admin/components/OrderDetails.d.ts.map +1 -1
  33. package/dist/admin/components/OrderDetails.js +5 -1
  34. package/dist/admin/components/OrderDetails.js.map +1 -1
  35. package/dist/admin/components/ProductForm.d.ts.map +1 -1
  36. package/dist/admin/components/ProductForm.js +36 -1
  37. package/dist/admin/components/ProductForm.js.map +1 -1
  38. package/dist/admin/components/TaxSettings.d.ts.map +1 -1
  39. package/dist/admin/components/TaxSettings.js +16 -4
  40. package/dist/admin/components/TaxSettings.js.map +1 -1
  41. package/dist/admin/components/emails/EmailSendingPanel.d.ts +2 -0
  42. package/dist/admin/components/emails/EmailSendingPanel.d.ts.map +1 -0
  43. package/dist/admin/components/emails/EmailSendingPanel.js +131 -0
  44. package/dist/admin/components/emails/EmailSendingPanel.js.map +1 -0
  45. package/dist/admin/integrations/registry.d.ts +2 -0
  46. package/dist/admin/integrations/registry.d.ts.map +1 -1
  47. package/dist/admin/integrations/registry.js +41 -0
  48. package/dist/admin/integrations/registry.js.map +1 -1
  49. package/dist/admin/types.d.ts +15 -0
  50. package/dist/admin/types.d.ts.map +1 -1
  51. package/dist/checkout/api/manual.d.ts.map +1 -1
  52. package/dist/checkout/api/manual.js +7 -0
  53. package/dist/checkout/api/manual.js.map +1 -1
  54. package/dist/checkout/api/shipping.d.ts +0 -18
  55. package/dist/checkout/api/shipping.d.ts.map +1 -1
  56. package/dist/checkout/api/shipping.js +33 -3
  57. package/dist/checkout/api/shipping.js.map +1 -1
  58. package/dist/checkout/components/CheckoutFlow.d.ts.map +1 -1
  59. package/dist/checkout/components/CheckoutFlow.js +30 -13
  60. package/dist/checkout/components/CheckoutFlow.js.map +1 -1
  61. package/dist/checkout/components/OrderSummary.d.ts +1 -1
  62. package/dist/checkout/components/OrderSummary.d.ts.map +1 -1
  63. package/dist/checkout/components/OrderSummary.js +5 -2
  64. package/dist/checkout/components/OrderSummary.js.map +1 -1
  65. package/dist/checkout/server/create-checkout-session.d.ts.map +1 -1
  66. package/dist/checkout/server/create-checkout-session.js +38 -9
  67. package/dist/checkout/server/create-checkout-session.js.map +1 -1
  68. package/dist/checkout/server/handle-webhook.js +6 -0
  69. package/dist/checkout/server/handle-webhook.js.map +1 -1
  70. package/dist/checkout/server/order-service.d.ts.map +1 -1
  71. package/dist/checkout/server/order-service.js +21 -0
  72. package/dist/checkout/server/order-service.js.map +1 -1
  73. package/dist/checkout/server/pricing.d.ts +28 -1
  74. package/dist/checkout/server/pricing.d.ts.map +1 -1
  75. package/dist/checkout/server/pricing.js +84 -6
  76. package/dist/checkout/server/pricing.js.map +1 -1
  77. package/dist/checkout/types.d.ts +32 -0
  78. package/dist/checkout/types.d.ts.map +1 -1
  79. package/dist/core/db/queries.d.ts +114 -25
  80. package/dist/core/db/queries.d.ts.map +1 -1
  81. package/dist/core/db/queries.js +124 -2
  82. package/dist/core/db/queries.js.map +1 -1
  83. package/dist/core/db/schema.d.ts +213 -0
  84. package/dist/core/db/schema.d.ts.map +1 -1
  85. package/dist/core/db/schema.js +42 -0
  86. package/dist/core/db/schema.js.map +1 -1
  87. package/dist/emails/index.d.ts +2 -0
  88. package/dist/emails/index.d.ts.map +1 -1
  89. package/dist/emails/index.js +1 -0
  90. package/dist/emails/index.js.map +1 -1
  91. package/dist/emails/send/orders.d.ts +4 -0
  92. package/dist/emails/send/orders.d.ts.map +1 -1
  93. package/dist/emails/send/orders.js +1 -0
  94. package/dist/emails/send/orders.js.map +1 -1
  95. package/dist/emails/sender.d.ts.map +1 -1
  96. package/dist/emails/sender.js +30 -5
  97. package/dist/emails/sender.js.map +1 -1
  98. package/dist/emails/templates/order-confirmation.d.ts +9 -0
  99. package/dist/emails/templates/order-confirmation.d.ts.map +1 -1
  100. package/dist/emails/templates/order-confirmation.js +19 -8
  101. package/dist/emails/templates/order-confirmation.js.map +1 -1
  102. package/dist/emails/transport-smtp.d.ts +58 -0
  103. package/dist/emails/transport-smtp.d.ts.map +1 -0
  104. package/dist/emails/transport-smtp.js +97 -0
  105. package/dist/emails/transport-smtp.js.map +1 -0
  106. package/dist/emails/transport.d.ts +77 -0
  107. package/dist/emails/transport.d.ts.map +1 -0
  108. package/dist/emails/transport.js +154 -0
  109. package/dist/emails/transport.js.map +1 -0
  110. package/dist/emails/types.d.ts +5 -0
  111. package/dist/emails/types.d.ts.map +1 -1
  112. package/dist/products/api/products.d.ts.map +1 -1
  113. package/dist/products/api/products.js +18 -3
  114. package/dist/products/api/products.js.map +1 -1
  115. package/docs/changelog/epoch-09-catalog-tax.md +91 -0
  116. package/docs/changelog/epoch-10-email-transport.md +97 -0
  117. package/package.json +2 -1
  118. package/templates/store-template/.claude/skills/manage-products/SKILL.md +1 -1
  119. package/templates/store-template/app/account/orders/[id]/invoice/page.tsx +19 -4
  120. package/templates/store-template/app/api/admin/emails/sending/route.ts +9 -0
  121. package/templates/store-template/app/api/admin/integrations/brevo/backfill/route.ts +9 -0
  122. package/templates/store-template/app/api/admin/integrations/brevo/route.ts +9 -0
  123. package/templates/store-template/next.config.js +4 -1
  124. package/templates/store-template/package.json +2 -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.
@@ -2550,6 +2607,20 @@ export async function ensureStoreSettingsSchema() {
2550
2607
  ADD COLUMN IF NOT EXISTS colissimo_enabled boolean DEFAULT false,
2551
2608
  ADD COLUMN IF NOT EXISTS colissimo_sender_address jsonb,
2552
2609
  ADD COLUMN IF NOT EXISTS colissimo_default_weight_kg decimal(8,2)
2610
+ `);
2611
+ // Transactional-email transport (SDK 0.23.0). NULL email_transport =
2612
+ // 'platform' (today's Resend path) — the whole fleet keeps its exact
2613
+ // behavior until a merchant opts in. smtp_password is server-only.
2614
+ await db.execute(sql `
2615
+ ALTER TABLE store_settings
2616
+ ADD COLUMN IF NOT EXISTS email_transport varchar(20),
2617
+ ADD COLUMN IF NOT EXISTS email_from_address varchar(255),
2618
+ ADD COLUMN IF NOT EXISTS email_from_name varchar(120),
2619
+ ADD COLUMN IF NOT EXISTS smtp_host varchar(255),
2620
+ ADD COLUMN IF NOT EXISTS smtp_port integer,
2621
+ ADD COLUMN IF NOT EXISTS smtp_secure boolean,
2622
+ ADD COLUMN IF NOT EXISTS smtp_user varchar(255),
2623
+ ADD COLUMN IF NOT EXISTS smtp_password varchar(255)
2553
2624
  `);
2554
2625
  storeSettingsSchemaEnsured = true;
2555
2626
  }
@@ -2753,6 +2824,15 @@ export async function findSettings() {
2753
2824
  colissimoDefaultWeightKg: settings.colissimoDefaultWeightKg
2754
2825
  ? Number(settings.colissimoDefaultWeightKg)
2755
2826
  : null,
2827
+ // Transactional-email transport (SDK 0.23.0)
2828
+ emailTransport: settings.emailTransport ?? null,
2829
+ emailFromAddress: settings.emailFromAddress,
2830
+ emailFromName: settings.emailFromName,
2831
+ smtpHost: settings.smtpHost,
2832
+ smtpPort: settings.smtpPort,
2833
+ smtpSecure: settings.smtpSecure,
2834
+ smtpUser: settings.smtpUser,
2835
+ smtpPassword: settings.smtpPassword,
2756
2836
  // Discount/promotion configuration
2757
2837
  promotionCodesEnabled: settings.promotionCodesEnabled,
2758
2838
  // Cookie consent banner (R021)
@@ -2842,6 +2922,15 @@ export async function upsertSettings(data) {
2842
2922
  colissimoDefaultWeightKg: data.colissimoDefaultWeightKg !== null && data.colissimoDefaultWeightKg !== undefined
2843
2923
  ? String(data.colissimoDefaultWeightKg)
2844
2924
  : undefined,
2925
+ // Email transport (SDK 0.23.0). Explicit null clears (disconnect).
2926
+ emailTransport: data.emailTransport !== undefined ? data.emailTransport : undefined,
2927
+ emailFromAddress: data.emailFromAddress !== undefined ? data.emailFromAddress : undefined,
2928
+ emailFromName: data.emailFromName !== undefined ? data.emailFromName : undefined,
2929
+ smtpHost: data.smtpHost !== undefined ? data.smtpHost : undefined,
2930
+ smtpPort: data.smtpPort !== undefined ? data.smtpPort : undefined,
2931
+ smtpSecure: data.smtpSecure !== undefined ? data.smtpSecure : undefined,
2932
+ smtpUser: data.smtpUser !== undefined ? data.smtpUser : undefined,
2933
+ smtpPassword: data.smtpPassword !== undefined ? data.smtpPassword : undefined,
2845
2934
  // Discount/promotion configuration
2846
2935
  promotionCodesEnabled: data.promotionCodesEnabled ?? undefined,
2847
2936
  // Cookie consent banner (R021)
@@ -2901,6 +2990,15 @@ export async function upsertSettings(data) {
2901
2990
  colissimoDefaultWeightKg: result.colissimoDefaultWeightKg
2902
2991
  ? Number(result.colissimoDefaultWeightKg)
2903
2992
  : null,
2993
+ // Transactional-email transport (SDK 0.23.0)
2994
+ emailTransport: result.emailTransport ?? null,
2995
+ emailFromAddress: result.emailFromAddress,
2996
+ emailFromName: result.emailFromName,
2997
+ smtpHost: result.smtpHost,
2998
+ smtpPort: result.smtpPort,
2999
+ smtpSecure: result.smtpSecure,
3000
+ smtpUser: result.smtpUser,
3001
+ smtpPassword: result.smtpPassword,
2904
3002
  // Discount/promotion configuration
2905
3003
  promotionCodesEnabled: result.promotionCodesEnabled,
2906
3004
  // Cookie consent banner (R021)
@@ -3401,6 +3499,25 @@ export async function findShippingOptionsForCountry(country, orderAmount, groupI
3401
3499
  // Convert to array and sort by carrier name
3402
3500
  return Array.from(carrierMap.values()).sort((a, b) => a.carrier.name.localeCompare(b.carrier.name));
3403
3501
  }
3502
+ /**
3503
+ * Sanitize a class-rates map at the write boundary: string keys (trimmed,
3504
+ * ≤50 chars — the products.tax_class width), finite rates clamped 0-100.
3505
+ * Invalid entries are dropped, never thrown — a bad row must not brick the
3506
+ * zone editor.
3507
+ */
3508
+ export function sanitizeClassRates(input) {
3509
+ if (!input || typeof input !== 'object' || Array.isArray(input))
3510
+ return {};
3511
+ const out = {};
3512
+ for (const [key, value] of Object.entries(input)) {
3513
+ const name = String(key).trim().slice(0, 50);
3514
+ const rate = typeof value === 'number' ? value : Number(value);
3515
+ if (!name || !Number.isFinite(rate) || rate < 0 || rate > 100)
3516
+ continue;
3517
+ out[name] = rate;
3518
+ }
3519
+ return out;
3520
+ }
3404
3521
  /**
3405
3522
  * Find all tax zones ordered by priority (sortOrder ascending)
3406
3523
  */
@@ -3418,6 +3535,7 @@ export async function findTaxZones() {
3418
3535
  countries: zone.countries || [],
3419
3536
  states: zone.states || [],
3420
3537
  taxRate: Number(zone.taxRate),
3538
+ classRates: sanitizeClassRates(zone.classRates),
3421
3539
  isActive: zone.isActive,
3422
3540
  sortOrder: zone.sortOrder,
3423
3541
  visibleToGroups: Array.isArray(zone.visibleToGroups) ? zone.visibleToGroups : [],
@@ -3452,6 +3570,7 @@ export async function createTaxZone(data) {
3452
3570
  countries: data.countries.map((c) => c.toUpperCase()),
3453
3571
  states: (data.states || []).map((s) => s.toUpperCase()),
3454
3572
  taxRate: String(data.taxRate),
3573
+ classRates: sanitizeClassRates(data.classRates),
3455
3574
  isActive: data.isActive ?? true,
3456
3575
  sortOrder: data.sortOrder ?? 0,
3457
3576
  visibleToGroups: data.visibleToGroups ?? [],
@@ -3474,6 +3593,8 @@ export async function updateTaxZone(id, data) {
3474
3593
  updateData.states = data.states.map((s) => s.toUpperCase());
3475
3594
  if (data.taxRate !== undefined)
3476
3595
  updateData.taxRate = String(data.taxRate);
3596
+ if (data.classRates !== undefined)
3597
+ updateData.classRates = sanitizeClassRates(data.classRates);
3477
3598
  if (data.isActive !== undefined)
3478
3599
  updateData.isActive = data.isActive;
3479
3600
  if (data.sortOrder !== undefined)
@@ -3537,6 +3658,7 @@ export async function findTaxRateForAddress(country, state, groupId) {
3537
3658
  zoneId: zone.id,
3538
3659
  zoneName: zone.name,
3539
3660
  taxRate: zone.taxRate,
3661
+ classRates: zone.classRates,
3540
3662
  };
3541
3663
  }
3542
3664
  return null; // No matching tax zone