@axova/shared 1.0.2 → 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.
Files changed (68) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +2 -0
  3. package/dist/lib/db.d.ts +34406 -1
  4. package/dist/lib/db.js +21 -1
  5. package/dist/middleware/storeOwnership.js +22 -3
  6. package/dist/middleware/storeValidationMiddleware.js +16 -39
  7. package/dist/schemas/admin/admin-schema.d.ts +2 -2
  8. package/dist/schemas/ai-moderation/ai-moderation-schema.d.ts +6 -6
  9. package/dist/schemas/common/common-schemas.d.ts +71 -71
  10. package/dist/schemas/compliance/compliance-schema.d.ts +20 -20
  11. package/dist/schemas/compliance/kyc-schema.d.ts +8 -8
  12. package/dist/schemas/customer/customer-schema.d.ts +18 -18
  13. package/dist/schemas/index.d.ts +28 -0
  14. package/dist/schemas/index.js +134 -3
  15. package/dist/schemas/inventory/inventory-tables.d.ts +188 -188
  16. package/dist/schemas/inventory/lot-tables.d.ts +102 -102
  17. package/dist/schemas/order/cart-schema.d.ts +2865 -0
  18. package/dist/schemas/order/cart-schema.js +396 -0
  19. package/dist/schemas/order/order-schema.d.ts +19 -19
  20. package/dist/schemas/order/order-schema.js +8 -2
  21. package/dist/schemas/product/discount-schema.d.ts +3 -3
  22. package/dist/schemas/product/product-schema.d.ts +3 -3
  23. package/dist/schemas/store/store-audit-schema.d.ts +20 -20
  24. package/dist/schemas/store/store-schema.d.ts +182 -2
  25. package/dist/schemas/store/store-schema.js +19 -0
  26. package/dist/schemas/store/storefront-config-schema.d.ts +434 -823
  27. package/dist/schemas/store/storefront-config-schema.js +35 -62
  28. package/dist/utils/subdomain.d.ts +1 -1
  29. package/dist/utils/subdomain.js +10 -15
  30. package/package.json +1 -1
  31. package/src/configs/index.ts +654 -654
  32. package/src/index.ts +26 -23
  33. package/src/interfaces/customer-events.ts +106 -106
  34. package/src/interfaces/inventory-events.ts +545 -545
  35. package/src/interfaces/inventory-types.ts +1004 -1004
  36. package/src/interfaces/order-events.ts +381 -381
  37. package/src/lib/auditLogger.ts +1117 -1117
  38. package/src/lib/authOrganization.ts +153 -153
  39. package/src/lib/db.ts +84 -64
  40. package/src/middleware/serviceAuth.ts +328 -328
  41. package/src/middleware/storeOwnership.ts +199 -181
  42. package/src/middleware/storeValidationMiddleware.ts +17 -50
  43. package/src/middleware/userAuth.ts +248 -248
  44. package/src/schemas/admin/admin-schema.ts +208 -208
  45. package/src/schemas/ai-moderation/ai-moderation-schema.ts +180 -180
  46. package/src/schemas/common/common-schemas.ts +108 -108
  47. package/src/schemas/compliance/compliance-schema.ts +927 -0
  48. package/src/schemas/compliance/kyc-schema.ts +649 -0
  49. package/src/schemas/customer/customer-schema.ts +576 -0
  50. package/src/schemas/index.ts +202 -3
  51. package/src/schemas/inventory/inventory-tables.ts +1927 -0
  52. package/src/schemas/inventory/lot-tables.ts +799 -0
  53. package/src/schemas/order/cart-schema.ts +652 -0
  54. package/src/schemas/order/order-schema.ts +1406 -0
  55. package/src/schemas/product/discount-relations.ts +44 -0
  56. package/src/schemas/product/discount-schema.ts +464 -0
  57. package/src/schemas/product/product-relations.ts +187 -0
  58. package/src/schemas/product/product-schema.ts +955 -0
  59. package/src/schemas/store/ethiopian_business_api.md.resolved +212 -0
  60. package/src/schemas/store/store-audit-schema.ts +1257 -0
  61. package/src/schemas/store/store-schema.ts +682 -0
  62. package/src/schemas/store/store-settings-schema.ts +231 -0
  63. package/src/schemas/store/storefront-config-schema.ts +382 -0
  64. package/src/schemas/types.ts +67 -67
  65. package/src/types/events.ts +646 -646
  66. package/src/utils/errorHandler.ts +44 -44
  67. package/src/utils/subdomain.ts +19 -23
  68. package/tsconfig.json +21 -21
@@ -0,0 +1,652 @@
1
+ import { createId } from "@paralleldrive/cuid2";
2
+ import { relations } from "drizzle-orm";
3
+ import {
4
+ boolean,
5
+ decimal,
6
+ index,
7
+ integer,
8
+ jsonb,
9
+ pgEnum,
10
+ pgTable,
11
+ text,
12
+ timestamp,
13
+ unique,
14
+ varchar,
15
+ } from "drizzle-orm/pg-core";
16
+
17
+ // =====================================================
18
+ // CART ENUMS
19
+ // =====================================================
20
+
21
+ export const cartStatusEnum = pgEnum("cart_status", [
22
+ "ACTIVE",
23
+ "ABANDONED",
24
+ "CONVERTED",
25
+ "EXPIRED",
26
+ "MERGED",
27
+ "ARCHIVED",
28
+ ]);
29
+
30
+ export const cartTypeEnum = pgEnum("cart_type", [
31
+ "STANDARD",
32
+ "WISHLIST",
33
+ "SAVE_FOR_LATER",
34
+ "QUOTE",
35
+ "SUBSCRIPTION",
36
+ "RECURRING",
37
+ ]);
38
+
39
+ // =====================================================
40
+ // SHOPPING CARTS TABLE
41
+ // =====================================================
42
+
43
+ export const shoppingCarts = pgTable(
44
+ "shopping_carts",
45
+ {
46
+ id: text("id")
47
+ .primaryKey()
48
+ .$defaultFn(() => createId()),
49
+
50
+ // Ownership
51
+ storeId: text("store_id").notNull(),
52
+ customerId: text("customer_id"), // Null for guest carts
53
+ userId: text("user_id"),
54
+ sessionId: text("session_id").notNull(), // Browser/device session
55
+ deviceFingerprint: text("device_fingerprint"),
56
+
57
+ // Cart Classification
58
+ cartType: cartTypeEnum("cart_type").notNull().default("STANDARD"),
59
+ status: cartStatusEnum("status").notNull().default("ACTIVE"),
60
+ isGuestCart: boolean("is_guest_cart").notNull().default(false),
61
+ isCheckoutStarted: boolean("is_checkout_started").notNull().default(false),
62
+
63
+ // Contact Information (for guest carts)
64
+ guestEmail: varchar("guest_email", { length: 255 }),
65
+ guestPhone: varchar("guest_phone", { length: 20 }),
66
+ guestName: varchar("guest_name", { length: 100 }),
67
+
68
+ // Financial Information
69
+ currency: varchar("currency", { length: 3 }).notNull().default("USD"),
70
+
71
+ // Calculated Amounts
72
+ subtotalAmount: decimal("subtotal_amount", { precision: 12, scale: 2 })
73
+ .notNull()
74
+ .default("0.00"),
75
+ taxAmount: decimal("tax_amount", { precision: 12, scale: 2 })
76
+ .notNull()
77
+ .default("0.00"),
78
+ shippingAmount: decimal("shipping_amount", { precision: 12, scale: 2 })
79
+ .notNull()
80
+ .default("0.00"),
81
+ discountAmount: decimal("discount_amount", { precision: 12, scale: 2 })
82
+ .notNull()
83
+ .default("0.00"),
84
+ totalAmount: decimal("total_amount", { precision: 12, scale: 2 })
85
+ .notNull()
86
+ .default("0.00"),
87
+
88
+ // Item Summary
89
+ itemCount: integer("item_count").notNull().default(0),
90
+ totalQuantity: integer("total_quantity").notNull().default(0),
91
+
92
+ // Discount and Promotion
93
+ appliedCoupons: jsonb("applied_coupons").$type<
94
+ Array<{
95
+ code: string;
96
+ discountAmount: string;
97
+ discountType: string;
98
+ }>
99
+ >().default([]),
100
+ availableDiscounts: jsonb("available_discounts").$type<
101
+ Array<{
102
+ id: string;
103
+ title: string;
104
+ description?: string;
105
+ discountAmount: string;
106
+ }>
107
+ >().default([]),
108
+
109
+ // Shipping Information
110
+ selectedShippingMethod: jsonb("selected_shipping_method").$type<{
111
+ id?: string;
112
+ name?: string;
113
+ cost?: string;
114
+ estimatedDays?: number;
115
+ }>(),
116
+ shippingAddressId: text("shipping_address_id"),
117
+ billingAddressId: text("billing_address_id"),
118
+
119
+ // Checkout Progress
120
+ checkoutStep: varchar("checkout_step", { length: 50 }).$type<
121
+ | "CART"
122
+ | "SHIPPING"
123
+ | "PAYMENT"
124
+ | "REVIEW"
125
+ | "CONFIRMATION"
126
+ >().default("CART"),
127
+ checkoutStartedAt: timestamp("checkout_started_at", { withTimezone: true }),
128
+ checkoutCompletedAt: timestamp("checkout_completed_at", {
129
+ withTimezone: true,
130
+ }),
131
+
132
+ // Conversion Tracking
133
+ convertedOrderId: text("converted_order_id"),
134
+ conversionRate: decimal("conversion_rate", { precision: 5, scale: 2 }),
135
+
136
+ // Abandonment Tracking
137
+ abandonedAt: timestamp("abandoned_at", { withTimezone: true }),
138
+ abandonmentReason: varchar("abandonment_reason", { length: 100 }).$type<
139
+ | "PRICE_TOO_HIGH"
140
+ | "UNEXPECTED_COSTS"
141
+ | "COMPLEX_CHECKOUT"
142
+ | "PAYMENT_ISSUES"
143
+ | "BROWSING_ONLY"
144
+ | "FOUND_BETTER_DEAL"
145
+ | "SECURITY_CONCERNS"
146
+ | "TECHNICAL_ISSUES"
147
+ | "UNKNOWN"
148
+ >(),
149
+ recoveryEmailSent: boolean("recovery_email_sent")
150
+ .notNull()
151
+ .default(false),
152
+ recoveryEmailSentAt: timestamp("recovery_email_sent_at", {
153
+ withTimezone: true,
154
+ }),
155
+ recoveryEmailCount: integer("recovery_email_count").notNull().default(0),
156
+ isRecovered: boolean("is_recovered").notNull().default(false),
157
+ recoveredAt: timestamp("recovered_at", { withTimezone: true }),
158
+
159
+ // Expiration and Cleanup
160
+ expiresAt: timestamp("expires_at", { withTimezone: true }),
161
+ autoExpireDays: integer("auto_expire_days").notNull().default(30),
162
+
163
+ // Marketing Attribution
164
+ referrerUrl: text("referrer_url"),
165
+ utmSource: varchar("utm_source", { length: 100 }),
166
+ utmMedium: varchar("utm_medium", { length: 100 }),
167
+ utmCampaign: varchar("utm_campaign", { length: 100 }),
168
+ affiliateId: text("affiliate_id"),
169
+
170
+ // Technical Information
171
+ userAgent: text("user_agent"),
172
+ ipAddress: varchar("ip_address", { length: 45 }),
173
+ browserInfo: jsonb("browser_info").$type<{
174
+ name?: string;
175
+ version?: string;
176
+ platform?: string;
177
+ language?: string;
178
+ }>(),
179
+ deviceInfo: jsonb("device_info").$type<{
180
+ type?: string;
181
+ brand?: string;
182
+ model?: string;
183
+ os?: string;
184
+ }>(),
185
+
186
+ // Cart Rules and Validation
187
+ validationErrors: jsonb("validation_errors").$type<
188
+ Array<{
189
+ field: string;
190
+ message: string;
191
+ code: string;
192
+ }>
193
+ >().default([]),
194
+ minimumOrderMet: boolean("minimum_order_met").notNull().default(true),
195
+ stockAvailable: boolean("stock_available").notNull().default(true),
196
+
197
+ // Special Features
198
+ isGiftCart: boolean("is_gift_cart").notNull().default(false),
199
+ giftMessage: text("gift_message"),
200
+ notes: text("notes"),
201
+ customerNotes: text("customer_notes"),
202
+ internalNotes: text("internal_notes"),
203
+
204
+ // Merge Tracking
205
+ mergedFromCartId: text("merged_from_cart_id"),
206
+ mergedToCartId: text("merged_to_cart_id"),
207
+ mergedAt: timestamp("merged_at", { withTimezone: true }),
208
+
209
+ // Analytics and Insights
210
+ totalViewTime: integer("total_view_time").notNull().default(0), // seconds
211
+ modificationsCount: integer("modifications_count").notNull().default(0),
212
+ lastModifiedBy: varchar("last_modified_by", { length: 50 }).$type<
213
+ "CUSTOMER" | "ADMIN" | "SYSTEM" | "AUTOMATION"
214
+ >(),
215
+
216
+ // Custom Fields
217
+ customFields: jsonb("custom_fields")
218
+ .$type<Record<string, unknown>>()
219
+ .default({}),
220
+ metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
221
+ tags: jsonb("tags").$type<string[]>().default([]),
222
+
223
+ // Timestamps
224
+ createdAt: timestamp("created_at", { withTimezone: true })
225
+ .defaultNow()
226
+ .notNull(),
227
+ updatedAt: timestamp("updated_at", { withTimezone: true })
228
+ .defaultNow()
229
+ .notNull(),
230
+ lastActivityAt: timestamp("last_activity_at", { withTimezone: true })
231
+ .defaultNow()
232
+ .notNull(),
233
+ deletedAt: timestamp("deleted_at", { withTimezone: true }),
234
+ },
235
+ (table) => ({
236
+ // Primary Indexes
237
+ sessionIndex: index("idx_carts_session").on(table.sessionId),
238
+ customerIndex: index("idx_carts_customer").on(table.customerId),
239
+ userIndex: index("idx_carts_user").on(table.userId),
240
+ guestEmailIndex: index("idx_carts_guest_email").on(table.guestEmail),
241
+
242
+ // Status and Type Indexes
243
+ statusIndex: index("idx_carts_status").on(table.status),
244
+ cartTypeIndex: index("idx_carts_type").on(table.cartType),
245
+ guestCartIndex: index("idx_carts_guest").on(table.isGuestCart),
246
+
247
+ // Store Indexes
248
+ storeIndex: index("idx_carts_store").on(table.storeId),
249
+
250
+ // Conversion Tracking
251
+ convertedOrderIndex: index("idx_carts_converted_order").on(
252
+ table.convertedOrderId,
253
+ ),
254
+ checkoutStartedIndex: index("idx_carts_checkout_started").on(
255
+ table.isCheckoutStarted,
256
+ ),
257
+
258
+ // Abandonment Tracking
259
+ abandonedIndex: index("idx_carts_abandoned").on(table.abandonedAt),
260
+ recoveryIndex: index("idx_carts_recovery").on(table.recoveryEmailSent),
261
+
262
+ // Expiration
263
+ expiresAtIndex: index("idx_carts_expires_at").on(table.expiresAt),
264
+
265
+ // Activity Tracking
266
+ lastActivityIndex: index("idx_carts_last_activity").on(
267
+ table.lastActivityAt,
268
+ ),
269
+ createdAtIndex: index("idx_carts_created_at").on(table.createdAt),
270
+
271
+ // Composite Indexes
272
+ storeStatusIndex: index("idx_carts_store_status").on(
273
+ table.storeId,
274
+ table.status,
275
+ ),
276
+ storeCustomerIndex: index("idx_carts_store_customer").on(
277
+ table.storeId,
278
+ table.customerId,
279
+ ),
280
+ sessionStoreIndex: index("idx_carts_session_store").on(
281
+ table.sessionId,
282
+ table.storeId,
283
+ ),
284
+ customerStatusIndex: index("idx_carts_customer_status").on(
285
+ table.customerId,
286
+ table.status,
287
+ ),
288
+
289
+ // Unique Constraints
290
+ activeCartPerSession: unique("unq_active_cart_session").on(
291
+ table.sessionId,
292
+ table.storeId,
293
+ table.status,
294
+ ),
295
+ }),
296
+ );
297
+
298
+ // =====================================================
299
+ // CART ITEMS TABLE
300
+ // =====================================================
301
+
302
+ export const cartItems = pgTable(
303
+ "cart_items",
304
+ {
305
+ id: text("id")
306
+ .primaryKey()
307
+ .$defaultFn(() => createId()),
308
+
309
+ cartId: text("cart_id")
310
+ .notNull()
311
+ .references(() => shoppingCarts.id, { onDelete: "cascade" }),
312
+
313
+ // Product Information
314
+ productId: text("product_id").notNull(),
315
+ variantId: text("variant_id"),
316
+ sku: varchar("sku", { length: 100 }).notNull(),
317
+ productHandle: varchar("product_handle", { length: 255 }),
318
+
319
+ // Product Details (snapshot)
320
+ productTitle: varchar("product_title", { length: 255 }).notNull(),
321
+ variantTitle: varchar("variant_title", { length: 255 }),
322
+ productType: varchar("product_type", { length: 50 }),
323
+ vendor: varchar("vendor", { length: 100 }),
324
+ productImage: text("product_image"),
325
+
326
+ // Quantity and Availability
327
+ quantity: integer("quantity").notNull().default(1),
328
+ availableQuantity: integer("available_quantity"),
329
+ isInStock: boolean("is_in_stock").notNull().default(true),
330
+ isBackordered: boolean("is_backordered").notNull().default(false),
331
+ estimatedRestockDate: timestamp("estimated_restock_date", {
332
+ withTimezone: true,
333
+ }),
334
+
335
+ // Pricing
336
+ unitPrice: decimal("unit_price", { precision: 12, scale: 2 }).notNull(),
337
+ compareAtPrice: decimal("compare_at_price", { precision: 12, scale: 2 }),
338
+ salePrice: decimal("sale_price", { precision: 12, scale: 2 }),
339
+ lineTotal: decimal("line_total", { precision: 12, scale: 2 }).notNull(),
340
+
341
+ // Discounts
342
+ discountAmount: decimal("discount_amount", { precision: 12, scale: 2 })
343
+ .notNull()
344
+ .default("0.00"),
345
+ appliedDiscounts: jsonb("applied_discounts").$type<
346
+ Array<{
347
+ id: string;
348
+ title: string;
349
+ discountAmount: string;
350
+ }>
351
+ >().default([]),
352
+
353
+ // Product Options and Customization
354
+ selectedOptions: jsonb("selected_options").$type<
355
+ Record<string, string>
356
+ >().default({}),
357
+ customization: jsonb("customization").$type<{
358
+ personalization?: string;
359
+ engraving?: string;
360
+ specialRequests?: string;
361
+ giftWrap?: boolean;
362
+ giftMessage?: string;
363
+ }>(),
364
+
365
+ // Subscription Information
366
+ isSubscription: boolean("is_subscription").notNull().default(false),
367
+ subscriptionFrequency: varchar("subscription_frequency", { length: 20 }),
368
+ subscriptionDiscount: decimal("subscription_discount", {
369
+ precision: 5,
370
+ scale: 2,
371
+ }),
372
+
373
+ // Item Flags
374
+ isGiftWrap: boolean("is_gift_wrap").notNull().default(false),
375
+ requiresShipping: boolean("requires_shipping").notNull().default(true),
376
+ isDigital: boolean("is_digital").notNull().default(false),
377
+ isFreeShipping: boolean("is_free_shipping").notNull().default(false),
378
+
379
+ // Inventory Location
380
+ inventoryLocationId: text("inventory_location_id"),
381
+ warehouseId: text("warehouse_id"),
382
+
383
+ // Product Snapshot (preserve data)
384
+ productSnapshot: jsonb("product_snapshot").$type<{
385
+ images?: string[];
386
+ description?: string;
387
+ attributes?: Record<string, unknown>;
388
+ categories?: string[];
389
+ weight?: number;
390
+ dimensions?: { length: number; width: number; height: number };
391
+ }>(),
392
+
393
+ // Validation
394
+ hasErrors: boolean("has_errors").notNull().default(false),
395
+ validationErrors: jsonb("validation_errors").$type<
396
+ Array<{
397
+ field: string;
398
+ message: string;
399
+ }>
400
+ >().default([]),
401
+
402
+ // Tracking
403
+ addedBy: varchar("added_by", { length: 50 }).$type<
404
+ "CUSTOMER" | "ADMIN" | "RECOMMENDATION" | "UPSELL" | "CROSS_SELL"
405
+ >().default("CUSTOMER"),
406
+ source: varchar("source", { length: 50 }).$type<
407
+ | "PRODUCT_PAGE"
408
+ | "CATEGORY"
409
+ | "SEARCH"
410
+ | "RECOMMENDATION"
411
+ | "WISHLIST"
412
+ | "QUICK_ADD"
413
+ | "ADMIN"
414
+ >().default("PRODUCT_PAGE"),
415
+
416
+ // Save for Later
417
+ savedForLater: boolean("saved_for_later").notNull().default(false),
418
+ savedForLaterAt: timestamp("saved_for_later_at", { withTimezone: true }),
419
+
420
+ // Notes
421
+ itemNotes: text("item_notes"),
422
+
423
+ // Analytics
424
+ viewCount: integer("view_count").notNull().default(0),
425
+ lastViewedAt: timestamp("last_viewed_at", { withTimezone: true }),
426
+
427
+ // Timestamps
428
+ addedAt: timestamp("added_at", { withTimezone: true })
429
+ .defaultNow()
430
+ .notNull(),
431
+ updatedAt: timestamp("updated_at", { withTimezone: true })
432
+ .defaultNow()
433
+ .notNull(),
434
+ },
435
+ (table) => ({
436
+ // Foreign Key Indexes
437
+ cartIdIndex: index("idx_cart_items_cart_id").on(table.cartId),
438
+ productIdIndex: index("idx_cart_items_product_id").on(table.productId),
439
+ variantIdIndex: index("idx_cart_items_variant_id").on(table.variantId),
440
+ skuIndex: index("idx_cart_items_sku").on(table.sku),
441
+
442
+ // Inventory
443
+ inventoryLocationIndex: index("idx_cart_items_inventory_location").on(
444
+ table.inventoryLocationId,
445
+ ),
446
+ warehouseIndex: index("idx_cart_items_warehouse").on(table.warehouseId),
447
+
448
+ // Stock Status
449
+ inStockIndex: index("idx_cart_items_in_stock").on(table.isInStock),
450
+ backorderedIndex: index("idx_cart_items_backordered").on(
451
+ table.isBackordered,
452
+ ),
453
+
454
+ // Item Flags
455
+ savedForLaterIndex: index("idx_cart_items_saved_for_later").on(
456
+ table.savedForLater,
457
+ ),
458
+ subscriptionIndex: index("idx_cart_items_subscription").on(
459
+ table.isSubscription,
460
+ ),
461
+
462
+ // Composite Indexes
463
+ cartProductIndex: index("idx_cart_items_cart_product").on(
464
+ table.cartId,
465
+ table.productId,
466
+ ),
467
+ cartSkuIndex: index("idx_cart_items_cart_sku").on(table.cartId, table.sku),
468
+
469
+ // Unique Constraint - prevent duplicate items in same cart
470
+ uniqueCartProduct: unique("unq_cart_product_variant").on(
471
+ table.cartId,
472
+ table.productId,
473
+ table.variantId,
474
+ ),
475
+ }),
476
+ );
477
+
478
+ // =====================================================
479
+ // CART ADDRESSES TABLE
480
+ // =====================================================
481
+
482
+ export const cartAddresses = pgTable(
483
+ "cart_addresses",
484
+ {
485
+ id: text("id")
486
+ .primaryKey()
487
+ .$defaultFn(() => createId()),
488
+
489
+ cartId: text("cart_id")
490
+ .notNull()
491
+ .references(() => shoppingCarts.id, { onDelete: "cascade" }),
492
+
493
+ addressType: varchar("address_type", { length: 20 })
494
+ .notNull()
495
+ .$type<"SHIPPING" | "BILLING">(),
496
+
497
+ // Contact Information
498
+ firstName: varchar("first_name", { length: 100 }),
499
+ lastName: varchar("last_name", { length: 100 }),
500
+ company: varchar("company", { length: 100 }),
501
+ email: varchar("email", { length: 255 }),
502
+ phone: varchar("phone", { length: 20 }),
503
+
504
+ // Address Information
505
+ address1: varchar("address1", { length: 255 }).notNull(),
506
+ address2: varchar("address2", { length: 255 }),
507
+ city: varchar("city", { length: 100 }).notNull(),
508
+ province: varchar("province", { length: 100 }),
509
+ provinceCode: varchar("province_code", { length: 10 }),
510
+ country: varchar("country", { length: 100 }).notNull(),
511
+ countryCode: varchar("country_code", { length: 2 }).notNull(),
512
+ postalCode: varchar("postal_code", { length: 20 }),
513
+
514
+ // Validation
515
+ isValidated: boolean("is_validated").notNull().default(false),
516
+ isResidential: boolean("is_residential").notNull().default(true),
517
+
518
+ // Delivery Preferences
519
+ deliveryInstructions: text("delivery_instructions"),
520
+ isDefaultShipping: boolean("is_default_shipping").notNull().default(false),
521
+ isDefaultBilling: boolean("is_default_billing").notNull().default(false),
522
+
523
+ // Timestamps
524
+ createdAt: timestamp("created_at", { withTimezone: true })
525
+ .defaultNow()
526
+ .notNull(),
527
+ updatedAt: timestamp("updated_at", { withTimezone: true })
528
+ .defaultNow()
529
+ .notNull(),
530
+ },
531
+ (table) => ({
532
+ cartIdIndex: index("idx_cart_addresses_cart_id").on(table.cartId),
533
+ addressTypeIndex: index("idx_cart_addresses_type").on(table.addressType),
534
+
535
+ // Ensure one address per type per cart
536
+ uniqueCartAddressType: unique("unq_cart_address_type").on(
537
+ table.cartId,
538
+ table.addressType,
539
+ ),
540
+ }),
541
+ );
542
+
543
+ // =====================================================
544
+ // CART ACTIVITY LOG TABLE
545
+ // =====================================================
546
+
547
+ export const cartActivityLog = pgTable(
548
+ "cart_activity_log",
549
+ {
550
+ id: text("id")
551
+ .primaryKey()
552
+ .$defaultFn(() => createId()),
553
+
554
+ cartId: text("cart_id")
555
+ .notNull()
556
+ .references(() => shoppingCarts.id, { onDelete: "cascade" }),
557
+
558
+ // Activity Information
559
+ activityType: varchar("activity_type", { length: 50 })
560
+ .notNull()
561
+ .$type<
562
+ | "CREATED"
563
+ | "ITEM_ADDED"
564
+ | "ITEM_REMOVED"
565
+ | "ITEM_UPDATED"
566
+ | "QUANTITY_CHANGED"
567
+ | "COUPON_APPLIED"
568
+ | "COUPON_REMOVED"
569
+ | "CHECKOUT_STARTED"
570
+ | "CHECKOUT_COMPLETED"
571
+ | "ABANDONED"
572
+ | "RECOVERED"
573
+ | "MERGED"
574
+ | "EXPIRED"
575
+ | "SAVED_FOR_LATER"
576
+ | "MOVED_FROM_SAVED"
577
+ >(),
578
+
579
+ // Details
580
+ activityDetails: jsonb("activity_details")
581
+ .$type<Record<string, unknown>>()
582
+ .default({}),
583
+ itemId: text("item_id"), // Reference to cart item if applicable
584
+ productId: text("product_id"),
585
+ variantId: text("variant_id"),
586
+
587
+ // Change Tracking
588
+ previousValue: jsonb("previous_value").$type<unknown>(),
589
+ newValue: jsonb("new_value").$type<unknown>(),
590
+
591
+ // Actor Information
592
+ actorType: varchar("actor_type", { length: 20 })
593
+ .notNull()
594
+ .$type<"CUSTOMER" | "ADMIN" | "SYSTEM" | "AUTOMATION">(),
595
+ actorId: text("actor_id"),
596
+
597
+ // Context
598
+ sessionId: text("session_id"),
599
+ ipAddress: varchar("ip_address", { length: 45 }),
600
+ userAgent: text("user_agent"),
601
+
602
+ // Timestamps
603
+ occurredAt: timestamp("occurred_at", { withTimezone: true })
604
+ .defaultNow()
605
+ .notNull(),
606
+ },
607
+ (table) => ({
608
+ cartIdIndex: index("idx_cart_activity_cart_id").on(table.cartId),
609
+ activityTypeIndex: index("idx_cart_activity_type").on(table.activityType),
610
+ productIdIndex: index("idx_cart_activity_product").on(table.productId),
611
+ occurredAtIndex: index("idx_cart_activity_occurred_at").on(
612
+ table.occurredAt,
613
+ ),
614
+
615
+ // Composite Indexes
616
+ cartActivityIndex: index("idx_cart_activity_cart_type").on(
617
+ table.cartId,
618
+ table.activityType,
619
+ ),
620
+ }),
621
+ );
622
+
623
+ // =====================================================
624
+ // RELATIONS
625
+ // =====================================================
626
+
627
+ export const shoppingCartsRelations = relations(shoppingCarts, ({ many }) => ({
628
+ items: many(cartItems),
629
+ addresses: many(cartAddresses),
630
+ activityLog: many(cartActivityLog),
631
+ }));
632
+
633
+ export const cartItemsRelations = relations(cartItems, ({ one }) => ({
634
+ cart: one(shoppingCarts, {
635
+ fields: [cartItems.cartId],
636
+ references: [shoppingCarts.id],
637
+ }),
638
+ }));
639
+
640
+ export const cartAddressesRelations = relations(cartAddresses, ({ one }) => ({
641
+ cart: one(shoppingCarts, {
642
+ fields: [cartAddresses.cartId],
643
+ references: [shoppingCarts.id],
644
+ }),
645
+ }));
646
+
647
+ export const cartActivityLogRelations = relations(cartActivityLog, ({ one }) => ({
648
+ cart: one(shoppingCarts, {
649
+ fields: [cartActivityLog.cartId],
650
+ references: [shoppingCarts.id],
651
+ }),
652
+ }));