@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
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @ebowwa/crm-core
3
+ *
4
+ * Core types and schemas for CRM system
5
+ */
6
+
7
+ // Export all core types and schemas
8
+ export * from './core/index.js';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * MCP module exports
3
+ */
4
+
5
+ export { CRMStorageClient } from './storage/client.js';
6
+ export type { StorageConfig, EntityType, StorageStats } from './storage/types.js';
7
+ export { CRMError, ValidationError, NotFoundError, DuplicateError } from './storage/types.js';
8
+
9
+ export { ToolHandlers } from './tools/handlers.js';
10
+ export { TOOL_DEFINITIONS } from './tools/definitions.js';
11
+ export { getToolDefinition, listToolNames } from './tools/definitions.js';
12
+ export type { ToolInput, ToolOutput } from './tools/types.js';
13
+ export type * from './tools/types.js';
14
+
15
+ export { StdioServer } from './transports/stdio.js';
16
+
17
+ export { createServer } from './server.js';
@@ -0,0 +1,26 @@
1
+ /**
2
+ * CRM MCP Server factory
3
+ */
4
+
5
+ import { CRMStorageClient } from './storage/client.js';
6
+ import type { StorageConfig } from './storage/types.js';
7
+ import { StdioServer } from './transports/stdio.js';
8
+
9
+ /**
10
+ * Create and initialize a CRM MCP server
11
+ */
12
+ export async function createServer(options: {
13
+ dbPath: string;
14
+ mapSize?: number;
15
+ maxDbs?: number;
16
+ }): Promise<StdioServer> {
17
+ const storage = new CRMStorageClient({
18
+ path: options.dbPath,
19
+ mapSize: options.mapSize,
20
+ maxDbs: options.maxDbs,
21
+ });
22
+
23
+ await storage.initialize();
24
+
25
+ return new StdioServer(storage);
26
+ }
@@ -0,0 +1,408 @@
1
+ /**
2
+ * CRM Storage Client
3
+ * LMDB-based storage for CRM entities
4
+ */
5
+
6
+ import { open, Database, RootDatabase } from 'lmdb';
7
+ import type { UUID, Timestamp } from '../../core/types.js';
8
+ import type { StorageConfig, EntityType, StorageStats } from './types.js';
9
+ import { CRMError, NotFoundError, DuplicateError } from './types.js';
10
+
11
+ /**
12
+ * CRM Storage Client
13
+ * Provides persistent storage for CRM entities using LMDB
14
+ */
15
+ export class CRMStorageClient {
16
+ private db: RootDatabase;
17
+ private collections: Map<EntityType, Database> = new Map();
18
+ private indexPaths: Map<string, Database> = new Map();
19
+
20
+ constructor(private config: StorageConfig) {
21
+ this.db = open({
22
+ path: config.path,
23
+ mapSize: config.mapSize ?? 1024 * 1024 * 1024, // 1GB default
24
+ maxDbs: config.maxDbs ?? 20,
25
+ });
26
+ }
27
+
28
+ /**
29
+ * Initialize the storage client
30
+ */
31
+ async initialize(): Promise<void> {
32
+ // Create collection databases for each entity type
33
+ const entityTypes: EntityType[] = [
34
+ 'contacts', 'deals', 'activities', 'media', 'notes', 'tags', 'companies', 'pipelines'
35
+ ];
36
+
37
+ for (const type of entityTypes) {
38
+ const collection = this.db.openDB({ name: type });
39
+ this.collections.set(type, collection);
40
+ }
41
+
42
+ // Create index databases
43
+ const indexes = [
44
+ 'contacts_by_email',
45
+ 'contacts_by_company',
46
+ 'deals_by_contact',
47
+ 'deals_by_stage',
48
+ 'activities_by_contact',
49
+ 'activities_by_deal',
50
+ 'media_by_entity',
51
+ 'notes_by_contact',
52
+ 'notes_by_deal',
53
+ ];
54
+
55
+ for (const indexName of indexes) {
56
+ const index = this.db.openDB({ name: `index_${indexName}`, dupSort: true });
57
+ this.indexPaths.set(indexName, index);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Get collection database
63
+ */
64
+ getCollection(type: EntityType): Database {
65
+ const collection = this.collections.get(type);
66
+ if (!collection) {
67
+ throw new CRMError(`Collection not found: ${type}`, 'COLLECTION_NOT_FOUND');
68
+ }
69
+ return collection;
70
+ }
71
+
72
+ /**
73
+ * Generate a new UUID
74
+ */
75
+ generateId(): UUID {
76
+ return crypto.randomUUID();
77
+ }
78
+
79
+ /**
80
+ * Get current timestamp
81
+ */
82
+ getTimestamp(): Timestamp {
83
+ return new Date().toISOString();
84
+ }
85
+
86
+ /**
87
+ * Insert an entity
88
+ */
89
+ async insert<T extends { id: UUID; createdAt: Timestamp; updatedAt: Timestamp }>(
90
+ type: EntityType,
91
+ entity: Omit<T, 'id' | 'createdAt' | 'updatedAt'>
92
+ ): Promise<T> {
93
+ const collection = this.getCollection(type);
94
+ const id = this.generateId();
95
+ const now = this.getTimestamp();
96
+
97
+ const fullEntity = {
98
+ ...entity,
99
+ id,
100
+ createdAt: now,
101
+ updatedAt: now,
102
+ } as T;
103
+
104
+ await collection.put(id, fullEntity);
105
+
106
+ // Update indexes
107
+ await this.updateIndexes(type, id, fullEntity);
108
+
109
+ return fullEntity;
110
+ }
111
+
112
+ /**
113
+ * Insert with specific ID
114
+ */
115
+ async insertWithId<T extends { id: UUID; createdAt: Timestamp; updatedAt: Timestamp }>(
116
+ type: EntityType,
117
+ entity: T
118
+ ): Promise<T> {
119
+ const collection = this.getCollection(type);
120
+
121
+ // Check for duplicate
122
+ const existing = collection.get(entity.id);
123
+ if (existing) {
124
+ throw new DuplicateError(`Entity with id ${entity.id} already exists`);
125
+ }
126
+
127
+ const now = this.getTimestamp();
128
+ const fullEntity = {
129
+ ...entity,
130
+ createdAt: entity.createdAt ?? now,
131
+ updatedAt: now,
132
+ } as T;
133
+
134
+ await collection.put(entity.id, fullEntity);
135
+ await this.updateIndexes(type, entity.id, fullEntity);
136
+
137
+ return fullEntity;
138
+ }
139
+
140
+ /**
141
+ * Get an entity by ID
142
+ */
143
+ get<T>(type: EntityType, id: UUID): T | null {
144
+ const collection = this.getCollection(type);
145
+ return collection.get(id) ?? null;
146
+ }
147
+
148
+ /**
149
+ * Update an entity
150
+ */
151
+ async update<T extends { id: UUID; updatedAt: Timestamp }>(
152
+ type: EntityType,
153
+ id: UUID,
154
+ updates: Partial<T>
155
+ ): Promise<T> {
156
+ const collection = this.getCollection(type);
157
+ const existing = collection.get(id);
158
+
159
+ if (!existing) {
160
+ throw new NotFoundError(`Entity with id ${id} not found`);
161
+ }
162
+
163
+ const updated = {
164
+ ...existing,
165
+ ...updates,
166
+ id, // Ensure ID is not changed
167
+ updatedAt: this.getTimestamp(),
168
+ } as T;
169
+
170
+ await collection.put(id, updated);
171
+
172
+ // Update indexes
173
+ await this.updateIndexes(type, id, updated);
174
+
175
+ return updated;
176
+ }
177
+
178
+ /**
179
+ * Delete an entity
180
+ */
181
+ async delete(type: EntityType, id: UUID): Promise<boolean> {
182
+ const collection = this.getCollection(type);
183
+ const existing = collection.get(id);
184
+
185
+ if (!existing) {
186
+ return false;
187
+ }
188
+
189
+ // Remove from indexes
190
+ await this.removeFromIndexes(type, id, existing);
191
+
192
+ return collection.remove(id);
193
+ }
194
+
195
+ /**
196
+ * Check if entity exists
197
+ */
198
+ exists(type: EntityType, id: UUID): boolean {
199
+ const collection = this.getCollection(type);
200
+ return collection.doesExist(id);
201
+ }
202
+
203
+ /**
204
+ * List entities with pagination
205
+ */
206
+ list<T>(type: EntityType, options?: { limit?: number; offset?: number }): T[] {
207
+ const collection = this.getCollection(type);
208
+ const results: T[] = [];
209
+ const limit = options?.limit ?? 100;
210
+ const offset = options?.offset ?? 0;
211
+
212
+ let count = 0;
213
+ for (const entry of collection.getRange()) {
214
+ if (count >= offset && count < offset + limit) {
215
+ results.push(entry.value as T);
216
+ }
217
+ count++;
218
+ if (results.length >= limit) break;
219
+ }
220
+
221
+ return results;
222
+ }
223
+
224
+ /**
225
+ * Count entities in collection
226
+ */
227
+ count(type: EntityType): number {
228
+ const collection = this.getCollection(type);
229
+ return collection.getCount();
230
+ }
231
+
232
+ /**
233
+ * Find entities by index
234
+ */
235
+ findByIndex<T>(indexName: string, key: string): T[] {
236
+ const index = this.indexPaths.get(indexName);
237
+ if (!index) {
238
+ throw new CRMError(`Index not found: ${indexName}`, 'INDEX_NOT_FOUND');
239
+ }
240
+
241
+ const entityIds = index.getValues(key);
242
+ const results: T[] = [];
243
+
244
+ // Determine collection type from index name
245
+ const collectionType = this.getCollectionTypeFromIndex(indexName);
246
+ const collection = this.getCollection(collectionType);
247
+
248
+ for (const id of entityIds) {
249
+ const entity = collection.get(id as string);
250
+ if (entity) {
251
+ results.push(entity as T);
252
+ }
253
+ }
254
+
255
+ return results;
256
+ }
257
+
258
+ /**
259
+ * Search entities by field value
260
+ */
261
+ search<T>(type: EntityType, field: string, value: unknown): T[] {
262
+ const collection = this.getCollection(type);
263
+ const results: T[] = [];
264
+
265
+ for (const entry of collection.getRange()) {
266
+ const entity = entry.value as Record<string, unknown>;
267
+ if (entity[field] === value) {
268
+ results.push(entity as T);
269
+ }
270
+ }
271
+
272
+ return results;
273
+ }
274
+
275
+ /**
276
+ * Get storage statistics
277
+ */
278
+ getStats(): StorageStats {
279
+ return {
280
+ contacts: this.count('contacts'),
281
+ deals: this.count('deals'),
282
+ activities: this.count('activities'),
283
+ media: this.count('media'),
284
+ notes: this.count('notes'),
285
+ tags: this.count('tags'),
286
+ companies: this.count('companies'),
287
+ pipelines: this.count('pipelines'),
288
+ };
289
+ }
290
+
291
+ /**
292
+ * Close the database connection
293
+ */
294
+ async close(): Promise<void> {
295
+ await this.db.close();
296
+ }
297
+
298
+ /**
299
+ * Update indexes for an entity
300
+ */
301
+ private async updateIndexes(type: EntityType, id: UUID, entity: unknown): Promise<void> {
302
+ const record = entity as Record<string, unknown>;
303
+
304
+ switch (type) {
305
+ case 'contacts': {
306
+ // Index by emails
307
+ const emails = record.emails as Array<{ email: string }> | undefined;
308
+ if (emails) {
309
+ const index = this.indexPaths.get('contacts_by_email');
310
+ if (index) {
311
+ for (const emailObj of emails) {
312
+ await index.put(emailObj.email.toLowerCase(), id);
313
+ }
314
+ }
315
+ }
316
+ // Index by company
317
+ if (record.companyId) {
318
+ const index = this.indexPaths.get('contacts_by_company');
319
+ if (index) {
320
+ await index.put(record.companyId as string, id);
321
+ }
322
+ }
323
+ break;
324
+ }
325
+ case 'deals': {
326
+ // Index by contact
327
+ if (record.contactId) {
328
+ const index = this.indexPaths.get('deals_by_contact');
329
+ if (index) {
330
+ await index.put(record.contactId as string, id);
331
+ }
332
+ }
333
+ // Index by stage
334
+ if (record.stage) {
335
+ const index = this.indexPaths.get('deals_by_stage');
336
+ if (index) {
337
+ await index.put(record.stage as string, id);
338
+ }
339
+ }
340
+ break;
341
+ }
342
+ case 'activities': {
343
+ // Index by contact
344
+ if (record.contactId) {
345
+ const index = this.indexPaths.get('activities_by_contact');
346
+ if (index) {
347
+ await index.put(record.contactId as string, id);
348
+ }
349
+ }
350
+ // Index by deal
351
+ if (record.dealId) {
352
+ const index = this.indexPaths.get('activities_by_deal');
353
+ if (index) {
354
+ await index.put(record.dealId as string, id);
355
+ }
356
+ }
357
+ break;
358
+ }
359
+ case 'media': {
360
+ // Index by entity
361
+ if (record.entityId) {
362
+ const index = this.indexPaths.get('media_by_entity');
363
+ if (index) {
364
+ await index.put(record.entityId as string, id);
365
+ }
366
+ }
367
+ break;
368
+ }
369
+ case 'notes': {
370
+ // Index by contact
371
+ if (record.contactId) {
372
+ const index = this.indexPaths.get('notes_by_contact');
373
+ if (index) {
374
+ await index.put(record.contactId as string, id);
375
+ }
376
+ }
377
+ // Index by deal
378
+ if (record.dealId) {
379
+ const index = this.indexPaths.get('notes_by_deal');
380
+ if (index) {
381
+ await index.put(record.dealId as string, id);
382
+ }
383
+ }
384
+ break;
385
+ }
386
+ }
387
+ }
388
+
389
+ /**
390
+ * Remove entity from indexes
391
+ */
392
+ private async removeFromIndexes(type: EntityType, id: UUID, entity: unknown): Promise<void> {
393
+ // For now, indexes remain - a production system would clean these up
394
+ // This is a simplification for the initial implementation
395
+ }
396
+
397
+ /**
398
+ * Get collection type from index name
399
+ */
400
+ private getCollectionTypeFromIndex(indexName: string): EntityType {
401
+ if (indexName.startsWith('contacts_')) return 'contacts';
402
+ if (indexName.startsWith('deals_')) return 'deals';
403
+ if (indexName.startsWith('activities_')) return 'activities';
404
+ if (indexName.startsWith('media_')) return 'media';
405
+ if (indexName.startsWith('notes_')) return 'notes';
406
+ throw new CRMError(`Unknown index: ${indexName}`, 'UNKNOWN_INDEX');
407
+ }
408
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Storage module exports
3
+ */
4
+
5
+ export { CRMStorageClient } from './client.js';
6
+ export type { StorageConfig, EntityType, StorageStats } from './types.js';
7
+ export { CRMError, ValidationError, NotFoundError, DuplicateError } from './types.js';
@@ -0,0 +1,72 @@
1
+ /**
2
+ * CRM Storage Types
3
+ * Error types and configuration for the CRM storage layer
4
+ */
5
+
6
+ /** Base CRM error class */
7
+ export class CRMError extends Error {
8
+ constructor(
9
+ message: string,
10
+ public code: string
11
+ ) {
12
+ super(message);
13
+ this.name = 'CRMError';
14
+ }
15
+ }
16
+
17
+ /** Validation error */
18
+ export class ValidationError extends CRMError {
19
+ constructor(message: string) {
20
+ super(message, 'VALIDATION_ERROR');
21
+ this.name = 'ValidationError';
22
+ }
23
+ }
24
+
25
+ /** Not found error */
26
+ export class NotFoundError extends CRMError {
27
+ constructor(message: string) {
28
+ super(message, 'NOT_FOUND');
29
+ this.name = 'NotFoundError';
30
+ }
31
+ }
32
+
33
+ /** Duplicate error */
34
+ export class DuplicateError extends CRMError {
35
+ constructor(message: string) {
36
+ super(message, 'DUPLICATE');
37
+ this.name = 'DuplicateError';
38
+ }
39
+ }
40
+
41
+ /** Storage configuration */
42
+ export interface StorageConfig {
43
+ /** Path to the database file */
44
+ path: string;
45
+ /** Map size in bytes (default: 1GB) */
46
+ mapSize?: number;
47
+ /** Maximum number of databases (default: 20) */
48
+ maxDbs?: number;
49
+ }
50
+
51
+ /** Entity type for storage operations */
52
+ export type EntityType =
53
+ | 'contacts'
54
+ | 'deals'
55
+ | 'activities'
56
+ | 'media'
57
+ | 'notes'
58
+ | 'tags'
59
+ | 'companies'
60
+ | 'pipelines';
61
+
62
+ /** Storage statistics */
63
+ export interface StorageStats {
64
+ contacts: number;
65
+ deals: number;
66
+ activities: number;
67
+ media: number;
68
+ notes: number;
69
+ tags: number;
70
+ companies: number;
71
+ pipelines: number;
72
+ }