@rovela-ai/sdk 0.6.0 → 0.7.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 (127) hide show
  1. package/dist/admin/api/discounts.d.ts +53 -0
  2. package/dist/admin/api/discounts.d.ts.map +1 -0
  3. package/dist/admin/api/discounts.js +325 -0
  4. package/dist/admin/api/discounts.js.map +1 -0
  5. package/dist/admin/api/index.d.ts +1 -0
  6. package/dist/admin/api/index.d.ts.map +1 -1
  7. package/dist/admin/api/index.js +2 -0
  8. package/dist/admin/api/index.js.map +1 -1
  9. package/dist/admin/api/orders.js +1 -1
  10. package/dist/admin/api/orders.js.map +1 -1
  11. package/dist/admin/api/settings.d.ts.map +1 -1
  12. package/dist/admin/api/settings.js +10 -0
  13. package/dist/admin/api/settings.js.map +1 -1
  14. package/dist/admin/components/CustomerAddressPanel.d.ts.map +1 -1
  15. package/dist/admin/components/CustomerAddressPanel.js +13 -6
  16. package/dist/admin/components/CustomerAddressPanel.js.map +1 -1
  17. package/dist/admin/components/DiscountsManager.d.ts +5 -0
  18. package/dist/admin/components/DiscountsManager.d.ts.map +1 -0
  19. package/dist/admin/components/DiscountsManager.js +363 -0
  20. package/dist/admin/components/DiscountsManager.js.map +1 -0
  21. package/dist/admin/components/ShippingSettings.d.ts.map +1 -1
  22. package/dist/admin/components/ShippingSettings.js +19 -2
  23. package/dist/admin/components/ShippingSettings.js.map +1 -1
  24. package/dist/admin/components/StoreSettings.d.ts.map +1 -1
  25. package/dist/admin/components/StoreSettings.js +8 -1
  26. package/dist/admin/components/StoreSettings.js.map +1 -1
  27. package/dist/admin/components/TaxSettings.d.ts.map +1 -1
  28. package/dist/admin/components/TaxSettings.js +8 -3
  29. package/dist/admin/components/TaxSettings.js.map +1 -1
  30. package/dist/admin/components/index.d.ts +2 -0
  31. package/dist/admin/components/index.d.ts.map +1 -1
  32. package/dist/admin/components/index.js +1 -0
  33. package/dist/admin/components/index.js.map +1 -1
  34. package/dist/admin/hooks/index.d.ts +2 -0
  35. package/dist/admin/hooks/index.d.ts.map +1 -1
  36. package/dist/admin/hooks/index.js +1 -0
  37. package/dist/admin/hooks/index.js.map +1 -1
  38. package/dist/admin/hooks/useAdminDiscounts.d.ts +12 -0
  39. package/dist/admin/hooks/useAdminDiscounts.d.ts.map +1 -0
  40. package/dist/admin/hooks/useAdminDiscounts.js +77 -0
  41. package/dist/admin/hooks/useAdminDiscounts.js.map +1 -0
  42. package/dist/admin/index.d.ts +3 -3
  43. package/dist/admin/index.d.ts.map +1 -1
  44. package/dist/admin/index.js +2 -2
  45. package/dist/admin/index.js.map +1 -1
  46. package/dist/admin/types.d.ts +72 -0
  47. package/dist/admin/types.d.ts.map +1 -1
  48. package/dist/auth/components/AddressForm.d.ts.map +1 -1
  49. package/dist/auth/components/AddressForm.js +22 -7
  50. package/dist/auth/components/AddressForm.js.map +1 -1
  51. package/dist/checkout/api/checkout.d.ts.map +1 -1
  52. package/dist/checkout/api/checkout.js +15 -2
  53. package/dist/checkout/api/checkout.js.map +1 -1
  54. package/dist/checkout/api/discount.d.ts +47 -0
  55. package/dist/checkout/api/discount.d.ts.map +1 -0
  56. package/dist/checkout/api/discount.js +77 -0
  57. package/dist/checkout/api/discount.js.map +1 -0
  58. package/dist/checkout/api/index.d.ts +1 -0
  59. package/dist/checkout/api/index.d.ts.map +1 -1
  60. package/dist/checkout/api/index.js +1 -0
  61. package/dist/checkout/api/index.js.map +1 -1
  62. package/dist/checkout/api/manual.d.ts.map +1 -1
  63. package/dist/checkout/api/manual.js +106 -155
  64. package/dist/checkout/api/manual.js.map +1 -1
  65. package/dist/checkout/api/shipping.d.ts.map +1 -1
  66. package/dist/checkout/api/shipping.js +19 -27
  67. package/dist/checkout/api/shipping.js.map +1 -1
  68. package/dist/checkout/components/CheckoutFlow.d.ts.map +1 -1
  69. package/dist/checkout/components/CheckoutFlow.js +89 -9
  70. package/dist/checkout/components/CheckoutFlow.js.map +1 -1
  71. package/dist/checkout/components/OrderSummary.d.ts +1 -1
  72. package/dist/checkout/components/OrderSummary.d.ts.map +1 -1
  73. package/dist/checkout/components/OrderSummary.js +3 -3
  74. package/dist/checkout/components/OrderSummary.js.map +1 -1
  75. package/dist/checkout/components/ShippingForm.d.ts.map +1 -1
  76. package/dist/checkout/components/ShippingForm.js +42 -13
  77. package/dist/checkout/components/ShippingForm.js.map +1 -1
  78. package/dist/checkout/constants.d.ts +38 -0
  79. package/dist/checkout/constants.d.ts.map +1 -1
  80. package/dist/checkout/constants.js +39 -0
  81. package/dist/checkout/constants.js.map +1 -1
  82. package/dist/checkout/hooks/useCheckout.d.ts.map +1 -1
  83. package/dist/checkout/hooks/useCheckout.js +2 -0
  84. package/dist/checkout/hooks/useCheckout.js.map +1 -1
  85. package/dist/checkout/server/checkout-customer.d.ts +30 -0
  86. package/dist/checkout/server/checkout-customer.d.ts.map +1 -0
  87. package/dist/checkout/server/checkout-customer.js +47 -0
  88. package/dist/checkout/server/checkout-customer.js.map +1 -0
  89. package/dist/checkout/server/create-checkout-session.d.ts.map +1 -1
  90. package/dist/checkout/server/create-checkout-session.js +149 -9
  91. package/dist/checkout/server/create-checkout-session.js.map +1 -1
  92. package/dist/checkout/server/discounts.d.ts +75 -0
  93. package/dist/checkout/server/discounts.d.ts.map +1 -0
  94. package/dist/checkout/server/discounts.js +294 -0
  95. package/dist/checkout/server/discounts.js.map +1 -0
  96. package/dist/checkout/server/handle-webhook.js +23 -1
  97. package/dist/checkout/server/handle-webhook.js.map +1 -1
  98. package/dist/checkout/server/index.d.ts +4 -0
  99. package/dist/checkout/server/index.d.ts.map +1 -1
  100. package/dist/checkout/server/index.js +4 -0
  101. package/dist/checkout/server/index.js.map +1 -1
  102. package/dist/checkout/server/order-service.d.ts.map +1 -1
  103. package/dist/checkout/server/order-service.js +60 -10
  104. package/dist/checkout/server/order-service.js.map +1 -1
  105. package/dist/checkout/server/pricing.d.ts +164 -0
  106. package/dist/checkout/server/pricing.d.ts.map +1 -0
  107. package/dist/checkout/server/pricing.js +309 -0
  108. package/dist/checkout/server/pricing.js.map +1 -0
  109. package/dist/checkout/types.d.ts +8 -0
  110. package/dist/checkout/types.d.ts.map +1 -1
  111. package/dist/core/StoreSettingsProvider.d.ts +4 -0
  112. package/dist/core/StoreSettingsProvider.d.ts.map +1 -1
  113. package/dist/core/StoreSettingsProvider.js +6 -0
  114. package/dist/core/StoreSettingsProvider.js.map +1 -1
  115. package/dist/core/api/settings.d.ts +1 -0
  116. package/dist/core/api/settings.d.ts.map +1 -1
  117. package/dist/core/api/settings.js +2 -0
  118. package/dist/core/api/settings.js.map +1 -1
  119. package/dist/core/db/queries.d.ts +249 -1
  120. package/dist/core/db/queries.d.ts.map +1 -1
  121. package/dist/core/db/queries.js +301 -1
  122. package/dist/core/db/queries.js.map +1 -1
  123. package/dist/core/db/schema.d.ts +618 -0
  124. package/dist/core/db/schema.d.ts.map +1 -1
  125. package/dist/core/db/schema.js +78 -0
  126. package/dist/core/db/schema.js.map +1 -1
  127. package/package.json +5 -1
@@ -849,6 +849,8 @@ export function periodBounds(periodDays) {
849
849
  * selector); omit it for all-time behavior (backward-compatible).
850
850
  */
851
851
  export async function getRecentOrders(limit = 5, periodDays) {
852
+ // Full orders SELECT enumerates payment/currency columns — self-heal first.
853
+ await ensurePaymentMethodsSchemaSafe();
852
854
  const db = getDb();
853
855
  const where = periodDays != null
854
856
  ? (() => {
@@ -1530,8 +1532,11 @@ export async function findCustomerGroups() {
1530
1532
  sortOrder: schema.customerGroups.sortOrder,
1531
1533
  createdAt: schema.customerGroups.createdAt,
1532
1534
  updatedAt: schema.customerGroups.updatedAt,
1535
+ // Plain identifier (not a drizzle column interpolation): inside a raw
1536
+ // correlated subquery the interpolated form mis-renders and the count
1537
+ // silently returns 0. Outer table is unaliased `customer_groups`.
1533
1538
  customerCount: sql `(
1534
- SELECT count(*)::int FROM customers c WHERE c.group_id = ${schema.customerGroups.id}
1539
+ SELECT count(*)::int FROM customers c WHERE c.group_id = customer_groups.id
1535
1540
  )`,
1536
1541
  })
1537
1542
  .from(schema.customerGroups)
@@ -1642,6 +1647,9 @@ export async function ensurePaymentMethodsSchema() {
1642
1647
  await db.execute(sql `ALTER TABLE orders ADD COLUMN IF NOT EXISTS payment_provider varchar(20) NOT NULL DEFAULT 'stripe'`);
1643
1648
  await db.execute(sql `ALTER TABLE orders ADD COLUMN IF NOT EXISTS payment_method_id uuid`);
1644
1649
  await db.execute(sql `ALTER TABLE orders ADD COLUMN IF NOT EXISTS payment_method_label varchar(100)`);
1650
+ // Per-order currency snapshot (SDK 0.7.0 — pricing core). Rides this
1651
+ // ensure because it already covers every order read/write chokepoint.
1652
+ await db.execute(sql `ALTER TABLE orders ADD COLUMN IF NOT EXISTS currency varchar(3)`);
1645
1653
  paymentMethodsSchemaEnsured = true;
1646
1654
  }
1647
1655
  catch (err) {
@@ -1740,6 +1748,256 @@ export async function deletePaymentMethod(id) {
1740
1748
  return deleted.length > 0;
1741
1749
  }
1742
1750
  // =============================================================================
1751
+ // Discounts (native engine — see schema.ts for the rule model)
1752
+ // =============================================================================
1753
+ let discountsSchemaEnsured = false;
1754
+ /**
1755
+ * Self-heal the discounts schema on stores provisioned before the native
1756
+ * discounts engine (SDK 0.7.0). Idempotent + module-cached; called at every
1757
+ * discount read/write chokepoint.
1758
+ */
1759
+ export async function ensureDiscountsSchema() {
1760
+ if (discountsSchemaEnsured)
1761
+ return;
1762
+ const db = getDb();
1763
+ try {
1764
+ await db.execute(sql `
1765
+ CREATE TABLE IF NOT EXISTS discounts (
1766
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
1767
+ method varchar(20) NOT NULL DEFAULT 'code',
1768
+ code varchar(64),
1769
+ title varchar(255) NOT NULL,
1770
+ type varchar(20) NOT NULL,
1771
+ value decimal(10,2) NOT NULL DEFAULT '0',
1772
+ applies_to varchar(20) NOT NULL DEFAULT 'order',
1773
+ target_ids jsonb NOT NULL DEFAULT '[]'::jsonb,
1774
+ min_subtotal decimal(10,2),
1775
+ min_quantity integer,
1776
+ eligibility varchar(20) NOT NULL DEFAULT 'all',
1777
+ group_ids jsonb NOT NULL DEFAULT '[]'::jsonb,
1778
+ usage_limit integer,
1779
+ once_per_customer boolean NOT NULL DEFAULT false,
1780
+ used_count integer NOT NULL DEFAULT 0,
1781
+ combines_with jsonb NOT NULL DEFAULT '{}'::jsonb,
1782
+ bxgy jsonb,
1783
+ starts_at timestamp,
1784
+ ends_at timestamp,
1785
+ enabled boolean NOT NULL DEFAULT true,
1786
+ sort_order integer NOT NULL DEFAULT 0,
1787
+ created_at timestamp NOT NULL DEFAULT now(),
1788
+ updated_at timestamp NOT NULL DEFAULT now()
1789
+ )
1790
+ `);
1791
+ await db.execute(sql `CREATE INDEX IF NOT EXISTS discounts_code_idx ON discounts (code)`);
1792
+ await db.execute(sql `CREATE INDEX IF NOT EXISTS discounts_enabled_idx ON discounts (enabled)`);
1793
+ await db.execute(sql `CREATE INDEX IF NOT EXISTS discounts_method_idx ON discounts (method)`);
1794
+ // Case-insensitive code uniqueness (codes are stored UPPERCASE; the
1795
+ // partial unique index is belt + suspenders against write-path drift).
1796
+ await db.execute(sql `CREATE UNIQUE INDEX IF NOT EXISTS discounts_code_unique_idx
1797
+ ON discounts (upper(code)) WHERE code IS NOT NULL`);
1798
+ await db.execute(sql `
1799
+ CREATE TABLE IF NOT EXISTS discount_redemptions (
1800
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
1801
+ discount_id uuid NOT NULL,
1802
+ order_id uuid NOT NULL,
1803
+ customer_id uuid,
1804
+ email varchar(255),
1805
+ amount decimal(10,2) NOT NULL DEFAULT '0',
1806
+ created_at timestamp NOT NULL DEFAULT now()
1807
+ )
1808
+ `);
1809
+ await db.execute(sql `CREATE INDEX IF NOT EXISTS discount_redemptions_discount_idx ON discount_redemptions (discount_id)`);
1810
+ await db.execute(sql `CREATE INDEX IF NOT EXISTS discount_redemptions_customer_idx ON discount_redemptions (discount_id, customer_id)`);
1811
+ await db.execute(sql `CREATE INDEX IF NOT EXISTS discount_redemptions_email_idx ON discount_redemptions (discount_id, email)`);
1812
+ await db.execute(sql `CREATE INDEX IF NOT EXISTS discount_redemptions_order_idx ON discount_redemptions (order_id)`);
1813
+ discountsSchemaEnsured = true;
1814
+ }
1815
+ catch (err) {
1816
+ console.error('[ensureDiscountsSchema] migration failed:', err);
1817
+ throw err;
1818
+ }
1819
+ }
1820
+ /** Best-effort variant — never throws (checkout must not 500 on a migration hiccup). */
1821
+ export async function ensureDiscountsSchemaSafe() {
1822
+ try {
1823
+ await ensureDiscountsSchema();
1824
+ }
1825
+ catch {
1826
+ // logged inside ensureDiscountsSchema
1827
+ }
1828
+ }
1829
+ /** All discounts, admin view (sorted). */
1830
+ export async function findDiscounts() {
1831
+ await ensureDiscountsSchema();
1832
+ const db = getDb();
1833
+ return db
1834
+ .select()
1835
+ .from(schema.discounts)
1836
+ .orderBy(asc(schema.discounts.sortOrder), desc(schema.discounts.createdAt));
1837
+ }
1838
+ export async function findDiscountById(id) {
1839
+ await ensureDiscountsSchema();
1840
+ const db = getDb();
1841
+ const rows = await db
1842
+ .select()
1843
+ .from(schema.discounts)
1844
+ .where(eq(schema.discounts.id, id))
1845
+ .limit(1);
1846
+ return rows[0] ?? null;
1847
+ }
1848
+ /** Enabled code discount by (case-insensitive) code — checkout entry point. */
1849
+ export async function findDiscountByCode(code) {
1850
+ await ensureDiscountsSchemaSafe();
1851
+ const db = getDb();
1852
+ const normalized = code.trim().toUpperCase();
1853
+ if (!normalized)
1854
+ return null;
1855
+ const rows = await db
1856
+ .select()
1857
+ .from(schema.discounts)
1858
+ .where(and(eq(schema.discounts.method, 'code'), sql `upper(${schema.discounts.code}) = ${normalized}`))
1859
+ .limit(1);
1860
+ return rows[0] ?? null;
1861
+ }
1862
+ /** Enabled automatic discounts — always evaluated at checkout. */
1863
+ export async function findActiveAutomaticDiscounts() {
1864
+ await ensureDiscountsSchemaSafe();
1865
+ const db = getDb();
1866
+ return db
1867
+ .select()
1868
+ .from(schema.discounts)
1869
+ .where(and(eq(schema.discounts.method, 'automatic'), eq(schema.discounts.enabled, true)))
1870
+ .orderBy(asc(schema.discounts.sortOrder), asc(schema.discounts.createdAt));
1871
+ }
1872
+ export async function createDiscount(data) {
1873
+ await ensureDiscountsSchema();
1874
+ const db = getDb();
1875
+ const [row] = await db
1876
+ .insert(schema.discounts)
1877
+ .values({
1878
+ method: data.method ?? 'code',
1879
+ code: data.code ? data.code.trim().toUpperCase() : null,
1880
+ title: data.title,
1881
+ type: data.type,
1882
+ value: String(data.value ?? 0),
1883
+ appliesTo: data.appliesTo ?? 'order',
1884
+ targetIds: data.targetIds ?? [],
1885
+ minSubtotal: data.minSubtotal != null ? String(data.minSubtotal) : null,
1886
+ minQuantity: data.minQuantity ?? null,
1887
+ eligibility: data.eligibility ?? 'all',
1888
+ groupIds: data.groupIds ?? [],
1889
+ usageLimit: data.usageLimit ?? null,
1890
+ oncePerCustomer: data.oncePerCustomer ?? false,
1891
+ combinesWith: data.combinesWith ?? {},
1892
+ bxgy: data.bxgy ?? null,
1893
+ startsAt: data.startsAt ?? null,
1894
+ endsAt: data.endsAt ?? null,
1895
+ enabled: data.enabled ?? true,
1896
+ sortOrder: data.sortOrder ?? 0,
1897
+ })
1898
+ .returning();
1899
+ return row;
1900
+ }
1901
+ export async function updateDiscount(id, data) {
1902
+ await ensureDiscountsSchema();
1903
+ const db = getDb();
1904
+ const [row] = await db
1905
+ .update(schema.discounts)
1906
+ .set({
1907
+ ...(data.method !== undefined && { method: data.method }),
1908
+ ...(data.code !== undefined && {
1909
+ code: data.code ? data.code.trim().toUpperCase() : null,
1910
+ }),
1911
+ ...(data.title !== undefined && { title: data.title }),
1912
+ ...(data.type !== undefined && { type: data.type }),
1913
+ ...(data.value !== undefined && { value: String(data.value ?? 0) }),
1914
+ ...(data.appliesTo !== undefined && { appliesTo: data.appliesTo }),
1915
+ ...(data.targetIds !== undefined && { targetIds: data.targetIds ?? [] }),
1916
+ ...(data.minSubtotal !== undefined && {
1917
+ minSubtotal: data.minSubtotal != null ? String(data.minSubtotal) : null,
1918
+ }),
1919
+ ...(data.minQuantity !== undefined && { minQuantity: data.minQuantity ?? null }),
1920
+ ...(data.eligibility !== undefined && { eligibility: data.eligibility }),
1921
+ ...(data.groupIds !== undefined && { groupIds: data.groupIds ?? [] }),
1922
+ ...(data.usageLimit !== undefined && { usageLimit: data.usageLimit ?? null }),
1923
+ ...(data.oncePerCustomer !== undefined && { oncePerCustomer: data.oncePerCustomer }),
1924
+ ...(data.combinesWith !== undefined && { combinesWith: data.combinesWith ?? {} }),
1925
+ ...(data.bxgy !== undefined && { bxgy: data.bxgy ?? null }),
1926
+ ...(data.startsAt !== undefined && { startsAt: data.startsAt ?? null }),
1927
+ ...(data.endsAt !== undefined && { endsAt: data.endsAt ?? null }),
1928
+ ...(data.enabled !== undefined && { enabled: data.enabled }),
1929
+ ...(data.sortOrder !== undefined && { sortOrder: data.sortOrder }),
1930
+ updatedAt: new Date(),
1931
+ })
1932
+ .where(eq(schema.discounts.id, id))
1933
+ .returning();
1934
+ return row ?? null;
1935
+ }
1936
+ /**
1937
+ * Hard delete. Redemption history survives (soft refs) so past orders keep
1938
+ * their recorded discount code + amount snapshots.
1939
+ */
1940
+ export async function deleteDiscount(id) {
1941
+ await ensureDiscountsSchema();
1942
+ const db = getDb();
1943
+ const deleted = await db
1944
+ .delete(schema.discounts)
1945
+ .where(eq(schema.discounts.id, id))
1946
+ .returning({ id: schema.discounts.id });
1947
+ return deleted.length > 0;
1948
+ }
1949
+ /**
1950
+ * Prior redemptions by this customer (id OR email) — powers oncePerCustomer.
1951
+ */
1952
+ export async function countDiscountRedemptionsForCustomer(discountId, customer) {
1953
+ await ensureDiscountsSchemaSafe();
1954
+ const db = getDb();
1955
+ const conditions = [];
1956
+ if (customer.customerId) {
1957
+ conditions.push(eq(schema.discountRedemptions.customerId, customer.customerId));
1958
+ }
1959
+ if (customer.email) {
1960
+ conditions.push(sql `lower(${schema.discountRedemptions.email}) = ${customer.email.toLowerCase()}`);
1961
+ }
1962
+ if (conditions.length === 0)
1963
+ return 0;
1964
+ const rows = await db
1965
+ .select({ count: sql `count(*)` })
1966
+ .from(schema.discountRedemptions)
1967
+ .where(and(eq(schema.discountRedemptions.discountId, discountId), or(...conditions)));
1968
+ return Number(rows[0]?.count || 0);
1969
+ }
1970
+ /**
1971
+ * Atomically claim one use of a discount (usage-limit guard) and record the
1972
+ * redemption. Returns false when the limit was exhausted between evaluation
1973
+ * and order creation — the order still stands (the race window is seconds);
1974
+ * the claim failure is logged for reconciliation.
1975
+ */
1976
+ export async function recordDiscountRedemption(params) {
1977
+ await ensureDiscountsSchemaSafe();
1978
+ const db = getDb();
1979
+ const claimed = await db.execute(sql `
1980
+ UPDATE discounts
1981
+ SET used_count = used_count + 1, updated_at = now()
1982
+ WHERE id = ${params.discountId}
1983
+ AND (usage_limit IS NULL OR used_count < usage_limit)
1984
+ RETURNING id
1985
+ `);
1986
+ const claimedRows = claimed.rows ?? [];
1987
+ const ok = Array.isArray(claimedRows) ? claimedRows.length > 0 : false;
1988
+ await db.insert(schema.discountRedemptions).values({
1989
+ discountId: params.discountId,
1990
+ orderId: params.orderId,
1991
+ customerId: params.customerId ?? null,
1992
+ email: params.email ? params.email.toLowerCase() : null,
1993
+ amount: (params.amountCents / 100).toFixed(2),
1994
+ });
1995
+ if (!ok) {
1996
+ console.warn('[Discounts] usage-limit claim failed post-order (limit hit in the race window):', params.discountId, 'order:', params.orderId);
1997
+ }
1998
+ return ok;
1999
+ }
2000
+ // =============================================================================
1743
2001
  // Order Queries (for admin module)
1744
2002
  // =============================================================================
1745
2003
  /**
@@ -1829,12 +2087,50 @@ export async function getOrdersByStatus(periodDays) {
1829
2087
  count: Number(r.count),
1830
2088
  }));
1831
2089
  }
2090
+ /**
2091
+ * Self-heal for store_settings columns added after a store's branch was
2092
+ * created (drizzle `.select()` enumerates every schema column, so a missing
2093
+ * column breaks ALL settings reads). Idempotent; cached in-process. Called
2094
+ * best-effort at the top of findSettings/upsertSettings — the settings
2095
+ * chokepoints every surface goes through.
2096
+ *
2097
+ * Columns owned here:
2098
+ * - store_country varchar(2) — SDK 0.7.0 (store-country single source of truth)
2099
+ */
2100
+ let storeSettingsSchemaEnsured = false;
2101
+ export async function ensureStoreSettingsSchema() {
2102
+ if (storeSettingsSchemaEnsured)
2103
+ return;
2104
+ const db = getDb();
2105
+ try {
2106
+ await db.execute(sql `
2107
+ ALTER TABLE store_settings
2108
+ ADD COLUMN IF NOT EXISTS store_country varchar(2)
2109
+ `);
2110
+ storeSettingsSchemaEnsured = true;
2111
+ }
2112
+ catch (err) {
2113
+ // Don't poison the cache on transient failures — retry next call.
2114
+ console.error('[ensureStoreSettingsSchema] migration failed:', err);
2115
+ throw err;
2116
+ }
2117
+ }
2118
+ /** Best-effort variant — never throws (the subsequent read surfaces real errors). */
2119
+ export async function ensureStoreSettingsSchemaSafe() {
2120
+ try {
2121
+ await ensureStoreSettingsSchema();
2122
+ }
2123
+ catch {
2124
+ // logged inside ensureStoreSettingsSchema
2125
+ }
2126
+ }
1832
2127
  /**
1833
2128
  * Find store settings.
1834
2129
  * Returns null if no settings exist (first time access).
1835
2130
  * Each store has only one settings row.
1836
2131
  */
1837
2132
  export async function findSettings() {
2133
+ await ensureStoreSettingsSchemaSafe();
1838
2134
  const db = getDb();
1839
2135
  const [settings] = await db
1840
2136
  .select()
@@ -1848,6 +2144,7 @@ export async function findSettings() {
1848
2144
  logoUrl: settings.logoUrl,
1849
2145
  storeCurrency: settings.storeCurrency,
1850
2146
  storeTimezone: settings.storeTimezone,
2147
+ storeCountry: settings.storeCountry,
1851
2148
  taxIncludedInPrices: settings.taxIncludedInPrices,
1852
2149
  shippingEnabled: settings.shippingEnabled,
1853
2150
  freeShippingThreshold: settings.freeShippingThreshold
@@ -1902,6 +2199,7 @@ export async function listMetafieldKeys() {
1902
2199
  * Each store has only one settings row.
1903
2200
  */
1904
2201
  export async function upsertSettings(data) {
2202
+ await ensureStoreSettingsSchemaSafe();
1905
2203
  const db = getDb();
1906
2204
  // Check if settings exist
1907
2205
  const existing = await db
@@ -1914,6 +2212,7 @@ export async function upsertSettings(data) {
1914
2212
  logoUrl: data.logoUrl ?? undefined,
1915
2213
  storeCurrency: data.storeCurrency ?? undefined,
1916
2214
  storeTimezone: data.storeTimezone ?? undefined,
2215
+ storeCountry: data.storeCountry ?? undefined,
1917
2216
  taxIncludedInPrices: data.taxIncludedInPrices ?? undefined,
1918
2217
  shippingEnabled: data.shippingEnabled ?? undefined,
1919
2218
  freeShippingThreshold: data.freeShippingThreshold !== null && data.freeShippingThreshold !== undefined
@@ -1959,6 +2258,7 @@ export async function upsertSettings(data) {
1959
2258
  logoUrl: result.logoUrl,
1960
2259
  storeCurrency: result.storeCurrency,
1961
2260
  storeTimezone: result.storeTimezone,
2261
+ storeCountry: result.storeCountry,
1962
2262
  taxIncludedInPrices: result.taxIncludedInPrices,
1963
2263
  shippingEnabled: result.shippingEnabled,
1964
2264
  freeShippingThreshold: result.freeShippingThreshold