@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.js CHANGED
@@ -22,6 +22,7 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  VlibeBaseAuth: () => VlibeBaseAuth,
24
24
  VlibeBaseDatabase: () => VlibeBaseDatabase,
25
+ VlibeBaseEcommerce: () => VlibeBaseEcommerce,
25
26
  VlibeBasePayments: () => VlibeBasePayments
26
27
  });
27
28
  module.exports = __toCommonJS(index_exports);
@@ -709,9 +710,526 @@ var VlibeBasePayments = class {
709
710
  return this.baseUrl;
710
711
  }
711
712
  };
713
+
714
+ // src/VlibeBaseEcommerce.ts
715
+ var VlibeBaseEcommerce = class {
716
+ constructor(db) {
717
+ this.db = db;
718
+ }
719
+ // ===========================
720
+ // PRODUCTS & INVENTORY
721
+ // ===========================
722
+ /**
723
+ * Create a new product
724
+ * Auto-generates SKU if not provided
725
+ */
726
+ async createProduct(input) {
727
+ const now = (/* @__PURE__ */ new Date()).toISOString();
728
+ const sku = input.sku || this.generateSKU();
729
+ const product = {
730
+ id: crypto.randomUUID(),
731
+ name: input.name,
732
+ description: input.description,
733
+ sku,
734
+ price: input.price,
735
+ currency: input.currency,
736
+ images: input.images || [],
737
+ stock: input.stock,
738
+ isActive: input.isActive ?? true,
739
+ category: input.category,
740
+ metadata: input.metadata,
741
+ created_at: now,
742
+ updated_at: now
743
+ };
744
+ await this.db.insert("products", product);
745
+ return product;
746
+ }
747
+ /**
748
+ * Update an existing product
749
+ */
750
+ async updateProduct(productId, updates) {
751
+ const existing = await this.getProduct(productId);
752
+ if (!existing) {
753
+ throw new Error(`Product not found: ${productId}`);
754
+ }
755
+ const updated = {
756
+ ...existing,
757
+ ...updates,
758
+ id: productId,
759
+ // Ensure ID doesn't change
760
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
761
+ };
762
+ await this.db.update("products", productId, updated);
763
+ return updated;
764
+ }
765
+ /**
766
+ * Get a single product by ID
767
+ */
768
+ async getProduct(productId) {
769
+ return await this.db.get("products", productId);
770
+ }
771
+ /**
772
+ * List products with filtering and pagination
773
+ */
774
+ async listProducts(options) {
775
+ const where = {};
776
+ if (options?.category) {
777
+ where.category = options.category;
778
+ }
779
+ if (options?.isActive !== void 0) {
780
+ where.isActive = options.isActive;
781
+ }
782
+ const products = await this.db.query("products", {
783
+ where,
784
+ orderBy: options?.sortBy,
785
+ orderDirection: "asc",
786
+ limit: options?.limit,
787
+ offset: options?.offset
788
+ });
789
+ const total = await this.db.count("products", where);
790
+ return { products, total };
791
+ }
792
+ /**
793
+ * Soft delete a product (sets isActive = false)
794
+ */
795
+ async deleteProduct(productId) {
796
+ await this.updateProduct(productId, { isActive: false });
797
+ }
798
+ /**
799
+ * Update product inventory with atomic operations
800
+ */
801
+ async updateInventory(productId, quantity, operation) {
802
+ const product = await this.getProduct(productId);
803
+ if (!product) {
804
+ throw new Error(`Product not found: ${productId}`);
805
+ }
806
+ let newStock;
807
+ switch (operation) {
808
+ case "set":
809
+ newStock = quantity;
810
+ break;
811
+ case "increment":
812
+ newStock = product.stock + quantity;
813
+ break;
814
+ case "decrement":
815
+ newStock = product.stock - quantity;
816
+ if (newStock < 0) {
817
+ throw new Error(`Insufficient stock for product ${productId}. Available: ${product.stock}, requested: ${quantity}`);
818
+ }
819
+ break;
820
+ }
821
+ return await this.updateProduct(productId, { stock: newStock });
822
+ }
823
+ /**
824
+ * Get products below stock threshold
825
+ */
826
+ async getLowStockProducts(threshold = 10) {
827
+ const products = await this.db.query("products", {
828
+ where: { isActive: true }
829
+ });
830
+ return products.filter((p) => p.stock <= threshold);
831
+ }
832
+ /**
833
+ * Bulk update inventory for multiple products
834
+ */
835
+ async bulkUpdateInventory(updates) {
836
+ const results = [];
837
+ for (const update of updates) {
838
+ const product = await this.updateInventory(update.productId, update.quantity, update.operation);
839
+ results.push(product);
840
+ }
841
+ return results;
842
+ }
843
+ // ===========================
844
+ // ORDERS & CHECKOUT
845
+ // ===========================
846
+ /**
847
+ * Calculate order totals from cart items
848
+ */
849
+ async calculateOrderTotal(items) {
850
+ const calculatedItems = [];
851
+ let subtotal = 0;
852
+ for (const item of items) {
853
+ const product = await this.getProduct(item.productId);
854
+ if (!product) {
855
+ throw new Error(`Product not found: ${item.productId}`);
856
+ }
857
+ const lineTotal = product.price * item.quantity;
858
+ const inStock = product.stock >= item.quantity;
859
+ calculatedItems.push({
860
+ productId: item.productId,
861
+ name: product.name,
862
+ price: product.price,
863
+ quantity: item.quantity,
864
+ lineTotal,
865
+ inStock
866
+ });
867
+ subtotal += lineTotal;
868
+ }
869
+ const allInStock = calculatedItems.every((item) => item.inStock);
870
+ if (!allInStock) {
871
+ const outOfStock = calculatedItems.filter((item) => !item.inStock);
872
+ throw new Error(`Items out of stock: ${outOfStock.map((i) => i.name).join(", ")}`);
873
+ }
874
+ const tax = Math.round(subtotal * 0.08);
875
+ const shipping = subtotal > 5e3 ? 0 : 500;
876
+ const total = subtotal + tax + shipping;
877
+ return {
878
+ subtotal,
879
+ tax,
880
+ shipping,
881
+ total,
882
+ items: calculatedItems
883
+ };
884
+ }
885
+ /**
886
+ * Create an order from cart items
887
+ * Reduces inventory atomically
888
+ */
889
+ async createOrder(input) {
890
+ const now = (/* @__PURE__ */ new Date()).toISOString();
891
+ const calculation = await this.calculateOrderTotal(
892
+ input.items.map((item) => ({ productId: item.productId, quantity: item.quantity }))
893
+ );
894
+ const orderItems = calculation.items.map((item) => ({
895
+ productId: item.productId,
896
+ name: item.name,
897
+ quantity: item.quantity,
898
+ price: item.price
899
+ }));
900
+ const order = {
901
+ id: crypto.randomUUID(),
902
+ userId: input.userId,
903
+ status: "pending",
904
+ items: orderItems,
905
+ subtotal: calculation.subtotal,
906
+ tax: calculation.tax,
907
+ shipping: calculation.shipping,
908
+ total: calculation.total,
909
+ shippingAddress: input.shippingAddress,
910
+ billingAddress: input.billingAddress,
911
+ paymentMethodId: input.paymentMethodId,
912
+ notes: input.notes,
913
+ created_at: now,
914
+ updated_at: now
915
+ };
916
+ await this.db.insert("orders", order);
917
+ for (const item of orderItems) {
918
+ await this.db.insert("order_items", {
919
+ id: crypto.randomUUID(),
920
+ orderId: order.id,
921
+ ...item,
922
+ lineTotal: item.price * item.quantity,
923
+ created_at: now
924
+ });
925
+ }
926
+ await this.bulkUpdateInventory(
927
+ input.items.map((item) => ({
928
+ productId: item.productId,
929
+ quantity: item.quantity,
930
+ operation: "decrement"
931
+ }))
932
+ );
933
+ return order;
934
+ }
935
+ /**
936
+ * Get an order by ID
937
+ */
938
+ async getOrder(orderId) {
939
+ const order = await this.db.get("orders", orderId);
940
+ if (!order) return null;
941
+ const items = await this.db.query("order_items", {
942
+ where: { orderId }
943
+ });
944
+ order.items = items.map((item) => ({
945
+ productId: item.productId,
946
+ name: item.name,
947
+ quantity: item.quantity,
948
+ price: item.price
949
+ }));
950
+ return order;
951
+ }
952
+ /**
953
+ * List orders with filtering
954
+ */
955
+ async listOrders(options) {
956
+ const where = {};
957
+ if (options?.userId) {
958
+ where.userId = options.userId;
959
+ }
960
+ if (options?.status) {
961
+ where.status = options.status;
962
+ }
963
+ const orders = await this.db.query("orders", {
964
+ where,
965
+ orderBy: options?.sortBy || "created_at",
966
+ orderDirection: "desc",
967
+ limit: options?.limit,
968
+ offset: options?.offset
969
+ });
970
+ for (const order of orders) {
971
+ const items = await this.db.query("order_items", {
972
+ where: { orderId: order.id }
973
+ });
974
+ order.items = items.map((item) => ({
975
+ productId: item.productId,
976
+ name: item.name,
977
+ quantity: item.quantity,
978
+ price: item.price
979
+ }));
980
+ }
981
+ const total = await this.db.count("orders", where);
982
+ return { orders, total };
983
+ }
984
+ /**
985
+ * Update order status
986
+ */
987
+ async updateOrderStatus(orderId, status) {
988
+ const order = await this.getOrder(orderId);
989
+ if (!order) {
990
+ throw new Error(`Order not found: ${orderId}`);
991
+ }
992
+ const updated = {
993
+ ...order,
994
+ status,
995
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
996
+ };
997
+ await this.db.update("orders", orderId, updated);
998
+ return updated;
999
+ }
1000
+ /**
1001
+ * Cancel an order and optionally restore inventory
1002
+ */
1003
+ async cancelOrder(orderId, restoreInventory = true) {
1004
+ const order = await this.getOrder(orderId);
1005
+ if (!order) {
1006
+ throw new Error(`Order not found: ${orderId}`);
1007
+ }
1008
+ if (order.status === "cancelled") {
1009
+ return order;
1010
+ }
1011
+ if (restoreInventory && order.items) {
1012
+ await this.bulkUpdateInventory(
1013
+ order.items.map((item) => ({
1014
+ productId: item.productId,
1015
+ quantity: item.quantity,
1016
+ operation: "increment"
1017
+ }))
1018
+ );
1019
+ }
1020
+ return await this.updateOrderStatus(orderId, "cancelled");
1021
+ }
1022
+ // ===========================
1023
+ // CART OPERATIONS
1024
+ // ===========================
1025
+ /**
1026
+ * Add item to user's cart
1027
+ */
1028
+ async addToCart(userId, item) {
1029
+ const existingCart = await this.db.query("carts", {
1030
+ where: { userId, productId: item.productId }
1031
+ });
1032
+ if (existingCart.length > 0) {
1033
+ const existing = existingCart[0];
1034
+ await this.db.update("carts", existing.id, {
1035
+ ...existing,
1036
+ quantity: existing.quantity + item.quantity,
1037
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1038
+ });
1039
+ } else {
1040
+ await this.db.insert("carts", {
1041
+ id: crypto.randomUUID(),
1042
+ userId,
1043
+ productId: item.productId,
1044
+ quantity: item.quantity,
1045
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
1046
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1047
+ });
1048
+ }
1049
+ return await this.getCart(userId);
1050
+ }
1051
+ /**
1052
+ * Update cart item quantity or remove if quantity = 0
1053
+ */
1054
+ async updateCartItem(userId, productId, quantity) {
1055
+ const items = await this.db.query("carts", {
1056
+ where: { userId, productId }
1057
+ });
1058
+ if (items.length === 0) {
1059
+ throw new Error(`Cart item not found for product: ${productId}`);
1060
+ }
1061
+ const item = items[0];
1062
+ if (quantity === 0) {
1063
+ await this.db.delete("carts", item.id);
1064
+ } else {
1065
+ await this.db.update("carts", item.id, {
1066
+ ...item,
1067
+ quantity,
1068
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1069
+ });
1070
+ }
1071
+ return await this.getCart(userId);
1072
+ }
1073
+ /**
1074
+ * Get user's cart
1075
+ */
1076
+ async getCart(userId) {
1077
+ const items = await this.db.query("carts", {
1078
+ where: { userId }
1079
+ });
1080
+ return items.map((item) => ({
1081
+ productId: item.productId,
1082
+ quantity: item.quantity
1083
+ }));
1084
+ }
1085
+ /**
1086
+ * Clear user's cart
1087
+ */
1088
+ async clearCart(userId) {
1089
+ const items = await this.db.query("carts", {
1090
+ where: { userId }
1091
+ });
1092
+ for (const item of items) {
1093
+ await this.db.delete("carts", item.id);
1094
+ }
1095
+ }
1096
+ /**
1097
+ * Checkout - convert cart to order and clear cart
1098
+ */
1099
+ async checkout(userId, shippingAddress, paymentMethodId) {
1100
+ const cart = await this.getCart(userId);
1101
+ if (cart.length === 0) {
1102
+ throw new Error("Cart is empty");
1103
+ }
1104
+ const order = await this.createOrder({
1105
+ userId,
1106
+ items: cart.map((item) => ({ productId: item.productId, quantity: item.quantity })),
1107
+ shippingAddress,
1108
+ paymentMethodId
1109
+ });
1110
+ await this.clearCart(userId);
1111
+ return order;
1112
+ }
1113
+ // ===========================
1114
+ // ANALYTICS
1115
+ // ===========================
1116
+ /**
1117
+ * Get revenue statistics for a time period
1118
+ */
1119
+ async getRevenueStats(period) {
1120
+ const now = /* @__PURE__ */ new Date();
1121
+ const periodStart = this.getPeriodStart(now, period);
1122
+ const previousPeriodStart = this.getPeriodStart(new Date(periodStart), period);
1123
+ const orders = await this.db.query("orders", {});
1124
+ const currentOrders = orders.filter((o) => new Date(o.created_at) >= periodStart);
1125
+ const previousOrders = orders.filter(
1126
+ (o) => new Date(o.created_at) >= previousPeriodStart && new Date(o.created_at) < periodStart
1127
+ );
1128
+ const totalRevenue = currentOrders.reduce((sum, o) => sum + o.total, 0);
1129
+ const previousRevenue = previousOrders.reduce((sum, o) => sum + o.total, 0);
1130
+ const totalOrders = currentOrders.length;
1131
+ const previousOrderCount = previousOrders.length;
1132
+ const averageOrderValue = totalOrders > 0 ? totalRevenue / totalOrders : 0;
1133
+ const revenueTrend = previousRevenue > 0 ? (totalRevenue - previousRevenue) / previousRevenue * 100 : 0;
1134
+ const ordersTrend = previousOrderCount > 0 ? (totalOrders - previousOrderCount) / previousOrderCount * 100 : 0;
1135
+ return {
1136
+ totalRevenue,
1137
+ totalOrders,
1138
+ averageOrderValue,
1139
+ period: {
1140
+ start: periodStart.toISOString(),
1141
+ end: now.toISOString()
1142
+ },
1143
+ trend: {
1144
+ revenue: Math.round(revenueTrend * 100) / 100,
1145
+ orders: Math.round(ordersTrend * 100) / 100
1146
+ }
1147
+ };
1148
+ }
1149
+ /**
1150
+ * Get top selling products
1151
+ */
1152
+ async getTopProducts(limit = 10, period) {
1153
+ const orders = await this.db.query("orders", {});
1154
+ let filteredOrders = orders;
1155
+ if (period) {
1156
+ const periodStart = this.getPeriodStart(/* @__PURE__ */ new Date(), period);
1157
+ filteredOrders = orders.filter((o) => new Date(o.created_at) >= periodStart);
1158
+ }
1159
+ const productMap = /* @__PURE__ */ new Map();
1160
+ for (const order of filteredOrders) {
1161
+ if (!order.items) continue;
1162
+ for (const item of order.items) {
1163
+ const existing = productMap.get(item.productId) || { name: item.name, totalSold: 0, revenue: 0 };
1164
+ existing.totalSold += item.quantity;
1165
+ existing.revenue += item.price * item.quantity;
1166
+ productMap.set(item.productId, existing);
1167
+ }
1168
+ }
1169
+ const stats = Array.from(productMap.entries()).map(([productId, data]) => ({
1170
+ productId,
1171
+ name: data.name,
1172
+ totalSold: data.totalSold,
1173
+ revenue: data.revenue
1174
+ }));
1175
+ stats.sort((a, b) => b.revenue - a.revenue);
1176
+ return stats.slice(0, limit);
1177
+ }
1178
+ /**
1179
+ * Get order statistics by status
1180
+ */
1181
+ async getOrderStats() {
1182
+ const orders = await this.db.query("orders", {});
1183
+ const stats = {
1184
+ pending: 0,
1185
+ processing: 0,
1186
+ shipped: 0,
1187
+ delivered: 0,
1188
+ cancelled: 0,
1189
+ averageOrderValue: 0
1190
+ };
1191
+ let totalValue = 0;
1192
+ for (const order of orders) {
1193
+ stats[order.status]++;
1194
+ totalValue += order.total;
1195
+ }
1196
+ stats.averageOrderValue = orders.length > 0 ? totalValue / orders.length : 0;
1197
+ return stats;
1198
+ }
1199
+ // ===========================
1200
+ // HELPER METHODS
1201
+ // ===========================
1202
+ generateSKU() {
1203
+ const timestamp = Date.now().toString(36).toUpperCase();
1204
+ const random = Math.random().toString(36).substring(2, 6).toUpperCase();
1205
+ return `${timestamp}-${random}`;
1206
+ }
1207
+ getPeriodStart(date, period) {
1208
+ const d = new Date(date);
1209
+ switch (period) {
1210
+ case "day":
1211
+ d.setHours(0, 0, 0, 0);
1212
+ break;
1213
+ case "week":
1214
+ d.setDate(d.getDate() - d.getDay());
1215
+ d.setHours(0, 0, 0, 0);
1216
+ break;
1217
+ case "month":
1218
+ d.setDate(1);
1219
+ d.setHours(0, 0, 0, 0);
1220
+ break;
1221
+ case "year":
1222
+ d.setMonth(0, 1);
1223
+ d.setHours(0, 0, 0, 0);
1224
+ break;
1225
+ }
1226
+ return d;
1227
+ }
1228
+ };
712
1229
  // Annotate the CommonJS export names for ESM import in node:
713
1230
  0 && (module.exports = {
714
1231
  VlibeBaseAuth,
715
1232
  VlibeBaseDatabase,
1233
+ VlibeBaseEcommerce,
716
1234
  VlibeBasePayments
717
1235
  });