@instockng/api-client 1.0.3 → 1.0.4

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 (170) hide show
  1. package/dist/apps/backend/src/generated/zod/index.d.ts +1114 -0
  2. package/dist/apps/backend/src/generated/zod/index.js +670 -0
  3. package/dist/apps/backend/src/http-app.d.ts +40 -0
  4. package/dist/apps/backend/src/http-app.js +106 -0
  5. package/dist/apps/backend/src/lib/brand-response.d.ts +14 -0
  6. package/dist/apps/backend/src/lib/brand-response.js +8 -0
  7. package/dist/apps/backend/src/lib/cart-helpers.d.ts +280 -0
  8. package/dist/apps/backend/src/lib/cart-helpers.js +93 -0
  9. package/dist/apps/backend/src/lib/cart-recovery.d.ts +30 -0
  10. package/dist/apps/backend/src/lib/cart-recovery.js +147 -0
  11. package/dist/apps/backend/src/lib/cart-response.d.ts +121 -0
  12. package/dist/apps/backend/src/lib/cart-response.js +150 -0
  13. package/dist/apps/backend/src/lib/clerk.d.ts +18 -0
  14. package/dist/apps/backend/src/lib/clerk.js +167 -0
  15. package/dist/apps/backend/src/lib/delivery-zone-response.d.ts +62 -0
  16. package/dist/apps/backend/src/lib/delivery-zone-response.js +24 -0
  17. package/dist/apps/backend/src/lib/discount-code-response.d.ts +42 -0
  18. package/dist/apps/backend/src/lib/discount-code-response.js +19 -0
  19. package/dist/apps/backend/src/lib/discount.d.ts +20 -0
  20. package/dist/apps/backend/src/lib/discount.js +35 -0
  21. package/dist/apps/backend/src/lib/inventory.d.ts +26 -0
  22. package/dist/apps/backend/src/lib/inventory.js +160 -0
  23. package/dist/apps/backend/src/lib/meta-capi.d.ts +48 -0
  24. package/dist/apps/backend/src/lib/meta-capi.js +120 -0
  25. package/dist/apps/backend/src/lib/openapi.d.ts +36 -0
  26. package/dist/apps/backend/src/lib/openapi.js +69 -0
  27. package/dist/apps/backend/src/lib/order-recovery.d.ts +367 -0
  28. package/dist/apps/backend/src/lib/order-recovery.js +373 -0
  29. package/dist/apps/backend/src/lib/order-response.d.ts +136 -0
  30. package/dist/apps/backend/src/lib/order-response.js +61 -0
  31. package/dist/apps/backend/src/lib/pricing.d.ts +39 -0
  32. package/dist/apps/backend/src/lib/pricing.js +62 -0
  33. package/dist/apps/backend/src/lib/prisma.d.ts +9 -0
  34. package/dist/apps/backend/src/lib/prisma.js +30 -0
  35. package/dist/apps/backend/src/lib/product-response.d.ts +82 -0
  36. package/dist/apps/backend/src/lib/product-response.js +29 -0
  37. package/dist/apps/backend/src/lib/utils.d.ts +32 -0
  38. package/dist/apps/backend/src/lib/utils.js +63 -0
  39. package/dist/apps/backend/src/middleware/clerk-auth.d.ts +8 -0
  40. package/dist/apps/backend/src/middleware/clerk-auth.js +89 -0
  41. package/dist/apps/backend/src/middleware/cors.d.ts +8 -0
  42. package/dist/apps/backend/src/middleware/cors.js +11 -0
  43. package/dist/apps/backend/src/notifications/producers/meta-capi-producer.d.ts +55 -0
  44. package/dist/apps/backend/src/notifications/producers/meta-capi-producer.js +125 -0
  45. package/dist/apps/backend/src/notifications/producers/order-notification.d.ts +9 -0
  46. package/dist/apps/backend/src/notifications/producers/order-notification.js +18 -0
  47. package/dist/apps/backend/src/notifications/producers/prospect-recovery-notification.d.ts +10 -0
  48. package/dist/apps/backend/src/notifications/producers/prospect-recovery-notification.js +11 -0
  49. package/dist/apps/backend/src/routes/admin/abandoned-carts.d.ts +605 -0
  50. package/dist/apps/backend/src/routes/admin/abandoned-carts.js +194 -0
  51. package/dist/apps/backend/src/routes/admin/brands.d.ts +175 -0
  52. package/dist/apps/backend/src/routes/admin/brands.js +118 -0
  53. package/dist/apps/backend/src/routes/admin/customers.d.ts +306 -0
  54. package/dist/apps/backend/src/routes/admin/customers.js +39 -0
  55. package/dist/apps/backend/src/routes/admin/delivery-zones.d.ts +438 -0
  56. package/dist/apps/backend/src/routes/admin/delivery-zones.js +300 -0
  57. package/dist/apps/backend/src/routes/admin/discount-codes.d.ts +478 -0
  58. package/dist/apps/backend/src/routes/admin/discount-codes.js +418 -0
  59. package/dist/apps/backend/src/routes/admin/inventory.d.ts +273 -0
  60. package/dist/apps/backend/src/routes/admin/inventory.js +189 -0
  61. package/dist/apps/backend/src/routes/admin/orders.d.ts +1478 -0
  62. package/dist/apps/backend/src/routes/admin/orders.js +503 -0
  63. package/dist/apps/backend/src/routes/admin/products.d.ts +860 -0
  64. package/dist/apps/backend/src/routes/admin/products.js +107 -0
  65. package/dist/apps/backend/src/routes/admin/stats.d.ts +288 -0
  66. package/dist/apps/backend/src/routes/admin/stats.js +55 -0
  67. package/dist/apps/backend/src/routes/admin/variants.d.ts +239 -0
  68. package/dist/apps/backend/src/routes/admin/variants.js +173 -0
  69. package/dist/apps/backend/src/routes/admin/warehouses.d.ts +373 -0
  70. package/dist/apps/backend/src/routes/admin/warehouses.js +123 -0
  71. package/dist/apps/backend/src/routes/public/brands.d.ts +40 -0
  72. package/dist/apps/backend/src/routes/public/brands.js +38 -0
  73. package/dist/apps/backend/src/routes/public/carts.d.ts +2655 -0
  74. package/dist/apps/backend/src/routes/public/carts.js +631 -0
  75. package/dist/apps/backend/src/routes/public/delivery-zones.d.ts +35 -0
  76. package/dist/apps/backend/src/routes/public/delivery-zones.js +62 -0
  77. package/dist/apps/backend/src/routes/public/orders.d.ts +323 -0
  78. package/dist/apps/backend/src/routes/public/orders.js +160 -0
  79. package/dist/apps/backend/src/routes/public/products.d.ts +449 -0
  80. package/dist/apps/backend/src/routes/public/products.js +133 -0
  81. package/dist/apps/backend/src/types/index.d.ts +42 -0
  82. package/dist/apps/backend/src/types/index.js +2 -0
  83. package/dist/apps/backend/src/validators/brand.d.ts +17 -0
  84. package/dist/apps/backend/src/validators/brand.js +15 -0
  85. package/dist/apps/backend/src/validators/delivery-zone.d.ts +31 -0
  86. package/dist/apps/backend/src/validators/delivery-zone.js +51 -0
  87. package/dist/apps/backend/src/validators/discount-code.d.ts +74 -0
  88. package/dist/apps/backend/src/validators/discount-code.js +50 -0
  89. package/dist/apps/backend/src/validators/inventory.d.ts +20 -0
  90. package/dist/apps/backend/src/validators/inventory.js +15 -0
  91. package/dist/apps/backend/src/validators/order.d.ts +87 -0
  92. package/dist/apps/backend/src/validators/order.js +61 -0
  93. package/dist/apps/backend/src/validators/product.d.ts +18 -0
  94. package/dist/apps/backend/src/validators/product.js +19 -0
  95. package/dist/apps/backend/src/validators/variant.d.ts +19 -0
  96. package/dist/apps/backend/src/validators/variant.js +19 -0
  97. package/dist/apps/backend/src/validators/warehouse.d.ts +15 -0
  98. package/dist/apps/backend/src/validators/warehouse.js +15 -0
  99. package/dist/fetchers/carts.d.ts +746 -754
  100. package/dist/hooks/public/carts.d.ts +694 -702
  101. package/dist/packages/api-client/src/backend-types.d.ts +10 -0
  102. package/dist/packages/api-client/src/backend-types.js +10 -0
  103. package/dist/packages/api-client/src/client.d.ts +20 -0
  104. package/dist/packages/api-client/src/client.js +40 -0
  105. package/dist/packages/api-client/src/fetchers/brands.d.ts +25 -0
  106. package/dist/packages/api-client/src/fetchers/brands.js +26 -0
  107. package/dist/packages/api-client/src/fetchers/carts.d.ts +2335 -0
  108. package/dist/packages/api-client/src/fetchers/carts.js +169 -0
  109. package/dist/packages/api-client/src/fetchers/delivery-zones.d.ts +28 -0
  110. package/dist/packages/api-client/src/fetchers/delivery-zones.js +26 -0
  111. package/dist/packages/api-client/src/fetchers/index.d.ts +22 -0
  112. package/dist/packages/api-client/src/fetchers/index.js +22 -0
  113. package/dist/packages/api-client/src/fetchers/orders.d.ts +283 -0
  114. package/dist/packages/api-client/src/fetchers/orders.js +44 -0
  115. package/dist/packages/api-client/src/fetchers/products.d.ts +386 -0
  116. package/dist/packages/api-client/src/fetchers/products.js +42 -0
  117. package/dist/packages/api-client/src/hooks/admin/abandoned-carts.d.ts +535 -0
  118. package/dist/packages/api-client/src/hooks/admin/abandoned-carts.js +79 -0
  119. package/dist/packages/api-client/src/hooks/admin/brands.d.ts +79 -0
  120. package/dist/packages/api-client/src/hooks/admin/brands.js +103 -0
  121. package/dist/packages/api-client/src/hooks/admin/customers.d.ts +278 -0
  122. package/dist/packages/api-client/src/hooks/admin/customers.js +25 -0
  123. package/dist/packages/api-client/src/hooks/admin/delivery-zones.d.ts +270 -0
  124. package/dist/packages/api-client/src/hooks/admin/delivery-zones.js +168 -0
  125. package/dist/packages/api-client/src/hooks/admin/discount-codes.d.ts +299 -0
  126. package/dist/packages/api-client/src/hooks/admin/discount-codes.js +157 -0
  127. package/dist/packages/api-client/src/hooks/admin/index.d.ts +16 -0
  128. package/dist/packages/api-client/src/hooks/admin/index.js +16 -0
  129. package/dist/packages/api-client/src/hooks/admin/inventory.d.ts +224 -0
  130. package/dist/packages/api-client/src/hooks/admin/inventory.js +102 -0
  131. package/dist/packages/api-client/src/hooks/admin/orders.d.ts +1380 -0
  132. package/dist/packages/api-client/src/hooks/admin/orders.js +169 -0
  133. package/dist/packages/api-client/src/hooks/admin/products.d.ts +374 -0
  134. package/dist/packages/api-client/src/hooks/admin/products.js +84 -0
  135. package/dist/packages/api-client/src/hooks/admin/stats.d.ts +277 -0
  136. package/dist/packages/api-client/src/hooks/admin/stats.js +24 -0
  137. package/dist/packages/api-client/src/hooks/admin/variants.d.ts +115 -0
  138. package/dist/packages/api-client/src/hooks/admin/variants.js +121 -0
  139. package/dist/packages/api-client/src/hooks/admin/warehouses.d.ts +277 -0
  140. package/dist/packages/api-client/src/hooks/admin/warehouses.js +103 -0
  141. package/dist/packages/api-client/src/hooks/public/brands.d.ts +33 -0
  142. package/dist/packages/api-client/src/hooks/public/brands.js +30 -0
  143. package/dist/packages/api-client/src/hooks/public/carts.d.ts +2405 -0
  144. package/dist/packages/api-client/src/hooks/public/carts.js +213 -0
  145. package/dist/packages/api-client/src/hooks/public/delivery-zones.d.ts +34 -0
  146. package/dist/packages/api-client/src/hooks/public/delivery-zones.js +28 -0
  147. package/dist/packages/api-client/src/hooks/public/index.d.ts +10 -0
  148. package/dist/packages/api-client/src/hooks/public/index.js +10 -0
  149. package/dist/packages/api-client/src/hooks/public/orders.d.ts +302 -0
  150. package/dist/packages/api-client/src/hooks/public/orders.js +50 -0
  151. package/dist/packages/api-client/src/hooks/public/products.d.ts +398 -0
  152. package/dist/packages/api-client/src/hooks/public/products.js +47 -0
  153. package/dist/packages/api-client/src/hooks/use-query-unwrapped.d.ts +20 -0
  154. package/dist/packages/api-client/src/hooks/use-query-unwrapped.js +22 -0
  155. package/dist/packages/api-client/src/hooks/useApiConfig.d.ts +11 -0
  156. package/dist/packages/api-client/src/hooks/useApiConfig.js +14 -0
  157. package/dist/packages/api-client/src/index.d.ts +20 -0
  158. package/dist/packages/api-client/src/index.js +25 -0
  159. package/dist/packages/api-client/src/provider.d.ts +33 -0
  160. package/dist/packages/api-client/src/provider.js +52 -0
  161. package/dist/packages/api-client/src/rpc-client.d.ts +9035 -0
  162. package/dist/packages/api-client/src/rpc-client.js +78 -0
  163. package/dist/packages/api-client/src/rpc-types.d.ts +76 -0
  164. package/dist/packages/api-client/src/rpc-types.js +7 -0
  165. package/dist/packages/api-client/src/types.d.ts +33 -0
  166. package/dist/packages/api-client/src/types.js +16 -0
  167. package/dist/packages/api-client/src/utils/query-keys.d.ts +106 -0
  168. package/dist/packages/api-client/src/utils/query-keys.js +108 -0
  169. package/dist/rpc-client.d.ts +685 -693
  170. package/package.json +10 -9
@@ -0,0 +1,42 @@
1
+ import type { Prisma } from "@prisma/client";
2
+ export type DiscountCodeDBResponse = Prisma.DiscountCodeGetPayload<{
3
+ include: {
4
+ brand: true;
5
+ };
6
+ }>;
7
+ export declare function formatDiscountCodeResponse(discountCode: DiscountCodeDBResponse): {
8
+ value: number;
9
+ minPurchase: number;
10
+ maxDiscount: number;
11
+ createdAt: string;
12
+ updatedAt: string;
13
+ deletedAt: string;
14
+ brand: {
15
+ createdAt: string;
16
+ updatedAt: string;
17
+ deletedAt: string;
18
+ name: string;
19
+ id: string;
20
+ slug: string;
21
+ logoUrl: string | null;
22
+ siteUrl: string;
23
+ domain: string;
24
+ metaPixelId: string | null;
25
+ };
26
+ isExpired: boolean;
27
+ usagePercentage: number;
28
+ id: string;
29
+ brandId: string | null;
30
+ isActive: boolean;
31
+ code: string;
32
+ type: string;
33
+ usageLimit: number | null;
34
+ usageCount: number;
35
+ perCustomerLimit: number | null;
36
+ validFrom: Date;
37
+ validUntil: Date | null;
38
+ isAutoApply: boolean;
39
+ description: string | null;
40
+ category: string;
41
+ createdBy: string | null;
42
+ };
@@ -0,0 +1,19 @@
1
+ import { formatBrandResponse } from "./brand-response";
2
+ import { toNumber } from "./utils";
3
+ export function formatDiscountCodeResponse(discountCode) {
4
+ const now = new Date();
5
+ return {
6
+ ...discountCode,
7
+ value: toNumber(discountCode.value),
8
+ minPurchase: discountCode.minPurchase ? toNumber(discountCode.minPurchase) : null,
9
+ maxDiscount: discountCode.maxDiscount ? toNumber(discountCode.maxDiscount) : null,
10
+ createdAt: discountCode.createdAt.toISOString(),
11
+ updatedAt: discountCode.updatedAt.toISOString(),
12
+ deletedAt: discountCode.deletedAt ? discountCode.deletedAt.toISOString() : null,
13
+ brand: discountCode.brand ? formatBrandResponse(discountCode.brand) : null,
14
+ isExpired: discountCode.validUntil ? discountCode.validUntil < now : false,
15
+ usagePercentage: discountCode.usageLimit
16
+ ? (discountCode.usageCount / discountCode.usageLimit) * 100
17
+ : null,
18
+ };
19
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Discount code generation and validation utilities
3
+ */
4
+ /**
5
+ * Generate a unique discount code for abandoned cart recovery
6
+ * Format: CART{RANDOM8} (e.g., CART5A9F3B2E)
7
+ */
8
+ export declare function generateDiscountCode(): string;
9
+ /**
10
+ * Calculate discount amount based on cart recovery attempt
11
+ */
12
+ export declare function getRecoveryDiscountPercentage(attemptNumber: number): number;
13
+ /**
14
+ * Calculate discount amount in currency
15
+ */
16
+ export declare function calculateDiscountAmount(subtotal: number, discountPercent: number): number;
17
+ /**
18
+ * Validate discount code format
19
+ */
20
+ export declare function isValidDiscountCodeFormat(code: string): boolean;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Discount code generation and validation utilities
3
+ */
4
+ import { RECOVERY_SCHEDULE } from './cart-recovery';
5
+ /**
6
+ * Generate a unique discount code for abandoned cart recovery
7
+ * Format: CART{RANDOM8} (e.g., CART5A9F3B2E)
8
+ */
9
+ export function generateDiscountCode() {
10
+ const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Excluding similar characters
11
+ let code = 'CART';
12
+ for (let i = 0; i < 8; i++) {
13
+ code += chars.charAt(Math.floor(Math.random() * chars.length));
14
+ }
15
+ return code;
16
+ }
17
+ /**
18
+ * Calculate discount amount based on cart recovery attempt
19
+ */
20
+ export function getRecoveryDiscountPercentage(attemptNumber) {
21
+ const schedule = RECOVERY_SCHEDULE.find((s) => s.attemptNumber === attemptNumber);
22
+ return schedule ? schedule.discountPercent : 0;
23
+ }
24
+ /**
25
+ * Calculate discount amount in currency
26
+ */
27
+ export function calculateDiscountAmount(subtotal, discountPercent) {
28
+ return Math.round(subtotal * (discountPercent / 100));
29
+ }
30
+ /**
31
+ * Validate discount code format
32
+ */
33
+ export function isValidDiscountCodeFormat(code) {
34
+ return /^CART[A-Z2-9]{8}$/.test(code);
35
+ }
@@ -0,0 +1,26 @@
1
+ import { PrismaClient, InventoryTransactionType } from '@prisma/client';
2
+ export interface InventoryAdjustment {
3
+ variantId: string;
4
+ warehouseId: string;
5
+ quantity: number;
6
+ type: InventoryTransactionType;
7
+ reason?: string;
8
+ userId?: string;
9
+ metadata?: any;
10
+ orderId?: string;
11
+ }
12
+ export declare function adjustInventory(prisma: PrismaClient, adjustment: InventoryAdjustment): Promise<void>;
13
+ export declare function transferInventory(prisma: PrismaClient, params: {
14
+ variantId: string;
15
+ fromWarehouseId: string;
16
+ toWarehouseId: string;
17
+ quantity: number;
18
+ userId?: string;
19
+ reason?: string;
20
+ }): Promise<void>;
21
+ export declare function getTotalInventoryForVariant(prisma: PrismaClient, variantId: string): Promise<number>;
22
+ export declare function checkInventoryAvailability(prisma: PrismaClient, variantId: string, requiredQuantity: number, warehouseId?: string): Promise<{
23
+ available: boolean;
24
+ totalInventory: number;
25
+ }>;
26
+ export declare function getLowStockVariants(prisma: PrismaClient, brandId?: string): Promise<any[]>;
@@ -0,0 +1,160 @@
1
+ export async function adjustInventory(prisma, adjustment) {
2
+ const { variantId, warehouseId, quantity, type, reason, userId, metadata, orderId } = adjustment;
3
+ // Get current inventory
4
+ let inventory = await prisma.warehouseInventory.findUnique({
5
+ where: {
6
+ variantId_warehouseId: {
7
+ variantId,
8
+ warehouseId,
9
+ },
10
+ },
11
+ });
12
+ // Create inventory record if it doesn't exist
13
+ if (!inventory) {
14
+ inventory = await prisma.warehouseInventory.create({
15
+ data: {
16
+ variantId,
17
+ warehouseId,
18
+ inventoryCount: 0,
19
+ },
20
+ });
21
+ }
22
+ // Calculate new balance
23
+ const balanceAfter = inventory.inventoryCount + quantity;
24
+ // TODO:Prevent negative inventory (optional - can be configured per variant)
25
+ if (balanceAfter < 0) {
26
+ // throw new Error('Insufficient inventory');
27
+ }
28
+ // Update inventory count
29
+ await prisma.warehouseInventory.update({
30
+ where: {
31
+ variantId_warehouseId: {
32
+ variantId,
33
+ warehouseId,
34
+ },
35
+ },
36
+ data: {
37
+ inventoryCount: balanceAfter,
38
+ },
39
+ });
40
+ // Create transaction record
41
+ await prisma.inventoryTransaction.create({
42
+ data: {
43
+ variantId,
44
+ warehouseId,
45
+ type,
46
+ quantity,
47
+ balanceAfter,
48
+ reason,
49
+ userId,
50
+ metadata,
51
+ orderId,
52
+ },
53
+ });
54
+ }
55
+ export async function transferInventory(prisma, params) {
56
+ const { variantId, fromWarehouseId, toWarehouseId, quantity, userId, reason } = params;
57
+ if (quantity <= 0) {
58
+ throw new Error('Transfer quantity must be positive');
59
+ }
60
+ const metadata = {
61
+ transferFrom: fromWarehouseId,
62
+ transferTo: toWarehouseId,
63
+ transferQuantity: quantity,
64
+ };
65
+ // Use transaction to ensure atomicity
66
+ await prisma.$transaction(async (tx) => {
67
+ // Decrement from source warehouse
68
+ await adjustInventory(tx, {
69
+ variantId,
70
+ warehouseId: fromWarehouseId,
71
+ quantity: -quantity,
72
+ type: 'transfer_out',
73
+ reason: reason || `Transfer to ${toWarehouseId}`,
74
+ userId,
75
+ metadata,
76
+ });
77
+ // Increment to destination warehouse
78
+ await adjustInventory(tx, {
79
+ variantId,
80
+ warehouseId: toWarehouseId,
81
+ quantity: quantity,
82
+ type: 'transfer_in',
83
+ reason: reason || `Transfer from ${fromWarehouseId}`,
84
+ userId,
85
+ metadata,
86
+ });
87
+ });
88
+ }
89
+ export async function getTotalInventoryForVariant(prisma, variantId) {
90
+ const inventories = await prisma.warehouseInventory.findMany({
91
+ where: { variantId },
92
+ });
93
+ return inventories.reduce((sum, inv) => sum + inv.inventoryCount, 0);
94
+ }
95
+ export async function checkInventoryAvailability(prisma, variantId, requiredQuantity, warehouseId) {
96
+ let totalInventory = 0;
97
+ if (warehouseId) {
98
+ // Check specific warehouse
99
+ const inventory = await prisma.warehouseInventory.findUnique({
100
+ where: {
101
+ variantId_warehouseId: {
102
+ variantId,
103
+ warehouseId,
104
+ },
105
+ },
106
+ });
107
+ totalInventory = inventory?.inventoryCount || 0;
108
+ }
109
+ else {
110
+ // Check total across all warehouses
111
+ totalInventory = await getTotalInventoryForVariant(prisma, variantId);
112
+ }
113
+ return {
114
+ available: totalInventory >= requiredQuantity,
115
+ totalInventory,
116
+ };
117
+ }
118
+ export async function getLowStockVariants(prisma, brandId) {
119
+ const where = {
120
+ trackInventory: true,
121
+ lowStockThreshold: { not: null },
122
+ deletedAt: null,
123
+ };
124
+ if (brandId) {
125
+ where.product = { brandId };
126
+ }
127
+ const variants = await prisma.productVariant.findMany({
128
+ where,
129
+ include: {
130
+ product: {
131
+ include: { brand: true },
132
+ },
133
+ warehouseInventories: {
134
+ include: { warehouse: true },
135
+ },
136
+ },
137
+ });
138
+ // Filter variants where total inventory is below threshold
139
+ const lowStockVariants = variants.filter((variant) => {
140
+ const totalInventory = variant.warehouseInventories.reduce((sum, inv) => sum + inv.inventoryCount, 0);
141
+ return totalInventory < (variant.lowStockThreshold || 0);
142
+ });
143
+ return lowStockVariants.map((variant) => {
144
+ const totalInventory = variant.warehouseInventories.reduce((sum, inv) => sum + inv.inventoryCount, 0);
145
+ return {
146
+ id: variant.id,
147
+ sku: variant.sku,
148
+ productName: variant.product.name,
149
+ name: variant.name,
150
+ brandName: variant.product.brand.name,
151
+ totalInventory,
152
+ threshold: variant.lowStockThreshold,
153
+ warehouses: variant.warehouseInventories.map((inv) => ({
154
+ warehouseId: inv.warehouseId,
155
+ warehouseName: inv.warehouse.name,
156
+ inventory: inv.inventoryCount,
157
+ })),
158
+ };
159
+ });
160
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Meta Conversions API (CAPI) Service
3
+ *
4
+ * Sends server-side events to Meta for conversion tracking.
5
+ * Used alongside browser pixel for maximum reliability and iOS 14+ compliance.
6
+ */
7
+ export type MetaEventName = 'AddToCart' | 'InitiateCheckout' | 'Purchase' | 'ViewContent';
8
+ export interface MetaCapiQueueMessage {
9
+ pixelId: string;
10
+ eventName: MetaEventName;
11
+ eventId: string;
12
+ eventTime: number;
13
+ eventSourceUrl: string;
14
+ userData: {
15
+ email?: string;
16
+ phone?: string;
17
+ firstName?: string;
18
+ lastName?: string;
19
+ city?: string;
20
+ state?: string;
21
+ country?: string;
22
+ zipCode?: string;
23
+ clientIpAddress?: string;
24
+ clientUserAgent?: string;
25
+ fbc?: string;
26
+ fbp?: string;
27
+ };
28
+ customData: {
29
+ value?: number;
30
+ currency?: string;
31
+ contentIds?: string[];
32
+ contentType?: string;
33
+ numItems?: number;
34
+ contentName?: string;
35
+ };
36
+ }
37
+ /**
38
+ * Send event to Meta Conversions API
39
+ *
40
+ * @param accessToken - Meta Access Token (from Cloudflare secrets)
41
+ * @param message - Queue message with event data
42
+ */
43
+ export declare function sendMetaCapiEvent(accessToken: string, message: MetaCapiQueueMessage): Promise<void>;
44
+ /**
45
+ * Helper to generate event ID for deduplication
46
+ * Uses existing DB IDs to ensure same event from browser and server have same ID
47
+ */
48
+ export declare function generateEventId(type: 'cart' | 'order', id: string, suffix?: string): string;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Meta Conversions API (CAPI) Service
3
+ *
4
+ * Sends server-side events to Meta for conversion tracking.
5
+ * Used alongside browser pixel for maximum reliability and iOS 14+ compliance.
6
+ */
7
+ import crypto from 'crypto';
8
+ /**
9
+ * Hash data with SHA256 for Meta CAPI
10
+ * Meta requires PII to be hashed before sending
11
+ */
12
+ function hashData(data) {
13
+ // Normalize: lowercase and trim whitespace
14
+ const normalized = data.toLowerCase().trim();
15
+ return crypto.createHash('sha256').update(normalized).digest('hex');
16
+ }
17
+ /**
18
+ * Normalize phone number to E.164 format
19
+ * Meta requires: country code + digits only (no spaces, dashes, etc.)
20
+ */
21
+ function normalizePhone(phone) {
22
+ // Remove all non-digit characters
23
+ let digits = phone.replace(/\D/g, '');
24
+ // If starts with 0, assume Nigerian number and add +234
25
+ if (digits.startsWith('0')) {
26
+ digits = '234' + digits.slice(1);
27
+ }
28
+ // If doesn't start with country code, assume Nigerian
29
+ if (!digits.startsWith('234')) {
30
+ digits = '234' + digits;
31
+ }
32
+ return digits;
33
+ }
34
+ /**
35
+ * Send event to Meta Conversions API
36
+ *
37
+ * @param accessToken - Meta Access Token (from Cloudflare secrets)
38
+ * @param message - Queue message with event data
39
+ */
40
+ export async function sendMetaCapiEvent(accessToken, message) {
41
+ const { pixelId, eventName, eventId, eventTime, eventSourceUrl, userData, customData } = message;
42
+ // Build user_data with hashed PII
43
+ const user_data = {
44
+ client_ip_address: userData.clientIpAddress,
45
+ client_user_agent: userData.clientUserAgent,
46
+ fbc: userData.fbc,
47
+ fbp: userData.fbp,
48
+ };
49
+ // Hash email if provided
50
+ if (userData.email) {
51
+ user_data.em = [hashData(userData.email)];
52
+ }
53
+ // Hash phone if provided
54
+ if (userData.phone) {
55
+ const normalized = normalizePhone(userData.phone);
56
+ user_data.ph = [hashData(normalized)];
57
+ }
58
+ // Hash name if provided
59
+ if (userData.firstName) {
60
+ user_data.fn = [hashData(userData.firstName)];
61
+ }
62
+ if (userData.lastName) {
63
+ user_data.ln = [hashData(userData.lastName)];
64
+ }
65
+ // Hash location if provided
66
+ if (userData.city) {
67
+ user_data.ct = [hashData(userData.city)];
68
+ }
69
+ if (userData.state) {
70
+ user_data.st = [hashData(userData.state)];
71
+ }
72
+ if (userData.zipCode) {
73
+ user_data.zp = [hashData(userData.zipCode)];
74
+ }
75
+ if (userData.country) {
76
+ user_data.country = [hashData(userData.country)];
77
+ }
78
+ // Build event
79
+ const event = {
80
+ event_name: eventName,
81
+ event_time: eventTime,
82
+ event_id: eventId,
83
+ event_source_url: eventSourceUrl,
84
+ action_source: 'website',
85
+ user_data,
86
+ custom_data: customData,
87
+ };
88
+ // Send to Meta CAPI
89
+ const url = `https://graph.facebook.com/v21.0/${pixelId}/events`;
90
+ const response = await fetch(url, {
91
+ method: 'POST',
92
+ headers: {
93
+ 'Content-Type': 'application/json',
94
+ },
95
+ body: JSON.stringify({
96
+ data: [event],
97
+ access_token: accessToken,
98
+ }),
99
+ });
100
+ if (!response.ok) {
101
+ const error = await response.text();
102
+ throw new Error(`Meta CAPI error: ${response.status} ${error}`);
103
+ }
104
+ const result = await response.json();
105
+ // Log if Meta returned any errors
106
+ if (result.events_received !== 1) {
107
+ console.error('[Meta CAPI] Event not received:', result);
108
+ throw new Error('Meta CAPI did not receive event');
109
+ }
110
+ }
111
+ /**
112
+ * Helper to generate event ID for deduplication
113
+ * Uses existing DB IDs to ensure same event from browser and server have same ID
114
+ */
115
+ export function generateEventId(type, id, suffix) {
116
+ if (type === 'order') {
117
+ return `order_${id}`;
118
+ }
119
+ return suffix ? `cart_${id}_${suffix}` : `cart_${id}`;
120
+ }
@@ -0,0 +1,36 @@
1
+ import { OpenAPIHono } from '@hono/zod-openapi';
2
+ import { AppContext } from '../types';
3
+ /**
4
+ * Create a new OpenAPI-enabled Hono app instance
5
+ * This extends the standard Hono with OpenAPI documentation capabilities
6
+ */
7
+ export declare function createOpenAPIApp(): OpenAPIHono<AppContext, {}, "/">;
8
+ /**
9
+ * OpenAPI documentation configuration
10
+ */
11
+ export declare const openapiConfig: {
12
+ openapi: string;
13
+ info: {
14
+ title: string;
15
+ version: string;
16
+ description: string;
17
+ };
18
+ servers: {
19
+ url: string;
20
+ description: string;
21
+ }[];
22
+ tags: {
23
+ name: string;
24
+ description: string;
25
+ }[];
26
+ components: {
27
+ securitySchemes: {
28
+ clerkAuth: {
29
+ type: string;
30
+ scheme: string;
31
+ bearerFormat: string;
32
+ description: string;
33
+ };
34
+ };
35
+ };
36
+ };
@@ -0,0 +1,69 @@
1
+ import { OpenAPIHono } from '@hono/zod-openapi';
2
+ /**
3
+ * Create a new OpenAPI-enabled Hono app instance
4
+ * This extends the standard Hono with OpenAPI documentation capabilities
5
+ */
6
+ export function createOpenAPIApp() {
7
+ return new OpenAPIHono({
8
+ defaultHook: (result, c) => {
9
+ if (!result.success) {
10
+ return c.json({
11
+ success: false,
12
+ error: {
13
+ code: 'VALIDATION_ERROR',
14
+ message: 'Request validation failed',
15
+ details: 'error' in result ? result.error.flatten() : undefined,
16
+ },
17
+ }, 400);
18
+ }
19
+ },
20
+ });
21
+ }
22
+ /**
23
+ * OpenAPI documentation configuration
24
+ */
25
+ export const openapiConfig = {
26
+ openapi: '3.1.0',
27
+ info: {
28
+ title: 'OMS API',
29
+ version: '1.0.0',
30
+ description: 'Order Management System API - A comprehensive e-commerce backend',
31
+ },
32
+ servers: [
33
+ {
34
+ url: 'https://api.example.com',
35
+ description: 'Production server',
36
+ },
37
+ {
38
+ url: 'http://localhost:8787',
39
+ description: 'Local development server',
40
+ },
41
+ ],
42
+ tags: [
43
+ { name: 'Public - Orders', description: 'Public order tracking endpoints' },
44
+ { name: 'Public - Products', description: 'Public product catalog endpoints' },
45
+ { name: 'Public - Carts', description: 'Public shopping cart endpoints' },
46
+ { name: 'Public - Delivery Zones', description: 'Public delivery zone endpoints' },
47
+ { name: 'Admin - Orders', description: 'Admin order management endpoints' },
48
+ { name: 'Admin - Products', description: 'Admin product management endpoints' },
49
+ { name: 'Admin - Brands', description: 'Admin brand management endpoints' },
50
+ { name: 'Admin - Variants', description: 'Admin variant management endpoints' },
51
+ { name: 'Admin - Warehouses', description: 'Admin warehouse management endpoints' },
52
+ { name: 'Admin - Inventory', description: 'Admin inventory management endpoints' },
53
+ { name: 'Admin - Customers', description: 'Admin customer management endpoints' },
54
+ { name: 'Admin - Stats', description: 'Admin statistics endpoints' },
55
+ { name: 'Admin - Abandoned Carts', description: 'Admin abandoned cart management endpoints' },
56
+ { name: 'Admin - Discount Codes', description: 'Admin discount code management endpoints' },
57
+ { name: 'Admin - Delivery Zones', description: 'Admin delivery zone management endpoints' },
58
+ ],
59
+ components: {
60
+ securitySchemes: {
61
+ clerkAuth: {
62
+ type: 'http',
63
+ scheme: 'bearer',
64
+ bearerFormat: 'JWT',
65
+ description: 'Clerk JWT token for admin authentication',
66
+ },
67
+ },
68
+ },
69
+ };