@withvlibe/base-sdk 1.0.1 → 1.1.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/dist/index.mjs CHANGED
@@ -681,8 +681,525 @@ var VlibeBasePayments = class {
681
681
  return this.baseUrl;
682
682
  }
683
683
  };
684
+
685
+ // src/VlibeBaseEcommerce.ts
686
+ var VlibeBaseEcommerce = class {
687
+ constructor(db) {
688
+ this.db = db;
689
+ }
690
+ // ===========================
691
+ // PRODUCTS & INVENTORY
692
+ // ===========================
693
+ /**
694
+ * Create a new product
695
+ * Auto-generates SKU if not provided
696
+ */
697
+ async createProduct(input) {
698
+ const now = (/* @__PURE__ */ new Date()).toISOString();
699
+ const sku = input.sku || this.generateSKU();
700
+ const product = {
701
+ id: crypto.randomUUID(),
702
+ name: input.name,
703
+ description: input.description,
704
+ sku,
705
+ price: input.price,
706
+ currency: input.currency,
707
+ images: input.images || [],
708
+ stock: input.stock,
709
+ isActive: input.isActive ?? true,
710
+ category: input.category,
711
+ metadata: input.metadata,
712
+ created_at: now,
713
+ updated_at: now
714
+ };
715
+ await this.db.insert("products", product);
716
+ return product;
717
+ }
718
+ /**
719
+ * Update an existing product
720
+ */
721
+ async updateProduct(productId, updates) {
722
+ const existing = await this.getProduct(productId);
723
+ if (!existing) {
724
+ throw new Error(`Product not found: ${productId}`);
725
+ }
726
+ const updated = {
727
+ ...existing,
728
+ ...updates,
729
+ id: productId,
730
+ // Ensure ID doesn't change
731
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
732
+ };
733
+ await this.db.update("products", productId, updated);
734
+ return updated;
735
+ }
736
+ /**
737
+ * Get a single product by ID
738
+ */
739
+ async getProduct(productId) {
740
+ return await this.db.get("products", productId);
741
+ }
742
+ /**
743
+ * List products with filtering and pagination
744
+ */
745
+ async listProducts(options) {
746
+ const where = {};
747
+ if (options?.category) {
748
+ where.category = options.category;
749
+ }
750
+ if (options?.isActive !== void 0) {
751
+ where.isActive = options.isActive;
752
+ }
753
+ const products = await this.db.query("products", {
754
+ where,
755
+ orderBy: options?.sortBy,
756
+ orderDirection: "asc",
757
+ limit: options?.limit,
758
+ offset: options?.offset
759
+ });
760
+ const total = await this.db.count("products", where);
761
+ return { products, total };
762
+ }
763
+ /**
764
+ * Soft delete a product (sets isActive = false)
765
+ */
766
+ async deleteProduct(productId) {
767
+ await this.updateProduct(productId, { isActive: false });
768
+ }
769
+ /**
770
+ * Update product inventory with atomic operations
771
+ */
772
+ async updateInventory(productId, quantity, operation) {
773
+ const product = await this.getProduct(productId);
774
+ if (!product) {
775
+ throw new Error(`Product not found: ${productId}`);
776
+ }
777
+ let newStock;
778
+ switch (operation) {
779
+ case "set":
780
+ newStock = quantity;
781
+ break;
782
+ case "increment":
783
+ newStock = product.stock + quantity;
784
+ break;
785
+ case "decrement":
786
+ newStock = product.stock - quantity;
787
+ if (newStock < 0) {
788
+ throw new Error(`Insufficient stock for product ${productId}. Available: ${product.stock}, requested: ${quantity}`);
789
+ }
790
+ break;
791
+ }
792
+ return await this.updateProduct(productId, { stock: newStock });
793
+ }
794
+ /**
795
+ * Get products below stock threshold
796
+ */
797
+ async getLowStockProducts(threshold = 10) {
798
+ const products = await this.db.query("products", {
799
+ where: { isActive: true }
800
+ });
801
+ return products.filter((p) => p.stock <= threshold);
802
+ }
803
+ /**
804
+ * Bulk update inventory for multiple products
805
+ */
806
+ async bulkUpdateInventory(updates) {
807
+ const results = [];
808
+ for (const update of updates) {
809
+ const product = await this.updateInventory(update.productId, update.quantity, update.operation);
810
+ results.push(product);
811
+ }
812
+ return results;
813
+ }
814
+ // ===========================
815
+ // ORDERS & CHECKOUT
816
+ // ===========================
817
+ /**
818
+ * Calculate order totals from cart items
819
+ */
820
+ async calculateOrderTotal(items) {
821
+ const calculatedItems = [];
822
+ let subtotal = 0;
823
+ for (const item of items) {
824
+ const product = await this.getProduct(item.productId);
825
+ if (!product) {
826
+ throw new Error(`Product not found: ${item.productId}`);
827
+ }
828
+ const lineTotal = product.price * item.quantity;
829
+ const inStock = product.stock >= item.quantity;
830
+ calculatedItems.push({
831
+ productId: item.productId,
832
+ name: product.name,
833
+ price: product.price,
834
+ quantity: item.quantity,
835
+ lineTotal,
836
+ inStock
837
+ });
838
+ subtotal += lineTotal;
839
+ }
840
+ const allInStock = calculatedItems.every((item) => item.inStock);
841
+ if (!allInStock) {
842
+ const outOfStock = calculatedItems.filter((item) => !item.inStock);
843
+ throw new Error(`Items out of stock: ${outOfStock.map((i) => i.name).join(", ")}`);
844
+ }
845
+ const tax = Math.round(subtotal * 0.08);
846
+ const shipping = subtotal > 5e3 ? 0 : 500;
847
+ const total = subtotal + tax + shipping;
848
+ return {
849
+ subtotal,
850
+ tax,
851
+ shipping,
852
+ total,
853
+ items: calculatedItems
854
+ };
855
+ }
856
+ /**
857
+ * Create an order from cart items
858
+ * Reduces inventory atomically
859
+ */
860
+ async createOrder(input) {
861
+ const now = (/* @__PURE__ */ new Date()).toISOString();
862
+ const calculation = await this.calculateOrderTotal(
863
+ input.items.map((item) => ({ productId: item.productId, quantity: item.quantity }))
864
+ );
865
+ const orderItems = calculation.items.map((item) => ({
866
+ productId: item.productId,
867
+ name: item.name,
868
+ quantity: item.quantity,
869
+ price: item.price
870
+ }));
871
+ const order = {
872
+ id: crypto.randomUUID(),
873
+ userId: input.userId,
874
+ status: "pending",
875
+ items: orderItems,
876
+ subtotal: calculation.subtotal,
877
+ tax: calculation.tax,
878
+ shipping: calculation.shipping,
879
+ total: calculation.total,
880
+ shippingAddress: input.shippingAddress,
881
+ billingAddress: input.billingAddress,
882
+ paymentMethodId: input.paymentMethodId,
883
+ notes: input.notes,
884
+ created_at: now,
885
+ updated_at: now
886
+ };
887
+ await this.db.insert("orders", order);
888
+ for (const item of orderItems) {
889
+ await this.db.insert("order_items", {
890
+ id: crypto.randomUUID(),
891
+ orderId: order.id,
892
+ ...item,
893
+ lineTotal: item.price * item.quantity,
894
+ created_at: now
895
+ });
896
+ }
897
+ await this.bulkUpdateInventory(
898
+ input.items.map((item) => ({
899
+ productId: item.productId,
900
+ quantity: item.quantity,
901
+ operation: "decrement"
902
+ }))
903
+ );
904
+ return order;
905
+ }
906
+ /**
907
+ * Get an order by ID
908
+ */
909
+ async getOrder(orderId) {
910
+ const order = await this.db.get("orders", orderId);
911
+ if (!order) return null;
912
+ const items = await this.db.query("order_items", {
913
+ where: { orderId }
914
+ });
915
+ order.items = items.map((item) => ({
916
+ productId: item.productId,
917
+ name: item.name,
918
+ quantity: item.quantity,
919
+ price: item.price
920
+ }));
921
+ return order;
922
+ }
923
+ /**
924
+ * List orders with filtering
925
+ */
926
+ async listOrders(options) {
927
+ const where = {};
928
+ if (options?.userId) {
929
+ where.userId = options.userId;
930
+ }
931
+ if (options?.status) {
932
+ where.status = options.status;
933
+ }
934
+ const orders = await this.db.query("orders", {
935
+ where,
936
+ orderBy: options?.sortBy || "created_at",
937
+ orderDirection: "desc",
938
+ limit: options?.limit,
939
+ offset: options?.offset
940
+ });
941
+ for (const order of orders) {
942
+ const items = await this.db.query("order_items", {
943
+ where: { orderId: order.id }
944
+ });
945
+ order.items = items.map((item) => ({
946
+ productId: item.productId,
947
+ name: item.name,
948
+ quantity: item.quantity,
949
+ price: item.price
950
+ }));
951
+ }
952
+ const total = await this.db.count("orders", where);
953
+ return { orders, total };
954
+ }
955
+ /**
956
+ * Update order status
957
+ */
958
+ async updateOrderStatus(orderId, status) {
959
+ const order = await this.getOrder(orderId);
960
+ if (!order) {
961
+ throw new Error(`Order not found: ${orderId}`);
962
+ }
963
+ const updated = {
964
+ ...order,
965
+ status,
966
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
967
+ };
968
+ await this.db.update("orders", orderId, updated);
969
+ return updated;
970
+ }
971
+ /**
972
+ * Cancel an order and optionally restore inventory
973
+ */
974
+ async cancelOrder(orderId, restoreInventory = true) {
975
+ const order = await this.getOrder(orderId);
976
+ if (!order) {
977
+ throw new Error(`Order not found: ${orderId}`);
978
+ }
979
+ if (order.status === "cancelled") {
980
+ return order;
981
+ }
982
+ if (restoreInventory && order.items) {
983
+ await this.bulkUpdateInventory(
984
+ order.items.map((item) => ({
985
+ productId: item.productId,
986
+ quantity: item.quantity,
987
+ operation: "increment"
988
+ }))
989
+ );
990
+ }
991
+ return await this.updateOrderStatus(orderId, "cancelled");
992
+ }
993
+ // ===========================
994
+ // CART OPERATIONS
995
+ // ===========================
996
+ /**
997
+ * Add item to user's cart
998
+ */
999
+ async addToCart(userId, item) {
1000
+ const existingCart = await this.db.query("carts", {
1001
+ where: { userId, productId: item.productId }
1002
+ });
1003
+ if (existingCart.length > 0) {
1004
+ const existing = existingCart[0];
1005
+ await this.db.update("carts", existing.id, {
1006
+ ...existing,
1007
+ quantity: existing.quantity + item.quantity,
1008
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1009
+ });
1010
+ } else {
1011
+ await this.db.insert("carts", {
1012
+ id: crypto.randomUUID(),
1013
+ userId,
1014
+ productId: item.productId,
1015
+ quantity: item.quantity,
1016
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
1017
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1018
+ });
1019
+ }
1020
+ return await this.getCart(userId);
1021
+ }
1022
+ /**
1023
+ * Update cart item quantity or remove if quantity = 0
1024
+ */
1025
+ async updateCartItem(userId, productId, quantity) {
1026
+ const items = await this.db.query("carts", {
1027
+ where: { userId, productId }
1028
+ });
1029
+ if (items.length === 0) {
1030
+ throw new Error(`Cart item not found for product: ${productId}`);
1031
+ }
1032
+ const item = items[0];
1033
+ if (quantity === 0) {
1034
+ await this.db.delete("carts", item.id);
1035
+ } else {
1036
+ await this.db.update("carts", item.id, {
1037
+ ...item,
1038
+ quantity,
1039
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1040
+ });
1041
+ }
1042
+ return await this.getCart(userId);
1043
+ }
1044
+ /**
1045
+ * Get user's cart
1046
+ */
1047
+ async getCart(userId) {
1048
+ const items = await this.db.query("carts", {
1049
+ where: { userId }
1050
+ });
1051
+ return items.map((item) => ({
1052
+ productId: item.productId,
1053
+ quantity: item.quantity
1054
+ }));
1055
+ }
1056
+ /**
1057
+ * Clear user's cart
1058
+ */
1059
+ async clearCart(userId) {
1060
+ const items = await this.db.query("carts", {
1061
+ where: { userId }
1062
+ });
1063
+ for (const item of items) {
1064
+ await this.db.delete("carts", item.id);
1065
+ }
1066
+ }
1067
+ /**
1068
+ * Checkout - convert cart to order and clear cart
1069
+ */
1070
+ async checkout(userId, shippingAddress, paymentMethodId) {
1071
+ const cart = await this.getCart(userId);
1072
+ if (cart.length === 0) {
1073
+ throw new Error("Cart is empty");
1074
+ }
1075
+ const order = await this.createOrder({
1076
+ userId,
1077
+ items: cart.map((item) => ({ productId: item.productId, quantity: item.quantity })),
1078
+ shippingAddress,
1079
+ paymentMethodId
1080
+ });
1081
+ await this.clearCart(userId);
1082
+ return order;
1083
+ }
1084
+ // ===========================
1085
+ // ANALYTICS
1086
+ // ===========================
1087
+ /**
1088
+ * Get revenue statistics for a time period
1089
+ */
1090
+ async getRevenueStats(period) {
1091
+ const now = /* @__PURE__ */ new Date();
1092
+ const periodStart = this.getPeriodStart(now, period);
1093
+ const previousPeriodStart = this.getPeriodStart(new Date(periodStart), period);
1094
+ const orders = await this.db.query("orders", {});
1095
+ const currentOrders = orders.filter((o) => new Date(o.created_at) >= periodStart);
1096
+ const previousOrders = orders.filter(
1097
+ (o) => new Date(o.created_at) >= previousPeriodStart && new Date(o.created_at) < periodStart
1098
+ );
1099
+ const totalRevenue = currentOrders.reduce((sum, o) => sum + o.total, 0);
1100
+ const previousRevenue = previousOrders.reduce((sum, o) => sum + o.total, 0);
1101
+ const totalOrders = currentOrders.length;
1102
+ const previousOrderCount = previousOrders.length;
1103
+ const averageOrderValue = totalOrders > 0 ? totalRevenue / totalOrders : 0;
1104
+ const revenueTrend = previousRevenue > 0 ? (totalRevenue - previousRevenue) / previousRevenue * 100 : 0;
1105
+ const ordersTrend = previousOrderCount > 0 ? (totalOrders - previousOrderCount) / previousOrderCount * 100 : 0;
1106
+ return {
1107
+ totalRevenue,
1108
+ totalOrders,
1109
+ averageOrderValue,
1110
+ period: {
1111
+ start: periodStart.toISOString(),
1112
+ end: now.toISOString()
1113
+ },
1114
+ trend: {
1115
+ revenue: Math.round(revenueTrend * 100) / 100,
1116
+ orders: Math.round(ordersTrend * 100) / 100
1117
+ }
1118
+ };
1119
+ }
1120
+ /**
1121
+ * Get top selling products
1122
+ */
1123
+ async getTopProducts(limit = 10, period) {
1124
+ const orders = await this.db.query("orders", {});
1125
+ let filteredOrders = orders;
1126
+ if (period) {
1127
+ const periodStart = this.getPeriodStart(/* @__PURE__ */ new Date(), period);
1128
+ filteredOrders = orders.filter((o) => new Date(o.created_at) >= periodStart);
1129
+ }
1130
+ const productMap = /* @__PURE__ */ new Map();
1131
+ for (const order of filteredOrders) {
1132
+ if (!order.items) continue;
1133
+ for (const item of order.items) {
1134
+ const existing = productMap.get(item.productId) || { name: item.name, totalSold: 0, revenue: 0 };
1135
+ existing.totalSold += item.quantity;
1136
+ existing.revenue += item.price * item.quantity;
1137
+ productMap.set(item.productId, existing);
1138
+ }
1139
+ }
1140
+ const stats = Array.from(productMap.entries()).map(([productId, data]) => ({
1141
+ productId,
1142
+ name: data.name,
1143
+ totalSold: data.totalSold,
1144
+ revenue: data.revenue
1145
+ }));
1146
+ stats.sort((a, b) => b.revenue - a.revenue);
1147
+ return stats.slice(0, limit);
1148
+ }
1149
+ /**
1150
+ * Get order statistics by status
1151
+ */
1152
+ async getOrderStats() {
1153
+ const orders = await this.db.query("orders", {});
1154
+ const stats = {
1155
+ pending: 0,
1156
+ processing: 0,
1157
+ shipped: 0,
1158
+ delivered: 0,
1159
+ cancelled: 0,
1160
+ averageOrderValue: 0
1161
+ };
1162
+ let totalValue = 0;
1163
+ for (const order of orders) {
1164
+ stats[order.status]++;
1165
+ totalValue += order.total;
1166
+ }
1167
+ stats.averageOrderValue = orders.length > 0 ? totalValue / orders.length : 0;
1168
+ return stats;
1169
+ }
1170
+ // ===========================
1171
+ // HELPER METHODS
1172
+ // ===========================
1173
+ generateSKU() {
1174
+ const timestamp = Date.now().toString(36).toUpperCase();
1175
+ const random = Math.random().toString(36).substring(2, 6).toUpperCase();
1176
+ return `${timestamp}-${random}`;
1177
+ }
1178
+ getPeriodStart(date, period) {
1179
+ const d = new Date(date);
1180
+ switch (period) {
1181
+ case "day":
1182
+ d.setHours(0, 0, 0, 0);
1183
+ break;
1184
+ case "week":
1185
+ d.setDate(d.getDate() - d.getDay());
1186
+ d.setHours(0, 0, 0, 0);
1187
+ break;
1188
+ case "month":
1189
+ d.setDate(1);
1190
+ d.setHours(0, 0, 0, 0);
1191
+ break;
1192
+ case "year":
1193
+ d.setMonth(0, 1);
1194
+ d.setHours(0, 0, 0, 0);
1195
+ break;
1196
+ }
1197
+ return d;
1198
+ }
1199
+ };
684
1200
  export {
685
1201
  VlibeBaseAuth,
686
1202
  VlibeBaseDatabase,
1203
+ VlibeBaseEcommerce,
687
1204
  VlibeBasePayments
688
1205
  };
package/dist/react.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { j as BaseRecord, V as VlibeBaseDatabase, w as UseCollectionOptions, v as UseCollectionReturn, x as UseKVReturn, c as VlibeBaseAuth, y as UseAuthReturn } from './VlibeBaseAuth-BxS9T0vB.mjs';
2
- export { z as UsePaymentsReturn, l as VlibeUser } from './VlibeBaseAuth-BxS9T0vB.mjs';
1
+ import { k as BaseRecord, V as VlibeBaseDatabase, I as UseCollectionOptions, H as UseCollectionReturn, J as UseKVReturn, c as VlibeBaseAuth, K as UseAuthReturn, d as VlibeBaseEcommerce, q as Product, u as CreateProductInput, t as CartItem, s as Address, O as Order } from './VlibeBaseEcommerce-B6l17uup.mjs';
2
+ export { v as CreateOrderInput, L as UsePaymentsReturn, m as VlibeUser } from './VlibeBaseEcommerce-B6l17uup.mjs';
3
3
 
4
4
  /**
5
5
  * React hook for working with a database collection
@@ -93,4 +93,108 @@ declare function useKV<T = unknown>(db: VlibeBaseDatabase, key: string): UseKVRe
93
93
  */
94
94
  declare function useAuth(auth: VlibeBaseAuth, initialToken?: string | null): UseAuthReturn;
95
95
 
96
- export { BaseRecord, UseAuthReturn, UseCollectionOptions, UseCollectionReturn, UseKVReturn, useAuth, useCollection, useKV };
96
+ interface UseProductsOptions {
97
+ category?: string;
98
+ isActive?: boolean;
99
+ autoLoad?: boolean;
100
+ }
101
+ interface UseProductsReturn {
102
+ products: Product[];
103
+ loading: boolean;
104
+ error: Error | null;
105
+ refresh: () => Promise<void>;
106
+ createProduct: (input: CreateProductInput) => Promise<Product>;
107
+ updateProduct: (productId: string, updates: Partial<Product>) => Promise<Product>;
108
+ deleteProduct: (productId: string) => Promise<void>;
109
+ updateInventory: (productId: string, quantity: number, operation: 'set' | 'increment' | 'decrement') => Promise<Product>;
110
+ }
111
+ /**
112
+ * React hook for managing products
113
+ *
114
+ * @param ecommerce - VlibeBaseEcommerce instance
115
+ * @param options - Optional filtering and configuration
116
+ * @returns Product list with CRUD operations
117
+ *
118
+ * @example
119
+ * ```tsx
120
+ * const { products, loading, createProduct } = useProducts(ecommerce, { isActive: true });
121
+ *
122
+ * const handleCreate = async () => {
123
+ * await createProduct({
124
+ * name: 'T-Shirt',
125
+ * price: 2999,
126
+ * stock: 50,
127
+ * currency: 'usd'
128
+ * });
129
+ * };
130
+ * ```
131
+ */
132
+ declare function useProducts(ecommerce: VlibeBaseEcommerce, options?: UseProductsOptions): UseProductsReturn;
133
+
134
+ interface UseCartReturn {
135
+ cart: CartItem[];
136
+ itemCount: number;
137
+ loading: boolean;
138
+ error: Error | null;
139
+ refresh: () => Promise<void>;
140
+ addItem: (item: CartItem) => Promise<CartItem[]>;
141
+ updateItem: (productId: string, quantity: number) => Promise<CartItem[]>;
142
+ removeItem: (productId: string) => Promise<CartItem[]>;
143
+ clear: () => Promise<void>;
144
+ checkout: (shippingAddress: Address, paymentMethodId?: string) => Promise<Order>;
145
+ calculateTotal: () => Promise<any>;
146
+ }
147
+ /**
148
+ * React hook for managing shopping cart
149
+ *
150
+ * @param ecommerce - VlibeBaseEcommerce instance
151
+ * @param userId - User ID for cart ownership
152
+ * @returns Shopping cart with add/update/remove/checkout operations
153
+ *
154
+ * @example
155
+ * ```tsx
156
+ * const { cart, itemCount, addItem, checkout } = useCart(ecommerce, userId);
157
+ *
158
+ * const handleAddToCart = async (productId: string) => {
159
+ * await addItem({ productId, quantity: 1 });
160
+ * };
161
+ *
162
+ * const handleCheckout = async () => {
163
+ * const order = await checkout(shippingAddress, paymentMethodId);
164
+ * };
165
+ * ```
166
+ */
167
+ declare function useCart(ecommerce: VlibeBaseEcommerce, userId: string): UseCartReturn;
168
+
169
+ interface UseOrdersOptions {
170
+ userId?: string;
171
+ status?: Order['status'];
172
+ autoLoad?: boolean;
173
+ }
174
+ interface UseOrdersReturn {
175
+ orders: Order[];
176
+ loading: boolean;
177
+ error: Error | null;
178
+ refresh: () => Promise<void>;
179
+ updateStatus: (orderId: string, status: Order['status']) => Promise<Order>;
180
+ cancelOrder: (orderId: string, restoreInventory?: boolean) => Promise<Order>;
181
+ }
182
+ /**
183
+ * React hook for managing orders
184
+ *
185
+ * @param ecommerce - VlibeBaseEcommerce instance
186
+ * @param options - Optional filtering and configuration
187
+ * @returns Order list with status management operations
188
+ *
189
+ * @example
190
+ * ```tsx
191
+ * const { orders, loading, updateStatus } = useOrders(ecommerce, { userId: 'user123' });
192
+ *
193
+ * const handleShip = async (orderId: string) => {
194
+ * await updateStatus(orderId, 'shipped');
195
+ * };
196
+ * ```
197
+ */
198
+ declare function useOrders(ecommerce: VlibeBaseEcommerce, options?: UseOrdersOptions): UseOrdersReturn;
199
+
200
+ export { BaseRecord, CartItem, CreateProductInput, Order, Product, UseAuthReturn, type UseCartReturn, UseCollectionOptions, UseCollectionReturn, UseKVReturn, type UseOrdersOptions, type UseOrdersReturn, type UseProductsOptions, type UseProductsReturn, useAuth, useCart, useCollection, useKV, useOrders, useProducts };