@classytic/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.
@@ -0,0 +1,1420 @@
1
+ import { DomainEvent, EventHandler, EventTransport } from "@classytic/primitives/events";
2
+ import { z } from "zod";
3
+ import { OutboxStore } from "@classytic/primitives/outbox";
4
+ import { ContactInfo, PersonName } from "@classytic/primitives/person";
5
+ import { Money } from "@classytic/primitives/money";
6
+
7
+ //#region src/domain/entities/account.d.ts
8
+ /**
9
+ * An account (company / organization). Contacts, Opportunities, and
10
+ * Activities can reference it.
11
+ */
12
+ interface Account {
13
+ id: string;
14
+ name: string;
15
+ /** Domain / website — used by host deduplication pipelines. */
16
+ domain?: string;
17
+ industry?: string;
18
+ /** Employee-count bucket, e.g. `1-10`, `11-50`. Free-form for flexibility. */
19
+ sizeBucket?: string;
20
+ annualRevenue?: {
21
+ amount: number;
22
+ currency: string;
23
+ };
24
+ ownerId?: string;
25
+ tags?: readonly string[];
26
+ metadata?: Record<string, unknown>;
27
+ createdAt: Date;
28
+ updatedAt: Date;
29
+ }
30
+ //#endregion
31
+ //#region src/domain/ports/account-repository.port.d.ts
32
+ interface AccountFilter {
33
+ ownerId?: string;
34
+ industry?: string;
35
+ domain?: string;
36
+ tags?: readonly string[];
37
+ }
38
+ interface AccountRepository {
39
+ findById(id: string): Promise<Account | null>;
40
+ findByDomain(domain: string): Promise<Account | null>;
41
+ list(filter?: AccountFilter): Promise<Account[]>;
42
+ create(a: Omit<Account, 'createdAt' | 'updatedAt'>): Promise<Account>;
43
+ update(id: string, patch: Partial<Account>): Promise<Account>;
44
+ delete(id: string): Promise<void>;
45
+ }
46
+ //#endregion
47
+ //#region src/domain/ports/clock.port.d.ts
48
+ interface Clock {
49
+ now(): Date;
50
+ }
51
+ declare const systemClock: Clock;
52
+ //#endregion
53
+ //#region src/domain/ports/id-generator.port.d.ts
54
+ interface IdGenerator {
55
+ next(): string;
56
+ }
57
+ //#endregion
58
+ //#region src/services/mutation-options.d.ts
59
+ /**
60
+ * Options passed to every service mutation. All fields optional — hosts
61
+ * opt into the behaviours they need.
62
+ */
63
+ interface MutationOptions {
64
+ /**
65
+ * When `false`, skip event emission for this call. Useful for bulk seeds
66
+ * and when a higher-level orchestration verb composes several services
67
+ * and only wants the outer event to fire (see `LeadService.convert`).
68
+ */
69
+ emitEvents?: boolean;
70
+ /**
71
+ * Host-provided DB transaction handle (mongoose `ClientSession`,
72
+ * Prisma tx, Postgres client with `BEGIN`, etc.). Typed as `unknown`
73
+ * to keep crm framework-agnostic.
74
+ *
75
+ * Forwarded to:
76
+ * - The host's repository adapter (if it reads `options.session`) so
77
+ * the business write joins the transaction.
78
+ * - `outbox.save(event, { session })` so the outbox row commits
79
+ * atomically with the business write — PACKAGE_RULES §5.5 + P8's
80
+ * session-bound dispatch pattern.
81
+ *
82
+ * Wrap the CRM service call in your own transaction:
83
+ *
84
+ * ```ts
85
+ * const session = await mongoose.connection.startSession();
86
+ * try {
87
+ * await session.withTransaction(async () => {
88
+ * await accountService.create({ name: 'Acme' }, { session });
89
+ * });
90
+ * } finally {
91
+ * await session.endSession();
92
+ * }
93
+ * ```
94
+ */
95
+ session?: unknown;
96
+ }
97
+ //#endregion
98
+ //#region src/services/account.service.d.ts
99
+ interface AccountServiceConfig {
100
+ repo: AccountRepository;
101
+ events?: EventTransport | undefined;
102
+ /**
103
+ * Optional host-owned outbox. See `emitCrmEvent` for the dispatch contract:
104
+ * outbox save (session-bound when `options.session` is passed) is issued
105
+ * before the transport publish so durable at-least-once delivery works
106
+ * even when the transport is down.
107
+ */
108
+ outbox?: OutboxStore | undefined;
109
+ clock?: Clock;
110
+ ids?: IdGenerator;
111
+ }
112
+ interface CreateAccountInput {
113
+ name: string;
114
+ domain?: string;
115
+ industry?: string;
116
+ sizeBucket?: string;
117
+ annualRevenue?: {
118
+ amount: number;
119
+ currency: string;
120
+ };
121
+ ownerId?: string;
122
+ tags?: readonly string[];
123
+ metadata?: Record<string, unknown>;
124
+ }
125
+ declare class AccountService {
126
+ private readonly repo;
127
+ private readonly events;
128
+ private readonly outbox;
129
+ private readonly clock;
130
+ private readonly ids;
131
+ constructor(config: AccountServiceConfig);
132
+ create(input: CreateAccountInput, options?: MutationOptions): Promise<Account>;
133
+ update(id: string, patch: Partial<Account>, options?: MutationOptions): Promise<Account>;
134
+ delete(id: string): Promise<void>;
135
+ findById(id: string): Promise<Account | null>;
136
+ findByDomain(domain: string): Promise<Account | null>;
137
+ list(filter?: Parameters<AccountRepository['list']>[0]): Promise<Account[]>;
138
+ private _require;
139
+ private _emit;
140
+ }
141
+ //#endregion
142
+ //#region src/domain/enums/activity.d.ts
143
+ /**
144
+ * Activity type vocabulary. The package stores `type` as free-form
145
+ * string so hosts can extend; these constants cover the common
146
+ * channels that automation tooling typically keys off.
147
+ */
148
+ declare const ACTIVITY_TYPES: readonly ["call", "email", "meeting", "task", "note", "sms", "social", "demo", "other"];
149
+ type ActivityType = (typeof ACTIVITY_TYPES)[number];
150
+ declare const ACTIVITY_STATUSES: readonly ["planned", "in_progress", "completed", "cancelled"];
151
+ type ActivityStatus = (typeof ACTIVITY_STATUSES)[number];
152
+ declare const ACTIVITY_TRANSITIONS: Record<ActivityStatus, readonly ActivityStatus[]>;
153
+ declare function canTransitionActivity(from: ActivityStatus, to: ActivityStatus): boolean;
154
+ /**
155
+ * Kinds of entity an Activity can reference. Polymorphic linking lets
156
+ * hosts log a call against a Lead, an Opportunity, a Contact, or a
157
+ * stand-alone Account.
158
+ */
159
+ declare const SUBJECT_KINDS: readonly ["lead", "opportunity", "contact", "account"];
160
+ type SubjectKind = (typeof SUBJECT_KINDS)[number];
161
+ //#endregion
162
+ //#region src/domain/entities/activity.d.ts
163
+ /**
164
+ * An activity is a polymorphic record of outreach / task / event
165
+ * attached to any CRM entity. `subjectKind` + `subjectId` define the
166
+ * link — this is how hosts build automation atop the package (subscribe
167
+ * to `crm:activity.*` events, filter by `subjectKind`, react).
168
+ */
169
+ interface Activity {
170
+ id: string;
171
+ type: ActivityType | string;
172
+ status: ActivityStatus;
173
+ subjectKind: SubjectKind;
174
+ subjectId: string;
175
+ subject?: string;
176
+ body?: string;
177
+ /** When the activity is / was scheduled to happen. */
178
+ scheduledAt?: Date;
179
+ completedAt?: Date;
180
+ cancelledAt?: Date;
181
+ /** User-level ownership (call made by / task assigned to). */
182
+ ownerId?: string;
183
+ participantIds?: readonly string[];
184
+ /**
185
+ * Optional external ref — e.g. Calendar event id, email thread id,
186
+ * call-recording url. Host-owned semantics.
187
+ */
188
+ externalRef?: string;
189
+ metadata?: Record<string, unknown>;
190
+ createdAt: Date;
191
+ updatedAt: Date;
192
+ }
193
+ //#endregion
194
+ //#region src/domain/entities/note.d.ts
195
+ /**
196
+ * A free-text note on a CRM entity. Separate from Activity because
197
+ * notes aren't status-tracked — they're typed once and stay; they're
198
+ * also the cheapest "activity log" entry to create.
199
+ */
200
+ interface Note {
201
+ id: string;
202
+ subjectKind: SubjectKind;
203
+ subjectId: string;
204
+ body: string;
205
+ authorId?: string;
206
+ /** Supports @-mentions, Markdown, etc. — host-interpreted. */
207
+ format?: 'plain' | 'markdown';
208
+ metadata?: Record<string, unknown>;
209
+ createdAt: Date;
210
+ updatedAt: Date;
211
+ }
212
+ //#endregion
213
+ //#region src/domain/ports/activity-repository.port.d.ts
214
+ interface ActivityFilter {
215
+ subjectKind?: SubjectKind;
216
+ subjectId?: string;
217
+ status?: ActivityStatus | readonly ActivityStatus[];
218
+ ownerId?: string;
219
+ scheduledBetween?: {
220
+ from: Date;
221
+ to: Date;
222
+ };
223
+ }
224
+ interface ActivityRepository {
225
+ findById(id: string): Promise<Activity | null>;
226
+ list(filter?: ActivityFilter): Promise<Activity[]>;
227
+ create(a: Omit<Activity, 'createdAt' | 'updatedAt'>): Promise<Activity>;
228
+ update(id: string, patch: Partial<Activity>): Promise<Activity>;
229
+ casStatus(id: string, expected: ActivityStatus, next: ActivityStatus, patch?: Partial<Activity>): Promise<Activity | null>;
230
+ }
231
+ //#endregion
232
+ //#region src/domain/ports/note-repository.port.d.ts
233
+ interface NoteRepository {
234
+ findById(id: string): Promise<Note | null>;
235
+ listBySubject(subjectKind: SubjectKind, subjectId: string): Promise<Note[]>;
236
+ create(n: Omit<Note, 'createdAt' | 'updatedAt'>): Promise<Note>;
237
+ update(id: string, patch: Partial<Note>): Promise<Note>;
238
+ delete(id: string): Promise<void>;
239
+ }
240
+ //#endregion
241
+ //#region src/services/activity.service.d.ts
242
+ interface ActivityServiceConfig {
243
+ repo: ActivityRepository;
244
+ notes: NoteRepository;
245
+ events?: EventTransport | undefined;
246
+ /** Optional host-owned outbox. See AccountServiceConfig.outbox. */
247
+ outbox?: OutboxStore | undefined;
248
+ clock?: Clock;
249
+ ids?: IdGenerator;
250
+ }
251
+ interface CreateActivityInput {
252
+ type: ActivityType | string;
253
+ subjectKind: SubjectKind;
254
+ subjectId: string;
255
+ subject?: string;
256
+ body?: string;
257
+ scheduledAt?: Date;
258
+ ownerId?: string;
259
+ participantIds?: readonly string[];
260
+ externalRef?: string;
261
+ metadata?: Record<string, unknown>;
262
+ }
263
+ interface CreateNoteInput {
264
+ subjectKind: SubjectKind;
265
+ subjectId: string;
266
+ body: string;
267
+ authorId?: string;
268
+ format?: 'plain' | 'markdown';
269
+ metadata?: Record<string, unknown>;
270
+ }
271
+ /**
272
+ * Polymorphic activity / note service. Activities have a lifecycle
273
+ * (`planned → in_progress → completed | cancelled`); notes are
274
+ * immutable-ish (create + update body, no status).
275
+ */
276
+ declare class ActivityService {
277
+ private readonly repo;
278
+ private readonly noteRepo;
279
+ private readonly events;
280
+ private readonly outbox;
281
+ private readonly clock;
282
+ private readonly ids;
283
+ constructor(config: ActivityServiceConfig);
284
+ plan(input: CreateActivityInput, options?: MutationOptions): Promise<Activity>;
285
+ start(id: string, options?: MutationOptions): Promise<Activity>;
286
+ complete(id: string, completedAt?: Date, options?: MutationOptions): Promise<Activity>;
287
+ cancel(id: string, reason?: string, options?: MutationOptions): Promise<Activity>;
288
+ addNote(input: CreateNoteInput, options?: MutationOptions): Promise<Note>;
289
+ listBySubject(subjectKind: SubjectKind, subjectId: string): Promise<Activity[]>;
290
+ listNotesBySubject(subjectKind: SubjectKind, subjectId: string): Promise<Note[]>;
291
+ findById(id: string): Promise<Activity | null>;
292
+ private _transition;
293
+ private _emit;
294
+ }
295
+ //#endregion
296
+ //#region src/domain/entities/contact.d.ts
297
+ /**
298
+ * A person — the atom of a CRM. Contacts may belong to an Account,
299
+ * but can also exist stand-alone (independent consultants, consumers).
300
+ *
301
+ * Structured `name` + `contact` come from `@classytic/primitives/person`.
302
+ * Hosts projecting over a commerce Customer record embed the same shapes —
303
+ * no translation needed at the boundary.
304
+ */
305
+ interface Contact {
306
+ id: string;
307
+ name: PersonName;
308
+ contact: ContactInfo;
309
+ jobTitle?: string;
310
+ accountId?: string;
311
+ ownerId?: string;
312
+ /** Free-form tags for segmentation. */
313
+ tags?: readonly string[];
314
+ /** Host-owned arbitrary payload (social handles, custom fields, etc.). */
315
+ metadata?: Record<string, unknown>;
316
+ createdAt: Date;
317
+ updatedAt: Date;
318
+ }
319
+ //#endregion
320
+ //#region src/domain/ports/contact-repository.port.d.ts
321
+ interface ContactFilter {
322
+ accountId?: string;
323
+ ownerId?: string;
324
+ email?: string;
325
+ tags?: readonly string[];
326
+ }
327
+ interface ContactRepository {
328
+ findById(id: string): Promise<Contact | null>;
329
+ findByEmail(email: string): Promise<Contact | null>;
330
+ list(filter?: ContactFilter): Promise<Contact[]>;
331
+ create(c: Omit<Contact, 'createdAt' | 'updatedAt'>): Promise<Contact>;
332
+ update(id: string, patch: Partial<Contact>): Promise<Contact>;
333
+ delete(id: string): Promise<void>;
334
+ }
335
+ //#endregion
336
+ //#region src/services/contact.service.d.ts
337
+ interface ContactServiceConfig {
338
+ repo: ContactRepository;
339
+ events?: EventTransport | undefined;
340
+ /** Optional host-owned outbox. See AccountServiceConfig.outbox. */
341
+ outbox?: OutboxStore | undefined;
342
+ clock?: Clock;
343
+ ids?: IdGenerator;
344
+ }
345
+ interface CreateContactInput {
346
+ name: PersonName;
347
+ contact?: ContactInfo;
348
+ jobTitle?: string;
349
+ accountId?: string;
350
+ ownerId?: string;
351
+ tags?: readonly string[];
352
+ metadata?: Record<string, unknown>;
353
+ }
354
+ declare class ContactService {
355
+ private readonly repo;
356
+ private readonly events;
357
+ private readonly outbox;
358
+ private readonly clock;
359
+ private readonly ids;
360
+ constructor(config: ContactServiceConfig);
361
+ create(input: CreateContactInput, options?: MutationOptions): Promise<Contact>;
362
+ update(id: string, patch: Partial<Contact>, options?: MutationOptions): Promise<Contact>;
363
+ delete(id: string): Promise<void>;
364
+ findById(id: string): Promise<Contact | null>;
365
+ findByEmail(email: string): Promise<Contact | null>;
366
+ list(filter?: Parameters<ContactRepository['list']>[0]): Promise<Contact[]>;
367
+ private _require;
368
+ private _emit;
369
+ }
370
+ //#endregion
371
+ //#region src/domain/enums/lead-status.d.ts
372
+ /**
373
+ * Lead lifecycle — from initial inquiry through conversion.
374
+ *
375
+ * new — just captured (web form, import, referral)
376
+ * contacted — SDR has reached out
377
+ * qualified — meets ICP / BANT
378
+ * converted — turned into Contact + Account + Opportunity
379
+ * disqualified — rejected (bad fit, spam, duplicate)
380
+ * nurturing — not ready now; parked for later outreach
381
+ */
382
+ declare const LEAD_STATUSES: readonly ["new", "contacted", "qualified", "converted", "disqualified", "nurturing"];
383
+ type LeadStatus = (typeof LEAD_STATUSES)[number];
384
+ declare const LEAD_TRANSITIONS: Record<LeadStatus, readonly LeadStatus[]>;
385
+ declare function canTransitionLead(from: LeadStatus, to: LeadStatus): boolean;
386
+ //#endregion
387
+ //#region src/domain/entities/lead.d.ts
388
+ /**
389
+ * An unqualified inquiry. Leads are the top of the funnel — they
390
+ * don't yet have a dedicated Contact + Account + Opportunity in the
391
+ * system. `LeadService.convert(...)` creates all three and
392
+ * transitions the lead to `converted`.
393
+ */
394
+ interface Lead {
395
+ id: string;
396
+ /** Name / email / phone / company captured at inquiry time. */
397
+ firstName?: string;
398
+ lastName?: string;
399
+ fullName: string;
400
+ email?: string;
401
+ phone?: string;
402
+ companyName?: string;
403
+ jobTitle?: string;
404
+ /** How the lead arrived (`web-form`, `referral`, `event`, `ads`, …). */
405
+ source?: string;
406
+ /** Free-form campaign identifier — the host's campaign system owns its semantics. */
407
+ campaignRef?: string;
408
+ /**
409
+ * Lead score — updated by `LeadService.recomputeScore(...)` whenever
410
+ * the score function decides. Stored on the record for indexing +
411
+ * routing. Raw score units; range is host-defined.
412
+ */
413
+ score?: number;
414
+ status: LeadStatus;
415
+ statusHistory: readonly LeadStatusEntry[];
416
+ ownerId?: string;
417
+ tags?: readonly string[];
418
+ /** On successful `convert`, links to the resulting records. */
419
+ convertedContactId?: string;
420
+ convertedAccountId?: string;
421
+ convertedOpportunityId?: string;
422
+ convertedAt?: Date;
423
+ /** Disqualification detail. */
424
+ disqualifyReason?: string;
425
+ metadata?: Record<string, unknown>;
426
+ createdAt: Date;
427
+ updatedAt: Date;
428
+ }
429
+ interface LeadStatusEntry {
430
+ status: LeadStatus;
431
+ occurredAt: Date;
432
+ by?: string;
433
+ note?: string;
434
+ }
435
+ //#endregion
436
+ //#region src/domain/enums/opportunity-status.d.ts
437
+ /**
438
+ * Top-level opportunity status. Movement through pipeline STAGES
439
+ * happens while status is `open`; `won` / `lost` are terminal
440
+ * outcomes. Re-opening a lost deal is a host-level decision —
441
+ * convention is to clone the opportunity rather than revive the
442
+ * terminal record.
443
+ */
444
+ declare const OPPORTUNITY_STATUSES: readonly ["open", "won", "lost", "abandoned"];
445
+ type OpportunityStatus = (typeof OPPORTUNITY_STATUSES)[number];
446
+ declare const OPPORTUNITY_TRANSITIONS: Record<OpportunityStatus, readonly OpportunityStatus[]>;
447
+ declare function canTransitionOpportunity(from: OpportunityStatus, to: OpportunityStatus): boolean;
448
+ //#endregion
449
+ //#region src/domain/entities/opportunity.d.ts
450
+ /**
451
+ * A qualified sales opportunity. Opportunities live inside a Pipeline
452
+ * and progress through Stages. Status captures the outcome
453
+ * (`open` / `won` / `lost` / `abandoned`); stage captures the current
454
+ * step.
455
+ */
456
+ interface Opportunity {
457
+ id: string;
458
+ name: string;
459
+ accountId?: string;
460
+ primaryContactId?: string;
461
+ /** Which pipeline this opportunity belongs to (sales, partnerships, renewals). */
462
+ pipelineId: string;
463
+ /** Current stage — index INTO the pipeline's `stages` array. */
464
+ stageId: string;
465
+ status: OpportunityStatus;
466
+ statusHistory: readonly OpportunityStatusEntry[];
467
+ /** Host-expected deal value. */
468
+ amount?: Money;
469
+ /**
470
+ * Win probability (0-1). Defaults to the stage's probability at
471
+ * every stage move; host can override for hand-tuned forecasts.
472
+ */
473
+ probability: number;
474
+ /** When the deal is expected to close. Drives forecast grouping. */
475
+ expectedCloseAt?: Date;
476
+ /** Populated on win/lose. */
477
+ closedAt?: Date;
478
+ lostReasonId?: string;
479
+ ownerId?: string;
480
+ sourceLeadId?: string;
481
+ tags?: readonly string[];
482
+ metadata?: Record<string, unknown>;
483
+ createdAt: Date;
484
+ updatedAt: Date;
485
+ }
486
+ interface OpportunityStatusEntry {
487
+ status: OpportunityStatus;
488
+ stageId?: string;
489
+ occurredAt: Date;
490
+ by?: string;
491
+ note?: string;
492
+ }
493
+ //#endregion
494
+ //#region src/domain/ports/lead-repository.port.d.ts
495
+ interface LeadFilter {
496
+ status?: LeadStatus | readonly LeadStatus[];
497
+ ownerId?: string;
498
+ source?: string;
499
+ minScore?: number;
500
+ }
501
+ interface LeadRepository {
502
+ findById(id: string): Promise<Lead | null>;
503
+ findByEmail(email: string): Promise<Lead | null>;
504
+ list(filter?: LeadFilter): Promise<Lead[]>;
505
+ create(l: Omit<Lead, 'createdAt' | 'updatedAt'>): Promise<Lead>;
506
+ update(id: string, patch: Partial<Lead>): Promise<Lead>;
507
+ casStatus(id: string, expected: LeadStatus, next: LeadStatus, patch?: Partial<Lead>): Promise<Lead | null>;
508
+ }
509
+ //#endregion
510
+ //#region src/domain/ports/opportunity-repository.port.d.ts
511
+ interface OpportunityFilter {
512
+ status?: OpportunityStatus | readonly OpportunityStatus[];
513
+ pipelineId?: string;
514
+ stageId?: string;
515
+ ownerId?: string;
516
+ accountId?: string;
517
+ closingBetween?: {
518
+ from: Date;
519
+ to: Date;
520
+ };
521
+ }
522
+ interface OpportunityRepository {
523
+ findById(id: string): Promise<Opportunity | null>;
524
+ list(filter?: OpportunityFilter): Promise<Opportunity[]>;
525
+ create(o: Omit<Opportunity, 'createdAt' | 'updatedAt'>): Promise<Opportunity>;
526
+ update(id: string, patch: Partial<Opportunity>): Promise<Opportunity>;
527
+ delete(id: string): Promise<void>;
528
+ casStatus(id: string, expected: OpportunityStatus, next: OpportunityStatus, patch?: Partial<Opportunity>): Promise<Opportunity | null>;
529
+ casStage(id: string, expectedStageId: string, nextStageId: string, patch?: Partial<Opportunity>): Promise<Opportunity | null>;
530
+ }
531
+ //#endregion
532
+ //#region src/services/opportunity.service.d.ts
533
+ interface OpportunityPipelinePort {
534
+ findById(id: string): Promise<{
535
+ id: string;
536
+ stages: readonly {
537
+ id: string;
538
+ }[];
539
+ } | null>;
540
+ getStage(pipelineId: string, stageId: string): Promise<{
541
+ id: string;
542
+ sequence: number;
543
+ defaultProbability: number;
544
+ }>;
545
+ getFirstStage(pipelineId: string): Promise<{
546
+ id: string;
547
+ sequence: number;
548
+ defaultProbability: number;
549
+ }>;
550
+ }
551
+ interface OpportunityServiceConfig {
552
+ repo: OpportunityRepository;
553
+ pipelines: OpportunityPipelinePort;
554
+ events?: EventTransport | undefined;
555
+ /** Optional host-owned outbox. See AccountServiceConfig.outbox. */
556
+ outbox?: OutboxStore | undefined;
557
+ clock?: Clock;
558
+ ids?: IdGenerator;
559
+ }
560
+ interface CreateOpportunityInput {
561
+ name: string;
562
+ pipelineId: string;
563
+ /** Starting stage — defaults to the pipeline's first stage. */
564
+ stageId?: string;
565
+ accountId?: string;
566
+ primaryContactId?: string;
567
+ sourceLeadId?: string;
568
+ amount?: Money;
569
+ /** Override the stage default. Defaults to the stage's `defaultProbability`. */
570
+ probability?: number;
571
+ expectedCloseAt?: Date;
572
+ ownerId?: string;
573
+ tags?: readonly string[];
574
+ metadata?: Record<string, unknown>;
575
+ }
576
+ interface MoveStageInput {
577
+ stageId: string;
578
+ /** Override probability. Defaults to the target stage's `defaultProbability`. */
579
+ probability?: number;
580
+ by?: string;
581
+ note?: string;
582
+ }
583
+ interface CloseInput {
584
+ amount?: Money;
585
+ closedAt?: Date;
586
+ by?: string;
587
+ note?: string;
588
+ }
589
+ interface LoseInput extends CloseInput {
590
+ lostReasonId?: string;
591
+ }
592
+ /**
593
+ * Opportunity lifecycle — stage movement + outcome resolution. Stage
594
+ * changes call the pipeline service to validate membership + default
595
+ * probability; status transitions (`won` / `lost` / `abandoned`) are
596
+ * terminal.
597
+ */
598
+ declare class OpportunityService {
599
+ private readonly repo;
600
+ private readonly pipelines;
601
+ private readonly events;
602
+ private readonly outbox;
603
+ private readonly clock;
604
+ private readonly ids;
605
+ constructor(config: OpportunityServiceConfig);
606
+ create(input: CreateOpportunityInput, options?: MutationOptions): Promise<Opportunity>;
607
+ moveToStage(id: string, input: MoveStageInput, options?: MutationOptions): Promise<Opportunity>;
608
+ win(id: string, input?: CloseInput, options?: MutationOptions): Promise<Opportunity>;
609
+ lose(id: string, input?: LoseInput, options?: MutationOptions): Promise<Opportunity>;
610
+ abandon(id: string, by?: string, note?: string, options?: MutationOptions): Promise<Opportunity>;
611
+ delete(id: string): Promise<void>;
612
+ findById(id: string): Promise<Opportunity | null>;
613
+ list(filter?: Parameters<OpportunityRepository['list']>[0]): Promise<Opportunity[]>;
614
+ private _require;
615
+ private _transitionStatus;
616
+ private _emit;
617
+ }
618
+ //#endregion
619
+ //#region src/services/scoring.d.ts
620
+ /**
621
+ * Pluggable lead scorer. Hosts inject whatever rubric they need —
622
+ * firmographic model, in-app signals, ML prediction, external enrichment.
623
+ *
624
+ * Stay pure: the scorer must not mutate anything, not hit I/O, and
625
+ * be deterministic given its input. Side-effects belong in the
626
+ * caller — `LeadService.recomputeScore` runs the scorer then persists.
627
+ */
628
+ type LeadScorer = (lead: Lead) => number;
629
+ /**
630
+ * Reference implementation. Intentionally simple so hosts read it and
631
+ * understand the shape, then replace it.
632
+ *
633
+ * +20 if email is present
634
+ * +20 if phone is present
635
+ * +20 if companyName is present
636
+ * +10 per populated tag up to 40
637
+ * +10 if jobTitle looks director/manager/head (case-insensitive)
638
+ */
639
+ declare function defaultLeadScorer(lead: Lead): number;
640
+ /**
641
+ * Compose a scoring pipeline — each rule returns an increment
642
+ * (positive or negative). Useful when host logic is a bunch of
643
+ * small, testable rules.
644
+ *
645
+ * @example
646
+ * ```ts
647
+ * const scorer = composeScorers(
648
+ * (l) => (l.email ? 20 : 0),
649
+ * (l) => (l.source === 'referral' ? 30 : 0),
650
+ * (l) => (l.tags?.includes('enterprise') ? 50 : 0),
651
+ * );
652
+ * ```
653
+ */
654
+ declare function composeScorers(...rules: readonly LeadScorer[]): LeadScorer;
655
+ //#endregion
656
+ //#region src/services/lead.service.d.ts
657
+ interface LeadAccountWriter {
658
+ create(input: CreateAccountInput, options?: MutationOptions): Promise<Account>;
659
+ delete?(id: string): Promise<void>;
660
+ }
661
+ interface LeadContactWriter {
662
+ create(input: CreateContactInput, options?: MutationOptions): Promise<Contact>;
663
+ delete?(id: string): Promise<void>;
664
+ }
665
+ interface LeadOpportunityWriter {
666
+ create(input: CreateOpportunityInput, options?: MutationOptions): Promise<Opportunity>;
667
+ delete?(id: string): Promise<void>;
668
+ }
669
+ interface LeadServiceConfig {
670
+ repo: LeadRepository;
671
+ contacts: LeadContactWriter;
672
+ accounts: LeadAccountWriter;
673
+ opportunities: LeadOpportunityWriter;
674
+ events?: EventTransport | undefined;
675
+ /** Optional host-owned outbox. See AccountServiceConfig.outbox. */
676
+ outbox?: OutboxStore | undefined;
677
+ clock?: Clock;
678
+ ids?: IdGenerator;
679
+ /** Override the default scorer (`defaultLeadScorer`). */
680
+ scorer?: LeadScorer;
681
+ /** When true (default), `create()` rejects dup emails. */
682
+ rejectDuplicates?: boolean;
683
+ }
684
+ interface CreateLeadInput {
685
+ firstName?: string;
686
+ lastName?: string;
687
+ fullName?: string;
688
+ email?: string;
689
+ phone?: string;
690
+ companyName?: string;
691
+ jobTitle?: string;
692
+ source?: string;
693
+ campaignRef?: string;
694
+ ownerId?: string;
695
+ tags?: readonly string[];
696
+ metadata?: Record<string, unknown>;
697
+ }
698
+ interface ConvertLeadInput {
699
+ /** Pipeline the resulting opportunity enters. */
700
+ pipelineId: string;
701
+ /** Opportunity name. Defaults to `<lead.companyName> — <lead.fullName>`. */
702
+ opportunityName?: string;
703
+ /** Optional deal amount. */
704
+ amount?: Money;
705
+ expectedCloseAt?: Date;
706
+ by?: string;
707
+ }
708
+ interface ConvertResult {
709
+ lead: Lead;
710
+ contactId: string;
711
+ accountId?: string;
712
+ opportunityId: string;
713
+ }
714
+ /**
715
+ * Lead lifecycle + the crown-jewel `convert()` verb that atomically
716
+ * spawns a Contact + Account + Opportunity and transitions the lead
717
+ * to `converted`.
718
+ */
719
+ declare class LeadService {
720
+ private readonly repo;
721
+ private readonly contacts;
722
+ private readonly accounts;
723
+ private readonly opportunities;
724
+ private readonly events;
725
+ private readonly outbox;
726
+ private readonly clock;
727
+ private readonly ids;
728
+ private readonly scorer;
729
+ private readonly rejectDuplicates;
730
+ constructor(config: LeadServiceConfig);
731
+ create(input: CreateLeadInput, options?: MutationOptions): Promise<Lead>;
732
+ markContacted(id: string, by?: string, note?: string, options?: MutationOptions): Promise<Lead>;
733
+ qualify(id: string, by?: string, note?: string, options?: MutationOptions): Promise<Lead>;
734
+ disqualify(id: string, reason: string, by?: string, options?: MutationOptions): Promise<Lead>;
735
+ nurture(id: string, by?: string, options?: MutationOptions): Promise<Lead>;
736
+ convert(id: string, input: ConvertLeadInput, options?: MutationOptions): Promise<ConvertResult>;
737
+ /** Recompute score and persist. Hosts can call on update or on demand. */
738
+ recomputeScore(id: string, options?: MutationOptions): Promise<Lead>;
739
+ findById(id: string): Promise<Lead | null>;
740
+ list(filter?: Parameters<LeadRepository['list']>[0]): Promise<Lead[]>;
741
+ private _updateScore;
742
+ private _require;
743
+ private _transition;
744
+ private _emit;
745
+ }
746
+ //#endregion
747
+ //#region src/domain/entities/pipeline.d.ts
748
+ /**
749
+ * A Stage in a Pipeline. Stages are ordered (`sequence` ascending).
750
+ * Default win-probability applies at every opportunity-movement unless
751
+ * the caller overrides.
752
+ */
753
+ interface Stage {
754
+ id: string;
755
+ name: string;
756
+ sequence: number;
757
+ /** Default win probability (0-1) assigned to opportunities entering this stage. */
758
+ defaultProbability: number;
759
+ /** Optional UX hint for color-coded kanbans. */
760
+ color?: string;
761
+ /** Describes what "reaching this stage" means. */
762
+ description?: string;
763
+ }
764
+ /**
765
+ * A Pipeline groups ordered Stages for a specific sales motion. Hosts
766
+ * typically have 1-N pipelines (e.g. `new-business`, `expansion`,
767
+ * `partnerships`, `renewals`). Opportunities pin to exactly one
768
+ * pipeline at create time.
769
+ */
770
+ interface Pipeline {
771
+ id: string;
772
+ name: string;
773
+ /** Deprecates this pipeline — opportunities can still move within it but no new ones are created. */
774
+ isArchived: boolean;
775
+ stages: readonly Stage[];
776
+ /** Free-form label for the sales team / motion that owns this pipeline. */
777
+ teamRef?: string;
778
+ metadata?: Record<string, unknown>;
779
+ createdAt: Date;
780
+ updatedAt: Date;
781
+ }
782
+ //#endregion
783
+ //#region src/domain/ports/pipeline-repository.port.d.ts
784
+ interface PipelineRepository {
785
+ findById(id: string): Promise<Pipeline | null>;
786
+ list(includeArchived?: boolean): Promise<Pipeline[]>;
787
+ create(p: Omit<Pipeline, 'createdAt' | 'updatedAt'>): Promise<Pipeline>;
788
+ update(id: string, patch: Partial<Pipeline>): Promise<Pipeline>;
789
+ }
790
+ //#endregion
791
+ //#region src/services/pipeline.service.d.ts
792
+ interface PipelineServiceConfig {
793
+ repo: PipelineRepository;
794
+ events?: EventTransport | undefined;
795
+ /** Optional host-owned outbox. See AccountServiceConfig.outbox. */
796
+ outbox?: OutboxStore | undefined;
797
+ clock?: Clock;
798
+ ids?: IdGenerator;
799
+ }
800
+ interface CreatePipelineInput {
801
+ name: string;
802
+ stages: readonly Omit<Stage, 'id'>[];
803
+ teamRef?: string;
804
+ metadata?: Record<string, unknown>;
805
+ }
806
+ /**
807
+ * Pipeline lifecycle + stage helpers. The service owns stage id
808
+ * generation so callers don't have to — create a pipeline with an
809
+ * array of stage seeds and the service hands back the persisted
810
+ * pipeline with ids assigned.
811
+ */
812
+ declare class PipelineService {
813
+ private readonly repo;
814
+ private readonly events;
815
+ private readonly outbox;
816
+ private readonly clock;
817
+ private readonly ids;
818
+ constructor(config: PipelineServiceConfig);
819
+ create(input: CreatePipelineInput, options?: MutationOptions): Promise<Pipeline>;
820
+ archive(id: string, options?: MutationOptions): Promise<Pipeline>;
821
+ getStage(pipelineId: string, stageId: string): Promise<Stage>;
822
+ /** First stage in the ordered pipeline (minimum `sequence`). */
823
+ getFirstStage(pipelineId: string): Promise<Stage>;
824
+ /** Next stage after `stageId` in the ordered pipeline, or `null` at the end. */
825
+ getNextStage(pipelineId: string, stageId: string): Promise<Stage | null>;
826
+ findById(id: string): Promise<Pipeline | null>;
827
+ list(includeArchived?: boolean): Promise<Pipeline[]>;
828
+ private _require;
829
+ private _emit;
830
+ }
831
+ //#endregion
832
+ //#region src/services/forecast.d.ts
833
+ interface ForecastRow {
834
+ /** Bucket key — typically ISO month `YYYY-MM` derived from `expectedCloseAt`. */
835
+ bucket: string;
836
+ /** Sum of unweighted `amount`. */
837
+ committed: number;
838
+ /** Sum of `amount × probability`. */
839
+ weighted: number;
840
+ /** Count of opportunities contributing. */
841
+ count: number;
842
+ currency: string;
843
+ }
844
+ interface ForecastOptions {
845
+ /**
846
+ * Bucket function — given an opportunity, return a string bucket
847
+ * key. Default is ISO-month `YYYY-MM`. Custom buckets (quarter,
848
+ * fiscal-year, custom periods) plug in here.
849
+ */
850
+ bucket?: (o: Opportunity) => string;
851
+ /**
852
+ * Filter opportunities before bucketing. Default includes only
853
+ * `open` opportunities with `amount` and `expectedCloseAt`.
854
+ */
855
+ include?: (o: Opportunity) => boolean;
856
+ }
857
+ /**
858
+ * Aggregate opportunities into a forecast table — one row per
859
+ * bucket (month by default). Committed is `Σ amount`; weighted is
860
+ * `Σ amount × probability`.
861
+ *
862
+ * Integer-safe: sums are computed on minor-unit amounts (consistent
863
+ * with `@classytic/primitives/money`'s integer convention). Currency
864
+ * mismatch across a bucket throws — hosts dealing in multi-currency
865
+ * run this once per currency after partitioning.
866
+ */
867
+ declare function forecast(opportunities: readonly Opportunity[], options?: ForecastOptions): ForecastRow[];
868
+ /**
869
+ * Pipeline conversion summary — how opportunities partitioned across
870
+ * stages and final outcome. Useful for "where do deals die?" analysis.
871
+ */
872
+ interface ConversionRow {
873
+ stageId: string;
874
+ currentCount: number;
875
+ wonFromHere: number;
876
+ lostFromHere: number;
877
+ conversionRate: number;
878
+ }
879
+ declare function pipelineConversion(opportunities: readonly Opportunity[], stageIds: readonly string[]): ConversionRow[];
880
+ //#endregion
881
+ //#region src/domain/entities/loss-reason.d.ts
882
+ /**
883
+ * Standardized reason for closing an opportunity as `lost`. Seeded
884
+ * once by the host; referenced by id on the opportunity. Keeping
885
+ * these as records (vs free-form strings) is what makes lost-reason
886
+ * analytics possible.
887
+ */
888
+ interface LossReason {
889
+ id: string;
890
+ name: string;
891
+ /** Broad category — `price`, `feature-gap`, `timing`, `competitor`, `no-decision`. */
892
+ category?: string;
893
+ description?: string;
894
+ active: boolean;
895
+ metadata?: Record<string, unknown>;
896
+ createdAt: Date;
897
+ updatedAt: Date;
898
+ }
899
+ //#endregion
900
+ //#region src/domain/ports/loss-reason-repository.port.d.ts
901
+ interface LossReasonRepository {
902
+ findById(id: string): Promise<LossReason | null>;
903
+ list(activeOnly?: boolean): Promise<LossReason[]>;
904
+ create(r: Omit<LossReason, 'createdAt' | 'updatedAt'>): Promise<LossReason>;
905
+ update(id: string, patch: Partial<LossReason>): Promise<LossReason>;
906
+ }
907
+ //#endregion
908
+ //#region src/domain/ports/outreach.port.d.ts
909
+ /**
910
+ * Optional outreach bridge. When wired, activity transitions can send
911
+ * real emails / SMS / etc. via the host's pipeline. This package does
912
+ * NOT call it automatically — hosts subscribe to `crm:activity.*`
913
+ * events and call `outreach.send()` from their own handler so pacing,
914
+ * retry, and bounce handling live where they belong.
915
+ *
916
+ * The port intentionally stays minimal; implementations forward to
917
+ * SendGrid, Postmark, Twilio, Mailgun, or an in-house SMTP stack.
918
+ */
919
+ interface OutreachSendRequest {
920
+ channel: 'email' | 'sms' | 'social' | string;
921
+ toAddress: string;
922
+ subject?: string;
923
+ body: string;
924
+ activityId?: string;
925
+ /** Host metadata — template id, campaign ref, personalization vars. */
926
+ metadata?: Record<string, unknown>;
927
+ }
928
+ interface OutreachSendResult {
929
+ /** Opaque ref — typically the provider's message id. */
930
+ providerMessageId?: string;
931
+ acceptedAt: Date;
932
+ }
933
+ interface OutreachPort {
934
+ send(request: OutreachSendRequest): Promise<OutreachSendResult>;
935
+ }
936
+ //#endregion
937
+ //#region src/adapters/in-memory/account-repository.d.ts
938
+ declare class InMemoryAccountRepository implements AccountRepository {
939
+ private readonly clock;
940
+ private readonly items;
941
+ constructor(clock?: Clock);
942
+ findById(id: string): Promise<Account | null>;
943
+ findByDomain(domain: string): Promise<Account | null>;
944
+ list(filter?: AccountFilter): Promise<Account[]>;
945
+ create(input: Omit<Account, 'createdAt' | 'updatedAt'>): Promise<Account>;
946
+ update(id: string, patch: Partial<Account>): Promise<Account>;
947
+ delete(id: string): Promise<void>;
948
+ }
949
+ //#endregion
950
+ //#region src/adapters/in-memory/activity-repository.d.ts
951
+ declare class InMemoryActivityRepository implements ActivityRepository {
952
+ private readonly clock;
953
+ private readonly items;
954
+ constructor(clock?: Clock);
955
+ findById(id: string): Promise<Activity | null>;
956
+ list(filter?: ActivityFilter): Promise<Activity[]>;
957
+ create(input: Omit<Activity, 'createdAt' | 'updatedAt'>): Promise<Activity>;
958
+ update(id: string, patch: Partial<Activity>): Promise<Activity>;
959
+ casStatus(id: string, expected: ActivityStatus, next: ActivityStatus, patch?: Partial<Activity>): Promise<Activity | null>;
960
+ }
961
+ //#endregion
962
+ //#region src/adapters/in-memory/contact-repository.d.ts
963
+ declare class InMemoryContactRepository implements ContactRepository {
964
+ private readonly clock;
965
+ private readonly items;
966
+ constructor(clock?: Clock);
967
+ findById(id: string): Promise<Contact | null>;
968
+ findByEmail(email: string): Promise<Contact | null>;
969
+ list(filter?: ContactFilter): Promise<Contact[]>;
970
+ create(input: Omit<Contact, 'createdAt' | 'updatedAt'>): Promise<Contact>;
971
+ update(id: string, patch: Partial<Contact>): Promise<Contact>;
972
+ delete(id: string): Promise<void>;
973
+ }
974
+ //#endregion
975
+ //#region src/adapters/in-memory/event-transport.d.ts
976
+ type RecordedEvent = DomainEvent;
977
+ declare class InMemoryEventTransport implements EventTransport {
978
+ readonly name = "crm-in-memory";
979
+ readonly events: RecordedEvent[];
980
+ private readonly subscribers;
981
+ publish(event: DomainEvent): Promise<void>;
982
+ subscribe(pattern: string, handler: EventHandler): Promise<() => void>;
983
+ }
984
+ //#endregion
985
+ //#region src/adapters/in-memory/lead-repository.d.ts
986
+ declare class InMemoryLeadRepository implements LeadRepository {
987
+ private readonly clock;
988
+ private readonly items;
989
+ constructor(clock?: Clock);
990
+ findById(id: string): Promise<Lead | null>;
991
+ findByEmail(email: string): Promise<Lead | null>;
992
+ list(filter?: LeadFilter): Promise<Lead[]>;
993
+ create(input: Omit<Lead, 'createdAt' | 'updatedAt'>): Promise<Lead>;
994
+ update(id: string, patch: Partial<Lead>): Promise<Lead>;
995
+ casStatus(id: string, expected: LeadStatus, next: LeadStatus, patch?: Partial<Lead>): Promise<Lead | null>;
996
+ }
997
+ //#endregion
998
+ //#region src/adapters/in-memory/loss-reason-repository.d.ts
999
+ declare class InMemoryLossReasonRepository implements LossReasonRepository {
1000
+ private readonly clock;
1001
+ private readonly items;
1002
+ constructor(clock?: Clock);
1003
+ findById(id: string): Promise<LossReason | null>;
1004
+ list(activeOnly?: boolean): Promise<LossReason[]>;
1005
+ create(input: Omit<LossReason, 'createdAt' | 'updatedAt'>): Promise<LossReason>;
1006
+ update(id: string, patch: Partial<LossReason>): Promise<LossReason>;
1007
+ }
1008
+ //#endregion
1009
+ //#region src/adapters/in-memory/note-repository.d.ts
1010
+ declare class InMemoryNoteRepository implements NoteRepository {
1011
+ private readonly clock;
1012
+ private readonly items;
1013
+ constructor(clock?: Clock);
1014
+ findById(id: string): Promise<Note | null>;
1015
+ listBySubject(subjectKind: SubjectKind, subjectId: string): Promise<Note[]>;
1016
+ create(input: Omit<Note, 'createdAt' | 'updatedAt'>): Promise<Note>;
1017
+ update(id: string, patch: Partial<Note>): Promise<Note>;
1018
+ delete(id: string): Promise<void>;
1019
+ }
1020
+ //#endregion
1021
+ //#region src/adapters/in-memory/opportunity-repository.d.ts
1022
+ declare class InMemoryOpportunityRepository implements OpportunityRepository {
1023
+ private readonly clock;
1024
+ private readonly items;
1025
+ constructor(clock?: Clock);
1026
+ findById(id: string): Promise<Opportunity | null>;
1027
+ list(filter?: OpportunityFilter): Promise<Opportunity[]>;
1028
+ create(input: Omit<Opportunity, 'createdAt' | 'updatedAt'>): Promise<Opportunity>;
1029
+ update(id: string, patch: Partial<Opportunity>): Promise<Opportunity>;
1030
+ delete(id: string): Promise<void>;
1031
+ casStatus(id: string, expected: OpportunityStatus, next: OpportunityStatus, patch?: Partial<Opportunity>): Promise<Opportunity | null>;
1032
+ casStage(id: string, expectedStageId: string, nextStageId: string, patch?: Partial<Opportunity>): Promise<Opportunity | null>;
1033
+ }
1034
+ //#endregion
1035
+ //#region src/adapters/in-memory/pipeline-repository.d.ts
1036
+ declare class InMemoryPipelineRepository implements PipelineRepository {
1037
+ private readonly clock;
1038
+ private readonly items;
1039
+ constructor(clock?: Clock);
1040
+ findById(id: string): Promise<Pipeline | null>;
1041
+ list(includeArchived?: boolean): Promise<Pipeline[]>;
1042
+ create(input: Omit<Pipeline, 'createdAt' | 'updatedAt'>): Promise<Pipeline>;
1043
+ update(id: string, patch: Partial<Pipeline>): Promise<Pipeline>;
1044
+ }
1045
+ //#endregion
1046
+ //#region src/adapters/in-memory/outreach-port.d.ts
1047
+ declare class NullOutreachPort implements OutreachPort {
1048
+ private counter;
1049
+ send(_request: OutreachSendRequest): Promise<OutreachSendResult>;
1050
+ }
1051
+ interface RecordedOutreach extends OutreachSendRequest, OutreachSendResult {}
1052
+ declare class InMemoryOutreachPort implements OutreachPort {
1053
+ private readonly nowMs;
1054
+ readonly sent: RecordedOutreach[];
1055
+ private counter;
1056
+ constructor(nowMs?: () => number);
1057
+ send(request: OutreachSendRequest): Promise<OutreachSendResult>;
1058
+ }
1059
+ //#endregion
1060
+ //#region src/adapters/in-memory/id-generator.d.ts
1061
+ declare const cryptoIdGenerator: IdGenerator;
1062
+ declare function createSequentialIdGenerator(prefix?: string): IdGenerator;
1063
+ //#endregion
1064
+ //#region src/domain/errors.d.ts
1065
+ /**
1066
+ * CRM error vocabulary. Hosts catch `CrmError` at API edges.
1067
+ */
1068
+ declare class CrmError extends Error {
1069
+ readonly code: string;
1070
+ readonly details?: Record<string, unknown>;
1071
+ constructor(code: string, message: string, details?: Record<string, unknown>);
1072
+ }
1073
+ declare class NotFoundError extends CrmError {
1074
+ constructor(kind: string, id: string);
1075
+ }
1076
+ declare class InvalidLeadTransitionError extends CrmError {
1077
+ constructor(details: {
1078
+ from: string;
1079
+ to: string;
1080
+ leadId: string;
1081
+ });
1082
+ }
1083
+ declare class InvalidOpportunityTransitionError extends CrmError {
1084
+ constructor(details: {
1085
+ from: string;
1086
+ to: string;
1087
+ opportunityId: string;
1088
+ });
1089
+ }
1090
+ declare class InvalidActivityTransitionError extends CrmError {
1091
+ constructor(details: {
1092
+ from: string;
1093
+ to: string;
1094
+ activityId: string;
1095
+ });
1096
+ }
1097
+ declare class StageNotInPipelineError extends CrmError {
1098
+ constructor(details: {
1099
+ pipelineId: string;
1100
+ stageId: string;
1101
+ });
1102
+ }
1103
+ declare class LeadAlreadyConvertedError extends CrmError {
1104
+ constructor(leadId: string);
1105
+ }
1106
+ declare class DuplicateLeadError extends CrmError {
1107
+ constructor(details: {
1108
+ email?: string;
1109
+ phone?: string;
1110
+ });
1111
+ }
1112
+ declare class EmptyPipelineError extends CrmError {
1113
+ constructor(pipelineId: string);
1114
+ }
1115
+ //#endregion
1116
+ //#region src/events/event-constants.d.ts
1117
+ /** PACKAGE_RULES rule 12: `package:resource.verb`. */
1118
+ declare const CRM_EVENTS: {
1119
+ readonly LEAD_CREATED: "crm:lead.created";
1120
+ readonly LEAD_STATUS_CHANGED: "crm:lead.status_changed";
1121
+ readonly LEAD_SCORED: "crm:lead.scored";
1122
+ readonly LEAD_CONVERTED: "crm:lead.converted";
1123
+ readonly LEAD_DISQUALIFIED: "crm:lead.disqualified";
1124
+ readonly ACCOUNT_CREATED: "crm:account.created";
1125
+ readonly ACCOUNT_UPDATED: "crm:account.updated";
1126
+ readonly CONTACT_CREATED: "crm:contact.created";
1127
+ readonly CONTACT_UPDATED: "crm:contact.updated";
1128
+ readonly OPPORTUNITY_CREATED: "crm:opportunity.created";
1129
+ readonly OPPORTUNITY_STAGE_ADVANCED: "crm:opportunity.stage_advanced";
1130
+ readonly OPPORTUNITY_STAGE_REGRESSED: "crm:opportunity.stage_regressed";
1131
+ readonly OPPORTUNITY_WON: "crm:opportunity.won";
1132
+ readonly OPPORTUNITY_LOST: "crm:opportunity.lost";
1133
+ readonly OPPORTUNITY_ABANDONED: "crm:opportunity.abandoned";
1134
+ readonly PIPELINE_CREATED: "crm:pipeline.created";
1135
+ readonly PIPELINE_ARCHIVED: "crm:pipeline.archived";
1136
+ readonly ACTIVITY_PLANNED: "crm:activity.planned";
1137
+ readonly ACTIVITY_COMPLETED: "crm:activity.completed";
1138
+ readonly ACTIVITY_CANCELLED: "crm:activity.cancelled";
1139
+ readonly NOTE_ADDED: "crm:note.added";
1140
+ };
1141
+ type CrmEventName = (typeof CRM_EVENTS)[keyof typeof CRM_EVENTS];
1142
+ //#endregion
1143
+ //#region src/events/event-catalog.d.ts
1144
+ interface CrmEventSchema {
1145
+ type: 'object';
1146
+ properties?: Record<string, {
1147
+ type?: string;
1148
+ format?: string;
1149
+ [key: string]: unknown;
1150
+ }>;
1151
+ required?: string[];
1152
+ [key: string]: unknown;
1153
+ }
1154
+ interface CrmEventDefinition<TSchema extends z.ZodType = z.ZodType> {
1155
+ readonly name: string;
1156
+ readonly version: number;
1157
+ readonly description?: string;
1158
+ readonly schema: CrmEventSchema;
1159
+ readonly zodSchema: TSchema;
1160
+ create(payload: z.infer<TSchema>, meta?: Partial<DomainEvent['meta']>): DomainEvent<z.infer<TSchema>>;
1161
+ readonly __payload?: z.infer<TSchema>;
1162
+ }
1163
+ type CrmEventPayloadOf<D> = D extends CrmEventDefinition<infer S> ? z.infer<S> : never;
1164
+ declare const leadCreatedSchema: z.ZodObject<{
1165
+ leadId: z.ZodString;
1166
+ email: z.ZodOptional<z.ZodString>;
1167
+ score: z.ZodOptional<z.ZodNumber>;
1168
+ }, z.core.$strip>;
1169
+ declare const leadStatusChangedSchema: z.ZodObject<{
1170
+ leadId: z.ZodString;
1171
+ from: z.ZodString;
1172
+ to: z.ZodString;
1173
+ }, z.core.$strip>;
1174
+ declare const leadScoredSchema: z.ZodObject<{
1175
+ leadId: z.ZodString;
1176
+ score: z.ZodNumber;
1177
+ }, z.core.$strip>;
1178
+ declare const leadConvertedSchema: z.ZodObject<{
1179
+ leadId: z.ZodString;
1180
+ contactId: z.ZodString;
1181
+ accountId: z.ZodOptional<z.ZodString>;
1182
+ opportunityId: z.ZodString;
1183
+ }, z.core.$strip>;
1184
+ declare const leadDisqualifiedSchema: z.ZodObject<{
1185
+ leadId: z.ZodString;
1186
+ reason: z.ZodString;
1187
+ }, z.core.$strip>;
1188
+ declare const accountCreatedSchema: z.ZodObject<{
1189
+ accountId: z.ZodString;
1190
+ }, z.core.$strip>;
1191
+ declare const accountUpdatedSchema: z.ZodObject<{
1192
+ accountId: z.ZodString;
1193
+ }, z.core.$strip>;
1194
+ declare const contactCreatedSchema: z.ZodObject<{
1195
+ contactId: z.ZodString;
1196
+ email: z.ZodOptional<z.ZodString>;
1197
+ }, z.core.$strip>;
1198
+ declare const contactUpdatedSchema: z.ZodObject<{
1199
+ contactId: z.ZodString;
1200
+ }, z.core.$strip>;
1201
+ declare const opportunityCreatedSchema: z.ZodObject<{
1202
+ opportunityId: z.ZodString;
1203
+ pipelineId: z.ZodString;
1204
+ stageId: z.ZodString;
1205
+ }, z.core.$strip>;
1206
+ declare const opportunityStageAdvancedSchema: z.ZodObject<{
1207
+ opportunityId: z.ZodString;
1208
+ from: z.ZodObject<{
1209
+ stageId: z.ZodString;
1210
+ sequence: z.ZodNumber;
1211
+ }, z.core.$strip>;
1212
+ to: z.ZodObject<{
1213
+ stageId: z.ZodString;
1214
+ sequence: z.ZodNumber;
1215
+ }, z.core.$strip>;
1216
+ }, z.core.$strip>;
1217
+ declare const opportunityStageRegressedSchema: z.ZodObject<{
1218
+ opportunityId: z.ZodString;
1219
+ from: z.ZodObject<{
1220
+ stageId: z.ZodString;
1221
+ sequence: z.ZodNumber;
1222
+ }, z.core.$strip>;
1223
+ to: z.ZodObject<{
1224
+ stageId: z.ZodString;
1225
+ sequence: z.ZodNumber;
1226
+ }, z.core.$strip>;
1227
+ }, z.core.$strip>;
1228
+ declare const opportunityWonSchema: z.ZodObject<{
1229
+ opportunityId: z.ZodString;
1230
+ amount: z.ZodOptional<z.ZodObject<{
1231
+ amount: z.ZodNumber;
1232
+ currency: z.ZodString;
1233
+ }, z.core.$strip>>;
1234
+ }, z.core.$strip>;
1235
+ declare const opportunityLostSchema: z.ZodObject<{
1236
+ opportunityId: z.ZodString;
1237
+ lostReasonId: z.ZodOptional<z.ZodString>;
1238
+ }, z.core.$strip>;
1239
+ declare const opportunityAbandonedSchema: z.ZodObject<{
1240
+ opportunityId: z.ZodString;
1241
+ }, z.core.$strip>;
1242
+ declare const pipelineCreatedSchema: z.ZodObject<{
1243
+ pipelineId: z.ZodString;
1244
+ }, z.core.$strip>;
1245
+ declare const pipelineArchivedSchema: z.ZodObject<{
1246
+ pipelineId: z.ZodString;
1247
+ }, z.core.$strip>;
1248
+ declare const activityPlannedSchema: z.ZodObject<{
1249
+ activityId: z.ZodString;
1250
+ type: z.ZodString;
1251
+ subjectKind: z.ZodEnum<{
1252
+ lead: "lead";
1253
+ opportunity: "opportunity";
1254
+ contact: "contact";
1255
+ account: "account";
1256
+ }>;
1257
+ subjectId: z.ZodString;
1258
+ }, z.core.$strip>;
1259
+ declare const activityCompletedSchema: z.ZodObject<{
1260
+ activityId: z.ZodString;
1261
+ completedAt: z.ZodISODateTime;
1262
+ }, z.core.$strip>;
1263
+ declare const activityCancelledSchema: z.ZodObject<{
1264
+ activityId: z.ZodString;
1265
+ reason: z.ZodOptional<z.ZodString>;
1266
+ }, z.core.$strip>;
1267
+ declare const noteAddedSchema: z.ZodObject<{
1268
+ noteId: z.ZodString;
1269
+ subjectKind: z.ZodEnum<{
1270
+ lead: "lead";
1271
+ opportunity: "opportunity";
1272
+ contact: "contact";
1273
+ account: "account";
1274
+ }>;
1275
+ subjectId: z.ZodString;
1276
+ }, z.core.$strip>;
1277
+ type LeadCreatedPayload = z.infer<typeof leadCreatedSchema>;
1278
+ type LeadStatusChangedPayload = z.infer<typeof leadStatusChangedSchema>;
1279
+ type LeadScoredPayload = z.infer<typeof leadScoredSchema>;
1280
+ type LeadConvertedPayload = z.infer<typeof leadConvertedSchema>;
1281
+ type LeadDisqualifiedPayload = z.infer<typeof leadDisqualifiedSchema>;
1282
+ type AccountCreatedPayload = z.infer<typeof accountCreatedSchema>;
1283
+ type AccountUpdatedPayload = z.infer<typeof accountUpdatedSchema>;
1284
+ type ContactCreatedPayload = z.infer<typeof contactCreatedSchema>;
1285
+ type ContactUpdatedPayload = z.infer<typeof contactUpdatedSchema>;
1286
+ type OpportunityCreatedPayload = z.infer<typeof opportunityCreatedSchema>;
1287
+ type OpportunityStageAdvancedPayload = z.infer<typeof opportunityStageAdvancedSchema>;
1288
+ type OpportunityStageRegressedPayload = z.infer<typeof opportunityStageRegressedSchema>;
1289
+ type OpportunityWonPayload = z.infer<typeof opportunityWonSchema>;
1290
+ type OpportunityLostPayload = z.infer<typeof opportunityLostSchema>;
1291
+ type OpportunityAbandonedPayload = z.infer<typeof opportunityAbandonedSchema>;
1292
+ type PipelineCreatedPayload = z.infer<typeof pipelineCreatedSchema>;
1293
+ type PipelineArchivedPayload = z.infer<typeof pipelineArchivedSchema>;
1294
+ type ActivityPlannedPayload = z.infer<typeof activityPlannedSchema>;
1295
+ type ActivityCompletedPayload = z.infer<typeof activityCompletedSchema>;
1296
+ type ActivityCancelledPayload = z.infer<typeof activityCancelledSchema>;
1297
+ type NoteAddedPayload = z.infer<typeof noteAddedSchema>;
1298
+ declare const LeadCreated: CrmEventDefinition<z.ZodObject<{
1299
+ leadId: z.ZodString;
1300
+ email: z.ZodOptional<z.ZodString>;
1301
+ score: z.ZodOptional<z.ZodNumber>;
1302
+ }, z.core.$strip>>;
1303
+ declare const LeadStatusChanged: CrmEventDefinition<z.ZodObject<{
1304
+ leadId: z.ZodString;
1305
+ from: z.ZodString;
1306
+ to: z.ZodString;
1307
+ }, z.core.$strip>>;
1308
+ declare const LeadScored: CrmEventDefinition<z.ZodObject<{
1309
+ leadId: z.ZodString;
1310
+ score: z.ZodNumber;
1311
+ }, z.core.$strip>>;
1312
+ declare const LeadConverted: CrmEventDefinition<z.ZodObject<{
1313
+ leadId: z.ZodString;
1314
+ contactId: z.ZodString;
1315
+ accountId: z.ZodOptional<z.ZodString>;
1316
+ opportunityId: z.ZodString;
1317
+ }, z.core.$strip>>;
1318
+ declare const LeadDisqualified: CrmEventDefinition<z.ZodObject<{
1319
+ leadId: z.ZodString;
1320
+ reason: z.ZodString;
1321
+ }, z.core.$strip>>;
1322
+ declare const AccountCreated: CrmEventDefinition<z.ZodObject<{
1323
+ accountId: z.ZodString;
1324
+ }, z.core.$strip>>;
1325
+ declare const AccountUpdated: CrmEventDefinition<z.ZodObject<{
1326
+ accountId: z.ZodString;
1327
+ }, z.core.$strip>>;
1328
+ declare const ContactCreated: CrmEventDefinition<z.ZodObject<{
1329
+ contactId: z.ZodString;
1330
+ email: z.ZodOptional<z.ZodString>;
1331
+ }, z.core.$strip>>;
1332
+ declare const ContactUpdated: CrmEventDefinition<z.ZodObject<{
1333
+ contactId: z.ZodString;
1334
+ }, z.core.$strip>>;
1335
+ declare const OpportunityCreated: CrmEventDefinition<z.ZodObject<{
1336
+ opportunityId: z.ZodString;
1337
+ pipelineId: z.ZodString;
1338
+ stageId: z.ZodString;
1339
+ }, z.core.$strip>>;
1340
+ declare const OpportunityStageAdvanced: CrmEventDefinition<z.ZodObject<{
1341
+ opportunityId: z.ZodString;
1342
+ from: z.ZodObject<{
1343
+ stageId: z.ZodString;
1344
+ sequence: z.ZodNumber;
1345
+ }, z.core.$strip>;
1346
+ to: z.ZodObject<{
1347
+ stageId: z.ZodString;
1348
+ sequence: z.ZodNumber;
1349
+ }, z.core.$strip>;
1350
+ }, z.core.$strip>>;
1351
+ declare const OpportunityStageRegressed: CrmEventDefinition<z.ZodObject<{
1352
+ opportunityId: z.ZodString;
1353
+ from: z.ZodObject<{
1354
+ stageId: z.ZodString;
1355
+ sequence: z.ZodNumber;
1356
+ }, z.core.$strip>;
1357
+ to: z.ZodObject<{
1358
+ stageId: z.ZodString;
1359
+ sequence: z.ZodNumber;
1360
+ }, z.core.$strip>;
1361
+ }, z.core.$strip>>;
1362
+ declare const OpportunityWon: CrmEventDefinition<z.ZodObject<{
1363
+ opportunityId: z.ZodString;
1364
+ amount: z.ZodOptional<z.ZodObject<{
1365
+ amount: z.ZodNumber;
1366
+ currency: z.ZodString;
1367
+ }, z.core.$strip>>;
1368
+ }, z.core.$strip>>;
1369
+ declare const OpportunityLost: CrmEventDefinition<z.ZodObject<{
1370
+ opportunityId: z.ZodString;
1371
+ lostReasonId: z.ZodOptional<z.ZodString>;
1372
+ }, z.core.$strip>>;
1373
+ declare const OpportunityAbandoned: CrmEventDefinition<z.ZodObject<{
1374
+ opportunityId: z.ZodString;
1375
+ }, z.core.$strip>>;
1376
+ declare const PipelineCreated: CrmEventDefinition<z.ZodObject<{
1377
+ pipelineId: z.ZodString;
1378
+ }, z.core.$strip>>;
1379
+ declare const PipelineArchived: CrmEventDefinition<z.ZodObject<{
1380
+ pipelineId: z.ZodString;
1381
+ }, z.core.$strip>>;
1382
+ declare const ActivityPlanned: CrmEventDefinition<z.ZodObject<{
1383
+ activityId: z.ZodString;
1384
+ type: z.ZodString;
1385
+ subjectKind: z.ZodEnum<{
1386
+ lead: "lead";
1387
+ opportunity: "opportunity";
1388
+ contact: "contact";
1389
+ account: "account";
1390
+ }>;
1391
+ subjectId: z.ZodString;
1392
+ }, z.core.$strip>>;
1393
+ declare const ActivityCompleted: CrmEventDefinition<z.ZodObject<{
1394
+ activityId: z.ZodString;
1395
+ completedAt: z.ZodISODateTime;
1396
+ }, z.core.$strip>>;
1397
+ declare const ActivityCancelled: CrmEventDefinition<z.ZodObject<{
1398
+ activityId: z.ZodString;
1399
+ reason: z.ZodOptional<z.ZodString>;
1400
+ }, z.core.$strip>>;
1401
+ declare const NoteAdded: CrmEventDefinition<z.ZodObject<{
1402
+ noteId: z.ZodString;
1403
+ subjectKind: z.ZodEnum<{
1404
+ lead: "lead";
1405
+ opportunity: "opportunity";
1406
+ contact: "contact";
1407
+ account: "account";
1408
+ }>;
1409
+ subjectId: z.ZodString;
1410
+ }, z.core.$strip>>;
1411
+ /**
1412
+ * Every CRM event defined in the package — pass to Arc's `EventRegistry`.
1413
+ * Hosts wire ONE array; the whole `crm:*` namespace becomes introspectable
1414
+ * via OpenAPI and auto-validated at publish time when
1415
+ * `eventPlugin({ validateMode: 'reject' })` is set.
1416
+ */
1417
+ declare const crmEventDefinitions: ReadonlyArray<CrmEventDefinition>;
1418
+ //#endregion
1419
+ export { ACTIVITY_STATUSES, ACTIVITY_TRANSITIONS, ACTIVITY_TYPES, type Account, AccountCreated, type AccountCreatedPayload, type AccountFilter, type AccountRepository, AccountService, type AccountServiceConfig, AccountUpdated, type AccountUpdatedPayload, type Activity, ActivityCancelled, type ActivityCancelledPayload, ActivityCompleted, type ActivityCompletedPayload, type ActivityFilter, ActivityPlanned, type ActivityPlannedPayload, type ActivityRepository, ActivityService, type ActivityServiceConfig, type ActivityStatus, type ActivityType, CRM_EVENTS, type Clock, type CloseInput, type Contact, ContactCreated, type ContactCreatedPayload, type ContactFilter, type ContactRepository, ContactService, type ContactServiceConfig, ContactUpdated, type ContactUpdatedPayload, type ConversionRow, type ConvertLeadInput, type ConvertResult, type CreateAccountInput, type CreateActivityInput, type CreateContactInput, type CreateLeadInput, type CreateNoteInput, type CreateOpportunityInput, type CreatePipelineInput, CrmError, type CrmEventDefinition, type CrmEventName, type CrmEventPayloadOf, type CrmEventSchema, DuplicateLeadError, EmptyPipelineError, type ForecastOptions, type ForecastRow, type IdGenerator, InMemoryAccountRepository, InMemoryActivityRepository, InMemoryContactRepository, InMemoryEventTransport, InMemoryLeadRepository, InMemoryLossReasonRepository, InMemoryNoteRepository, InMemoryOpportunityRepository, InMemoryOutreachPort, InMemoryPipelineRepository, InvalidActivityTransitionError, InvalidLeadTransitionError, InvalidOpportunityTransitionError, LEAD_STATUSES, LEAD_TRANSITIONS, type Lead, LeadAlreadyConvertedError, LeadConverted, type LeadConvertedPayload, LeadCreated, type LeadCreatedPayload, LeadDisqualified, type LeadDisqualifiedPayload, type LeadFilter, type LeadRepository, LeadScored, type LeadScoredPayload, type LeadScorer, LeadService, type LeadServiceConfig, type LeadStatus, LeadStatusChanged, type LeadStatusChangedPayload, type LeadStatusEntry, type LoseInput, type LossReason, type LossReasonRepository, type MoveStageInput, NotFoundError, type Note, NoteAdded, type NoteAddedPayload, type NoteRepository, NullOutreachPort, OPPORTUNITY_STATUSES, OPPORTUNITY_TRANSITIONS, type Opportunity, OpportunityAbandoned, type OpportunityAbandonedPayload, OpportunityCreated, type OpportunityCreatedPayload, type OpportunityFilter, OpportunityLost, type OpportunityLostPayload, type OpportunityRepository, OpportunityService, type OpportunityServiceConfig, OpportunityStageAdvanced, type OpportunityStageAdvancedPayload, OpportunityStageRegressed, type OpportunityStageRegressedPayload, type OpportunityStatus, type OpportunityStatusEntry, OpportunityWon, type OpportunityWonPayload, type OutreachPort, type OutreachSendRequest, type OutreachSendResult, type Pipeline, PipelineArchived, type PipelineArchivedPayload, PipelineCreated, type PipelineCreatedPayload, type PipelineRepository, PipelineService, type PipelineServiceConfig, type RecordedEvent, type RecordedOutreach, SUBJECT_KINDS, type Stage, StageNotInPipelineError, type SubjectKind, canTransitionActivity, canTransitionLead, canTransitionOpportunity, composeScorers, createSequentialIdGenerator, crmEventDefinitions, cryptoIdGenerator, defaultLeadScorer, forecast, pipelineConversion, systemClock };
1420
+ //# sourceMappingURL=index.d.ts.map