@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
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracer module for CRM system
|
|
3
|
+
*
|
|
4
|
+
* Provides distributed tracing support with spans and trace context.
|
|
5
|
+
* Can export traces in OpenTelemetry-compatible format.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { randomUUID } from 'crypto';
|
|
9
|
+
|
|
10
|
+
export interface TraceContext {
|
|
11
|
+
traceId: string;
|
|
12
|
+
spanId: string;
|
|
13
|
+
parentSpanId?: string;
|
|
14
|
+
sampled: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SpanAttribute {
|
|
18
|
+
key: string;
|
|
19
|
+
value: string | number | boolean | string[] | number[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SpanEvent {
|
|
23
|
+
timestamp: number;
|
|
24
|
+
name: string;
|
|
25
|
+
attributes?: SpanAttribute[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SpanLink {
|
|
29
|
+
traceId: string;
|
|
30
|
+
spanId: string;
|
|
31
|
+
attributes?: SpanAttribute[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface SpanStatus {
|
|
35
|
+
code: 'ok' | 'error' | 'unset';
|
|
36
|
+
message?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface Span {
|
|
40
|
+
traceId: string;
|
|
41
|
+
spanId: string;
|
|
42
|
+
parentSpanId?: string;
|
|
43
|
+
name: string;
|
|
44
|
+
kind: 'internal' | 'server' | 'client' | 'producer' | 'consumer';
|
|
45
|
+
startTime: number;
|
|
46
|
+
endTime?: number;
|
|
47
|
+
duration?: number;
|
|
48
|
+
attributes: SpanAttribute[];
|
|
49
|
+
events: SpanEvent[];
|
|
50
|
+
links: SpanLink[];
|
|
51
|
+
status: SpanStatus;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface TracerConfig {
|
|
55
|
+
serviceName: string;
|
|
56
|
+
serviceVersion: string;
|
|
57
|
+
samplingRate: number; // 0.0 to 1.0
|
|
58
|
+
maxEventsPerSpan: number;
|
|
59
|
+
maxAttributesPerSpan: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Tracer for creating and managing spans
|
|
64
|
+
*/
|
|
65
|
+
export class Tracer {
|
|
66
|
+
private readonly config: TracerConfig;
|
|
67
|
+
private readonly spans: Span[] = [];
|
|
68
|
+
private activeSpans: Map<string, Span> = new Map();
|
|
69
|
+
|
|
70
|
+
constructor(config: Partial<TracerConfig> & { serviceName: string }) {
|
|
71
|
+
this.config = {
|
|
72
|
+
serviceVersion: '0.1.0',
|
|
73
|
+
samplingRate: 1.0,
|
|
74
|
+
maxEventsPerSpan: 128,
|
|
75
|
+
maxAttributesPerSpan: 128,
|
|
76
|
+
...config,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Start a new span
|
|
82
|
+
*/
|
|
83
|
+
startSpan(
|
|
84
|
+
name: string,
|
|
85
|
+
options?: {
|
|
86
|
+
kind?: Span['kind'];
|
|
87
|
+
parent?: TraceContext | Span;
|
|
88
|
+
attributes?: SpanAttribute[];
|
|
89
|
+
links?: SpanLink[];
|
|
90
|
+
}
|
|
91
|
+
): SpanHandle {
|
|
92
|
+
const traceId = options?.parent
|
|
93
|
+
? ('traceId' in options.parent ? options.parent.traceId : options.parent.traceId)
|
|
94
|
+
: this.generateTraceId();
|
|
95
|
+
|
|
96
|
+
const spanId = this.generateSpanId();
|
|
97
|
+
const parentSpanId = options?.parent
|
|
98
|
+
? ('spanId' in options.parent ? options.parent.spanId : options.parent.spanId)
|
|
99
|
+
: undefined;
|
|
100
|
+
|
|
101
|
+
const sampled = Math.random() < this.config.samplingRate;
|
|
102
|
+
|
|
103
|
+
const span: Span = {
|
|
104
|
+
traceId,
|
|
105
|
+
spanId,
|
|
106
|
+
parentSpanId,
|
|
107
|
+
name,
|
|
108
|
+
kind: options?.kind ?? 'internal',
|
|
109
|
+
startTime: Date.now(),
|
|
110
|
+
attributes: options?.attributes ?? [],
|
|
111
|
+
events: [],
|
|
112
|
+
links: options?.links ?? [],
|
|
113
|
+
status: { code: 'unset' },
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
this.activeSpans.set(spanId, span);
|
|
117
|
+
|
|
118
|
+
return new SpanHandle(span, this, sampled);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Start a span from HTTP headers
|
|
123
|
+
*/
|
|
124
|
+
startSpanFromHeaders(
|
|
125
|
+
name: string,
|
|
126
|
+
headers: Record<string, string>,
|
|
127
|
+
options?: {
|
|
128
|
+
kind?: Span['kind'];
|
|
129
|
+
attributes?: SpanAttribute[];
|
|
130
|
+
}
|
|
131
|
+
): SpanHandle {
|
|
132
|
+
const traceparent = headers['traceparent'] || headers['Traceparent'];
|
|
133
|
+
let parentContext: TraceContext | undefined;
|
|
134
|
+
|
|
135
|
+
if (traceparent) {
|
|
136
|
+
const parsed = this.parseTraceParent(traceparent);
|
|
137
|
+
if (parsed) {
|
|
138
|
+
parentContext = parsed;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return this.startSpan(name, {
|
|
143
|
+
...options,
|
|
144
|
+
parent: parentContext,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Create a child span from a parent span
|
|
150
|
+
*/
|
|
151
|
+
startChildSpan(parent: Span | SpanHandle, name: string, options?: {
|
|
152
|
+
kind?: Span['kind'];
|
|
153
|
+
attributes?: SpanAttribute[];
|
|
154
|
+
}): SpanHandle {
|
|
155
|
+
const parentSpan = 'span' in parent ? parent.span : parent;
|
|
156
|
+
return this.startSpan(name, {
|
|
157
|
+
...options,
|
|
158
|
+
parent: parentSpan,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get trace context from a span
|
|
164
|
+
*/
|
|
165
|
+
getTraceContext(span: Span | SpanHandle): TraceContext {
|
|
166
|
+
const s = 'span' in span ? span.span : span;
|
|
167
|
+
return {
|
|
168
|
+
traceId: s.traceId,
|
|
169
|
+
spanId: s.spanId,
|
|
170
|
+
parentSpanId: s.parentSpanId,
|
|
171
|
+
sampled: true,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get all completed spans
|
|
177
|
+
*/
|
|
178
|
+
getSpans(): Span[] {
|
|
179
|
+
return [...this.spans];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get spans by trace ID
|
|
184
|
+
*/
|
|
185
|
+
getSpansByTraceId(traceId: string): Span[] {
|
|
186
|
+
return this.spans.filter(s => s.traceId === traceId);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Export spans in OpenTelemetry format
|
|
191
|
+
*/
|
|
192
|
+
exportOpenTelemetry(): {
|
|
193
|
+
resourceSpans: {
|
|
194
|
+
resource: {
|
|
195
|
+
attributes: { key: string; value: { stringValue: string } }[];
|
|
196
|
+
};
|
|
197
|
+
scopeSpans: {
|
|
198
|
+
scope: { name: string; version: string };
|
|
199
|
+
spans: {
|
|
200
|
+
traceId: string;
|
|
201
|
+
spanId: string;
|
|
202
|
+
parentSpanId?: string;
|
|
203
|
+
name: string;
|
|
204
|
+
kind: number;
|
|
205
|
+
startTimeUnixNano: string;
|
|
206
|
+
endTimeUnixNano: string;
|
|
207
|
+
attributes: { key: string; value: { stringValue?: string; intValue?: string } }[];
|
|
208
|
+
status: { code: number; message?: string };
|
|
209
|
+
}[];
|
|
210
|
+
}[];
|
|
211
|
+
}[];
|
|
212
|
+
} {
|
|
213
|
+
const spans = this.spans.map(span => ({
|
|
214
|
+
traceId: span.traceId,
|
|
215
|
+
spanId: span.spanId,
|
|
216
|
+
parentSpanId: span.parentSpanId,
|
|
217
|
+
name: span.name,
|
|
218
|
+
kind: this.spanKindToNumber(span.kind),
|
|
219
|
+
startTimeUnixNano: (span.startTime * 1e6).toString(),
|
|
220
|
+
endTimeUnixNano: span.endTime ? (span.endTime * 1e6).toString() : '0',
|
|
221
|
+
attributes: span.attributes.map(attr => ({
|
|
222
|
+
key: attr.key,
|
|
223
|
+
value: typeof attr.value === 'string'
|
|
224
|
+
? { stringValue: attr.value }
|
|
225
|
+
: { intValue: String(attr.value) },
|
|
226
|
+
})),
|
|
227
|
+
status: {
|
|
228
|
+
code: span.status.code === 'ok' ? 1 : span.status.code === 'error' ? 2 : 0,
|
|
229
|
+
message: span.status.message,
|
|
230
|
+
},
|
|
231
|
+
}));
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
resourceSpans: [{
|
|
235
|
+
resource: {
|
|
236
|
+
attributes: [
|
|
237
|
+
{ key: 'service.name', value: { stringValue: this.config.serviceName } },
|
|
238
|
+
{ key: 'service.version', value: { stringValue: this.config.serviceVersion } },
|
|
239
|
+
],
|
|
240
|
+
},
|
|
241
|
+
scopeSpans: [{
|
|
242
|
+
scope: {
|
|
243
|
+
name: this.config.serviceName,
|
|
244
|
+
version: this.config.serviceVersion,
|
|
245
|
+
},
|
|
246
|
+
spans,
|
|
247
|
+
}],
|
|
248
|
+
}],
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Clear all spans
|
|
254
|
+
*/
|
|
255
|
+
clear(): void {
|
|
256
|
+
this.spans.length = 0;
|
|
257
|
+
this.activeSpans.clear();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Internal: End a span
|
|
262
|
+
*/
|
|
263
|
+
endSpan(span: Span): void {
|
|
264
|
+
span.endTime = Date.now();
|
|
265
|
+
span.duration = span.endTime - span.startTime;
|
|
266
|
+
this.activeSpans.delete(span.spanId);
|
|
267
|
+
this.spans.push(span);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Internal: Add event to span
|
|
272
|
+
*/
|
|
273
|
+
addEvent(span: Span, name: string, attributes?: SpanAttribute[]): void {
|
|
274
|
+
if (span.events.length < this.config.maxEventsPerSpan) {
|
|
275
|
+
span.events.push({
|
|
276
|
+
timestamp: Date.now(),
|
|
277
|
+
name,
|
|
278
|
+
attributes,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Internal: Set attribute on span
|
|
285
|
+
*/
|
|
286
|
+
setAttribute(span: Span, key: string, value: SpanAttribute['value']): void {
|
|
287
|
+
if (span.attributes.length < this.config.maxAttributesPerSpan) {
|
|
288
|
+
const existing = span.attributes.findIndex(a => a.key === key);
|
|
289
|
+
if (existing >= 0) {
|
|
290
|
+
span.attributes[existing].value = value;
|
|
291
|
+
} else {
|
|
292
|
+
span.attributes.push({ key, value });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Internal: Set span status
|
|
299
|
+
*/
|
|
300
|
+
setStatus(span: Span, code: SpanStatus['code'], message?: string): void {
|
|
301
|
+
span.status = { code, message };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private generateTraceId(): string {
|
|
305
|
+
return randomUUID().replace(/-/g, '');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private generateSpanId(): string {
|
|
309
|
+
return randomUUID().replace(/-/g, '').slice(0, 16);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private parseTraceParent(header: string): TraceContext | null {
|
|
313
|
+
// Parse W3C Trace Context format: version-traceid-spanid-flags
|
|
314
|
+
const match = header.match(/^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/i);
|
|
315
|
+
if (!match) return null;
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
traceId: match[2],
|
|
319
|
+
spanId: match[3],
|
|
320
|
+
sampled: match[4] === '01',
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private spanKindToNumber(kind: Span['kind']): number {
|
|
325
|
+
switch (kind) {
|
|
326
|
+
case 'internal': return 1;
|
|
327
|
+
case 'server': return 2;
|
|
328
|
+
case 'client': return 3;
|
|
329
|
+
case 'producer': return 4;
|
|
330
|
+
case 'consumer': return 5;
|
|
331
|
+
default: return 1;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Handle to an active span
|
|
338
|
+
*/
|
|
339
|
+
export class SpanHandle {
|
|
340
|
+
private ended = false;
|
|
341
|
+
|
|
342
|
+
constructor(
|
|
343
|
+
public readonly span: Span,
|
|
344
|
+
private readonly tracer: Tracer,
|
|
345
|
+
public readonly sampled: boolean
|
|
346
|
+
) {}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Set an attribute on the span
|
|
350
|
+
*/
|
|
351
|
+
setAttribute(key: string, value: SpanAttribute['value']): this {
|
|
352
|
+
if (!this.ended) {
|
|
353
|
+
this.tracer.setAttribute(this.span, key, value);
|
|
354
|
+
}
|
|
355
|
+
return this;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Set multiple attributes
|
|
360
|
+
*/
|
|
361
|
+
setAttributes(attributes: Record<string, SpanAttribute['value']>): this {
|
|
362
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
363
|
+
this.setAttribute(key, value);
|
|
364
|
+
}
|
|
365
|
+
return this;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Add an event to the span
|
|
370
|
+
*/
|
|
371
|
+
addEvent(name: string, attributes?: Record<string, SpanAttribute['value']>): this {
|
|
372
|
+
if (!this.ended) {
|
|
373
|
+
const attrs = attributes
|
|
374
|
+
? Object.entries(attributes).map(([key, value]) => ({ key, value }))
|
|
375
|
+
: undefined;
|
|
376
|
+
this.tracer.addEvent(this.span, name, attrs);
|
|
377
|
+
}
|
|
378
|
+
return this;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Set span status to OK
|
|
383
|
+
*/
|
|
384
|
+
ok(): this {
|
|
385
|
+
if (!this.ended) {
|
|
386
|
+
this.tracer.setStatus(this.span, 'ok');
|
|
387
|
+
}
|
|
388
|
+
return this;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Set span status to error
|
|
393
|
+
*/
|
|
394
|
+
error(message?: string): this {
|
|
395
|
+
if (!this.ended) {
|
|
396
|
+
this.tracer.setStatus(this.span, 'error', message);
|
|
397
|
+
}
|
|
398
|
+
return this;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Record an exception
|
|
403
|
+
*/
|
|
404
|
+
recordException(error: Error): this {
|
|
405
|
+
if (!this.ended) {
|
|
406
|
+
this.addEvent('exception', {
|
|
407
|
+
'exception.type': error.name,
|
|
408
|
+
'exception.message': error.message,
|
|
409
|
+
'exception.stacktrace': error.stack ?? '',
|
|
410
|
+
});
|
|
411
|
+
this.error(error.message);
|
|
412
|
+
}
|
|
413
|
+
return this;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* End the span
|
|
418
|
+
*/
|
|
419
|
+
end(): void {
|
|
420
|
+
if (!this.ended) {
|
|
421
|
+
this.tracer.endSpan(this.span);
|
|
422
|
+
this.ended = true;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Create traceparent header value for propagation
|
|
428
|
+
*/
|
|
429
|
+
toTraceParent(): string {
|
|
430
|
+
const flags = this.sampled ? '01' : '00';
|
|
431
|
+
return `00-${this.span.traceId}-${this.span.spanId}-${flags}`;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Get trace context for propagation
|
|
436
|
+
*/
|
|
437
|
+
getContext(): TraceContext {
|
|
438
|
+
return {
|
|
439
|
+
traceId: this.span.traceId,
|
|
440
|
+
spanId: this.span.spanId,
|
|
441
|
+
parentSpanId: this.span.parentSpanId,
|
|
442
|
+
sampled: this.sampled,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Default tracer instance
|
|
449
|
+
*/
|
|
450
|
+
export const tracer = new Tracer({
|
|
451
|
+
serviceName: 'crm',
|
|
452
|
+
serviceVersion: '0.1.0',
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Create a new tracer instance
|
|
457
|
+
*/
|
|
458
|
+
export function createTracer(config: Partial<TracerConfig> & { serviceName: string }): Tracer {
|
|
459
|
+
return new Tracer(config);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Helper: Run a function within a span
|
|
464
|
+
*/
|
|
465
|
+
export async function withSpan<T>(
|
|
466
|
+
name: string,
|
|
467
|
+
fn: (span: SpanHandle) => Promise<T>,
|
|
468
|
+
options?: {
|
|
469
|
+
kind?: Span['kind'];
|
|
470
|
+
parent?: Span | SpanHandle;
|
|
471
|
+
attributes?: SpanAttribute[];
|
|
472
|
+
}
|
|
473
|
+
): Promise<T> {
|
|
474
|
+
const span = tracer.startSpan(name, options);
|
|
475
|
+
try {
|
|
476
|
+
const result = await fn(span);
|
|
477
|
+
span.ok();
|
|
478
|
+
return result;
|
|
479
|
+
} catch (error) {
|
|
480
|
+
if (error instanceof Error) {
|
|
481
|
+
span.recordException(error);
|
|
482
|
+
}
|
|
483
|
+
throw error;
|
|
484
|
+
} finally {
|
|
485
|
+
span.end();
|
|
486
|
+
}
|
|
487
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Activity } from '../types';
|
|
2
|
+
import { generateId } from './contacts';
|
|
3
|
+
import { contactsStore } from './contacts';
|
|
4
|
+
import { dealsStore } from './deals';
|
|
5
|
+
|
|
6
|
+
// In-memory storage (would be replaced with database in production)
|
|
7
|
+
export const activitiesStore: Activity[] = [];
|
|
8
|
+
|
|
9
|
+
// List activities
|
|
10
|
+
export async function listActivities(request: Request): Promise<Response> {
|
|
11
|
+
const url = new URL(request.url);
|
|
12
|
+
const limit = parseInt(url.searchParams.get('limit') || '100');
|
|
13
|
+
const type = url.searchParams.get('type') || '';
|
|
14
|
+
|
|
15
|
+
let results = activitiesStore.slice(0, limit);
|
|
16
|
+
|
|
17
|
+
if (type) {
|
|
18
|
+
results = results.filter(a => a.type === type);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return Response.json({ success: true, data: results });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Create activity
|
|
25
|
+
export async function createActivity(data: Omit<Activity['id']>): Promise<Activity> {
|
|
26
|
+
const id = generateId();
|
|
27
|
+
const activity: Activity = {
|
|
28
|
+
id,
|
|
29
|
+
type: data.type || 'note',
|
|
30
|
+
description: data.description,
|
|
31
|
+
contactId: data.contactId,
|
|
32
|
+
dealId: data.dealId,
|
|
33
|
+
userId: data.userId || 'system',
|
|
34
|
+
timestamp: new Date().toISOString(),
|
|
35
|
+
metadata: data.metadata,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
activitiesStore.unshift(activity);
|
|
39
|
+
|
|
40
|
+
return activity;
|
|
41
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { Contact } from '../types';
|
|
2
|
+
|
|
3
|
+
// In-memory storage (would be replaced with database in production)
|
|
4
|
+
export const contactsStore = new Map<string, Contact>();
|
|
5
|
+
|
|
6
|
+
// Helper to generate IDs
|
|
7
|
+
export function generateId(): string {
|
|
8
|
+
return crypto.randomUUID();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// List contacts with optional filters
|
|
12
|
+
export async function listContacts(request: Request): Promise<Response> {
|
|
13
|
+
const url = new URL(request.url);
|
|
14
|
+
const search = url.searchParams.get('search') || '';
|
|
15
|
+
const status = url.searchParams.get('status') || '';
|
|
16
|
+
|
|
17
|
+
let results = Array.from(contactsStore.values());
|
|
18
|
+
|
|
19
|
+
if (search) {
|
|
20
|
+
const searchLower = search.toLowerCase();
|
|
21
|
+
results = results.filter(c =>
|
|
22
|
+
c.name.toLowerCase().includes(searchLower) ||
|
|
23
|
+
c.email.toLowerCase().includes(searchLower) ||
|
|
24
|
+
c.company?.toLowerCase().includes(searchLower)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (status) {
|
|
29
|
+
results = results.filter(c => c.status === status);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return Response.json({ success: true, data: results });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Create a new contact
|
|
36
|
+
export async function createContact(request: Request): Promise<Response> {
|
|
37
|
+
try {
|
|
38
|
+
const body = await request.json();
|
|
39
|
+
const id = generateId();
|
|
40
|
+
const now = new Date().toISOString();
|
|
41
|
+
|
|
42
|
+
const contact: Contact = {
|
|
43
|
+
id,
|
|
44
|
+
name: body.name,
|
|
45
|
+
email: body.email,
|
|
46
|
+
phone: body.phone,
|
|
47
|
+
company: body.company,
|
|
48
|
+
title: body.title,
|
|
49
|
+
status: body.status || 'lead',
|
|
50
|
+
tags: body.tags || [],
|
|
51
|
+
notes: body.notes,
|
|
52
|
+
createdAt: now,
|
|
53
|
+
updatedAt: now,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
contactsStore.set(id, contact);
|
|
57
|
+
|
|
58
|
+
return Response.json({ success: true, data: contact });
|
|
59
|
+
} catch (error) {
|
|
60
|
+
return Response.json(
|
|
61
|
+
{ success: false, error: 'Invalid request body' },
|
|
62
|
+
{ status: 400 }
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Get a single contact
|
|
68
|
+
export async function getContact(request: Request, params: Record<string, string>): Promise<Response> {
|
|
69
|
+
const contact = contactsStore.get(params.id);
|
|
70
|
+
if (!contact) {
|
|
71
|
+
return Response.json({ success: false, error: 'Contact not found' }, { status: 404 });
|
|
72
|
+
}
|
|
73
|
+
return Response.json({ success: true, data: contact });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Update a contact
|
|
77
|
+
export async function updateContact(request: Request, params: Record<string, string>): Promise<Response> {
|
|
78
|
+
const contact = contactsStore.get(params.id);
|
|
79
|
+
if (!contact) {
|
|
80
|
+
return Response.json({ success: false, error: 'Contact not found' }, { status: 404 });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const body = await request.json();
|
|
85
|
+
const updated: Contact = {
|
|
86
|
+
...contact,
|
|
87
|
+
...body,
|
|
88
|
+
id: contact.id,
|
|
89
|
+
createdAt: contact.createdAt,
|
|
90
|
+
updatedAt: new Date().toISOString(),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
contactsStore.set(params.id, updated);
|
|
94
|
+
|
|
95
|
+
return Response.json({ success: true, data: updated });
|
|
96
|
+
} catch (error) {
|
|
97
|
+
return Response.json(
|
|
98
|
+
{ success: false, error: 'Invalid request body' },
|
|
99
|
+
{ status: 400 }
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Delete a contact
|
|
105
|
+
export async function deleteContact(request: Request, params: Record<string, string>): Promise<Response> {
|
|
106
|
+
const contact = contactsStore.get(params.id);
|
|
107
|
+
if (!contact) {
|
|
108
|
+
return Response.json({ success: false, error: 'Contact not found' }, { status: 404 });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
contactsStore.delete(params.id);
|
|
112
|
+
|
|
113
|
+
return Response.json({ success: true });
|
|
114
|
+
}
|