@fayz-ai/shop 0.7.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 +2 -0
- package/dist/index.cjs +38 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +38 -27
- package/dist/index.js.map +1 -1
- package/dist/provider.d.ts +6 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/supabase-provider.d.ts.map +1 -1
- package/dist/tables.d.ts +10 -0
- package/dist/tables.d.ts.map +1 -0
- package/package.json +10 -4
- package/src/provider.ts +6 -0
- package/src/supabase-provider.ts +30 -29
- package/src/tables.ts +12 -0
package/README.md
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@fayz-ai/shop)
|
|
6
6
|
[](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
|
@@ -651,6 +651,17 @@ function getShopTenantId() {
|
|
|
651
651
|
}
|
|
652
652
|
}
|
|
653
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
|
+
|
|
654
665
|
// src/supabase-provider.ts
|
|
655
666
|
function getDb() {
|
|
656
667
|
const supabase = core.getSupabaseClientOptional();
|
|
@@ -778,7 +789,7 @@ var SupabaseShopProvider = class {
|
|
|
778
789
|
async listProducts(options) {
|
|
779
790
|
const db = getDb();
|
|
780
791
|
const tenantId = getTenantId();
|
|
781
|
-
let q = db.from(
|
|
792
|
+
let q = db.from(T.products).select(`*, images:${T.productImages}(*)`);
|
|
782
793
|
if (tenantId) q = q.eq("tenant_id", tenantId);
|
|
783
794
|
if (options?.status) q = q.eq("status", options.status);
|
|
784
795
|
if (options?.categoryId) q = q.eq("category_id", options.categoryId);
|
|
@@ -793,14 +804,14 @@ var SupabaseShopProvider = class {
|
|
|
793
804
|
}
|
|
794
805
|
async getProduct(id) {
|
|
795
806
|
const db = getDb();
|
|
796
|
-
const { data, error } = await db.from(
|
|
807
|
+
const { data, error } = await db.from(T.products).select(`*, images:${T.productImages}(*)`).eq("id", id).maybeSingle();
|
|
797
808
|
if (error) throw error;
|
|
798
809
|
return data ? rowToProduct(data) : null;
|
|
799
810
|
}
|
|
800
811
|
async createProduct(input) {
|
|
801
812
|
const db = getDb();
|
|
802
813
|
const tenantId = getTenantId();
|
|
803
|
-
const { data, error } = await db.from(
|
|
814
|
+
const { data, error } = await db.from(T.products).insert({
|
|
804
815
|
tenant_id: tenantId,
|
|
805
816
|
name: input.name,
|
|
806
817
|
slug: input.slug ?? slugify(input.name),
|
|
@@ -817,7 +828,7 @@ var SupabaseShopProvider = class {
|
|
|
817
828
|
is_physical: input.isPhysical ?? true,
|
|
818
829
|
weight: input.weight ?? null,
|
|
819
830
|
weight_unit: input.weightUnit ?? "kg"
|
|
820
|
-
}).select(
|
|
831
|
+
}).select(`*, images:${T.productImages}(*)`).single();
|
|
821
832
|
if (error) throw error;
|
|
822
833
|
return rowToProduct(data);
|
|
823
834
|
}
|
|
@@ -839,12 +850,12 @@ var SupabaseShopProvider = class {
|
|
|
839
850
|
if (input.categoryId !== void 0) updates.category_id = input.categoryId;
|
|
840
851
|
if (input.isPhysical !== void 0) updates.is_physical = input.isPhysical;
|
|
841
852
|
if (input.weight !== void 0) updates.weight = input.weight;
|
|
842
|
-
const { data, error } = await db.from(
|
|
853
|
+
const { data, error } = await db.from(T.products).update(updates).eq("id", id).select(`*, images:${T.productImages}(*)`).single();
|
|
843
854
|
if (error) throw error;
|
|
844
855
|
return rowToProduct(data);
|
|
845
856
|
}
|
|
846
857
|
async deleteProduct(id) {
|
|
847
|
-
const { error } = await getDb().from(
|
|
858
|
+
const { error } = await getDb().from(T.products).delete().eq("id", id);
|
|
848
859
|
if (error) throw error;
|
|
849
860
|
}
|
|
850
861
|
async uploadProductImage(productId, file) {
|
|
@@ -854,12 +865,12 @@ var SupabaseShopProvider = class {
|
|
|
854
865
|
const { error: uploadErr } = await db.storage.from("shop-images").upload(path, file);
|
|
855
866
|
if (uploadErr) throw uploadErr;
|
|
856
867
|
const { data: { publicUrl } } = db.storage.from("shop-images").getPublicUrl(path);
|
|
857
|
-
const { data, error } = await db.from(
|
|
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();
|
|
858
869
|
if (error) throw error;
|
|
859
870
|
return rowToImage(data);
|
|
860
871
|
}
|
|
861
872
|
async deleteProductImage(imageId) {
|
|
862
|
-
const { error } = await getDb().from(
|
|
873
|
+
const { error } = await getDb().from(T.productImages).delete().eq("id", imageId);
|
|
863
874
|
if (error) throw error;
|
|
864
875
|
}
|
|
865
876
|
// ---------------------------------------------------------------------------
|
|
@@ -868,7 +879,7 @@ var SupabaseShopProvider = class {
|
|
|
868
879
|
async listCategories() {
|
|
869
880
|
const db = getDb();
|
|
870
881
|
const tenantId = getTenantId();
|
|
871
|
-
let q = db.from(
|
|
882
|
+
let q = db.from(T.categories).select("*").order("sort_order", { ascending: true });
|
|
872
883
|
if (tenantId) q = q.eq("tenant_id", tenantId);
|
|
873
884
|
const { data, error } = await q;
|
|
874
885
|
if (error) throw error;
|
|
@@ -876,7 +887,7 @@ var SupabaseShopProvider = class {
|
|
|
876
887
|
}
|
|
877
888
|
async createCategory(input) {
|
|
878
889
|
const db = getDb();
|
|
879
|
-
const { data, error } = await db.from(
|
|
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();
|
|
880
891
|
if (error) throw error;
|
|
881
892
|
return rowToCategory(data);
|
|
882
893
|
}
|
|
@@ -888,12 +899,12 @@ var SupabaseShopProvider = class {
|
|
|
888
899
|
if (input.description !== void 0) updates.description = input.description;
|
|
889
900
|
if (input.parentId !== void 0) updates.parent_id = input.parentId;
|
|
890
901
|
if (input.sortOrder !== void 0) updates.sort_order = input.sortOrder;
|
|
891
|
-
const { data, error } = await db.from(
|
|
902
|
+
const { data, error } = await db.from(T.categories).update(updates).eq("id", id).select().single();
|
|
892
903
|
if (error) throw error;
|
|
893
904
|
return rowToCategory(data);
|
|
894
905
|
}
|
|
895
906
|
async deleteCategory(id) {
|
|
896
|
-
const { error } = await getDb().from(
|
|
907
|
+
const { error } = await getDb().from(T.categories).delete().eq("id", id);
|
|
897
908
|
if (error) throw error;
|
|
898
909
|
}
|
|
899
910
|
// ---------------------------------------------------------------------------
|
|
@@ -902,7 +913,7 @@ var SupabaseShopProvider = class {
|
|
|
902
913
|
async listOrders(options) {
|
|
903
914
|
const db = getDb();
|
|
904
915
|
const tenantId = getTenantId();
|
|
905
|
-
let q = db.from(
|
|
916
|
+
let q = db.from(T.orders).select(`*, items:${T.orderItems}(*)`);
|
|
906
917
|
if (tenantId) q = q.eq("tenant_id", tenantId);
|
|
907
918
|
if (options?.status) q = q.eq("status", options.status);
|
|
908
919
|
if (options?.financialStatus) q = q.eq("financial_status", options.financialStatus);
|
|
@@ -917,7 +928,7 @@ var SupabaseShopProvider = class {
|
|
|
917
928
|
return (data ?? []).map(rowToOrder);
|
|
918
929
|
}
|
|
919
930
|
async getOrder(id) {
|
|
920
|
-
const { data, error } = await getDb().from(
|
|
931
|
+
const { data, error } = await getDb().from(T.orders).select(`*, items:${T.orderItems}(*)`).eq("id", id).maybeSingle();
|
|
921
932
|
if (error) throw error;
|
|
922
933
|
return data ? rowToOrder(data) : null;
|
|
923
934
|
}
|
|
@@ -928,10 +939,10 @@ var SupabaseShopProvider = class {
|
|
|
928
939
|
const discountTotal = input.discountTotal ?? 0;
|
|
929
940
|
const shippingTotal = input.shippingTotal ?? 0;
|
|
930
941
|
const total = Math.round((subtotal - discountTotal + shippingTotal) * 100) / 100;
|
|
931
|
-
const { data: order, error: orderErr } = await db.from(
|
|
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();
|
|
932
943
|
if (orderErr) throw orderErr;
|
|
933
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 }));
|
|
934
|
-
await db.from(
|
|
945
|
+
await db.from(T.orderItems).insert(items);
|
|
935
946
|
return this.getOrder(order.id);
|
|
936
947
|
}
|
|
937
948
|
async updateOrder(id, input) {
|
|
@@ -940,7 +951,7 @@ var SupabaseShopProvider = class {
|
|
|
940
951
|
if (input.financialStatus !== void 0) updates.financial_status = input.financialStatus;
|
|
941
952
|
if (input.fulfillmentStatus !== void 0) updates.fulfillment_status = input.fulfillmentStatus;
|
|
942
953
|
if (input.notes !== void 0) updates.notes = input.notes;
|
|
943
|
-
const { error } = await getDb().from(
|
|
954
|
+
const { error } = await getDb().from(T.orders).update(updates).eq("id", id);
|
|
944
955
|
if (error) throw error;
|
|
945
956
|
return this.getOrder(id);
|
|
946
957
|
}
|
|
@@ -973,7 +984,7 @@ var SupabaseShopProvider = class {
|
|
|
973
984
|
async listCustomers(options) {
|
|
974
985
|
const db = getDb();
|
|
975
986
|
const tenantId = getTenantId();
|
|
976
|
-
let q = db.from(
|
|
987
|
+
let q = db.from(T.customers).select("*").order("first_name", { ascending: true });
|
|
977
988
|
if (tenantId) q = q.eq("tenant_id", tenantId);
|
|
978
989
|
if (options?.search) q = q.or(`first_name.ilike.%${options.search}%,last_name.ilike.%${options.search}%,email.ilike.%${options.search}%`);
|
|
979
990
|
if (options?.limit) q = q.limit(options.limit);
|
|
@@ -982,12 +993,12 @@ var SupabaseShopProvider = class {
|
|
|
982
993
|
return (data ?? []).map(rowToCustomer);
|
|
983
994
|
}
|
|
984
995
|
async getCustomer(id) {
|
|
985
|
-
const { data, error } = await getDb().from(
|
|
996
|
+
const { data, error } = await getDb().from(T.customers).select("*").eq("id", id).maybeSingle();
|
|
986
997
|
if (error) throw error;
|
|
987
998
|
return data ? rowToCustomer(data) : null;
|
|
988
999
|
}
|
|
989
1000
|
async createCustomer(input) {
|
|
990
|
-
const { data, error } = await getDb().from(
|
|
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();
|
|
991
1002
|
if (error) throw error;
|
|
992
1003
|
return rowToCustomer(data);
|
|
993
1004
|
}
|
|
@@ -998,12 +1009,12 @@ var SupabaseShopProvider = class {
|
|
|
998
1009
|
if (input.email !== void 0) updates.email = input.email;
|
|
999
1010
|
if (input.phone !== void 0) updates.phone = input.phone;
|
|
1000
1011
|
if (input.notes !== void 0) updates.notes = input.notes;
|
|
1001
|
-
const { data, error } = await getDb().from(
|
|
1012
|
+
const { data, error } = await getDb().from(T.customers).update(updates).eq("id", id).select().single();
|
|
1002
1013
|
if (error) throw error;
|
|
1003
1014
|
return rowToCustomer(data);
|
|
1004
1015
|
}
|
|
1005
1016
|
async deleteCustomer(id) {
|
|
1006
|
-
const { error } = await getDb().from(
|
|
1017
|
+
const { error } = await getDb().from(T.customers).delete().eq("id", id);
|
|
1007
1018
|
if (error) throw error;
|
|
1008
1019
|
}
|
|
1009
1020
|
// Find-or-create + link to auth.uid server-side (SECURITY DEFINER RPC), so the
|
|
@@ -1028,7 +1039,7 @@ var SupabaseShopProvider = class {
|
|
|
1028
1039
|
async listDiscounts(options) {
|
|
1029
1040
|
const db = getDb();
|
|
1030
1041
|
const tenantId = getTenantId();
|
|
1031
|
-
let q = db.from(
|
|
1042
|
+
let q = db.from(T.discounts).select("*").order("created_at", { ascending: false });
|
|
1032
1043
|
if (tenantId) q = q.eq("tenant_id", tenantId);
|
|
1033
1044
|
if (options?.status) q = q.eq("status", options.status);
|
|
1034
1045
|
if (options?.type) q = q.eq("type", options.type);
|
|
@@ -1039,12 +1050,12 @@ var SupabaseShopProvider = class {
|
|
|
1039
1050
|
return (data ?? []).map(rowToDiscount);
|
|
1040
1051
|
}
|
|
1041
1052
|
async getDiscount(id) {
|
|
1042
|
-
const { data, error } = await getDb().from(
|
|
1053
|
+
const { data, error } = await getDb().from(T.discounts).select("*").eq("id", id).maybeSingle();
|
|
1043
1054
|
if (error) throw error;
|
|
1044
1055
|
return data ? rowToDiscount(data) : null;
|
|
1045
1056
|
}
|
|
1046
1057
|
async createDiscount(input) {
|
|
1047
|
-
const { data, error } = await getDb().from(
|
|
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();
|
|
1048
1059
|
if (error) throw error;
|
|
1049
1060
|
return rowToDiscount(data);
|
|
1050
1061
|
}
|
|
@@ -1057,12 +1068,12 @@ var SupabaseShopProvider = class {
|
|
|
1057
1068
|
if (input.usageLimit !== void 0) updates.usage_limit = input.usageLimit;
|
|
1058
1069
|
if (input.status !== void 0) updates.status = input.status;
|
|
1059
1070
|
if (input.endsAt !== void 0) updates.ends_at = input.endsAt;
|
|
1060
|
-
const { data, error } = await getDb().from(
|
|
1071
|
+
const { data, error } = await getDb().from(T.discounts).update(updates).eq("id", id).select().single();
|
|
1061
1072
|
if (error) throw error;
|
|
1062
1073
|
return rowToDiscount(data);
|
|
1063
1074
|
}
|
|
1064
1075
|
async deleteDiscount(id) {
|
|
1065
|
-
const { error } = await getDb().from(
|
|
1076
|
+
const { error } = await getDb().from(T.discounts).delete().eq("id", id);
|
|
1066
1077
|
if (error) throw error;
|
|
1067
1078
|
}
|
|
1068
1079
|
};
|