@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,231 @@
1
+ import { createId } from "@paralleldrive/cuid2";
2
+ import { relations } from "drizzle-orm";
3
+ import {
4
+ boolean,
5
+ decimal,
6
+ index,
7
+ jsonb,
8
+ pgTable,
9
+ text,
10
+ timestamp,
11
+ unique,
12
+ varchar,
13
+ } from "drizzle-orm/pg-core";
14
+ import { stores } from "./store-schema";
15
+
16
+ export const storeSettings = pgTable(
17
+ "store_settings",
18
+ {
19
+ id: text("id")
20
+ .primaryKey()
21
+ .$defaultFn(() => createId()),
22
+ storeId: text("store_id")
23
+ .notNull()
24
+ .references(() => stores.id, { onDelete: "cascade" }),
25
+
26
+ storeCountry: varchar("store_country", { length: 100 })
27
+ .notNull()
28
+ .default("Ethiopia"),
29
+
30
+ storeCurrency: varchar("store_currency", { length: 3 })
31
+ .notNull()
32
+ .default("ETB"),
33
+
34
+ storeLanguage: varchar("store_language", { length: 10 })
35
+ .notNull()
36
+ .default("en"),
37
+
38
+ supportedLanguages: jsonb("supported_languages")
39
+ .$type<string[]>()
40
+ .notNull()
41
+ .default(["en"]),
42
+
43
+ supportedCurrencies: jsonb("supported_currencies")
44
+ .$type<string[]>()
45
+ .notNull()
46
+ .default(["ETB"]),
47
+
48
+ localizationSettings: jsonb("localization_settings")
49
+ .$type<{
50
+ dateFormat?: string;
51
+ timeFormat?: string;
52
+ numberFormat?: string;
53
+ firstDayOfWeek?: number;
54
+ weightUnit?: "kg" | "lb";
55
+ dimensionUnit?: "cm" | "inch";
56
+ }>()
57
+ .default({
58
+ dateFormat: "DD/MM/YYYY",
59
+ timeFormat: "HH:mm",
60
+ numberFormat: "1,234.56",
61
+ firstDayOfWeek: 0,
62
+ weightUnit: "kg",
63
+ dimensionUnit: "cm",
64
+ }),
65
+
66
+ generalSettings: jsonb("general_settings")
67
+ .$type<{
68
+ autoApplyTax?: boolean;
69
+ pricesIncludeTax?: boolean;
70
+ enableMultiCurrency?: boolean;
71
+ enableMultiLanguage?: boolean;
72
+ currencyDisplayFormat?: "symbol" | "code" | "both";
73
+ }>()
74
+ .default({
75
+ autoApplyTax: true,
76
+ pricesIncludeTax: false,
77
+ enableMultiCurrency: false,
78
+ enableMultiLanguage: false,
79
+ currencyDisplayFormat: "symbol",
80
+ }),
81
+
82
+ metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
83
+
84
+ createdAt: timestamp("created_at", { withTimezone: true })
85
+ .defaultNow()
86
+ .notNull(),
87
+ updatedAt: timestamp("updated_at", { withTimezone: true })
88
+ .defaultNow()
89
+ .notNull(),
90
+ },
91
+ (table) => ({
92
+ storeIdIndex: index("idx_store_settings_store_id").on(table.storeId),
93
+ storeIdUnique: unique("idx_store_settings_store_unique").on(table.storeId),
94
+ countryIndex: index("idx_store_settings_country").on(table.storeCountry),
95
+ }),
96
+ );
97
+
98
+ export const marketRegions = pgTable(
99
+ "market_regions",
100
+ {
101
+ id: text("id")
102
+ .primaryKey()
103
+ .$defaultFn(() => createId()),
104
+ storeId: text("store_id")
105
+ .notNull()
106
+ .references(() => stores.id, { onDelete: "cascade" }),
107
+
108
+ regionName: varchar("region_name", { length: 100 }).notNull(),
109
+ regionCode: varchar("region_code", { length: 10 }).notNull(),
110
+
111
+ country: varchar("country", { length: 100 }).notNull().default("Ethiopia"),
112
+ countryCode: varchar("country_code", { length: 2 })
113
+ .notNull()
114
+ .default("ET"),
115
+
116
+ currency: varchar("currency", { length: 3 }).notNull().default("ETB"),
117
+ language: varchar("language", { length: 10 }).notNull().default("en"),
118
+
119
+ taxEnabled: boolean("tax_enabled").notNull().default(true),
120
+ taxPercentage: decimal("tax_percentage", { precision: 5, scale: 2 })
121
+ .notNull()
122
+ .default("15.00"),
123
+
124
+ taxName: varchar("tax_name", { length: 50 }).default("VAT"),
125
+
126
+ taxConfiguration: jsonb("tax_configuration")
127
+ .$type<{
128
+ taxType?: "VAT" | "GST" | "SALES_TAX" | "CUSTOM";
129
+ taxNumber?: string;
130
+ taxRegistrationDate?: string;
131
+ includeTaxInPrice?: boolean;
132
+ compoundTax?: boolean;
133
+ exemptCategories?: string[];
134
+ reducedRates?: Array<{
135
+ categoryId: string;
136
+ categoryName: string;
137
+ rate: number;
138
+ }>;
139
+ }>()
140
+ .default({
141
+ taxType: "VAT",
142
+ includeTaxInPrice: false,
143
+ compoundTax: false,
144
+ exemptCategories: [],
145
+ reducedRates: [],
146
+ }),
147
+
148
+ isDefault: boolean("is_default").notNull().default(false),
149
+ isActive: boolean("is_active").notNull().default(true),
150
+
151
+ shippingSettings: jsonb("shipping_settings")
152
+ .$type<{
153
+ enableShipping?: boolean;
154
+ freeShippingThreshold?: number;
155
+ defaultShippingRate?: number;
156
+ shippingZones?: Array<{
157
+ name: string;
158
+ rate: number;
159
+ estimatedDays: number;
160
+ }>;
161
+ }>()
162
+ .default({
163
+ enableShipping: true,
164
+ freeShippingThreshold: 0,
165
+ defaultShippingRate: 0,
166
+ shippingZones: [],
167
+ }),
168
+
169
+ paymentSettings: jsonb("payment_settings")
170
+ .$type<{
171
+ enabledPaymentMethods?: string[];
172
+ preferredPaymentMethod?: string;
173
+ paymentProcessingFee?: number;
174
+ }>()
175
+ .default({
176
+ enabledPaymentMethods: ["cash", "card"],
177
+ preferredPaymentMethod: "cash",
178
+ paymentProcessingFee: 0,
179
+ }),
180
+
181
+ metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
182
+
183
+ createdAt: timestamp("created_at", { withTimezone: true })
184
+ .defaultNow()
185
+ .notNull(),
186
+ updatedAt: timestamp("updated_at", { withTimezone: true })
187
+ .defaultNow()
188
+ .notNull(),
189
+ },
190
+ (table) => ({
191
+ storeIdIndex: index("idx_market_regions_store_id").on(table.storeId),
192
+ regionCodeIndex: index("idx_market_regions_code").on(
193
+ table.storeId,
194
+ table.regionCode,
195
+ ),
196
+ defaultIndex: index("idx_market_regions_default").on(
197
+ table.storeId,
198
+ table.isDefault,
199
+ ),
200
+ activeIndex: index("idx_market_regions_active").on(
201
+ table.storeId,
202
+ table.isActive,
203
+ ),
204
+ uniqueRegionCode: unique("idx_market_regions_unique_code").on(
205
+ table.storeId,
206
+ table.regionCode,
207
+ ),
208
+ }),
209
+ );
210
+
211
+ export const storeSettingsRelations = relations(storeSettings, ({ one, many }) => ({
212
+ store: one(stores, {
213
+ fields: [storeSettings.storeId],
214
+ references: [stores.id],
215
+ }),
216
+ }));
217
+
218
+ export const marketRegionsRelations = relations(marketRegions, ({ one }) => ({
219
+ store: one(stores, {
220
+ fields: [marketRegions.storeId],
221
+ references: [stores.id],
222
+ }),
223
+ }));
224
+
225
+ export const storesRelationsExtended = relations(stores, ({ one, many }) => ({
226
+ settings: one(storeSettings, {
227
+ fields: [stores.id],
228
+ references: [storeSettings.storeId],
229
+ }),
230
+ marketRegions: many(marketRegions),
231
+ }));
@@ -0,0 +1,382 @@
1
+ import { createId } from "@paralleldrive/cuid2";
2
+ import { relations } from "drizzle-orm";
3
+ import {
4
+ boolean,
5
+ index,
6
+ jsonb,
7
+ pgTable,
8
+ text,
9
+ timestamp,
10
+ varchar,
11
+ integer,
12
+ } from "drizzle-orm/pg-core";
13
+ import { stores } from "./store-schema";
14
+
15
+ /**
16
+ * Storefront Configuration Schema
17
+ * Stores all visual, layout, and content configurations for multi-tenant storefronts
18
+ */
19
+ export const storefrontConfigs = pgTable(
20
+ "storefront_configs",
21
+ {
22
+ id: text("id")
23
+ .primaryKey()
24
+ .$defaultFn(() => createId()),
25
+ storeId: text("store_id")
26
+ .notNull()
27
+ .references(() => stores.id, { onDelete: "cascade" }),
28
+
29
+ // Configuration type and version
30
+ configType: varchar("config_type", { length: 50 })
31
+ .notNull()
32
+ .default("default")
33
+ .$type<"default" | "custom" | "theme">(),
34
+ version: integer("version").notNull().default(1),
35
+ isActive: boolean("is_active").notNull().default(true),
36
+
37
+ // Branding Configuration
38
+ branding: jsonb("branding")
39
+ .$type<{
40
+ name?: string;
41
+ logo?: {
42
+ type?: "text" | "image" | "both";
43
+ text?: string;
44
+ image?: string;
45
+ imageWidth?: number;
46
+ imageHeight?: number;
47
+ showText?: boolean;
48
+ };
49
+ favicon?: string;
50
+ colors?: {
51
+ primary?: string;
52
+ secondary?: string;
53
+ accent?: string;
54
+ background?: string;
55
+ text?: string;
56
+ muted?: string;
57
+ };
58
+ typography?: {
59
+ headings?: {
60
+ family?: string;
61
+ weight?: string;
62
+ letterSpacing?: string;
63
+ };
64
+ body?: {
65
+ family?: string;
66
+ weight?: string;
67
+ letterSpacing?: string;
68
+ };
69
+ display?: {
70
+ family?: string;
71
+ weight?: string;
72
+ letterSpacing?: string;
73
+ };
74
+ };
75
+ }>()
76
+ .default({}),
77
+
78
+ // Layout Configuration
79
+ layout: jsonb("layout")
80
+ .$type<{
81
+ header?: {
82
+ announcement?: {
83
+ enabled?: boolean;
84
+ text?: string;
85
+ link?: {
86
+ text?: string;
87
+ url?: string;
88
+ };
89
+ };
90
+ navigation?: {
91
+ mainMenu?: Array<{
92
+ id: string;
93
+ label?: string;
94
+ url: string;
95
+ children?: Array<{ id: string; label?: string; url: string }>;
96
+ }>;
97
+ position?: "left" | "center" | "right";
98
+ };
99
+ features?: {
100
+ search?: boolean;
101
+ cart?: boolean;
102
+ wishlist?: boolean;
103
+ account?: boolean;
104
+ language?: boolean;
105
+ currency?: boolean;
106
+ };
107
+ sticky?: boolean;
108
+ transparent?: boolean;
109
+ };
110
+ footer?: {
111
+ columns?: Array<{
112
+ title?: string;
113
+ links?: Array<{ label?: string; url: string }>;
114
+ }>;
115
+ social?: Array<{
116
+ platform: string;
117
+ url?: string;
118
+ icon?: string;
119
+ }>;
120
+ payment?: Array<{
121
+ name: string;
122
+ icon?: string;
123
+ }>;
124
+ newsletter?: {
125
+ enabled?: boolean;
126
+ title?: string;
127
+ placeholder?: string;
128
+ buttonText?: string;
129
+ };
130
+ copyright?: string;
131
+ showPoweredBy?: boolean;
132
+ };
133
+ sidebar?: {
134
+ enabled?: boolean;
135
+ position?: "left" | "right";
136
+ };
137
+ }>()
138
+ .default({}),
139
+
140
+ // Pages Content Configuration
141
+ pages: jsonb("pages")
142
+ .$type<{
143
+ home?: {
144
+ hero?: {
145
+ badge?: string;
146
+ title?: string;
147
+ subtitle?: string;
148
+ cta?: {
149
+ text?: string;
150
+ link?: string;
151
+ variant?: "primary" | "secondary" | "outline";
152
+ };
153
+ secondaryCta?: {
154
+ text?: string;
155
+ link?: string;
156
+ };
157
+ backgroundImage?: string;
158
+ backgroundVideo?: string;
159
+ newsletter?: {
160
+ enabled?: boolean;
161
+ placeholder?: string;
162
+ buttonText?: string;
163
+ };
164
+ trustedBy?: {
165
+ enabled?: boolean;
166
+ text?: string;
167
+ logos?: Array<{ name: string; url: string }>;
168
+ };
169
+ };
170
+ features?: {
171
+ title?: string;
172
+ subtitle?: string;
173
+ items?: Array<{
174
+ icon?: string;
175
+ title: string;
176
+ description: string;
177
+ }>;
178
+ };
179
+ sections?: Array<{
180
+ id: string;
181
+ type: string;
182
+ enabled?: boolean;
183
+ data?: Record<string, unknown>;
184
+ }>;
185
+ };
186
+ about?: {
187
+ hero?: {
188
+ title?: string;
189
+ subtitle?: string;
190
+ image?: string;
191
+ };
192
+ vision?: {
193
+ title?: string;
194
+ content?: string;
195
+ };
196
+ values?: Array<{
197
+ title: string;
198
+ description: string;
199
+ icon?: string;
200
+ }>;
201
+ };
202
+ contact?: {
203
+ hero?: {
204
+ title?: string;
205
+ subtitle?: string;
206
+ };
207
+ form?: {
208
+ title?: string;
209
+ submitButton?: string;
210
+ successMessage?: string;
211
+ };
212
+ info?: {
213
+ address?: string;
214
+ phone?: string;
215
+ email?: string;
216
+ hours?: string;
217
+ };
218
+ };
219
+ [key: string]: unknown;
220
+ }>()
221
+ .default({}),
222
+
223
+ // Promotions & Popups Configuration
224
+ promotions: jsonb("promotions")
225
+ .$type<{
226
+ popup?: {
227
+ enabled?: boolean;
228
+ trigger?: "immediate" | "exit" | "scroll" | "time";
229
+ triggerValue?: number;
230
+ image?: {
231
+ url?: string;
232
+ alt?: string;
233
+ };
234
+ discount?: {
235
+ highlightText?: string;
236
+ };
237
+ copy?: {
238
+ title?: string;
239
+ descriptionPrefix?: string;
240
+ descriptionSuffix?: string;
241
+ offerEndsInLabel?: string;
242
+ emailPlaceholder?: string;
243
+ ctaButtonText?: string;
244
+ submittingText?: string;
245
+ successTitle?: string;
246
+ successDescription?: string;
247
+ finePrint?: string;
248
+ };
249
+ timer?: {
250
+ enabled?: boolean;
251
+ hours?: number;
252
+ minutes?: number;
253
+ seconds?: number;
254
+ };
255
+ };
256
+ banner?: {
257
+ enabled?: boolean;
258
+ text?: string;
259
+ link?: string;
260
+ backgroundColor?: string;
261
+ textColor?: string;
262
+ };
263
+ saleEvents?: Array<{
264
+ id: string;
265
+ name: string;
266
+ startDate?: string;
267
+ endDate?: string;
268
+ discountPercent?: number;
269
+ enabled?: boolean;
270
+ }>;
271
+ }>()
272
+ .default({}),
273
+
274
+ // Localization Configuration
275
+ localization: jsonb("localization")
276
+ .$type<{
277
+ languages?: Array<{
278
+ code: string;
279
+ name?: string;
280
+ flag?: string;
281
+ enabled?: boolean;
282
+ }>;
283
+ currencies?: Array<{
284
+ code: string;
285
+ symbol?: string;
286
+ flag?: string;
287
+ enabled?: boolean;
288
+ }>;
289
+ defaultLanguage?: string;
290
+ defaultCurrency?: string;
291
+ }>()
292
+ .default({}),
293
+
294
+ // Character Limits & Validation Rules
295
+ validationRules: jsonb("validation_rules")
296
+ .$type<{
297
+ homeHero?: {
298
+ badgeTextMax?: number;
299
+ titleMax?: number;
300
+ subtitleMax?: number;
301
+ newsletterPlaceholderMax?: number;
302
+ newsletterButtonTextMax?: number;
303
+ trustedByTextMax?: number;
304
+ };
305
+ [key: string]: unknown;
306
+ }>()
307
+ .default({}),
308
+
309
+ // SEO & Meta Configuration
310
+ seo: jsonb("seo")
311
+ .$type<{
312
+ defaultTitle?: string;
313
+ defaultDescription?: string;
314
+ defaultImage?: string;
315
+ keywords?: string[];
316
+ ogTags?: Record<string, string>;
317
+ twitterCard?: Record<string, string>;
318
+ }>()
319
+ .default({}),
320
+
321
+ // Custom Scripts & Integrations
322
+ integrations: jsonb("integrations")
323
+ .$type<{
324
+ googleAnalytics?: {
325
+ enabled?: boolean;
326
+ trackingId?: string;
327
+ };
328
+ facebookPixel?: {
329
+ enabled?: boolean;
330
+ pixelId?: string;
331
+ };
332
+ customScripts?: {
333
+ header?: string;
334
+ footer?: string;
335
+ };
336
+ chatWidget?: {
337
+ enabled?: boolean;
338
+ provider?: string;
339
+ config?: Record<string, unknown>;
340
+ };
341
+ }>()
342
+ .default({}),
343
+
344
+ // Additional metadata
345
+ metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
346
+
347
+ // Audit fields
348
+ createdBy: text("created_by"),
349
+ updatedBy: text("updated_by"),
350
+ publishedAt: timestamp("published_at", { withTimezone: true }),
351
+ createdAt: timestamp("created_at", { withTimezone: true })
352
+ .defaultNow()
353
+ .notNull(),
354
+ updatedAt: timestamp("updated_at", { withTimezone: true })
355
+ .defaultNow()
356
+ .notNull(),
357
+ deletedAt: timestamp("deleted_at", { withTimezone: true }),
358
+ },
359
+ (table) => ({
360
+ storeIdIndex: index("idx_storefront_configs_store_id").on(table.storeId),
361
+ activeIndex: index("idx_storefront_configs_active").on(
362
+ table.storeId,
363
+ table.isActive,
364
+ ),
365
+ typeIndex: index("idx_storefront_configs_type").on(table.configType),
366
+ }),
367
+ );
368
+
369
+ // Relations
370
+ export const storefrontConfigsRelations = relations(
371
+ storefrontConfigs,
372
+ ({ one }) => ({
373
+ store: one(stores, {
374
+ fields: [storefrontConfigs.storeId],
375
+ references: [stores.id],
376
+ }),
377
+ }),
378
+ );
379
+
380
+ // TypeScript types
381
+ export type StorefrontConfig = typeof storefrontConfigs.$inferSelect;
382
+ export type NewStorefrontConfig = typeof storefrontConfigs.$inferInsert;