@ebowwa/crm 0.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 (187) hide show
  1. package/README.md +174 -0
  2. package/dist/cli/commands/activities.d.ts +11 -0
  3. package/dist/cli/commands/activities.d.ts.map +1 -0
  4. package/dist/cli/commands/activities.js +427 -0
  5. package/dist/cli/commands/activities.js.map +1 -0
  6. package/dist/cli/commands/contacts.d.ts +11 -0
  7. package/dist/cli/commands/contacts.d.ts.map +1 -0
  8. package/dist/cli/commands/contacts.js +458 -0
  9. package/dist/cli/commands/contacts.js.map +1 -0
  10. package/dist/cli/commands/deals.d.ts +11 -0
  11. package/dist/cli/commands/deals.d.ts.map +1 -0
  12. package/dist/cli/commands/deals.js +498 -0
  13. package/dist/cli/commands/deals.js.map +1 -0
  14. package/dist/cli/commands/media.d.ts +11 -0
  15. package/dist/cli/commands/media.d.ts.map +1 -0
  16. package/dist/cli/commands/media.js +417 -0
  17. package/dist/cli/commands/media.js.map +1 -0
  18. package/dist/cli/commands/search.d.ts +11 -0
  19. package/dist/cli/commands/search.d.ts.map +1 -0
  20. package/dist/cli/commands/search.js +346 -0
  21. package/dist/cli/commands/search.js.map +1 -0
  22. package/dist/cli/index.d.ts +13 -0
  23. package/dist/cli/index.d.ts.map +1 -0
  24. package/dist/cli/index.js +173 -0
  25. package/dist/cli/index.js.map +1 -0
  26. package/dist/cli/repl.d.ts +15 -0
  27. package/dist/cli/repl.d.ts.map +1 -0
  28. package/dist/cli/repl.js +318 -0
  29. package/dist/cli/repl.js.map +1 -0
  30. package/dist/cli/utils/config.d.ts +91 -0
  31. package/dist/cli/utils/config.d.ts.map +1 -0
  32. package/dist/cli/utils/config.js +212 -0
  33. package/dist/cli/utils/config.js.map +1 -0
  34. package/dist/cli/utils/output.d.ts +136 -0
  35. package/dist/cli/utils/output.d.ts.map +1 -0
  36. package/dist/cli/utils/output.js +323 -0
  37. package/dist/cli/utils/output.js.map +1 -0
  38. package/dist/cli/utils/prompt.d.ts +81 -0
  39. package/dist/cli/utils/prompt.d.ts.map +1 -0
  40. package/dist/cli/utils/prompt.js +341 -0
  41. package/dist/cli/utils/prompt.js.map +1 -0
  42. package/dist/cli.d.ts +3 -0
  43. package/dist/cli.d.ts.map +1 -0
  44. package/dist/cli.js +8 -0
  45. package/dist/cli.js.map +1 -0
  46. package/dist/core/index.d.ts +6 -0
  47. package/dist/core/index.d.ts.map +1 -0
  48. package/dist/core/index.js +32 -0
  49. package/dist/core/index.js.map +1 -0
  50. package/dist/core/schemas.d.ts +3050 -0
  51. package/dist/core/schemas.d.ts.map +1 -0
  52. package/dist/core/schemas.js +667 -0
  53. package/dist/core/schemas.js.map +1 -0
  54. package/dist/core/types.d.ts +597 -0
  55. package/dist/core/types.d.ts.map +1 -0
  56. package/dist/core/types.js +8 -0
  57. package/dist/core/types.js.map +1 -0
  58. package/dist/index.d.ts +7 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +8 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/mcp/index.d.ts +14 -0
  63. package/dist/mcp/index.d.ts.map +1 -0
  64. package/dist/mcp/index.js +11 -0
  65. package/dist/mcp/index.js.map +1 -0
  66. package/dist/mcp/server.d.ts +13 -0
  67. package/dist/mcp/server.d.ts.map +1 -0
  68. package/dist/mcp/server.js +18 -0
  69. package/dist/mcp/server.js.map +1 -0
  70. package/dist/mcp/storage/client.d.ts +109 -0
  71. package/dist/mcp/storage/client.d.ts.map +1 -0
  72. package/dist/mcp/storage/client.js +355 -0
  73. package/dist/mcp/storage/client.js.map +1 -0
  74. package/dist/mcp/storage/index.d.ts +7 -0
  75. package/dist/mcp/storage/index.d.ts.map +1 -0
  76. package/dist/mcp/storage/index.js +6 -0
  77. package/dist/mcp/storage/index.js.map +1 -0
  78. package/dist/mcp/storage/types.d.ts +44 -0
  79. package/dist/mcp/storage/types.d.ts.map +1 -0
  80. package/dist/mcp/storage/types.js +35 -0
  81. package/dist/mcp/storage/types.js.map +1 -0
  82. package/dist/mcp/tools/definitions.d.ts +16 -0
  83. package/dist/mcp/tools/definitions.d.ts.map +1 -0
  84. package/dist/mcp/tools/definitions.js +914 -0
  85. package/dist/mcp/tools/definitions.js.map +1 -0
  86. package/dist/mcp/tools/handlers.d.ts +50 -0
  87. package/dist/mcp/tools/handlers.d.ts.map +1 -0
  88. package/dist/mcp/tools/handlers.js +760 -0
  89. package/dist/mcp/tools/handlers.js.map +1 -0
  90. package/dist/mcp/tools/index.d.ts +7 -0
  91. package/dist/mcp/tools/index.d.ts.map +1 -0
  92. package/dist/mcp/tools/index.js +6 -0
  93. package/dist/mcp/tools/index.js.map +1 -0
  94. package/dist/mcp/tools/types.d.ts +314 -0
  95. package/dist/mcp/tools/types.d.ts.map +1 -0
  96. package/dist/mcp/tools/types.js +5 -0
  97. package/dist/mcp/tools/types.js.map +1 -0
  98. package/dist/mcp/transports/stdio.d.ts +27 -0
  99. package/dist/mcp/transports/stdio.d.ts.map +1 -0
  100. package/dist/mcp/transports/stdio.js +237 -0
  101. package/dist/mcp/transports/stdio.js.map +1 -0
  102. package/dist/telemetry/index.d.ts +58 -0
  103. package/dist/telemetry/index.d.ts.map +1 -0
  104. package/dist/telemetry/index.js +109 -0
  105. package/dist/telemetry/index.js.map +1 -0
  106. package/dist/telemetry/logger.d.ts +116 -0
  107. package/dist/telemetry/logger.d.ts.map +1 -0
  108. package/dist/telemetry/logger.js +256 -0
  109. package/dist/telemetry/logger.js.map +1 -0
  110. package/dist/telemetry/metrics.d.ts +115 -0
  111. package/dist/telemetry/metrics.d.ts.map +1 -0
  112. package/dist/telemetry/metrics.js +292 -0
  113. package/dist/telemetry/metrics.js.map +1 -0
  114. package/dist/telemetry/tracer.d.ts +227 -0
  115. package/dist/telemetry/tracer.d.ts.map +1 -0
  116. package/dist/telemetry/tracer.js +355 -0
  117. package/dist/telemetry/tracer.js.map +1 -0
  118. package/dist/web/app.d.ts +2 -0
  119. package/dist/web/app.d.ts.map +1 -0
  120. package/dist/web/app.js +115 -0
  121. package/dist/web/app.js.map +1 -0
  122. package/dist/web/components/ContactList.d.ts +3 -0
  123. package/dist/web/components/ContactList.d.ts.map +1 -0
  124. package/dist/web/components/ContactList.js +262 -0
  125. package/dist/web/components/ContactList.js.map +1 -0
  126. package/dist/web/components/Dashboard.d.ts +3 -0
  127. package/dist/web/components/Dashboard.d.ts.map +1 -0
  128. package/dist/web/components/Dashboard.js +158 -0
  129. package/dist/web/components/Dashboard.js.map +1 -0
  130. package/dist/web/components/DealPipeline.d.ts +3 -0
  131. package/dist/web/components/DealPipeline.d.ts.map +1 -0
  132. package/dist/web/components/DealPipeline.js +306 -0
  133. package/dist/web/components/DealPipeline.js.map +1 -0
  134. package/dist/web/index.d.ts +2 -0
  135. package/dist/web/index.d.ts.map +1 -0
  136. package/dist/web/index.js +269 -0
  137. package/dist/web/index.js.map +1 -0
  138. package/dist/web/types.d.ts +75 -0
  139. package/dist/web/types.d.ts.map +1 -0
  140. package/dist/web/types.js +3 -0
  141. package/dist/web/types.js.map +1 -0
  142. package/native/index.d.ts +571 -0
  143. package/native/index.js +687 -0
  144. package/package.json +105 -0
  145. package/src/cli/commands/activities.ts +543 -0
  146. package/src/cli/commands/contacts.ts +563 -0
  147. package/src/cli/commands/deals.ts +637 -0
  148. package/src/cli/commands/media.ts +521 -0
  149. package/src/cli/commands/search.ts +426 -0
  150. package/src/cli/index.ts +203 -0
  151. package/src/cli/repl.ts +379 -0
  152. package/src/cli/utils/config.ts +299 -0
  153. package/src/cli/utils/output.ts +386 -0
  154. package/src/cli/utils/prompt.ts +444 -0
  155. package/src/cli.ts +11 -0
  156. package/src/core/index.ts +184 -0
  157. package/src/core/schemas.ts +770 -0
  158. package/src/core/types.ts +969 -0
  159. package/src/index.ts +8 -0
  160. package/src/mcp/index.ts +17 -0
  161. package/src/mcp/server.ts +26 -0
  162. package/src/mcp/storage/client.ts +408 -0
  163. package/src/mcp/storage/index.ts +7 -0
  164. package/src/mcp/storage/types.ts +72 -0
  165. package/src/mcp/tools/definitions.ts +961 -0
  166. package/src/mcp/tools/handlers.ts +805 -0
  167. package/src/mcp/tools/index.ts +7 -0
  168. package/src/mcp/tools/types.ts +390 -0
  169. package/src/mcp/transports/stdio.ts +225 -0
  170. package/src/telemetry/index.ts +131 -0
  171. package/src/telemetry/logger.ts +318 -0
  172. package/src/telemetry/metrics.ts +393 -0
  173. package/src/telemetry/tracer.ts +487 -0
  174. package/src/web/api/activities.ts +41 -0
  175. package/src/web/api/contacts.ts +114 -0
  176. package/src/web/api/deals.ts +108 -0
  177. package/src/web/api/media.ts +98 -0
  178. package/src/web/app.tsx +143 -0
  179. package/src/web/components/ActivityFeed.tsx +195 -0
  180. package/src/web/components/ContactList.tsx +340 -0
  181. package/src/web/components/Dashboard.tsx +214 -0
  182. package/src/web/components/DealPipeline.tsx +405 -0
  183. package/src/web/components/MediaGallery.tsx +334 -0
  184. package/src/web/index.html +14 -0
  185. package/src/web/index.ts +326 -0
  186. package/src/web/styles/main.css +180 -0
  187. package/src/web/types.ts +311 -0
@@ -0,0 +1,770 @@
1
+ /**
2
+ * Zod Validation Schemas for CRM System
3
+ *
4
+ * Provides runtime validation for all CRM types.
5
+ * Import and use schemas to validate data at boundaries.
6
+ */
7
+
8
+ import { z } from 'zod';
9
+
10
+ // ============================================================================
11
+ // Base Schemas
12
+ // ============================================================================
13
+
14
+ /** UUID validation */
15
+ export const uuidSchema = z.string().uuid();
16
+
17
+ /** Timestamp validation (ISO 8601) */
18
+ export const timestampSchema = z.string().datetime();
19
+
20
+ /** Metadata schema for flexible key-value objects */
21
+ export const metadataSchema: z.ZodType<Record<string, unknown>> = z.record(
22
+ z.string(),
23
+ z.union([
24
+ z.string(),
25
+ z.number(),
26
+ z.boolean(),
27
+ z.null(),
28
+ z.lazy(() => metadataSchema),
29
+ z.array(z.lazy(() => metadataSchema)),
30
+ ])
31
+ );
32
+
33
+ // ============================================================================
34
+ // Contact Schemas
35
+ // ============================================================================
36
+
37
+ export const phoneNumberTypeSchema = z.enum(['mobile', 'home', 'work', 'other']);
38
+
39
+ export const emailTypeSchema = z.enum(['personal', 'work', 'other']);
40
+
41
+ export const addressTypeSchema = z.enum(['home', 'work', 'billing', 'shipping', 'other']);
42
+
43
+ export const socialPlatformSchema = z.enum([
44
+ 'linkedin',
45
+ 'twitter',
46
+ 'facebook',
47
+ 'instagram',
48
+ 'github',
49
+ 'other',
50
+ ]);
51
+
52
+ export const customFieldTypeSchema = z.enum(['string', 'number', 'boolean', 'date', 'select', 'multiselect']);
53
+
54
+ export const contactSourceSchema = z.enum([
55
+ 'organic',
56
+ 'referral',
57
+ 'advertisement',
58
+ 'social_media',
59
+ 'email_campaign',
60
+ 'website',
61
+ 'event',
62
+ 'cold_outreach',
63
+ 'partner',
64
+ 'other',
65
+ ]);
66
+
67
+ export const contactStatusSchema = z.enum([
68
+ 'lead',
69
+ 'prospect',
70
+ 'qualified',
71
+ 'customer',
72
+ 'churned',
73
+ 'archived',
74
+ ]);
75
+
76
+ export const phoneNumberSchema = z.object({
77
+ number: z.string().min(1).max(50),
78
+ type: phoneNumberTypeSchema,
79
+ primary: z.boolean().default(false),
80
+ });
81
+
82
+ export const emailSchema = z.object({
83
+ email: z.string().email(),
84
+ type: emailTypeSchema,
85
+ primary: z.boolean().default(false),
86
+ });
87
+
88
+ export const addressSchema = z.object({
89
+ street: z.string().min(1).max(500),
90
+ city: z.string().min(1).max(100),
91
+ state: z.string().min(1).max(100),
92
+ postalCode: z.string().min(1).max(20),
93
+ country: z.string().min(2).max(100),
94
+ type: addressTypeSchema,
95
+ });
96
+
97
+ export const socialProfileSchema = z.object({
98
+ platform: socialPlatformSchema,
99
+ handle: z.string().min(1).max(100),
100
+ url: z.string().url().optional(),
101
+ });
102
+
103
+ export const customFieldSchema = z.object({
104
+ key: z.string().min(1).max(100),
105
+ value: z.union([z.string(), z.number(), z.boolean(), z.coerce.date(), z.null()]),
106
+ type: customFieldTypeSchema,
107
+ });
108
+
109
+ export const contactSchema = z.object({
110
+ id: uuidSchema,
111
+ name: z.string().min(1).max(500),
112
+ firstName: z.string().min(1).max(100).optional(),
113
+ lastName: z.string().min(1).max(100).optional(),
114
+ emails: z.array(emailSchema).default([]),
115
+ phones: z.array(phoneNumberSchema).default([]),
116
+ addresses: z.array(addressSchema).default([]),
117
+ company: z.string().max(200).optional(),
118
+ title: z.string().max(200).optional(),
119
+ department: z.string().max(100).optional(),
120
+ socialProfiles: z.array(socialProfileSchema).default([]),
121
+ website: z.string().url().optional(),
122
+ tags: z.array(z.string().max(100)).default([]),
123
+ customFields: z.array(customFieldSchema).default([]),
124
+ source: contactSourceSchema.optional(),
125
+ status: contactStatusSchema.default('lead'),
126
+ ownerId: uuidSchema.optional(),
127
+ avatar: z.string().optional(),
128
+ preferredContact: z.enum(['email', 'phone', 'sms', 'none']).optional(),
129
+ language: z.string().max(10).optional(),
130
+ timezone: z.string().max(100).optional(),
131
+ preferences: z.string().max(2000).optional(),
132
+ leadScore: z.number().int().min(0).max(100).optional(),
133
+ doNotContact: z.boolean().default(false),
134
+ lastContactedAt: timestampSchema.optional(),
135
+ nextFollowUpAt: timestampSchema.optional(),
136
+ createdAt: timestampSchema,
137
+ updatedAt: timestampSchema,
138
+ });
139
+
140
+ // Contact creation schema (without id, timestamps)
141
+ export const createContactSchema = contactSchema.omit({
142
+ id: true,
143
+ createdAt: true,
144
+ updatedAt: true,
145
+ });
146
+
147
+ // Contact update schema (partial, with required id)
148
+ export const updateContactSchema = contactSchema.partial().required({ id: true });
149
+
150
+ // ============================================================================
151
+ // Deal Schemas
152
+ // ============================================================================
153
+
154
+ export const dealStageSchema = z.enum([
155
+ 'prospecting',
156
+ 'qualification',
157
+ 'needs_analysis',
158
+ 'proposal',
159
+ 'negotiation',
160
+ 'closed_won',
161
+ 'closed_lost',
162
+ ]);
163
+
164
+ export const dealPrioritySchema = z.enum(['low', 'medium', 'high', 'urgent']);
165
+
166
+ export const currencySchema = z.enum([
167
+ 'USD',
168
+ 'EUR',
169
+ 'GBP',
170
+ 'CAD',
171
+ 'AUD',
172
+ 'JPY',
173
+ 'CNY',
174
+ 'INR',
175
+ 'BRL',
176
+ 'MXN',
177
+ ]);
178
+
179
+ export const discountTypeSchema = z.enum(['percentage', 'fixed']);
180
+
181
+ export const dealLineItemSchema = z.object({
182
+ id: uuidSchema,
183
+ name: z.string().min(1).max(200),
184
+ description: z.string().max(1000).optional(),
185
+ quantity: z.number().int().positive(),
186
+ unitPrice: z.number().nonnegative(),
187
+ discount: z.number().nonnegative().optional(),
188
+ discountType: discountTypeSchema.optional(),
189
+ total: z.number().nonnegative(),
190
+ });
191
+
192
+ export const competitorSchema = z.object({
193
+ name: z.string().min(1).max(200),
194
+ strengths: z.array(z.string()).optional(),
195
+ weaknesses: z.array(z.string()).optional(),
196
+ notes: z.string().max(2000).optional(),
197
+ });
198
+
199
+ export const dealSchema = z.object({
200
+ id: uuidSchema,
201
+ title: z.string().min(1).max(500),
202
+ contactId: uuidSchema,
203
+ companyId: uuidSchema.optional(),
204
+ value: z.number().nonnegative(),
205
+ currency: currencySchema.default('USD'),
206
+ stage: dealStageSchema.default('prospecting'),
207
+ probability: z.number().int().min(0).max(100).default(0),
208
+ expectedClose: timestampSchema,
209
+ actualClose: timestampSchema.optional(),
210
+ priority: dealPrioritySchema.default('medium'),
211
+ lineItems: z.array(dealLineItemSchema).default([]),
212
+ discount: z.number().nonnegative().optional(),
213
+ discountType: discountTypeSchema.optional(),
214
+ totalValue: z.number().nonnegative(),
215
+ notes: z.string().max(10000).default(''),
216
+ tags: z.array(z.string().max(100)).default([]),
217
+ competitors: z.array(competitorSchema).default([]),
218
+ lossReason: z.string().max(1000).optional(),
219
+ nextSteps: z.array(z.string()).optional(),
220
+ ownerId: uuidSchema.optional(),
221
+ source: z.string().max(200).optional(),
222
+ customFields: z.array(customFieldSchema).default([]),
223
+ lastActivityAt: timestampSchema.optional(),
224
+ createdAt: timestampSchema,
225
+ updatedAt: timestampSchema,
226
+ });
227
+
228
+ export const createDealSchema = dealSchema.omit({
229
+ id: true,
230
+ createdAt: true,
231
+ updatedAt: true,
232
+ });
233
+
234
+ export const updateDealSchema = dealSchema.partial().required({ id: true });
235
+
236
+ // ============================================================================
237
+ // Activity Schemas
238
+ // ============================================================================
239
+
240
+ export const activityTypeSchema = z.enum([
241
+ 'call',
242
+ 'email',
243
+ 'meeting',
244
+ 'task',
245
+ 'note',
246
+ 'sms',
247
+ 'video_call',
248
+ 'demo',
249
+ 'proposal_sent',
250
+ 'contract_sent',
251
+ 'follow_up',
252
+ 'social_media',
253
+ 'event',
254
+ 'other',
255
+ ]);
256
+
257
+ export const callOutcomeSchema = z.enum([
258
+ 'connected',
259
+ 'voicemail',
260
+ 'no_answer',
261
+ 'busy',
262
+ 'wrong_number',
263
+ 'disconnected',
264
+ ]);
265
+
266
+ export const meetingOutcomeSchema = z.enum([
267
+ 'completed',
268
+ 'rescheduled',
269
+ 'cancelled',
270
+ 'no_show',
271
+ ]);
272
+
273
+ export const emailStatusSchema = z.enum([
274
+ 'sent',
275
+ 'delivered',
276
+ 'opened',
277
+ 'clicked',
278
+ 'replied',
279
+ 'bounced',
280
+ 'unsubscribed',
281
+ ]);
282
+
283
+ export const taskStatusSchema = z.enum([
284
+ 'pending',
285
+ 'in_progress',
286
+ 'completed',
287
+ 'cancelled',
288
+ ]);
289
+
290
+ export const taskPrioritySchema = z.enum(['low', 'medium', 'high']);
291
+
292
+ export const callDirectionSchema = z.enum(['inbound', 'outbound']);
293
+
294
+ export const callMetadataSchema = z.object({
295
+ outcome: callOutcomeSchema,
296
+ recording: z.string().optional(),
297
+ transcription: z.string().optional(),
298
+ fromNumber: z.string().max(50).optional(),
299
+ toNumber: z.string().max(50).optional(),
300
+ direction: callDirectionSchema,
301
+ });
302
+
303
+ export const meetingMetadataSchema = z.object({
304
+ outcome: meetingOutcomeSchema,
305
+ location: z.string().max(500).optional(),
306
+ meetingUrl: z.string().url().optional(),
307
+ calendarEventId: z.string().optional(),
308
+ attendees: z.array(z.string()).optional(),
309
+ recording: z.string().optional(),
310
+ notes: z.string().max(10000).optional(),
311
+ });
312
+
313
+ export const emailMetadataSchema = z.object({
314
+ status: emailStatusSchema,
315
+ subject: z.string().min(1).max(500),
316
+ from: z.string().email(),
317
+ to: z.array(z.string().email()).min(1),
318
+ cc: z.array(z.string().email()).optional(),
319
+ bcc: z.array(z.string().email()).optional(),
320
+ templateId: z.string().optional(),
321
+ trackingOpens: z.boolean().default(true),
322
+ trackingClicks: z.boolean().default(true),
323
+ });
324
+
325
+ export const checklistItemSchema = z.object({
326
+ item: z.string().min(1).max(500),
327
+ completed: z.boolean().default(false),
328
+ });
329
+
330
+ export const taskMetadataSchema = z.object({
331
+ status: taskStatusSchema,
332
+ dueDate: timestampSchema.optional(),
333
+ reminderAt: timestampSchema.optional(),
334
+ priority: taskPrioritySchema,
335
+ assigneeId: uuidSchema.optional(),
336
+ checklist: z.array(checklistItemSchema).optional(),
337
+ });
338
+
339
+ // Discriminated union for activity metadata
340
+ export const activityMetadataSchema = z.discriminatedUnion('type', [
341
+ z.object({ type: z.literal('call'), ...callMetadataSchema.shape }),
342
+ z.object({ type: z.literal('meeting'), ...meetingMetadataSchema.shape }),
343
+ z.object({ type: z.literal('email'), ...emailMetadataSchema.shape }),
344
+ z.object({ type: z.literal('task'), ...taskMetadataSchema.shape }),
345
+ ]).or(metadataSchema);
346
+
347
+ export const activitySchema = z.object({
348
+ id: uuidSchema,
349
+ contactId: uuidSchema.optional(),
350
+ dealId: uuidSchema.optional(),
351
+ type: activityTypeSchema,
352
+ title: z.string().min(1).max(500),
353
+ description: z.string().max(10000).default(''),
354
+ timestamp: timestampSchema,
355
+ duration: z.number().int().nonnegative().optional(),
356
+ metadata: metadataSchema,
357
+ createdBy: uuidSchema.optional(),
358
+ tags: z.array(z.string().max(100)).default([]),
359
+ customFields: z.array(customFieldSchema).default([]),
360
+ createdAt: timestampSchema,
361
+ updatedAt: timestampSchema,
362
+ });
363
+
364
+ export const createActivitySchema = activitySchema.omit({
365
+ id: true,
366
+ createdAt: true,
367
+ updatedAt: true,
368
+ });
369
+
370
+ export const updateActivitySchema = activitySchema.partial().required({ id: true });
371
+
372
+ // ============================================================================
373
+ // Media Schemas
374
+ // ============================================================================
375
+
376
+ export const mediaTypeSchema = z.enum([
377
+ 'image',
378
+ 'video',
379
+ 'audio',
380
+ 'document',
381
+ 'spreadsheet',
382
+ 'presentation',
383
+ 'pdf',
384
+ 'archive',
385
+ 'other',
386
+ ]);
387
+
388
+ export const mimeTypeSchema = z.string().max(200);
389
+
390
+ export const mediaEntityTypeSchema = z.enum([
391
+ 'contact',
392
+ 'deal',
393
+ 'activity',
394
+ 'note',
395
+ 'company',
396
+ ]);
397
+
398
+ export const imageMetadataSchema = z.object({
399
+ width: z.number().int().positive().optional(),
400
+ height: z.number().int().positive().optional(),
401
+ orientation: z.enum(['landscape', 'portrait', 'square']).optional(),
402
+ colorSpace: z.string().max(50).optional(),
403
+ hasAlpha: z.boolean().optional(),
404
+ });
405
+
406
+ export const videoMetadataSchema = z.object({
407
+ width: z.number().int().positive().optional(),
408
+ height: z.number().int().positive().optional(),
409
+ duration: z.number().nonnegative().optional(),
410
+ frameRate: z.number().positive().optional(),
411
+ codec: z.string().max(50).optional(),
412
+ bitrate: z.number().nonnegative().optional(),
413
+ hasAudio: z.boolean().optional(),
414
+ });
415
+
416
+ export const audioMetadataSchema = z.object({
417
+ duration: z.number().nonnegative().optional(),
418
+ sampleRate: z.number().positive().optional(),
419
+ channels: z.number().int().positive().optional(),
420
+ bitrate: z.number().nonnegative().optional(),
421
+ codec: z.string().max(50).optional(),
422
+ });
423
+
424
+ export const documentMetadataSchema = z.object({
425
+ pageCount: z.number().int().positive().optional(),
426
+ author: z.string().max(200).optional(),
427
+ title: z.string().max(500).optional(),
428
+ subject: z.string().max(500).optional(),
429
+ keywords: z.array(z.string()).optional(),
430
+ createdAt: timestampSchema.optional(),
431
+ modifiedAt: timestampSchema.optional(),
432
+ });
433
+
434
+ export const mediaSchema = z.object({
435
+ id: uuidSchema,
436
+ entityType: mediaEntityTypeSchema,
437
+ entityId: uuidSchema,
438
+ type: mediaTypeSchema,
439
+ filename: z.string().min(1).max(500),
440
+ mimeType: mimeTypeSchema,
441
+ size: z.number().int().nonnegative(),
442
+ url: z.string().min(1).max(2000),
443
+ thumbnailUrl: z.string().max(2000).optional(),
444
+ metadata: metadataSchema,
445
+ altText: z.string().max(500).optional(),
446
+ caption: z.string().max(1000).optional(),
447
+ isPublic: z.boolean().default(false),
448
+ downloadCount: z.number().int().nonnegative().default(0),
449
+ uploadedBy: uuidSchema.optional(),
450
+ expiresAt: timestampSchema.optional(),
451
+ createdAt: timestampSchema,
452
+ });
453
+
454
+ export const createMediaSchema = mediaSchema.omit({
455
+ id: true,
456
+ createdAt: true,
457
+ downloadCount: true,
458
+ });
459
+
460
+ export const updateMediaSchema = mediaSchema.partial().required({ id: true });
461
+
462
+ // ============================================================================
463
+ // Note Schemas
464
+ // ============================================================================
465
+
466
+ export const noteVisibilitySchema = z.enum(['private', 'team', 'public']);
467
+
468
+ export const noteFormatSchema = z.enum(['plain', 'markdown', 'html']);
469
+
470
+ export const noteSchema = z.object({
471
+ id: uuidSchema,
472
+ contactId: uuidSchema.optional(),
473
+ dealId: uuidSchema.optional(),
474
+ activityId: uuidSchema.optional(),
475
+ content: z.string().min(1).max(100000),
476
+ format: noteFormatSchema.default('markdown'),
477
+ title: z.string().max(500).optional(),
478
+ visibility: noteVisibilitySchema.default('team'),
479
+ createdBy: uuidSchema.optional(),
480
+ pinned: z.boolean().default(false),
481
+ tags: z.array(z.string().max(100)).default([]),
482
+ mediaIds: z.array(uuidSchema).default([]),
483
+ createdAt: timestampSchema,
484
+ updatedAt: timestampSchema,
485
+ });
486
+
487
+ export const createNoteSchema = noteSchema.omit({
488
+ id: true,
489
+ createdAt: true,
490
+ updatedAt: true,
491
+ });
492
+
493
+ export const updateNoteSchema = noteSchema.partial().required({ id: true });
494
+
495
+ // ============================================================================
496
+ // Tag Schemas
497
+ // ============================================================================
498
+
499
+ export const tagCategorySchema = z.enum([
500
+ 'general',
501
+ 'industry',
502
+ 'source',
503
+ 'status',
504
+ 'priority',
505
+ 'product',
506
+ 'region',
507
+ 'custom',
508
+ ]);
509
+
510
+ export const tagSchema = z.object({
511
+ id: uuidSchema,
512
+ name: z.string().min(1).max(100).regex(/^[a-z0-9-]+$/, 'Tag name must be lowercase alphanumeric with hyphens'),
513
+ label: z.string().min(1).max(100),
514
+ color: z.string().max(20).regex(/^#?[0-9a-fA-F]{3,6}$|^[a-zA-Z]+$/, 'Invalid color format'),
515
+ category: tagCategorySchema.default('general'),
516
+ description: z.string().max(500).optional(),
517
+ icon: z.string().max(50).optional(),
518
+ usageCount: z.number().int().nonnegative().default(0),
519
+ parentId: uuidSchema.optional(),
520
+ createdAt: timestampSchema,
521
+ updatedAt: timestampSchema,
522
+ });
523
+
524
+ export const createTagSchema = tagSchema.omit({
525
+ id: true,
526
+ usageCount: true,
527
+ createdAt: true,
528
+ updatedAt: true,
529
+ });
530
+
531
+ export const updateTagSchema = tagSchema.partial().required({ id: true });
532
+
533
+ // ============================================================================
534
+ // Company Schemas
535
+ // ============================================================================
536
+
537
+ export const companySizeSchema = z.enum([
538
+ 'sole_proprietor',
539
+ 'startup',
540
+ 'small',
541
+ 'medium',
542
+ 'large',
543
+ 'enterprise',
544
+ ]);
545
+
546
+ export const industrySchema = z.enum([
547
+ 'technology',
548
+ 'finance',
549
+ 'healthcare',
550
+ 'education',
551
+ 'retail',
552
+ 'manufacturing',
553
+ 'consulting',
554
+ 'marketing',
555
+ 'legal',
556
+ 'real_estate',
557
+ 'construction',
558
+ 'transportation',
559
+ 'hospitality',
560
+ 'energy',
561
+ 'telecommunications',
562
+ 'government',
563
+ 'non_profit',
564
+ 'other',
565
+ ]);
566
+
567
+ export const companySchema = z.object({
568
+ id: uuidSchema,
569
+ name: z.string().min(1).max(200),
570
+ website: z.string().url().optional(),
571
+ industry: industrySchema.optional(),
572
+ size: companySizeSchema.optional(),
573
+ employeeCount: z.number().int().nonnegative().optional(),
574
+ annualRevenue: z.number().nonnegative().optional(),
575
+ currency: currencySchema.optional(),
576
+ address: addressSchema.optional(),
577
+ phone: z.string().max(50).optional(),
578
+ emailDomain: z.string().max(200).optional(),
579
+ linkedInUrl: z.string().url().optional(),
580
+ tags: z.array(z.string().max(100)).default([]),
581
+ customFields: z.array(customFieldSchema).default([]),
582
+ ownerId: uuidSchema.optional(),
583
+ notes: z.string().max(10000).optional(),
584
+ createdAt: timestampSchema,
585
+ updatedAt: timestampSchema,
586
+ });
587
+
588
+ export const createCompanySchema = companySchema.omit({
589
+ id: true,
590
+ createdAt: true,
591
+ updatedAt: true,
592
+ });
593
+
594
+ export const updateCompanySchema = companySchema.partial().required({ id: true });
595
+
596
+ // ============================================================================
597
+ // Pipeline Schemas
598
+ // ============================================================================
599
+
600
+ export const pipelineStageSchema = z.object({
601
+ id: uuidSchema,
602
+ name: z.string().min(1).max(100),
603
+ order: z.number().int().nonnegative(),
604
+ probability: z.number().int().min(0).max(100),
605
+ color: z.string().max(20),
606
+ description: z.string().max(500).optional(),
607
+ });
608
+
609
+ export const pipelineSchema = z.object({
610
+ id: uuidSchema,
611
+ name: z.string().min(1).max(200),
612
+ description: z.string().max(1000).optional(),
613
+ stages: z.array(pipelineStageSchema).min(1),
614
+ isDefault: z.boolean().default(false),
615
+ createdAt: timestampSchema,
616
+ updatedAt: timestampSchema,
617
+ });
618
+
619
+ // ============================================================================
620
+ // Filter Schemas
621
+ // ============================================================================
622
+
623
+ export const sortDirectionSchema = z.enum(['asc', 'desc']);
624
+
625
+ export const paginationSchema = z.object({
626
+ page: z.number().int().positive().default(1),
627
+ limit: z.number().int().min(1).max(100).default(20),
628
+ offset: z.number().int().nonnegative().optional(),
629
+ });
630
+
631
+ export const dateRangeSchema = z.object({
632
+ start: timestampSchema,
633
+ end: timestampSchema,
634
+ }).refine(
635
+ (data) => new Date(data.start) <= new Date(data.end),
636
+ { message: 'Start date must be before or equal to end date' }
637
+ );
638
+
639
+ export const contactFilterSchema = z.object({
640
+ search: z.string().max(500).optional(),
641
+ status: z.array(contactStatusSchema).optional(),
642
+ tags: z.array(z.string()).optional(),
643
+ source: z.array(contactSourceSchema).optional(),
644
+ ownerId: uuidSchema.optional(),
645
+ companyId: uuidSchema.optional(),
646
+ createdAt: dateRangeSchema.optional(),
647
+ updatedAt: dateRangeSchema.optional(),
648
+ lastContactedAt: dateRangeSchema.optional(),
649
+ nextFollowUpAt: dateRangeSchema.optional(),
650
+ hasEmail: z.boolean().optional(),
651
+ hasPhone: z.boolean().optional(),
652
+ leadScoreMin: z.number().int().min(0).max(100).optional(),
653
+ leadScoreMax: z.number().int().min(0).max(100).optional(),
654
+ doNotContact: z.boolean().optional(),
655
+ });
656
+
657
+ export const dealFilterSchema = z.object({
658
+ search: z.string().max(500).optional(),
659
+ contactId: uuidSchema.optional(),
660
+ companyId: uuidSchema.optional(),
661
+ stage: z.array(dealStageSchema).optional(),
662
+ priority: z.array(dealPrioritySchema).optional(),
663
+ ownerId: uuidSchema.optional(),
664
+ tags: z.array(z.string()).optional(),
665
+ createdAt: dateRangeSchema.optional(),
666
+ updatedAt: dateRangeSchema.optional(),
667
+ expectedClose: dateRangeSchema.optional(),
668
+ valueMin: z.number().nonnegative().optional(),
669
+ valueMax: z.number().nonnegative().optional(),
670
+ probabilityMin: z.number().int().min(0).max(100).optional(),
671
+ probabilityMax: z.number().int().min(0).max(100).optional(),
672
+ });
673
+
674
+ export const activityFilterSchema = z.object({
675
+ contactId: uuidSchema.optional(),
676
+ dealId: uuidSchema.optional(),
677
+ type: z.array(activityTypeSchema).optional(),
678
+ createdBy: uuidSchema.optional(),
679
+ timestamp: dateRangeSchema.optional(),
680
+ tags: z.array(z.string()).optional(),
681
+ });
682
+
683
+ // ============================================================================
684
+ // Paginated Response Schema
685
+ // ============================================================================
686
+
687
+ export function paginatedResponseSchema<T extends z.ZodTypeAny>(itemSchema: T) {
688
+ return z.object({
689
+ data: z.array(itemSchema),
690
+ total: z.number().int().nonnegative(),
691
+ page: z.number().int().positive(),
692
+ limit: z.number().int().positive(),
693
+ totalPages: z.number().int().nonnegative(),
694
+ hasMore: z.boolean(),
695
+ });
696
+ }
697
+
698
+ // ============================================================================
699
+ // Export & Integration Schemas
700
+ // ============================================================================
701
+
702
+ export const exportFormatSchema = z.enum(['csv', 'xlsx', 'json', 'pdf']);
703
+
704
+ export const exportStatusSchema = z.enum(['pending', 'processing', 'completed', 'failed']);
705
+
706
+ export const exportJobSchema = z.object({
707
+ id: uuidSchema,
708
+ entityType: z.enum(['contacts', 'deals', 'activities', 'notes']),
709
+ format: exportFormatSchema,
710
+ status: exportStatusSchema,
711
+ filters: z.record(z.unknown()).optional(),
712
+ fields: z.array(z.string()).optional(),
713
+ downloadUrl: z.string().url().optional(),
714
+ error: z.string().optional(),
715
+ createdAt: timestampSchema,
716
+ completedAt: timestampSchema.optional(),
717
+ createdBy: uuidSchema,
718
+ });
719
+
720
+ export const integrationProviderSchema = z.enum([
721
+ 'google',
722
+ 'microsoft',
723
+ 'slack',
724
+ 'mailchimp',
725
+ 'hubspot',
726
+ 'salesforce',
727
+ 'zapier',
728
+ 'custom',
729
+ ]);
730
+
731
+ export const integrationStatusSchema = z.enum(['connected', 'disconnected', 'error', 'pending']);
732
+
733
+ export const integrationSchema = z.object({
734
+ id: uuidSchema,
735
+ provider: integrationProviderSchema,
736
+ name: z.string().min(1).max(200),
737
+ status: integrationStatusSchema,
738
+ configuredAt: timestampSchema,
739
+ lastSyncAt: timestampSchema.optional(),
740
+ settings: metadataSchema,
741
+ error: z.string().optional(),
742
+ });
743
+
744
+ // ============================================================================
745
+ // Telemetry Schemas
746
+ // ============================================================================
747
+
748
+ export const telemetryEventTypeSchema = z.enum([
749
+ 'entity_created',
750
+ 'entity_updated',
751
+ 'entity_deleted',
752
+ 'entity_viewed',
753
+ 'search_performed',
754
+ 'export_created',
755
+ 'integration_sync',
756
+ 'error_occurred',
757
+ ]);
758
+
759
+ export const telemetryEventSchema = z.object({
760
+ id: uuidSchema,
761
+ type: telemetryEventTypeSchema,
762
+ entityType: z.string().max(100).optional(),
763
+ entityId: uuidSchema.optional(),
764
+ userId: uuidSchema.optional(),
765
+ timestamp: timestampSchema,
766
+ properties: metadataSchema,
767
+ duration: z.number().nonnegative().optional(),
768
+ success: z.boolean(),
769
+ errorMessage: z.string().optional(),
770
+ });