@fayz-ai/shop 0.9.0 → 0.10.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 (144) hide show
  1. package/README.md +18 -1
  2. package/dist/admin.d.ts +2 -0
  3. package/dist/admin.d.ts.map +1 -0
  4. package/dist/catalog.d.ts +1 -3
  5. package/dist/catalog.d.ts.map +1 -1
  6. package/dist/catalog.js +2 -2
  7. package/dist/chunk-3GERSY5F.js +526 -0
  8. package/dist/chunk-3GERSY5F.js.map +1 -0
  9. package/dist/chunk-FQPGE3YN.js +3 -0
  10. package/dist/{chunk-JR25TISO.js.map → chunk-FQPGE3YN.js.map} +1 -1
  11. package/dist/{chunk-CTKZ6O2J.js → chunk-IKNGCRUM.js} +41 -15
  12. package/dist/chunk-IKNGCRUM.js.map +1 -0
  13. package/dist/{chunk-IYQHKMLB.js → chunk-IL6GA4ER.js} +4 -4
  14. package/dist/chunk-IL6GA4ER.js.map +1 -0
  15. package/dist/chunk-KLDS7IPU.js +17 -0
  16. package/dist/chunk-KLDS7IPU.js.map +1 -0
  17. package/dist/chunk-RGTIJQJL.js +93 -0
  18. package/dist/chunk-RGTIJQJL.js.map +1 -0
  19. package/dist/{chunk-SVTCJ6HP.js → chunk-U3O2I3RM.js} +395 -27
  20. package/dist/chunk-U3O2I3RM.js.map +1 -0
  21. package/dist/chunk-XERLK5LD.js +1415 -0
  22. package/dist/chunk-XERLK5LD.js.map +1 -0
  23. package/dist/chunk-XOP6SHPB.js +142 -0
  24. package/dist/chunk-XOP6SHPB.js.map +1 -0
  25. package/dist/client.d.ts +2 -0
  26. package/dist/client.d.ts.map +1 -0
  27. package/dist/{mock-catalog.d.ts → data/mock-catalog.d.ts} +21 -1
  28. package/dist/data/mock-catalog.d.ts.map +1 -0
  29. package/dist/data/mock-provider.d.ts +133 -0
  30. package/dist/data/mock-provider.d.ts.map +1 -0
  31. package/dist/data/mock.d.ts +3 -0
  32. package/dist/data/mock.d.ts.map +1 -0
  33. package/dist/data/provider.d.ts +142 -0
  34. package/dist/data/provider.d.ts.map +1 -0
  35. package/dist/data/supabase-provider.d.ts +100 -0
  36. package/dist/data/supabase-provider.d.ts.map +1 -0
  37. package/dist/data/tables.d.ts +35 -0
  38. package/dist/data/tables.d.ts.map +1 -0
  39. package/dist/data/tenant.d.ts.map +1 -0
  40. package/dist/entities/entity-reconcile.d.ts +32 -0
  41. package/dist/entities/entity-reconcile.d.ts.map +1 -0
  42. package/dist/entities/entity-sql.d.ts +50 -0
  43. package/dist/entities/entity-sql.d.ts.map +1 -0
  44. package/dist/entities/entity-store.d.ts +58 -0
  45. package/dist/entities/entity-store.d.ts.map +1 -0
  46. package/dist/entities/index.d.ts +11 -0
  47. package/dist/entities/index.d.ts.map +1 -0
  48. package/dist/entities/mock-entity-store.d.ts +17 -0
  49. package/dist/entities/mock-entity-store.d.ts.map +1 -0
  50. package/dist/entities/sidecar.d.ts +9 -0
  51. package/dist/entities/sidecar.d.ts.map +1 -0
  52. package/dist/entities/supabase-entity-store.d.ts +8 -0
  53. package/dist/entities/supabase-entity-store.d.ts.map +1 -0
  54. package/dist/entities.js +159 -0
  55. package/dist/entities.js.map +1 -0
  56. package/dist/index.d.ts +17 -10
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +728 -41
  59. package/dist/index.js.map +1 -1
  60. package/dist/inventory/allocation.d.ts +98 -0
  61. package/dist/inventory/allocation.d.ts.map +1 -0
  62. package/dist/inventory/index.d.ts +13 -0
  63. package/dist/inventory/index.d.ts.map +1 -0
  64. package/dist/inventory/mock-stock-store.d.ts +89 -0
  65. package/dist/inventory/mock-stock-store.d.ts.map +1 -0
  66. package/dist/inventory/postgrest-double.d.ts +65 -0
  67. package/dist/inventory/postgrest-double.d.ts.map +1 -0
  68. package/dist/inventory/shopify-mapping.d.ts +67 -0
  69. package/dist/inventory/shopify-mapping.d.ts.map +1 -0
  70. package/dist/inventory/stock-contract.d.ts +46 -0
  71. package/dist/inventory/stock-contract.d.ts.map +1 -0
  72. package/dist/inventory/stock-store.d.ts +96 -0
  73. package/dist/inventory/stock-store.d.ts.map +1 -0
  74. package/dist/inventory/stock-types.d.ts +292 -0
  75. package/dist/inventory/stock-types.d.ts.map +1 -0
  76. package/dist/inventory/supabase-stock-store.d.ts +59 -0
  77. package/dist/inventory/supabase-stock-store.d.ts.map +1 -0
  78. package/dist/inventory/vocabulary.d.ts +86 -0
  79. package/dist/inventory/vocabulary.d.ts.map +1 -0
  80. package/dist/inventory.js +4 -0
  81. package/dist/inventory.js.map +1 -0
  82. package/dist/lib/catalog.d.ts +4 -0
  83. package/dist/lib/catalog.d.ts.map +1 -0
  84. package/dist/{commerce.d.ts → lib/commerce.d.ts} +2 -2
  85. package/dist/lib/commerce.d.ts.map +1 -0
  86. package/dist/lib/emails.d.ts +50 -0
  87. package/dist/lib/emails.d.ts.map +1 -0
  88. package/dist/lib/message-events.d.ts +4 -0
  89. package/dist/lib/message-events.d.ts.map +1 -0
  90. package/dist/lib/runtime.d.ts +5 -0
  91. package/dist/lib/runtime.d.ts.map +1 -0
  92. package/dist/mock.d.ts +1 -2
  93. package/dist/mock.d.ts.map +1 -1
  94. package/dist/mock.js +4 -2
  95. package/dist/provider.d.ts +1 -88
  96. package/dist/provider.d.ts.map +1 -1
  97. package/dist/rules/adapter.d.ts +36 -0
  98. package/dist/rules/adapter.d.ts.map +1 -0
  99. package/dist/rules/attributes.d.ts +64 -0
  100. package/dist/rules/attributes.d.ts.map +1 -0
  101. package/dist/rules/context.d.ts +43 -0
  102. package/dist/rules/context.d.ts.map +1 -0
  103. package/dist/rules/evaluator.d.ts +37 -0
  104. package/dist/rules/evaluator.d.ts.map +1 -0
  105. package/dist/rules/index.d.ts +13 -0
  106. package/dist/rules/index.d.ts.map +1 -0
  107. package/dist/rules/prices.d.ts +25 -0
  108. package/dist/rules/prices.d.ts.map +1 -0
  109. package/dist/rules/rule-types.d.ts +160 -0
  110. package/dist/rules/rule-types.d.ts.map +1 -0
  111. package/dist/rules.js +22 -0
  112. package/dist/rules.js.map +1 -0
  113. package/dist/runtime.d.ts +1 -4
  114. package/dist/runtime.d.ts.map +1 -1
  115. package/dist/runtime.js +5 -3
  116. package/dist/types.d.ts +231 -0
  117. package/dist/types.d.ts.map +1 -1
  118. package/package.json +26 -20
  119. package/dist/chunk-CTKZ6O2J.js.map +0 -1
  120. package/dist/chunk-IYQHKMLB.js.map +0 -1
  121. package/dist/chunk-JR25TISO.js +0 -3
  122. package/dist/chunk-SVTCJ6HP.js.map +0 -1
  123. package/dist/commerce.d.ts.map +0 -1
  124. package/dist/mock-catalog.d.ts.map +0 -1
  125. package/dist/mock-provider.d.ts +0 -60
  126. package/dist/mock-provider.d.ts.map +0 -1
  127. package/dist/supabase-provider.d.ts +0 -56
  128. package/dist/supabase-provider.d.ts.map +0 -1
  129. package/dist/tables.d.ts +0 -12
  130. package/dist/tables.d.ts.map +0 -1
  131. package/dist/tenant.d.ts.map +0 -1
  132. package/src/catalog.ts +0 -3
  133. package/src/commerce.ts +0 -120
  134. package/src/index.ts +0 -40
  135. package/src/mock-catalog.ts +0 -266
  136. package/src/mock-provider.ts +0 -480
  137. package/src/mock.ts +0 -2
  138. package/src/provider.ts +0 -117
  139. package/src/runtime.ts +0 -20
  140. package/src/supabase-provider.ts +0 -754
  141. package/src/tables.ts +0 -17
  142. package/src/tenant.ts +0 -21
  143. package/src/types.ts +0 -623
  144. /package/dist/{tenant.d.ts → data/tenant.d.ts} +0 -0
package/dist/index.js CHANGED
@@ -1,11 +1,18 @@
1
- import { getShopProvider } from './chunk-IYQHKMLB.js';
2
- export { getShopProvider, resetShopProvider, setShopProvider } from './chunk-IYQHKMLB.js';
3
- import './chunk-JR25TISO.js';
4
- export { MockShopProvider, createMockShopProvider } from './chunk-SVTCJ6HP.js';
5
- export { MOCK_CATEGORIES, MOCK_DISCOUNTS, MOCK_PRODUCTS, buildMockCatalog, svgPlaceholder } from './chunk-CTKZ6O2J.js';
1
+ import { T } from './chunk-XERLK5LD.js';
2
+ export { DEFAULT_STOCK_TRACKING, MockStockStore, STOCK_TRACKING_MODES, StockMovementError, SupabaseStockStore, createMockStockStore, createSupabaseStockStore, listStockMovementKinds, listStockReasonCodes, mirroredInventoryCount, registerStockMovementKind, registerStockReasonCode, registerStockStore, replayStockLedger, resolveStockStore, validateStockMovement } from './chunk-XERLK5LD.js';
3
+ import { getShopProvider } from './chunk-IL6GA4ER.js';
4
+ export { getShopProvider, resetShopProvider, setShopProvider } from './chunk-IL6GA4ER.js';
5
+ import './chunk-FQPGE3YN.js';
6
+ export { MockShopProvider, createMockShopProvider } from './chunk-U3O2I3RM.js';
7
+ export { MOCK_CATEGORIES, MOCK_DISCOUNTS, MOCK_PRODUCTS, buildMockCatalog, svgPlaceholder } from './chunk-IKNGCRUM.js';
8
+ import { createSupabaseEntityStore } from './chunk-RGTIJQJL.js';
9
+ import { getShopTenantId } from './chunk-KLDS7IPU.js';
10
+ export { getShopTenantId, setShopTenantResolver } from './chunk-KLDS7IPU.js';
11
+ import { resolveEntityStore, SIDECAR_TABLE, validateSidecarWrite } from './chunk-XOP6SHPB.js';
12
+ import './chunk-3GERSY5F.js';
6
13
  import { getSupabaseClientOptional } from '@fayz-ai/core';
7
14
 
8
- // src/commerce.ts
15
+ // src/lib/commerce.ts
9
16
  function round2(n) {
10
17
  return Math.round(n * 100) / 100;
11
18
  }
@@ -79,38 +86,6 @@ async function computeCartTotals(input, provider = getShopProvider()) {
79
86
  async function getCustomerOrders(customerId, provider = getShopProvider()) {
80
87
  return provider.listOrders({ customerId, limit: 100 });
81
88
  }
82
-
83
- // src/tenant.ts
84
- var _resolver = null;
85
- function setShopTenantResolver(resolver) {
86
- _resolver = resolver;
87
- }
88
- function getShopTenantId() {
89
- if (!_resolver) return void 0;
90
- try {
91
- return _resolver();
92
- } catch {
93
- return void 0;
94
- }
95
- }
96
-
97
- // src/tables.ts
98
- var T = {
99
- products: "plg_shop_products",
100
- productImages: "plg_shop_product_images",
101
- categories: "plg_shop_categories",
102
- orders: "plg_shop_orders",
103
- orderItems: "plg_shop_order_items",
104
- customers: "plg_shop_customers",
105
- discounts: "plg_shop_discounts",
106
- salesChannels: "plg_shop_channels",
107
- // Core, not plg_shop_*: a delivery zone is a fact about the business, and
108
- // fulfilment and financial read it too. Same reasoning as addresses and
109
- // orders — see migration 0021.
110
- shippingZones: "shipping_zones"
111
- };
112
-
113
- // src/supabase-provider.ts
114
89
  function getDb() {
115
90
  const supabase = getSupabaseClientOptional();
116
91
  if (!supabase) throw new Error("@fayz-ai/shop: Supabase client not initialized. Call setGlobalSupabaseClient() first.");
@@ -297,6 +272,122 @@ function rowToSalesChannel(row) {
297
272
  updatedAt: row.updated_at
298
273
  };
299
274
  }
275
+ function segmentPayload(input) {
276
+ const p = {};
277
+ if (input.name !== void 0) p.name = input.name;
278
+ if (input.description !== void 0) p.description = input.description;
279
+ if (input.minOrders !== void 0) p.min_orders = nullableNumber(input.minOrders);
280
+ if (input.maxOrders !== void 0) p.max_orders = nullableNumber(input.maxOrders);
281
+ if (input.minSpent !== void 0) p.min_spent = nullableNumber(input.minSpent);
282
+ if (input.maxSpent !== void 0) p.max_spent = nullableNumber(input.maxSpent);
283
+ if (input.joinedAfter !== void 0) p.joined_after = input.joinedAfter || null;
284
+ if (input.joinedBefore !== void 0) p.joined_before = input.joinedBefore || null;
285
+ return p;
286
+ }
287
+ var GIFT_CODE_ALPHABET = "ABCDEFGHJKLMNPQRTUVWXY2346789";
288
+ var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "avif", "bmp"]);
289
+ var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set(["mp4", "mov", "webm", "m4v", "avi", "mkv"]);
290
+ function generateGiftCardCode() {
291
+ const bytes = new Uint8Array(12);
292
+ globalThis.crypto.getRandomValues(bytes);
293
+ const chars = Array.from(bytes, (b) => GIFT_CODE_ALPHABET[b % GIFT_CODE_ALPHABET.length]);
294
+ return `${chars.slice(0, 4).join("")}-${chars.slice(4, 8).join("")}-${chars.slice(8, 12).join("")}`;
295
+ }
296
+ function mediaKindOf(name, mime) {
297
+ if (mime?.startsWith("image/")) return "image";
298
+ if (mime?.startsWith("video/")) return "video";
299
+ const ext = (name.split(".").pop() ?? "").toLowerCase();
300
+ if (IMAGE_EXTENSIONS.has(ext)) return "image";
301
+ if (VIDEO_EXTENSIONS.has(ext)) return "video";
302
+ return "document";
303
+ }
304
+ function nullableNumber(value) {
305
+ if (value === null || value === void 0 || value === "") return null;
306
+ const n = Number(value);
307
+ return Number.isFinite(n) ? n : null;
308
+ }
309
+ function rowToCollection(row) {
310
+ const members = row.members ?? [];
311
+ return {
312
+ id: row.id,
313
+ tenantId: row.tenant_id,
314
+ title: row.title,
315
+ handle: row.handle ?? slugify(row.title ?? ""),
316
+ description: row.description ?? null,
317
+ imageUrl: row.image_url ?? null,
318
+ seoTitle: row.seo_title ?? null,
319
+ seoDescription: row.seo_description ?? null,
320
+ published: row.published ?? true,
321
+ sortOrder: row.sort_order ?? 0,
322
+ productIds: [...members].sort((a, b) => (a.position ?? 0) - (b.position ?? 0)).map((m) => m.product_id),
323
+ createdAt: row.created_at,
324
+ updatedAt: row.updated_at ?? row.created_at
325
+ };
326
+ }
327
+ function rowToCustomerGroup(row) {
328
+ const members = row.members ?? [];
329
+ return {
330
+ id: row.id,
331
+ tenantId: row.tenant_id,
332
+ name: row.name,
333
+ description: row.description ?? null,
334
+ color: row.color ?? null,
335
+ customerIds: members.map((m) => m.customer_id),
336
+ createdAt: row.created_at,
337
+ updatedAt: row.updated_at ?? row.created_at
338
+ };
339
+ }
340
+ function rowToCustomerSegment(row) {
341
+ return {
342
+ id: row.id,
343
+ tenantId: row.tenant_id,
344
+ name: row.name,
345
+ description: row.description ?? null,
346
+ minOrders: nullableNumber(row.min_orders),
347
+ maxOrders: nullableNumber(row.max_orders),
348
+ minSpent: nullableNumber(row.min_spent),
349
+ maxSpent: nullableNumber(row.max_spent),
350
+ joinedAfter: row.joined_after ?? null,
351
+ joinedBefore: row.joined_before ?? null,
352
+ createdAt: row.created_at,
353
+ updatedAt: row.updated_at ?? row.created_at
354
+ };
355
+ }
356
+ var SHOP_EVENT_PREFIX = "shop.";
357
+ var EMAIL_CHANNEL = "email";
358
+ function rowToEmailTemplate(row) {
359
+ return {
360
+ id: row.id,
361
+ tenantId: row.tenant_id,
362
+ event: String(row.event_key ?? "").slice(SHOP_EVENT_PREFIX.length),
363
+ // NULL stays NULL: the catalog resolves the default (resolveEmailTemplates),
364
+ // not this mapping. Collapsing to `false` here would silently disable every
365
+ // template whose row exists only because its text was edited.
366
+ enabled: row.enabled ?? null,
367
+ // Empty string and NULL both mean "no override" — a subject the merchant
368
+ // cleared should fall back to the default, not send a blank subject line.
369
+ subject: row.subject || null,
370
+ body: row.body || null,
371
+ updatedAt: row.updated_at ?? row.created_at
372
+ };
373
+ }
374
+ function rowToGiftCard(row) {
375
+ return {
376
+ id: row.id,
377
+ tenantId: row.tenant_id,
378
+ code: String(row.code ?? "").toUpperCase(),
379
+ initialBalance: Number(row.initial_balance ?? 0),
380
+ balance: Number(row.balance ?? 0),
381
+ currency: row.currency ?? "BRL",
382
+ status: row.status ?? "active",
383
+ expiresAt: row.expires_at ?? null,
384
+ customerId: row.customer_id ?? null,
385
+ customerEmail: row.customer_email ?? null,
386
+ note: row.note ?? null,
387
+ createdAt: row.created_at,
388
+ updatedAt: row.updated_at ?? row.created_at
389
+ };
390
+ }
300
391
  var SupabaseShopProvider = class {
301
392
  // ---------------------------------------------------------------------------
302
393
  // Products
@@ -323,6 +414,38 @@ var SupabaseShopProvider = class {
323
414
  if (error) throw error;
324
415
  return data ? rowToProduct(data) : null;
325
416
  }
417
+ /** Option axes + variants from the 0028 tables (spine M5). The storefront
418
+ * RLS on both tables already restricts anon reads to active variants of
419
+ * publicly readable products; the status filter here keeps the admin path
420
+ * (authenticated tenant member, who CAN see archived rows) consistent. */
421
+ async getProductVariants(productId) {
422
+ const db = getDb();
423
+ const [optionsRes, variantsRes] = await Promise.all([
424
+ db.from(T.productOptions).select("*").eq("product_id", productId).order("position", { ascending: true }),
425
+ db.from(T.productVariants).select("*").eq("product_id", productId).eq("status", "active").order("position", { ascending: true })
426
+ ]);
427
+ if (optionsRes.error) throw optionsRes.error;
428
+ if (variantsRes.error) throw variantsRes.error;
429
+ const options = (optionsRes.data ?? []).map((row) => ({
430
+ id: row.id,
431
+ name: row.name,
432
+ values: Array.isArray(row.values) ? row.values.map(String) : [],
433
+ position: row.position ?? 0
434
+ }));
435
+ const variants = (variantsRes.data ?? []).map((row) => ({
436
+ id: row.id,
437
+ productId: row.product_id,
438
+ options: row.options && typeof row.options === "object" ? row.options : {},
439
+ sku: row.sku ?? null,
440
+ price: row.price == null ? null : Number(row.price),
441
+ compareAtPrice: row.compare_at_price == null ? null : Number(row.compare_at_price),
442
+ inventoryCount: row.inventory_count ?? 0,
443
+ position: row.position ?? 0,
444
+ status: row.status ?? "active",
445
+ imageId: row.image_id ?? null
446
+ }));
447
+ return { options, variants };
448
+ }
326
449
  async createProduct(input) {
327
450
  const db = getDb();
328
451
  const tenantId = getTenantId();
@@ -549,7 +672,15 @@ var SupabaseShopProvider = class {
549
672
  if (!tenantId) throw new Error("@fayz-ai/shop: placeOrder requires a tenant id (configure the shop tenant resolver).");
550
673
  const { data, error } = await db.rpc("shop_place_order", {
551
674
  p_tenant_id: tenantId,
552
- p_items: input.items.map((i) => ({ product_id: i.productId, quantity: i.quantity, options_label: i.optionsLabel ?? null })),
675
+ // variant_id rides on each line (0029): the RPC prices and decrements
676
+ // the variant when present, product-level otherwise. Pre-0029 servers
677
+ // ignore unknown jsonb keys, so this stays compatible either way.
678
+ p_items: input.items.map((i) => ({
679
+ product_id: i.productId,
680
+ variant_id: asUuid(i.variantId) ?? void 0,
681
+ quantity: i.quantity,
682
+ options_label: i.optionsLabel ?? null
683
+ })),
553
684
  p_customer_id: asUuid(input.customerId),
554
685
  p_customer_name: input.customer?.name ?? null,
555
686
  p_customer_email: input.customer?.email ?? null,
@@ -572,7 +703,11 @@ var SupabaseShopProvider = class {
572
703
  label: input.shippingAddress.label ?? null
573
704
  } : null,
574
705
  p_payment_method: input.paymentMethod ?? null,
575
- p_notes: input.notes ?? null
706
+ p_notes: input.notes ?? null,
707
+ // Only when something contributed: p_metadata exists from 0029 on, and
708
+ // a pre-0029 server rejects unknown NAMED arguments (unlike unknown
709
+ // jsonb keys). Absent metadata therefore must not send the key at all.
710
+ ...input.metadata !== void 0 ? { p_metadata: input.metadata } : {}
576
711
  });
577
712
  if (error) throw error;
578
713
  const orderId = typeof data === "string" ? data : data?.id ?? data?.order_id ?? data;
@@ -636,6 +771,50 @@ var SupabaseShopProvider = class {
636
771
  return rowToCustomer(row);
637
772
  }
638
773
  // ---------------------------------------------------------------------------
774
+ // Promotions (spine M6) — evaluation happens INSIDE the database via the
775
+ // SECURITY DEFINER function (migrations/0033), so an anonymous storefront
776
+ // never reads promotion rows or codes directly: it sends the cart snapshot
777
+ // and receives ordered actions. The function mirrors the TS reference
778
+ // evaluator (src/rules/evaluator.ts), which is the tested spec.
779
+ // ---------------------------------------------------------------------------
780
+ async evaluatePromotions(input) {
781
+ const db = getDb();
782
+ const tenantId = getTenantId();
783
+ if (!tenantId) throw new Error("@fayz-ai/shop: evaluatePromotions requires a tenant id (configure the shop tenant resolver).");
784
+ const cart = input.cart;
785
+ const { data, error } = await db.rpc("shop_compute_promotion_actions", {
786
+ p_tenant_id: tenantId,
787
+ // The serializable RuleCartSnapshot, snake_case on the wire — the same
788
+ // contract 0033 documents for p_cart.
789
+ p_cart: {
790
+ items: cart.items.map((item) => ({
791
+ id: item.id ?? item.productId,
792
+ product_id: item.productId,
793
+ quantity: item.quantity,
794
+ unit_price: item.unitPrice,
795
+ category_id: item.categoryId ?? null,
796
+ collection_id: item.collectionId ?? null
797
+ })),
798
+ shipping_total: cart.shippingTotal ?? null,
799
+ region: cart.region ?? null,
800
+ customer: cart.customer ? { email: cart.customer.email ?? null, tags: cart.customer.tags ?? [] } : null,
801
+ codes: cart.codes ?? []
802
+ }
803
+ });
804
+ if (error) throw error;
805
+ const rows = Array.isArray(data) ? data : [];
806
+ return rows.map((row) => {
807
+ const code = row.code ?? null;
808
+ if (row.action === "addShippingDiscount") {
809
+ return { action: "addShippingDiscount", promotionId: String(row.promotion_id), code, amount: Number(row.amount) };
810
+ }
811
+ if (row.action === "addFreeItem") {
812
+ return { action: "addFreeItem", promotionId: String(row.promotion_id), code, productId: String(row.product_id), quantity: Number(row.quantity) };
813
+ }
814
+ return { action: "addItemDiscount", promotionId: String(row.promotion_id), code, itemId: String(row.item_id), amount: Number(row.amount) };
815
+ });
816
+ }
817
+ // ---------------------------------------------------------------------------
639
818
  // Discounts
640
819
  // ---------------------------------------------------------------------------
641
820
  async listDiscounts(options) {
@@ -714,11 +893,519 @@ var SupabaseShopProvider = class {
714
893
  const { error } = await getDb().from(T.salesChannels).delete().eq("id", id);
715
894
  if (error) throw error;
716
895
  }
896
+ // ---------------------------------------------------------------------------
897
+ // Custom entities (spine M4). Storage resolves through the EntityStore
898
+ // indirection (I5): a promoted entity's registered store wins; everything
899
+ // else reads/writes the generic jsonb table (migration 0030), whose row
900
+ // access is the RLS generated from each declaration (I6).
901
+ // ---------------------------------------------------------------------------
902
+ entityStore(entity) {
903
+ return resolveEntityStore(entity, createSupabaseEntityStore);
904
+ }
905
+ async listEntityRecords(entity, options) {
906
+ return this.entityStore(entity).list(options);
907
+ }
908
+ async getEntityRecord(entity, id) {
909
+ return this.entityStore(entity).get(id);
910
+ }
911
+ async getEntityRecordBySlug(entity, slug) {
912
+ return this.entityStore(entity).getBySlug(slug);
913
+ }
914
+ async createEntityRecord(entity, input) {
915
+ return this.entityStore(entity).create(input);
916
+ }
917
+ async updateEntityRecord(entity, id, input) {
918
+ return this.entityStore(entity).update(id, input);
919
+ }
920
+ async deleteEntityRecord(entity, id) {
921
+ return this.entityStore(entity).delete(id);
922
+ }
923
+ // Sidecar fields on core records (migration 0031): one row per
924
+ // (reference, reference_id), validated on write against defineSidecarFields.
925
+ async getEntityFields(reference, referenceId) {
926
+ const { data, error } = await getDb().from(SIDECAR_TABLE).select("data").eq("reference", reference).eq("reference_id", referenceId).maybeSingle();
927
+ if (error) throw error;
928
+ return data ? data.data ?? {} : null;
929
+ }
930
+ async setEntityFields(reference, referenceId, fields) {
931
+ validateSidecarWrite(reference, fields);
932
+ const { data, error } = await getDb().from(SIDECAR_TABLE).upsert(
933
+ { tenant_id: getTenantId(), reference, reference_id: referenceId, data: fields },
934
+ { onConflict: "tenant_id,reference,reference_id" }
935
+ ).select("data").single();
936
+ if (error) throw error;
937
+ return data.data ?? {};
938
+ }
939
+ // -------------------------------------------------------------------------
940
+ // Coleções, grupos/segmentos, vale-presente, e-mails e mídia.
941
+ // -------------------------------------------------------------------------
942
+ async listCollections() {
943
+ const db = getDb();
944
+ const tenantId = getTenantId();
945
+ let q = db.from(T.collections).select(`*, members:${T.collectionProducts}(product_id, position)`).order("sort_order", { ascending: true });
946
+ if (tenantId) q = q.eq("tenant_id", tenantId);
947
+ const { data, error } = await q;
948
+ if (error) throw error;
949
+ return (data ?? []).map(rowToCollection);
950
+ }
951
+ async getCollection(id) {
952
+ const { data, error } = await getDb().from(T.collections).select(`*, members:${T.collectionProducts}(product_id, position)`).eq("id", id).maybeSingle();
953
+ if (error) throw error;
954
+ return data ? rowToCollection(data) : null;
955
+ }
956
+ async createCollection(input) {
957
+ const db = getDb();
958
+ const tenantId = getTenantId();
959
+ const { data, error } = await db.from(T.collections).insert({
960
+ tenant_id: tenantId,
961
+ title: input.title,
962
+ handle: input.handle?.trim() || slugify(input.title),
963
+ description: input.description ?? null,
964
+ image_url: input.imageUrl ?? null,
965
+ seo_title: input.seoTitle ?? null,
966
+ seo_description: input.seoDescription ?? null,
967
+ published: input.published ?? true,
968
+ sort_order: input.sortOrder ?? 0
969
+ }).select().single();
970
+ if (error) throw error;
971
+ await this.syncCollectionProducts(data.id, tenantId, input.productIds);
972
+ return await this.getCollection(data.id) ?? rowToCollection(data);
973
+ }
974
+ async updateCollection(id, input) {
975
+ const db = getDb();
976
+ const updates = {};
977
+ if (input.title !== void 0) updates.title = input.title;
978
+ if (input.handle !== void 0) updates.handle = input.handle?.trim() || slugify(String(input.title ?? ""));
979
+ if (input.description !== void 0) updates.description = input.description;
980
+ if (input.imageUrl !== void 0) updates.image_url = input.imageUrl;
981
+ if (input.seoTitle !== void 0) updates.seo_title = input.seoTitle;
982
+ if (input.seoDescription !== void 0) updates.seo_description = input.seoDescription;
983
+ if (input.published !== void 0) updates.published = input.published;
984
+ if (input.sortOrder !== void 0) updates.sort_order = input.sortOrder;
985
+ if (Object.keys(updates).length > 0) {
986
+ const { error } = await db.from(T.collections).update(updates).eq("id", id);
987
+ if (error) throw error;
988
+ }
989
+ if (input.productIds !== void 0) {
990
+ await this.syncCollectionProducts(id, getTenantId(), input.productIds);
991
+ }
992
+ const updated = await this.getCollection(id);
993
+ if (!updated) throw new Error("Collection not found");
994
+ return updated;
995
+ }
996
+ async deleteCollection(id) {
997
+ const { error } = await getDb().from(T.collections).delete().eq("id", id);
998
+ if (error) throw error;
999
+ }
1000
+ async listCustomerGroups() {
1001
+ const db = getDb();
1002
+ const tenantId = getTenantId();
1003
+ let q = db.from(T.customerGroups).select(`*, members:${T.customerGroupMembers}(customer_id)`).order("name", { ascending: true });
1004
+ if (tenantId) q = q.eq("tenant_id", tenantId);
1005
+ const { data, error } = await q;
1006
+ if (error) throw error;
1007
+ return (data ?? []).map(rowToCustomerGroup);
1008
+ }
1009
+ async createCustomerGroup(input) {
1010
+ const tenantId = getTenantId();
1011
+ const { data, error } = await getDb().from(T.customerGroups).insert({
1012
+ tenant_id: tenantId,
1013
+ name: input.name,
1014
+ description: input.description ?? null,
1015
+ color: input.color ?? null
1016
+ }).select().single();
1017
+ if (error) throw error;
1018
+ await this.syncGroupMembers(data.id, tenantId, input.customerIds);
1019
+ return { ...rowToCustomerGroup(data), customerIds: input.customerIds ?? [] };
1020
+ }
1021
+ async updateCustomerGroup(id, input) {
1022
+ const db = getDb();
1023
+ const updates = {};
1024
+ if (input.name !== void 0) updates.name = input.name;
1025
+ if (input.description !== void 0) updates.description = input.description;
1026
+ if (input.color !== void 0) updates.color = input.color;
1027
+ if (Object.keys(updates).length > 0) {
1028
+ const { error: error2 } = await db.from(T.customerGroups).update(updates).eq("id", id);
1029
+ if (error2) throw error2;
1030
+ }
1031
+ if (input.customerIds !== void 0) {
1032
+ await this.syncGroupMembers(id, getTenantId(), input.customerIds);
1033
+ }
1034
+ const { data, error } = await db.from(T.customerGroups).select(`*, members:${T.customerGroupMembers}(customer_id)`).eq("id", id).single();
1035
+ if (error) throw error;
1036
+ return rowToCustomerGroup(data);
1037
+ }
1038
+ async deleteCustomerGroup(id) {
1039
+ const { error } = await getDb().from(T.customerGroups).delete().eq("id", id);
1040
+ if (error) throw error;
1041
+ }
1042
+ async listCustomerSegments() {
1043
+ const db = getDb();
1044
+ const tenantId = getTenantId();
1045
+ let q = db.from(T.customerSegments).select("*").order("name", { ascending: true });
1046
+ if (tenantId) q = q.eq("tenant_id", tenantId);
1047
+ const { data, error } = await q;
1048
+ if (error) throw error;
1049
+ return (data ?? []).map(rowToCustomerSegment);
1050
+ }
1051
+ async createCustomerSegment(input) {
1052
+ const { data, error } = await getDb().from(T.customerSegments).insert({ tenant_id: getTenantId(), ...segmentPayload(input), name: input.name }).select().single();
1053
+ if (error) throw error;
1054
+ return rowToCustomerSegment(data);
1055
+ }
1056
+ async updateCustomerSegment(id, input) {
1057
+ const { data, error } = await getDb().from(T.customerSegments).update(segmentPayload(input)).eq("id", id).select().single();
1058
+ if (error) throw error;
1059
+ return rowToCustomerSegment(data);
1060
+ }
1061
+ async deleteCustomerSegment(id) {
1062
+ const { error } = await getDb().from(T.customerSegments).delete().eq("id", id);
1063
+ if (error) throw error;
1064
+ }
1065
+ async listGiftCards() {
1066
+ const db = getDb();
1067
+ const tenantId = getTenantId();
1068
+ let q = db.from(T.giftCards).select("*").order("created_at", { ascending: false });
1069
+ if (tenantId) q = q.eq("tenant_id", tenantId);
1070
+ const { data, error } = await q;
1071
+ if (error) throw error;
1072
+ return (data ?? []).map(rowToGiftCard);
1073
+ }
1074
+ async createGiftCard(input) {
1075
+ const amount = Number(input.initialBalance) || 0;
1076
+ const { data, error } = await getDb().from(T.giftCards).insert({
1077
+ tenant_id: getTenantId(),
1078
+ code: (input.code?.trim() || generateGiftCardCode()).toUpperCase(),
1079
+ initial_balance: amount,
1080
+ // A new card is worth its face value. The admin never types the
1081
+ // remaining balance at issue — that is what redemption is for.
1082
+ balance: amount,
1083
+ currency: input.currency ?? "BRL",
1084
+ status: input.status ?? "active",
1085
+ expires_at: input.expiresAt ?? null,
1086
+ customer_id: asUuid(input.customerId),
1087
+ customer_email: input.customerEmail ?? null,
1088
+ note: input.note ?? null
1089
+ }).select().single();
1090
+ if (error) throw error;
1091
+ return rowToGiftCard(data);
1092
+ }
1093
+ async updateGiftCard(id, input) {
1094
+ const updates = {};
1095
+ if (input.code !== void 0) updates.code = input.code.trim().toUpperCase();
1096
+ if (input.balance !== void 0) updates.balance = input.balance;
1097
+ if (input.status !== void 0) updates.status = input.status;
1098
+ if (input.expiresAt !== void 0) updates.expires_at = input.expiresAt;
1099
+ if (input.customerId !== void 0) updates.customer_id = asUuid(input.customerId);
1100
+ if (input.customerEmail !== void 0) updates.customer_email = input.customerEmail;
1101
+ if (input.note !== void 0) updates.note = input.note;
1102
+ const { data, error } = await getDb().from(T.giftCards).update(updates).eq("id", id).select().single();
1103
+ if (error) throw error;
1104
+ return rowToGiftCard(data);
1105
+ }
1106
+ async deleteGiftCard(id) {
1107
+ const { error } = await getDb().from(T.giftCards).delete().eq("id", id);
1108
+ if (error) throw error;
1109
+ }
1110
+ async listEmailTemplates() {
1111
+ const db = getDb();
1112
+ const tenantId = getTenantId();
1113
+ let q = db.from(T.messageTemplates).select("*").like("event_key", `${SHOP_EVENT_PREFIX}%`).eq("channel", EMAIL_CHANNEL);
1114
+ if (tenantId) q = q.eq("tenant_id", tenantId);
1115
+ const { data, error } = await q;
1116
+ if (error) throw error;
1117
+ return (data ?? []).map(rowToEmailTemplate);
1118
+ }
1119
+ async upsertEmailTemplate(input) {
1120
+ const payload = {
1121
+ tenant_id: getTenantId(),
1122
+ event_key: `${SHOP_EVENT_PREFIX}${input.event}`,
1123
+ channel: EMAIL_CHANNEL
1124
+ };
1125
+ if (input.enabled !== void 0) payload.enabled = input.enabled;
1126
+ if (input.subject !== void 0) payload.subject = input.subject;
1127
+ if (input.body !== void 0) payload.body = input.body;
1128
+ const { data, error } = await getDb().from(T.messageTemplates).upsert(payload, { onConflict: "tenant_id,event_key,channel" }).select().single();
1129
+ if (error) throw error;
1130
+ return rowToEmailTemplate(data);
1131
+ }
1132
+ async listMediaFiles() {
1133
+ const db = getDb();
1134
+ const tenantId = getTenantId();
1135
+ if (!tenantId) return [];
1136
+ const bucket = db.storage.from(T.mediaBucket);
1137
+ const listAt = async (prefix) => {
1138
+ const { data, error } = await bucket.list(prefix, {
1139
+ limit: 1e3,
1140
+ sortBy: { column: "created_at", order: "desc" }
1141
+ });
1142
+ if (error) return [];
1143
+ return (data ?? []).map((entry) => ({ prefix, entry }));
1144
+ };
1145
+ const top = await listAt(tenantId);
1146
+ const folders = top.filter(({ entry }) => !entry.id).map(({ entry }) => `${tenantId}/${entry.name}`);
1147
+ const nested = (await Promise.all(folders.map(listAt))).flat();
1148
+ return [...top, ...nested].filter(({ entry }) => entry.id).map(({ prefix, entry }) => {
1149
+ const path = `${prefix}/${entry.name}`;
1150
+ const { data: { publicUrl } } = bucket.getPublicUrl(path);
1151
+ const file = {
1152
+ id: path,
1153
+ name: entry.name,
1154
+ url: publicUrl,
1155
+ kind: mediaKindOf(entry.name, entry.metadata?.mimetype),
1156
+ size: Number(entry.metadata?.size ?? 0),
1157
+ extension: (entry.name.split(".").pop() ?? "").toUpperCase(),
1158
+ createdAt: entry.created_at ?? entry.updated_at ?? ""
1159
+ };
1160
+ return file;
1161
+ }).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
1162
+ }
1163
+ async uploadMediaFile(file) {
1164
+ const db = getDb();
1165
+ const tenantId = getTenantId();
1166
+ if (!tenantId) throw new Error("@fayz-ai/shop: no active tenant for media upload");
1167
+ const path = `${tenantId}/library/${Date.now()}-${file.name}`;
1168
+ const bucket = db.storage.from(T.mediaBucket);
1169
+ const { error } = await bucket.upload(path, file);
1170
+ if (error) throw error;
1171
+ const { data: { publicUrl } } = bucket.getPublicUrl(path);
1172
+ return {
1173
+ id: path,
1174
+ name: file.name,
1175
+ url: publicUrl,
1176
+ kind: mediaKindOf(file.name, file.type),
1177
+ size: file.size,
1178
+ extension: (file.name.split(".").pop() ?? "").toUpperCase(),
1179
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1180
+ };
1181
+ }
1182
+ async deleteMediaFile(id) {
1183
+ const { error } = await getDb().storage.from(T.mediaBucket).remove([id]);
1184
+ if (error) throw error;
1185
+ }
1186
+ async syncCollectionProducts(collectionId, tenantId, productIds) {
1187
+ if (productIds === void 0) return;
1188
+ const db = getDb();
1189
+ const { error: delErr } = await db.from(T.collectionProducts).delete().eq("collection_id", collectionId);
1190
+ if (delErr) throw delErr;
1191
+ if (productIds.length === 0) return;
1192
+ const rows = productIds.map((productId, position) => ({
1193
+ collection_id: collectionId,
1194
+ product_id: productId,
1195
+ tenant_id: tenantId,
1196
+ position
1197
+ }));
1198
+ const { error } = await db.from(T.collectionProducts).insert(rows);
1199
+ if (error) throw error;
1200
+ }
1201
+ async syncGroupMembers(groupId, tenantId, customerIds) {
1202
+ if (customerIds === void 0) return;
1203
+ const db = getDb();
1204
+ const { error: delErr } = await db.from(T.customerGroupMembers).delete().eq("group_id", groupId);
1205
+ if (delErr) throw delErr;
1206
+ if (customerIds.length === 0) return;
1207
+ const rows = customerIds.map((customerId) => ({
1208
+ group_id: groupId,
1209
+ customer_id: customerId,
1210
+ tenant_id: tenantId
1211
+ }));
1212
+ const { error } = await db.from(T.customerGroupMembers).insert(rows);
1213
+ if (error) throw error;
1214
+ }
717
1215
  };
718
1216
  function createSupabaseShopProvider() {
719
1217
  return new SupabaseShopProvider();
720
1218
  }
721
1219
 
722
- export { SupabaseShopProvider, applyDiscount, checkInventory, computeCartTotals, createSupabaseShopProvider, getCustomerOrders, getShopTenantId, setShopTenantResolver, validateDiscount };
1220
+ // src/lib/emails.ts
1221
+ var EMAIL_PLACEHOLDERS = [
1222
+ "{{store_name}}",
1223
+ "{{customer_name}}",
1224
+ "{{order_number}}",
1225
+ "{{order_total}}",
1226
+ "{{order_items}}",
1227
+ "{{tracking_code}}",
1228
+ "{{cart_url}}"
1229
+ ];
1230
+ var SHOP_EMAIL_EVENTS = [
1231
+ {
1232
+ event: "order_confirmation",
1233
+ kind: "transactional",
1234
+ icon: "CircleCheck",
1235
+ title: "Confirma\xE7\xE3o de pedido",
1236
+ description: "Enviado quando o cliente conclui a compra",
1237
+ trigger: "Imediatamente ap\xF3s o checkout",
1238
+ defaultSubject: "Pedido #{{order_number}} confirmado",
1239
+ defaultBody: "Ol\xE1 {{customer_name}},\n\nRecebemos seu pedido #{{order_number}}.\n\n{{order_items}}\n\nTotal: {{order_total}}\n\nObrigado por comprar na {{store_name}}!",
1240
+ defaultEnabled: true
1241
+ },
1242
+ {
1243
+ event: "order_paid",
1244
+ kind: "transactional",
1245
+ icon: "CreditCard",
1246
+ title: "Pagamento aprovado",
1247
+ description: "Enviado quando o pagamento \xE9 confirmado",
1248
+ trigger: "Quando o pagamento \xE9 aprovado",
1249
+ defaultSubject: "Pagamento aprovado \u2014 pedido #{{order_number}}",
1250
+ defaultBody: "Ol\xE1 {{customer_name}},\n\nSeu pagamento de {{order_total}} foi aprovado. J\xE1 estamos preparando o pedido #{{order_number}}.\n\n{{store_name}}",
1251
+ defaultEnabled: false
1252
+ },
1253
+ {
1254
+ event: "order_shipped",
1255
+ kind: "transactional",
1256
+ icon: "Truck",
1257
+ title: "Pedido enviado",
1258
+ description: "Enviado quando o pedido sai para entrega, com o rastreio",
1259
+ trigger: "Quando a entrega \xE9 marcada como enviada",
1260
+ defaultSubject: "Seu pedido saiu para entrega \u2014 #{{order_number}}",
1261
+ defaultBody: "Ol\xE1 {{customer_name}},\n\nSeu pedido #{{order_number}} est\xE1 a caminho.\n\nRastreio: {{tracking_code}}\n\n{{store_name}}",
1262
+ defaultEnabled: false
1263
+ },
1264
+ {
1265
+ event: "order_delivered",
1266
+ kind: "transactional",
1267
+ icon: "PackageCheck",
1268
+ title: "Pedido entregue",
1269
+ description: "Enviado quando a entrega \xE9 conclu\xEDda",
1270
+ trigger: "Quando a entrega \xE9 marcada como conclu\xEDda",
1271
+ defaultSubject: "Seu pedido chegou \u2014 #{{order_number}}",
1272
+ defaultBody: "Ol\xE1 {{customer_name}},\n\nSeu pedido #{{order_number}} foi entregue. Esperamos que goste!\n\n{{store_name}}",
1273
+ defaultEnabled: false
1274
+ },
1275
+ {
1276
+ event: "order_cancelled",
1277
+ kind: "transactional",
1278
+ icon: "CircleX",
1279
+ title: "Pedido cancelado",
1280
+ description: "Enviado quando um pedido \xE9 cancelado",
1281
+ trigger: "Quando o pedido \xE9 cancelado",
1282
+ defaultSubject: "Pedido #{{order_number}} cancelado",
1283
+ defaultBody: "Ol\xE1 {{customer_name}},\n\nSeu pedido #{{order_number}} foi cancelado. Se voc\xEA n\xE3o pediu isso, responda este e-mail.\n\n{{store_name}}",
1284
+ defaultEnabled: false
1285
+ },
1286
+ {
1287
+ event: "refund_confirmation",
1288
+ kind: "transactional",
1289
+ icon: "RotateCcw",
1290
+ title: "Reembolso processado",
1291
+ description: "Enviado quando um reembolso \xE9 processado",
1292
+ trigger: "Quando o pagamento \xE9 estornado",
1293
+ defaultSubject: "Reembolso emitido \u2014 pedido #{{order_number}}",
1294
+ defaultBody: "Ol\xE1 {{customer_name}},\n\nO reembolso de {{order_total}} do pedido #{{order_number}} foi processado. O prazo de cr\xE9dito depende do seu banco.\n\n{{store_name}}",
1295
+ defaultEnabled: false
1296
+ },
1297
+ {
1298
+ event: "customer_welcome",
1299
+ kind: "transactional",
1300
+ icon: "Mail",
1301
+ title: "Boas-vindas",
1302
+ description: "Enviado quando um cliente cria conta na loja",
1303
+ trigger: "Quando o cliente se cadastra",
1304
+ defaultSubject: "Bem-vindo(a) \xE0 {{store_name}}",
1305
+ defaultBody: "Ol\xE1 {{customer_name}},\n\nSua conta na {{store_name}} est\xE1 pronta. Bom proveito!\n\n{{store_name}}",
1306
+ defaultEnabled: false
1307
+ },
1308
+ {
1309
+ event: "abandoned_cart",
1310
+ kind: "marketing",
1311
+ icon: "ShoppingCart",
1312
+ title: "Carrinho abandonado",
1313
+ description: "Enviado quando o cliente deixa itens no carrinho sem finalizar",
1314
+ trigger: "1 hora ap\xF3s o abandono do carrinho",
1315
+ defaultSubject: "Voc\xEA esqueceu alguns itens no carrinho",
1316
+ defaultBody: "Ol\xE1 {{customer_name}},\n\nSeus itens ainda est\xE3o te esperando.\n\n{{cart_url}}\n\n{{store_name}}",
1317
+ defaultEnabled: false
1318
+ }
1319
+ ];
1320
+ function renderEmailTemplate(text, values) {
1321
+ return text.replace(/\{\{\s*(\w+)\s*\}\}/g, (whole, key) => values[key] ?? whole);
1322
+ }
1323
+ var EMAIL_PREVIEW_SAMPLE = {
1324
+ store_name: "Sua Loja",
1325
+ customer_name: "Maria Silva",
1326
+ order_number: "1042",
1327
+ order_total: "R$ 189,90",
1328
+ order_items: "1 \xD7 Camiseta Cl\xE1ssica \u2014 R$ 89,90\n2 \xD7 Caneca Esmaltada \u2014 R$ 100,00",
1329
+ tracking_code: "BR123456789BR",
1330
+ cart_url: "https://sualoja.com.br/carrinho"
1331
+ };
1332
+ function resolveEmailTemplates(overrides) {
1333
+ const byEvent = new Map(overrides.map((o) => [o.event, o]));
1334
+ return SHOP_EMAIL_EVENTS.map((def) => {
1335
+ const override = byEvent.get(def.event) ?? null;
1336
+ return {
1337
+ ...def,
1338
+ override,
1339
+ enabled: override?.enabled ?? def.defaultEnabled,
1340
+ subject: override?.subject ?? def.defaultSubject,
1341
+ body: override?.body ?? def.defaultBody,
1342
+ customized: Boolean(override?.subject || override?.body)
1343
+ };
1344
+ });
1345
+ }
1346
+
1347
+ // src/lib/message-events.ts
1348
+ var ORDER_FIELDS = [
1349
+ { name: "order_number", label: "N\xFAmero do pedido", type: "string", example: "1042" },
1350
+ { name: "order_total", label: "Total", type: "money", example: "R$ 189,90" },
1351
+ { name: "order_items", label: "Itens", type: "string", example: "1 \xD7 Camiseta Cl\xE1ssica \u2014 R$ 89,90\n2 \xD7 Caneca Esmaltada \u2014 R$ 100,00" },
1352
+ { name: "customer_name", label: "Cliente", type: "string", example: "Maria Silva" },
1353
+ { name: "store_name", label: "Loja", type: "string", example: "Sua Loja" }
1354
+ ];
1355
+ var TRACKING_FIELD = {
1356
+ name: "tracking_code",
1357
+ label: "C\xF3digo de rastreio",
1358
+ type: "string",
1359
+ example: "BR123456789BR"
1360
+ };
1361
+ var CART_FIELD = {
1362
+ name: "cart_url",
1363
+ label: "Link do carrinho",
1364
+ type: "url",
1365
+ example: "https://sualoja.com.br/carrinho"
1366
+ };
1367
+ var FIELDS_BY_EVENT = {
1368
+ order_shipped: [...ORDER_FIELDS, TRACKING_FIELD],
1369
+ order_delivered: [...ORDER_FIELDS, TRACKING_FIELD],
1370
+ abandoned_cart: [...ORDER_FIELDS, CART_FIELD]
1371
+ };
1372
+ var NOT_AUDITED = /* @__PURE__ */ new Set(["abandoned_cart"]);
1373
+ function buildShopDomainEvents() {
1374
+ return SHOP_EMAIL_EVENTS.map((def) => ({
1375
+ name: `shop.${def.event}`,
1376
+ title: def.title,
1377
+ description: def.description,
1378
+ trigger: def.trigger,
1379
+ icon: def.icon,
1380
+ fields: FIELDS_BY_EVENT[def.event] ?? ORDER_FIELDS,
1381
+ audit: !NOT_AUDITED.has(def.event)
1382
+ }));
1383
+ }
1384
+ var WHATSAPP_EVENTS = /* @__PURE__ */ new Set(["order_confirmation", "order_shipped", "abandoned_cart"]);
1385
+ var WHATSAPP_BODY = {
1386
+ order_confirmation: "Oi {{customer_name}}! Recebemos seu pedido #{{order_number}} \u2705\n\nTotal: {{order_total}}\n\nQualquer d\xFAvida \xE9 s\xF3 chamar aqui. \u2014 {{store_name}}",
1387
+ order_shipped: "Oi {{customer_name}}! Seu pedido #{{order_number}} saiu para entrega \u{1F69A}\n\nRastreio: {{tracking_code}}\n\n\u2014 {{store_name}}",
1388
+ abandoned_cart: "Oi {{customer_name}}! Vi que ficaram itens no seu carrinho \u{1F6D2}\n\nQuer que eu segure para voc\xEA?\n{{cart_url}}\n\n\u2014 {{store_name}}"
1389
+ };
1390
+ function buildShopMessageDefaults() {
1391
+ return SHOP_EMAIL_EVENTS.map((def) => {
1392
+ const whatsapp = WHATSAPP_EVENTS.has(def.event) ? WHATSAPP_BODY[def.event] : void 0;
1393
+ return {
1394
+ eventKey: `shop.${def.event}`,
1395
+ kind: def.kind,
1396
+ channels: whatsapp ? ["email", "whatsapp"].slice() : ["email"],
1397
+ defaults: {
1398
+ email: { subject: def.defaultSubject, body: def.defaultBody },
1399
+ ...whatsapp ? { whatsapp: { body: whatsapp } } : {}
1400
+ },
1401
+ // Only email inherits "on by default". WhatsApp bills per message and
1402
+ // needs an approved number — switching itself on would spend someone's
1403
+ // money unasked.
1404
+ defaultEnabled: { email: def.defaultEnabled }
1405
+ };
1406
+ });
1407
+ }
1408
+
1409
+ export { EMAIL_PLACEHOLDERS, EMAIL_PREVIEW_SAMPLE, SHOP_EMAIL_EVENTS, SupabaseShopProvider, applyDiscount, buildShopDomainEvents, buildShopMessageDefaults, checkInventory, computeCartTotals, createSupabaseShopProvider, getCustomerOrders, renderEmailTemplate, resolveEmailTemplates, validateDiscount };
723
1410
  //# sourceMappingURL=index.js.map
724
1411
  //# sourceMappingURL=index.js.map