@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.
- package/README.md +174 -0
- package/dist/cli/commands/activities.d.ts +11 -0
- package/dist/cli/commands/activities.d.ts.map +1 -0
- package/dist/cli/commands/activities.js +427 -0
- package/dist/cli/commands/activities.js.map +1 -0
- package/dist/cli/commands/contacts.d.ts +11 -0
- package/dist/cli/commands/contacts.d.ts.map +1 -0
- package/dist/cli/commands/contacts.js +458 -0
- package/dist/cli/commands/contacts.js.map +1 -0
- package/dist/cli/commands/deals.d.ts +11 -0
- package/dist/cli/commands/deals.d.ts.map +1 -0
- package/dist/cli/commands/deals.js +498 -0
- package/dist/cli/commands/deals.js.map +1 -0
- package/dist/cli/commands/media.d.ts +11 -0
- package/dist/cli/commands/media.d.ts.map +1 -0
- package/dist/cli/commands/media.js +417 -0
- package/dist/cli/commands/media.js.map +1 -0
- package/dist/cli/commands/search.d.ts +11 -0
- package/dist/cli/commands/search.d.ts.map +1 -0
- package/dist/cli/commands/search.js +346 -0
- package/dist/cli/commands/search.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +173 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/repl.d.ts +15 -0
- package/dist/cli/repl.d.ts.map +1 -0
- package/dist/cli/repl.js +318 -0
- package/dist/cli/repl.js.map +1 -0
- package/dist/cli/utils/config.d.ts +91 -0
- package/dist/cli/utils/config.d.ts.map +1 -0
- package/dist/cli/utils/config.js +212 -0
- package/dist/cli/utils/config.js.map +1 -0
- package/dist/cli/utils/output.d.ts +136 -0
- package/dist/cli/utils/output.d.ts.map +1 -0
- package/dist/cli/utils/output.js +323 -0
- package/dist/cli/utils/output.js.map +1 -0
- package/dist/cli/utils/prompt.d.ts +81 -0
- package/dist/cli/utils/prompt.d.ts.map +1 -0
- package/dist/cli/utils/prompt.js +341 -0
- package/dist/cli/utils/prompt.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +32 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/schemas.d.ts +3050 -0
- package/dist/core/schemas.d.ts.map +1 -0
- package/dist/core/schemas.js +667 -0
- package/dist/core/schemas.js.map +1 -0
- package/dist/core/types.d.ts +597 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +8 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +14 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +11 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +13 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +18 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/storage/client.d.ts +109 -0
- package/dist/mcp/storage/client.d.ts.map +1 -0
- package/dist/mcp/storage/client.js +355 -0
- package/dist/mcp/storage/client.js.map +1 -0
- package/dist/mcp/storage/index.d.ts +7 -0
- package/dist/mcp/storage/index.d.ts.map +1 -0
- package/dist/mcp/storage/index.js +6 -0
- package/dist/mcp/storage/index.js.map +1 -0
- package/dist/mcp/storage/types.d.ts +44 -0
- package/dist/mcp/storage/types.d.ts.map +1 -0
- package/dist/mcp/storage/types.js +35 -0
- package/dist/mcp/storage/types.js.map +1 -0
- package/dist/mcp/tools/definitions.d.ts +16 -0
- package/dist/mcp/tools/definitions.d.ts.map +1 -0
- package/dist/mcp/tools/definitions.js +914 -0
- package/dist/mcp/tools/definitions.js.map +1 -0
- package/dist/mcp/tools/handlers.d.ts +50 -0
- package/dist/mcp/tools/handlers.d.ts.map +1 -0
- package/dist/mcp/tools/handlers.js +760 -0
- package/dist/mcp/tools/handlers.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +7 -0
- package/dist/mcp/tools/index.d.ts.map +1 -0
- package/dist/mcp/tools/index.js +6 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mcp/tools/types.d.ts +314 -0
- package/dist/mcp/tools/types.d.ts.map +1 -0
- package/dist/mcp/tools/types.js +5 -0
- package/dist/mcp/tools/types.js.map +1 -0
- package/dist/mcp/transports/stdio.d.ts +27 -0
- package/dist/mcp/transports/stdio.d.ts.map +1 -0
- package/dist/mcp/transports/stdio.js +237 -0
- package/dist/mcp/transports/stdio.js.map +1 -0
- package/dist/telemetry/index.d.ts +58 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +109 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/logger.d.ts +116 -0
- package/dist/telemetry/logger.d.ts.map +1 -0
- package/dist/telemetry/logger.js +256 -0
- package/dist/telemetry/logger.js.map +1 -0
- package/dist/telemetry/metrics.d.ts +115 -0
- package/dist/telemetry/metrics.d.ts.map +1 -0
- package/dist/telemetry/metrics.js +292 -0
- package/dist/telemetry/metrics.js.map +1 -0
- package/dist/telemetry/tracer.d.ts +227 -0
- package/dist/telemetry/tracer.d.ts.map +1 -0
- package/dist/telemetry/tracer.js +355 -0
- package/dist/telemetry/tracer.js.map +1 -0
- package/dist/web/app.d.ts +2 -0
- package/dist/web/app.d.ts.map +1 -0
- package/dist/web/app.js +115 -0
- package/dist/web/app.js.map +1 -0
- package/dist/web/components/ContactList.d.ts +3 -0
- package/dist/web/components/ContactList.d.ts.map +1 -0
- package/dist/web/components/ContactList.js +262 -0
- package/dist/web/components/ContactList.js.map +1 -0
- package/dist/web/components/Dashboard.d.ts +3 -0
- package/dist/web/components/Dashboard.d.ts.map +1 -0
- package/dist/web/components/Dashboard.js +158 -0
- package/dist/web/components/Dashboard.js.map +1 -0
- package/dist/web/components/DealPipeline.d.ts +3 -0
- package/dist/web/components/DealPipeline.d.ts.map +1 -0
- package/dist/web/components/DealPipeline.js +306 -0
- package/dist/web/components/DealPipeline.js.map +1 -0
- package/dist/web/index.d.ts +2 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +269 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/types.d.ts +75 -0
- package/dist/web/types.d.ts.map +1 -0
- package/dist/web/types.js +3 -0
- package/dist/web/types.js.map +1 -0
- package/native/index.d.ts +571 -0
- package/native/index.js +687 -0
- package/package.json +105 -0
- package/src/cli/commands/activities.ts +543 -0
- package/src/cli/commands/contacts.ts +563 -0
- package/src/cli/commands/deals.ts +637 -0
- package/src/cli/commands/media.ts +521 -0
- package/src/cli/commands/search.ts +426 -0
- package/src/cli/index.ts +203 -0
- package/src/cli/repl.ts +379 -0
- package/src/cli/utils/config.ts +299 -0
- package/src/cli/utils/output.ts +386 -0
- package/src/cli/utils/prompt.ts +444 -0
- package/src/cli.ts +11 -0
- package/src/core/index.ts +184 -0
- package/src/core/schemas.ts +770 -0
- package/src/core/types.ts +969 -0
- package/src/index.ts +8 -0
- package/src/mcp/index.ts +17 -0
- package/src/mcp/server.ts +26 -0
- package/src/mcp/storage/client.ts +408 -0
- package/src/mcp/storage/index.ts +7 -0
- package/src/mcp/storage/types.ts +72 -0
- package/src/mcp/tools/definitions.ts +961 -0
- package/src/mcp/tools/handlers.ts +805 -0
- package/src/mcp/tools/index.ts +7 -0
- package/src/mcp/tools/types.ts +390 -0
- package/src/mcp/transports/stdio.ts +225 -0
- package/src/telemetry/index.ts +131 -0
- package/src/telemetry/logger.ts +318 -0
- package/src/telemetry/metrics.ts +393 -0
- package/src/telemetry/tracer.ts +487 -0
- package/src/web/api/activities.ts +41 -0
- package/src/web/api/contacts.ts +114 -0
- package/src/web/api/deals.ts +108 -0
- package/src/web/api/media.ts +98 -0
- package/src/web/app.tsx +143 -0
- package/src/web/components/ActivityFeed.tsx +195 -0
- package/src/web/components/ContactList.tsx +340 -0
- package/src/web/components/Dashboard.tsx +214 -0
- package/src/web/components/DealPipeline.tsx +405 -0
- package/src/web/components/MediaGallery.tsx +334 -0
- package/src/web/index.html +14 -0
- package/src/web/index.ts +326 -0
- package/src/web/styles/main.css +180 -0
- package/src/web/types.ts +311 -0
package/src/index.ts
ADDED
package/src/mcp/index.ts
ADDED
|
@@ -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,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
|
+
}
|