@axova/shared 1.0.2 → 1.0.9

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 (66) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +2 -0
  3. package/dist/lib/db.d.ts +34226 -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 +8 -8
  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 +6 -6
  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 +104 -104
  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 +22 -22
  24. package/dist/schemas/store/storefront-config-schema.d.ts +434 -823
  25. package/dist/schemas/store/storefront-config-schema.js +35 -62
  26. package/dist/utils/subdomain.d.ts +1 -1
  27. package/dist/utils/subdomain.js +10 -15
  28. package/package.json +1 -1
  29. package/src/configs/index.ts +654 -654
  30. package/src/index.ts +26 -23
  31. package/src/interfaces/customer-events.ts +106 -106
  32. package/src/interfaces/inventory-events.ts +545 -545
  33. package/src/interfaces/inventory-types.ts +1004 -1004
  34. package/src/interfaces/order-events.ts +381 -381
  35. package/src/lib/auditLogger.ts +1117 -1117
  36. package/src/lib/authOrganization.ts +153 -153
  37. package/src/lib/db.ts +84 -64
  38. package/src/middleware/serviceAuth.ts +328 -328
  39. package/src/middleware/storeOwnership.ts +199 -181
  40. package/src/middleware/storeValidationMiddleware.ts +17 -50
  41. package/src/middleware/userAuth.ts +248 -248
  42. package/src/schemas/admin/admin-schema.ts +208 -208
  43. package/src/schemas/ai-moderation/ai-moderation-schema.ts +180 -180
  44. package/src/schemas/common/common-schemas.ts +108 -108
  45. package/src/schemas/compliance/compliance-schema.ts +927 -0
  46. package/src/schemas/compliance/kyc-schema.ts +649 -0
  47. package/src/schemas/customer/customer-schema.ts +576 -0
  48. package/src/schemas/index.ts +202 -3
  49. package/src/schemas/inventory/inventory-tables.ts +1927 -0
  50. package/src/schemas/inventory/lot-tables.ts +799 -0
  51. package/src/schemas/order/cart-schema.ts +652 -0
  52. package/src/schemas/order/order-schema.ts +1406 -0
  53. package/src/schemas/product/discount-relations.ts +44 -0
  54. package/src/schemas/product/discount-schema.ts +464 -0
  55. package/src/schemas/product/product-relations.ts +187 -0
  56. package/src/schemas/product/product-schema.ts +955 -0
  57. package/src/schemas/store/ethiopian_business_api.md.resolved +212 -0
  58. package/src/schemas/store/store-audit-schema.ts +1257 -0
  59. package/src/schemas/store/store-schema.ts +661 -0
  60. package/src/schemas/store/store-settings-schema.ts +231 -0
  61. package/src/schemas/store/storefront-config-schema.ts +382 -0
  62. package/src/schemas/types.ts +67 -67
  63. package/src/types/events.ts +646 -646
  64. package/src/utils/errorHandler.ts +44 -44
  65. package/src/utils/subdomain.ts +19 -23
  66. package/tsconfig.json +21 -21
@@ -1,180 +1,180 @@
1
- import { createId } from "@paralleldrive/cuid2";
2
- import {
3
- boolean,
4
- decimal,
5
- index,
6
- integer,
7
- jsonb,
8
- pgTable,
9
- text,
10
- timestamp,
11
- varchar,
12
- } from "drizzle-orm/pg-core";
13
-
14
- // Content Scans Table
15
- export const contentScans = pgTable(
16
- "content_scans",
17
- {
18
- id: text("id")
19
- .primaryKey()
20
- .$defaultFn(() => createId()),
21
-
22
- // Content Information
23
- contentId: text("content_id").notNull(),
24
- contentType: varchar("content_type", { length: 50 })
25
- .notNull()
26
- .$type<
27
- | "STORE_DESCRIPTION"
28
- | "PRODUCT_TITLE"
29
- | "PRODUCT_DESCRIPTION"
30
- | "BLOG_POST"
31
- | "USER_REVIEW"
32
- | "STORE_NAME"
33
- | "IMAGE"
34
- | "VIDEO"
35
- >(),
36
-
37
- storeId: text("store_id"),
38
- userId: text("user_id"),
39
-
40
- // Scan Configuration
41
- scanType: varchar("scan_type", { length: 30 })
42
- .$type<"AUTOMATED" | "MANUAL" | "SCHEDULED" | "TRIGGERED">()
43
- .default("AUTOMATED"),
44
-
45
- provider: varchar("provider", { length: 30 })
46
- .default("OPENAI")
47
- .$type<
48
- "OPENAI" | "GOOGLE_PERSPECTIVE" | "AZURE_CONTENT_SAFETY" | "INTERNAL"
49
- >(),
50
-
51
- // Content Data
52
- originalContent: text("original_content").notNull(),
53
- processedContent: text("processed_content"), // Cleaned/normalized content
54
- contentHash: varchar("content_hash", { length: 64 }), // SHA-256 for deduplication
55
-
56
- // Scan Results
57
- flagged: boolean("flagged").notNull().default(false),
58
- confidence: decimal("confidence", { precision: 5, scale: 4 }), // 0-1
59
-
60
- // OpenAI Moderation Categories
61
- categories: jsonb("categories").$type<{
62
- sexual?: boolean;
63
- hate?: boolean;
64
- harassment?: boolean;
65
- selfHarm?: boolean;
66
- sexualMinors?: boolean;
67
- hateThreatening?: boolean;
68
- violenceGraphic?: boolean;
69
- selfHarmIntent?: boolean;
70
- selfHarmInstructions?: boolean;
71
- harassmentThreatening?: boolean;
72
- violence?: boolean;
73
- }>(),
74
-
75
- categoryScores: jsonb("category_scores").$type<{
76
- sexual?: number;
77
- hate?: number;
78
- harassment?: number;
79
- selfHarm?: number;
80
- sexualMinors?: number;
81
- hateThreatening?: number;
82
- violenceGraphic?: number;
83
- selfHarmIntent?: number;
84
- selfHarmInstructions?: number;
85
- harassmentThreatening?: number;
86
- violence?: number;
87
- }>(),
88
-
89
- // Custom Rules and Patterns
90
- customViolations: jsonb("custom_violations")
91
- .$type<
92
- Array<{
93
- ruleId: string;
94
- ruleName: string;
95
- matched: boolean;
96
- confidence: number;
97
- matchedText?: string;
98
- }>
99
- >()
100
- .default([]),
101
-
102
- // Status and Actions
103
- status: varchar("status", { length: 20 })
104
- .notNull()
105
- .default("COMPLETED")
106
- .$type<"PENDING" | "PROCESSING" | "COMPLETED" | "FAILED" | "CANCELLED">(),
107
-
108
- actionTaken: varchar("action_taken", { length: 30 }).$type<
109
- "NONE" | "FLAGGED" | "BLOCKED" | "QUARANTINED" | "REPORTED"
110
- >(),
111
-
112
- // Processing Metadata
113
- processingTime: integer("processing_time"), // milliseconds
114
- cost: decimal("cost", { precision: 8, scale: 6 }), // API cost in USD
115
-
116
- createdAt: timestamp("created_at", { withTimezone: true })
117
- .defaultNow()
118
- .notNull(),
119
- },
120
- (table) => ({
121
- contentIdIndex: index("idx_scans_content").on(table.contentId),
122
- storeIdIndex: index("idx_scans_store").on(table.storeId),
123
- flaggedIndex: index("idx_scans_flagged").on(
124
- table.flagged,
125
- table.confidence,
126
- ),
127
- statusIndex: index("idx_scans_status").on(table.status),
128
- contentTypeIndex: index("idx_scans_content_type").on(table.contentType),
129
- hashIndex: index("idx_scans_hash").on(table.contentHash),
130
- }),
131
- );
132
-
133
- // Moderation Rules Table
134
- export const moderationRules = pgTable(
135
- "moderation_rules",
136
- {
137
- id: text("id")
138
- .primaryKey()
139
- .$defaultFn(() => createId()),
140
-
141
- name: varchar("name", { length: 100 }).notNull(),
142
- description: text("description"),
143
-
144
- ruleType: varchar("rule_type", { length: 30 })
145
- .notNull()
146
- .$type<
147
- "KEYWORD_FILTER" | "PATTERN_MATCH" | "ML_CLASSIFIER" | "CUSTOM_LOGIC"
148
- >(),
149
-
150
- severity: varchar("severity", { length: 20 })
151
- .notNull()
152
- .$type<"LOW" | "MEDIUM" | "HIGH" | "CRITICAL">(),
153
-
154
- // Rule Configuration
155
- configuration: jsonb("configuration").$type<{
156
- keywords?: string[];
157
- patterns?: string[];
158
- threshold?: number;
159
- customLogic?: string;
160
- }>(),
161
-
162
- // Targeting
163
- contentTypes: jsonb("content_types").$type<string[]>().default([]),
164
- storeTypes: jsonb("store_types").$type<string[]>().default([]),
165
-
166
- isActive: boolean("is_active").notNull().default(true),
167
-
168
- createdAt: timestamp("created_at", { withTimezone: true })
169
- .defaultNow()
170
- .notNull(),
171
- updatedAt: timestamp("updated_at", { withTimezone: true })
172
- .defaultNow()
173
- .notNull(),
174
- },
175
- (table) => ({
176
- nameIndex: index("idx_rules_name").on(table.name),
177
- typeIndex: index("idx_rules_type").on(table.ruleType),
178
- activeIndex: index("idx_rules_active").on(table.isActive),
179
- }),
180
- );
1
+ import { createId } from "@paralleldrive/cuid2";
2
+ import {
3
+ boolean,
4
+ decimal,
5
+ index,
6
+ integer,
7
+ jsonb,
8
+ pgTable,
9
+ text,
10
+ timestamp,
11
+ varchar,
12
+ } from "drizzle-orm/pg-core";
13
+
14
+ // Content Scans Table
15
+ export const contentScans = pgTable(
16
+ "content_scans",
17
+ {
18
+ id: text("id")
19
+ .primaryKey()
20
+ .$defaultFn(() => createId()),
21
+
22
+ // Content Information
23
+ contentId: text("content_id").notNull(),
24
+ contentType: varchar("content_type", { length: 50 })
25
+ .notNull()
26
+ .$type<
27
+ | "STORE_DESCRIPTION"
28
+ | "PRODUCT_TITLE"
29
+ | "PRODUCT_DESCRIPTION"
30
+ | "BLOG_POST"
31
+ | "USER_REVIEW"
32
+ | "STORE_NAME"
33
+ | "IMAGE"
34
+ | "VIDEO"
35
+ >(),
36
+
37
+ storeId: text("store_id"),
38
+ userId: text("user_id"),
39
+
40
+ // Scan Configuration
41
+ scanType: varchar("scan_type", { length: 30 })
42
+ .$type<"AUTOMATED" | "MANUAL" | "SCHEDULED" | "TRIGGERED">()
43
+ .default("AUTOMATED"),
44
+
45
+ provider: varchar("provider", { length: 30 })
46
+ .default("OPENAI")
47
+ .$type<
48
+ "OPENAI" | "GOOGLE_PERSPECTIVE" | "AZURE_CONTENT_SAFETY" | "INTERNAL"
49
+ >(),
50
+
51
+ // Content Data
52
+ originalContent: text("original_content").notNull(),
53
+ processedContent: text("processed_content"), // Cleaned/normalized content
54
+ contentHash: varchar("content_hash", { length: 64 }), // SHA-256 for deduplication
55
+
56
+ // Scan Results
57
+ flagged: boolean("flagged").notNull().default(false),
58
+ confidence: decimal("confidence", { precision: 5, scale: 4 }), // 0-1
59
+
60
+ // OpenAI Moderation Categories
61
+ categories: jsonb("categories").$type<{
62
+ sexual?: boolean;
63
+ hate?: boolean;
64
+ harassment?: boolean;
65
+ selfHarm?: boolean;
66
+ sexualMinors?: boolean;
67
+ hateThreatening?: boolean;
68
+ violenceGraphic?: boolean;
69
+ selfHarmIntent?: boolean;
70
+ selfHarmInstructions?: boolean;
71
+ harassmentThreatening?: boolean;
72
+ violence?: boolean;
73
+ }>(),
74
+
75
+ categoryScores: jsonb("category_scores").$type<{
76
+ sexual?: number;
77
+ hate?: number;
78
+ harassment?: number;
79
+ selfHarm?: number;
80
+ sexualMinors?: number;
81
+ hateThreatening?: number;
82
+ violenceGraphic?: number;
83
+ selfHarmIntent?: number;
84
+ selfHarmInstructions?: number;
85
+ harassmentThreatening?: number;
86
+ violence?: number;
87
+ }>(),
88
+
89
+ // Custom Rules and Patterns
90
+ customViolations: jsonb("custom_violations")
91
+ .$type<
92
+ Array<{
93
+ ruleId: string;
94
+ ruleName: string;
95
+ matched: boolean;
96
+ confidence: number;
97
+ matchedText?: string;
98
+ }>
99
+ >()
100
+ .default([]),
101
+
102
+ // Status and Actions
103
+ status: varchar("status", { length: 20 })
104
+ .notNull()
105
+ .default("COMPLETED")
106
+ .$type<"PENDING" | "PROCESSING" | "COMPLETED" | "FAILED" | "CANCELLED">(),
107
+
108
+ actionTaken: varchar("action_taken", { length: 30 }).$type<
109
+ "NONE" | "FLAGGED" | "BLOCKED" | "QUARANTINED" | "REPORTED"
110
+ >(),
111
+
112
+ // Processing Metadata
113
+ processingTime: integer("processing_time"), // milliseconds
114
+ cost: decimal("cost", { precision: 8, scale: 6 }), // API cost in USD
115
+
116
+ createdAt: timestamp("created_at", { withTimezone: true })
117
+ .defaultNow()
118
+ .notNull(),
119
+ },
120
+ (table) => ({
121
+ contentIdIndex: index("idx_scans_content").on(table.contentId),
122
+ storeIdIndex: index("idx_scans_store").on(table.storeId),
123
+ flaggedIndex: index("idx_scans_flagged").on(
124
+ table.flagged,
125
+ table.confidence,
126
+ ),
127
+ statusIndex: index("idx_scans_status").on(table.status),
128
+ contentTypeIndex: index("idx_scans_content_type").on(table.contentType),
129
+ hashIndex: index("idx_scans_hash").on(table.contentHash),
130
+ }),
131
+ );
132
+
133
+ // Moderation Rules Table
134
+ export const moderationRules = pgTable(
135
+ "moderation_rules",
136
+ {
137
+ id: text("id")
138
+ .primaryKey()
139
+ .$defaultFn(() => createId()),
140
+
141
+ name: varchar("name", { length: 100 }).notNull(),
142
+ description: text("description"),
143
+
144
+ ruleType: varchar("rule_type", { length: 30 })
145
+ .notNull()
146
+ .$type<
147
+ "KEYWORD_FILTER" | "PATTERN_MATCH" | "ML_CLASSIFIER" | "CUSTOM_LOGIC"
148
+ >(),
149
+
150
+ severity: varchar("severity", { length: 20 })
151
+ .notNull()
152
+ .$type<"LOW" | "MEDIUM" | "HIGH" | "CRITICAL">(),
153
+
154
+ // Rule Configuration
155
+ configuration: jsonb("configuration").$type<{
156
+ keywords?: string[];
157
+ patterns?: string[];
158
+ threshold?: number;
159
+ customLogic?: string;
160
+ }>(),
161
+
162
+ // Targeting
163
+ contentTypes: jsonb("content_types").$type<string[]>().default([]),
164
+ storeTypes: jsonb("store_types").$type<string[]>().default([]),
165
+
166
+ isActive: boolean("is_active").notNull().default(true),
167
+
168
+ createdAt: timestamp("created_at", { withTimezone: true })
169
+ .defaultNow()
170
+ .notNull(),
171
+ updatedAt: timestamp("updated_at", { withTimezone: true })
172
+ .defaultNow()
173
+ .notNull(),
174
+ },
175
+ (table) => ({
176
+ nameIndex: index("idx_rules_name").on(table.name),
177
+ typeIndex: index("idx_rules_type").on(table.ruleType),
178
+ activeIndex: index("idx_rules_active").on(table.isActive),
179
+ }),
180
+ );
@@ -1,108 +1,108 @@
1
- import { z } from "zod";
2
-
3
- // =====================================================
4
- // SHARED COMMON SCHEMAS
5
- // =====================================================
6
-
7
- // CUID2 validation schema aligned with createId() from @paralleldrive/cuid2
8
- export const CuidSchema = z.string().cuid2();
9
-
10
- export const GeoLocationSchema = z.object({
11
- lat: z.number().min(-90).max(90),
12
- lng: z.number().min(-180).max(180),
13
- address: z.object({
14
- line1: z.string(),
15
- line2: z.string().optional(),
16
- city: z.string(),
17
- state: z.string(),
18
- postalCode: z.string(),
19
- country: z.string(),
20
- }),
21
- });
22
-
23
- export const AuditTrailSchema = z.object({
24
- action: z.string(),
25
- performedBy: z.string(),
26
- performedAt: z.date(),
27
- details: z.record(z.any()).optional(),
28
- ipAddress: z.string().optional(),
29
- userAgent: z.string().optional(),
30
- });
31
-
32
- export const MoneySchema = z.object({
33
- amount: z.number(),
34
- currency: z.string().length(3).default("USD"),
35
- });
36
-
37
- export const ContactInfoSchema = z.object({
38
- name: z.string().optional(),
39
- phone: z.string().optional(),
40
- email: z.string().email().optional(),
41
- alternatePhone: z.string().optional(),
42
- });
43
-
44
- // Allow days that are explicitly closed to omit open/close times
45
- const DayHoursSchema = z.union([
46
- z.object({
47
- closed: z.literal(true),
48
- open: z.string().optional(),
49
- close: z.string().optional(),
50
- }),
51
- z.object({
52
- open: z.string(),
53
- close: z.string(),
54
- closed: z.literal(false).optional(),
55
- }),
56
- ]);
57
-
58
- export const BusinessHoursSchema = z.object({
59
- timezone: z.string(),
60
- monday: DayHoursSchema.optional(),
61
- tuesday: DayHoursSchema.optional(),
62
- wednesday: DayHoursSchema.optional(),
63
- thursday: DayHoursSchema.optional(),
64
- friday: DayHoursSchema.optional(),
65
- saturday: DayHoursSchema.optional(),
66
- sunday: DayHoursSchema.optional(),
67
- holidays: z
68
- .array(
69
- z.object({
70
- date: z.string(),
71
- name: z.string(),
72
- closed: z.boolean().default(true),
73
- }),
74
- )
75
- .optional(),
76
- });
77
-
78
- // Common enum types
79
- export const CurrencyEnum = z.enum([
80
- "USD",
81
- "EUR",
82
- "GBP",
83
- "CAD",
84
- "AUD",
85
- "JPY",
86
- "CNY",
87
- "INR",
88
- ]);
89
- export const TimezoneEnum = z.enum([
90
- "UTC",
91
- "America/New_York",
92
- "America/Chicago",
93
- "America/Denver",
94
- "America/Los_Angeles",
95
- "Europe/London",
96
- "Europe/Paris",
97
- "Europe/Berlin",
98
- "Asia/Tokyo",
99
- "Asia/Shanghai",
100
- "Asia/Kolkata",
101
- "Australia/Sydney",
102
- ]);
103
-
104
- export type GeoLocation = z.infer<typeof GeoLocationSchema>;
105
- export type AuditTrail = z.infer<typeof AuditTrailSchema>;
106
- export type Money = z.infer<typeof MoneySchema>;
107
- export type ContactInfo = z.infer<typeof ContactInfoSchema>;
108
- export type BusinessHours = z.infer<typeof BusinessHoursSchema>;
1
+ import { z } from "zod";
2
+
3
+ // =====================================================
4
+ // SHARED COMMON SCHEMAS
5
+ // =====================================================
6
+
7
+ // CUID2 validation schema aligned with createId() from @paralleldrive/cuid2
8
+ export const CuidSchema = z.string().cuid2();
9
+
10
+ export const GeoLocationSchema = z.object({
11
+ lat: z.number().min(-90).max(90),
12
+ lng: z.number().min(-180).max(180),
13
+ address: z.object({
14
+ line1: z.string(),
15
+ line2: z.string().optional(),
16
+ city: z.string(),
17
+ state: z.string(),
18
+ postalCode: z.string(),
19
+ country: z.string(),
20
+ }),
21
+ });
22
+
23
+ export const AuditTrailSchema = z.object({
24
+ action: z.string(),
25
+ performedBy: z.string(),
26
+ performedAt: z.date(),
27
+ details: z.record(z.any()).optional(),
28
+ ipAddress: z.string().optional(),
29
+ userAgent: z.string().optional(),
30
+ });
31
+
32
+ export const MoneySchema = z.object({
33
+ amount: z.number(),
34
+ currency: z.string().length(3).default("USD"),
35
+ });
36
+
37
+ export const ContactInfoSchema = z.object({
38
+ name: z.string().optional(),
39
+ phone: z.string().optional(),
40
+ email: z.string().email().optional(),
41
+ alternatePhone: z.string().optional(),
42
+ });
43
+
44
+ // Allow days that are explicitly closed to omit open/close times
45
+ const DayHoursSchema = z.union([
46
+ z.object({
47
+ closed: z.literal(true),
48
+ open: z.string().optional(),
49
+ close: z.string().optional(),
50
+ }),
51
+ z.object({
52
+ open: z.string(),
53
+ close: z.string(),
54
+ closed: z.literal(false).optional(),
55
+ }),
56
+ ]);
57
+
58
+ export const BusinessHoursSchema = z.object({
59
+ timezone: z.string(),
60
+ monday: DayHoursSchema.optional(),
61
+ tuesday: DayHoursSchema.optional(),
62
+ wednesday: DayHoursSchema.optional(),
63
+ thursday: DayHoursSchema.optional(),
64
+ friday: DayHoursSchema.optional(),
65
+ saturday: DayHoursSchema.optional(),
66
+ sunday: DayHoursSchema.optional(),
67
+ holidays: z
68
+ .array(
69
+ z.object({
70
+ date: z.string(),
71
+ name: z.string(),
72
+ closed: z.boolean().default(true),
73
+ }),
74
+ )
75
+ .optional(),
76
+ });
77
+
78
+ // Common enum types
79
+ export const CurrencyEnum = z.enum([
80
+ "USD",
81
+ "EUR",
82
+ "GBP",
83
+ "CAD",
84
+ "AUD",
85
+ "JPY",
86
+ "CNY",
87
+ "INR",
88
+ ]);
89
+ export const TimezoneEnum = z.enum([
90
+ "UTC",
91
+ "America/New_York",
92
+ "America/Chicago",
93
+ "America/Denver",
94
+ "America/Los_Angeles",
95
+ "Europe/London",
96
+ "Europe/Paris",
97
+ "Europe/Berlin",
98
+ "Asia/Tokyo",
99
+ "Asia/Shanghai",
100
+ "Asia/Kolkata",
101
+ "Australia/Sydney",
102
+ ]);
103
+
104
+ export type GeoLocation = z.infer<typeof GeoLocationSchema>;
105
+ export type AuditTrail = z.infer<typeof AuditTrailSchema>;
106
+ export type Money = z.infer<typeof MoneySchema>;
107
+ export type ContactInfo = z.infer<typeof ContactInfoSchema>;
108
+ export type BusinessHours = z.infer<typeof BusinessHoursSchema>;