@fayz-ai/shop 0.6.0 → 0.8.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.
package/README.md CHANGED
@@ -5,6 +5,8 @@
5
5
  [![npm](https://img.shields.io/npm/v/@fayz-ai/shop.svg)](https://www.npmjs.com/package/@fayz-ai/shop)
6
6
  [![license](https://img.shields.io/npm/l/@fayz-ai/shop.svg)](https://github.com/FayaLabs/fayz-sdk/blob/main/LICENSE)
7
7
 
8
+ **Status:** beta — published to npm and used across Fayz dogfood apps. Pre-1.0: minor APIs may change before 1.0.
9
+
8
10
  `@fayz-ai/shop` is the headless ecommerce engine: a `ShopProvider` contract plus the domain types for products, carts, orders, customers, discounts, and shipping. It owns *what commerce means* so the storefront (`@fayz-ai/storefront`) and admin surfaces only have to render it.
9
11
 
10
12
  It's provider-first by design. Ship a demo on `createMockShopProvider`, then drop in `createSupabaseShopProvider` (or your own `ShopProvider`) for real data — the UI doesn't change. Tenancy is resolved through a pluggable resolver, so the same engine serves one store or a marketplace of thousands.
package/dist/index.cjs CHANGED
@@ -178,6 +178,7 @@ function now() {
178
178
  }
179
179
  var ORDERS_KEY = "fayz.shop.mock.orders.v1";
180
180
  var CUSTOMERS_KEY = "fayz.shop.mock.customers.v1";
181
+ var ENQUIRIES_KEY = "fayz.shop.mock.enquiries.v1";
181
182
  function loadStored(key) {
182
183
  try {
183
184
  if (typeof localStorage === "undefined") return [];
@@ -198,6 +199,7 @@ var MockShopProvider = class {
198
199
  constructor(seed) {
199
200
  this.orders = loadStored(ORDERS_KEY);
200
201
  this.customers = loadStored(CUSTOMERS_KEY);
202
+ this.enquiries = loadStored(ENQUIRIES_KEY);
201
203
  this.products = (seed?.products ?? MOCK_PRODUCTS).map((p) => ({ ...p }));
202
204
  this.categories = (seed?.categories ?? MOCK_CATEGORIES).map((c) => ({ ...c }));
203
205
  this.discounts = (seed?.discounts ?? MOCK_DISCOUNTS).map((d) => ({ ...d }));
@@ -210,12 +212,18 @@ var MockShopProvider = class {
210
212
  syncCustomers() {
211
213
  this.customers = loadStored(CUSTOMERS_KEY);
212
214
  }
215
+ syncEnquiries() {
216
+ this.enquiries = loadStored(ENQUIRIES_KEY);
217
+ }
213
218
  persistOrders() {
214
219
  store(ORDERS_KEY, this.orders);
215
220
  }
216
221
  persistCustomers() {
217
222
  store(CUSTOMERS_KEY, this.customers);
218
223
  }
224
+ persistEnquiries() {
225
+ store(ENQUIRIES_KEY, this.enquiries);
226
+ }
219
227
  // ---------------------------------------------------------------------------
220
228
  // Products
221
229
  // ---------------------------------------------------------------------------
@@ -270,6 +278,44 @@ var MockShopProvider = class {
270
278
  }
271
279
  async deleteProductImage(_imageId) {
272
280
  }
281
+ async createProductEnquiry(input) {
282
+ this.syncEnquiries();
283
+ const ts = now();
284
+ const enquiry = {
285
+ id: uid(),
286
+ tenantId: "mock",
287
+ productId: input.productId ?? null,
288
+ productName: input.productName,
289
+ productSlug: input.productSlug ?? null,
290
+ customerName: input.customerName,
291
+ customerEmail: input.customerEmail,
292
+ customerPhone: input.customerPhone ?? null,
293
+ subject: input.subject ?? null,
294
+ message: input.message,
295
+ sourceUrl: input.sourceUrl ?? null,
296
+ status: "new",
297
+ metadata: input.metadata ?? {},
298
+ createdAt: ts,
299
+ updatedAt: ts
300
+ };
301
+ this.enquiries.unshift(enquiry);
302
+ this.persistEnquiries();
303
+ return enquiry;
304
+ }
305
+ async listProductEnquiries(opts) {
306
+ this.syncEnquiries();
307
+ let list = [...this.enquiries];
308
+ if (opts?.productId) list = list.filter((e) => e.productId === opts.productId);
309
+ if (opts?.customerEmail) {
310
+ const email = opts.customerEmail.toLowerCase();
311
+ list = list.filter((e) => e.customerEmail.toLowerCase() === email);
312
+ }
313
+ if (opts?.status) list = list.filter((e) => e.status === opts.status);
314
+ list.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
315
+ const offset = opts?.offset ?? 0;
316
+ if (offset || opts?.limit) list = list.slice(offset, opts?.limit ? offset + opts.limit : void 0);
317
+ return list;
318
+ }
273
319
  // ---------------------------------------------------------------------------
274
320
  // Categories
275
321
  // ---------------------------------------------------------------------------
@@ -605,6 +651,17 @@ function getShopTenantId() {
605
651
  }
606
652
  }
607
653
 
654
+ // src/tables.ts
655
+ var T = {
656
+ products: "plg_shop_products",
657
+ productImages: "plg_shop_product_images",
658
+ categories: "plg_shop_categories",
659
+ orders: "plg_shop_orders",
660
+ orderItems: "plg_shop_order_items",
661
+ customers: "plg_shop_customers",
662
+ discounts: "plg_shop_discounts"
663
+ };
664
+
608
665
  // src/supabase-provider.ts
609
666
  function getDb() {
610
667
  const supabase = core.getSupabaseClientOptional();
@@ -732,7 +789,7 @@ var SupabaseShopProvider = class {
732
789
  async listProducts(options) {
733
790
  const db = getDb();
734
791
  const tenantId = getTenantId();
735
- let q = db.from("shop_products").select("*, images:shop_product_images(*)");
792
+ let q = db.from(T.products).select(`*, images:${T.productImages}(*)`);
736
793
  if (tenantId) q = q.eq("tenant_id", tenantId);
737
794
  if (options?.status) q = q.eq("status", options.status);
738
795
  if (options?.categoryId) q = q.eq("category_id", options.categoryId);
@@ -747,14 +804,14 @@ var SupabaseShopProvider = class {
747
804
  }
748
805
  async getProduct(id) {
749
806
  const db = getDb();
750
- const { data, error } = await db.from("shop_products").select("*, images:shop_product_images(*)").eq("id", id).maybeSingle();
807
+ const { data, error } = await db.from(T.products).select(`*, images:${T.productImages}(*)`).eq("id", id).maybeSingle();
751
808
  if (error) throw error;
752
809
  return data ? rowToProduct(data) : null;
753
810
  }
754
811
  async createProduct(input) {
755
812
  const db = getDb();
756
813
  const tenantId = getTenantId();
757
- const { data, error } = await db.from("shop_products").insert({
814
+ const { data, error } = await db.from(T.products).insert({
758
815
  tenant_id: tenantId,
759
816
  name: input.name,
760
817
  slug: input.slug ?? slugify(input.name),
@@ -771,7 +828,7 @@ var SupabaseShopProvider = class {
771
828
  is_physical: input.isPhysical ?? true,
772
829
  weight: input.weight ?? null,
773
830
  weight_unit: input.weightUnit ?? "kg"
774
- }).select("*, images:shop_product_images(*)").single();
831
+ }).select(`*, images:${T.productImages}(*)`).single();
775
832
  if (error) throw error;
776
833
  return rowToProduct(data);
777
834
  }
@@ -793,12 +850,12 @@ var SupabaseShopProvider = class {
793
850
  if (input.categoryId !== void 0) updates.category_id = input.categoryId;
794
851
  if (input.isPhysical !== void 0) updates.is_physical = input.isPhysical;
795
852
  if (input.weight !== void 0) updates.weight = input.weight;
796
- const { data, error } = await db.from("shop_products").update(updates).eq("id", id).select("*, images:shop_product_images(*)").single();
853
+ const { data, error } = await db.from(T.products).update(updates).eq("id", id).select(`*, images:${T.productImages}(*)`).single();
797
854
  if (error) throw error;
798
855
  return rowToProduct(data);
799
856
  }
800
857
  async deleteProduct(id) {
801
- const { error } = await getDb().from("shop_products").delete().eq("id", id);
858
+ const { error } = await getDb().from(T.products).delete().eq("id", id);
802
859
  if (error) throw error;
803
860
  }
804
861
  async uploadProductImage(productId, file) {
@@ -808,12 +865,12 @@ var SupabaseShopProvider = class {
808
865
  const { error: uploadErr } = await db.storage.from("shop-images").upload(path, file);
809
866
  if (uploadErr) throw uploadErr;
810
867
  const { data: { publicUrl } } = db.storage.from("shop-images").getPublicUrl(path);
811
- const { data, error } = await db.from("shop_product_images").insert({ product_id: productId, tenant_id: tenantId, url: publicUrl, sort_order: 0, is_primary: false }).select().single();
868
+ const { data, error } = await db.from(T.productImages).insert({ product_id: productId, tenant_id: tenantId, url: publicUrl, sort_order: 0, is_primary: false }).select().single();
812
869
  if (error) throw error;
813
870
  return rowToImage(data);
814
871
  }
815
872
  async deleteProductImage(imageId) {
816
- const { error } = await getDb().from("shop_product_images").delete().eq("id", imageId);
873
+ const { error } = await getDb().from(T.productImages).delete().eq("id", imageId);
817
874
  if (error) throw error;
818
875
  }
819
876
  // ---------------------------------------------------------------------------
@@ -822,7 +879,7 @@ var SupabaseShopProvider = class {
822
879
  async listCategories() {
823
880
  const db = getDb();
824
881
  const tenantId = getTenantId();
825
- let q = db.from("shop_categories").select("*").order("sort_order", { ascending: true });
882
+ let q = db.from(T.categories).select("*").order("sort_order", { ascending: true });
826
883
  if (tenantId) q = q.eq("tenant_id", tenantId);
827
884
  const { data, error } = await q;
828
885
  if (error) throw error;
@@ -830,7 +887,7 @@ var SupabaseShopProvider = class {
830
887
  }
831
888
  async createCategory(input) {
832
889
  const db = getDb();
833
- const { data, error } = await db.from("shop_categories").insert({ tenant_id: getTenantId(), name: input.name, slug: input.slug ?? slugify(input.name), description: input.description ?? null, parent_id: input.parentId ?? null, sort_order: input.sortOrder ?? 0 }).select().single();
890
+ const { data, error } = await db.from(T.categories).insert({ tenant_id: getTenantId(), name: input.name, slug: input.slug ?? slugify(input.name), description: input.description ?? null, parent_id: input.parentId ?? null, sort_order: input.sortOrder ?? 0 }).select().single();
834
891
  if (error) throw error;
835
892
  return rowToCategory(data);
836
893
  }
@@ -842,12 +899,12 @@ var SupabaseShopProvider = class {
842
899
  if (input.description !== void 0) updates.description = input.description;
843
900
  if (input.parentId !== void 0) updates.parent_id = input.parentId;
844
901
  if (input.sortOrder !== void 0) updates.sort_order = input.sortOrder;
845
- const { data, error } = await db.from("shop_categories").update(updates).eq("id", id).select().single();
902
+ const { data, error } = await db.from(T.categories).update(updates).eq("id", id).select().single();
846
903
  if (error) throw error;
847
904
  return rowToCategory(data);
848
905
  }
849
906
  async deleteCategory(id) {
850
- const { error } = await getDb().from("shop_categories").delete().eq("id", id);
907
+ const { error } = await getDb().from(T.categories).delete().eq("id", id);
851
908
  if (error) throw error;
852
909
  }
853
910
  // ---------------------------------------------------------------------------
@@ -856,7 +913,7 @@ var SupabaseShopProvider = class {
856
913
  async listOrders(options) {
857
914
  const db = getDb();
858
915
  const tenantId = getTenantId();
859
- let q = db.from("shop_orders").select("*, items:shop_order_items(*)");
916
+ let q = db.from(T.orders).select(`*, items:${T.orderItems}(*)`);
860
917
  if (tenantId) q = q.eq("tenant_id", tenantId);
861
918
  if (options?.status) q = q.eq("status", options.status);
862
919
  if (options?.financialStatus) q = q.eq("financial_status", options.financialStatus);
@@ -871,7 +928,7 @@ var SupabaseShopProvider = class {
871
928
  return (data ?? []).map(rowToOrder);
872
929
  }
873
930
  async getOrder(id) {
874
- const { data, error } = await getDb().from("shop_orders").select("*, items:shop_order_items(*)").eq("id", id).maybeSingle();
931
+ const { data, error } = await getDb().from(T.orders).select(`*, items:${T.orderItems}(*)`).eq("id", id).maybeSingle();
875
932
  if (error) throw error;
876
933
  return data ? rowToOrder(data) : null;
877
934
  }
@@ -882,10 +939,10 @@ var SupabaseShopProvider = class {
882
939
  const discountTotal = input.discountTotal ?? 0;
883
940
  const shippingTotal = input.shippingTotal ?? 0;
884
941
  const total = Math.round((subtotal - discountTotal + shippingTotal) * 100) / 100;
885
- const { data: order, error: orderErr } = await db.from("shop_orders").insert({ tenant_id: tenantId, customer_id: input.customerId ?? null, customer_name: input.customerName ?? null, customer_email: input.customerEmail ?? null, currency: input.currency ?? "BRL", notes: input.notes ?? null, subtotal, discount_total: discountTotal, shipping_total: shippingTotal, discount_code: input.discountCode ?? null, total, status: "open", financial_status: "pending", fulfillment_status: "unfulfilled" }).select().single();
942
+ const { data: order, error: orderErr } = await db.from(T.orders).insert({ tenant_id: tenantId, customer_id: input.customerId ?? null, customer_name: input.customerName ?? null, customer_email: input.customerEmail ?? null, currency: input.currency ?? "BRL", notes: input.notes ?? null, subtotal, discount_total: discountTotal, shipping_total: shippingTotal, discount_code: input.discountCode ?? null, total, status: "open", financial_status: "pending", fulfillment_status: "unfulfilled" }).select().single();
886
943
  if (orderErr) throw orderErr;
887
944
  const items = input.items.map((i) => ({ order_id: order.id, product_id: asUuid(i.productId), name: i.name, sku: i.sku ?? null, quantity: i.quantity, unit_price: i.unitPrice, total: i.quantity * i.unitPrice, image_url: i.imageUrl ?? null }));
888
- await db.from("shop_order_items").insert(items);
945
+ await db.from(T.orderItems).insert(items);
889
946
  return this.getOrder(order.id);
890
947
  }
891
948
  async updateOrder(id, input) {
@@ -894,7 +951,7 @@ var SupabaseShopProvider = class {
894
951
  if (input.financialStatus !== void 0) updates.financial_status = input.financialStatus;
895
952
  if (input.fulfillmentStatus !== void 0) updates.fulfillment_status = input.fulfillmentStatus;
896
953
  if (input.notes !== void 0) updates.notes = input.notes;
897
- const { error } = await getDb().from("shop_orders").update(updates).eq("id", id);
954
+ const { error } = await getDb().from(T.orders).update(updates).eq("id", id);
898
955
  if (error) throw error;
899
956
  return this.getOrder(id);
900
957
  }
@@ -927,7 +984,7 @@ var SupabaseShopProvider = class {
927
984
  async listCustomers(options) {
928
985
  const db = getDb();
929
986
  const tenantId = getTenantId();
930
- let q = db.from("shop_customers").select("*").order("first_name", { ascending: true });
987
+ let q = db.from(T.customers).select("*").order("first_name", { ascending: true });
931
988
  if (tenantId) q = q.eq("tenant_id", tenantId);
932
989
  if (options?.search) q = q.or(`first_name.ilike.%${options.search}%,last_name.ilike.%${options.search}%,email.ilike.%${options.search}%`);
933
990
  if (options?.limit) q = q.limit(options.limit);
@@ -936,12 +993,12 @@ var SupabaseShopProvider = class {
936
993
  return (data ?? []).map(rowToCustomer);
937
994
  }
938
995
  async getCustomer(id) {
939
- const { data, error } = await getDb().from("shop_customers").select("*").eq("id", id).maybeSingle();
996
+ const { data, error } = await getDb().from(T.customers).select("*").eq("id", id).maybeSingle();
940
997
  if (error) throw error;
941
998
  return data ? rowToCustomer(data) : null;
942
999
  }
943
1000
  async createCustomer(input) {
944
- const { data, error } = await getDb().from("shop_customers").insert({ tenant_id: getTenantId(), first_name: input.firstName, last_name: input.lastName ?? "", email: input.email ?? null, phone: input.phone ?? null, notes: input.notes ?? null }).select().single();
1001
+ const { data, error } = await getDb().from(T.customers).insert({ tenant_id: getTenantId(), first_name: input.firstName, last_name: input.lastName ?? "", email: input.email ?? null, phone: input.phone ?? null, notes: input.notes ?? null }).select().single();
945
1002
  if (error) throw error;
946
1003
  return rowToCustomer(data);
947
1004
  }
@@ -952,12 +1009,12 @@ var SupabaseShopProvider = class {
952
1009
  if (input.email !== void 0) updates.email = input.email;
953
1010
  if (input.phone !== void 0) updates.phone = input.phone;
954
1011
  if (input.notes !== void 0) updates.notes = input.notes;
955
- const { data, error } = await getDb().from("shop_customers").update(updates).eq("id", id).select().single();
1012
+ const { data, error } = await getDb().from(T.customers).update(updates).eq("id", id).select().single();
956
1013
  if (error) throw error;
957
1014
  return rowToCustomer(data);
958
1015
  }
959
1016
  async deleteCustomer(id) {
960
- const { error } = await getDb().from("shop_customers").delete().eq("id", id);
1017
+ const { error } = await getDb().from(T.customers).delete().eq("id", id);
961
1018
  if (error) throw error;
962
1019
  }
963
1020
  // Find-or-create + link to auth.uid server-side (SECURITY DEFINER RPC), so the
@@ -982,7 +1039,7 @@ var SupabaseShopProvider = class {
982
1039
  async listDiscounts(options) {
983
1040
  const db = getDb();
984
1041
  const tenantId = getTenantId();
985
- let q = db.from("shop_discounts").select("*").order("created_at", { ascending: false });
1042
+ let q = db.from(T.discounts).select("*").order("created_at", { ascending: false });
986
1043
  if (tenantId) q = q.eq("tenant_id", tenantId);
987
1044
  if (options?.status) q = q.eq("status", options.status);
988
1045
  if (options?.type) q = q.eq("type", options.type);
@@ -993,12 +1050,12 @@ var SupabaseShopProvider = class {
993
1050
  return (data ?? []).map(rowToDiscount);
994
1051
  }
995
1052
  async getDiscount(id) {
996
- const { data, error } = await getDb().from("shop_discounts").select("*").eq("id", id).maybeSingle();
1053
+ const { data, error } = await getDb().from(T.discounts).select("*").eq("id", id).maybeSingle();
997
1054
  if (error) throw error;
998
1055
  return data ? rowToDiscount(data) : null;
999
1056
  }
1000
1057
  async createDiscount(input) {
1001
- const { data, error } = await getDb().from("shop_discounts").insert({ tenant_id: getTenantId(), title: input.title, code: input.code ?? null, type: input.type, method: input.method ?? "code", value: input.value, usage_limit: input.usageLimit ?? null, once_per_customer: input.oncePerCustomer ?? false, starts_at: input.startsAt ?? (/* @__PURE__ */ new Date()).toISOString(), ends_at: input.endsAt ?? null, status: input.status ?? "active", times_used: 0 }).select().single();
1058
+ const { data, error } = await getDb().from(T.discounts).insert({ tenant_id: getTenantId(), title: input.title, code: input.code ?? null, type: input.type, method: input.method ?? "code", value: input.value, usage_limit: input.usageLimit ?? null, once_per_customer: input.oncePerCustomer ?? false, starts_at: input.startsAt ?? (/* @__PURE__ */ new Date()).toISOString(), ends_at: input.endsAt ?? null, status: input.status ?? "active", times_used: 0 }).select().single();
1002
1059
  if (error) throw error;
1003
1060
  return rowToDiscount(data);
1004
1061
  }
@@ -1011,12 +1068,12 @@ var SupabaseShopProvider = class {
1011
1068
  if (input.usageLimit !== void 0) updates.usage_limit = input.usageLimit;
1012
1069
  if (input.status !== void 0) updates.status = input.status;
1013
1070
  if (input.endsAt !== void 0) updates.ends_at = input.endsAt;
1014
- const { data, error } = await getDb().from("shop_discounts").update(updates).eq("id", id).select().single();
1071
+ const { data, error } = await getDb().from(T.discounts).update(updates).eq("id", id).select().single();
1015
1072
  if (error) throw error;
1016
1073
  return rowToDiscount(data);
1017
1074
  }
1018
1075
  async deleteDiscount(id) {
1019
- const { error } = await getDb().from("shop_discounts").delete().eq("id", id);
1076
+ const { error } = await getDb().from(T.discounts).delete().eq("id", id);
1020
1077
  if (error) throw error;
1021
1078
  }
1022
1079
  };